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

в моем приложении я постоянно перемещается от одного элемента управления к другому. Я создал нет. пользовательских элементов управления, но во время навигации мои элементы управления мерцают. обновление занимает 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, оставляя отверстия, куда идут окна дочернего элемента управления. Затем каждый дочерний элемент управления получает сообщение, чтобы нарисовать себя, они заполнят отверстие своим содержимым окна. Когда у вас есть много элементов управления, эти отверстия некоторое время видны пользователю. Они обычно белые, плохо контрастирующие с фоновым изображением, когда темно. Или они могут быть черными, если форма имеет свой набор свойств непрозрачности или прозрачности, плохо контрастируя с чем угодно.

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

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 в разы быстрее любого другого пиксельного формата.

следующее, что вы можете сделать, это предотвратить отверстия от того, чтобы быть настолько заметным и контрастировать плохо с изображением. Ты можешь!--17-->выключить флаг стиля 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 и нарисуйте то, что теперь показано в дочернем элементе. Конкретные метки и PictureBox являются очень расточительно. Удобно для точки и щелчка, но их легкая альтернатива (рисование строки или изображения) занимает всего один строка кода в методе OnPaint ().


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

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

кроме того, анимированные значки (изменение значков в цикле ожидания) не работают. Удаление значков на вкладке.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-это, вероятно, то, что вы хотите.



в основной форме или пользовательском элементе управления, где находится фоновое изображение, установите 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);
}

поместите код ниже в свой конструктор или событие 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 });
}

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

дополнительная информация о двойной буферизации технику можно найти здесь.

есть еще одно свойство, которое я обычно переопределяю для сортировки этой проблемы:

protected override CreateParams CreateParams
{
    get
    {
        CreateParams parms = base.CreateParams;
        parms.ExStyle |= 0x00000020; // WS_EX_COMPOSITED
        return parms;
    }
}

WS_EX_COMPOSITED - рисует все потомки окна в порядке рисования снизу вверх с использованием двойной буферизации.

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

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


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

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

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

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


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

у меня было много проблем с Tabcontrol мерцание в форме с перекрытая OnPaint и/или OnPaintBackGround в Windows 8 с помощью .NET 4.0.

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

надеюсь, что это помогает!


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

http://gfycat.com/FineGlitteringDeermouse

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


ты Control.DoubleBuffered собственность?

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

и этой и этой может помочь.


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

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

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

это единственное лучшее решение для все приложение. См. код для ввода в основной форме:

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