次の方法で共有


パッケージ化されたデスクトップ アプリをエクスプローラーと統合する

一部の Windows アプリでは、ユーザーがアプリに関連するオプションを実行できるようにするコンテキスト メニュー エントリを追加するエクスプローラー拡張機能が定義されています。 MSI や ClickOnce などの以前の Windows アプリ展開テクノロジでは、レジストリを介してエクスプローラーの拡張機能が定義されています。 レジストリには、エクスプローラーの拡張機能やその他の種類のシェル拡張機能を制御する一連のハイブがあります。 通常、これらのインストーラーは、コンテキスト メニューに含めるさまざまな項目を構成する一連のレジストリ キーを作成します。

MSIX使用して Windows アプリをパッケージ化すると、レジストリが仮想化されるため、アプリはレジストリを介してエクスプローラー拡張機能を登録できません。 代わりに、パッケージ マニフェストで定義するパッケージ拡張機能を使用して、エクスプローラーの拡張機能を定義する必要があります。 この記事では、これを行ういくつかの方法について説明します。

この記事 で使用されている完全なサンプル コードは、GitHubにあります。

スタートアップ パラメーターをサポートするコンテキスト メニュー エントリを追加する

エクスプローラーと統合する最も簡単な方法の 1 つは、ユーザーがエクスプローラーで特定のファイルの種類を右クリックしたときに、コンテキスト メニューで使用可能なアプリの一覧にアプリを追加するパッケージ拡張機能を定義することです。 ユーザーがアプリを開いた場合、拡張機能はアプリにパラメーターを渡すことができます。

このシナリオには、いくつかの制限があります。

  • これは、ファイルの種類の関連付け機能と組み合わせてのみ機能します。 メイン アプリに関連付けられているファイルの種類に対してのみ、コンテキスト メニューに追加のオプションを表示できます (たとえば、アプリではエクスプローラーでファイルをダブルクリックしてファイルを開くことができます)。
  • コンテキスト メニューのオプションは、アプリがそのファイルの種類の既定として設定されている場合にのみ表示されます。
  • サポートされている唯一のアクションは、アプリのメイン実行可能ファイル (つまり、[スタート] メニュー エントリに接続されているのと同じ実行可能ファイル) を起動することです。 ただし、すべてのアクションで異なるパラメーターを指定できます。これは、アプリが実行をトリガーしたアクションを理解し、さまざまなタスクを実行するときに使用できます。

これらの制限にもかかわらず、このアプローチは多くのシナリオで十分です。 たとえば、イメージ エディターを構築している場合は、コンテキスト メニューに簡単にエントリを追加してイメージのサイズを変更できます。イメージ エディターがウィザードで直接起動され、サイズ変更プロセスが開始されます。

コンテキスト メニュー エントリを実装する

このシナリオをサポートするには、カテゴリ を持つ windows.fileTypeAssociation 要素をパッケージ マニフェストに追加します。 この要素は、Application 要素の下に Extensions 要素の子として追加する必要があります。

次の例では、.foo 拡張子を持つファイルのコンテキスト メニューを有効にするアプリの登録を示します。 この例では、.foo 拡張機能を指定します。これは、通常、特定のコンピューター上の他のアプリに登録されていない偽の拡張機能であるためです。 既に取得されているファイルの種類 (.txt や .jpgなど) を管理する必要がある場合は、アプリがそのファイルの種類の既定として設定されるまで、オプションを表示できないことに注意してください。 この例は、GitHub の関連サンプルの Package.appxmanifest ファイルからの抜粋です。

<Extensions>
  <uap3:Extension Category="windows.fileTypeAssociation">
    <uap3:FileTypeAssociation Name="foo" Parameters="&quot;%1&quot;">
      <uap:SupportedFileTypes>
        <uap:FileType>.foo</uap:FileType>
      </uap:SupportedFileTypes>
      <uap2:SupportedVerbs>
        <uap3:Verb Id="Resize" Parameters="&quot;%1&quot; /p">Resize file</uap3:Verb>
      </uap2:SupportedVerbs>
    </uap3:FileTypeAssociation>
  </uap3:Extension>
</Extensions>

この例では、マニフェストのルート <Package> 要素で次の名前空間とエイリアスが宣言されていることを前提としています。

