玻璃上的渲染控制:已找到解决方案,需要双缓冲/完善
我(终于!)找到了一种在玻璃上渲染 Windows.Forms 控件的方法,该方法似乎没有任何重大缺点,也没有任何较长的实施时间。它的灵感来自 Coded 的这篇文章 ,它基本上解释了如何本机覆盖控件的绘制以在其上进行绘制。
我使用这种方法将控件渲染为位图,并使用 GDI+ 和 NativeWindow 绘画区域上适当的 Alpha 通道将其重新绘制。实现很简单,但可以完善可用性,但这不是这个问题的重点。然而,结果非常令人满意:
有 2 个区域需要修复才能解决此问题不过确实有用。
- 双缓冲,因为这个叠加图像和真实控件之间的闪烁频繁且可怕(用代码测试一下)。使用
SetStyles(this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true)
将基本控件设置为双缓冲是行不通的,但我怀疑我们可以通过一些尝试和错误来使其工作。 某些控件不起作用,我已经能够进行以下操作:
- 文本框
- 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:
There are 2 areas that need to be fixed for this to be really usable, however.
- 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. 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
这是一个闪烁少得多的版本,但仍然不完美。
Here is a version with much less flickering, still not perfect though.
我之前遇到过闪烁问题(表单上有很多控件,用户控件)。几乎尝试了一切。这对我有用:
您是否尝试过将其放入表单类中?
在你的构造函数中,你必须启用双缓冲,否则它将无法工作:
它仅在启用 aero 时才有效,如果没有启用,它会使闪烁变得更糟。
您还可以将其添加
到您的 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?
And in your constructor you have to enable double buffering, otherwise it won't work:
It only works when aero is enabled, if not it can make flickering even worse.
and you can also add this
to your UserControls class.