Schreckliche Neuzeichnungsleistung von DataGridView auf einem meiner beiden Bildschirme


81

Ich habe das tatsächlich gelöst, aber ich poste es für die Nachwelt.

Ich hatte ein sehr merkwürdiges Problem mit DataGridView auf meinem Dual-Monitor-System. Das Problem äußert sich in einem EXTREM langsamen Repaint des Steuerelements ( wie 30 Sekunden für ein vollständiges Repaint ), jedoch nur, wenn es sich auf einem meiner Bildschirme befindet. Auf der anderen Seite ist die Repaint-Geschwindigkeit in Ordnung.

Ich habe einen Nvidia 8800 GT mit den neuesten Nicht-Beta-Treibern (175. etwas). Ist es ein Treiberfehler? Ich werde das in der Luft lassen, da ich mit dieser speziellen Konfiguration leben muss. (Bei ATI-Karten ist dies jedoch nicht der Fall ...)

Die Malgeschwindigkeit hat nichts mit dem Zelleninhalt zu tun, und das benutzerdefinierte Zeichnen verbessert die Leistung überhaupt nicht - selbst wenn nur ein festes Rechteck gemalt wird.

Ich finde später heraus, dass das Platzieren eines ElementHost (aus dem System.Windows.Forms.Integration-Namespace) im Formular das Problem behebt. Es muss nicht durcheinander gebracht werden; Es muss nur ein untergeordnetes Element der Form sein, in der sich auch DataGridView befindet. Die Größe kann auf (0, 0) geändert werden, solange die Visible- Eigenschaft wahr ist.

Ich möchte meiner Anwendung die .NET 3 / 3.5-Abhängigkeit nicht explizit hinzufügen. Ich mache eine Methode, um dieses Steuerelement zur Laufzeit (wenn es kann) unter Verwendung von Reflektion zu erstellen. Es funktioniert, und zumindest schlägt es auf Computern, die nicht über die erforderliche Bibliothek verfügen, ordnungsgemäß fehl - es wird einfach wieder langsam.

Mit dieser Methode kann ich auch Korrekturen anwenden, während die App ausgeführt wird, sodass ich leichter sehen kann, was die WPF-Bibliotheken in meinem Formular ändern (mit Spy ++).

Nach vielen Versuchen und Irrtümern stelle ich fest, dass das Aktivieren der doppelten Pufferung auf dem Steuerelement selbst (im Gegensatz zum Formular) das Problem behebt!


Sie müssen also nur eine benutzerdefinierte Klasse erstellen, die auf DataGridView basiert, damit Sie deren DoubleBuffering aktivieren können. Das ist es!

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

