史萊姆論壇

返回   史萊姆論壇 > 教學文件資料庫 > 作業系統操作技術文件
忘記密碼?
論壇說明 標記討論區已讀

歡迎您來到『史萊姆論壇』 ^___^

您目前正以訪客的身份瀏覽本論壇,訪客所擁有的權限將受到限制,您可以瀏覽本論壇大部份的版區與文章,但您將無法參與任何討論或是使用私人訊息與其他會員交流。若您希望擁有完整的使用權限,請註冊成為我們的一份子,註冊的程序十分簡單、快速,而且最重要的是--註冊是完全免費的!

請點擊這裡:『註冊成為我們的一份子!』

Google 提供的廣告


 
 
主題工具 顯示模式
舊 2006-01-13, 10:02 PM   #1
psac
榮譽會員
 
psac 的頭像
榮譽勳章
UID - 3662
在線等級: 級別:30 | 在線時長:1048小時 | 升級還需:37小時級別:30 | 在線時長:1048小時 | 升級還需:37小時級別:30 | 在線時長:1048小時 | 升級還需:37小時級別:30 | 在線時長:1048小時 | 升級還需:37小時級別:30 | 在線時長:1048小時 | 升級還需:37小時
註冊日期: 2002-12-07
住址: 木柵市立動物園
文章: 17381
現金: 5253 金幣
資產: 33853 金幣
預設 MS VC程序中設定對話視窗中的Enter鍵鍵

VC程序中設定對話視窗中的Enter鍵鍵

關於對話視窗的程序中,每當用戶按下Enter鍵鍵時,程序都會結束,其效果和按下對話視窗中的預設"OK"按鈕是一樣的,即使去掉"OK"按鈕的 BS_DEFPUSHBUTTON 內容也沒用。那麼如何設定Enter鍵鍵的行為呢?

這個問題在Windows的開發中由來已久,對於初學者來說,這是個惱人的問題,幸運的是,人們找到了很多種解決這個問題的方案。本實例將告訴你實現設定Enter鍵鍵行為的方法。

  一、實現方法

  如果想要使Enter鍵鍵無效,最簡單的方法是重載OnOK()函數,這固然是個不壞的主意,但如果重載OnOK()函數,讓它什麼事情也不干,那麼當用戶用滑鼠按下"OK"按鈕想真正做些什麼的時候怎麼辦呢?

你可以改變Enter鍵鍵的ID,如:ID_MY_OK,並寫一個使用EndDialog()的處理器,這個方法雖然也能行得通,但顯得有點不專業。

  另外一種方法是"disable"Enter鍵鍵的"預設"內容。這也是本文開始所提出的方法,之所以沒有成功,是因為僅僅不設定"OK"按鈕的BS_DEFPUSHBUTTON 內容是不夠的,可以利用Visual C++中的Spy++工具仔細地觀察,就能發現Enter鍵鍵仍然我行我素傳送結束消息。


問題出在哪呢?你必須區分OK按鈕和Enter鍵鍵,你可以寫一個OnOK處理器使用GetCurrentMessage()函數獲取最後傳送的消息,應該是WM_COMMAND,再檢查WPARAM的低位元字(low-order word)看看指令來自何處。

  要解決問題,必須搞清楚背後所發生的一切,在Spy++中可以看到,當用戶按下Enter鍵鍵時,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巨集。這樣用戶可以隨意按Enter鍵鍵,但什麼事都不會發生。

  上面的做法是解決了按Enter鍵鍵程序結束的問題,但是又產生了另外一個問題:如果想要Enter鍵鍵做些事情怎麼辦呢?

有一些人曾經問過如何將Enter鍵鍵映射到TAB鍵,既按下Enter鍵鍵就像按下TAB鍵一樣,也就是說輸入焦點移動到下一個對話視窗控制。這需要做一些工作才行,但最簡單的方式是使用加速鍵。


許多程序員試圖用OnChar()回應函數,但它是一個低級趣味的東西,應該想方設法盡量避免使用它,更糟的還有WM_KEYDOWN,WM_KEYUP之類的消息。


