0
votes

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.

  • image1 enter image description here

  • image2

enter image description here

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);
    }
1
And where's the code?wingerse
I can see you bind to the ViewModel object directly as your node display text in Image1. The output is simply the ViewModel.ToString(). We cannot help you until you put your xmal and ViewModel for reviewcscmh99
@cscmh99, I gave you the article that I use it to create my TreeView. I used exactly the same code. So why should I put my code if we have all the source. ! Can you please explain more what did you mean by "I bind the ViewModel object directly" !xtensa1408
@EmpereurAiman you can open the article abovextensa1408
@SADOK You could post the minimum code for us to understand without reading a big article.. See : stackoverflow.com/help/mcvewingerse

1 Answers

1
votes

I can see the bug in your code.

Here's the steps to reproduce:

  1. At state node (never expand it first)
  2. Without Expanding the Child upfront, Your StateViewModel's Children contain a DummyChild.
  3. Added 1 new City into the list which cause the HasDummyChild won't work as the count is now 2 in Children's list
  4. Then when you try to expand the node to check the result. Your treelist will have the DummyChild which is a base class that screwed up everything

So, basically that's why "Expand" first is the key of your problem as at that time HasDummyChild still working as it compares .Count == 1. The tree won't remove the DummyChild out from your Children list if you add an extra child to the list that makes .Count == 2.

ADDITIONAL INFO as requested

Just change the HasDummyChild as the following

    public bool HasDummyChild
    {
        //get { return this.Children.Count == 1 && this.Children[0] == DummyChild; }
        get { return Children.Any() && Children.Contains(DummyChild); }
    }

enter image description here