问题描述
我有一个数据绑定的 TreeView
,我想绑定 SelectedItem
.此附加行为无需HierarchicalDataTemplate
即可完美运行,但使用它附加行为只能以一种方式起作用(UI数据)而不是另一个,因为现在 e.NewValue
是 MyViewModel
而不是 TreeViewItem
.
I have a data-bound TreeView
and I want to bind SelectedItem
. This attached behavior works perfectly without HierarchicalDataTemplate
but with it the attached behavior only works one way (UI to data) not the other because now e.NewValue
is MyViewModel
not TreeViewItem
.
这是附加行为的代码片段:
This is a code snippet from the attached behavior:
private static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var item = e.NewValue as TreeViewItem;
if (item != null)
{
item.SetValue(TreeViewItem.IsSelectedProperty, true);
}
}
这是我的TreeView
定义:
<Window xmlns:interactivity="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity">
<TreeView ItemsSource="{Binding MyItems}" VirtualizingStackPanel.IsVirtualizing="True">
<interactivity:Interaction.Behaviors>
<behaviors:TreeViewSelectedItemBindingBehavior SelectedItem="{Binding SelectedItem, Mode=TwoWay}" />
</interactivity:Interaction.Behaviors>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:MyViewModel}" ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</Window>
如果我可以在附加的行为方法 OnSelectedItemChanged
中获得对 TreeView
的引用,也许我可以使用 这个问题 获得 TreeViewItem
但我不知道如何到达那里.有谁知道如何以及这是正确的方法吗?
If I can get a reference to the TreeView
in the attached behavior method OnSelectedItemChanged
, maybe I can use the answers in this question to get the TreeViewItem
but I don't know how to get there. Does anyone know how and is it the right way to go?
推荐答案
这是上述附加行为的改进版本.它完全支持双向绑定,还可以与 HeriarchicalDataTemplate
和 TreeView
一起使用,其中的项目是虚拟的.请注意,虽然要找到需要选择的TreeViewItem",但它会实现(即创建)虚拟化的 TreeViewItem
,直到找到正确的TreeViewItem
.这可能是大型虚拟化树的潜在性能问题.
Here is an improved version of the above mentioned attached behavior. It fully supports twoway binding and also works with HeriarchicalDataTemplate
and TreeView
s where its items are virtualized. Please note though that to find the 'TreeViewItem' that needs to be selected, it will realize (i.e. create) the virtualized TreeViewItem
s until it finds the right one. This could potentially be a performance problem with big virtualized trees.
/// <summary>
/// Behavior that makes the <see cref="System.Windows.Controls.TreeView.SelectedItem" /> bindable.
/// </summary>
public class BindableSelectedItemBehavior : Behavior<TreeView>
{
/// <summary>
/// Identifies the <see cref="SelectedItem" /> dependency property.
/// </summary>
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register(
"SelectedItem",
typeof(object),
typeof(BindableSelectedItemBehavior),
new UIPropertyMetadata(null, OnSelectedItemChanged));
/// <summary>
/// Gets or sets the selected item of the <see cref="TreeView" /> that this behavior is attached
/// to.
/// </summary>
public object SelectedItem
{
get
{
return this.GetValue(SelectedItemProperty);
}
set
{
this.SetValue(SelectedItemProperty, value);
}
}
/// <summary>
/// Called after the behavior is attached to an AssociatedObject.
/// </summary>
/// <remarks>
/// Override this to hook up functionality to the AssociatedObject.
/// </remarks>
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.SelectedItemChanged += this.OnTreeViewSelectedItemChanged;
}
/// <summary>
/// Called when the behavior is being detached from its AssociatedObject, but before it has
/// actually occurred.
/// </summary>
/// <remarks>
/// Override this to unhook functionality from the AssociatedObject.
/// </remarks>
protected override void OnDetaching()
{
base.OnDetaching();
if (this.AssociatedObject != null)
{
this.AssociatedObject.SelectedItemChanged -= this.OnTreeViewSelectedItemChanged;
}
}
private static Action<int> GetBringIndexIntoView(Panel itemsHostPanel)
{
var virtualizingPanel = itemsHostPanel as VirtualizingStackPanel;
if (virtualizingPanel == null)
{
return null;
}
var method = virtualizingPanel.GetType().GetMethod(
"BringIndexIntoView",
BindingFlags.Instance | BindingFlags.NonPublic,
Type.DefaultBinder,
new[] { typeof(int) },
null);
if (method == null)
{
return null;
}
return i => method.Invoke(virtualizingPanel, new object[] { i });
}
/// <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();
var 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 = container.GetVisualDescendant<ItemsPresenter>();
if (itemsPresenter == null)
{
container.UpdateLayout();
itemsPresenter = container.GetVisualDescendant<ItemsPresenter>();
}
}
var itemsHostPanel = (Panel)VisualTreeHelper.GetChild(itemsPresenter, 0);
// Ensure that the generator for this panel has been created.
#pragma warning disable 168
var children = itemsHostPanel.Children;
#pragma warning restore 168
var bringIndexIntoView = GetBringIndexIntoView(itemsHostPanel);
for (int i = 0, count = container.Items.Count; i < count; i++)
{
TreeViewItem subContainer;
if (bringIndexIntoView != null)
{
// Bring the item into view so
// that the container will be generated.
bringIndexIntoView(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)
{
continue;
}
// Search the next level for the object.
var resultContainer = GetTreeViewItem(subContainer, item);
if (resultContainer != null)
{
return resultContainer;
}
// The object is not under this TreeViewItem
// so collapse it.
subContainer.IsExpanded = false;
}
}
return null;
}
private static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var item = e.NewValue as TreeViewItem;
if (item != null)
{
item.SetValue(TreeViewItem.IsSelectedProperty, true);
return;
}
var behavior = (BindableSelectedItemBehavior)sender;
var treeView = behavior.AssociatedObject;
if (treeView == null)
{
// at designtime the AssociatedObject sometimes seems to be null
return;
}
item = GetTreeViewItem(treeView, e.NewValue);
if (item != null)
{
item.IsSelected = true;
}
}
private void OnTreeViewSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
this.SelectedItem = e.NewValue;
}
}
为了完整起见,GetVisualDescentants
的实现是:
And for the sake of completeness hier is the implementation of GetVisualDescentants
:
/// <summary>
/// Extension methods for the <see cref="DependencyObject" /> type.
/// </summary>
public static class DependencyObjectExtensions
{
/// <summary>
/// Gets the first child of the specified visual that is of tyoe <typeparamref name="T" />
/// in the visual tree recursively.
/// </summary>
/// <param name="visual">The visual to get the visual children for.</param>
/// <returns>
/// The first child of the specified visual that is of tyoe <typeparamref name="T" /> of the
/// specified visual in the visual tree recursively or <c>null</c> if none was found.
/// </returns>
public static T GetVisualDescendant<T>(this DependencyObject visual) where T : DependencyObject
{
return (T)visual.GetVisualDescendants().FirstOrDefault(d => d is T);
}
/// <summary>
/// Gets all children of the specified visual in the visual tree recursively.
/// </summary>
/// <param name="visual">The visual to get the visual children for.</param>
/// <returns>All children of the specified visual in the visual tree recursively.</returns>
public static IEnumerable<DependencyObject> GetVisualDescendants(this DependencyObject visual)
{
if (visual == null)
{
yield break;
}
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(visual); i++)
{
var child = VisualTreeHelper.GetChild(visual, i);
yield return child;
foreach (var subChild in GetVisualDescendants(child))
{
yield return subChild;
}
}
}
}
这篇关于在 HierarchicalDataTemplate 应用的 WPF TreeView 中绑定 SelectedItem的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!