c# .net winapi gdi+ aero

c# - Controles de renderizado en vidrio: solución encontrada, necesita doble buffering/perfeccionamiento



.net winapi (2)

Yo (¡finalmente!) Encontré una forma de renderizar los controles de Windows.Forms sobre el vidrio que no parece tener ningún inconveniente importante ni gran tiempo de implementación. Está inspirado en este artículo de Coded, que básicamente explica cómo anular de forma nativa la pintura de controles para dibujar sobre ellos.

Usé ese enfoque para renderizar el control en un mapa de bits y volver a pintarlo con GDI + y el canal alfa apropiado sobre el área de pintura de NativeWindow. La implementación es simple, pero podría perfeccionarse para la usabilidad, pero ese no es el punto de esta pregunta. Los resultados son, sin embargo, bastante satisfactorios:

Sin embargo, hay 2 áreas que necesitan ser arregladas para que esto sea realmente útil.

  1. Double-buffering , porque el parpadeo entre esta imagen superpuesta y el control real es frecuente y horrible (compruébalo con el código). Establecer el control básico como doble buffer con SetStyles(this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true) no funciona, pero sospecho que podemos hacerlo funcionar con un poco de prueba y error.
  2. Algunos controles no funcionan . He podido hacer el siguiente trabajo:

    • Caja de texto
    • MaskedComboBox
    • ComboBox (DropDownStyle == DropDownList)
    • Cuadro de lista
    • CheckedListBox
    • Vista de la lista
    • Vista de árbol
    • DateTimePicker
    • MonthCalendar

    Pero no puedo hacer que funcionen, aunque no veo por qué no. Mi conjetura es que el asa de NativeWindow real hace referencia al control completo, mientras que necesito hacer referencia a la parte de "entrada" (textual), probablemente un elemento secundario. Cualquier ayuda de los expertos de WinAPI sobre cómo obtener el control de la ventana de entrada es bienvenida.

    • ComboBox (DropDownStyle! = DropDownList)
    • NumericUpDown
    • RichTextBox

Pero arreglar el doble buffering sería el enfoque principal para la usabilidad.

Aquí hay un uso de muestra:

new GlassControlRenderer(textBox1);

Aquí está el código:

public class GlassControlRenderer : NativeWindow { private Control Control; private Bitmap Bitmap; private Graphics ControlGraphics; protected override void WndProc(ref Message m) { switch (m.Msg) { case 0xF: // WM_PAINT case 0x85: // WM_NCPAINT case 0x100: // WM_KEYDOWN case 0x200: // WM_MOUSEMOVE case 0x201: // WM_LBUTTONDOWN this.Control.Invalidate(); base.WndProc(ref m); this.CustomPaint(); break; default: base.WndProc(ref m); break; } } public GlassControlRenderer(Control control) { this.Control = control; this.Bitmap = new Bitmap(this.Control.Width, this.Control.Height); this.ControlGraphics = Graphics.FromHwnd(this.Control.Handle); this.AssignHandle(this.Control.Handle); } public void CustomPaint() { this.Control.DrawToBitmap(this.Bitmap, new Rectangle(0, 0, this.Control.Width, this.Control.Height)); this.ControlGraphics.DrawImageUnscaled(this.Bitmap, -1, -1); // -1, -1 for content controls (e.g. TextBox, ListBox) } }

Estaría encantado de arreglar esto, y de una vez por todas tengo una manera real de renderizar en vidrio, para todos los controles .NET, sin WPF.

EDITAR: Posibles rutas para doble buffer / anti parpadeo:

  • Al eliminar la línea this.Control.Invalidate() elimina el parpadeo, pero se interrumpe el tipeo en un cuadro de texto.
  • Probé el método WM_SETREDRAW y el método SuspendLayout, sin suerte:

    [DllImport("user32.dll")] public static extern int SendMessage(IntPtr hWnd, Int32 wMsg, bool wParam, Int32 lParam); private const int WM_SETREDRAW = 11; public static void SuspendDrawing(Control parent) { SendMessage(parent.Handle, WM_SETREDRAW, false, 0); } public static void ResumeDrawing(Control parent) { SendMessage(parent.Handle, WM_SETREDRAW, true, 0); parent.Refresh(); } protected override void WndProc(ref Message m) { switch (m.Msg) { case 0xF: // WM_PAINT case 0x85: // WM_NCPAINT case 0x100: // WM_KEYDOWN case 0x200: // WM_MOUSEMOVE case 0x201: // WM_LBUTTONDOWN //this.Control.Parent.SuspendLayout(); //GlassControlRenderer.SuspendDrawing(this.Control); //this.Control.Invalidate(); base.WndProc(ref m); this.CustomPaint(); //GlassControlRenderer.ResumeDrawing(this.Control); //this.Control.Parent.ResumeLayout(); break; default: base.WndProc(ref m); break; } }


Aquí hay una versión con mucho menos parpadeo, sin embargo, no es perfecta.

public class GlassControlRenderer : NativeWindow { private Control Control; private Bitmap Bitmap; private Graphics ControlGraphics; private object Lock = new object(); protected override void WndProc(ref Message m) { switch (m.Msg) { case 0x14: // WM_ERASEBKGND this.CustomPaint(); break; case 0x0F: // WM_PAINT case 0x85: // WM_NCPAINT case 0x100: // WM_KEYDOWN case 0x101: // WM_KEYUP case 0x102: // WM_CHAR case 0x200: // WM_MOUSEMOVE case 0x2A1: // WM_MOUSEHOVER case 0x201: // WM_LBUTTONDOWN case 0x202: // WM_LBUTTONUP case 0x285: // WM_IME_SELECT case 0x300: // WM_CUT case 0x301: // WM_COPY case 0x302: // WM_PASTE case 0x303: // WM_CLEAR case 0x304: // WM_UNDO base.WndProc(ref m); this.CustomPaint(); break; default: base.WndProc(ref m); break; } } private Point Offset { get; set; } public GlassControlRenderer(Control control, int xOffset, int yOffset) { this.Offset = new Point(xOffset, yOffset); this.Control = control; this.Bitmap = new Bitmap(this.Control.Width, this.Control.Height); this.ControlGraphics = Graphics.FromHwnd(this.Control.Handle); this.AssignHandle(this.Control.Handle); } public void CustomPaint() { this.Control.DrawToBitmap(this.Bitmap, new Rectangle(0, 0, this.Control.Width, this.Control.Height)); this.ControlGraphics.DrawImageUnscaled(this.Bitmap, this.Offset); // -1, -1 for content controls (e.g. TextBox, ListBox) } }


Tuve un problema con el parpadeo antes (muchos controles en el formulario, controles de usuario). Intenté casi todo. Esto es lo que funcionó para mí:

¿Has intentado poner esto en tu clase de forma?

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

Y en su constructor debe habilitar el doble almacenamiento en búfer; de lo contrario, no funcionará:

this.DoubleBuffered = true; this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);

Solo funciona cuando Aero está habilitado, si no puede empeorar el parpadeo.

y también puedes agregar esto

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

a su clase UserControls.