【转】DWM 窗体玻璃效果实现
我一直盼望着 Windows 新版本的发布。令人感兴趣的事情莫过于浏览 MSDN® 和 SDK 文档,查找一些可以利用和依赖的最新创新,然后让朋友和同事以及您的老板(如果幸运的话)大开眼界。Windows Vista™ 在这方面包含许多诱人的内容。自从听说该版本将三维/组合层集成到桌面以来,我就特别希望获得该版本。多年来我已编写了数不清的三维应用程序,但我发现令人烦恼的一件事是,虽然能够在三维应用程序中提供非常酷且令人赏心悦目的用户界面,但在非三维应用程序中却不能。有了 Windows Vista 和桌面窗口管理器 (DWM),这种情况就发生了改变(请参见图 1)。
图 1 DWM 可以实现诸如 Flip 3D 任务切换等功能 (单击该图像获得较大视图)
DWM 是一种新界面,用于管理如何将运行和呈现的各种窗口合并到 Windows Vista 桌面上。Windows®Presentation Foundation (WPF) 提供了一种更高级别的层,控制着到桌面层的呈现,Windows Display Driver Model (WDDM) 用于处理到显示器的实际低级呈现。本文仅讨论如何使用 DWM 界面。有关该主题的详细信息,请阅读 David Chappell 编写的 MSDN 文章“Windows Presentation Foundation 简介”(msdn2.microsoft.com/aa663364.aspx)。WDDM 之所以是本文通篇关注的唯一内容,是因为它可以通过 DWM 界面实现新效果,而且可以修复一些特定问题。
DWM 的技术概览
在除 Windows Vista Home Basic 之外的所有 Windows Vista 版本中均提供 DWM 界面,通过 dwm.exe 可以启动该界面。系统中的所有应用程序都可以从 DWM 获益,而无需进行修改或重新编译。不过,选择利用特定 DWM 功能的应用程序可以调用 dwmapi.dll 中的界面(DWM 的公用界面),然后将这些界面传递到 dwm.exe。界面声明可在 dwmapi.h 中找到,并且可以从 windowssdk.msdn.microsoft.com 在线获取最新的 API 信息。
Windows Vista 在设计上对每个窗口都使用图形加速器,而不是仅针对三维 DirectX® 应用程序。为了实现这一点,DWM 需要与 WDDM 通信,后者是图形处理器和视频内存的最终所有者。(DWM 依赖于 milcore.dll,后者是与 Windows Presentation Foundation 共享的组件,用于输出和呈现到 DirectX。)呈现操作由 DWM 中的单独线程处理,拥有 DirectX 设备的用户无法对其进行访问。另外,大多数应用程序本身有处理呈现操作和 UI 的线程(如典型的 Win32® 应用程序中的 USER 消息弹出线程),但其与 DWM 的呈现线程不冲突。DWM 获取一个窗口列表,并在树结构中管理其位图,然后将其组合到最终桌面。换言之,每个应用程序均呈现自己的位图,然后由 DWM 进行组合。
应用程序的主窗口线程呈现其场景,DWM 呈现线程对该场景进行访问,并且呈现线程通过其 DirectX 界面更新桌面。传递的信息被压缩为仅对以前呈现的更改(差异),大型数据(如图像)则置于共享内存中。这就潜在地允许在一台计算机上生成场景,而最终的呈现操作可以在另一台计算机上完成。使用过 OpenGL 的三维程序员会对此体系结构非常熟悉,它允许服务器管理三维场景,并且仅将差异发送到客户端计算机。您可以分布式呈现三维场景,并让其在任何数量的客户端计算机上使用完全硬件加速运行。此体系结构使 DWM 能够为远程桌面方案提供一级支持。
尽管 Windows Vista 将与旧版 Windows XP 兼容驱动程序一起运行,但需要让 WDDM 视频驱动程序来获得所有 DWM 功能。与某些假定情况相反,DWM 不需要 DirectX 10,但它确实需要更多的视频/纹理内存和支持 Shader Model 2.0 或更高版本的视频卡。使用 WDDM 的最大改变是它引入了 Video Memory Manager (VidMM),后者可以在系统内存和视频内存之间交换视频内存分布。这意味着 WDDM 可以虚拟化视频卡的资源,因而在共享和交换视频内存方面以及在不同应用程序的不同线程之间对图形处理器进行上下文切换方面可以做得更好。以前几乎不可能稳定地运行多个三维应用程序,原因是驱动程序无法处理上下文切换。而且在 WDDM 出现之前,没有可用的正式计划,因此通常会出现一个 DirectX 应用程序耗尽其他应用程序的资源。而使用 WDDM 就很难发生这种情况。Windows Vista 对驱动程序的控制也严格得多,并要求比以前 Windows 版本的驱动程序更强大可靠。
另外请注意,DirectX 10 是一个仅用于 Windows Vista 的 API。为 DirectX 以前版本设计的应用程序将运行于旧版 DirectX API 实现(预计将被称为 DirectX 9 L)上。这将是 WDDM 之前的驱动程序支持的最后版本。DirectX 9 L 应用程序预计将运行于装有 DirectX 9 L 的 Windows XP 上和 Windows Vista 上。DirectX 10 不包含旧式界面。
组合桌面的好处
所有这些新的子系统可让您独立呈现窗口,并在将它们呈现到桌面之前对其执行组合步骤。在 Windows Vista 和随之发布的一些更新应用程序中可以很好地利用这一点。关于这一点,我要向您展示的两个用法是 Aero™ 玻璃效果和缩略图。玻璃效果仅在运行 Aero Glass 方案并打开组合功能时才可用。Aero Basic 不提供此效果。
由于每个窗口都在自己的视频内存区中创建,因此需要由 DWM 将该窗口的最后组合呈现到桌面。这意味着,DWM 可以访问桌面上的图像,并可以将其与您的窗口呈现混合在一起,创建由二者组合的呈现。在与现有桌面图像混合以创建霜冻玻璃效果的任何窗口区域中,这一点非常明显。由于每个窗口都呈现到中间屏外表面,这意味着 DWM 是涉及更新玻璃效果的唯一程序。在移动使用玻璃效果的窗口时,不需要使任何基本窗口无效。DWM 会处理半透明可视图像到新坐标的更新。可以指示 DWM 为要呈现玻璃效果的窗口添加某些客户端区域,这样可让您创建供自己使用的玻璃区域。
此屏外组合使得桌面响应更快。由于每个窗口现在都从桌面中单独呈现,因此就消除了您在更新较慢的应用程序(如 Web 浏览器)中经常遇到的问题。在 Windows 的以前版本中,图 2 显示的内容并不陌生。在另一个应用程序上方来回移动窗口时会出现这种撕裂现象,这是因为下面的窗口更新的速度太慢。进行桌面组合之后,就不会再看到这种现象。
图 2 慢速呈现导致撕裂现象 (单击该图像获得较大视图)
随 Windows Vista 一起发布的一些应用程序利用此功能将玻璃效果呈现到客户端区域中。其中一个最好的示例是 Windows Media® Player,该程序将框架扩展到客户端区域的底部,它在该区域中绘制一些自定义控件。最小版本看起来与图 3 类似。
图 3 Windows Media Player 中的玻璃效果 (单击该图像获得较大视图)
当在我的桌面上来回移动窗口时,通过控件可以看到的图像由播放器下面的桌面部分组成。如果将窗口移动到某个动画上,我可以透过上面的窗口看到动画!这就是组合桌面的功能所在。
如果关闭组合效果,则仅会在窗口中获得“极光”效果,并且窗口将呈现不透明的默认窗口颜色,如图 4 所示。
图 4 关闭玻璃效果的 Windows Media Player (单击该图像获得较大视图)
从本质上讲,Aero Basic 和 Aero Glass 界面是 DWM UI 之前的标准和新标准之间的分水岭。Aero Basic 界面为程序提供同一 API,以保持向后兼容性,但是运行 Basic 界面则意味着您在使用旧式窗口管理器,DWM 是不活动的。旧式界面意味着 UI 布局将像 Windows Vista 之前编写的程序预期的那样。DWM 控制着 Aero Glass 界面并限制对它的访问。如果应用程序进入非客户端区域(玻璃框架),DWM 将检测到它,并切换到 Aero Basic 框架。
Windows Vista 中由于组合桌面而提供的其他新功能还有,新的 Windows Flip (Alt+Tab) 和 Flip 3D(Alt+Windows 键)任务切换器。Flip 3D 特别有趣,因为它的功能取决于 DWM 中的一些代码,这些代码将获取场景图形中的每个顶级窗口,并将其呈现到一系列扭曲的窗口上,您可以使用键盘或鼠标在这些窗口中进行滚动(请参见图 1)。
DWM 控制着窗口与桌面组合引擎的交互方式。若要让您的程序集成到 DWM 功能中,需要了解 DWM 的工作方式以及如何与之交互。除了一些其他功能外,DWM 在其公共 API 中有四个主要功能区:
- 基本桌面组合设置
- 在客户端窗口中呈现玻璃效果
- 呈现缩略图
- 调整呈现效果以便与多媒体程序交互
我将在本文中讲述前三部分。最后一部分为 DirectX 和视频播放应用程序提供,因为 DWM 是异步运行的,如果不进行严格控制可能会导致采样失真。
一些常见的组合函数
如果您希望在程序中使用桌面组合功能,则需要查询和设置各种 DWM 参数。例如,如果某些应用程序切换为全屏显示,然后 DWM 关闭组合,并以不透明的桌面背景颜色呈现,那么您的应用程序应识别这种情况并禁用特定于组合的功能。下面是将您的程序与 DWM 集成的一些基本函数:
DwmEnableComposition 启用或禁用 DWM 组合。DWM 将在当前进程中或直到重设前保持此设置。更改设置会导致发出 WM_DWMCOMPOSITIONCHANGED 通知。多数应用程序不需要调用此函数,但您可能需要监视 Windows 的最终消息。
DwmIsCompositionEnabled 获取桌面启用 DWM 组合的状态。
DwmSetWindowAttribute 为窗口设置指定的 DWM 属性的值,控制如何处理 DWM 过渡,是否允许非客户端呈现,以及 Flip 3D 将如何处理窗口。例如,如果对某个窗口关闭非客户端呈现,则稍后扩展框架或使窗口后面的内容变模糊的要求将会失败。
DwmGetWindowAttribute 为指定窗口检索指定 DWMWINDOWATTRIBUTE 的当前值。
DwmGetColorizationColor 检索用于 DWM 玻璃组合的当前颜色。此值基于当前颜色方案。更改此设置将导致发出 WM_WMCOLORIZATIONCOLORCHANGED 通知。
DwmDefWindowProc 在使用 WM_NCHITTEST 通知进行调用时以及由于在扩展了客户端框架而需要处理 WM_NCCALCSIZE 及类似消息时,使 DWM 命中测试位于非客户端区域。
在您的程序中呈现玻璃效果相当简单。DWM 为此提供了两个函数:
DwmExtendFrameIntoClientArea 一个简单的函数,将非客户端框架的边缘扩展到窗口内。
DwmEnableBlurBehindWindow 一种更为复杂的函数,对玻璃效果的呈现方式提供更多的控制。
由于所有组合窗口都通过 DWM 呈现到一个屏外窗口,然后进行组合后再呈现到桌面上,因此获取这些图像并提供应用程序的实时缩略图表示并不困难。DWM 为您提供了四个函数来控制缩略图的呈现方式:
DwmQueryThumbnailSourceSize 返回 DWM 缩略图的原始大小。
DwmRegisterThumbnail 创建目标窗口和源窗口之间的缩略图关系。
DwmUnregisterThumbnail 删除由 DwmRegisterThumbnail 创建的 DWM 缩略图关系。
DwmUpdateThumbnailProperties 更新给定缩略图的属性。
DWM 提供了五个函数用于微调 DWM 的呈现方式,但这些函数超出了本文的讨论范围。
为 Aero Glass 做好准备
若要对 DWM 界面编程,您需要运行能够显示 Aero Glass 的 Windows Vista 版本。虽然最方便的方法是使用 C++ 代码调用这些新函数,但如果可以的话,我喜欢用 C# 编写用户界面代码。本文的所有代码均使用 C# 编写,但这意味着您必须经受严峻的考验。若要使用本文讨论的函数,您要么需要使用 C++ 和正确的库中的链接,要么必须用 C# 为函数和结构编写 P/Invoke 包装。在本文的下载部分中包含一个库,其中提供了 DWM 所需函数和结构的包装,因此您可以从 C# 程序调用它。基本上,这只是从 dwmapi.dll 下载界面的一组说明。为使用本文中使用的用于玻璃效果和缩略图的 DWM 函数,您需要创建 DWM 函数和数据结构的 C# 声明。我为本文创建的声明类似于图 5。
Figure 5 DWM 的 C# 声明
internal class DwmApi { [DllImport("dwmapi.dll", PreserveSig = false)] public static extern void DwmEnableBlurBehindWindow( IntPtr hWnd, DWM_BLURBEHIND pBlurBehind); [DllImport("dwmapi.dll", PreserveSig = false)] public static extern void DwmExtendFrameIntoClientArea( IntPtr hWnd, MARGINS pMargins); [DllImport("dwmapi.dll", PreserveSig = false)] public static extern bool DwmIsCompositionEnabled(); [DllImport("dwmapi.dll", PreserveSig = false)] public static extern void DwmEnableComposition(bool bEnable); [DllImport("dwmapi.dll", PreserveSig = false)] public static extern void DwmGetColorizationColor( out int pcrColorization, [MarshalAs(UnmanagedType.Bool)]out bool pfOpaqueBlend); [DllImport("dwmapi.dll", PreserveSig = false)] public static extern IntPtr DwmRegisterThumbnail( IntPtr dest, IntPtr source); [DllImport("dwmapi.dll", PreserveSig = false)] public static extern void DwmUnregisterThumbnail(IntPtr hThumbnail); [DllImport("dwmapi.dll", PreserveSig = false)] public static extern void DwmUpdateThumbnailProperties( IntPtr hThumbnail, DWM_THUMBNAIL_PROPERTIES props); [DllImport("dwmapi.dll", PreserveSig = false)] public static extern void DwmQueryThumbnailSourceSize( IntPtr hThumbnail, out Size size); [StructLayout(LayoutKind.Sequential)] public class DWM_THUMBNAIL_PROPERTIES { public uint dwFlags; public RECT rcDestination; public RECT rcSource; public byte opacity; [MarshalAs(UnmanagedType.Bool)] public bool fVisible; [MarshalAs(UnmanagedType.Bool)] public bool fSourceClientAreaOnly; public const uint DWM_TNP_RECTDESTINATION = 0x00000001; public const uint DWM_TNP_RECTSOURCE = 0x00000002; public const uint DWM_TNP_OPACITY = 0x00000004; public const uint DWM_TNP_VISIBLE = 0x00000008; public const uint DWM_TNP_SOURCECLIENTAREAONLY = 0x00000010; } [StructLayout(LayoutKind.Sequential)] public class MARGINS { public int cxLeftWidth, cxRightWidth, cyTopHeight, cyBottomHeight; public MARGINS(int left, int top, int right, int bottom) { cxLeftWidth = left; cyTopHeight = top; cxRightWidth = right; cyBottomHeight = bottom; } } [StructLayout(LayoutKind.Sequential)] public class DWM_BLURBEHIND { public uint dwFlags; [MarshalAs(UnmanagedType.Bool)] public bool fEnable; public IntPtr hRegionBlur; [MarshalAs(UnmanagedType.Bool)] public bool fTransitionOnMaximized; public const uint DWM_BB_ENABLE = 0x00000001; public const uint DWM_BB_BLURREGION = 0x00000002; public const uint DWM_BB_TRANSITIONONMAXIMIZED = 0x00000004; } [StructLayout(LayoutKind.Sequential)] public struct RECT { public int left, top, right, bottom; public RECT(int left, int top, int right, int bottom) { this.left = left; this.top = top; this.right = right; this.bottom = bottom; } } }
如果您使用的是 C#,则需要在您的代码中创建与此类似的声明。然后,如果您运行的是 Windows Vista,则可以进行 DWM 调用。当然,不应仅假定您的应用程序运行于 Windows Vista 上。为保险起见,您需要确认 Environment.OSVersion.Version.Major 至少为 6.0。或者,您可以捕获由于尝试通过 P/Invoke 调用不存在的函数而导致的异常。
如果希望使用玻璃效果,则您使用的计算机需要满足三个要求。首先,需要运行 Windows Vista 的 Premium、Business 或 Ultimate 版本。其次,硬件应能够运行 Aero 界面(详细信息请参见microsoft.com/windowsvista/getready/capable.mspx)。最后,需要在 Windows Vista 中选择 Windows Aero 颜色方案。需要谨慎使用此操作,因为用户过度使用该效果会对计算机的 GPU 造成负担。
图 6 窗口颜色和外观选项 (单击该图像获得较大视图)
通过打开“个性化”控制面板并单击“Window 颜色和外观”选项启用 Aero 方案。在此屏幕上(请参见图 6),确保选中了“启用透明效果”选项,然后单击“打开传统风格的外观属性”链接。在“外观设置”对话框中的“颜色方案”下单击 Windows Aero(请参见图 7)。在单击“确定”后,将会看到 Aero 界面和玻璃效果。如果愿意,还可以自定义窗口颜色和不透明级别。
图 7 Windows Aero 玻璃效果 (单击该图像获得较大视图)
对 DWM 编程
通过调用 DwmIsCompositionEnabled 可以检查在您的程序中是否启用了 Aero 方案。但是请注意,不但用户可以随时更改当前 Aero 方案,而且其他应用程序也能够以编程方式启用或禁用该方案。因此检查一次此函数的结果可能不够可靠。
DwmEnableComposition 函数可让程序打开或关闭 Aero 方案。例如,如果您编写的应用程序可能遇到兼容性问题,则在应用程序运行时可能需要禁用组合(如果您编写的是全屏 DirectX 专用应用程序,将会自动禁用组合)。此设置仅在设置它的进程期间保持,当进程结束时,组合标志将被重设为其初始值。通常情况下,除非由于应用程序兼容性原因,否则应用程序不应使用此设置,而应让系统或用户作出决定。
当桌面组合的状态更改时,将广播 WM_DWMCOMPOSITIONCHANGED 消息。您无法通过参数了解是否启用或禁用了组合,因此,如果感兴趣,由您自己决定调用 DwmIsCompositionEnabled。执行检查的代码非常简单,难点是当禁用组合时,如何决定窗口的外观。
// Check to see if composition is Enabled if (DwmIsCompositionEnabled()) { // enable glass rendering } else { // fallback rendering }
最后,即使启用了 Aero 方案,用户可能已经更改了玻璃颜色并使组合效果变得不透明。我编写了一个小应用程序,使用该程序可以创建完全是玻璃效果的窗口,然后我在控制面板中更改了玻璃属性(请参见图 8)。第一个图像显示窗口颜色和透明度的默认设置。然后我关闭了透明度,留下一个不透明窗口。我更改为红色窗口颜色和默认的透明度,您仍可从中辨认出某些底层窗口图像。在一个足够大的不透明窗口中,您将观察到放置在玻璃呈现中的高亮区域,它就是窗口上可见的斜纹“极光”效果。
图 8a 更改颜色和透明度
图 8b
图 8c
您可以通过调用 DwmGetColorizationColor 函数查看组合颜色和不透明度。如果此函数成功,它将设置一个 GDI+ ARGB 颜色值和布尔值,指示该颜色是否不透明。就像在控制面板中更改 Aero 方案一样,当组合颜色更改时将会广播一条消息。在出现这种情况时,将发送 WM_DWMCOLORIZATIONCOLORCHANGED,但是,在这种情况下,参数将告诉您新颜色和不透明度是什么。
protected override void WndProc(ref Message msg) { switch (msg.Msg) { case WM_DWMCOLORIZATIONCOLORCHANGED: // The color format of currColor is 0xAARRGGBB. uint currColor = (uint)msg.WParam.ToInt32(); bool opacityblend = (msg.LParam.ToInt32() != 0); ... break; } }
在对非客户端区域更改 DWM 呈现时,将发送 WM_DWMNCRENDERINGCHANGED 消息。如果启用了 DWM 的非客户端呈现,wParam 将为 true。当最大化或非最大化 DWM 组合窗口时收到 WM_DWMWINDOWMAXIMIZEDCHANGE 消息,您还将得到通知。如果已最大化窗口,wParam 将为 true。
前面我们介绍了两个函数,您可以使用这两个函数为您的程序提供玻璃效果。第一个是 DwmExtendFrameIntoClientArea。使用 Aero 方案的窗口在标题栏区域和窗口边缘的边界处具有玻璃效果(实质上是窗口的所有非客户端区域)。此函数可让您扩展呈现到客户端区域的非客户端区域的每个边,并使用玻璃效果呈现它。换句话说,您可以将玻璃窗口框架的顶边、左边、右边和底边无缝扩展到您的窗口中。
第二个函数是 DwmEnableBlurBehindWindow,它可让您呈现边缘为任意形状的玻璃效果,并指定更多参数来更好地控制呈现效果,但是,我认为多数使用玻璃效果的用户只是将玻璃效果从边缘扩展到客户端区域。无论使用哪个函数,都需要密切跟踪组合状态,以查看是否应在呈现时启用玻璃效果。这意味着要跟踪四个 WM_DWM* 消息,或者调用 DwmIsCompositionEnabled,以便了解是关闭玻璃效果进行呈现,还是在打开时呈现。
首先来看一下较简单的调用。此函数专用于无框架窗口(如任务栏、侧栏、Tablet 笔输入窗口和“开始”菜单);未定义有框架窗口上的行为。
DwmExtendFrameIntoClientArea 函数采用窗口句柄和 MARGINS 结构。窗口句柄是框架从边缘扩展到客户端区域的窗口。您需要设置包含像素数的 MARGINS 结构,以便将框架扩展到客户端区域。MARGINS 结构的 C# 实现如图 5 所示。
刚开始您可能会感到迷惑,因为没有其他 Win32 函数与此作用类似,但是基本上您可以从其他函数独立地控制每个边。选择您要扩展的边,并指定要呈现的效果在客户端区域的深入程度(请参见图 9)。如果需要扩展多个边,它们可以重叠。如果希望该效果动态跟踪窗口大小,则每次在窗口大小更改时都需要调用 DwmExtendFrameIntoClientArea 函数。一种特殊情况是将一个或多个边距设置为 -1,这样会将玻璃效果扩展到整个窗口。若要重设边距,只需将所有边距值设置为 0,并再次调用 DwmExtendFrameIntoClientArea。
图 9 客户端区域中的玻璃边距 (单击该图像获得较大视图)
如果您不希望将玻璃效果从框架扩展到客户端区域,该怎么办?DwmEnableBlurBehindWindow 函数可让您更好地控制如何将玻璃效果添加到窗口。同样,该函数采用您要添加玻璃效果的窗口的窗口句柄,但它还采用 DWM_BLURRBEHIND 结构,以便让您设置有关如何在窗口上使用模糊效果的各种参数。其中最重要的一个参数是区域参数,它是一个 GDI 术语,用于描述通常由一系列直线和曲线构成的任意形状的区域。
DWM_BLURRBEHIND 结构(如图 5 所示)包含控制如何显示模糊效果的参数。
如果希望在客户端区域打开玻璃效果,则可以将 fEnable 标志设置为 true。若要将其关闭,应将该标志设置为 false。hRgnBlur 参数是您创建的要显示玻璃效果的区域的句柄。就像在 Dwm-ExtendFrameIntoClientArea 中将边距值设置为 -1 一样,在模糊结构中将 hRgnBlur 参数设置为 null 可以通知 DWM 将玻璃效果应用到整个窗口。
您可能会对最后一个参数 fTransitionOnMaximized 产生误解。由于在最大化的窗口中关闭了玻璃效果,您会认为该标志与此有关。此标志实际控制当桌面上存在最大化窗口时,窗口是否过渡到最大化的颜色。遗憾的是,如果将此参数设置为 true,则在呈现窗口时,出现的不是透明区域,而只是极光效果。
您可以通过 dwFlags 参数告诉界面您要设置哪些参数;当希望设置一个参数时,需要在 dwFlags 参数中打开相应的位。这在整个 DWM 界面中是一致的。
不要忘记必须将玻璃颜色呈现到该区域。在指定区域创建玻璃效果时,使用与 DwmExtendFrameIntoClientArea 中相同的黑色画笔效果比较好。
显而易见,DWM 为您提供了两个界面,较为复杂的界面可让您构建任意形状的区域,简单的界面仅可让您将窗口框架的玻璃效果扩展到您的客户端区域,以便您可以将附加控件和类似内容绘制为类似于标题栏的一部分。在这两种情况下,令人担心的情况是,当重新调整窗口的大小和更新呈现玻璃效果的区域(如果该区域不是整个客户端区域)时,区域的形状会发生什么变化。本文提供的源代码可让您设置扩展的客户端框架或区域,并能够切换组合标志。
在玻璃区域上绘制
在窗口中使用玻璃效果作为背景需要一些技巧。如果呈现本身不透明的任何内容(如 GDI 函数),则会将您的项目呈现在玻璃区域上,但有时会出现异常效果。如果您希望实际将呈现混合到玻璃界面中,则需要利用能够使用 Alpha 颜色管道的功能,如 GDI+、Windows Presentation Foundation 或 Windows XP Theme API。
一个特殊问题是使用位模式 0x00000000 以黑色呈现 GDI 项目,在使用 Alpha 管道时也会碰巧出现完全透明的黑色。这意味着如果您使用黑色 GDI 画笔或笔进行绘制,将会得到透明的颜色,而不是黑色。当您尝试使用默认文本颜色控制位于玻璃区域中的文本标签时,这种问题表现得就特别明显。因为默认文本颜色通常为黑色,DWM 会认为它是透明的,因此文本将错误地写入玻璃区域。图 10 显示了一个这样的示例。第一行使用 GDI+ 编写,第二行是一个使用默认颜色的文本标签控件。可以看出,其中的内容几乎无法辨认,因为它实际上是错误呈现的文件,文本显示为灰色,而不是黑色。
图 10 透明对话框
令人欣慰的是,可以通过多种方法解决此问题。其中一种方法是使用所有者描述的控件。另一种方法是呈现到具有 Alpha 管道的位图。但控制文本的最简单方法是让 .NET Framework 2.0 为您使用 GDI+。通过在您的控件上设置 UseCompatibleTextRendering 属性可以很容易地做到这一点。默认情况下,此属性设置为 false,这样,为 .NET Framework 的以前版本编写的控件将以相同的方式呈现。但是,如果将其设置为 true,则文本将正确呈现。您可以使用 Application.SetUseCompatibleTextRenderingDefault 方法在全局设置该属性。如果您使用的是 Visual Studio® 2005,则模板代码将包括一个调用,以便在创建窗体之前在主例程中将兼容文本呈现设置为 false。您可以编辑它,将其设置为 true(如下所示),这时在玻璃窗口中进行编写时,所有控件看上去都会是正确的。
static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(true); Application.Run(new GlassForm()); }
您可以在 Miguel A. Lacouture 在 2006 年 3 月份的《MSDN 杂志》上发表的文章“Build World-Ready Apps Using Complex Scripts In Windows Forms Controls”中找到有关此内容和使用 TextRenderer 类的详细信息。
在开始呈现窗口之前应启用玻璃效果。组合引擎将查看您窗口的 Alpha 值,并将模糊效果应用到透明区域。这在使用某些 GDI 函数时可能会出现问题,因为这些函数不保留 Alpha 值。您可以在需要时使用 GDI+,但应谨慎使用,因为 GDI+ 调用在软件中呈现,而不是在硬件上呈现,因此窗口刷新频率较高时使用 GDI+ 调用可能导致耗费大量的系统资源。
可以通过相同方法在 DirectX 应用程序中获得玻璃效果。您需要做的只是控制呈现目标的 Alpha 值和使用两个启用玻璃效果的 DWM 函数之一。无论在何处指示 DWM 使用玻璃效果,它都将使用呈现目标的 Alpha 值。无论在其他任何地方,呈现目标均应为不透明的,否则将得到未定义的行为。
缩略图
缩略图是打开的应用程序上由 DWM 呈现的实时显示窗口。缩略图由 Flip 和 Alt+Tab 任务切换器使用。实质上,您可以请求应用程序窗口的缩略图,并让其在应用程序中呈现。缩略图 API 将为您提供应用程序窗口的实时表示。
缩略图易于使用,因为 Windows 已为您做好了大部分艰苦工作。困难之处在于获取应用程序的 HWND。在获取所需的 HWND 后,您只需注册一个缩略图,并将该 HWND 与要呈现缩略图的 HWND 和该窗口中的位置相关联。这时操作系统将负责以后的更新。每当源窗口更改时,更改将反映到目标窗口中。
若要使用缩略图,必须首先使用 DwmRegisterThumbnail 函数注册缩略图。您提供了两个窗口句柄:源 HWND(即需要缩略图视图的窗口)和目标 HWND(需要呈现缩略图的窗口)。在使用缩略图结束之后,您需要通过调用 DwmUnregisterThumbnail 让 DWM 知道关系已结束。在创建了缩略图之后,DwmRegisterThumbnail 函数将返回一个缩略图句柄,所有其他缩略图函数将把该句柄作为参数。在注册缩略图之后,您需要调用 DwmUpdateThumbnailProperties 才能更新缩略图。图 11 显示了呈现另一窗口实时缩略图的窗体示例代码。
public partial class Thumbnail : Form { private IntPtr m_hThumbnail; public Thumbnail() { InitializeComponent(); } public void CreateAndShow(IntPtr sourceWindow) { m_hThumbnail = DwmApi.DwmRegisterThumbnail( Handle, sourceWindow); DwmApi.DWM_THUMBNAIL_PROPERTIES m_ThumbnailProperties = new DwmApi.DWM_THUMBNAIL_PROPERTIES(); m_ThumbnailProperties.dwFlags = DwmApi.DWM_THUMBNAIL_PROPERTIES.DWM_TNP_VISIBLE + DwmApi.DWM_THUMBNAIL_PROPERTIES.DWM_TNP_OPACITY + DwmApi.DWM_THUMBNAIL_PROPERTIES.DWM_TNP_RECTDESTINATION + DwmApi.DWM_THUMBNAIL_PROPERTIES. DWM_TNP_SOURCECLIENTAREAONLY; m_ThumbnailProperties.opacity = 255; m_ThumbnailProperties.fVisible = true; m_ThumbnailProperties.rcSource = m_ThumbnailProperties.rcDestination = new DwmApi.RECT(0, 0, ClientRectangle.Right, ClientRectangle.Bottom); m_ThumbnailProperties.fSourceClientAreaOnly = false; DwmApi.DwmUpdateThumbnailProperties( m_hThumbnail, m_ThumbnailProperties); Show(); } protected override void Dispose(bool disposing) { if (disposing && (components != null)) components.Dispose(); base.Dispose(disposing); if (m_hThumbnail != IntPtr.Zero) { if (DwmApi.DwmIsCompositionEnabled()) DwmApi.DwmUnregisterThumbnail(m_hThumbnail); m_hThumbnail = IntPtr.Zero; } } }
除注册和取消注册缩略图的两个函数外,还有其他两个函数与缩略图一起使用。DwmQueryThumbnailSourceSize 返回指定缩略图的原始大小。DwmUpdateThumbnailProperties 可让您更新给定 DWM 缩略图的属性。它采用 DWM_THUMBNAIL_PROPERTIES 结构,其 C# 实现如图 5 所示。
在不想使用整个源窗口时,使用 DWM_THUMBNAIL_PROPERTIES 结构可以指定属性的数量,如目标窗口中的目标矩形(rcDestination 成员)和要使用的源窗口的矩形区域(rcSource 成员)。
如果不想让缩略图完全不透明,还可以使用不透明度成员指定缩略图的不透明度,其中 0 为透明,255 为不透明。如果希望缩略图不可视,可以将 fVisible 标志设置为 false。如果仅希望使用缩略图窗口的客户端区域,而不是整个源窗口(其中包括非客户端区域,如框架和标题栏),则只需将 fSourceClientAreaOnly 布尔值设置为 true。您可以通过 dwFlags 参数告诉界面要设置哪些参数。当设置某个参数时,您需要在 dwFlags 参数中打开相应的位。
最后,在诸如目标窗口大小方面没有限制,通常使用缩略图界面扩大和缩小源窗口。在保持长宽比方面存在限制。始终保持源窗口的长宽比。如果更改了源窗口的大小,缩略图也将更改大小,以保持其本身处于指定的边界内。
在本文随附的源代码中提供了一个按钮,使用该按钮将创建主应用程序窗口的微型实时缩略图,如图 12 所示。
您会很容易发现,缩略图的呈现是实时的。如果更改主应用程序窗口,则会看到缩略图更新。您会发现,稍微发挥一下自己的聪明才智,就可以相当容易地使用缩略图以及 FindWindow 和 GetWindow Win32 函数创建自己的任务切换器。
图 12a 创建实时缩略图
图 12b
总结
本文简要概述了 DWM 界面。我认为您将会发现一些适用于这些 API 的优秀应用程序。尤其是,随着人们习惯于将玻璃效果应用于自己的窗口,预计将来会有一些使用玻璃效果的非常吸引人的应用程序。若要了解详细信息,建议您访问 Greg Schechter 的博客,位置是:blogs.msdn.com/greg_schechter。
小生在这只是起到搬运工的作用
原文地址:http://msdn.microsoft.com/zh-cn/magazine/cc163435.aspx#S4
/****************************邪恶的分割线*******************************/
在这小生献上实现的一段代码
以下代码实现了玻璃效果只需将代码拷入然后构造函数里调用方法dwmInitialize();即可.
需添加命名空间using System.Runtime.InteropServices;
补充一点,将窗体样式设置成None,通过以下代码可以看到半透明窗体的效果.
/// <summary> /// /// </summary> /// <param name="e"></param> protected override void OnPaintBackground(PaintEventArgs e) { base.OnPaintBackground(e); e.Graphics.Clear(Color.FromArgb(100, 10, 10, 10)); } /// <summary> /// dwm初始化 /// </summary> private void dwmInitialize() { #region[dwmInitialize] int flag = 0; MARGINS mg = new MARGINS(); mg.m_Buttom = -1;// 100; mg.m_Left = -1;// 100; mg.m_Right = -1;//100; mg.m_Top = -1;// 100; //判断Vista系统 if (System.Environment.OSVersion.Version.Major >= 6) { DwmIsCompositionEnabled(ref flag); //检测Aero是否为打开 if (flag > 0) { DwmExtendFrameIntoClientArea(this.Handle, ref mg); } else { MessageBox.Show("Desktop Composition is Disabled!"); } } else { MessageBox.Show("Please run this on Windows Vista."); } #endregion } /// <summary> /// /// </summary> public struct MARGINS { public int m_Left; public int m_Right; public int m_Top; public int m_Buttom; }; [DllImport("dwmapi.dll")] private static extern void DwmIsCompositionEnabled(ref int enabledptr); [DllImport("dwmapi.dll", PreserveSig = false)] public static extern bool DwmIsCompositionEnabled(); [DllImport("dwmapi.dll")] private static extern void DwmExtendFrameIntoClientArea(IntPtr hWnd, ref MARGINS margin);