译:MFC 程序员的 WTL 教程(四)(第二版)

特别注:由于本页内容栏宽度不够,会导致部分内容看不见,请点击这里以获得最佳浏览效果。

链接:上一部分下一部分

第四部分 – 对话框和控件

内容

  • 简介
  • 重温 ATL 对话框
  • 通用控件封装类
  • 使用 AppWizard 创建基于对话框的应用
  • 使用控件封装类
    • ATL 方法 1 – 附着到 CWindow
    • ATL 方法 2 – CContainedWindow
    • ATL 方法 3 – 子类化
    • WTL 方法 1 – DDX_CONTROL
    • WTL 方法 2 – DDX_CONTROL_HANDLE
  • 更多 DDX 的内容
    • DDX 宏
    • 关于 DoDataExchange() 的更多信息
    • 使用 DDX
  • 处理来自控件的通知
    • 在父窗口中处理通知
    • 反射通知
      • 用于处理反射消息的 WTL 宏
  • 拾零
    • 对话框字体
    • _ATL_MIN_CRT
  • 修订历史

第四部分简介

对话框和控件是 MFC 确确实实节省你时间和精力的一个地方。如果没有 MFC 的控件类,你就会被淹没在填充结构以及写下成吨的 SendMessage 调用以管理控件的琐事中。而且 MFC 还提供了对话框数据交换(DDX),可以在控件和变量之间传递数据。WTL 也支持所有这些特性,并且在其通用控件的封装类里还加入了一些改进。在本文中,我们致力于一个基于对话框的应用,它演示了你所使用过的 MFC 特性,以及一些 WTL 在消息处理上的增强。高级 UI 特性以及 WTL 中的新控件将在第五部分里介绍。

重温 ATL 对话框

回忆一下第一部分,ATL 有两个对话框类, CDialogImplCAxDialogImplCAxDialogImpl 用于掌控 ActiveX 控件的对话框。我们在本文中不包括 ActiveX 控件的内容,因而示例代码使用的是 CDialogImpl

创建一个新的对话框类,要做三件事:

  1. 创建对话框资源
  2. 写一个派生于 CDialogImpl 的新类
  3. 创建一个名为 IDD 的 public 成员变量并将其设置为对话框的资源 ID

然后你就可以像在框架窗口中那样添加消息处理器了。WTL 并没有改变此过程,但确实添加了可用于对话框的附加特性。

控件封装类

WTL 有许多的控件封装类,对于它们,你应该感到熟悉,因为 WTL 类通常与其在 MFC 中的对应类使用相同的(或者极其相似的)名字。通常方法的名字也是一致的,因此当你使用 WTL 的封装类时你可以使用 MFC 的文档。不过当你需要跳转到某个类的定义时,F12 键就派不上用场了。

下面是内建控件的封装类:

  • 用户控件: CStaticCButtonCListBoxCComboBoxCEditCScrollBarCDragListBox
  • 通用控件: CImageListCListViewCtrl(MFC 中为 CListCtrl)、 CTreeViewCtrl(MFC 中为 CTreeCtrl)、 CHeaderCtrlCToolBarCtrlCStatusBarCtrlCTabCtrlCToolTipCtrlCTrackBarCtrl(MFC 中为 CSliderCtrl)、 CUpDownCtrl(MFC 中为 CSpinButtonCtrl)、 CProgressBarCtrlCHotKeyCtrlCAnimateCtrlCRichEditCtrlCReBarCtrlCComboBoxExCDateTimePickerCtrlCMonthCalendarCtrlCIPAddressCtrl
  • MFC 中没有的通用控件封装类: CPagerCtrlCFlatScrollBarCLinkCtrl(可点击的超链接,仅在 XP 中可用)

还有一些 WTL 特有的类: CBitmapButtonCCheckListViewCtrl(具有复选框的列表视图)、 CTreeViewCtrlExCTreeItem (两个类一起使用, CTreeItem 封装了 HTREEITEM)、 CHyperLink(可点击的超链接,在所有操作系统上均可用)。

