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

81

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

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

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

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

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

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

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

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


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

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

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

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

Кори Росс
источник
1
У нас была аналогичная проблема с клиентами, у которых установлен Multimon . По какой-то причине, когда Multimon отключают, проблема уходит.
BlueRaja - Дэнни Пфлугхофт,
Кто-нибудь знает и может объяснить, почему это происходит и почему DoubleBuffered не может быть включен по умолчанию?
Vojtěch Dohnal

Ответы:

64

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


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

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

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

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

Бенуа
источник
1
Как вы можете сделать это с помощью интеграции Windows Forms для WPF?
Частично,
Спасибо за ответ. Как бы вы иногда не могли использовать решение подкласса? (Я не понял бит «если у меня нет кода»).
Dan W
Фантастика! Работает как шарм в моем проекте, который страдал от странного замедления как при
заполнении, так
61

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

typeof(DataGridView).InvokeMember(
   "DoubleBuffered", 
   BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetProperty,
   null, 
   myDataGridViewObject, 
   new object[] { true });
Брайан Энсинк
источник
3
Рад помочь! Я его почти не публиковал, потому что этому вопросу был уже год.
Брайан Энсинк,
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})
GELR
источник
10

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

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

Вызовите DrawingControl.SetDoubleBuffered (control) после InitializeComponent () в конструкторе.

Вызовите DrawingControl.SuspendDrawing (элемент управления) перед выполнением больших обновлений данных.

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

Эти последние 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();
    }
}
brtmckn
источник
7

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

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

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

Дополнительные сведения см. В разделе «Обнаружение подключения к удаленному рабочему столу».

Кев
источник
1

Нашел решение проблемы. Перейдите на вкладку устранения неполадок в расширенных свойствах дисплея и проверьте ползунок аппаратного ускорения. Когда я получил от ИТ-отдела свой новый корпоративный компьютер, он был установлен на одну отметку от полной, и у меня не было никаких проблем с сетями данных. Как только я обновил драйвер видеокарты и установил его на полную, отрисовка элементов управления сеткой данных стала очень медленной. Поэтому я вернул его на место, и проблема исчезла.

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


источник
1

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

Для полноты картины это была карта Nvidia Quadro NVS 290 с драйверами от марта 2008 г. (v. 169). Обновление до последней версии (версия 182 от февраля 2009 г.) значительно улучшило события рисования для всех моих элементов управления, особенно для 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
user3727004
источник
0

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

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

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

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


источник