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

为旧版 Windows 应用程序添加或改造 Aero Glass

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (57投票s)

2007年5月23日

CPOL

20分钟阅读

viewsIcon

267031

downloadIcon

7971

为 Windows 应用程序添加 Aero Glass,同时保持其向后兼容旧版 Windows 版本。

Screenshot - autoaero.png

毫无疑问,Windows Vista 最具吸引力的新功能之一是新的桌面合成引擎,称为 Aero Glass。如果运行 Vista 的计算机拥有支持 DirectX 9 的图形适配器,则会启用 Aero Glass。它允许实现炫酷的半透明效果,从而显示窗口后面的窗口,并将其模糊为背景图像。但是,Aero Glass 的一个问题是,如果您想在应用程序中使用它,您的应用程序将无法在旧版 Windows(如 Windows XP 或 Windows 2000)上运行。除非您在延迟加载实现 Aero Glass 的 DLL 以及仔细为应用程序应运行的不同操作系统版本实现不同的代码路径方面付出巨大的努力。然而,一个更大的障碍是,传统的基于 GDI 和 USER 的应用程序与 Aero Glass 并不兼容。这一点稍后将进行讨论。

所有这些都意味着,大多数用 C/C++ 编写的旧应用程序无法轻松地在 UI 中使用 Aero Glass。对于我们这些像我一样,工作时仍不得不坚持使用 VC6 的人来说,在我们的应用程序中使用 Aero Glass 几乎是不可能的。这是因为为了使用它,您必须链接到 Vista SDK 的新导入库,而 VC6 链接器在没有问题的情况下无法使用这些库。

因此,本文以及附带的源代码的目标是帮助缓解这些问题。事实上,本文的源代码在开发系统方面几乎是无关紧要的:本文的示例应用程序不仅是用 Visual Studio 2005 开发的,而且还用 VC6 以及与 VC6 兼容的两个最新 Platform SDK(XP SP2 Platform SDK 和 W2K3 Server Platform SDK)进行了开发。x64 版本可以使用 VC6 以及 W2K3 Server SP1 PlatSDK 附带的 x64 头文件和库进行编译。毋庸置疑,也包含了用于 x86 和 x64 构建以及 ANSI 和 Unicode 构建的 Visual Studio 2005 解决方案和工作区。所有平台、所有字符集表示(ANSI 与 Unicode)和所有编译器版本的所有构建目标都应该在最高警告级别下干净地构建,而不会出现错误或警告。

使用本文源代码创建的二进制文件有一个特别之处,那就是它们仍然可以在旧版 Windows 上运行。您可以直接构建本文附带的演示应用程序,并在启用 Aero Glass 的 Vista 上运行它,也可以在 Windows XP 或 2000 上运行。唯一的区别是,在旧版操作系统上,您将拥有标准的窗口背景,而不是半透明的背景。

GDI 和 USER 控件在半透明背景上的问题

本文最顶部的图像显示了本文的示例应用程序,这是一个基于 MFC 的对话框应用程序。它具有自 Windows 95 及其当时新通用控件问世以来我们所熟知的标准控件类型,以及 Internet Explorer 4 随附的通用控件的附加项。现在考虑一下,如果我们只是天真地启用了窗口背景的 Aero Glass 半透明效果,这个应用程序会是什么样子?

Screenshot - autoaerougly.png

很难看,不是吗?GDI 和 USER 的问题在于,这两个系统 DLL 都不知道 Aero Glass 使用的 Alpha 通道。更糟糕的是,Aero 将黑色视为其透明颜色。这就是为什么在上图中,您可以看到文本本应出现的地方,背景图像却闪烁出来。从这张截图可以看出,仅仅将半透明背景应用于窗口的方法对于传统的子窗口控件来说是行不通的。然而,本文的代码力求使 Aero Glass 轻松应用于此类窗口。它使用一个函数调用来子类化窗口及其所有子窗口。这样,它就可以一举同时将 Aero Glass 应用于窗口本身及其子控件。您需要在每个窗口级别——即每个窗口实例——调用的函数具有以下原型:

