IContextMenu第十一部分:組合擴充套件的實現

現在,我們已經有兩個選單處理器了,一個是來自外殼的上下文選單,另一個是我們自定義的選單(包含兩個自定義選單項),下面我們看看如何將它們組合在一起。具體來說,我們會使用到一個組合上下文處理器來實現。

組合上下文選單的核心思想是將多個上下文選單組合到一個處理器中,並使用選單識別符號的偏移來對選單項進行路由和定位。

請看下面的程式碼。

IContextMenu第十一部分:組合擴充套件的實現

IContextMenu第十一部分:組合擴充套件的實現

在上面的程式碼中,我們使用到了一個結構體CONTEXTMENUINFO,它包含了選單中的一些資訊。我們不僅需要上下文選單指標,而且也需要被

IContextMenu::QueryContextMenu使用到的選單項的數量。

下面的程式碼演示了我們如何實現這個類。

IContextMenu第十一部分:組合擴充套件的實現

因為對於一個C++物件來說,它的建構函式不會失敗,因此在構造過程中如何處理失敗有多種設計慣例。我在這裡使用的一個設計是,將大部分工作放在Initialize方法中,如果初始化失敗,該方法可以返回適當的錯誤程式碼。(請注意,這裡我假設new運算子不會丟擲異常)

我們的初始化函式分配了一堆CONTEXTMENUINFO結構並複製IContextMenu指標以妥善保管。(注意 m_ccmi 成員直到我們知道記憶體分配成功後才會設定。)

下面是類的解構函式實現:

IContextMenu第十一部分:組合擴充套件的實現

接下來是Create方法的實現:

IContextMenu第十一部分:組合擴充套件的實現

因為我們的介面繼承自IUnknown介面,所以還需要實現IUnknown介面的三個方法,如下圖所示:

IContextMenu第十一部分:組合擴充套件的實現

接下來,是一個我們比較感興趣的方法實現:

IContextMenu::QueryContextMenu。

IContextMenu第十一部分:組合擴充套件的實現

我們依次要求每個包含的上下文選單將其命令新增到上下文選單中。你可以在此處看到

IContextMenu::QueryContextMenu 方法返回值的原因之一。 透過告訴容器使用了多少個選單識別符號,容器知道還剩下多少給其他人使用。 然後容器返回所有上下文選單使用的選單識別符號總數。

IContextMenu::QueryContextMenu 方法返回值的解釋請看下面的幫助函式:

IContextMenu第十一部分:組合擴充套件的實現

此方法採用選單偏移量並計算出它屬於哪個包含的上下文選單,使用來自

IContextMenu::QueryContextMenu 的返回值來決定如何劃分識別符號空間。 pidCmd 引數是輸入/輸出。 進入函式時,它是複合上下文選單的選單偏移量; 退出函式時,它是包含的上下文選單的選單偏移量,透過 ppcmi 引數返回。

IContextMenu::InvokeCommand 可能是最複雜的,因為它需要支援四種不同的命令排程方式,請看下圖:

IContextMenu第十一部分:組合擴充套件的實現

經過一些初步調整後我們找到了命令識別符號,然後我們分三個步驟進行呼叫。

首先,如果命令是作為字串傳送的,那麼這是最簡單的情況。我們遍歷所有包含的上下文選單,詢問每個選單是否識別命令。一旦確定,我們就可以執行對應的操作了。如果沒有人知道,那麼我們聳聳肩說我們也不知道。

其次,如果分派的命令是序數,我們要求 ReduceOrdinal 找出它屬於哪個包含的上下文選單處理程式。

第三,我們重寫了 CMINVOKECOMMANDINFO 結構,使其適用於包含的上下文選單處理程式。這意味著更改 lpVerb 成員和可能的 lpVerbW 成員以包含相對於包含的上下文選單處理程式而不是相對於容器的新選單偏移。由於 Unicode 動詞 lpVerbW 可能不存在,因此這會稍微複雜一些。我們將其隱藏在 pszVerbWFake 區域性變數後面,如果沒有真正的 lpVerbW,它就會出現。

好的,現在你已經瞭解了將方法呼叫分發到相應包含的上下文選單背後的基本思想,其餘的應該相對容易一些了。

下面是GetCommandString的參考實現:

IContextMenu第十一部分:組合擴充套件的實現

