Data binding to SelectedItem in a WPF Treeview

后端 未结 20 801
南方客
南方客 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:18

    (Let's just all agree that TreeView is obviously busted in respect to this problem. Binding to SelectedItem would have been obvious. Sigh)

    I needed the solution to interact properly with the IsSelected property of TreeViewItem, so here's how I did it:

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

    With this 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 讨论(0)
  • 2020-11-22 06:21

    I know this thread is 10 years old but the problem still exists....

    The original question was 'to retrieve' the selected item. I also needed to "get" the selected item in my viewmodel (not set it). Of all the answers in this thread, the one by 'Wes' is the only one that approaches the problem differently: If you can use the 'Selected Item' as a target for databinding use it as a source for databinding. Wes did it to another view property, I will do it to a viewmodel property:

    We need two things:

    • Create a dependency property in the viewmodel (in my case of type 'MyObject' as my treeview is bound to object of the 'MyObject' type)
    • Bind from the Treeview.SelectedItem to this property in the View's constructor (yes that is code behind but, it's likely that you will init your datacontext there as well)

    Viewmodel:

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

    View constructor:

    Binding binding = new Binding("SelectedItem")
            {
                Source = treeView, //name of tree view in xaml
                Mode = BindingMode.OneWay
            };
    
            BindingOperations.SetBinding(DataContext, MyViewModel.SelectedTreeViewItemProperty, binding);
    
    0 讨论(0)
  • 2020-11-22 06:22

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

    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 ;
                }
            }
    
    0 讨论(0)
  • 2020-11-22 06:24

    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:

    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:

    <TreeView>
        <e:Interaction.Behaviors>
            <behaviours:BindableSelectedItemBehavior SelectedItem="{Binding SelectedItem, Mode=TwoWay}" />
        </e:Interaction.Behaviors>
    </TreeView>
    

    Hopefully it will help someone!

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

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

    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.

    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.

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

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

    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:

     <local:ExtendedTreeView ItemsSource="{Binding Items}" SelectedItem_="{Binding Item, Mode=TwoWay}">
     .....
     </local:ExtendedTreeView>
    
    0 讨论(0)
提交回复
热议问题