且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

使用 MVVM WPF 在树视图中添加、重命名、删除项目

更新时间:2023-10-13 20:11:40

添加项目没有反映在 UI 中,因为源集合 Person.Children 没有实现 INotifyCollectionChanged.每当您需要动态集合时,其中添加、删除或移动操作应更新绑定目标,您应该使用 ObservableCollection,它实现了 INotifyCollectionChanged.>

类似适用于 Person.Name 属性.如果您希望属性的更改反映到 UI,那么您的视图模型必须实现 INotifyPropertyChanged 并在绑定源(视图模型属性)时引发 INotifyPropertyChanged.PropertyChanged 事件) 已更改.

一般来说,当一个类作为数据绑定的绑定源时,这个类必须实现INotifyPropertyChanged(如果不实现这个接口,那么数据绑定的性能会变得很差).
当属性的修改应通过调用数据绑定更新 UI (binding.target) 时,修改后的属性必须引发 INotifyPropertyChanged.PropertyChanged 事件.
当集合的修改应通过调用数据绑定更新 UI(绑定目标)时,修改后的集合必须实现 INotifyCollectionChanged 并引发 INotifyCollectionChanged.CollectionChanged 事件.ObservableCollection 提供了 INotifyCollectionChanged 的默认实现.

以下示例遵循上述规则.对 Person 类所做的更改应该可以解决您的问题.对数据模型的更改现在将反映在 TreeView 中:

public class Person : INotifyPropertyChanged{私有 ObservableCollection_children = new ObservableCollection();公共 ObservableCollection孩子们{得到 { 返回 _children;}}私有字符串名称公共字符串名称{得到 =>这个.name;放{this.name = 值;OnPropertyChanged();}}公共事件 PropertyChangedEventHandler PropertyChanged;protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null){this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));}}

I refer excellent tutorial of Josh Smith to work with treeview.

https://www.codeproject.com/Articles/26288/Simplifying-the-WPF-TreeView-by-Using-the-ViewMode

I try to modified with this code to add, remove, rename item to this treeview but I don't know why it not update

Rename item command

#region RenameCommand

/// <summary>
/// Returns the command used to execute a search in the family tree.
/// </summary>
public ICommand RenameCommand
{
    get { return _renameCommand; }
}

private class RenameFamilyTreeCommand : ICommand
{
    readonly FamilyTreeViewModel _familyTree;

    public RenameFamilyTreeCommand(FamilyTreeViewModel familyTree)
    {
        _familyTree = familyTree;
    }

    public bool CanExecute(object parameter)
    {
        return true;
    }

    event EventHandler ICommand.CanExecuteChanged
    {
        // I intentionally left these empty because
        // this command never raises the event, and
        // not using the WeakEvent pattern here can
        // cause memory leaks.  WeakEvent pattern is
        // not simple to implement, so why bother.
        add { }
        remove { }
    }

    public void Execute(object parameter)
    {
        //MessageBox.Show("Rename command");



        _familyTree._rootPerson.Children[0].Children[0].Header = "Hello";

        if (_familyTree._rootPerson.Children[0] == null)
            return;

        // Ensure that this person is in view.
        if (_familyTree._rootPerson.Children[0].Parent != null)
            _familyTree._rootPerson.Children[0].Parent.IsExpanded = true;

        _familyTree._rootPerson.Children[0].IsSelected = true;
    }
}

#endregion // RenameCommand

Add item command

#region AddCommand

    /// <summary>
    /// Returns the command used to execute a search in the family tree.
    /// </summary>
    public ICommand AddCommand
    {
        get { return _addCommand; }
    }

    private class AddFamilyTreeCommand : ICommand
    {
        public FamilyTreeViewModel _familyTree;

        public AddFamilyTreeCommand(FamilyTreeViewModel familyTree)
        {
            _familyTree = familyTree;
        }

        public bool CanExecute(object parameter)
        {
            return true;
        }

        event EventHandler ICommand.CanExecuteChanged
        {
            // I intentionally left these empty because
            // this command never raises the event, and
            // not using the WeakEvent pattern here can
            // cause memory leaks.  WeakEvent pattern is
            // not simple to implement, so why bother.
            add { }
            remove { }
        }

        public void Execute(object parameter)
        {
            Person newPerson = new Person();
            newPerson.Header = "New Person";
            newPerson.Name = "1.1.1.75";
            PersonViewModel newPersonViewModel = new PersonViewModel(newPerson);
            ////_rootPerson.Children.Add(newPersonViewModel);


            //_rootPerson.Children.Add(newPersonViewModel);

            //if (newPersonViewModel.Parent != null)
            //    newPersonViewModel.Parent.IsExpanded = true;

            //newPersonViewModel.IsSelected = true;

            _familyTree._rootPerson.Children[0].Children.Add(newPersonViewModel);

            if (_familyTree._rootPerson.Children[0] == null)
                return;

            // Ensure that this person is in view.
            if (_familyTree._rootPerson.Children[0].Parent != null)
                _familyTree._rootPerson.Children[0].Parent.IsExpanded = true;

            _familyTree._rootPerson.Children[0].IsSelected = true;


        }
    }

#endregion // AddCommand

Add command working fine but it's seem to be GUI not update. Rename command is not working but GUI is updated. I don't know reason why, And it's hard to access person class (use parent, person, children,..)

Is there anyone successfully update add, rename, remove command to Josh Smith project.

p/s: I debug by messagebox.show and see binding command for add and rename are working well, But the problem is I don't know what exactly to use Add, remove, rename person in Josh Smith project

Adding items is not reflected in the UI, because the source collection Person.Children doesn't implement INotifyCollectionChanged. Whenever you need dynamic collections, where add, remove or move operations should update the binding target, you should use the ObservableCollection<T>, which implements INotifyCollectionChanged.

Similar applies to the Person.Name property. If you want a property's change to be reflected to the UI, then your view model must implement INotifyPropertyChanged and raise the INotifyPropertyChanged.PropertyChanged event whenever the binding source (the view model property) has changed.

Generally, when a class serves as a binding source for data binding, then this class must implement INotifyPropertyChanged (if this interface is not implemented, then the performance of data binding becomes very bad).
When the modification of a property should update the UI (binding.target) by invoking the data binding, then the modified property must raise the INotifyPropertyChanged.PropertyChanged event.
When the modification of a collection should update the UI (binding target) by invoking the data binding, then the modified collection must implement INotifyCollectionChanged and raise the INotifyCollectionChanged.CollectionChanged event. ObservableCollection provides a default implementation of INotifyCollectionChanged.

The following example follows the above rules. The changes made to the Person class should fix your issues. Changes to the data model will now be reflected in the TreeView:

public class Person : INotifyPropertyChanged
{
    private ObservableCollection<Person> _children = new ObservableCollection<Person>();
    public ObservableCollection<Person> Children
    {
        get { return _children; }
    }

    private string name
    public string Name 
    {
        get => this.name;
        set 
        { 
            this.name = value; 
            OnPropertyChanged();
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
      this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}