下载本文所附源代码
应用程序中键盘消息从WM_KEYFIRST到WM_KEYLAST那么多,但我们知道最原始的键盘消息只有两个那就是WM_KEYDOWN,WM_KEYUP,在键盘钩子中我们能截获的也就这两个消息,那其它消息是从何产生的,如何动作的呢?下面我们就WM_CHAR消息来分析一下。
一个键按下后,便会有一个或多个WM_KEYDOWN消息产生,这些消息会从系统消息队列发送到目标窗口的线程的消息队列中,这样在对应线程处理消息时便会对此消息处理,并在处理的过程中产生一系列的其它键盘消息,其中便有WM_CHAR。
以下是向对话框中的编辑框输入'a'的部分处理过程(以所附源码调试、整理而得)
CGetInputDlg::DoModal() //对话框创建
往编辑框按下一键'a'
001 CGetInputDlg::RunModalLoop //消息循环,在这里peek到WM_KEYDOWN
002 CGetInputDlg::PumpMessage //消息泵,取消息、翻译、处理这些消息
003 CGetInputDlg::PreTranslateMessage //线程开始消息预处理
004 CGetInputDlg::WalkPreTranslateTree //从目标窗口到主窗口历遍
005 CMyEdit::PreTranslateMessage //目标窗口,没有处理(返回FALSE)。
006 CGetInputDlg::PreTranslateMessage //子窗口没有处理,所以流到父窗口
007 CDialogDlg::PreTranslateMessage //由基类处理
...
008 CMyEdit::WindowProc
009 CMyEdit::OnWndMsg
010 CMyEdit::OnKeyDown
011 CMyEdit::DefWindowProc
012 CGetInputDlg::RunModalLoop //在这里peek到WM_CHAR,不过一般情况下之间会有几个WM_KICKIDLE
...
由上有人就会说,这并不能说明WM_CHAR是WM_KEYDOWN消息处理过程中产生的,这仅仅说明WM_CHAR在WM_KEYDOWN之后。i嗯,大家再看看下面。
这些是所附源码中的调试输出信息,无~~代表对应函数的进入,有~~代表一个函数的返回点,msg后的值为消息的16进制值(WM_KEYDOWN=0X100, WM_KEYUP=0X101, WM_CHAR=0X102),haveCharMsg为消息队列中是否有WM_CHAR消息(通过peekmessage来实现)
英文输入'a',对应调试信息整理如下:
对应005 101 CMyEdit::PreTranslateMessage msg=100, haveCharMsg=0
102 ~~CMyEdit::PreTranslateMessage msg=100, haveCharMsg=0, Ret=0
对应006
103 CGetInputDlg::PreTranslateMessage msg=100, haveCharMsg=0
105 CMyEdit::OnKeyDown haveCharMsg=1
106 CMyEdit::DefWindowProc msg=100, haveCharMsg=1
107 ~~CMyEdit::DefWindowProc msg=100, haveCharMsg=1, Ret=1
108 ~~CMyEdit::OnKeyDown haveCharMsg=1
109 ~~CGetInputDlg::PreTranslateMessage msg=100, haveCharMsg=1, Ret=1
110 CMyEdit::PreTranslateMessage msg=102, haveCharMsg=0
111 ~~CMyEdit::PreTranslateMessage msg=102, haveCharMsg=0, Ret=0
112 CGetInputDlg::PreTranslateMessage msg=102, haveCharMsg=0
113 CMyEdit::OnChar 61
114 CMyEdit::DefWindowProc msg=102, haveCharMsg=0
115 ~~CMyEdit::DefWindowProc msg=102, haveCharMsg=0, Ret=1
116 ~~CMyEdit::OnChar 61
117 ~~CGetInputDlg::PreTranslateMessage msg=102, haveCharMsg=0, Ret=1
118 CMyEdit::PreTranslateMessage msg=101, haveCharMsg=0
119 ~~CMyEdit::PreTranslateMessage msg=101, haveCharMsg=0, Ret=0
120 CGetInputDlg::PreTranslateMessage msg=101, haveCharMsg=0
121 CMyEdit::OnKeyUp
122 CMyEdit::DefWindowProc msg=101, haveCharMsg=0
123 ~~CMyEdit::DefWindowProc msg=101, haveCharMsg=0, Ret=0
124 ~~CMyEdit::OnKeyUp
125 ~~CGetInputDlg::PreTranslateMessage msg=101, haveCharMsg=0, Ret=1
看到了吗?在103处(对应处理过程006处)的CGetInputDlg::PreTranslateMessage里产生了WM_CHAR消息,并放入了消息队列中。由此可见消息确实是WM_KEYDOWN处理过程中产生的。在WM_KEYDOWN处理完成后,消息循环便检测到它,并在PumpMessage中Get它,并进入110相应的处理。也就在此时队列中已无WM_CHAR消息了。
耶,不对啊,很多地方不是说TranslateMessage产生WM_CHAR消息?他们的意思也就是PumpMessage中的
::TranslateMessage(&m_msgCur),我将PumpMessage简化如下: 200 BOOL CWinThread::PumpMessage() (CGetInputDlg::PumpMessage)
201 {
202 ASSERT_VALID(this);
203 if (!::GetMessage(&m_msgCur, NULL, NULL, NULL))
204 return FALSE;
205 // process this message
206 if (m_msgCur.message != WM_KICKIDLE && !PreTranslateMessage(&m_msgCur))
207 {
208 ::TranslateMessage(&m_msgCur);
209 ::DispatchMessage(&m_msgCur);
210 }
211 return TRUE;
212 }
这里可见,只有当PreTranslateMessage(CGetInputDlg::)返回FALSE时才会执行208处对应的函数,而事实上从103处可以看到CGetInputDlg::PreTranslateMessage还没有返回,就有了WM_CHAR消息,而且从~~CGetInputDlg::PreTranslateMessage可以看出PreTranslateMessage返回为1,不会执行208、209。那是不是那些说法错了呢?
也不一定,如果他指的是对话框程序,那我想他就确实错了,但对于非对话框程序是对的,我建了一SDI工程,PreTranslateMessage中对于非加速键返加是FALS,即会执行208、209。::TranslateMessage(&m_msgCur)来翻译键盘消息。当然,如果我想PreTranslateMessage中的默认处最终还是调TranslateMessage来完成翻译的,只是不对应208处的代码而己。
到此以也就差不多了,但点要提醒大家,如果你在验证这些时,在某些函数中设了断点(如OnKeyDown),然后按键输入,这时会停在断点处,然后你再运行,这时会得出某些不一样的结果。主要是WM_KEYUP消息没有了,因为收到WM_KEYUP消息的窗体为调试器。
|