数据绑定到WPF Treeview中的SelectedItem


241

如何检索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的codebehindfile中:

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

51
真烂 它也打我。我来到这里是希望找到一种体面的方式,而我只是个白痴。这是第一次,我很伤心,我不是白痴..
安德烈Rînea

6
这真的很烂扰乱了绑定的概念
三角洲

希望能帮助一些要绑定到ICommand的树视图项选择改回叫jacobaloysious.wordpress.com/2012/02/19/...
雅各布aloysious

9
在绑定和MVVM方面,后面的代码不是“被禁止的”,而是后面的代码应支持该视图。在我所见过的所有其他解决方案中,我认为,背后的代码是一个更好的选择,因为它仍在处理将视图“绑定”到视图模型的问题。唯一的负面影响是,如果您的团队中只有一名设计人员只能在XAML中工作,那么后面的代码可能会损坏/被忽略。只需10秒钟即可实施一个解决方案,这是一个很小的代价。
nrjohnstone

最简单的解决方案之一可能是:stackoverflow.com/questions/1238304/…–
JoanComasFdz

Answers:


240

我意识到这已经得到了一个答案,但是我将其汇总以解决问题。它使用了与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>

希望它将对某人有所帮助!


5
正如布伦特指出的那样,我还需要向绑定添加Mode = TwoWay。我不是“混合器”,所以对System.Windows.Interactivity的Behavior <>类不熟悉。该程序集是Expression Blend的一部分。对于不想购买/安装试用版以获得此程序集的用户,可以下载BlendSDK,其中包括System.Windows.Interactivity。适用于3.5的BlendSDK 3 ...我认为它是适用于4.0的BlendSDK 4。注意:这仅允许您获取选中的项目,而不允许设置所选的项目
Mike Rowley

4
您还可以通过FrameworkPropertyMetadata(null,FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,OnSelectedItemChanged))替换UIPropertyMetadata。
Filimindji 2011年

3
这将是解决问题的一种方法:stackoverflow.com/a/18700099/4227
bitbonk 2013年

2
@Lukas完全与上面的XAML代码段所示相同。只需替换{Binding SelectedItem, Mode=TwoWay}{Binding MyViewModelField, Mode=TwoWay}
Steve Greatrex

4
@Pascal,这是xmlns:e="http://schemas.microsoft.com/expression/2010/interactivity"
Steve Greatrex 2014年


43

如果有需要,请附上附加的属性,而无需任何外部依赖关系!

您可以创建一个可绑定的具有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。


4
+1,这个线程最好的答案恕我直言。不依赖于System.Windows.Interactivity,并允许双向绑定(在MVVM环境中以编程方式设置)。完善。
克里斯·雷

5
这种方法的问题是,只有通过绑定(即从ViewModel)设置了选定项后,行为才会开始起作用。如果VM中的初始值为null,则绑定将不会更新DP值,并且不会激活该行为。您可以使用其他默认的选定项目(例如无效项目)来解决此问题。
2014年

6
@Mark:在实例化附加属性的UIPropertyMetadata时,只需使用new object()而不是上面的null。然后问题应该消失了……
barnacleboy

2
对于我来说,强制转换为TreeViewItem失败了,因为我正在使用按数据类型从资源中应用的HierarchicalDataTemplate。但是,如果删除ChangeSelectedItem,则可以绑定到视图模型并检索该项目。
Casey Sebben 2015年

1
我在投射到TreeViewItem时也遇到了问题。那时,ItemContainerGenerator仅包含对根项目的引用,但是我需要它也能够获取非根项目。如果传递对1的引用,则转换将失败并返回null。不确定如何解决?
Bob Tway

39

好吧,我找到了解决方案。它可以移动混乱,从而使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>

3
到目前为止,这是唯一为我工作的东西。我真的很喜欢这个解决方案。
拉切尔

