c# - 具有虚拟化的 WPF TreeView - 选择项目并将其置于 View 中

标签 c# wpf treeview

我最近一直在使用 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/

相关文章:

c# - 如何正确签署可执行文件

c# - 在 C# 中绑定(bind)到 DataGridView 时使用的一个很好的集合

c# - 来自 WPF 的数据库连接

c# - 未找到 MSBuild 社区任务

C# 使用多线程或并行执行执行 SQL SP

c# - PayPal Adaptive Payments - 通过 IPN/API 检索发件人的账单地址?

c# - 为什么我不能在这种情况下设置不透明度?

c# - 将 XML 解析为 Treeview 列表

events - 如何在TreeView kendo ui上获取选定节点的数据?

delphi - 当动态重新定义快捷方式时,Del 键会删除整个 TreeView