玻璃上的渲染控件:找到解决方案,需要翻一番

我(终于!)找到了一种在Windows上呈现Windows.Forms控件的方式,它似乎没有任何主要缺点,也没有任何大的实施时间。 它的灵感来自Coded的这篇文章,它基本上解释了如何在本地重写控件的绘画来绘制它们。

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

玻璃上的真实文本框

但是,有两个方面需要修复才能真正使用。

  • 双缓冲 ,因为这个叠加图像和实际控制之间的闪烁频繁且可怕(用代码测试自己)。 将基本控件设置为使用SetStyles(this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true)进行双缓冲SetStyles(this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true)不起作用,但我怀疑我们可以通过一些试验和错误使它工作。
  • 一些控件不起作用 。 我已经能够做出以下工作:

  • 文本框
  • MaskedComboBox
  • 组合框(DropDownStyle == DropDownList)
  • 列表框
  • CheckedListBox
  • 列表显示
  • 树视图
  • 的DateTimePicker
  • 的MonthCalendar
  • 但我不能让这些工作,但我不明白为什么不。 我的教育猜测是,我引用整个控件的实际NativeWindow句柄,而我需要引用它的“输入”(文本)部分,可能是一个孩子。 欢迎任何来自WinAPI专家的关于如何获得输入窗口句柄的帮助。

  • 组合框(DropDownStyle!= DropDownList)
  • 的NumericUpDown
  • RichTextBox的
  • 但修复双缓冲将是可用性的主要焦点

    以下是一个示例用法:

    new GlassControlRenderer(textBox1);
    

    代码如下:

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

    我真的很高兴能够解决这个问题,并且一劳永逸地为所有的.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;
        }
    }
    

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

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

    之前我有一个闪烁的问题(表单上的很多控件,用户控件)。 尝试几乎所有的东西。 这对我来说很有用:

    你有没有尝试把这个放在你的表单类中?

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

    而在你的构造函数中你必须启用双缓冲,否则它将无法工作:

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

    它只在航空启用时才起作用,否则会使闪烁变得更糟。

    你也可以添加这个

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

    到你的UserControls类。

    链接地址: http://www.djcxy.com/p/50305.html

    上一篇: Rendering controls on glass: Solution found, needs double

    下一篇: Why does DrawImageUnscaled cause flickering when used from WM