Winforms: SuspendLayout / ResumeLayout не достаточно? - PullRequest
26 голосов
/ 07 мая 2009

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

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

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

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

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

Ответы [ 10 ]

12 голосов
/ 07 мая 2009

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

Один из способов «исправить» - это полностью приостановить отрисовку элемента управления, пока мы не будем готовы к работе. Для этого мы отправляем сообщение 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);
11 голосов

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

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

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

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

9 голосов
/ 07 мая 2009

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

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

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

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

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

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

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

EDIT:

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

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

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

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

4 голосов
/ 07 мая 2009

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

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

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

2 голосов
/ 30 октября 2009

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

2 голосов
/ 18 августа 2009

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

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

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

2 голосов
/ 16 июля 2009

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

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

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
}

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

0 голосов
/ 24 января 2016

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

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

0 голосов
/ 04 февраля 2010

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

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

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;
        }
    }
}
0 голосов
/ 16 июня 2009

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

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

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

...