我(终于!)找到了一种在玻璃背景上呈现 Windows.Forms 控件的方法,它看起来没有任何主要缺点或任何大的实现时间。这是受 这篇文章 的启发,该文章基本上解释了如何本地覆盖控件的绘制以进行绘制。
我使用了这种方法将控件渲染到位图上,并使用 GDI+ 和适当的 Alpha 通道在 NativeWindow 的绘图区域上重新绘制它。这个实现很简单,但可以完善易用性,但这不是这个问题的重点。然而,结果相当令人满意:
然而,还有两个需要修复的地方才能使其真正可用。
- 双缓冲,因为此覆盖图像和实际控件之间的闪烁频繁且可怕(通过代码自行测试)。使用
SetStyles(this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true)
设置基本控件为双缓存无效,但我猜测我们可以通过一些试错使其起作用。 某些控件无法工作。我已经能够让以下内容正常工作:
- 文本框
- 掩码组合框
- 组合框(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")] 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; } }
this.Control.Invalidate()
引起的,您是否尝试过在没有this.Control.Invalidate()
的情况下进行修复,可能需要创建一个keyDown处理程序,将所有的keyDown操作传递给当前焦点对象,然后调用重绘?(这只是一个想法,我知道很容易陷入复杂性中) - Barkermn01