简体   繁体   English

数据绑定到一个 WPF Treeview 中的 SelectedItem

[英]Data binding to SelectedItem in a WPF Treeview

How can I retrieve the item that is selected in a WPF-treeview?如何检索在 WPF 树视图中选择的项目? I want to do this in XAML, because I want to bind it.我想在XAML做这个,因为我要绑定。

You might think that it is SelectedItem but apparently that does not exist is readonly and therefore unusable.您可能认为它是SelectedItem ,但显然它不存在是只读的,因此无法使用。

This is what I want to do:这就是我想要做的:

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

I want to bind the SelectedItem to a property on my Model.我想将SelectedItem绑定到我的 Model 上的一个属性。

But this gives me the error:但这给了我错误:

'SelectedItem' property is read-only and cannot be set from markup. “SelectedItem”属性是只读的,不能从标记中设置。

Edit: Ok, this is the way that I solved this:编辑:好的,这是我解决这个问题的方式:

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

and in the codebehindfile of my xaml:在我的 xaml 的代码隐藏文件中:

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

I realise this has already had an answer accepted, but I put this together to solve the problem.我意识到这已经有一个答案被接受,但我把它放在一起来解决问题。 It uses a similar idea to Delta's solution, but without the need to subclass the TreeView:它使用与 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;
    }
}

You can then use this in your XAML as:然后,您可以在您的 XAML 中将其用作:

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

Hopefully it will help someone!希望它能帮助别人!

This property exists: TreeView.SelectedItem此属性存在: TreeView.SelectedItem

But it is readonly, so you cannot assign it through a binding, only retrieve it但它是只读的,所以你不能通过绑定分配它,只能检索它

Answer with attached properties and no external dependencies, should the need ever arise!如果需要,请使用附加属性回答并且没有外部依赖项!

You can create an attached property that is bindable and has a getter and setter:您可以创建一个可绑定并具有 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;
        }
    }
}

Add the namespace declaration containing that class to your XAML and bind as follows (local is how I named the namespace declaration):将包含该类的命名空间声明添加到您的 XAML 并绑定如下(本地是我命名命名空间声明的方式):

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

    </TreeView>

Now you can bind the selected item, and also set it in your view model to change it programmatically, should that requirement ever arise.现在,您可以绑定所选项目,并在您的视图模型中设置它,以便在出现该需求时以编程方式更改它。 This is, of course, assuming that you implement INotifyPropertyChanged on that particular property.当然,这是假设您在该特定属性上实现 INotifyPropertyChanged。

Well, I found a solution.好吧,我找到了解决方案。 It moves the mess, so that MVVM works.它移动了混乱,以便 MVVM 工作。

First add this class:先加上这个class:

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

and add this to your xaml:并将其添加到您的 xaml:

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

It answers a little more than the OP is expecting... But I hope it could help some one at least.它的回答比 OP 预期的要多一些……但我希望它至少能对某些人有所帮助。

If you want to execute a ICommand whenever the SelectedItem changed, you can bind a command on an event and the use of a property SelectedItem in the ViewModel isn't needed anymore.如果您想在SelectedItem更改时执行ICommand ,您可以在事件上绑定命令,并且不再需要在ViewModel中使用SelectedItem属性。

To do so:为此:

1- Add reference to System.Windows.Interactivity 1- 添加对System.Windows.Interactivity的引用

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

2- Bind the command to the event SelectedItemChanged 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>

This can be accomplished in a 'nicer' way using only binding and the GalaSoft MVVM Light library's EventToCommand.这可以通过仅使用绑定和 GalaSoft MVVM Light 库的 EventToCommand 以“更好”的方式完成。 In your VM add a command which will be called when the selected item is changed, and initialize the command to perform whatever action is necessary.在您的 VM 中添加一个命令,该命令将在更改所选项目时调用,并初始化该命令以执行任何必要的操作。 In this example I used a RelayCommand and will just set the SelectedCluster property.在此示例中,我使用了 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; }
}

Then add the EventToCommand behavior in your xaml. This is really easy using blend.然后在您的 xaml 中添加 EventToCommand 行为。这使用 blend 非常容易。

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

