在我的两个屏幕之一上可怕的重绘DataGridView性能

Cor*_*oss 81 .net performance datagridview nvidia winforms

我实际上已经解决了这个问题,但我是为后人发布的.

我的双显示器系统上的DataGridView遇到了一个非常奇怪的问题.该问题表现为对控件的极快重复(如完全重绘的30秒),但只有在我的某个屏幕上时才会显示.在另一方面,重绘速度很好.

我有一个Nvidia 8800 GT,带有最新的非beta驱动程序(175件).这是驱动程序错误吗?我会把它留在空中,因为我必须忍受这种特殊的配置.(它不会发生在ATI卡上,但......)

绘画速度与单元格内容无关,自定义绘图根本不会改善性能 - 即使只绘制实心矩形也是如此.

我后来发现在表单上放置一个ElementHost(来自System.Windows.Forms.Integration命名空间)可以解决问题.它不必被搞砸; 它只需要是DataGridView所在的表单的子级.只要Visible属性为true ,就可以将其大小调整为(0,0).

我不想明确地将.NET 3/3.5依赖项添加到我的应用程序中; 我创建了一个方法来在运行时创建此控件(如果可以)使用反射.它工作正常,至少它在没有所需库的机器上优雅地失败 - 它只是变得缓慢.

这个方法还允许我在应用程序运行时应用修复,这样可以更容易地看到WPF库在我的表单上发生了什么变化(使用Spy ++).

经过大量的试验和错误,我注意到在控件本身上启用双缓冲(而不仅仅是表单)可以解决问题!


因此,您只需要根据DataGridView创建一个自定义类,以便启用其DoubleBuffering.而已!

class CustomDataGridView: DataGridView
{
    public CustomDataGridView()
    {
        DoubleBuffered = true;
    }
}
Run Code Online (Sandbox Code Playgroud)

只要我的所有网格实例都使用这个自定义版本,一切都很好.如果我遇到由此引起的情况,我无法使用子类解决方案(如果我没有代码),我想我可以尝试将该控件注入表单:)(虽然我'将更有可能尝试使用反射来强制从外部启用DoubleBuffered属性再次避免依赖).

令人遗憾的是,这么简单的事情花了我很多时间......

Ben*_*oit 64

您只需要根据DataGridView创建一个自定义类,以便启用其DoubleBuffering.而已!


class CustomDataGridView: DataGridView
{
    public CustomDataGridView()
    {
        DoubleBuffered = true;
    } 
}
Run Code Online (Sandbox Code Playgroud)

只要我的所有网格实例都使用这个自定义版本,一切都很好.如果我遇到由此引起的情况,我无法使用子类解决方案(如果我没有代码),我想我可以尝试将该控件注入表单:)(虽然我'将更有可能尝试使用反射来强制从外部启用DoubleBuffered属性再次避免依赖).

令人遗憾的是,这么简单的事情花了我很多时间......

注意:将答案作为答案,以便将问题标记为已回答


Bri*_*ink 59

下面是一些使用反射设置属性的代码,没有像Benoit建议的子类化.

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

  • 乐意效劳!我几乎没有发布它,因为这个问题已经有一年了. (3认同)
  • 只是给其他发现这个想法的人:这是“Control”类上的一个有用的扩展方法。`public static void ToggleDoubleBuffered(this Control control, bool isDoubleBuffered)`。 (2认同)

GEL*_*ELR 16

对于在VB.NET中搜索如何操作的人,这里是代码:

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


小智 8

添加到以前的帖子,对于Windows窗体应用程序,这是我用于DataGridView组件以使它们快速的.DrawingControl类的代码如下.

DrawingControl.SetDoubleBuffered(control)
DrawingControl.SuspendDrawing(control)
DrawingControl.ResumeDrawing(control)
Run Code Online (Sandbox Code Playgroud)

在构造函数中的InitializeComponent()之后调用DrawingControl.SetDoubleBuffered(control).

在进行大数据更新之前调用DrawingControl.SuspendDrawing(控件).

在进行大数据更新后调用DrawingControl.ResumeDrawing(控件).

最后2个最好用try/finally块完成.(或者甚至更好地重写类,IDisposableSuspendDrawing()在构造函数ResumeDrawing()中调用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();
    }
}
Run Code Online (Sandbox Code Playgroud)


Kev*_*Kev 6

答案对我也有用.我想我会添加一个改进,我认为应该是实施解决方案的任何人的标准做法.

该解决方案很有效,除非在远程桌面下作为客户端会话运行UI,尤其是在可用网络带宽较低的情况下.在这种情况下,通过使用双缓冲可以使性能变差.因此,我建议以下作为更完整的答案:

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

有关更多详细信息,请参阅检测远程桌面连接