Как исправить мерцание в пользовательских элементах управления - PullRequest
103 голосов
/ 10 апреля 2010

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

SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
or
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.AllPaintingInWmPaint, true); 
SetStyle(ControlStyles.DoubleBuffer, true);

но это не помогло ... Каждый элемент управления имеет одно и то же фоновое изображение с различными элементами управления. Так что решение для этого ..
Спасибо.

Ответы [ 12 ]

296 голосов
/ 10 апреля 2010

Это не тот тип мерцания, который может решить двойная буферизация. Ни BeginUpdate, ни SuspendLayout. У вас слишком много элементов управления, BackgroundImage может сделать его много хуже.

Запускается, когда пользовательский элемент управления рисует сам. Он рисует 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 ().

8 голосов
/ 20 мая 2013

Это реальная проблема, и ответ, который дал Ханс Пассант, отлично подходит для сохранения мерцания. Однако, как он упомянул, есть побочные эффекты, которые могут быть уродливыми (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;
    }
6 голосов
/ 10 апреля 2010

Если вы выполняете какую-либо пользовательскую рисование в элементе управления (например, переопределяете 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, вероятно, то, что вы хотите.

3 голосов
/ 10 апреля 2010
2 голосов
/ 21 апреля 2017

Поместите приведенный ниже код в ваш конструктор или событие 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 - Рисует всех потомков окна в порядке отрисовки снизу вверх, используя двойную буферизацию.

Вы можете найти больше этих флагов стиля здесь .

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

2 голосов
/ 05 сентября 2016

Я пытался добавить это как комментарий, но у меня недостаточно очков. Это единственное, что когда-либо помогало моим мерцающим проблемам. Огромное спасибо Гансу за его пост. Для тех, кто использует 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);
}
2 голосов
/ 31 мая 2014

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

1 голос
/ 15 марта 2013

Просто чтобы добавить к ответу Ганс дал:

(версия TLDR: прозрачность тяжелее, чем вы думаете, везде используйте только сплошные цвета)

Если WS_EX_COMPOSITED, DoubleBuffered и WS_CLIPCHILDREN не решили ваше мерцание (для меня WS_CLIPCHILDREN сделало его еще хуже), попробуйте следующее: просмотрите ВСЕ ваши элементы управления и весь ваш код, а также везде, где у вас есть прозрачность или полупрозрачность для BackColor, ForeColor или любой другой цвет, просто удалите его, используйте только сплошные цвета. В большинстве случаев, когда вы думаете, что у вас просто есть для использования прозрачности, вы этого не делаете. Перепроектируйте свой код и элементы управления и используйте сплошные цвета. У меня было ужасное мерцание, и программа работала вяло. После того, как я удалил прозрачность, он значительно ускорился, и мерцание 0.

РЕДАКТИРОВАТЬ: Чтобы добавить дальше, я только что обнаружил, что WS_EX_COMPOSITED не должен быть широким окном, он может быть применен только к определенным элементам управления! Это спасло меня от многих проблем. Просто создайте пользовательский элемент управления, унаследованный от любого элемента управления, который вам нужен, и вставьте уже опубликованное переопределение для WS_EX_COMPOSITED. Таким образом, вы получаете низкоуровневый двойной буфер только для этого элемента управления, избегая неприятных побочных эффектов в остальной части приложения!

0 голосов
/ 24 марта 2016

Я скомбинировал это исправление мерцания и это исправление шрифта , затем мне пришлось добавить немного собственного кода, чтобы запустить таймер при рисовании, чтобы отключить 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 (с иконками) для меня.

видео с результатами различий: vanilla tabcontrol против tabcontrolex

http://gfycat.com/FineGlitteringDeermouse

пс. вам нужно будет установить HotTrack = true, потому что это тоже исправляет эту ошибку

0 голосов
/ 27 июня 2014

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

У меня было много проблем с мерцанием Tabcontrol в форме с переопределенными OnPaint и / или OnPaintBackGround в Windows 8 с использованием .NET 4.0.

Единственное, что сработало, было НЕ ИСПОЛЬЗОВАТЬ метод Graphics.DrawImage в OnPaint переопределяет, другими словами, когда рисование было выполнено непосредственно для графики, предоставленной PaintEventArgs, даже рисование весь прямоугольник, мерцание исчезло. Но если вызвать метод DrawImage, даже рисуя обрезанное растровое изображение (созданное для двойной буферизации), появляется мерцание.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...