All to complicated... Go with Caliburn Micro (http://caliburnmicro.codeplex.com/)所有复杂... Go 与 Caliburn Micro (http://caliburnmicro.codeplex.com/)

View:看法:

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

ViewModel:视图模型:

public void SetSelectedItem(YourNodeViewModel item) {}; 

I came across this page looking for the same answer as the original author, and proving there's always more than one way to do it, the solution for me was even easier than the answers provided here so far, so I figured I might as well add to the pile.我偶然发现这个页面寻找与原作者相同的答案,并证明总是有不止一种方法可以做到这一点,对我来说,解决方案比目前为止提供的答案更容易,所以我想我不妨添加到桩。

The motivation for the binding is to keep it nice & MVVM.绑定的动机是保持良好状态和 MVVM。 The probable usage of the ViewModel is to have a property w/ a name such as "CurrentThingy", and somewhere else, the DataContext on some other thing is bound to "CurrentThingy". ViewModel 的可能用途是拥有一个带有名称(例如“CurrentThingy”)的属性,而在其他地方,某些其他事物上的 DataContext 绑定到“CurrentThingy”。

Rather than going through additional steps required (eg: custom behavior, 3rd party control) to support a nice binding from the TreeView to my Model, and then from something else to my Model, my solution was to use simple Element binding the other thing to TreeView.SelectedItem, rather than binding the other thing to my ViewModel, thereby skipping the extra work required.我的解决方案不是通过额外的步骤(例如:自定义行为、第 3 方控制)来支持从 TreeView 到我的 Model,然后从其他东西到我的 Model 的良好绑定,而是使用简单的元素将其他东西绑定到TreeView.SelectedItem,而不是将其他东西绑定到我的 ViewModel,从而跳过所需的额外工作。

XAML: 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}" />

Of course, this is great for reading the currently selected item, but not setting it, which is all I needed.当然,这对于读取当前选定的项目非常有用,但不是设置它,这就是我所需要的。

You might also be able to use TreeViewItem.IsSelected property您也许还可以使用 TreeViewItem.IsSelected 属性

My requirement was for PRISM-MVVM based solution where a TreeView was needed and the bound object is of type Collection<> and hence needs HierarchicalDataTemplate.我的要求是基于 PRISM-MVVM 的解决方案,其中需要 TreeView 并且绑定的 object 是 Collection<> 类型,因此需要 HierarchicalDataTemplate。 The default BindableSelectedItemBehavior wont be able to identify the child TreeViewItem.默认的 BindableSelectedItemBehavior 将无法识别子 TreeViewItem。 To make it to work in this scenario.让它在这种情况下工作。

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

This enables to iterate through all the elements irrespective of the level.这使得能够遍历所有元素,而不管级别如何。

There is also a way to create XAML bindable SelectedItem property without using Interaction.Behaviors.还有一种方法可以在不使用 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
}

You can then use this in your XAML as:然后,您可以在您的 XAML 中将其用作:

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

I tried all solutions of this questions.我尝试了这个问题的所有解决方案。 No one solved my problem fully.没有人完全解决我的问题。 So I think it's better to use such inherited class with redefined property SelectedItem.因此,我认为最好使用具有重新定义的属性 SelectedItem 的继承 class。 It will work perfectly if you choose tree element from GUI and if you set this property value in your code如果您从 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;
    }
} 

I suggest an addition to the behavior provided by Steve Greatrex.我建议对 Steve Greatrex 提供的行为进行补充。 His behavior doesn't reflects changes from the source because it may not be a collection of TreeViewItems.他的行为不反映来自源的更改,因为它可能不是 TreeViewItems 的集合。 So it is a matter of finding the TreeViewItem in the tree which datacontext is the selectedValue from the source.所以这是在树中找到 TreeViewItem 的问题,其中 datacontext 是来自源的 selectedValue。 The TreeView has a protected property called "ItemsHost", which holds the TreeViewItem collection. TreeView 有一个名为“ItemsHost”的受保护属性,它包含 TreeViewItem 集合。 We can get it through reflection and walk the tree searching for the selected item.我们可以通过反射获取它并遍历树搜索所选项目。

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

This way the behavior works for two-way bindings.这种行为适用于双向绑定。 Alternatively, it is possible to move the ItemsHost acquisition to the Behavior's OnAttached method, saving the overhead of using reflection every time the binding updates.或者,可以将 ItemsHost 获取移动到 Behavior 的 OnAttached 方法,从而节省每次绑定更新时使用反射的开销。

WPF MVVM TreeView SelectedItem WPF MVVM TreeView SelectedItem

