WPF DataGrid DataContext 极慢

Posted

技术标签:

【中文标题】WPF DataGrid DataContext 极慢【英文标题】:WPF DataGrid DataContext extremely slow 【发布时间】:2011-10-28 07:55:24 【问题描述】:

我正在开发一个简单的 WPF 应用程序,它执行 SQL 查询并在 DataGrid 中显示结果数据。

一切都按预期工作,只是性能很糟糕。从单击按钮加载数据到实际看到数据显示在 DataGrid 中的时间长度约为 3-4 秒。打开行虚拟化会快一点,但我不得不将其关闭,因为我需要能够对滚动后不再可见的单元格执行操作。即使启用了虚拟化,显示数据的速度也比我想的要慢。

我最初认为是 SQL 数据库运行缓慢,但我做了一些测试,发现我正在将 SQL 服务器中的所有数据(数百行)在几分之一秒内读取到 DataTable 中。直到我将 DataTable 绑定到 DataGrid 的 DataContext 之后,所有内容才会锁定几秒钟。

那么为什么 DataContext 这么慢呢?我的电脑是全新的,所以考虑到我一开始检索数据的速度,我很难理解为什么填写 DataGrid 需要这么长的时间。

(我也尝试绑定到 DataGrid 的 ItemSource 而不是 DataContext,但性能是一样的。)

是否有另一种方法可以将数据加载到性能更合理的 DataGrid 中?如果需要,即使是 DataGrid 的替代品也值得探索。


编辑:在 Vlad 的建议下,我尝试了另一个绕过 SQL 查询的测试。我改为用 1000 行随机生成的数据填充 DataTable。不用找了。数据在一秒钟内生成并写入 DataTable。但是,将其附加到 DataGrid 需要 20 多秒。

下面是我正在使用的 DataGrid XAML。除了绑定之外,非常简单,没有附加任何自定义代码。

