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

时间:2008-09-23 01:01:09

标签: .net winforms performance datagridview nvidia

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

我的双显示器系统上的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;
    }
}

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

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

9 个答案:

答案 0 :(得分:63)

您只需要根据DataGridView创建一个自定义类,以便启用其DoubleBuffering。就是这样!


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

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

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

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

答案 1 :(得分:59)

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

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

答案 2 :(得分:13)

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

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

答案 3 :(得分:8)

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

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

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

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

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

最后2个最好用try / finally块完成。 (或者甚至更好地将课程改写为IDisposable并在构造函数中调用SuspendDrawing()并在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();
    }
}

答案 4 :(得分: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;
    } 
}

有关详细信息,请参阅Detecting remote desktop connection

答案 5 :(得分:1)

只是添加我们为解决此问题所做的工作:我们升级到最新的Nvidia驱动程序解决了这个问题。没有代码必须重写。

为了完整起见,该卡是Nvidia Quadro NVS 290,其驱动程序日期为2008年3月(第169页)。升级到最新版本(2009年2月发布的第182页)显着改善了我所有控件的绘制事件,特别是对于DataGridView。

在任何ATI卡上都没有出现此问题(发生了开发)。

答案 6 :(得分:1)

我找到了问题的解决方案。转到高级显示属性中的疑难解答选项卡,然后检查硬件加速滑块。当我从IT部门获得新的公司PC时,它被设置为完整的一个刻度线,我对datagrids没有任何问题。一旦我更新了视频卡驱动程序并将其设置为完整,数据网格控件的绘制变得非常慢。所以我将它重置回原来的位置并且问题消失了。

希望这个技巧也适合你。

答案 7 :(得分:1)

最佳:

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

答案 8 :(得分:0)

我们在双显示器系统上使用.NET 3.0和DataGridView遇到了类似的问题。

我们的应用程序将显示灰色背景的网格,表示无法更改单元格。在选择“改变设置”按钮时,程序将改变单元格的背景颜色为白色以向用户指示可以改变单元格文本。 “取消”按钮会将上述单元格的背景颜色更改为灰色。

随着背景颜色的改变,会出现闪烁,对具有相同行数和列数的默认大小网格的简要印象。此问题仅发生在主监视器上(从不是辅助监视器),并且不会发生在单个监视器系统上。

使用上面的例子双重缓冲控件解决了我们的问题。我们非常感谢您的帮助。