... is a better answer, but does not mention a way to get/set the SelectedItem in the ViewModel. ... 是一个更好的答案,但没有提到在 ViewModel 中获取/设置 SelectedItem 的方法。

  1. Add a IsSelected boolean property to your ItemViewModel, and bind to it in a Style Setter for the TreeViewItem.将 IsSelected boolean 属性添加到您的 ItemViewModel,并在 TreeViewItem 的样式设置器中绑定到它。
  2. Add a SelectedItem property to your ViewModel used as the DataContext for the TreeView. This is the missing piece in the solution above.将 SelectedItem 属性添加到用作 TreeView 的 DataContext 的 ViewModel。这是上述解决方案中缺少的部分。
    ' ItemVM...
    Public Property IsSelected As Boolean
        Get
            Return _func.SelectedNode Is Me
        End Get
        Set(value As Boolean)
            If IsSelected  value Then
                _func.SelectedNode = If(value, Me, Nothing)
            End If
            RaisePropertyChange()
        End Set
    End Property
    ' TreeVM...
    Public Property SelectedItem As ItemVM
        Get
            Return _selectedItem
        End Get
        Set(value As ItemVM)
            If _selectedItem Is value Then
                Return
            End If
            Dim prev = _selectedItem
            _selectedItem = value
            If prev IsNot Nothing Then
                prev.IsSelected = False
            End If
            If _selectedItem IsNot Nothing Then
                _selectedItem.IsSelected = True
            End If
        End Set
    End Property
<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>

After studying the Inte.net for a day I found my own solution for selecting an item after create a normal treeview in a normal WPF/C# environment在研究了一天的 Inte.net 之后,我找到了自己的解决方案,用于在正常的 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 ;
            }
        }

It can also be done using the IsSelected property of the TreeView item.也可以使用 TreeView 项的 IsSelected 属性来完成。 Here's how I managed it,这是我的管理方式,

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

Then in the ViewModel that contains the data your TreeView is bound to, just subscribe to the event in the TreeViewItem class.然后在包含你的TreeView绑定数据的ViewModel中,订阅TreeViewItem class中的事件即可。

TreeViewItem.OnItemSelected += TreeViewItemSelected;

And finally, implement this handler in the same ViewModel,最后,在同一个 ViewModel 中实现这个处理程序,

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

And the binding of course,当然还有绑定,

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

I know this thread is 10 years old but the problem still exists....我知道这个线程已有 10 年历史了,但问题仍然存在....

The original question was 'to retrieve' the selected item.最初的问题是“检索”所选项目。 I also needed to "get" the selected item in my viewmodel (not set it).我还需要在我的视图模型中“获取”所选项目(而不是设置它)。 Of all the answers in this thread, the one by 'Wes' is the only one that approaches the problem differently: If you can use the 'Selected Item' as a target for databinding use it as a source for databinding.在此线程中的所有答案中,“Wes”的答案是唯一以不同方式处理问题的答案:如果您可以将“选定项目”用作数据绑定的目标,请将其用作数据绑定的源。 Wes did it to another view property, I will do it to a viewmodel property: Wes 对另一个视图属性执行此操作,我将对 viewmodel 属性执行此操作:

We need two things:我们需要两件事:

  • Create a dependency property in the viewmodel (in my case of type 'MyObject' as my treeview is bound to object of the 'MyObject' type)在视图模型中创建一个依赖属性(在我的情况下,类型为“MyObject”,因为我的 treeview 绑定到“MyObject”类型的 object)
  • Bind from the Treeview.SelectedItem to this property in the View's constructor (yes that is code behind but, it's likely that you will init your datacontext there as well)在 View 的构造函数中从 Treeview.SelectedItem 绑定到这个属性(是的,这是代码,但是,您可能也会在那里初始化您的数据上下文)

Viewmodel:视图模型:

public static readonly DependencyProperty SelectedTreeViewItemProperty = DependencyProperty.Register("SelectedTreeViewItem", typeof(MyObject), typeof(MyViewModel), new PropertyMetadata(OnSelectedTreeViewItemChanged));

    private static void OnSelectedTreeViewItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        (d as MyViewModel).OnSelectedTreeViewItemChanged(e);
    }

    private void OnSelectedTreeViewItemChanged(DependencyPropertyChangedEventArgs e)
    {
        //do your stuff here
    }

    public MyObject SelectedWorkOrderTreeViewItem
    {
        get { return (MyObject)GetValue(SelectedTreeViewItemProperty); }
        set { SetValue(SelectedTreeViewItemProperty, value); }
    }

