WPF MVVM TreeView SelectedItem

这不可能是如此困难。 WPF中的TreeView不允许你设置SelectedItem,说属性是ReadOnly。 我有TreeView填充,甚至更新数据绑定收集更改时。

我只需要知道什么项目被选中。 我正在使用MVVM,所以没有代码隐藏或variables来引用树视图。 这是我find的唯一的解决scheme ,但它是一个明显的黑客攻击,它创build另一个元素在XAML中使用ElementName绑定将其自身设置为树视图select项目,然后您必须绑定您的Viewmodel。 有几个其他的问题被问到,但没有给出其他工作解决scheme。

我已经看到这个问题 ,但使用给出的答案给我编译错误,出于某种原因,我不能添加到我的项目混合sdk System.Windows.Interactivity的引用。 它说“未知的错误system.windows尚未预装”,我还没有想出如何过去的。

对于奖励积分:为什么微软会让这个元素的SelectedItem属性为ReadOnly?

您不需要直接处理SelectedItem属性,将IsSelected绑定到视图IsSelected上的属性并跟踪所选项目。

草图:

 <TreeView ItemsSource="{Binding TreeData}"> <TreeView.ItemContainerStyle> <Style TargetType="{x:Type TreeViewItem}"> <Setter Property="IsSelected" Value="{Binding IsSelected}" /> </Style> </TreeView.ItemContainerStyle> </TreeView> 
 public class TViewModel : INotifyPropertyChanged { private static object _selectedItem = null; // This is public get-only here but you could implement a public setter which // also selects the item. // Also this should be moved to an instance property on a VM for the whole tree, // otherwise there will be conflicts for more than one tree. public static object SelectedItem { get { return _selectedItem; } private set { if (_selectedItem != value) { _selectedItem = value; OnSelectedItemChanged(); } } } static virtual void OnSelectedItemChanged() { // Raise event / do other things } private bool _isSelected; public bool IsSelected { get { return _isSelected; } set { if (_isSelected != value) { _isSelected = value; OnPropertyChanged("IsSelected"); if (_isSelected) { SelectedItem = this; } } } } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) { var handler = this.PropertyChanged; if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); } } 

您可以创build一个可绑定的附加属性,并具有一个getter和setter:

 public class TreeViewHelper { private static Dictionary<DependencyObject, TreeViewSelectedItemBehavior> behaviors = new Dictionary<DependencyObject, TreeViewSelectedItemBehavior>(); public static object GetSelectedItem(DependencyObject obj) { return (object)obj.GetValue(SelectedItemProperty); } public static void SetSelectedItem(DependencyObject obj, object value) { obj.SetValue(SelectedItemProperty, value); } // Using a DependencyProperty as the backing store for SelectedItem. This enables animation, styling, binding, etc... public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.RegisterAttached("SelectedItem", typeof(object), typeof(TreeViewHelper), new UIPropertyMetadata(null, SelectedItemChanged)); private static void SelectedItemChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) { if (!(obj is TreeView)) return; if (!behaviors.ContainsKey(obj)) behaviors.Add(obj, new TreeViewSelectedItemBehavior(obj as TreeView)); TreeViewSelectedItemBehavior view = behaviors[obj]; view.ChangeSelectedItem(e.NewValue); } private class TreeViewSelectedItemBehavior { TreeView view; public TreeViewSelectedItemBehavior(TreeView view) { this.view = view; view.SelectedItemChanged += (sender, e) => SetSelectedItem(view, e.NewValue); } internal void ChangeSelectedItem(object p) { TreeViewItem item = (TreeViewItem)view.ItemContainerGenerator.ContainerFromItem(p); item.IsSelected = true; } } } 

将包含该类的名称空间声明添加到您的XAML中并进行绑定,如下所示(本地是我如何命名该名称空间声明):

 <TreeView ItemsSource="{Binding Path=Root.Children}" local:TreeViewHelper.SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}"/> 

现在,您可以绑定选定的项目,并将其设置在您的视图模型中,以编程方式进行更改,如果此要求出现的话。 当然,这是假定您在该特定属性上实现INotifyPropertyChanged。

以MVVM可接受的方式来解决这个问题非常不寻常但相当有效的方法如下:

  1. 在TreeView所在的视图上创build可见性折叠的ContentControl。 将其命名适当,并将其内容绑定到viewmodel中的某个SelectedSomething属性。 这个ContentControl将“保存”选定的对象并处理它的绑定,OneWayToSource;
  2. 收听TreeView中的SelectedItemChanged ,并在代码隐藏中添加一个处理程序,将ContentControl.Content设置为新select的项目。

XAML:

 <ContentControl x:Name="SelectedItemHelper" Content="{Binding SelectedObject, Mode=OneWayToSource}" Visibility="Collapsed"/> <TreeView ItemsSource="{Binding SomeCollection}" SelectedItemChanged="TreeView_SelectedItemChanged"> 

代码背后:

  private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e) { SelectedItemHelper.Content = e.NewValue; } 

视图模型:

  public object SelectedObject // Class is not actually "object" { get { return _selected_object; } set { _selected_object = value; RaisePropertyChanged(() => SelectedObject); Console.WriteLine(SelectedObject); } } object _selected_object; 

使用OneWayToSource绑定模式。 这不起作用。 请参阅编辑。

编辑 :根据这个问题 ,看起来这是微软的一个错误或“devise”行为; 但是,有一些变通办法。 为你的TreeView做这些工作吗?

Microsoft Connect问题: https : //connect.microsoft.com/WPF/feedback/details/523865/read-only-dependency-properties-does-not-support-onewaytosource-bindings

微软于2010年1月10日下午2:46发布

我们现在不能在WPF中这样做,因为我们不能支持不属于DependencyProperties的属性的绑定。 绑定的运行时每个实例状态保存在BindingExpression中,我们将其存储在目标DependencyObject的EffectiveValueTable中。 当目标属性不是DP或DP是只读的时,就没有地方存储BindingExpression了。

可能有一天我们可能select将绑定function扩展到这两种情况。 我们经常被问到他们。 换句话说,您的请求已经在我们未来版本中要考虑的function列表中。

感谢您的反馈意见。

我决定使用后面的代码和viewmodel代码的组合。 xaml是这样的:

 <TreeView Name="tvCountries" ItemsSource="{Binding Path=Countries}" ItemTemplate="{StaticResource ResourceKey=countryTemplate}" SelectedValuePath="Name" SelectedItemChanged="tvCountries_SelectedItemChanged"> 

代码后面

 private void tvCountries_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e) { var vm = this.FindResource("vm") as ViewModels.CoiEditorViewModel; if (vm != null) { var treeItem = sender as TreeView; vm.TreeItemSelected = treeItem.SelectedItem; } } 

在视图模型中有一个TreeItemSelected对象,您可以在视图模型中访问该对象。

您始终可以创build使用ICommand的DependencyProperty并侦听TreeView上的SelectedItemChanged事件。 这可能比绑定IsSelected更容易,但我想你会结束IsSelected无论如何由于其他原因。 如果您只想在IsSelected上进行绑定,则只要IsSelected发生更改,就可以让您的项目发送消息。 然后,您可以在程序中的任何位置收听这些消息。