使用ATL 窗口类

    ATL窗口类并不难学,但是许多开发者认为既然有强大的MFC支持,为什么还要同时学习ATL   窗口开发?这是因为MFC强迫你使用应用程序框架结构,同时存在代码过大,性能低下的问题,你还必须审查基于文档和文档模块的连载机制的持续有效性。使用ATL   窗口类的另一个大好处当然就是易于整合COM支持。如果你希望应用程序更快,更小,并且在选择应用程序结构和连续性协议方面更有弹性,你就不能不试试ATL。
在本文中,我将介绍ATL窗口类,并给出一个ATL框架视图程序的简单向导。你会发现实际上很容易实现与MFC同样的前端功能。由于ATL窗口类与MFC相比之下要小的多,所以ATL窗口类的学习曲线会更短,弯路更少。

尽管ATL主要是设计来支持COM,但它还是包括了一些窗口模型类,你可以用这些类建立有窗口的COM对象(如ActivX控件)和那些不需要包括COM的Windows应用程序。以下列出了最重要的ATL窗口类 :

 


Cwindow   一个对用于维护窗口的Win32API函数的简单包装,包括一个窗口句柄和HWND运算用于把一个CWindow对象转换为一个HWND,因此你可以把一个Cwindow对象传送给任何需要一个窗口句柄的函数。
CWindowImpl   你可以使用CWindowImpl建立一个基于新窗口类的窗口,是一个存在类的超类或者是一个存在窗口的子类。
CContainedWindow   实现一个窗口类,该窗口发送消息给其他类的消息映射,允许你在一个类中集中进行消息处理。
CAxWindow   允许你实现一个寄生了一个ActiveX控件的窗口,可以建立一个控件或连接一个已存在的控件。
CDialogImpl   作为基类以实现一个模式的或者非模式对话框。CDialogImpl提供一个对话框处理过程发送消息给派生类的缺省消息映射,不支持ActiveX控件。
CSimpleDialog   实现了一个给定了对话框资源ID的简单的模式对话框。有一个预定义的消息映射处理已知的命令如IDOK和IDCANCEL。
CAxDialogImpl   和CDialogImpl一样作为一个基类实现一个模式的或者非模式的对话框,并提供一个对话框处理过程发送消息到派生类的缺省的消息映射。但支持ActiveX控件,ATL对象向导支持添加一个CAxDialogImpl派生的类到你的工程中并产生一个相应的对话框资源。
CWndClassInfo   管理一个新的窗口类的信息,实质上是WNDCLASSEX的封装。

CWinTraits   and   CWinTraitsOR 封装了ATL窗口对象的特性(WS_Windows   sytle)。

 

消息映射
    一个阻挠人们花时间学习ATL   Windows的原因是一种认为ATL消息映射很怪异的观点,不错,它们确实与MFC消息映射不同,但是当你第一次看见那些宏的时候是否就能理解MFC消息映射?事实上,ATL映射令人惊奇的容易掌握。为了可以在CWindowImpl派生类中处理窗口消息,ATL从抽象类CMessageMap中继承。CMessageMap声明了一个纯虚函数ProcessWindowMessasge,它通过BEGIN_MSG_AMP和END_MESSAGE_MAP宏在CwindowImpl派生类中实现。
    除了熟悉的MFC格式的消息处理,ATL消息处理函数还接受一个额外的BOOL&类型的参数,这个参数指示该消息是否已经被处理,通常为缺省值TURE。消息处理函数可以设定该参数为FALSE以表示它已经处理了该消息。在这种情况下,ATL可以继续查找一个在消息映射中更深层的消息处理函数。通过设定该参数为FALSE,你可以先作一些处理响应一个消息,然后可以允许缺省的消息过程或者其他处理函数来完成处理该消息。