View constructor:查看构造函数:

Binding binding = new Binding("SelectedItem")
        {
            Source = treeView, //name of tree view in xaml
            Mode = BindingMode.OneWay
        };

        BindingOperations.SetBinding(DataContext, MyViewModel.SelectedTreeViewItemProperty, binding);

I propose this solution (which I consider the easiest and memory leaks free) which works perfectly for updating the ViewModel's selected item from the View's selected item.我提出了这个解决方案(我认为这是最简单的并且 memory 没有泄漏),它非常适合从视图的选定项目更新 ViewModel 的选定项目。

Please note that changing the selected item from the ViewModel won't update the selected item of the View.请注意,从 ViewModel 更改所选项目不会更新视图的所选项目。

public class TreeViewEx : TreeView
{
    public static readonly DependencyProperty SelectedItemExProperty = DependencyProperty.Register("SelectedItemEx", typeof(object), typeof(TreeViewEx), new FrameworkPropertyMetadata(default(object))
    {
        BindsTwoWayByDefault = true // Required in order to avoid setting the "BindingMode" from the XAML
    });

    public object SelectedItemEx
    {
        get => GetValue(SelectedItemExProperty);
        set => SetValue(SelectedItemExProperty, value);
    }

    protected override void OnSelectedItemChanged(RoutedPropertyChangedEventArgs<object> e)
    {
        SelectedItemEx = e.NewValue;
    }
}

XAML usage XAML 用法

<l:TreeViewEx ItemsSource="{Binding Path=Items}" SelectedItemEx="{Binding Path=SelectedItem}" >

(Let's just all agree that TreeView is obviously busted in respect to this problem. Binding to SelectedItem would have been obvious. Sigh ) (让我们都同意 TreeView 显然在这个问题上被破坏了。绑定到 SelectedItem 本来是显而易见的。叹息

I needed the solution to interact properly with the IsSelected property of TreeViewItem, so here's how I did it:我需要解决方案与 TreeViewItem 的 IsSelected 属性正确交互,所以我是这样做的:

// the Type CustomThing needs to implement IsSelected with notification
// for this to work.
public class CustomTreeView : TreeView
{
    public CustomThing SelectedCustomThing
    {
        get
        {
            return (CustomThing)GetValue(SelectedNode_Property);
        }
        set
        {
            SetValue(SelectedNode_Property, value);
            if(value != null) value.IsSelected = true;
        }
    }

    public static DependencyProperty SelectedNode_Property =
        DependencyProperty.Register(
            "SelectedCustomThing",
            typeof(CustomThing),
            typeof(CustomTreeView),
            new FrameworkPropertyMetadata(
                null,
                FrameworkPropertyMetadataOptions.None,
                SelectedNodeChanged));

    public CustomTreeView(): base()
    {
        this.SelectedItemChanged += new RoutedPropertyChangedEventHandler<object>(SelectedItemChanged_CustomHandler);
    }

    void SelectedItemChanged_CustomHandler(object sender, RoutedPropertyChangedEventArgs<object> e)
    {
        SetValue(SelectedNode_Property, SelectedItem);
    }

    private static void SelectedNodeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var treeView = d as CustomTreeView;
        var newNode = e.NewValue as CustomThing;

        treeView.SelectedCustomThing = (CustomThing)e.NewValue;
    }
}

With this XAML:有了这个 XAML:

<local:CustonTreeView ItemsSource="{Binding TreeRoot}" 
    SelectedCustomThing="{Binding SelectedNode,Mode=TwoWay}">
    <TreeView.ItemContainerStyle>
        <Style TargetType="TreeViewItem">
            <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
        </Style>
    </TreeView.ItemContainerStyle>
</local:CustonTreeView>

I bring you my solution which offers the following features:我为您带来了我的解决方案,它具有以下特点:

  • Supports 2 ways binding支持2种方式绑定

  • Auto updates the TreeViewItem.IsSelected properties (according to the SelectedItem)自动更新 TreeViewItem.IsSelected 属性(根据 SelectedItem)

  • No TreeView subclassing没有 TreeView 子类化

  • Items bound to ViewModel can be of any type (even null)绑定到 ViewModel 的项目可以是任何类型(甚至是 null)

