ATL窗口,第一部分
--------------------------------------------------------------------------------
这篇文章由Andrew Whitechapel所写,由孙凯翻译。
绪论
ATL窗口类并不难学,但其与MFC非常不同,很多开发者便说我喜欢丰富的MFC支持,为什么要花心思学ATL窗口呢?唔,MFC有着很大的尺寸和高的性能消费,强迫你用那套应用程序框架体系工作 -- 这些天你又怀疑文档是否继续有效 - 基于文档模板的连载机制。使用ATL窗口的一个大大的优点,当然是非常容易整合COM支持。如果你想更快的、轻量级的窗口和自由选择你的应用程序体系和持续协议的灵活性,你不能不选择ATL。
在这篇文章中,我会提供ATL窗口类介绍和一个简单的基于ATL框架/视图应用程序的食谱指南 -- 你会看到居然可以十分容易的实现象MFC一样的前端功能。ATL窗口的学习曲线比起MFC来更短更平滑,因为ATL更小。
虽然ATL最初是为支持COM而设计的,它也包含了一系列用于构建窗口的类。你可以用这些类构建有窗口的COM对象,如ActiveX控制,创建Windows应用程序时却可以不必包含COM。最重要的ATL窗口类列于下表中:
CWindow 一个小的包装了用于操作窗口的Win32 API的类,包含一个窗口句柄和一个将CWindow对象转换为HWND的HWND操作符。因而你可以将一个CWindow对象传递给任何一个需要窗口句柄参数的函数。
CWindowImpl 你可以用CWindowImpl创建基于一个新的窗口类的、已存在的类的超类或子类的一个窗口。
CContainedWindow 一个实现了发送消息到其它类的消息映射的窗口,允许你把消息处理集中到一个类中的类。
CAxWindow 允许你实现一个可包含ActiveX控制的窗口,它有一些创建控制或绑定一个存在的控制的函数。
CDialogImpl 用于实现一个模态或非模态的对话框的基类。CDialogImpl提供一个包含发送消息到你的派生类的默认消息映射的对话框过程。但它不支持ActiveX控制。
CSimpleDialog 依靠对话框资源ID实现模态对话框。CSimpleDialog有一个预先确定的能够处理IDOK和IDCANCEL命令的消息映射。
CAxDialogImpl 和CDialogImpl一样,也用于实现一个模态或非模态的对话框的基类。同样提供一个包含发送消息到你的派生类的默认消息映射的对话框过程。且它支持ActiveX控制。ATL Object Wizard支持向你的工程添加一个CAxDialogImpl的派生类,并产生相关的对话框资源。
CWndClassInfo 管理一个新的窗口类(Windows的WNDCLASS)的信息 -- 其实就是封装了WNDCLASSEX。
CWinTraits和CWinTraintsOR 封装了一个ATL窗口对象的窗口特征(WS_ 窗口风格)。
消息映射
人们不愿花时间学习ATL窗口类的一个主要原因是觉得ATL的消息映射机制有些奇怪。是的,它与MFC的不同,但是在你第一次看到MFC消息映射宏时,你是否理解它呢?事实上,ATL消息映射机制是非常容易掌握的。允许你在CWindowImpl的派生类中处理窗口消息,因为ATL类继承自一个抽象的基类CMessageMap。CMessageMap声明了一个纯虚函数ProcessWindowMessage,它是通过宏BEGIN_MSG_MAP和END_MSG_MAP在你的CWindowImpl派生类中实现。
另外,与MFC的消息处理很相似,ATL的消息处理函数只是多一个BOOL&的变量(译者说明:在ATL源代码中,ProcessWindowMessage函数定义了变量BOOL bHandled = TRUE,这里即指它)。这是变量指出一个消息是否被处理,它默认为TRUE。一个消息处理函数可以将这个变量设置为FALSE,以指示消息未被处理。在这种情况下,ATL会进入消息映射中更深一层的消息处理函数。通过将这个变量设置为FALSE,你可以首先执行一些操作以响应消息,然后允许默认处理或另外的处理函数完成消息响应。
下表中列出了三组消息映射宏:
.所有消息的消息处理者
.WM_COMMAND消息的命令处理者
.WM_NOTIFY消息的通知处理者
MESSAGE_HANDLER 将一个窗口消息映射到一个处理函数。
COMMAND_HANDLER 将一个WM_COMMAND消息映射到一个基于通知代码和菜单项、控制或快捷键的ID的处理函数
COMMAND_ID_HANDLER 将一个WM_COMMAND消息映射到一个基于菜单项、控制或快捷键的ID的处理函数
COMMAND_CODE_HANDLER 将一个WM_COMMAND消息映射到一个基于通知代码的处理函数
NOTIFY_HANDLER 将一个WM_NOTIFY消息映射到一个基于通知代码和控制标识的处理函数
NOTIFY_ID_HANDLER 将一个WM_NOTIFY消息映射到一个基于控制标识的处理函数
NOTIFY_CODE_HANDLER 将一个WM_NOTIFY消息映射到一个基于通知代码的处理函数
例如,如果你有一个带有子控制的窗体的ATL对话框类,你会有一个像下面这样的消息映射:
BEGIN_MSG_MAP(CMyDialog)
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
COMMAND_HANDLER(IDC_EDIT1, EN_CHANGE, OnChangeEdit1)
COMMAND_ID_HANDLER(IDOK, OnOK)
COMMAND_CODE_HANDLER(EN_ERRSPACE, OnErrEdits)
NOTIFY_HANDLER(IDC_LIST1, NM_CLICK, OnClickList1)
NOTIFY_ID_HANDLER(IDC_LIST2, OnSomethingList2)
NOTIFY_CODE_HANDLER(NM_DBLCLK, OnDblClkLists)
END_MSG_MAP()
MFC体系允许使用两种不同的消息传送方案:向基类纵向传送Windows消息,和在文档/视图类中横向传送命令消息。第一种方案不太适合ATL,因为ATL有着比较松散的用类模板实现的层次结构。第二种方案也不适合,因为ATL没有像MFC文档/视图体系相当的严格的运用(体系)。
为了在一个消息映射中处理不同窗口发送的消息,ATL提供两种方法:预备的消息映射和链接的消息映射。一个父窗口也可以处理一个子控制发送给它的消息,一个回送给它的作为反射消息的消息。
预备的消息映射
预备的消息映射被主要设计成为ALT类CContainedWindow所使用的。这个类发送所有它的消息到其它类的消息映射中。
CContainedWindow的构造函数需要得到一个包含被使用的消息映射的类的地址,和消息映射内部的预备的消息映射的ID(或默认的消息映射值0)。
例如,当你创建一个基于Windows控制的ATL控制,ATL Object Wizard会为控制产生一个类,它包含一个代表子控制的CContainedWindow类型的成员。实际上,这个包含窗口超类,特殊的Windows控制,你已经决定让它基于你的ActiveX控制:
class ATL_NO_VTABLE CMyButton :
public CComObjectRootEx,
public CComCoClass,
public CComControl,
//...
{
public:
CContainedWindow m_ctlButton;
CMyButton() : m_ctlButton(_T("Button"), this, 1) { }
BEGIN_MSG_MAP(CMyButton)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus)
CHAIN_MSG_MAP(CComControl) file://链接消息映射
ALT_MSG_MAP(1) file://预备消息映射
END_MSG_MAP()
//...
注意“Button”是WNDCLASS风格,不是标题。包容类的this指针被作为第2个参数,值1被传递给ContainedWindow的构造函数用以标识预备消息映射。
如果你想处理控制的WM_LBUTTONDOWN消息,你需要更新消息映射如下。这样,这个消息会被发送到父窗口听消息映射中,然后再发送给消息映射的预备部分。
BEGIN_MSG_MAP(CMyButton)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus)
CHAIN_MSG_MAP(CComControl)
ALT_MSG_MAP(1)
MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown) file://看这里
END_MSG_MAP()
因而,预备的消息映射是一种简单的策略,让你合并消息处理,在单个BEGIN_MSG_MAP/END_MSG_MAP宏块中。
链接的消息映射
链接的消息映射通过其它类或对象的消息映射发送消息。ATL提供几个映射链接宏:
CHAIN_MSG_MAP(theBaseClass) 传送消息到一个基类的默认消息映射。
CHAIN_MSG_MAP_ALT(theBaseClass, mapID) 传送消息到一个基类的预备的消息映射。
CHAIN_MSG_MAP_MEMBER(theMember) 传送消息到指定的数据成员的默认消息映射(派生于CMessageMap)。
CHAIN_MSG_MAP_ALT_MEMBER(theMember, mapID) 传送消息到指定数据成员的预备消息映射。
例如,当你创建一个基于Windows控制的ATL控制,ATL Object Wizard产生代码如下:
BEGIN_MSG_MAP(CMyButton)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus)
CHAIN_MSG_MAP(CComControl) file://看这里
ALT_MSG_MAP(1)
END_MSG_MAP()
这个说明WM_CREATE和WM_SETFOCUS消息被这个类处理,但其它消息被传送到基类CComControl<>中。同样,如果WM_CREATE和WM_SETFOCUS的处理者将变量bHandled设置为false,这些消息仍会被传送到基类作更进一步的处理。
传送消息到一个数据成员,你需要更新映射如下:
BEGIN_MSG_MAP(CMyButton)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus)
CHAIN_MSG_MAP(CComControl)
ALT_MSG_MAP(1)
CHAIN_MSG_MAP_MEMBER(m_ctlButton) file://看这里
END_MSG_MAP()
这里假定m_ctlButton是包容窗口的一个成员,而且是一个CContainedWindow的派生类的实例,它可以为你感兴趣的消息获得一个消息映射的入口:
class CMyButtonControl : public CContainedWindow
{
//...
BEGIN_MSG_MAP(CMyButtonControl)
MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown)
END_MSG_MAP()
因此,链接消息映射允许你形成消息传送链从一个映射到另外的映射--MFC也采用了类似的消息传送方案。
反射消息
一个父窗口可以处理发送给它的窗口消息,而子控制则依靠一个回送的称为反射消息的消息--这是在原始消息上加上一个标志。当子控制得到这些消息,它能识别出它们是反射自包容窗口并适当地处理它们。例如,一个子控制想处理WM_DRAWITEM消息。为了做这个工作,REFLECT_NOTIFICATIONS必须在父窗口的消息映射中被给出:
BEGIN_MSG_MAP(CMyDialog)
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
COMMAND_ID_HANDLER(IDOK, OnOk)
NOTIFY_HANDLER(IDC_EDIT1, EN_CHANGE, OnChangeEdit1)
REFLECT_NOTIFICATIONS() //看这里
END_MSG_MAP()
REFLECT_NOTIFICATIONS宏展开来便是一个对CWindowImpl::ReflectNotifications的调用,该函数具有这个信号:
template
LRESULT CWindowImplRoot< TBase >::ReflectNotifications(UINT uMsg,
WPARAM wParam, LPARAM lParam, BOOL& bHandled);
这个函数取出子控制的窗口句柄,发送的消息的wParam或lParam(决定于消息类型),然后用如下的方式发送消息:
::SendMessage(hWndChild, OCM_ _BASE + uMsg, wParam, lParam);
子窗口使用标准的MESSAGE_HANDLER宏处理反射消息,预定义的反射消息的ID定义在olectrl.h中:
BEGIN_MSG_MAP(CMyContainedControl)
MESSAGE_HANDLER(OCM_DRAWITEM, OnDrawItem)
DEFAULT_REFLECTION_HANDLER()
END_MSG_MAP()
OCM_DRAWITEM, 在这个例子中看到的符号,其定义如下:
#define OCM_ _BASE (WM_USER+0x1c00)
#define OCM_COMMAND (OCM_ _BASE + WM_COMMAND)
//...
#define OCM_DRAWITEM (OCM_ _BASE + WM_DRAWITEM)
DEFAULT_REFLECTION_HANDLER宏将反射消息还原在原始消息,并传递给DefWindowProc。
第1步:ATL Window App
这是一个非常简单的练习,设计来示范使用ATL窗口类创建一个简单的应用程序是多么的容易。这个我很早就做好了:
ATL COM AppWizard是提供给COM对象服务的。如果你想创建一个非COM应用程序,ATL COM AppWizard产生的代码有许多你可以不要。因此,创建一个ATL应用程序,你有两种选择:
创建一个ATL COM AppWizard的EXE服务器,并接受常用选择(可能有些多余)。
创建一个Win32 Application,并手工加入ATL支持。
只有这样,你才可以看到什么是必需的,我们会故意的消除一些向导产生的代码,并随着第2条路线完成我们的应用程序的最小的轻量级的框架。
创建一个新的Win32 Application,选择简单的选项,这样我们可以得到stdafx.h和stdafx.cpp('afx',当然是MFC的残留,但名字是不相关的-什么是最重要的,是PCH)。
ATL支持
修改stdafx.h加入ATL需要的头文件和一个对CComModule类型的全局对象的外部引用:
#include
extern CComModule _Module;
#include
#include
加入一个ATL对象映射在主要的CPP文件中 - 它是空的,但为了CComModule对象我们需要它。声明全局的CComModule对象:
CComModule _Module;
BEGIN_OBJECT_MAP(ObjectMap)
END_OBJECT_MAP()
加入一个与这个工程同名的IDL文件。
library SomethingOrOther
{
};
窗口
因为没有向导支持创建一个派生于ATL窗口类的新类,所以我们使用新建类对话框加入一个普通类,然后手工修改。在ClassView的根节点上单击鼠标右键,行动新建类。选择类类型:普通类,名字CMyWindow,基类是CWindowImpl<CMyWindow>。你会不满意ATL窗口类,因为向导为你的新类产生一个新的头文件但没有#include stdafx.h(其中包含atlwin.h)且不知道这些类。自己补上#include stdafx.h。
在你的新窗口类中,声明一个消息映射并保存文件:
BEGIN_MSG_MAP(CMyWindow)
END_MSG_MAP()
在ClassView中,在你的新的窗口类上单击鼠标右键获得WM_DESTROY的句柄。加入邮递一个退出消息的代码:
LRESULT OnDestroy(UINT uMsg, WPARAM wParam,
LPARAM lParam, BOOL& bHandled)
{
PostQuitMessage(0);
return 0;
}
同样的,我们可以获得WM_PAINT的句柄,并编写打印一个字符串的代码。你可以直接看到 - ATL常用的方法 - 我们直接使用API编码。ATL没有包装HDC的类,WTL则实现了它:
LRESULT OnPaint(UINT uMsg, WPARAM wParam,
LPARAM lParam, BOOL& bHandled)
{
PAINTSTRUCT ps;
HDC hDC = GetDC();
BeginPaint(&ps);
TextOut(hDC, 0, 0, _T("Hello world"), 11);
EndPaint(&ps);
return 0;
}
WinMain
在WinMain函数的开始和结束处,书写常用CComModule::Init和Term调用:
_Module.Init(NULL, hInstance);
// ...
_Module.Term();
在Init和Term之间,声明一个你的窗口类的实例,并调用Create函数初始化它(不要忘了用#include包含它的头文件)。然后设置一个消息循环:
CMyWindow wnd;
wnd.Create(NULL, CWindow::rcDefault, _T("Hello"),
WS_OVERLAPPEDWINDOW|WS_VISIBLE);
MSG msg;
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
哈,是不是很容易?
第2步:ATL框架/视图应用程序
在这个工程中我们会创建一个像MFC SDI框架/视图结构的范例,但我们使用的是ATL窗口类。这个程序的第一个版本看起来跟上一个例子SimpleWin差不多,但我们会加入视图、菜单和对话框。
创建一个新的“Simple”Win32 Application。在编码之间,先修改stdafx.h以加入ATL所需的头文件和全局的CComModule对象的外部引用。在主CPP文件中加入ATL对象映射并声明一个全局的CComModule对象。还要加入一个具有library块框架的IDL文件。
主框架窗口
在ClassView的根节点上单击鼠标右键,选择New Class。使用类类型(Class Type):普通类(Generic Class),命名为CMainFrame,设置基类为CWindowImpl<CMainFrame, CWindow, CFrameWinTraits>。注意,CFrameWinTraits是用typedef定义的(在atlwin.h中)为主框架窗口类型优化的CWinTraits。在这个新的CMainFrame类中,声明WNDCLASS结构名和一个消息映射:
DECLARE_WND_CLASS(_T("MyFrame"))
BEGIN_MSG_MAP(CMainFrame)
END_MSG_MAP()
我们能过继承得到一个函数OnFinalMessage - ATL中少数几个虚函数之一 - 当收到WM_NCDESTROY消息时它会被调用。我们需要重载这个函数以发送一个退出消息:
void OnFinalMessage(HWND /*hWnd*/)
{
::PostQuitMessage(0);
}
现在加入一些代码到WinMain中。在其开始和结束处加入常用的CComModule的初始化/终止函数:
_Module.Init(NULL, hInstance, NULL);
_Module.Term();
用#include包含主框架窗口(mainframe)的头文件,在Init和Term之间声明一个它的实例,也可声明框架的另外的实例,调用Create初始化它。然后是一个消息循环:
CMainFrame mf;
mf.Create(GetDesktopWindow(), CWindow::rcDefault,
_T("My App"), 0, 0, 0);
mf.ShowWindow(SW_SHOWNORMAL);
MSG msg;
while (GetMessage(&msg, 0, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
编译并运行。
视图窗口
现在我们将加入一个视图类。在ClassView中单击鼠标右键创建另一个新的类。同样,也是生成一个普通类。命名为CViewWin,派生于CWindowImpl<CViewWin, CWindow, CWinTraits>。记住,没有用typedef预先的为视图优化的CWinTraits。
用#include包含stdafx.h并声明WNDCLASS和消息映射。然后加入一个CViewWin的实例作为CMainFrame的成员,在CMainFrame类中处理WM_CREATE来创建视图:
LRESULT OnCreate(UINT uMsg, WPARAM wParam,
LPARAM lParam, BOOL& bHandled)
{
m_wndView.Create(m_hWnd, CWindow::rcDefault,
_T("MyView"), 0, 0, 0);
return 0;
}
我们还要在框架中处理WM_SIZE以确定视图的大小。再次编译运行:
LRESULT OnSize(UINT uMsg, WPARAM wParam,
LPARAM lParam, BOOL& bHandled)
{
RECT r;
GetClientRect(&r);
m_wndView.SetWindowPos(NULL, &r,
SWP_NOZORDER | SWP_NOACTIVATE );
return 0;
}
用户界面
现在,我们将处理WM_LBUTTONDOWN、WM_MOUSEMOVE和WM_LBUTTONUP消息以提供非常简单的可让用户用鼠标画线的乱画程序版本。是的,我知道,但它提供了非常易于掌握的手段来学习UI响应和消息处理:
首先,添加两个POINT类型的数据成员到视图类中,在构造函数中用(-1,-1)来初始化它们。我们需要保留这些画线的开始和结束点的轨迹 -- (-1,-1)当然是鼠标消息中所没有的坐标值:
m_startPoint.x = m_startPoint.y = -1;
m_endPoint.x = m_endPoint.y = -1;
接下来,通过单击鼠标右键取得三个鼠标消息的句柄。与MFC中CWnd类的鼠标消息处理函数不同,ATL并不声明lParam为一个CPoint对象(也不是POINT),所以你必须自己从中分解鼠标坐标。首先,OnLButtonDown将鼠标坐标记录成画线的起始点:
LRESULT OnLButtonDown(UINT uMsg, WPARAM wParam,
LPARAM lParam, BOOL& bHandled)
{
m_startPoint.x = LOWORD(lParam);
m_startPoint.y = HIWORD(lParam);
return 0;
}
在OnLButtonUp中,将起始点复位为-1:
LRESULT OnLButtonUP(UINT uMsg, WPARAM wParam,
LPARAM lParam, BOOL& bHandled)
{
m_startPoint.x = m_startPoint.y = -1;
return 0;
}
OnMouseMove函数稍稍多一些工作。首先,设置画线的结束点为所取得的鼠标坐标值。然后获得一个DC,并调用MoveToEx和LineTo来画线。最后,更新起始点为当前的结束点,这样下一条线就可以从当前结束点开始:
LRESULT OnMouseMove(UINT uMsg, WPARAM wParam,
LPARAM lParam, BOOL& bHandled)
{
m_endPoint.x = LOWORD(lParam);
m_endPoint.y = HIWORD(lParam);
HDC hdc = GetDC();
if (m_startPoint.x != -1 )
{
MoveToEx(hdc, m_startPoint.x, m_startPoint.y, NULL);
LineTo(hdc, m_endPoint.x, m_endPoint.y);
m_startPoint.x = m_endPoint.x;
m_startPoint.y = m_endPoint.y;
}
return 0;
}
编译并运行。你也许会问为什么在ATL中没有消息解密?唔,当然,这是WTL要作的事...
第3步:ATL菜单
继续框架/视图工程。我们会加入一个简单的菜单,给用户选择画笔颜色的机会:
继续这个工程。首先,在视类中加入一个COLORREF类型的公有成员变量,命名为m_color。在视类的构造函数中将它初始化为黑色的。然后,在OnMouseMove函数中用它创建画笔并选入DC中,操作如下。和往常一样,在使用完后,要将原来的画笔选回DC中,以便释放GDI资源:
HPEN hp = CreatePen(PS_SOLID, 2, m_color);
HPEN op = (HPEN)SelectObject(hdc, hp);
插入一个菜单资源。加入顶级标题:Color和三个菜单项:red、green和blue。
在WinMain中,用#include包含资源头文件。并在创建主框架窗口之间,装入菜单资源,然后将菜单资源的句柄作为参数来调用主框架窗口的Create函数:
HMENU hMenu = LoadMenu(_Module.GetResourceInstance(),
MAKEINTRESOURCE(IDR_MENU1));
mf.Create(GetDesktopWindow(), CWindow::rcDefault,
_T("My App"), 0, 0, (UINT)hMenu);
为了在主框架窗口中处理菜单项我们会加入命令处理函数,所以需要用#include将资源头文件包含进来,然后手工修改消息映射以加入三个新的条目:
BEGIN_MSG_MAP(CMainFrame)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
MESSAGE_HANDLER(WM_SIZE, OnSize)
COMMAND_ID_HANDLER(ID_COLOR_RED, OnColorRed)
COMMAND_ID_HANDLER(ID_COLOR_GREEN, OnColorGreen)
COMMAND_ID_HANDLER(ID_COLOR_BLUE, OnColorBlue)
END_MSG_MAP()
设置三个处理函数作如下的工作,再次运行它:
LRESULT OnColorRed(WORD wNotifyCode, WORD wID,
HWND hWndCtl, BOOL& bHandled)
{
m_wndView.m_color = RGB(255,0,0);
return 0;
}
第4步:ATL对话框
我们现在将加入一个简单的对话框资源。一个包含丰富子控制(CEdit、CComboBox等等)的MFC用户界面,是ATL所没有的 -- 虽然WTL有。使用ATL创建对话框有多难?唔,我们的对话框会包含一个组合框,我们会故意在资源编辑器中不放入字符串到组合框中 - 这样可以看到在对话框程序机制中怎么样与控制一起工作。
继续我们的工程。加入一个顶级菜单标题:View和一个菜单项:Dialog。我们在主框架窗口的消息映射中加入一个菜单项的命令处理函数:
COMMAND_ID_HANDLER(ID_VIEW_DIALOG, OnViewDialog)
现在开始我们的对话框:第一版中,我们直接使用CSimpleDialog。首先,插入一个新的对话框资源,并在上面放置一个简单的静态框。然后改变菜单项命令处理函数来使用这个对话框。编译并运行:
LRESULT OnViewDialog(WORD wNotifyCode, WORD wID,
HWND hWndCtl, BOOL& bHandled)
{
CSimpleDialog dlg;
dlg.DoModal();
return 0;
}
如果我们想让我们的对话框拥有更多的复杂的行为,我们会从CSimpleDialog类派生。所以,首先回到资源编辑器向对话框加入一个下拉的组合框。
创建一个名叫CListDialog的新类,派生于CSimpleDialog<IDD_DIALOG1>。不要忘了包含stdafx.h。加入消息映射到这个新类,加入一个条目到映射中以链接到基类的消息映射:
BEGIN_MSG_MAP(CListDialog)
CHAIN_MSG_MAP(CSimpleDialog)
END_MSG_MAP()
下一步,我们响应WM_INITDIALOG对话框以加入一些字符串到组合框中。首先,去掉组合框的Sort风格。然后在(ClassView中)CListDialog类上单击鼠标右键并选择Add Windows Message Handler。改变对话框的消息过滤方式。加入并编辑WM_INITDIALOG处理函数。编码如下 - 你会看到,组合框对象的关键部分与MFC中的实现是一样的:
LRESULT OnInitDialog(UINT uMsg, WPARAM wParam,
LPARAM lParam, BOOL& bHandled)
{
CWindow combo(GetDlgItem(IDC_COMBO1));
combo.SendMessage(CB_ADDSTRING, 0, (LPARAM)"Red");
combo.SendMessage(CB_ADDSTRING, 0, (LPARAM)"Green");
combo.SendMessage(CB_ADDSTRING, 0, (LPARAM)"Blue");
return CSimpleDialog::OnInitDialog(
uMsg, wParam, lParam, bHandled);
}
注意:确保CHAIN_MSG_MAP宏是消息映射中的最后一条。改变主框架中的菜单处理函数以使用这个新的CListDialog类。编译并运行。
对了,DDX/DDV怎么办?我听到你所说的了。好,让我们为IDOK按钮编写代码,返回在组合框中选中的字符串。首先在消息映射中加入适当的宏:
COMMAND_ID_HANDLER(IDOK, OnOK)
对OnOK处理函数编码如下。我们把文本保存在对话框类的成员变量m_text中,一个CComBSTR类的对象:
LRESULT OnOK(WORD, WORD wID, HWND, BOOL&)
{
CComBSTR text;
GetDlgItemText(IDC_COMBO1, m_text.m_str);
::EndDialog(m_hWnd, wID);
return 0;
}
最后,修改主框架中的菜单项处理函数,使用来自对话框的文本,然后编译并运行:
LRESULT OnViewDialog(WORD wNotifyCode, WORD wID,
HWND hWndCtl, BOOL& bHandled)
{
// CSimpleDialog dlg;
CListDialog dlg;
if (IDOK == dlg.DoModal())
{
if (dlg.m_text == CComBSTR("Red"))
m_wndView.m_color = RGB(255,0,0);
else if (dlg.m_text == CComBSTR("Green"))
m_wndView.m_color = RGB(0,255,0);
else if (dlg.m_text == CComBSTR("Blue"))
m_wndView.m_color = RGB(0,0,255);
}
return 0;
}
如果你扩展这个应用程序以支持工具条和状态条,你可以使用ATL的CStatusBarCtrl和CToolBarCtrl类 - 这些类定义在atlcontrols.h中,虽然Microsoft没有正式支持它们。下一篇文章中,我会讲WTL -- 同样是Microsoft没有正式支持的库。你能够聪明的找出ATL和WTL之间前端支持的差异,和全面比较ATL/WTL和MFC。