Data binding to SelectedItem in a WPF Treeview

后端 未结 20 764
南方客
南方客 2020-11-22 05:39

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.

You might think that it is SelectedItem

相关标签:
20条回答
  • 2020-11-22 06:11

    You might also be able to use TreeViewItem.IsSelected property

    0 讨论(0)
  • 2020-11-22 06:12

    I propose this solution (which I consider the easiest and memory leaks free) which works perfectly for updating the ViewModel's selected item from the View's selected item.

    Please note that changing the selected item from the ViewModel won't update the selected item of the 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 usage

    <l:TreeViewEx ItemsSource="{Binding Path=Items}" SelectedItemEx="{Binding Path=SelectedItem}" >
    
    0 讨论(0)
  • 2020-11-22 06:13

    My requirement was for PRISM-MVVM based solution where a TreeView was needed and the bound object is of type Collection<> and hence needs HierarchicalDataTemplate. The default BindableSelectedItemBehavior wont be able to identify the child TreeViewItem. To make it to work in this scenario.

    public class BindableSelectedItemBehavior : Behavior<TreeView>
    {
        #region SelectedItem Property
    
        public object SelectedItem
        {
            get { return (object)GetValue(SelectedItemProperty); }
            set { SetValue(SelectedItemProperty, value); }
        }
    
        public static readonly DependencyProperty SelectedItemProperty =
            DependencyProperty.Register("SelectedItem", typeof(object), typeof(BindableSelectedItemBehavior), new UIPropertyMetadata(null, OnSelectedItemChanged));
    
        private static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            var behavior = sender as BindableSelectedItemBehavior;
            if (behavior == null) return;
            var tree = behavior.AssociatedObject;
            if (tree == null) return;
            if (e.NewValue == null)
                foreach (var item in tree.Items.OfType<TreeViewItem>())
                    item.SetValue(TreeViewItem.IsSelectedProperty, false);
            var treeViewItem = e.NewValue as TreeViewItem;
            if (treeViewItem != null)
                treeViewItem.SetValue(TreeViewItem.IsSelectedProperty, true);
            else
            {
                var itemsHostProperty = tree.GetType().GetProperty("ItemsHost", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
                if (itemsHostProperty == null) return;
                var itemsHost = itemsHostProperty.GetValue(tree, null) as Panel;
                if (itemsHost == null) return;
                foreach (var item in itemsHost.Children.OfType<TreeViewItem>())
                {
                    if (WalkTreeViewItem(item, e.NewValue)) 
                        break;
                }
            }
        }
    
        public static bool WalkTreeViewItem(TreeViewItem treeViewItem, object selectedValue)
        {
            if (treeViewItem.DataContext == selectedValue)
            {
                treeViewItem.SetValue(TreeViewItem.IsSelectedProperty, true);
                treeViewItem.Focus();
                return true;
            }
            var itemsHostProperty = treeViewItem.GetType().GetProperty("ItemsHost", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
            if (itemsHostProperty == null) return false;
            var itemsHost = itemsHostProperty.GetValue(treeViewItem, null) as Panel;
            if (itemsHost == null) return false;
            foreach (var item in itemsHost.Children.OfType<TreeViewItem>())
            {
                if (WalkTreeViewItem(item, selectedValue))
                    break;
            }
            return false;
        }
        #endregion
    
        protected override void OnAttached()
        {
            base.OnAttached();
            this.AssociatedObject.SelectedItemChanged += OnTreeViewSelectedItemChanged;
        }
    
        protected override void OnDetaching()
        {
            base.OnDetaching();
            if (this.AssociatedObject != null)
            {
                this.AssociatedObject.SelectedItemChanged -= OnTreeViewSelectedItemChanged;
            }
        }
    
        private void OnTreeViewSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            this.SelectedItem = e.NewValue;
        }
    }
    

    This enables to iterate through all the elements irrespective of the level.

    0 讨论(0)
  • 2020-11-22 06:13

    WPF MVVM TreeView SelectedItem

    ... is a better answer, but does not mention a way to get/set the SelectedItem in the ViewModel.

    1. Add a IsSelected boolean property to your ItemViewModel, and bind to it in a Style Setter for the TreeViewItem.
    2. Add a SelectedItem property to your ViewModel used as the DataContext for the TreeView. This is the missing piece in the solution above.
        ' ItemVM...
        Public Property IsSelected As Boolean
            Get
                Return _func.SelectedNode Is Me
            End Get
            Set(value As Boolean)
                If IsSelected  value Then
                    _func.SelectedNode = If(value, Me, Nothing)
                End If
                RaisePropertyChange()
            End Set
        End Property
        ' TreeVM...
        Public Property SelectedItem As ItemVM
            Get
                Return _selectedItem
            End Get
            Set(value As ItemVM)
                If _selectedItem Is value Then
                    Return
                End If
                Dim prev = _selectedItem
                _selectedItem = value
                If prev IsNot Nothing Then
                    prev.IsSelected = False
                End If
                If _selectedItem IsNot Nothing Then
                    _selectedItem.IsSelected = True
                End If
            End Set
        End Property
    
    <TreeView ItemsSource="{Binding Path=TreeVM}" 
              BorderBrush="Transparent">
        <TreeView.ItemContainerStyle>
            <Style TargetType="TreeViewItem">
                <Setter Property="IsExpanded" Value="{Binding IsExpanded}"/>
                <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
            </Style>
        </TreeView.ItemContainerStyle>
        <TreeView.ItemTemplate>
            <HierarchicalDataTemplate ItemsSource="{Binding Children}">
                <TextBlock Text="{Binding Name}"/>
            </HierarchicalDataTemplate>
        </TreeView.ItemTemplate>
    </TreeView>
    
    0 讨论(0)
  • 2020-11-22 06:15

    This property exists : TreeView.SelectedItem

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

    0 讨论(0)
  • 2020-11-22 06:16

    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:

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

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

    0 讨论(0)
提交回复
热议问题