Jak naprawić migotanie w kontrolkach użytkownika

108

W mojej aplikacji nieustannie przechodzę od jednej kontrolki do drugiej. Stworzyłem nie. elementów sterujących użytkownika, ale podczas nawigacji moje elementy sterujące migają. Aktualizacja zajmuje 1 lub 2 sekundy. Próbowałem to ustawić

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

ale to nie pomogło ... Każda kontrolka ma ten sam obraz tła z różnymi kontrolkami. Więc jakie jest na to rozwiązanie…
Dzięki.

Royson
źródło
Gdzie są te stwierdzenia? Najlepiej umieścić je w konstruktorze. Zadzwoniłeś UpdateStylespo ustawieniu tych? Jest to źle udokumentowane, ale czasami może być konieczne.
Thomas,

Odpowiedzi:

307

Nie jest to rodzaj migotania, który może rozwiązać podwójne buforowanie. Ani BeginUpdate ani SuspendLayout. Masz zbyt wiele kontrolek, obraz BackgroundImage może to znacznie pogorszyć.

Rozpoczyna się, gdy UserControl maluje się. Rysuje BackgroundImage, pozostawiając dziury w miejscach, w których znajdują się okna kontroli dzieci. Każdy element sterujący podrzędny otrzymuje następnie komunikat, aby sam się malował, wypełniając otwór zawartością swojego okna. Kiedy masz dużo kontrolek, te dziury są widoczne dla użytkownika przez chwilę. Zwykle są białe i źle kontrastują z obrazem BackgroundImage, gdy jest ciemno. Lub mogą być czarne, jeśli formularz ma ustawioną właściwość Opacity lub TransparencyKey, źle kontrastując z prawie wszystkim.

Jest to dość fundamentalne ograniczenie Windows Forms, utknęło w sposobie renderowania okien przez Windows. Naprawione przez WPF przy okazji, nie używa okien do kontrolek podrzędnych. To, czego chcesz, to podwójne buforowanie całego formularza, w tym kontrolek podrzędnych. To możliwe, sprawdź mój kod w tym wątku, aby znaleźć rozwiązanie. Ma jednak efekty uboczne i tak naprawdę nie zwiększa szybkości malowania. Kod jest prosty, wklej go w formularzu (nie w kontrolce użytkownika):

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

Jest wiele rzeczy, które możesz zrobić, aby przyspieszyć malowanie, do tego stopnia, że ​​migotanie nie jest już zauważalne. Zacznij od rozwiązania BackgroundImage. Mogą być naprawdę drogie, gdy obraz źródłowy jest duży i trzeba go zmniejszyć, aby pasował do kontroli. Zmień właściwość BackgroundImageLayout na „Tile”. Jeśli daje to zauważalne przyspieszenie, wróć do programu do malowania i zmień rozmiar obrazu, aby lepiej pasował do typowego rozmiaru elementu sterującego. Lub napisz kod w metodzie OnResize () UC, aby utworzyć kopię obrazu o odpowiednim rozmiarze, tak aby nie trzeba było zmieniać rozmiaru za każdym razem, gdy formant jest malowany. Użyj formatu pikseli Format32bppPArgb dla tej kopii, renderuje ona około 10 razy szybciej niż jakikolwiek inny format pikseli.

Następną rzeczą, którą możesz zrobić, jest zapobieganie takiemu zauważaniu dziur i złemu kontrastowaniu z obrazem. Możesz wyłączyć flagę stylu WS_CLIPCHILDREN dla UC, flagę, która zapobiega malowaniu UC w obszarze, do którego przechodzą kontrolki potomne. Wklej ten kod w kodzie UserControl:

protected override CreateParams CreateParams {
  get {
    var parms = base.CreateParams;
    parms.Style &= ~0x02000000;  // Turn off WS_CLIPCHILDREN
    return parms;
  }
}

