WPF数据网格分组虚拟化

25
我正在使用来自CodePlex的WPF DataGrid,并且需要使分组与虚拟化一起工作。
这个问题很相关,并指向了一个MSDN示例,但它只涵盖具有简单(即单个“列”)DataTemplates的ListControls。
对于网格来说,分组和虚拟化似乎是一个非常普遍的用例。是否有一种标准/推荐/简单的方法来实现这一点?
4个回答

40
我意识到我来晚了...但最近我遇到了这个问题(使用.NET 4内置的DataGrid)。不幸的是,一旦在DataGrid上使用分组,行就没有虚拟化...但我发现了一个非常巧妙的性能增强技巧,希望其他人也会发现它有用。
假设您在GroupItem的模板中使用ItemsPresenter,并且默认情况下您的展开器没有展开,那么只需将ItemsPresenter的可见性绑定到展开器的IsEnabled属性,使用默认的BooleanToVisibilityConverter即可:
<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加载非常缓慢的问题(因为它基本上要把你datagrid中的每条记录都绘制出来,即使它在一个折叠的expander中)...那么使用上面的代码将导致datagrid不会绘制你的记录,直到你展开一个分组,然后,它只会绘制出该特定组的记录。
缺点是,这只有在默认情况下折叠了扩展器才会有所帮助,而且行仍然不会虚拟化(如果你在一个扩展组中有100个项目,但只有20个适合屏幕,当你展开组时所有100个项目都会被绘制)。
好处是,你实际上已经实现了数据网格记录的延迟加载,所以除非你真正需要查看项目(选择展开组),否则不会执行绘图工作。对于我的产品,我的组标题内置了按钮,用于对其组内的所有项目执行操作,因此更多地是用户在需要对组内的单个项目执行操作时才会展开组。
*如果你使用这个技巧,请注意设置一些列标题的显式宽度或最小宽度(因为当DataGrid第一次加载时条目没有被绘制,所以列标题无法自动调整大小以适应最大的条目)。
希望真正的虚拟化能在未来的服务包中得到实现,但如果没有,我希望这将帮助其他人!
更新
看起来,在.NET 4.5中,一个新的附加属性VirtualizingPanel.IsVirtualizingWhenGrouping将修复此问题。

4
如果我能点赞两次就好了……一下子就有了惊人的性能提升。 - Steve Greatrex
2
这太棒了!我有一个地方每次都会出现内存不足的问题,但这个方法完美解决了! - John Gardner
@Scott - 这很好,但我遇到了一个情况,即我所有的组都展开,并且每个展开器有多行。您知道一种在Expander内部虚拟化ItemsPresenter的方法吗? - Dr. Andrew Burnett-Thompson
@Dr.AndrewBurnett-Thompson - 很遗憾,我无法给您一个好的答案,直到.NET 4.5发布,它将在VirtualizingPanel类上具有一个IsVirtualizingWhenGrouping属性:http://msdn.microsoft.com/en-us/library/system.windows.controls.virtualizingpanel.isvirtualizingwhengrouping(v=vs.110).aspx - Scott
@Scott 哈哈,“在边缘条件下是否能正常工作” - 谢谢你的信息!我正在研究它。我找到了一个例子,他们使用一行来假装成一个组,这可能允许分组时进行虚拟化。http://blog.smoura.com/wpf-toolkit-datagrid-part-iv-templatecolumns-and-row-grouping/ - Dr. Andrew Burnett-Thompson
1
VirtualizingPanel.IsVirtualizingWhenGrouping起了作用!感谢您提供的有关它的更新信息! - Unknown Coder

19

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

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

2
在ListView或DataGrid中启用分组时,没有内置功能可以让您启用UI虚拟化。如果您仔细思考一下,这也是有道理的。如何对不存在的项目进行分组?要应用分组,控件需要加载整个集合,这将使虚拟化失去意义。你可能能做的最好的事情就是在你的视图模型(你绑定的对象)中提供某种虚拟化,只提供当前所需的数据以及关于存在的数据量的某些通用数据,然后自己模拟视图。
对于分组,可以按以下方式进行:当启用分组时,所有组最初都将被折叠。因此,您的视图模型只需要为每个组提供一个项目。只是为了确保视图包含所有现有的组。一旦用户展开一个组,视图模型就会动态地重新填充该组的项目。这是一种非常简单和基本的虚拟化方法,不是最优的,但可能是一个很好的起点。这只是说明这种方法的途径。

3
我不同意分组会破坏用户界面可视化这一说法。如果你仔细想想,你所讨论的是数据可视化,而我并不关心它。但是可以提出一个有根据的观点,即需要强制分组与其包含行的高度相同,这将简化高度布局计算。 - Jan Bannister
OP在谈论UI虚拟化而不是数据虚拟化(我认为没有WPF控件支持)。 - user585968

0

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

我认为你不会找到解决这个问题的简单方法。我建议你检查一下可用的WPF DataGrid之一,例如XCeeed,他们的控件可能已经内置了这个功能。


我知道其他网格在某种程度上也具有这些功能,但我更愿意使用WPF CodePlex网格,因为它将成为标准,而所有其他网格在不同的方面都很糟糕。 - Jan Bannister

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接