使用分层窗口 API 实现无闪烁的渐变窗体和设置不透明度






3.58/5 (10投票s)
通过手动创建分层窗口 API 的包装器来获得对窗体不透明度的更多控制
引言
大家都曾在 Visual Studio 属性检查器中看到窗体的 Opacity 属性。但是很少有人真正了解其背后的工作原理,甚至不了解它的实现是多么糟糕。Windows 2K Microsoft 已经使用分层窗口 API 来美化鼠标光标,添加一个透明的阴影。这与 .NET Windows 窗体中的 opacity 设置以相同的方式完成。当通过 opacity 属性更改窗体的透明度时,窗体会在幕后进行 Windows API 调用,没错,它只是一个简单的 winAPI 包装器,集成到一个属性中。显然,您失去了一些控制权。让我们拿回一些控制权。
背景
在 .NET Framework 之前,透明窗口的实现方式与现在相同,只是需要更多的代码行。当将透明度设置为除 100% 之外的值时,.NET Framework 会调用 SetWindowLong
并抛出一个标志来启用 WS_EX_LAYERED API
。分层窗口是一个非常强大的工具,但我们将只讨论其最简单的用法,即设置窗体的 Opacity,之后调用 SetLayeredWindowAttributes
,并向其传递一个字节值,指示透明度级别。0 表示完全透明,255 表示完全不透明。
听起来很简单,但是,Windows 不喜欢这种变化,当您在非分层窗口和分层窗口之间切换时,您的窗体会闪烁黑色。这很不美观。并且每次您在 100% 和任何其他有效值之间更改 opacity 属性时,都会进行切换,并且您几乎不可避免地会看到臭名昭著的黑色闪烁。
这里的技巧是始终保持窗体为分层窗口,您的窗体将使用更多的系统资源,但这不足以产生真正的差异。您的计算机已经在对分层窗口 API 进行数百次调用,再多几次也不会产生任何重大影响。
Using the Code
源代码下载中包含我名为 DDFormFader
的类。它是一个分层窗口包装类,可以为您处理所有麻烦!您只需构造它,并将要控制其透明度的窗体的句柄传递给它,然后使用其 public
函数来操作透明效果。
DDFormsExtentions.DDFormFader FF;
public Form1()
{
InitializeComponent();
//pass the class constructor the handle to the form
FF = new DDFormsExtentions.DDFormFader(Handle);
//set the form to a Layered Window Form
FF.setTransparentLayeredWindow();
//sets the length of time between fade steps in Milliseconds
FF.seekSpeed = 5;
// sets the amount of steps to take to reach target opacity
FF.StepsToFade = 8;
FF.updateOpacity((byte)0, false); // set the forms opacity to 0;
Load += new EventHandler(Form1_Load);
}
void Form1_Load(object sender, EventArgs e)
{
FF.seekTo((byte)255); // fade the form to full
//opacity on load, cleanly with no flicker.
}
您可以使用 UpdateOpacity
方法轻松更改窗体的透明度,并使用 seekTo
方法淡入淡出到任何透明度。
够了。让我们看看真正的代码吧!
首先,我们拦截负责设置标志的 user32
方法,这些标志使魔法发生
//gets information about the windows
[DllImport("user32.dll", SetLastError = true)]
static extern int GetWindowLong(IntPtr hWnd, int nIndex);
//sets bigflags that control the windows styles
DllImport("user32.dll")]
static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
//changes flags that modify attributes of the
//layered window such as alpha(opacity)
[DllImport("user32.dll")]
static extern bool SetLayeredWindowAttributes(IntPtr hwnd, uint crKey,
byte bAlpha, uint dwFlags);
并设置您需要的标志
//WS_EX constants
private const int GWL_EXSTYLE = (-20);
private const int WS_EX_LAYERED = 0x80000;
private const long LWA_ALPHA = 0x2L;
现在,我们通过简单地调用 setWindowLong
函数来启用分层窗口样式,然后调用 SetLayeredWindowAtrributes
,其中第 3rd 个参数是一个字节,表示透明度级别,0 表示透明,255 表示不透明,以及介于两者之间的任何数字。比 .NET 的百分比系统更精细的控制。
SetWindowLong(frmHandle, GWL_EXSTYLE,
GetWindowLong(frmHandle, GWL_EXSTYLE) ^ WS_EX_LAYERED);
SetLayeredWindowAttributes(frmHandle, 0, _currentTransparency, (uint)LWA_ALPHA);
要禁用分层窗口并恢复为标准窗口,您只需使用相同的参数再次调用 SetWindowLong
方法即可。包含的 DDFormFader
类具有更多功能,并且已完全注释。谢谢!
关注点
有一点需要注意,让我感到困惑的是,在调用 setWindowLong
后,必须立即调用 SetLayeredWindowAttributes
。如果它不在之后立即调用,窗体将停止正确绘制,并且会发生疯狂的事情,只需记住在下一行调用 SetLayeredWindowAttributes
。或者,当然,只需使用我的类,它可以为您处理所有事情。 :)
历史
- 当前版本 1.0 原始发布