提高WPF DataGrid性能

40
在我的.NET 3.5 WPF应用程序中,我有一个WPF DataGrid,并且该DataGrid将填充500列和50行。当我滚动或执行DataGrid.Items.Refresh()或选择行时,应用程序的性能非常差。
实际上,应用程序需要大约20秒才能更新布局。Layout_Updated()事件将在20秒后触发。
如果我将列数减少到50个或更少,应用程序将非常响应。根据我的调查结果,性能与列数直接相关。
我该如何改善DataGrid的性能?

6
一个拥有10列以上的网格可能不是一个好主意。但是请考虑使用“自定义分页”。 - Boomer
4
真的吗?你有见过外汇网格吗? - TomTom
@TomTom - 你能告诉我一些第三方的网格控件吗? - Kishor
3
我不知道外汇是什么,但我认为任何理智的人都不会浏览500列数据。 - Federico Berasategui
500列肯定会对性能造成影响...但至少在初始加载和滚动时,确保DataGrid的附加属性ScrollViewer.CanContentScroll=True(以确保未显示的行的虚拟化)。如果您将该值设置为False以启用“平滑滚动”,则即使未显示,所有行也将被渲染。 - Scott
显示剩余3条评论
7个回答

95

有一些选项可以打开,帮助您使用DataGrid对象。

EnableColumnVirtualization = true
EnableRowVirtualization = true

我认为这两个可能会有帮助。接下来,尝试将您的绑定设为异步。

ItemsSource="{Binding MyStuff, IsAsync=True}"

最后,我听说设置最大高度和宽度可以帮助即使超过了最大屏幕尺寸,但我个人没有注意到差别(该说法与自动大小测量有关)

MaxWidth="2560"
MaxHeight="1600"

同时,永远不要将 DataGrid 放在 ScrollViewer 中,因为这样会导致虚拟化失效。如果有帮助,请告诉我!


2
我还会将您的列设置为固定宽度。 - Kelly
4
太棒了。我不明白为什么其他帖子从未讨论过行和列的虚拟化。非常感谢。 - Vikas
1
绝对值得一试!事实上,它们为我提高了很多性能。谢谢@Alan - Siva Gopal
1
设置MaxHeight显著改进了。非常感谢。 同时,设置MaxWidth也稍微有所改善。在我找到这个解决方案之前,我都快疯了。谢谢你。 - Zeyad
1
真的很有帮助!移除ScrollViewer后,Datagrid的重渲染性能得到了很大的提升! - BangZ
显示剩余6条评论

11

检查是否已将属性ScrollViewer.CanContentScroll设置为False。将此属性设置为false将禁用虚拟化,这将降低数据网格的性能。要获取更多澄清,请参考CanContentScroll


3

设置 DataGrid.RowHeight 的值,这将产生巨大的影响。

我知道这是一个非常旧的问题,但我刚刚遇到它,并且这是我端的最大差异。我的默认高度为25。


1

步骤1:从2分钟到10秒钟

这个答案(将ScrollViewer.CanContentScroll设置为True)让我找到了正确的方向。但是我需要将其设置为false。在进行刷新时将其设置为true,我编写了这两个方法。

internal static void DataGridRefreshItems(DataGrid dataGridToRefresh)
{
    /// Get the scrollViewer from the datagrid
    ScrollViewer scrollViewer = WpfToolsGeneral.FindVisualChildren<ScrollViewer>(dataGridToRefresh).ElementAt(0);
    bool savedContentScrollState = scrollViewer.CanContentScroll;
    scrollViewer.CanContentScroll = true;

    dataGridToRefresh.Items.Refresh();

    /// Was set to false, restore it
    if (!savedContentScrollState)
    {
        /// This method finishes even when the update of the DataGrid is not 
        /// finished. Therefore we use this call to perform the restore of
        /// the setting after the UI work has finished.
        Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() => SetScrollViewerCanContentScrollFalse(scrollViewer)), DispatcherPriority.ContextIdle, null);
    }
}

private static void SetScrollViewerCanContentScrollFalse(ScrollViewer scrollViewer)
{
    scrollViewer.CanContentScroll = false;
}

这是我用来获取 VisualChildren 的方法:
public static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
{
    if (depObj != null)
    {
        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
        {
            DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
            if (child != null && child is T)
            {
                yield return (T)child;
            }

            foreach (T childOfChild in FindVisualChildren<T>(child))
            {
                yield return childOfChild;
            }
        }
    }
}

在此之后,我刷新了50,000个新项目,仅需要10秒,而不是以前的2分钟,并且仅消耗2 MB的RAM,而不是4 GB。

步骤2:从10秒到0.5秒

为了测试,我禁用了所有的IValueConverter并实现了直接绑定属性。没有转换器,DataGrid会立即刷新。所以我保留了它。


0

谢谢您的建议。但是我的要求是一次性拥有500列。 - Kishor
1
你说了滚动,所以我认为你不会一次显示所有的行(如果有500列,那你该怎么办呢?)数据虚拟化的想法是,你加载一定数量的行来填充网格,然后在滚动时删除和替换行。 - Constanta

0
我在一个 ScrollViewer 中放置了一个 DataGrid,允许 DataGrid 的高度无限增长。将 DataGrid 的 Height 或 MaxHeight 设置为合理的值解决了大部分性能问题。

0

我发现的一个在加载和滚动性能方面取得最佳折衷的解决方案是为所有列的绑定(而不是DataGrid的ItemsSource绑定)设置IsAsync=True。例如:

<DataGridTextColumn Binding="{Binding Path=MaterialName, Mode=OneTime, IsAsync=True}" Header="Name" />

顺便说一句,在关闭数据网格的虚拟化时,这个解决方案效果更好。然而,即使开启了虚拟化,仍然值得一试。此外,当应用于显示图像或其他内容的DataGridTemplateColumn时,这个解决方案非常有效。


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