TN021:命令和訊息路由
注意
下列技術提示自其納入線上文件以來,未曾更新。 因此,有些程序和主題可能已過期或不正確。 如需最新資訊,建議您在線上文件索引中搜尋相關的主題。
此附注說明命令路由和分派架構,以及一般視窗訊息路由中的進階主題。
如需此處所述架構的一般詳細資料,請參閱 Visual C++,特別是 Windows 訊息、控制項通知和命令之間的差異。 此附注假設您非常熟悉列印檔案中所述的問題,且只處理非常進階的主題。
命令路由和分派 MFC 1.0 功能演進至 MFC 2.0 架構
Windows 具有多載的WM_COMMAND訊息,可提供功能表命令、快速鍵和對話方塊控制項通知的通知。
MFC 1.0 藉由允許衍生類別中的 CWnd
命令處理常式(例如,「OnFileNew」)呼叫,以回應特定的WM_COMMAND來建置該處理常式。 這會與稱為訊息對應的資料結構黏附在一起,並產生非常有空間效率的命令機制。
MFC 1.0 也提供將控制項通知與命令訊息分開的額外功能。 命令會以 16 位識別碼表示,有時稱為命令識別碼。 命令通常會從 CFrameWnd
開始 ,也就是功能表選取或翻譯的快速鍵,並路由傳送至各種其他視窗。
MFC 1.0 在有限意義上使用命令路由來實作多個檔介面 (MDI)。 (MDI 框架視窗會將命令委派給其作用中的 MDI 子視窗。
這項功能已在 MFC 2.0 中一般化和擴充,以允許由更廣泛的物件處理命令(而不僅僅是視窗物件)。 它提供更正式且可延伸的架構來路由訊息,並重複使用命令目標路由,不僅用於處理命令,也可用於更新 UI 物件(例如功能表項目和工具列按鈕),以反映命令的目前可用性。
命令 ID
如需命令路由和系結程式的說明,請參閱 Visual C++。 技術附注 20 包含識別碼命名的相關資訊。
我們使用命令識別碼的泛型前置詞 「ID_」。 命令識別碼為 > = 0x8000。 如果有與命令識別碼相同的 STRINGTABLE 資源,消息行或狀態列會顯示命令描述字串。
在應用程式的資源中,命令識別碼可以出現在數個位置:
在一個與消息行提示字元具有相同識別碼的 STRINGTABLE 資源中。
在可能許多附加至叫用相同命令之功能表項目的 MENU 資源中。
(ADVANCED) 在 GOSUB 命令的對話方塊按鈕中。
在應用程式的原始程式碼中,命令識別碼可以出現在數個位置:
在您的 RESOURCE 中。H (或其他主要符號標頭檔)用來定義應用程式特定的命令識別碼。
也許在用來建立工具列的識別碼陣列中。
在ON_COMMAND宏中。
也許在ON_UPDATE_COMMAND_UI宏中。
目前,MFC 中唯一需要命令識別碼的實作是 > = 0x8000 是 GOSUB 對話方塊/命令的實作。
GOSUB 命令,在對話方塊中使用命令架構
路由和啟用命令的命令架構適用于框架視窗、功能表項目、工具列按鈕、對話方塊列按鈕、其他控制列和其他使用者介面元素,其設計目的是視需要更新,並將命令或控制項識別碼路由傳送至主要命令目標(通常是主框架視窗)。 該主要命令目標可能會視需要將命令或控制項通知路由傳送至其他命令目標物件。
如果您將對話方塊控制項的控制項識別碼指派給適當的命令識別碼,對話方塊(強制回應或無模式)可以受益于命令架構的某些功能。 對話方塊的支援不是自動的,因此您可能必須撰寫一些額外的程式碼。
請注意,若要讓所有這些功能正常運作,您的命令識別碼應該是 > = 0x8000。 由於許多對話方塊可以路由傳送至相同的框架,因此共用命令應為 > = 0x8000,而特定對話方塊中的非共用 IDC 應為 < = 0x7FFF。
您可以將一般按鈕放在一般強制回應對話方塊中,並將按鈕的 IDC 設定為適當的命令識別碼。 當使用者選取按鈕時,對話方塊的擁有者(通常是主框架視窗)會取得命令,就像任何其他命令一樣。 這稱為 GOSUB 命令,因為它通常用來顯示另一個對話(第一個對話方塊的 GOSUB)。
您也可以在對話方塊上呼叫 函式 CWnd::UpdateDialogControls
,並將主框架視窗的位址傳遞給它。 此函式會根據框架中的命令處理常式,啟用或停用對話方塊控制項。 此函式會自動為您呼叫應用程式閒置迴圈中的控制列,但您必須直接針對您想要擁有此功能的一般對話方塊呼叫它。
呼叫ON_UPDATE_COMMAND_UI時
維護所有程式功能表項目的啟用/核取狀態,可能是計算成本高昂的問題。 常見的技巧是在使用者選取 POPUP 時,才啟用/檢查功能表項目。 MFC 2.0 實作 CFrameWnd
會處理WM_INITMENUPOPUP訊息,並使用命令路由架構透過ON_UPDATE_COMMAND_UI處理常式來判斷功能表的狀態。
CFrameWnd
也會處理WM_ENTERIDLE訊息,以描述狀態列上選取的目前功能表項目(也稱為消息行)。
Visual C++ 編輯的應用程式功能表結構可用來代表WM_INITMENUPOPUP時可用的潛在命令。 ON_UPDATE_COMMAND_UI處理常式可以修改功能表的狀態或文字,或進階用途(例如檔案 MRU 清單或 OLE Verbs 快顯功能表),實際上修改功能表結構,然後才繪製功能表。
當應用程式進入閒置迴圈時,工具列(和其他控制列)會執行同一種ON_UPDATE_COMMAND_UI處理。 如需控制列的詳細資訊,請參閱類別庫參考 和技術 附注 31 。
巢狀快顯功能表
如果您使用巢狀功能表結構,您會發現快顯視窗功能表中第一個功能表項目的ON_UPDATE_COMMAND_UI處理常式會在兩個不同的案例中呼叫。
首先,它會針對快顯功能表本身呼叫。 這是必要的,因為快顯功能表沒有識別碼,而且我們使用快顯功能表第一個功能表項目的識別碼來參考整個快顯功能表。 在此情況下, 物件的m_pSubMenu 成員變數 CCmdUI
將是非 Null,而且會指向快顯視窗。
其次,它會在快顯功能表中的功能表項目要繪製之前呼叫。 在此情況下,識別碼只會參考第一個功能表項目,而 物件的m_pSubMenu 成員變數 CCmdUI
會是 Null。
這可讓您啟用與其功能表項目不同的快顯功能表,但需要您撰寫一些功能表感知程式碼。 例如,在具有下列結構的巢狀功能表中:
File>
New>
Sheet (ID_NEW_SHEET)
Chart (ID_NEW_CHART)
ID_NEW_SHEET和ID_NEW_CHART命令可以獨立啟用或停用。 如果已啟用這兩個功能表之一,則應該啟用 [新增 ] 快顯視窗。
ID_NEW_SHEET的命令處理常式(快顯中的第一個命令)看起來會像這樣:
void CMyApp::OnUpdateNewSheet(CCmdUI* pCmdUI)
{
if (pCmdUI->m_pSubMenu != NULL)
{
// enable entire pop-up for "New" sheet and chart
BOOL bEnable = m_bCanCreateSheet || m_bCanCreateChart;
// CCmdUI::Enable is a no-op for this case, so we
// must do what it would have done.
pCmdUI->m_pMenu->EnableMenuItem(pCmdUI->m_nIndex,
MF_BYPOSITION |
(bEnable MF_ENABLED : (MF_DISABLED | MF_GRAYED)));
return;
}
// otherwise just the New Sheet command
pCmdUI->Enable(m_bCanCreateSheet);
}
ID_NEW_CHART的命令處理常式會是一般的更新命令處理常式,看起來像這樣:
void CMyApp::OnUpdateNewChart(CCmdUI* pCmdUI)
{
pCmdUI->Enable(m_bCanCreateChart);
}
ON_COMMAND和ON_BN_CLICKED
ON_COMMAND 和 ON_BN_CLICKED 的訊息對應宏 相同。 MFC 命令和控制通知路由機制只會使用命令識別碼來決定要路由傳送到的位置。 具有零 ( BN_CLICKED ) 控制項通知碼的控制通知會解譯為命令。
注意
事實上,所有控制項通知訊息都會經過命令處理常式鏈結。 例如,在技術上,您可以在檔類別中為EN_CHANGE 撰寫控制項通知處理常式 。 這通常不建議使用,因為此功能的實際應用程式很少,ClassWizard 不支援此功能,而使用此功能可能會導致程式碼脆弱。
停用按鈕控制項的自動停用
如果您在對話方塊列上放置按鈕控制項,或使用您自己呼叫 CWnd::UpdateDialogControls 的對話方塊,您會發現架構會自動停用沒有 ON_COMMAND 或 ON_UPDATE_COMMAND_UI 處理常式的按鈕。 在某些情況下,您不需要有處理常式,但您會想要讓按鈕保持啟用狀態。 達成此目的最簡單的方式是新增虛擬命令處理常式(使用 ClassWizard 輕鬆執行),並在其中執行任何動作。
視窗訊息路由
下列說明 MFC 類別的一些更進階主題,以及 Windows 訊息路由和其他主題如何影響它們。 此處的資訊只會簡短說明。 如需公用 API 的詳細資訊, 請參閱類別庫參考 。 如需實作詳細資料的詳細資訊,請參閱 MFC 程式庫原始程式碼。
如需視窗清除的詳細資料,請參閱 Technical Note 17 ,這是所有 CWnd 衍生類別非常重要的主題。
CWnd 問題
實作成員函 式 CWnd::OnChildNotify 為子視窗(也稱為控制項)提供強大且可延伸的架構,以攔截或通知傳送至其父系的訊息、命令和控制通知(或「擁有者」)。 如果子視窗 (/control) 是 C++ CWnd 物件本身,則會先使用原始訊息中的參數來呼叫虛擬函 式 OnChildNotify (也就是 MSG 結構)。 子視窗可以單獨留下資訊,吃它,或修改父母的資訊(罕見)。
預設 CWnd 實作會處理下列訊息,並使用 OnChildNotify 攔截來允許子視窗 (controls) 先存取訊息:
WM_MEASUREITEM 和 WM_DRAWITEM (自畫)
WM_COMPAREITEM 和 WM_DELETEITEM (自畫)
WM_HSCROLL 和 WM_VSCROLL
WM_CTLCOLOR
WM_PARENTNOTIFY
您會發現 OnChildNotify 勾點用於將擁有者繪製訊息變更為自我繪製訊息。
除了 OnChildNotify 攔截之外,捲動訊息還有進一步的路由行為。 如需捲軸和WM_HSCROLL 和 WM_VSCROLL 訊息來源 的詳細資訊,請參閱下方。
CFrameWnd 問題
CFrameWnd 類別提供大部分的命令路由和使用者介面更新實作。 這主要用於應用程式的主框架視窗( CWinApp::m_pMainWnd ),但適用于所有框架視窗。
主框架視窗是具有功能表列的視窗,而且是狀態列或消息行的父系。 請參閱上述關於命令路由和 WM_INITMENUPOPUP的討論。
CFrameWnd 類別提供使用中檢視的管理。 下列訊息會透過使用中檢視路由傳送:
所有命令訊息(使用中檢視會先存取它們)。
從同層級捲軸WM_HSCROLL和 WM_VSCROLL 訊息(請參閱下方)。
WM_ACTI加值稅E (和 MDI 的WM_MDIACTI加值稅E ) 會變成對虛擬函式 CView::OnActivateView 的呼叫。
CMDIFrameWnd/CMDIChildWnd 問題
這兩個 MDI 框架視窗類別都是衍生自 CFrameWnd ,因此會針對 CFrameWnd 中 提供的相同命令路由和使用者介面更新啟用。 在典型的 MDI 應用程式中,只有主框架視窗(也就是 CMDIFrameWnd 物件)會保留功能表列和狀態列,因此是命令路由實作的主要來源。
一般路由配置是使用中的 MDI 子視窗會先存取命令。 預設 PreTranslateMessage 函式會處理 MDI 子視窗 (first) 和 MDI 框架 (second) 的快速鍵資料表,以及 TranslateMDISysAccel 通常處理 的標準 MDI 系統命令加速器(最後一個)。
捲軸問題
處理 scroll-message ( WM_HSCROLL / OnHScroll 和/或 WM_VSCROLL / OnVScroll 時,您應該嘗試撰寫處理常式程式碼,使其不依賴捲軸訊息的來源。 這不僅是一般 Windows 問題,因為捲動訊息可能來自真正的捲軸控制項,或是從 不是捲軸控制項的捲軸WS_HSCROLL / WS_VSCROLL 捲軸。
MFC 擴充,可讓捲軸控制項成為捲動視窗的子系或同層級(事實上,捲軸與捲動視窗之間的父子關聯性可以是任何專案)。 對於具有分割器視窗的共用捲軸來說,這特別重要。 如需 CSplitterWnd 實作 的詳細資訊,請參閱 Technical Note 29 ,包括共用捲軸問題的詳細資訊。
在側注中,有兩 個 CWnd 衍生類別,其中在建立時間指定的捲軸樣式會被截獲,而不會傳遞至 Windows。 傳遞至建立常式時, 可以獨立設定WS_HSCROLL 和 WS_VSCROLL ,但無法變更建立之後。 當然,您不應該直接測試或設定其所建立視窗的WS_SCROLL樣式位。
針對 CMDIFrameWnd ,您傳入 Create 或 LoadFrame 的捲軸樣式是用來建立 MDICLIENT。 如果您想要有可捲動的 MDICLIENT 區域(例如 Windows 程式管理員),請務必為用來建立 CMDIFrameWnd 的 樣式設定這兩個捲軸樣式 。 WS_HSCROLL | WS_VSCROLL
。
針對 CSplitterWnd ,捲軸樣式會套用至分隔器區域的特殊共用捲軸。 對於靜態分隔器視窗,您通常不會設定任一捲軸樣式。 針對動態分割器視窗,您通常會針對您要分割的方向設定捲軸樣式,也就是說, 如果您可以分割資料列, WS_HSCROLL,WS_VSCROLL 如果可以分割資料行。