Contrôles de rendu sur verre: la solution trouvée, nécessite un double tamponnage / perfectionnement

J’ai (enfin!) Trouvé un moyen de rendre les contrôles Windows.Forms sur le verre qui ne semble pas avoir d’inconvénient majeur, ni de temps de mise en œuvre important. Il s’inspire de cet article de Coded, qui explique essentiellement comment remplacer nativement la peinture des contrôles pour les dessiner.

J’ai utilisé cette approche pour rendre le contrôle sur un bitmap et le peindre avec GDI + et le canal alpha approprié sur la zone de peinture de NativeWindow. L’implémentation est simple mais pourrait être perfectionnée pour la facilité d’utilisation, mais ce n’est pas le but de cette question. Les résultats sont cependant très satisfaisants:

Real textbox on glass

Il y a cependant deux domaines à corriger pour que cela soit vraiment utilisable.

  1. Double buffering , car le scintillement entre cette image superposée et le contrôle réel est fréquent et horrible (testez-vous avec le code). Définir le contrôle de base comme étant à double tampon avec SetStyles(this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true) ne fonctionne pas, mais je suppose que nous pouvons le faire fonctionner avec un peu d’essais et d’erreurs.
  2. Certains contrôles ne fonctionnent pas . J’ai pu faire le travail suivant:

    • Zone de texte
    • MaskedComboBox
    • ComboBox (DropDownStyle == DropDownList)
    • ListBox
    • CheckedListBox
    • ListView
    • TreeView
    • DateTimePicker
    • MonthCalendar

    Mais je ne peux pas les faire fonctionner, même si je ne vois pas pourquoi. Ma supposition éclairée est que le NativeWindow gère la référence, alors que je dois faire référence à la partie “entrée” (textuelle), probablement un enfant. Toute aide des experts WinAPI sur la manière d’obtenir ce descripteur de fenêtre d’entrée est la bienvenue.

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

Mais fixer le double tampon constituerait le principal objective de la facilité d’utilisation.

Voici un exemple d’utilisation:

 new GlassControlRenderer(textBox1); 

Voici le code:

 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 (eg TextBox, ListBox) } } 

Je serais ravi de résoudre ce problème, et une fois pour toutes, il existe un véritable moyen de rendre le rendu sur verre, pour tous les contrôles .NET, sans WPF.

EDIT: chemins possibles pour le double tampon / anti-scintillement:

  • Supprimer la ligne this.Control.Invalidate() supprime le scintillement, mais rompt la saisie dans une zone de texte.
  • J’ai essayé l’approche WM_SETREDRAW et la méthode SuspendLayout, sans succès:

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

Voici une version avec beaucoup moins de scintillement, mais pas encore parfaite.

 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 (eg TextBox, ListBox) } } 

J’ai eu un problème avec le scintillement avant (beaucoup de contrôles sur le formulaire, les contrôles utilisateur). J’ai essayé presque tout. C’est ce qui a fonctionné pour moi:

Avez-vous essayé de mettre cela dans votre classe de formulaire?

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

Et dans votre constructeur, vous devez activer le double tampon, sinon cela ne fonctionnera pas:

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

Cela ne fonctionne que si l’aero est activé, sinon cela peut rendre le scintillement encore pire.

et vous pouvez également append ceci

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

à votre classe UserControls.