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

Vista 主题化的属主绘制和全自定义任务按钮 & 任务对话框

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.96/5 (56投票s)

2007 年 5 月 8 日

公共领域

9分钟阅读

viewsIcon

143383

downloadIcon

3085

再现 Vista 动画任务按钮(命令链接)的类,以及任务对话框布局

引言

我的第一篇文章描述了带主题的自绘和完全自定义的 Vista 风格的推/菜单/图像按钮。本文描述了一组 Vista 风格的任务按钮——或命令链接按钮——以及一个任务对话框基类。在 Vista 中,任务按钮提供淡入淡出过渡和微妙的发光效果。

任务按钮与普通图像按钮有一些区别。首先,当未选中时,它们显示为平面,没有可见边框。其次,如下所示,第一行绘制得特别大以使其真正突出。最后,任务按钮使用的热/默认/普通边框显著不同。

这些任务按钮类与 Windows 98、2000、XP 和 Vista 兼容。它们作为 C++/MFC 控件实现。主题感知的视觉效果源自标准按钮,因此可以在 XP 上利用主题方面。

Screenshot - task.gif

背景

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 提供了两种主要类型的任务对话框。讽刺的是,基本版本不使用任务按钮或命令链接,而更复杂的版本可以使用。常见的方面是双色背景、图像和大标题字体。这是一个基本任务对话框

Screenshot - task2.png

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 对话框,需要一个资源模板。您现在完全控制了布局。以下用于创建演示

Screenshot - task3.png

取消按钮上方的分隔线很重要。您必须为其分配一个资源 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_bgcolorm_bgbrushWM_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

Screenshot - ownerdraw.png

添加完全自定义按钮

有两种方法可以添加完全自定义按钮。第一种方法是如上所述的标准按钮路径,但没有“自绘”样式。

Screenshot - custctrl1.png

第二种方法是在对话框编辑器中添加一个自定义控件,并在属性中指定 CButtonVE2_Task 类名。在您的 WM_INITDIALOG 处理程序中,您需要使用此方法配置完全自定义控件的字体和窗口文本。

Screenshot - custctrl2.png

使用基本任务对话框

基本任务对话框与 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_* 参数,为图标和按钮请求。它解析文本字段,将第一句话作为标题,其余作为任务对话框内容。

Screenshot - msgbox.png

请注意,文本的措辞对任务对话框的呈现方式有很大影响。上面简单的例子运行良好。其他的提示可能需要重新措辞。祝您玩得开心!

版权和许可

本文版权所有 © 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 上无法正确显示。
Vista 主题自绘和完全自定义任务按钮及任务对话框 - CodeProject - 代码之家
© . All rights reserved.