So beheben Sie das Flackern in den Benutzersteuerelementen

107

In meiner Anwendung wechsle ich ständig von einem Steuerelement zum anderen. Ich habe nein geschaffen. von Benutzersteuerelementen, aber während der Navigation flackern meine Steuerelemente. Die Aktualisierung dauert 1 oder 2 Sekunden. Ich habe versucht, dies einzustellen

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

aber es hat nicht geholfen ... Jedes Steuerelement hat das gleiche Hintergrundbild mit unterschiedlichen Steuerelementen. Also, was ist die Lösung dafür.
Danke.

Royson
quelle
Wo sind diese Aussagen? Im Idealfall legen Sie sie in den Konstruktor. Haben Sie UpdateStylesnach dem Einstellen angerufen ? Es ist schlecht dokumentiert, kann aber manchmal notwendig sein.
Thomas

Antworten:

306

Es ist nicht die Art von Flimmern, die durch Doppelpufferung gelöst werden kann. Weder BeginUpdate noch SuspendLayout. Sie haben zu viele Steuerelemente, das BackgroundImage kann es noch viel schlimmer machen.

Es beginnt, wenn sich das UserControl selbst malt. Es zeichnet das BackgroundImage und hinterlässt Löcher in den Fenstern der untergeordneten Steuerung. Jedes untergeordnete Steuerelement erhält dann eine Nachricht, um sich selbst zu malen. Sie füllen das Loch mit ihrem Fensterinhalt aus. Wenn Sie viele Steuerelemente haben, sind diese Löcher für den Benutzer für eine Weile sichtbar. Sie sind normalerweise weiß und bilden einen schlechten Kontrast zum BackgroundImage, wenn es dunkel ist. Oder sie können schwarz sein, wenn für das Formular die Eigenschaft Deckkraft oder Transparenzschlüssel festgelegt ist, die sich stark von fast allem abhebt.

Dies ist eine ziemlich grundlegende Einschränkung von Windows Forms. Sie hängt von der Art und Weise ab, wie Windows Windows rendert. Übrigens von WPF behoben, verwendet es keine Fenster für untergeordnete Steuerelemente. Sie möchten das gesamte Formular einschließlich der untergeordneten Steuerelemente doppelt puffern. Das ist möglich, überprüfen Sie meinen Code in diesem Thread für die Lösung. Es hat jedoch Nebenwirkungen und erhöht die Malgeschwindigkeit nicht wirklich. Der Code ist einfach. Fügen Sie ihn in Ihr Formular ein (nicht in das Benutzersteuerelement):

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

Es gibt viele Dinge, die Sie tun können, um die Malgeschwindigkeit zu verbessern, bis das Flimmern nicht mehr wahrnehmbar ist. Beginnen Sie mit dem BackgroundImage. Sie können sehr teuer sein, wenn das Quellbild groß ist und verkleinert werden muss, um in das Steuerelement zu passen. Ändern Sie die BackgroundImageLayout-Eigenschaft in "Tile". Wenn dies zu einer spürbaren Beschleunigung führt, kehren Sie zu Ihrem Malprogramm zurück und ändern Sie die Bildgröße, um eine bessere Übereinstimmung mit der typischen Steuerelementgröße zu erzielen. Oder schreiben Sie Code in die OnResize () -Methode des UC, um eine Kopie des Bildes mit der richtigen Größe zu erstellen, damit die Größe nicht jedes Mal geändert werden muss, wenn das Steuerelement neu gezeichnet wird. Verwenden Sie für diese Kopie das Pixelformat Format32bppPArgb. Es wird etwa zehnmal schneller gerendert als jedes andere Pixelformat.

Als nächstes können Sie verhindern, dass die Löcher so auffällig sind und einen schlechten Kontrast zum Bild bilden. Sie können das WS_CLIPCHILDREN-Stilflag für die UC deaktivieren. Dieses Flag verhindert, dass die UC in dem Bereich malt, in dem sich die untergeordneten Steuerelemente befinden. Fügen Sie diesen Code in den Code von UserControl ein:

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

Die untergeordneten Steuerelemente malen sich jetzt über das Hintergrundbild. Sie können immer noch sehen, wie sie sich einzeln malen, aber das hässliche weiße oder schwarze Zwischenloch ist nicht sichtbar.

Last but not least ist die Reduzierung der Anzahl der untergeordneten Steuerelemente immer ein guter Ansatz, um Probleme beim langsamen Malen zu lösen. Überschreiben Sie das OnPaint () - Ereignis der UC und zeichnen Sie, was jetzt in einem untergeordneten Element angezeigt wird. Bestimmte Beschriftungen und PictureBox sind sehr verschwenderisch. Praktisch für Zeigen und Klicken, aber ihre leichte Alternative (Zeichnen einer Zeichenfolge oder eines Bildes) benötigt in Ihrer OnPaint () -Methode nur eine einzige Codezeile.

Hans Passant
quelle
Deaktivieren Sie WS_CLIPCHILDREN, um die Benutzererfahrung für mich zu verbessern.
Mahesh
Absolut perfekt! .. Vielen Dank
AlejandroAlis
8

Dies ist ein echtes Problem, und die Antwort, die Hans Passant gab, ist großartig, um das Flimmern zu retten. Es gibt jedoch Nebenwirkungen, wie er erwähnte, und sie können hässlich sein (UI hässlich). Wie bereits erwähnt, "Sie können das WS_CLIPCHILDRENStilflag für die UC deaktivieren", aber das deaktiviert es nur für eine UC. Die Komponenten im Hauptformular weisen weiterhin Probleme auf.

Beispiel: Eine Bildlaufleiste wird nicht gemalt, da sie sich technisch im untergeordneten Bereich befindet. Die untergeordnete Komponente zeichnet die Bildlaufleiste jedoch nicht, sodass sie erst mit der Maus gezeichnet wird (oder ein anderes Ereignis sie auslöst).

Außerdem funktionieren animierte Symbole (Ändern von Symbolen in einer Warteschleife) nicht. Durch das Entfernen von tabPage.ImageKeySymbolen auf einem wird die Größe der anderen Registerkarten nicht entsprechend geändert / neu gezeichnet.

Daher suchte ich nach einer Möglichkeit, das WS_CLIPCHILDRENerste Mal zu deaktivieren, damit mein Formular gut gemalt geladen wird, oder besser, es nur einzuschalten, während ich die Größe meines Formulars mit vielen Komponenten verändere.

Der Trick besteht darin, die Anwendung dazu zu bringen, CreateParamsmit dem gewünschten WS_EX_COMPOSITED/WS_CLIPCHILDRENStil aufzurufen . Ich habe hier einen Hack gefunden ( 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 ) und es funktioniert großartig. Danke AngryHacker!

Ich setze den TurnOnFormLevelDoubleBuffering()Aufruf in das Formularereignis ResizeBeginund TurnOffFormLevelDoubleBuffering()rufe das Formular ResizeEnd-Ereignis auf (oder lasse es einfach, WS_CLIPCHILDRENnachdem es anfänglich richtig gezeichnet wurde .)

    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
quelle
Ihr Code enthält nicht die TurnOnFormLevelDoubleBuffering () -Methode ...
Dan W
@ DanW Schauen Sie sich die URL an, die in dieser Antwort veröffentlicht wurde ( Angryhacker.com/blog/archive/2010/07/21/… )
ChrisB
Der Link in dieser Antwort scheint tot zu sein. Ich bin gespannt auf die Lösung. Haben Sie einen Link zu einem anderen Beispiel?
Pratt Hinds
6

Wenn Sie ein benutzerdefiniertes Bild im Steuerelement erstellen (dh OnPaint überschreiben), können Sie die doppelte Pufferung selbst ausprobieren.

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

Und machen Sie Ihre Kontrolle mit einer Eigenschaft ungültig NeedRepaint

Andernfalls ist die obige Antwort mit SuspendLayout und ResumeLayout wahrscheinlich genau das, was Sie wollen.

Patrick
quelle
Dies ist eine kreative Methode, um Doppelpuffer zu simulieren!. Sie können if (image != null) image.Dispose();vorimage = new Bitmap...
S.Serpooshan
2

Setzen Sie im Hauptformular oder Benutzersteuerelement, in dem sich das Hintergrundbild befindet, die BackgroundImageLayoutEigenschaft auf Centeroder Stretch. Sie werden einen großen Unterschied feststellen, wenn das Benutzersteuerelement gerendert wird.

revobtz
quelle
2

Ich habe versucht, dies als Kommentar hinzuzufügen, aber ich habe nicht genug Punkte. Dies ist das einzige, was meinen flackernden Problemen jemals geholfen hat. Vielen Dank an Hans für seinen Beitrag. Für alle, die C ++ Builder wie mich verwenden, ist hier die Übersetzung

Fügen Sie die CreateParams-Deklaration zur .h-Datei des Hauptformulars Ihrer Anwendung hinzu, z

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

und fügen Sie dies Ihrer CPP-Datei hinzu

void __fastcall TYourMainForm::CreateParams(TCreateParams &Params)
{
    Params.ExStyle |= 0x02000000;  // Turn on WS_EX_COMPOSITED
    TForm::CreateParams(Params);
}
NoComprende
quelle
2

Fügen Sie den folgenden Code in Ihren Konstruktor oder Ihr OnLoad-Ereignis ein. Wenn Sie ein benutzerdefiniertes Benutzersteuerelement mit Untersteuerelementen verwenden, müssen Sie sicherstellen, dass diese benutzerdefinierten Steuerelemente auch doppelt gepuffert sind (auch wenn dies in der MS-Dokumentation angegeben ist) es ist standardmäßig auf true gesetzt).

Wenn Sie ein benutzerdefiniertes Steuerelement erstellen, möchten Sie möglicherweise dieses Flag zu Ihrem ctor hinzufügen:

SetStyle(ControlStyles.OptimizedDoubleBuffer, true);

Optional können Sie diesen Code in Ihrem Formular / Steuerelement verwenden:

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

Wir durchlaufen alle Steuerelemente im Formular / Steuerelement und greifen auf deren DoubleBufferedEigenschaften zu. Anschließend ändern wir sie in true, um jedes Steuerelement im Formular doppelt zu puffern. Der Grund, warum wir hier nachdenken, ist, dass Sie sich vorstellen, dass Sie ein Steuerelement haben, dessen untergeordnete Steuerelemente nicht zugänglich sind. Selbst wenn es sich um private Steuerelemente handelt, ändern wir ihre Eigenschaft dennoch in true.

Weitere Informationen zur Doppelpuffertechnik finden Sie hier .

Es gibt eine andere Eigenschaft, die ich normalerweise überschreibe, um dieses Problem zu lösen:

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

WS_EX_COMPOSITED - Malt alle Nachkommen eines Fensters in der Reihenfolge von unten nach oben mit doppelter Pufferung.

Weitere dieser Stilflaggen finden Sie hier .

Hoffentlich hilft das!


quelle
1

Nur um die Antwort zu ergänzen, die Hans gab:

(TLDR-Version: Transparenz ist schwerer als Sie denken, verwenden Sie überall nur Volltonfarben)

Wenn WS_EX_COMPOSITED, DoubleBuffered und WS_CLIPCHILDREN Ihr Flimmern nicht gelöst haben (für mich hat WS_CLIPCHILDREN es noch schlimmer gemacht), versuchen Sie Folgendes: Gehen Sie ALLE Ihre Steuerelemente und Ihren gesamten Code durch und überall dort, wo Sie Transparenz oder Halbtransparenz für BackColor, ForeColor oder haben Jede andere Farbe, entfernen Sie sie einfach und verwenden Sie nur Volltonfarben. In den meisten Fällen , in denen Sie denken , dass Sie gerade haben Transparenz zu verwenden, tun Sie nicht. Gestalten Sie Ihren Code und Ihre Steuerelemente neu und verwenden Sie Volltonfarben. Ich hatte schreckliches, schreckliches Flackern und das Programm lief träge. Sobald ich die Transparenz entfernt habe, hat sie sich erheblich beschleunigt und es gibt kein Flimmern.

BEARBEITEN: Um weiter hinzuzufügen, habe ich gerade festgestellt, dass WS_EX_COMPOSITED nicht fensterweit sein muss, sondern nur auf bestimmte Steuerelemente angewendet werden kann! Das hat mir viel Ärger erspart. Erstellen Sie einfach ein benutzerdefiniertes Steuerelement, das von dem gewünschten Steuerelement geerbt wurde, und fügen Sie die bereits veröffentlichte Überschreibung für WS_EX_COMPOSITED ein. Auf diese Weise erhalten Sie nur auf diesem Steuerelement einen Doppelpuffer auf niedriger Ebene, wodurch die unangenehmen Nebenwirkungen im Rest der Anwendung vermieden werden!

Daniel
quelle
0

Ich weiß, dass diese Frage sehr alt ist, möchte aber meine Erfahrungen dazu geben.

Ich hatte viele Probleme mit dem TabcontrolFlackern in einem Formular mit überschriebenem OnPaintund / oder OnPaintBackGroundin Windows 8 unter Verwendung von .NET 4.0.

Der einzige Gedanke, der funktioniert hat, war, die Methode NICHTGraphics.DrawImage beim OnPaintÜberschreiben zu verwenden, mit anderen Worten, wenn das Zeichnen direkt auf die Grafiken erfolgte, die durch das PaintEventArgssogar das Malen des gesamten Rechtecks bereitgestellt wurden , verschwand das Flackern. Wenn Sie die DrawImageMethode aufrufen und sogar eine abgeschnittene Bitmap zeichnen (erstellt für doppelte Pufferung), wird das Flimmern angezeigt.

Ich hoffe es hilft!

ZeroWorks
quelle
0

Ich habe diesen Flimmer-Fix und diesen Font-Fix kombiniert , dann musste ich ein bisschen meinen eigenen Code hinzufügen, um einen Timer für das Malen zu starten, um das TabControl ungültig zu machen, wenn es außerhalb des Bildschirms und zurück geht usw.

Alle drei machen das:

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

Ich bin nicht der Schöpfer, aber soweit ich weiß, umgeht die Bitmap den ganzen Fehler.

Dies war das einzige, was TabControl (mit Symbolen) für mich endgültig gelöst hat.

Unterschied Ergebnis Video: Vanille Tabcontrol vs Tabcontrolex

http://gfycat.com/FineGlitteringDeermouse

ps. Sie müssen HotTrack = true setzen, da dies auch diesen Fehler behebt

user3732487
quelle
-2

Haben Sie Control.DoubleBufferedProperty ausprobiert ?

Ruft einen Wert ab oder legt einen Wert fest, der angibt, ob dieses Steuerelement seine Oberfläche mithilfe eines sekundären Puffers neu zeichnen soll, um Flimmern zu reduzieren oder zu verhindern.

Auch dies und das könnte helfen.

KMån
quelle
-9

Es ist keine doppelte Pufferung und all das Zeug nötig ...

Eine einfache Lösung ...

Wenn Sie die MDI-Schnittstelle verwenden, fügen Sie einfach den folgenden Code in das Hauptformular ein. Dadurch wird jegliches Flackern von den Seiten entfernt. Einige Seiten, die mehr Zeit zum Laden benötigen, werden jedoch in 1 oder 2 Sekunden angezeigt. Dies ist jedoch besser, als eine flackernde Seite anzuzeigen, auf der jedes Element einzeln angezeigt wird.

Dies ist die einzig beste Lösung für die gesamte Anwendung. Siehe den Code, der in das Hauptformular eingefügt werden soll:

protected override CreateParams CreateParams {
  get {
    CreateParams cp = base.CreateParams;
    cp.ExStyle |= 0x02000000;  // Turn on WS_EX_COMPOSITED
    return cp;
  }
} 
Kshitiz
quelle
12
Sie sagen also, dass die Antwort, die Hans vor über zwei Jahren gegeben hat, tatsächlich richtig ist? Danke, Kshitiz. Das ist wirklich sehr hilfreich!
Fernando