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

发布于 2024-11-29 08:03:36 字数 3756 浏览 1 评论 0原文

我(终于!)找到了一种在玻璃上渲染 Windows.Forms 控件的方法,该方法似乎没有任何重大缺点,也没有任何较长的实施时间。它的灵感来自 Coded 的这篇文章 ,它基本上解释了如何本机覆盖控件的绘制以在其上进行绘制。

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

Real textbox on glass

有 2 个区域需要修复才能解决此问题不过确实有用。

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

    • 文本框
    • MaskedComboBox
    • 组合框(DropDownStyle == DropDownList)
    • 列表框
    • 选中列表框
    • 列表视图
    • 树视图
    • 日期时间选择器
    • 月历

    但是我无法让这些工作,尽管我不明白为什么不可以。我有根据的猜测是,实际的 NativeWindow 句柄我正在引用整个控件,而我需要引用它的“输入”(文本)部分,可能是一个孩子。欢迎 WinAPI 专家提供有关如何获取输入窗口句柄的任何帮助。

    • 组合框(DropDownStyle!= DropDownList)
    • 数字上下
    • 富文本框

但是修复双缓冲将是可用性的主要焦点

这是一个示例用法:

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")] 公共静态 extern int SendMessage(IntPtr hWnd, Int32 wMsg, bool wParam, Int32 lParam); 私有常量 int WM_SETREDRAW = 11; 公共静态无效SuspendDrawing(控制父级) { SendMessage(parent.Handle, WM_SETREDRAW, false, 0); } 公共静态无效 ResumeDrawing(控制父级) { SendMessage(parent.Handle, WM_SETREDRAW, true, 0); 父级.Refresh(); } protected override void WndProc(ref Message m) { 开关(m.Msg) { 情况 0xF: // WM_PAINT 情况 0x85: // WM_NCPAINT 案例 0x100: // WM_KEYDOWN 情况 0x200: // WM_MOUSEMOVE 情况 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(); 休息; 默认: base.WndProc(ref m); 休息; } }

I (finally!) found a way of rendering Windows.Forms controls on glass that doesn't seem to have any major drawback nor any big implementation time. It's inspired by this article from Coded, which basically explains how to natively override the painting of controls to draw over them.

I used that approach to render the control to a bitmap and paint it back with GDI+ and the appropriate alpha channel over the NativeWindow's painting area. The implementation is simple but could be perfected for usability, but that's not the point of this question. The results are, however, quite satisfying:

Real textbox on glass

There are 2 areas that need to be fixed for this to be really usable, however.

  1. Double-buffering, because the flicker between this overlay image and the real control is frequent and horrible (test yourself with the code). Setting the basic control to be double buffered with SetStyles(this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true) doesn't work, but I suspect we can make it work with a little trial and error.
  2. Some controls don't work. I've been able to make the following work:

    • TextBox
    • MaskedComboBox
    • ComboBox (DropDownStyle == DropDownList)
    • ListBox
    • CheckedListBox
    • ListView
    • TreeView
    • DateTimePicker
    • MonthCalendar

    But I can't get these to work, although I don't see why not. My educated guess is that the actual NativeWindow handle I'm referencing the whole control, while I need to reference the "input" (textual) part of it, probably a child. Any help from WinAPI experts on how to get that input window handle is welcome.

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

But fixing the double buffering would be the main focus for usability.

Here's a sample usage:

new GlassControlRenderer(textBox1);

Here's the 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 (e.g. TextBox, ListBox)
    }
}

I'd be really glad to fix this, and once and for all have a real way of rendering on glass, for all .NET controls, without WPF.

EDIT: Possible paths for double-buffering/anti-flicker:

  • Removing the line this.Control.Invalidate() removes the flicker, but breaks the typing in a textbox.
  • I've tried the WM_SETREDRAW approach and the SuspendLayout method, with no luck:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

感情旳空白 2024-12-06 08:03:36

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

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

Here is a version with much less flickering, still not perfect though.

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)
    }
}
寄居者 2024-12-06 08:03:36

我之前遇到过闪烁问题(表单上有很多控件,用户控件)。几乎尝试了一切。这对我有用:

您是否尝试过将其放入表单类中?

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

它仅在启用 aero 时才有效,如果没有启用,它会使闪烁变得更糟。

您还可以将其添加

  protected override CreateParams CreateParams
    {
        get
        {
            CreateParams cp = base.CreateParams;

                cp.ExStyle |= 0x02000000; // WS_EX_COMPOSITED
            return cp;
        }
    } 

到您的 UserControls 类中。

I had a problem with flickering before (lots of controls on the form, user controls). Tried almost everything. This is what worked for me:

Have you tried putting this in your form class?

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

And in your constructor you have to enable double buffering, otherwise it won't work:

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

It only works when aero is enabled, if not it can make flickering even worse.

and you can also add this

  protected override CreateParams CreateParams
    {
        get
        {
            CreateParams cp = base.CreateParams;

                cp.ExStyle |= 0x02000000; // WS_EX_COMPOSITED
            return cp;
        }
    } 

to your UserControls class.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文