Cómo corregir el parpadeo en los controles de usuario

107

En mi aplicación, me muevo constantemente de un control a otro. He creado no. de los controles de usuario, pero durante la navegación mis controles parpadean. tarda 1 o 2 segundos en actualizarse. Traté de configurar esto

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

pero no ayudó ... Cada control tiene la misma imagen de fondo con diferentes controles. Entonces, ¿cuál es la solución?
Gracias.

Royson
fuente
¿Dónde están estas declaraciones? Lo ideal es colocarlos en el constructor. ¿Llamaste UpdateStylesdespués de configurar estos? Está mal documentado, pero a veces puede ser necesario.
Thomas

Respuestas:

306

No es el tipo de parpadeo que puede resolver el doble búfer. Ni BeginUpdate o SuspendLayout. Tienes demasiados controles, la BackgroundImage puede hacer que sea un montón peor.

Comienza cuando UserControl se pinta a sí mismo. Dibuja la imagen de fondo, dejando huecos donde van las ventanas de control secundario. Luego, cada control secundario recibe un mensaje para pintarse a sí mismo, llenarán el agujero con el contenido de su ventana. Cuando tienes muchos controles, esos agujeros son visibles para el usuario por un tiempo. Normalmente son de color blanco, lo que contrasta mal con la imagen de fondo cuando está oscuro. O pueden ser negros si el formulario tiene su propiedad Opacity o TransparencyKey establecida, lo que contrasta mal con casi cualquier cosa.

Esta es una limitación bastante fundamental de Windows Forms, está atascada con la forma en que Windows representa Windows. Corregido por WPF por cierto, no usa ventanas para controles secundarios. Lo que querría es almacenar en búfer doble todo el formulario, incluidos los controles secundarios. Eso es posible, verifique mi código en este hilo para ver la solución. Sin embargo, tiene efectos secundarios y en realidad no aumenta la velocidad de pintura. El código es simple, pegue esto en su formulario (no en el control de usuario):

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

Hay muchas cosas que puede hacer para mejorar la velocidad de pintura, hasta el punto de que el parpadeo ya no se nota. Empiece por abordar la imagen de fondo. Pueden ser muy costosos cuando la imagen de origen es grande y necesita reducirse para ajustarse al control. Cambie la propiedad BackgroundImageLayout a "Tile". Si eso le da una aceleración notable, vuelva a su programa de pintura y cambie el tamaño de la imagen para que coincida mejor con el tamaño de control típico. O escriba el código en el método OnResize () de la UC para crear una copia de la imagen con el tamaño adecuado para que no tenga que cambiar de tamaño cada vez que el control vuelva a pintar. Utilice el formato de píxeles Format32bppPArgb para esa copia, se procesa unas 10 veces más rápido que cualquier otro formato de píxeles.

Lo siguiente que puedes hacer es evitar que los agujeros sean tan notorios y contrasten mal con la imagen. Puede desactivar la bandera de estilo WS_CLIPCHILDREN para UC, la bandera que evita que UC pinte en el área donde van los controles secundarios. Pegue este código en el código de UserControl:

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

Los controles secundarios ahora se pintarán sobre la imagen de fondo. Es posible que aún los vea pintándose uno por uno, pero el feo agujero intermedio blanco o negro no será visible.

Por último, pero no menos importante, la reducción del número de controles secundarios es siempre un buen enfoque para resolver problemas de pintura lenta. Anule el evento OnPaint () de la UC y dibuje lo que ahora se muestra en un niño. Particular Label y PictureBox son un desperdicio. Conveniente para apuntar y hacer clic, pero su alternativa liviana (dibujar una cadena o una imagen) toma solo una línea de código en su método OnPaint ().

Hans Passant
fuente
Desactive WS_CLIPCHILDREN la experiencia de usuario mejorada para mí.
Mahesh
¡Absolutamente perfecto! .. Muchas gracias
AlejandroAlis
8

Este es un problema real, y la respuesta que dio Hans Passant es excelente para salvar el parpadeo. Sin embargo, hay efectos secundarios, como él mencionó, y pueden ser feos (UI fea). Como se dijo, "Puede desactivar la WS_CLIPCHILDRENbandera de estilo para el UC", pero eso solo lo desactiva para un UC. Los componentes del formulario principal todavía tienen problemas.

