Intégrer une application de bureau empaquetée avec l’Explorateur de fichiers

Certaines applications Windows définissent des extensions de l’Explorateur de fichiers qui ajoutent des entrées de menu contextuel permettant aux clients d’effectuer des options associées à l’application. Les anciennes technologies de déploiement d’applications Windows comme MSI et ClickOnce définissent les extensions de l’Explorateur de fichiers par le biais du Registre. Le Registre comporte une série de ruches qui contrôlent les extensions de l’Explorateur de fichiers et d’autres types d’extensions Shell. Ces programmes d’installation créent généralement une série de clés de Registre qui permettent de configurer les différents éléments à inclure dans le menu contextuel.

Si vous empaquetez votre application Windows au format MSIX, le Registre est virtualisé. Votre application ne peut donc pas inscrire les extensions de l’Explorateur de fichiers par le biais du Registre. Vous devez alors définir vos extensions de l’Explorateur de fichiers par le biais d’extensions de package que vous définissez dans le manifeste du package. Cet article décrit plusieurs façons de procéder.

L’exemple de code complet utilisé dans cet article est disponible sur GitHub.

Ajouter une entrée de menu contextuel prenant en charge les paramètres de démarrage

L’une des moyens les plus simples d’intégrer une application à l’Explorateur de fichiers consiste à définir une extension de package qui ajoute votre application à la liste des applications disponibles dans le menu contextuel quand un utilisateur clique avec le bouton droit sur un type de fichier spécifique dans l’Explorateur de fichiers. Si l’utilisateur ouvre votre application, votre extension peut passer des paramètres à l’application.

Ce scénario présente plusieurs limitations :

  • Il fonctionne uniquement en combinaison avec la fonctionnalité d’association de types de fichiers. Vous pouvez uniquement afficher des options supplémentaires dans le menu contextuel pour les types de fichiers associés à l’application principale (par exemple, un double-clic sur un fichier dans l’Explorateur de fichiers entraîne son ouverture dans votre application).
  • Les options du menu contextuel n’apparaissent que si votre application est définie par défaut pour ce type de fichier.
  • La seule action prise en charge est le lancement de l’exécutable principal de l’application (le même que celui connecté à l’entrée du menu Démarrer). Toutefois, chaque action peut spécifier des paramètres différents, que vous pouvez utiliser quand les applications commencent à comprendre l’action qui a déclenché l’exécution et effectuent différentes tâches.

Malgré ces limitations, cette approche est suffisante dans de nombreux scénarios. Par exemple, si vous créez un éditeur d’images, vous pouvez facilement ajouter une entrée dans le menu contextuel pour redimensionner une image. L’éditeur d’image est alors lancé directement avec un Assistant pour démarrer le processus de redimensionnement.

Implémenter l’entrée de menu contextuel

Pour prendre en charge ce scénario, ajoutez un élément Extension avec la catégorie windows.fileTypeAssociation à votre manifeste de package. Cet élément doit être ajouté en tant qu’enfant de l’élément Extension sous l’élément Application.

L’exemple suivant illustre l’inscription d’une application qui active des menus contextuels pour les fichiers avec l’extension .foo. Cet exemple spécifie l’extension factice .foo, celles-ci n’étant généralement pas inscrite auprès d’autres applications sur un ordinateur donné. Si vous devez gérer un type de fichier qui est peut-être déjà pris (comme .txt ou .jpg), n’oubliez pas que vous devez définir votre application comme application par défaut pour ce type de fichier pour voir l’option. Cet exemple est un extrait du fichier Package.appxmanifestdans l’exemple associé sur GitHub.

<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>

Cet exemple part du principe que les espaces de noms et les alias suivants sont déclarés dans l’élément <Package> racine du manifeste.

<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>

L’élément FileTypeAssociation associe votre application au(x) type(s) de fichier(s) que vous souhaitez prendre en charge. Pour plus de détails, consultez Associer votre application empaquetée à un ensemble de types de fichiers. Voici les éléments les plus importants liés à cet élément.

Attribut ou élément Description
Attribut Name Correspond au nom de l’extension que vous souhaitez inscrire, sans le point (dans l’exemple précédent, foo).
Attribut Parameters Contient les paramètres à passer à votre application quand l’utilisateur double-clique sur un fichier doté d’une telle extension. En général, vous passez au moins %1, qui est un paramètre spécial contenant le chemin du fichier sélectionné. De cette façon, quand vous double-cliquez sur un fichier, l’application connaît son chemin complet et peut le charger.
Élément SupportedFileTypes Spécifie le(s) nom(s) de l’extension à inscrire, point compris (dans cet exemple, .foo). Vous pouvez spécifier plusieurs entrées <FileType> pour prendre en charge d’autres types de fichiers.