<Package
  xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
  xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
  xmlns:uap2="http://schemas.microsoft.com/appx/manifest/uap/windows10/2"
  xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
  xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
  IgnorableNamespaces="uap uap2 uap3 rescap">
  ...
</Package>

FileTypeAssociation 要素は、アプリをサポートするファイルの種類に関連付けます。 詳細については、「パッケージ アプリケーションを一連のファイルの種類関連付ける」を参照してください。 この要素に関連する最も重要な項目を次に示します。

属性または要素 説明
Name 属性 登録する拡張機能の名前からドットを引いた名前と一致します (前の例では、foo)。
Parameters 属性 ユーザーがそのような拡張子を持つファイルをダブルクリックしたときにアプリケーションに渡すパラメーターが含まれます。 通常、少なくとも、%1を渡します。これは、選択したファイルのパスを含む特殊なパラメーターです。 この方法では、ファイルをダブルクリックすると、アプリケーションはその完全なパスを認識し、読み込むことができます。
SupportedFileTypes 要素 ドット (この例では、.foo) を含む、登録する拡張機能の名前を指定します。 より多くのファイルの種類をサポートする複数の <FileType> エントリを指定できます。

コンテキスト メニューの統合を定義するには、SupportedVerbs 子要素も追加する必要があります。 この要素には、エクスプローラーで拡張子が .foo のファイルを右クリックしたときに表示されるオプションを定義する 1 つ以上の Verb 要素が含まれています。 詳細については、「特定のファイルタイプを持つファイルのコンテキストメニューにオプションを追加する」を参照してください。 Verb 要素に関連する最も重要な項目を次に示します。

属性または要素 説明
Id 属性 アクションの識別子を一意に指定します。
Parameters 属性 FileTypeAssociation 要素と同様に、Verb 要素のこの属性には、ユーザーがコンテキスト メニュー エントリをクリックしたときにアプリケーションに渡されるパラメーターが含まれます。 通常、選択したファイルのパスを取得する特殊なパラメーター %1 以外に、コンテキストを取得するために 1 つ以上のパラメーターも渡します。 これにより、アプリはコンテキスト メニュー エントリから開かれたことを理解できます。
要素の値 Verb 要素の値には、コンテキスト メニュー エントリに表示するラベルが含まれています (この例では、Resize file)。

アプリ コードのスタートアップ パラメーターにアクセスする

アプリがパラメーターを受け取る方法は、作成したアプリの種類によって異なります。 たとえば、WPF アプリは通常、OnStartup クラスの App メソッドでスタートアップ イベント引数を処理します。 スタートアップ パラメーターがあるかどうかを確認し、結果に基づいて最も適切なアクションを実行できます (メインウィンドウではなく、アプリケーションの特定のウィンドウを開くなど)。

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        if (e.Args.Contains("Resize"))
        {
            // Open a specific window of the app.
        }
        else
        {
            MainWindow main = new MainWindow();
            main.Show();
        }
    }
}

次のスクリーンショットは、前の例で作成した Resize File コンテキスト メニュー エントリを示しています。

ショートカット メニューの [ファイルのサイズ変更] コマンドのスクリーンショット

汎用ファイルまたはフォルダーをサポートし、複雑なタスクを実行する

前のセクションで説明したように、パッケージ マニフェストで FileTypeAssociation 拡張子を使用するだけで十分ですが、制限がある場合があります。 2 つの最大の課題は次のとおりです。

  • 関連付けられているファイルの種類のみを処理できます。 たとえば、汎用フォルダーを処理することはできません。
  • アプリを起動できるのは、一連のパラメーターのみです。 別の実行可能ファイルを起動したり、メイン アプリを開かずにタスクを実行したりするなど、高度な操作を実行することはできません。

これらの目標を達成するには、エクスプローラーと統合するためのより強力な方法を提供する Shell 拡張機能を作成する必要があります。 このシナリオでは、実行するラベル、アイコン、状態、タスクなど、ファイル コンテキスト メニューの管理に必要なすべてのものを含む DLL を作成します。 この機能は DLL に実装されているため、通常のアプリで実行できるほぼすべての操作を実行できます。 DLL を実装したら、パッケージ マニフェストで定義した拡張機能を使用して DLL を登録する必要があります。

