深入剖析WTL窗口框架

深入剖析WTL

1、  wtl对窗口的封装

2、  消息封装

3、  消息路由

 

一、Wtl对窗口的封装

一般我们使用WTL创建窗口的时候继承CWindowImpl和CDialogImpl两个基类来分别创建自己的窗口,关于不同的基类,创建窗口的方式不同

 

CWindowImpl 创建窗口

         创建一个窗口分为以下几个步骤:

a)        注册一个窗口类

b)        根据窗口类创建窗口

c)        显示创建的窗口

 

在wtl中窗口类的设置以及注册是通过以下方式进行的:

设置

         以下是一个宏定义,在CWindowImpl的开始部分会有这个宏定义,自己的窗口也可以使用这个来设置窗口的相关属性。

DECLARE_WND_CLASS(WndClassName)

         DECLARE_WND_CLASS_EX(WndClassName,style, bkgnd)

 

#define DECLARE_WND_CLASS_EX(WndClassName,style, bkgnd) \

static ATL::CWndClassInfo&GetWndClassInfo() \

{ \

         staticATL::CWndClassInfo wc = \

         {\

                   { sizeof(WNDCLASSEX), style, StartWindowProc, \

                     0, 0,NULL, NULL, NULL, (HBRUSH)(bkgnd + 1), NULL, WndClassName, NULL }, \

                   NULL,NULL, IDC_ARROW, TRUE, 0, _T("") \

         };\

         returnwc; \

}

其中,返回的是CWndClassInfo,是struct_ATL_WNDCLASSINFOW结构体的typedef定义,

_ATL_WNDCLASSINFOW的定义如下:

struct _ATL_WNDCLASSINFOW

{

         WNDCLASSEXWm_wc;

         LPCWSTRm_lpszOrigName;

         WNDPROCpWndProc;

         LPCWSTRm_lpszCursorID;

         BOOLm_bSystemCursor;

         ATOMm_atom;

         WCHARm_szAutoName[5+sizeof(void*)*CHAR_BIT];

         ATOMRegister(WNDPROC* p)

         {

                   returnAtlWinModuleRegisterWndClassInfoW(&_AtlWinModule, &_AtlBaseModule,this, p);

         }

};

第一个成员WNDCLASSEXW是注册窗口时使用的参数,上述红色部分是对这个结构体进行初始化。由于这个是静态函数,当自己的窗口定义了这个宏之后会使用自己的宏定义来注册窗口。

{ sizeof(WNDCLASSEX), style, StartWindowProc, \

                     0, 0, NULL, NULL, NULL, (HBRUSH)(bkgnd + 1),NULL, WndClassName, NULL },

可以看出最开始设置的窗口处理函数是StartWindowProc。在CWindowImplBase(这是CWindowImpl的基类)中定义了两个静态函数:

