WPF DataGrid DataContext 速度极慢
我正在开发一个简单的 WPF 应用程序,它执行 SQL 查询并在 DataGrid 中显示结果数据。
一切都按预期进行,只是性能很糟糕。从单击按钮加载数据到实际看到数据显示在 DataGrid 中的时间长度约为 3-4 秒。打开行虚拟化后,速度会快一些,但我必须将其关闭,因为我需要能够对滚动后不再可见的单元格执行操作。即使打开了虚拟化,显示数据的速度也比我想要的要慢。
我首先假设是 SQL 数据库速度慢,但我做了一些测试,发现我在不到一秒的时间内将所有数据从 SQL 服务器(数百行)读取到 DataTable 中。直到我将 DataTable 绑定到 DataGrid 的 DataContext 后,所有内容才会锁定几秒钟。
那么为什么 DataContext 这么慢呢?我的计算机是全新的,因此考虑到我首先检索数据的速度,我很难理解为什么需要花费很长的时间来填写 DataGrid。
(我还尝试绑定到 DataGrid 的 ItemSource 而不是 DataContext,但性能是相同的。)
是否有其他方法可以将数据加载到具有更合理性能的 DataGrid 中?如果需要的话,甚至 DataGrid 的替代方案也值得探索。
编辑:根据Vlad的建议,我尝试了另一个绕过SQL查询的测试。相反,我用 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>
I have a simple WPF application I'm working on, which performs a SQL query and displays the resulting data in a DataGrid.
Everything works as expected, except that performance is terrible. The length of time from clicking a button to load data, and actually seeing the data show up in the DataGrid, is on the order of 3-4 seconds. It is a good bit faster with row virtualization turned on, but I've had to turn it off since I need to be able to perform operations on cells that are no longer visible after scrolling. And even with virtualization turned on, getting the data displayed is slower than I would like.
I first assumed it was the SQL database that was being slow, but I did some tests and found that I'm reading all data from the SQL server (several hundred rows) into a DataTable in a fraction of a second. It isn't until I bind the DataTable to the DataGrid's DataContext that everything locks up for several seconds.
So why is the DataContext so slow? My computer is brand new, so I have a hard time understanding why it takes any length of time to fill out the DataGrid, considering how quickly I retrieve the data in the first place.
(I also tried binding to the DataGrid's ItemSource rather than the DataContext, but performance was the same.)
Is there an alternative method of loading data into a DataGrid that has more reasonable performance? Even an alternative to DataGrid might be worth exploring if needed.
Edit: At Vlad's suggestion, I tried another test which bypassed the SQL query. I instead filled the DataTable with 1000 rows of random generated data. No change. The data was generated and written to the DataTable in under a second. However, attaching that to the DataGrid took over 20 seconds.
Below is the DataGrid XAML I'm using. Other than the binding, it is very simple, no custom code attached to it.
<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>
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
有太多变数,无法确定地回答这个问题。但是,您需要考虑以下事项:
您向网格提供的数据量是否必要?您是否可能为其提供了过多的数据而不是用户真正使用的数据?这可能会减慢速度。
您是否使用太多模板渲染列或单元格?我知道,这使您的演示文稿变得灵活,但太多模板(或控件)会减慢速度。
您的数据网格中有很多价值转换器吗?是否需要每行或每列运行一些稍微昂贵的代码才能渲染?
您是否可能有很多嵌套样式(使用 BasedOn),并且也许更重要的是,这些样式中的许多触发器会窃取渲染时间来应用?
您是否在演示文稿中使用了大量用户控件(尤其是嵌套控件),这可能会导致演示文稿中的渲染延迟?
您用于单元格的绑定字符串是否复杂?应用许多 StringFormat 或 ElementName 或 Ancestory 查找?这些会导致渲染速度变慢。
是否有可能使用可视画笔来显示比用户立即可见的数据更多的数据?这会使虚拟化逻辑短路。
您是否考虑过在绑定中使用 FallBackValue 并将 IsAsync 设置为 true?将其设置为 tru 将显示 FallBackValue,直到数据准备就绪。
您是否在 UI 中使用了许多 MultiBindings 或 PriorityBindings,这可能会导致渲染速度变慢,因为它处理多个字段或值?
您使用的样式复杂吗?尤其是渐变画笔,渲染这些画笔的成本可能很高,尤其是如果您在网格中的每一行都进行渲染的话。
您是否考虑过使用分页来减少数据量?最后,Nairou,如果能让用户接受,这就是解决此类问题的最佳方案。
您是否正在查看内存和 CPU 使用情况?您使用的硬件是否有可能只是在努力渲染您在此处创建的 UI?
您是否正在观察调试输出,以查看是否存在导致性能下降的绑定错误或其他吞没错误?
你是否对着屏幕微笑,给你的代码带来了良好的感觉,因此它愿意为你更加努力?开玩笑,但是有很多变数 - 嗯?
您是否实现了 CanExecute 处理程序被频繁调用并且执行成本可能很高的命令?这些可能是性能的无声杀手。
同样,这个问题没有 100% 的答案。但这些可能会有所帮助。
还要考虑的一件事是您的枚举可以是一个可观察的列表 - 这将允许您分部分加载数据。如果您想加载第一页并在异步过程中附加下一页和下一页,依此类推,用户体验应该非常接近一次加载所有页面,除非初始渲染速度更快。这可能很复杂,但它是您的另一种选择。可观察列表就是这样的漂亮。
类似这样的内容:
这就是我希望您了解的内容 - WPF 中的绑定从来都不是即时的。您永远不会在没有延迟的情况下进行复杂的表单渲染和绑定。您可以使用上述一些技巧来控制此处的疼痛。但是,您永远无法将其全部删除。然而,WPF 中的绑定是最强大、最棒的绑定技术。我曾经经历过。
祝你好运!
There are too many variables to answer this with certainty. However, here are some things for you to consider:
Is the amount of data you are feeding the grid necessary? Are you possibly giving it too much data than the user will really use? This can slow things down.
Are you rendering the columns or cells with too many templates? This makes your presentation flexible, I know, but too many templates (or controls) can slow things down.
Do you have a lot of value converters in your datagrid? Is it required that every row or every column run some slightly expensive code in order to render?
Is it possible that you have a lot of nested styles (using BasedOn) and, perhaps more important, many triggers in those styles which steal render time to apply?
Are you using a lot of user controls, esp nested controls, in your presentation that might be causing the render delay in your presentation?
Is the binding string you are using for your cells complex? Applying many StringFormat or ElementName or Ancestory lookups? These contribute to slowness in rendering.
Is it possible that a visual brush is being used to show more of the data than is immediately visible to the user? This would short circuit the virtualization logic.
Have you considered using a FallBackValue to your bindings and setting IsAsync to true? Setting this to tru will show FallBackValue until the data is ready.
Are you using many MultiBindings or PriorityBindings in your UI that might be causing your rendering to slow down as it processes more than one field or value?
Are the styles you are using complex? Especially gradient brushes, rendering these can be costly, especially if you are doing it every single row in your grid.
Have you considered using paging so that you have less data? In the end, Nairou, this is the best solution for these types of problems if you can make users accept it.
Are you looking at Memory and CPU usage? Is it possible that the hardware you are using is simply struggling to render the UI you have created here?
Are you watching the debug output to see if there are binding errors or other swallowed errors that contribute to the degrade in performance?
Did you smile at the screen and give your code a good feeling so it is willing to try harder for you? Just kidding, but there are lots of variables - huh?
Have you implemented Commands whose CanExecute handlers are called very frequently and are perhaps expensive to execute? These can be silent killers of performance.
Again, there is no 100% answer to this problem. But these might help.
One more thing to consider is that your enumeration can be an observable list - this will let you load data in parts. If you want to load the first page and in an async process append the next page and the next and so on, the user experience should be very close to loading it all at once except it will be a faster initial render. This can be complex, but it another option for you. Observable lists are nifty like that.
Something like this:
Here's what I want you to take away - binding in WPF is never instant. You will never have a complex form render and bind without SOME delay. You can control the pain here with some of the techniques above. However, you can never remove it all. However, binding in WPF is the most powerful and awesome binding tech. I have ever experienced.
Best of luck!
您可能会发现性能缓慢与附加本身无关,而是与显示数据时发生的 DataGrid 重绘有关。你提到的延迟似乎相当过分。
我在使用 DataGrid 时遇到了一个问题,在调整窗口大小、列排序等之后,它实际上需要几秒钟的时间来刷新,并在执行此操作时锁定窗口 UI(1000 行,5 列)。
这归结为 WPF 大小计算的问题(错误?)。我将它放在 RowDefinition Height=“Auto” 的网格中,这导致渲染系统在运行时尝试通过测量每个列和行的大小来重新计算 DataGrid 的大小,大概是通过填充整个网格(据我了解)。它应该以某种方式智能地处理这个问题,但在这种情况下却没有。
快速检查这是否是相关问题的方法是,在测试期间将 DataGrid 的 Height 和 Width 属性设置为固定大小,然后再次尝试运行。如果您的性能得到恢复,永久修复可能包括以下选项:
固定值
比正常使用时可以得到的
DockPanel 等)。事实上,我发现的最简单的解决方案是将数据网格放入网格中作为其直接容器,并将数据网格作为唯一的元素
You might find that the slow performance is not related to the attaching itself, but to the redrawing of the DataGrid that happens when the data is displayed. The delay you mentioned seems fairly excessive.
I had a problem with the DataGrid in which it took literally seconds to refresh after a window resize, column sort, etc. and locked up the window UI while it was doing so (1000 rows, 5 columns).
It came down to an issue (bug?) with the WPF sizing calculations. I had it in a grid with the RowDefinition Height="Auto" which was causing the rendering system to try and recalculate the size of the DataGrid at runtime by measuring the size of each and every column and row, presumably by filling the whole grid (as I understand it). It is supposed to handle this intelligently somehow but in this case it was not.
A quick check to see if this is a related problem is to set the Height and Width properties of the DataGrid to a fixed size for the duration of the test, and try running again. If your performance is restored, a permanent fix may be among these options:
fixed values
than it could get in normal use
DockPanel, etc). In fact, the simplest solution I found was to put the datagrid inside a Grid as its immediate container, with the DataGrid as the only element
这可能对某些人有用:
我有一个数据网格,它的绑定速度很慢,就像原始海报一样。
应用程序查询数据库并在短时间内执行大量逻辑,然后花费一秒或多秒将可观察集合简单地绑定到数据网格。
就我而言,事实证明,虽然大部分数据已准备就绪,但其中一些数据是延迟加载的(这意味着直到需要时才加载 - 这是大多数 ORM 工具(如 NHibernate、iBatis 等)的常见且有用的部分。) 。在绑定发生之前不需要它。
就我而言,延迟加载的不是所有数据,而是一列。
事实证明,WPF 已经有一个非常简单的机制来处理类似的事情。
将此列的绑定设置为以下内容解决了问题:
我的数据网格几乎立即加载。一列仅包含文本“...”几秒钟,然后出现正确的数据。
This might be useful to some:
I had a datagrid that was slow to bind just like the original poster.
The application queried the database and did a lot of logic in a short while and then took one or more seconds to simply bind the observable collection to the datagrid.
In my case it turned out that although most of the data was ready, some of it was lazy-loaded (meaning it did not get loaded until needed - this is a common and useful part of most ORM tools like NHibernate, iBatis etc.). It wasn't needed until the binding happened.
In my case it was not all the data but only one single column that was lazy-loaded.
It turned out that WPF already has a very simple mechanism for handling something like this.
Setting the binding for this column to the following solved the problem:
My datagrid loaded almost instantly. One column contained just the text "..." for a few seconds, and then the correct data appeared.
由于没有看到您的任何代码,我建议安装性能或内存分析器的免费试用版(例如 http://www.red-gate.com/products/dotnet-development/)。它可能会很快告诉您瓶颈在哪里。
For lack of seeing any of your code, I'd suggest installing a free trial of a performance or memory profiler (eg http://www.red-gate.com/products/dotnet-development/). It'll likely very quickly tell you where the bottleneck is.
您在应用程序的调试或发布版本中是否遇到这种缓慢的情况?是否附加了 Visual Studio?
如果它处于附加了 Visual Studio 的调试版本中,则可能是写入输出窗口的数据绑定错误。我这么说是因为今晚早些时候,我解决了显示 5000 多个项目的 ListBox 中的严重暂停/缓慢问题,这是由
ListBoxItem
的默认模板尝试执行的绑定引起的VerticalContentAlignment
和HorizontalContentAlignment
总是失败。Are you experiencing this slowness in Debug or Release builds of your application? With or without Visual Studio attached?
If it is in a Debug build with Visual Studio attached, then it could be a DataBinding errors being written to the Output window. I say this, as earlier this evening I resolved a significant pause/slowness in a ListBox that was displaying 5000+ items which was being caused by the default template for
ListBoxItem
trying to perform a binding forVerticalContentAlignment
andHorizontalContentAlignment
that always failed.我是 WPF 新手,但我发现如果您在同一数据网格上的多个位置分配 EnableRowVirtualization,那么在渲染网格时它几乎会削弱您的应用程序。我在我们正在开发的应用程序上发现了两次这个。一次是在一种风格中设置的,两次是在一种行为和一种风格中设置的。我会检查以确保您在同一数据网格上虚拟化的次数不会超过 1 次。
I am new to WPF but I have found that if you assign EnableRowVirtualization in more than one location on the same data grid, it will nearly cripple your app when rendering the grid. I have found this 2 times on the app we are working on. 1 time time it was set in a style 2 times and the other time it was set in a behavior and a style. I would check to make sure your not virtualizing more than 1 time on the same data grid.