Condividi tramite


Implementare un provider di widget in un'app di Windows C#

Questo articolo illustra come creare un semplice provider di widget che implementa l'interfaccia IWidgetProvider. I metodi di questa interfaccia vengono richiamati dall'host del widget per richiedere i dati che definiscono un widget o per consentire al provider di widget di rispondere a un'azione dell'utente in un widget. I provider di widget possono supportare un singolo widget o più widget. In questo esempio verranno definiti due widget diversi. Un widget è un widget meteo fittizio che illustra alcune delle opzioni di formattazione fornite dal framework Shede adattive. Il secondo widget illustra le azioni dell'utente e la funzionalità di stato del widget personalizzato mantenendo un contatore incrementato ogni volta che l'utente fa clic su un pulsante visualizzato nel widget.

Screenshot di un semplice widget meteo. Il widget mostra alcuni dati e grafici correlati al meteo, nonché un testo di diagnostica che illustra che viene visualizzato il modello per il widget di dimensioni medie.

Screenshot di un semplice widget di conteggio. Il widget mostra una stringa contenente il valore numerico da incrementare e un pulsante con etichetta Increment, nonché un testo di diagnostica che illustra che viene visualizzato il modello di widget di piccole dimensioni.

Il codice di esempio in questo articolo è adattato da Esempio Widget Windows App SDK. Per implementare un provider di widget con C++/WinRT, vedere Implementare un provider di widget in un'app win32 (C++/WinRT).

Prerequisiti

  • Il dispositivo deve avere la modalità sviluppatore abilitata. Per altre informazioni, vedere Abilitare il dispositivo per lo sviluppo.
  • Visual Studio 2022 o versioni successive con il carico di lavoro Sviluppo per la piattaforma UWP (Universal Windows Platform). Assicurarsi di aggiungere il componente per C++ (v143) dall'elenco a discesa facoltativo.

Creare una nuova app console C#

In Visual Studio creare un nuovo progetto. Nella finestra di dialogo Creare un nuovo progetto impostare il filtro del linguaggio su "C#" e il filtro della piattaforma su Windows, quindi selezionare il modello di progetto App console. Assegnare il nome al nuovo progetto "ExampleWidgetProvider". Quando richiesto, impostare la versione .NET di destinazione su 8.0.

Durante il caricamento del progetto, in Esplora soluzioni fare clic con il pulsante destro del mouse sul nome del progetto e selezionare Proprietà. Nella pagina Generale, scorrere verso il basso fino a Sistema operativo di destinazione e selezionare "Windows". In Versione del Sistema operativo di destinazione, selezionare la versione 10.0.19041.0 o successiva.

Per aggiornare il progetto per il supporto di .NET 8.0, in Esplora soluzioni, fare clic con il pulsante destro del mouse sul nome del progetto e selezionare Modifica file di progetto. All'interno di PropertyGroup, aggiungere l'elemento RuntimeIdentifiers seguente.

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

Si noti che questa procedura dettagliata usa un'app console che visualizza la finestra della console quando il widget viene attivato per consentire un debug facilitato. Quando si è pronti per pubblicare l'app del provider di widget, è possibile convertire l'applicazione console in un'applicazione Windows seguendo la procedura descritta in Convertire l'app console in un'app di Windows.

Aggiungere riferimenti a Windows App SDK

Questo esempio usa il pacchetto NuGet Windows App SDK stabile più recente. In Esplora soluzioni fare clic con il pulsante destro del mouse su Dipendenze e selezionare Gestisci pacchetti NuGet.... Nella gestione pacchetti NuGet, selezionare la scheda Sfoglia e cercare "Microsoft.WindowsAppSDK". Selezionare la versione stabile più recente nell'elenco a discesa Versione e quindi fare clic su Installa.

Aggiungere una classe WidgetProvider per gestire le operazioni del widget

In Visual Studio, fare clic con il pulsante destro del mouse sul progetto ExampleWidgetProvider in Esplora soluzioni e selezionare Aggiungi->Classe. Nella finestra di dialogo Aggiungi classe, assegnare alla classe il nome "WidgetProvider" e fare clic su Aggiungi. Nel file WidgetProvider.cs generato aggiornare la definizione della classe per indicare che implementa l'interfaccia IWidgetProvider.