static LRESULTCALLBACK StartWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

         static LRESULT CALLBACK WindowProc(HWNDhWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

说明:在StartWindowProc会使用thunk技术来设置保存当前窗口类对象的this指针。并且修改当前窗口的回调函数为WindowProc:

static LRESULT CALLBACKStartWindowProc(HWND hWnd, UINT uMsg,

                   WPARAMwParam, LPARAM lParam)

         {

                   CContainedWindowT<TBase >* pThis = (CContainedWindowT< TBase>*)_AtlWinModule.ExtractCreateWndData();

                   ATLASSERT(pThis!= NULL);

                   if(!pThis)

                   {

                            return0;

                   }

                   pThis->m_hWnd= hWnd;

 

                   //Initialize the thunk.  This was allocatedin CContainedWindowT::Create,

                   //so failure is unexpected here.

 

                   pThis->m_thunk.Init(WindowProc, pThis);

                   WNDPROCpProc = pThis->m_thunk.GetWNDPROC();

                   WNDPROC pOldProc = (WNDPROC)::SetWindowLongPtr(hWnd,GWLP_WNDPROC, (LONG_PTR)pProc);

#ifdef _DEBUG

                   //check if somebody has subclassed us already since we discard it

                   if(pOldProc!= StartWindowProc)

                            ATLTRACE(atlTraceWindowing,0, _T("Subclassing through a hook discarded.\n"));

#else

                   pOldProc;          // avoid unused warning

#endif

                   returnpProc(hWnd, uMsg, wParam, lParam);

         }

 

创建

HWND Create(HWND hWndParent, _U_RECT rect =NULL, LPCTSTR szWindowName = NULL,

                            DWORDdwStyle = 0, DWORD dwExStyle = 0,

                            _U_MENUorIDMenuOrID = 0U, LPVOID lpCreateParam = NULL)

         {

                   if (T::GetWndClassInfo().m_lpszOrigName == NULL)

                            T::GetWndClassInfo().m_lpszOrigName =GetWndClassName();

                   ATOMatom = T::GetWndClassInfo().Register(&m_pfnSuperWindowProc);

 

                   dwStyle= T::GetWndStyle(dwStyle);

                   dwExStyle= T::GetWndExStyle(dwExStyle);

 

                   //set caption

                   if(szWindowName == NULL)

                            szWindowName =T::GetWndCaption();

 

                   returnCWindowImplBaseT< TBase, TWinTraits >::Create(hWndParent, rect,szWindowName,

                            dwStyle,dwExStyle, MenuOrID, atom, lpCreateParam);

         }

         红色部分正是调用使用DECLARE_WND_CLASS设置的窗口类信息,其调用方式是T::的方式,CWindowImpl声明如下:

template <class T, class TBase /* =CWindow */, class TWinTraits /* = CControlWinTraits */>

class ATL_NO_VTABLE CWindowImpl : publicCWindowImplBaseT< TBase, TWinTraits >

{

         …

}

其中的T是在继承CWindowImpl实现窗口的时候传递的自己的窗口类。从前面可以知道,GetWndClassInfo是一个静态函数,并且子类的同名函数会隐藏父类的同名函数。

紫色部分内部使用DECLARE_WND_CLASS中CWndClassInfo中的WNDCLASS结构体进行注册窗口。紫色部分最后掉用到如下注释:

ATLINLINE ATLAPIINL_(ATOM)AtlWinModuleRegisterClassExA(_ATL_WIN_MODULE* pWinModule, const WNDCLASSEXA*lpwc)

{

         if(pWinModule == NULL || lpwc == NULL)

                   return0;

         ATOM atom = ::RegisterClassExW(lpwc);

         BOOLbRet = pWinModule->m_rgWindowClassAtoms.Add(atom);

         ATLASSERT(bRet);

         (bRet);

         returnatom;

}

         其中的lpwc就是上述的WNDCLASS

 

创建窗口

         窗口的创建最终要调用系统的API来创建窗口,CreateWIndowEx。在CWindowImpl中使用Create来创建窗口,需要传递创建窗口的一些参数。

CWindowImpl

HWND Create(HWND hWndParent, _U_RECT rect =NULL, LPCTSTR szWindowName = NULL,

                            DWORDdwStyle = 0, DWORD dwExStyle = 0,

                            _U_MENUorIDMenuOrID = 0U, LPVOID lpCreateParam = NULL)

         {

                   if(T::GetWndClassInfo().m_lpszOrigName == NULL)

                            T::GetWndClassInfo().m_lpszOrigName= GetWndClassName();

                   ATOMatom = T::GetWndClassInfo().Register(&m_pfnSuperWindowProc);

 

                   dwStyle= T::GetWndStyle(dwStyle);

                   dwExStyle= T::GetWndExStyle(dwExStyle);

 

                   //set caption

                   if(szWindowName == NULL)

                            szWindowName= T::GetWndCaption();

 

                   return CWindowImplBaseT< TBase, TWinTraits>::Create(hWndParent, rect, szWindowName,

                            dwStyle, dwExStyle, MenuOrID, atom,lpCreateParam);

         }       

         创建成功之后返回一个窗口句柄,最后调用到CWindowImplBaseT的Create函数。

template <class TBase, classTWinTraits>

HWND CWindowImplBaseT< TBase, TWinTraits>::Create(HWND hWndParent, _U_RECT rect, LPCTSTR szWindowName,

                                                                                                                   DWORD dwStyle, DWORD dwExStyle, _U_MENUorIDMenuOrID, ATOM atom, LPVOID lpCreateParam)

{

   BOOL result;

         ATLASSUME(m_hWnd== NULL);

 

   // Allocate the thunk structure here, where we can fail gracefully.

         result= m_thunk.Init(NULL,NULL);

         if(result == FALSE) {

                   SetLastError(ERROR_OUTOFMEMORY);

                   returnNULL;

         }

 

         if(atom== 0)

                   returnNULL;

 

         _AtlWinModule.AddCreateWndData(&m_thunk.cd,this);

 

         if(MenuOrID.m_hMenu== NULL && (dwStyle & WS_CHILD))

                   MenuOrID.m_hMenu= (HMENU)(UINT_PTR)this;

         if(rect.m_lpRect== NULL)

                   rect.m_lpRect= &TBase::rcDefault;

 

         HWND hWnd = ::CreateWindowEx(dwExStyle, MAKEINTATOM(atom),szWindowName,

                   dwStyle, rect.m_lpRect->left,rect.m_lpRect->top, rect.m_lpRect->right - rect.m_lpRect->left,

                   rect.m_lpRect->bottom - rect.m_lpRect->top,hWndParent, MenuOrID.m_hMenu,

                   _AtlBaseModule.GetModuleInstance(),lpCreateParam);

 

         ATLASSUME(m_hWnd== hWnd);

 

         returnhWnd;

}

         红色部分表示创建一个窗口。第二个参数使用的是RegisterClassEx的返回值,在MSDN上有一句话:

RegisterClass Function

If the functionsucceeds, the return value is a class atom that uniquely identifies the classbeing registered. This atom can only be used by the CreateWindow,CreateWindowEx

所以使用MAKEINTATOM(atom),因为在CreateWindowEx的介绍中有一句话The atom must be in the low-order word of lpClassName,所以使用MAKEINTAUTO这个宏,MAKEINTAUTO的相关信息如下MSDN介绍:

         TheMAKEINTATOM macro converts the specified atom into a string, so it can bepassed to functions which accept either atoms or strings.

#defineMAKEINTATOM(i) (LPTSTR)((ULONG_PTR)((WORD)(i)))

         到为止,注册了一个窗口类并且创建窗口成功。

 

 

二、消息封装

在Wtl中消息封装在一个atlapp.h中的CMessageLoop这个类中,其中最核心的成员函数是Run。执行流程是:首先看是不是空闲消息,是的话进去处理,从消息队列中取消息,哦按段是否成功,成功之后判断是不是WM_QUIT消息,因为WM_QUIT消息在MSDN的解释是:If the function retrieves the WM_QUIT message, the return value iszero。否则就处理

PreTranslateMessage,之后根据这个函数的返回值来判断是否需要进入窗口的消息处理。

 

int Run()

         {

                   BOOLbDoIdle = TRUE;

                   intnIdleCount = 0;

                   BOOLbRet;

 

                   for(;;)

                   {

                            while(bDoIdle && !::PeekMessage(&m_msg, NULL, 0,0, PM_NOREMOVE))

                            {

                                     if(!OnIdle(nIdleCount++))

                                               bDoIdle = FALSE;

                            }

 

                            bRet = ::GetMessage(&m_msg, NULL, 0, 0);

 

                            if(bRet== -1)

                            {

                                     ATLTRACE2(atlTraceUI,0, _T("::GetMessage returned -1 (error)\n"));

                                     continue;   // error, don't process

                            }

                            elseif(!bRet)

                            {

                                     ATLTRACE2(atlTraceUI,0, _T("CMessageLoop::Run - exiting\n"));

                                     break;   // WM_QUIT, exit message loop

                            }

 

                            if(!PreTranslateMessage(&m_msg))

                            {

                                     ::TranslateMessage(&m_msg);

                                     ::DispatchMessage(&m_msg);

                            }

 

                            if(IsIdleMessage(&m_msg))

                            {

                                     bDoIdle = TRUE;

                                     nIdleCount = 0;

                            }

                   }

 

                   return(int)m_msg.wParam;

         }

 

说明:

a)        进来之后首先PeekMessage,判断当前消息队列中是否有消息,有消息的话如果当前是空闲模式,则处理空闲消息,空闲消息是一个虚函数,所以会调用到真正实现这个函数的类方法中。

