按鈕控件使能

新建一對話框應用程序PraDlg,將對話框模板上的靜態文件控件ID改爲IDC_STATIC_TEST,再添加一按鈕,改ID爲IDC_BTN_TEST, Caption改爲Test。
        雙擊Test按鈕, 即可添加該按鈕的消息映像函數。在此函數中做如下處理:

< 1 >.
    void
 CPraDlgDlg::OnBtnTest() 
    {
           CButton 
*pBut = (CButton *)GetDlgItem(IDC_BTN_TEST);    //獲得按鈕指針

        pBut->EnableWindow(FALSE);                                                    //將該按鈕禁止使能,變灰。
        static int nNum = 0;                                                                       //統計該按鈕被點擊次數。我使用靜態變量。
        SetDlgItemInt(IDC_STATIC_TEST, ++nNum);                         //將此數據顯示在靜態文本框中。
        Sleep(5000);                                                                                  //這裏是問題的關鍵的關鍵。假定我們做五秒鐘的工作
        pBut->EnableWindow(TRUE);                                                    //做完工作後再使能
    }


        可能你還沒弄明白我要幹什麼,沒關係,咱們運行,然後按照我說的做。
        在按鈕上連續點擊五次,當然這五次不要超過五秒,因爲我剛纔Sleep了五秒,注意觀察按鈕和文本框的變化。問題出來了。
        問題描述:
        我們原意是在按鈕被點擊時,爲防止按鈕再次被點擊,先將該按鈕禁止使能,直到要做的處理全部做完後再允許使用者點擊。這裏的Sleep (5000); 是假定我們要做的處理工作。但上面的例子運行的現象是,當第一次點擊按鈕時,按鈕狀態立即變灰,即非使能,靜態文本框中出現數字1,這一點,完全在我們的 預料之中。 每過五秒,文本框中的數字會一直累加直到五(因爲剛纔點擊了五次)。
        嗯? 問題出來了,完了,按鈕眞的有BUG,因爲後四次肯定是在按鈕變灰後點擊的,可函數還是執行了五次,難道是按鈕的使能狀態沒用? 但不對呀,以前做的按鈕使能都沒問題的呀。 那麼,是不是在消息響應函數執行完之前點擊的,按鈕變灰不影響點擊。 好,我們再做下面實驗:

< 2 >
    
void CPraDlgDlg::OnBtnTest() 
    {
        CButton 
*pBut = (CButton *)GetDlgItem(IDC_BTN_TEST);    //獲得按鈕指針
        pBut->EnableWindow(FALSE);                                                     //將該按鈕禁止使能,變灰。
        static int nNum = 0;                                                                         //統計該按鈕被點擊次數。我使用靜態變量。
        SetDlgItemInt(IDC_STATIC_TEST, ++nNum);                           //將此數據顯示在靜態文本框中。
        Sleep(5000);                                                                                     //這裏是問題的關鍵的關鍵。假定我們做五秒鐘的工作
        //  pBut->EnableWindow(TRUE);                                                   //屏蔽掉按鈕使能,驗證下是否按鈕響應退出前不受變灰狀態的改變。
    }

        好了,將最後一條語句屏蔽掉後,運行,連續點擊五次按鈕,發現點擊完一次後,按鈕立即變灰,文本框出現數字1, 再等n久,按鈕還是沒反應。也就是說,該按鈕只接
受了一次點擊,和上面做的推測顯然不符,那麼究竟什麼原因導致問題的呢?
        從1和2的現象綜合分析,得出如下結論:
        a:  按鈕消息處理的時候,鼠標點擊事件被記錄下來了,這說明按鈕的點擊事件優先級較高。
        b:  按鈕消息處理完後,再處理下一次的按鈕消息。這就是爲什麼消息能再次被響應。(最後一句又使能了嘛,所以能再次被處理)
        c:  按鈕是否根據使能狀態響應函數是在派遣消息時判斷的,而不是在點擊時判斷的。
        通過上面b和c結論可以初步推測出鼠標點擊到響應過程的眞面目。
        那麼怎麼解決這個問題, 以達到我們的目的呢? 定時器? 好,繼續做上面的實驗:
< 3 > 
    
void CPraDlgDlg::OnBtnTest() 
    {
        CButton 
*pBut = (CButton *)GetDlgItem(IDC_BTN_TEST);    //獲得按鈕指針
        pBut->EnableWindow(FALSE);                                                     //將該按鈕禁止使能,變灰。
        static int nNum = 0;                                                                         //統計該按鈕被點擊次數。我使用靜態變量。
        SetDlgItemInt(IDC_STATIC_TEST, ++nNum);                           //將此數據顯示在靜態文本框中。
        Sleep(5000);                                                                                     //這裏是問題的關鍵的關鍵。假定我們做五秒鐘的工作
        SetTimer(10, NULL);                                                                    //處理完後啓動定時器,讓定時器取消息按鈕使能。
        //pBut->EnableWindow(TRUE);                                                    //做完工作後再使能
    }
    
void CPraDlgDlg::OnTimer(UINT nIDEvent) 
    {
        GetDlgItem(IDC_BTN_TEST)
->EnableWindow(TRUE);           //取消按鈕使能。
        KillTimer(nIDEvent);                                                                     //去掉定時器。
        CDialog::OnTimer(nIDEvent);
    }


        運行,連續點擊n次按鈕,發現第一次點擊後按鈕變灰,之後經過五秒鐘,按鈕變可用,文本框中數字變爲1而沒變成5,哈哈成功了。
        你可能會問,爲什麼牠不先執行定時器的函數,使按鈕可用,再執行鼠標點擊的事件函數,繼續響應按鈕呢。嗯,這個呢,是因爲定時器的優先級更低,沒有按鈕消息時
纔會響應定時器。
而在派遣按鈕消息時,按鈕又是非使能的,所以也沒法運行。
        眞像差不多大白了,但上面的處理還要藉助定時器,太麻煩了,有沒有更簡單的方法呢?
   
        回顧一下Windows的消息機制, Windows應用程序都有一個消息隊列,系統會從消息隊列中一個一個的取出消息,翻譯,派遣,再來看一下所做的實驗 1:
        當鼠標點擊時, 點擊消息被放到消息隊列中,Windows從消息隊列中取出一條消息,然後派遣,執行消息響應函數。在函數中,先將按鈕禁止使能,再後做我們需要的工
作,這時又過來幾條消息,因爲當前工作尙未完成,所以仍然堆到消息隊列中。當工作完成後,使按鈕可用,退出響應函數,Windows再從消息隊列中取出一條消息,因爲此
時按鈕已經可用,所以該消息仍然可以被派遣,執行。 實驗1中就這樣一次一次的將按鈕消息執行完。
        完美解決方法: 在消息響應函數退出之前,將所有的按鈕消息從消息隊列中取出來,仍掉。


void CPraDlgDlg::OnBtnTest() 
{
    
// TODO: Add your control notification handler code here
    CButton *pBut = (CButton *)GetDlgItem(IDC_BTN_TEST);    //獲得按鈕指針
    SetTimer(10, NULL);
    pBut
->EnableWindow(FALSE);                                                     //將該按鈕禁止使能,變灰。
    static int nNum = 0;                                                                         //統計該按鈕被點擊次數。我使用靜態變量。
    SetDlgItemInt(IDC_STATIC_TEST, ++nNum);                           //將此數據顯示在靜態文本框中。
    Sleep(5000);                                                                                    //這裡是問題的關鍵的關鍵。假定我們做五秒鐘的工作
    pBut->EnableWindow(TRUE);                                                      //做完工作後再使能
    MSG msg;
    
while(PeekMessage(&
msg, GetSafeHwnd(), WM_MOUSEFIRST, WM_MOUSELAST, PM_REMOVE ))
        NULL;

}

 

 

 

( ?? 雜碎
用戶在按鈕上單擊鼠標,消息是發送到(類似於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了,自然也就可以響應這個消息了。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章