这里有三组消息映射宏,如下表所列:
· 对所有消息进行处理
· 对WM_COMMAND消息进行命令处理。
· 对WM_NOTIFY消息进行通告处理。
MESSAGE_HANDLER   映射一个窗口消息到一个处理函数。
COMMAND_HANDLER   映射一个WM_COMMAND消息到一个基于通告代码和菜单项ID、控件、或者是加速键的处理函数。
COMMAND_ID_HANDLER   映射一个WM_COMMAND消息一个给基于菜单项、控件、或者加速键的处理函数。
COMMAND_CODE_HANDLER   映射一个WM_COMMAND消息道义个基于通告代码的处理函数。
NOTIFY_HANDLER   映射一个WM_NOTIFY消息到一个基于通告代码和控件ID的处理函数
NOTIFY_ID_HANDLER   映射一个WM_NOTIFY消息到一个基于控件ID的处理函数。
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结构允许它使用两种不同的消息发送方案:沿着层次向上发送窗口消息,或者通过文档-视类发送命令消息。第一个方案在ATL中不太合适,因为它所部分实现的模版类层次结构太松散。第二个方案更不可能,因为ATL没有严格的实现任何等同于MFC文档-视图机构的东西。

ATL提供两种方式处理在单个消息映射中的不同的窗口发出的消息:修改消息映射和消息映射链。一个父窗口也可以处理子控件发送的消息,然后可以作为一个反射消息发回。

 


可选消息映射
    可选消息映射主要设计来与ATL类CContainedWindow共用。该类可以发送它所有的消息到其他类的消息映射。这可以用于实现发送到子窗口的消息被父窗口处理。
    CContainedWindow构造器需要给出包括将要使用的消息映射的类的地址、该消息映射中的可选消息映射的ID(对缺省的消息映射为0)。
    例如,当你建立一个基于一个Windows控件的ATL控件,对象向导将为该控件产生一个类,含一个CContainedWindow对象来表示该子控件。结果就是,内含的窗口成为ActiveX控件所基于的窗口控件的超类:
class   ATL_NO_VTABLE   CMyButton   :
  public   CComObjectRootEx <CComSingleThreadModel> ,
  public   CComCoClass <CMyButton,   &CLSID_MyButton> ,
  public   CComControl <CMyButton> ,
  //...
{
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 <CMyButton> )
  ALT_MSG_MAP(1)
END_MSG_MAP()
//...
注意:Button是WNDCLASS类型,不是名称。包含类的指针作为第二个参数传送,数值1送给CcontainedWindow构造函数来标记可选消息映射。
如果你希望为控件处理WM_LBUTTONDOWN消息,你可以按如下方法修改消息映射。在这种情况下消息将会被发送到父窗口的消息映射,然后发送到消息映射的可选部分:
BEGIN_MSG_MAP(CMyButton)
MESSAGE_HANDLER(WM_CREATE,   OnCreate)
MESSAGE_HANDLER(WM_SETFOCUS,   OnSetFocus)
  CHAIN_MSG_MAP(CComControl <CMyButton> )
ALT_MSG_MAP(1)
MESSAGE_HANDLER(WM_LBUTTONDOWN,   OnLButtonDown)
END_MSG_MAP()

可选消息映射是一个允许你在一个BEGIN_MSG_MAP/ENDMSG_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控件时,对象向导会产生如下代码:
BEGIN_MSG_MAP(CMyButton)
MESSAGE_HANDLER(WM_CREATE,   OnCreate)
  MESSAGE_HANDLER(WM_SETFOCUS,   OnSetFocus)
  CHAIN_MSG_MAP(CComControl <CMyButton> )
ALT_MSG_MAP(1)
END_MSG_MAP()
这指定WM_CREAT和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 <CMyButton> )
ALT_MSG_MAP(1)
  CHAIN_MSG_MAP_MEMBER(m_ctlButton)
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::ReflectNotificaions,定义如下:
template   <class   TBase>
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_INFLECTION_HANDLER宏转换消息到原始消息并送入DefWindowProc。

 


秘诀   1:   ATL   Window   App
这是一个简单练习,证明使用ATL窗口类建立一个简单应用程序是多么的简单:
 
    ATL COM应用程序向导是设计来为COM对象提供一个宿主,如果你要建立一个非COM应用程序,ATL   COM应用程序向导代码对你不合适,所以要建立一个ATL应用程序,你有两个选择:
· 建立一个ATL   COM应用程序向导的EXE服务器,接受冗余的代码。
· 建立一个Win32应用程序,手动增加ATL支持。
     为了能更清楚那些必须的,我们故意不使用任何向导生成的代码,按照第二个方法为我们的应用程序构建最小化的轻量记得框架。
