65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.58/5 (10投票s)

2009年10月15日

CPOL

3分钟阅读

viewsIcon

64189

downloadIcon

2107

通过手动创建分层窗口 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 原始发布
© . All rights reserved.