Pour définir l’intégration au menu contextuel, vous devez également ajouter l’élément enfant SupportedVerbs. Cet élément contient un ou plusieurs éléments Verb qui définissent les options listées quand un utilisateur clique avec le bouton droit sur un fichier avec l’extension .foo dans l’Explorateur de fichiers. Pour plus de détails, consultez Ajouter des options aux menus contextuels de fichiers d’un certain type. Voici les éléments les plus importants liés à l’élément Verb.

Attribut ou élément Description
Attribut Id Spécifie l’identificateur unique de l’action.
Attribut Parameters Semblable à l’élément FileTypeAssociation, cet attribut de l’élément Verb contient les paramètres passés à votre application quand l’utilisateur clique sur l’entrée du menu contextuel. Outre le paramètre spécial %1 contenant le chemin du fichier sélectionné, vous passez en général un ou plusieurs paramètres supplémentaires liés au contexte. Cela permet à votre application de comprendre qu’elle a été ouverte à partir d’une entrée de menu contextuel.
Valeur d’élément La valeur de l’élément Verb contient l’étiquette à afficher dans l’entrée du menu contextuel (dans cet exemple, Resize file).

Accéder aux paramètres de démarrage dans le code de votre application

La façon dont votre application reçoit les paramètres dépend du type d’application que vous avez créé. Par exemple, une application WPF traite généralement les arguments d’événement de démarrage dans la méthode OnStartup de la classe App. Vous pouvez vérifier s’il existe des paramètres de démarrage et, en fonction du résultat, prendre l’action la plus appropriée (comme ouvrir une fenêtre spécifique de l’application au lieu de la fenêtre principale).

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

La capture d’écran suivante illustre l’entrée de menu contextuel Resize file créée par l’exemple précédent.

Screenshot of Resize file command in the shortcut menu

Prendre en charge des fichiers ou des dossiers génériques et effectuer des tâches complexes

Comme nous l’avons vu dans la section précédente, l’extension FileTypeAssociation convient à la plupart des scénarios. Toutefois, elle présente des limitations. Les deux limitations les plus importantes sont les suivantes :

  • Vous ne pouvez gérer que les types de fichiers avec lesquels une association est établie. Par exemple, vous ne pouvez pas gérer un dossier générique.
  • Vous pouvez uniquement lancer l’application avec une série de paramètres. Vous ne pouvez pas réaliser d’opérations avancées, comme lancer un autre exécutable ou effectuer une tâche sans ouvrir l’application principale.

Pour atteindre ces objectifs, vous devez créer une extension Shell qui fournit des méthodes d’intégration plus puissantes à l’Explorateur de fichiers. Dans ce scénario, vous créez une DLL qui contient tous les éléments requis pour gérer le menu contextuel du fichier, notamment l’étiquette, l’icône, l’état et les tâches à effectuer. Cette fonctionnalité étant implémentée dans une DLL, vous pouvez faire presque tout ce qu’il est possible de faire avec une application normale. Après avoir implémenté la DLL, vous devez l’inscrire par le biais d’extensions que vous définissez dans votre manifeste de package.

Note

Le processus décrit dans cette section a une limitation. Une fois le package MSIX contenant l’extension installé sur un ordinateur cible, l’Explorateur de fichiers doit être redémarré pour que l’extension Shell puisse être chargée. Pour cela, l’utilisateur peut redémarrer l’ordinateur ou redémarrer le processus explorer.exe à l’aide du Gestionnaire des tâches.

Implémenter l’extension Shell

Les extensions Shell sont basées sur COM (Component Object Model). Votre DLL expose un ou plusieurs objets COM inscrits dans le registre système. Windows découvre ces objets COM et intègre votre extension à l’Explorateur de fichiers. Étant donné que vous intégrez votre code au Windows Shell, les performances et l’empreinte mémoire sont importants. Ces types d’extensions sont donc généralement créés en C++.

Pour obtenir un exemple de code illustrant comment implémenter des extensions Shell, consultez le projet ExplorerCommandVerb dans l’exemple associé sur GitHub. Ce projet, basé sur cet exemple contenu dans les exemples classiques Windows, propose plusieurs révisions pour faciliter son utilisation avec les dernières versions de Visual Studio.

Ce projet contient une large section de code réutilisable pour différentes tâches, comme les menus dynamiques et statiques et l’inscription manuelle de la DLL. La majeure partie de ce code n’est pas nécessaire si vous empaquetez votre application au format MSIX, car l’empaquetage s’occupe de ces tâches pour vous. Le fichier ExplorerCommandVerb.cpp contient l’implémentation du menu contextuel. Il s’agit du fichier de code le plus intéressant pour cette procédure pas à pas.

La fonction clé est CExplorerCommandVerb::Invoke. Cette fonction est appelée quand un utilisateur clique sur l’entrée dans le menu contextuel. Dans l’exemple, pour minimiser l’impact sur les performances, l’opération est effectuée sur un autre thread. La véritable implémentation se trouve donc dans 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;
}

