84

我实际上已经解决了这个问题,但我将其发布为后代。

我的双显示器系统上的 DataGridView 遇到了一个非常奇怪的问题。该问题表现为控件的极慢重绘(例如完全重绘需要 30 秒),但仅当它位于我的一个屏幕上时。另一方面,重绘速度很好。

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

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

后来我发现在表单上放置一个 ElementHost(来自 System.Windows.Forms.Integration 命名空间)可以解决问题。它不必被弄乱;它只需要是 DataGridView 也打开的表单的子项。只要Visible属性为真,它就可以调整为 (0, 0)。

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

此方法还允许我在应用程序运行时申请修复,从而更容易查看 WPF 库在我的表单上发生的变化(使用 Spy++)。

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


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

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

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

可悲的是,这么简单的事情占用了我这么多时间……

4

9 回答 9

66

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


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

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

可悲的是,这么简单的事情占用了我这么多时间……

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

于 2008-10-01T12:41:14.397 回答
61

这是一些使用反射设置属性的代码,没有像 Benoit 建议的那样进行子类化。

typeof(DataGridView).InvokeMember(
   "DoubleBuffered", 
   BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetProperty,
   null, 
   myDataGridViewObject, 
   new object[] { true });
于 2009-10-01T19:49:51.043 回答
20

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

DataGridView1.GetType.InvokeMember("DoubleBuffered", Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance Or System.Reflection.BindingFlags.SetProperty, Nothing, DataGridView1, New Object() {True})
于 2012-04-24T18:52:47.950 回答
11

添加到以前的帖子中,对于 Windows 窗体应用程序,这是我用于 DataGridView 组件以使其快速运行的方法。DrawingControl 类的代码如下。

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

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

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

大数据更新后调用 DrawingControl.ResumeDrawing(control)。

最后两个最好用 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();
    }
}
于 2013-05-18T15:15:03.437 回答
7

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

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

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

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

于 2013-04-13T16:45:37.523 回答
1

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

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

在任何 ATI 卡(发生开发的地方)上都没有发现此问题。

于 2009-02-26T22:35:41.230 回答
1

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

希望这个技巧也适用于你。

于 2009-10-07T20:28:17.023 回答
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
于 2014-06-10T16:54:18.120 回答
0

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

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

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

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

于 2008-10-07T19:45:51.177 回答