將已封裝傳統型應用程式與檔案總管整合

某些 Windows 應用程式會定義檔案總管延伸模組,以新增操作功能表項目,讓客戶能夠執行與應用程式相關的選項。 舊版 Windows 應用程式部署技術,例如 MSI 和 ClickOnce 會透過登錄定義檔案總管延伸模組。 登錄有一系列 Hive,可控制檔案總管延伸模組和其他類型的 Shell 延伸模組。 這些安裝程式通常會建立一系列的登錄機碼,以設定要包含在操作功能表中的各種項目。

如果您使用 MSIX 封裝 Windows 應用程式,登錄就會虛擬化,因此您的應用程式無法透過登錄註冊檔案總管延伸模組。 相反地,您必須透過套件延伸模組來定義檔案總管延伸模組,您可以在套件資訊清單中定義。 本文說明數種執行此項作業的方式。

您可以在 GitHub 上找到本文中使用的完整範例程式碼。

新增支援啟動參數的操作功能表項目

與檔案總管整合的最簡單方式之一是定義套件延伸模組,當使用者以滑鼠右鍵按一下 [檔案總管] 中的特定檔案類型時,將您的應用程式新增至操作功能表中的可用應用程式清單。 如果使用者開啟您的應用程式,您的延伸模組可以將參數傳遞至您的應用程式。

此案例有幾個限制:

  • 它只能與檔案類型關聯功能搭配運作。 您只能針對與主要應用程式相關聯的檔案類型,在操作功能表中顯示其他選項 (例如,您的應用程式支援在 [檔案總管] 中按兩下檔案來開啟檔案)。
  • 只有在您的應用程式設定為該檔案類型的預設值時,才會顯示操作功能表中的選項。
  • 唯一支援的動作是啟動應用程式的主要可執行檔 (也就是連線到 [開始] 功能表項目的相同執行檔)。 不過,每個動作都可以指定不同的參數,當應用程式開始瞭解哪些動作觸發執行並執行不同的工作時,您可以使用這些參數。

儘管有這些限制,但這種方法在許多案例中都已足夠。 例如,如果您要建置影像編輯器,您可以輕鬆地在操作功能表中新增項目來調整影像大小,這會使用精靈直接啟動影像編輯器,以啟動調整大小程式。

實作操作功能表項目

若要支援此案例,請將類別目錄為 windows.fileTypeAssociationExtension 元素新增至套件資訊清單。 此元素必須新增為 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 子元素。 此元素包含一個或多個 Verb 元素,這些元素定義當使用者以滑鼠右鍵按一下檔案總管中副檔名為 .foo 的檔案時,將會列出的選項。 如需詳細資訊,請參閱將選項新增至具有特定檔案類型的檔案操作功能表。 以下是與 Verb 元素相關的最重要的項目。

屬性或元素 描述
Id 屬性 指定動作的唯一識別碼。
Parameters 屬性 FileTypeAssociation 元素類似,Verb 元素的這個屬性包含當使用者按一下操作功能表項目時傳遞至應用程式的參數。 一般而言,除了 %1 特殊參數來取得所選檔案的路徑以外,您也會傳遞一個或多個參數來取得內容。 這可讓您的應用程式瞭解它已從操作功能表項目開啟。
元素值 Verb 元素的值包含要顯示在操作功能表項目中的標籤 (在此範例中為 調整檔案大小)。

存取應用程式程式碼中的啟動參數

應用程式接收參數的方式取決於您所建立的應用程式類型。 例如,WPF 應用程式通常會在 App 類別的 OnStartup 方法中處理啟動事件引數。 您可以檢查是否有啟動參數,並依據結果採取最適當的動作 (例如開啟應用程式的特定視窗,而不是主要視窗)。

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();
        }
    }
}

下列螢幕擷取畫面示範上一個範例所建立調整檔案大小操作功能表項目。

Screenshot of Resize file command in the shortcut menu

支援一般檔案或資料夾,並執行複雜的工作

