问题
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