Как исправить мерцание в элементах управления пользователя

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

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

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

Ответ 1

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

Он запускается, когда сам UserControl рисует. Он рисует BackgroundImage, оставляя дыры, где идут дочерние окна управления. Затем каждый дочерний элемент получает сообщение для рисования, они заполняют отверстие содержимым окна. Когда у вас много элементов управления, эти дыры видны пользователю некоторое время. Они обычно белые, плохо контрастируют с BackgroundImage, когда темно. Или они могут быть черными, если форма имеет свой набор свойств Opacity или TransparencyKey, что плохо контрастирует с чем угодно.

Это довольно фундаментальное ограничение Windows Forms, оно застряло в том, как Windows создает окна. Исправлено с помощью WPF btw, оно не использует окна для дочерних элементов управления. То, что вы хотите, - это двойная буферизация всей формы, включая дочерние элементы управления. Это возможно, проверьте мой код в этот поток для решения. Тем не менее, он имеет побочные эффекты и фактически не увеличивает скорость окраски. Код прост, вставьте его в свою форму (а не пользовательское управление):

protected override CreateParams CreateParams {
  get {
    CreateParams cp = base.CreateParams;
    cp.ExStyle |= 0x02000000;  // Turn on WS_EX_COMPOSITED
    return cp;
  }
} 

Есть много вещей, которые вы можете сделать, чтобы улучшить скорость рисования, до такой степени, что мерцание уже не заметно. Начните с решения BackgroundImage. Они могут быть действительно дорогими, когда исходное изображение является большим и должно быть сжато, чтобы соответствовать управлению. Измените свойство BackgroundImageLayout на "Плитка". Если это дает заметное ускорение, вернитесь к своей программе рисования и измените размер изображения, чтобы оно соответствовало типичному размеру управления. Или напишите код в методе UC OnResize(), чтобы создать копию изображения с соответствующим размером, чтобы он не изменялся каждый раз, когда элемент управления перерисовывается. Используйте формат пикселя 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;
  }
}

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

И последнее, но не менее важное: уменьшение количества дочерних элементов управления всегда является хорошим подходом к решению проблем медленной живописи. Переопределите событие UC OnPaint() и нарисуйте то, что теперь показано в дочернем элементе. Особые ярлыки и PictureBox очень расточительны. Удобно для точек и кликов, но их легкая альтернатива (рисование строки или изображения) занимает только одну строку кода в методе OnPaint().

Ответ 2

Это реальная проблема, и ответ, который дал Ханс Пассант, отлично подходит для сохранения мерцания. Однако, по его словам, есть побочные эффекты, и они могут быть уродливыми (UI уродливый). Как указано, "Вы можете отключить флаг стиля WS_CLIPCHILDREN для UC", но это только отключает его для UC. У компонентов основной формы все еще есть проблемы.

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

Кроме того, анимированные значки (изменение значков в цикле ожидания) не работают. Удаление значков на вкладке tabPage.ImageKey не изменяет размер/перерисовку других tabPages соответствующим образом.

Итак, я искал способ отключить 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;
    }

Ответ 3

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

Ответ 5

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

Ответ 6

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

Ответ 7

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

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

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

Ответ 8

Просто добавлю ответ, который дал Ганс:

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

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

EDIT: Чтобы добавить далее, я только что обнаружил, что WS_EX_COMPOSITED не обязательно должен быть оконным, он может применяться только к определенным элементам управления! Это смутило меня. Просто создайте собственный элемент управления, унаследованный от любого управляемого элемента, и вставьте уже отправленное переопределение для WS_EX_COMPOSITED. Таким образом, вы получаете двойной буфер с низким уровнем только для этого элемента управления, избегая неприятных побочных эффектов в остальной части приложения!

Ответ 9

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

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

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

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

Ответ 10

Я объединил это исправление фликкера и это исправление шрифта, тогда мне пришлось добавить немного моего собственного кода, чтобы запустить таймер на краске, чтобы Invalidate 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 vs tabcontrolex

http://gfycat.com/FineGlitteringDeermouse

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

Ответ 11

Вы попробовали Control.DoubleBuffered Свойство?

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

Также this и это может помочь.

Ответ 12

Нет необходимости в двойной буферизации и всех этих парней...

Простое решение...

Если вы используете интерфейс MDI, просто вставьте код ниже в основную форму. Он удалит все мерцание со страниц. Однако некоторые страницы, для которых требуется больше времени для загрузки, будут отображаться через 1 или 2 секунды. Но это лучше, чем отображение мерцающей страницы, в которой каждый элемент приходит один за другим.

Это единственное лучшее решение для всего приложения. См. Код для размещения в основной форме:

protected override CreateParams CreateParams {
  get {
    CreateParams cp = base.CreateParams;
    cp.ExStyle |= 0x02000000;  // Turn on WS_EX_COMPOSITED
    return cp;
  }
}