class CIdleHandler

{

public:

         virtualBOOL OnIdle() = 0;

};

 

b)        bRet = ::GetMessage(&m_msg, NULL, 0, 0);从消息队列中拿出消息,与PeekMessage不同的是GetMessage会从消息队列中将消息拿走。

GetMessage第个参数是用来获取MSG结构指针第 2个参数是个窗口句柄(HWND)用来获取指定窗口消息填NULL表示获取当前线程所有窗口消息或者线程消息(Thread message)最后两个参数是wMsgFilterMin和wMsgFilterMax用来获取指定消息当都填0则表示获取所有消息

 

c)        接下来的消息处理

if(!PreTranslateMessage(&m_msg))

         {

                   ::TranslateMessage(&m_msg);

                   ::DispatchMessage(&m_msg);

         }

         PreTranslateMessage是一个虚函数, 类似于OnIdle一样,也是一个纯虚函数。接下来就是翻译消息与派发消息。

DispatchMessage将窗口消息交给注册窗口类时指定的窗口过程(WindowProc)来处理这个消息。

 

三、消息路由

消息路由就是窗口的消息处理过程。在每个窗口创建的时候都会实现虚函数ProcessWindowMessage,因为由最开始的图可以知道CWindowImpl的父类继承了CMessageMap,这个类的声明如下:

 