需要注意的是大多数的封装类都是窗口接口类,就像 CWindow 一样。它们封装了 HWND 并提供了围绕消息的封装层(例如, CListBox::GetCurSel() 封装了 LB_GETCURSEL)。因而像 CWindow 一样,创建一个临时的控件封装对象并将之关联到现存的控件上,其代价是很低的。仍然像 CWindow 一样,当控件封装对象析构时控件并不会被销毁,不过 CBitmapButtonCCheckListViewCtrlCHyperLink 例外。

因为本系列面向有经验的 MFC 程序员,我不会在与 MFC 中的对应类相似的封装类的细节上花费太多的时间。不过,我会介绍 WTL 中的新类。 CBitmapButton 与 MFC 中的同名类有很大的不同,而 CHyperLink 则是全新的。

使用 AppWizard 创建基于对话框的应用

启动 VC 并打开 WTL AppWizard。我敢确信你和我一样,对于时钟程序已经厌倦了,所以我们不妨把下一个应用叫做 ControlMania1(译者注:意思是控件狂)。在 AppWizard 的第一页,点击 Dialog Based。我们还需要在制作模态还是非模态对话框之间作一个选择。其差异很重要,我将会在第五部分里介绍,不过现在我们可以拣一个简单一点的来,模态的吧。象下面一样选中 Modal DialogGenerate .CPP Files

 [AppWizard page 1 - 21K]

 [AppWizard page 1 - 25K]

VC 6 的向导的第二页或者 VC 7 的向导的“User Interface Features”标签中,所有的选项仅当主窗口是框架窗口时才有意义,所以它们全部都被禁止掉了。点击 Finish,完成整个向导。

正如你所希望的,AppWizard 对于对话框应用生成的代码是相当简单的。ControlMania1.cpp 有一个 _tWinMain() 函数,下面是其主要部分:

代码首先初始化 COM 并创建一个单线程单元(single-threaded apartment)。这对于要掌控 ActiveX 控件的对话框的是必需的,如果你的应用不使用 COM,你可以安全地把 CoInitialize()CoUninitialize() 调用移除。接下来,调用了 WTL 辅助函数 AtlInitCommonControls(),这只是 InitCommonControlsEx() 的一个封装。全局 _Module 初始化完毕后,显示主对话框(注意,用 DoModal() 创建的 ATL 对话框的确是模态的,不像 MFC,所有的对话框都是非模态的,而 MFC 通过禁止掉对话框的父窗口来模拟模态)。最后, _Module 以及 COM 被逆初始化, DoModal() 的返回值用作应用的退出码。

包围着 CMainDlg 变量的代码块(译者注:即代码中的左右花括号)很重要,因为 CMainDlg 可能有使用了 ATL 和 WTL 特性的成员。而这些成员有可能在其析构函数中也使用了 ATL/WTL 特性。如果没有代码块, CMainDlg 的析构函数(以及成员的析构函数)将会在 _Module.Term()(此函数逆初始化了 ATL/WTL)调用之后运行,并试图使用 ATL/WTL 特性,这就可能导致一个难于诊断的崩溃。(作为一个历史问题,WTL 3 的 AppWizard 生成的代码没有此代码块,我的一些程序因而崩溃了)。

尽管此对话框还相当的赤贫,但你还是可以立即编链并运行它:

CMainDlg 中的代码处理了 WM_INITDIALOGWM_CLOSE 以及全部三个按钮。如果愿意的话浏览一下代码,你应该能理解 CMainDlg 的声明,它的消息映射和它的消息处理器了。

