Partage via


Implémenter un fournisseur de widgets dans une application Windows C#

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.

Capture d’écran d’un widget météo simple. Le widget affiche des graphiques et des données liés à la météo, ainsi que du texte de diagnostic illustrant que le modèle du widget de taille moyenne est affiché.

Capture d’écran d’un widget de comptage simple. Le widget affiche une chaîne contenant la valeur numérique à incrémenter et un bouton intitulé Incrémenter, ainsi que du texte de diagnostic illustrant que le modèle du widget de petite taille est affiché.

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++/WinRT, consultez Implémenter un fournisseur de widgets dans une application win32 (C++/WinRT).

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 nouvelle application console C#

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. Nommez le nouveau projet « ExampleWidgetProvider ». Lorsque vous y êtes invité, mettez la version cible de .NET sur 8.0.

Une fois que le projet se charge, dans l’Explorateur de solutions, cliquez avec le bouton droit sur le nom du projet, puis sélectionnez Propriétés. Dans la page Général, faites défiler jusqu’à Système d’exploitation cible et sélectionnez « Windows ». Sous Version du système d’exploitation cible, sélectionnez la version 10.0.19041.0 ou ultérieure.

Pour mettre à jour votre projet pour pouvoir prendre en charge .NET 8.0, faites un clic droit sur le nom du projet dans Explorateur de solutions et sélectionnez Modifier le fichier projet. À l’intérieur de PropertyGroup, ajoutez l’élément RuntimeIdentifiers suivant.

<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>

Notez que cette procédure pas à pas utilise une application console qui affiche la fenêtre de console lorsque le widget est activé pour faciliter le débogage. Lorsque vous êtes prêt à publier votre application fournisseur de widgets, vous pouvez convertir l’application console en application Windows en suivant les étapes décrites dans Convertir votre application console en application Windows.

Ajouter des références au Kit de développement logiciel (SDK) d’application 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 Dépendances 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.

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. Dans le fichier WidgetProvider.cs généré, mettez à jour la définition de classe pour indiquer qu’elle implémente l’interface IWidgetProvider.

// WidgetProvider.cs
internal class WidgetProvider : IWidgetProvider

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.cs, à l’intérieur de l’espace de noms ExampleWidgetProvider, mais en dehors de la définition de classe WidgetProvider.

// WidgetProvider.cs

public class CompactWidgetInfo
{
    public string? widgetId { get; set; }
    public string? widgetName { get; set; }
    public int customState = 0;
    public bool isActive = false;

}

Dans la définition de classe WidgetProvider dans WidgetProvider.cs, 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.cs

// Class member of WidgetProvider
public static Dictionary<string, CompactWidgetInfo> RunningWidgets = new Dictionary<string, CompactWidgetInfo>(); 

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 membres de la 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.cs

