動的な動詞を使用してショートカット メニューをカスタマイズする
ショートカット メニュー ハンドラーは、コンテキスト メニュー ハンドラーまたは動詞ハンドラーとも呼ばれます。 ショートカット メニュー ハンドラーは、ファイルの種類ハンドラーの種類です。
このトピックは次のように構成されています。
- 静的動詞と動的な動詞について
- ショートカット メニュー ハンドラーが動的な動詞を処理する仕組み
- 修飾されていない動詞名による競合の回避
- 動的な動詞を使用したショートカット メニュー ハンドラーの登録
- IContextMenu インターフェイスの実装
- 関連トピック
静的動詞と動的な動詞について
静的動詞メソッドのいずれかを使用してショートカット メニューを実装することを強くおすすめします。 「コンテキスト メニュー ハンドラーの作成」の「静的動詞を使用したショートカット メニューのカスタマイズ」セクションに記載されている手順に従うことをおすすめします。 Windows 7 以降で静的動詞の動的動作を取得するには、「コンテキスト メニュー ハンドラーの作成」の「静的動詞の動的動作の取得」を参照してください。 静的動詞の実装の詳細と、避ける必要がある動的な動詞については、「ショートカット メニューの静的動詞または動的な動詞の選択」を参照してください。
ファイルの種類の動的動詞を登録して、ファイルの種類のショートカット メニューを拡張する必要がある場合は、このトピックで後述する手順に従ってください。
Note
32 ビット アプリケーションのコンテキストで動作するハンドラーを登録する場合、64 ビット Windows には特別な考慮事項があります。シェル動詞が 32 ビット アプリケーションのコンテキストで呼び出されると、WOW64 サブシステムはファイル システムのアクセスを一部のパスにリダイレクトします。 .exe ハンドラーがこれらのパスのいずれかに格納されている場合、このコンテキストではアクセスできません。 したがって、回避策として、リダイレクトされないパスに .exe を格納するか、実際のバージョンを起動する .exe のスタブ バージョンを格納します。
ショートカット メニュー ハンドラーが動的な動詞を処理する仕組み
ショートカット メニュー ハンドラーは、IUnknown 加えて、次の追加インターフェイスをエクスポートして、所有者が描画したメニュー項目を実装するために必要なメッセージングを処理します。
- IShellExtInit (必須)
- IContextMenu (必須)
- IContextMenu2 (省略可能)
- IContextMenu3 (省略可能)
所有者が描画したメニュー項目の詳細については、「メニューの使用」の「所有者描画メニュー項目の作成」セクションを参照してください。
シェルは IShellExtInit インターフェイスを使用してハンドラーを初期化します。 シェルが IShellExtInit::Initialize を呼び出すと、オブジェクトの名前とファイルを含むフォルダーの項目識別子リストへのポインター (PIDL) を持つデータ オブジェクトが渡されます。 hkeyProgID パラメーターは、ショートカット メニュー ハンドルが登録されているレジストリの場所です。 IShellExtInit::Initialize メソッドは、後で使用するために、データ オブジェクトからファイル名を抽出し、名前とフォルダーの項目識別子リストへのポインター (PIDL) を格納する必要があります。 ハンドラーの初期化の詳細については、「IShellExtInit の実装」を参照してください。
動詞がショートカット メニューに表示されると、最初に検出され、次にユーザーに表示され、最後に呼び出されます。 次の一覧では、これら 3 つの手順について詳しく説明します。
- シェルは IContextMenu::QueryContextMenu を呼び出します。これは、項目またはシステムの状態に基づく動詞のセットを返します。
- システムは、メソッドがショートカット メニューに項目を追加するために使用できる HMENU ハンドルを渡します。
- ユーザーがハンドラーの項目のいずれかをクリックすると、シェルは IContextMenu::InvokeCommand を呼び出 します。 ハンドラーは、適切なコマンドを実行できます。
修飾されていない動詞名による競合の回避
動詞は型ごとに登録されるため、異なる項目の動詞に同じ動詞名を使用できます。 これにより、アプリケーションは項目の種類に依存しない共通の動詞を参照できます。 この機能は便利ですが、修飾されていない名前を使用すると、同じ動詞名を選択する複数の独立系ソフトウェア ベンダー (ISV) との競合が発生する可能性があります。 これを回避するには、次のように常に動詞の前に ISV 名を付けます。
ISV_Name.verb
常にアプリケーション固有の ProgID を使用します。 ProgID が提供する ISV にファイル名拡張子をマッピングする規則を採用すると、潜在的な競合を回避できます。 ただし、一部の項目の種類ではこのマッピングが使用されないため、ベンダー固有の名前が必要です。 既にその動詞が登録されている可能性がある既存の ProgID に動詞を追加する場合は、独自の動詞を追加する前に、まず古い動詞のレジストリ キーを削除する必要があります。 2 つの動詞から動詞情報がマージされないようにする必要があります。 これを行わないと、予期しない動作が発生します。
動的な動詞を使用したショートカット メニュー ハンドラーの登録
ショートカット メニュー ハンドラーは、ファイルの種類またはフォルダーに関連付けられます。 ファイルの種類の場合、ハンドラーは次のサブキーの下に登録されます。
HKEY_CLASSES_ROOT
Program ID
shellex
ContextMenuHandlers
ショートカット メニュー ハンドラーをファイルの種類またはフォルダーに関連付けるには、まず ContextMenuHandlers サブキーの下にサブキーを作成します。 ハンドラーのサブキーに名前を付け、サブキーの既定値をハンドラーのクラス識別子 (CLSID) GUID の文字列形式に設定します。
次に、ショートカット メニュー ハンドラーをさまざまな種類のフォルダーに関連付けるには、次の例に示すように、ファイルの種類と同じ方法で、FolderType サブキーの下にハンドラーを登録します。
HKEY_CLASSES_ROOT
FolderType
shellex
ContextMenuHandlers
ハンドラーを登録できるフォルダーの種類の詳細については、「シェル拡張機能ハンドラーの登録」を参照してください。
ファイルの種類にショートカット メニューが関連付けられている場合、通常はオブジェクトをダブルクリックすると既定のコマンドが起動され、ハンドラーの 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 の 3 つのメソッドがあります。これについては、ここで詳しく説明します。
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::QueryContextMenu メソッド セクションで指定された IContextMenu::QueryContextMenu の例に対応する IContextMenu::GetCommandString の単純な実装を示しています。 ハンドラーはメニュー項目を 1 つだけ追加するため、返すことができる文字列のセットは 1 つだけです。 このメソッドは idCmd が有効かどうかをテストし、有効な場合は要求された文字列を返します。
StringCchCopy 関数は、コピーされた文字列が cchName で指定されたバッファーのサイズを超えないように、要求された文字列を pszName にコピーするために使用されます。 この例では、Windows 2000 以降の Windows エクスプローラーでのみ使用されているため、uFlags の Unicode 値のサポートのみが実装されています。
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 は CMINVOKECOMMANDINFO 構造体として Shlobj.h で宣言されていますが、実際には CMINVOKECOMMANDINFOEX 構造体を指すことがよくあります。 この構造体は CMINVOKECOMMANDINFO の拡張バージョンであり、Unicode 文字列を渡すことのできるいくつかの追加メンバーがあります。
pici の cbSize メンバーを調べて、渡された構造体を判別します。 CMINVOKECOMMANDINFOEX 構造体であり、fMask メンバーに CMIC_MASK_UNICODE フラグが設定されている場合は、pici を CMINVOKECOMMANDINFOEX にキャストします。 これにより、アプリケーションは構造体の最後の 5 つのメンバーに含まれる Unicode 情報を使用できます。
構造体の lpVerb または lpVerbW メンバーは、実行するコマンドを識別するために使用されます。 コマンドは、次の 2 つの方法のいずれかで識別されます。
- コマンドの動詞文字列
- コマンドの識別子オフセット
これら 2 つのケースを区別するには、ANSI ケースの場合は lpVerb の上位ワードを、Unicode ケースの場合は lpVerbW の上位ワードをチェックします。 上位ワードが 0 以外の場合、lpVerb または lpVerbW は動詞文字列を保持します。 上位ワードが 0 の場合、コマンド オフセットは lpVerb の下位ワードにあります。
次の例は、このセクションの前後に指定された IContextMenu::QueryContextMenu と IContextMenu::GetCommandString の例に対応する IContextMenu::InvokeCommand の単純な実装を示しています。 メソッドは、最初に渡される構造体を決定します。 次に、コマンドがそのオフセットまたは動詞によって識別されるかどうかを決定します。 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 メソッド
シェルは IContextMenu::QueryContextMenu を呼び出して、ショートカット メニュー ハンドラーがメニュー項目をメニューに追加できるようにします。 hmenu パラメーターの HMENU ハンドルを渡します。 indexMenu パラメーターは、追加する最初のメニュー項目に使用するインデックスに設定されます。
ハンドラーによって追加されるすべてのメニュー項目には、idCmdFirst パラメーターと idCmdLast パラメーターの値の間にある識別子が必要です。 通常、最初のコマンド識別子は idCmdFirst に設定され、追加コマンドごとに 1 ずつインクリメントされます。 この方法は、idCmdLast を超えないようにするのに役立ち、シェルが複数のハンドラーを呼び出した場合に使用できる識別子の数を最大化します。
項目識別子のコマンド オフセットは、idCmdFirst 内の識別子と値の違いです。 後で IContextMenu::GetCommandString または IContextMenu::InvokeCommand を呼び出した場合にシェルで項目を識別する場合があるため、ハンドラーがショートカット メニューに追加する各項目のオフセットを格納します。
また、追加する各コマンドに動詞を割り当てる必要もあります。 動詞は、IContextMenu::InvokeCommand が呼び出されたときにコマンドを識別するためにオフセットの代わりに使用できる文字列です。 また、ShellExecuteEx などの関数でショートカット メニュー コマンドを実行するためにも使用されます。
ショートカット メニュー ハンドラーに関連する uFlags パラメーターを介して渡すことができる 3 つのフラグがあります。 これらについては、次の表で説明します。
フラグ | 説明 |
---|---|
CMF_DEFAULTONLY | ユーザーは既定のコマンドを選択しました。通常は、オブジェクトをダブルクリックします。 IContextMenu::QueryContextMenu は、メニューを変更せずにシェルに制御を返す必要があります。 |
CMF_NODEFAULT | メニュー内の項目が既定の項目である必要はありません。 メソッドは、そのコマンドをメニューに追加する必要があります。 |
CMF_NORMAL | ショートカット メニューが通常どおりに表示されます。 メソッドは、そのコマンドをメニューに追加する必要があります。 |
リストにメニュー項目を追加するには、InsertMenu または InsertMenuItem を使用します。 次に、重大度が SEVERITY_SUCCESS に設定された HRESULT 値を返します。 コード値を、割り当てられた最大のコマンド識別子のオフセットに 1 を加えたものに設定します。 たとえば、idCmdFirst が 5 に設定され、コマンド識別子が 5、7、8 の 3 つの項目をメニューに追加するとします。 戻り値が MAKE_HRESULT(SEVERITY_SUCCESS, 0, 8 - 5 + 1)
になる必要があります。
次の例は、1 つのコマンドを挿入する IContextMenu::QueryContextMenu の簡単な実装を示しています。 コマンドの識別子オフセットは IDM_DISPLAY で、0 に設定されます。 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));
}
その他の動詞実装タスクについては、「コンテキスト メニュー ハンドラーの作成」を参照してください。
関連トピック