使用動態動詞自訂捷徑選單
快捷選單處理常式也稱為內容選單處理常式或動詞處理常式。 快捷方式功能表處理程式是檔案類型處理程式的類型。
本主題的組織方式如下:
關於靜態和動態動詞
強烈建議您使用其中一個靜態動詞方法來實作快捷方式功能表。 建議您參考並遵循「使用靜態方法自訂快捷選單」中 建立內容選單處理程式的指示。 若要在 Windows 7 和更新版本中取得靜態動詞的動態行為,請參閱 建立內容功能表處理程式中的「Getting Dynamic Behavior for Static Verbs」。 如需靜態動詞實作的詳細數據,以及要避免的動態動詞,請參閱 為快捷方式功能表選擇靜態或動態動詞。
如果您必須透過註冊動態動詞來延伸檔類型的捷徑功能表,請遵循本主題中稍後提供的指示。
注意
註冊運行於32位應用程式內容中的處理程序時,需注意64位的Windows特殊考量:當在32位應用程式環境中叫用殼層動詞時,WOW64子系統會將檔案系統存取重新導向至某些路徑。 如果您的 .exe 處理程式儲存在其中一個路徑中,則無法在此環境中存取。 因此,作為一種替代方案,您可以將 .exe 儲存在不會被重新導向的路徑中,或者儲存一個用來啟動實際版本的 .exe 簡化版本。
捷徑選單處理程式如何與動態動詞運作
除了 IUnknown之外,快捷選單處理程式也會匯出下列其他介面來處理實作自行繪製選單項所需的訊息傳遞:
- IShellExtInit (強制)
- IContextMenu (強制)
- IContextMenu2 (選擇性)
- IContextMenu3 (選擇性)
如需有關擁有者繪製的功能表項的詳細資訊,請參閱 使用功能表中的 建立 Owner-Drawn 功能表項 一節。
Shell 會使用 IShellExtInit 介面來初始化處理器。 當 Shell 呼叫 IShellExtInit::Initialize時,它會傳入一個包含物件名稱的數據物件,以及一個指向包含該檔案的資料夾的專案識別符號清單(PIDL)的指標。 hkeyProgID 參數是登錄快捷方式功能表句柄的登錄位置。 IShellExtInit::Initialize 方法必須從資料物件擷取檔名,並儲存專案標識符清單 (PIDL) 的名稱和資料夾指標,以供稍後使用。 如需處理程式初始化的詳細資訊,請參閱 實作 IShellExtInit。
當動詞顯示在快捷功能表中時,首先會被發現,然後呈現給使用者,最後被執行。 下列清單詳細說明這三個步驟:
- Shell 會呼叫 IContextMenu:QueryContextMenu,它會傳回一組動詞,該動詞組可以根據項目或系統的狀態。
- 系統會傳入 HMENU 句柄,方法可用來將專案新增至快捷方式功能表。
- 如果使用者點擊其中一個處理程式的項目,Shell 會呼叫 IContextMenu::InvokeCommand。 處理程式接著可以執行適當的命令。
避免因無限定動詞名稱而發生衝突
由於動詞是按類型註冊的,因此可以在不同項目上使用相同的動詞名稱。 這麼做可讓應用程式參考與專案類型無關的常見動詞。 雖然這項功能很有用,但使用不合格的名稱可能會導致與多個選擇相同動詞名稱的獨立軟體廠商(ISV)發生衝突。 若要避免這種情況,請將動詞一律以ISV名稱作為前綴,如下所示:
ISV_Name.verb
一律使用應用程式特定的 ProgID。 採用將檔案名稱副檔名對應至獨立軟體供應商提供的 ProgID 的慣例,可以避免潛在的衝突。 不過,由於某些項目類型不會使用此對應,因此需要廠商唯一的名稱。 將動詞新增至可能已註冊該動詞的現有 ProgID 時,您必須先移除舊動詞的登錄機碼,再新增自己的動詞。 您必須這樣做,以避免合併兩個動詞的資訊。 無法這麼做會導致無法預期的行為。
註冊具有動態指令的捷徑選單處理程式
快捷方式功能表處理程式與文件類型或資料夾相關聯。 針對文件類型,處理程式會在下列子機碼下註冊。
HKEY_CLASSES_ROOT
Program ID
shellex
ContextMenuHandlers
若要將快捷選單處理程式與文件類型或資料夾關聯,請先在 ContextMenuHandlers 子機碼下建立一個子機碼。 為處理程式命名子機碼,並將子機碼的預設值設定為處理程式類別標識元 (CLSID) GUID 的字串形式。
然後,若要將快捷方式功能表處理程式與不同類型的資料夾產生關聯,請以與檔類型相同的方式註冊處理程式,但在 FolderType 子機碼下,如下列範例所示。
HKEY_CLASSES_ROOT
FolderType
shellex
ContextMenuHandlers
如需了解您可以註冊處理程式的資料夾類型的詳細資訊,請查看 註冊 Shell 擴充功能處理程式。
如果文件類型有與其相關聯的快捷方式功能表,則按兩下物件通常會啟動預設命令,而且不會呼叫處理程式的 IContextMenu::QueryContextMenu 方法。 若要指定在物件雙擊時呼叫處理程式的 IContextMenu::QueryContextMenu 方法,請在處理程式的 CLSID 子機碼下建立一個子機碼,如下所示。
HKEY_CLASSES_ROOT
CLSID
{00000000-1111-2222-3333-444444444444}
shellex
MayChangeDefaultMenu
按兩下與處理程式相關聯的物件時,會呼叫 IContextMenu::QueryContextMenu,並在 uFlags 參數中設定 CMF_DEFAULTONLY 旗標。
只有在可能需要變更捷徑選單的預設動詞時,捷徑選單處理程式才應設定 MayChangeDefaultMenu 子機碼。 設定此子機碼會強制系統在雙擊相關聯的項目時載入處理程式的 DLL。 如果您的處理程式未變更默認動詞,則不應該設定此子機碼,因為這樣做會導致系統不必要地載入 DLL。
下列範例說明會啟用 .myp 檔案類型的捷徑選單處理程式的登錄項目。 處理程式的 CLSID 子機碼包含 MayChangeDefaultMenu 子機碼,以確保當使用者按兩下相關物件時,會呼叫該處理程式。
HKEY_CLASSES_ROOT
.myp
(Default) = MyProgram.1
CLSID
{00000000-1111-2222-3333-444444444444}
InProcServer32
(Default) = C:\MyDir\MyCommand.dll
ThreadingModel = Apartment
shellex
MayChangeDefaultMenu
MyProgram.1
(Default) = MyProgram Application
shellex
ContextMenuHandler
MyCommand = {00000000-1111-2222-3333-444444444444}
實作 IContextMenu 介面
IContextMenu 是最強大的,也是最複雜的實作方法。 強烈建議您使用其中一個靜態動詞方法實作動詞。 如需詳細資訊,請參閱 為快捷方式選單選擇靜態或動態動詞。 IContextMenu 有三種方法,GetCommandString、InvokeCommand,以及 QueryContextMenu,詳細討論這裡。
IContextMenu::GetCommandString 方法
處理程式的 IContextMenu::GetCommandString 方法可用來傳回動詞的規範名稱。 這個方法是選擇性的。 在 Windows XP 和較早版本的 Windows 中,當 Windows 檔案總管顯示狀態列時,此方法用於擷取顯示在狀態列中的功能表項的說明文字。
idCmd 參數會保存呼叫 IContextMenu::QueryContextMenu 時所定義的命令標識符位移。 如果要求說明字串,uFlags 會設定為 GCS_HELPTEXTW。 將幫助字串複製到 pszName 緩衝區,並將它轉型為 PWSTR。 將 uFlags 設定為 GCS_VERBW來請求動詞字串。 將適當的字串複製到 pszName,就像複製到輔助字串一樣。 快捷方式功能表處理程式不會使用 GCS_VALIDATEA 和 GCS_VALIDATEW 旗標。
下列範例示範 IContextMenu::GetCommandString 的簡單實作,其對應於本主題 IContextMenu::QueryContextMenu範例中提供的IContextMenu::QueryContextMenu 方法 一節。 因為處理程式只會新增一個功能表項,因此只能傳回一組字串。 方法會測試 idCmd 是否有效,如果為 ,則會傳回要求的字串。
StringCchCopy 函式可用來將要求的字串複製到 pszName ,以確保複製的字串不會超過 cchName 所指定的緩衝區大小。 此範例只會實作 uFlags的 Unicode 值支援,因為只有 Windows 2000 以來已在 Windows 檔案總管中使用。
IFACEMETHODIMP CMenuExtension::GetCommandString(UINT idCommand,
UINT uFlags,
UINT *pReserved,
PSTR pszName,
UINT cchName)
{
HRESULT hr = E_INVALIDARG;
if (idCommand == IDM_DISPLAY)
{
switch (uFlags)
{
case GCS_HELPTEXTW:
// Only useful for pre-Vista versions of Windows that
// have a Status bar.
hr = StringCchCopyW(reinterpret_cast<PWSTR>(pszName),
cchName,
L"Display File Name");
break;
case GCS_VERBW:
// GCS_VERBW is an optional feature that enables a caller
// to discover the canonical name for the verb passed in
// through idCommand.
hr = StringCchCopyW(reinterpret_cast<PWSTR>(pszName),
cchName,
L"DisplayFileName");
break;
}
}
return hr;
}
IContextMenu::InvokeCommand 方法
當使用者單擊功能表項,告訴處理程式執行相關聯的命令時,會呼叫這個方法。 pici 參數指向包含所需信息的結構。
雖然 pici 在 Shlobj.h 中宣告為 CMINVOKECOMMANDINFO 結構,但實際上通常指向 CMINVOKECOMMANDINFOEX 結構。 此結構是 CMINVOKECOMMANDINFO 的延伸版本,並具有數個額外的成員,可讓您傳遞 Unicode 字串。
檢查 picicbSize 成員,以判斷傳入哪一個結構。 如果它是 CMINVOKECOMMANDINFOEX 結構,且 fMask 成員已設定 CMIC_MASK_UNICODE 旗標,請將 pici 轉換成 CMINVOKECOMMANDINFOEX。 這可讓應用程式使用 結構最後五個成員中包含的 Unicode 資訊。
結構的 lpVerb 或 lpVerbW 成員可用來識別要執行的命令。 下列兩種方式之一會識別命令:
- 根據命令中的動詞字串
- 依命令的標識碼位移
若要區分這兩個案例,請檢查 ANSI 案例的 lpVerb 的高階字,或檢查 Unicode 案例的 lpVerbW 。 如果高階單字不是零字,lpVerb 或 lpVerbW 保留動詞字串。 如果高序字為零,則命令位移為低序字組,lpVerb。
下列範例示範 IContextMenu::InvokeCommand 的簡單實作,其對應於本節前後提供的 IContextMenu::QueryContextMenu::QueryContextMenu 和 IContextMenu::GetCommandString 範例。 方法首先會確定傳入的是哪一個結構。 然後,它會判斷命令是由它的位移或動詞所識別。 如果 lpVerb 或 lpVerbW 保留有效的動詞或位移,則方法會顯示消息框。
STDMETHODIMP CShellExtension::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)
{
BOOL fEx = FALSE;
BOOL fUnicode = FALSE;
if(lpcmi->cbSize == sizeof(CMINVOKECOMMANDINFOEX))
{
fEx = TRUE;
if((lpcmi->fMask & CMIC_MASK_UNICODE))
{
fUnicode = TRUE;
}
}
if( !fUnicode && HIWORD(lpcmi->lpVerb))
{
if(StrCmpIA(lpcmi->lpVerb, m_pszVerb))
{
return E_FAIL;
}
}
else if( fUnicode && HIWORD(((CMINVOKECOMMANDINFOEX *) lpcmi)->lpVerbW))
{
if(StrCmpIW(((CMINVOKECOMMANDINFOEX *)lpcmi)->lpVerbW, m_pwszVerb))
{
return E_FAIL;
}
}
else if(LOWORD(lpcmi->lpVerb) != IDM_DISPLAY)
{
return E_FAIL;
}
else
{
MessageBox(lpcmi->hwnd,
"The File Name",
"File Name",
MB_OK|MB_ICONINFORMATION);
}
return S_OK;
}
IContextMenu::QueryContextMenu 方法
Shell 會呼叫 IContextMenu::QueryContextMenu,讓快捷方式功能表處理程式將其功能表項新增至功能表。 它會在 hmenu 參數中傳遞 HMENU 句柄。 indexMenu 參數被設定為用於加入第一個功能表項的索引。
處理程式新增的任何功能表項都必須有標識符,這些標識碼落在 idCmdFirst 和 idCmdLast 參數之間。 一般而言,第一個命令標識符會設定為 idCmdFirst,而這個標識符會針對每個額外的命令遞增一(1)。 這種做法可以讓您不超過 idCmdLast,並在可能殼層會呼叫多個處理程式的情況下最大化可用標識符的數量。
項目識別碼的 命令偏移 是識別碼與 idCmdFirst中的值之間的差異。 儲存每個處理程式新增至捷徑功能表的項目的位移,因為 Shell 可能會在後續呼叫 IContextMenu::GetCommandString 或 IContextMenu::InvokeCommand時,使用它來識別項目。
您也應該將 動詞 指派給您新增的每個命令。 動詞是一個字串,可在呼叫 IContextMenu::InvokeCommand 時,取代用來識別命令的位移。 ShellExecuteEx等函式也會使用來執行捷徑選單命令。
有三個旗標可以透過與快捷方式功能表處理程式相關的 uFlags 參數傳入。 下表說明它們。
旗 | 描述 |
---|---|
CMF_DEFAULTONLY | 用戶已選取預設命令,通常是按兩下物件。 IContextMenu::QueryContextMenu 應該將控制權傳回 Shell,而不需修改功能表。 |
CMF_NODEFAULT | 功能表中的任何項目都不應該是預設的項目。 方法應該將其命令新增至功能表。 |
CMF_NORMAL | 快速選單會正常顯示。 方法應該把命令新增至選單。 |
使用 InsertMenu 或 InsertMenuItem,將選單項目新增至清單。 然後傳回 HRESULT 值,其嚴重性設定為 SEVERITY_SUCCESS。 將程式碼值設定為已分配的最大命令標識符的位移,然後加上一(1)。 例如,假設 idCmdFirst 設定為 5,而且您會將三個項目新增至具有 5、7 和 8 命令標識碼的功能表。 傳回值應為 MAKE_HRESULT(SEVERITY_SUCCESS, 0, 8 - 5 + 1)
。
下列範例示範 IContextMenu::QueryContextMenu 插入單一命令的簡單實作。 命令的標識碼位移IDM_DISPLAY,設定為零。 m_pszVerb 和 m_pwszVerb 變數是私用變數,用來以 ANSI 和 Unicode 格式儲存相關聯的語言獨立動詞字串。
#define IDM_DISPLAY 0
STDMETHODIMP CMenuExtension::QueryContextMenu(HMENU hMenu,
UINT indexMenu,
UINT idCmdFirst,
UINT idCmdLast,
UINT uFlags)
{
HRESULT hr;
if(!(CMF_DEFAULTONLY & uFlags))
{
InsertMenu(hMenu,
indexMenu,
MF_STRING | MF_BYPOSITION,
idCmdFirst + IDM_DISPLAY,
"&Display File Name");
hr = StringCbCopyA(m_pszVerb, sizeof(m_pszVerb), "display");
hr = StringCbCopyW(m_pwszVerb, sizeof(m_pwszVerb), L"display");
return MAKE_HRESULT(SEVERITY_SUCCESS, 0, USHORT(IDM_DISPLAY + 1));
}
return MAKE_HRESULT(SEVERITY_SUCCESS, 0, USHORT(0));
}
對於其他動詞實作任務,請參閱 建立內容選單處理程式。
相關主題