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

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

44

对于那些仍在寻找解决此问题的正确方法的人,以下是一个可行方案。我在“WPF TreeView Selection”文章(http://www.codeproject.com/KB/WPF/TreeView_SelectionWPF.aspx)的评论中找到了这个方案,作者是DaWanderer。该方案由Kenrae于2008年11月25日发布,对我非常有效。谢谢Kenrae!

以下是他的帖子:

不要遍历整个树,而是让您自己的数据对象具有“IsSelected”属性(我建议也要有“IsExpanded”属性)。使用TreeView的ItemContainerStyle属性为树的TreeViewItems定义样式,将这些属性从TreeViewItem绑定到您的数据对象。类似这样:

<Style x:Key="LibraryTreeViewItemStyle"
               TargetType="{x:Type TreeViewItem}">
            <Setter Property="IsExpanded"
                        Value="{Binding IsExpanded, Mode=TwoWay}" />
            <Setter Property="IsSelected"
                        Value="{Binding IsSelected, Mode=TwoWay}" />
            <Setter Property="FontWeight"
                        Value="Normal" />
            <Style.Triggers>
                  <Trigger Property="IsSelected"
                              Value="True">
                        <Setter Property="FontWeight"
                                    Value="Bold" />
                  </Trigger>
            </Style.Triggers>
      </Style>

<TreeView ItemsSource="{Binding Path=YourCollection}"
               ItemContainerStyle="{StaticResource LibraryTreeViewItemStyle}"
               ItemTemplate={StaticResource YourHierarchicalDataTemplate}/>

5
对我来说不起作用;编程选择总是自动重置为之前选中的内容,除非之前没有选中任何内容。我已经创建了一个相关问题(https://dev59.com/SGvXa4cB1Zd3GeqPNNek)并提供了示例代码。 - O. R. Mapper
对我不起作用;只有我设置为IsExpanded = true的第一个项目被展开,我尝试展开的所有其他项目都不会展开,也没有任何项目被选中。 - Nick
1
没关系。我之前使用的是IEnumerable<T>,现在改成了ObservableCollection<T>,现在可以工作了。 - Nick
3
这种方法并不总是有效。TreeView.SelectedItem 会保留旧的值,在某些情况下(如多选场景)可能会导致奇怪的错误。 - Pavel Tupitsyn
6
表现很好,以下是一些额外的说明:该模型需要实现INotifyPropertyChanged接口,并触发IsSelected和IsExpanded属性的PropertyChangedEvent。此外,如果您想展开一个项目,则还需要展开其所有祖先项目,因此使模型具有父级关系也是有意义的。 - Nikolaos Georgiou
显示剩余6条评论

33

因为某种奇怪的原因,这真是一件令人头痛的事情,你必须使用ContainerFromItem来获取容器,然后调用select方法。

//  selectedItemObject is not a TreeViewItem, but an item from the collection that 
//  populated the TreeView.

var tvi = treeView.ItemContainerGenerator.ContainerFromItem(selectedItemObject) 
          as TreeViewItem;

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

以前有一篇博客文章介绍如何做这个,在这里,但是链接现在已经失效了。


7
对我来说没用。 即使 TreeView.HasItems 为 true,ContainerFromItem 返回 null。我也尝试了 ContainerFromIndex(0),同样返回 null。 - Shimmy Weitzhandler
3
@SlapY 这是一个缓存副本链接:https://web.archive.org/web/20110624120453/http://askernest.com/archive/2008/01/23/how-to-programmatically-change-the-selecteditem-in-a-wpf-treeview.aspx。本文介绍如何在WPF TreeView中编程更改所选项的方法。 - Zaaier
不是这样的!请查看Kent Boogaart的解决方案。 - Anytoe
2
这只适用于一个级别,而且很令人困惑。看看我的答案吧,它才是真正的解决方案。 - Fandi Susanto
这里是适合我的一个解决方案:https://blogs.msdn.microsoft.com/hulyap/2008/07/06/setting-treeview-selecteditem-in-code/ - Dave
显示剩余3条评论

22
您需要获取 TreeViewItem 并将 IsSelected 设置为 true

14
我该如何获取TreeViewItem? - Shimmy Weitzhandler
2
你可以使用 treeview.ItemContainerGenerator.ContainerFromItem(item) 来获取 TreeViewItem,其中 item 是树视图项集合中的单个数据绑定对象。 - RobJohnson
1
是的,这就是对我有效的方法: ((TreeViewItem){treeview_name}.Items[0]).IsSelected = true; - Yash Gadhiya
2
如果您使用不使用TreeViewItem的自定义数据绑定上下文,则此方法无法正常工作。 - Michael Brown

13

我已经成功运行了这段代码:

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

使用方法:

var tvi = FindTviFromObjectRecursive(TheTreeView, TheModel);
if (tvi != null) tvi.IsSelected = true;

4
这应该是被采纳的答案。这段代码还可以找到并选中子元素。 - uceumern
1
在我添加了一个空值检查之后,这个方法对我起作用了:if (tvi2 != null) ,它包围了对FindTviFromObjectRecursive的递归调用和接下来返回tvi的代码行。如果没有这个检查,我不确定它是如何工作的,因为否则它会试图继续处理叶节点... - Allen Pestaluky

5
这并不像看起来那么简单,Steven提供的链接中有一个2008年发布的解决方案,该方案可能仍然有效,但没有考虑到虚拟化树视图。此外,该文章的评论中提到了许多其他问题。请不要介意,我也遇到了同样的问题,找不到完美的解决方案。以下是一些帮助我的文章/帖子链接-

如何展开TreeView中的项?-第三部分: http://bea.stollnitz.com/blog/?p=59

在TreeView中以编程方式选择项: http://blog.quantumbitdesigns.com/2008/07/22/programmatically-selecting-an-item-in-a-treeview/#respond

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


查看此帖子以了解使用虚拟化和按需加载实现选择的问题 - https://dev59.com/aVDTa4cB1Zd3GeqPJXvC - akjoshi
MSDN文档"How to:在TreeView中查找TreeViewItem"也涉及虚拟化的TreeView。http://msdn.microsoft.com/en-us/library/ff407130.aspx#Y486 - Omer Raviv
2
我认为你提供的第二个链接(quantumbdesigns)是正确的解决方案,因为它允许您从代表MVVM实例的TreeView中选择一个节点。 - JoanComasFdz

3

我写了一个扩展方法:

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

我可以像这样使用它:

if (_items.Count > 0)
    _treeView.SelectItem(_items[0]);

1
你可以通过代码后台来完成,例如:
if (TreeView1.Items.Count > 0)
        (TreeView1.Items[0] as TreeViewItem).IsSelected = true;

1
提出的答案不起作用。@fandisusanto的答案可能有效,但可以更简单。这是我能想到的最简单的答案:
    private static void DeselectTreeViewItem(IEnumerable<TreeViewItem> treeViewItems)
    {
        foreach (var treeViewItem in treeViewItems)
        {
            if (treeViewItem.IsSelected)
            {
                treeViewItem.IsSelected = false;
                return;
            }

            DeselectTreeViewItem(treeViewItem.Items.Cast<TreeViewItem>());
        }
    }

使用方法:

    private void ClearSelectedItem()
    {
        if (AssetTreeView.SelectedItem != null)
        {
            DeselectTreeViewItem(AssetTreeView.Items.Cast<TreeViewItem>());
        }
    }

1
只有在ItemsSource是TreeViewItem列表的情况下才能起作用。 - The Pademelon

1
如果您想选择嵌套在子元素中的项目,可以使用递归来实现。
public bool Select(TreeViewItem item, object select) // recursive function to set item selection in treeview
{
    if (item == null)
        return false;
    TreeViewItem child = item.ItemContainerGenerator.ContainerFromItem(select) as TreeViewItem;
    if (child != null)
    {
        child.IsSelected = true;
        return true;
    }
    foreach (object c in item.Items)
    {
        bool result = Select(item.ItemContainerGenerator.ContainerFromItem(c) as TreeViewItem, select);
        if (result == true)
            return true;
    }
    return false;
}

1
我创建了一个方法VisualTreeExt.GetDescendants<T>,它返回一个可枚举的元素集合,这些元素与指定类型匹配:
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;
    }
  }
}

当你请求 VisualTreeHelperExt.GetDescendants<TreeViewItem>(MyAmazingTreeView) 时,你将获得所有的 TreeViewItem 子项。你可以使用下面的代码选择特定的值:
var treeViewItem = VisualTreeExt.GetDescendants<TreeViewItem>(MyTreeView).FirstOrDefault(tvi => tvi.DataContext == newValue);
if (treeViewItem != null)
  treeViewItem.IsSelected = true;

这个方法有点“脏”(可能不是最有效的),如果您使用虚拟化TreeView,则无法使用它,因为它依赖于实际可视元素的存在。但对于我的情况有效...


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