数据绑定到WPF Treeview中的SelectedItem

我怎样才能检索在WPF树视图中选择的项目? 我想在XAML中这样做,因为我想绑定它。

你可能会认为它是SelectedItem但显然不存在是只读的,因此不可用。

这是我想要做的:

 <TreeView ItemsSource="{Binding Path=Model.Clusters}" ItemTemplate="{StaticResource ClusterTemplate}" SelectedItem="{Binding Path=Model.SelectedCluster}" /> 

我想将SelectedItem绑定到我的模型上的一个属性。

但是这给了我错误:

“SelectedItem”属性是只读的,不能从标记中设置。

编辑:好的,这是我解决这个问题的方法:

 <TreeView ItemsSource="{Binding Path=Model.Clusters}" ItemTemplate="{StaticResource HoofdCLusterTemplate}" SelectedItemChanged="TreeView_OnSelectedItemChanged" /> 

并在我的xaml的codebehind文件中:

 private void TreeView_OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e) { Model.SelectedCluster = (Cluster)e.NewValue; } 

该属性存在: TreeView.SelectedItem

但它是只读的,所以你不能通过绑定来分配它,只能检索它

我意识到这已经有了一个可以接受的答案,但我把它放在一起来解决问题。 它使用与Delta的解决方案类似的想法,但不需要子类化TreeView:

 public class BindableSelectedItemBehavior : Behavior<TreeView> { #region SelectedItem Property public object SelectedItem { get { return (object)GetValue(SelectedItemProperty); } set { SetValue(SelectedItemProperty, value); } } public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register("SelectedItem", typeof(object), typeof(BindableSelectedItemBehavior), new UIPropertyMetadata(null, OnSelectedItemChanged)); private static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { var item = e.NewValue as TreeViewItem; if (item != null) { item.SetValue(TreeViewItem.IsSelectedProperty, true); } } #endregion protected override void OnAttached() { base.OnAttached(); this.AssociatedObject.SelectedItemChanged += OnTreeViewSelectedItemChanged; } protected override void OnDetaching() { base.OnDetaching(); if (this.AssociatedObject != null) { this.AssociatedObject.SelectedItemChanged -= OnTreeViewSelectedItemChanged; } } private void OnTreeViewSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e) { this.SelectedItem = e.NewValue; } } 

然后你可以在你的XAML中使用这个:

 <TreeView> <e:Interaction.Behaviors> <behaviours:BindableSelectedItemBehavior SelectedItem="{Binding SelectedItem, Mode=TwoWay}" /> </e:Interaction.Behaviors> </TreeView> 

希望它会帮助别人!

那么,我找到了一个解决方案。 它移动了混乱,所以MVVM的作品。

首先添加这个类:

 public class ExtendedTreeView : TreeView { public ExtendedTreeView() : base() { this.SelectedItemChanged += new RoutedPropertyChangedEventHandler<object>(___ICH); } void ___ICH(object sender, RoutedPropertyChangedEventArgs<object> e) { if (SelectedItem != null) { SetValue(SelectedItem_Property, SelectedItem); } } public object SelectedItem_ { get { return (object)GetValue(SelectedItem_Property); } set { SetValue(SelectedItem_Property, value); } } public static readonly DependencyProperty SelectedItem_Property = DependencyProperty.Register("SelectedItem_", typeof(object), typeof(ExtendedTreeView), new UIPropertyMetadata(null)); } 

并添加到您的XAML:

  <local:ExtendedTreeView ItemsSource="{Binding Items}" SelectedItem_="{Binding Item, Mode=TwoWay}"> ..... </local:ExtendedTreeView> 

答案与附加属性,并没有外部依赖,如果需要永远出现!

您可以创建一个可绑定的附加属性,并具有一个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}"> </TreeView> 

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

这可以通过仅使用绑定和GalaSoft MVVM Light库的EventToCommand的“更好”方式来实现。 在您的虚拟机中添加一个命令,当所选项目被更改时将被调用,并初始化该命令以执行任何必要的操作。 在这个例子中,我使用了一个RelayCommand,并将只设置SelectedCluster属性。

 public class ViewModel { public ViewModel() { SelectedClusterChanged = new RelayCommand<Cluster>( c => SelectedCluster = c ); } public RelayCommand<Cluster> SelectedClusterChanged { get; private set; } public Cluster SelectedCluster { get; private set; } } 