1. 建立一个新的Win32应用程序,选择简单选项,这样我门获得了stdafx.h和stdafx.cpp(‘afx’是从MFC残留的,但是名字没有关系,重要的是预编译头文件(PCH))
ATL   支持
2. 修改stdafx.h增加必要的ATL头文件和一个全局CComModule对象的外部引用。  
#include   <atlbase.h>
extern   CComModule   _Module;
#include   <atlcom.h>
#include   <atlwin.h>
3. 增加一个ATL对象映射到主CPP文件,它是空的,我们在这里声明CcomModual对象。
CComModule   _Module;
BEGIN_OBJECT_MAP(ObjectMap)
END_OBJECT_MAP()
4. 增加一个和工程名称相同的IDL文件,它必须有一个库块,它不需要作为工程的一部分建立(你可以设置Project/Settings把它从建立中排除),但是为了使用向导它必须存在,你可以使用任何你喜欢的库名称。
library   SomethingOrOther
{
};
Window  
5. 没有任何建立指定的ATL   Window类的向导支持,所以你只能使用新建对话框增加一个普通类,然后手动修改它。在类视图中右击根节点选择新类,使用Generic   Class类类型,名称CmyWindow,基类CwindowImpl <CmyWindow> ,你会获得一个关于ATL窗口类的警告,因为向导会为你的新类产生一个新的头文件但是不会包括stdafx.h(因为已经包括了atlwin.h),修改的方法是包括stdafx.h。
6. 在你的新窗口类中,声明一个消息映射然后保存。  
BEGIN_MSG_MAP(CMyWindow)
END_MSG_MAP()
7. 在类视图中,右击你的新窗口类增加一个WM_DESTORY消息的处理以发出一个退出消息:
LRESULT   OnDestroy(UINT   uMsg,   WPARAM   wParam,
                                    LPARAM   lParam,   BOOL&   bHandled)
{
  PostQuitMessage(0);
  return   0;
}
8. 同样的处理WM_PAINT,编码打印一个字符串。由于没有ATL类包装HDC(在WTL中有)我们直接使用API代码:
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  
9. 在WinMain的开始和结尾分别调用CcomModule::Init和Term
_Module.Init(NULL,   hInstance);
//   ...
_Module.Term();
10. 在Init和Term中间,声明一个你的Window类对象,初始化并调用Create函数,并设置一个消息循环:
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);
}
11. 现在,建立和运行你的程序,你会发现一个象上面那样在客户区写者“Hello   World”的窗口显示。

很容易是吗?现在让我们继续看下一个例子……

 


秘诀2:   ATL   框架视图程序

 


在这个工程中我们模仿MFC   SDI框架视图为范例建立了一个应用程序,但是使用了ATL窗口类。本程序的第一个版本看起来和前一个简单程序一样,但是我们会以后加上视图,菜单和对话框。
1. 建立一个新的‘Simple’Win32应用程序,象以前一样修改stdafx.h增加需要的ATL头文件和对CcomModule对象的外部引用,增加一个ATL对象映射到主CPP文件并声明全局CcomModule对象,增加一个包括库块的框架IDL文件。
Mainframe   Window  
2. 右击类视图的根节点,新建Generic类,名为CmainFrame,基类CWindowImpl <CmainFrame,Cwindow,CframeWinTraits> ,注意CframeWinTraits是在atlwin.h中定义的特别适用于主框架窗口。在新的CmainFrame类中,声明WNDCLASS结构名和消息映射。
DECLARE_WND_CLASS(_T( "MyFrame "))  
BEGIN_MSG_MAP(CMainFrame)
END_MSG_MAP()    
3. 我们已经继承了函数OnFinalMessage——ATL中极少的虚函数之一——,当收到WM_NCDESTROY消息时会被ATL调用,你必须重载它以发送退出消息。
void   OnFinalMessage(HWND   /*hWnd*/)
{
  ::PostQuitMessage(0);
}
4. 现在在WinMain中增加一些代码。在开始和结尾处调用CcomModule初始化/中止例程。
_Module.Init(NULL,   hInstance,   NULL);
 