BOOL AeroAutoSubclass(HWND hWnd, DWORD dwFlags, DWORD dwReserved);

hWnd 参数是窗口本身​​的窗口句柄。dwFlags 参数指定函数应该子类化哪些类型的窗口控件。它还决定 Aero Glass 是否应用于整个窗口,或者您是否想自己控制将半透明部分应用于窗口。在最简单的情况下,就像本文最顶部的截图一样,您会在基于 MFC 且派生自 CDialog 的窗口中这样调用此函数:

BOOL CAutoaeroDlg::OnInitDialog()
{
    CDialog::OnInitDialog();
    
    /// ... other initialization code
    
    if(!AeroAutoSubclass(m_hWnd, ASC_SUBCLASS_ALL_CONTROLS, 0L))
    {
        TRACE(_T("Failed to autosubclass with %lu\n"), GetLastError());
    }
    
    /// ... more initialization code
    
    return TRUE;  
}

有问题的控件

本文最顶部的截图中的所有控件都经过了专门的子类化处理,以便在具有部分半透明背景的情况下能够很好地显示。本文源代码中的一些实现为了在 Aero Glass 上看起来效果好,付出了很大的努力。动画控件的子类化代码是一个很好的例子,它允许您在半透明背景上显示动画而不会闪烁,它使用了低级的 Video for Windows 例程和 GDI+。其他控件,例如列表框控件,只是对其子类化代码中的 SendMessage(..., WM_PRINTCLIENT,...) 调用以及其 WM_PAINT 处理程序的包装。然而,有两种标准控件有点棘手,即 MonthCalendar 控件和 RichText 控件。

当您向上或向下钻取日历日期时,MonthCalendar 控件会使用动画效果。在动画期间和动画之后,控件上的黑色文本将始终让背景窗口闪烁,因为,正如我们现在所知,黑色被视为 Alpha 通道。我找不到控制这种行为的方法,因为没有记录在案的方法可以关闭动画或在动画开始和停止时收到通知。

另一个有问题的控件是 RichText 控件,它根本不支持 WM_PRINTCLIENT 消息。大多数其他控件的子类化代码都使用它来在桌面合成引擎使用的绘图缓冲区中呈现控件。为了演示如何仍然使用这些有问题的控件,演示应用程序中有两个按钮,分别标有“MonthCal”和“RichEdit”。如果您按下这些按钮,会出现对话框,演示如何在父窗口中使用这些控件,而该父窗口否则将完全半透明。

Screenshot - monthcal.png

Screenshot - richedit.png

这两个对话框背后的想法是,窗口的半透明区域涵盖了窗口的整个客户区,除了 MonthCalendar 控件或 RichEdit 控件使用的区域。但是,由于窗口的半透明性是通过像素值来表达的,这些半透明区域从边框延伸到客户区,因此每个窗口最多只能有一个此类有问题的控件。这意味着您无法在一个窗口上拥有多个 RichEdit 控件并将其背景应用于 Aero Glass。如果您确实需要这样做,或者想显示多个应用了 Aero Glass 的控件和另一个标准战舰灰色背景的控件,您可以使用按下“混合控件”按钮时使用的对话框技术。

Screenshot - mixed.png

正如您所见,在灰色背景区域外有一些控件具有半透明背景。其他一些控件位于一个标准背景出现的矩形内。完成所有这些工作的代码如下所示:

/// const aray of control IDs that should be made translucent:
/// 
const UINT g_dwAeroCtrlIds[] = 
{
    IDOK,
    IDCANCEL,
    IDC_STATIC_GLASS,
    IDC_STATIC_GLASS2,
    IDC_STATIC_ICO_GLASS,
    IDC_RADIO4,
    IDC_RADIO5,
    IDC_CHECK1
};