Por ejemplo, una barra de desplazamiento del panel no se pinta, porque técnicamente está en el área secundaria. Sin embargo, el componente secundario no dibuja la barra de desplazamiento, por lo que no se pinta hasta que se pasa el mouse (u otro evento lo activa).

Además, los íconos animados (cambiar íconos en un ciclo de espera) no funcionan. La eliminación de íconos en un tabPage.ImageKeyno redimensiona / repinta las otras páginas de pestañas de manera apropiada.

Así que estaba buscando una manera de desactivar la WS_CLIPCHILDRENpintura inicial para que mi formulario se cargue bien pintado, o mejor aún, solo lo encienda mientras cambia el tamaño de mi formulario con muchos componentes.

El truco consiste en hacer que la aplicación llame CreateParamscon el WS_EX_COMPOSITED/WS_CLIPCHILDRENestilo deseado . Encontré un truco aquí ( 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 ) y funciona muy bien. ¡Gracias AngryHacker!

Pongo la TurnOnFormLevelDoubleBuffering()llamada en el ResizeBeginevento de formulario y TurnOffFormLevelDoubleBuffering()llamo en el evento ResizeEnd de formulario (o simplemente lo dejo WS_CLIPCHILDRENdespués de que se haya pintado inicialmente correctamente).

    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;
    }
usuario2044810
fuente
Su código no incluye el método TurnOnFormLevelDoubleBuffering () ...
Dan W
@DanW Eche un vistazo a la URL publicada en esta respuesta ( angryhacker.com/blog/archive/2010/07/21/… )
ChrisB
El enlace en esta respuesta parece estar muerto. Tengo curiosidad por la solución, ¿tiene un enlace a otro ejemplo?
Pratt Hinds
6

Si está haciendo alguna pintura personalizada en el control (es decir, anulando OnPaint), puede probar el doble búfer usted mismo.

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 invalidar tu control con una propiedad NeedRepaint

De lo contrario, la respuesta anterior con SuspendLayout y ResumeLayout es probablemente lo que desea.

Patricio
fuente
Este es un método creativo para simular el doble búfer. Puede agregar if (image != null) image.Dispose();antesimage = new Bitmap...
S.Serpooshan
2

En el formulario principal o control de usuario donde reside la imagen de fondo, establezca la BackgroundImageLayoutpropiedad en Centero Stretch. Notará una gran diferencia cuando el control de usuario esté renderizando.

revobtz
fuente
2

Traté de agregar esto como un comentario pero no tengo suficientes puntos. Esto es lo único que ha ayudado a mis problemas de parpadeo. Muchas gracias a Hans por su publicación. Para cualquiera que esté usando el constructor de c ++ como yo, aquí está la traducción

Agregue la declaración CreateParams al archivo .h del formulario principal de su aplicación, por ejemplo

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

y agregue esto a su archivo .cpp

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

Coloque el código a continuación en su constructor o evento OnLoad y si está utilizando algún tipo de control de usuario personalizado que tenga subcontroles, deberá asegurarse de que estos controles personalizados también tengan doble búfer (aunque en la documentación de MS dicen está establecido en verdadero de forma predeterminada).

Si está creando un control personalizado, es posible que desee agregar esta bandera a su ctor:

SetStyle(ControlStyles.OptimizedDoubleBuffer, true);

Opcionalmente, puede usar este código en su formulario / control:

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

Repasamos todos los controles del formulario / control y DoubleBufferedaccedemos a su propiedad y luego lo cambiamos a verdadero para que cada control del formulario tenga doble búfer. La razón por la que reflexionamos aquí es porque imagina que tienes un control que tiene controles secundarios que no son accesibles, de esa manera, incluso si son controles privados, cambiaremos su propiedad a verdadera.

Puede encontrar más información sobre la técnica de doble búfer aquí .

Hay otra propiedad que normalmente anulo para solucionar este problema:

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

WS_EX_COMPOSITED - Pinta todos los descendientes de una ventana en orden de pintura de abajo hacia arriba usando doble búfer.

