消息
消息分类:
队列消息和非队列消息:
Windows维护一个系统消息队列(System message queue),每个GUI线程有一个线程消息队列(Thread message queue)。鼠标、键盘事件由鼠标或键盘驱动程序转换成输入消息并把消息放进系统消息队列,例如WM_MOUSEMOVE、WM_LBUTTONUP、WM_KEYDOWN、WM_CHAR等等。Windows每次从系统消息队列移走一个消息,确定它是送给哪个窗口的和这个窗口是由哪个线程创建的,然后,把它放进窗口创建线程的线程消息队列。线程消息队列接收送给该线程所创建窗口的消息。线程从消息队列取出消息,通过Windows把它送给适当的窗口过程来处理。
除了键盘、鼠标消息以外,队列消息还有WM_PAINT、WM_TIMER和WM_QUIT。这些队列消息以外的绝大多数消息是非队列消息。
系统消息和应用程序消息:
从消息的来源来看,可以分为:系统定义的消息和应用程序定义的消息。(控件改变时自己也会向系统发送消息,如invalidate) 系统消息ID的范围是从0到WM_USER-1,或0X80000到0XBFFFF;应用程序消息从WM_USER(0X0400)到0X7FFF,或0XC000到0XFFFF;WM_USER到0X7FFF范围的消息由应用程序自己使用;0XC000到0XFFFF范围的消息用来和其他应用程序通信,为了ID的唯一性,使用::RegisterWindowMessage来得到该范围的消息ID。static UINT WM_add_event_ok = RegisterWindowMessage(_T("User"));
窗口消息,命令消息,控件通知消息:根据处理过程的不同,可以分为三类:
窗口消息:一般以WM_开头,如WM_CREATE, WM_SIZE, WM_MOUSEMOVE等标准的Windows消息, 用于窗口相关的事件通知,窗口消息将由系统分配到该窗口的窗口过程处理。
命令消息 (WM_COMMAND):一种特殊的窗口消息,它从一个窗口发送到另一个窗口以处理来自用户的请求,通常是从子窗口发送到父窗口,例如,点击按钮时,按钮的父窗口会收到WM_COMMAND消息,用以通知父窗口按钮被点击,经测试:子窗口向父窗口发送WM_COMMAND消息,或者称为父窗口会收到WM_COMMAND消息,操作系统并不是通过将WM_COMMAND消息放入到父窗口的消息队列中去,而是直接调用了父窗口的窗口过程,以 WM_COMMAND 为消息标识参数(UINT uMsg),实现这个功能的API函数正是: LRESULT DispatchMessage(const MSG *lpmsg);
控件通知消息:WM_NOTIFY消息,当用户与控件交互(Edit, Button...)时,通知消息会从控件窗口发送到父窗口,这种消息的目的不是为了处理用户命令,而是为了让父窗 口能够适时的改变控件。
SendMessage和PostMessage:
BOOL PostMessage(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam)
{ //把消息放到窗口消息队列中 return TRUE; }
LRESULT SendMessage(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam)
{ LRESULT ret=WndProc(hWnd,Msg,wParam,lParam); return ret; }
while(1)
{ GetMessage(&msg,NULL,0,0) ; TranslateMessage(&msg); DispatchMessage(&msg); }
LRESULT CALLBACK WndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{ switch(message)
case WM_CLOSE:
case WM_PASTE:
default: return DefWindowProc(hWnd,message,wParam,lParam);}
BOOL GetMessage(LPMSG lpMsg,HWND hWnd,wMsgFilterMin,wMsgFilterMax)
{ 堵塞结构,系统如果发现消息队列里没有消息,就让代码停下来,整个程序也就没有“运动了”(后台程序往往处于这种情况)不停地查看消息队列里是否有消息,如果没有就停在这里,不往下执行。如果发现有消息了,比如系统发现鼠标点击了窗口,会把WM_LBUTTONDOWN放到消息队列里,这时就可以从队列中取出WM_LBUTTONDOWN消息,继续往下执行lpMsg->message=WM_LBUTTONDOWN;
return TRUE;//GetMessage基本都是返回TRUE,否则while循环就推出,程序也就结束了}
PreTranslateMessage
我们可以PreTranslateMessage来对消息预处理,该用的用,不该用的不用。所以说,用SendMessage发送的消息是不能用PreTranslateMessage来预处理的。而Post的就可以,因为它要经过“第三者”(消息队列)。
重载窗口的PreTranslateMessage函数可以使用post或者send将消息发到指定窗口(通过窗口句柄)在指定窗口进行相应(指定窗口要有相应的消息处理函数),返回TRUE。
自定义消息发送和接收
第一步:定义消息。
#define WM_MY_MESSAGE (WM_USER+100)
第二步:实现消息处理函数。该函数使用WPRAM和LPARAM参数并返回LPESULT。
LRESULT CMainFrame::OnMyMessage(WPARAM wParam, LPARAM lParam)
第三步:在类头文件的AFX_MSG块中说明消息处理函数:
afx_msg LRESULT OnMyMessage(WPARAM wParam, LPARAM lParam);
第四步:在用户类的消息块中,使用ON_MESSAGE宏指令将消息映射到消息处理函数中。
ON_MESSAGE(WM_MY_MESSAGE, OnMyMessage)
如果用户需要一个定义整个系统唯一的消息,可以调用SDK函数RegisterWindowMessage定义消息:
static UINT WM_MY_MESSAGE=RegisterWindowMessage("User");
并使用ON_REGISTERED_MESSAGE宏指令取代ON_MESSAGE宏指令,其余步骤同上。
可以在相应类中的函数中调用函数PostMessage或SendMessage发送消息PoseMessage(WM_MY_MESSAGE,O,O);
如果向其他进程发送消息可通过如下方法发送消息:
DWORD result; SendMessageTimeout(wnd->m_hWnd, WM_MY_MESSAGE, 0, 0,
SMTO_ABORTIFHUNG|SMTO_NORMAL,TIMEOUT_INTERVAL,&result);
以避免其它进程如果被阻塞而造成系统死等状态。
可以通过获取某个类的指针而向这个类发送消息,而自定义消息的各种动作则在这个类中定义,这样就可以自由自在的向其它类发送消息了。
在主框架类中向视类发送消息:
ON_REGISTERED_MESSAGE(WM_MY_MESSAGE,OnMyMessage) //定义消息映射
LRESULT CMessageView::OnMyMessage(WPARAM wParam, LPARAM lParam)
void CMainFrame::OnTest() { CView * active = GetActiveView();
if(active != NULL) active->PostMessage(WM_MY_MESSAGE,0,0); }
子类化窗体
窗口子类化技术最大的特点就是能够截取Windows的消息。一旦用户自定义的窗口函数截取了传向原窗口函数的消息,就可以对被截取的消息进行如下处理:
将其传给原来的窗口函数。这是对大多数消息应该采取的措施,因为子类通常只对原来的窗口特性作少量的改动截取该消息,阻止其向原窗口函数发送。
修改该消息,修改完毕以后再向原窗口函数发送。
Windows SDK提供了一些设计好的窗口类,如EDIT、LISTBOX、TREEVIEW等。通过截取这些通用窗口类的消息,用户程序可以为它们添加新的特性,改善其外观,扩充其功能。子类化的优点主要体现在以下两个方面:首先,它不需要创建新的窗口类,不需要了解一个窗口的窗口过程。这在原来的窗口函数是由别人编写,而且创建过程不可见的情况下非常有用;其次,子类化比较容易实现,因为所有要做的工作仅仅就是写一个窗口函数
MFC为广大编程者提供了很多功能丰富的窗口类,如果能在这些通用窗口类的基础上进行子类化的话,将会给编程者带来很多便利。下面举一个例子来说明MFC编程中的子类化是多么的简单易行。该例完成上面提到的在编辑控件只接受非数字字符输入的功能。实现这个子类化的基本步骤和相关代码如下:
(1)利用AppWziard创建一个基于对话框的程序SubClassing。
(2)对话框中的新控件的ID为IDC_EDIT。
(3)从CEdit类派生一个新的窗口类,新窗口的窗口类叫CNoNumEdit。截取CNoNumEdit类的WM_CHAR消息,在OnChar函中完成忽略任何数字的输入的处理。实现代码如下:
void CNoNumEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
TCHAR ch=nChar;
if(ch>=_T('0')&&ch<=_T('9'))
{
AfxMessageBox(("请不要输入数字!"),MB_OK); //当输入数字字符时将被忽略,并显示警告信息
return;
}
CEdit::OnChar(nChar, nRepCnt, nFlags);//输入为非数字字符时调用原处理函数
}
(4)在对话框窗口类CSubClassingDlg的定义中添加变量CNoNumEdit ed。在CSubClassingDlg::OnInitDialog()函数中调用CWnd类的成员函数SubClassWindow进行子类化。
ed.SubclassWindow(GetDlgItem(IDC_EDIT)->m_hWnd);
(5) 在对话框窗口类CsubClassing的OnDestroy中调用ed.UnSubClassWindow()执行窗口类的反子类化。
现在可以编译执行这个程序了,当用户输入数字字符时将会忽略该输入,并显示警告信息。
子类化的概念是针对一个已经创建的窗口来谈的,所以修改窗口函数是在窗口创建之后进行的,在窗口创建期间的消息无法捕获,也就无法处理。另外有些窗口的特性与窗口类本身的属性有关。比如如果一个窗口类没有CS_DBLCLKS属性的话,那么要想通过子类化这些窗口达到处理WM_LBUTTONDBLCLK消息的目的是无法实现的。针对子类化的以上局限性,可以使用超类化(SuperClassing)技术。

浙公网安备 33010602011771号