Elementy sterujące podrzędne będą teraz malować się na obrazie tła. Nadal możesz zobaczyć, jak malują się jeden po drugim, ale brzydka pośrednia biała lub czarna dziura nie będzie widoczna.

Wreszcie, zmniejszenie liczby kontroli dzieci jest zawsze dobrym podejściem do rozwiązywania problemów z powolnym malowaniem. Zastąp zdarzenie OnPaint () UC i narysuj to, co jest teraz pokazane w dziecku. Poszczególne etykiety i PictureBox są bardzo marnotrawne. Wygodne do wskazywania i klikania, ale ich lekka alternatywa (rysowanie ciągu znaków lub obrazu) zajmuje tylko jeden wiersz kodu w metodzie OnPaint ().

Hans Passant
źródło
Wyłącz WS_CLIPCHILDREN ulepszone doświadczenie użytkownika dla mnie.
Mahesh
Absolutnie idealne! .. Wielkie dzięki
AlejandroAlis
9

To jest prawdziwy problem, a odpowiedź, której udzielił Hans Passant, świetnie nadaje się do oszczędzania migotania. Jednak, jak wspomniał, istnieją efekty uboczne, które mogą być brzydkie (brzydki interfejs użytkownika). Jak wspomniano, „Możesz wyłączyć WS_CLIPCHILDRENflagę stylu dla UC”, ale to wyłącza ją tylko dla UC. Komponenty w głównym formularzu nadal mają problemy.

Na przykład pasek przewijania panelu nie maluje, ponieważ technicznie znajduje się w obszarze podrzędnym. Jednak komponent podrzędny nie rysuje paska przewijania, więc nie jest malowany, dopóki nie zostanie najechany myszką (lub inne zdarzenie go wyzwoli).

Ponadto animowane ikony (zmiana ikon w pętli oczekiwania) nie działają. Usunięcie ikon na stronie tabPage.ImageKeynie zmienia odpowiednio rozmiaru / przemalowywania innych stron karty.

Szukałem więc sposobu, aby wyłączyć WS_CLIPCHILDRENpoczątkowe malowanie, aby moja forma ładnie ładowała się pomalowana, lub lepiej, ale włączała ją tylko podczas zmiany rozmiaru mojej formy z wieloma komponentami.

Sztuczka polega na tym, aby aplikacja zadzwoniła CreateParamsw pożądanym WS_EX_COMPOSITED/WS_CLIPCHILDRENstylu. Znalazłem tu hack ( 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 ) i działa świetnie. Dzięki AngryHacker!

Umieszczam TurnOnFormLevelDoubleBuffering()wywołanie w ResizeBeginzdarzeniu formularza i TurnOffFormLevelDoubleBuffering()wywołuję w formularzu zdarzenie ResizeEnd (lub po prostu zostawiam je WS_CLIPCHILDRENpo początkowym poprawnym namalowaniu).

    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;
    }
user2044810
źródło
Twój kod nie zawiera metody TurnOnFormLevelDoubleBuffering () ...
Dan W
@DanW Spójrz na adres URL zamieszczony w tej odpowiedzi ( angryhacker.com/blog/archive/2010/07/21/… )
ChrisB
Link w tej odpowiedzi wydaje się martwy. Ciekawi mnie rozwiązanie, czy masz link do innego przykładu?
Pratt Hinds
6

Jeśli wykonujesz dowolne niestandardowe malowanie w kontrolce (np. Nadpisując OnPaint), możesz sam spróbować podwójnego buforowania.

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);
}

I unieważnij swoją kontrolę za pomocą właściwości NeedRepaint

W przeciwnym razie powyższa odpowiedź z SuspendLayout i ResumeLayout jest prawdopodobnie tym, czego potrzebujesz.

Patrick
źródło
To kreatywna metoda symulacji podwójnego bufora !. Możesz dodać if (image != null) image.Dispose();wcześniejimage = new Bitmap...
S.Serpooshan
2