<DataGrid ItemsSource="Binding" AutoGenerateColumns="False" Name="dataGridWellReadings" GridLinesVisibility="None" CanUserResizeRows="False" SelectionUnit="Cell" AlternatingRowBackground="#FFE0E0E0" RowBackground="#FFF0F0F0" HorizontalScrollBarVisibility="Disabled" SelectedCellsChanged="dataGridWellReadings_SelectedCellsChanged" EnableRowVirtualization="False">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Date" Binding="Binding readingDate, StringFormat=yyyy-MM-dd" Width="3*">
            <DataGridTextColumn.CellStyle>
                <Style TargetType="DataGridCell">
                    <Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
                    <Setter Property="BorderThickness" Value="0"/>
                </Style>
            </DataGridTextColumn.CellStyle>
        </DataGridTextColumn>
        <DataGridTextColumn Header="Pt" Binding="Binding readingPt, StringFormat=0.#" Width="2*">
            <DataGridTextColumn.CellStyle>
                <Style TargetType="DataGridCell">
                    <Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
                    <Setter Property="BorderThickness" Value="0"/>
                </Style>
            </DataGridTextColumn.CellStyle>
        </DataGridTextColumn>
        <DataGridTextColumn Header="Pc" Binding="Binding readingPc, StringFormat=0.#" Width="2*">
            <DataGridTextColumn.CellStyle>
                <Style TargetType="DataGridCell">
                    <Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
                    <Setter Property="BorderThickness" Value="0"/>
                </Style>
            </DataGridTextColumn.CellStyle>
        </DataGridTextColumn>
        <DataGridTextColumn Header="Ppl" Binding="Binding readingPpl, StringFormat=0.#" Width="2*">
            <DataGridTextColumn.CellStyle>
                <Style TargetType="DataGridCell">
                    <Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
                    <Setter Property="BorderThickness" Value="0"/>
                </Style>
            </DataGridTextColumn.CellStyle>
        </DataGridTextColumn>
        <DataGridTextColumn Header="MCFD" Binding="Binding readingMCFD, StringFormat=0.#" Width="2*">
            <DataGridTextColumn.CellStyle>
                <Style TargetType="DataGridCell">
                    <Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
                    <Setter Property="BorderThickness" Value="0"/>
                </Style>
            </DataGridTextColumn.CellStyle>
        </DataGridTextColumn>
        <DataGridTextColumn Header="Water Produced" Binding="Binding readingWaterProduced, StringFormat=0.#" Width="3*">
            <DataGridTextColumn.CellStyle>
                <Style TargetType="DataGridCell">
                    <Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
                    <Setter Property="BorderThickness" Value="0"/>
                </Style>
            </DataGridTextColumn.CellStyle>
        </DataGridTextColumn>
        <DataGridTextColumn Header="Water Hauled" Binding="Binding readingWaterHauled, StringFormat=0.#" Width="3*">
            <DataGridTextColumn.CellStyle>
                <Style TargetType="DataGridCell">
                    <Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
                    <Setter Property="BorderThickness" Value="0"/>
                </Style>
            </DataGridTextColumn.CellStyle>
        </DataGridTextColumn>
        <DataGridTextColumn Header="Temperature" Binding="Binding readingTemperature, StringFormat=0.#" Width="3*">
            <DataGridTextColumn.CellStyle>
                <Style TargetType="DataGridCell">
                    <Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
                    <Setter Property="BorderThickness" Value="0"/>
                </Style>
            </DataGridTextColumn.CellStyle>
        </DataGridTextColumn>
        <DataGridTextColumn Header="Hours On (actual)" Binding="Binding readingHoursOnActual, StringFormat=0.#" Width="3*">
            <DataGridTextColumn.CellStyle>
                <Style TargetType="DataGridCell">
                    <Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
                    <Setter Property="BorderThickness" Value="0"/>
                </Style>
            </DataGridTextColumn.CellStyle>
        </DataGridTextColumn>
        <DataGridTextColumn Header="Hours On (planned)" Binding="Binding readingHoursOnPlanned, StringFormat=0.#" Width="3*">
            <DataGridTextColumn.CellStyle>
                <Style TargetType="DataGridCell">
                    <Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
                    <Setter Property="BorderThickness" Value="0"/>
                </Style>
            </DataGridTextColumn.CellStyle>
        </DataGridTextColumn>
        <DataGridTextColumn Header="Clock Cycles" Binding="Binding readingClockCycles, StringFormat=0.#" Width="3*">
            <DataGridTextColumn.CellStyle>
                <Style TargetType="DataGridCell">
                    <Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
                    <Setter Property="BorderThickness" Value="0"/>
                </Style>
            </DataGridTextColumn.CellStyle>
        </DataGridTextColumn>
    </DataGrid.Columns>
</DataGrid>

【问题讨论】:

你确定是DataContext慢吗?可能是 LINQ 没有具体化查询,而是在 UI 绑定到它时懒惰地执行它。检查您是否在非 UI 线程中实现查询。 ... 或者只是在没有数据库访问权限的情况下尝试相同的代码,只是使用一些假数据。 好的,我没有使用数据库再次测试,只是使用了1000行随机生成的数据。同样,数据在一秒钟内生成并写入 DataTable。但是,将其附加到 DataGrid 需要 20 多秒。 【参考方案1】:

