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

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

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

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

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

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


person Martin Marconcini    schedule 07.05.2009    source источник
comment
Вам также следует попробовать приостановить / возобновить операции перерисовки ... см. Мой обновленный ответ.   -  person Adam Robinson    schedule 07.05.2009
comment
У вас определенно проблемы с производительностью. Я не думаю, что рисование градиентов и четвертей круга должно быть таким медленным.   -  person DonkeyMaster    schedule 07.05.2009
comment
Ну, как я уже сказал, библиотека пользовательского интерфейса не самая быстрая, но у нас также есть много кода рисования GDI +, чтобы кнопка выглядела так, как мы хотим. Это не просто рисование дуги x 4 и раскрашивание поверхности градиентом. Думаю, нам тоже придется поработать над этим ... но мне было интересно, есть ли способ ускорить это. Если он удваивает буферы, он должен быстро отображать его при переворачивании, не так ли?   -  person Martin Marconcini    schedule 07.05.2009
comment
Я все еще изучаю проблему, скоро сообщу. Спасибо за идеи.   -  person Martin Marconcini    schedule 16.06.2009


Ответы (10)


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

Один из способов, который мы видели, чтобы «исправить» это, - это полностью приостановить рисование элемента управления до тех пор, пока мы не будем готовы к работе. Для этого мы отправляем элементу управления сообщение 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);
person Judah Gabriel Himango    schedule 07.05.2009
comment
Это С ++? У меня нет UsafeSharedNativeMethods… мне не хватает ссылки? (хе-хе-хе, я звучу как компилятор vcs);) - person Martin Marconcini; 07.05.2009
comment
Это может быть C ++, но это та же концепция, которую я включил в ссылку в своем ответе. - person Adam Robinson; 07.05.2009
comment
Это не C ++, это очень стандартный способ наименования вашего класса PInvoke. Вы должны создать свой собственный статический класс и добавить метод PInvoke под названием SendMessage. WindowMessages, очевидно, также является определяемым пользователем перечислением со значениями, правильно указанными из Win32 API. Наконец, Адам, ты еще не опубликовал это, ты просто разместил ссылку, и твое отношение довольно раздражает. - person Jon Grant; 16.06.2009

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

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

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

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

person DevComponents - Denis Basaric    schedule 18.05.2009

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

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

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

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

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

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

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

РЕДАКТИРОВАТЬ:

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

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

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

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

person DonkeyMaster    schedule 07.05.2009
comment
Если вы хотите пойти по этому пути, у меня есть еще несколько идей. (Понедельник, потому что 8 мая во Франции выходной) - person DonkeyMaster; 07.05.2009
comment
Будем думать обо всем этом. Tres bien;) Не праздник в Испании. Я расскажу вам еще немного о том, что я делаю, может быть, есть ОЧЕНЬ возможности для улучшения. ;) Спасибо. - person Martin Marconcini; 07.05.2009
comment
Этот совет о создании всего Control[] и его однократной установке с AddRange отлично решил мою проблему. Большое спасибо за это, так как не думаю, что я бы долго пробовал это самостоятельно;) - person julealgon; 27.12.2013

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

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

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

person Adam Robinson    schedule 07.05.2009
comment
Мы уже делаем это, а также ControlStyles.UserPaint; Думаю, мы перепробовали все возможные комбинации. Результаты всегда более или менее одинаковы: S - person Martin Marconcini; 07.05.2009
comment
Я испытываю то же поведение, что и OP другого вопроса, не помогло ... Я также заметил, что когда всплывающие меню перекрывают некоторые кнопки и заставляют их перерисовывать, перерисовка происходит особенно медленно (например, вы видите границы кнопок нарисованы, кнопка залита сплошным цветом, наконец, кнопка рисует свое изображение, затем следует за следующей кнопкой) Теперь он не только супер медленнее, но еще и имеет границу ... это странно: S - person Martin Marconcini; 07.05.2009
comment
В коде рисования настраиваемого элемента управления вы каждый раз перерисовываете весь элемент управления или только ту часть, которая должна быть? - person Adam Robinson; 07.05.2009
comment
Это похоже на то же самое, что и установка свойства DoubleBuffered в соответствии с linksource.microsoft.com/#System.Windows.Forms/winforms/. - person Mike Henry; 29.07.2019

Вы можете посмотреть ответ на мой вопрос, Как мне приостановить рисование для элемента управления и его дочерних элементов? для лучшей приостановки / возобновления.

person Simon    schedule 30.10.2009

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

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

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

person Community    schedule 17.08.2009
comment
Мне было весело играть с этим флагом, но в конце концов я не смог обойти ошибку, которая заставляла бы ядро ​​ЦП раскручиваться до 100% и оставаться там для любой формы с открытыми вкладками. - person Daniel Keogh; 28.09.2017

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

Под вашим контролем

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
}

Приведенный выше код был несколько протестирован.

person Community    schedule 16.07.2009

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

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

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;
        }
    }
}
person Kevin    schedule 04.02.2010

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

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

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

person Dennis Ecclestone    schedule 16.06.2009
comment
Спасибо, Деннис, я бы хотел переключиться на что-то подобное, но мы используем настраиваемый пользовательский интерфейс (уникальный), который является товарным знаком нашего приложения. Переход на другой набор элементов управления Windows нам не подходит. Наш интерфейс тактильно-ориентирован + TabletPC, поэтому мы используем только несколько элементов управления из WinCtl, но почти всегда делаем пользовательский рисунок поверх этого. - person Martin Marconcini; 18.06.2009
comment
@ Мартин, здесь мы сделали то же самое. Думаю, плохой выбор. Мы планируем перейти на WPF, но это большая работа .... - person Benjol; 02.12.2010
comment
@benjol да, изменение этого приложения на WPF - это месяцы работы! или больше! : S - person Martin Marconcini; 17.12.2010

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

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

person jm.    schedule 24.01.2016