// Class members of WidgetProvider
        const string weatherWidgetTemplate = """
{
    "$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 string countWidgetTemplate = """
{                                                                     
    "type": "AdaptiveCard",                                         
    "body": [                                                         
        {                                                               
            "type": "TextBlock",                                    
            "text": "You have clicked the button ${count} times"    
        },
        {
                "text":"Rendering Only if Small",
                "type":"TextBlock",
                "$when":"${$host.widgetSize==\"small\"}"
        },
        {
                "text":"Rendering Only if Medium",
                "type":"TextBlock",
                "$when":"${$host.widgetSize==\"medium\"}"
        },
        {
            "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.

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.

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

public void CreateWidget(WidgetContext widgetContext)
{
    var widgetId = widgetContext.Id; // To save RPC calls
    var widgetName = widgetContext.DefinitionId;
    CompactWidgetInfo runningWidgetInfo = new CompactWidgetInfo() { widgetId = widgetId, widgetName = 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.cs

public void DeleteWidget(string widgetId, string customState)
{
    RunningWidgets.Remove(widgetId);

    if(RunningWidgets.Count == 0)
    {
        emptyWidgetListEvent.Set();
    }
}

Pour cet exemple, en plus de supprimer le widget avec l’identifiant spécifié dans la liste des widgets activés, nous vérifions également si la liste est désormais vide et, si c’est le cas, nous définissons un événement qui sera utilisé ultérieurement pour autoriser l’application à quitter lorsqu’il n’y a pas de widgets activés. Dans votre définition de classe, ajoutez la déclaration de ManualResetEvent et une fonction d’accesseur public.

// WidgetProvider.cs
static ManualResetEvent emptyWidgetListEvent = new ManualResetEvent(false);

public static ManualResetEvent GetEmptyWidgetListEvent()
{
    return emptyWidgetListEvent;
}

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

public void OnActionInvoked(WidgetActionInvokedArgs actionInvokedArgs)
{
    var verb = actionInvokedArgs.Verb;
        if (verb == "inc")
        {
            var 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:
            var data = actionInvokedArgs.Data;
            if (RunningWidgets.ContainsKey(widgetId))
            {
                var localWidgetInfo = RunningWidgets[widgetId];
                // 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.cs

public void OnWidgetContextChanged(WidgetContextChangedArgs contextChangedArgs)
{
    var widgetContext = contextChangedArgs.WidgetContext;
    var widgetId = widgetContext.Id;
    var widgetSize = widgetContext.Size;
    if (RunningWidgets.ContainsKey(widgetId))
    {
        var localWidgetInfo = RunningWidgets[widgetId];
        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.

// WidgetProvider.cs

public void Activate(WidgetContext widgetContext)
{
    var widgetId = widgetContext.Id;

    if (RunningWidgets.ContainsKey(widgetId))
    {
        var localWidgetInfo = RunningWidgets[widgetId];
        localWidgetInfo.isActive = true;

        UpdateWidget(localWidgetInfo);
    }
}
public void Deactivate(string widgetId)
{
    if (RunningWidgets.ContainsKey(widgetId))
    {
        var localWidgetInfo = RunningWidgets[widgetId];
        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.cs

void UpdateWidget(CompactWidgetInfo localWidgetInfo)
{
    WidgetUpdateRequestOptions updateOptions = new WidgetUpdateRequestOptions(localWidgetInfo.widgetId);

    string? templateJson = null;
    if (localWidgetInfo.widgetName == "Weather_Widget")
    {
        templateJson = weatherWidgetTemplate.ToString();
    }
    else if (localWidgetInfo.widgetName == "Counting_Widget")
    {
        templateJson = countWidgetTemplate.ToString();
    }

    string? dataJson = null;
    if (localWidgetInfo.widgetName == "Weather_Widget")
    {
        dataJson = "{}";
    }
    else if (localWidgetInfo.widgetName == "Counting_Widget")
    {
        dataJson = "{ \"count\": " + localWidgetInfo.customState.ToString() + " }";
    }

    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= localWidgetInfo.customState.ToString();
    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 la définition de classe pour la classe WidgetProvider.

// WidgetProvider.cs

public WidgetProvider()
{
    var runningWidgets = WidgetManager.GetDefault().GetWidgetInfos();

    foreach (var widgetInfo in runningWidgets)
    {
        var widgetContext = widgetInfo.WidgetContext;
        var widgetId = widgetContext.Id;
        var widgetName = widgetContext.DefinitionId;
        var customState = widgetInfo.CustomState;
        if (!RunningWidgets.ContainsKey(widgetId))
        {
            CompactWidgetInfo runningWidgetInfo = new CompactWidgetInfo() { widgetId = widgetName, widgetName = widgetId };
            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 = Convert.ToInt32(customState.ToString());
                runningWidgetInfo.customState = count;
            }
            catch
            {

            }
            RunningWidgets[widgetId] = runningWidgetInfo;
        }
    }
}

Implémenter une fabrique de classes qui instancie WidgetProvider à la demande

Pour que l’hôte du widget communique avec notre fournisseur de widgets, nous devons appeler CoRegisterClassObject. Cette fonction nous oblige à créer une implémentation de IClassFactory qui créera un objet de classe pour notre classe WidgetProvider. Nous allons implémenter notre fabrique de classes dans une classe d’assistance autonome.

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 FactoryHelper, puis cliquez sur Ajouter.

Remplacez le contenu du fichier FactoryHelper.cs par le code ci-après. Ce code définit l’interface IClassFactory et implémente ses deux méthodes, CreateInstance et LockServer. Ce code est typiquement générique pour l’implémentation d’une fabrique de classes et n’est pas spécifique aux fonctionnalités d’un fournisseur de widgets, sauf que nous indiquons que l’objet de classe en cours de création implémente l’interface IWidgetProvider.

// FactoryHelper.cs

using Microsoft.Windows.Widgets.Providers;
using System.Runtime.InteropServices;
using WinRT;

namespace COM
{
    static class Guids
    {
        public const string IClassFactory = "00000001-0000-0000-C000-000000000046";
        public const string IUnknown = "00000000-0000-0000-C000-000000000046";
    }

    /// 
    /// IClassFactory declaration
    /// 
    [ComImport, ComVisible(false), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid(COM.Guids.IClassFactory)]
    internal interface IClassFactory
    {
        [PreserveSig]
        int CreateInstance(IntPtr pUnkOuter, ref Guid riid, out IntPtr ppvObject);
        [PreserveSig]
        int LockServer(bool fLock);
    }

    [ComVisible(true)]
    class WidgetProviderFactory<T> : IClassFactory
    where T : IWidgetProvider, new()
    {
        public int CreateInstance(IntPtr pUnkOuter, ref Guid riid, out IntPtr ppvObject)
        {
            ppvObject = IntPtr.Zero;

            if (pUnkOuter != IntPtr.Zero)
            {
                Marshal.ThrowExceptionForHR(CLASS_E_NOAGGREGATION);
            }

            if (riid == typeof(T).GUID || riid == Guid.Parse(COM.Guids.IUnknown))
            {
                // Create the instance of the .NET object
                ppvObject = MarshalInspectable<IWidgetProvider>.FromManaged(new T());
            }
            else
            {
                // The object that ppvObject points to does not support the
                // interface identified by riid.
                Marshal.ThrowExceptionForHR(E_NOINTERFACE);
            }

            return 0;
        }

        int IClassFactory.LockServer(bool fLock)
        {
            return 0;
        }

        private const int CLASS_E_NOAGGREGATION = -2147221232;
        private const int E_NOINTERFACE = -2147467262;

    }
}

Créer un GUID représentant le CLSID pour votre fournisseur de widgets

Ensuite, vous devez créer un GUID représentant le CLSID qui sera utilisé pour identifier votre fournisseur de widgets pour l’activation COM. La même valeur sera également utilisée lors de l’empaquetage de votre application. Générez un GUID dans Visual Studio en accédant à Outils->Créer un GUID. Sélectionnez l’option de format du Registre, cliquez sur Copier, puis collez le contenu dans un fichier texte afin de pouvoir le copier ultérieurement.

Inscrire l’objet de classe de fournisseur de widgets auprès d’OLE

Dans le fichier Program.cs de notre exécutable, nous allons appeler CoRegisterClassObject pour inscrire notre fournisseur de widgets auprès d’OLE, afin que l’hôte du widget puisse interagir avec. Remplacez le contenu de Program.cs par le code suivant. Ce code importe la fonction CoRegisterClassObject et l’appelle, en passant l’interface WidgetProviderFactory que nous avons définie à l’étape précédente. Veillez à mettre à jour la déclaration de variable CLSID_Factory pour utiliser le GUID que vous avez généré à l’étape précédente.

// Program.cs

using System.Runtime.InteropServices;
using ComTypes = System.Runtime.InteropServices.ComTypes;
using Microsoft.Windows.Widgets;
using ExampleWidgetProvider;
using COM;
using System;

[DllImport("kernel32.dll")]
static extern IntPtr GetConsoleWindow();

[DllImport("ole32.dll")]

static extern int CoRegisterClassObject(
            [MarshalAs(UnmanagedType.LPStruct)] Guid rclsid,
            [MarshalAs(UnmanagedType.IUnknown)] object pUnk,
            uint dwClsContext,
            uint flags,
            out uint lpdwRegister);

[DllImport("ole32.dll")] static extern int CoRevokeClassObject(uint dwRegister);

Console.WriteLine("Registering Widget Provider");
uint cookie;

Guid CLSID_Factory = Guid.Parse("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX");
CoRegisterClassObject(CLSID_Factory, new WidgetProviderFactory<WidgetProvider>(), 0x4, 0x1, out cookie);
Console.WriteLine("Registered successfully. Press ENTER to exit.");
Console.ReadLine();

if (GetConsoleWindow() != IntPtr.Zero)
{
    Console.WriteLine("Registered successfully. Press ENTER to exit.");
    Console.ReadLine();
}
else
{
    // Wait until the manager has disposed of the last widget provider.
    using (var emptyWidgetListEvent = WidgetProvider.GetEmptyWidgetListEvent())
    {
        emptyWidgetListEvent.WaitOne();
    }

    CoRevokeClassObject(cookie);
}

Notez que cet exemple de code importe la fonction GetConsoleWindow pour déterminer si l’application s’exécute en tant qu’application console, le comportement par défaut de cette procédure pas à pas. Si la fonction retourne un pointeur valide, nous écrivons des informations de débogage dans la console. Sinon, l’application s’exécute en tant qu’application Windows. Dans ce cas, nous attendons l’événement que nous avons défini dans la méthode DeleteWidget lorsque la liste des widgets activés est vide, puis nous quittons l’application. Pour plus d’informations sur la conversion de l’exemple d’application console en application Windows, consultez Convertir votre application console en application Windows.

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 :

  1. Dans Visual Studio, cliquez sur Déboguer -> Attacher au processus.
  2. Filtrez les processus et recherchez l’application de fournisseur de widgets souhaitée.
  3. Attachez le débogueur.

Pour attacher automatiquement le débogueur au processus lors de son démarrage initial :

  1. Dans Visual Studio, cliquez sur Déboguer -> Autres cibles de débogage -> Déboguer le package d’application installé.
  2. Filtrez les packages et recherchez le package de fournisseur de widgets souhaité.
  3. Sélectionnez-le et cochez la case Ne pas lancer, mais déboguer mon code au démarrage.
  4. 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. Sous Application->Général, remplacez le Type de sortie « Application console » en « Application Windows ».

Capture d’écran montrant les propriétés du projet de fournisseur de widgets C# avec le type de sortie défini sur Application Windows

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.

Capture d’écran du Microsoft Store montrant la collection de widgets qui permet aux utilisateurs de découvrir les applications qui comportent des widgets Windows.

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.

Capture d’écran montrant un widget avec la boîte de dialogue de personnalisation affichée.

Les étapes suivantes résument le processus de personnalisation d’un widget.

  1. En mode normal, le fournisseur du widget répond aux demandes de l’hôte du widget avec les charges utiles de modèle et de données pour l’expérience de widget standard.
  2. L’utilisateur clique sur le bouton Personnaliser le widget dans le menu de sélection.
  3. 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.
  4. 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.
  5. 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.
  6. 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.
  7. 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">
...

Suivre quand un widget est en mode de personnalisation

L’exemple de cet article utilise le struct d’assistance CompactWidgetInfo pour 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.cs
public class CompactWidgetInfo
{
    public string widgetId { get; set; }
    public string widgetName { get; set; }
    public int customState = 0;
    public bool isActive = false;
    public bool inCustomization = false;
}

Implémenter IWidgetProvider2

La fonctionnalité de personnalisation du widget est exposée via l’interface IWidgetProvider2. Mettez à jour la définition de classe WidgetProvider pour implémenter cette interface.

// WidgetProvider.cs
internal class WidgetProvider : IWidgetProvider, IWidgetProvider2

Ajoutez une implémentation pour le rappel OnCustomizationRequested de l’interface IWidgetProvider2. 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.cs
public void OnCustomizationRequested(WidgetCustomizationRequestedArgs customizationInvokedArgs)
{
    var widgetId = customizationInvokedArgs.WidgetContext.Id;
    if (RunningWidgets.ContainsKey(widgetId))
    {
        var localWidgetInfo = RunningWidgets[widgetId];
        localWidgetInfo.inCustomization = true;
        UpdateWidget(localWidgetInfo);
    }
}

À présent, 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. Placez cette définition en regard des autres définitions de modèle.

// WidgetProvider.cs
const string countWidgetCustomizationTemplate = @"
{
    ""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""
}";

Envoyer un modèle de personnalisation dans UpdateWidget

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.cs
void UpdateWidget(CompactWidgetInfo localWidgetInfo)
{
    ...
    else if (localWidgetInfo.widgetName == "Counting_Widget")
    {
        if (!localWidgetInfo.inCustomization)
        {
            templateJson = countWidgetTemplate.ToString();
        }
        else
        {
            templateJson = countWidgetCustomizationTemplate.ToString();
        }
    
    }
    ...
    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 = localWidgetInfo.customState.ToString();
    WidgetManager.GetDefault().UpdateWidget(updateOptions);
}

Répondre aux actions de personnalisation

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.cs
public void OnActionInvoked(WidgetActionInvokedArgs actionInvokedArgs)
{
    var verb = actionInvokedArgs.Verb;
    if (verb == "inc")
    {
        var 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:
        var data = actionInvokedArgs.Data;
        if (RunningWidgets.ContainsKey(widgetId))
        {
            var localWidgetInfo = RunningWidgets[widgetId];
            // Increment the count
            localWidgetInfo.customState++;
            UpdateWidget(localWidgetInfo);
        }
    } 
    else if (verb == "reset") 
    {
        var widgetId = actionInvokedArgs.WidgetContext.Id;
        var data = actionInvokedArgs.Data;
        if (RunningWidgets.ContainsKey(widgetId))
        {
            var localWidgetInfo = RunningWidgets[widgetId];
            // Reset the count
            localWidgetInfo.customState = 0;
            localWidgetInfo.inCustomization = false;
            UpdateWidget(localWidgetInfo);
        }
    }
    else if (verb == "exitCustomization")
    {
        var widgetId = actionInvokedArgs.WidgetContext.Id;
        var data = actionInvokedArgs.Data;
        if (RunningWidgets.ContainsKey(widgetId))
        {
            var localWidgetInfo = RunningWidgets[widgetId];
            // 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.

Une capture d’écran montrant l’interface utilisateur des personnalisations de widgets.

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.