Vista 主题化的属主绘制和全自定义任务按钮 & 任务对话框
再现 Vista 动画任务按钮(命令链接)的类,以及任务对话框布局
引言
我的第一篇文章描述了带主题的自绘和完全自定义的 Vista 风格的推/菜单/图像按钮。本文描述了一组 Vista 风格的任务按钮——或命令链接按钮——以及一个任务对话框基类。在 Vista 中,任务按钮提供淡入淡出过渡和微妙的发光效果。
任务按钮与普通图像按钮有一些区别。首先,当未选中时,它们显示为平面,没有可见边框。其次,如下所示,第一行绘制得特别大以使其真正突出。最后,任务按钮使用的热/默认/普通边框显著不同。
这些任务按钮类与 Windows 98、2000、XP 和 Vista 兼容。它们作为 C++/MFC 控件实现。主题感知的视觉效果源自标准按钮,因此可以在 XP 上利用主题方面。
背景
Vista 引入了任务对话框的概念,它是老式 MessageBox 的改进版本。它具有极大的灵活性,从基本的 TaskDialog
函数到使用 TaskDialogIndirect
的高级实现。这在Michael Dunn 的文章中有所描述。
就我个人而言,一旦某个东西开始需要回调,我就会将代码放到它自己的类中。一旦发生这种情况,就很难抉择:为包装器创建新类还是为自定义对话框创建新类。如果用户不喜欢 TaskDialogIndirect
的结果,并且它避免了向后兼容性问题,那么自定义对话框可以提供更好的布局控制。但是,自定义任务对话框的一些元素更难实现,例如双色背景、大字体和任务按钮本身。但现在不一样了!
其他人正在为较旧的操作系统完全重现 TaskDialogIndirect
。然而,我拒绝了这种方法,因为它太过分了。对于许多应用程序,您会无缘无故地增加数百 KB。我想要一种占用空间小的方法。首先,我制作了一个派生自 CButtonVE_Image
类的任务按钮。其次,我使用了一个任务对话框基类。第三,我引入了一些派生自 CStatic
的类来装饰事物,即文本和图标。
正如我在第一篇文章中一样,我通过使用 VC6 保持与 Win98 的向后兼容性。必须喜欢虚拟机。它还可以与 VS7/2003 或 VS8/2005 正常编译。只需打开 DSW 项目文件。VS8 用户必须事先从 RT_MANIFEST
资源类别中删除条目。David Zhao 的视觉样式类用于避免 Win98/2k 上的 DLL 问题。您将需要一个带有 XP 主题头文件的平台 SDK。Visual Studio VS8/2005 Express 用户和 Windows Server 2003 R2 平台 SDK(常见配置)也可以在进行一些调整后编译本文。打开演示中的 VS2005_Express_MFC_Stubs.zip 以获取详细信息。
Vista 按钮过渡
与我之前的按钮类一样,在 Vista 中,这些自定义任务按钮在状态之间提供平滑过渡。主题 API 支持按钮的五种状态:禁用、正常、热、默认和按下。Vista 以不同的速度在这些状态之间执行过渡:有些快,有些慢。在状态更改时,会初始化一个计时器 tickcount
。这用于计算 alpha 混合因子,以便随着时间合并旧状态和新状态的图像。有关更完整的描述和示例代码,请参阅我的第一篇文章。
基本任务对话框
Vista 提供了两种主要类型的任务对话框。讽刺的是,基本版本不使用任务按钮或命令链接,而更复杂的版本可以使用。常见的方面是双色背景、图像和大标题字体。这是一个基本任务对话框
CTaskDialogVE
原生支持这些,无需任何额外代码。该类支持带阴影的透明图像(位图或图标)、大标题文本、内容文本和一个或多个标准按钮。基本任务对话框是动态创建的;不需要对话框资源。它们会根据需要调整大小以适应不同数量的文本。上面的示例是用这个片段创建的
CTaskDialogVE dlg(
NULL/*parent*/, AfxGetApp()->m_hInstance,
_T("ButtonVE CTaskDialogVE Demo"),
_T("Your viewing the default behavior for CTaskDialogVE"),
_T("Most features of the Vista 'TaskDialog' function are "\
"available. You can also derive from CTaskDialogVE to "\
"customize dialogs.\n\n"\
"Would you like to see a customized dialog with task "\
"buttons?"), TDCBF_YES_BUTTON|TDCBF_NO_BUTTON,
MAKEINTRESOURCE(IDB_BITMAP1));
DWORD result = dlg.DoModal();
从 MessageBox API
升级到 CTaskDialogVE
基本对话框实际上根本不需要太多。不过,我稍后会介绍一种更简单的方法。
自定义任务对话框
对于更精细的 CTaskDialogVE
对话框,需要一个资源模板。您现在完全控制了布局。以下用于创建演示
取消按钮上方的分隔线很重要。您必须为其分配一个资源 ID,然后通过调用 SetHorzLine
配置 CTaskDialogVE
。分隔符控制双色效果的绘制位置并提供参考点。通过这种方式,WM_CTLCOLOR
处理程序知道哪些控件获得哪种背景颜色。双色由 WM_PAINT
处理程序根据分隔符位置提供
CStatic *horz = (CStatic*)GetDlgItem(m_horz_nID);
if (horz)
{
CPaintDC dc(this); // device context for painting
CRect rc, horz_rw;
GetClientRect(&rc);
// Convert separator to dialog client coords...
horz->GetWindowRect(&horz_rw);
ScreenToClient(&horz_rw);
// Draw two-tone background...
rc.bottom = horz_rw.bottom;
dc.FillSolidRect(rc, m_bgcolor);
}
控件的背景颜色由 WM_CTLCOLOR
处理程序管理,同样由分隔符引导
CStatic *horz = (CStatic*)GetDlgItem(m_horz_nID);
if (horz)
{
CRect rw,horz_rw;
pWnd->GetWindowRect(&rw);
horz->GetWindowRect(&horz_rw);
if (rw.top < horz_rw.top)
{
// Set text background mode so bgbrush color visible...
pDC->SetBkMode(TRANSPARENT);
hbr = m_bgbrush;
}
}
变量 m_bgcolor
和 m_bgbrush
由 WM_SYSCOLORCHANGE
处理程序维护。现在双色对话框简单多了。
间接对话框模板
对于那些感兴趣的人,这里有一个关于间接对话框模板的简短介绍。基本的 CTaskDialogVE
任务对话框像所有对话框一样需要一个布局模板。模板是动态创建的。它们由几个块组成,首先是 DLGTEMPLATE 结构,然后是几个可变大小的字段,用于菜单、类、标题和默认字体。最后,零个或多个 DLGITEMTEMPLATE 结构用于对话框控件。一些包装器简化了事情
AllocTemplateSpace
- 向连续对话框模板添加一块空间。AddTemplateWord
- 添加一个 16 位字。AddTemplateString
- 添加一个 Unicode 字符串。AddTemplateItem
- 添加一个对话框控件。
要创建基本任务对话框模板,首先我们添加标题
DLGTEMPLATE *pDT = (DLGTEMPLATE*)AllocTemplateSpace(sizeof(DLGTEMPLATE));
pDT->style = WS_VISIBLE | WS_CAPTION | WS_SYSMENU | DS_MODALFRAME |
DS_SETFONT;
pDT->dwExtendedStyle = WS_EX_DLGMODALFRAME;
pDT->cdit = 0; // inc'd by AddTemplateItem
pDT->x = 0;
pDT->y = 0;
pDT->cx = TASKVE_DLGWIDTH;
pDT->cy = TASKVE_DLGHEIGHT;
AddTemplateWord(0); // menu (none)
AddTemplateWord(0); // class (default)
AddTemplateString(pszWindowTitle); // title
AddTemplateWord(8); // font point size
AddTemplateString("MS Sans Serif"); // font name
这指定了窗口样式、位置、大小、菜单、注册的窗口类、标题和字体。接下来是标准按钮
for (i=0; btnlist[i].mask>0; i++)
if (dwCommonButtons & btnlist[i].mask)
{
AddTemplateItem(
TASKVE_ATOM_BUTTON,
BS_PUSHBUTTON|WS_TABSTOP|WS_VISIBLE, 0,
xofs, yofs, TASKVE_BTNWIDTH, TASKVE_BTNHEIGHT,
btnlist[i].nID);
xofs += (TASKVE_BTNWIDTH+TASKVE_BTNSPACEX);
AddTemplateString(btnlist[i].label); // item title
AddTemplateWord(0);
}
btnlist
数组包含配置掩码、按钮文本标签和资源 ID。xofs
坐标随着每个按钮的添加而更新。控件添加的顺序与 Tab 键顺序相对应。接下来,添加水平分隔线、图标、标题和内容文本。这里的步骤被省略了,因为它变得重复。最后,使用我们的自定义模板调用 CDialog 的 InitModalIndirect
InitModalIndirect((LPCDLGTEMPLATE)m_dlgtemplate, pParentWnd);
启动 DoModel
,您就拥有了一个对话框!几乎不费力,是吧?
使用代码
要评估 Vista Effects 任务按钮,请将任何 CButton
的按钮实例替换或子类化为自绘 CButtonVE_Task
。只需将源文件添加到您的项目,添加一个图像,您就可以开始使用了。提供了以下类
CButtonVE_Task
- 自绘任务按钮。最适合大多数情况。CButtonVE2_Task
- 完全自定义任务按钮。适用于注入其他窗口。CTaskDialogVE
- 任务对话框基类(基本或自定义)。CStaticTextVE
- 派生自 CStatic 的类,用于绘制大字体。CStaticImageVE
- 派生自 CStatic 的类,用于绘制带透明度和阴影的位图/图标。
派生自 CButtonVE_Image
的任务按钮类提供以下函数
SetOwner
- 指定接收按钮点击和菜单按钮命令的窗口。默认为父级。SetContentHorz
- 指定按钮图像/文本内容的水平对齐方式。也可以使用 ModifyStyle。SetContentVert
- 指定按钮图像/文本内容的垂直对齐方式。也可以使用 ModifyStyle。SetContentMargin
- 指定按钮边框和内容之间的间距。SetBackgroundColor
- 指定背景颜色。SetImagePosition
- 指定图像相对于文本的位置(左、右、上或下)。SetImageSpacing
- 指定图像和文本之间的间距。SetImageShadow
- 控制图像下方是否显示阴影。SetTransparentColor
- 指定位图背景颜色。默认为左上角像素。SetHotImage
- 指定按钮处于热状态时显示的位图或图标。SetDisabledImage
- 指定按钮禁用时显示的位图或图标。默认情况下,会生成源图像的阴影版本。SetBigFontScale
- 控制大文本的大小。默认为正常大小的 1.6 倍。
CStaticTextVE
类提供
SetFontScale
- 指定文本大小。默认为正常大小的 1 倍。SetFontColor
- 指定前景色。覆盖默认值。SetBackgroundColor
- 指定背景颜色。覆盖默认值。
CStaticImageVE
类提供
SetTransparentColor
- 指定透明颜色。默认为左上角像素。SetBackgroundColor
- 指定背景颜色。覆盖默认值。SetImageShadow
- 控制图像下方是否显示高斯模糊的阴影
添加自绘按钮
在对话框编辑器中添加一个标准按钮,设置“owner draw”(自绘)和“multi-line”(多行)样式。如果要多行文本,例如一行大,一行小,则需要多行样式。添加一个控件类型成员变量,对其进行子类化,并将 CButton
头文件实例替换为 CButtonVE_Task
。
添加完全自定义按钮
有两种方法可以添加完全自定义按钮。第一种方法是如上所述的标准按钮路径,但没有“自绘”样式。
第二种方法是在对话框编辑器中添加一个自定义控件,并在属性中指定 CButtonVE2_Task
类名。在您的 WM_INITDIALOG
处理程序中,您需要使用此方法配置完全自定义控件的字体和窗口文本。
使用基本任务对话框
基本任务对话框与 Vista 的 TaskDialog
函数并行。但是,字符串不需要 Unicode 参数。所有字符串都支持 MAKEINTRESOURCE
作为参数,因此它们可以选择基于资源。hInstance
参数用于查找这些资源,即如果存储在 DLL 中。NULL 表示仅查找 EXE 资源。如果窗口标题为 NULL,则使用程序名代替。
CTaskDialogVE(
CWnd *pParentWnd,
HINSTANCE hInstance,
LPCTSTR pszWindowTitle,
LPCTSTR pszMainInstruction,
LPCTSTR pszContent,
DWORD dwCommonButtons=TDCBF_OK_BUTTON,
LPCTSTR pszImage=NULL)
dwCommonButtons
可以是以下任意组合
TDCBF_OK_BUTTON
TDCBF_YES_BUTTON
TDCBF_NO_BUTTON
TDCBF_CANCEL_BUTTON
TDCBF_RETRY_BUTTON
TDCBF_CLOSE_BUTTON
可选的图像参数是位图或图标的 MAKEINTRESOURCE。以下标准图标也可以按原样使用
TD_WARNING_ICON
TD_ERROR_ICON
TD_INFORMATION_ICON
TD_QUESTION_ICON
从 CTaskDialogVE 派生自定义对话框
创建一个新的 CDialog
类,并将基类更改为 CTaskDialogVE
。添加一个 WM_INITDIALOG
处理程序来配置事物。演示使用此方法
// Tell CTaskDialogVE about seperator...
SetHorzLine(IDC_STATIC1);
// Setup big font for headline...
m_info.SetFontScale(1.6);
m_info2.SetFontScale(1.2);
// Setup owner-drawn task button...
HBITMAP hTask = (HBITMAP)(LoadImage(
AfxGetApp()->m_hInstance,
MAKEINTRESOURCE(IDB_TASK),
IMAGE_BITMAP, 0, 0, LR_DEFAULTCOLOR));
HBITMAP hTaskHot = (HBITMAP)(LoadImage(
AfxGetApp()->m_hInstance,
MAKEINTRESOURCE(IDB_TASK_HOT),
IMAGE_BITMAP, 0, 0, LR_DEFAULTCOLOR));
m_buttonvet.ModifyStyle(0,BS_BITMAP);
m_buttonvet.SetImage(hTask);
m_buttonvet.SetHotImage(hTaskHot);
// Setup custom-drawn task button...
m_buttonvet2.ModifyStyle(0,BS_BITMAP);
m_buttonvet2.SetImage(hTask);
m_buttonvet2.SetHotImage(hTaskHot);
首先,使用 SetHorzLine
配置分隔符,从而实现双色背景。接下来,我们为对话框标题文本指定字体大小。所示的比例近似于 Vista,在 Win98/2k/XP 上也很好看。最后,将正常和热图像添加到两个按钮。所有控件都已子类化,以简化设置。其余的由您决定。添加事件处理程序以响应按钮。演示有一个复选框,所以也在那里添加了一个处理程序。
其他有用的功能
正如您所见,将 CTaskDialogVE
用于基本对话框是简单的。但是,如果您有一个合适的 MessageBox 实例,将其升级到 CTaskDialogVE
是非常容易的。只需添加“VE:”
MessageBoxVE(
_T("The game is over. Would you like to play again?"),
_T("ButtonVE_Task_demo"),
MB_YESNO|MB_ICONQUESTION);
全局函数 MessageBoxVE
自动执行简单的转换。它转换 nType
字段,即 MB_*
参数,为图标和按钮请求。它解析文本字段,将第一句话作为标题,其余作为任务对话框内容。
请注意,文本的措辞对任务对话框的呈现方式有很大影响。上面简单的例子运行良好。其他的提示可能需要重新措辞。祝您玩得开心!
版权和许可
本文版权所有 © 2007 by Ian E Davis。本文随附的演示代码和源代码特此发布到公共领域。
历史
- 2007 年 5 月 7 日 - 首次发布。
- 2007 年 5 月 24 日 - 修复了
MessageBoxVE
在给定单句文本时解析问题。CTaskDialogVE
现在查询字符串资源以获取按钮文本覆盖,即 ID 1 的字符串资源替换“OK”标签,以便于本地化。修复了TD_*
图标的定义,使其与 Vista 平台 SDK 兼容。 - 2007 年 7 月 17 日 - 一些小的更改。修复了错误情况下的内存分配问题,并升级了
ButtonVE_Helper
中的 AlphaBlt 代码,以动态链接 AlphaBlend API。感谢 bmallabon 和 blindd0t!代码现在允许在 Win95 和 NT4.0 上运行。还将箭头位图更改为 24 位,因为 16 位图像在 NT4.0 上无法正确显示。