Ужасная производительность перерисовки DataGridView на одном из моих двух экранов

StackOverflow https://stackoverflow.com/questions/118528

Вопрос

На самом деле я решил эту проблему, но публикую ее для потомков.

Я столкнулся с очень странной проблемой с DataGridView в моей системе с двумя мониторами.Проблема проявляется в ЧРЕЗВЫЧАЙНО медленном перерисовывании элемента управления (примерно 30 секунд на полную перекраску), но только когда он находится на одном из моих экранов.С другой стороны, скорость перекраски в норме.

У меня есть Nvidia 8800 GT с последними драйверами, не являющимися бета-версией (175.что-то).Это ошибка драйвера?Я оставлю это в воздухе, поскольку мне приходится жить с этой конкретной конфигурацией.(Однако на картах ATI этого не происходит ...)

Скорость рисования не имеет никакого отношения к содержимому ячейки, и пользовательское рисование вообще не улучшает производительность - даже при простом рисовании сплошного прямоугольника.

Позже я узнаю, что размещение ElementHost (из Системы.Windows.Формы.Пространство имен интеграции) в форме устраняет проблему.С этим не нужно возиться;он просто должен быть дочерним по отношению к форме, в которой также используется DataGridView.Его размер может быть изменен на (0, 0) до тех пор, пока Видимый свойство истинно.

Я не хочу явно добавлять зависимость .NET 3/3.5 в мое приложение;Я создаю метод для создания этого элемента управления во время выполнения (если это возможно), используя отражение.Это работает, и, по крайней мере, корректно завершается сбоем на машинах, у которых нет необходимой библиотеки - она просто снова становится медленной.

Этот метод также позволяет мне применять исправление во время работы приложения, облегчая просмотр того, что библиотеки WPF изменяют в моей форме (используя Spy ++).

После долгих проб и ошибок я заметил, что включение двойной буферизации в самом элементе управления (в отличие от просто формы) устраняет проблему!


Итак, вам просто нужно создать пользовательский класс на основе DataGridView, чтобы вы могли включить его двойную буферизацию.Вот и все!

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

Пока все мои экземпляры сетки используют эту пользовательскую версию, все хорошо.Если я когда-нибудь столкнусь с ситуацией, вызванной этим, когда я не смогу использовать решение подкласса (если у меня нет кода), я полагаю, я мог бы попытаться внедрить этот элемент управления в форму :) (хотя я, скорее всего, попытаюсь использовать отражение, чтобы принудительно включить свойство с двойным буфером извне, чтобы еще раз избежать зависимости).

Печально, что такая тривиально простая вещь отняла у меня так много времени...

Это было полезно?

Решение

Вам просто нужно создать пользовательский класс на основе DataGridView, чтобы вы могли включить его двойную буферизацию.Вот и все!


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

Пока все мои экземпляры сетки используют эту пользовательскую версию, все хорошо.Если я когда-нибудь столкнусь с ситуацией, вызванной этим, когда я не смогу использовать решение подкласса (если у меня нет кода), я полагаю, я мог бы попытаться внедрить этот элемент управления в форму :) (хотя я, скорее всего, попытаюсь использовать отражение, чтобы принудительно включить свойство DoubleBuffered извне, чтобы еще раз избежать зависимости).

Печально, что такая тривиально простая вещь отняла у меня так много времени...

Примечание:Превращаем ответ в ответ, чтобы вопрос можно было пометить как отвеченный

Другие советы

Вот некоторый код, который устанавливает свойство с использованием отражения, без выделения подклассов, как предлагает Бенуа.

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

Для людей, ищущих, как это сделать в VB.NET, вот код:

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

Добавляя к предыдущим постам, для приложений Windows Forms это то, что я использую для компонентов DataGridView, чтобы ускорить их работу.Код для класса DrawingControl приведен ниже.

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

Вызовите DrawingControl.Установите DoubleBuffered(элемент управления) после инициализации функции Component() в конструкторе.

Вызовите DrawingControl.Приостановите рисование (control) перед выполнением обновления больших данных.

Вызовите 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();
    }
}

Ответ на этот вопрос сработал и для меня.Я подумал, что хотел бы добавить уточнение, которое, по моему мнению, должно быть стандартной практикой для всех, кто реализует это решение.

Решение работает хорошо, за исключением случаев, когда пользовательский интерфейс запускается как клиентский сеанс под управлением удаленного рабочего стола, особенно там, где доступная пропускная способность сети невелика.В таком случае производительность может быть снижена за счет использования двойной буферизации.Поэтому я предлагаю следующее в качестве более полного ответа:

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

Для получения более подробной информации обратитесь к Обнаружение подключения к удаленному рабочему столу

Я нашел решение этой проблемы.Перейдите на вкладку Устранение неполадок в дополнительных свойствах дисплея и установите флажок аппаратное ускорение.Когда я получил свой новый корпоративный компьютер от IT, он был установлен на один галочку от full, и у меня не было никаких проблем с сетками данных.Как только я обновил драйвер видеокарты и установил его на полный, рисование элементов управления datagrid стало очень медленным.Поэтому я вернул его на прежнее место, и проблема исчезла.

Надеюсь, этот трюк сработает и у вас.

Просто добавлю, что мы сделали, чтобы исправить эту проблему:Мы обновили драйверы Nvidia до последней версии и решили проблему.Никакой код переписывать не пришлось.

Для полноты картины, картой была Nvidia Quadro NVS 290 с драйверами, датированными мартом 2008 (v.169).Обновление до последней версии (v.182 от февраля 2009) значительно улучшил события рисования для всех моих элементов управления, особенно для DataGridView.

Эта проблема не была замечена ни на каких картах ATI (где происходит разработка).

Лучший!:

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

Мы столкнулись с аналогичной проблемой при использовании .NET 3.0 и DataGridView в системе с двумя мониторами.

Наше приложение будет отображать сетку на сером фоне, указывая, что ячейки не могут быть изменены.При нажатии кнопки "изменить настройки" программа изменит цвет фона ячеек на белый, чтобы указать пользователю, что текст ячейки может быть изменен.Кнопка "Отмена" изменила бы цвет фона вышеупомянутых ячеек обратно на серый.

При изменении цвета фона возникало мерцание - краткое изображение сетки стандартного размера с тем же количеством строк и столбцов.Эта проблема возникала бы только на основном мониторе (никогда на вторичном) и не возникала бы в системе с одним монитором.

Двойная буферизация элемента управления, используя приведенный выше пример, решила нашу проблему.Мы очень признательны вам за помощь.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top