Як виправити мерехтіння в елементах управління користувачами


107

У своїй програмі я постійно переходжу від одного управління до іншого. Я створив ні. управління користувачами, але під час навігації мій елемент управління мерехтить. для оновлення потрібно 1 або 2 секунди. Я намагався це встановити

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

але це не допомогло ... Кожен елемент управління має однакове фонове зображення з різними елементами управління. Тож яке рішення для цього ..
Дякую.


Де ці заяви? В ідеалі, помістіть їх у конструктор. Ви телефонували UpdateStylesпісля встановлення цих? Це погано задокументовано, але іноді може знадобитися.
Томас

Відповіді:


306

Подвійне буферування може вирішити не такий тип мерехтіння. Ні BeginUpdate або SuspendLayout. У вас занадто багато елементів керування, 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 на "Плитка". Якщо це дає помітне прискорення, поверніться до програми малювання та змініть розмір зображення, щоб він краще відповідав типовому розміру керування. Або запишіть код у методі 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 та намалюйте те, що зараз показано дитині. Конкретні етикетки та PictureBox дуже марнотратні. Зручний для точки та клацання, але їх легка альтернатива (малювання рядка чи зображення) містить лише один рядок коду у вашому методі OnPaint ().


Вимкніть WS_CLIPCHILDREN покращений досвід роботи для мене.
Махеш

Абсолютно ідеально! .. Дякую велике
AlejandroAlis

8

Це справжнє питання, і відповідь, яку дав Ганс Пасант, відмінно допомагає зберегти мерехтіння. Однак, як він згадував, є побічні ефекти, і вони можуть бути негарними (UI некрасивими). Як зазначено, "Ви можете вимкнути WS_CLIPCHILDRENпрапор стилю для UC", але це вимикає лише для UC. Компоненти основної форми все ще мають проблеми.

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

Також анімовані піктограми (зміна піктограм у циклі очікування) не працюють. Видалення піктограм на tabPage.ImageKeyне змінює розмір / перефарбовує інші вкладки Сторінки належним чином.

Тому я шукав спосіб вимкнути 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-form-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 () ...
Dan W

@DanW Подивіться на URL-адресу, опубліковану у цій відповіді ( beshacker.com/blog/archive/2010/07/21/… )
ChrisB

Посилання у цій відповіді видається мертвим. Мені цікаво рішення, чи є у вас посилання на інший приклад?
Pratt Hinds

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...
S.Serpooshan


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

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

Існує ще одна властивість, яку я зазвичай перекриваю, щоб сортувати цю проблему:

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

WS_EX_COMPOSITED - Фарбує всіх нащадків вікна в порядку малювання знизу вгору за допомогою подвійного буферизації.

Більше цих прапорів стилів можна знайти тут .

Сподіваюся, що це допомагає!


1

Просто, щоб додати відповідь, яку дав Ганс:

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

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

EDIT: Щоб додати ще, я щойно виявив, що 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 (з іконами).

відео різниці результатів: ванільний табконтроль проти табконтролекс

http://gfycat.com/FineGlitteringDeermouse

пс. вам потрібно буде встановити HotTrack = true, оскільки це також виправляє цю помилку


-2

Ви пробували Control.DoubleBufferedProperty?

Отримує або встановлює значення, яке вказує, чи повинен цей елемент управління перемальовувати його поверхню за допомогою вторинного буфера для зменшення або запобігання мерехтіння.

Також це і це може допомогти.


-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.