Как исправить мерцание в пользовательских элементах управления
В моем приложении я постоянно перехожу с одного элемента управления на другой. Я создал нет. пользовательских элементов управления, но во время навигации мои элементы управления мерцают. Обновление занимает 1 или 2 секунды. Я пытался установить это
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
or
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.DoubleBuffer, true);
но это не помогло... Каждый элемент управления имеет одно и то же фоновое изображение с различными элементами управления. Так что решение для этого..
Благодарю.
12 ответов
Это не та вспышка, которую может решить двойная буферизация. Ни BeginUpdate, ни SuspendLayout. У вас слишком много элементов управления, BackgroundImage может сделать его намного хуже.
Это начинается, когда UserControl рисует сам. Он рисует BackgroundImage, оставляя дыры в окнах дочерних элементов управления. Каждый дочерний элемент управления получает сообщение для рисования, и они заполнят дыру содержимым окна. Когда у вас много элементов управления, эти отверстия некоторое время видны пользователю. Они обычно белые, плохо контрастируют с BackgroundImage, когда темно. Или они могут быть черными, если в форме установлено свойство Opacity или TransparencyKey, плохо контрастирующее с чем-либо.
Это довольно фундаментальное ограничение Windows Forms, оно застряло в способе, которым Windows отображает окна. Исправлено WPF, кстати, он не использует окна для дочерних элементов управления. То, что вы хотите, это двойная буферизация всей формы, включая дочерние элементы управления. Это возможно, проверьте мой код в этой теме для решения. Он имеет побочные эффекты и не увеличивает скорость рисования. Код прост, вставьте его в форму (не пользовательский элемент управления):
protected override CreateParams CreateParams {
get {
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x02000000; // Turn on WS_EX_COMPOSITED
return cp;
}
}
Есть много вещей, которые вы можете сделать, чтобы улучшить скорость рисования, до такой степени, что мерцание больше не заметно. Начните с решения BackgroundImage. Они могут быть очень дорогими, когда исходное изображение велико и его нужно сжать, чтобы соответствовать элементу управления. Измените свойство BackgroundImageLayout на "Плитка". Если это дает заметное ускорение, вернитесь к своей программе рисования и измените размер изображения, чтобы оно лучше соответствовало типичному размеру элемента управления. Или напишите код в методе OnResize() UC, чтобы создать копию изображения правильного размера, чтобы его не нужно было изменять каждый раз, когда элемент управления перерисовывается. Используйте для этой копии пиксельный формат Format32bppPArgb, он визуализируется примерно в 10 раз быстрее, чем любой другой пиксельный формат.
Следующее, что вы можете сделать, это предотвратить дыры, чтобы они были настолько заметны и плохо контрастировали с изображением. Вы можете отключить флаг стиля WS_CLIPCHILDREN для UC, флаг, который не позволяет UC рисовать в области, где находятся дочерние элементы управления. Вставьте этот код в код UserControl:
protected override CreateParams CreateParams {
get {
var parms = base.CreateParams;
parms.Style &= ~0x02000000; // Turn off WS_CLIPCHILDREN
return parms;
}
}
Дочерние элементы управления теперь будут рисовать себя поверх фонового изображения. Вы можете все еще видеть, как они рисуют сами по себе, но уродливая промежуточная белая или черная дыра не будет видна.
И последнее, но не менее важное: сокращение числа дочерних элементов управления - это всегда хороший подход для решения проблем с медленной прорисовкой. Переопределите событие OnPaint() в UC и нарисуйте то, что сейчас отображается у дочернего элемента. Конкретные Label и PictureBox очень расточительны. Удобно для указания и щелчка, но их легкая альтернатива (рисование строки или изображения) занимает только одну строку кода в вашем методе OnPaint().
Это реальная проблема, и ответ, который дал Ханс Пассант, отлично подходит для сохранения мерцания. Однако, как он упомянул, есть побочные эффекты, которые могут быть уродливыми (UI уродлив). Как уже говорилось, "Вы можете отключить флаг стиля WS_CLIPCHILDREN для UC", но это только отключает его для UC. Компоненты в главной форме все еще имеют проблемы.
Например, полоса прокрутки панели не рисует, потому что технически она находится в дочерней области. Однако дочерний компонент не рисует полосу прокрутки, поэтому он не закрашивается до тех пор, пока не наведет курсор мыши (или другое событие не сработает).
Кроме того, анимированные значки (смена значков в цикле ожидания) не работают. Удаление значков на tabPage.ImageKey не изменяет размеры / перерисовывает другие вкладки соответствующим образом.
Поэтому я искал способ отключить WS_CLIPCHILDREN при начальном рисовании, чтобы моя форма загружалась красиво, или, что еще лучше, включала его только при изменении размера формы с большим количеством компонентов.
Хитрость заключается в том, чтобы заставить приложение вызывать CreateParams с желаемым стилем WS_EX_COMPOSITED/WS_CLIPCHILDREN? Я нашел хак здесь ( http://www.angryhacker.com/blog/archive/2010/07/21/how-to-get-rid-of-flicker-on-windows-forms-applications.aspx) и это работает отлично. Спасибо AngryHacker!
Я поместил вызов TurnOnFormLevelDoubleBuffering() в форме события ResizeBegin. Вызов TurnOffFormLevelDoubleBuffering() в форме события ResizeEnd (или просто оставьте его WS_CLIPCHILDREN после того, как он изначально правильно нарисован.)
int originalExStyle = -1;
bool enableFormLevelDoubleBuffering = true;
protected override CreateParams CreateParams
{
get
{
if (originalExStyle == -1)
originalExStyle = base.CreateParams.ExStyle;
CreateParams cp = base.CreateParams;
if (enableFormLevelDoubleBuffering)
cp.ExStyle |= 0x02000000; // WS_EX_COMPOSITED
else
cp.ExStyle = originalExStyle;
return cp;
}
}
public void TurnOffFormLevelDoubleBuffering()
{
enableFormLevelDoubleBuffering = false;
this.MaximizeBox = true;
}
Если вы делаете какую-либо пользовательскую рисование в элементе управления (т.е. переопределяете OnPaint), вы можете попробовать двойную буферизацию самостоятельно.
Image image;
protected override OnPaint(...) {
if (image == null || needRepaint) {
image = new Bitmap(Width, Height);
using (Graphics g = Graphics.FromImage(image)) {
// do any painting in image instead of control
}
needRepaint = false;
}
e.Graphics.DrawImage(image, 0, 0);
}
И лишить вас права собственности NeedRepaint
В противном случае ответ выше с SuspendLayout и ResumeLayout, вероятно, то, что вы хотите.
Поместите приведенный ниже код в ваш конструктор или событие OnLoad, и если вы используете какой-то пользовательский пользовательский элемент управления, имеющий субэлементы управления, вам необходимо убедиться, что эти пользовательские элементы управления также имеют двойную буферизацию (хотя в документации MS говорится, что по умолчанию установлено значение true).
Если вы делаете пользовательский элемент управления, вы можете добавить этот флаг в свой ctor:
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
При желании вы можете использовать этот код в вашей форме / элементе управления:
foreach (Control control in Controls)
{
typeof(Control).InvokeMember("DoubleBuffered",
BindingFlags.SetProperty | BindingFlags.Instance | BindingFlags.NonPublic,
null, control, new object[] { true });
}
Мы перебираем все элементы управления в форме / элементе управления и обращаемся к их DoubleBuffered
свойство, а затем мы изменяем его на true, чтобы каждый элемент управления в форме двойной буферизации. Причина, по которой мы здесь размышляем, заключается в том, что представьте, что у вас есть элемент управления с дочерними элементами управления, которые недоступны, и даже если они являются частными элементами управления, мы все равно изменим их свойство на true.
Более подробную информацию о технике двойной буферизации можно найти здесь.
Есть другое свойство, которое я обычно перезаписываю для решения этой проблемы:
protected override CreateParams CreateParams
{
get
{
CreateParams parms = base.CreateParams;
parms.ExStyle |= 0x00000020; // WS_EX_COMPOSITED
return parms;
}
}
WS_EX_COMPOSITED
- Рисует всех потомков окна в порядке отрисовки снизу вверх, используя двойную буферизацию.
Вы можете найти больше этих флагов стиля здесь.
Надеюсь, это поможет!
Попробуйте методы BeginUpdate/EndUpdate ИЛИ SuspendLayout/ResumeLayout. Смотрите следующее
Как исправить проблемы с мерцанием вложенного элемента управления winform
Мерцание при обновлении элементов управления в WinForms (например, DataGridView)
В главной форме или пользовательском элементе управления, где находится фоновое изображение, установите BackgroundImageLayout
собственность на Center
или же Stretch
, Вы заметите большую разницу, когда пользовательский элемент управления рендеринг.
Я пытался добавить это как комментарий, но у меня недостаточно очков. Это единственное, что когда-либо помогало моим мерцающим проблемам. Огромное спасибо Гансу за его пост. Для тех, кто использует C++ Builder, как я, вот перевод
Добавьте объявление CreateParams в файл.h основной формы вашего приложения, например
class TYourMainFrom : public TForm
{
protected:
virtual void __fastcall CreateParams(TCreateParams &Params);
}
и добавьте это в ваш.cpp файл
void __fastcall TYourMainForm::CreateParams(TCreateParams &Params)
{
Params.ExStyle |= 0x02000000; // Turn on WS_EX_COMPOSITED
TForm::CreateParams(Params);
}
Просто чтобы добавить к ответу Ганс дал:
(Версия TLDR: прозрачность тяжелее, чем вы думаете, везде используйте только сплошные цвета)
Если WS_EX_COMPOSITED, DoubleBuffered и WS_CLIPCHILDREN не разрешили ваше мерцание (для меня WS_CLIPCHILDREN сделало это еще хуже), попробуйте следующее: просмотрите ВСЕ ваши элементы управления и весь ваш код, а также везде, где у вас есть прозрачность или полупрозрачность для BackColor, ForeColor или любой другой цвет, просто удалите его, используйте только сплошные цвета. В большинстве случаев, когда вы думаете, что вам просто нужно использовать прозрачность, вы этого не делаете. Перепроектируйте свой код и элементы управления и используйте сплошные цвета. У меня было ужасное мерцание, и программа работала вяло. Как только я убрал прозрачность, он значительно ускорился, и мерцание стало 0.
РЕДАКТИРОВАТЬ: Чтобы добавить дальше, я только что обнаружил, что WS_EX_COMPOSITED не обязательно должен быть шириной окна, он может быть применен только к конкретным элементам управления! Это спасло меня от многих проблем. Просто создайте пользовательский элемент управления, унаследованный от любого элемента управления, который вам нужен, и вставьте уже опубликованное переопределение для WS_EX_COMPOSITED. Таким образом, вы получаете низкоуровневый двойной буфер только для этого элемента управления, избегая неприятных побочных эффектов в остальной части приложения!
Я соединил это исправление мерцания и это исправление шрифта, затем мне пришлось добавить немного собственного кода, чтобы запустить таймер при рисовании, чтобы сделать недействительным TabControl, когда он выходит за пределы экрана и обратно и т. Д.
Все три делают это:
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class TabControlEx:TabControl
{
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
private const int WM_PAINT = 0x0f;
private const int WM_SETFONT = 0x30;
private const int WM_FONTCHANGE = 0x1d;
private System.Drawing.Bitmap buffer;
private Timer timer = new Timer();
public TabControlEx()
{
timer.Interval = 1;
timer.Tick += timer_Tick;
this.SetStyle(ControlStyles.UserPaint | ControlStyles.DoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);
}
void timer_Tick(object sender, EventArgs e)
{
this.Invalidate();
this.Update();
timer.Stop();
}
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_PAINT) timer.Start();
base.WndProc(ref m);
}
protected override void OnPaint(PaintEventArgs pevent)
{
this.SetStyle(ControlStyles.UserPaint, false);
base.OnPaint(pevent);
System.Drawing.Rectangle o = pevent.ClipRectangle;
System.Drawing.Graphics.FromImage(buffer).Clear(System.Drawing.SystemColors.Control);
if (o.Width > 0 && o.Height > 0)
DrawToBitmap(buffer, new System.Drawing.Rectangle(0, 0, Width, o.Height));
pevent.Graphics.DrawImageUnscaled(buffer, 0, 0);
this.SetStyle(ControlStyles.UserPaint, true);
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
buffer = new System.Drawing.Bitmap(Width, Height);
}
protected override void OnCreateControl()
{
base.OnCreateControl();
this.OnFontChanged(EventArgs.Empty);
}
protected override void OnFontChanged(EventArgs e)
{
base.OnFontChanged(e);
IntPtr hFont = this.Font.ToHfont();
SendMessage(this.Handle, WM_SETFONT, hFont, (IntPtr)(-1));
SendMessage(this.Handle, WM_FONTCHANGE, IntPtr.Zero, IntPtr.Zero);
this.UpdateStyles();
}
}
Я не создатель, но, насколько я понимаю, растровое изображение делает все ошибки в обход.
Это было единственное, что окончательно решило мерцание TabControl (с иконками) для меня.
видео результата разницы: ванильный tabcontrol против tabcontrolex
http://gfycat.com/FineGlitteringDeermouse
пс. вам нужно будет установить HotTrack = true, потому что это тоже исправляет эту ошибку
Я знаю, что этот вопрос очень старый, но хочу поделиться своим опытом по нему.
У меня было много проблем с Tabcontrol
мерцание в форме с переопределением OnPaint
и / или OnPaintBackGround
в Windows 8 с использованием.NET 4.0.
Единственная мысль, которая сработала, была НЕ ИСПОЛЬЗОВАТЬ Graphics.DrawImage
метод в OnPaint
переопределяет, другими словами, когда рисование было выполнено непосредственно для графики, предоставленной PaintEventArgs
, даже нарисовав весь прямоугольник, мерцание исчезло. Но если позвонить DrawImage
метод, даже рисуя обрезанное растровое изображение (создается для двойной буферизации), появляется мерцание.
Надеюсь, поможет!
Ты пробовал Control.DoubleBuffered
Имущество?
Получает или задает значение, указывающее, должен ли этот элемент управления перерисовывать его поверхность, используя вторичный буфер для уменьшения или предотвращения мерцания.
Нет необходимости в двойной буферизации и тому подобном, ребята...
Простое решение...
Если вы используете интерфейс MDI, просто вставьте приведенный ниже код в основную форму. Это удалит все мерцание со страниц. Однако некоторые страницы, которые требуют больше времени для загрузки, будут отображаться через 1 или 2 секунды. Но это лучше, чем показ мерцающей страницы, на которой каждый элемент появляется один за другим.
Это единственное лучшее решение для всего приложения. Смотрите код, чтобы положить в основной форме:
protected override CreateParams CreateParams {
get {
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x02000000; // Turn on WS_EX_COMPOSITED
return cp;
}
}