Get selected item in treview MVVM

馋奶兔 提交于 2020-06-17 15:58:05

问题


I try to apply MVVM for treeview by refer to Josh's tutorial https://www.codeproject.com/Articles/26288/Simplifying-the-WPF-TreeView-by-Using-the-ViewMode

Here is my full source code

TreeNode.cs

public class TreeNode
{
    private ObservableCollection<TreeNode> _children = new ObservableCollection<TreeNode>();

    public ObservableCollection<TreeNode> Children
    {
        get { return _children; }
        set { _children = value; }
    }

    public string Name { get; set; }

    public string ID { get; set; }

}

TreeNodeViewModel.cs

public class TreeNodeViewModel : INotifyPropertyChanged
{
    private ObservableCollection<TreeNodeViewModel> _children;
    private TreeNodeViewModel _seletected;
    private TreeNodeViewModel _parent;
    private TreeNode _node;

    bool _isExpanded;
    bool _isSelected;



    public TreeNodeViewModel(TreeNode node)
        : this(node, null)
    {
    }

    private TreeNodeViewModel(TreeNode node, TreeNodeViewModel parent)
    {
        _node = node;
        _parent = parent;

        _children = new ObservableCollection<TreeNodeViewModel>(
                (from child in _node.Children
                 select new TreeNodeViewModel(child, this))
                 .ToList<TreeNodeViewModel>());
    }

    public ObservableCollection<TreeNodeViewModel> Children
    {
        get { return _children; }
        set { _children = value; }
    }