BOOL CMixedCtrlDlg::OnInitDialog() 
{
    CDialog::OnInitDialog();
    
    if(!AeroAutoSubclass(m_hWnd, ASC_NO_FRAME_EXTENSION, 0L))
    {
        TRACE(_T("Failed to autosubclass with %lu\n"), GetLastError());
    }
    else
    {
        /// 
        /// loop over the controls outside the placeholder control
        /// and let autoaero subclass them:
        /// 
        size_t stIdx = 0;
        for (;stIdx<dimof(g_dwAeroCtrlIds);stIdx++)
        {
            HWND hCtrl = ::GetDlgItem(m_hWnd, g_dwAeroCtrlIds[stIdx]);
            ASSERT(hCtrl);
            VERIFY(AeroSubClassCtrl(hCtrl));    
        }
    }
    
    return TRUE;  
}

正如您所见,这次调用 AeroAutoSubclass 时带有标志 ASC_NO_FRAME_EXTENSION。这基本上意味着:“允许对各个控件进行子类化,但不要将半透明背景应用于此窗口或其任何子控件。我将自己处理一切。”上面的代码然后遍历资源 ID 数组。您已经猜到了:这些是灰色背景外的控件。接下来,它调用 AeroSubClassCtrl 并传入相应的窗口句柄。但是,如何将半透明背景应用于对话框呢?一种方法是指定一个不应半透明的区域,该区域由一个隐藏的控件占据。这使您可以在 VC 的资源编辑器中方便地移动控件。在我的例子中,这个控件的 ID 是 IDC_STATIC_PLACEHOLDER。将 Aero Glass 应用于此窗口然后由实现该对话框的类的单个方法——在我的例子中是 CMixedCtrlDlg::ApplyAeroGlass——完成。该方法从对话框的 WM_PAINT 处理程序中调用。下一个代码片段显示了此方法的实现:

void CMixedCtrlDlg::ApplyAeroGlass(CDC *pDC)const
{
    RECT rcPlaceholder;
    ASSERT(pDC);
    RECT rcClient;
    GetClientRect(&rcClient);

    MARGINS marGlassInset = {-1, -1, -1, -1}; 
    GetDlgItem(IDC_STATIC_PLACEHOLDER)->GetWindowRect(&rcPlaceholder);
    ::ScreenToClient(m_hWnd, &rcPlaceholder);
    marGlassInset.cxLeftWidth = rcPlaceholder.left;
    marGlassInset.cxRightWidth = rcClient.right - rcPlaceholder.right;
    marGlassInset.cyBottomHeight = rcClient.bottom - rcPlaceholder.bottom;
    marGlassInset.cyTopHeight = rcPlaceholder.top;

    CDwmApiImpl dwmApi;
    if (dwmApi.Initialize() && dwmApi.IsDwmCompositionEnabled() && 
        SUCCEEDED(dwmApi.DwmExtendFrameIntoClientArea(m_hWnd, 
        &marGlassInset)))
    {
        RECT rcScratch = rcClient;
        rcScratch.right = marGlassInset.cxLeftWidth;
        pDC->PatBlt(rcScratch.left, rcScratch.top, RECTWIDTH(rcScratch), 
            RECTHEIGHT(rcScratch), BLACKNESS);
        rcScratch = rcClient;
        rcScratch.bottom = marGlassInset.cyTopHeight;
        pDC->PatBlt(rcScratch.left, rcScratch.top, RECTWIDTH(rcScratch), 
            RECTHEIGHT(rcScratch), BLACKNESS);
        
        rcScratch = rcClient;
        rcScratch.left = rcScratch.right - marGlassInset.cxRightWidth;
        pDC->PatBlt(rcScratch.left, rcScratch.top, RECTWIDTH(rcScratch), 
            RECTHEIGHT(rcScratch), BLACKNESS);
        
        rcScratch = rcClient;
        rcScratch.top = rcScratch.bottom - marGlassInset.cyBottomHeight;
        pDC->PatBlt(rcScratch.left, rcScratch.top, RECTWIDTH(rcScratch), 
            RECTHEIGHT(rcScratch), BLACKNESS);
    }
}

