在玻璃上渲染控件:找到解决方案,需要双缓冲/完善

Laz*_*zlo 46 .net c# winapi gdi+ aero

我(终于!)找到了一种在玻璃上渲染Windows.Forms控件的方法,它似乎没有任何重大缺点,也没有任何大的实现时间.它受到Coded 的这篇文章的启发,它基本上解释了如何本地覆盖控件绘制以绘制它们.

我使用该方法将控件渲染为位图,并使用GDI +和适当的Alpha通道将其绘制回NativeWindow的绘制区域.实现很简单,但可以完善可用性,但这不是这个问题的重点.然而,结果非常令人满意:

在玻璃上的真正文本框

但是,有两个区域需要修复才能真正使用.

  1. 双缓冲,因为这个叠加图像和真实控件之间的闪烁是频繁和可怕的(用代码测试自己).将基本控件设置为双缓冲SetStyles(this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true)不起作用,但我怀疑我们可以通过一些试验和错误使其工作.
  2. 某些控件不起作用.我已经能够完成以下工作:

    • 文本框
    • MaskedComboBox
    • ComboBox(DropDownStyle == DropDownList)
    • 列表框
    • CheckedListBox
    • 列表显示
    • 树视图
    • 的DateTimePicker
    • 的MonthCalendar

    但我不能让这些工作,虽然我不明白为什么不.我有根据的猜测是,实际的NativeWindow句柄我引用了整个控件,而我需要引用它的"输入"(文本)部分,可能是一个孩子.有关如何获取输入窗口句柄的WinAPI专家的任何帮助都是受欢迎的.

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

但修复双缓冲将是可用性的主要关注点.

这是一个示例用法:

new GlassControlRenderer(textBox1);
Run Code Online (Sandbox Code Playgroud)

这是代码:

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

我真的很高兴解决这个问题,并且一劳永逸地在玻璃上渲染,对于所有.NET控件,没有WPF.

编辑:双缓冲/防闪烁的可能路径:

  • 删除行this.Control.Invalidate()会消除闪烁,但会破坏文本框中的输入.
  • 我尝试了WM_SETREDRAW方法和SuspendLayout方法,没有运气:

    [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;
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

Laz*_*zlo 6

这是一个闪烁少得多的版本,但仍然不完美.

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)
    }
}
Run Code Online (Sandbox Code Playgroud)