誰能處理這些消息呢?OnChar()可以用來限制允許輸入編輯框的字元,如:數位,字母等。如果想要將一個鍵映射到一個指令,加速鍵才是最好的方法。

  在本實例中為VK_RETURN新增了一個加速鍵,將它映射到指令ID_MY_ENTER,並寫一個指令處理器來實現任何想實現的事情。

  如果你細心的話會發現另外一個還沒有得到解決的問題,那就是在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(),就什麼都不用擔心了。

  隨著Visual 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()是一個虛擬函數,不用增加一個消息映射入口。

僅僅寫這個函數就可以了。


  二、編程步驟

  1、 啟動Visual C++6.0,產生一個Win32應用程式,將該程序命名為"DlgKeys";

  2、 使用CLASSWIZARD為應用程式增加CdlgWithAccelerators和CmyDlg類;

  3、 在程序的資源中增加增加加速鍵資源,內容如下:ID_MY_ENTER, VK_RETURNVIRTKEY,VIRTKEY;

  4、增加程式碼,編譯執行程序。
三、程序程式碼

/////////////////////////////////////////
#include "stdafx.h"
// Generic dialog-that-uses-accelerators.
class CDlgWithAccelerators : public CDialog {
public:
 CDlgWithAccelerators(UINT nIDTemplate, CWnd* pParentWnd = NULL);
 CDlgWithAccelerators(LPCTSTR lpszTemplateName, CWnd* pParentWnd = NULL);
 ~CDlgWithAccelerators();
protected:
 HACCEL m_hAccel; // accelerator table
 // MFC overrides
 virtual BOOL OnInitDialog();
 virtual BOOL PreTranslateMessage(MSG* pMsg);
 DECLARE_MESSAGE_MAP()
};

///////////////////////////////////////////
// CDlgWithAccelerators is a general-purpose class that adds accelerators to CDialog.
#include "stdafx.h"
#include "dlgaccel.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
BEGIN_MESSAGE_MAP(CDlgWithAccelerators,CDialog)
END_MESSAGE_MAP()
CDlgWithAccelerators::CDlgWithAccelerators(LPCTSTR lpszTemplateName,
CWnd* pParentWnd) : CDialog(lpszTemplateName, pParentWnd)
{}

CDlgWithAccelerators::CDlgWithAccelerators(UINT nIDTemplate,
CWnd* pParentWnd) : CDialog(nIDTemplate, pParentWnd)
{}

CDlgWithAccelerators::~CDlgWithAccelerators()
{}

/////////////////////////// Pre-translate message: translate keystrokes using acclerator table.
BOOL CDlgWithAccelerators::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);
}

//////////////////// Initialize dialog: load accelerators
BOOL CDlgWithAccelerators::OnInitDialog()
{
 BOOL bRet = CDialog::OnInitDialog();
 // Load dialog's accelerators
 m_hAccel = ::LoadAccelerators(AfxGetResourceHandle(),
 m_lpszTemplateName); // use same resource name as dialog
 return bRet;
}

/////////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "resource.h"
#include "dlgaccel.h"
#include "TraceWin.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////MFC app
class CMyApp : public CWinApp {
public:
 CMyApp();
 ~CMyApp();
 virtual BOOL InitInstance();
 DECLARE_MESSAGE_MAP()
};

CMyApp theApp; // THE one-and-only app

//////////// frame window
class CMainFrame : public CFrameWnd {
protected:
 virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
public:
 CMainFrame();
 ~CMainFrame();
};

//////////////////// Typical dialog
class CMyDlg : public CDlgWithAccelerators {
public:
 CMyDlg(CWnd* pParent = NULL); // standard constructor
protected:
 HICON m_hIcon;
 void NextInTabOrder();
 // MFC overrides
 virtual BOOL OnInitDialog();
 afx_msg void OnMyEnter();
 afx_msg LRESULT OnGetDefID(WPARAM wp, LPARAM lp);
 DECLARE_MESSAGE_MAP()
};

BEGIN_MESSAGE_MAP(CMyApp, CWinApp)
END_MESSAGE_MAP()

CMyApp::CMyApp()
{
 // nothing to do
}

CMyApp::~CMyApp()
{
 // nothing to do
}