上面代码片段中使用的 CDwmApiImpl 类在不使用其导入库的情况下实现了 Vista 的桌面窗口管理器 API。所以,它基本上是 LoadLibrary/GetProcAddress 调用​​的集合。如果 CDwmApiImpl 对象可以初始化,并且使用 CDwmApiImpl::DwmExtendFrameIntoClientArea 扩展半透明背景成功,代码只需在占位符控件周围绘制填充的黑色矩形,从而使背景半透明。演示如何处理 MonthCalendar 控件和 RichText 控件的对话框的工作方式也大致相同。

使用代码和构建代码示例

要使用该代码,您需要 VC6 或 Visual Studio 2005。如果您只有 Visual Studio 2002 或 2003,我假设您可以直接用这些开发环境打开 VC6 的 .dsw 文件。在这种情况下,下面段落中适用于 VC6 的内容可能也适用于 Visual Studio 2002 和 2003。但是,我不知道使用 VC6 工作区与这些开发环境一起使用是否真的有效,仅仅是因为我没有这些开发系统的副本或许可证,因此从未尝试过。

您还需要 Vista 软件开发工具包,您可以免费下载。无论如何都需要此 SDK,因为即使我们使用 VC6,我们也需要该 SDK 的一些头文件。如果您使用 VC6,对于 x86 构建,您还需要 Windows Server 2003 Platform SDK 或 Windows XP SP2 Platform SDK。还应将这两个 Platform SDK 的目录集成到 VC6 中。对于 VC6 的 x64 构建,您需要 Windows Server 2003 SP1 Platform SDK。

本文代码和演示项目的用法因您的开发环境配置而异。我将这四种情况区分开来:

  • Visual Studio 2005 集成了 Vista SDK
  • Visual Studio 2005 未集成 Vista SDK
  • Visual C/C++ 6 x86 构建
  • Visual C/C++ 6 x64 构建

Visual Studio 2005 集成了 Vista SDK

如果您有此配置,使用本文的源代码将是轻而易举的事。只需解压缩附带的 .zip 文件并保留目录结构,删除 aerohdr 子目录,然后构建并运行演示应用程序。

Visual Studio 2005 未集成 Vista SDK

这是 Visual Studio 2005 的开箱即用配置。解压缩附带的 .zip 文件并保留目录结构,注意 aerohdrs 目录,“Aero 头文件”。此时,您应该已经在计算机上安装了 Vista SDK。现在,将以下文件从 Vista SDK 的 include 目录复制到 aerohdrs 目录:

  • uxtheme.h
  • vsstyle.h
  • vssym32.h
  • specstrings.h

我可以轻松地将这些来自 Vista SDK 的文件添加到本文的演示应用程序中,但我不能重新分发这些文件。因此,您需要下载这些文件并将它们移动到正确的位置。然后,删除 aerohdr 子目录中的头文件 sal.h。现在选择一个适当的构建目标,构建并运行演示应用程序。

Visual C/C++ 6 x86 构建

首先,解压缩附带的 .zip 文件并保留目录结构。我假设您已安装并集成了 Windows 2003 Server Platform SDK 或 Windows XP SP2 Platform SDK 到 VC6。对于 x86 构建和 x64 构建,您现在需要将与 Visual Studio 2005 未集成 Vista SDK 过程中的相同文件复制到 aerohdr 子目录(uxtheme.hvsstyle.hvssym32.hspecstrings.h)。但是,这次我们将把 sal.h 文件保留在 aerohdr 子目录中。现在只需启动 VC6,加载 autoaero.dsw 工作区文件,选择其中一个非 x64 目标,构建并运行示例。

Visual C/C++ 6 x64 构建

与之前一样,解压缩附带的 .zip 文件并保留目录结构。对于使用 VC6 的 x64 构建,您首先需要下载并安装 Windows Server 2003 SP1 Platform SDK。此 Platform SDK 包含生成 x64 二进制文件所需的库和开发工具。下载后,我建议创建一个具有以下内容的批处理文件:

