WPF TreeViewItem

 

问题描述:

http://stackoverflow.com/questions/616948/how-to-get-treeviewitem-from-hierarchicaldatatemplate-item

找到比较靠谱的答案:

http://blogs.msdn.com/b/wpfsdk/archive/2010/02/23/finding-an-object-treeviewitem.aspx

 

The question often comes up, "how do I get to a certain TreeViewItem?" The question arises because a TreeView is bound to a data source, TreeViewItems are implicitly created and wrap the data object. However, the TreeView.SelectedItem property returns the data object, not the TreeViewItem. This is good when you need to access the data, but what if you need to manipulate the TreeViewItem? It has been suggested that if you design your data model correctly, you can avoid needing to manipulate TreeViewItems directly. While that is good general advice, there might be cases where manipulating the TreeViewItem is unavoidable.

Finding the TreeViewItem in a TreeView is more involved than finding item containers in other ItemControls (such as a ListBoxItem in a ListBox) because TreeViewItems are, naturally, nested. For example, to return the ListBoxItem of an object in a ListBox, you can callListBox.ItemContainerGenerator.GetContainerFromItem. But if you call GetContainerFromItem on a TreeView, the ItemContainerGenerator searches only the direct child objects of the TreeView. So you need to recursively traverse the TreeView and child TreeViewItem objects. A further complication is that if the TreeView virtualizes its items (you enable virtualization by setting theVirtualizingStackPanel.IsVirtualizing property to true), the child items need to be created before you can check its data object.

Given the complexity of the problem, I (with help from the development team) created a method that traverses the TreeView and realizes any virtualized items.

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

}

Note that this code looks for a Panel called, MyVirtualizingStackPanel. This new type exposes a method that allows you to bring an item into view by passing in the item's index.

public class MyVirtualizingStackPanel : VirtualizingStackPanel

{

/// <summary>

/// Publically expose BringIndexIntoView.

/// </summary>

public void BringIntoView(int index)

{

this.BringIndexIntoView(index);

}

}

The final step is to use the custom VirtualizingStackPanel as the TreeView's ItemsPanel:

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

Now that you have the code, you can search for any object in any TreeView, regardless of its depth in the tree.

I attached a sample that uses this technique to find any item in the TreeView. The sample asks for a number, finds the corresponding data object in the data model, and then selects the TreeViewItem that contains the object. Note that the way I find the data object in the model is specific to the organization of my data. I could have kept track of the data object's location within the data hierarchy, and then find the corresponding TreeViewItem in the TreeView.

For example, the item that corresponds to 41 is, starting from the root of the TreeView, under the second item, then under the first item, then under the second item, and finally the first item. I could have kept track of its location with a list of indices, 2,1,2,1, and then used those to navigate the TreeView. However, this approach is dependent on the organization of the underlying TreeView. The implementation I show above works on any TreeView and it doesn't require knowledge of the data model.

The attached sample has C# and Visual Basic versions.

源代码

Silverligh中目前仍有问题:

TreeViewItem中不包含:BringIntoView 方法

ItemsPresenter中不包含:ApplyTemplate 方法

ControlTemplate中不包含:FindName 方法

Silverlight中的问题仍然没有解决!目前知道一种方法可以定位到节点,但是这种方法在实际的项目中可能并不使用。

1、先将第一层的节点的IsExpanded设置为true

2、递归的方式将节点的IsExpanded设置为true,然后就可以通过遍历DataContext的属性进行访问。

 结果最好的方法是

I ran into this same problem. I needed to get to the TreeViewItem so that I could have it be selected. I then realized that I could just add a property IsSelected to my ViewModel, which I then bound to the TreeViewItems IsSelectedProperty. This can be achieved with the ItemContainerStyle:

<TreeView><TreeView.ItemContainerStyle><StyleTargetType="{x:Type TreeViewItem}"><Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/><Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/></Style></TreeView.ItemContainerStyle></TreeView>

Now if I want to have an item in the treeview selected, I just call IsSelected on my ViewModel class directly.

Hope it helps someone.

Done.

 

posted @ 2013-06-13 14:23  小白快跑  阅读(1015)  评论(1编辑  收藏  举报