如何定製對話框中的回車鍵(轉)

基於對話框的程序中,每次按下回車鍵時,程序都退出。去掉按鈕的   BS_DEFPUSHBUTTON   屬性並重寫OnOK函數也沒用。那麼如何定製回車鍵的行爲呢?這個問題很easy,但是要說明白,卻要費點時間。
 
這個問題在Windows的開發中由來已久,對於初學者來說,這是個惱人的問題,幸運的是,人們找到了多種解決這個問題的方案。本文將告訴你定製回車鍵行爲的方法。
 
如果你想要disable回車鍵,最簡單的方法是重載OnOK函數,這固然是個不壞的主意,但如果你重載OnOK,讓它什麼事情也不幹,那麼當用戶用鼠標按下回車鍵想真正做些什麼的時候怎麼辦呢?你可以改變回車鍵的ID,如:ID_MY_OK,並寫一個調用EndDialog的處理器,這個方法雖然也能行得通,但顯得有點不專業。
 
另外一種方法是disable回車鍵的“默認”屬性。這也是本文開始所提出的方法,之所以沒有成功,是因爲僅僅uncheck   回車鍵的   BS_DEFPUSHBUTTON   屬性是不夠的,你可以利用Spy++仔細地觀察控制和實驗就能發現回車鍵仍然我行我素髮送退出消息。
 
問題出在哪呢?你必須區分OK鍵和回車鍵,你可以寫一個OnOK處理器調用GetCurrentMessage函數獲取最後發送的消息,應該是WM_COMMAND,再檢查WPARAM的低位字(low-order   word)看看命令來自何處。
 
要解決問題,必須搞清楚背後所發生的一切,在Spy++中可以看到,當用戶按下回車鍵時,Windows發送一個特殊的WM_GETDEFID消息來獲得缺省的命令ID,Windows再將它作爲WM_COMMAND發送。所以,你要做的就是重載WM_GETDEFID消息,在有關Windows的文檔中是這樣描述WM_GETDEFID返回值的:“如果有缺省得按鈕,則返回值的高位字包含DC_HASDEFID,低位字包含控制的標識符。否則,返回值是零。”
 
根據這段描述,假設如果沒有缺省得按鈕,則返回值應該是零。如果想要disable缺省得ID,必須在高位字中返回DC_HASDEFID。      
  BEGIN_MESSAGE_MAP(CMyDlg,   CDialog)  
          ON_MESSAGE(DM_GETDEFID,   OnGetDefID)  
          ...  
  END_MESSAGE_MAP()  
   
  LRESULT   CMyDlg::OnGetDefID(WPARAM   wp,   LPARAM   lp)    
  {  
          return   MAKELONG(0,DC_HASDEFID);    
  }     
     
因爲MFC沒有對應DM_GETDEFID的宏,你必須使用通用的ON_MASSAGE宏。這樣用戶可以隨意按回車鍵,但什麼事都不會發生。上面的做法是解決了按回車鍵程序退出的問題。但是又產生了另外一個問題:如果想要回車鍵做些事情怎麼辦呢?有一些人曾經問過如何將回車鍵映射到TAB鍵,既按下回車鍵就象按下TAB鍵一樣-也就是說輸入焦點移動到下一個對話框控制。這需要做一些工作才行,但最簡單的方式是使用加速鍵。許多程序員試圖用OnChar,我會對他們說:No,no,no!   OnChar是一個低級趣味的東西,你應該想方設法避免它,更糟的還有WM_KEYDOWN,WM_KEYUP之類的東西。誰能處理這些東西?OnChar可以用來限制允許輸入編輯框的字符,如:數字,字母等。如果想要將一個鍵映射到一個命令,加速鍵纔是最好的方法。
 
在本文的例子爲VK_RETURN創建了一個加速鍵,將它映射到命令ID_MY_ENTER,並寫一個命令處理器來做你想做的事情。      
  BEGIN_MESSAGE_MAP(CMyDlg,   CDialog)    
          ON_COMMAND(ID_MY_ENTER,   OnMyEnter)  
          ......  
  END_MESSAGE_MAP()    
   
  void   CMyDlg::OnMyEnter()    
  {  
          NextInTabOrder();    
  }     
     
下圖是本文例子的對話框和代碼,代碼中的NextInTabOrder是實際起作用的函數。它使用GetNextDlgTabItem來獲得Tab順序的下一個控制焦點。              
如果你細心的話會發現另外一個還沒有得到解決的問題,那就是在MFC對話框不自動處理加速鍵,你必須自己編寫代碼來做這件事情。爲了理解弄清楚這是爲什麼,讓我們回首Windows開發的歷程,在使用C和原始的Windows   API的年代,每一個Windows程序中都有一個叫做消息泵的中樞循環:      
  while   (GetMessage(...))   {    
          TranslateMessage(...);  
          DispatchMessage(...);    
  }
 
在這裏細節不是重要的,重要的是消息並不到達程序的流程,你必須請求消息。這是一種人爲的非搶先式多任務方法,這種方法通過每一個任務精誠協作來仿造多任務環境,隨着增加的功能越來越多,有人想到了加速鍵表的主意,這個表用來映射按鍵和命令IDs。爲了實現這個目的,他們發明了一個叫TranslateAccelerator的函數。現在這個消息泵變成了如下的樣子:      
  while   (GetMessage(...))   {    
          if   (TranslateAccelerator(hAccel...))   {    
                  //   handled,   continue   looping            
          }   else   {    
                  TranslateMessage(...);    
                  DispatchMessage(...);    
          }    
  }   
 
