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


81

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

我的双显示器系统上的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属性,以再次避免该依赖关系

可惜如此琐碎的事情占用了我很多时间...


1
对于安装了Multimon的客户端,我们也遇到类似的问题。无论出于何种原因,当他们关闭Multimon时,问题都会消失。
BlueRaja-Danny Pflughoeft

任何人都知道并可以解释为什么会发生这种情况,以及为什么默认情况下无法打开DoubleBuffered吗?
Vojtěch多纳尔

Answers:


64

您只需要基于DataGridView创建自定义类,即可启用其DoubleBuffering。而已!


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

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

可惜如此琐碎的事情占用了我很多时间...

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


1
您如何使用Windows Forms Integration for WPF做到这一点?
部分

谢谢你的回答。有时您如何无法使用子类解决方案?(我不理解“如果我没有代码”位)。
Dan W

太棒了!就像我的项目中的一种魅力一样,它在填充和滚动表格时都受到怪异的减速的困扰(:
knut 2015年

61

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

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

3
乐意效劳!我几乎没有发布它,因为这个问题已经有一年了。
Brian Ensink 09年

1
好的,将来,它总是会帮助刚刚从Google找到该线索的人,甚至我。谢谢!顺便说一句,是否最好将其放在Form1_Load部分中?
Dan W

2
只是给其他发现此想法的人一个想法:这是在Control类上有用的扩展方法。public static void ToggleDoubleBuffered(this Control control, bool isDoubleBuffered)
安东尼

可以将其放置在放置数据网格视图的FOrm加载事件中吗?
Arie,

18

对于在VB.NET中搜索如何执行此操作的人员,以下代码:

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

10

除了以前的文章,对于Windows窗体应用程序,这就是我使用DataGridView组件以使其快速运行的功能。类DrawingControl的代码如下。

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

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

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

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

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

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

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


1

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

希望这个技巧也对您有用。


1

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

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

在任何ATI卡(发生开发)上均未发现此问题。


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

0

在双监视器系统上使用.NET 3.0和DataGridView时,我们也遇到了类似的问题。

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

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.