    public string Name
    {
        get { return _node.Name; }
        set { _node.Name = value; }
    }
    public TreeNodeViewModel Selected
    {
        get { return _seletected; }
        set { _seletected = value; }
    }


    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;
        }
    }

    public bool IsSelected
    {
        get { return _isSelected; }
        set
        {
            if (value != _isSelected)
            {
                _isSelected = value;
                this.OnPropertyChanged("IsSelected");
                if (_isSelected) { _seletected = this; }
            }

        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

TreeViewViewModel.cs

public class TreeViewViewModel
{
    readonly ObservableCollection<TreeNodeViewModel> _firstLevel;
    readonly TreeNodeViewModel _rootNode;

    private ICommand _addCommand;

    public TreeViewViewModel(TreeNode rootNode)
    {
        _rootNode = new TreeNodeViewModel(rootNode);

        _firstLevel = new ObservableCollection<TreeNodeViewModel>(_rootNode.Children);

        _addCommand = new AddCommand(this);
    }

    public ObservableCollection<TreeNodeViewModel> FirstLevel
    {
        get { return _firstLevel; }
    }

    public ICommand AddCommand
    {
        get { return _addCommand; }
    }
}

AddCommand.cs

public class AddCommand : ICommand
{
    private TreeViewViewModel _TreeView;

    public AddCommand(TreeViewViewModel treeView)
    {
        _TreeView = treeView;
    }

    public event EventHandler CanExecuteChanged;

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

    public void Execute(object parameter)
    {
        //Show Selected item ????
        MessageBox.Show(_TreeView.????);




    }
}

My goal is when I click to add command, It will show selected item name and parent selected item name. But the problem is there is no anything in TreeViewViewModel to access to TreeNodeViewModel.

It's was prevent by middle class TreeViewViewModel to access to TreeNodeViewModel's property when live in Josh's world


回答1:


As per my comment on your previous question. Add a SelectedNode to TreeViewViewModel and set it in your Node Selected setter:

private TreeNodeViewModel(TreeNode node, TreeNodeViewModel parent, TreeViewViewModel root)
{
    _node = node;
    _parent = parent;  
    _root = root;
    //snip rest
}

public bool IsSelected
{
    get { return _isSelected; }
    set
    {
        if (value != _isSelected)
        {
            _isSelected = value;
            this.OnPropertyChanged("IsSelected");
            if (_isSelected) { _root.SelectedNode = this; }
        }

    }
}



回答2:


If you are concerned with you application's performance, then always implement INotifyPropertyChanged on your binding source - even if you are not raising the PropertyChanged event (because the property won't change).

In your case, TreeNode serves as a binding source for the TreeView. Therefore TreeNode should implement INotifyPropertyChanged.

Since IsSelected and IsExpanded are definitely attributes of a node, they should be members of TreeNode. This makes TreeNodeViewModel redundant. Removing TreeNodeViewModel will also remove the duplicate properties e.g., Children or Name and their awkward initialization.
For this reason I merged both classes using only the TreeNode and the TreeViewModel in my examples.

Also check the proper implementation of the ICommand.CanExecuteChanged event. I have fixed it in my examples.

Instead of passing string values of members or types to methods use nameof. I also fixed this where you invoke OnPropertyChanged. For the most optimal implementation of the OnPropertyChanged event invocator see Microsoft Docs: INotifyPropertyChanged.PropertyChanged.

The manipulation of the tree e.g., add/remove items to/from the tree, should always happen centralized in the class that manages the tree e.g., TreeViewModel. I've implemented corresponding methods in this class. The commands now call this methods to manipulate the tree.

The solution to your problem is to add a SelectedNode property to the class that manages the tree, which is in your case the TreeViewModel. The TreeViewModel listens to a SelctedChanged event of the TreeNode to update the TreeViewModel.SelectedNode property.

TreeNode.cs

public class TreeNode : INotifyPropertyChanged
{
    public event EventHandler SelectedChanged;
    public event PropertyChangedEventHandler PropertyChanged;

    private ObservableCollection<TreeNode> _children;
    private TreeNode _parent;
    private TreeNode _selectedTreeNode;
    private string _name;
    private string _id;
    private bool _isExpanded;
    private bool _isSelected;

    public TreeNode() => this(null);

    public TreeNode(TreeNode parent)
    {
        _parent = parent;    
        _children = new ObservableCollection<TreeNodeViewModel>();
    }

    public ObservableCollection<TreeNode> Children
    {
        get => _children; 
        set
        {
            if (value != _children)
            {
                _children = value;
                OnPropertyChanged(nameof(this.Children));
            }
        }
    }

    public TreeNode Parent
    {
        get => _parent; 
        set
        {
            if (value != _parent)
            {
                _parent = value;
                OnPropertyChanged(nameof(this.Parent));
            }
        }
    }

    public string Name
    {
        get => _name; 
        set
        {
            if (value != _name)
            {
                _name = value;
                OnPropertyChanged(nameof(this.Name));
            }
        }
    }

    public string Id
    {
        get => _id; 
        set
        {
            if (value != _id)
            {
                _id = value;
                OnPropertyChanged(nameof(this.Id));
            }
        }
    }

    public bool IsExpanded
    {
        get => _isExpanded; 
        set
        {
            if (value != _isExpanded)
            {
                _isExpanded = value;
                OnPropertyChanged(nameof(this.IsExpanded));
            }

            // Expand all the way up to the root.
            if (_isExpanded && _parent != null)
                _parent.IsExpanded = true;
        }
    }

    public bool IsSelected
    {
        get => _isSelected; 
        set
        {
            if (value != _isSelected)
            {
                _isSelected = value;
                OnPropertyChanged(nameof(this.IsSelected));

                OnSelectionChanged();
            }
        }
    }

    private void OnSelectionChanged()
    {
        this.SelectedChanged?.Invoke(this, EventArgs.Empty);
    }

    private void OnPropertyChanged(string propertyName)
    {
        this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

TreeViewModel.cs

public class TreeViewModel : INotifyPropertyChanged
{
    private readonly ObservableCollection<TreeNode> _firstLevel;
    private readonly TreeNode _rootNode;    
    private TreeNode _selectedTreeNode;
    private ICommand _addCommand;
    private ICommand _removeCommand;

    public TreeViewModel(TreeNode rootNode)
    {
        _rootNode = rootNode;

        _firstLevel = new ObservableCollection<TreeNode>(_rootNode.Children);

        _addCommand = new AddCommand(this);
        _removeCommand = new RemoveCommand(this);
    }

    public void AddNode(TreeNode treeNode)
    {
        treeNode.SelectedChanged += OnTreeNodeSelectedChanged;
        this.FirstLevel.Add(treeNode);
    }

    public void RemoveNode(TreeNode treeNode)
    {
        treeNode.SelectedChanged -= OnTreeNodeSelectedChanged;
        this.FirstLevel.Remove(treeNode);
    }

    public void OnTreeNodeSelectedChanged(object sender, EventArgs e)
    {
        var treeNode = sender as TreeNode;
        if (treeNode.isSelected)
        {
            this.SelectedNode = treeNode;
        }
    }

    private void OnPropertyChanged(string propertyName)
    {
        this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    public TreeNode RootNode => _rootNode; 

    public TreeNode SelectedNode
    {
        get => _selectedNode; 
        set
        {
            if (value != _selectedNode)
            {
                _selectedNode = value;
                OnPropertyChanged(nameof(this.SelectedNode));
            }
        }
    }

    public ObservableCollection<TreeNode> FirstLevel => _firstLevel; 

    public ICommand AddCommand => _addCommand;     

    public ICommand RemoveCommand => _removeCommand; 
}

AddCommand.cs

public class AddCommand : ICommand
{
    private TreeViewModel _tree;

    public AddCommand(TreeViewModel tree)
    {
        _tree = tree;
    }

    public event EventHandler CanExecuteChanged
    {
        add => CommandManager.RequerySuggested += value;
        remove => CommandManager.RequerySuggested -= value;
    }

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

    public void Execute(object parameter)
    {
        _tree.AddNode(new TreeNode(_tree.RootNode));

        //Show Selected item's name
        MessageBox.Show(_tree.SelectedNode.Name);
    }
}

RemoveCommand.cs

public class AddCommand : ICommand
{
    private TreeViewModel _tree;

    public AddCommand(TreeViewModel tree)
    {
        _tree = tree;
    }

    public event EventHandler CanExecuteChanged
    {
        add => CommandManager.RequerySuggested += value;
        remove => CommandManager.RequerySuggested -= value;
    }

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

    public void Execute(object parameter)
    {
        _tree.RemoveNode(parameter as TreeNode);
    }
}



回答3:


The MVVM pure methodology for binding to the selected item in a TreeView is using a Behavior class

public class perTreeViewHelper : Behavior<TreeView>
{
    public object BoundSelectedItem
    {
        get => GetValue(BoundSelectedItemProperty);
        set => SetValue(BoundSelectedItemProperty, value);
    }

    public static readonly DependencyProperty BoundSelectedItemProperty =
        DependencyProperty.Register("BoundSelectedItem",
            typeof(object),
            typeof(perTreeViewHelper),
            new FrameworkPropertyMetadata(null,
                FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                OnBoundSelectedItemChanged));

    private static void OnBoundSelectedItemChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
    {
        if (args.NewValue is perTreeViewItemViewModelBase item)
        {
            item.IsSelected = true;
        }
    }

    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.SelectedItemChanged += OnTreeViewSelectedItemChanged;
    }

    protected override void OnDetaching()
    {
        AssociatedObject.SelectedItemChanged -= OnTreeViewSelectedItemChanged;
        base.OnDetaching();
    }

    private void OnTreeViewSelectedItemChanged(object obj, RoutedPropertyChangedEventArgs<object> args)
    {
        BoundSelectedItem = args.NewValue;
    }
}

More details of its usage on my blog post.



来源:https://stackoverflow.com/questions/62342745/get-selected-item-in-treview-mvvm

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!