【问题标题】:Data binding to SelectedItem in a WPF Treeview数据绑定到 WPF 树视图中的 SelectedItem
【发布时间】:2010-11-03 06:10:12
【问题描述】:

如何检索在 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 的代码隐藏文件中:

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

【问题讨论】:

  • 这太糟糕了。它也击中了我。我来到这里希望找到一种体面的方式,而我只是一个白痴。这是我第一次为自己不是白痴而难过..
  • 这真的很糟糕并且搞砸了绑定概念
  • 希望这可以帮助某些人绑定到选定的树视图项目更改回调 Icommand jacobaloysious.wordpress.com/2012/02/19/…
  • 在绑定和 MVVM 方面,code behind 不是“禁止”的,而是 code behind 应该支持视图。在我看到的所有其他解决方案中,我认为背后的代码是一个更好的选择,因为它仍在处理将视图“绑定”到视图模型。唯一的缺点是,如果您的团队中有一个设计人员只在 XAML 中工作,那么背后的代码可能会被破坏/忽略。对于需要 10 秒实施的解决方案,这是一个很小的代价。
  • 我觉得很遗憾和震惊的是,近十几年过去了,微软仍然没有修复这种可怕的开发者体验。真是……难以置信,真的。

标签: c# wpf mvvm treeview selecteditem


【解决方案1】:

我意识到这已经接受了一个答案,但我把它放在一起来解决问题。它使用了与 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>

希望它对某人有所帮助!

【讨论】:

  • 正如 Brent 指出的那样,我还需要将 Mode=TwoWay 添加到绑定中。我不是“搅拌机”,所以不熟悉 System.Windows.Interactivity 中的 Behavior 类。该程序集是 Expression Blend 的一部分。对于那些不想购买/安装试用版来获得此程序集的人,您可以下载包含 System.Windows.Interactivity 的 BlendSDK。 BlendSDK 3 for 3.5...我认为它是 BlendSDK 4 for 4.0。注意:这里只允许你获取被选中的项目,不允许你设置被选中的项目
  • 您也可以将 UIPropertyMetadata 替换为 FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedItemChanged));
  • 这将是解决问题的一种方法:stackoverflow.com/a/18700099/4227
  • @Pascal 这是xmlns:e="http://schemas.microsoft.com/expression/2010/interactivity"
  • 这仅在您的 SelectedItem 是 TreeViewItem 时才有效,在 MVVM 世界中这是一个禁忌...为了使其适用于 SelectedItem 中的任何数据,我们需要获取数据,这很棘手,因为每个树视图父节点都有自己的容器
【解决方案2】:

此属性存在:TreeView.SelectedItem

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

【讨论】:

  • 我接受这个答案,因为在那里我找到了这个链接,这让我自己回答:msdn.microsoft.com/en-us/library/ms788714.aspx
  • 那么当用户选择一个项目(又名OneWayToSource)时,我可以让这个TreeView.SelectedItem 影响模型上的属性吗?
【解决方案3】:

嗯,我找到了解决办法。它移动了混乱,使 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>

【讨论】:

  • 这是迄今为止唯一接近为我工作的事情。我真的很喜欢这个解决方案。
  • 不知道为什么,但它对我不起作用:(我成功地从树中获取所选项目,但反之亦然 - 从树外更改所选项目。跨度>
  • 将依赖属性设置为 BindsTwoWayByDefault 会稍微简洁一些,这样您就不需要在 XAML 中指定 TwoWay
  • 这是最好的方法。它不使用交互性引用,不使用后面的代码,它没有像某些行为那样的内存泄漏。谢谢。
  • 如前所述,此解决方案不适用于 2 路绑定。如果您在视图模型中设置值,则更改不会传播到 TreeView。
【解决方案4】:

它的回答比 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>

【讨论】:

  • 引用System.Windows.Interactivity可以从NuGet安装:nuget.org/packages/System.Windows.Interactivity.WPF
  • 我已经尝试解决这个问题几个小时了,我已经实现了这个但是我的命令不起作用,请你帮帮我吗?
  • 微软在2018年底推出了XAML Behaviors for WPF,可以代替System.Windows.Interactivity使用。它对我有用(尝试使用 .NET Core 项目)。要进行设置,只需添加 Microsoft.Xaml.Behaviors.Wpf nuget 包,将命名空间更改为 xmlns:i="http://schemas.microsoft.com/xaml/behaviors"。要获取更多信息 - 请参阅blog
【解决方案5】:

这可以通过仅使用绑定和 GalaSoft MVVM Light 库的 EventToCommand 以“更好”的方式完成。在您的 VM 中添加一个命令,该命令将在更改所选项目时调用,并初始化该命令以执行任何必要的操作。在此示例中,我使用了 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.xml 中添加 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>

【讨论】:

  • 这是一个很好的解决方案,特别是如果您已经在使用 MvvmLight 工具包。然而,它并没有解决设置选定节点的问题并让树视图更新选择。
【解决方案6】:

一切都变得复杂...使用 Caliburn Micro (http://caliburnmicro.codeplex.com/)

查看:

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

视图模型:

public void SetSelectedItem(YourNodeViewModel item) {}; 

【讨论】:

  • 是的...TreeView设置 SelectedItem 的部分在哪里?
  • Caliburn 漂亮而优雅。很容易适用于嵌套层次结构
【解决方案7】:

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

绑定的动机是让它保持美观和 MVVM。 ViewModel 的可能用途是拥有一个带有名称的属性,例如“CurrentThingy”,而在其他地方,其他事物上的 DataContext 绑定到“CurrentThingy”。

我的解决方案是使用简单的 Element 绑定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}" />

当然,这对于读取当前选定的项目非常有用,但不是设置它,这就是我所需要的。

【讨论】:

  • 什么是本地:MyThingyDetailsView?我得到那个 local:MyThingyDetailsView 包含选定的项目,但是您的视图模型如何获取此信息?这看起来是一种不错、干净的方法,但我需要更多信息...
  • local:MyThingyDetailsView 只是一个充满 XAML 的 UserControl,构成关于一个“thingy”实例的详细信息视图。它作为内容嵌入另一个视图的中间,该视图的 DataContext 是当前选择的树视图项,使用元素绑定。
【解决方案8】:

您也可以使用 TreeViewItem.IsSelected 属性

【讨论】:

  • 我认为这可能是正确的答案。但我希望看到有关如何将 Items 的 IsSelected 属性传递给 TreeView 的示例或最佳实践建议。
【解决方案9】:

还有一种方法可以在不使用 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}">