然后在xaml中添加EventToCommand行为。 使用混合这非常简单。

 <TreeView x:Name="lstClusters" ItemsSource="{Binding Path=Model.Clusters}" ItemTemplate="{StaticResource HoofdCLusterTemplate}"> <i:Interaction.Triggers> <i:EventTrigger EventName="SelectedItemChanged"> <GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding SelectedClusterChanged}" CommandParameter="{Binding ElementName=lstClusters,Path=SelectedValue}"/> </i:EventTrigger> </i:Interaction.Triggers> </TreeView> 

所有复杂…去与Caliburn微(http://caliburnmicro.codeplex.com/);

视图:

 <TreeView Micro:Message.Attach="[Event SelectedItemChanged] = [Action SetSelectedItem($this.SelectedItem)]" /> 

视图模型:

 public void SetSelectedItem(YourNodeViewModel item) {}; 

它比OP期待的要多一点,但是我希望至少可以帮到一些。

如果你想在SelectedItem改变的时候执行一个ICommand ,你可以在一个事件上绑定一个命令,并且不再需要在ViewModel使用属性SelectedItem

要做到这一点:

1-添加对System.Windows.Interactivity引用

 xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" 

2-将命令绑定到事件SelectedItemChanged

 <TreeView x:Name="myTreeView" Margin="1" ItemsSource="{Binding Directories}"> <i:Interaction.Triggers> <i:EventTrigger EventName="SelectedItemChanged"> <i:InvokeCommandAction Command="{Binding SomeCommand}" CommandParameter=" {Binding ElementName=myTreeView ,Path=SelectedItem}"/> </i:EventTrigger> </i:Interaction.Triggers> <TreeView.ItemTemplate> <!-- ... --> </TreeView.ItemTemplate> </TreeView> 

您可能也可以使用TreeViewItem.IsSelected属性

我遇到了这个页面寻找与原作者相同的答案,并证明总是有不止一种方法来做到这一点,对我来说,解决方案比迄今为止提供的答案更容易,所以我想我也可以添加到堆。

绑定的动机是保持良好的MVVM。 ViewModel的可能用法是有一个名为“CurrentThingy”的属性,而另一些地方的DataContext则绑定到“CurrentThingy”。

为了支持从TreeView到我的模型的良好绑定,然后从其他模型到我的模型,我的解决方案是使用简单的元素绑定另一个东西,而不是通过额外的步骤(例如:自定义行为,第三方控制) TreeView.SelectedItem,而不是绑定另一个东西到我的ViewModel,从而跳过所需的额外工作。

XAML:

 <TreeView x:Name="myTreeView" ItemsSource="{Binding MyThingyCollection}"> .... stuff </TreeView> <!-- then.. somewhere else where I want to see the currently selected TreeView item: --> <local:MyThingyDetailsView DataContext="{Binding ElementName=myTreeView, Path=SelectedItem}" /> 

当然,这对于阅读当前选择的项目是好的,但是不能设置它,这是我所需要的。

还有一种方法可以在不使用Interaction.Behaviors的情况下创建XAML可绑定的SelectedItem属性。

 public static class BindableSelectedItemHelper { #region Properties public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.RegisterAttached("SelectedItem", typeof(object), typeof(BindableSelectedItemHelper), new FrameworkPropertyMetadata(null, OnSelectedItemPropertyChanged)); public static readonly DependencyProperty AttachProperty = DependencyProperty.RegisterAttached("Attach", typeof(bool), typeof(BindableSelectedItemHelper), new PropertyMetadata(false, Attach)); private static readonly DependencyProperty IsUpdatingProperty = DependencyProperty.RegisterAttached("IsUpdating", typeof(bool), typeof(BindableSelectedItemHelper)); #endregion #region Implementation public static void SetAttach(DependencyObject dp, bool value) { dp.SetValue(AttachProperty, value); } public static bool GetAttach(DependencyObject dp) { return (bool)dp.GetValue(AttachProperty); } public static string GetSelectedItem(DependencyObject dp) { return (string)dp.GetValue(SelectedItemProperty); } public static void SetSelectedItem(DependencyObject dp, object value) { dp.SetValue(SelectedItemProperty, value); } private static bool GetIsUpdating(DependencyObject dp) { return (bool)dp.GetValue(IsUpdatingProperty); } private static void SetIsUpdating(DependencyObject dp, bool value) { dp.SetValue(IsUpdatingProperty, value); } private static void Attach(DependencyObject sender, DependencyPropertyChangedEventArgs e) { TreeListView treeListView = sender as TreeListView; if (treeListView != null) { if ((bool)e.OldValue) treeListView.SelectedItemChanged -= SelectedItemChanged; if ((bool)e.NewValue) treeListView.SelectedItemChanged += SelectedItemChanged; } } private static void OnSelectedItemPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { TreeListView treeListView = sender as TreeListView; if (treeListView != null) { treeListView.SelectedItemChanged -= SelectedItemChanged; if (!(bool)GetIsUpdating(treeListView)) { foreach (TreeViewItem item in treeListView.Items) { if (item == e.NewValue) { item.IsSelected = true; break; } else item.IsSelected = false; } } treeListView.SelectedItemChanged += SelectedItemChanged; } } private static void SelectedItemChanged(object sender, RoutedEventArgs e) { TreeListView treeListView = sender as TreeListView; if (treeListView != null) { SetIsUpdating(treeListView, true); SetSelectedItem(treeListView, treeListView.SelectedItem); SetIsUpdating(treeListView, false); } } #endregion } 

然后你可以在你的XAML中使用这个:

 <TreeView helper:BindableSelectedItemHelper.Attach="True" helper:BindableSelectedItemHelper.SelectedItem="{Binding SelectedItem, Mode=TwoWay}"> 

我建议增加Steve Greatrex提供的行为。 他的行为不反映来自源的变化,因为它可能不是TreeViewItems的集合。 所以这是一个在树中找到TreeViewItem的问题,其中datacontext是来自源的selectedValue。 TreeView有一个名为“ItemsHost”的保护属性,它保存着TreeViewItem集合。 我们可以通过反射来获取它,然后漫游搜索所选项目的树。

 private static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { var behavior = sender as BindableSelectedItemBehaviour; if (behavior == null) return; var tree = behavior.AssociatedObject; if (tree == null) return; if (e.NewValue == null) foreach (var item in tree.Items.OfType<TreeViewItem>()) item.SetValue(TreeViewItem.IsSelectedProperty, false); var treeViewItem = e.NewValue as TreeViewItem; if (treeViewItem != null) { treeViewItem.SetValue(TreeViewItem.IsSelectedProperty, true); } else { var itemsHostProperty = tree.GetType().GetProperty("ItemsHost", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); if (itemsHostProperty == null) return; var itemsHost = itemsHostProperty.GetValue(tree, null) as Panel; if (itemsHost == null) return; foreach (var item in itemsHost.Children.OfType<TreeViewItem>()) if (WalkTreeViewItem(item, e.NewValue)) break; } } public static bool WalkTreeViewItem(TreeViewItem treeViewItem, object selectedValue) { if (treeViewItem.DataContext == selectedValue) { treeViewItem.SetValue(TreeViewItem.IsSelectedProperty, true); treeViewItem.Focus(); return true; } foreach (var item in treeViewItem.Items.OfType<TreeViewItem>()) if (WalkTreeViewItem(item, selectedValue)) return true; return false; } 

这样的行为适用于双向绑定。 或者,可以将ItemsHost获取移动到行为的OnAttached方法,从而节省每次绑定更新时使用反射的开销。

我尝试了这个问题的所有解决方案。 没有人完全解决了我的问题。 所以我认为最好是使用这样的继承类与重新定义的属性SelectedItem。 如果从GUI中选择树元素,并且在代码中设置了此属性值,它将完美工作

 public class TreeViewEx : TreeView { public TreeViewEx() { this.SelectedItemChanged += new RoutedPropertyChangedEventHandler<object>(TreeViewEx_SelectedItemChanged); } void TreeViewEx_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e) { this.SelectedItem = e.NewValue; } #region SelectedItem /// <summary> /// Gets or Sets the SelectedItem possible Value of the TreeViewItem object. /// </summary> public new object SelectedItem { get { return this.GetValue(TreeViewEx.SelectedItemProperty); } set { this.SetValue(TreeViewEx.SelectedItemProperty, value); } } // Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc... public new static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register("SelectedItem", typeof(object), typeof(TreeViewEx), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, SelectedItemProperty_Changed)); static void SelectedItemProperty_Changed(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) { TreeViewEx targetObject = dependencyObject as TreeViewEx; if (targetObject != null) { TreeViewItem tvi = targetObject.FindItemNode(targetObject.SelectedItem) as TreeViewItem; if (tvi != null) tvi.IsSelected = true; } } #endregion SelectedItem public TreeViewItem FindItemNode(object item) { TreeViewItem node = null; foreach (object data in this.Items) { node = this.ItemContainerGenerator.ContainerFromItem(data) as TreeViewItem; if (node != null) { if (data == item) break; node = FindItemNodeInChildren(node, item); if (node != null) break; } } return node; } protected TreeViewItem FindItemNodeInChildren(TreeViewItem parent, object item) { TreeViewItem node = null; bool isExpanded = parent.IsExpanded; if (!isExpanded) //Can't find child container unless the parent node is Expanded once { parent.IsExpanded = true; parent.UpdateLayout(); } foreach (object data in parent.Items) { node = parent.ItemContainerGenerator.ContainerFromItem(data) as TreeViewItem; if (data == item && node != null) break; node = FindItemNodeInChildren(node, item); if (node != null) break; } if (node == null && parent.IsExpanded != isExpanded) parent.IsExpanded = isExpanded; if (node != null) parent.IsExpanded = true; return node; } } 

WPF MVVM TreeView SelectedItem

…是一个更好的答案,但没有提到在ViewModel中获取/设置SelectedItem的方法。

  1. 将一个IsSelected布尔属性添加到您的ItemViewModel,并将其绑定到TreeViewItem的样式设置器中。
  2. 将SelectedItem属性添加到用作TreeView的DataContext的ViewModel中。 这是上面解决方案中缺失的部分。
     'ItemVM ...
    公共属性被选为布尔值
        得到
            返回_func.SelectedNode是我
        结束获取
        设置(值为布尔值)
            如果IsSelected值然后
                 _func.SelectedNode = If(value,Me,Nothing)
            万一
             RaisePropertyChange()
        结束集
    最终财产
     'TreeVM ...
    公共属性SelectedItem作为ItemVM
        得到
            返回_selectedItem
        结束获取
        设置(值为ItemVM)
            如果_selectedItem是value那么
                返回
            万一
             Dim prev = _selectedItem
             _selectedItem =值
            如果prev IsNot Nothing那么
                 prev.IsSelected = False
            万一
            如果_selectedItem IsNot Nothing Then
                 _selectedItem.IsSelected = True
            万一
        结束集
    最终财产
 <TreeView ItemsSource="{Binding Path=TreeVM}" BorderBrush="Transparent"> <TreeView.ItemContainerStyle> <Style TargetType="TreeViewItem"> <Setter Property="IsExpanded" Value="{Binding IsExpanded}"/> <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/> </Style> </TreeView.ItemContainerStyle> <TreeView.ItemTemplate> <HierarchicalDataTemplate ItemsSource="{Binding Children}"> <TextBlock Text="{Binding Name}"/> </HierarchicalDataTemplate> </TreeView.ItemTemplate> </TreeView> 

我的需求是基于PRISM-MVVM的解决方案,需要一个TreeView,绑定对象的类型为Collection <>,因此需要HierarchicalDataTemplate。 默认的BindableSelectedItemBehavior不能识别子TreeViewItem。 使其在这种情况下工作。

 public class BindableSelectedItemBehavior : Behavior<TreeView> { #region SelectedItem Property public object SelectedItem { get { return (object)GetValue(SelectedItemProperty); } set { SetValue(SelectedItemProperty, value); } } public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register("SelectedItem", typeof(object), typeof(BindableSelectedItemBehavior), new UIPropertyMetadata(null, OnSelectedItemChanged)); private static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { var behavior = sender as BindableSelectedItemBehavior; if (behavior == null) return; var tree = behavior.AssociatedObject; if (tree == null) return; if (e.NewValue == null) foreach (var item in tree.Items.OfType<TreeViewItem>()) item.SetValue(TreeViewItem.IsSelectedProperty, false); var treeViewItem = e.NewValue as TreeViewItem; if (treeViewItem != null) treeViewItem.SetValue(TreeViewItem.IsSelectedProperty, true); else { var itemsHostProperty = tree.GetType().GetProperty("ItemsHost", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); if (itemsHostProperty == null) return; var itemsHost = itemsHostProperty.GetValue(tree, null) as Panel; if (itemsHost == null) return; foreach (var item in itemsHost.Children.OfType<TreeViewItem>()) { if (WalkTreeViewItem(item, e.NewValue)) break; } } } public static bool WalkTreeViewItem(TreeViewItem treeViewItem, object selectedValue) { if (treeViewItem.DataContext == selectedValue) { treeViewItem.SetValue(TreeViewItem.IsSelectedProperty, true); treeViewItem.Focus(); return true; } var itemsHostProperty = treeViewItem.GetType().GetProperty("ItemsHost", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); if (itemsHostProperty == null) return false; var itemsHost = itemsHostProperty.GetValue(treeViewItem, null) as Panel; if (itemsHost == null) return false; foreach (var item in itemsHost.Children.OfType<TreeViewItem>()) { if (WalkTreeViewItem(item, selectedValue)) break; } return false; } #endregion protected override void OnAttached() { base.OnAttached(); this.AssociatedObject.SelectedItemChanged += OnTreeViewSelectedItemChanged; } protected override void OnDetaching() { base.OnDetaching(); if (this.AssociatedObject != null) { this.AssociatedObject.SelectedItemChanged -= OnTreeViewSelectedItemChanged; } } private void OnTreeViewSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e) { this.SelectedItem = e.NewValue; } } 

这使得能够遍历所有元素而不管级别如何。

在学习了一天的互联网之后,我找到了自己的解决方案,在普通的 WPF / C#环境中创建了一个普通的 treeview后选择一个项目

 private void BuildSortTree(int sel) { MergeSort.Items.Clear(); TreeViewItem itTemp = new TreeViewItem(); itTemp.Header = SortList[0]; MergeSort.Items.Add(itTemp); TreeViewItem prev; itTemp.IsExpanded = true; if (0 == sel) itTemp.IsSelected= true; prev = itTemp; for(int i = 1; i<SortList.Count; i++) { TreeViewItem itTempNEW = new TreeViewItem(); itTempNEW.Header = SortList[i]; prev.Items.Add(itTempNEW); itTempNEW.IsExpanded = true; if (i == sel) itTempNEW.IsSelected = true; prev = itTempNEW ; } } 

也可以使用TreeView项目的IsSelected属性来完成。 以下是我如何管理它,

 public delegate void TreeviewItemSelectedHandler(TreeViewItem item); public class TreeViewItem { public static event TreeviewItemSelectedHandler OnItemSelected = delegate { }; public bool IsSelected { get { return isSelected; } set { isSelected = value; if (value) OnItemSelected(this); } } } 

然后在包含TreeView绑定数据的ViewModel中,只需订阅TreeViewItem类中的事件即可。

 TreeViewItem.OnItemSelected += TreeViewItemSelected; 

最后,在同一个ViewModel中实现这个处理程序,

 private void TreeViewItemSelected(TreeViewItem item) { //Do something } 

而当然的约束,

 <Setter Property="IsSelected" Value="{Binding IsSelected}" />