// WidgetProvider.cs
internal class WidgetProvider : IWidgetProvider

Prepararsi a tenere traccia dei widget abilitati

Un provider di widget può supportare un singolo widget o più widget. Ogni volta che l'host del widget avvia un'operazione con il provider di widget, passa un ID per identificare il widget associato all'operazione. Ogni widget ha anche un nome associato e un valore di stato che può essere usato per archiviare dati personalizzati. Per questo esempio, si indicherà una semplice struttura helper per archiviare l'ID, il nome e i dati per ogni widget aggiunto. I widget possono anche trovarsi in uno stato attivo, come illustrato nella sezione Attivare e Disattivare riportata di seguito e verrà monitorato questo stato per ogni widget con un valore booleano. Aggiungere la definizione seguente al file WidgetProvider.cs, all'interno dello spazio dei nomi ExampleWidgetProvider, ma all'esterno della definizione della 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;

}

All'interno della definizione della classe WidgetProvider in WidgetProvider.cs, aggiungere un membro per la mappa che manterrà l'elenco dei widget abilitati, usando l'ID widget come chiave per ogni voce.

// WidgetProvider.cs

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

Dichiarare le stringhe JSON del modello di widget

Questo esempio dichiara alcune stringhe statiche per definire i modelli JSON per ogni widget. Per praticità, questi modelli vengono archiviati nelle variabili membro della classe WidgetProvider. Se è necessaria una risorsa di archiviazione generale per i modelli, questi possono essere inclusi come parte del pacchetto dell'applicazione: Accesso ai file del pacchetto. Per informazioni sulla creazione del documento JSON del modello widget, vedere Creare un modello di widget con Adaptive Card Designer (Progettazione di Schede adattive).