このセクションで説明するプロセスには、1 つの制限があります。 拡張機能を含む MSIX パッケージがターゲット コンピューターにインストールされたら、シェル拡張機能を読み込む前にエクスプローラーを再起動する必要があります。 これを実現するには、コンピューターを再起動するか、タスク マネージャーを使用して explorer.exe プロセス 再起動します。

シェル拡張機能を実装する

シェル拡張機能は、COM (コンポーネント オブジェクト モデル)に基づいています。 DLL は、システム レジストリに登録されている 1 つ以上の COM オブジェクトを公開します。 Windows はこれらの COM オブジェクトを検出し、拡張機能をエクスプローラーと統合します。 コードを Windows シェルと統合しているため、パフォーマンスとメモリのフットプリントが重要です。 そのため、これらの種類の拡張機能は通常、C++ を使用してビルドされます。

シェル拡張機能を実装する方法を示すサンプル コードについては、GitHub の関連サンプルの explorerCommandVerb プロジェクト を参照してください。 このプロジェクトは、Windows デスクトップ サンプル このサンプル に基づいており、最新バージョンの Visual Studio でサンプルをより使いやすくするために、いくつかのリビジョンがあります。

このプロジェクトには、動的メニューと静的メニュー、DLL の手動登録など、さまざまなタスクの定型コードが多数含まれています。 MSIX を使用してアプリをパッケージ化する場合、このコードのほとんどは必要ありません。パッケージ化のサポートによってこれらのタスクが自動的に処理されるためです。 ExplorerCommandVerb.cpp ファイルにはコンテキスト メニューの実装が含まれており、このチュートリアルで使用するメイン コード ファイルです。

キー関数は CExplorerCommandVerb::Invokeです。 これは、ユーザーがコンテキスト メニューのエントリをクリックしたときに呼び出される関数です。 このサンプルでは、パフォーマンスへの影響を最小限に抑えるために、操作は別のスレッドで実行されるため、実際には CExplorerCommandVerb::_ThreadProcで実際の実装を見つけることができます。

DWORD CExplorerCommandVerb::_ThreadProc()
{
	IShellItemArray* psia;
	HRESULT hr = CoGetInterfaceAndReleaseStream(_pstmShellItemArray, IID_PPV_ARGS(&psia));
	_pstmShellItemArray = NULL;
	if (SUCCEEDED(hr))
	{
		DWORD count;
		psia->GetCount(&count);

		IShellItem2* psi;
		HRESULT hr = GetItemAt(psia, 0, IID_PPV_ARGS(&psi));
		if (SUCCEEDED(hr))
		{
			PWSTR pszName;
			hr = psi->GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, &pszName);
			if (SUCCEEDED(hr))
			{
				WCHAR szMsg[128];
				StringCchPrintf(szMsg, ARRAYSIZE(szMsg), L"%d item(s), first item is named %s", count, pszName);

				MessageBox(_hwnd, szMsg, L"ExplorerCommand Sample Verb", MB_OK);

				CoTaskMemFree(pszName);
			}

			psi->Release();
		}
		psia->Release();
	}

	return 0;
}

ユーザーがファイルまたはフォルダーを右クリックすると、選択したファイルまたはフォルダーの完全なパスを含むメッセージ ボックスが表示されます。 シェル拡張機能を他の方法でカスタマイズする場合は、サンプルで次の関数を拡張できます。

  • GetTitle 関数を変更して、コンテキスト メニューのエントリのラベルをカスタマイズできます。
  • GetIcon 関数を変更して、コンテキスト メニューのエントリの近くに表示されるアイコンをカスタマイズできます。
  • GetTooltip 関数を変更して、コンテキスト メニューでエントリをポイントしたときに表示される ツールヒントの をカスタマイズできます。

シェル拡張機能を登録する

シェル拡張機能は COM に基づいているため、Windows がエクスプローラーと統合できるように、実装 DLL を COM サーバーとして公開する必要があります。 通常、これは、COM サーバーに一意の ID (CLSID と呼ばれる) を割り当て、システム レジストリの特定のハイブに登録することによって行われます。 ExplorerCommandVerb プロジェクトでは、CExplorerCommandVerb 拡張子の CLSID は、Dll.h ファイルで定義されます。

class __declspec(uuid("CC19E147-7757-483C-B27F-3D81BCEB38FE")) CExplorerCommandVerb;

MSIX パッケージにシェル拡張 DLL をパッケージ化する場合は、同様の方法に従います。 ただし、ここで説明するように、GUID はレジストリではなくパッケージ マニフェスト内に登録する必要

