Rating: none
Andrew Whitechapel (view profile) April 12, 2001 |
In this article I'll provide an introduction to the ATL windowing classes, and a simple cookbook tutorial on an ATL frame-view application -- you'll see that it's actually quite easy to implement front-end functionality equivalent to the MFC. The learning curve with ATL windowing is much less steep and much shorter than learning the MFC because the ATL is so much smaller. Although the ATL is designed primarily to support COM, it does contain a range of classes for modeling windows. You can use these classes for COM objects that have windows, such as ActiveX Controls, and for Windows applications that do not necessarily involve COM. The most important ATL windowing classes are listed in the table below: In addition to the familiar format of MFC message handlers, ATL message handler functions accept an additional argument of type BOOL&. This argument indicates whether a message has been processed, and it's set to TRUE by default. A handler function can then set the argument to FALSE to indicate that it has not handled a message. In this case, ATL will continue to look for a handler function further in the message map. By setting this argument to FALSE, you can first perform some action in response to a message and then allow the default processing or another handler function to finish handling the message. There are three groups of message map macros, as listed in the table below: For example, if you have an ATL dialog class with child controls on the form, you might have a message map like the one shown below: The MFC architecture allows it to use two distinct message routing schemes: routing windows messages up through the hierarchy, and routing command messages across the doc-view classes. The first scheme is less appropriate in the ATL, which has a much looser hierarchy of partially implemented template classes. The second scheme is not appropriate because the ATL does not rigidly impose anything equivalent to the MFC doc-view architecture. The ATL provides two ways to handle messages sent by different windows in a single message map: alternate message maps and chained message maps. A parent window can also handle messages sent to it by a child control by sending the message back as a reflected message. The CContainedWindow constructor needs to be given the address of the class that contains the message map to be used, and the ID of the alternate message map within the message map (or zero for the default message map). For example, when you create an ATL control based on a Windows control, the Object Wizard will generate a class for the control with an embedded CContainedWindow member to represent the child control. In effect, this contained window superclasses the particular Windows control you have chosen to base your ActiveX control on: Note that Button is the WNDCLASS style, not the caption. This pointer of the containing class is passed as the second parameter, and the value 1 is passed to the CContainedWindow constructor to identify the alternate message map. If you then want to handle the WM_LBUTTONDOWN for the control, you would update the message map as shown below. In this way, the message would be routed to the parent window's message map, and then routed to the alternate part of that message map: So, alternate message maps are a simple strategy to allow you to consolidate message handlers within a single BEGIN_MSG_MAP/END_MSG_MAP macro pair. For example, when you create an ATL control based on a Windows control, the Object Wizard will generate code like this: This specifies that WM_CREATE and WM_SETFOCUS messages will be handled in this class, but any other message will be routed to the message map in the CComControl<> base class. Also, if the handlers for WM_CREATE or WM_SETFOCUS set bHandled to false, these messages will then be passed on to the base class for further handling. To route messages to a data member, you'd have to update the map like this: This assumes that m_ctlButton is a member of the container window, and is an instance of a class derived from CContainedWindow where you have an entry in the message map for the messages you're interested in: So, chained message maps allow you to chain-route messages from one map to another -- similar in concept to the message routing schemes adopted by the MFC. The REFLECT_NOTIFICATIONS macro expands to a call to CWindowImpl::ReflectNotifications, which has this signature: The function extracts the window handle to the child control that sent the message from the wParam or lParam (depending on the type of message), and then sends the message on like this: The child window handles the reflected message using the standard MESSAGE_HANDLER macros and the predefined reflected message IDs defined in olectrl.h: OCM_DRAWITEM, shown in this example, is defined as follows: The DEFAULT_REFLECTION_HANDLER macro converts the message back to the original message and passes it to DefWindowProc. The ATL COM AppWizard is designed to provide a host for COM objects. If you want a non-COM application, the ATL COM AppWizard code is more than you need. So, to create an ATL application, you have two choices: Just so you can see exactly what's required, we'll deliberately avoid any wizard-generated code, and follow the second route to achieve the minimum lightweight framework for our application. ATL Support Window WinMain In this project we'll create an application modeled on the MFC SDI frame-view paradigm, but use the ATL window classes. The first version of this app will look almost the same as the previous SimpleWin, but then we'll add a view, menus, and dialogs. Mainframe Window View Window User Interface Now, we'll handle the WM_LBUTTONDOWN, WM_MOUSEMOVE and WM_LBUTTONUP messages to provide a very simple version of the Scribble program that allows a user to draw lines with the mouse Yes, I know, but it does provide a very manageable vehicle for exploring UI response and message handling: If you want to extend this app with toolbars and statusbars, you can use the ATL CStatusBarCtrl and CToolBarCtrl classes - these are defined in atlcontrols.h, although Microsoft doesn't officially support them. In the next article, I'll consider the WTL -- another officially unsupported Microsoft library. You'll then be able to make intelligent comparisons between ATL and WTL front-end support, and informed decisions about ATL/WTL versus MFC.
(continued) CWindow A thin wrapper to the Win32 APIs for manipulating a window, including a window handle and an HWND operator that converts a CWindow object to an HWND. Thus you can pass a CWindow object to any function that requires a handle to a window. CWindowImpl You can use CWindowImpl to create a window based on a new Windows class, superclass an existing class, or subclass an existing window. CContainedWindow A class that implements a window which routes messages to the message map of another class, allowing you to centralize message processing in one class. CAxWindow Allows you to implement a window that hosts an ActiveX control, with functions to create a control or attach to an existing control. CDialogImpl Used as a base class for implementing a modal or modeless dialog box. CDialogImpl provides a dialog box procedure that routes messages to the default message map in your derived class. Does not support ActiveX controls. CSimpleDialog Implements a simple modal dialog box given the resource ID of the dialog box. CSimpleDialog has a predefined message map that handles known commands such as IDOK and IDCANCEL. CAxDialogImpl Like CDialogImpl, this is used as a base class for implementing a modal or modeless dialog box, and provides a dialog box procedure that routes messages to the default message map in your derived class. Additionally supports ActiveX controls. The ATL Object Wizard supports adding a CAxDialogImpl-derived class to your project and generates an accompanying dialog resource. CWndClassInfo Manages the information of a new window class -- essentially encapsulates WNDCLASSEX. CWinTraits and CWinTraitsOR Encapsulate the traits (WS_ window styles) of an ATL window object. Message Maps
One factor in the reluctance to invest the time in learning ATL windowing is a perception that ATL message maps are weird. OK, they're different from the MFC message maps, but did you understand MFC message maps the first time you saw the macros? In fact, the ATL maps are surprisingly easy to grasp. To allow you to process window messages in a CWindowImpl-derived class, ATL inherits from the abstract base class CMessageMap. CMessageMap declares one pure virtual function, ProcessWindowMessage, which is implemented in your CWindowImpl-derived class via the BEGIN_MSG_MAP and END_MSG_MAP macros. MESSAGE_HANDLER Maps a window message to a handler function. COMMAND_HANDLER Maps a WM_COMMAND message to a handler function based on the notification code and the ID of the menuitem, control, or accelerator. COMMAND_ID_HANDLER Maps a WM_COMMAND message to a handler function based on the ID of the menuitem, control, or accelerator. COMMAND_CODE_HANDLER Maps a WM_COMMAND message to a handler function based on the notification code. NOTIFY_HANDLER Maps a WM_NOTIFY message to a handler based on the notification code and the control identifier. NOTIFY_ID_HANDLER Maps a WM_NOTIFY message to a handler based on the control identifier. NOTIFY_CODE_HANDLER Maps a WM_NOTIFY message to a handler based on the notification code. 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()
Alternate Message Maps
Alternate message maps are primarily designed for use with the ATL class CContainedWindow. This class is written to route all of its messages to the message map in another class. This allows messages sent to a child window to be handled by its parent window. 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()//...
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()
Chained Message Maps
Chaining message maps routes the message through to the message map in another class or object. ATL supplies several map-chaining macros: CHAIN_MSG_MAP(theBaseClass) Routs messages to the default message map of a base class. CHAIN_MSG_MAP_ALT(theBaseClass, mapID) Routes messages to the alternate message map of a base class. CHAIN_MSG_MAP_MEMBER(theMember) Routes messages to the default message map of the specified data member (derived from CMessageMap). CHAIN_MSG_MAP_ALT_MEMBER(theMember, mapID) Routes messages to the alternate message map of the specified data member. 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()
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()
class CMyButtonControl : public CContainedWindow{ //... BEGIN_MSG_MAP(CMyButtonControl) MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown) END_MSG_MAP()
Reflected Messages
A parent window can handle windows messages sent to it by a child control by sending the message back as a reflected message -- this will be the original message plus a flag. When the control gets these messages, it can identify them as being reflected from the container and handle them appropriately. For example, a child control might want to handle WM_DRAWITEM messages. For this to work, REFLECT_NOTIFICATIONS must be present in the parent window's message map: 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()
template <class TBase>LRESULT CWindowImplRoot<TBase>::ReflectNotifications(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
::SendMessage(hWndChild, OCM_ _BASE + uMsg, wParam, lParam);
BEGIN_MSG_MAP(CMyContainedControl) MESSAGE_HANDLER(OCM_DRAWITEM, OnDrawItem) DEFAULT_REFLECTION_HANDLER()END_MSG_MAP()
#define OCM_ _BASE (WM_USER+0x1c00)#define OCM_COMMAND (OCM_ _BASE + WM_COMMAND)//...#define OCM_DRAWITEM (OCM_ _BASE + WM_DRAWITEM)
Recipe 1: ATL Window App
This is a very simple exercise, designed to demonstrate how easy it is to create a simple application using the ATL window classes. Here's one I made earlier:
Easy-peasy, huh? Now let's take this a step further with another demo... #include <atlbase.h>extern CComModule _Module;#include <atlcom.h>#include <atlwin.h>
CComModule _Module;BEGIN_OBJECT_MAP(ObjectMap)END_OBJECT_MAP()
library SomethingOrOther{};
BEGIN_MSG_MAP(CMyWindow)END_MSG_MAP()
LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled){ PostQuitMessage(0); return 0;}
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;}
_Module.Init(NULL, hInstance);// ..._Module.Term();
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);}
Recipe 2: ATL Frame-View App
DECLARE_WND_CLASS(_T("MyFrame"))BEGIN_MSG_MAP(CMainFrame)END_MSG_MAP()
void OnFinalMessage(HWND /*hWnd*/){ ::PostQuitMessage(0);}
_Module.Init(NULL, hInstance, NULL);_Module.Term();
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);}
LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled){ m_wndView.Create(m_hWnd, CWindow::rcDefault, _T("MyView"), 0, 0, 0); return 0;}
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;}
m_startPoint.x = m_startPoint.y = -1;m_endPoint.x = m_endPoint.y = -1;
LRESULT OnLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled){ m_startPoint.x = LOWORD(lParam); m_startPoint.y = HIWORD(lParam); return 0;}
LRESULT OnLButtonUP(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled){ m_startPoint.x = m_startPoint.y = -1; return 0;}
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;}
Recipe 3: ATL Menus
Continue with the Frame-View project. We will add a simple menu to give the user a choice of pen colors.:
HPEN hp = CreatePen(PS_SOLID, 2, m_color);HPEN op = (HPEN)SelectObject(hdc, hp);
HMENU hMenu = LoadMenu(_Module.GetResourceInstance(), MAKEINTRESOURCE(IDR_MENU1));mf.Create(GetDesktopWindow(), CWindow::rcDefault, _T("My App"), 0, 0, (UINT)hMenu);
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;}
Recipe 4: ATL Dialogs
We'll now add a simple dialog resource. Again, one of the features of the MFC is its rich support of child controls (CEdit, CComboBox, and others), which the ATL doesn't have -- although the WTL does. So how hard is it in ATL? Well, our dialog will feature a combobox, and we'll deliberately not put the strings into the combo in the resource editor - just to show how to work with the controls in a dialog programmatically. COMMAND_ID_HANDLER(ID_VIEW_DIALOG, OnViewDialog)
LRESULT OnViewDialog(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled){ CSimpleDialog<IDD_DIALOG1> dlg; dlg.DoModal(); return 0;}
BEGIN_MSG_MAP(CListDialog) CHAIN_MSG_MAP(CSimpleDialog<IDD_DIALOG1>)END_MSG_MAP()
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);}
COMMAND_ID_HANDLER(IDOK, OnOK)
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<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;}
'Computer Science' 카테고리의 다른 글
인터넷 소켓 활용 (0) | 2009.05.18 |
---|---|
[ VMWare ] Workstation / GSX Server / ESX Server 의 차이점 (0) | 2009.05.16 |
ATL::CWindow 사용하기 (0) | 2009.04.28 |
wtl (0) | 2009.04.25 |
WTL code (0) | 2009.04.25 |