現在,我們已經有兩個菜單處理器了,一個是來自外殼的上下文菜單,另一個是我們自定義的菜單(包含兩個自定義菜單項),下面我們看看如何将它們組合在一起。具體來說,我們會使用到一個組合上下文處理器來實作。
組合上下文菜單的核心思想是将多個上下文菜單組合到一個處理器中,并使用菜單辨別符的偏移來對菜單項進行路由和定位。
請看下面的代碼。
在上面的代碼中,我們使用到了一個結構體CONTEXTMENUINFO,它包含了菜單中的一些資訊。我們不僅需要上下文菜單指針,而且也需要被
IContextMenu::QueryContextMenu使用到的菜單項的數量。
下面的代碼示範了我們如何實作這個類。
因為對于一個C++對象來說,它的構造函數不會失敗,是以在構造過程中如何處理失敗有多種設計慣例。我在這裡使用的一個設計是,将大部分工作放在Initialize方法中,如果初始化失敗,該方法可以傳回适當的錯誤代碼。(請注意,這裡我假設new操作符不會抛出異常)
我們的初始化函數配置設定了一堆CONTEXTMENUINFO結構并複制IContextMenu指針以妥善保管。(注意 m_ccmi 成員直到我們知道記憶體配置設定成功後才會設定。)
下面是類的析構函數實作:
接下來是Create方法的實作:
因為我們的接口繼承自IUnknown接口,是以還需要實作IUnknown接口的三個方法,如下圖所示:
接下來,是一個我們比較感興趣的方法實作:
IContextMenu::QueryContextMenu。
我們依次要求每個包含的上下文菜單将其指令添加到上下文菜單中。你可以在此處看到
IContextMenu::QueryContextMenu 方法傳回值的原因之一。 通過告訴容器使用了多少個菜單辨別符,容器知道還剩下多少給其他人使用。 然後容器傳回所有上下文菜單使用的菜單辨別符總數。
IContextMenu::QueryContextMenu 方法傳回值的解釋請看下面的幫助函數:
此方法采用菜單偏移量并計算出它屬于哪個包含的上下文菜單,使用來自
IContextMenu::QueryContextMenu 的傳回值來決定如何劃分辨別符空間。 pidCmd 參數是輸入/輸出。 進入函數時,它是複合上下文菜單的菜單偏移量; 退出函數時,它是包含的上下文菜單的菜單偏移量,通過 ppcmi 參數傳回。
IContextMenu::InvokeCommand 可能是最複雜的,因為它需要支援四種不同的指令排程方式,請看下圖:
經過一些初步調整後我們找到了指令辨別符,然後我們分三個步驟進行調用。
首先,如果指令是作為字元串發送的,那麼這是最簡單的情況。我們周遊所有包含的上下文菜單,詢問每個菜單是否識别指令。一旦确定,我們就可以執行對應的操作了。如果沒有人知道,那麼我們聳聳肩說我們也不知道。
其次,如果分派的指令是序數,我們要求 ReduceOrdinal 找出它屬于哪個包含的上下文菜單處理程式。
第三,我們重寫了 CMINVOKECOMMANDINFO 結構,使其适用于包含的上下文菜單處理程式。這意味着更改 lpVerb 成員和可能的 lpVerbW 成員以包含相對于包含的上下文菜單處理程式而不是相對于容器的新菜單偏移。由于 Unicode 動詞 lpVerbW 可能不存在,是以這會稍微複雜一些。我們将其隐藏在 pszVerbWFake 局部變量後面,如果沒有真正的 lpVerbW,它就會出現。
好的,現在你已經了解了将方法調用分發到相應包含的上下文菜單背後的基本思想,其餘的應該相對容易一些了。
下面是GetCommandString的參考實作:
GetCommandString 方法遵循與 InvokeCommand 相同的三步模式。
首先,通過調用每個包含的上下文菜單處理程式來分派任何基于字元串的指令,直到有人接受它。 如果沒有人這樣做,則拒絕該指令。 (注意 GCS_VALIDATE 的特殊處理,它需要 S_FALSE 而不是錯誤代碼。)
其次,如果指令由 ordinal 指定,則要求 ReduceOrdinal 找出它屬于哪個包含的上下文菜單處理程式。
第三,将縮減的指令傳遞給适用的包含上下文菜單處理程式。
最後的方法通過一個小的幫助函數來實作:
此輔助函數采用 IContextMenu 接口指針并嘗試調用
IContextMenu3::HandleMenuMsg2; 如果失敗,則嘗試 IContextMenu2::HandleMenuMsg; 如果這也失敗了,那麼它就放棄了。
有了這個輔助函數,最後兩個方法小菜一碟。
IContextMenu2::HandleMenuMsg 方法隻是 IContextMenu3::HandleMenuMsg2 方法的轉發器:
而IContextMenu3::HandleMenuMsg2 方法隻是周遊上下文菜單處理程式清單,詢問每個處理程式是否希望處理該指令,并在最終執行時停止。
有了這個複合菜單類,我們可以在我們的示例程式中通過将“真實”上下文菜單與我們的 CTopContextMenu 組合在一起來展示它,進而展示如何将多個上下文菜單組合成一個大的上下文菜單,如下圖所示:
此函數通過建立兩個包含的上下文菜單處理程式來建構複合,然後建立一個包含它們的複合上下文菜單。 我們可以通過對我們上次調整的 OnContextMenu 函數進行相同的一行調整來使用這個函數:
請注意,使用此複合上下文菜單,我們在視窗标題中更新的菜單幫助文本跨越原始檔案上下文菜單和“頂部”上下文菜單。來自任何一方的指令也被成功調用。
與第九部分中的方法相比,此方法的價值在于你不再需要在兩段代碼之間協調上下文菜單的自定義。根據之前的技術,你必須確定更新菜單幫助文本的代碼與添加自定義指令的代碼同步。
在新方法下,所有自定義都儲存在一個地方(在複合上下文菜單内的“頂部”上下文菜單中),是以視窗過程不需要知道發生了哪些自定義。如果有多個顯示上下文菜單的點,一些未自定義,另一些以不同方式自定義,則會變得更有價值。
好的,我認為現在在上下文菜單這個主題已經講得差不多了。我希望你已經更好地了解它們的工作原理,如何利用它們,最重要的是,如何使用組合等技術對它們執行元操作(meta-operations)。
你還可以使用上下文菜單執行其他一些操作,你可以自行進行研究。例如,可以使用
IContextMenu::GetCommandString 方法周遊菜單并為每個項目擷取與語言無關的指令。如果你想删除“删除”選項,這很友善:可以查找與語言無關的名稱為“删除”的指令。當使用者更改語言時,此名稱不會更改;它将永遠是英文的。
正如我們之前所注意到的,你需要注意許多上下文菜單處理程式并沒有完全實作
IContextMenu::GetCommandString 方法,是以可能會有一些你根本無法獲得名稱的指令。這個要特别注意。
總結
總算是完成這個系列了,我可以去開一瓶香槟休息一下了。
感謝各位老哥的一路陪伴。