パッケージ マニフェストで、まず、Package 要素に次の名前空間を追加します。

<Package
  xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
  xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4"
  xmlns:desktop5="http://schemas.microsoft.com/appx/manifest/desktop/windows10/5"
  xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10" 
  IgnorableNamespaces="desktop desktop4 desktop5 com">
    
    ...
</Package>

CLSID を登録するには、パッケージ マニフェストに カテゴリを持つ windows.comServer 要素を追加します。 この要素は、Application 要素の下に Extensions 要素の子として追加する必要があります。 この例は、GitHub の関連サンプルの Package.appxmanifest ファイルからの抜粋です。

<com:Extension Category="windows.comServer">
  <com:ComServer>
    <com:SurrogateServer DisplayName="ContextMenuSample">
      <com:Class Id="CC19E147-7757-483C-B27F-3D81BCEB38FE" Path="ExplorerCommandVerb.dll" ThreadingModel="STA"/>
    </com:SurrogateServer>
  </com:ComServer>
</com:Extension>

com:Class 要素で構成する重要な属性は 2 つあります。

特性 説明
Id 属性 これは、登録するオブジェクトの CLSID と一致している必要があります。 この例では、これは、Dll.h クラスに関連付けられている CExplorerCommandVerb ファイルで宣言されている CLSID です。
Path 属性 これには、COM オブジェクトを公開する DLL の名前が含まれている必要があります。 この例では、パッケージのルートに DLL が含まれているため、ExplorerCommandVerb プロジェクトによって生成される DLL の名前を指定できます。

次に、ファイル コンテキスト メニューを登録する別の拡張機能を追加します。 これを行うには、desktop4:Extension 要素をパッケージ マニフェストに windows.fileExplorerContextMenus カテゴリと共に追加します。 この要素は、Application 要素の下に Extensions 要素の子として追加する必要もあります。

<desktop4:Extension Category="windows.fileExplorerContextMenus">
  <desktop4:FileExplorerContextMenus>
    <desktop5:ItemType Type="Directory">
      <desktop5:Verb Id="Command1" Clsid="CC19E147-7757-483C-B27F-3D81BCEB38FE" />
    </desktop5:ItemType>
  </desktop4:FileExplorerContextMenus>
</desktop4:Extension>

desktop4:Extension 要素の下に構成する重要な属性は 2 つあります。

属性または要素 説明
Type 属性 これにより、コンテキスト メニューに関連付ける項目の種類が定義されます。 すべてのファイルに対して表示する場合は、星 (*) にすることができます。特定のファイル拡張子 (.foo) を指定できます。または、フォルダー (Directory) で使用できます。
Clsid 属性 これは、以前に COM サーバーとしてパッケージ マニフェスト ファイルに登録した CLSID と一致する必要があります。

パッケージ内の DLL を構成する

シェル拡張機能を実装する DLL (このサンプルでは、ExplorerCommandVerb.dll) を MSIX パッケージのルートに含めます。 Windows アプリケーション パッケージ プロジェクトを使用している場合、最も簡単な解決策は、プロジェクトに DLL をコピーして貼り付け、DLL ファイルのプロパティの [出力ディレクトリにコピー] オプションが [新しいの場合は Copy] に設定されていることを確認することです。

パッケージに最新バージョンの DLL が常に含まれていることを確認するために、ビルド後の イベント をシェル拡張プロジェクトに追加して、ビルドするたびに DLL が Windows アプリケーション パッケージ プロジェクトにコピーされるようにすることができます。

エクスプローラーを再起動する

シェル拡張機能パッケージをインストールしたら、シェル拡張機能を読み込む前にエクスプローラーを再起動する必要があります。 これは、MSIX パッケージを介してデプロイおよび登録されるシェル拡張機能の制限です。

シェル拡張機能をテストするには、PC を再起動するか、タスク マネージャーを使用して explorer.exe プロセス 再起動します。 これを行うと、コンテキスト メニューにエントリが表示されます。

カスタム コンテキスト メニュー エントリの のスクリーンショット

これをクリックすると、CExplorerCommandVerb::_ThreadProc 関数が呼び出され、選択したフォルダーのパスを含むメッセージ ボックスが表示されます。

カスタム ポップアップ のスクリーンショット