DataGridView 在我的两个屏幕之一上的糟糕的重绘性能

我已经解决了这个问题,但我要把它发给后人。

在我的双显示器系统上,DataGridView 遇到了一个非常奇怪的问题。这个问题表现为对控件(大概30秒重新粉刷一遍)极其缓慢的重新绘制,但只有当它出现在我的一个屏幕上时才会出现。另一方面,重新刷漆的速度很好。

我有一个 Nvidia 8800 GT 与最新的非测试驱动程序(175。东西)。是司机的问题吗?我会把它留在空气中,因为我必须生活在这个特殊的配置。(不过,这种情况不会发生在 ATI 卡上... ...)

绘制速度与单元格内容无关,自定义绘制根本不能提高性能——即使只是绘制一个实心矩形。

后来我发现,放置一个 ElementHost (从系统。窗户。表格。集成命名空间)更正问题。它不需要被改动; 它只需要是 DataGridView 所在表单的子表单即可。只要 看得见属性为 true,就可以将其大小调整为(0,0)。

我不想显式地添加。NET 3/3.5对我的应用程序的依赖; 我创建了一个方法来使用反射在运行时创建这个控件(如果可以的话)。它能正常工作,而且至少在没有所需库的机器上它会优雅地失败——它只会回到慢的状态。

这个方法还允许我在应用程序运行时应用修复功能,这使得在我的表单上查看 WPF 库的更改变得更加容易(使用 Spy + +)。

经过大量的尝试和错误,我注意到在控件本身启用双缓冲(而不仅仅是窗体)可以纠正这个问题!


因此,您只需要基于 DataGridView 创建一个自定义类,这样就可以启用它的 DoubleBuffering。就是这样!

class CustomDataGridView: DataGridView
{
public CustomDataGridView()
{
DoubleBuffered = true;
}
}

只要我的所有网格实例都使用这个自定义版本,一切都很好。如果我曾经遇到这样的情况,我不能使用子类解决方案(如果我没有代码) ,我想我可以尝试注入该控件的形式:)(尽管我更可能尝试使用反射从外部强制 DoubleBuffered 属性,以再次避免依赖关系)。

这么简单的一件小事就占用了我这么多时间,真是可悲。

50327 次浏览

You just need to make a custom class based off of DataGridView so you can enable its DoubleBuffering. That's it!


class CustomDataGridView: DataGridView
{
public CustomDataGridView()
{
DoubleBuffered = true;
}
}

