I constructed a treeView WPF MVVM with the help of this very good article Then I created a contextMenu for some node that allowed me to add children from selected parent.
The problem is if I click on "Add" without expanding manually the selected node(parent), a strange child is created automatically in addition to the node expected to be generated when clicking on "Add".
I tried to detect the problem so I change the code below from:
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
to:
<Setter Property="IsExpanded" Value="True" />
Image 1 below shows the result of this test or Image 2 shows what my treeView must show.
Rq: I used image from the article that I talked about it. Also, I used the same approach described in the article (including the class TreeViewItemViewModel.cs )
Base class for all ViewModel
public class TreeViewItemViewModel : INotifyPropertyChanged { #region Data
static readonly TreeViewItemViewModel DummyChild = new TreeViewItemViewModel(); readonly ObservableCollection<TreeViewItemViewModel> _children; readonly TreeViewItemViewModel _parent; bool _isExpanded; bool _isSelected; #endregion // Data #region Constructors protected TreeViewItemViewModel(TreeViewItemViewModel parent, bool lazyLoadChildren) { _parent = parent; _children = new ObservableCollection<TreeViewItemViewModel>(); if (lazyLoadChildren) _children.Add(DummyChild); } // This is used to create the DummyChild instance. private TreeViewItemViewModel() { } #endregion // Constructors #region Presentation Members #region Children /// <summary> /// Returns the logical child items of this object. /// </summary> public ObservableCollection<TreeViewItemViewModel> Children { get { return _children; } } #endregion // Children #region HasLoadedChildren /// <summary> /// Returns true if this object's Children have not yet been populated. /// </summary> public bool HasDummyChild { get { return this.Children.Count == 1 && this.Children[0] == DummyChild; } } #endregion // HasLoadedChildren #region IsExpanded /// <summary> /// Gets/sets whether the TreeViewItem /// associated with this object is expanded. /// </summary> public bool IsExpanded { get { return _isExpanded; } set { if (value != _isExpanded) { _isExpanded = value; this.OnPropertyChanged("IsExpanded"); } // Expand all the way up to the root. if (_isExpanded && _parent != null) _parent.IsExpanded = true; // Lazy load the child items, if necessary. if (this.HasDummyChild) { this.Children.Remove(DummyChild); this.LoadChildren(); } } } #endregion // IsExpanded #region IsSelected /// <summary> /// Gets/sets whether the TreeViewItem /// associated with this object is selected. /// </summary> public bool IsSelected { get { return _isSelected; } set { if (value != _isSelected) { _isSelected = value; this.OnPropertyChanged("IsSelected"); } } } #endregion // IsSelected #region LoadChildren /// <summary> /// Invoked when the child items need to be loaded on demand. /// Subclasses can override this to populate the Children collection. /// </summary> protected virtual void LoadChildren() { } #endregion // LoadChildren #region Parent public TreeViewItemViewModel Parent { get { return _parent; } } #endregion // Parent #endregion // Presentation Members #region INotifyPropertyChanged Members public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) { if (this.PropertyChanged != null) this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } #endregion // INotifyPropertyChanged Members
}
Myxml:
<TreeView ItemsSource="{Binding Regions}" IsEnabled="{Binding EnableTree}" >
<TreeView.ItemContainerStyle>
<!--
This Style binds a TreeViewItem to a TreeViewItemViewModel.
-->
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="FontWeight" Value="Normal" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<ContextMenu x:Key="AddCity" ItemsSource="{Binding AddCityItems}"/>
<HierarchicalDataTemplate
DataType="{x:Type local:StateViewModel}"
ItemsSource="{Binding Children}"
>
<StackPanel Orientation="Horizontal" ContextMenu="{StaticResource AddCity}">
<Image Width="16" Height="16" Margin="3,0" Source="Images\Region.png" />
<TextBlock Text="{Binding RegionName}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
- RegionViewModel:
`public class StateViewModel : TreeViewItemViewModel {
readonly State _state;
public ICommand AddCityCommand { get; private set; }
public List<MenuItem> AddCityItems { get; set; }
public StateViewModel(State state, RegionViewModel parentRegion)
: base(parentRegion, true)
{
_state = state;
AddCityItems = new List<MenuItem>();
AddCityCommand = new DelegateCommand<CancelEventArgs>(OnAddCityCommandExecute, OnAddCityCommandCanExecute);
AddCityItems.Add(new MenuItem() { Header = "Add City", Command = AddCityCommand });
}
public string StateName
{
get { return _state.StateName; }
}
protected override void LoadChildren()
{
foreach (City city in Database.GetCities(_state))
base.Children.Add(new CityViewModel(city, this));
}
bool OnAddCityCommandCanExecute(CancelEventArgs parameter)
{
return true;
}
public void OnAddCityCommandExecute(CancelEventArgs parameter)
{
var myNewCity = new city();
Children.Add(new CityViewModel(myNewCity, this));
}
}`
BTW, if I expand my parent node then I click into add City, I have the result as expected but if I don't expand parent node and I click on contextMenu I have another child created in addition to the child I want to create
EDIT I add the statemnt below to my add() method and I don't have any problem now:
public void OnAddCityCommandExecute(CancelEventArgs parameter)
{
var myNewCity = new city();
Children.Add(new CityViewModel(myNewCity, this));
//the modif
this.Children.Remove(DummyChild);
}