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


108

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

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

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


Где эти заявления? В идеале поместить их в конструктор. Вы звонили UpdateStylesпосле установки этих? Это плохо задокументировано, но иногда может быть необходимо.
Томас

Ответы:


307

Это не тот вид мерцания, который может решить двойная буферизация. Ни 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 на «Плитка». Если это дает заметное ускорение, вернитесь в свою программу рисования и измените размер изображения, чтобы оно лучше соответствовало типичному размеру элемента управления. Или напишите код в методе 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;
  }
}

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

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


Отключите WS_CLIPCHILDREN, улучшив взаимодействие с пользователем.
Махеш

Абсолютно идеально! .. Большое спасибо
AlejandroAlis

9

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

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

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

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

Уловка состоит в том, чтобы заставить приложение вызывать CreateParamsжелаемый WS_EX_COMPOSITED/WS_CLIPCHILDRENстиль. Я нашел здесь взлом ( https://web.archive.org/web/20161026205944/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;
    }

В вашем коде нет метода TurnOnFormLevelDoubleBuffering () ...
Дэн В.

@DanW Взгляните на URL-адрес, опубликованный в этом ответе ( angryhacker.com/blog/archive/2010/07/21/… )
ChrisB

Ссылка в этом ответе кажется мертвой. Мне интересно решение, у вас есть ссылка на другой пример?
Пратт Хайндс

6

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


Это творческий метод имитации двойного буфера !. Вы можете добавить if (image != null) image.Dispose();раньшеimage = new Bitmap...
С.Серпушан


2

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


2

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

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

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

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


1

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

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

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

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


0

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

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

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

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


0

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


-2

Вы пробовали Control.DoubleBufferedнедвижимость?

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

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


-9

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

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

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

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

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

12
Итак, вы говорите, что ответ, который Ганс дал более двух лет назад, на самом деле правильный? Спасибо, Кшитиз. Это действительно очень полезно!
Фернандо
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.