Straszna wydajność odświeżania DataGridView na jednym z moich dwóch ekranów


81

Właściwie to rozwiązałem, ale wysyłam to dla potomności.

Napotkałem bardzo dziwny problem z DataGridView w moim systemie z dwoma monitorami. Problem objawia się BARDZO powolnym przemalowywaniem kontrolki ( np. 30 sekund w przypadku pełnego odmalowania ), ale tylko wtedy, gdy jest na jednym z moich ekranów. Z drugiej strony prędkość odświeżania jest dobra.

Mam Nvidię 8800 GT z najnowszymi sterownikami innymi niż beta (175. coś). Czy to błąd sterownika? Zostawię to w powietrzu, ponieważ muszę żyć w tej konkretnej konfiguracji. (Nie zdarza się to jednak na kartach ATI ...)

Szybkość malowania nie ma nic wspólnego z zawartością komórki, a niestandardowe rysowanie w ogóle nie poprawia wydajności - nawet podczas malowania zwykłego prostokąta.

Później dowiedziałem się, że umieszczenie elementu ElementHost (z przestrzeni nazw System.Windows.Forms.Integration) w formularzu rozwiązuje problem. Nie trzeba się z tym bawić; musi po prostu być elementem podrzędnym formularza, w którym znajduje się również DataGridView. Można zmienić jej rozmiar na (0, 0), o ile właściwość Visible ma wartość true.

Nie chcę jawnie dodawać zależności .NET 3 / 3.5 do mojej aplikacji; Tworzę metodę tworzenia tej kontrolki w czasie wykonywania (jeśli to możliwe) przy użyciu odbicia. Działa, a przynajmniej zawodzi wdzięcznie na maszynach, które nie mają wymaganej biblioteki - po prostu wraca do powolnego działania.

Ta metoda pozwala mi również zastosować poprawki, gdy aplikacja jest uruchomiona, dzięki czemu łatwiej jest zobaczyć, co biblioteki WPF zmieniają się w moim formularzu (przy użyciu Spy ++).

Po wielu próbach i błędach zauważyłem, że włączenie podwójnego buforowania w samym formancie (a nie tylko w formularzu) rozwiązuje problem!


Wystarczy więc utworzyć niestandardową klasę opartą na DataGridView, aby móc włączyć jej DoubleBuffering. Otóż ​​to!

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