1
不知道为什么,但它并没有为我工作:(我成功地从树上获取选中项,但不是反之亦然-所选项目从树外部改变。
埃雷兹

将依赖项属性设置为BindsTwoWayByDefault会更加整洁,那么您无需在XAML中指定TwoWay
Stephen Holt,

这是最好的方法。它不使用交互性引用,不使用背后的代码,不像某些行为那样发生内存泄漏。谢谢。
亚历山德鲁·迪库

如前所述,该解决方案不适用于双向绑定。如果在视图模型中设置该值,则更改不会传播到TreeView。
理查德·摩尔

25

它的回答比OP预期的要多...但是我希望它至少可以帮助一些人。

如果你想执行ICommand每当SelectedItem改变,你可以在一个事件和使用属性的绑定命令SelectedItemViewModel已经不再需要。

为此:

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>

3
System.Windows.Interactivity可以从NuGet安装该参考:nuget.org/packages/System.Windows.Interactivity.WPF
Li

我已经尝试解决这个问题了好几个小时,已经实现了,但是我的命令不起作用,请您能帮我吗?
Alfie

1
Microsoft在2018年底引入了WPFXAML行为。它可以代替来使用System.Windows.Interactivity。它为我工作(与.NET Core项目一起试用)。要进行设置,只需添加 Microsoft.Xaml.Behaviors.Wpf nuget包,并将名称空间更改为xmlns:i="http://schemas.microsoft.com/xaml/behaviors"。要获取更多信息-请参阅博客
rychlmoj

19

仅使用绑定和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中添加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工具包。但是,它不能解决设置所选节点并使树视图更新所选内容的问题。
keft 2015年

12

全部复杂...使用Caliburn Micro(http://caliburnmicro.codeplex.com/)

视图:

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

ViewModel:

public void SetSelectedItem(YourNodeViewModel item) {}; 

5
是的... 在TreeView设置 SelectedItem 的部分在哪里?
mnn 2013年

卡利本很优雅。适用于嵌套层次结构非常容易
Purusartha

8

我碰到了这个页面,寻找与原始作者相同的答案,并且证明总是有不止一种方法,对我来说,解决方案比到目前为止提供的答案还要容易,所以我想我也可以添加到堆。

绑定的动机是保持良好的MVVM。ViewModel的可能用法是拥有一个带有名称的属性,例如“ CurrentThingy”,在其他地方,DataContext在其他地方绑定到“ CurrentThingy”。

为了解决从TreeView到我的模型以及从其他东西到模型的良好绑定,不需要执行其他步骤(例如:自定义行为,第三者控制),我的解决方案是使用简单的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}" />

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


1
什么是本地:MyThingyDetailsView?我得到的是本地信息:MyThingyDetailsView保存选定的项目,但是您的视图模型如何获得此信息?这看起来像是一种不错的方法,但是我需要更多信息……
Bob Horn 2012年

local:MyThingyDetailsView只是一个充满XAML的UserControl,构成了有关一个“ thingy”实例的详细信息视图。它作为内容嵌入在另一个视图的中间,带有该视图的DataContext是当前选择的树视图项,使用元素绑定。
Wes 2012年

6

您也许还可以使用TreeViewItem.IsSelected属性


我认为这可能是正确的答案。但是我想看看有关Items的IsSelected属性如何传递到TreeView的示例或最佳实践建议。
anhoppe

3

还有一种无需使用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}">

3

我尝试了此问题的所有解决方案。没有人完全解决我的问题。因此,我认为最好将此类继承的类与重新定义的属性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时,才可以扩展父节点。
CoperNick

3

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

这使得可以遍历所有元素,而与级别无关。


谢谢!这是唯一适用于我的方案的方案,与您的方案相同。
罗伯特

效果很好,并且不会导致所选/扩展的绑定变得混乱
Rusty

2

我建议对Steve Greatrex提供的行为进行补充。他的行为无法反映出源头的变化,因为它可能不是TreeViewItems的集合。因此,只需在树中查找TreeViewItem,其中数据上下文是源中的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方法,从而节省每次绑定更新时使用反射的开销。


2

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是值,则
                返回
            万一
            昏暗prev = _selectedItem
            _selectedItem =值
            如果prev不是什么,那么
                prev.IsSelected =假
            万一
            如果_selectedItem不存在
                _selectedItem.IsSelected =真
            万一
        端套
    最终财产
<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>

1

经过一天的互联网学习,我发现了在普通的 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 ;
            }
        }

1

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

这实际上是一个被低估的解决方案。通过更改思维方式并绑定每个treeview元素的IsSelected属性,并冒泡IsSelected事件,您可以使用内置功能,该功能可以很好地与双向绑定结合使用。我已经尝试了许多针对此问题的建议解决方案,这是第一个可行的方案。连接起来有点复杂。谢谢。
理查德·摩尔

1

我知道此线程已有10年历史了,但问题仍然存在。

最初的问题是“检索”所选项目。我还需要“获取”我的视图模型中的选定项目(不设置它)。在该主题的所有答案中,“ Wes”的答案是唯一以不同方式解决该问题的答案:如果可以将“ Selected Item”用作数据绑定的目标,则可以将其用作数据绑定的源。Wes对另一个view属性进行了处理,我将对viewmodel属性进行了处理:

我们需要两件事:

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

视图模型:

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

0

(让我们只是都同意,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>

0

我为您带来了具有以下功能的解决方案:

  • 支持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}" />  

0

我提出了这种解决方案(我认为这是最简单且无内存泄漏的解决方案),该解决方案非常适合从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}" >
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.