How to programmatically select an item in a WPF TreeView?

后端 未结 16 2190
时光取名叫无心
时光取名叫无心 2020-11-28 08:42

How is it possible to programmatically select an item in a WPF TreeView? The ItemsControl model seems to prevent it.

相关标签:
16条回答
  • 2020-11-28 08:52

    I've succeeded with this code:

    public static TreeViewItem FindTviFromObjectRecursive(ItemsControl ic, object o) {
      //Search for the object model in first level children (recursively)
      TreeViewItem tvi = ic.ItemContainerGenerator.ContainerFromItem(o) as TreeViewItem;
      if (tvi != null) return tvi;
      //Loop through user object models
      foreach (object i in ic.Items) {
        //Get the TreeViewItem associated with the iterated object model
        TreeViewItem tvi2 = ic.ItemContainerGenerator.ContainerFromItem(i) as TreeViewItem;
        tvi = FindTviFromObjectRecursive(tvi2, o);
        if (tvi != null) return tvi;
      }
      return null;
    }
    

    Usage:

    var tvi = FindTviFromObjectRecursive(TheTreeView, TheModel);
    if (tvi != null) tvi.IsSelected = true;
    
    0 讨论(0)
  • 2020-11-28 08:52

    I have created a method VisualTreeExt.GetDescendants<T> that returns an enumerable collection of elements that match the specified type:

    public static class VisualTreeExt
    {
      public static IEnumerable<T> GetDescendants<T>(DependencyObject parent) where T : DependencyObject
      {
        var count = VisualTreeHelper.GetChildrenCount(parent);
        for (var i = 0; i < count; ++i)
        {
           // Obtain the child
           var child = VisualTreeHelper.GetChild(parent, i);
           if (child is T)
             yield return (T)child;
    
           // Return all the descendant children
           foreach (var subItem in GetDescendants<T>(child))
             yield return subItem;
        }
      }
    }
    

    When you ask for VisualTreeHelperExt.GetDescendants<TreeViewItem>(MyAmazingTreeView) you'll get all the TreeViewItem childs. You can select a particular value using the following piece of code:

    var treeViewItem = VisualTreeExt.GetDescendants<TreeViewItem>(MyTreeView).FirstOrDefault(tvi => tvi.DataContext == newValue);
    if (treeViewItem != null)
      treeViewItem.IsSelected = true;
    

    It's a bit of a dirty solution (and probably not the most efficient) and won't work if you're using a virtualized TreeView, because it depends on the existance of the actual visual elements. But it works for my situation...

    0 讨论(0)
  • 2020-11-28 08:52

    Yeah.. I know many years past since the question was asked but.. still no quick solution to this problem.. and So:

    The following will do what the OP asked for.

    What I basically done is reading all the answers in this page and following all the relevant links to create a once and for all solution to this irritating problem.

    Benefits:

    • It support Virtualizing TreeView as well.
    • It using the behavior technique, so XAML is way easy.
    • Adds a dependancy-property to allow binding to the selected TreeView Item.


    This part is the only code you need to copy, the other parts are just to help complete an example.

    public static class TreeViewSelectedItemExBehavior
    {
        private static List<TreeView> isRegisteredToSelectionChanged = new List<TreeView>();
    
        public static readonly DependencyProperty SelectedItemExProperty =
            DependencyProperty.RegisterAttached("SelectedItemEx",
                typeof(object),
                typeof(TreeViewSelectedItemExBehavior),
                new FrameworkPropertyMetadata(new object(), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedItemExChanged, null));
    
        #region SelectedItemEx
    
        public static object GetSelectedItemEx(TreeView target)
        {
            return target.GetValue(SelectedItemExProperty);
        }
    
        public static void SetSelectedItemEx(TreeView target, object value)
        {
            target.SetValue(SelectedItemExProperty, value);
            var treeViewItemToSelect = GetTreeViewItem(target, value);
            if (treeViewItemToSelect == null)
            {
                if (target.SelectedItem == null)
                    return;
                var treeViewItemToUnSelect = GetTreeViewItem(target, target.SelectedItem);
                treeViewItemToUnSelect.IsSelected = false;
            }
            else
                treeViewItemToSelect.IsSelected = true;
        }
    
        public static void OnSelectedItemExChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
        {
            var treeView = depObj as TreeView;
            if (treeView == null)
                return;
            if (!isRegisteredToSelectionChanged.Contains(treeView))
            {
                treeView.SelectedItemChanged += TreeView_SelectedItemChanged;
                isRegisteredToSelectionChanged.Add(treeView);
            }
        }
    
        #endregion
    
        private static void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            var treeView = (TreeView)sender;
            SetSelectedItemEx(treeView, e.NewValue);
        }
    
        #region Helper Structures & Methods
    
        public class MyVirtualizingStackPanel : VirtualizingStackPanel
        {
            /// <summary>
            /// Publically expose BringIndexIntoView.
            /// </summary>
            public void BringIntoView(int index)
            {
                BringIndexIntoView(index);
            }
        }
    
        /// <summary>Recursively search for an item in this subtree.</summary>
        /// <param name="container">The parent ItemsControl. This can be a TreeView or a TreeViewItem.</param>
        /// <param name="item">The item to search for.</param>
        /// <returns>The TreeViewItem that contains the specified item.</returns>
        private static TreeViewItem GetTreeViewItem(ItemsControl container, object item)
        {
            if (container != null)
            {
                if (container.DataContext == item)
                {
                    return container as TreeViewItem;
                }
    
                // Expand the current container
                if (container is TreeViewItem && !((TreeViewItem)container).IsExpanded)
                {
                    container.SetValue(TreeViewItem.IsExpandedProperty, true);
                }
    
                // Try to generate the ItemsPresenter and the ItemsPanel.
                // by calling ApplyTemplate.  Note that in the 
                // virtualizing case even if the item is marked 
                // expanded we still need to do this step in order to 
                // regenerate the visuals because they may have been virtualized away.
    
                container.ApplyTemplate();
                ItemsPresenter itemsPresenter =
                    (ItemsPresenter)container.Template.FindName("ItemsHost", container);
                if (itemsPresenter != null)
                {
                    itemsPresenter.ApplyTemplate();
                }
                else
                {
                    // The Tree template has not named the ItemsPresenter, 
                    // so walk the descendents and find the child.
                    itemsPresenter = FindVisualChild<ItemsPresenter>(container);
                    if (itemsPresenter == null)
                    {
                        container.UpdateLayout();
    
                        itemsPresenter = FindVisualChild<ItemsPresenter>(container);
                    }
                }
    
                Panel itemsHostPanel = (Panel)VisualTreeHelper.GetChild(itemsPresenter, 0);
    
    
                // Ensure that the generator for this panel has been created.
                UIElementCollection children = itemsHostPanel.Children;
    
                MyVirtualizingStackPanel virtualizingPanel =
                    itemsHostPanel as MyVirtualizingStackPanel;
    
                for (int i = 0, count = container.Items.Count; i < count; i++)
                {
                    TreeViewItem subContainer;
                    if (virtualizingPanel != null)
                    {
                        // Bring the item into view so 
                        // that the container will be generated.
                        virtualizingPanel.BringIntoView(i);
    
                        subContainer =
                            (TreeViewItem)container.ItemContainerGenerator.
                            ContainerFromIndex(i);
                    }
                    else
                    {
                        subContainer =
                            (TreeViewItem)container.ItemContainerGenerator.
                            ContainerFromIndex(i);
    
                        // Bring the item into view to maintain the 
                        // same behavior as with a virtualizing panel.
                        subContainer.BringIntoView();
                    }
    
                    if (subContainer != null)
                    {
                        // Search the next level for the object.
                        TreeViewItem resultContainer = GetTreeViewItem(subContainer, item);
                        if (resultContainer != null)
                        {
                            return resultContainer;
                        }
                        else
                        {
                            // The object is not under this TreeViewItem
                            // so collapse it.
                            subContainer.IsExpanded = false;
                        }
                    }
                }
            }
    
            return null;
        }
    
        /// <summary>Search for an element of a certain type in the visual tree.</summary>
        /// <typeparam name="T">The type of element to find.</typeparam>
        /// <param name="visual">The parent element.</param>
        /// <returns></returns>
        private static T FindVisualChild<T>(Visual visual) where T : Visual
        {
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(visual); i++)
            {
                Visual child = (Visual)VisualTreeHelper.GetChild(visual, i);
                if (child != null)
                {
                    T correctlyTyped = child as T;
                    if (correctlyTyped != null)
                    {
                        return correctlyTyped;
                    }
    
                    T descendent = FindVisualChild<T>(child);
                    if (descendent != null)
                    {
                        return descendent;
                    }
                }
            }
            return null;
        }
    
        #endregion
    }
    

    And this is an example of how the TreeView line looks like in XAML:

    <TreeView x:Name="trvwSs"
              Grid.Column="2" Grid.Row="1" Margin="4" ItemsSource="{Binding ItemsTreeViewSs}"
              behaviors:TreeViewSelectedItemExBehavior.SelectedItemEx="{Binding SelectedItemTreeViewSs}" />
    

    Only thing to worry about is to make sure your view-model property that you about to bound to SelectedItemEx is not null. But that is not a special case.. Just mentioned it in case people get confused.

    public class VmMainContainer : INotifyPropertyChanged
    {
        private object selectedItemTreeViewSs = new object();
        private ObservableCollection<object> selectedItemsTreeViewSs = new ObservableCollection<object>();
        private ObservableCollection<VmItem> itemsTreeViewSs = new ObservableCollection<VmItem>();
    
     public object SelectedItemTreeViewSs
            {
                get
                {
                    return selectedItemTreeViewSs;
                }
                set
                {
                    selectedItemTreeViewSs = value;
                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedItemTreeViewSs)));
                }
            }
    
    public ObservableCollection<object> SelectedItemsTreeViewSs
            {
                get
                {
                    return selectedItemsTreeViewSs;
                }
                set
                {
                    selectedItemsTreeViewSs = value;
                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedItemsTreeViewSs)));
                }
            }
    
     public ObservableCollection<VmItem> ItemsTreeViewSs
            {
                get { return itemsTreeViewSs; }
                set
                {
                    itemsTreeViewSs = value;
                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ItemsTreeViewSs)));
                }
            }
        }
    

    And last thing.. example of selecting programmatically: I created a button on my MainWindow.xaml and from its handler..

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        TreeViewSelectedItemExBehavior.SetSelectedItemEx(trvwSs, trvwSs.Items[3]);
        //TreeViewSelectedItemExBehavior.SetSelectedItemEx(trvwSs, null);
    }
    

    Hope this helps someone :)

    0 讨论(0)
  • 2020-11-28 08:55

    I wrote an extension method:

    using System.Windows.Controls;
    
    namespace Extensions
    {
        public static class TreeViewEx
        {
            /// <summary>
            /// Select specified item in a TreeView
            /// </summary>
            public static void SelectItem(this TreeView treeView, object item)
            {
                var tvItem = treeView.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem;
                if (tvItem != null)
                {
                    tvItem.IsSelected = true;
                }
            }
        }
    }
    

    Which I can use like this:

    if (_items.Count > 0)
        _treeView.SelectItem(_items[0]);
    
    0 讨论(0)
  • 2020-11-28 08:56

    You can do it via code behind like

    if (TreeView1.Items.Count > 0)
            (TreeView1.Items[0] as TreeViewItem).IsSelected = true;
    
    0 讨论(0)
  • 2020-11-28 08:57

    This is not as simple as it looks, the link provided by Steven has a solution posted in 2008, which may still works but doesn't take care of Virtualized TreeViews. Moreover many other problems are mentioned in comments of that article. No offences, but I am also stuck with same problem and can't find a perfect solution. Here are the links to some of the articles/posts which helped me a lot-

    How can I expand items in a TreeView? – Part III: http://bea.stollnitz.com/blog/?p=59

    Programmatically Selecting an Item in a TreeView: http://blog.quantumbitdesigns.com/2008/07/22/programmatically-selecting-an-item-in-a-treeview/#respond

    TreeView, TreeViewItem and IsSelected: http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/7e368b93-f509-4cd6-88e7-561e8d3246ae/

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