数据绑定到WPF Treeview中的SelectedItem

Nat*_*ium 231 c# wpf treeview selecteditem mvvm

如何检索在WPF树视图中选择的项目?我想在XAML中这样做,因为我想绑定它.

你可能会认为它SelectedItem显然是不存在的只是readonly因此无法使用.

这就是我想要做的:

<TreeView ItemsSource="{Binding Path=Model.Clusters}" 
            ItemTemplate="{StaticResource ClusterTemplate}"
            SelectedItem="{Binding Path=Model.SelectedCluster}" />
Run Code Online (Sandbox Code Playgroud)

我想绑定SelectedItem到我的模型上的属性.

但这给了我错误:

'SelectedItem'属性是只读的,不能通过标记设置.

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

<TreeView
          ItemsSource="{Binding Path=Model.Clusters}" 
          ItemTemplate="{StaticResource HoofdCLusterTemplate}"
          SelectedItemChanged="TreeView_OnSelectedItemChanged" />
Run Code Online (Sandbox Code Playgroud)

在我的xaml的codebehindfile中:

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

Ste*_*rex 230

我意识到这已经接受了答案,但我把它放在一起解决问题.它使用与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;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以在XAML中将其用作:

<TreeView>
    <e:Interaction.Behaviors>
        <behaviours:BindableSelectedItemBehavior SelectedItem="{Binding SelectedItem, Mode=TwoWay}" />
    </e:Interaction.Behaviors>
</TreeView>
Run Code Online (Sandbox Code Playgroud)

希望它会帮助某人!

  • 正如布伦特指出的那样,我还需要将Mode = TwoWay添加到绑定中.我不是"Blender",因此不熟悉System.Windows.Interactivity中的Behavior <>类.该程序集是Expression Blend的一部分.对于那些不想购买/安装试用版以获得此程序集的人,可以下载包含System.Windows.Interactivity的BlendSDK.BlendSDK 3 for 3.5 ...我认为它是BlendSDK 4 for 4.0.注意:这只允许您获取所选项目,不允许您设置所选项目 (4认同)
  • 您还可以通过FrameworkPropertyMetadata替换UIPropertyMetadata(null,FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,OnSelectedItemChanged)); (4认同)
  • @Pascal是`xmlns:e =“ http://schemas.microsoft.com/expression/2010/interactivity” (4认同)
  • 这仅在您的 SelectedItem 是 TreeViewItem 时才有效,这在 MVVM 世界中是禁止的...父节点有自己的容器 (4认同)
  • 这将是解决问题的方法:http://stackoverflow.com/a/18700099/4227 (3认同)
  • @Lukas完全如上面的XAML代码片段所示.只需用`{Binding MyViewModelField,Mode = TwoWay}替换`{Binding SelectedItem,Mode = TwoWay}`` (2认同)

Tho*_*que 45

此属性存在:TreeView.SelectedItem

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

  • 那么当用户选择一个项目(又名“OneWayToSource”)时,我可以让这个`TreeView.SelectedItem`影响模型上的属性吗? (3认同)
  • 但是你可以让它不是只读,看看我的回答 (2认同)

Bas*_*Bas 42

如果需要,请回答附加属性并且没有外部依赖性!

您可以创建一个可绑定的附加属性,并具有一个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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

将包含该类的名称空间声明添加到XAML并按如下方式绑定(本地是我命名名称空间声明的方式):

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

    </TreeView>
Run Code Online (Sandbox Code Playgroud)

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

  • 这种方法的一个问题是只有通过绑定(即从ViewModel)设置了一次所选项后,行为才会开始工作.如果VM中的初始值为null,则绑定将不会更新DP值,并且不会激活该行为.您可以使用不同的默认选定项目(例如,无效项目)来解决此问题. (5认同)
  • @Mark:在实例化附加属性的UIPropertyMetadata时,只需使用new object()而不是上面的null.那个问题应该消失了...... (5认同)
  • +1,这个帖子中的最佳答案imho.不依赖于System.Windows.Interactivity,并允许双向绑定(在MVVM环境中以编程方式设置).完善. (4认同)
  • 我假设对 TreeViewItem 的转换对我来说失败了,因为我使用的是按数据类型从资源应用的 HierarchicalDataTemplate。但是,如果您删除 ChangeSelectedItem,则绑定到视图模型并检索该项目可以正常工作。 (2认同)

Del*_*lta 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));
}
Run Code Online (Sandbox Code Playgroud)

并将其添加到您的xaml:

 <local:ExtendedTreeView ItemsSource="{Binding Items}" SelectedItem_="{Binding Item, Mode=TwoWay}">
 .....
 </local:ExtendedTreeView>
Run Code Online (Sandbox Code Playgroud)

  • 为此,我会做一个附加行为! (13认同)
  • 不知道为什么,但它对我不起作用:( 我成功从树中获取所选项目,但反之亦然 - 从树外部更改所选项目。 (4认同)
  • 到目前为止,这是我唯一能够为我工作的事情.我非常喜欢这个解决方案. (3认同)

JiB*_*evé 22

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

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

为此:

1-添加参考 System.Windows.Interactivity

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

  • Microsoft 在 2018 年底引入了 [WPF 的 XAML 行为](https://github.com/microsoft/XamlBehaviorsWpf)。它可以用来代替 `System.Windows.Interactivity`。它对我有用(在 .NET Core 项目中尝试过)。要进行设置,只需添加 [Microsoft.Xaml.Behaviors.Wpf](https://www.nuget.org/packages/Microsoft.Xaml.Behaviors.Wpf/) nuget 包,将命名空间更改为 `xmlns:我=“http://schemas.microsoft.com/xaml/behaviors”`。要获取更多信息 - 请参阅[博客](https://devblogs.microsoft.com/dotnet/open-commerce-xaml-behaviors-for-wpf/) (4认同)
  • 可以从NuGet安装引用`System.Windows.Interactivity`:https://www.nuget.org/packages/System.Windows.Interactivity.WPF/ (3认同)

bst*_*ney 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; }
}
Run Code Online (Sandbox Code Playgroud)

然后在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>
Run Code Online (Sandbox Code Playgroud)


Dev*_*gig 13

一切都很复杂......跟Caliburn Micro一起去(http://caliburnmicro.codeplex.com/)

视图:

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

视图模型:

public void SetSelectedItem(YourNodeViewModel item) {}; 
Run Code Online (Sandbox Code Playgroud)

  • 是的......**TreeView**上_sets_ SelectedItem的部分在哪里? (4认同)

Wes*_*Wes 8

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

绑定的动机是保持它和MVVM.ViewModel的可能用法是拥有一个带有名称的属性,例如"CurrentThingy",而在其他地方,某些其他东西上的DataContext被绑定到"CurrentThingy".

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

XAML:

<TreeView x:Name="myTreeView" ItemsSource="{Binding MyThingyCollection}">
.... stuff
</TreeView>

<!-- then.. somewhere else where I want to see the currently selected TreeView item: -->

<local:MyThingyDetailsView 
       DataContext="{Binding ElementName=myTreeView, Path=SelectedItem}" />
Run Code Online (Sandbox Code Playgroud)

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


nab*_*rid 6

您也可以使用TreeViewItem.IsSelected属性