Tak długo, jak wszystkie moje instancje gridu używają tej niestandardowej wersji, wszystko jest w porządku. Jeśli kiedykolwiek napotkam sytuację spowodowaną przez to, że nie będę w stanie użyć rozwiązania podklasy (jeśli nie mam kodu), przypuszczam, że mógłbym spróbować wstrzyknąć tę kontrolkę do formularza :) ( chociaż ja ' Bardziej prawdopodobne jest, że spróbujemy użyć odbicia, aby wymusić włączenie właściwości DoubleBuffered z zewnątrz, aby ponownie uniknąć zależności ).

To smutne, że tak banalnie prosta rzecz pochłonęła tyle mojego czasu ...


1
Mieliśmy podobny problem z klientami, którzy zainstalowali Multimon . Z jakiegoś powodu, kiedy wyłączają Multimon, problem znika.
BlueRaja - Danny Pflughoeft

Każdy wie i może wyjaśnić, dlaczego tak się dzieje i dlaczego DoubleBuffered nie może być domyślnie włączony?
Vojtěch Dohnal,

Odpowiedzi:


64

Wystarczy utworzyć niestandardową klasę opartą na DataGridView, aby móc włączyć jej DoubleBuffering. Otóż ​​to!


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

Tak długo, jak wszystkie moje instancje gridu używają tej niestandardowej wersji, wszystko jest w porządku. Jeśli kiedykolwiek napotkam sytuację spowodowaną przez to, że nie będę w stanie użyć rozwiązania podklasy (jeśli nie mam kodu), przypuszczam, że mógłbym spróbować wstrzyknąć tę kontrolkę do formularza :) (chociaż ja ' Bardziej prawdopodobne jest, że spróbujemy użyć odbicia, aby wymusić włączenie właściwości DoubleBuffered z zewnątrz, aby ponownie uniknąć zależności).

To smutne, że tak banalnie prosta rzecz pochłonęła tyle mojego czasu ...

Uwaga: Uczynienie odpowiedzi jako odpowiedź, aby pytanie można było oznaczyć jako odpowiedź


1
Jak można to zrobić za pomocą integracji Windows Forms dla WPF?
Częściowy

Dziękuję za odpowiedź. Jak czasami nie byłbyś w stanie użyć rozwiązania podklasy? (Nie rozumiem bitu „jeśli nie mam kodu”).
Dan W,

Fantastyczny! Działa jak urok w moim projekcie, który cierpiał z powodu dziwnego spowolnienia zarówno podczas zapełniania, jak i przewijania tabeli (:
knut

61

Oto kod, który ustawia właściwość za pomocą odbicia, bez podklas, jak sugeruje Benoit.

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

3
Miło, że mogłem pomóc! Prawie tego nie opublikowałem, ponieważ to pytanie miało już rok.
Brian Ensink,

1
Neah, zawsze pomoże to komuś w przyszłości, na przykład może nawet mnie, który właśnie znalazł ten wątek w Google. Dzięki! Btw, czy lepiej jest umieścić to w sekcji Form1_Load?
Dan W,

2
Tylko po to, aby dać pomysł komuś, kto to znajdzie: Jest to przydatna metoda rozszerzenia Controlklasy. public static void ToggleDoubleBuffered(this Control control, bool isDoubleBuffered).
Anthony

Czy można go umieścić na zdarzeniu obciążenia FOrm, w którym znajduje się widok siatki danych?
Arie

18

Dla osób szukających jak to zrobić w VB.NET, oto kod:

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

10

Dodając do poprzednich postów, w przypadku aplikacji Windows Forms używam tego dla składników DataGridView, aby je przyspieszyć. Kod dla klasy DrawingControl znajduje się poniżej.

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

Wywołaj DrawingControl.SetDoubleBuffered (control) po InitializeComponent () w konstruktorze.

Wywołaj DrawingControl.SuspendDrawing (control) przed wykonaniem aktualizacji dużych zbiorów danych.

Wywołaj DrawingControl.ResumeDrawing (control) po wykonaniu dużych aktualizacji danych.

Te ostatnie 2 najlepiej wykonać za pomocą bloku try / final. (lub jeszcze lepiej przepisz klasę jako IDisposablei wywołaj SuspendDrawing()w konstruktorze i ResumeDrawing()w 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

Odpowiedź na to zadziałała również dla mnie. Pomyślałem, że dodam udoskonalenie, które moim zdaniem powinno być standardową praktyką dla każdego wdrażającego to rozwiązanie.

Rozwiązanie działa dobrze, z wyjątkiem sytuacji, gdy interfejs użytkownika jest uruchamiany jako sesja klienta na zdalnym pulpicie, zwłaszcza gdy dostępna przepustowość sieci jest niska. W takim przypadku wydajność może ulec pogorszeniu przez zastosowanie podwójnego buforowania. Dlatego proponuję jako pełniejszą odpowiedź:

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

Aby uzyskać więcej informacji, zobacz Wykrywanie połączenia z pulpitem zdalnym


1

Znalazłem rozwiązanie problemu. Przejdź do zakładki rozwiązywania problemów w zaawansowanych właściwościach ekranu i sprawdź suwak przyspieszenia sprzętowego. Kiedy otrzymałem od IT mój nowy komputer firmowy, był ustawiony na jeden tick od pełnego i nie miałem żadnych problemów z datagridami. Kiedy zaktualizowałem sterownik karty graficznej i ustawiłem go na pełny, malowanie kontrolek datagrid stało się bardzo powolne. Więc zresetowałem go z powrotem do miejsca, w którym był i problem zniknął.

Mam nadzieję, że ta sztuczka zadziała również dla Ciebie.


1

Wystarczy dodać, co zrobiliśmy, aby rozwiązać ten problem: zaktualizowaliśmy sterowniki do najnowszych sterowników Nvidia, rozwiązując problem. Żaden kod nie musiał być przepisywany.

Dla kompletności, karta była Nvidia Quadro NVS 290 ze sterownikami datowanymi na marzec 2008 (v. 169). Aktualizacja do najnowszej (wersja 182 z lutego 2009 r.) Znacznie poprawiła zdarzenia malowania dla wszystkich moich kontrolek, zwłaszcza dla DataGridView.

Ten problem nie występował na żadnej karcie ATI (gdzie występuje rozwój).


1

Najlepsza!:

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

Podobny problem napotkaliśmy podczas korzystania z .NET 3.0 i DataGridView w systemie z dwoma monitorami.

Nasza aplikacja wyświetli siatkę na szarym tle, co oznacza, że ​​nie można zmienić komórek. Po wybraniu przycisku „zmień ustawienia” program zmienił kolor tła komórek na biały, aby wskazać użytkownikowi, że tekst komórki może zostać zmieniony. Przycisk „Anuluj” zmieniłby kolor tła wspomnianych komórek z powrotem na szary.

Gdy zmienił się kolor tła, wystąpiłoby migotanie, krótkie wrażenie siatki o domyślnym rozmiarze z taką samą liczbą wierszy i kolumn. Ten problem wystąpiłby tylko na głównym monitorze (nigdy na drugim) i nie wystąpiłby w systemie z jednym monitorem.

Podwójne buforowanie kontrolki na powyższym przykładzie rozwiązało nasz problem. Bardzo dziękujemy za pomoc.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.