Winforms: SuspendLayout/ResumeLayout недостаточно?

У меня есть библиотека из нескольких "настраиваемых элементов управления". По существу у нас есть собственные кнопки, круглые угловые панели и несколько групповых ящиков с определенной пользовательской краской. Несмотря на "математику" в методах OnPaint, элементы управления довольно стандартизированы. В большинстве случаев все, что мы делаем, это рисовать округлые углы и добавлять градиент к фону. Мы используем GDI + для всего этого.

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

Я уверен, что наши кнопки не самые быстрые на земле, но мой вопрос: если двойной буфер "on", не все, что перерисовывается в фоновом режиме, а подсистема Windows должна показывать результаты "мгновенно" "?

С другой стороны, если существует "сложный" цикл foreach, который будет создавать метки, добавьте их на панель (с двойной буферизацией) и измените их свойства, если мы приостановим работу панели перед циклом и возобновим макет панели, когда цикл закончился, не должны ли все эти элементы управления (метки и кнопки) появляться "почти мгновенно"? Это не так, вы можете увидеть заполняемую панель.

Любая идея, почему этого не происходит? Я знаю, что это сложно оценить без примера кода, но это сложно воспроизвести. Я мог бы сделать видео с камерой, но поверьте мне, это не так быстро:)

Ответ 1

Мы тоже видели эту проблему.

Один из способов, по которым мы решили "исправить", - полностью приостановить рисование элемента управления до тех пор, пока мы не будем готовы. Для этого мы отправляем сообщение WM_SETREDRAW в элемент управления:

// Note that WM_SetRedraw = 0XB

// Suspend drawing.
UnsafeSharedNativeMethods.SendMessage(handle, WindowMessages.WM_SETREDRAW, IntPtr.Zero, IntPtr.Zero);

...

// Resume drawing.
UnsafeSharedNativeMethods.SendMessage(handle, WindowMessages.WM_SETREDRAW, new IntPtr(1), IntPtr.Zero);

Ответ 2

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

Windows Forms не использует реальную прозрачность, а использует "поддельный". Каждый вызов детской контрольной краски генерирует вызов краски для родителя, поэтому родитель может рисовать свой фон, над которым дочерний элемент управления рисует его содержимое, чтобы он выглядел прозрачным.

Итак, если у вас есть 50 дочерних элементов управления, которые будут генерировать дополнительные 50 вызовов краски для родительского элемента управления для рисования фона. И поскольку градиенты, как правило, медленнее, вы увидите ухудшение производительности.

Надеюсь, что это поможет.

Ответ 3

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

цикл foreach, который будет создавать метки, добавьте их в панель (с двойной буферизацией) и изменить их свойства

Если это будет сделано, то есть место для улучшения. Сначала создайте все свои ярлыки, измените их свойства, и когда они все готовы, добавьте их на панель: Panel.Controls.AddRange(Control[])

В большинстве случаев все, что мы делаем, это рисовать закругленные углы и добавить градиент на задний план

Вы делаете то же самое снова и снова? Как генерируются ваши градиенты? Написание изображения не может быть таким медленным. Мне когда-то приходилось создавать градиент 1680x1050 в памяти, и это было очень быстро, например, слишком быстро для Stopwatch, поэтому рисовать градиент не может быть так сложно.

Мой совет - попытаться кэшировать некоторые вещи. Откройте Paint, нарисуйте свои углы и сохраните на диск или создайте изображение в памяти только один раз. Затем загрузите (и измените размер) по мере необходимости. То же самое для градиента.

Даже если разные кнопки имеют разные цвета, но один и тот же мотив, вы можете создать растровое изображение с помощью Paint или любой другой, а во время выполнения загрузите его и умножьте значения Color другим цветом.

EDIT:

если мы приостановим работу панели до цикл и возобновление макета панели при завершении цикла

Это не то, что для SuspendLayout и ResumeLayout. Они приостанавливают логику компоновки, то есть автоматическое позиционирование элементов управления. Наиболее важны для FlowLayoutPanel и TableLayoutPanel.

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

