c# – 带虚拟化的WPF TreeView – 选择项目并将其置于视图中

我最近一直在使用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个元素的嵌套数据结构,但是选择任何随机节点的响应是瞬时的.

上一篇:c# – 文件下载后如何修复冻结页面?(TreeView SelectedNodeChanged中的TransmitFile)


下一篇:OpenCV245之SURF源代码分析