首页 > 代码库 > 飞鸽传书源码分析二消息机制
飞鸽传书源码分析二消息机制
转载请注明出处:http://blog.csdn.net/mxway/article/details/40225725
本篇文章是在飞鸽传书2.06源码的基础的分析的。
飞鸽传书的消息大致可分为三类:普通窗口类(后面以TMainWin为例进行分析)消息,对话框类(后面以TSendDlg为例进行分析)消息,对话框控件(后面以TEditSub为例进行分析)消息。这三类消息先合后分,这三类窗口设置的消息处理函数都是TApp::WinProc,在TApp::WinProc函数中再分发给各自的消息处理函数
图1:消息处理
首先看下三种窗口的继承关系
图2:TMainWin类的继承关系
图3:TsendDlg继承关系
图4:TEditSub继承关系
从上面的三个图中可以看出,TWin是所有窗口类的根类。即在飞鸽传书的源码中,所有的窗口都需要继承TWin类。飞鸽传书所有消息都是经过TApp::WinProc传到TWin::WinProc或其子类重写的WinProc函数中。
TWin::WinProc部分源码如下:
LRESULT TWin::WinProc(UINT uMsg, WPARAM wParam, LPARAM lParam) { BOOL done = FALSE; LRESULT result = 0; switch(uMsg) { case WM_CREATE: done = EvCreate(lParam); break; case WM_MOUSEMOVE: done = EvMouseMove((UINT)wParam, MAKEPOINTS(lParam)); break; case WM_LBUTTONUP: case WM_RBUTTONUP: case WM_NCLBUTTONUP: case WM_NCRBUTTONUP: case WM_LBUTTONDOWN: case WM_RBUTTONDOWN: case WM_NCLBUTTONDOWN: case WM_NCRBUTTONDOWN: case WM_LBUTTONDBLCLK: case WM_RBUTTONDBLCLK: case WM_NCLBUTTONDBLCLK: case WM_NCRBUTTONDBLCLK: done = EventButton(uMsg, wParam, MAKEPOINTS(lParam)); break; default: if (uMsg >= WM_USER && uMsg < 0x7FFF || uMsg >= 0xC000 && uMsg <= 0xFFFF) result = done = EventUser(uMsg, wParam, lParam); break; } return done ? result : DefWindowProc(uMsg, wParam, lParam); }从上面可以看出TWin的WM_CREATE消息交由EvCreate函数进行处理,WM_MOUSEMOVE消息交由EvMouseMove处理,鼠标左右键或双击等事件交由EventButton处理,对于用户自定义的事件交由EventUser处理。
一、TMainWin的消息处理
在上篇文章飞鸽传书程序启动过程中提到过TMsgApp::InitWindow,在这个方法中有下面的一些代码
void TMsgApp::InitWindow(void) { wc.lpfnWndProc = TApp::WinProc; ... wc.lpszClassName = class_name; ... mainWnd = new TMainWin(nicAddr, port_no); mainWnd->Create(class_name, IP_MSG, WS_OVERLAPPEDWINDOW | (IsNewShell() ? WS_MINIMIZE : 0)); }这个就是创建飞鸽传书主窗口的函数,并设置主窗口的消息响应函数为TApp:WinProc。消息处理函数出来,那么消息循环及消息分发处理在哪呢?回到上一篇文章中飞鸽传书的程序启动过程。在TApp::Run函数中调用TApp::InitApp,然后由于虚函数的原因调用TMsgApp::InitWindow函数。调用完TMsgApp::InitWindow后又回到TApp::Run方法中继续执行后面的代码,TApp::Run后面的代码就是进行消息循环及分发。下面是TApp:Run函数的源码
int TApp::Run(void) { MSG msg; InitApp(); InitWindow(); while (::GetMessage(&msg, NULL, 0, 0)) { if (PreProcMsg(&msg)) continue; ::TranslateMessage(&msg); ::DispatchMessage(&msg); } return msg.wParam; }TMainWin的消息处理函数及消息分发都设置好了,我们设置的消息处理函数是TApp::WinProc。那么TApp::WinProc是怎么识别出是TMainWin窗口,并把消息处理函数转到TMainWin中去做呢?先看下TApp::WinProc的源码
LRESULT CALLBACK TApp::WinProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { TWin *win = SearchWnd(hWnd); if (win) return win->WinProc(uMsg, wParam, lParam); if ((win = preWnd) != NULL) { preWnd = NULL; AddWinByWnd(win, hWnd); return win->WinProc(uMsg, wParam, lParam); } return DefWindowProc(hWnd, uMsg, wParam, lParam); }飞鸽传书将所有需要进行消息处理的窗口都加到TApp的wndArray成员变量中,Search方法是从wndArray中找到中找到窗口句柄为hWnd的TWin对象返回。现在找到TMainWin的对象win,调用win->WinProc(uMsg,wParam,lParam)。由于TMainWin继承TWin类,TWin类的WinProc是虚函数,所以win->WinProc相当于调用TMainWin的WinProc方法,至此TMainWin的消息通过TApp::WinProc再回到TMainWin类中进行处理。注:为了简化问题,if(win=preWnd)!=NULL)这段代码没有进行分析,这段代码实际是对TMainWin窗口第一次消息处理进行的特殊判断。
二、TSendDlg消息处理
对话框分为模态对话框及非模态对话框。创建非模态对话框使用win API的CreateDialog(),创建模态对话框使用DialogBox()。从图2及图3中可以看出来,TMainWin及TDlg都继承TWin类,那么在调用Create函数如何区别是普通窗口或是创建对话框呢?
BOOL TWin::Create(LPCSTR className, LPCSTR title, DWORD style, DWORD exStyle, HMENU hMenu) { if (className == NULL) className = TApp::defaultClass; TApp::AddWin(this); if ((hWnd = ::CreateWindowEx(exStyle, className, title, style, rect.left, rect.top, rect.right, rect.bottom, parent ? parent->hWnd : NULL, hMenu, TApp::hI, NULL)) == NULL) return TApp::DelWin(this), FALSE; else return TRUE; }
BOOL TDlg::Create(HINSTANCE hInstance) { TApp::AddWin(this); if ((hWnd = ::CreateDialog(hInstance ? hInstance : TApp::hI, resId ? (LPCSTR)resId : resName, parent ? parent->hWnd : NULL, (DLGPROC)TApp::WinProc)) == NULL) return TApp::DelWin(this), FALSE; else return TRUE; }TWin的Create是虚函数,TDlg继承TWin并重写了Create函数。创建TDlg的对象最后就会调用CreateDialog创建对话框,对于其它的继承TWin并且没有重写Create函数的类调用的就是TWin的Create创建出普通的窗口。在TDlg::Create中的CreateDialog中设置的对话框的消息处理函数为TApp::WinProc方法,在这之前调用TApp::AddWin(this)将创建的对话框对象加到TApp的wndArray成员变量中。与TMainWin窗口的消息处理相似,对话框的消息先经过TApp::WinProc。然后再传给TDlg中进行处理。还有一个很重要的问题:对话框有WM_INITDIALOG等消息是普通窗口不具有的,怎么处理这些消息呢。通过上面的分析我们知道是通过重写TWin类的WinProc虚函数。
LRESULT TDlg::WinProc(UINT uMsg, WPARAM wParam, LPARAM lParam) { LRESULT result = 0; switch (uMsg) { case WM_INITDIALOG: return EvCreate(lParam); ... case WM_ENDSESSION: EvEndSession((BOOL)wParam, (BOOL)lParam); return 0; ... } }三、TEditSub窗口消息处理
在TSendDlg对话框中有一个文本框控件,该控件有一个功能就是在文本框中双击的时候选中文本框中的内容。TSendDlg对话框中只有关于对话框的消息处理,那么对话框中文本框的消息是如何进行处理的
在TSendDlg的EvCreate函数中有如下代码
BOOL TSendDlg::EvCreate(LPARAM lParam) { ... editSub.CreateByWnd(GetDlgItem(SEND_EDIT)); ... }TEditSub继承TSubClass类,没有对CreateByWnd进行重写
BOOL TSubClass::CreateByWnd(HWND _hWnd) { TApp::AddWinByWnd(this, _hWnd); return (oldProc = (WNDPROC)::SetWindowLong(_hWnd, GWL_WNDPROC, (LONG)TApp::WinProc)) ? TRUE : FALSE; }这个功能是设置TEditSub的消息处理函数为TApp::WinProc。与前面两种窗口的处理过程相似,对于TEditSub消息先传到TApp::WinProc中,再传到TEditSub的窗口类中进行处理。注:使用SetWindowLong方法设置GWL_WNDPROC为TApp::WinProc,在TApp::WinProc(实际上是TEditSub窗口类的消息处理函数)中没有处理的消息,必须使用CallWindowProc函数调用SetWindowLong之前的消息处理函数,如果不使用CallWindowProc即使使用WIN API的DefWindowProc也会出现如文本框无法显示的问题。
TEditSub中重写了EventButton函数。
BOOL TEditSub::EventButton(UINT uMsg, int nHitTest, POINTS pos) { switch (uMsg) { case WM_LBUTTONDBLCLK: PostMessage(WM_EDIT_DBLCLK, 0, 0); break; } return FALSE; }所以在鼠标双击文本框后就会调用PostMessage(WM_EDIT_DBLCLK,0,0);触发一条自定义的消息。该自定义的消息的处理就是完成选定文本框内容的功能。
飞鸽传书源码分析二消息机制