// 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"                                                
}
""";

Implementare i metodi IWidgetProvider

Nelle sezioni successive verranno implementati i metodi dell'interfaccia IWidgetProvider. Il metodo helper UpdateWidget chiamato in diverse implementazioni di questi metodi verrà illustrato più avanti in questo articolo.

Nota

Gli oggetti passati nei metodi di callback dell'interfaccia IWidgetProvider sono garantiti solo per essere validi all'interno del callback. Non è consigliabile archiviare riferimenti a questi oggetti perché il relativo comportamento all'esterno del contesto del callback non è definito.

CreateWidget

L'host del widget chiama CreateWidget quando l'utente ha aggiunto uno dei widget dell'app nell'host del widget. In primo luogo, questo metodo ottiene l'ID e il nome del widget associato e aggiunge una nuova istanza della struttura helper, CompactWidgetInfo, alla raccolta di widget abilitati. Successivamente, viene inviato il modello iniziale e i dati per il widget, che viene incapsulato nel metodo helper 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'host del widget chiama DeleteWidget quando l'utente ha rimosso uno dei widget dell'app dall'host del widget. In questo caso, il widget associato verrà rimosso dall'elenco dei widget abilitati in modo che non vengano inviati altri aggiornamenti per tale widget.

// WidgetProvider.cs

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

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

Per questo esempio, oltre a rimuovere il widget con il valore specificato dall'elenco dei widget abilitati, viene controllato anche se l'elenco ora è vuoto e, in tal caso, viene impostato un evento che verrà usato in un secondo momento per consentire all'app di uscire quando non sono presenti widget abilitati. All'interno della definizione della classe aggiungere la dichiarazione di ManualResetEvent e una funzione di accesso pubblica.

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

public static ManualResetEvent GetEmptyWidgetListEvent()
{
    return emptyWidgetListEvent;
}

OnActionInvoked

L'host del widget chiama OnActionInvoked quando l'utente interagisce con un'azione definita nel modello di widget. Per il widget del contatore usato in questo esempio, un'azione è stata dichiarata con un valore verbo "inc" nel modello JSON per il widget. Il codice del provider di widget userà questo valore verbo per determinare l'azione da intraprendere in risposta all'interazione dell'utente.

...
    "actions": [                                                      
        {                                                               
            "type": "Action.Execute",                               
            "title": "Increment",                                   
            "verb": "inc"                                           
        }                                                               
    ], 
...

Nel metodo OnActionInvoked ottenere il valore del verbo controllando la proprietà Verbo di WidgetActionInvokedArgs passato nel metodo. Se il verbo è "inc", si viene a conoscenza che il conteggio verrà incrementato nello stato personalizzato per il widget. Da WidgetActionInvokedArgs ottenere l'oggetto WidgetContext e quindi WidgetId per ottenere l'ID per il widget da aggiornare. Trovare la voce nella mappa dei widget abilitati con l'ID specificato e quindi aggiornare il valore dello stato personalizzato usato per archiviare il numero di incrementi. Aggiornare infine il contenuto del widget con il nuovo valore con la funzione helper 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);
            }
        }
}

Per informazioni sulla sintassi Action.Execute per le Schede adattive, vedere Action.Execute. Per indicazioni sulla progettazione dell'interazione per i widget, vedere Linee guida per la progettazione dell'interazione con i widget

OnWidgetContextChanged

Nella versione corrente, OnWidgetContextChanged viene chiamato solo quando l'utente modifica le dimensioni di un widget aggiunto. È possibile scegliere di restituire un diverso modello JSON/dati all'host del widget a seconda delle dimensioni richieste. È anche possibile progettare il codice JSON del modello per supportare tutte le dimensioni disponibili usando il rendering condizionale in base al valore di host.widgetSize. Se non è necessario inviare un nuovo modello o nuovi dati per tenere conto della modifica delle dimensioni, è possibile usare OnWidgetContextChanged per scopi di telemetria.

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

Attivare e disattivare

Il metodo Attivare viene chiamato per notificare al provider di widget che l'host del widget è attualmente interessato a ricevere contenuto aggiornato dal provider. Ad esempio, potrebbe significare che l'utente sta attualmente visualizzando attivamente l'host del widget. Il metodo Disattivare viene chiamato per notificare al provider di widget che l'host del widget non richiede più aggiornamenti del contenuto. Questi due metodi definiscono una finestra in cui l'host del widget è più interessato a visualizzare il contenuto maggiormente aggiornato. I provider di widget possono inviare aggiornamenti al widget in qualsiasi momento, ad esempio in risposta a una notifica push, ma come per qualsiasi attività in background, è importante bilanciare la fornitura di contenuto aggiornato con problemi di risorse come la durata della batteria.

Attivare e Disattivare vengono chiamate in base al widget. Questo esempio tiene traccia dello stato attivo di ogni widget in strutt. dell'helper CompactWidgetInfo. Nel metodo Attivare viene chiamato il metodo helper UpdateWidget per aggiornare il widget. Si noti che l'intervallo di tempo tra Attivare e Disattivare può essere ridotto, quindi è consigliabile provare a rendere il percorso del codice di aggiornamento del widget il più rapido possibile.

// 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;
    }
}

Aggiornare un widget

Definire il metodo helper UpdateWidget per aggiornare un widget abilitato. In questo esempio si controlla il nome del widget nella struttura helper CompactWidgetInfo passato nel metodo e quindi si imposta il modello appropriato e il JSON di dati in base al widget da aggiornare. WidgetUpdateRequestOptions viene inizializzato con il modello, i dati e lo stato personalizzato per il widget da aggiornare. Chiamare WidgetManager::GetDefault per ottenere un'istanza della classe WidgetManager e quindi chiamare UpdateWidget per inviare i dati del widget aggiornati all'host del 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);
}

Inizializzare l'elenco dei widget abilitati all'avvio

Quando il provider di widget viene inizializzato per la prima volta, è consigliabile chiedere WidgetManager, se sono presenti widget in esecuzione attualmente in uso dal provider. Consente di ripristinare lo stato precedente dell'app in caso di riavvio del computer o arresto anomalo del provider. Chiamare WidgetManager.GetDefault per ottenere l'istanza predefinita di gestione widget per l'app. Quindi chiamare GetWidgetInfos, che restituisce una matrice di oggetti WidgetInfo. Copiare gli ID del widget, i nomi e lo stato personalizzato in strutt. dell'helper CompactWidgetInfo e salvare nella variabile membro RunningWidgets. Incollare il codice seguente nella definizione di classe per 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;
        }
    }
}

Implementare una class factory che creerà un'istanza di WidgetProvider su richiesta

Affinché l'host del widget comunichi con il provider di widget, è necessario chiamare CoRegisterClassObject. Questa funzione richiede di creare un'implementazione di IClassFactory che creerà un oggetto classe per la classe FeedProvider. La class factory verrà implementata in una classe helper autonoma.

In Visual Studio, fare clic con il pulsante destro del mouse sul progetto ExampleWidgetProvider in Esplora soluzioni e selezionare Aggiungi->Classe. Nella finestra di dialogo Aggiungi classe, assegnare alla classe il nome "FactoryHelper" e fare clic su Aggiungi.

Sostituire il contenuto del file FactoryHelper.cs con il codice seguente. Questo codice definisce l'interfaccia IClassFactory e implementa i due metodi, CreateInstance e LockServer. Questo codice è boilerplate tipico per l'implementazione di una class factory e non è specifico per la funzionalità di un provider di widget, ad eccezione del fatto che si indica che l'oggetto classe creato implementa l'interfaccia 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;

    }
}

Creare un GUID che rappresenta il CLSID per il provider di widget

Successivamente, è necessario creare un GUID che rappresenta il CLSID che verrà usato per identificare il provider di widget per l'attivazione COM. Lo stesso valore verrà usato anche durante la creazione del pacchetto dell'app. Generare un GUID in Visual Studio passando a Strumenti->Crea GUID. Selezionare l'opzione del formato del registro di sistema e fare clic su Copia e quindi incollare in un file di testo in modo che sia possibile copiarla in un secondo momento.

Registrare l'oggetto classe del provider di widget con OLE

Nel file Program.cs per l'eseguibile, viene chiamato CoRegisterClassObject per registrare il provider di widget con OLE, in modo che l'host del widget possa interagire con essa. Sostituire il contenuto di Program.cs con il codice seguente. Questo codice importa la funzione CoRegisterClassObject e la chiama, passando l'interfaccia WidgetProviderFactory definita in un passaggio precedente. Assicurarsi di aggiornare la dichiarazione di variabile CLSID_Factory per usare il GUID generato nel passaggio precedente.

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

Si noti che questo esempio di codice importa la funzione GetConsoleWindow per determinare se l'app è in esecuzione come applicazione console, il comportamento predefinito per questa procedura dettagliata. Se la funzione restituisce un puntatore valido, le informazioni di debug vengono scritte nella console. In caso contrario, l'app viene eseguita come app di Windows. In tal caso, aspettiamo l'evento impostato nel metodo DeleteWidget quando l'elenco dei widget abilitati è vuoto e l'app viene chiusa. Per informazioni sulla conversione dell'app console di esempio in un'app di Windows, consultare Convertire l'app console in un'app di Windows.

Crea il pacchetto dell'app del provider di widget

Nella versione corrente, solo le app in pacchetto possono essere registrate come provider di widget. La procedura seguente illustra il processo di creazione del pacchetto dell'app e l'aggiornamento del manifesto dell'app per registrare l'app con il sistema operativo come provider di widget.

Creare un progetto di creazione di pacchetti MSIX

In Esplora soluzioni fare clic con il pulsante destro del mouse sulla soluzione e selezionare Aggiungi->Nuovo progetto.... Nella finestra di dialogo Aggiungi un nuovo progetto selezionare il modello "Progetto di creazione pacchetti per applicazioni Windows" e fare clic su Avanti. Impostare il nome progetto su "ExampleWidgetProviderPackage" e fare clic su Crea. Quando richiesto, impostare la versione di destinazione alla versione 1809 o versione successiva e fare clic su OK. Quindi fare clic con il pulsante destro del mouse sul progetto ExampleWidgetProviderPackage e selezionare Aggiungi->Riferimento progetto. Selezionare il progetto ExampleWidgetProvider e fare clic su OK.

Aggiungere il riferimento pacchetto Windows App SDK al progetto di creazione del pacchetto

È necessario aggiungere un riferimento al pacchetto nuget Windows App SDK al progetto di creazione pacchetti MSIX. In Esplora soluzioni, fare doppio clic sul progetto ExampleWidgetProviderPackage per aprire il file ExampleWidgetProviderPackage.wapproj. Aggiungere il codice XML seguente all'interno dell'elemento Project.

<!--ExampleWidgetProviderPackage.wapproj-->
<ItemGroup>
    <PackageReference Include="Microsoft.WindowsAppSDK" Version="1.2.221116.1">
        <IncludeAssets>build</IncludeAssets>
    </PackageReference>  
</ItemGroup>

Nota

Assicurarsi che la Versione specificata nell'elemento PackageReference corrisponda alla versione stabile più recente a cui si fa riferimento nel passaggio precedente.

Se la versione corretta di Windows App SDK è già installata nel computer e non si vuole aggregare il runtime SDK nel pacchetto, è possibile specificare la dipendenza del pacchetto nel file Package.appxmanifest per il progetto 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>
...

Aggiornare il manifesto del pacchetto

In Esplora soluzioni fare clic con il pulsante destro del mouse sul Package.appxmanifest file e selezionare Visualizza codice per aprire il file XML del manifesto. Successivamente è necessario aggiungere alcune dichiarazioni dello spazio dei nomi per le estensioni del pacchetto dell'app che verranno usati. Aggiungere le definizioni dello spazio dei nomi seguenti all'elemento Pacchetto di livello superiore.

<!-- Package.appmanifest -->
<Package
  ...
  xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
  xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"

All'interno dell'elemento Applicazione creare un nuovo elemento vuoto denominato Estensioni. Assicurarsi che venga dopo il tag di chiusura per uap:VisualElements.

<!-- Package.appxmanifest -->
<Application>
...
    <Extensions>

    </Extensions>
</Application>

La prima estensione da aggiungere è l'estensione ComServer. In questo modo viene registrato il punto di ingresso del file eseguibile con il sistema operativo. Questa estensione è l'app in pacchetto equivalente alla registrazione di un server COM impostando una chiave del registro di sistema e non è specifica per i provider di widget. Aggiungere l'elemento com:Extension seguente come elemento figlio dell'elemento Estensioni. Modificare il GUID nell'attributo Id dell'elemento com:Class impostando il GUID generato in un passaggio precedente.

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

Aggiungere quindi l'estensione che registra l'app come provider di widget. Incollare l'elemento uap3:Extension nel frammento di codice seguente, come elemento figlio dell'elemento Estensioni. Assicurarsi di sostituire l'attributo ClassId dell'elemento COM con il GUID usato nei passaggi precedenti.

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

Per descrizioni dettagliate e informazioni sul formato per tutti questi elementi, vedere Formato XML del manifesto del pacchetto del provider di widget.

Aggiungere icone e altre immagini al progetto di creazione del pacchetto

In Esplora soluzioni, fare clic con il pulsante destro del mouse su ExampleWidgetProviderPackage e selezionare Aggiungi->Nuova cartella. Denominare questa cartella ProviderAssets perché è ciò che è stato usato in Package.appxmanifest dal passaggio precedente. Qui verranno archiviate le icone e gli screenshot per i widget. Dopo aver aggiunto icone e screenshot desiderati, assicurarsi che i nomi delle immagini corrispondano a ciò che viene dopo Path=ProviderAssets\ in Package.appxmanifest o i widget non verranno visualizzati in host del widget.

Per informazioni sui requisiti di progettazione per le immagini di screenshot e sulle convenzioni di denominazione per gli screenshot localizzati, vedere Integrare con la selezione widget.

Test del provider di widget

Assicurarsi di aver selezionato l'architettura corrispondente al computer di sviluppo dall'elenco a discesa Piattaforme soluzione, ad esempio "x64". In Esplora soluzioni, fare clic con il pulsante destro del mouse sulla soluzione e scegliere Compila soluzione. Al termine, fare clic con il pulsante destro del mouse su ExampleWidgetProviderPackage e scegliere Distribuisci. Nella versione corrente, l'unico host di widget supportato è la scheda Widget. Per visualizzare i widget è necessario aprire la scheda Widget e selezionare Aggiungi widget in alto a destra. Scorrere fino alla fine dei widget disponibili e per visualizzare il Widget Meteo fittizio e Widget di conteggio Microsoft creati in questa esercitazione. Fare clic sui widget per aggiungerli alla scheda Widget e testarne le funzionalità.

Debug del provider di widget

Dopo aver aggiunto i widget, la piattaforma Widget avvierà l'applicazione del provider di widget per ricevere e inviare informazioni pertinenti sul widget. Per eseguire il debug del widget in esecuzione, è possibile collegare un debugger all'applicazione del provider di widget in esecuzione oppure configurare Visual Studio per avviare automaticamente il debug del processo del provider di widget dopo l'avvio.

Per connettersi al processo in esecuzione:

  1. In Visual Studio fare clic su Debug -> Connetti a processo.
  2. Filtrare i processi e trovare l'applicazione del provider di widget desiderata.
  3. Collegare il debugger.

Per collegare automaticamente il debugger al processo all'avvio iniziale:

  1. In Visual Studio fare clic su Debug -> Altre destinazioni di debug -> Debug pacchetto applicazione installato.
  2. Filtrare i pacchetti e trovare il pacchetto del provider di widget desiderato.
  3. Selezionarlo e selezionare la casella Non avviare, ma eseguire il debug del codice all'avvio.
  4. Scegliere Connetti.

Convertire l'app console in un'app di Windows

Per convertire l'app console creata in questa procedura dettagliata in un'app di Windows, fare clic con il pulsante destro del mouse sul progetto ExampleWidgetProvider in Esplora soluzioni e scegliere Proprietà. In Applicazione->Generale modificare il tipo di output da "Applicazione console" a "Applicazione Windows".

Screenshot che mostra le proprietà del progetto provider di widget C# con il tipo di output impostato su Applicazione Windows

Pubblicazione del widget

Dopo aver sviluppato e testato il widget, è possibile pubblicare l'app in Microsoft Store per consentire agli utenti di installare i widget nei propri dispositivi. Per istruzioni dettagliate per la pubblicazione di un'app, consultare Pubblicare l'app in Microsoft Store.

Raccolta Store dei widget

Dopo che l'app è stata pubblicata in Microsoft Store, è possibile richiedere che l'app sia inclusa nella raccolta dello Store dei widget che consente agli utenti di individuare le app che presentano widget di Windows. Per inviare la richiesta, vedere Inviare le informazioni sul widget per l'aggiunta alla raccolta dello Store.

Screenshot di Microsoft Store che mostra la raccolta di widget che consente agli utenti di individuare le app che presentano widget di Windows.

Implementazione della personalizzazione dei widget

A partire da Windows App SDK 1.4, i widget possono supportare la personalizzazione degli utenti. Quando questa funzionalità viene implementata, viene aggiunta un'opzione Personalizza widget al menu con i puntini di sospensione sopra l'opzione Rimuovi widget.

Screenshot che mostra un widget con la finestra di dialogo di personalizzazione visualizzata.

I passaggi seguenti riepilogano il processo di personalizzazione dei widget.

  1. Nel normale funzionamento, il provider di widget risponde alle richieste dall'host del widget con il modello e i payload di dati per l'esperienza regolare del widget.
  2. L'utente fa clic sul pulsante Personalizza widget nel menu con i puntini di sospensione.
  3. Il widget genera l'evento OnCustomizationRequested nel provider di widget per indicare che l'utente ha richiesto l'esperienza di personalizzazione del widget.
  4. Il provider di widget imposta un flag interno per indicare che il widget è in modalità di personalizzazione. In modalità di personalizzazione, il provider di widget invia i modelli JSON per l'interfaccia utente di personalizzazione del widget anziché l'interfaccia utente del widget normale.
  5. In modalità di personalizzazione, il provider di widget riceve gli eventi OnActionInvoked mentre l'utente interagisce con l'interfaccia utente di personalizzazione e modifica la configurazione interna e il comportamento in base alle azioni dell'utente.
  6. Quando l'azione associata all'evento OnActionInvoked è l'azione di "uscire da personalizzazione" definita dall'app, il provider di widget reimposta il flag interno per indicare che non si trova più in modalità di personalizzazione e riprende l'invio dei modelli JSON di dati e visivi per l'esperienza regolare del widget, riflettendo le modifiche richieste durante la personalizzazione.
  7. Il provider di widget mantiene le opzioni di personalizzazione sul disco o sul cloud in modo che le modifiche vengano mantenute tra le chiamate del provider di widget.

Nota

Esiste un bug noto con la bacheca dei widget di Windows, per i widget creati usando Windows App SDK, che causa la mancata risposta del menu con i puntini di sospensione dopo la visualizzazione della scheda di personalizzazione.

Negli scenari di personalizzazione dei widget tipici, l'utente sceglierà i dati visualizzati nel widget o regola la presentazione visiva del widget. Per semplicità, l'esempio in questa sezione aggiungerà il comportamento di personalizzazione che consente all'utente di reimpostare il contatore del widget di conteggio implementato nei passaggi precedenti.

Nota

La personalizzazione dei widget è supportata solo in Windows App SDK 1.4 e versioni successive. Assicurarsi di aggiornare i riferimenti nel progetto alla versione più recente del pacchetto NuGet.

Aggiornare il manifesto del pacchetto per indicare il supporto per la personalizzazione

Per informare l'host del widget che il widget supporta la personalizzazione, aggiungere l'attributo IsCustomizable all'elemento Definizione per il widget e impostarlo su true.

...
<Definition Id="Counting_Widget"
    DisplayName="Microsoft Counting Widget"
    Description="CONFIG counting widget description"
    IsCustomizable="true">
...

Registrare quando un widget è in modalità di personalizzazione

L'esempio in questo articolo usa la strutt. helper CompactWidgetInfo per registrare lo stato corrente dei widget attivi. Aggiungere il campo inCustomization che verrà usato per tenere traccia del momento in cui l'host del widget prevede di inviare il modello JSON di personalizzazione anziché il modello di widget normale.

// 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;
}

Implementare IWidgetProvider2

La funzionalità di personalizzazione del widget viene esposta tramite l'interfaccia IWidgetProvider2. Aggiornare la definizione della classe WidgetProvider per implementare questa interfaccia.

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

Aggiungere un'implementazione per la callback OnCustomizationRequested dell'interfaccia IWidgetProvider2. Questo metodo usa lo stesso modello delle altre callback usate. Ottenere l'ID per il widget da personalizzare da WidgetContext e trovare la strutt. helper CompactWidgetInfo associata a tale widget e impostare il campo inCustomization su 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);
    }
}

A questo punto, indicare una variabile stringa che definisce il modello JSON per l'interfaccia utente di personalizzazione del widget. Per questo esempio, abbiamo un pulsante "Reimposta contatore" e un pulsante "Esci da personalizzazione" che segnalerà al provider di tornare al normale comportamento del widget. Inserire questa definizione accanto alle altre definizioni di modello.

// 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""
}";

Inviare un modello di personalizzazione in UpdateWidget

Si aggiornerà quindi il metodo helper UpdateWidget che invia i dati e i modelli JSON visivi all'host del widget. Quando si aggiorna il widget di conteggio, viene inviato il modello di widget regolare o il modello di personalizzazione a seconda del valore del campo inCustomization. Per brevità, il codice non rilevante per la personalizzazione viene omesso in questo frammento di codice.

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

Rispondere alle azioni di personalizzazione

Quando gli utenti interagiscono con gli input nel modello di personalizzazione, chiamare lo stesso gestore OnActionInvoked come quando l'utente interagisce con l'esperienza regolare del widget. Per supportare la personalizzazione, cercare i verbi "reset" e "exitCustomization" dal modello JSON di personalizzazione. Se l'azione è relativa al pulsante "Reimposta contatore", il contatore viene reimpostato nel campo customState della strutt. helper su 0. Se l'azione è relativa al pulsante "Esci da personalizzazione", viene impostato il campo inCustomization su false in modo che quando si chiama UpdateWidget, il metodo helper invierà i normali modelli JSON e non il modello di personalizzazione.

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

Ora, quando si distribuisce il widget, verrà visualizzato il pulsante Personalizza widget nel menu con i puntini di sospensione. Facendo clic sul pulsante Personalizza verrà visualizzato il modello di personalizzazione.

Screenshot che mostra l'interfaccia utente di personalizzazione dei widget.

Fare clic sul pulsante Reimposta contatore per reimpostare il contatore su 0. Fare clic sul pulsante Esci da personalizzazione per tornare al comportamento regolare del widget.