W głównym formularzu lub kontrolce użytkownika, w której znajduje się obraz tła, ustaw BackgroundImageLayoutwłaściwość na Centerlub Stretch. Zauważysz dużą różnicę podczas renderowania kontrolki użytkownika.

revobtz
źródło
2

Próbowałem dodać to jako komentarz, ale nie mam wystarczającej liczby punktów. To jedyna rzecz, która kiedykolwiek pomogła moim migotliwym problemom, dziękuję Hansowi za jego post. Dla każdego, kto używa konstruktora C ++, takiego jak ja, oto tłumaczenie

Dodaj deklarację CreateParams do głównego formularza aplikacji .h pliku np

class TYourMainFrom : public TForm
{
protected:
    virtual void __fastcall CreateParams(TCreateParams &Params);
}

i dodaj to do swojego pliku .cpp

void __fastcall TYourMainForm::CreateParams(TCreateParams &Params)
{
    Params.ExStyle |= 0x02000000;  // Turn on WS_EX_COMPOSITED
    TForm::CreateParams(Params);
}
NoComprende
źródło
2

Umieść kod poniżej w konstruktorze lub zdarzeniu OnLoad, a jeśli używasz jakiejś niestandardowej kontrolki użytkownika, która ma kontrolki podrzędne, musisz upewnić się, że te niestandardowe kontrolki są również podwójnie buforowane (nawet jeśli w dokumentacji MS mówią domyślnie jest ustawione na true).

Jeśli tworzysz niestandardową kontrolkę, możesz dodać tę flagę do swojego ctora:

SetStyle(ControlStyles.OptimizedDoubleBuffer, true);

Opcjonalnie możesz użyć tego kodu w swoim formularzu / kontrolce:

foreach (Control control in Controls)
{
    typeof(Control).InvokeMember("DoubleBuffered",
        BindingFlags.SetProperty | BindingFlags.Instance | BindingFlags.NonPublic,
        null, control, new object[] { true });
}

Iterujemy przez wszystkie kontrolki w formularzu / kontrolce i uzyskujemy dostęp do ich DoubleBufferedwłaściwości, a następnie zmieniamy ją na true, aby każda kontrolka w formularzu była podwójnie buforowana. Powodem, dla którego dokonujemy tutaj refleksji, jest to, że wyobraź sobie, że masz kontrolkę, która ma kontrolki podrzędne, które nie są dostępne, w ten sposób, nawet jeśli są to kontrolki prywatne, nadal zmienimy ich właściwość na true.

Więcej informacji na temat techniki podwójnego buforowania można znaleźć tutaj .

Jest jeszcze jedna właściwość, którą zwykle nadpisuję, aby rozwiązać ten problem:

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

WS_EX_COMPOSITED - Maluje wszystkie elementy potomne okna w kolejności malowania od dołu do góry przy użyciu podwójnego buforowania.

Możesz znaleźć więcej takich flag stylów tutaj .

Mam nadzieję, że to pomoże!


źródło
1

Aby dodać do odpowiedzi udzielonej przez Hansa:

(Wersja TLDR: przezroczystość jest cięższa niż myślisz, wszędzie używaj tylko jednolitych kolorów)

Jeśli WS_EX_COMPOSITED, DoubleBuffered i WS_CLIPCHILDREN nie rozwiązały problemu z migotaniem (dla mnie WS_CLIPCHILDREN jeszcze gorzej), spróbuj tego: przejrzyj WSZYSTKIE kontrolki i cały kod i gdziekolwiek masz jakąkolwiek przezroczystość lub półprzezroczystość dla BackColor, ForeColor lub jakikolwiek inny kolor, po prostu go usuń, używaj tylko jednolitych kolorów. W większości przypadków, gdy uważasz, że po prostu muszą korzystać przejrzystości, nie. Zaprojektuj ponownie kod i kontrolki oraz użyj jednolitych kolorów. Miałem okropne, okropne migotanie, a program działał wolno. Po usunięciu przezroczystości znacznie przyspieszyło i nie ma migotania.

