共用方式為


TN021:命令和訊息路由

備註

自第一次包含在在線文件中以來,尚未更新下列技術附注。 因此,某些程式和主題可能已過期或不正確。 如需最新信息,建議您搜尋在線檔索引中感興趣的主題。

此附註說明命令路由和分派架構,以及一般視窗訊息路由中的進階主題。

如需此處所述架構的一般詳細數據,請參閱Visual C++,特別是 Windows 訊息、控制項通知和命令之間的差異。 此附註假設您非常熟悉列印檔中所述的問題,且只處理非常進階的主題。

命令路由和分派 MFC 1.0 功能演進至 MFC 2.0 架構

Windows 具有被重載的 WM_COMMAND 訊息,用以提供功能表命令、快捷鍵和對話框控制項通知的通知。

MFC 1.0 稍微擴展了功能,允許在衍生類別中響應特定 WM_COMMAND 時,呼叫命令處理器(例如,「OnFileNew」)。 這會與稱為訊息對應的數據結構黏附在一起,併產生非常有空間效率的命令機制。

MFC 1.0 也提供將控制項通知與命令訊息分開的額外功能。 命令會以16位標識符表示,有時稱為命令標識碼。 命令通常會從 CFrameWnd 開始 ,也就是功能表選取或翻譯的快速鍵,並路由傳送至各種其他視窗。

