一.Duilib消息系统
Windows是靠消息来驱动的,所有的动作/事件都被映射成了一个消息,熟悉Duilib整个消息处理和流向,掌握消息系统的来龙去脉将有助于我们更好的追踪和定位问题!
二.Win32常识
1. 窗体
Windows窗体其实就是屏幕上的一个矩形区域,可以响应用户的输入和操作,以文本或者图形的格式来展示!
2. 句柄
句柄其实是Windows上面用来标识一个实体对象的一个ID,比如:文件句柄(标识打开的文件对象),窗体句柄(标识一个窗体对象),内核对象句柄(信号量,互斥量,事件等)
3. 消息
Windows是靠消息来驱动的,所有的动作/事件都被映射成一个消息,通过消息我们可以知晓当前发生的动作或者事件!
4. Windows消息模型
5. 消息队列
1. 系统队列:操作系统维护的消息队列!
2. 应用队列:GUI应用程序的消息队列!
3. 消息泵:获取消息-加工消息-消息分发!
6. 消息类型
同步消息:不放置队列,直接交付窗口过程处理等待处理!PostMessage
异步消息:放置队列中,不等待异步处理!SendMessage
7. 消息处理
窗口过程:其实就是消息处理回调函数,将来会把消息交付给对应窗体的窗口过程!
三. Duilib消息系统
1. Duilib 窗体类(CWindowWnd)
//CWindowWnd 其实就是对Win32窗体的封装
//注册-创建-显示等窗口相关操作,用来标识一个Win32窗体的类
HWND CWindowWnd::Create(HWND hwndParent, LPCTSTR pstrName, DWORD dwStyle, DWORD dwExStyle, int x, int y, int cx, int cy, HMENU hMenu)
{
//关注1:RegisterWindowClass注册窗体
if( GetSuperClassName() != NULL && !RegisterSuperclass() ) return NULL;
if( GetSuperClassName() == NULL && !RegisterWindowClass() ) return NULL;
//关注2:CreateWindowEx 创建窗体
m_hWnd = ::CreateWindowEx(dwExStyle, GetWindowClassName(), pstrName, dwStyle, x, y, cx, cy, hwndParent, hMenu, CPaintManagerUI::GetInstance(), this/*注意看这里,它把当前窗体对象塞了进去*/);
ASSERT(m_hWnd!=NULL);
return m_hWnd;
}
//注册窗体
bool CWindowWnd::RegisterWindowClass()
{
WNDCLASS wc = { 0 };
wc.style = GetClassStyle();
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hIcon = NULL;
wc.lpfnWndProc = CWindowWnd::__WndProc;//关注3:指定窗口过程:消息处理函数!
wc.hInstance = CPaintManagerUI::GetInstance();
wc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = NULL;
wc.lpszMenuName = NULL;
wc.lpszClassName = GetWindowClassName();
ATOM ret = ::RegisterClass(&wc);
ASSERT(ret!=NULL || ::GetLastError()==ERROR_CLASS_ALREADY_EXISTS);
return ret != NULL || ::GetLastError() == ERROR_CLASS_ALREADY_EXISTS;
}
//窗口过程
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);
//这里取出创建窗体时,塞进去的窗体对象this
pThis = static_cast<CWindowWnd*>(lpcs->lpCreateParams);
//将句柄设置到窗体类对象里面
pThis->m_hWnd = hWnd;
//将窗体类CWindowWnd对象塞进了对应句柄GWLP_USERDATA
::SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LPARAM>(pThis));
//我们在创建窗体时塞进去的窗体类对象this就和对应的窗体句柄绑定了起来
}
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 ) {
//如果对应句柄存在窗体类对象,就将消息传递给CWindowWnd
//这里消息就从创建窗体的窗口过程流向了CWindowWnd HandleMessage
return pThis->HandleMessage(uMsg, wParam, lParam);
}
else {
return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
}
}
总结:创建窗体主要干了以下几件事!
1.注册窗体->指定窗口过程CWindowWnd::__WndProc
2.创建窗体->将当前窗体类对象this塞了进去!
3.窗口过程:
取出this->将this塞进句柄的GWLP_USERDATA(将窗体类对象CWindowWnd和对应窗体句柄绑定起来)
消息流入窗口过程后->流向CWindowWnd pThis->HandleMessage(uMsg, wParam, lParam);
2.Duilib消息循环
//主程序Duilib消息泵:
CPaintManagerUI::MessageLoop();
int CPaintManagerUI::MessageLoop()
{
MSG msg = { 0 };
while( ::GetMessage(&msg, NULL, 0, 0) ) {
if( !CPaintManagerUI::TranslateMessage(&msg) ) {
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
return msg.wParam;
}
总结:消息循环,Duilib预处理拦截!
Duilib的消息循环其实就是Win32消息循环,只不过多了分发前也就是消息流向窗口过程前,流向了Duilib
CPaintManagerUI::TranslateMessage(&msg) 这里蕴含了我们后续对消息流入窗口过程,流入CWindowWnd
HandleMessage前,进行拦截的方式(继承IMessageFilterUI,重写MessageHandler,将当前窗体对象加入
提前拦截数组m_aPreMessageFilters中,消息分发前,会先交付给Duilib一次预处理逻辑中)
3. Duilib消息处理
//消息正常DispatchMessage分发->消息也就自然流向了CWindowWnd::__WndProc->交付给对应句柄的窗体类对象this的HandleMessage去处理!
//我们重写CWindowWnd HandleMessage
LRESULT CBaseDuilibWnd::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
//Windows原生消息,我们是否需要处理,如果需要就处理!
LRESULT lRes = 0;
BOOL bHandled = TRUE;
switch (uMsg)
{
case WM_CREATE:
lRes = OnCreate(uMsg, wParam, lParam, bHandled);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
bHandled = FALSE;
}
if (bHandled)
{
return lRes;
}
//我们不处理原生消息,让消息流向Duilib去处理!
if (m_pUI.MessageHandler(uMsg, wParam, lParam, lRes))
return lRes;
//都不处理,就流向默认的windows默认处理过程!
return CWindowWnd::HandleMessage(uMsg, wParam, lParam);
}
//Duilib消息处理:这里面Duilib对很多消息都进行了处理并且代码很多!
//我们重点关注:
//case WM_PAINT:定位,渲染都在这里面,用了递归机制!
//case WM_LBUTTONUP:这个举个例子来看原生消息流向Duilib后,Duilib进行自身逻辑处理后如何传递逻辑控件的点击事件!
m_pUI.MessageHandler(uMsg, wParam, lParam, lRes)
/*
{
case WM_LBUTTONUP:
{
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
m_ptLastMousePos = pt;
if( m_pEventClick == NULL ) break;
ReleaseCapture();
TEventUI event = { 0 };
event.Type = UIEVENT_BUTTONUP;
event.pSender = m_pEventClick;
event.wParam = wParam;
event.lParam = lParam;
event.ptMouse = pt;
event.wKeyState = (WORD)wParam;
event.dwTimestamp = ::GetTickCount();
// By daviyang35 at 2015-6-5 16:10:13
// 在Click事件中弹出了模态对话框,退出阶段窗口实例可能已经删除
// this成员属性赋值将会导致heap错误
// this成员函数调用将会导致野指针异常
// 使用栈上的成员来调用响应,提前清空成员
// 当阻塞的模态窗口返回时,回栈阶段不访问任何类实例方法或属性
// 将不会触发异常
CControlUI* pClick = m_pEventClick;
m_pEventClick = NULL;
pClick->Event(event);
if( OnEvent(&event) ) DoEvent(event);
DoEvent->Activate()
m_pManager->SendNotify(this, DUI_MSGTYPE_CLICK);
}
break;
}
4. Duilib事件
/*通过上面Duilib对windows消息处理,事件被传输到CPaintManagerUI SendNotify*/
void CPaintManagerUI::SendNotify(CControlUI* pControl, LPCTSTR pstrMessage, WPARAM wParam /*= 0*/, LPARAM lParam /*= 0*/, bool bAsync /*= false*/, bool bEnableRepeat /*= true*/)
{
//Duilib事件封装
TNotifyUI Msg;
Msg.pSender = pControl;
Msg.sType = pstrMessage;
Msg.wParam = wParam;
Msg.lParam = lParam;
SendNotify(Msg, bAsync, bEnableRepeat);
}
void CPaintManagerUI::SendNotify(TNotifyUI& Msg, bool bAsync /*= false*/, bool bEnableRepeat /*= true*/)
{
Msg.ptMouse = m_ptLastMousePos;
Msg.dwTimestamp = ::GetTickCount();
if( m_bUsedVirtualWnd )
{
Msg.sVirtualWnd = Msg.pSender->GetVirtualWnd();
}
if( !bAsync ) {
// Send to all listeners
if( Msg.pSender != NULL ) {
if( Msg.pSender->OnNotify ) Msg.pSender->OnNotify(&Msg);
}
//重点看这个:其实Duilib的消息是通过Notify发送给上层
//所以如果想处理Duilib消息只需要m_aNotifiers添加接受者,重写Notify
//这也就是我们之前提过的:继承INotifyUI接口,重写Notify方法就可以收到Duilib消息
for( int i = 0; i < m_aNotifiers.GetSize(); i++ ) {
static_cast<INotifyUI*>(m_aNotifiers[i])->Notify(Msg);
}
}
else {
if( !bEnableRepeat ) {
for( int i = 0; i < m_aAsyncNotify.GetSize(); i++ ) {
TNotifyUI* pMsg = static_cast<TNotifyUI*>(m_aAsyncNotify[i]);
if( pMsg->pSender == Msg.pSender && pMsg->sType == Msg.sType) {
if (m_bUsedVirtualWnd) pMsg->sVirtualWnd = Msg.sVirtualWnd;
pMsg->wParam = Msg.wParam;
pMsg->lParam = Msg.lParam;
pMsg->ptMouse = Msg.ptMouse;
pMsg->dwTimestamp = Msg.dwTimestamp;
return;
}
}
}
TNotifyUI *pMsg = new TNotifyUI;
if (m_bUsedVirtualWnd) pMsg->sVirtualWnd = Msg.sVirtualWnd;
pMsg->pSender = Msg.pSender;
pMsg->sType = Msg.sType;
pMsg->wParam = Msg.wParam;
pMsg->lParam = Msg.lParam;
pMsg->ptMouse = Msg.ptMouse;
pMsg->dwTimestamp = Msg.dwTimestamp;
m_aAsyncNotify.Add(pMsg);//加入到异步消息Notify
PostAsyncNotify();
}
}
总结:
Duilib的事件最终传递给static_cast<INotifyUI*>(m_aNotifiers[i])->Notify(Msg);
所以,我们只需要继承INotifyUI,重写Notify,加入到这个Duilib事件监听数组中,创建窗体时m_pUI.AddNotifier(this);即可!
Duilib消息系统概述:
1. 获取消息
2. Duilib消息分发预处理
2.1 具体细节:CPaintManagerUI::TranslateMessage(&msg)
2.2 实现拦截:继承IMessageFilterUI,重写MessageHandler,创建窗体时加入到消息预处理数组中:m_pUI.AddPreMessageFilter(this);
3. 加工消息
4. 分发消息
4.1 消息分发!
4.2 创建窗体时指定的窗体过程CWindowWnd __WndProc!
4.3 Duilib 窗体类处理函数CWindowWnd HandleMessage!
4.4 自己处理,Duilib处理(接收Duilib事件:继承INotifyUI,重写Notify,创建窗体时加入到Duilib事件接收数组中:m_pUI.AddNotifier(this);),系统默认处理!
以上就是Duilib消息系统的一个大致流程,更多细节需要调试跟踪具体代码!
作者: 祁莫问.