【讨论】:

    【解决方案10】:

    我尝试了这个问题的所有解决方案。没有人完全解决我的问题。所以我认为最好使用带有重新定义属性 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;
        }
    } 
    

    【讨论】:

    • 如果某些节点不调用 UpdateLayout() 和 IsExpanded 会快得多。什么时候不需要调用 UpdateLayout() 和 IsExpanded?以前访问过树项时。怎么知道? ContainerFromItem() 为未访问的节点返回 null。所以只有当 ContainerFromItem() 为子节点返回 null 时,我们才能展开父节点。
    【解决方案11】:

    我的要求是基于 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;
        }
    }
    

    这使得无论关卡如何都可以遍历所有元素。

    【讨论】:

    • 谢谢!这是唯一适用于我的方案的方案,与您的方案不同。
    • 效果很好,不会导致选中/展开的绑定混淆
    【解决方案12】:

    我建议对 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 获取移至 Behavior 的 OnAttached 方法,从而节省每次绑定更新时使用反射的开销。

    【讨论】:

      【解决方案13】:

      WPF MVVM TreeView SelectedItem

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

      1. 将 IsSelected 布尔属性添加到您的 ItemViewModel,并在 TreeViewItem 的样式设置器中绑定到它。
      2. 将 SelectedItem 属性添加到用作 TreeView 的 DataContext 的 ViewModel 中。这是上述解决方案中缺少的部分。
      ' 项目虚拟机... 公共属性被选为布尔值 得到 返回 _func.SelectedNode 是我 结束获取 设置(值作为布尔值) If IsSelected 值 Then _func.SelectedNode = If(value, Me, Nothing) 万一 提高属性更改() 结束集 结束属性 ' 树虚拟机... 公共属性 SelectedItem 作为 ItemVM 得到 返回 _selectedItem 结束获取 设置(值作为 ItemVM) 如果 _selectedItem 是值 那么 返回 万一 暗淡上一个 = _selectedItem _selectedItem = 值 If prev IsNot Nothing Then 上一页.IsSelected = False 万一 If _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>
      

      【讨论】:

        【解决方案14】:

        普通 WPF/C#环境中创建普通树视图后,我找到了自己的选择项目的解决方案

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

        【讨论】:

          【解决方案15】:

          也可以使用 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}" />    
          

          【讨论】:

          • 这实际上是一个被低估的解决方案。通过改变你的思维方式和绑定每个树视图元素的 IsSelected 属性,并让 IsSelected 事件冒泡,你可以使用内置的功能,该功能与双向绑定配合得很好。我已经尝试了许多针对此问题的建议解决方案,这是第一个有效的解决方案。只是连接起来有点复杂。谢谢。
          【解决方案16】:

          我知道这个帖子有 10 年历史了,但问题仍然存在......

          最初的问题是“检索”所选项目。我还需要在我的视图模型中“获取”选定的项目(而不是设置它)。在此线程中的所有答案中,“Wes”的答案是唯一一个以不同方式解决问题的答案:如果您可以将“选定项”用作数据绑定的目标,请将其用作数据绑定的源。 Wes 对另一个视图属性执行此操作,我将对视图模型属性执行此操作:

          我们需要两件事:

          • 在视图模型中创建一个依赖属性(在我的“MyObject”类型的情况下,因为我的树视图绑定到“MyObject”类型的对象)
          • 从 Treeview.SelectedItem 绑定到视图构造函数中的此属性(是的,这是代码,但您很可能也会在那里初始化数据上下文)

          视图模型:

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

          视图构造函数:

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

          【讨论】:

            【解决方案17】:

            (让我们都同意 TreeView 在这个问题上显然是失败的。绑定到 SelectedItem 本来是显而易见的。叹息

            我需要解决方案与 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;
                }
            }
            

            使用此 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>
            

            【讨论】:

              【解决方案18】:

              我为您带来我的解决方案,它提供以下功能:

              • 支持2种方式绑定

              • 自动更新 TreeViewItem.IsSelected 属性(根据 SelectedItem)

              • 没有 TreeView 子类化

              • 绑定到 ViewModel 的项目可以是任何类型(甚至为 null)

              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/ XAML 文件中的使用示例

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

              【讨论】:

                【解决方案19】:

                我提出了这个解决方案(我认为这是最简单的并且没有内存泄漏),它非常适合从 View 的选定项更新 ViewModel 的选定项。

                请注意,从 ViewModel 更改选定项不会更新 View 的选定项。

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

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

                【讨论】:

                  【解决方案20】:

                  我意识到这已经有一段时间没有发布了,但是 FWIW 我正在使用 Telerik 的 RadTreeView,并且 SelectedItem 似乎工作正常 - 在此期间问题已经解决,或者 Telerik 已经为我们解决了这个问题。

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 2012-05-23
                    • 2011-09-12
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2012-02-20
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多