MFC 1.0 在有限意義上使用命令路由來實作多個檔介面 (MDI)。 (MDI 框架視窗會將命令委派給其作用中的 MDI 子視窗。

這項功能已在 MFC 2.0 中一般化和擴充,以允許由更廣泛的對象處理命令(而不僅僅是窗口物件)。 它提供更正式且可延伸的架構來路由訊息,並重複使用命令目標路由,不僅用於處理命令,也可用於更新UI物件(例如功能表項和工具列按鈕),以反映命令的目前可用性。

命令標識碼

如需命令路由和系結程序的說明,請參閱Visual C++。 技術附註 20 包含標識碼命名的相關信息。

我們使用命令標識碼的泛型前置詞 「ID_」。 命令識別碼為 >= 0x8000。 如果有與命令標識元相同的 STRINGTABLE 資源,消息行或狀態列會顯示命令描述字串。

在應用程式的資源中,命令標識碼可以出現在數個位置:

  • 在一個與訊息行提示具有相同識別碼的 STRINGTABLE 資源中。

  • 在可能有許多 MENU 資源被附加到叫用相同命令的功能表項中。

  • 在 GOSUB 命令的對話框按鈕中有(ADVANCED)。

在應用程式的原始碼中,命令標識元可以出現在數個位置:

  • 在您的 RESOURCE.H (或其他主要符號頭檔)中定義應用程式特定的命令碼。

  • 也許在用來建立工具列的標識碼陣列中。

  • 在 ON_COMMAND 宏中。

  • 可能在 ON_UPDATE_COMMAND_UI 巨集中。

目前,MFC 中,唯一需要命令標識符必須大於或等於 0x8000 的實作是用於 GOSUB 對話框/命令的實作。

GOSUB 命令,在對話框中使用命令架構

路由和啟用命令的命令架構適用於框架視窗、功能表項、工具列按鈕、對話框列按鈕、其他控制列和其他使用者介面元素,其設計目的是視需要更新,並將命令或控件標識符路由傳送至主要命令目標(通常是主框架視窗)。 該主要命令目標可能會視需要將命令或控件通知路由傳送至其他命令目標物件。

如果您將對話框控制項的控制識別碼指派至適當的指令識別碼以便獲得指令架構的某些功能,模態或非模態對話框都可以受益。 對話框的支援不是自動的,因此您可能必須撰寫一些額外的程序代碼。

請注意,若要讓所有這些功能正常運作,您的命令標識元應該是 >= 0x8000。 由於許多對話框可以路由傳送至相同的框架,因此共用命令應為 >= 0x8000,而特定對話方塊中的非共用 IDC 應為 <= 0x7FFF。

您可以將一般按鈕放在一般模態對話框中,並將按鈕的 IDC 設定為適當的命令 ID。 當使用者選取按鈕時,對話框的擁有者(通常是主框架視窗)會取得命令,就像任何其他命令一樣。 此稱為 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 動詞彈出選單時,實際上在繪製選單之前修改選單結構。

當應用程式進入空閒迴圈時,工具列(和其他控制列)會執行相同的 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_COMMANDON_BN_CLICKED的訊息對應巨集相同。 MFC 命令和控制通知路由機制只會使用命令標識碼來決定要路由傳送到的位置。 具有零 (BN_CLICKED) 控件通知碼的控制通知會解譯為命令。

備註

事實上,所有控件通知訊息都會經過命令處理程式鏈結。 例如,在技術上,您可以在文件類別中為 EN_CHANGE 撰寫控件通知處理程式。 這通常不建議使用,因為此功能的實際應用程式很少,ClassWizard 不支援此功能,而使用此功能可能會導致程式代碼脆弱。

取消按鈕控制元件的自動停用功能

如果您在對話框列上放置按鈕控件,或使用您自己呼叫 CWnd::UpdateDialogControls 的對話框,您會發現架構會自動停用沒有 ON_COMMANDON_UPDATE_COMMAND_UI 處理程式的按鈕。 在某些情況下,您不需要有處理程式,但您會想要讓按鈕保持啟用狀態。 達成此目的最容易的方式是新增假命令處理程式(使用 ClassWizard 輕鬆執行),並在其中不執行任何動作。

視窗訊息路由

下列說明 MFC 類別的一些更進階主題,以及 Windows 訊息路由和其他主題如何影響它們。 此處的資訊只會簡短說明。 如需公用 API 的詳細資訊,請參閱 類別庫參考 。 如需了解實作細節,請參閱 MFC 庫的原始程式碼。

如需視窗清除的詳細數據,請參閱 Technical Note 17 ,這是所有 CWnd 衍生類別非常重要的主題。

CWnd 問題

實作成員函式 CWnd::OnChildNotify 提供了一個強大且可擴展的架構,讓子視窗(也稱為控制項)能夠攔截或接收發送至其父視窗(或「擁有者」)的訊息、命令和控制通知。 如果子視窗 (/control) 是 CWnd 物件本身的C++,則會先呼叫虛擬函式 OnChildNotify ,其中包含來自原始訊息的參數(也就是 MSG 結構)。 子視窗可以獨立留下訊息、處理訊息,或在罕見情況下修改父視窗的訊息。

預設 CWnd 實作會處理下列訊息,並使用 OnChildNotify 攔截來允許子視窗 (controls) 先存取訊息:

  • WM_MEASUREITEMWM_DRAWITEM (自畫)

  • WM_COMPAREITEMWM_DELETEITEM (自繪)

  • WM_HSCROLLWM_VSCROLL

  • WM_CTLCOLOR

  • WM_PARENTNOTIFY

您會發現 OnChildNotify 勾子用於將擁有者繪製訊息轉化為自行繪製訊息。

除了 OnChildNotify 鉤子之外,捲動訊息還有更進一步的路由行為。 如需滾動條和 WM_HSCROLLWM_VSCROLL 訊息來源的詳細資訊,請參閱下方。

CFrameWnd 問題

CFrameWnd 類別提供大部分的命令路由和使用者介面更新實作。 這主要用於應用程式的主框架視窗(CWinApp::m_pMainWnd),但適用於所有框架視窗。

主框架視窗是具有功能表欄的視窗,而且是狀態列或消息行的父系。 請參閱上述關於命令路由和 WM_INITMENUPOPUP的討論。

CFrameWnd 類別提供使用中檢視的管理。 下列訊息會透過使用中檢視路由傳送:

  • 所有命令訊息(使用中檢視會先存取它們)。

  • WM_HSCROLLWM_VSCROLL 訊息來自同層級的滾動條(請參閱下方)。

  • WM_ACTIVATE(和 MDI 的 WM_MDIACTIVATE)會變成對虛擬函式 CView::OnActivateView 的呼叫。

CMDIFrameWnd/CMDIChildWnd 問題

這兩個 MDI 框架視窗類別都衍生自 CFrameWnd,因此具備在 CFrameWnd 中所提供的相同命令路由和使用者介面更新功能。 在典型的 MDI 應用程式中,只有主框架視窗(也就是 CMDIFrameWnd 物件)會保留功能表欄和狀態列,因此是命令路由實作的主要來源。

一般路由配置是使用中的 MDI 子視窗會先存取命令。 默認 PreTranslateMessage 函式會處理 MDI 子視窗(首先)和 MDI 框架(其次)的快速鍵表,以及 TranslateMDISysAccel 通常處理的標準 MDI 系統命令的快速鍵(最後一個)。

滾動條問題

處理滾動訊息時(WM_HSCROLL/OnHScroll 和/或 WM_VSCROLL/OnVScroll),您應該嘗試撰寫處理程式程式代碼,使其不依賴滾動條訊息的來源。 這不僅是一般的 Windows 問題,因為卷動訊息可能來自實際的滾動條控件,或是從那些不是滾動條控制項的 WS_HSCROLL/WS_VSCROLL 滾動條。

MFC 擴展,允許滾動條控件成為捲動視窗的子系或兄弟(事實上,滾動條與捲動視窗之間的父子關係可以是任何形式)。 對於共用滾動條與分割視窗來說,這尤其重要。 如需 CSplitterWnd 實作的詳細資訊,請參閱 Technical Note 29,包括共用滾動條問題的詳細資訊。

順帶一提,有兩個 CWnd 衍生類別,建立時指定的滾動條樣式會被截獲,不會傳遞至 Windows。 傳遞至建立例程時,可以獨立設定 WS_HSCROLLWS_VSCROLL ,但在建立之後無法變更。 當然,您不應該直接測試或設定他們所創建的視窗之 WS_SCROLL 樣式位元。

針對 CMDIFrameWnd ,您傳入 CreateLoadFrame 的滾動條樣式是用來建立 MDICLIENT。 如果您想要有可捲動的 MDICLIENT 區域(例如 Windows 程式管理員),請務必為用來建立 WS_HSCROLL | WS_VSCROLL 的樣式設定這兩個滾動條樣式

對於 CSplitterWnd,卷軸樣式會套用到分隔器區域中的特別共用卷軸。 對於靜態分隔器視窗,您通常不會設定任一滾動條樣式。 針對動態分割器視窗,您通常會針對您要分割的方向設定滾動條樣式,也就是說,如果您可以分割數據行,WS_HSCROLL,如果您可以分割數據列,WS_VSCROLL

另請參閱

依編號的技術注意事項
依類別排序的技術注意事項