如何以编程方式在WPF TreeView中选择项目?

61
如何通过编程方式在 WPF TreeView 中选择项目?ItemsControl 模型似乎会阻止此操作。
16个回答

0

试试这个

    /// <summary>
    /// Selects the tree view item.
    /// </summary>
    /// <param name="Collection">The collection.</param>
    /// <param name="Value">The value.</param>
    /// <returns></returns>
    private TreeViewItem SelectTreeViewItem(ItemCollection Collection, String Value)
    {
        if (Collection == null) return null;
        foreach(TreeViewItem Item in Collection)
        {
            /// Find in current
            if (Item.Header.Equals(Value))
            {
                Item.IsSelected = true;
                return Item;
            }
            /// Find in Childs
            if (Item.Items != null)
            {
                TreeViewItem childItem = this.SelectTreeViewItem(Item.Items, Value);
                if (childItem != null)
                {
                    Item.IsExpanded = true;
                    return childItem;
                }
            }
        }
        return null;
    }

参考资料: http://amastaneh.blogspot.com/2011/06/wpf-selectedvalue-for-treeview.html


1
只有当itemcollections的条目是“TreeViewItem”时,才能起作用 - 如果使用数据绑定,那么“Items”的内容就是数据绑定条目的类型。 - lunatix

0

是的..我知道自从问题被提出已经过去了很多年,但是..仍然没有快速解决这个问题的方法..所以:

以下将会做到OP所要求的。

我基本上所做的就是阅读了此页面中所有答案并跟随所有相关链接来创建一个一劳永逸的解决方案来解决这个恼人的问题。

好处:

  • 它也支持虚拟化TreeView。
  • 它使用行为技术,因此XAML更容易。
  • 添加了一个依赖属性,允许绑定到选定的TreeView项。

这部分是您需要复制的唯一代码,其他部分只是帮助完成示例。

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
}

这是一个在XAML中TreeView外观的例子:

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

唯一需要担心的是确保您要绑定到SelectedItemEx的视图模型属性不为null。但这并不是一个特殊情况...只是提到以防有人感到困惑。

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

最后一件事...编程选择的示例: 我在MainWindow.xaml上创建了一个按钮,并从其处理程序中...

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

希望这能帮助到某个人 :)

1
看起来这是一个可行的解决方案。我不喜欢它的原因是它是全局的。这会导致内存泄漏,因为它存储了所有的 TreeView。此外,您从订阅的事件中永远不会注销。如果元素被删除,您的行为仍将保留对它们的引用。有一些不错的工作可以拿出来,但整体上是有漏洞的。 - Shimmy Weitzhandler

0
只是想分享一下我采用的解决方案,以防有人需要。请注意,最好的方法是像kuninl的答案那样使用绑定属性,比如“IsSelected”,但在我的情况下,这是一个遗留应用程序,没有遵循MVVM,所以我最终采用了以下方法。
private void ChangeSessionSelection()
{
    foreach (SessionContainer item in this.treeActiveSessions.Items)
    {
        var treeviewItem = this.treeActiveSessions.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem;

        if (item.Session == this.selectedSession.Session)
        {
            treeviewItem.IsSelected = true;
            treeviewItem.IsExpanded = true;
        }
        else
        {
            treeviewItem.IsSelected = false;
            treeviewItem.IsExpanded = false;
        }
    }            
}

这段代码的作用是在UI中选择并展开表示代码后台中所选数据项的树形视图项目。其目的是在同一窗口中的项控件中用户选择更改时,使树形视图中的选择也随之更改。

0
我为此编写了一个Helper类,支持MVVM和延迟加载项。
public class TreeViewHelper<TModel>
{
    public TreeViewHelper(TreeView treeView, Func<TModel, TModel> getParent, Func<TModel, IList<TModel>> getSubItems)
    {
        TreeView = treeView;
        GetParent = getParent;
        GetSubItems = getSubItems;
    }

    public TreeView TreeView { get; }
    public Func<TModel, TModel> GetParent { get; }
    public Func<TModel, IList<TModel>> GetSubItems { get; }

    public void SelectItemWhileLoaded(TModel node, IList<TModel> rootNodes)
    {
        if (TreeView.IsLoaded)
        {
            SelectItem(node, rootNodes);
        }
        else
        {
            TreeView.Loaded += TreeView_Loaded;
            void TreeView_Loaded(object sender, System.Windows.RoutedEventArgs e)
            {
                TreeView.Loaded -= TreeView_Loaded;
                SelectItem(node, rootNodes);
            }
        }
    }


    public void SelectItem(TModel node, IList<TModel> rootNodes)
    {
        Stack<TModel> nodes = new Stack<TModel>();
        //push into stack
        while (!rootNodes.Contains(node))
        {
            nodes.Push(node);
            node = GetParent(node);
        }
        TreeViewItem treeViewItem = TreeView.ItemContainerGenerator
            .ContainerFromItem(node) as TreeViewItem;
        if (nodes.Count == 0)
        {
            //Top level
            treeViewItem.IsSelected = true;
            treeViewItem.BringIntoView();
            return;
        }
        Expanded(true);
        void Expanded(bool top)
        {
            if (!top)
            {
                treeViewItem = treeViewItem.ItemContainerGenerator
                    .ContainerFromItem(node) as TreeViewItem;
                if (nodes.Count == 0)
                {
                    treeViewItem.IsSelected = true;
                    treeViewItem.BringIntoView();
                    return;
                }
            }
            node = nodes.Pop();
            treeViewItem.IsExpanded = true;
            if (treeViewItem.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
            {
                Expanded(true);
            }
            else
            {
                //Lazy
                treeViewItem.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged;
            }
        }
        void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
        {
            if (treeViewItem.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
            {
                treeViewItem.ItemContainerGenerator.StatusChanged -= ItemContainerGenerator_StatusChanged;
                Expanded(false);
            }
        }
    }
}

0
这是我的解决方案。其他方法都以各种方式失败了。你需要从顶部到底部遍历树,每一层找到树的项并在途中扩展和更新布局。
这个函数接受一个节点堆栈,其中堆栈中最先出来的是最顶层的节点,堆栈中的每个后续节点都是前一个父节点的子节点。第二个参数是TreeView。
当找到每个项时,该项会被展开,并返回最终项,调用者可以选择它。
    TreeViewItem FindTreeViewItem( Stack<object> nodeStack, TreeView treeView )
    {
        ItemsControl itemsControl = treeView;

        while (nodeStack.Count > 0) {
            object node = nodeStack.Pop();
            bool found = false;

            foreach (object item in itemsControl.Items) {
                if (item == node) {
                    found = true;

                    if (itemsControl.ItemContainerGenerator.ContainerFromItem( item ) is TreeViewItem treeViewItem) {
                        if (nodeStack.Count == 0) {
                            return treeViewItem;
                        }

                        itemsControl = treeViewItem;
                        treeViewItem.IsExpanded = true;
                        treeViewItem.UpdateLayout();
                        break;
                    }
                }
            }

            if (!found) {
                return null;
            }
        }

        return null;
    }

调用示例:

    // Build nodeStack here from your data

    TreeViewItem treeViewItem = FindTreeViewItem( nodeStack, treeView );

    if (treeViewItem != null) {
        treeViewItem.IsSelected = true;
        treeViewItem.BringIntoView();
    }

-1

我认为这是最简单的解决方案:

private void MouseDownEventProcessing(TreeNodeMouseClickEventArgs e)
{
    tvEmployeeDirectory.SelectedNode = e.Node;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接