有太多变数无法确定地回答这个问题。但是,您需要考虑以下几点:

    您提供给网格的数据量是否必要?您是否可能给它提供了太多的数据,而不是用户真正使用的数据?这会减慢速度。

    您是否使用太多模板渲染列或单元格?我知道,这使您的演示更加灵活,但是太多的模板(或控件)会减慢速度。

    您的数据网格中有很多值转换器吗?是否需要每一行或每一列都运行一些稍微昂贵的代码才能呈现?

    您是否可能有很多嵌套样式(使用 BasedOn),或许更重要的是,这些样式中有很多触发器会占用渲染时间来应用?

    您是否在演示文稿中使用了很多用户控件,尤其是嵌套控件,这可能会导致演示文稿呈现延迟?

    您用于细胞的绑定字符串是否复杂?应用许多 StringFormat 或 ElementName 或 Ancesstory 查找?这些会导致渲染速度变慢。

    是否有可能使用视觉画笔来显示比用户立即可见的更多的数据?这会使虚拟化逻辑短路。

    您是否考虑过在绑定中使用 FallBackValue 并将 IsAsync 设置为 true?将此设置为 tru 将显示 FallBackValue,直到数据准备好。

    您是否在 UI 中使用了许多 MultiBinding 或 PriorityBinding,这可能会导致渲染速度变慢,因为它处理多个字段或值?

    您使用的样式是否复杂?尤其是渐变画笔,渲染它们的成本可能很高,尤其是当您在网格中的每一行都这样做时。

    您是否考虑过使用分页来减少数据量?最后,奈柔,如果你能让用户接受,这是解决这类问题的最佳方案。

    您是否在查看内存和 CPU 使用情况?您使用的硬件是否可能只是在努力渲染您在此处创建的 UI?

    您是否正在查看调试输出以查看是否存在导致性能下降的绑定错误或其他吞咽错误?

    您是否对着屏幕微笑并给您的代码一种良好的感觉,以便它愿意为您更加努力?开个玩笑,但有很多变数 - 嗯?

    您是否实现过 CanExecute 处理程序被非常频繁地调用并且执行起来可能很昂贵的命令?这些可能是性能的无声杀手。

同样,这个问题没有 100% 的答案。但这些可能会有所帮助。

要考虑的另一件事是,您的枚举可以是一个可观察的列表 - 这将允许您分部分加载数据。如果您想加载第一页并在异步过程中附加下一页和下一页等等,用户体验应该非常接近一次加载所有页面,除了它将是更快的初始渲染。这可能很复杂,但它是您的另一种选择。像这样可观察的列表很漂亮。

类似这样的:

ObservableCollection<User> Users  get; set; 

void LoadUsers()

    int _Size = 2;
    int _Page = 0;
    using (System.ComponentModel.BackgroundWorker _Worker
        = new System.ComponentModel.BackgroundWorker())
    
        _Worker.WorkerReportsProgress = true;
        _Worker.DoWork += (s, arg) =>
        
            List<User> _Data = null;
            while (_Data == null || _Data.Any())
            
                _Data = GetData(_Size, _Page++);
                _Worker.ReportProgress(_Page, _Data);
            
        ;
        _Worker.ProgressChanged += (s, e) =>
        
            List<User> _Data = null;
            _Data = e.UserState as List<User>;
            _Data.ForEach(x => Users.Add(x));
        ;
        _Worker.RunWorkerAsync();
    


List<User> GetData(int size, int page)

    // never return null
    return m_Context.Users.Take(size).Skip(page).ToList();

这就是我希望您带走的内容 - WPF 中的绑定从来都不是即时的。你永远不会有一个复杂的表单渲染和绑定没有一些延迟。你可以用上面的一些技巧来控制这里的疼痛。但是,您永远无法将其全部删除。然而,WPF 中的绑定是最强大和最棒的绑定技术。我曾经经历过。

祝你好运!

【讨论】:

我已更新我的帖子以显示我正在使用的 DataGrid XAML。它非常简单,没有自定义代码,这增加了为什么我不明白它很慢。 带有可观察列表的异步加载听起来很有希望。有关于如何实现它的链接吗?【参考方案2】:

您可能会发现性能缓慢与附加本身无关,而是与显示数据时发生的 DataGrid 重绘有关。你提到的延迟似乎相当过分。

我遇到了 DataGrid 的问题,在该问题中,在调整窗口大小、列排序等之后需要几秒钟才能刷新,并且在执行此操作时锁定了窗口 UI(1000 行,5 列)。

这归结为 WPF 大小计算的问题(错误?)。我将它放在 RowDefinition Height="Auto" 的网格中,这导致渲染系统在运行时尝试通过测量每一列和每一行的大小来重新计算 DataGrid 的大小,大概是通过填充整个网格(据我了解)。它应该以某种方式智能地处理这个问题,但在这种情况下它不是。

快速检查这是否是相关问题是在测试期间将 DataGrid 的 Height 和 Width 属性设置为固定大小,然后再次尝试运行。如果您的性能得到恢复,则可以使用以下选项进行永久性修复:

将包含元素的大小更改为相对 (*) 或 固定值 将 DataGrid 的 MaxHeight 和 MaxWidth 设置为更大的固定值 比正常使用时更容易 尝试其他具有不同大小调整策略的容器类型(网格、 码头面板等)。事实上,我找到的最简单的解决方案是将数据网格放入 Grid 作为其直接容器,并将 DataGrid 作为唯一元素

【讨论】:

【参考方案3】:

这可能对某些人有用:

我有一个数据网格,绑定速度很慢,就像原始海报一样。

应用程序查询数据库并在短时间内完成大量逻辑,然后花费一秒或多秒将可观察集合简单地绑定到数据网格。 就我而言,事实证明,尽管大部分数据都准备好了,但其中一些是延迟加载的(这意味着它在需要时才加载 - 这是大多数 ORM 工具(如 NHibernate、iBatis 等)的常见且有用的部分) .在绑定发生之前不需要它。 在我的情况下,不是所有的数据,而是只有一列被延迟加载。 事实证明,WPF 已经有一个非常简单的机制来处理这样的事情。 将此列的绑定设置为以下解决了问题:

<Binding Path="SomeProperty" IsAsync="True" FallbackValue="..." />

我的数据网格几乎立即加载。一列只包含文本“...”几秒钟,然后出现了正确的数据。

【讨论】:

【参考方案4】:

由于看不到您的任何代码,我建议安装性能或内存分析器的免费试用版(例如http://www.red-gate.com/products/dotnet-development/)。它可能会很快告诉您瓶颈在哪里。

【讨论】:

帖子已更新代码。我会看看那个分析器。但是,我的 DataGrid 背后几乎没有自定义代码,所以我不确定这对我有多大作用。【参考方案5】:

您是否在应用程序的调试或发布版本中遇到这种缓慢的情况?附加或不附加 Visual Studio?

如果它在附加了 Visual Studio 的调试版本中,则可能是写入输出窗口的数据绑定错误。我这么说,就像今天晚上早些时候我解决了显示 5000 多个项目的 ListBox 中的显着暂停/缓慢问题,这是由 ListBoxItem 的默认模板尝试执行 VerticalContentAlignmentHorizontalContentAlignment 的绑定引起的总是失败。

【讨论】:

无论哪种方式,缓慢似乎都是一样的。调试或发布,无论是否在 Visual Studio 中。我查看了“输出”窗口,但没有看到任何异常项目。 好吧,那么我认为您的解决方案是重新开启虚拟化,然后重新考虑您的模型,以便您可以在虚拟化后与单元进行交互。 我可能不得不这样做。我只是希望我了解性能损失的原因。所有数据都在内存中。我不明白为什么 DataGrid 显示它的速度这么慢。 它只是为您显示的每一行创建许多复杂的 UI 元素。启用虚拟化后,至少这个数字明显减少。如果您真的想了解,请抓住 Snoop(它是一个 WPF 内省工具)并在您的应用程序上打开放大工具。 补充一点,任何 UI 元素都是复杂的 UI 元素。一些用户将 TextBox 误认为是简单的,或者将按钮误认为是简单的。现实情况是,每个元素都有成本,没有元素是“原生的”或闪电般的。话虽如此,它们仍然很快 - 只是从来没有瞬间。【参考方案6】:

我是 WPF 新手,但我发现如果您在同一个数据网格上的多个位置分配 EnableRowVirtualization,它几乎会在渲染网格时削弱您的应用程序。我在我们正在开发的应用程序上发现了 2 次。 1 次设置为样式 2 次设置为行为和样式。我会检查以确保您在同一个数据网格上的虚拟化次数不超过 1 次。

【讨论】:

以上是关于WPF DataGrid DataContext 极慢的主要内容,如果未能解决你的问题,请参考以下文章

wpf datagrid observablecollection

WPF DataGrid 忽略 SortDescription

在wpf中怎么获取datagrid某行某列的值

WPF DataGrid 中的 IsSelected 绑定

WPF DataGrid的可见性

WPF DataGrid的可见性