我最近一直在使用WPF树视图,当用户使用在后备对象上设置IsSelected属性的搜索功能时,我正试图让所选项目显示在屏幕上时非常糟糕.
目前我的方法是使用这个答案中的方法:https://*.com/a/34620549/800318
private void FocusTreeViewNode(TreeViewEntry node)
{
if (node == null) return;
var nodes = (IEnumerable<TreeViewEntry>)LeftSide_TreeView.ItemsSource;
if (nodes == null) return;
var stack = new Stack<TreeViewEntry>();
stack.Push(node);
var parent = node.Parent;
while (parent != null)
{
stack.Push(parent);
parent = parent.Parent;
}
var generator = LeftSide_TreeView.ItemContainerGenerator;
while (stack.Count > 0)
{
var dequeue = stack.Pop();
LeftSide_TreeView.UpdateLayout();
var treeViewItem = (TreeViewItem)generator.ContainerFromItem(dequeue);
if (stack.Count > 0)
{
treeViewItem.IsExpanded = true;
}
else
{
if (treeViewItem == null)
{
//This is being triggered when it shouldn't be
Debugger.Break();
}
treeViewItem.IsSelected = true;
}
treeViewItem.BringIntoView();
generator = treeViewItem.ItemContainerGenerator;
}
}
TreeViewEntry是我的后备数据类型,它具有对其父节点的引用. Leftside_TreeView是绑定到我的对象列表的虚拟化TreeView.关闭虚拟化不是一种选择,因为性能非常糟糕.
当我搜索一个对象并找到后备数据对象时,我将该对象作为其参数调用此FocusTreeViewNode()方法.它通常在第一次调用时工作,选择对象并将其带入视图.
在第二次执行搜索时,将传入要选择的节点,但是当清空堆栈时,ContainerFromItem()调用(因此它尝试为对象本身生成容器)返回null.当我调试这个时,我可以在ContainerGenerator的项目列表中看到我正在搜索的对象,但由于某种原因它没有被返回.我查看了所有与UpdateLayout()和其他事情有关的事情,但我无法弄清楚这一点.
即使在将父节点带入视图之后,容器中的一些对象也可能在页面外 – 例如,扩展器下面有250个项目,并且每次只渲染60个.这可能是个问题吗?
更新
这是一个示例项目,它使虚拟化树视图显示此问题. https://github.com/Mgamerz/TreeViewVirtualizingErrorDemo
在VS中构建它,然后在搜索框中输入类似于4.按几次搜索,它会抛出一个异常,说容器为空,即使你打开生成器对象,你也可以清楚地看到它在生成器中.
解决方法:
与WPF开发的许多其他方面一样,可以使用MVVM设计模式来处理此操作.
创建一个ViewModel类,包括一个IsSelected属性,该属性保存每个树项的数据.
然后,可以通过附加属性处理将所选项目置于视图中
public static class perTreeViewItemHelper
{
public static bool GetBringSelectedItemIntoView(TreeViewItem treeViewItem)
{
return (bool)treeViewItem.GetValue(BringSelectedItemIntoViewProperty);
}
public static void SetBringSelectedItemIntoView(TreeViewItem treeViewItem, bool value)
{
treeViewItem.SetValue(BringSelectedItemIntoViewProperty, value);
}
public static readonly DependencyProperty BringSelectedItemIntoViewProperty =
DependencyProperty.RegisterAttached(
"BringSelectedItemIntoView",
typeof(bool),
typeof(perTreeViewItemHelper),
new UIPropertyMetadata(false, BringSelectedItemIntoViewChanged));
private static void BringSelectedItemIntoViewChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (!(args.NewValue is bool))
return;
var item = obj as TreeViewItem;
if (item == null)
return;
if ((bool)args.NewValue)
item.Selected += OnTreeViewItemSelected;
else
item.Selected -= OnTreeViewItemSelected;
}
private static void OnTreeViewItemSelected(object sender, RoutedEventArgs e)
{
var item = e.OriginalSource as TreeViewItem;
item?.BringIntoView();
// prevent this event bubbling up to any parent nodes
e.Handled = true;
}
}
然后,这可以用作TreeViewItems样式的一部分
<Style x:Key="perTreeViewItemContainerStyle"
TargetType="{x:Type TreeViewItem}">
<!-- Link the properties of perTreeViewItemViewModelBase to the corresponding ones on the TreeViewItem -->
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="IsEnabled" Value="{Binding IsEnabled}" />
<!-- Include the two "Scroll into View" behaviors -->
<Setter Property="vhelp:perTreeViewItemHelper.BringSelectedItemIntoView" Value="True" />
<Setter Property="vhelp:perTreeViewItemHelper.BringExpandedChildrenIntoView" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TreeViewItem}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"
MinWidth="14" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ToggleButton x:Name="Expander"
Grid.Row="0"
Grid.Column="0"
ClickMode="Press"
IsChecked="{Binding Path=IsExpanded, RelativeSource={RelativeSource TemplatedParent}}"
Style="{StaticResource perExpandCollapseToggleStyle}" />
<Border x:Name="PART_Border"
Grid.Row="0"
Grid.Column="1"
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ContentPresenter x:Name="PART_Header"
Margin="0,2"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
ContentSource="Header" />
</Border>
<ItemsPresenter x:Name="ItemsHost"
Grid.Row="1"
Grid.Column="1" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="false">
<Setter TargetName="ItemsHost" Property="Visibility" Value="Collapsed" />
</Trigger>
<Trigger Property="HasItems" Value="false">
<Setter TargetName="Expander" Property="Visibility" Value="Hidden" />
</Trigger>
<!-- Use the same colors for a selected item, whether the TreeView is focussed or not -->
<Trigger Property="IsSelected" Value="true">
<Setter TargetName="PART_Border" Property="Background" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" />
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}" />
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type TreeView}">
<Setter Property="ItemContainerStyle" Value="{StaticResource perTreeViewItemContainerStyle}" />
</Style>
更多详细信息以及我最近blog post的完整使用示例.
10月13日更新
在标准(非延迟加载模式)下运行时,博客文章已经过修改.关联的演示项目显示了在TreeView中显示的超过400,000个元素的嵌套数据结构,但是选择任何随机节点的响应是瞬时的.