GetCommandString 方法遵循與 InvokeCommand 相同的三步模式。

首先,透過呼叫每個包含的上下文選單處理程式來分派任何基於字串的命令,直到有人接受它。 如果沒有人這樣做,則拒絕該命令。 (注意 GCS_VALIDATE 的特殊處理,它需要 S_FALSE 而不是錯誤程式碼。)

其次,如果命令由 ordinal 指定,則要求 ReduceOrdinal 找出它屬於哪個包含的上下文選單處理程式。

第三,將縮減的命令傳遞給適用的包含上下文選單處理程式。

最後的方法透過一個小的幫助函式來實現:

IContextMenu第十一部分:組合擴充套件的實現

此輔助函式採用 IContextMenu 介面指標並嘗試呼叫

IContextMenu3::HandleMenuMsg2; 如果失敗,則嘗試 IContextMenu2::HandleMenuMsg; 如果這也失敗了,那麼它就放棄了。

有了這個輔助函式,最後兩個方法小菜一碟。

IContextMenu第十一部分:組合擴充套件的實現

IContextMenu2::HandleMenuMsg 方法只是 IContextMenu3::HandleMenuMsg2 方法的轉發器:

IContextMenu第十一部分:組合擴充套件的實現

IContextMenu3::HandleMenuMsg2 方法只是遍歷上下文選單處理程式列表,詢問每個處理程式是否希望處理該命令,並在最終執行時停止。

有了這個複合選單類,我們可以在我們的示例程式中透過將“真實”上下文選單與我們的 CTopContextMenu 組合在一起來展示它,從而展示如何將多個上下文選單組合成一個大的上下文選單,如下圖所示:

IContextMenu第十一部分:組合擴充套件的實現

此函式透過建立兩個包含的上下文選單處理程式來構建複合,然後建立一個包含它們的複合上下文選單。 我們可以透過對我們上次調整的 OnContextMenu 函式進行相同的一行調整來使用這個函式:

IContextMenu第十一部分:組合擴充套件的實現

請注意,使用此複合上下文選單,我們在視窗標題中更新的選單幫助文字跨越原始檔案上下文選單和“頂部”上下文選單。來自任何一方的命令也被成功呼叫。

與第九部分中的方法相比,此方法的價值在於你不再需要在兩段程式碼之間協調上下文選單的自定義。根據之前的技術,你必須確保更新選單幫助文字的程式碼與新增自定義命令的程式碼同步。

在新方法下,所有自定義都儲存在一個地方(在複合上下文選單內的“頂部”上下文選單中),因此視窗過程不需要知道發生了哪些自定義。如果有多個顯示上下文選單的點,一些未自定義,另一些以不同方式自定義,則會變得更有價值。

好的,我認為現在在上下文選單這個主題已經講得差不多了。我希望你已經更好地瞭解它們的工作原理,如何利用它們,最重要的是,如何使用組合等技術對它們執行元操作(meta-operations)。

你還可以使用上下文選單執行其他一些操作,你可以自行進行研究。例如,可以使用

IContextMenu::GetCommandString 方法遍歷選單併為每個專案獲取與語言無關的命令。如果你想刪除“刪除”選項,這很方便:可以查詢與語言無關的名稱為“刪除”的命令。當用戶更改語言時,此名稱不會更改;它將永遠是英文的。

正如我們之前所注意到的,你需要注意許多上下文選單處理程式並沒有完全實現

IContextMenu::GetCommandString 方法,因此可能會有一些你根本無法獲得名稱的命令。這個要特別注意。

總結

總算是完成這個系列了,我可以去開一瓶香檳休息一下了。

感謝各位老哥的一路陪伴。

最後

Raymond Chen的《The Old New Thing》是我非常喜歡的部落格之一,裡面有很多關於Windows的小知識,對於廣大Windows平臺開發者來說,確實十分有幫助。

本文來自:《How to host an IContextMenu, part 11 – Composite extensions – composition》

最近我寫了個東西

正如你們所知道的,拓撲梅爾智慧辦公平臺(Topomel Box)是一款綠色軟體,主要面向經常使用電腦的朋友。它提供了各種提升辦公效率的小功能,同時操作上儘可能地簡單方便。

我想:你值得擁有。

IContextMenu第十一部分:組合擴充套件的實現