call <PLATSDKDIR>\setenv.cmd /X64 /RETAIL
start <MSVC6DIR>\Common\MSDev98\Bin\MSDEV.COM  /useenv

在此批处理文件中,<PLATSDKDIR> 代表您安装 Server 2003 SP1 Platform SDK 的目录。<MSVC6DIR> 代表您的 VC6 安装目录。如果您运行此批处理文件,VC6 开发环境将启动,并为您提供正确的头文件和库目录设置,以及用于生成本机 x64 二进制文件的编译器文件。但是,这仅在您安装了最新的 VC6 Service Pack 之一时才有效。因此,我建议您下载并安装 Visual Studio 6 Service Pack 6,如果您尚未安装的话。然后,确保您已将四个头文件(uxtheme.hvsstyle.hvssym32.hspecstrings.h)从 Vista SDK 复制到 aerohdrs 子目录。现在选择一个 x64 目标并构建演示应用程序的 x64 版本。

这个 sal.h 文件有什么用?

我添加到 aerohdr 子目录的 sal.h 文件使得可以使用 Vista SDK 的头文件与不了解 SAL 注释的编译器版本一起使用,例如 VC6 附带的版本。SAL 注释是添加到最近 SDK 头文件中的装饰。它们允许 PREfast 工具(也称为 /analyze 选项)进行静态代码分析。sal.h ——以及 specstrings_strict.hspecstrings_adt.h ——由我们必须从 Vista SDK 复制到 aerohdr 目录的 specstrings.h 头文件隐式包含。我提供的 sal.h 文件仅包含对我们在 aerohdrs 子目录中复制的 Vista SDK 头文件中使用的所有 SAL 注释宏的空宏定义。Visual Studio 2005 附带了一个内置的 sal.h 文件。在这种情况下,您肯定希望使用官方的 Visual Studio 2005 版本,并删除我添加到 aerohdr 子目录的文件。

将代码改造/添加到您自己的项目中