As long as all of my instances of the grid are using this custom version, all is well. If I ever run into a situation caused by this where I'm not able to use the subclass solution (if I don't have the code), I suppose I could try to inject that control onto the form :) (although I'll be more likely to try using reflection to force the DoubleBuffered property on from the outside to once again avoid the dependency).

It is sad that such a trivially simple thing ate up so much of my time...

Note: Making the answer an answer so the question can be marked as answered

We've experienced a similar problem using .NET 3.0 and DataGridView on a dual monitor system.

Our application would display the grid with a gray background, indicating that the cells could not be changed. Upon selecting a "change settings" button, the program would change the background color of the cells white to indicate to the user that the cell text could be changed. A "cancel" button would change the background color of the aforementioned cells back to gray.

As the background color changed there would be a flicker, a brief impression of a default sized grid with the same number of rows and columns. This problem would only occur on the primary monitor (never the secondary) and it would not occur on a single monitor system.

Double buffering the control, using the above example, solved our problem. We greatly appreciated your help.

Just to add what we did to fix this issue: We upgraded to the latest Nvidia drivers solved the problem. No code had to be rewritten.

For completeness, the card was an Nvidia Quadro NVS 290 with drivers dated March 2008 (v. 169). Upgrading to the latest (v. 182 dated Feb 2009) significantly improved the paint events for all my controls, especially for the DataGridView.

This issue was not seen on any ATI cards (where development occurs).

Here is some code that sets the property using reflection, without subclassing as Benoit suggests.

typeof(DataGridView).InvokeMember(
"DoubleBuffered",
BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetProperty,
null,
myDataGridViewObject,
new object[] { true });

I found a solution to the problem. Go to troubleshoot tab in the advanced display properties and check the hardware acceleration slider. When I got my new company PC from IT, it was set to one tick from full and I didn't have any problems with datagrids. Once I updated the video card driver and set it to full, painting of datagrid controls became very slow. So I reset it back to where it was and the problem went away.

Hope this trick works for you as well.

For people searching how to do it in VB.NET, here is the code:

DataGridView1.GetType.InvokeMember("DoubleBuffered", Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance Or System.Reflection.BindingFlags.SetProperty, Nothing, DataGridView1, New Object() {True})

The answer to this worked for me too. I thought I would add a refinement that I think should be standard practise for anyone implementing the solution.

The solution works well except when the UI is being run as a client session under remote desktop, especially where the available network bandwidth is low. In such a case, performance can be made worse by the use of double-buffering. Therefore, I suggest the following as a more complete answer:

class CustomDataGridView: DataGridView
{
public CustomDataGridView()
{
// if not remote desktop session then enable double-buffering optimization
if (!System.Windows.Forms.SystemInformation.TerminalServerSession)
DoubleBuffered = true;
}
}

For more details, refer to Detecting remote desktop connection

Adding to previous posts, for Windows Forms applications this is what I use for DataGridView components to make them fast. The code for the class DrawingControl is below.

DrawingControl.SetDoubleBuffered(control)
DrawingControl.SuspendDrawing(control)
DrawingControl.ResumeDrawing(control)

Call DrawingControl.SetDoubleBuffered(control) after InitializeComponent() in the constructor.

Call DrawingControl.SuspendDrawing(control) before doing big data updates.

Call DrawingControl.ResumeDrawing(control) after doing big data updates.

These last 2 are best done with a try/finally block. (or even better rewrite the class as IDisposable and call SuspendDrawing() in the constructor and ResumeDrawing() in Dispose().)

using System.Runtime.InteropServices;


public static class DrawingControl
{
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, Int32 wMsg, bool wParam, Int32 lParam);


private const int WM_SETREDRAW = 11;


/// <summary>
/// Some controls, such as the DataGridView, do not allow setting the DoubleBuffered property.
/// It is set as a protected property. This method is a work-around to allow setting it.
/// Call this in the constructor just after InitializeComponent().
/// </summary>
/// <param name="control">The Control on which to set DoubleBuffered to true.</param>
public static void SetDoubleBuffered(Control control)
{
// if not remote desktop session then enable double-buffering optimization
if (!System.Windows.Forms.SystemInformation.TerminalServerSession)
{


// set instance non-public property with name "DoubleBuffered" to true
typeof(Control).InvokeMember("DoubleBuffered",
System.Reflection.BindingFlags.SetProperty |
System.Reflection.BindingFlags.Instance |
System.Reflection.BindingFlags.NonPublic,
null,
control,
new object[] { true });
}
}


/// <summary>
/// Suspend drawing updates for the specified control. After the control has been updated
/// call DrawingControl.ResumeDrawing(Control control).
/// </summary>
/// <param name="control">The control to suspend draw updates on.</param>
public static void SuspendDrawing(Control control)
{
SendMessage(control.Handle, WM_SETREDRAW, false, 0);
}


/// <summary>
/// Resume drawing updates for the specified control.
/// </summary>
/// <param name="control">The control to resume draw updates on.</param>
public static void ResumeDrawing(Control control)
{
SendMessage(control.Handle, WM_SETREDRAW, true, 0);
control.Refresh();
}
}

Best!:

Private Declare Function SendMessage Lib "user32" _
Alias "SendMessageA" _
(ByVal hWnd As Integer, ByVal wMsg As Integer, _
ByVal wParam As Integer, ByRef lParam As Object) _
As Integer


Const WM_SETREDRAW As Integer = &HB


Public Sub SuspendControl(this As Control)
SendMessage(this.Handle, WM_SETREDRAW, 0, 0)
End Sub


Public Sub ResumeControl(this As Control)
RedrawControl(this, True)
End Sub


Public Sub RedrawControl(this As Control, refresh As Boolean)
SendMessage(this.Handle, WM_SETREDRAW, 1, 0)
If refresh Then
this.Refresh()
End If
End Sub