EDYCJA: Aby dodać więcej, właśnie odkryłem, że WS_EX_COMPOSITED nie musi obejmować całego okna, można go zastosować tylko do określonych kontrolek! Oszczędziło mi to wielu kłopotów. Po prostu ustaw niestandardową kontrolkę dziedziczoną z dowolnej kontrolki, której potrzebujesz, i wklej już opublikowane nadpisanie dla WS_EX_COMPOSITED. W ten sposób uzyskasz podwójny bufor niskiego poziomu tylko w tej kontrolce, unikając nieprzyjemnych efektów ubocznych w pozostałej części aplikacji!

Daniel
źródło
0

Wiem, że to pytanie jest bardzo stare, ale chcę podzielić się z nim swoim doświadczeniem.

Miałem sporo problemów z Tabcontrolmigotaniem w formularzu z nadpisaniem OnPainti / lub OnPaintBackGroundw Windows 8 używając .NET 4.0.

Jedyną myślą, która pracowała była NIE UŻYWAĆ ten Graphics.DrawImagesposób w OnPaintzadajnikami, innymi słowy, kiedy losowanie przeprowadzono bezpośrednio do grafiki pod warunkiem przez PaintEventArgsnawet malowanie wszystkich prostokąta migotanie zniknął. Ale jeśli wywołasz DrawImagemetodę, nawet rysując obciętą bitmapę (utworzoną do podwójnego buforowania), pojawi się migotanie.

Mam nadzieję, że to pomoże!

ZeroWorks
źródło
0

Połączyłem tę poprawkę migotania i tę poprawkę czcionki , a następnie musiałem dodać trochę własnego kodu, aby uruchomić licznik czasu podczas malowania, aby unieważnić TabControl, gdy znika z ekranu iz powrotem itp.

Wszystkie trzy sprawiają, że:

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();
    }
}

Nie jestem twórcą, ale z tego, co rozumiem, mapa bitowa omija wszystkie błędy.

To była jedyna rzecz, która ostatecznie rozwiązała dla mnie migotanie TabControl (z ikonami).

film z wynikami różnic: vanilla tabcontrol vs tabcontrolex

http://gfycat.com/FineGlitteringDeermouse

ps. będziesz musiał ustawić HotTrack = true, ponieważ to również naprawia ten błąd

user3732487
źródło
-2

Czy wypróbowałeś Control.DoubleBufferedProperty?

Pobiera lub ustawia wartość wskazującą, czy ten formant powinien ponownie narysować swoją powierzchnię przy użyciu dodatkowego bufora, aby zmniejszyć lub zapobiec migotaniu.

Również to i to może pomóc.

KMån
źródło
-9

Nie ma potrzeby podwójnego buforowania i tych wszystkich rzeczy ...

Proste rozwiązanie ...

Jeśli używasz interfejsu MDI, po prostu wklej poniższy kod w formularzu głównym. To usunie wszelkie migotanie ze stron. Jednak niektóre strony, które wymagają więcej czasu na załadowanie, pojawią się w ciągu 1 lub 2 sekund. Jest to jednak lepsze niż wyświetlanie migoczącej strony, na której każdy element pojawia się jeden po drugim.

To jedyne najlepsze rozwiązanie dla całej aplikacji. Zobacz kod do umieszczenia w głównym formularzu:

protected override CreateParams CreateParams {
  get {
    CreateParams cp = base.CreateParams;
    cp.ExStyle |= 0x02000000;  // Turn on WS_EX_COMPOSITED
    return cp;
  }
} 
Kshitiz
źródło
12
Więc mówisz, że odpowiedź, której Hans udzielił ponad dwa lata temu, jest w rzeczywistości poprawna? Dziękuję, Kshitiz. To naprawdę bardzo pomocne!
Fernando