如何以编程方式选择 WPF TreeView 中的项目?
Posted
技术标签:
【中文标题】如何以编程方式选择 WPF TreeView 中的项目?【英文标题】:How to programmatically select an item in a WPF TreeView? 【发布时间】:2010-09-29 15:47:27 【问题描述】:如何以编程方式选择 WPF TreeView
中的项目? ItemsControl
模型似乎可以阻止它。
【问题讨论】:
【参考方案1】:对于那些仍在寻找此问题的正确解决方案的人,请参阅以下内容。我在 DaWanderer 的代码项目文章“WPF TreeView Selection”http://www.codeproject.com/KB/WPF/TreeView_SelectionWPF.aspx 的 cmets 中找到了这个。 它由 Kenrae 于 2008 年 11 月 25 日发布。这对我来说非常有用。谢谢肯雷!
这是他的帖子:
让您自己的数据对象拥有 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/>
【讨论】:
有效!ItemContainerStyle="StaticResource LibraryTreeViewItemStyle"
对我不起作用;程序选择总是自动重置为之前选择的内容,除非之前没有选择任何内容。我用示例代码创建了一个related question。
没关系。我为我的收藏使用了 IEnumerable出于某种奇怪的原因,这真的很痛苦,您必须使用 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;
曾经有一篇关于如何做到这一点的博客文章here,但现在链接已失效。
【讨论】:
它对我不起作用。尽管 TreeView.HasItems 为真,但 ContainerFromItem 返回 null。就此而言,我还尝试了 ContainerFromIndex(0),它也返回了 null。 很遗憾,链接不见了 @SlapY 这是一个缓存副本:web.archive.org/web/20110624120453/http://askernest.com/archive/… 不,不是!查看 Kent Boogaart 的解决方案 这仅适用于一个级别并且令人困惑。看我的回答。这是真正的交易。【参考方案3】:您需要获取TreeViewItem
,然后将IsSelected
设置为true
。
【讨论】:
那么如何获取 TreeViewItem? 您可以使用 treeview.ItemContainerGenerator.ContainerFromItem(item) 获取 treeviewitem,其中 item 是 treeview.items 集合中的单个数据绑定对象。 是的,这对我有用 ((TreeViewItem)treeview_name.Items[0]).IsSelected = true; 如果您使用不使用 TreeViewItem 的自定义数据绑定上下文,这将不起作用【参考方案4】:我已成功使用此代码:
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;
【讨论】:
这应该是公认的答案。此代码还查找和选择子元素。 在我添加了一个空检查后,这对我有用:if (tvi2 != null) 围绕对 FindTviFromObjectRecursive 的递归调用和返回 tvi 的以下行。如果没有该检查,我不确定这是如何工作的,因为否则它会尝试在叶子上继续......【参考方案5】:这并不像看起来那么简单,Steven 提供的链接在 2008 年发布了一个解决方案,该解决方案可能仍然有效,但无法处理虚拟化 TreeViews。此外,该文章的 cmets 中还提到了许多其他问题。没有冒犯,但我也遇到了同样的问题,找不到完美的解决方案。以下是一些对我有很大帮助的文章/帖子的链接-
如何在 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/
【讨论】:
查看这篇文章,了解使用虚拟化和按需加载实现选择的问题 - ***.com/questions/3715583/… MSDN 文档“如何:在 TreeView 中查找 TreeViewItem”也指虚拟化 TreeView。 msdn.microsoft.com/en-us/library/ff407130.aspx#Y486 我发现您的第二个链接 (quantumbdesigns) 是正确的解决方案,因为它允许您从代表 MVVM 实例的 TreeView 中选择一个节点。【参考方案6】:我写了一个扩展方法:
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]);
【讨论】:
【参考方案7】:如果您想选择位于孩子的孩子的项目,您可以使用递归来做到这一点。
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;
【讨论】:
【参考方案8】:你可以通过后面的代码来做到这一点
if (TreeView1.Items.Count > 0)
(TreeView1.Items[0] as TreeViewItem).IsSelected = true;
【讨论】:
【参考方案9】:试试这个
/// <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
【讨论】:
这仅在 itemcollections 条目是“TreeViewItem”时才有效 - 如果使用数据绑定,“Items”的内容是数据绑定条目的类型。【参考方案10】:只是想我会加入我所采用的解决方案,以防万一这可以帮助任何人。请注意,执行此操作的最佳方法是根据 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 中选择并展开树视图项,它代表后面代码中选定的数据项。这样做的目的是当用户在同一窗口中的项目控件中更改选择时,在树视图中更改选择。
【讨论】:
【参考方案11】:我创建了一个方法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,它将无法工作,因为它取决于实际视觉元素的存在。但它适用于我的情况......
【讨论】:
【参考方案12】:建议的答案不起作用。 @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>());
【讨论】:
这仅适用于您的 ItemsSource 是 TreeViewItem 列表的情况。【参考方案13】:这是我的解决方案。其他人以各种方式对我来说失败了。您需要从上到下遍历树,在每个级别查找树项,沿途扩展和更新布局。
此函数获取一个节点堆栈,其中第一个出堆栈的节点是最顶部的节点,堆栈上的每个后续节点都是前一个父节点的子节点。第二个参数是 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();
【讨论】:
【参考方案14】:我为此编写了一个 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);
【讨论】:
【参考方案15】:是的..我知道这个问题已经过去很多年了,但是..仍然没有快速解决这个问题..所以:
以下将执行 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
这是 TreeView 行在 XAML 中的外观示例:
<TreeView x:Name="trvwSs"
Grid.Column="2" Grid.Row="1" Margin="4" ItemsSource="Binding ItemsTreeViewSs"
behaviors:TreeViewSelectedItemExBehavior.SelectedItemEx="Binding SelectedItemTreeViewSs" />
唯一需要担心的是确保要绑定到 SelectedItemEx 的视图模型属性不为空。但这不是特例。只是提到它以防人们感到困惑。
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);
希望这可以帮助某人:)
【讨论】:
它看起来像是一个可行的解决方案。我不喜欢它的是它是全球性的。这将导致内存泄漏,因为它存储了所有TreeView
s。此外,您永远不会从您订阅的事件中取消注册。如果元素被删除,您的行为中仍然会保留对它们的引用。一些不错的工作可以从中取出东西,但整体上是泄漏的。【参考方案16】:
我认为这是最简单的解决方案:
private void MouseDownEventProcessing(TreeNodeMouseClickEventArgs e)
tvEmployeeDirectory.SelectedNode = e.Node;
【讨论】:
以上是关于如何以编程方式选择 WPF TreeView 中的项目?的主要内容,如果未能解决你的问题,请参考以下文章
C#/WPF:如何以编程方式创建所需数量的文本块以显示表中的数据