有没有办法在虚拟化TreeView中手动选择一个节点,然后将其带到视图中?
我在TreeView中使用的数据模型是基于VM-M-V模型实现的。每个TreeViewItem的IsSelected属性都绑定到ViewModel中相应的属性。我还为TreeView的ItemSelected事件创建了一个侦听器,其中我为选定的TreeViewItem调用BringIntoView()。
这种方法的问题似乎是在创建实际的TreeViewItem之前不会引发ItemSelected事件。因此,在启用虚拟化的情况下,节点选择不会做任何事情,直到TreeView滚动足够多,然后当事件最终被引发时,它会“神奇地”跳到所选的节点。
我真的很想使用虚拟化,因为我的树中有数千个节点,而且在启用虚拟化后,我已经看到了令人印象深刻的性能改进。
发布于 2020-10-30 07:06:28
@splintor's excellent answer的更新,使用了一些现代的C#特性,并且没有任何反射。
public class Node
{
public Node Parent { get; set; }
}
public class NodeTreeSelectionBehavior : Behavior<TreeView>
{
public Node SelectedItem
{
get { return (Node)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register(
"SelectedItem",
typeof(Node),
typeof(NodeTreeSelectionBehavior),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedItemChanged));
private static void OnSelectedItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(e.NewValue is Node newNode)) return;
var treeView = ((NodeTreeSelectionBehavior)d).AssociatedObject;
var ancestors = new List<Node> { newNode };
var parent = newNode;
while ((parent = parent.Parent) != null)
{
ancestors.Insert(0, parent);
}
var currentParent = treeView as ItemsControl;
foreach (var node in ancestors)
{
// first try the easy way
var newParent = currentParent.ItemContainerGenerator.ContainerFromItem(node) as TreeViewItem;
if (newParent == null)
{
// if this failed, it's probably because of virtualization, and we will have to do it the hard way.
// see also the question at http://stackoverflow.com/q/183636/46635
var itemsPresenter = (ItemsPresenter)currentParent.Template.FindName("ItemsHost", currentParent);
var virtualizingPanel = (VirtualizingPanel)VisualTreeHelper.GetChild(itemsPresenter, 0);
var index = currentParent.Items.IndexOf(node);
if (index < 0)
{
throw new InvalidOperationException("Node '" + node + "' cannot be fount in container");
}
virtualizingPanel.BringIndexIntoViewPublic(index);
newParent = currentParent.ItemContainerGenerator.ContainerFromIndex(index) as TreeViewItem;
}
if (newParent == null)
{
throw new InvalidOperationException("Tree view item cannot be found or created for node '" + node + "'");
}
if (node == newNode)
{
newParent.IsSelected = true;
newParent.BringIntoView();
break;
}
newParent.IsExpanded = true;
currentParent = newParent;
}
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectedItemChanged += OnTreeViewSelectedItemChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.SelectedItemChanged -= OnTreeViewSelectedItemChanged;
}
private void OnTreeViewSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
SelectedItem = e.NewValue as Node;
}
}
以同样的方式使用:
<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:local="clr-namespace:MyProject">
<Grid>
<TreeView ItemsSource="{Binding MyItems}"
ScrollViewer.CanContentScroll="True"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling">
<i:Interaction.Behaviors>
<local:NodeTreeSelectionBehavior SelectedItem="{Binding MySelectedItem}" />
</i:Interaction.Behaviors>
</TreeView>
<Grid>
<UserControl>
https://stackoverflow.com/questions/183636
复制相似问题