hAccel是個加速鍵表句柄,在這裏細節同樣不是重要的,重要的是如何利用加速鍵表,也就是要有一個專門的函數將按鍵消息解釋爲WM_COMMAND消息。TranslateAccelerator尋找WM_KEYDOWN,WM_CHAR,WM_KEYUP序列與表中鍵值匹配的字符。如果找到,它插入一條WM_COMMAND到消息隊列,在消息隊列中的命令ID可以是加速鍵表定義的任何入口。這樣你只要設置加速鍵表(在資源中)並記住調用對應的函數TranslateAccelerator,就什麼都不用擔心了。
 
時間轉眼間進入了21世紀,隨着C++和MFC的日臻成熟,現在幾乎整個消息循環(但不是全部)都被隱藏到了MFC中,爲了能讓任何窗口都有機會獲得一點消息泵的行爲,MFC提供了一個專門的虛函數PreTranslateMessage,如果你有足夠的勇氣去探究CWinThread中的消息處理機制的話,你會遇到類似如下的代碼:      
  //   簡化後的   CWinThread    
  while   (GetMessage(...))   {    
          if   (PreTranslateMessage(...))   {    
                  //   continue   looping    
          }   else   {    
                  TranslateMessage(...);    
                  DispatchMessage(...);    
          }  
  }     
     
CWinThread::PreTranslateMessage是個虛函數,在應用中,其缺省的實現以相同的名字調用另一個虛函數,CWnd::PreTranslateMessage。因此,如果你需要在消息循環中做些什麼的話-如解釋加速鍵-你只要重載PreTranslateMessage即可。實際上,這就是CFrameWnd處理加速鍵的方法。      
  BOOL   CFrameWnd::PreTranslateMessage(MSG*   pMsg)    
  {    
          ......  
          if   (pMsg->message   >=   WM_KEYFIRST   &&    
                  pMsg->message   <=   WM_KEYLAST)    
          {    
                  ::TranslateAccelerator(m_hAccelTable,...);    
          }    
  }  
 
CFrameWnd   從哪裏獲得加速鍵表呢?當你加載框架時,CFrameWnd::LoadFrame用與文檔模板相同的ID(如IDR_MAINFRAME)查找加速鍵表,並將他加載到m_hAccelTable。所有的處理細節在MFC中都是自動的、隱蔽的,你不用去操心-僅對主框架而言,如果是對話框,則是另外一種情況。因爲CDialog不是從CFrameWnd派生而來,所以不繼承任何有關加速鍵的內容。
 
不用擔心,我們可以模仿CFrameWnd的工作,很容易爲對話框增加加速鍵的功能。第一步是加載加速鍵,加載加速鍵最好的地方是在對話框的OnInitDialog函數中:      
  BOOL   CMyDlg::OnInitDialog()    
  {    
          CDialog::OnInitDialog();    
          ......  
          //   Load   accelerators    
          m_hAccel   =   ::LoadAccelerators(AfxGetResourceHandle(),    
                  m_lpszTemplateName);    
          ASSERT(m_hAccel);    
   
          return   TRUE;    
  }
 
在加速鍵表中,你可以使用任何ID。這裏我使用的是對話框本身的ID,(m_lpszTemplateName既可以是一個串名,也可以是一個MAKEINTRESOURCE使用的整型ID):      
  //   本文例子中的加速鍵(In   DlgKeys.rc   )  
  IDD_MYDIALOG   ACCELERATORS   DISCARDABLE      
  BEGIN    
          VK_RETURN,   ID_MY_ENTER,   VIRTKEY,   NOINVERT    
  END    
     
  一旦你已經加載加速鍵,剩下的事情是重載PreTranslateMessage函數:      
  BOOL   CMyDlg::PreTranslateMessage(MSG*   pMsg)    
  {    
          if   (WM_KEYFIRST   <=   pMsg->message   &&    
                  pMsg->message   <=   WM_KEYLAST)    
          {    
                  HACCEL   hAccel   =   m_hAccel;    
                  if   (hAccel   &&    
                          ::TranslateAccelerator(m_hWnd,   hAccel,   pMsg))    
                          return   TRUE;    
          }    
          return   CDialog::PreTranslateMessage(pMsg);    
  }
 
之所以要檢查按鍵類的消息(從WM_   KEYFIRST   到   WM_KEYLAST)是爲了提高速度。如果你知道不是一個按鍵消息,你就不用浪費時間去調用TranslateAccelerator。再說TranslateAccelerator是一個虛擬函數,不用增加一個消息映射入口。僅僅寫這個函數就可以了。
 
綜上所述,MFC中爲對話框添加加速鍵功能的方法就是:加載加速鍵和重載PreTranslateMessage函數。也就是說,如果你決定使用加速鍵,不用去操心OnGetDefID,而是將沒有命令處理器的ID映射到VK_RETURN。本文的例子代碼中封裝了一個又加速鍵的新對話框類:CdlgWinAccelerators,它是一個通用類。希望大家喜歡它。最後祝大夥編程愉快。
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章