class ATL_NO_VTABLE CMessageMap

{

public:

         virtual BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAMwParam, LPARAM lParam,

                   LRESULT& lResult, DWORD dwMsgMapID) = 0;

};

         可以看到ProcessWindowMessage是一个纯虚函数,并且在继承类中并没有去实现这个函数,所以我们自己定义的窗口必须去实现这个函数。待会介绍实现方法。

         在设置窗口类的时候指定了窗口的默认处理函数,是StartWindowProc,当窗口创建成功之后发送的第一个消息是WM_NCCREATE,此时进来之后StartWindowProc会修改窗口处理函数为WindowProc。具体实现代码如下:

static LRESULT CALLBACK StartWindowProc(HWNDhWnd, UINT uMsg,

                   WPARAMwParam, LPARAM lParam)

         {

                   CContainedWindowT<TBase >* pThis = (CContainedWindowT< TBase>*)_AtlWinModule.ExtractCreateWndData();

                   ATLASSERT(pThis!= NULL);

                   if(!pThis)

                   {

                            return0;

                   }

                   pThis->m_hWnd= hWnd;

 

                   //Initialize the thunk.  This was allocatedin CContainedWindowT::Create,

                   //so failure is unexpected here.

 

                   pThis->m_thunk.Init(WindowProc,pThis);

                   WNDPROCpProc = pThis->m_thunk.GetWNDPROC();

                   WNDPROC pOldProc = (WNDPROC)::SetWindowLongPtr(hWnd, GWLP_WNDPROC,(LONG_PTR)pProc);

#ifdef _DEBUG

                   //check if somebody has subclassed us already since we discard it

                   if(pOldProc!= StartWindowProc)

                            ATLTRACE(atlTraceWindowing,0, _T("Subclassing through a hook discarded.\n"));

#else

                   pOldProc;          // avoid unused warning

#endif

                   returnpProc(hWnd, uMsg, wParam, lParam);

         }

         红色部分就是修改窗口处理函数为WindowProc。之后的消息都由这个窗口函数去处理。

在WindowProc中的实现就是调用具体类的ProcessWindowMessage。

static LRESULT CALLBACK WindowProc(HWNDhWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

         {

                   CContainedWindowT<TBase >* pThis = (CContainedWindowT< TBase >*)hWnd;

                   ......

                   BOOL bRet =pThis->m_pObject->ProcessWindowMessage(pThis->m_hWnd, uMsg, wParam,lParam, lRes, pThis->m_dwMsgMapID);

                   ......

                                     lRes = pThis->DefWindowProc(uMsg, wParam, lParam);

                   ......

                   returnlRes;

         }

         根据ProcessWindowMessage的返回值,这个窗口过程会来判断是否需要调用窗口默认的处理函数,

         LRESULTDefWindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam)

         {

#ifdef STRICT

                   return ::CallWindowProc(m_pfnSuperWindowProc, m_hWnd, uMsg,wParam, lParam);

#else

                   return::CallWindowProc((FARPROC)m_pfnSuperWindowProc, m_hWnd, uMsg, wParam, lParam);

#endif

         }

其中m_pfnSuperWindowProc在CWindowImplBaseT中初始化的时候指定为:

CWindowImplBaseT() : m_pfnSuperWindowProc(::DefWindowProc);

         DefWindowProc是系统的默认消息处理函数。创建普通的窗口时不会修改指针所指向的内容,只有超类化和子类化的情况下才会去更改m_pfnSuperWindowProc中窗口函数。

         CContainedWindowT<TBase >* pThis = (CContainedWindowT< TBase >*)hWnd;将窗口句柄转换成类的this指针,这个适用的是thunk技术,即同辉汇编代码来修改当前函数的第一个参数,将其存放当前类的this指针。

在自己创建的窗口在继承CWindowImpl之后,WTL中使用消息映射的方式来实现之前提到的纯虚函数。

BEGIN_MSG_MAP(KSimple)

MESSAGE_HANDLER(WM_CREATE,      OnCreate)

END_MSG_MAP()

查看相应的宏定义之后会发现:

#define BEGIN_MSG_MAP(theClass) \

public: \

         BOOLProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam,LRESULT& lResult, DWORD dwMsgMapID = 0) \

         {\

                   BOOL bHandled = TRUE; \

                   (hWnd);\

                   (uMsg);\

                   (wParam);\

                   (lParam);\

                   (lResult);\

                   (bHandled);\

                   switch(dwMsgMapID)\

                   {\

                   case0:

         BEGIN_MSG_MAP正是实现了ProcessWidnowsMessage这个纯虚函数。其中有一个局部变量bHandled,这个标志用来表示当前消息是否需要传递下去,

TRUE:表示这个消息我已经处理了,不需要上一级或者默认消息处理函数来处理这个消息。

FALSE:表示这个消息还需要上一级或者默认消息处理函数来处理。

 

#define MESSAGE_HANDLER(msg, func) \

         if(uMsg== msg) \

         {\

                   bHandled= TRUE; \

                   lResult= func(uMsg, wParam, lParam, bHandled); \

                   if(bHandled) \

                            return TRUE; \

         }

         MESSAGE_HANDLER是其中的一个类型的消息,可以发现有一个bHandled = TRUE默认是TRUE,表示这个消息不需要路由到上一级或者默认消息处理中。红色部分表示为TRUE则直接return TRUE,否则,由END_MSG_MAP的定义可知:return FALSE,这样再拿出WindowProc的处理:

static LRESULT CALLBACK WindowProc(HWNDhWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

         {

                   ......

                   BOOL bRet = pThis->m_pObject->ProcessWindowMessage(pThis->m_hWnd,uMsg, wParam, lParam, lRes, pThis->m_dwMsgMapID);

                   //restore saved value for the current message

                   ATLASSERT(pThis->m_pCurrentMsg== &msg);

                   pThis->m_pCurrentMsg= pOldMsg;

                   //do the default processing if message was not handled

                   if(!bRet)

                   {

                            if(uMsg != WM_NCDESTROY)

                                     lRes =pThis->DefWindowProc(uMsg, wParam, lParam);

                            else

                            {

                                     // unsubclass, if needed

                                     LONG_PTR pfnWndProc = ::GetWindowLongPtr(pThis->m_hWnd, GWLP_WNDPROC);

                                     lRes= pThis->DefWindowProc(uMsg, wParam, lParam);

                                     if(pThis->m_pfnSuperWindowProc!= ::DefWindowProc && ::GetWindowLongPtr(pThis->m_hWnd,GWLP_WNDPROC) == pfnWndProc)

                                               ::SetWindowLongPtr(pThis->m_hWnd,GWLP_WNDPROC, (LONG_PTR)pThis->m_pfnSuperWindowProc);

                                     //clear out window handle

                                     pThis->m_hWnd= NULL;

                            }

                   }

                   returnlRes;

         }

         如果bRet为TRUE,则什么也不做,否则进入DefWindowProc进而走系统的默认消息处理函数。这就是没有处理的消息都由系统统一去处理。

 

#define END_MSG_MAP() \

                            break;\

                   default:\

                            ATLTRACE(ATL::atlTraceWindowing,0, _T("Invalid message map ID (%i)\n"), dwMsgMapID); \

                            ATLASSERT(FALSE);\

                            break;\

                   }\

                   returnFALSE; \

         }

         这个是END_MSG_MAP的宏定义。

         关于消息映射,在WTL中还有BEIGN_MSG_MAP_EX,定义在atlcrack.h文件中。其中将标准消息的处理可以使用两种宏来支持:

方式一:MESSAGE_HANDLER_EX

方式二:MSG_WM_XXX

 

 

CDialogImpl 的处理流程跟CWidnowImpl一样。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值