雖然按照上一節所述在套件資訊清單中使用 FileTypeAssociation 延伸模組足以滿足許多案例,但您可能會發現它有限制。 兩個最大的挑戰是:

  • 您只能處理與您相關聯的檔案類型。 例如,您無法處理泛型資料夾。
  • 您只能使用一系列參數啟動應用程式。 您無法執行進階作業,例如啟動另一個可執行檔或執行工作,而不需要開啟主要應用程式。

若要達成這些目標,您必須建立殼層延伸模組,以提供更強大的方式來與檔案總管整合。 在此案例中,您會建立 DLL,其中包含管理檔案操作功能表所需的所有項目,包括標籤、圖示、狀態及要執行的工作。 由於此功能是在 DLL 中實作,因此您幾乎可以執行一般應用程式所能執行的作業。 實作 DLL 之後,您必須透過套件資訊清單中定義的延伸模組來註冊它。

注意

本節所述的處理程序有一項限制。 在目的電腦上安裝包含延伸模組的 MSIX 套件之後,必須先重新啟動檔案總管,才能載入殼層延伸模組。 若要達成此目的,使用者可以重新啟動電腦,或使用 Task Manager 重新啟動 explorer.exe 程式。

實作殼層延伸模組功能

殼層延伸模組是以 COM (元件物件模型) 為基礎。 您的 DLL 公開在系統登錄中註冊的一個或多個 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 為基礎,所以實作 DLL 必須公開為 COM 伺服器,讓 Windows 可以將它與檔案總管整合。 一般而言,這是藉由將唯一識別碼 (稱為 CLSID) 指派給 COM 伺服器,並在系統登錄的特定 Hive 中註冊它來完成。 在 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.comServercom.Extension 元素新增至套件資訊清單。 此元素必須新增為 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 元素中設定兩個重要屬性。

屬性 描述
Id 屬性 這必須與您想要註冊之物件的 CLSID 相符。 在此範例中,這是與 CExplorerCommandVerb 類別相關聯之 Dll.h 檔案中宣告的 CLSID。
Path 屬性 這必須包含公開 COM 物件的 DLL 名稱。 這個範例會在封裝的根目錄中包含 DLL,因此它只能指定 ExplorerCommandVerb 專案所產生的 DLL 名稱。

接下來,新增另一個延伸模組功能,以註冊檔案操作功能表。 若要這樣做,請將類別目錄 windows.fileExplorerContextMenusdesktop4:Extension 元素新增至套件資訊清單。 此元素也必須新增為 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 元素底下設定兩個重要屬性。

屬性或元素 描述
desktop5:ItemTypeType 屬性 這會定義您想要與操作功能表產生關聯的項目類型。 如果您想為所有檔案顯示它,它可以是一個星號 (*);它可以是特定的副檔名 (.foo);或者它可以用於資料夾 (Directory)。
desktop5:VerbClsid 屬性 這必須符合您先前在套件資訊清單檔案中註冊為 COM 伺服器的 CLSID。

在封裝中設定 DLL

在 MSIX 套件的根目錄中,包含實作殼層延伸模組的 DLL (在此範例中為 ExplorerCommandVerb.dll)。 如果您使用 Windows 應用程式封裝專案,最簡單的解決方案是將 DLL 複製並貼到專案中,並確定 DLL 檔案屬性的複製到輸出目錄選項會設定為如果較新版本則複製

若要確定套件一律包含最新版本的 DLL,您可以將建置後事件新增至殼層延伸模組專案,如此一來,每次建置時,DLL 都會複製到 Windows 應用程式封裝專案。

重新啟動檔案總管

安裝殼層延伸模組套件之後,您必須先重新啟動檔案總管,才能載入殼層延伸模組。 這是透過 MSIX 套件部署和註冊的殼層延伸模組限制。

若要測試殼層延伸模組功能,請使用 Task Manager 重新啟動電腦,或重新啟動 explorer.exe 處理程序。 這麼做之後,您應該可以在操作功能表中看到項目。

Screenshot of the custom context menu entry

如果您按一下該函式,將會呼叫 CExplorerCommandVerb::_ThreadProc 函式,以顯示具有所選資料夾路徑的訊息方塊。

Screenshot of the custom popup