Horrible redraw performance of the DataGridView on one of my two screens

痴心易碎 提交于 2019-11-26 21:36:35

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

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 });
GELR

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})
brtmckn

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();
    }
}
Kev

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

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.

Richard Morgan

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).

user3727004

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

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!