Puedes encontrar más de estas banderas de estilo aquí .

¡Espero que ayude!


fuente
1

Solo para agregar a la respuesta que dio Hans:

(Versión TLDR: la transparencia es más pesada de lo que cree, use solo colores sólidos en todas partes)

Si WS_EX_COMPOSITED, DoubleBuffered y WS_CLIPCHILDREN no resolvieron su parpadeo (para mí WS_CLIPCHILDREN lo empeoró aún más), intente esto: revise TODOS sus controles y todo su código, y donde sea que tenga cualquier transparencia o semitransparencia para BackColor, ForeColor o cualquier otro color, solo quítalo, usa solo colores sólidos. En la mayoría de los casos en los que pensar que sólo tiene que usar la transparencia, no es así. Rediseñe su código y controles, y use colores sólidos. Tenía un parpadeo terrible, terrible y el programa funcionaba lento. Una vez que eliminé la transparencia, se aceleró significativamente y hay 0 parpadeo.

EDITAR: Para agregar más, acabo de descubrir que WS_EX_COMPOSITED no tiene que ser en toda la ventana, ¡podría aplicarse solo a controles específicos! Esto me ahorró muchos problemas. Simplemente cree un control personalizado heredado de cualquier control que necesite y pegue la anulación ya publicada para WS_EX_COMPOSITED. De esta manera, obtiene doble búfer de bajo nivel solo en este control, ¡evitando los desagradables efectos secundarios en el resto de la aplicación!

Daniel
fuente
0

Sé que esta pregunta es muy antigua, pero quiero dar mi experiencia al respecto.

Tuve muchos problemas con el Tabcontrolparpadeo en un formulario anulado OnPainty / o OnPaintBackGrounden Windows 8 usando .NET 4.0.

Lo único que ha funcionado ha sido NO UTILIZAR el Graphics.DrawImagemétodo en OnPaintoverrides, es decir, cuando se dibujaba directamente en los Gráficos proporcionados por el PaintEventArgs, incluso pintando todo el rectángulo, el parpadeo desaparecía. Pero si llama al DrawImagemétodo, incluso dibujando un mapa de bits recortado, (creado para doble búfer) aparece el parpadeo.

¡Espero eso ayude!

ZeroWorks
fuente
0

Combiné esta corrección de parpadeo y esta corrección de fuente , luego tuve que agregar un poco de mi propio código para iniciar un temporizador en la pintura para invalidar el TabControl cuando sale de la pantalla y regresa, etc.

Los tres hacen esto:

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

No soy el creador, pero por lo que tengo entendido, el mapa de bits evita todos los errores.

Esto fue lo único que resolvió definitivamente el parpadeo de TabControl (con iconos) para mí.

video de resultado de diferencia: vanilla tabcontrol vs tabcontrolex

http://gfycat.com/FineGlitteringDeermouse

PD. deberá establecer HotTrack = true, porque esto también corrige ese error

usuario3732487
fuente
-2

¿Probaste Control.DoubleBufferedProperty?

Obtiene o establece un valor que indica si este control debe volver a dibujar su superficie mediante un búfer secundario para reducir o evitar el parpadeo.

También esto y esto podrían ayudar.

KMån
fuente
-9

No hay necesidad de ningún búfer doble y todas esas cosas chicos ...

Una solución simple ...

Si está utilizando la interfaz MDI, simplemente pegue el código a continuación en el formulario principal. Eliminará todo parpadeo de las páginas. Sin embargo, algunas páginas que requieren más tiempo para cargarse aparecerán en 1 o 2 segundos. Pero esto es mejor que mostrar una página parpadeante en la que cada elemento viene uno por uno.

Esta es la única mejor solución para toda la aplicación. Vea el código para poner en el formulario principal:

protected override CreateParams CreateParams {
  get {
    CreateParams cp = base.CreateParams;
    cp.ExStyle |= 0x02000000;  // Turn on WS_EX_COMPOSITED
    return cp;
  }
} 
Kshitiz
fuente
12
Entonces, ¿lo que estás diciendo es que la respuesta que Hans dio hace más de dos años es, de hecho, correcta? Gracias, Kshitiz. ¡Eso es muy útil!
Fernando