Получить выбранный элемент в обзоре MVVM

Я пытаюсь применить MVVM для просмотра дерева, обратившись к руководству Джоша https://www.codeproject.com/Articles/26288/Simplifying-the-WPF-TreeView-by-Using-the-ViewMode

Вот мой полный исходный код

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.????);




    }
}

Моя цель - когда я нажимаю, чтобы добавить команду, она показывает имя выбранного элемента и имя родительского выбранного элемента. Но проблема в том, что в TreeViewViewModel нет ничего для доступа к TreeNodeViewModel.

Среднему классу TreeViewViewModel запрещается доступ к свойству TreeNodeViewModel, когда он живет в мире Джоша.


person Bruce    schedule 12.06.2020    source источник
comment
show — в представлении, привязанном к TreeViewViewModel?   -  person asaf92    schedule 12.06.2020
comment
Кстати, ваша TreeViewViewModel (плохое имя, должно быть просто TreeViewModel) не имеет свойства SelectedItem   -  person asaf92    schedule 12.06.2020
comment
Я создал свойство, подобное SelectedItem в TreeViewViewModel, но как мне с ним справиться? Поскольку выбранный элемент находится в TreeNodeViewModel, я не могу получить к нему доступ   -  person Bruce    schedule 12.06.2020
comment
Вы сказали, что у вас есть свойство SelectedItem в виртуальной машине дерева, но вы не можете получить к нему доступ, потому что оно находится в виртуальной машине TreeNode? Команда находится в виртуальной машине дерева, а виртуальная машина дерева передается классу команд (который не должен существовать, но это совсем другая проблема), поэтому вы можете получить к нему доступ из метода выполнения команды.   -  person asaf92    schedule 12.06.2020
comment
@ asaf92 Я до сих пор не понимаю твой идеал. Как я могу получить доступ к выбранному элементу из treeVM. Я могу добавить атрибут SelectedNode, но как он может отразить его на выбранном узле в TReeNode VM   -  person Bruce    schedule 12.06.2020
comment
Вы можете сделать это разными способами, один из них — способ, предложенный @GazTheDestroyer. Другим вариантом может быть подписка TreeViewViewModel на события на каждом узле, которые запускаются узлами при изменении статуса выбора. Это действительно не имеет значения.   -  person asaf92    schedule 12.06.2020
comment
Я бы, вероятно, использовал поведение, чтобы установить прикрепленное свойство зависимости, привязать его к свойству в модели просмотра окна. stackoverflow.com/ вопросы/1000040/   -  person Andy    schedule 12.06.2020


Ответы (3)


Согласно моему комментарию к вашему предыдущему вопросу. Добавьте SelectedNode в TreeViewViewModel и установите его в установщике Node Selected:

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

    }
}
person GazTheDestroyer    schedule 12.06.2020
comment
Как я могу передать root конструктору третьего параметра? Из TreeViewViewModel мы только сталкиваемся с общедоступной TreeNodeViewModel (узел TreeNode), для корневого узла нет параметра, если я изменю подпись этого параметра, у меня возникнет много проблем. - person Bruce; 12.06.2020
comment
Корень TreeViewViewModel, какой именно здесь root, root здесь должен быть TreeNodeViewModel, если это TreeNodeViewModel, не SelectedNode. Если это TreeViewViewModel, то корня нет (у нас есть только public TreeNodeViewModel _rootNode;) - person Bruce; 12.06.2020
comment
Вам нужно изменить подпись конструктора и передать TreeViewViewModel в качестве зависимости, чтобы это работало. Что вы имеете в виду под множеством проблем? - person asaf92; 12.06.2020
comment
Не обращайте внимания на наименование. Это должно быть дерево вместо корня - person asaf92; 12.06.2020
comment
я имею в виду, что является корнем для конструктора из модели дерева просмотра, есть только одно, что можно добавить в этот конструктор, это TreeNodeViewmodel _root, но это узел дерева, а не вид дерева - person Bruce; 12.06.2020
comment
Привет, gazthedestroyer, не могли бы вы дать мне более подробную информацию о третьем параметре в конструкторе, что я должен добавить к этому параметру из treeviewviewmodel - person Bruce; 12.06.2020

Если вас беспокоит производительность приложения, всегда реализуйте INotifyPropertyChanged в источнике привязки, даже если вы не вызываете событие PropertyChanged (поскольку свойство не изменится).

В вашем случае TreeNode служит источником привязки для TreeView. Поэтому TreeNode должен реализовать INotifyPropertyChanged.

Поскольку IsSelected и IsExpanded определенно являются атрибутами узла, они должны быть членами TreeNode. Это делает TreeNodeViewModel избыточным. Удаление TreeNodeViewModel также удалит повторяющиеся свойства, например, Children или Name, и их неудобную инициализацию.
По этой причине я объединил оба класса, используя в своих примерах только TreeNode и TreeViewModel.

Также проверьте правильную реализацию события ICommand.CanExecuteChanged. Я исправил это в своих примерах.

Вместо передачи строковых значений членов или типов в методы используйте nameof. Я также исправил это, когда вы вызываете OnPropertyChanged. Наиболее оптимальную реализацию вызывающего устройства события OnPropertyChanged см. в разделе Microsoft Docs: INotifyPropertyChanged.PropertyChanged.

Манипулирование деревом, например, добавление/удаление элементов в/из дерева, всегда должно происходить централизованно в классе, который управляет деревом, например, TreeViewModel. Я реализовал соответствующие методы в этом классе. Команды теперь вызывают эти методы для управления деревом.

Решение вашей проблемы состоит в том, чтобы добавить свойство SelectedNode к классу, управляющему деревом, которое в вашем случае является TreeViewModel. TreeViewModel прослушивает событие SelctedChanged TreeNode, чтобы обновить свойство TreeViewModel.SelectedNode.

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

УдалитьCommand.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);
    }
}
person BionicCode    schedule 13.06.2020

Чистая методология MVVM для привязки к выбранному элементу в TreeView использует Behavior класс

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

Подробнее о его использовании читайте в моем сообщении в блоге.

person Peregrine    schedule 13.06.2020