当将示例应用程序的功能改造到您自己的代码时,请遵循以下步骤:

  1. 向项目的预处理器设置添加每个项目的包含路径,指向解压缩的 common 目录所在的位置。
  2. 向项目的预处理器设置添加每个项目的包含路径,指向解压缩的 aerohdrs 目录所在的位置。如果您使用集成了 Vista SDK 的 VS2005,则应省略此步骤。
  3. 向您的应用程序添加 GDI+ 的每个进程初始化。如果您不知道如何执行此操作,请参阅示例应用程序及其 GdiplusStartupGdiplusShutdown 函数的使用。如果您计划使应用程序也能在 Windows 2000 上运行,并且不随应用程序分发 GDI+ 可再发行组件,请在应用程序中延迟加载 gdiplus.dll。我避免在使用 GDI+ 的函数的每个函数级别进行 GDI+ 的内部初始化和取消初始化。这是因为我在各种 usenet 帖子中读到,GDI+ 的初始化/取消初始化在每个调用级别上并不可靠,而应该是一个每个进程的初始化/取消初始化。
  4. 确保您已添加 comctl32.dll 版本 6.0 的清单。对于 VC6,您可以通过将清单文件添加到资源文件来实现,就像本文示例应用程序中的文件一样。对于 VS2005,您可以使用一系列 pragma 来添加它,就像我在示例的 stdafx.h 文件中所做的那样。
    #if _MSC_VER>=1400
    #if defined _M_IX86
    #pragma comment(linker,"/manifestdependency:\"type='win32' 
        name='Microsoft.Windows.Common-Controls'  version='6.0.0.0' 
        processorArchitecture='x86' publicKeyToken='6595b64144ccf1df' 
        language='*'\"")
    #elif defined _M_IA64
    #pragma comment(linker,"/manifestdependency:\"type='win32' 
        name='Microsoft.Windows.Common-Controls'  version='6.0.0.0' 
        processorArchitecture='ia64' publicKeyToken='6595b64144ccf1df' 
        language='*'\"")
    #elif defined _M_X64
    #pragma comment(linker,"/manifestdependency:\"type='win32' 
        name='Microsoft.Windows.Common-Controls'  version='6.0.0.0' 
        processorArchitecture='amd64' publicKeyToken='6595b64144ccf1df' 
        language='*'\"")
    #else
    #pragma comment(linker,"/manifestdependency:\"type='win32' 
        name='Microsoft.Windows.Common-Controls'  version='6.0.0.0' 
        processorArchitecture='*' publicKeyToken='6595b64144ccf1df' 
        language='*'\"")
    #endif
    #endif /// _MSC_VER>=1400 
    请注意,出于某种我实在不理解的模糊原因,Visual Studio 2005 中创建的新项目不会获得 6.0 版通用控件清单。除非您选择使用 Unicode 版本的运行时或 MFC 库来创建项目。使用上面的代码片段可以解决此问题。
  5. 将 common 目录中的所有文件添加到您的应用程序项目中。将此目录中所有 .cpp 文件的设置设置为“不使用预编译头”。
  6. 如果您使用本文的代码在带有 Aero Glass 的窗口上使用动画控件,请确保设置动画的文件名或资源 ID —— 使用 SendMessage(..., ACM_OPEN,...)Animate_OpenAnimate_OpenEx 或 MFC 的 CAnimateCtrl::Open(...) —— 在使用 AeroAutoSubclass 子类化动画控件的父窗口之后,或者在使用 AeroSubClassCtrl 子类化动画控件之后。这是必需的,因为在设置动画控件的文件名或资源 ID 后,没有文档记录的方法可以检索它们。因此,为了从辅助存储加载动画,动画控件的子类化代码需要拦截 ACM_OPEN 消息或使用 Video for Windows 例程的资源。
  7. 根据您的开发环境,对 aerohdrs 目录进行如上所述的更改。
  8. 现在请仔细考虑在应用程序中添加炫酷 Aero Glass 效果的合理位置。请注意,使用 Aero Glass 背景根本不会提高应用程序对话框或窗口的可读性。标准配色方案和标准对话框之所以在白色或灰色背景上显示黑色文本,是有原因的。这是因为高对比度可以提高可读性。不要仅仅因为您现在拥有一个锤子,一切看起来都像钉子就使用 Aero Glass。我认为 Aero Glass 在一些选定的窗口或对话框上看起来会很好,例如那些带有动画控件的无模式对话框,或者在执行冗长操作时出现的进度条。一个好的地方也可以是您应用程序的“关于”框或启动画面。通常作为安装 CD 的自动启动应用程序出现的应用程序也是不错的候选。不要将 Aero Glass 添加到用户经常使用的窗口或对话框中,因为随着时间的推移,在半透明背景上阅读文本会变得非常乏味。

实现细节

大多数单个控件的子类化代码依赖于 WM_PRINTCLIENT 消息。您可以使用此消息并将其发送到控件,同时提供设备上下文句柄 (HDC)。如果控件支持此消息,它将在设备上下文中呈现自身。另一方面,对于静态控件,子类化代码采用主题 API,最值得注意的是 DrawThemeTextEx API。使用此 API,可以在控件渲染的文本上添加发光效果。这对于可读性至关重要,因为发光效果会产生额外的对比度,独立于半透明背景。按钮控件的子类化代码也大量使用了主题 API,特别是 DrawThemeBackgroundGetThemeBitmapDrawThemeTextEx。如前所述,动画控件的子类化代码使用了低级的 Video for Windows API。此外,动画子类化代码包含一个后备实现,以防 Video-for-Windows 基于的实现中的某个初始化步骤失败。这个后备代码在低端机器上会闪烁,并且比 Video-for-Windows 基于的实现需要更多的 CPU 利用率。然而,Video-for-Windows 基于的实现有一个问题,那就是对于存在的每个动画控件,都需要创建一个临时文件。我很乐意提供关于不需要临时文件的替代实现的任何提示。

