C# 数据绑定到 WPF 树视图中的 SelectedItem

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1000040/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 05:12:58  来源:igfitidea点击:

Data binding to SelectedItem in a WPF Treeview

c#wpfmvvmtreeviewselecteditem

提问by Natrium

How can I retrieve the item that is selected in a WPF-treeview? I want to do this in XAML, because I want to bind it.

如何检索在 WPF-treeview 中选择的项目?我想在 XAML 中执行此操作,因为我想绑定它。

You might think that it is SelectedItembut apparently that does not existis 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 SelectedItemto a property on my Model.

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

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

采纳答案by Steve Greatrex

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!

希望它会帮助某人!

回答by Thomas Levesque

This property exists : TreeView.SelectedItem

此属性存在:TreeView.SelectedItem

But it is readonly, so you cannot assign it through a binding, only retrieve it

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

回答by nabeelfarid

You might also be able to use TreeViewItem.IsSelected property

您也可以使用 TreeViewItem.IsSelected 属性

回答by Delta

Well, I found a solution. It moves the mess, so that MVVM works.

嗯,我找到了解决办法。它移动了混乱,以便 MVVM 工作。

First add this 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>

回答by bstoney

This can be accomplished in a 'nicer' way using only binding and the GalaSoft MVVM Light library's 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. In this example I used a RelayCommand and will just set the SelectedCluster property.

这可以通过仅使用绑定和 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; }
}

Then add the EventToCommand behavior in your xaml. This is really easy using blend.

然后在您的 xaml 中添加 EventToCommand 行为。这很容易使用混合。

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

回答by Wes

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

绑定的动机是保持良好和 MVVM。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 到我的模型的良好绑定,然后从其他东西到我的模型,我的解决方案是使用简单的元素将其他东西绑定到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.

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

回答by Arthur Nunes

I suggest an addition to the behavior provided by Steve Greatrex. His behavior doesn't reflects changes from the source because it may not be a collection of TreeViewItems. So it is a matter of finding the TreeViewItem in the tree which datacontext is the selectedValue from the source. The TreeView has a protected property called "ItemsHost", which holds the TreeViewItem collection. We can get it through reflection and walk the tree searching for the selected item.

我建议对 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;
    }

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 方法,从而节省每次绑定更新时使用反射的开销。

回答by Devgig

All to complicated... Go with Caliburn Micro (http://caliburnmicro.codeplex.com/)

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

View:

看法:

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

ViewModel:

视图模型:

public void SetSelectedItem(YourNodeViewModel item) {}; 

回答by Bas

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。

回答by karma

After studying the Internet for a day I found my own solution for selecting an item after create a normaltreeview in a normalWPF/C# environment

在网上研究了一天后,我找到了自己的解决方案,用于在普通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 ;
            }
        }