Практическое руководство. Поиск элемента TreeViewItem в TreeView
Элемент управления TreeView позволяет отображать иерархические данные. Если элемент TreeView привязан к источнику данных, с помощью свойства SelectedItem можно быстро получить выбранный объект данных. Обычно лучше работать с базовым объектом данных, но иногда может потребоваться программно управлять объектом TreeViewItem, в котором содержатся данные. Например, может программно развернуть элемент TreeViewItem или выбрать другой элемент в TreeView.
Чтобы найти элемент TreeViewItem, в котором содержится заданный объект данных, необходимо просмотреть каждый уровень TreeView. Элементы в TreeView могут виртуализироваться для повышения производительности. Если в определенном случае могут присутствовать виртуализированные элементы, необходимо реализовать TreeViewItem и проверить, содержится ли в нем объект данных.
Пример
Description
В следующем примере в TreeView выполняется поиск объекта и возвращается элемент TreeViewItem, в котором содержится этот объект. В примере в обязательном порядке создается экземпляр каждого объекта TreeViewItem, чтобы можно было выполнять поиск в их дочерних элементах. Этот пример работает, даже если в TreeView не используются виртуализированные элементы.
Примечание.
Следующий пример работает для любого элемента управления TreeView, независимо от базовой модели данных. В нем проверяется каждый элемент TreeViewItem, пока не будет найден объект. Более эффективный метод — найти нужный объект в модели данных, отметить его расположение в иерархии данных, а затем найти соответствующий элемент TreeViewItem в TreeView. Однако для этого метода нужно знать модель данных, и его нельзя обобщить для любого элемента TreeView.
Код
/// <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 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 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;
}
''' <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 Function GetTreeViewItem(ByVal container As ItemsControl,
ByVal item As Object) As TreeViewItem
If container IsNot Nothing Then
If container.DataContext Is item Then
Return TryCast(container, TreeViewItem)
End If
' Expand the current container
If TypeOf container Is TreeViewItem AndAlso
Not DirectCast(container, TreeViewItem).IsExpanded Then
container.SetValue(TreeViewItem.IsExpandedProperty, True)
End If
' Try to generate the ItemsPresenter and the ItemsPanel.
' by calling ApplyTemplate. Note that in the
' virtualizing case, even if IsExpanded = true,
' we still need to do this step in order to
' regenerate the visuals because they may have been virtualized away.
container.ApplyTemplate()
Dim itemsPresenter As ItemsPresenter =
DirectCast(container.Template.FindName("ItemsHost", container), ItemsPresenter)
If itemsPresenter IsNot Nothing Then
itemsPresenter.ApplyTemplate()
Else
' The Tree template has not named the ItemsPresenter,
' so walk the descendents and find the child.
itemsPresenter = FindVisualChild(Of ItemsPresenter)(container)
If itemsPresenter Is Nothing Then
container.UpdateLayout()
itemsPresenter = FindVisualChild(Of ItemsPresenter)(container)
End If
End If
Dim itemsHostPanel As Panel =
DirectCast(VisualTreeHelper.GetChild(itemsPresenter, 0), Panel)
' Do this to ensure that the generator for this panel has been created.
Dim children As UIElementCollection = itemsHostPanel.Children
Dim virtualizingPanel As MyVirtualizingStackPanel =
TryCast(itemsHostPanel, MyVirtualizingStackPanel)
For index As Integer = 0 To container.Items.Count - 1
Dim subContainer As TreeViewItem
If virtualizingPanel IsNot Nothing Then
' Bring the item into view so
' that the container will be generated.
virtualizingPanel.BringIntoView(index)
subContainer =
DirectCast(container.ItemContainerGenerator.ContainerFromIndex(index),
TreeViewItem)
Else
subContainer =
DirectCast(container.ItemContainerGenerator.ContainerFromIndex(index),
TreeViewItem)
' Bring the item into view to maintain the
' same behavior as with a virtualizing panel.
subContainer.BringIntoView()
End If
If subContainer IsNot Nothing Then
' Search the next level for the object.
Dim resultContainer As TreeViewItem =
GetTreeViewItem(subContainer, item)
If resultContainer IsNot Nothing Then
Return resultContainer
Else
' The object is not under this TreeViewItem
' so collapse it.
subContainer.IsExpanded = False
End If
End If
Next
End If
Return Nothing
End Function
''' <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 Function FindVisualChild(Of T As Visual)(ByVal visual As Visual) As T
For i As Integer = 0 To VisualTreeHelper.GetChildrenCount(visual) - 1
Dim child As Visual = DirectCast(VisualTreeHelper.GetChild(visual, i), Visual)
If child IsNot Nothing Then
Dim correctlyTyped As T = TryCast(child, T)
If correctlyTyped IsNot Nothing Then
Return correctlyTyped
End If
Dim descendent As T = FindVisualChild(Of T)(child)
If descendent IsNot Nothing Then
Return descendent
End If
End If
Next
Return Nothing
End Function
В предыдущем коде применяется пользовательский объект VirtualizingStackPanel, который предоставляет метод BringIntoView
. Этот пользовательский объект VirtualizingStackPanel определяется в коде ниже.
public class MyVirtualizingStackPanel : VirtualizingStackPanel
{
/// <summary>
/// Publically expose BringIndexIntoView.
/// </summary>
public void BringIntoView(int index)
{
this.BringIndexIntoView(index);
}
}
Public Class MyVirtualizingStackPanel
Inherits VirtualizingStackPanel
''' <summary>
''' Publically expose BringIndexIntoView.
''' </summary>
Public Overloads Sub BringIntoView(ByVal index As Integer)
Me.BringIndexIntoView(index)
End Sub
End Class
В следующем коде XAML показано, как создать элемент TreeView, в котором используется пользовательский объект VirtualizingStackPanel.
<TreeView VirtualizingStackPanel.IsVirtualizing="True">
<!--Use the custom class MyVirtualizingStackPanel
as the ItemsPanel for the TreeView and
TreeViewItem object.-->
<TreeView.ItemsPanel>
<ItemsPanelTemplate>
<src:MyVirtualizingStackPanel/>
</ItemsPanelTemplate>
</TreeView.ItemsPanel>
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<src:MyVirtualizingStackPanel/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
См. также
.NET Desktop feedback