duilib学习领悟(2)
再次强调,duilib只不过是一种思想!
在上一节中,我剖析了duilib中窗口类的注册,其中遗留两个小问题没有细说的?
第一个问题:过程函数中__WndProc()中有这么一小段代码:
pThis = reinterpret_cast<CWindowWnd*>(::GetWindowLongPtr(hWnd, GWLP_USERDATA));
if( uMsg == WM_NCDESTROY && pThis != NULL ) {
LRESULT lRes = ::CallWindowProc(pThis->m_OldWndProc, hWnd, uMsg, wParam, lParam);
::SetWindowLongPtr(pThis->m_hWnd, GWLP_USERDATA, 0L);
if( pThis->m_bSubclassed ) pThis->Unsubclass();
pThis->m_hWnd = NULL;
pThis->OnFinalMessage(hWnd);
return lRes;
}
有这么一段代码,请看我分析一下流程,首先第一句,从USER_DATA中取出this指针,接着判断是否是WM_NCDESTROY消息,这段有什么用呢?众所周知,这个WM_NCDESTROY消息是Windows程序退出时所发送的最后一个消息,那么拦截这个消息就是留给我们一个清场的机会,看到pThis->OnFinalMessage(hWnd)没?这个函数就是个虚函数,我们可以重写它,从而处理程序退出时应该做的后续工作,比如资源释放等.
第二个问题:就是我上一节提到的duilib另外一套处理消息的机制
接着看过程函数__WndProc()的代码:
LRESULT CALLBACK CWindowWnd::__WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
CWindowWnd* pThis = NULL;
if( uMsg == WM_NCCREATE ) {
LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam);
pThis = static_cast<CWindowWnd*>(lpcs->lpCreateParams);
pThis->m_hWnd = hWnd;
::SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LPARAM>(pThis));
}
else {
pThis = reinterpret_cast<CWindowWnd*>(::GetWindowLongPtr(hWnd, GWLP_USERDATA));
if( uMsg == WM_NCDESTROY && pThis != NULL ) {
LRESULT lRes = ::CallWindowProc(pThis->m_OldWndProc, hWnd, uMsg, wParam, lParam);
::SetWindowLongPtr(pThis->m_hWnd, GWLP_USERDATA, 0L);
if( pThis->m_bSubclassed ) pThis->Unsubclass();
pThis->m_hWnd = NULL;
pThis->OnFinalMessage(hWnd);
return lRes;
}
}
if( pThis != NULL ) {
return pThis->HandleMessage(uMsg, wParam, lParam);
}
else {
return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
}
}
请看红色标记部分.
我之所以说,duilib采用的消息处理机制,和MFC稍微有些不同,是因为duilib的消息处理是在另一个类当中处理的,CPaintManagerUI 就是这个类!
那么duilib为什么要设计这样一个类来处理消息, 消息处理为什么不放在CWindowWnd类中直接处理,而要放在CPaintManagerUI类中?这些归功于duilib设计者们的良苦用心!为什么?
如果我们在不看duilib源码的基础上,也去设计一个这样的duilib,也许我们会这样设计:我们对消息的处理是在用户构建的类CDuiFrameWnd类(派生自CWindowWnd),为了在控件中可以实时刷新,那么每个控件都必须强制的保存一个变量---m_hWnd(主窗口句柄),而且,我们在每次处理消息时,都必然做一些重复性的工作(代码),那么我们将这些控件都具有的变量与操作都封装起来,这就是CPaintManagerUI类的由来!当然从名字看来,它主要处理的是绘图方面的消息,因为整个应用程序只有一个句柄(主窗口句柄),所以CPaintManagerUI作为管理者,承载了很多.看代码:
class CDuiFrameWnd : public CWindowWnd, public INotifyUI
{
public:
virtual LPCTSTR GetWindowClassName() const
{
return _T("DUIMainFrame");
} virtual void Notify(TNotifyUI& msg)
{
if (msg.sType == _T("click"))
{
if (msg.pSender->GetName() == _T("btnClick"))
{
::MessageBox(NULL, _T("我是按钮"), _T("点击了按钮"), NULL);
}
}
} virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
LRESULT lRes = ; if( uMsg == WM_CREATE )
{
CControlUI *pWnd = new CButtonUI;
pWnd->SetName(_T("btnClick"));
pWnd->SetText(_T("My First DUI")); // 设置文字
pWnd->SetBkColor(0xFF808080); // 设置背景色 m_PaintManager.Init(m_hWnd); //主窗口句柄
m_PaintManager.AttachDialog(pWnd);
m_PaintManager.AddNotifier(this);
return lRes;
} if( m_PaintManager.MessageHandler(uMsg, wParam, lParam, lRes) )
{
return lRes;
} return __super::HandleMessage(uMsg, wParam, lParam);
} protected:
CPaintManagerUI m_PaintManager;
};
这就是我们的CDuiFrameWnd类,其中就有类CPaintManagerUI的对象m_PaintManager. 红色标记部分就是m_PaintManager处理绘图消息.如果没有被处理的其他消息,则给基类处理;
不过CPaintManager为我们处理了最重要的两个消息:命令消息 和 通知消息 ,请看代码:
case WM_NOTIFY:
{
LPNMHDR lpNMHDR = (LPNMHDR) lParam;
if( lpNMHDR != NULL ) lRes = ::SendMessage(lpNMHDR->hwndFrom, OCM__BASE + uMsg, wParam, lParam);
return true;
}
break;
case WM_COMMAND:
{
if( lParam == ) break;
HWND hWndChild = (HWND) lParam;
lRes = ::SendMessage(hWndChild, OCM__BASE + uMsg, wParam, lParam);
return true;
}
从代码上看,SendMessage()中的参数和MFC源码中是如出一辙,没什么两样,话不多说,上图,正所谓源码面前,了无秘语--j.j.hou
LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
// OnWndMsg does most of the work, except for DefWindowProc call
LRESULT lResult = ;
if (!OnWndMsg(message, wParam, lParam, &lResult))
lResult = DefWindowProc(message, wParam, lParam);
return lResult;
}
BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
LRESULT lResult = ;
union MessageMapFunctions mmf;
mmf.pfn = ;
CInternalGlobalLock winMsgLock;
// special case for commands
if (message == WM_COMMAND)
{
if (OnCommand(wParam, lParam))
{
lResult = ;
goto LReturnTrue;
}
return FALSE;
} // special case for notifies
if (message == WM_NOTIFY)
{
NMHDR* pNMHDR = (NMHDR*)lParam;
if (pNMHDR->hwndFrom != NULL && OnNotify(wParam, lParam, &lResult))
goto LReturnTrue;
return FALSE;
}
...//代码太多,请自己查看余下的部分,这里只挑出我要讲的部分,代码在wincore.cpp中
}
接着看
BOOL CWnd::OnCommand(WPARAM wParam, LPARAM lParam)
// return TRUE if command invocation was attempted
{
UINT nID = LOWORD(wParam);
HWND hWndCtrl = (HWND)lParam;
int nCode = HIWORD(wParam); // default routing for command messages (through closure table) if (hWndCtrl == NULL)
{
// zero IDs for normal commands are not allowed
if (nID == )
return FALSE; // make sure command has not become disabled before routing
CTestCmdUI state;
state.m_nID = nID;
OnCmdMsg(nID, CN_UPDATE_COMMAND_UI, &state, NULL);
if (!state.m_bEnabled)
{
TRACE(traceAppMsg, , _T("Warning: not executing disabled command %d\n"), nID);
return TRUE;
} // menu or accelerator
nCode = CN_COMMAND;
}
else //请注意这里的控件反射消息
{
// control notification
ASSERT(nID == || ::IsWindow(hWndCtrl)); if (_afxThreadState->m_hLockoutNotifyWindow == m_hWnd)
return TRUE; // locked out - ignore control notification // reflect notification to child window control
if (ReflectLastMsg(hWndCtrl))
return TRUE; // eaten by child // zero IDs for normal commands are not allowed
if (nID == )
return FALSE;
} #ifdef _DEBUG
if (nCode < && nCode != (int)0x8000)
TRACE(traceAppMsg, , _T("Implementation Warning: control notification = $%X.\n"),
nCode);
#endif return OnCmdMsg(nID, nCode, NULL, NULL);
}
再看通知消息的处理
BOOL CWnd::OnNotify(WPARAM, LPARAM lParam, LRESULT* pResult)
{
ASSERT(pResult != NULL);
NMHDR* pNMHDR = (NMHDR*)lParam;
HWND hWndCtrl = pNMHDR->hwndFrom; // get the child ID from the window itself
UINT_PTR nID = _AfxGetDlgCtrlID(hWndCtrl);
int nCode = pNMHDR->code; ASSERT(hWndCtrl != NULL);
ASSERT(::IsWindow(hWndCtrl)); if (_afxThreadState->m_hLockoutNotifyWindow == m_hWnd)
return TRUE; // locked out - ignore control notification // reflect notification to child window control
if (ReflectLastMsg(hWndCtrl, pResult))
return TRUE; // eaten by child AFX_NOTIFY notify;
notify.pResult = pResult;
notify.pNMHDR = pNMHDR;
return OnCmdMsg((UINT)nID, MAKELONG(nCode, WM_NOTIFY), ¬ify, NULL);
}
我复制了这么多代码干嘛?我想让大家引起注意的是我用蓝色加粗部分的ReflectLastMsg(hWndCtrl, pResult)函数,别着急,接着往下看:
BOOL PASCAL CWnd::ReflectLastMsg(HWND hWndChild, LRESULT* pResult)
{
// get the map, and if no map, then this message does not need reflection
CHandleMap* pMap = afxMapHWND();
if (pMap == NULL)
return FALSE; // check if in permanent map, if it is reflect it (could be OLE control)
CWnd* pWnd = (CWnd*)pMap->LookupPermanent(hWndChild);
ASSERT(pWnd == NULL || pWnd->m_hWnd == hWndChild);
if (pWnd == NULL)
{
#ifndef _AFX_NO_OCC_SUPPORT
// check if the window is an OLE control
CWnd* pWndParent = (CWnd*)pMap->LookupPermanent(::GetParent(hWndChild));
if (pWndParent != NULL && pWndParent->m_pCtrlCont != NULL)
{
// If a matching control site exists, it's an OLE control
COleControlSite* pSite = (COleControlSite*)pWndParent->
m_pCtrlCont->m_siteMap.GetValueAt(hWndChild);
if (pSite != NULL)
{
CWnd wndTemp(hWndChild);
wndTemp.m_pCtrlSite = pSite;
LRESULT lResult = wndTemp.SendChildNotifyLastMsg(pResult);
wndTemp.m_hWnd = NULL;
return lResult != ;
}
}
#endif //!_AFX_NO_OCC_SUPPORT
return FALSE;
} // only OLE controls and permanent windows will get reflected msgs
ASSERT(pWnd != NULL);
return pWnd->SendChildNotifyLastMsg(pResult);
}
BOOL CWnd::SendChildNotifyLastMsg(LRESULT* pResult)
{
_AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();
return OnChildNotify(pThreadState->m_lastSentMsg.message,
pThreadState->m_lastSentMsg.wParam, pThreadState->m_lastSentMsg.lParam, pResult);
}
BOOL CWnd::OnChildNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
#ifndef _AFX_NO_OCC_SUPPORT
if (m_pCtrlSite != NULL)
{
// first forward raw OCM_ messages to OLE control sources
LRESULT lResult = SendMessage(OCM__BASE+uMsg, wParam, lParam);
if (uMsg >= WM_CTLCOLORMSGBOX && uMsg <= WM_CTLCOLORSTATIC &&
(HBRUSH)lResult == NULL)
{
// for WM_CTLCOLOR msgs, returning NULL implies continue routing
return FALSE;
}
if (pResult != NULL)
*pResult = lResult;
return TRUE;
}
#endif return ReflectChildNotify(uMsg, wParam, lParam, pResult);
}
BOOL CWnd::ReflectChildNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
// Note: reflected messages are send directly to CWnd::OnWndMsg
// and CWnd::OnCmdMsg for speed and because these messages are not
// routed by normal OnCmdMsg routing (they are only dispatched) switch (uMsg)
{
// normal messages (just wParam, lParam through OnWndMsg)
case WM_HSCROLL:
case WM_VSCROLL:
#ifndef _WIN32_WCE
case WM_PARENTNOTIFY:
#endif // !_WIN32_WCE
case WM_DRAWITEM:
case WM_MEASUREITEM:
case WM_DELETEITEM:
case WM_VKEYTOITEM:
case WM_CHARTOITEM:
case WM_COMPAREITEM:
// reflect the message through the message map as WM_REFLECT_BASE+uMsg
return CWnd::OnWndMsg(WM_REFLECT_BASE+uMsg, wParam, lParam, pResult); // special case for WM_COMMAND
case WM_COMMAND:
{
// reflect the message through the message map as OCM_COMMAND
int nCode = HIWORD(wParam);
if (CWnd::OnCmdMsg(0, MAKELONG(nCode, WM_REFLECT_BASE+WM_COMMAND), NULL, NULL))
{
if (pResult != NULL)
*pResult = 1;
return TRUE;
}
}
break; // special case for WM_NOTIFY
case WM_NOTIFY:
{
// reflect the message through the message map as OCM_NOTIFY
NMHDR* pNMHDR = (NMHDR*)lParam;
int nCode = pNMHDR->code;
AFX_NOTIFY notify;
notify.pResult = pResult;
notify.pNMHDR = pNMHDR;
return CWnd::OnCmdMsg(0, MAKELONG(nCode, WM_REFLECT_BASE+WM_NOTIFY), ¬ify, NULL);
} // other special cases (WM_CTLCOLOR family)
default:
if (uMsg >= WM_CTLCOLORMSGBOX && uMsg <= WM_CTLCOLORSTATIC)
{
// fill in special struct for compatiblity with 16-bit WM_CTLCOLOR
AFX_CTLCOLOR ctl;
ctl.hDC = (HDC)wParam;
ctl.nCtlType = uMsg - WM_CTLCOLORMSGBOX;
//ASSERT(ctl.nCtlType >= CTLCOLOR_MSGBOX);
ASSERT(ctl.nCtlType <= CTLCOLOR_STATIC); // reflect the message through the message map as OCM_CTLCOLOR
BOOL bResult = CWnd::OnWndMsg(WM_REFLECT_BASE+WM_CTLCOLOR, , (LPARAM)&ctl, pResult);
if ((HBRUSH)*pResult == NULL)
bResult = FALSE;
return bResult;
}
break;
} return FALSE; // let the parent handle it
}
好了,我们终于到了最核心的部分,看到我用红色标记的部分没,这两个case就是WM_COMMAND和WM_NOTIFY消息的反射处理,我们可以看到MAKELONG(nCode, WM_REFLECT_BASE+WM_NOTIFY)..是不是和duilib的如出一辙?
这些消息最终会流向基类CCmdTarget去查询消息映射表!
反射消息处理路线,暂时先到这吧,东拉西扯了这么多,呵呵!
duilib学习领悟(2)的更多相关文章
- duilib学习领悟(3)
世上本无窗口,窗口只是人的眼睛和电脑屏幕及鼠标键盘相互操作后的视觉效果! 下面我们来看看我们之前讲过的代码: class CDuiFrameWnd : public CWindowWnd, publi ...
- duilib学习领悟(1)
学习duilib已经有一段时间,一直没时间写总结,今天得出空来,写写心得体会! 由于本人知识有限,若有错误地方,望批评指正.多谢.! 初识duilib 刚开始接触duilib的时候,觉的他好神奇,整个 ...
- duilib学习领悟(4)
使用duilib创建的主窗口绘制工作全都发生在一个 真实存在的主窗口句柄当中,这个绘制过程稍稍有些复杂,但再复杂也逃不过WM_PAINT消息,所有的绘制工作也都由这个WM_PAINT消息完成.在上几篇 ...
- DuiLib学习笔记(二) 扩展CScrollbar属性
DuiLib学习笔记(二) 扩展CScrollbar属性 Duilib的滚动条滑块默认最小值为滚动条的高度(HScrollbar)或者宽度(VScrollbar).并且这个值默认为16.当采用系统样式 ...
- Duilib学习笔记《06》— 窗体基类WindowImpBase
在前面的例子中我们发现,窗口都是继承CWindowWnd.INotifyUI,然后重载相关函数去实现.显然,我们发现窗口的创建流程实际上都是差不多的,主要只是在OnCreate加载的配置文件不同等等… ...
- Duilib学习笔记《05》— 消息响应处理
在Duilib学习笔记<04>中已经知道了如何将窗体显示出来,而如何处理窗体上的事件.消息呢? 一. 系统消息 窗体显示的时候我们就已经说了,窗体是继承CWindowWnd类的,对于窗体的 ...
- Duilib学习笔记《04》— 窗体显示
在前面已经了解了duilib控件以及界面布局相关内容,接下来就要考虑该如何将xml中描述的布局通过界面展现出来.实际上在 Duilib学习笔记<01> 中我们已经简单提到过基本的流程及元素 ...
- Duilib学习笔记《03》— 控件使用
在前面已经对duilib有个一个基本的了解,并且创建了简单的空白窗体.这仅仅只是一个开始,如何去创建一个绚丽多彩的界面呢?这就需要一些控件元素(按钮.文本框.列表框等等)来完善. 一. Duilib控 ...
- duilib学习 --- 360demo 学习
我想通过360demo的学习,大概就能把握duilib的一般用法,同时引申出一些普遍问题,和普遍解决方法.并在此分享一些链接和更多内容的深入学习..... 原谅我是一个菜鸟,什么都想知道得清清楚楚.. ...
随机推荐
- layer弹出层,结合art-template实现弹出编辑
模板 <!-- 模板 --> <script id="render-tpl" type="text/html"> <div cla ...
- (四)Resquest 知识点总结 (来自那些年的笔记)
目录 URL和URI的区别 获取URL.URI 什么是HttpServletResquest 获取请求头中字段的内容 获取请求信息的数据 将客户机的请求变为一个流返回 常用的方法 request乱码问 ...
- python--接口自动化测试(接口状态)
本节开始,开始介绍python的接口自动化测试,首先需要搭建python开发环境,到https://www.python.org/下载python 版本直接安装就以了,建议 下载python2.7.1 ...
- 20191011-构建我们公司自己的自动化接口测试框架-ProVar模块
ProVar模块主要定义测试数据所在目录,以及定义变量和测试数据excel里面的column对应这样后续在进行excel操作的时候直接使用变量即可进行操作,后期excel的column有增删的时候,修 ...
- Vue起飞前的准备
Vue起飞前的准备 一.什么是ECMAScript,以及es6的诞生? 1997年 ECMAScript 1.0 诞生 1999年12月 ECMAScript 3.0诞生,它 是一个巨大的成功,在业界 ...
- 怎样给回调函数绑定this
在三种绑定this的方法中, Function.prototype.call() 和 Function.prototye.apply() 都是会立即执行该函数的, 但回调函数是不能立即执行的, 它只是 ...
- Unity UGUI动态生成控件
一. 首先你得先清楚RectTransform组件的一些程序控制 1. 先得到UGUI控件上面的RectTransform组件 RectTransform rtr = gameObject.GetCo ...
- Asp.Net Mvc项目添加WebApi
1.添加一个WebApi 空项目 2.删除WebApi项目下的 Global.asax 文件,因为我们要把WebApi项目整合到Mvc项目中去,全局只需要一个Global 3.修改 WebApi 项目 ...
- Nginx实现同一端口HTTP跳转HTTPS
小目标:在只监听一个端口的情况下,将http访问跳转为https. 一般情况下http协议使用80端口,https协议443端口.要实现http强制转https是非常简单的事,随便都可以找到很多方案. ...
- 冒泡(bubblesort)、选择排序、插入排序、快速排序
冒泡排序(bubblesort) 特点:通过换位置的方式,一直向上冒泡 package main import "fmt" func bubbleSortAsc(arrayA [] ...