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的一般用法,同时引申出一些普遍问题,和普遍解决方法.并在此分享一些链接和更多内容的深入学习..... 原谅我是一个菜鸟,什么都想知道得清清楚楚.. ...
随机推荐
- mysql--简单操作
一.数据库的基本操作 查看当前所有存在的数据库 show databases; //mysql 中不区分大小写.(databank 是之前创建的) 创建数据库 create database data ...
- Android之Systrace
1 简介 https://www.lizenghai.com/archives/26061.html 2 用法 用命令行启动Systrace抓取采样:<Capture a system trac ...
- (七)mysql 记录长度
MySQL记录长度 MySQL中规定:任何一条记录最长不能超过 65535个字节: 这句话,就表明 varchar 永远也达不到理论值 : varchar 的实际存储长度能达到多少,是需要看具体的字符 ...
- asp.net core-14.JWT认证授权 生成 JWT Token
源码下载 语言组织能力不好 ,看这个 视频 用visual studio code打开文件,运行dotnet watch run 之后在postman里面去访问 拿到Token后
- jq勾选
1.取消勾选 $("box").attr("checked", false); 2.勾选 $("kbox").attr("chec ...
- NIPS 2018 | 程序翻译新突破:UC伯克利提出树到树的程序翻译神经网络
NIPS 2018 | 程序翻译新突破:UC伯克利提出树到树的程序翻译神经网络 机器之心 已认证的官方帐号 49 人赞同了该文章 选自arXiv,作者:Xinyun Chen.Chang Liu. ...
- 在JAVA中如何获取当前源文件名以及代码的行号
在最近经历中,遇见了这样一个问题,如何获取当前源文件名以及代码的行号,只是了解到C语言有预定义宏__FILE__.__LINE__,它们在预处理的时候都已经确定好了,但是在JAVA中应该怎么获取输出呢 ...
- 【转载】在使用JDBC连接MySql时报错:You must configure either the server or JDBC driver (via the serverTimezone configuration property) to use a more specifc time zone value if you want to utilize time zone support
在使用JDBC连接MySql时报错:You must configure either the server or JDBC driver (via the serverTimezone config ...
- 私服nexus的权限问题,带admin和带view的区别
admin和view的区别只找到了这个解释: https://blog.csdn.net/tian_111222333/article/details/100159983 最终得出答案,我只需要给他们 ...
- 前端知识总结--css用div画环形圆
如何用最少的div画最多的环形?如下图所示最少需要多少个div? 暂时想到的利用div的边框.内外阴影及befor和after的伪元素实现 以下代码可以实现上图效果: <style> di ...