WPF DataGrid DataContext非常慢

时间:2011-08-16 18:30:56

标签: c# wpf performance wpfdatagrid

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

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

我首先假设SQL数据库速度很慢,但我做了一些测试,发现我在几分之一秒内将所有数据从SQL服务器(几百行)读入DataTable。直到我将DataTable绑定到DataGrid的DataContext,所有内容都锁定了几秒钟。

那么为什么DataContext如此之慢?我的计算机是全新的,所以我很难理解为什么需要花费任何时间来填写DataGrid,考虑到我首先检索数据的速度有多快。

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

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


编辑:在Vlad的建议中,我尝试了另一个绕过SQL查询的测试。我用DataTable填充了1000行随机生成的数据。没变。数据生成并在一秒钟内写入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>

6 个答案:

答案 0 :(得分:16)

有太多变数可以肯定地回答这个问题。但是,您可以考虑以下事项:

  1. 您需要为网格提供的数据量是多少?您是否可能提供的数据太多而不是用户真正使用的数据?这可能会减慢速度。

  2. 您是否使用太多模板渲染列或单元格?我知道,这使您的演示文稿变得灵活,但是太多的模板(或控件)可能会减慢速度。

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

  4. 您是否有可能拥有大量嵌套样式(使用BasedOn),或许更重要的是,这些样式中的许多触发器会窃取渲染时间?

  5. 您是否在演示文稿中使用了大量用户控件,尤其是嵌套控件,可能会导致演示文稿中的渲染延迟?

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

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

  8. 您是否考虑过将FallBackValue用于绑定并将IsAsync设置为true?将此设置为tru将显示FallBackValue,直到数据准备就绪。

  9. 您是否在UI中使用了许多MultiBindings或PriorityBindings,这可能导致渲染在处理多个字段或值时变慢?

  10. 您使用的样式是否复杂?特别是渐变画笔,渲染它们可能会很昂贵,特别是如果你在网格中的每一行都这样做。

  11. 您是否考虑过使用分页以减少数据量?最后,Nairou,如果你能让用户接受它,这是解决这类问题的最佳方法。

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

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

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

  15. 您是否实现了可以非常频繁地调用CanExecute处理程序并且执行起来可能很昂贵的命令?这些可能是沉默的表演杀手。

  16. 同样,这个问题没有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中的绑定是最强大和最棒的绑定技术。我曾经历过。

    祝你好运!

答案 1 :(得分:1)

这可能对某些人有用:

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

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

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

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

答案 2 :(得分:1)

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

我遇到了一个DataGrid的问题,在窗口大小调整,列排序等之后,它花了几秒钟的时间来刷新,并在它执行时锁定了窗口UI(1000行,5列)。

使用WPF大小计算得出问题(bug?)。我在RowDefinition Height =“Auto”的网格中得到它,这导致渲染系统尝试通过测量每个列和行的大小来尝试重新计算DataGrid的大小,可能是通过填充整个网格(据我所知)。它应该以某种方式智能地处理它,但在这种情况下它不是。

快速检查以确定这是否是一个相关问题是在测试期间将DataGrid的高度和宽度属性设置为固定大小,然后再次尝试运行。如果您的性能已恢复,则可以在以下选项中进行永久性修复:

  • 将包含元素的大小更改为相对(*)或 固定值
  • 将DataGrid的MaxHeight和MaxWidth设置为更大的固定值 比在正常使用中可以得到的
  • 尝试使用不同大小调整策略的其他容器类型(Grid, DockPanel等)。实际上,我发现最简单的解决方案是将datagrid放在Grid中作为其直接容器,将DataGrid作为唯一元素

答案 3 :(得分:0)

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

答案 4 :(得分:0)

您是否在应用程序的Debug或Release版本中遇到这种缓慢的问题?是否附带Visual Studio?

如果它在附加了Visual Studio的Debug构建中,则可能是DataBinding错误被写入Output窗口。我这样说,就像今晚早些时候我解决了一个显示5000多个项目的ListBox中显着的暂停/缓慢,这是由ListBoxItem试图对VerticalContentAlignment执行绑定的默认模板引起的始终失败的HorizontalContentAlignment

答案 5 :(得分:0)

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