此示例工程将演示如何把变量挂接到控件上。下面是应用的样子,又多了几个控件。你在后续的讨论中可以回来参看此图。

 [Add'l controls - 12K]

由于应用使用了一个列表视图(list view)控件,所以需要改动一下对 AtlInitCommonControls() 的调用。将之改为:

这会比需要的多注册几个类,但它使我们在添加不同类型的控件时省却了记忆 ICC_* 常量之苦。

使用控件封装类

把一个成员变量联系到控件有好几种方法。有的使用了简单的 CWindow 之属(或者其他的窗口接口类,如 CListViewCtrl),另外的使用了 CWindowImpl 的派生类。如果你只是需要一个临时变量,那么使用 CWindow 就相当不错,而如果你需要子类化一个控件并处理发给它的消息,那就会需要 CWindowImpl 了。

ATL 方法 1 – 附着一个 CWindow

最简单的方法是声明一个 CWindow 或者其他的窗口接口类,然后调用其 Attach() 方法。你也可以使用 CWindow 的构造函数或者赋值操作符把变量关联到控件的 HWND

下面的代码演示了把变量关联到列表控件的全部三种方法:

记住, CWindow 析构函数并不销毁窗口,所以在变量离开作用域前并不需要将之与控件脱离。如果愿意,你也可以对成员变量使用此方法 – 你可以在 OnInitDialog() 处理器中关联变量。

ATL 方法 2 – CContainedWindow

CContainedWindow 是使用 CWindowCWindowImpl 的一个折中。它允许你子类化一个控件,并在其父窗口中处理控件的消息。这就允许你把所有的消息处理器置于对话框类里,而不必再为每个控件写独立的 CWindowImpl 类。注意,不要使用 CContainedWindow 来处理 WM_COMMANDWM_NOTIFY 或者其他通知消息,因为这些消息总是发送给控件的父窗口。

至于实际的类, CContainedWindowT,是一个模板类,它接受一个窗口接口类名作为模板参数。一个经过特化的 CContainedWindowT<CWindow> 类像简单的 CWindow 一样地工作,并被 typedef 成了一个更短的名字 CContainedWindow。要使用不同窗口接口类,可以把其名字作为模板参数,例如 CContainedWindowT<CListViewCtrl>

想搞定一个 CContainedWindow,有四件事情要做:

  1. 在对话框中创建一个 CContainedWindowT 成员变量
  2. 将处理器放到对话框消息映射的一个 ALT_MSG_MAP 节中
  3. 在对话框的构造函数里,调用 CContainedWindowT 构造函数并告诉它应该把消息路由到哪一个 ALT_MSG_MAP 节中
  4. OnInitDialog() 函数中,调用 CContainedWindowT::SubclassWindow() 方法把变量关联到控件上

在 ControlMania1 里,我们为 OK 按钮和 Exit 按钮各使用一个 CContainedWindow。对话框将处理发送到每个按钮的 WM_SETCURSOR 消息并改变光标。

现在我们来实践这些步骤。首先,向 CMainDlg中添加 CContainedWindow 成员。

其次,添加 ALT_MSG_MAP 节。OK 按钮将使用第一节,Exit 按钮使用第二节。这意味着发送到 OK 按钮的所有消息都会被路由到 ALT_MSG_MAP(1) 节而发送到 Exit 按钮的所有消息会被路由到 ALT_MSG_MAP(2) 节。

第三,为每个成员调用 CContainedWindow 构造函数并把要使用的 ALT_MSG_MAP 节告诉它。

构造函数的参数是一个 CMessageMap* 和一个 ALT_MSG_MAP 节号。第一个参数通常是 this,表示将使用对话框自己的消息映射,第二个参数告诉对象应该把它的消息发送到哪一个 ALT_MSG_MAP 节。

重要注意事项:如果你在使用 VC 7.0/7.1 以及 WTL 7.0/7.1,而一个 CWindowImpl 或者 CDialogImpl 的派生类又同时做了以下工作,那你就会运行到一个失败的断言上:

    • 消息映射使用了 BEGIN_MSG_MAP 而不是 BEGIN_MSG_MAP_EX
    • 映射里包括一个 ALT_MSG_MAP 节。
    • 某一 CContainedWindowT 变量将消息路由到了该 ALT_MSG_MAP 节。
    • ALT_MSG_MAP 节使用了新的 WTL 消息处理器宏。

详情请参看本文论坛内的此帖。解决方案是使用 BEGIN_MSG_MAP_EX 而不是 BEGIN_MSG_MAP

最后,为每个 CContainedWindow 关联控件。

下面是新的 WM_SETCURSOR 处理器:

如果你要使用 CButton 的特性,你可以把变量声明为:

然后 CButton 的方法就可用了。

当你把光标移动到按钮上的时候,你就可以看到 WM_SETCURSOR 处理器在起作用:

 [OK button cursor - 10K]  [Exit button cursor - 10K]

ATL 方法 3 – 子类化

方法 3 致力于创建一个 CWindowImpl 派生类并使用它来子类化控件。这与方法 2 相似,但是消息处理器在 CWindowImpl 类中而不是在对话框类中。

ControlMania1 使用此方法来子类化主对话框中的 About 按钮。下面是 CButtonImpl 类,派生于 CWindowImpl 并处理了 WM_SETCURSOR

然后在主对话框里声明一个 CButtonImpl 成员变量:

最后在 OnInitDialog() 里子类化按钮。

WTL 方法 1 – DDX_CONTROL

WTL DDX(对话框数据交换)工作起来很像 MFC,而且可以相当轻松地把变量连接到控件上。首先,你需要像在前一个例子中一样,有一个 CWindowImpl 的派生类。这次我们要使用一个新类, CEditImpl,这是因为这个例子里我们要子类化编辑框控件。你还需要在 stdafx.h 中 #include atlddx.h 以得到 DDX 支持代码。

为了向 CMainDlg 添加 DDX 支持,把 CWinDataExchange 加入到继承列表中:

接下来在类中创建一个 DDX 映射,它与 MFC 应用中 ClassWizard 生成的 DoDataExchange() 函数相似。针对不同类型的数据,存在着好多个 DDX_* 宏,我们在这儿要使用的是 DDX_CONTROL,以把变量连接到控件上。这一次,当你在控件上右击时,我们用 CEditImpl 处理 WM_CONTEXTMENU 消息来做一些事情。

最后,在 OnInitDialog() 中,我们调用继承于 CWinDataExchangeDoDataExchange() 函数。在 DoDataExchange() 被第一次调用的时候,它会按需子类化控件。在本例中, DoDataExchange() 会子类化 ID 为 IDC_EDIT 的控件,并把它连接到变量 m_wndEdit 上。

DoDataExchange() 的参数和 MFC UpdateData() 函数的参数具有相同的含义。我们在下一节中讨论其更多细节。

如果你运行 ControlMania1 工程,你会看到所有的这些子类化都在起作用。在编辑框上右击会弹出消息框,在按钮上的光标像在前面显示的一样会改变其形状。

WTL 方法 2 – DDX_CONTROL_HANDLE

WTL 7.1 中添加的一个新特性是 DDX_CONTROL_HANDLE 宏。在 WTL 7.0 里,如果你想对一个单纯的窗口接口类(像 CWindowCListViewCtrl 等)使用 DDX,你是不能使用 DDX_CONTROL 的,因为 DDX_CONTROL 只能和 CWindowImpl 的派生类一起工作。除了需要基类不同之外, DDX_CONTROL_HANDLEDDX_CONTROL 一样地工作。

如果你还在使用 WTL 7.0,你可以使用以下宏定义一个可以和 DDX_CONTROL 一起工作的 CWindowImpl 派生类:

然后你就可以这样写:

这样你就有了一个名为 CListViewCtrl_ddx 的类,它的功能与 CListViewCtrl 一样,但是可以被 DDX_CONTROL 接受。

更多 DDX 的内容

DDX 可以,当然,实际上也确实是作数据交换的。WTL 支持在编辑框和字符串变量间交换字符串数据。它也可以将字符串解析为一个数字,并将该数据在整数型或者浮点型变量间传送。它还支持向/从 int 中传输复选框或者单选框组的状态。

DDX 宏

每个 DDX 宏都会展开为一个真正工作的对 CWinDataExchange 方法的调用。这些宏都有统一的形式: DDX_FOO(controlID, variable)。每个宏接受一种不同的变量类型,不过有的宏,例如 DDX_TEXT,被重载为可以接受多个类型。

DDX_TEXT
向/从编辑框中传送文本数据。变量可以是 CStringBSTRCComBSTR,或者静态分配的字符数组。但用 new 分配的数组不能工作。
DDX_INT
在编辑框和 int 间传输数字数据。
DDX_UINT
在编辑框和 unsigned int 间传输数字数据。
DDX_FLOAT
在编辑框和 float 或者 double 间传输数字数据。
DDX_CHECK
向/从 int 中传输复选框的状态。
DDX_RADIO
向/从 int 中传输单选按钮组的状态。

DDX_CHECK 既可以接受 int 变量也可以接受 bool 变量。int 版本接收/返回 0、1 或者 2,也即相当于 BST_UNCHECKEDBST_ CHECKEDBST_INDETERMINATE。bool 版本是 WTL 7.1 中加入的,可以用于没有中间状态的复选框,此版本在复选框被选中时接收/返回 true,未选中时接收/返回 false。如果复选框发生了出现中间状态的情况,则 DDX_CHECK 返回 false。

WTL 7.1 中还加入了另外一个用于浮点类型的宏:

DDX_FLOAT_P(controlID, variable, precision)
DDX_FLOAT 相似,但是在设置编辑框的文本时,数值被格式化为可以显示由 precision 指示的最高精度。

注意,如果在你的应用中使用了 DDX_FLOAT 或者 DDX_FLOAT_P,你需要在 stdafx.h 中添加如下的一个 #define,并且需要位于所包含的所有 WTL 头文件之前:

这是因为出于代码大小的优化,缺省对于浮点的支持是被禁止掉了的。

关于 DoDataExchange() 的更多信息

你可以象在 MFC 中调用 UpdateData() 一样调用 DoDataExchange() 方法。 DoDataExchange() 的原型为:

参数:

bSaveAndValidate
指示数据传递方向的标志。传入 TRUE 为从控件传递数据到变量,传入 FALSE 为从变量传递数据到控件。注意此参数的缺省值是 FALSE,但 MFC 中 UpdateData() 函数的缺省值是 TRUE。你还可以使用符号 DDX_SAVEDDX_LOAD(相应地被定义为 TRUEFALSE)作为参数,如果你觉得更便于记忆的话。
nCtlID
传入 -1 更新所有控件。否则的话,如果你仅仅是要对某一个控件使用 DDX,则应该传入控件的 ID。

如果控件被成功更新, DoDataExchange() 返回 TRUE,否则返回 FALSE。在对话框中你可以覆盖两个函数以处理错误。一个是 OnDataExchangeError(),任何原因导致的数据交换失败都会调用到它。在 CWinDataExchange 中的缺省实现会发出哔响并将焦点设置到导致错误的控件上。另一个函数是 OnDataValidateError(),不过要到第五部分里介绍 DDV 的时候再讨论它。

使用 DDX

为了使用 DDX,我们向 CMainDlg 中添加两个变量。

OK 按钮的处理器中,我们首先调用 DoDataExchange() 将数据从编辑框中传输到我们刚刚添加的两个变量中,然后再把结果显示到列表控件中。

 [DDX results - 11K]

如果你在编辑框中输入了非数字文本, DDX_INT 就会失败,并调用 OnDataExchangeError()CMainDlg 覆盖了 OnDataExchangeError() 以显示一个消息框:

 [DDX error msg - 18K]

作为最后的 DDX 例子,我们添加一个复选框来演示 DDX_CHECK 的用法:

 [Msg checkbox - 12K]

DDX_CHECK 没有中间状态,所以我们可以为 DDX_CHECK 使用一个 bool 变量。下面是为复选框使用 DDX 而作的改动:

OnOK() 的末尾,我们检查 m_bShowMsg 来看复选框是否被选中。

示例工程中还有使用其他 DDX_* 宏的例子。

处理来自控件的通知

在 WTL 中处理通知与 API 级编程类似。控件以 WM_COMMAND 或者 WM_NOTIFY 消息的形式向其父窗口发送通知,其父窗口负责处理。另外还有几个消息也可以视作为通知,例如 WM_DRAWITEM,该消息在属主绘制控件需要绘制的时候发送。父窗口既可以自己处理通知消息,也可将消息反射回控件。反射像在 MFC 中一样工作 – 控件可以自己处理通知,使得代码具有自包容的形态,易于移到其他的工程中。

在父窗口中处理通知

WM_NOTIFYWM_COMMAND 发送的通知包含有很多信息。 WM_COMMAND 消息的参数中既有发送消息的控件的 ID,又有控件的 HWND,还有通知代码。 WM_NOTIFY 消息除此之外还有一个指向 NMHDR 数据结构的指针。ATL 和 WTL 有若干的消息映射宏用于处理通知。在这儿我只介绍 WTL 的宏,毕竟这是一篇关于 WTL 的文章。注意,所有的这些宏都需要在消息映射中使用 BEGIN_MSG_MAP_EX 宏,并要在 stdafx.h 中 #include atlcrack.h。

消息映射宏

要处理 WM_COMMAND 通知,可以使用 COMMAND_HANDLER_EX 若干宏之一:

COMMAND_HANDLER_EX(id, code, func)
处理来自于一个特定控件的特定代码的通知
COMMAND_ID_HANDLER_EX(id, func)
处理特定代码的所有通知,不管是哪个控件发出的
COMMAND_CODE_HANDLER_EX(code, func)
处理特定控件的所有通知,不管通知代码
COMMAND_RANGE_HANDLER_EX(idFirst, idLast, func)
处理来自于 ID 处于 idFirst 到 idLast 范围之内的控件的所有通知,不管通知代码
COMMAND_RANGE_CODE_HANDLER_EX(idFirst, idLast, code, func)
处理来自于 ID 处于 idFirst 到 idLast 范围之内的控件的特定代码的通知

示例:

  • COMMAND_HANDLER_EX(IDC_USERNAME, EN_CHANGE, OnUsernameChange):处理来自于 ID 为 IDC_USERNAME 的编辑框的 EN_CHANGE 通知
  • COMMAND_ID_HANDLER_EX(IDOK, OnOK):处理来自于 ID 为 IDOK 的控件的所有通知
  • COMMAND_RANGE_CODE_HANDLER_EX(IDC_MONDAY, IDC_FRIDAY, BN_CLICKED, OnDayClicked):处理来自于  ID 介于 IDC_MONDAY 和 IDC_FRIDAY 之间的控件的 BN_CLICKED 通知

也有处理 WM_NOTIFY 消息的宏。它们像上述宏一样工作,但是它们的名字以“ NOTIFY_”开头而不是“ COMMAND_”。

WM_COMMAND 处理器的原型:

WM_COMMAND 通知不使用返回值,所以处理器是返回 voidWM_NOTIFY 处理器的原型:

处理器的返回值用作消息的结果。这与 MFC 不同,MFC 中处理器接受一个 LRESULT* 参数并通过该变量来设置消息的结果。通知代码和发送通知的控件的 HWNDNMHDR 结构中,分别为 codehwndFrom 成员。和在 MFC 中一样,如果随通知发送的结构不是一个简单的 NMHDR,你的处理器应该把 phdr 参数转型为正确的类型。

我们要为 CMainDlg 添加一个通知处理器来处理由列表控件发送的 LVN_ITEMCHANGED,并在对话框中显示当前选中的条目。我们从添加消息映射宏以及消息处理器开始:

下面是消息处理器:

此处理器并未使用 phdr 参数,但出于演示的目的,我还是将之转型为了 NMLISTVIEW*

反射通知

如果你有一个 CWindowImpl 派生类,像先前我们的 CEditImpl 一样实现了一个控件,你就可以在此类中而不是父对话框中处理通知。这称作反射通知,与 MFC 的消息反射类似。所不同的是父窗口和控件都参与了反射,而 MFC 中仅有控件参与。

如果你要将通知反射回控件类,你只需向对话框的消息映射中添加一个宏 REFLECT_NOTIFICATIONS()

这一宏添加了一些代码到消息映射中,可以处理任何先前的宏都没有处理的通知消息。代码将检查消息的 HWND 并将消息发送到该窗口,但是消息的值会被改变为 OLE 控件所使用的值,OLE 控件具有相似的消息反射系统。新的值被称为 OCM_xxx 而不是 WM_xxx,不过在其他方面和其他非反射消息一样处理。

反射的消息共有 18 个:

  • 控件通知: WM_COMMANDWM_NOTIFYWM_PARENTNOTIFY
  • 属主绘制: WM_DRAWITEMWM_MEASUREITEMWM_COMPAREITEMWM_DELETEITEM
  • 列表框键盘消息: WM_VKEYTOITEMWM_CHARTOITEM
  • 其他: WM_HSCROLLWM_VSCROLLWM_CTLCOLOR*

在控件类中,你可以仅为感兴趣的反射消息添加处理器,然后在最后加上 DEFAULT_REFLECTION_HANDLER()DEFAULT_REFLECTION_HANDLER() 确保未处理的消息能正确地路由到 DefWindowProc()。下面是一个简单的属主绘制按钮类,处理了反射的 WM_DRAWITEM

用于处理反射消息的 WTL 宏

我们仅仅看到了一个用于反射消息的 WTL 宏,即 MSG_OCM_DRAWITEM。对于其他的 17 个消息也有对应的 MSG_OCM_* 反射宏。由于 WM_NOTIFYWM_COMMAND 具有需要拆解的参数,所以 WTL 除 MSG_OCM_COMMANDMSG_OCM_NOTIFY 之外还为它们提供了特殊的宏。这些宏像 COMMAND_HANDLER_EXNOTIFY_HANDLER_EX 一样地工作,但是具有 “ REFLECTED_” 前缀。例如,树控件可以有这样的消息映射:

如果你检点一下示例代码中的 ControlMania1 对话框,就会发现有一个像上面一样处理了 TVN_ITEMEXPANDING 的树控件。 CMainDlg 的成员 m_wndTree 通过 DDX 连接到树控件,而且 CMainDlg 反射了通知消息。树的 OnItemExpanding() 处理器看起来是这样的:

如果你运行 ControlMania1 并点击树中的 +/- 按钮,你就可以看到处理器在工作 – 一旦你展开了一个节点,就再也不能折叠回去了。

拾零

对话框字体

如果你和我一样对用户界面吹毛求疵而又正好在使用 Win 2000 或者 XP,你就可能会对对话框为什么使用的是 MS Sans Serif 字体而不是 Tahoma 字体而感到奇怪。其实是因为 VC 6 实在是太古老了,它生成的资源文件在 NT 4 上可以很好地工作,但对于 NT 的后续版本却不能。你可以修正这一问题,不过需要手动编辑资源文件。

你需要对资源文件中存在的每个对话框做三样改动:

  1. 对话框类型:把 DIALOG 改为 DIALOGEX
  2. 窗口风格:添加 DS_SHELLFONT
  3. 对话框字体:把 MS Sans Serif 改为 MS Shell Dlg

不幸的是,如果你又改动并保存(译者注:在集成环境的资源编辑器里)了资源,前两项改动会丢失,你需要再次修改。下面是对话框改动之前的一个示例:

下面是之后的样子:

做完这些改动之后,对话框在新的操作系统上会使用 Tahoma 字体,在旧的操作系统上(在需要时)仍然会使用 MS Sans Serif 字体。

在 VC 7 里,要使用正确的字体你只需要在对话框编辑器里改变一个设置:

 [VC7 dlg editor setting - 8K]

当你把 Use System Font 改为 True 时,编辑器会帮你把字体改成 MS Shell Dlg

_ATL_MIN_CRT

正如在 VC Forum FAQ 中讲到的,ATL 具有一个优化功能,可以使你创建无需链接 C 运行时库(CRT)的应用程序。这一优化通过在预处理选项中添加 _ATL_MIN_CRT 符号来启用。AppWizard 生成的应用在 Release 配置中包含了这一符号。由于我从来没有写过任何一个有价值而又不需要使用 CRT 中任何东西的应用程序,所以我总是去掉这一符号。而且在任何情况下,如果你在 CString 或者 DDX 中使用了浮点特性,你都需要去掉它。

下一步

在第五部分里,会涵盖以下知识,对话框数据验证(DDV)、WTL 中的新控件以及诸如属主绘制(owner draw)和定制绘制(custom draw)这样的高级用户界面特性。

修订历史

2003 年 4 月 27 日:首次发布
2005 年 12 月 20 日:更新,包括了 WTL 7.1 中的改变

链接:上一部分下一部分

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注