//////////////////// InitInstance: create dialog as child
BOOL CMyApp::InitInstance()
{
 // create frame window and load it
 CMainFrame* pFrame = new CMainFrame;
 m_pMainWnd = pFrame;
 pFrame->LoadFrame(IDR_MAINFRAME, WS_OVERLAPPED, NULL, NULL);
 CMyDlg dlg(pFrame); // create dialog and run it
 int nResponse = dlg.DoModal();
 if (nResponse == IDOK)
 {}
 else if (nResponse == IDCANCEL)
 {}
 return FALSE; // quit
}

CMainFrame::CMainFrame()
{
 // nothing to do
}

CMainFrame::~CMainFrame()
{
 // nothing to do
}

///////////// Pre-create window: set WS_EX_TOOLWINDOW style to hide dialog from task bar
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
 if (CFrameWnd::PreCreateWindow(cs)) {
  cs.dwExStyle |= WS_EX_TOOLWINDOW;
  return TRUE;
 }
 return FALSE;
}

BEGIN_MESSAGE_MAP(CMyDlg, CDlgWithAccelerators)
ON_COMMAND(ID_MY_ENTER, OnMyEnter)

// The following is NOT needed since I am using accelerators to map
// ENTER to ID_MY_ENTER. But if all you want to do is ignore the Enter key,
// you can handle DM_GETDEFID as below.
// ON_MESSAGE(DM_GETDEFID, OnGetDefID) // not used
END_MESSAGE_MAP()

CMyDlg::CMyDlg(CWnd* pParent) : CDlgWithAccelerators(IDD_MYDIALOG, pParent)
{}

//////////////////// Initialize dialog:
BOOL CMyDlg::OnInitDialog()
{
 CDlgWithAccelerators::OnInitDialog();
 // Set the icon for this dialog. The framework does this automatically
 // when the application's main window is not a dialog
 m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
 ASSERT(m_hIcon);
 SetIcon(m_hIcon, TRUE); // Set big icon
 SetIcon(m_hIcon, FALSE); // Set small icon
 // use same resource name as dialog to load dialog's accelerators
 m_hAccel = ::LoadAccelerators(AfxGetResourceHandle(),m_lpszTemplateName);
 ASSERT(m_hAccel);
 return TRUE; // return TRUE unless you set the focus to a control
}

//////////////////// This is called to handle ID_MY_ENTER--ie, Enter key.
void CMyDlg::OnMyEnter()
{
 TRACE(_T("CMyDlg::OnMyEnter\n"));
 NextInTabOrder(); // move to next control
}

//////////////////// Helper function to move focus to the next control.
void CMyDlg::NextInTabOrder()
{
 CWnd* pWndNext = GetNextDlgTabItem(GetFocus());
 if (pWndNext) {
  pWndNext->SetFocus();
 }
}

//////////////////
// This function is not used, since its message map entry is commented out.
// If all you want to do is ignore the Enter key (not map it to a command),
// then all you have to do is return zero here. Note that you MUST return
// the special code DC_HASDEFID in the high-order word!!
LRESULT CMyDlg::OnGetDefID(WPARAM wp, LPARAM lp)
{
 TRACE(_T("CMyDlg::OnGetDefID\n"));
 return MAKELONG(0,DC_HASDEFID);
}

  四、小結

  綜上所述,MFC中為對話視窗增加加速鍵功能的方法就是:載入加速鍵和重載PreTranslateMessage()函數。也就是說,如果開發人員決定在對話視窗中使用加速鍵,不用去操心OnGetDefID()函數的處理,而是要重點實現加速鍵對應的WM_COMMAND消息回應處理函數
__________________
http://bbsimg.qianlong.com/upload/01/08/29/68/1082968_1136014649812.gif
psac 目前離線  
送花文章: 3, 收花文章: 1631 篇, 收花: 3205 次
 


主題工具
顯示模式

發表規則
不可以發文
不可以回覆主題
不可以上傳附加檔案
不可以編輯您的文章

論壇啟用 BB 語法
論壇啟用 表情符號
論壇啟用 [IMG] 語法
論壇禁用 HTML 語法
Trackbacks are 禁用
Pingbacks are 禁用
Refbacks are 禁用


所有時間均為台北時間。現在的時間是 05:38 PM


Powered by vBulletin® 版本 3.6.8
版權所有 ©2000 - 2024, Jelsoft Enterprises Ltd.


SEO by vBSEO 3.6.1