新建一對話框應用程序PraDlg,將對話框模板上的靜態文件控件ID改爲IDC_STATIC_TEST,再添加一按鈕,改ID爲IDC_BTN_TEST, Caption改爲Test。
雙擊Test按鈕, 即可添加該按鈕的消息映像函數。在此函數中做如下處理:
void CPraDlgDlg::OnBtnTest()
{
CButton *pBut = (CButton *)GetDlgItem(IDC_BTN_TEST); //獲得按鈕指針
可能你還沒弄明白我要幹什麼,沒關係,咱們運行,然後按照我說的做。
在按鈕上連續點擊五次,當然這五次不要超過五秒,因爲我剛纔Sleep了五秒,注意觀察按鈕和文本框的變化。問題出來了。
問題描述:
我們原意是在按鈕被點擊時,爲防止按鈕再次被點擊,先將該按鈕禁止使能,直到要做的處理全部做完後再允許使用者點擊。這裏的Sleep
(5000);
是假定我們要做的處理工作。但上面的例子運行的現象是,當第一次點擊按鈕時,按鈕狀態立即變灰,即非使能,靜態文本框中出現數字1,這一點,完全在我們的
預料之中。 每過五秒,文本框中的數字會一直累加直到五(因爲剛纔點擊了五次)。
嗯?
問題出來了,完了,按鈕眞的有BUG,因爲後四次肯定是在按鈕變灰後點擊的,可函數還是執行了五次,難道是按鈕的使能狀態沒用?
但不對呀,以前做的按鈕使能都沒問題的呀。 那麼,是不是在消息響應函數執行完之前點擊的,按鈕變灰不影響點擊。 好,我們再做下面實驗:
好了,將最後一條語句屏蔽掉後,運行,連續點擊五次按鈕,發現點擊完一次後,按鈕立即變灰,文本框出現數字1, 再等n久,按鈕還是沒反應。也就是說,該按鈕只接
受了一次點擊,和上面做的推測顯然不符,那麼究竟什麼原因導致問題的呢?
從1和2的現象綜合分析,得出如下結論:
a: 按鈕消息處理的時候,鼠標點擊事件被記錄下來了,這說明按鈕的點擊事件優先級較高。
b: 按鈕消息處理完後,再處理下一次的按鈕消息。這就是爲什麼消息能再次被響應。(最後一句又使能了嘛,所以能再次被處理)
c: 按鈕是否根據使能狀態響應函數是在派遣消息時判斷的,而不是在點擊時判斷的。
通過上面b和c結論可以初步推測出鼠標點擊到響應過程的眞面目。
那麼怎麼解決這個問題, 以達到我們的目的呢? 定時器? 好,繼續做上面的實驗:
運行,連續點擊n次按鈕,發現第一次點擊後按鈕變灰,之後經過五秒鐘,按鈕變可用,文本框中數字變爲1而沒變成5,哈哈成功了。
你可能會問,爲什麼牠不先執行定時器的函數,使按鈕可用,再執行鼠標點擊的事件函數,繼續響應按鈕呢。嗯,這個呢,是因爲定時器的優先級更低,沒有按鈕消息時
纔會響應定時器。而在派遣按鈕消息時,按鈕又是非使能的,所以也沒法運行。
眞像差不多大白了,但上面的處理還要藉助定時器,太麻煩了,有沒有更簡單的方法呢?
回顧一下Windows的消息機制, Windows應用程序都有一個消息隊列,系統會從消息隊列中一個一個的取出消息,翻譯,派遣,再來看一下所做的實驗 1:
當鼠標點擊時, 點擊消息被放到消息隊列中,Windows從消息隊列中取出一條消息,然後派遣,執行消息響應函數。在函數中,先將按鈕禁止使能,再後做我們需要的工
作,這時又過來幾條消息,因爲當前工作尙未完成,所以仍然堆到消息隊列中。當工作完成後,使按鈕可用,退出響應函數,Windows再從消息隊列中取出一條消息,因爲此
時按鈕已經可用,所以該消息仍然可以被派遣,執行。 實驗1中就這樣一次一次的將按鈕消息執行完。
完美解決方法: 在消息響應函數退出之前,將所有的按鈕消息從消息隊列中取出來,仍掉。
(
?? 雜碎
用戶在按鈕上單擊鼠標,消息是發送到(類似於PostMessage)按鈕的Owner窗口上(Dialog)的,因爲這時候Dialog的主線程還在執
行中(使用Sleep),所以消息還是在消息隊列中,當你函數退出,Dialog從消息隊列中取道了鼠標消息,根據POINT可以獲知是在BUTTON
上,然後再把消息投遞到按鈕上,因爲這時候按鈕已經Enabled了,所以能夠處理消息。
關鍵的地方就是即使在一個按鈕上單擊鼠標,這個消息並不是發送到這個按鈕上的,而是發送到這個按鈕Owner上的。使用Spy++可以看到Owner是誰。推薦使用MySpy,簡單、小巧:)??)
CRect rec;
GetDlgItem(IDC_BUTTON1)->GetWindowRect(rec);
ScreenToClient(&rec);
rec.OffsetRect(2, 2);
PostMessage(WM_LBUTTONDOWN, MK_LBUTTON, *(LONG*)&rec.TopLeft() );
Sleep(200);
PostMessage(WM_LBUTTONDOWN, MK_LBUTTON, *(LONG *)&rec.TopLeft() );
發現IDC_BUTTON1響應了單擊消息。對話框控件的按鍵消息是由對話框接收後根據位置傳遞給控件的
擬鼠標單擊的消息
SendInput
The SendInput function synthesizes keystrokes, mouse motions, and button clicks.
UINT SendInput(
UINT nInputs, // count of input events
LPINPUT pInputs, // array of input events
int cbSize // size of structure
);
做消息隊列,並且進行處理的時候是類似於這樣的:
MSGQUEUE msg;
BOOL GetMssage(MSGQUEUE)
{
EnterCS(&csMsg); //Sync message queue
LeaveCS(&csMsg);
}
main_thread()
{
while(GetMessage(&msg))
{
switch(msg.message)
{
EnterCS(&csMain);
LeaveCS(&csMain);
}
}
}
LONG SendMessage(msg)
{
LONG lRet;
EnterCS(&csMain)
swittch(msg.message)
{
//Call proc
lRet = pfn(...);
}
LeaveCS(&csMain);
return lRet;
}
void PostMessage(msg)
{
EnterCS(&csMsg); // Sync message queue
AddToMessage(&msg); // Only to add message queue
LeaveCS(&csMsg);
}
因爲CS存在,所以在另外一個線程中使用SendMessage可能會導致死鎖(SendMessage中存在EnterCS(&csMain))。
你在窗口上單擊鼠標,僅僅是相當於調用了PostMessge,也就是說把鼠標單擊這個消息加入到了消息隊列。但是因爲你Seelp了,主線程還
沒有返回,等Sleep結束了,在此GetMessage時發現了那個鼠標單擊的消息,這時候根據msg.hwnd會調用對應的WndProc函數,注
意:這時候你的那個BUTTON已經Enabled了,自然也就可以響應這個消息了。