Solange alle meine Instanzen des Rasters diese benutzerdefinierte Version verwenden, ist alles in Ordnung. Wenn ich jemals auf eine dadurch verursachte Situation stoße, in der ich die Unterklassenlösung nicht verwenden kann (wenn ich den Code nicht habe), könnte ich versuchen, dieses Steuerelement in das Formular einzufügen :) ( obwohl ich ' Es ist wahrscheinlicher, dass Sie versuchen, die DoubleBuffered-Eigenschaft mithilfe von Reflection von außen zu aktivieren, um die Abhängigkeit erneut zu vermeiden .

Es ist traurig, dass so eine trivial einfache Sache so viel Zeit in Anspruch genommen hat ...


1
Wir hatten ein ähnliches Problem mit Clients, auf denen Multimon installiert ist. Aus welchem ​​Grund auch immer, wenn sie Multimon ausschalten, verschwindet das Problem.
BlueRaja - Danny Pflughoeft

Jeder, der weiß und erklären kann, warum dies geschieht und warum DoubleBuffered nicht standardmäßig aktiviert werden kann?
Vojtěch Dohnal

Antworten:


64

Sie müssen nur eine benutzerdefinierte Klasse erstellen, die auf DataGridView basiert, damit Sie das DoubleBuffering aktivieren können. Das ist es!


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

Solange alle meine Instanzen des Rasters diese benutzerdefinierte Version verwenden, ist alles in Ordnung. Wenn ich jemals auf eine dadurch verursachte Situation stoße, in der ich die Unterklassenlösung nicht verwenden kann (wenn ich den Code nicht habe), könnte ich versuchen, dieses Steuerelement in das Formular einzufügen :) (obwohl ich ' Es ist wahrscheinlicher, dass Sie versuchen, die DoubleBuffered-Eigenschaft mithilfe von Reflection von außen einzuschalten, um die Abhängigkeit erneut zu vermeiden.

Es ist traurig, dass so eine trivial einfache Sache so viel Zeit in Anspruch genommen hat ...

Hinweis: Machen Sie die Antwort zu einer Antwort, damit die Frage als beantwortet markiert werden kann


1
Wie können Sie dies mit Windows Forms Integration für WPF tun?
Teilweise

Danke für die Antwort. Wie können Sie die Unterklassenlösung manchmal nicht verwenden? (Ich habe das Bit "Wenn ich den Code nicht habe" nicht verstanden).
Dan W

Fantastisch! Funktioniert wie ein Zauber in meinem Projekt, der sowohl beim Auffüllen als auch beim Scrollen der Tabelle unter einer merkwürdigen Verlangsamung litt (:
Knut

61

Hier ist ein Code, der die Eigenschaft mithilfe von Reflection festlegt, ohne Unterklassen, wie von Benoit vorgeschlagen.

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

3
Froh, dass ich Helfen kann! Ich habe es fast nicht gepostet, weil diese Frage bereits ein Jahr alt war.
Brian Ensink

1
Nein, es wird in Zukunft immer jemandem helfen, wie vielleicht sogar mir, der gerade diesen Thread von Google gefunden hat. Vielen Dank! Übrigens, ist es vorzuziehen, dies in den Abschnitt Form1_Load aufzunehmen?
Dan W

2
Nur um jemand anderem, der dies findet, eine Idee zu geben: Dies ist eine nützliche Erweiterungsmethode für die ControlKlasse. public static void ToggleDoubleBuffered(this Control control, bool isDoubleBuffered).
Anthony

18

Für Leute, die suchen, wie es in VB.NET geht, ist hier der Code:

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

10

Wenn ich zu früheren Beiträgen hinzufüge, verwende ich dies für Windows Forms-Anwendungen für DataGridView-Komponenten, um sie schnell zu machen. Der Code für die Klasse DrawingControl ist unten.

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

Rufen Sie DrawingControl.SetDoubleBuffered (Steuerelement) nach InitializeComponent () im Konstruktor auf.

Rufen Sie DrawingControl.SuspendDrawing (Steuerelement) auf, bevor Sie Big Data-Updates durchführen.

Rufen Sie DrawingControl.ResumeDrawing (Steuerelement) auf, nachdem Sie Big Data-Updates durchgeführt haben.

Diese letzten beiden werden am besten mit einem try / finally-Block ausgeführt. (oder noch besser die Klasse umschreiben als IDisposableund SuspendDrawing()den Konstruktor und ResumeDrawing()in aufrufen 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

Die Antwort darauf hat auch bei mir funktioniert. Ich dachte, ich würde eine Verfeinerung hinzufügen, die meiner Meinung nach Standard für jeden sein sollte, der die Lösung implementiert.

Die Lösung funktioniert gut, außer wenn die Benutzeroberfläche als Clientsitzung unter Remotedesktop ausgeführt wird, insbesondere wenn die verfügbare Netzwerkbandbreite gering ist. In einem solchen Fall kann die Leistung durch die Verwendung von Doppelpufferung verschlechtert werden. Daher schlage ich Folgendes als vollständigere Antwort vor:

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

Weitere Informationen finden Sie unter Erkennen der Remotedesktopverbindung


1

Ich habe eine Lösung für das Problem gefunden. Wechseln Sie in den erweiterten Anzeigeeigenschaften zur Registerkarte Fehlerbehebung, und überprüfen Sie den Schieberegler für die Hardwarebeschleunigung. Als ich meinen neuen Firmen-PC von der IT bekam, war er auf einen Tick von voll eingestellt und ich hatte keine Probleme mit Datagrids. Nachdem ich den Grafikkartentreiber aktualisiert und auf "Voll" gesetzt hatte, wurde das Malen der Datagrid-Steuerelemente sehr langsam. Also habe ich es wieder auf den Stand zurückgesetzt und das Problem ist behoben.

Hoffe, dieser Trick funktioniert auch für Sie.


1

Nur um hinzuzufügen, was wir getan haben, um dieses Problem zu beheben: Wir haben ein Upgrade auf die neuesten Nvidia-Treiber durchgeführt, um das Problem zu beheben. Es musste kein Code neu geschrieben werden.

Der Vollständigkeit halber handelte es sich bei der Karte um eine Nvidia Quadro NVS 290 mit Treibern vom März 2008 (Version 169). Durch das Upgrade auf die neueste Version (Version 182 vom Februar 2009) wurden die Malereignisse für alle meine Steuerelemente, insbesondere für DataGridView, erheblich verbessert.

Dieses Problem wurde auf keiner ATI-Karte festgestellt (wo die Entwicklung stattfindet).


1

Beste!:

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

Bei der Verwendung von .NET 3.0 und DataGridView auf einem System mit zwei Monitoren ist ein ähnliches Problem aufgetreten.

Unsere Anwendung würde das Raster mit einem grauen Hintergrund anzeigen, was darauf hinweist, dass die Zellen nicht geändert werden konnten. Bei Auswahl einer Schaltfläche "Einstellungen ändern" ändert das Programm die Hintergrundfarbe der weißen Zellen, um dem Benutzer anzuzeigen, dass der Zellentext geändert werden kann. Eine Schaltfläche "Abbrechen" würde die Hintergrundfarbe der oben genannten Zellen wieder in Grau ändern.

Wenn sich die Hintergrundfarbe ändert, flackert es, ein kurzer Eindruck eines Rasters mit Standardgröße mit der gleichen Anzahl von Zeilen und Spalten. Dieses Problem würde nur auf dem primären Monitor (niemals auf dem sekundären) und nicht auf einem einzelnen Monitorsystem auftreten.

Die doppelte Pufferung der Steuerung anhand des obigen Beispiels löste unser Problem. Wir haben Ihre Hilfe sehr geschätzt.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.