原文地址:http://msdn.microsoft.com/en-us/library/windows/desktop/aa969540(v=vs.85).aspx
桌面合成功能,在Windows Vista版本中被引用,从根本上改变了应用程序在屏幕上显示像素的方式。当启用桌面合成时,单独的窗体不再像之前的Windows版本一样直接绘制到屏幕或主显示设备上。相反,他们的绘制被重新定向到视频内存中的离线表面,这些离线表面之后被渲染成桌面图片并且显示到屏幕上。
桌面合成由桌面窗体管理器(DWM)处理。通过桌面合成,DWM在桌面上启用视觉特效,比如玻璃窗体边框,3-D窗体过渡动画,窗体翻转和3D翻转,和高分辨率支持。
桌面窗体管理器作为Windows的服务运行。它可以被启用和禁用,通过控制面板中的管理工具项,在服务下面的Desktop Window Manager Session Manager。
很多DWM的功能可以被控制或应用程序通过DWM API访问。下面这些文档描述DWM API实现的功能和要求。
DWM概述
主题 |
描述 |
Enable and Control DWM Composition 启用和控制DWM合成 |
桌面窗体窗理器(DWM)的合成API提供一些函数来设置和查询DWM使用的基本信息。这些API允许你查询和更改合成状态。另外,你可以为不同的DWM窗体属性设置和查询渲染策略。 |
DWM Blur Behind Overview DWM背景模糊概述 |
标志DWM效果的其中之一就是半透明以及模糊非客户区。DWM的API允许应用程序应用这些效果到他们顶层窗体的客户区。 |
DWM Thumbnail Overview DWM缩略图概述 |
DWM可以显示应用程序窗体的缩略图。这不是静态的窗体截图,相反,它是动态的,在缩略图源窗体和目标窗体上的位置之间的固定连接接收实时缩略图渲染。通过鼠标停靠在任务栏的应用程序上或使用ALT+TAB键来查看和快速切换到应用程序,来允许快速查看正在运行的应用程序。 |
Accessing and Controlling DWM Frame Data DWM边框数据的访问和控制 |
这个章节讨论用来调度和媒体表现的DWM API。 |
Performance Considerations and Best Practices 性能注意事项和最佳实践 |
这个章节呈现了一组使用DWM API的最佳实践。 |
Custom Window Frame Using DWM 使用DWM自定义窗体边框 |
这个章节演示如何使用DWM API来为你的应用程序创建自定义的窗体边框。 |
启用和控制DWM合成
桌面窗体管理器(DWM)合成API提供了一些函数来设置和查询被DWM使用的一些基本信息。这些API允许你查询和更改合成状态。另外,你可以为不同的DWM窗体属性设置和查询渲染策略。
这篇文章包含以下章节:
1.禁用DWM组合
2.接收变色信息
3.控制非客户区区域渲染
4.消息
1.DisablingDWM Composition:禁用DWM组合
注意 对于Windows 8 消费者预览版,在这个章节中的信息不再可用。DWM不能够再被编程禁用,也不能够在一个应用程序试图绘制主显示平面的时候禁用。下面的信息只适用于Windows 7以及更早的系统。
因为DWM使用图处理单元(GPU)来桌面合成,一定部分的应用程序可能为了兼容性而必须禁用DWM。完全控制桌面的应用程序,例如在全屏模式下运行的游戏,必须判断DWM是否启用,并且如果DWM启用,禁用它。为了实现这些,需要两个函数:DwmIsCompositionEnabled和DwmEnableComposition。
调用DwmEnableComposition 时,参数fEnable设置为DWM_EC_DISABLECOMPOSITION,它会禁用DWM合成,直到调用进程关闭或通过调用DwmEnableComposition(fEnalbe参数设置为DWM_EC_ENABLECOMPOSITION)来再次启用合成。当所有曾经禁用合成的应用程序都被关闭或通过调用DwmEnableCompostion来手动重启组合时,DWM合成将自动重启
注意 当一个应用程序试图在主显示平面上直接绘制的时候,DWM会自动禁用合成。合成会被禁用直到主显示平面被应用程序释放。
2.Retrievingthe Colorization Information:接收变色信息
窗体非客户区域的颜色由当前系统主题处理。提供这个颜色值,通过DWM API允许应用程序来匹配客户UI与系统颜色主题。
要访问这个颜色值以及监视颜色变化,使用DwmGetColorizationColor函数以及WM_DWMCOLORIZATIONCOLORCHANGED消息。
下面的例子演示如何来监视颜色更改以及访问新的颜色。
C:
...
case WM_DWMCOLORIZATIONCOLORCHANGED:
{
g_currColor = (DWORD)wParam;
g_opacityblend = (BOOL)lParam;
}
break;
...
D2010(引用Dwmapi.pas):
procedure WMDWMCOLORIZATIONCOLORCHANGED(varMessage:TMessage);message WM_DWMCOLORIZATIONCOLORCHANGED;
var
pcrColorization: DWORD;
pfOpaqueBlend: BOOL;
begin
pcrColorization:=Message.WParam;//当前主题颜色
pfOpaqueBlend:=BOOL(Message.LParam);//是否启用透明效果
end;
主题颜色和是否启用透明效果的当前状态可以在控制面板中看到:
3.Controlling Non-ClientRegion Rendering:控制非客户区区域渲染
DWM启用的两个可视效果分别是窗体非客户区域的透明以及过渡效果。一些应用程序可能为了风格或兼容原因而必须禁用或重新启用这些效果。下面的函数用来管理透明度和过渡效果的方式。
1. DwmGetWindowAttribute
2. DwmSetWindowAttribute
为了让应用程序接收当前非客户区渲染状态,使用带DWMWA_NCRENDERING_ENABLED属性的DwmGetWindowAttribute。下面的例子显示了一个典型的DwmGetWindowAttribute调用。
C:
BOOL enabled = FALSE;
HRESULT hr = DwmGetWindowAttribute(hwnd,DWMWA_NCRENDERING_ENABLED, &enabled, sizeof(enabled));
D2010(引用Dwmapi.pas):
var
Attribute: BOOL;
HReturn:Integer;
begin
Attribute := FALSE;
HReturn := DwmGetWindowAttribute(Handle, DWMWA_NCRENDERING_ENABLED,@Attribute, sizeof(Attribute));
注意 每个DWMWINDOWATTRIBUTE有一个实现的类型与之相关。请参阅每个枚举成员的详细信息。
DwmSetWindowAttribute允许应用程序设置非客户区域的渲染策略。它也决定了一个应用程序该如何处理DWM过渡效果。
下面的例子禁用了非客户区渲染。
C:
HRESULT DisableNCRendering(HWND hwnd)
{
HRESULT hr = S_OK;
DWMNCRENDERINGPOLICY ncrp = DWMNCRP_DISABLED;
// Disable non-client area rendering on the window.
hr = DwmSetWindowAttribute(hwnd, DWMWA_NCRENDERING_POLICY, &ncrp,sizeof(ncrp));
if (SUCCEEDED(hr))
{
// ...
}
return hr;
}
D2010(引用Dwmapi.pas):
var
Attribute: DWORD;
HReturn:Integer;
begin
Attribute := DWMNCRP_DISABLED;
HReturn := DwmSetWindowAttribute(Handle, DWMWA_NCRENDERING_POLICY,@Attribute, sizeof(Attribute));
除了控制非客户区的非客户区渲染,DwmSetWindowAttribute也可以控制DWM过渡效果。过渡方式可以通过使用DWMWA_TRANSITIONS_FORCEDISABLED做为dwAttribute参数来设置。
4.Messaging:消息
下面的消息提供了DWM事件的通知。这些消息可以用来监视例如合成状态更改或系统颜色主题更改的变化。
1. WM_DWMCOLORIZATIONCOLORCHANGED
2. WM_DWMCOMPOSITIONCHANGED
3. WM_DWMNCRENDERINGCHANGED
4. WM_DWMWINDOWMAXIMIZEDCHANGE
DWM Blur Behind Overview:DWM背景模糊
标志桌面窗体管理器效果的其中之一就是半透明以及模糊非客户区。DWM API允许应用程序应用这些特效到顶层窗体的客户区。
注意Windows Vista Home Basic 版本不支持透明玻璃效果。通常在其他Windows版本上渲染为透明玻璃效果的区域会被渲染为不透明。
这篇文章讨论下列DWM允许的客户区背景模糊情景。
1. Adding Blur to a Specific Region of the Client Area 给客户区指定的区域添加模糊
2. Extending the Window Frame into the Client Area 扩展窗体边框到客户区
1.Adding Blur to a SpecificRegion of the Client Area:给客户区指定的区域添加模糊
应用程序可以在整个窗体的客户区或一个指定的子区域实现背景模糊效果。这允许应用程序添加与其他应用程序看起来不同的风格条和搜索条。
在这种情况下要用到的的API是DwmEnableBlurBehindWindow函数,它使用DWM Blur Behind Constants和DWM_BLURBEHIND结构。
下面的例子函数,EnableBlurBehind
,说明了如何来给整个窗体实现模糊效果。
C:
HRESULT EnableBlurBehind(HWND hwnd)
{
HRESULT hr = S_OK;
// Create and populate the blur-behind structure.
DWM_BLURBEHIND bb = {0};
// Specify blur-behind and blur region.
bb.dwFlags = DWM_BB_ENABLE;
bb.fEnable = true;
bb.hRgnBlur = NULL;
// Enable blur-behind.
hr = DwmEnableBlurBehindWindow(hwnd, &bb);
if (SUCCEEDED(hr))
{
// ...
}
return hr;
}
D2010(引用Dwmapi.pas):
var
pBlurBehind: TDwmBlurbehind;
begin
ZeroMemory(@pBlurBehind,SizeOf(TDwmBlurbehind));
pBlurBehind.dwFlags:=DWM_BB_ENABLE;
pBlurBehind.fEnable:=True;
pBlurBehind.hRgnBlur:=0;
DwmEnableBlurBehindWindow(Handle, pBlurBehind);
注意给hRgnBlur参数指定的是NULL。这告诉DWM给整个窗体应用背景模糊。
下面的图片说明了将背景模糊效果应用到了整个窗体。
为了在一个子区域中应用模糊,将一个有效的句柄(HRGN)赋给hRgnBlur成员(DWM_BLURBEHIND结构的成员)并且添加DWM_BB_BLURREGION 标志给dwFlags成员。
当你给一个窗体的子区域应用背景模糊效果的时候,窗体的透明通道是非模糊区域使用的。在窗体的非模糊区可能会引起一些非预期的透明效果。因些,当你应用模糊效果到一个子区域的时候要小心。
2.Extending the Window Frameinto the Client Area:扩展窗体的边框到客户区
一个应用程序可以扩展模糊的窗体边框到客户区。当你应用背景模糊效果到一个带有停靠工具栏或与其他应用程序看起来不同的控件的窗体时是非常有用的。这个功能由DwmExtendFrameIntoClientArea函数实现。
通过使用DwmExtendFrameIntoClientArea来启用模糊,使用MARGINS结构来表明你要扩展到客户区到多少宽。下面的例子函数,ExtendIntoClientBottom
,
扩展模糊在非客户区边框底部到客户区。
C:
HRESULT ExtendIntoClientBottom(HWND hwnd)
{
HRESULT hr = S_OK;
// Set the margins, extending the bottom margin.
MARGINS margins = {0,0,0,25};
// Extend the frame on the bottom of the client area.
hr = DwmExtendFrameIntoClientArea(hwnd,&margins);
if (SUCCEEDED(hr))
{
// ...
}
return hr;
}
D2010(引用Dwmapi.pas):
var
rcClient:TRect;
var
pMarInset: UxTheme.TMargins;
begin
pMarInset.cxLeftWidth:=0;
pMarInset.cyTopHeight:=0;
pMarInset.cxRightWidth:=0;
pMarInset.cyBottomHeight:=25;
DwmExtendFrameIntoClientArea(Handle,pMarInset);
end;
下面的图片说明了背景模糊效果扩展到客户区的底部。
也可以通过DwmExtendFrameIntoClientArea方法来实现“玻璃板”的效果,模糊效果应用到整个窗体表示而没有一个可见的窗体边。下面的例子演示了这个客户区被渲染成没有窗体边的效果,
C:
HRESULT ExtendIntoClientAll(HWND hwnd)
{
HRESULT hr = S_OK;
// Negative margins have special meaning toDwmExtendFrameIntoClientArea.
// Negative margins create the "sheet of glass" effect, wherethe client
// area is rendered as a solid surface without a window border.
MARGINS margins = {-1};
// Extend the frame across the whole window.
hr = DwmExtendFrameIntoClientArea(hwnd,&margins);
if (SUCCEEDED(hr))
{
// ...
}
return hr;
}
D2010(引用Dwmapi.pas):
var
rcClient:TRect;
var
pMarInset: UxTheme.TMargins;
begin
pMarInset.cxLeftWidth:=-1;
pMarInset.cyTopHeight:=-1;
pMarInset.cxRightWidth:=-1;
pMarInset.cyBottomHeight:=-1;
DwmExtendFrameIntoClientArea(Handle,pMarInset);
end;
下图展示了在“玻璃板”窗体风格中的背景模糊。
Performance Considerations and Best Practices:性能注意事项和最佳实践
这篇文章列举了一系列使用桌面窗体管理器(DWM)API最佳实践。
这篇文章包含下列章节:
1. Application Practices for DWM DWM应用程序实践
2. Drawing Practices for DWM DWM绘制实践
3. DWM Blur-Behind Client Region DWM背景模糊客户区
1.Application Practices forDWM:DWM应用程序实践
如果你的应用程序需要处理每英寸点数(DPI)缩放,你可以将一个应用程序声明为DPI-Aware,并且通过在程序Manifest的dpi-aware标记或在程序初始期间通过调用SetProcessDPIAware函数来阻止自动缩放。
当DWM合成打开的时候,遮蔽的应用程序不再接收到WM_PAINT消息并且不再询问是否重新渲染。每个窗体的内容已经可以用来合成到桌面图片。
为了实现点击测试,顶层WS_EX_TRANSPARENT窗体应该与WS_EX_LAYERED风格结合。WS_EX_TRANSPARENT在经典模式下,没有重定向,对于同属于相同线程的同一个层次的子窗体是有用的,但不是顶层窗体。
使用区域或分层来创建异形或混合窗体。注意,在Windows Vista或之后版本中,只自定义绘制的顶层窗体的部分将不会在非绘制区域提供期望的陈旧内容。
一些API,例如GetDCOrgEx可以被用来决定准确的实际值。如果你的重定向窗体有一个设备上下文(DC),GetDCOrgEx返回的原点不再与在屏幕上你的窗体的原点相符合。这个原点会被替换成为窗体的后台缓冲平面的原点:(0,0)。
当所有其他失败,通过调用DwmSetWindowAttribute函数来禁用窗体的渲染。
2.Drawing Practices for DWM:DWM绘制实践
避免直接绘制到主显示平面。做这些会强制DWM来禁用合成,直接你的应用程序释放主设备平面。
评估你的应用程序是否必须提供它自己的双缓冲。DWM有效地双缓冲内容,并在一个单一的帧中显示窗体。
避免从显示DC中读取或写入。尽管这些区域可以被应用程序访问,并且Microsoft Win32 API支持在这里绘制,做这些可能会导致窗体失去它原先拥有的玻璃边。
避免混合Windows Graphics Device Interface(GDI)和MicrosoftDirectX,除非它们不重叠。如果必需混合时,要么绘制GDI内容到DirectX软件平面并在合成到屏幕之前组合他们,还要么在单独的窗体中绘制他们。
为了渲染,请使用BitBlt或StretchBlt 函数替代WindowsGDI+来呈现你的绘制。GDI+使用软件渲染来渲染一条扫描线在同一时间。这可能会在你的应用程序中引起忽隐忽现。
3.DWM Blur-Behind ClientRegion:DWM模糊客户区域
渲染模糊效果对于CPU和图形处理单元(GPU)来讲都是资源密集型操作。应用程序开发者应该考虑客户区模糊的实现,以便于它不消耗过多的资源。你应该使用特别注意下面情况:
1. 当你希望客户区模糊的大小是明显的,即便模糊区域本身没有发生刷新。模糊必须在任何窗体模糊区域下发生刷新的情况下被渲染,这会导致CPU和GPU的消耗。另外,在窗体上的操作(Move/Resize/Transitions)会导致更多的消耗。
2. 当你希望在模糊客户区有明显刷新。这会在每个刷新上需要模糊重绘并且会消耗很多资源。
3. 如果模糊预计将包括一个明显的区域,并且了预计刷新到该区域,我们强列建议你不要模糊客户区。
Custom Window Frame Using DWM:使用DWM扩展窗体边框
这篇文章演示了如何使用桌面管理器(DWM)API为你的应用程序创建自定义窗体边框。
1. 介绍
2. 扩展客户边框
3. 去除标准边框
4. 在扩展边框窗体上绘制
5. 为自定义边框启用点击测试
6. 附录A:例子窗体过程
7. 附录B:绘制标题栏
8. 附录C:HitTestNCA函数
9. 相关主题
1.介绍
在Windows Vista和之后版本,应用程序的非客户区区域的显示(标题栏,图标,窗体边框,标题按钮)由DWM控制。使用DWM API,你可以改变DWM渲染窗体边框的方式。
DWM API的其中一个功能是能够扩展应用程序边框到客户区。它允许你整合客户UI元素——例如工具栏——到边框,给UI控件一个在应用程序界面中更加突出的位置。例如,在Windows Vista 上的Windows Internet Explorer 7通过扩展边框顶部边框来整合导航栏到窗体边框上,屏幕截图如下显示。
扩展窗体边框的功能也允许你创建自定义边框同时保持窗体的外观和感觉。例如,Microsoft Office Word 2007绘制Office按钮和快速访问工具栏内置到自定义边框同时提供标准最小化,最大小和关闭标题按钮,屏幕截图如下显示。
2.扩展客户边框
扩展边框到客户区的功能通过DwmExtendFrameIntoClientArea函数来实现。为了扩展边框,将目标窗体的句柄和边距插入值一起传递给DwmExtendFrameIntoClientArea。边距插入值决定了在窗体的四个边上扩展多少宽度的边框。
下面的代码演示了使用DwmExtendFrameIntoClientArea来扩展边框。
C:
// Handle the window activation.
if (message == WM_ACTIVATE)
{
// Extend the frame into the client area.
MARGINS margins;
margins.cxLeftWidth = LEFTEXTENDWIDTH; // 8
margins.cxRightWidth = RIGHTEXTENDWIDTH; // 8
margins.cyBottomHeight = BOTTOMEXTENDWIDTH; // 20
margins.cyTopHeight = TOPEXTENDWIDTH; // 27
hr = DwmExtendFrameIntoClientArea(hWnd, &margins);
if (!SUCCEEDED(hr))
{
// Handle the error.
}
fCallDWP = true;
lRet = 0;
}
注意,边框扩展是在WM_ACTIVATE消息中完成而不是在WM_CREATE消息中。这保证窗体在默认大小以及在最大化的时候边框扩展被妥善处理。
下面的图片展示了一个标准窗体边框(在左边)和相同边框扩展的(在右边)。边框使用上面例子代码扩展以及默认的Microsoft Visual Studio的 WNDCLASS/WNDCLASSEX背景(COLOR_WINDOW+1)。
这两个窗体的显示不同是十分细小的。两个窗体之间的唯一不同点是在左边窗体中客户区中的细黑线边框在右边窗体中不存在。失去边框的原因是它纳入到扩展边框,但是客户区的其余部分没有。为了扩展边框可以被看见,边框每个边的扩展区域必须有透明度为零的像素数据。在客户区周围的黑色边框有所有颜色值(红,蓝,绿和透明度)设置为零的像素数据。背景其余部分没有透明度设置为零的,所有扩展边框的其余部分是不可见的。
确保扩展边框可见的最方便的方法就是绘制整个客户区为黑色。为了实现这个,将你的WNDCLASS orWNDCLASSEX结构的hbrBackground成员初始为BLACK_BRUSH画刷的句柄。下面的图片显示相同的标准边框(左边)和扩展边框(右边)。然而这次,hbrBackground设置为从GetStockObject 函数获取的BLACK_BRUSH句柄。
3.去除标准边框
在你扩展你的应用程序边框并且使它可见之后,你可以去除标准边框。去除标准边框允许你控制边框每一边的宽度而不是简单的扩展标准边框。
要去掉标准窗体边框,你必须处理WM_NCCALCSIZE消息,当它的wParam值为TRUE的时候返回值为0。做了这些,你的应用程序可以使整个窗体区域做为客户区域,并且去除标准边框。
处理WM_NCCALCSIZE消息的结果是直到客户区域需要重新改变大小时可见。直到那时,窗体显示的初始视图带着标准边框和扩展边。为了解决这个问题,你必须要么重新调整你的窗体的尺寸,要么在窗体创建的时候执行一个初始WM_NCCALCSIZE消息的动作。这可以通过使用SetWindowPos函数移动你的窗体并重新调整窗体大小来实现。下面的代码演示了使用当前窗体矩形属性和SWP_FRAMECHANGED标志的SetWindowPos调用来强制发送WM_NCCALCSIZE消息。
C:
// Handle window creation.
if (message == WM_CREATE)
{
RECT rcClient;
GetWindowRect(hWnd, &rcClient);
// Inform the application of the frame change.
SetWindowPos(hWnd,
NULL,
rcClient.left, rcClient.top,
RECTWIDTH(rcClient),RECTHEIGHT(rcClient),
SWP_FRAMECHANGED);
fCallDWP = true;
lRet = 0;
}
下面的图片显示了标准边框(左边)和最新的没有标准的扩展边框(右边)。
4.在扩展边框中绘制
通过去掉标准边框,你会失去应用程序图标和标题的自动绘制。为了将这些添加回你的应用程序,你必须自已绘制他们。为了做这些,首先看看你的客户区发生了哪些变化。
随着标准边框的去除,你的客户区现在包含整个窗体,包括扩展边框。这包含标题按钮绘制的区域。在下面的边与对的比较中,标准边框和自定义扩展边框的客户区都用红色高亮显示。标准边框窗体的客户区(左边)是黑色区域。在扩展边框窗体上(右边),客户区是整个窗体。
因为整个窗体是你的客户区,你可以简单的绘制你想要的东西到扩展边框上。为了给你的应用程序添加标题,只需要在合适的区域绘制文本。下面的图片显示了主题字体绘制在自定义的标题栏上。标题使用DrawThemeTextEx函数绘制。想查看绘制标题的代码,请看:AppendixB: Painting the Caption Title.
注意 当你在自定义边框中绘制的时候,放置UI控件时请小心。因为整个窗体是你的客户区,你必须调整你每个边框的UI控件摆放,如果你不希望他们出现在你的扩展边框上。
5.给你的扩展边框启用点击测试
去除标准边框的一方面影响就是失去默认的调整大小和移动功能。为了你的应用程序能够正确的模拟标准窗体行为,你将需要实现逻辑来处理标题按钮点击测试和边框调整大小/移动。
给标题按钮点击测试,DWM提供了DwmDefWindowProc函数。自定义边框情况下,为了正确的点击测试标题按钮,消息必须首先被传递到DwmDefWindowProc处理。如果消息被处理了,DwmDefWindowProc返回TRUE,如果返回FALSE,那么消息没有被处理。如果消息没有被DwmDefWindowProc处理,你的应用程序应该自己处理此消息或传递消息给DefWindowProc.
为了实现边框调整大小和移动功能,你的应用程序必须提供点击测试逻辑和处理边框点击测试消息。边框点击测试消息通过WM_NCHITTEST消息发送给你,尽管你的应用程序创建了一个自定义边框的窗体而不是标准边框窗体。下面的代码演示了当DwmDefWindowProc没有处理消息时我们自己处理WM_NCHITTEST消息。想看所调用的HitTestNCA
函数的代码,请看AppendixC: HitTestNCA Function.
C:
// Handle hit testing in the NCA if nothandled by DwmDefWindowProc.
if ((message == WM_NCHITTEST) &&(lRet == 0))
{
lRet = HitTestNCA(hWnd, wParam, lParam);
if (lRet != HTNOWHERE)
{
fCallDWP = false;
}
}
7.附录A:窗体过程例子
下面的例子演示了一个窗体过程以及它支持用来创建自定义边框应用程序的工作函数。
//
// Main WinProc.
//
LRESULT CALLBACK WndProc(HWND hWnd, UINTmessage, WPARAM wParam, LPARAM lParam)
{
bool fCallDWP = true;
BOOL fDwmEnabled = FALSE;
LRESULT lRet = 0;
HRESULT hr = S_OK;
// Winproc worker for custom frame issues.
hr = DwmIsCompositionEnabled(&fDwmEnabled)
if (SUCCEEDED(hr))
{
lRet = CustomCaptionProc(hWnd,message, wParam, lParam, &fCallDWP);
}
// Winproc worker for the rest of the application.
if (fCallDWP)
{
lRet = AppWinProc(hWnd, message, wParam, lParam);
}
return lRet;
}
//
// Message handler for handling the customcaption messages.
//
LRESULT CustomCaptionProc(HWND hWnd, UINTmessage, WPARAM wParam, LPARAM lParam, bool* pfCallDWP)
{
LRESULT lRet = 0;
HRESULT hr = S_OK;
bool fCallDWP = true; // Pass on to DefWindowProc?
fCallDWP = !DwmDefWindowProc(hWnd, message,wParam, lParam, &lRet);
// Handle window creation.
if (message == WM_CREATE)
{
RECT rcClient;
GetWindowRect(hWnd, &rcClient);
// Inform application of the frame change.
SetWindowPos(hWnd,
NULL,
rcClient.left,rcClient.top,
RECTWIDTH(rcClient),RECTHEIGHT(rcClient),
SWP_FRAMECHANGED);
fCallDWP = true;
lRet = 0;
}
// Handle window activation.
if (message == WM_ACTIVATE)
{
// Extend the frame into the client area.
MARGINS margins;
margins.cxLeftWidth = LEFTEXTENDWIDTH; // 8
margins.cxRightWidth = RIGHTEXTENDWIDTH; // 8
margins.cyBottomHeight = BOTTOMEXTENDWIDTH; // 20
margins.cyTopHeight = TOPEXTENDWIDTH; // 27
hr = DwmExtendFrameIntoClientArea(hWnd, &margins);
if (!SUCCEEDED(hr))
{
// Handle error.
}
fCallDWP = true;
lRet = 0;
}
if (message == WM_PAINT)
{
HDC hdc;
{
hdc = BeginPaint(hWnd, &ps);
PaintCustomCaption(hWnd, hdc);
EndPaint(hWnd, &ps);
}
fCallDWP = true;
lRet = 0;
}
// Handle the non-client size message.
if ((message == WM_NCCALCSIZE) && (wParam == TRUE))
{
// Calculate new NCCALCSIZE_PARAMS based on custom NCA inset.
NCCALCSIZE_PARAMS *pncsp =reinterpret_cast(lParam);
pncsp->rgrc[0].left =pncsp->rgrc[0].left + 0;
pncsp->rgrc[0].top =pncsp->rgrc[0].top + 0;
pncsp->rgrc[0].right =pncsp->rgrc[0].right - 0;
pncsp->rgrc[0].bottom = pncsp->rgrc[0].bottom - 0;
lRet = 0;
// No need to pass the message on to the DefWindowProc.
fCallDWP = false;
}
// Handle hit testing in the NCA if not handled by DwmDefWindowProc.
if ((message == WM_NCHITTEST) && (lRet == 0))
{
lRet = HitTestNCA(hWnd, wParam, lParam);
if (lRet != HTNOWHERE)
{
fCallDWP = false;
}
}
*pfCallDWP = fCallDWP;
return lRet;
}
//
// Message handler for the application.
//
LRESULT AppWinProc(HWND hWnd, UINT message,WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
HRESULT hr;
LRESULT result = 0;
switch (message)
{
case WM_CREATE:
{}
break;
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// Parse the menu selections:
switch (wmId)
{
default:
return DefWindowProc(hWnd,message, wParam, lParam);
}
break;
case WM_PAINT:
{
hdc = BeginPaint(hWnd,&ps);
PaintCustomCaption(hWnd, hdc);
// Add any drawing code here...
EndPaint(hWnd, &ps);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
8.附录B:绘制标题栏
下面的代码演示了如何绘制一个标题栏到扩展边框上。这个函数在BeginPaint和EndPaint调用之间被调用。
// Paint the title on the custom frame.
void PaintCustomCaption(HWND hWnd, HDC hdc)
{
RECT rcClient;
GetClientRect(hWnd, &rcClient);
HTHEME hTheme = OpenThemeData(NULL,L"CompositedWindow::Window");
if (hTheme)
{
HDC hdcPaint = CreateCompatibleDC(hdc);
if (hdcPaint)
{
int cx = RECTWIDTH(rcClient);
int cy = RECTHEIGHT(rcClient);
// Define the BITMAPINFO structureused to draw text.
// Note that biHeight is negative. This is done because
// DrawThemeTextEx() needs the bitmap to be in top-to-bottom
// order.
BITMAPINFO dib = { 0 };
dib.bmiHeader.biSize =sizeof(BITMAPINFOHEADER);
dib.bmiHeader.biWidth =cx;
dib.bmiHeader.biHeight =-cy;
dib.bmiHeader.biPlanes =1;
dib.bmiHeader.biBitCount =BIT_COUNT;
dib.bmiHeader.biCompression =BI_RGB;
HBITMAP hbm = CreateDIBSection(hdc, &dib, DIB_RGB_COLORS, NULL,NULL, 0);
if (hbm)
{
HBITMAP hbmOld =(HBITMAP)SelectObject(hdcPaint, hbm);
// Setup the theme drawingoptions.
DTTOPTS DttOpts ={sizeof(DTTOPTS)};
DttOpts.dwFlags =DTT_COMPOSITED | DTT_GLOWSIZE;
DttOpts.iGlowSize = 15;
// Select a font.
LOGFONT lgFont;
HFONT hFontOld = NULL;
if(SUCCEEDED(GetThemeSysFont(hTheme, TMT_CAPTIONFONT, &lgFont)))
{
HFONT hFont = CreateFontIndirect(&lgFont);
hFontOld = (HFONT)SelectObject(hdcPaint, hFont);
}
// Draw the title.
RECT rcPaint = rcClient;
rcPaint.top += 8;
rcPaint.right -= 125;
rcPaint.left += 8;
rcPaint.bottom = 50;
DrawThemeTextEx(hTheme,
hdcPaint,
0, 0,
szTitle,
-1,
DT_LEFT |DT_WORD_ELLIPSIS,
&rcPaint,
&DttOpts);
// Blit text to the frame.
BitBlt(hdc, 0, 0, cx, cy, hdcPaint,0, 0, SRCCOPY);
SelectObject(hdcPaint, hbmOld);
if (hFontOld)
{
SelectObject(hdcPaint,hFontOld);
}
DeleteObject(hbm);
}
DeleteDC(hdcPaint);
}
CloseThemeData(hTheme);
}
}
9.附录C:HitTestNCA函数
下面的代码显示了在EnablingHit Testing for the Custom Frame.中使用的HitTestNCA
函数。这个函数处理了当DwmDefWindowProc不能处理WM_NCHITTEST时的点击测试逻辑。
// Hit test the frame for resizing andmoving.
LRESULT HitTestNCA(HWND hWnd, WPARAMwParam, LPARAM lParam)
{
// Get the point coordinates for the hit test.
POINT ptMouse = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
// Get the window rectangle.
RECT rcWindow;
GetWindowRect(hWnd, &rcWindow);
// Get the frame rectangle, adjusted for the style without a caption.
RECT rcFrame = { 0 };
AdjustWindowRectEx(&rcFrame, WS_OVERLAPPEDWINDOW & ~WS_CAPTION,FALSE, NULL);
// Determine if the hit test is for resizing. Default middle (1,1).
USHORT uRow = 1;
USHORT uCol = 1;
bool fOnResizeBorder = false;
// Determine if the point is at the top or bottom of the window.
if (ptMouse.y >= rcWindow.top && ptMouse.y < rcWindow.top+ TOPEXTENDWIDTH)
{
fOnResizeBorder = (ptMouse.y < (rcWindow.top - rcFrame.top));
uRow = 0;
}
else if (ptMouse.y < rcWindow.bottom && ptMouse.y >=rcWindow.bottom - BOTTOMEXTENDWIDTH)
{
uRow = 2;
}
// Determine if the point is at the left or right of the window.
if (ptMouse.x >= rcWindow.left && ptMouse.x = rcWindow.right- RIGHTEXTENDWIDTH)
{
uCol = 2; // right side
}
// Hit test (HTTOPLEFT, ... HTBOTTOMRIGHT)
LRESULT hitTests[3][3] =
{
{ HTTOPLEFT, fOnResizeBorder ?HTTOP : HTCAPTION, HTTOPRIGHT },
{ HTLEFT, HTNOWHERE, HTRIGHT },
{ HTBOTTOMLEFT, HTBOTTOM, HTBOTTOMRIGHT },
};
return hitTests[uRow][uCol];
}
下面是我用
Delphi
做的例子,可能会与上面的
C
语言版本稍有不同:
贴上.dfm和.pas的代码,大家参考一下吧!
大家最好先自己动手,遇到问题再我的代码。
LearnDWMBaseForm.dfm:
object frmBaseDWMLearn: TfrmBaseDWMLearn
Left = 0
Top = 0
Caption = 'Desktop Window Manager'
ClientHeight = 290
ClientWidth = 554
Color = clBlack
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
OnCreate = FormCreate
PixelsPerInch = 96
TextHeight = 13
end
LearnDWMBaseForm.pas:
unit LearnDWMBaseForm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, Dwmapi, UxTheme, ExtCtrls;
type
TfrmBaseDWMLearn = class(TForm)
procedure FormCreate(Sender: TObject);
private
FMarInset: UxTheme.TMargins;
procedure DoWMNCCalcSize(var Message: TWMNCCalcSize);
procedure WMPAINT(var Message:TMessage);message WM_PAINT;
procedure WMNCHITTEST(var Message:TMessage);message WM_NCHITTEST;
procedure WMNCCALCSIZE(var Message:TMessage);message WM_NCCALCSIZE;
procedure WMNCACTIVATE(var Message:TMessage);message WM_NCACTIVATE;
procedure WMDWMCOMPOSITIONCHANGED(var Message:TMessage);message WM_DWMCOMPOSITIONCHANGED;
procedure WMDWMNCRENDERINGCHANGED(var Message:TMessage);message WM_DWMNCRENDERINGCHANGED;
procedure WMDWMWINDOWMAXIMIZEDCHANGE(var Message:TMessage);message WM_DWMWINDOWMAXIMIZEDCHANGE;
{ Private declarations }
public
constructor Create(AOwner:TComponent);override;
{ Public declarations }
end;
var
frmBaseDWMLearn: TfrmBaseDWMLearn;
implementation
{$R *.dfm}
{ TForm2 }
constructor TfrmBaseDWMLearn.Create(AOwner: TComponent);
begin
FMarInset.cxLeftWidth:=8;
FMarInset.cyTopHeight:=27;
FMarInset.cxRightWidth:=8;
FMarInset.cyBottomHeight:=27;
inherited Create(AOwner);
end;
procedure TfrmBaseDWMLearn.FormCreate(Sender: TObject);
var
rcClient:TRect;
begin
GetWindowRect(Handle,rcClient);
SetWindowPos(Handle,0,
rcClient.Left,rcClient.Top,
rcClient.Right-rcClient.Left,
rcClient.Bottom-rcClient.Top,
SWP_FRAMECHANGED);
end;
procedure TfrmBaseDWMLearn.WMDWMCOMPOSITIONCHANGED(var Message: TMessage);
begin
Inherited;
DwmExtendFrameIntoClientArea(Handle,FMarInset);
end;
procedure TfrmBaseDWMLearn.WMDWMNCRENDERINGCHANGED(var Message: TMessage);
begin
Inherited;
DwmExtendFrameIntoClientArea(Handle,FMarInset);
end;
procedure TfrmBaseDWMLearn.WMDWMWINDOWMAXIMIZEDCHANGE(var Message: TMessage);
begin
Inherited;
end;
procedure TfrmBaseDWMLearn.WMNCACTIVATE(var Message: TMessage);
begin
Inherited;
DwmExtendFrameIntoClientArea(Handle,FMarInset);
end;
procedure TfrmBaseDWMLearn.WMNCCALCSIZE(var Message: TMessage);
begin
if (Message.WParam=1) then
begin
Inherited;
DoWMNCCalcSize(TWMNCCalcSize(Message));
Message.Result:=1;
end
else
begin
Inherited;
end;
end;
procedure TfrmBaseDWMLearn.DoWMNCCalcSize(var Message: TWMNCCalcSize);
var
NCCalcSizeParams: PNCCalcSizeParams;
tmpCaptionY:Integer;
tmpFrameY:Integer;
tmpFrameX:Integer;
tmpTitleHeight:Integer;
begin
NCCalcSizeParams:=Message.CalcSize_Params;
tmpFrameY:=GetSystemMetrics(SM_CYFRAME);
tmpFrameX:=GetSystemMetrics(SM_CXFRAME);
tmpCaptionY:=GetSystemMetrics(SM_CYCAPTION);
tmpTitleHeight:=tmpFrameY+tmpCaptionY;
//标题栏高度
Dec(NCCalcSizeParams.rgrc[0].Top,tmpTitleHeight);
//左右下边框
Dec(NCCalcSizeParams.rgrc[0].Left,tmpFrameX);
Inc(NCCalcSizeParams.rgrc[0].Right,tmpFrameX);
Inc(NCCalcSizeParams.rgrc[0].Bottom,tmpFrameY); //这句启用的时候窗体最大化或拖动右边框到屏幕边缘的时候整个窗体会黑掉
end;
procedure TfrmBaseDWMLearn.WMNCHITTEST(var Message: TMessage);
var
dwStyle:Cardinal;
rcWindow:TRect;
rcFrame:TRect;
ptMouse:TPoint;
uRow,uCol:Integer;
fOnResizeBorder:Boolean;
const
// Hit test (HTTOPLEFT, ... HTBOTTOMRIGHT) //fOnResizeBorder ? HTTOP :HTCAPTION
hitTests:Array [0..2] of Array [0..2] of Cardinal =
(
( HTTOPLEFT, HTTOP, HTTOPRIGHT ),
( HTLEFT, HTNOWHERE, HTRIGHT ),
( HTBOTTOMLEFT, HTBOTTOM, HTBOTTOMRIGHT )
);
begin
if Not DwmDefWindowProc(Handle,Message.Msg,Message.WParam,Message.LParam,Message.Result) then
begin
//下面这些是直接从MSDN的C语言例子翻译过来的。。请大家自便
Inherited;
// Get the point coordinates for the hit test.
ptMouse.X:=TWMNCHITTEST(Message).XPos;
ptMouse.Y:=TWMNCHITTEST(Message).YPos;
// Get the window rectangle.
GetWindowRect(Handle,rcWindow);
// Get the frame rectangle, adjusted for the style without a caption.
rcFrame:=Rect(0,0,0,0);
dwStyle:=WS_OVERLAPPEDWINDOW and Not WS_CAPTION;
AdjustWindowRectEx(rcFrame, dwStyle, False, 0);
// Determine if the hit test is for resizing. Default middle (1,1).
uRow:=1;
uCol:=1;
fOnResizeBorder:=False;
// Determine if the point is at the top or bottom of the window.
if (ptMouse.y >= rcWindow.top) and (ptMouse.y < rcWindow.top + FMarInset.cyTopHeight) then
begin
fOnResizeBorder := (ptMouse.y < (rcWindow.top - rcFrame.top));
uRow := 0;
end;
if (ptMouse.y < rcWindow.bottom) and (ptMouse.y >= rcWindow.bottom - FMarInset.cyBottomHeight) then
begin
uRow := 2;
end;
// Determine if the point is at the left or right of the window.
if (ptMouse.x >= rcWindow.left) and (ptMouse.x < rcWindow.left + FMarInset.cxLeftWidth) then
begin
uCol := 0; // left side
end;
if (ptMouse.x < rcWindow.right) and (ptMouse.x >= rcWindow.right - FMarInset.cxRightWidth) then
begin
uCol := 2; // right side
end;
Message.Result:=hitTests[uRow][uCol];
if (uRow=0) and (uCol=1) and Not fOnResizeBorder then
begin
Message.Result:=HTCAPTION;
end;
end;
end;
procedure TfrmBaseDWMLearn.WMPAINT(var Message: TMessage);
var
hThm:HTHEME;
hMemDC:HDC;
bmpinfo:BITMAPINFO;
hBmp:HBITMAP;
hBmpOld:HGDIOBJ;
tmpdttopts:DTTOPTS;
rc:TRect;
hr:HRESULT;
p:Pointer;
hFontOld:HGDIOBJ;
begin
Inherited;
//获取主题句柄
hThm:=OpenThemeData(GetDesktopWindow(),PWideChar('TextStyle'));
//创建DIB
hMemDC := CreateCompatibleDC(Canvas.Handle);
FillChar(bmpinfo,SizeOf(bmpinfo),0);
bmpinfo.bmiHeader.biSize := sizeof(bmpinfo.bmiHeader);
bmpinfo.bmiHeader.biBitCount := 32;
bmpinfo.bmiHeader.biCompression := BI_RGB;
bmpinfo.bmiHeader.biPlanes := 1;
bmpinfo.bmiHeader.biWidth := Width;
bmpinfo.bmiHeader.biHeight := -FMarInset.cyTopHeight;
hBmp := CreateDIBSection(hMemDC, &bmpinfo, DIB_RGB_COLORS, p, 0, 0);
hBmpOld := SelectObject(hMemDC, hBmp);
hFontOld:=SelectObject(hMemDC,Font.Handle);
//绘制选项
FillChar(tmpdttopts,SizeOf(tmpdttopts),0);
tmpdttopts.dwSize := sizeof(DTTOPTS);
tmpdttopts.dwFlags := DTT_GLOWSIZE or DTT_COMPOSITED or DTT_TEXTCOLOR;
//发光的范围大小
tmpdttopts.iGlowSize := 6;
tmpdttopts.crText:=ColorToRGB(Font.Color);
//绘制文本
rc := Rect(10,0,Width,FMarInset.cyTopHeight);
hr := DrawThemeTextEx(hThm,hMemDC, TEXT_BODYTITLE, 0, Caption, -1, DT_LEFT or DT_VCENTER or DT_SINGLELINE , rc, tmpdttopts);
//绘制缓存
if (FAILED(hr)) then Exit;
BitBlt(Canvas.Handle, 10, 0, Width,FMarInset.cyTopHeight, hMemDC, 0, 0, SRCCOPY);
SelectObject(hMemDC, hFontOld);
SelectObject(hMemDC, hBmpOld);
DeleteObject(hBmp);
DeleteDC(hMemDC);
CloseThemeData(hThm);
end;
end.