Двойное буферирование: Это очень просто, пара строк кода. В событии paint визуализируйте растровое изображение вместо рендеринга на объект Graphics, а затем нарисуйте это растровое изображение на объект Graphics.

Ответ 4

В дополнение к свойству DoubleBuffered также попробуйте добавить это в свой конструктор управления:

SetStyle(ControlStyles.OptimizedDoubleBuffer | 
         ControlStyles.AllPaintingInWmPaint, true);

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

Ответ 5

Возможно, сначала нарисуйте только "видимый" (закрытый) буфер управления, а затем визуализируйте его:

В вашем управлении

BufferedGraphicsContext gfxManager;
BufferedGraphics gfxBuffer;
Graphics gfx;

Функция установки графики

private void InstallGFX(bool forceInstall)
{
    if (forceInstall || gfxManager == null)
    {
        gfxManager = BufferedGraphicsManager.Current;
        gfxBuffer = gfxManager.Allocate(this.CreateGraphics(), new Rectangle(0, 0, Width, Height));
        gfx = gfxBuffer.Graphics;
    }
}

В методе рисования

protected override void OnPaint(PaintEventArgs e)
{
    InstallGFX(false);
    // .. use GFX to draw
    gfxBuffer.Render(e.Graphics);
}

В своем методе изменения размера

protected override void OnSizeChanged(EventArgs e)
{
    base.OnSizeChanged(e);
    InstallGFX(true); // To reallocate drawing space of new size
}

Вышеупомянутый код был несколько протестирован.

Ответ 6

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

Обратите внимание, что обычно, если вы не испортили некоторые стили окон, каждый элемент управления Windows Form отвечает за само рисование. То есть каждый элемент управления получает трещины в сообщениях WM_ PAINT, WM_NCPAINT, WM_ERASEBKGND и т.д. И обрабатывает эти сообщения независимо. Это означает, что двойная буферизация применяется только к одному элементу управления, с которым вы имеете дело. Чтобы немного приблизиться к чистому, скомпонованному эффекту, вам нужно заботиться не только о своих пользовательских элемента управления, которые вы рисуете, но также о средствах управления контейнерами, на которых они размещены. Например, если у вас есть Form, который содержит GroupBox, который, в свою очередь, содержит несколько пользовательских рисованных кнопок, каждый из этих элементов управления должен иметь свойство DoubleBuffered, равное True. Обратите внимание, что это свойство защищено, поэтому это означает, что вы либо наследуете различные элементы управления (просто установите свойство двойной буферизации), либо используете отражение для установки защищенного свойства. Кроме того, не все средства управления Windows Form уважают свойство DoubleBuffered, так как внутренне некоторые из них являются просто обертки вокруг собственных "общих" элементов управления.

Существует способ установить скомпилированный флаг, если вы ориентируетесь на Windows XP (и, предположительно, позже). Существует стиль окна WS_ EX_ COMPOSITED. Я использовал его раньше, чтобы смешать результаты. Это плохо работает с гибридными приложениями WPF/WinForm, а также не очень хорошо работает с элементом управления DataGridView. Если вы идете по этому маршруту, убедитесь, что вы проводите много испытаний на разных машинах, потому что я видел странные результаты. В конце концов, я отказался от использования этого подхода.

Ответ 8

У меня было много подобных проблем в прошлом, и я решил, что это использовать сторонний набор пользовательских интерфейсов (то есть DevExpress), а не стандартные элементы управления Microsoft.

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

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

Ответ 9

У меня была такая же проблема с tablelayoutpanel при переключении пользовательских элементов управления, которые я хотел отобразить.

Я полностью избавился от мерцания, создав класс, который унаследовал таблицу, а затем включил doublebuffering.

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;

namespace myNameSpace.Forms.UserControls
{
    public class TableLayoutPanelNoFlicker : TableLayoutPanel
    {
        public TableLayoutPanelNoFlicker()
        {
            this.DoubleBuffered = true;
        }
    }
}

Ответ 10

Я видел, что плохие winforms мерцают на формах, где элементы управления ссылаются на отсутствующий шрифт.

Это, вероятно, не является обычным явлением, но стоит заглянуть, если вы пробовали все остальное.