Implémenter un fournisseur de widgets dans une application win32 (C++/WinRT)
Cet article vous guide tout au long de la création d’un fournisseur de widgets simple qui implémente l’interface IWidgetProvider. Les méthodes de cette interface sont appelées par l’hôte du widget pour demander les données qui définissent un widget ou pour permettre au fournisseur de widgets de répondre à une action de l’utilisateur sur un widget. Les fournisseurs de widgets peuvent prendre en charge un seul widget ou plusieurs widgets. Dans cet exemple, nous allons définir deux widgets différents. Un de ces widgets est un widget météo fictif qui illustre certaines des options de mise en forme fournies par le framework Cartes adaptatives. Le deuxième widget illustre les actions de l’utilisateur et la fonctionnalité d’état du widget personnalisé en conservant un compteur incrémenté chaque fois que l’utilisateur clique sur un bouton affiché sur le widget.
L’exemple de code de cet article est adapté de l’exemple de widgets du SDK d’application Windows. Pour implémenter un fournisseur de widgets à l’aide de C#, consultez Implémenter un fournisseur de widgets dans une application win32 (C#).
Prérequis
- Le mode développeur doit être activé sur votre appareil. Pour plus d’informations, consultez Activer votre appareil pour le développement.
- Visual Studio 2022 ou ultérieur avec la charge de travail Développement pour la plateforme Windows universelle. Veillez à ajouter le composant pour C++ (v143) dans la liste déroulante facultative.
Créer une application console win32 C++/WinRT
Dans Visual Studio, créez un projet. Dans la boîte de dialogue Créer un projet, définissez le filtre de langage sur « C++ » et le filtre de plateforme sur Windows, puis sélectionnez le modèle de projet Application console Windows (C++/WinRT). Nommez le nouveau projet « ExampleWidgetProvider ». Lorsque vous y êtes invité, définissez la version cible de Windows de l’application sur la version 1809 ou ultérieure.
Ajouter des références aux packages NuGet du SDK d’application Windows et de la bibliothèque d’implémentation Windows
Cet exemple utilise la dernière version stable du package NuGet du SDK d’application Windows. Dans l’Explorateur de solutions, cliquez avec le bouton droit sur Références et sélectionnez Gérer les packages NuGet.... Dans le gestionnaire de package NuGet, sélectionnez l’onglet Parcourir et recherchez « Microsoft.WindowsAppSDK ». Sélectionnez la dernière version stable dans la liste déroulante Version, puis cliquez sur Installer.
Cet exemple utilise également le package NuGet de la bibliothèque d’implémentation Windows. Dans l’Explorateur de solutions, cliquez avec le bouton droit sur Références et sélectionnez Gérer les packages NuGet.... Dans le gestionnaire de package NuGet, sélectionnez l’onglet Parcourir et recherchez « Microsoft.Windows.ImplementationLibrary ». Sélectionnez la dernière version dans la liste déroulante Version, puis cliquez sur Installer.
Dans le fichier d’en-tête précompilé, pch.h, ajoutez les directives include suivantes.
//pch.h
#pragma once
#include <wil/cppwinrt.h>
#include <wil/resource.h>
...
#include <winrt/Microsoft.Windows.Widgets.Providers.h>
Notes
Vous devez d’abord inclure l’en-tête wil/cppwinrt.h, avant les en-têtes WinRT.
Pour gérer correctement l’arrêt de l’application fournisseur de widgets, nous avons besoin d’une implémentation personnalisée de winrt::get_module_lock. Nous prédéclarons la méthode SignalLocalServerShutdown, qui sera définie dans notre fichier main.cpp et définira un événement signalant la fermeture de l’application. Ajoutez le code suivant à votre fichier pch.h, juste en dessous de la directive #pragma once
, avant que les autres includes.
//pch.h
#include <stdint.h>
#include <combaseapi.h>
// In .exe local servers the class object must not contribute to the module ref count, and use
// winrt::no_module_lock, the other objects must and this is the hook into the C++ WinRT ref counting system
// that enables this.
void SignalLocalServerShutdown();
namespace winrt
{
inline auto get_module_lock() noexcept
{
struct service_lock
{
uint32_t operator++() noexcept
{
return ::CoAddRefServerProcess();
}
uint32_t operator--() noexcept
{
const auto ref = ::CoReleaseServerProcess();
if (ref == 0)
{
SignalLocalServerShutdown();
}
return ref;
}
};
return service_lock{};
}
}
#define WINRT_CUSTOM_MODULE_LOCK
Ajouter une classe WidgetProvider pour gérer les opérations de widget
Dans Visual Studio, cliquez avec le bouton droit sur le projet ExampleWidgetProvider
dans l’Explorateur de solutions et sélectionnez Ajouter->Classe. Dans la boîte de dialogue Ajouter une classe, nommez la classe WidgetProvider, puis cliquez sur Ajouter.
Déclarer une classe qui implémente l’interface IWidgetProvider
L’interface IWidgetProvider définit les méthodes que l’hôte du widget appellera pour lancer des opérations avec le fournisseur de widgets. Remplacez la définition de classe vide dans le fichier WidgetProvider.h par le code suivant. Ce code déclare une structure qui implémente l’interface IWidgetProvider et déclare des prototypes pour les méthodes d’interface.
// WidgetProvider.h
struct WidgetProvider : winrt::implements<WidgetProvider, winrt::Microsoft::Windows::Widgets::Providers::IWidgetProvider>
{
WidgetProvider();
/* IWidgetProvider required functions that need to be implemented */
void CreateWidget(winrt::Microsoft::Windows::Widgets::Providers::WidgetContext widgetContext);
void DeleteWidget(winrt::hstring const& widgetId, winrt::hstring const& customState);
void OnActionInvoked(winrt::Microsoft::Windows::Widgets::Providers::WidgetActionInvokedArgs actionInvokedArgs);
void OnWidgetContextChanged(winrt::Microsoft::Windows::Widgets::Providers::WidgetContextChangedArgs contextChangedArgs);
void Activate(winrt::Microsoft::Windows::Widgets::Providers::WidgetContext widgetContext);
void Deactivate(winrt::hstring widgetId);
/* IWidgetProvider required functions that need to be implemented */
};
Ajoutez également une méthode privée, UpdateWidget, qui est une méthode d’assistance qui envoie des mises à jour de notre fournisseur à l’hôte du widget.
// WidgetProvider.h
private:
void UpdateWidget(CompactWidgetInfo const& localWidgetInfo);
Préparer le suivi des widgets activés
Un fournisseur de widgets peut prendre en charge un seul widget ou plusieurs widgets. Chaque fois que l’hôte du widget lance une opération avec le fournisseur de widgets, il transmet un ID pour identifier le widget associé à l’opération. Chaque widget a également un nom associé et une valeur d’état qui peut être utilisée pour stocker des données personnalisées. Pour cet exemple, nous allons déclarer une structure d’assistance simple pour stocker l’ID, le nom et les données de chaque widget épinglé. Les widgets peuvent également être dans un état actif, comme décrit dans la section Activer et désactiver ci-dessous, et nous allons suivre cet état pour chaque widget avec une valeur booléenne. Ajoutez la définition suivante au fichier WidgetProvider.h, au-dessus de la déclaration de struct WidgetProvider.
// WidgetProvider.h
struct CompactWidgetInfo
{
winrt::hstring widgetId;
winrt::hstring widgetName;
int customState = 0;
bool isActive = false;
};
Dans la déclaration WidgetProvider dans WidgetProvider.h, ajoutez un membre pour la carte qui conservera la liste des widgets activés, en utilisant l’ID de widget comme clé pour chaque entrée.
// WidgetProvider.h
#include <unordered_map>
struct WidgetProvider : winrt::implements<WidgetProvider, winrt::Microsoft::Windows::Widgets::Providers::IWidgetProvider>
{
...
private:
...
static std::unordered_map<winrt::hstring, CompactWidgetInfo> RunningWidgets;
Déclarer des chaînes JSON de modèle de widget
Cet exemple déclare des chaînes statiques pour définir les modèles JSON pour chaque widget. Pour des raisons pratiques, ces modèles sont stockés dans les variables locales déclarées en dehors de la définition de classe WidgetProvider. Si vous avez besoin d’un stockage général pour les modèles, ces derniers peuvent être inclus dans le package d’application : Accès aux fichiers de package. Pour plus d’informations sur la création du document JSON du modèle de widget, consultez Créer un modèle de widget avec le Concepteur de cartes adaptatives.
// WidgetProvider.h
const std::string weatherWidgetTemplate = R"(
{
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.0",
"speak": "<s>The forecast for Seattle January 20 is mostly clear with a High of 51 degrees and Low of 40 degrees</s>",
"backgroundImage": "https://messagecardplayground.azurewebsites.net/assets/Mostly%20Cloudy-Background.jpg",
"body": [
{
"type": "TextBlock",
"text": "Redmond, WA",
"size": "large",
"isSubtle": true,
"wrap": true
},
{
"type": "TextBlock",
"text": "Mon, Nov 4, 2019 6:21 PM",
"spacing": "none",
"wrap": true
},
{
"type": "ColumnSet",
"columns": [
{
"type": "Column",
"width": "auto",
"items": [
{
"type": "Image",
"url": "https://messagecardplayground.azurewebsites.net/assets/Mostly%20Cloudy-Square.png",
"size": "small",
"altText": "Mostly cloudy weather"
}
]
},
{
"type": "Column",
"width": "auto",
"items": [
{
"type": "TextBlock",
"text": "46",
"size": "extraLarge",
"spacing": "none",
"wrap": true
}
]
},
{
"type": "Column",
"width": "stretch",
"items": [
{
"type": "TextBlock",
"text": "°F",
"weight": "bolder",
"spacing": "small",
"wrap": true
}
]
},
{
"type": "Column",
"width": "stretch",
"items": [
{
"type": "TextBlock",
"text": "Hi 50",
"horizontalAlignment": "left",
"wrap": true
},
{
"type": "TextBlock",
"text": "Lo 41",
"horizontalAlignment": "left",
"spacing": "none",
"wrap": true
}
]
}
]
}
]
})";
const std::string countWidgetTemplate = R"(
{
"type": "AdaptiveCard",
"body": [
{
"type": "TextBlock",
"text": "You have clicked the button ${count} times"
},
{
"text":"Rendering Only if Medium",
"type":"TextBlock",
"$when":"${$host.widgetSize==\"medium\"}"
},
{
"text":"Rendering Only if Small",
"type":"TextBlock",
"$when":"${$host.widgetSize==\"small\"}"
},
{
"text":"Rendering Only if Large",
"type":"TextBlock",
"$when":"${$host.widgetSize==\"large\"}"
}
],
"actions": [
{
"type": "Action.Execute",
"title": "Increment",
"verb": "inc"
}
],
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.5"
})";
Implémenter les méthodes IWidgetProvider
Dans les sections suivantes, nous allons implémenter les méthodes de l’interface IWidgetProvider. La méthode d’assistance UpdateWidget appelée dans plusieurs de ces implémentations de méthode sera présentée plus loin dans cet article. Avant de plonger dans les méthodes d’interface, ajoutez les lignes suivantes à WidgetProvider.cpp
, après les directives include, pour extraire les API du fournisseur de widgets dans l’espace de noms winrt et autoriser l’accès à la carte que nous avons déclarée à l’étape précédente.
Notes
La validité des objets passés dans les méthodes de rappel de l’interface IWidgetProvider est garantie uniquement dans le rappel. Vous ne devez pas stocker les références à ces objets, car leur comportement en dehors du contexte du rappel n’est pas défini.
// WidgetProvider.cpp
namespace winrt
{
using namespace Microsoft::Windows::Widgets::Providers;
}
std::unordered_map<winrt::hstring, CompactWidgetInfo> WidgetProvider::RunningWidgets{};
CreateWidget
L’hôte de widget appelle CreateWidget lorsque l’utilisateur a épinglé l’un des widgets de votre application dans l’hôte du widget. Tout d’abord, cette méthode obtient l’ID et le nom du widget associé et ajoute une nouvelle instance de notre structure d’assistance, CompactWidgetInfo, à la collection de widgets activés. Ensuite, nous envoyons le modèle initial et les données pour le widget, qui sont encapsulés dans la méthode d’assistance UpdateWidget.
// WidgetProvider.cpp
void WidgetProvider::CreateWidget(winrt::WidgetContext widgetContext)
{
auto widgetId = widgetContext.Id();
auto widgetName = widgetContext.DefinitionId();
CompactWidgetInfo runningWidgetInfo{ widgetId, widgetName };
RunningWidgets[widgetId] = runningWidgetInfo;
// Update the widget
UpdateWidget(runningWidgetInfo);
}
DeleteWidget
L’hôte du widget appelle DeleteWidget lorsque l’utilisateur a désépinglé l’un des widgets de votre application de l’hôte du widget. Dans ce cas, nous supprimerons le widget associé de notre liste de widgets activés afin de ne pas envoyer d’autres mises à jour pour ce widget.
// WidgetProvider.cpp
void WidgetProvider::DeleteWidget(winrt::hstring const& widgetId, winrt::hstring const& customState)
{
RunningWidgets.erase(widgetId);
}
OnActionInvoked
L’hôte du widget appelle OnActionInvoked lorsque l’utilisateur interagit avec une action que vous avez définie dans votre modèle de widget. Pour le widget de compteur utilisé dans cet exemple, une action a été déclarée avec une valeur verb « inc » dans le modèle JSON du widget. Le code du fournisseur de widgets utilise cette valeur verb pour déterminer l’action à entreprendre en réponse à l’interaction de l’utilisateur.
...
"actions": [
{
"type": "Action.Execute",
"title": "Increment",
"verb": "inc"
}
],
...
Dans la méthode OnActionInvoked, obtenez la valeur du verbe en vérifiant la propriété Verb de WidgetActionInvokedArgs passé dans la méthode. Si le verbe est « inc », nous savons que nous allons incrémenter le nombre dans l’état personnalisé du widget. À partir de WidgetActionInvokedArgs, obtenez l’objet WidgetContext, puis le WidgetId pour obtenir l’ID du widget en cours de mise à jour. Recherchez l’entrée dans notre carte de widgets activés avec l’ID spécifié, puis mettez à jour la valeur d’état personnalisée utilisée pour stocker le nombre d’incréments. Enfin, mettez à jour le contenu du widget avec la nouvelle valeur avec la fonction d’assistance UpdateWidget.
// WidgetProvider.cpp
void WidgetProvider::OnActionInvoked(winrt::WidgetActionInvokedArgs actionInvokedArgs)
{
auto verb = actionInvokedArgs.Verb();
if (verb == L"inc")
{
auto widgetId = actionInvokedArgs.WidgetContext().Id();
// If you need to use some data that was passed in after
// Action was invoked, you can get it from the args:
auto data = actionInvokedArgs.Data();
if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
{
auto& localWidgetInfo = iter->second;
// Increment the count
localWidgetInfo.customState++;
UpdateWidget(localWidgetInfo);
}
}
}
Pour plus d’informations sur la syntaxe Action.Execute pour les cartes adaptatives, consultez Action.Execute. Pour obtenir des conseils sur la conception de l’interaction pour les widgets, consultez Instructions relatives à la conception des interactions des widgets
OnWidgetContextChanged
Dans la version actuelle, OnWidgetContextChanged est appelé uniquement lorsque l’utilisateur modifie la taille d’un widget épinglé. Vous pouvez choisir de retourner un modèle/des données JSON différents à l’hôte du widget en fonction de la taille demandée. Vous pouvez également concevoir le modèle JSON pour prendre en charge toutes les tailles disponibles à l’aide du rendu conditionnel basé sur la valeur de host.widgetSize. Si vous n’avez pas besoin d’envoyer un nouveau modèle ou de nouvelles données pour tenir compte du changement de taille, vous pouvez utiliser OnWidgetContextChanged à des fins de télémétrie.
// WidgetProvider.cpp
void WidgetProvider::OnWidgetContextChanged(winrt::WidgetContextChangedArgs contextChangedArgs)
{
auto widgetContext = contextChangedArgs.WidgetContext();
auto widgetId = widgetContext.Id();
auto widgetSize = widgetContext.Size();
if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
{
auto localWidgetInfo = iter->second;
UpdateWidget(localWidgetInfo);
}
}
Activer et désactiver
La méthode Activate est appelée pour informer le fournisseur de widgets que l’hôte du widget souhaite actuellement recevoir du contenu mis à jour du fournisseur. Par exemple, cela peut signifier que l’utilisateur affiche actuellement activement l’hôte du widget. La méthode Deactivate est appelée pour informer le fournisseur de widgets que l’hôte du widget ne demande plus de mises à jour de contenu. Ces deux méthodes définissent une fenêtre dans laquelle l’hôte du widget est le plus intéressé par l’affichage du contenu le plus à jour. Les fournisseurs de widgets peuvent envoyer des mises à jour au widget à tout moment, par exemple en réponse à une notification Push, mais comme pour toute tâche en arrière-plan, il est important de trouver un équilibre entre fournir du contenu à jour et les questions liées aux ressources, comme l’autonomie de la batterie.
Activate et Deactivate sont appelés pour chaque widget. Cet exemple suit le statut actif de chaque widget dans le struct d’assistance CompactWidgetInfo. Dans la méthode Activate, nous appelons la méthode d’assistance UpdateWidget pour mettre à jour notre widget. Notez que la fenêtre de temps entre Activate et Deactivate peut être petite. Il est donc recommandé d’essayer de rendre le chemin du code de mise à jour de votre widget aussi rapide que possible.
void WidgetProvider::Activate(winrt::Microsoft::Windows::Widgets::Providers::WidgetContext widgetContext)
{
auto widgetId = widgetContext.Id();
if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
{
auto& localWidgetInfo = iter->second;
localWidgetInfo.isActive = true;
UpdateWidget(localWidgetInfo);
}
}
void WidgetProvider::Deactivate(winrt::hstring widgetId)
{
if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
{
auto& localWidgetInfo = iter->second;
localWidgetInfo.isActive = false;
}
}
Mettre à jour un widget
Définissez la méthode d’assistance UpdateWidget pour mettre à jour un widget activé. Dans cet exemple, nous vérifions le nom du widget dans le struct d’assistance CompactWidgetInfo passé dans la méthode, puis nous définissons le modèle et les données JSON appropriés en fonction du widget en cours de mise à jour. WidgetUpdateRequestOptions est initialisé avec le modèle, les données et l’état personnalisé du widget en cours de mise à jour. Appelez WidgetManager::GetDefault pour obtenir une instance de la classe WidgetManager, puis appelez UpdateWidget pour envoyer les données de widget mises à jour à l’hôte du widget.
// WidgetProvider.cpp
void WidgetProvider::UpdateWidget(CompactWidgetInfo const& localWidgetInfo)
{
winrt::WidgetUpdateRequestOptions updateOptions{ localWidgetInfo.widgetId };
winrt::hstring templateJson;
if (localWidgetInfo.widgetName == L"Weather_Widget")
{
templateJson = winrt::to_hstring(weatherWidgetTemplate);
}
else if (localWidgetInfo.widgetName == L"Counting_Widget")
{
templateJson = winrt::to_hstring(countWidgetTemplate);
}
winrt::hstring dataJson;
if (localWidgetInfo.widgetName == L"Weather_Widget")
{
dataJson = L"{}";
}
else if (localWidgetInfo.widgetName == L"Counting_Widget")
{
dataJson = L"{ \"count\": " + winrt::to_hstring(localWidgetInfo.customState) + L" }";
}
updateOptions.Template(templateJson);
updateOptions.Data(dataJson);
// You can store some custom state in the widget service that you will be able to query at any time.
updateOptions.CustomState(winrt::to_hstring(localWidgetInfo.customState));
winrt::WidgetManager::GetDefault().UpdateWidget(updateOptions);
}
Initialiser la liste des widgets activés au démarrage
Lorsque notre fournisseur de widgets est initialisé pour la première fois, il est judicieux de demander à WidgetManager s’il existe des widgets en cours d’exécution que notre fournisseur sert actuellement. Cela vous aidera à récupérer l’application à l’état précédent en cas de redémarrage de l’ordinateur ou de plantage du fournisseur. Appelez WidgetManager::GetDefault pour obtenir l’instance de gestionnaire de widgets par défaut pour l’application. Appelez ensuite GetWidgetInfos, qui retourne un tableau d’objets WidgetInfo. Copiez les ID, les noms et l’état personnalisé du widget dans le struct d’assistance CompactWidgetInfo et enregistrez-les dans la variable membre RunningWidgets. Collez le code suivant dans le constructeur pour la classe WidgetProvider.
// WidgetProvider.cpp
WidgetProvider::WidgetProvider()
{
auto runningWidgets = winrt::WidgetManager::GetDefault().GetWidgetInfos();
for (auto widgetInfo : runningWidgets )
{
auto widgetContext = widgetInfo.WidgetContext();
auto widgetId = widgetContext.Id();
auto widgetName = widgetContext.DefinitionId();
auto customState = widgetInfo.CustomState();
if (RunningWidgets.find(widgetId) == RunningWidgets.end())
{
CompactWidgetInfo runningWidgetInfo{ widgetId, widgetName };
try
{
// If we had any save state (in this case we might have some state saved for Counting widget)
// convert string to required type if needed.
int count = std::stoi(winrt::to_string(customState));
runningWidgetInfo.customState = count;
}
catch (...)
{
}
RunningWidgets[widgetId] = runningWidgetInfo;
}
}
}
Inscrire une fabrique de classes qui instanciera WidgetProvider à la demande
Ajoutez l’en-tête qui définit la classe WidgetProvider sur les includes en haut du fichier de main.cpp
votre application. Nous allons également inclure mutex ici.
// main.cpp
...
#include "WidgetProvider.h"
#include <mutex>
Déclarez l’événement qui déclenchera la fermeture de notre application et la fonction SignalLocalServerShutdown qui définira l’événement. Collez le code suivant dans main.cpp.
// main.cpp
wil::unique_event g_shudownEvent(wil::EventOptions::None);
void SignalLocalServerShutdown()
{
g_shudownEvent.SetEvent();
}
Ensuite, vous devez créer un CLSID qui sera utilisé pour identifier votre fournisseur de widgets pour l’activation COM. Générez un GUID dans Visual Studio en accédant à Outils->Créer un GUID. Sélectionnez l’option « static const GUID = », cliquez sur Copier, puis collez le contenu dans main.cpp
. Mettez à jour la définition de GUID avec la syntaxe C++/WinRT suivante, en définissant le nom de la variable GUID widget_provider_clsid. Laissez la version commentée du GUID, car vous aurez besoin de ce format plus tard, lors de l’empaquetage de votre application.
// main.cpp
...
// {80F4CB41-5758-4493-9180-4FB8D480E3F5}
static constexpr GUID widget_provider_clsid
{
0x80f4cb41, 0x5758, 0x4493, { 0x91, 0x80, 0x4f, 0xb8, 0xd4, 0x80, 0xe3, 0xf5 }
};
Ajoutez la définition de fabrique de classes suivante à main.cpp
. Il s’agit principalement d’un code réutilisable qui n’est pas spécifique aux implémentations du fournisseur de widgets. Notez que CoWaitForMultipleObjects attend que notre événement d’arrêt soit déclenché avant la fermeture de l’application.
// main.cpp
template <typename T>
struct SingletonClassFactory : winrt::implements<SingletonClassFactory<T>, IClassFactory>
{
STDMETHODIMP CreateInstance(
::IUnknown* outer,
GUID const& iid,
void** result) noexcept final
{
*result = nullptr;
std::unique_lock lock(mutex);
if (outer)
{
return CLASS_E_NOAGGREGATION;
}
if (!instance)
{
instance = winrt::make<WidgetProvider>();
}
return instance.as(iid, result);
}
STDMETHODIMP LockServer(BOOL) noexcept final
{
return S_OK;
}
private:
T instance{ nullptr };
std::mutex mutex;
};
int main()
{
winrt::init_apartment();
wil::unique_com_class_object_cookie widgetProviderFactory;
auto factory = winrt::make<SingletonClassFactory<winrt::Microsoft::Windows::Widgets::Providers::IWidgetProvider>>();
winrt::check_hresult(CoRegisterClassObject(
widget_provider_clsid,
factory.get(),
CLSCTX_LOCAL_SERVER,
REGCLS_MULTIPLEUSE,
widgetProviderFactory.put()));
DWORD index{};
HANDLE events[] = { g_shudownEvent.get() };
winrt::check_hresult(CoWaitForMultipleObjects(CWMO_DISPATCH_CALLS | CWMO_DISPATCH_WINDOW_MESSAGES,
INFINITE,
static_cast<ULONG>(std::size(events)), events, &index));
return 0;
}
Empaqueter votre application de fournisseur de widgets
Dans la version actuelle, seules les applications empaquetées peuvent être inscrites en tant que fournisseurs de widgets. Les étapes suivantes vous guident tout au long du processus d’empaquetage de votre application et de mise à jour du manifeste de l’application pour inscrire votre application auprès du système d’exploitation en tant que fournisseur de widgets.
Créer un projet d’empaquetage MSIX
Dans l’Explorateur de solutions, cliquez avec le bouton droit sur votre solution et sélectionnez Ajouter->Nouveau projet.... Dans la boîte de dialogue Ajouter un nouveau projet, sélectionnez le modèle « Projet de création de packages d’applications Windows », puis cliquez sur Suivant. Définissez le nom du projet sur « ExampleWidgetProviderPackage », puis cliquez sur Créer. Lorsque vous y êtes invité, définissez la version cible sur la version 1809 ou ultérieure, puis cliquez sur OK. Ensuite, cliquez avec le bouton droit sur le projet ExampleWidgetProviderPackage, puis sélectionnez Ajouter->Référence de projet. Sélectionnez le projet ExampleWidgetProvider, puis cliquez sur OK.
Ajouter une référence de package de SDK d’application Windows au projet d’empaquetage
Vous devez ajouter une référence au package NuGet du SDK d’application Windows au projet d’empaquetage MSIX. Dans l’Explorateur de solutions, double-cliquez sur le projet ExampleWidgetProviderPackage pour ouvrir le fichier ExampleWidgetProviderPackage.wapproj. Ajoutez le code XML suivant dans l’élément Project.
<!--ExampleWidgetProviderPackage.wapproj-->
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.2.221116.1">
<IncludeAssets>build</IncludeAssets>
</PackageReference>
</ItemGroup>
Notes
Vérifiez que la version spécifiée dans l’élément PackageReference correspond à la dernière version stable que vous avez référencée à l’étape précédente.
Si la bonne version du SDK d’application Windows est déjà installée sur l’ordinateur et que vous ne souhaitez pas regrouper le runtime du SDK dans votre package, vous pouvez spécifier la dépendance de package dans le fichier Package.appxmanifest pour le projet ExampleWidgetProviderPackage.
<!--Package.appxmanifest-->
...
<Dependencies>
...
<PackageDependency Name="Microsoft.WindowsAppRuntime.1.2-preview2" MinVersion="2000.638.7.0" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" />
...
</Dependencies>
...
Mettre à jour le manifeste du package
Dans l’Explorateur de solutions, cliquez avec le bouton droit sur le fichier Package.appxmanifest
et sélectionnez Afficher le code pour ouvrir le fichier XML du manifeste. Ensuite, vous devez ajouter des déclarations d’espace de noms pour les extensions de package d’application que nous allons utiliser. Ajoutez les définitions d’espace de noms suivantes à l’élément Package de niveau supérieur.
<!-- Package.appmanifest -->
<Package
...
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
À l’intérieur de l’élément Application, créez un élément vide nommé Extensions. Assurez-vous que cela arrive après la balise de fermeture pour uap:VisualElements.
<!-- Package.appxmanifest -->
<Application>
...
<Extensions>
</Extensions>
</Application>
La première extension que nous devons ajouter est l’extension ComServer. Cela inscrit le point d’entrée de l’exécutable avec le système d’exploitation. Cette extension est l’application empaquetée équivalente à l’inscription d’un serveur COM en définissant une clé de Registre et n’est pas spécifique aux fournisseurs de widgets. Ajoutez l’élément com:Extension suivant en tant qu’enfant de l’élément Extensions. Remplacez le GUID dans l’attribut Id de l’élément com:Class par le GUID que vous avez généré à l’étape précédente.
<!-- Package.appxmanifest -->
<Extensions>
<com:Extension Category="windows.comServer">
<com:ComServer>
<com:ExeServer Executable="ExampleWidgetProvider\ExampleWidgetProvider.exe" DisplayName="ExampleWidgetProvider">
<com:Class Id="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" DisplayName="ExampleWidgetProvider" />
</com:ExeServer>
</com:ComServer>
</com:Extension>
</Extensions>
Ensuite, ajoutez l’extension qui inscrit l’application en tant que fournisseur de widgets. Collez l’élément uap3:Extension dans l’extrait de code suivant, en tant qu’enfant de l’élément Extensions. Veillez à remplacer l’attribut ClassId de l’élément COM par le GUID que vous avez utilisé lors des étapes précédentes.
<!-- Package.appxmanifest -->
<Extensions>
...
<uap3:Extension Category="windows.appExtension">
<uap3:AppExtension Name="com.microsoft.windows.widgets" DisplayName="WidgetTestApp" Id="ContosoWidgetApp" PublicFolder="Public">
<uap3:Properties>
<WidgetProvider>
<ProviderIcons>
<Icon Path="Images\StoreLogo.png" />
</ProviderIcons>
<Activation>
<!-- Apps exports COM interface which implements IWidgetProvider -->
<CreateInstance ClassId="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" />
</Activation>
<TrustedPackageFamilyNames>
<TrustedPackageFamilyName>Microsoft.MicrosoftEdge.Stable_8wekyb3d8bbwe</TrustedPackageFamilyName>
</TrustedPackageFamilyNames>
<Definitions>
<Definition Id="Weather_Widget"
DisplayName="Weather Widget"
Description="Weather Widget Description"
AllowMultiple="true">
<Capabilities>
<Capability>
<Size Name="small" />
</Capability>
<Capability>
<Size Name="medium" />
</Capability>
<Capability>
<Size Name="large" />
</Capability>
</Capabilities>
<ThemeResources>
<Icons>
<Icon Path="ProviderAssets\Weather_Icon.png" />
</Icons>
<Screenshots>
<Screenshot Path="ProviderAssets\Weather_Screenshot.png" DisplayAltText="For accessibility" />
</Screenshots>
<!-- DarkMode and LightMode are optional -->
<DarkMode />
<LightMode />
</ThemeResources>
</Definition>
<Definition Id="Counting_Widget"
DisplayName="Microsoft Counting Widget"
Description="Couting Widget Description">
<Capabilities>
<Capability>
<Size Name="small" />
</Capability>
</Capabilities>
<ThemeResources>
<Icons>
<Icon Path="ProviderAssets\Counting_Icon.png" />
</Icons>
<Screenshots>
<Screenshot Path="ProviderAssets\Counting_Screenshot.png" DisplayAltText="For accessibility" />
</Screenshots>
<!-- DarkMode and LightMode are optional -->
<DarkMode>
</DarkMode>
<LightMode />
</ThemeResources>
</Definition>
</Definitions>
</WidgetProvider>
</uap3:Properties>
</uap3:AppExtension>
</uap3:Extension>
</Extensions>
Pour obtenir des descriptions détaillées et des informations de format pour tous ces éléments, consultez Format XML du manifeste du package du fournisseur de widgets.
Ajouter des icônes et d’autres images à votre projet d’empaquetage
Dans l’Explorateur de solutions, cliquez avec le bouton droit sur ExempleWidgetProviderPackage, puis sélectionnez Ajouter->Nouveau dossier. Nommez ce dossier ProviderAssets, car il s’agit de ce qui a été utilisé dans Package.appxmanifest
à l’étape précédente. C’est là que nous allons stocker nos icônes et captures d’écran pour nos widgets. Une fois que vous avez ajouté vos icônes et captures d’écran souhaitées, assurez-vous que les noms d’image correspondent à ce qui vient après Path=ProviderAssets\ dans votre Package.appxmanifest
ou que les widgets ne s’affichent pas dans l’hôte du widget.
Pour plus d’informations sur les exigences de conception pour les images de capture d’écran et les conventions de nommage des captures d’écran localisées, consultez Intégrer au sélecteur de widgets.
Test de votre fournisseur de widgets
Vérifiez que vous avez sélectionné l’architecture qui correspond à votre machine de développement dans la liste déroulante Plateformes de solution, par exemple « x64 ». Dans l’Explorateur de solutions, cliquez avec le bouton droit sur votre solution et sélectionnez Générer la solution. Une fois cette opération effectuée, cliquez avec le bouton droit sur ExempleWidgetProviderPackage, puis sélectionnez Déployer. Dans la version actuelle, le seul hôte de widget pris en charge est le tableau de widgets. Pour afficher les widgets, vous devez ouvrir le tableau de widgets et sélectionner Ajouter des widgets en haut à droite. Faites défiler jusqu’au bas des widgets disponibles, et vous devriez voir les exemples de Widget météo et de Widget de comptage Microsoft qui ont été créés dans ce tutoriel. Cliquez sur les widgets pour les épingler à votre tableau de widgets et tester leurs fonctionnalités.
Débogage de votre fournisseur de widgets
Une fois que vous avez épinglé vos widgets, la plateforme de widget démarre votre application de fournisseur de widgets afin de recevoir et d’envoyer des informations pertinentes sur le widget. Pour déboguer le widget en cours d’exécution, vous pouvez attacher un débogueur à l’application du fournisseur de widget en cours d’exécution, ou configurer Visual Studio pour démarrer automatiquement le débogage du processus du fournisseur de widgets une fois qu’il a démarré.
Pour l’attacher au processus en cours d’exécution :
- Dans Visual Studio, cliquez sur Déboguer -> Attacher au processus.
- Filtrez les processus et recherchez l’application de fournisseur de widgets souhaitée.
- Attachez le débogueur.
Pour attacher automatiquement le débogueur au processus lors de son démarrage initial :
- Dans Visual Studio, cliquez sur Déboguer -> Autres cibles de débogage -> Déboguer le package d’application installé.
- Filtrez les packages et recherchez le package de fournisseur de widgets souhaité.
- Sélectionnez-le et cochez la case Ne pas lancer, mais déboguer mon code au démarrage.
- Cliquez sur Attacher.
Convertir votre application console en application Windows
Pour convertir l’application console créée dans cette procédure pas à pas en application Windows :
- Cliquez avec le bouton droit sur le projet ExampleWidgetProvider dans l’Explorateur de solutions, puis sélectionnez Propriétés. Accédez à Éditeur de liens -> Système et remplacez SubSystem de « Console » à « Windows ». Cela peut également être fait en ajoutant <SubSystem>Windows</SubSystem> à la section <Link>..</Link> du fichier .vcxproj.
- Dans main.cpp, remplacez
int main()
parint WINAPI wWinMain(_In_ HINSTANCE /*hInstance*/, _In_opt_ HINSTANCE /*hPrevInstance*/, _In_ PWSTR pCmdLine, _In_ int /*nCmdShow*/)
.
Publication de votre widget
Une fois que vous avez développé et testé votre widget, vous pouvez publier votre application sur le Microsoft Store pour que les utilisateurs puissent installer vos widgets sur leurs appareils. Pour obtenir des instructions pas à pas sur la publication d’une application, consultez Publier votre application dans le Microsoft Store.
La collection de widgets du Store
Une fois que votre application a été publiée sur le Microsoft Store, vous pouvez demander l’inclusion de votre application dans la collection de widgets du Store, qui aide les utilisateurs à découvrir les applications qui comportent des widgets Windows. Pour envoyer votre demande, consultez Envoyer vos informations de widget pour l’ajout à la collection du Store.
Implémentation de la personnalisation de widgets
À compter du SDK d’application Windows 1.4, les widgets peuvent prendre en charge la personnalisation des utilisateurs. Lorsque cette fonctionnalité est implémentée, une option Personnaliser le widget est ajoutée au menu de sélection au-dessus de l’option Désépingler le widget.
Les étapes suivantes résument le processus de personnalisation d’un widget.
- En mode normal, le fournisseur du widget répond aux demandes de l’hôte du widget avec les modèles JSON visuels et de données pour l’expérience de widget standard.
- L’utilisateur clique sur le bouton Personnaliser le widget dans le menu de sélection.
- Le widget déclenche l’événement OnCustomizationRequested sur le fournisseur du widget pour indiquer que l’utilisateur a demandé l’expérience de personnalisation du widget.
- Le fournisseur du widget définit un indicateur interne pour montrer que le widget est en mode de personnalisation. En mode personnalisation, le fournisseur du widget envoie les modèles JSON pour l’interface utilisateur de personnalisation du widget au lieu de l’interface utilisateur du widget standard.
- En mode personnalisation, le fournisseur du widget reçoit les événements OnActionInvoked lorsque l’utilisateur interagit avec l’interface utilisateur de personnalisation et adapte sa configuration interne et son comportement en fonction des actions de l’utilisateur.
- Lorsque l’action associée à l’événement OnActionInvoked est l’action « Quitter la personnalisation » définie par l’application, le fournisseur du widget réinitialise son indicateur interne pour montrer qu’il n’est plus en mode de personnalisation et reprend l’envoi de modèles JSON visuels et de données pour l’expérience de widget standard, tout en reflétant les changements demandés lors de la personnalisation.
- Le fournisseur du widget conserve les options de personnalisation sur le disque ou le cloud afin que les modifications soient conservées entre les appels du fournisseur du widget.
Remarque
Il existe un bogue connu avec le tableau de widgets Windows, pour les widgets générés à l’aide du SDK d’application Windows, où le menu de sélection ne répond plus après l’affichage de la carte de personnalisation.
Dans les scénarios de personnalisation de widgets classiques, l’utilisateur choisit les données à afficher sur le widget ou adapte la présentation visuelle du widget. Pour que cela reste simple, l’exemple de cette section ajoute un comportement de personnalisation qui permet à l’utilisateur de réinitialiser le compteur du widget de comptage implémenté dans les étapes précédentes.
Remarque
La personnalisation du widget est prise en charge uniquement dans le SDK d’application Windows 1.4 et ultérieur. Veillez à mettre à jour les références dans votre projet avec la dernière version du package Nuget.
Mettre à jour le manifeste du package pour déclarer la prise en charge de la personnalisation
Pour informer l’hôte du widget que le widget prend en charge la personnalisation, ajoutez l’attribut IsCustomizable à l’élément Definition du widget et définissez-le avec la valeur true.
...
<Definition Id="Counting_Widget"
DisplayName="Microsoft Counting Widget"
Description="CONFIG counting widget description"
IsCustomizable="true">
...
Mettre à jour WidgetProvider.h
Pour ajouter la prise en charge de la personnalisation au widget créé dans les étapes précédentes de cet article, nous devons mettre à jour le fichier d’en-tête de notre fournisseur de widget, WidgetProvider.h.
Tout d’abord, mettez à jour la définition de CompactWidgetInfo. Ce struct d’assistance nous permet de suivre l’état actuel de nos widgets actifs. Ajoutez le champ inCustomization, qui est utilisé pour effectuer le suivi lorsque l’hôte du widget attend de recevoir notre modèle JSON de personnalisation plutôt que le modèle de widget standard.
// WidgetProvider.h
struct CompactWidgetInfo
{
winrt::hstring widgetId;
winrt::hstring widgetName;
int customState = 0;
bool isActive = false;
bool inCustomization = false;
};
Mettez à jour la déclaration WidgetProvider pour implémenter l’interface IWidgetProvider2.
// WidgetProvider.h
struct WidgetProvider : winrt::implements<WidgetProvider, winrt::Microsoft::Windows::Widgets::Providers::IWidgetProvider, winrt::Microsoft::Windows::Widgets::Providers::IWidgetProvider2>
Ajoutez une déclaration pour le rappel OnCustomizationRequested de l’interface IWidgetProvider2.
// WidgetProvider.h
void OnCustomizationRequested(winrt::Microsoft::Windows::Widgets::Providers::WidgetCustomizationRequestedArgs args);
Enfin, déclarez une variable de chaîne qui définit le modèle JSON pour l’interface utilisateur de personnalisation du widget. Pour cet exemple, nous avons un bouton « Réinitialiser le compteur » et un bouton « Quitter la personnalisation » qui indiquent à notre fournisseur de revenir au comportement normal du widget.
// WidgetProvider.h
const std::string countWidgetCustomizationTemplate = R"(
{
"type": "AdaptiveCard",
"actions" : [
{
"type": "Action.Execute",
"title" : "Reset counter",
"verb": "reset"
},
{
"type": "Action.Execute",
"title": "Exit customization",
"verb": "exitCustomization"
}
],
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.5"
})";
Mettre à jour WidgetProvider.cpp
Maintenant, mettez à jour le fichier WidgetProvider.cpp pour implémenter le comportement de personnalisation du widget. Cette méthode utilise le même modèle que les autres rappels que nous avons utilisés. Nous obtenons l’ID du widget à personnaliser à partir du WidgetContext, recherchons le struct d’assistance CompactWidgetInfo associé à ce widget et définissons le champ inCustomization avec la valeur true.
//WidgetProvider.cpp
void WidgetProvider::OnCustomizationRequested(winrt::WidgetCustomizationRequestedArgs args)
{
auto widgetId = args.WidgetContext().Id();
if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
{
auto& localWidgetInfo = iter->second;
localWidgetInfo.inCustomization = true;
UpdateWidget(localWidgetInfo);
}
}
Ensuite, nous allons mettre à jour notre méthode d’assistance UpdateWidget qui envoie nos modèles JSON visuels et de données à l’hôte du widget. Lorsque nous mettons à jour le widget de comptage, nous envoyons le modèle de widget standard ou le modèle de personnalisation en fonction de la valeur du champ inCustomization. Par souci de concision, le code non concerné par la personnalisation est omis dans cet extrait de code.
//WidgetProvider.cpp
void WidgetProvider::UpdateWidget(CompactWidgetInfo const& localWidgetInfo)
{
...
else if (localWidgetInfo.widgetName == L"Counting_Widget")
{
if (!localWidgetInfo.inCustomization)
{
std::wcout << L" - not in customization " << std::endl;
templateJson = winrt::to_hstring(countWidgetTemplate);
}
else
{
std::wcout << L" - in customization " << std::endl;
templateJson = winrt::to_hstring(countWidgetCustomizationTemplate);
}
}
...
updateOptions.Template(templateJson);
updateOptions.Data(dataJson);
// !! You can store some custom state in the widget service that you will be able to query at any time.
updateOptions.CustomState(winrt::to_hstring(localWidgetInfo.customState));
winrt::WidgetManager::GetDefault().UpdateWidget(updateOptions);
}
Lorsque les utilisateurs interagissent avec des entrées dans notre modèle de personnalisation, celui-ci appelle le même gestionnaire OnActionInvoked que lorsque l’utilisateur interagit avec l’expérience de widget standard. Pour prendre en charge la personnalisation, nous recherchons les verbes « reset » et « exitCustomization » dans notre modèle JSON de personnalisation. Si l’action concerne le bouton « Réinitialiser le compteur », nous remettons le compteur conservé dans le champ customState de notre struct d’assistance à 0. Si l’action concerne le bouton « Quitter la personnalisation », nous définissons le champ inCustomization avec la valeur false afin que lorsque nous appelons UpdateWidget, notre méthode d’assistance envoie les modèles JSON standard et non le modèle de personnalisation.
//WidgetProvider.cpp
void WidgetProvider::OnActionInvoked(winrt::WidgetActionInvokedArgs actionInvokedArgs)
{
auto verb = actionInvokedArgs.Verb();
if (verb == L"inc")
{
auto widgetId = actionInvokedArgs.WidgetContext().Id();
// If you need to use some data that was passed in after
// Action was invoked, you can get it from the args:
auto data = actionInvokedArgs.Data();
if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
{
auto& localWidgetInfo = iter->second;
// Increment the count
localWidgetInfo.customState++;
UpdateWidget(localWidgetInfo);
}
}
else if (verb == L"reset")
{
auto widgetId = actionInvokedArgs.WidgetContext().Id();
auto data = actionInvokedArgs.Data();
if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
{
auto& localWidgetInfo = iter->second;
// Reset the count
localWidgetInfo.customState = 0;
localWidgetInfo.inCustomization = false;
UpdateWidget(localWidgetInfo);
}
}
else if (verb == L"exitCustomization")
{
auto widgetId = actionInvokedArgs.WidgetContext().Id();
// If you need to use some data that was passed in after
// Action was invoked, you can get it from the args:
auto data = actionInvokedArgs.Data();
if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
{
auto& localWidgetInfo = iter->second;
// Stop sending the customization template
localWidgetInfo.inCustomization = false;
UpdateWidget(localWidgetInfo);
}
}
}
Maintenant, lorsque vous déployez votre widget, vous devriez voir le bouton Personnaliser le widget dans le menu de sélection. Cliquez sur le bouton Personnaliser pour afficher votre modèle de personnalisation.
Cliquez sur le bouton Réinitialiser le compteur pour remettre le compteur à 0. Cliquez sur le bouton Quitter la personnalisation pour revenir au comportement normal de votre widget.
Windows developer