1/ Paste the following code in your CS: 1/ 将以下代码粘贴到您的 CS 中:

public class BindableSelectedItem
{
    public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.RegisterAttached(
        "SelectedItem", typeof(object), typeof(BindableSelectedItem), new PropertyMetadata(default(object), OnSelectedItemPropertyChangedCallback));

    private static void OnSelectedItemPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var treeView = d as TreeView;
        if (treeView != null)
        {
            BrowseTreeViewItems(treeView, tvi =>
            {
                tvi.IsSelected = tvi.DataContext == e.NewValue;
            });
        }
        else
        {
            throw new Exception("Attached property supports only TreeView");
        }
    }

    public static void SetSelectedItem(DependencyObject element, object value)
    {
        element.SetValue(SelectedItemProperty, value);
    }

    public static object GetSelectedItem(DependencyObject element)
    {
        return element.GetValue(SelectedItemProperty);
    }

    public static void BrowseTreeViewItems(TreeView treeView, Action<TreeViewItem> onBrowsedTreeViewItem)
    {
        var collectionsToVisit = new System.Collections.Generic.List<Tuple<ItemContainerGenerator, ItemCollection>> { new Tuple<ItemContainerGenerator, ItemCollection>(treeView.ItemContainerGenerator, treeView.Items) };
        var collectionIndex = 0;
        while (collectionIndex < collectionsToVisit.Count)
        {
            var itemContainerGenerator = collectionsToVisit[collectionIndex].Item1;
            var itemCollection = collectionsToVisit[collectionIndex].Item2;
            for (var i = 0; i < itemCollection.Count; i++)
            {
                var tvi = itemContainerGenerator.ContainerFromIndex(i) as TreeViewItem;
                if (tvi == null)
                {
                    continue;
                }

                if (tvi.ItemContainerGenerator.Status == System.Windows.Controls.Primitives.GeneratorStatus.ContainersGenerated)
                {
                    collectionsToVisit.Add(new Tuple<ItemContainerGenerator, ItemCollection>(tvi.ItemContainerGenerator, tvi.Items));
                }

                onBrowsedTreeViewItem(tvi);
            }

            collectionIndex++;
        }
    }

}

2/ Example of use in your XAML file 2/ XAML 文件中的使用示例

<TreeView myNS:BindableSelectedItem.SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}" />  

When clicking on some item list you'll get the data in "Selected" property.单击某些项目列表时,您将获得“已选择”属性中的数据。

ViewModel :视图模型

public class ShellViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void NotifyPropertyChanged(String info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }

    private ObservableCollection<Books> _books;

    private List<Books> bookList = new List<Books>();

    public ObservableCollection<Books> Books
    {
        get { return _books; }
        set { _books = value; NotifyPropertyChanged("Books"); }
    }

    private Books books;

    public Books Selected
    {
        get { return books; }
        set { books = value; }
    }


    public ShellViewModel()
    {
        bookList = new List<Books>()
        {
            new Books{BookName = "Harry Poter",Price ="15$"},
            new Books{BookName = "Harry Poter 2 ",Price ="14.95$"},
            new Books{BookName = "Harry Poter 3",Price ="18.50$"},
            new Books{BookName = "Harry Poter 4",Price ="32.90$"},
        };

        Books = new ObservableCollection<Books>(bookList);
    }
}

public class Books
{
    public string BookName { get; set; }
    public string Price { get; set; }
}

XAML : XAML :

 <ListView x:Name="lst" Grid.Row="2" ItemsSource="{Binding Books}" SelectedItem="{Binding Selected}">
        <ListView.View>
            <GridView >
                <GridViewColumn Header="Name" DisplayMemberBinding="{Binding BookName}"  />
                <GridViewColumn Header="Price" Width="100" DisplayMemberBinding="{Binding Price}"/>
            </GridView>
        </ListView.View>
    </ListView>

I realise it's been a while since this was posted, but FWIW I'm using Telerik's RadTreeView, and SelectedItem seems to work fine - either the problem has been fixed in the meantime, or Telerik have worked round it for us.我意识到自发布以来已经有一段时间了,但 FWIW 我正在使用 Telerik 的 RadTreeView,并且 SelectedItem 似乎工作正常 - 同时问题已得到解决,或者 Telerik 已经为我们解决了这个问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM