Come correggere lo sfarfallio nei controlli utente


107

Nella mia applicazione mi sposto costantemente da un controllo all'altro. Ho creato no. dei controlli utente, ma durante la navigazione i miei controlli iniziano a lampeggiare. ci vogliono 1 o 2 secondi per l'aggiornamento. Ho provato a impostarlo

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

ma non ha aiutato ... Ogni controllo ha la stessa immagine di sfondo con controlli diversi. Allora qual è la soluzione per questo ..
Grazie.


Dove sono queste affermazioni? Idealmente, inseriscili nel costruttore. Hai chiamato UpdateStylesdopo averli impostati? È mal documentato, ma a volte può essere necessario.
Thomas

Risposte:


306

Non è il tipo di sfarfallio che il doppio buffering può risolvere. Né BeginUpdate o SuspendLayout. Hai troppi controlli, BackgroundImage può renderlo molto di peggio.

Inizia quando UserControl si dipinge. Disegna l'immagine di sfondo, lasciando dei buchi dove vanno le finestre di controllo figlio. Ogni controllo figlio riceve quindi un messaggio per dipingere se stesso, riempiranno il buco con il contenuto della finestra. Quando si hanno molti controlli, questi buchi sono visibili all'utente per un po '. Normalmente sono bianchi e contrasta male con l'immagine di sfondo quando è buio. Oppure possono essere neri se il modulo ha la sua proprietà Opacity o TransparencyKey impostata, che contrasta male con qualsiasi cosa.

Questa è una limitazione piuttosto fondamentale di Windows Forms, è legata al modo in cui Windows esegue il rendering di Windows. Risolto da WPF btw, non utilizza finestre per i controlli figlio. Quello che vorresti è il doppio buffering dell'intero modulo, inclusi i controlli figlio. È possibile, controlla il mio codice in questo thread per la soluzione. Tuttavia, ha effetti collaterali e in realtà non aumenta la velocità di pittura. Il codice è semplice, incollalo nel modulo (non nel controllo utente):

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

Ci sono molte cose che puoi fare per migliorare la velocità di pittura, al punto che lo sfarfallio non si nota più. Inizia affrontando l'immagine di sfondo. Possono essere molto costosi quando l'immagine di origine è grande e deve essere ridotta per adattarsi al controllo. Modificare la proprietà BackgroundImageLayout in "Tile". Se questo dà una notevole velocità, torna al tuo programma di pittura e ridimensiona l'immagine per adattarla meglio alle dimensioni tipiche del controllo. Oppure scrivi il codice nel metodo OnResize () dell'UC per creare una copia dell'immagine di dimensioni adeguate in modo che non debba essere ridimensionata ogni volta che il controllo viene ridisegnato. Usa il formato pixel Format32bppPArgb per quella copia, rende circa 10 volte più veloce di qualsiasi altro formato pixel.

La prossima cosa che puoi fare è impedire che i fori siano così evidenti e contrastino male con l'immagine. È possibile disattivare il flag di stile WS_CLIPCHILDREN per UC, il flag che impedisce all'UC di disegnare nell'area in cui si trovano i controlli figlio. Incolla questo codice nel codice di UserControl:

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

I controlli figlio ora dipingeranno se stessi sopra l'immagine di sfondo. Potresti ancora vederli dipingersi uno per uno, ma il brutto buco bianco o nero intermedio non sarà visibile.

Ultimo ma non meno importante, ridurre il numero di controlli figlio è sempre un buon approccio per risolvere i problemi di pittura lenta. Ignora l'evento OnPaint () dell'UC e disegna ciò che ora viene mostrato in un bambino. Etichetta particolare e PictureBox sono molto dispendiosi. Comodo per punta e clicca ma la loro alternativa leggera (disegnare una stringa o un'immagine) richiede solo una singola riga di codice nel tuo metodo OnPaint ().


Disattiva WS_CLIPCHILDREN esperienza utente migliorata per me.
Mahesh

Assolutamente perfetto! .. Grazie mille
AlejandroAlis

8

Questo è un vero problema e la risposta data da Hans Passant è ottima per salvare lo sfarfallio. Tuttavia, come ha detto, ci sono effetti collaterali e possono essere brutti (UI brutta). Come affermato, "Puoi disattivare il WS_CLIPCHILDRENflag di stile per l'UC", ma questo lo disattiva solo per un UC. I componenti nel modulo principale presentano ancora problemi.

Ad esempio, una barra di scorrimento del pannello non dipinge, perché tecnicamente è nell'area figlio. Tuttavia, il componente figlio non disegna la barra di scorrimento, quindi non viene dipinto fino a quando il mouse non viene passato (o un altro evento lo attiva).

Inoltre, le icone animate (che cambiano le icone in un ciclo di attesa) non funzionano. La rimozione delle icone su un tabPage.ImageKeynon ridimensiona / ridipinge le altre tabPages in modo appropriato.

Quindi stavo cercando un modo per disattivare il WS_CLIPCHILDRENdisegno iniziale in modo che il mio modulo verrà caricato ben dipinto, o meglio ancora accenderlo solo durante il ridimensionamento del mio modulo con molti componenti.

Il trucco sta nel far chiamare l'applicazione CreateParamscon lo WS_EX_COMPOSITED/WS_CLIPCHILDRENstile desiderato . Ho trovato un hack qui ( 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 ) e funziona alla grande. Grazie AngryHacker!

Inserisco la TurnOnFormLevelDoubleBuffering()chiamata ResizeBeginnell'evento del modulo e TurnOffFormLevelDoubleBuffering()chiamo nel modulo ResizeEnd (o lo lascio semplicemente WS_CLIPCHILDRENdopo che è stato inizialmente dipinto correttamente).

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

Il tuo codice non include il metodo TurnOnFormLevelDoubleBuffering () ...
Dan W

@DanW Dai un'occhiata all'URL pubblicato in questa risposta ( angryhacker.com/blog/archive/2010/07/21/… )
ChrisB

Il collegamento in questa risposta sembra essere morto. Sono curioso della soluzione, hai un link a un altro esempio?
Pratt Hinds

6

Se stai facendo una pittura personalizzata nel controllo (cioè sovrascrivendo OnPaint) puoi provare tu stesso il doppio buffering.

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

E invalida il tuo controllo con una proprietà NeedRepaint

Altrimenti la risposta sopra con SuspendLayout e ResumeLayout è probabilmente quello che vuoi.


Questo è un metodo creativo per simulare il doppio buffer !. Puoi aggiungere if (image != null) image.Dispose();primaimage = new Bitmap...
S.Serpooshan


2

Nella maschera principale o nel controllo utente in cui risiede l'immagine di sfondo impostare la BackgroundImageLayoutproprietà su Centero Stretch. Noterai una grande differenza durante il rendering del controllo utente.


2

Ho provato ad aggiungerlo come commento ma non ho abbastanza punti. Questa è l'unica cosa che abbia mai aiutato i miei problemi di sfarfallio così tante grazie a Hans per il suo post. Per chiunque stia usando C ++ builder come me, ecco la traduzione

Aggiungi la dichiarazione CreateParams al file .h del form principale della tua applicazione es

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

e aggiungilo al tuo file .cpp

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

2

Inserisci il codice qui sotto nel tuo costruttore o evento OnLoad e se stai usando una sorta di controllo utente personalizzato che ha sottocontrolli, dovrai assicurarti che anche questi controlli personalizzati siano a doppio buffer (anche se nella documentazione MS dicono è impostato su true per impostazione predefinita).

Se stai creando un controllo personalizzato, potresti voler aggiungere questo flag nel tuo ctor:

SetStyle(ControlStyles.OptimizedDoubleBuffer, true);

Facoltativamente puoi usare questo codice nel tuo modulo / controllo:

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

Stiamo iterando attraverso tutti i controlli nel form / control e accedendo alle loro DoubleBufferedproprietà, quindi lo cambiamo in true per fare in modo che ogni controllo sul form sia doppio buffer. Il motivo per cui riflettiamo qui è perché immagina di avere un controllo con controlli figlio non accessibili, in questo modo, anche se sono controlli privati, cambieremo comunque la loro proprietà in true.

Ulteriori informazioni sulla tecnica del doppio buffering sono disponibili qui .

C'è un'altra proprietà che di solito ignoro per risolvere questo problema:

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

WS_EX_COMPOSITED - Dipinge tutti i discendenti di una finestra in ordine di pittura dal basso verso l'alto utilizzando il doppio buffering.

Puoi trovare altre di queste bandiere di stile qui .

Spero che aiuti!


1

Giusto per aggiungere alla risposta data da Hans:

(Versione TLDR: la trasparenza è più pesante di quanto pensi, usa solo colori solidi ovunque)

Se WS_EX_COMPOSITED, DoubleBuffered e WS_CLIPCHILDREN non hanno risolto il tuo sfarfallio (per me WS_CLIPCHILDREN lo ha reso ancora peggiore), prova questo: passa attraverso TUTTI i tuoi controlli e tutto il tuo codice, e ovunque tu abbia Qualsiasi trasparenza o semitrasparenza per BackColor, ForeColor o qualsiasi altro colore, basta rimuoverlo, utilizzare solo colori solidi. Nella maggior parte dei casi in cui pensi di dover usare solo la trasparenza, non lo fai. Riprogettare il codice e i controlli e utilizzare colori a tinta unita. Ho avuto uno sfarfallio terribile e terribile e il programma era lento. Una volta rimossa la trasparenza, ha accelerato in modo significativo e c'è 0 sfarfallio.

EDIT: Per aggiungere ulteriori, ho appena scoperto che WS_EX_COMPOSITED non deve essere a livello di finestra, potrebbe essere applicato solo a controlli specifici! Questo mi ha risparmiato molti problemi. Basta creare un controllo personalizzato ereditato da qualsiasi controllo necessario e incollare l'override già pubblicato per WS_EX_COMPOSITED. In questo modo si ottiene un doppio buffer di basso livello solo su questo controllo, evitando i fastidiosi effetti collaterali nel resto dell'applicazione!


0

So che questa domanda è molto vecchia, ma voglio dare la mia esperienza al riguardo.

Ho avuto molti problemi con lo Tabcontrolsfarfallio in un modulo con sovrascrittura OnPainte / o OnPaintBackGroundin Windows 8 utilizzando .NET 4.0.

L'unico pensiero che ha funzionato è stato NON USARE il Graphics.DrawImagemetodo in OnPaintoverride, in altre parole, quando si disegnava direttamente alla Grafica fornita dal PaintEventArgs, anche dipingendo tutto il rettangolo, lo sfarfallio scompariva. Ma se si chiama il DrawImagemetodo, anche disegnando una Bitmap ritagliata, (creata per il doppio buffering) appare lo sfarfallio.

Spero che sia d'aiuto!


0

Ho combinato questa correzione dello sfarfallio e questa correzione del carattere , quindi ho dovuto aggiungere un po 'del mio codice per avviare un timer sulla vernice per invalidare il TabControl quando esce dallo schermo e torna indietro, ecc.

Tutti e tre fanno questo:

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

Non sono il creatore, ma da quello che ho capito la bitmap fa tutti i bug bypassando.

Questa è stata l'unica cosa che ha risolto definitivamente lo sfarfallio di TabControl (con icone) per me.

video di risultati di differenza: vanilla tabcontrol vs tabcontrolex

http://gfycat.com/FineGlitteringDeermouse

ps. dovrai impostare HotTrack = true, perché questo risolve anche quel bug


-2

Hai provato Control.DoubleBufferedProperty?

Ottiene o imposta un valore che indica se questo controllo deve ridisegnare la sua superficie usando un buffer secondario per ridurre o prevenire lo sfarfallio.

Anche questo e questo potrebbe aiutare.


-9

Non c'è bisogno di alcun doppio buffering e tutta quella roba ragazzi ...

Una soluzione semplice ...

Se stai usando l'interfaccia MDI, incolla il codice sottostante nel modulo principale. Rimuoverà tutto lo sfarfallio dalle pagine. Tuttavia, alcune pagine che richiedono più tempo per il caricamento verranno visualizzate in 1 o 2 secondi. Ma questo è meglio che mostrare una pagina tremolante in cui ogni elemento arriva uno per uno.

Questa è l'unica soluzione migliore per l'intera applicazione. Vedi il codice da inserire nel form principale:

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

12
Quindi, quello che stai dicendo è che la risposta fornita da Hans più di due anni fa è, in effetti, corretta? Grazie, Kshitiz. È davvero molto utile!
Fernando
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.