带有分组的 WPF DataGrid 虚拟化

Posted

技术标签:

【中文标题】带有分组的 WPF DataGrid 虚拟化【英文标题】:WPF DataGrid Virtualization with Grouping 【发布时间】:2010-12-01 09:55:32 【问题描述】:

我正在使用WPF DataGrid from CodePlex,我需要让虚拟化与分组一起工作。

此question 是主题并指向MSDN Example,但它仅涵盖带有简单(即单个“列”)DataTemplates 的ListControls。

分组和虚拟化似乎是网格的一个非常常见的用例。是否有标准/推荐/简单的方法来实现这一点?

【问题讨论】:

【参考方案1】:

如您所说,启用分组将禁用 UI 虚拟化。

我认为您不会找到解决此问题的简单方法。我建议您检查可用的 WPF DataGrid 之一,例如 XCeeed 可能在其控件中内置此功能的一个。

【讨论】:

我知道其他网格在某种程度上具有这些功能,但我更喜欢使用 WPF CodePlex 网格,因为它将是标准的,并且所有其他网格在不同的方面都是垃圾。【参考方案2】:

当在 ListView 或 DataGrid 中启用分组时,没有允许您启用 UI 虚拟化的内置功能,如果您考虑一下它也很有意义。 DataGrid 如何对不存在的项目进行分组。要应用分组,控件需要加载整个集合,这将破坏虚拟化的整个目的。 您可能做的最好的事情是在您的视图模型(您再次绑定的对象)中提供某种虚拟化,因为您只提供当前需要的数据加上某种关于存在的数据量的通用数据然后伪造自己的看法。

通过分组,它可以是这样的:当最初启用分组时,所有组都将被折叠。因此,您的视图模型只需为每个组提供一个项目。只是为了确保视图包含所有现有组。一旦用户展开一个组,ViewModel 就会动态地重新填充该组的项目。这是一种非常简单且基本的虚拟化方式,不是最优的,但它可能是一个很好的起点。这只是为了说明方法。

【讨论】:

我不同意分组本质上会破坏 UI 可视化。如果您考虑一下,您正在考虑的是数据可视化,我不在乎。围绕组被强制与它们包含的行具有相同的高度,这是一个有效的观点。这将简化高度布局计算。 OP 谈论的是 UI 虚拟化而不是数据虚拟化(我相信没有 WPF 控件支持)。【参考方案3】:

我意识到我在这里聚会迟到了……但我最近遇到了这个问题(使用 .NET 4 中内置的 DataGrid)。不幸的是,在 DataGrid 上使用 Grouping 后,仍然没有对行进行虚拟化......但是我发现了一个非常巧妙的性能增强技巧,希望其他人也会发现它有用。

假设您在 GroupItem 模板的扩展器中使用 ItemsPresenter,并且默认情况下您的扩展器未扩展,则尝试使用默认 BooleanToVisibilityConverter 将 ItemsPresenter 的可见性简单地绑定到 Expander 的 IsEnabled 属性:

<BooleanToVisibilityConverter x:Key="bool2vis" />


<DataGrid.GroupStyle>
    <GroupStyle>
        <GroupStyle.ContainerStyle>
            <Style TargetType="x:Type GroupItem">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="x:Type GroupItem">
                            <Expander x:Name="exp">
                                <ItemsPresenter Visibility="Binding ElementName=exp, Path=IsExpanded, Converter=StaticResource bool2vis" />
                            </Expander>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </GroupStyle.ContainerStyle>
    </GroupStyle>
</DataGrid.GroupStyle>

如果您遇到的问题是 DataGrid 需要很长时间才能加载(因为它实际上是在绘制数据网格中的每条记录,即使它位于折叠的扩展器中)......那么使用上面的代码将导致数据网格在扩展组之前不绘制记录,然后,它只会绘制该特定组的记录。

不利的一面是,这仅在您的扩展器默认折叠时才有帮助,并且行仍然没有虚拟化(如果您在展开的组中有 100 个项目,但屏幕上只有 20 个,则所有 100 个都将在您扩展组时绘制)。

好处是您基本上实现了 DataGrid 记录的延迟加载,因此在您真正需要查看项目(您选择扩展组)之前,您不会执行绘图工作。对于我的产品,我的组标题具有内置按钮来对其组中的所有项目执行操作,因此用户通常不会扩展组,除非他们需要对组中的单个项目执行操作。

*如果使用此技巧,需要注意的一点是,您可能应该为列标题设置一些显式宽度或最小宽度(因为在 DataGrid 首次加载时未绘制项目,因此列标题无法自动调整为适合最大的项目)。

希望真正的虚拟化能够在未来的服务包中实现,但如果没有,我希望这对其他人有所帮助!

更新

看来这个问题将在 .NET 4.5 中通过一个新的附加属性 VirtualizingPanel.IsVirtualizingWhenGrouping 得到修复。

【讨论】:

如果我能投两次票就好了......一次惊人的性能提升 这太棒了!我有一个地方每次都会失忆,这就像一个魅力! @Scott - 这很好,但我遇到了这样一种情况,即所有组都已扩展,每个扩展器多行。你知道虚拟化 ItemsPresenter insite Expander 的方法吗? @Dr.AndrewBurnett-Thompson - 不幸的是,在 .NET 4.5 发布之前我没有一个好的答案,它将在 VirtualizingPanel 类上有一个 IsVirtualizingWhenGrouping 属性:msdn.microsoft.com/en-us/library/… VirtualizingPanel.IsVirtualizingWhenGrouping 成功了!感谢您提供有关它的最新信息!【参考方案4】:

在框架 4.5 VirtualizingPanel.IsVirtualizingWhenGrouping 中有一个新的附加属性,它允许在分组时打开虚拟化。

<DataGrid EnableColumnVirtualization="True" EnableRowVirtualization="True"
   VirtualizingPanel.IsVirtualizingWhenGrouping="True">

【讨论】:

以上是关于带有分组的 WPF DataGrid 虚拟化的主要内容,如果未能解决你的问题,请参考以下文章

WPF DataGrid Multiselect启用了虚拟化MVVM

没有虚拟化的 WPF DataGrid 性能

具有循环虚拟化功能的C#WPF Datagrid

WPF ListBox/ListView/DataGrid 列表滚动与虚拟化

wpf datagrid 有没有像 winforms DataGridView 这样的虚拟模式?

WPF DataGrid DataContext 极慢