_Module.Term();
5. #include   主框架类头文件,在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);
}
6. 试试看
View   Window  
7. 现在我们将加入视图。右击类视图建立另一个新的类,同样使用普通类,名称为CviewWin,从CwindowImpl <CviewWin,   Cwindow,   CwinTraits> ,注意这里没有为视图预定义特别的CwinTraits。
8. 像前面一样包括stdafx.h并声明WNDCLASS和消息映射,在CmainFrame类中加入一个CviewWin实例作为成员,在框架中处理WM_CREAT消息,在其中建立视图。
LRESULT   OnCreate(UINT   uMsg,   WPARAM   wParam,
LPARAM   lParam,   BOOL&   bHandled)
{
  m_wndView.Create(m_hWnd,   CWindow::rcDefault,
                                    _T( "MyView "),   0,   0,   0);
  return   0;
}
9. 在框架中处理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消息来提供一个简单版本的图写程序,允许用户用鼠标画线。他很清晰的表现了用户界面反映和消息处理。
 
10. 首先,增加两个POINT数据成员到视图类,在构造函数中初始化它们为-1,-1。我们需要保持跟踪所画每一条线的开始和结束点,-1,-1是鼠标不可能到达的坐标点值。
m_startPoint.x   =   m_startPoint.y   =   -1;
m_endPoint.x   =   m_endPoint.y   =   -1;
11. 下一步,添加对三个鼠标消息的处理,与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;
}
12. 在OnLButtonUp,重置开始点为-1:
LRESULT   OnLButtonUP(UINT   uMsg,   WPARAM   wParam,
                                        LPARAM   lParam,   BOOL&   bHandled)
{
  m_startPoint.x   =   m_startPoint.y   =   -1;
  return   0;
}
13. 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;
}

14. 编译运行,你可能会问为什么在ATL重没有任何消息Crackers?似的,这就是WTL所要做的事情之一…

 


秘诀3:   ATL   菜单
继续上一个框架视图工程,我们将加上一个简单的菜单让用户选择钢笔颜色。
 
1. 继续这个工程,首先加入一个公共的COLORREF成员变量到视图类,叫做m_color,在构造函数重初始化为黑色。在OnMouseMove处理函数重建立一个笔,并选入DC,完成之后如下所示选择原来的黑色到DC中。
HPEN   hp   =   CreatePen(PS_SOLID,   2,   m_color);
HPEN   op   =   (HPEN)SelectObject(hdc,   hp);
2. 插入菜单资源,增加一个顶层菜单名:Color和三个子菜单red,greed,blue。
3. 在WinMain中,#inlcude资源头文件,在建立主框架之前读入菜单,并将菜单句柄传送给Create调用。
HMENU   hMenu   =   LoadMenu(_Module.GetResourceInstance(),
                                              MAKEINTRESOURCE(IDR_MENU1));
mf.Create(GetDesktopWindow(),   CWindow::rcDefault,
                      _T( "My   App "),   0,   0,   (UINT)hMenu);
4. 我们现在将在主框架中处理菜单命令,包括资源头文件并手动更新消息映射
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()
5. 使三个处理函数作一些显而易见的工作,并重新测试:
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有。那么这又有多难?我们的对话框将有一个Combobox,为了示意如何在对话框中编程操作控件,我们将故意不在资源编辑器中把字符串加入Combo。
1. 继续以上工程,加入一个新的顶级菜单“View”和一个菜单项“Dialog”到菜单中,在主框架中处理该菜单项命令消息:
COMMAND_ID_HANDLER(ID_VIEW_DIALOG,   OnViewDialog)  
2. 现在增加对话框,在第一个版本中,我们直接使用CsimpleDialog,首先插入一个新的对话框资源,加入一个新的静态框,然后更改菜单命令处理使用该对话框,重建并测试它。
LRESULT   OnViewDialog(WORD   wNotifyCode,   WORD   wID,
                                          HWND   hWndCtl,   BOOL&   bHandled)
{
  CSimpleDialog <IDD_DIALOG1>   dlg;
  dlg.DoModal();
 
  return   0;
}
3. 如果我们在对话框中需要更复杂的功能,我们必须从CsimpleDialog中派生,首先在资源编辑器中为对话框加上一个下拉框。
 
4. 建立一个新的类ClistDialog,从CSimpleDialog <IDD_DIALOG1> 中派生。别忘了包括stdafx.h,为新的类增加一个消息映射,并在消息映射中增加一个入口,连接到基类的消息映射中。
BEGIN_MSG_MAP(CListDialog)
  CHAIN_MSG_MAP(CSimpleDialog <IDD_DIALOG1> )