本文子类化代码中所有绘图代码的粘合剂由 GDI+ 提供,因为该库的例程天生就支持 Alpha 通道。Windows XP 和所有后续的 Windows 操作系统版本都预装了 GDI+。此外,还有适用于旧版操作系统版本的 GDI+ 可再发行组件,因此我决定不为 GDI+ 编写包装器,就像我对 dwmapi.dlluxtheme.dll 所做的那样。这个决定的缺点是,对于 Windows 2000,如果您计划使用本文的代码分发应用程序,您将需要显式地延迟加载 gdiplus.dll

但是……

...关于 .NET 代码?

老实说,我不知道您是否可以使用本文的代码来编写 .NET 代码。很可能,您可以创建一个具有 AeroAutoSubclassAeroSubClassCtrl 作为其导出函数的本机 DLL。然后,应该可以创建一个 P/Invoke 包装器并从您的 Windows 窗体应用程序中调用它。也许我会在本文的未来版本中添加此内容。

...关于我的 ActiveX 控件?

如果您想为托管 ActiveX 控件的窗口添加 Aero Glass,那么该控件可能会遇到与我们在上面关于天真的 Aero Glass 实现的截图中所见到的相同的问题。您可能需要子类化该控件——或占位符控件,如隐藏的静态窗口——并尝试 WM_PRINTCLIENT 方法,以便让控件在绘图缓冲区中呈现自身。您还需要了解本文子类化代码的实现细节,并准备好根据需要进行调试。

...我可以在 Windows 9x 或 ME 上运行本文的代码吗?

坦白说,我没有尝试过。但是,我不知道本文源代码中的任何函数会阻止代码在这些旧的消费者 Windows 版本上运行。如果存在问题,请查看延迟加载实现有问题的 API 的 DLL 是否能解决问题。如果您想在这样的消费者 Windows 版本上运行演示应用程序,您可能需要安装通用控件 DLL 的更新或最新版本的 Internet Explorer。这仅仅是因为演示应用程序使用的是不属于这些消费者 Windows 版本 RTM 版本中的控件。

致谢和动机

灵感来自 Michael Dunn 和 Jens Schacherl。Michael 在 CodeProject 上撰写了出色的文章,而他关于 Aero Glass 的文章是我入门编程 Aero Glass 的契机。Jens 在 CodeGuru 上撰写了一篇出色的文章,展示了如何使用低级的 Video for Windows API 在透明背景上渲染 .avi 文件。在我将 Jens 在他的文章中使用的例程集成进来之前,动画子类化代码需要大量的 CPU 使用,依赖于动画控件的未文档行为,并且在慢速机器上经常闪烁。

然而,本文代码的主要动机来自于微软的那些人,他们大约在 15 年前(Win16 时代)负责创建当时著名的 ctl3d.dll。那时,Windows UI 的对话框有一个白色背景,一切看起来都平淡无奇。但是,通过使用 ctl3d.dll ——或其后继者 ctl3dv2.dll ——可以添加时尚的战舰灰色背景,使控件看起来具有三维感。使用一个神奇的函数 Ctl3dAutoSubclass,甚至可以将 3D 外观应用于整个进程——呃……任务——中的所有对话框和控件。所以我问自己,“是否可以像 Ctl3dAutoSubclass 当时启用 3D 外观一样,为应用程序启用 Aero Glass 呢?”本文的代码肯定不像当时的 ctl3d.dll 那样方便、全面或精致,但我希望它尽可能接近。

摘要

我提供给本文的例程可能远非完美,因为我可能忽略了标准 Windows 控件及其海量不同样式标志工作方式中的许多细微差别。我的希望是它们足够好,人们可以在他们的应用程序中使用它们,并且错误和烦恼会随着时间的推移而被消除。我也希望其他人能提供替代实现来解决代码中的问题,或者仅仅是提供一个更好或更优雅的解决方案。我花了两个多月的时间在我的业余时间进行实验、调试和编写与本文相关的代码,所以我真的希望您喜欢它。

历史

  • 2007/05/21:初始版本
  • 2007/06/19:修复了头控件和组合框销毁代码中的悬空指针问题
© . All rights reserved.