Quand un utilisateur clique avec le bouton droit sur un fichier ou sur un dossier, cette fonction affiche une zone de message avec le chemin complet du fichier ou du dossier sélectionné. Si vous souhaitez personnaliser l’extension Shell d’une autre manière, vous pouvez étendre les fonctions suivantes dans l’exemple :

  • Vous pouvez modifier la fonction GetTitle pour personnaliser l’étiquette de l’entrée dans le menu contextuel.
  • Vous pouvez modifier la fonction GetIcon pour personnaliser l’icône apparaissant à côté de l’entrée dans le menu contextuel.
  • Vous pouvez modifier la fonction GetTooltip pour personnaliser l’info-bulle apparaissant quand vous pointez sur l’entrée dans le menu contextuel.

Inscrire l’extension Shell

L’extension Shell étant basée sur COM, la DLL d’implémentation doit être exposée en tant que serveur COM pour que Windows puisse l’intégrer à l’Explorateur de fichiers. En général, vous effectuez cette opération en affectant un ID unique (appelé CLSID) au serveur COM et en l’inscrivant dans une ruche spécifique du Registre système. Dans le projet ExplorerCommandVerb, le CLSID de l’extension CExplorerCommandVerb est défini dans le fichier Dll.h.

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

Quand vous empaquetez une DLL d’extension Shell dans un package MSIX, vous suivez une approche similaire. Toutefois, le GUID doit être inscrit à l’intérieur du manifeste de package au lieu du Registre, comme expliqué ici.

Dans votre manifeste de package, commencez par ajouter les espaces de noms suivants à votre élément 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>

Pour inscrire le CLSID, ajoutez un élément com.Extension avec la catégorie windows.comServer à votre manifeste de package. Cet élément doit être ajouté en tant qu’enfant de l’élément Extension sous l’élément Application. Cet exemple est un extrait du fichier Package.appxmanifestdans l’exemple associé sur GitHub.

<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>

Vous devez configurer deux attributs critiques dans l’élément com:Class.

Attribute Description
Attribut Id Doit correspondre au CLSID de l’objet que vous souhaitez inscrire. Dans cet exemple, il s’agit du CLSID déclaré dans le fichier Dll.h associé à la classe CExplorerCommandVerb.
Attribut Path Doit contenir le nom de la DLL qui expose l’objet COM. Cet exemple comprend la DLL à la racine du package. Il peut donc simplement spécifier le nom de la DLL générée par le projet ExplorerCommandVerb.

Ajoutez ensuite une autre extension qui inscrit le menu contextuel du fichier. Pour cela, ajoutez un élément desktop4:Extension avec la catégorie windows.fileExplorerContextMenus à votre manifeste de package. Cet élément doit également être ajouté en tant qu’enfant de l’élément Extension sous l’élément Application.

<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>

Vous devez configurer deux attributs critiques sous l’élément desktop4:Extension.

Attribut ou élément Description
Attribut Type de desktop5:ItemType Définit le type d’élément à associer au menu contextuel. Vous pouvez indiquer une étoile (*) si vous souhaitez l’afficher pour tous les fichiers ou une extension de fichier spécifique (.foo). Il peut également être disponible pour des dossiers (Directory).
Attribut Clsid de desktop5:Verb Doit correspondre au CLSID que vous avez précédemment enregistré en tant que serveur COM dans le fichier de manifeste de package.

Configurer la DLL dans le package

Incluez la DLL qui implémente l’extension Shell (dans cet exemple, ExplorerCommandVerb.dll) à la racine du package MSIX. Si vous utilisez le Projet de création de packages d’applications Windows, la solution la plus simple consiste à copier et coller la DLL dans le projet et à vérfier que l’option Copier dans le répertoire de sortie pour les propriétés du fichier DLL est définie sur Copier si plus récent.

Pour vérifier que le package contient toujours la version la plus récente de la DLL, vous pouvez ajouter un événement après génération au projet d’extension Shell. De cette façon, chaque fois que vous la générez, la DLL est copiée dans le Projet de création de packages d’applications Windows.

Redémarrer l’Explorateur de fichiers

Une fois le package d’extension Shell installé, vous devez redémarrer l’Explorateur de fichiers pour que l’extension Shell soit chargée. Il s’agit d’une limitation des extensions Shell qui sont déployées et inscrites par le biais de packages MSIX.

Pour tester l’extension Shell, redémarrez votre ordinateur ou redémarrez le processus explorer.exe à l’aide du Gestionnaire des tâches. Après cela, vous devriez voir l’entrée dans le menu contextuel.

Screenshot of the custom context menu entry

Si vous cliquez dessus, la fonction CExplorerCommandVerb::_ThreadProc est appelée pour afficher la zone de message avec le chemin du dossier sélectionné.

Screenshot of the custom popup