END_MSG_MAP()
5. 下一步,我们编辑WM_INITDIALOG以增加一些字符串到Combobox,首先,去掉Combobox的排序属性,右击ClistDialog类选择增加Window消息处理,更改过滤器到Dialog,增加和编辑WM_INITDIALOG的消息处理函数,如下所示,你会发现Combobox类对象的定义与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 <IDD_DIALOG1> ::OnInitDialog(
    uMsg,   wParam,   lParam,   bHandled);
}
6. 注意,确信CHAIN_MSGMAP宏是该映射中的最后一个入口,更改主框架的菜单项处理函数使用新的ClistDialog类,编译测试。
7. 好了,但是DDX/DDV又怎么办?让我们编辑IDOK按钮的代码以从列表中获得选定的字符串,首先在消息映射中加入相应的宏:
COMMAND_ID_HANDLER(IDOK,   OnOK)
8. 下一步修改OnOk代码如下,我们将把文本存入我们对话框的一个成员CComBSTR对象m_text:
LRESULT   OnOK(WORD,   WORD   wID,   HWND,   BOOL&)
{
  CComBSTR   text;
  GetDlgItemText(IDC_COMBO1,   m_text.m_str);
  ::EndDialog(m_hWnd,   wID);
 
  return   0;
}
9. 最后,更新框架类菜单项处理函数,使用对话框类的文本,编译并测试。
LRESULT   OnViewDialog(WORD   wNotifyCode,   WORD   wID,
                                          HWND   hWndCtl,   BOOL&   bHandled)
{
  //   CSimpleDialog <IDD_DIALOG1>   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中定义,尽管微软没有官方支持他们,在下一篇文章中,我将考虑使用WTL——另一个非官方支持的为人类库,你可以在WTL和ATL的前端支持之间做一些比较,就可以知道ATL/WTL与MFC的关系。

 

本文由Andrew   Whitechapel投稿。
注:我在阅读和试用过程中,发现不使用COM支持时不加入IDL文件和对象映射也没有关系。

 

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
使用 ATL 创建窗口可以遵循以下步骤: 1. 在 Visual Studio 中创建一个 ATL 项目。 2. 在 ATL 项目中,打开 `resource.h` 文件并添加新的资源 ID。 3. 在 ATL 项目中,打开 `MyWindow.h` 文件并添加以下代码: ```c++ class CMyWindow : public CWindowImpl<CMyWindow> { public: DECLARE_WND_CLASS(_T("MyWindowClass")) BEGIN_MSG_MAP(CMyWindow) MESSAGE_HANDLER(WM_CREATE, OnCreate) MESSAGE_HANDLER(WM_PAINT, OnPaint) MESSAGE_HANDLER(WM_DESTROY, OnDestroy) END_MSG_MAP() private: LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { return 0; } LRESULT OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { PAINTSTRUCT ps; HDC hdc = BeginPaint(&ps); EndPaint(&ps); return 0; } LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { PostQuitMessage(0); return 0; } }; ``` 4. 在 ATL 项目中,打开 `MyWindow.cpp` 文件并添加以下代码: ```c++ #include "stdafx.h" #include "resource.h" #include "MyWindow.h" CMyWindow::CMyWindow() { } LRESULT CMyWindow::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { // 创建窗口 Create(NULL, CRect(0, 0, 640, 480), _T("My Window"), WS_OVERLAPPEDWINDOW); return 0; } LRESULT CMyWindow::OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { PAINTSTRUCT ps; HDC hdc = BeginPaint(&ps); // 绘制图形 EndPaint(&ps); return 0; } LRESULT CMyWindow::OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { PostQuitMessage(0); return 0; } ``` 5. 在 ATL 项目中,打开 `stdafx.h` 文件并添加以下代码: ```c++ #define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS #define _AFX_ALL_WARNINGS #include <afxwin.h> #include <atlbase.h> #include <atlapp.h> extern CAppModule _Module; #include <atlwin.h> ``` 6. 在 ATL 项目中,打开 `main.cpp` 文件并添加以下代码: ```c++ #include "stdafx.h" #include "MyWindow.h" CAppModule _Module; int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { _Module.Init(NULL, hInstance); CMyWindow wnd; wnd.Create(NULL); wnd.ShowWindow(nCmdShow); MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } _Module.Term(); return 0; } ``` 以上是使用 ATL 创建窗口的基本步骤,可以根据实际需求进行修改和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值