我最近一直在使用 WPF treeview,当用户使用在支持对象。
目前我的方法是使用这个答案中的方法:https://stackoverflow.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
是绑定(bind)到我的对象列表的虚拟化 TreeView。关闭虚拟化不是一种选择,因为关闭它后性能真的很差。
当我搜索对象并找到支持数据对象时,我调用此 FocusTreeViewNode() 方法并将该对象作为其参数。它通常会在第一次调用时工作,选择对象并将其置于 View 中。
第二次进行搜索时,将传入要选择的节点,但是当堆栈清空时调用 ContainerFromItem()(因此它正在尝试为对象本身生成容器)返回 null。当我调试它时,我可以在 ContainerGenerator 的项目列表中看到我正在搜索的对象,但由于某种原因它没有被返回。我查找了所有与 UpdateLayout() 和其他东西有关的东西,但我想不通。
即使在父节点进入 View 后,容器中的某些对象也可能不在页面上 - 例如一个扩展器下面有 250 个项目,一次只渲染 60 个。这可能是个问题吗?
更新
这是一个示例项目,它制作了一个显示此问题的虚拟化 TreeView 。 https://github.com/Mgamerz/TreeViewVirtualizingErrorDemo
在 VS 中构建它,然后在搜索框中输入类似 4 的内容。按几次搜索,它会抛出一个异常,指出容器为空,即使你打开了 generator
对象可以清楚地看到它在发电机中。
最佳答案
与 WPF 开发的许多其他方面一样,可以使用 MVVM 设计模式处理此操作。
创建一个 ViewModel 类,包括一个 IsSelected 属性,它保存每个树项的数据。
然后可以通过附加属性处理将所选项目置于 View 中
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 日更新
博客文章已针对在标准(非延迟加载模式)下运行时进行了修改。相关的演示项目显示了一个包含超过 400,000 个元素的嵌套数据结构显示在 TreeView 中,而且选择任何随机节点的响应都是即时的。
关于c# - 具有虚拟化的 WPF TreeView - 选择项目并将其置于 View 中,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52378051/