Implementare un provider di widget in un'app win32 (C++/WinRT)

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 di Schede 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.

Questo codice di esempio in questo articolo è adattato dall'esempio di widget SDK per app di Windows. Per implementare un provider di widget con C#, vedere Implementare un provider di widget in un'app win32 (C#).

Prerequisiti

  • Un dispositivo Windows 11 con la scheda Widgets disponibile per i test.
  • Il dispositivo deve avere la modalità sviluppatore abilitata. Per altre informazioni, vedere Impostazioni per gli sviluppatori.
  • Visual Studio 2022 o versione successiva con il carico di lavoro per lo sviluppo di applicazioni WinUI. Assicurarsi di aggiungere il componente per C++ (v143) dall'elenco a discesa facoltativo.

Creare una nuova app console win32 C++/WinRT

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 Windows Applicazione console (C++/WinRT). Assegnare il nome al nuovo progetto "ExampleWidgetProvider". Quando richiesto, impostare la versione Windows di destinazione per l'app sulla versione 1809 o successiva.

Aggiungere riferimenti ai pacchetti NuGet del SDK per app di Windows e della Windows Implementation Library.

Questa procedura dettagliata usa Microsoft. WindowsAppSDK versione 2.5.1. In Esplora soluzioni fare clic con il pulsante destro del mouse su References e selezionare Gestisci pacchetti NuGet... . Nella gestione pacchetti NuGet selezionare la scheda Browse e cercare "Microsoft. WindowsAppSDK". Selezionare la versione 2.5.1 o una versione stabile supportata successiva nell'elenco a discesa Versione e quindi fare clic su Installa. Per informazioni sul supporto correnti, vedere SDK per app di Windows canali di rilascio.

Questo esempio usa anche il pacchetto NuGet della libreria di implementazione Windows. In Esplora soluzioni fare clic con il pulsante destro del mouse su References e selezionare Gestisci pacchetti NuGet... . Nella gestione pacchetti NuGet selezionare la scheda Browse e cercare "Microsoft.Windows. ImplementazioneLibrary". Selezionare la versione più recente nell'elenco a discesa Versione e quindi fare clic su Installa.

Nel file di intestazione precompilato, pch.h, aggiungere le direttive di inclusione seguenti.

//pch.h 
#pragma once
#include <wil/cppwinrt.h>
#include <wil/resource.h>
...
#include <winrt/Microsoft.Windows.Widgets.Providers.h>

Nota

È necessario includere prima l'intestazione wil/cppwinrt.h prima di qualsiasi intestazione WinRT.

Per gestire correttamente l'arresto dell'app del provider di widget, è necessario un'implementazione personalizzata di winrt::get_module_lock. Emettiamo una predichiarazione del metodo SignalLocalServerShutdown che viene definito nel file main.cpp e impostiamo un evento che segnala l'uscita dall'app. Aggiungere il codice seguente al file pch.h, subito sotto la direttiva #pragma once, prima degli altri include.

//pch.h
#include <stdint.h>
#include <combaseapi.h>

// In .exe local servers the class object must not contribute to the module ref count, and use
// winrt::no_module_lock, the other objects must and this is the hook into the C++ WinRT ref counting system
// that enables this.
void SignalLocalServerShutdown();

namespace winrt
{
    inline auto get_module_lock() noexcept
    {
        struct service_lock
        {
            uint32_t operator++() noexcept
            {
                return ::CoAddRefServerProcess();
            }

            uint32_t operator--() noexcept
            {
                const auto ref = ::CoReleaseServerProcess();

                if (ref == 0)
                {
                    SignalLocalServerShutdown();
                }
                return ref;
            }
        };

        return service_lock{};
    }
}


#define WINRT_CUSTOM_MODULE_LOCK

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 Add->Class. Nella finestra di dialogo Aggiungi classe, assegnare alla classe il nome "WidgetProvider" e fare clic su Aggiungi.

Dichiarare una classe che implementa l'interfaccia IWidgetProvider

L'interfaccia IWidgetProvider definisce i metodi che l'host del widget richiamerà per avviare operazioni con il provider di widget. Sostituire la definizione di classe vuota nel file WidgetProvider.h con il codice seguente. Questo codice dichiara una struttura che implementa l'interfaccia IWidgetProvider e dichiara i prototipi per i metodi di interfaccia.

// WidgetProvider.h
struct WidgetProvider : winrt::implements<WidgetProvider, winrt::Microsoft::Windows::Widgets::Providers::IWidgetProvider>
{
    WidgetProvider();

    /* IWidgetProvider required functions that need to be implemented */
    void CreateWidget(winrt::Microsoft::Windows::Widgets::Providers::WidgetContext widgetContext);
    void DeleteWidget(winrt::hstring const& widgetId, winrt::hstring const& customState);
    void OnActionInvoked(winrt::Microsoft::Windows::Widgets::Providers::WidgetActionInvokedArgs actionInvokedArgs);
    void OnWidgetContextChanged(winrt::Microsoft::Windows::Widgets::Providers::WidgetContextChangedArgs contextChangedArgs);
    void Activate(winrt::Microsoft::Windows::Widgets::Providers::WidgetContext widgetContext);
    void Deactivate(winrt::hstring widgetId);
    /* IWidgetProvider required functions that need to be implemented */

    
};

Aggiungere anche un metodo privato, UpdateWidget, che è un metodo helper che invierà gli aggiornamenti dal provider all'host del widget.

// WidgetProvider.h
private: 

void UpdateWidget(CompactWidgetInfo const& localWidgetInfo);

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 seguente definizione al file WidgetProvider.h, sopra la dichiarazione della struttura WidgetProvider.

// WidgetProvider.h
struct CompactWidgetInfo
{
    winrt::hstring widgetId;
    winrt::hstring widgetName;
    int customState = 0;
    bool isActive = false;
};

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

// WidgetProvider.h
#include <mutex>
#include <unordered_map>
struct WidgetProvider : winrt::implements<WidgetProvider, winrt::Microsoft::Windows::Widgets::Providers::IWidgetProvider>
{
...
    private:
        ...
        static std::unordered_map<winrt::hstring, CompactWidgetInfo> RunningWidgets;
        static std::mutex RunningWidgetsMutex;

        

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 locali dichiarate all'esterno della definizione 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 di widget, vedere Creare un modello di widget con Schede adattive Designer.

È possibile personalizzare l'intestazione visualizzata per il widget nella bacheca dei widget, sostituendo la presentazione predefinita. Per altre informazioni, vedere Personalizzare l'area dell'intestazione del widget.

Nota

Il supporto del widget Web è stato aggiunto in SDK per app di Windows 1.6.3. È possibile visualizzare il codice HTML fornito da un URL invece di definire il corpo del widget con Schede adattive. Si fornisce comunque un payload JSON della scheda adattiva contenente l'URL del contenuto. Per l'intercettazione e la messaggistica delle risorse tra il provider e il contenuto Web, vedere Provider di widget Web. Queste funzionalità integrano IWidgetProvider; non lo sostituiscono.

// WidgetProvider.h
const std::string weatherWidgetTemplate = R"(
{
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "type": "AdaptiveCard",
    "version": "1.0",
    "speak": "<s>The forecast for Seattle January 20 is mostly clear with a High of 51 degrees and Low of 40 degrees</s>",
    "backgroundImage": "https://raw.githubusercontent.com/microsoft/AdaptiveCards/59402c8d43f7a69a68e0b4fccc08f8a76fc98ccd/source/nodejs/adaptivecards-site/themes/adaptivecards/source/content/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://raw.githubusercontent.com/microsoft/AdaptiveCards/59402c8d43f7a69a68e0b4fccc08f8a76fc98ccd/source/nodejs/adaptivecards-site/themes/adaptivecards/source/content/Mostly%20Cloudy-Square.png",
                            "size": "small",
                            "altText": "Mostly cloudy weather"
                        }
                    ]
                },
                {
                    "type": "Column",
                    "width": "auto",
                    "items": [
                        {
                            "type": "TextBlock",
                            "text": "46",
                            "size": "extraLarge",
                            "spacing": "none",
                            "wrap": true
                        }
                    ]
                },
                {
                    "type": "Column",
                    "width": "stretch",
                    "items": [
                        {
                            "type": "TextBlock",
                            "text": "°F",
                            "weight": "bolder",
                            "spacing": "small",
                            "wrap": true
                        }
                    ]
                },
                {
                    "type": "Column",
                    "width": "stretch",
                    "items": [
                        {
                            "type": "TextBlock",
                            "text": "Hi 50",
                            "horizontalAlignment": "left",
                            "wrap": true
                        },
                        {
                            "type": "TextBlock",
                            "text": "Lo 41",
                            "horizontalAlignment": "left",
                            "spacing": "none",
                            "wrap": true
                        }
                    ]
                }
            ]
        }
    ]
})";

const std::string countWidgetTemplate = R"(
{                                                                     
    "type": "AdaptiveCard",                                         
    "body": [                                                         
        {                                                               
            "type": "TextBlock",                                    
            "text": "You have clicked the button ${count} times"    
        },
        {
             "text":"Rendering Only if Medium",
             "type":"TextBlock",
             "$when":"${$host.widgetSize==\"medium\"}"
        },
        {
             "text":"Rendering Only if Small",
             "type":"TextBlock",
             "$when":"${$host.widgetSize==\"small\"}"
        },
        {
         "text":"Rendering Only if Large",
         "type":"TextBlock",
         "$when":"${$host.widgetSize==\"large\"}"
        }                                                                    
    ],                                                                  
    "actions": [                                                      
        {                                                               
            "type": "Action.Execute",                               
            "title": "Increment",                                   
            "verb": "inc"                                           
        }                                                               
    ],                                                                  
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "version": "1.5"                                                
})";

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. Prima di approfondire i metodi di interfaccia, aggiungere le righe seguenti a WidgetProvider.cpp, dopo le direttive di inclusione, per eseguire il pull delle API del provider di widget nello spazio dei nomi winrt e consentire l'accesso alla mappa dichiarata nel passaggio precedente.

Nota

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

// WidgetProvider.cpp
#include <optional>

namespace winrt
{
    using namespace Microsoft::Windows::Widgets::Providers;
}

std::unordered_map<winrt::hstring, CompactWidgetInfo> WidgetProvider::RunningWidgets{};
std::mutex WidgetProvider::RunningWidgetsMutex{};

Tutte le istanze del provider condividono questa mappa. Proteggere ogni accesso con RunningWidgetsMutex, compresi i callback di ripristino all’avvio e di personalizzazione. Copiare lo stato necessario per un aggiornamento mantenendo il mutex, quindi rilasciarlo prima di chiamare UpdateWidget in modo che una chiamata al widget-manager non mantenga il lock dello stato condiviso.

CreaWidget

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.cpp
void WidgetProvider::CreateWidget(winrt::WidgetContext widgetContext)
{
    auto widgetId = widgetContext.Id();
    auto widgetName = widgetContext.DefinitionId();
    CompactWidgetInfo runningWidgetInfo{ widgetId, widgetName };
    {
        std::scoped_lock lock(RunningWidgetsMutex);
        RunningWidgets[widgetId] = runningWidgetInfo;
    }
    
    // Update the widget
    UpdateWidget(runningWidgetInfo);
}

EliminaWidget

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.cpp
void WidgetProvider::DeleteWidget(winrt::hstring const& widgetId, winrt::hstring const& customState)
{
    std::scoped_lock lock(RunningWidgetsMutex);
    RunningWidgets.erase(widgetId);
}

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 la relativa proprietà Id per identificare l'istanza del 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.cpp
void WidgetProvider::OnActionInvoked(winrt::WidgetActionInvokedArgs actionInvokedArgs)
{
    auto verb = actionInvokedArgs.Verb();
    if (verb == L"inc")
    {
        auto widgetId = actionInvokedArgs.WidgetContext().Id();
        // If you need to use some data that was passed in after
        // Action was invoked, you can get it from the args:
        auto data = actionInvokedArgs.Data();
        std::optional<CompactWidgetInfo> widgetToUpdate;
        {
            std::scoped_lock lock(RunningWidgetsMutex);
            if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
            {
                iter->second.customState++;
                widgetToUpdate = iter->second;
            }
        }
        if (widgetToUpdate)
        {
            UpdateWidget(*widgetToUpdate);
        }
    }
}

Per informazioni sulla sintassi Action.Execute per 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.

QuandoIlContestoDelWidgetCambia

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.cpp
void WidgetProvider::OnWidgetContextChanged(winrt::WidgetContextChangedArgs contextChangedArgs)
{
    auto widgetContext = contextChangedArgs.WidgetContext();
    auto widgetId = widgetContext.Id();
    std::optional<CompactWidgetInfo> widgetToUpdate;
    {
        std::scoped_lock lock(RunningWidgetsMutex);
        if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
        {
            widgetToUpdate = iter->second;
        }
    }
    if (widgetToUpdate)
    {
        UpdateWidget(*widgetToUpdate);
    }
}
    

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.

void WidgetProvider::Activate(winrt::Microsoft::Windows::Widgets::Providers::WidgetContext widgetContext)
{
    auto widgetId = widgetContext.Id();

    std::optional<CompactWidgetInfo> widgetToUpdate;
    {
        std::scoped_lock lock(RunningWidgetsMutex);
        if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
        {
            iter->second.isActive = true;
            widgetToUpdate = iter->second;
        }
    }
    if (widgetToUpdate)
    {
        UpdateWidget(*widgetToUpdate);
    }
}

void WidgetProvider::Deactivate(winrt::hstring widgetId)
{
    std::scoped_lock lock(RunningWidgetsMutex);
    if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
    {
        auto& localWidgetInfo = iter->second;
        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.cpp
void WidgetProvider::UpdateWidget(CompactWidgetInfo const& localWidgetInfo)
{
    winrt::WidgetUpdateRequestOptions updateOptions{ localWidgetInfo.widgetId };

    winrt::hstring templateJson;
    if (localWidgetInfo.widgetName == L"Weather_Widget")
    {
        templateJson = winrt::to_hstring(weatherWidgetTemplate);
    }
    else if (localWidgetInfo.widgetName == L"Counting_Widget")
    {
        templateJson = winrt::to_hstring(countWidgetTemplate);
    }

    winrt::hstring dataJson;
    if (localWidgetInfo.widgetName == L"Weather_Widget")
    {
        dataJson = L"{}";
    }
    else if (localWidgetInfo.widgetName == L"Counting_Widget")
    {
        dataJson = L"{ \"count\": " + winrt::to_hstring(localWidgetInfo.customState) + L" }";
    }

    updateOptions.Template(templateJson);
    updateOptions.Data(dataJson);
    updateOptions.IsPlaceholderContent(localWidgetInfo.widgetName == L"Weather_Widget");
    // You can store some custom state in the widget service that you will be able to query at any time.
    updateOptions.CustomState(winrt::to_hstring(localWidgetInfo.customState));
    winrt::WidgetManager::GetDefault().UpdateWidget(updateOptions);
}

Quando il contenuto non è ancora personalizzato o utilizzabile, impostare WidgetUpdateRequestOptions.IsPlaceholderContent su true. Ad esempio, il widget meteo fittizio in questa procedura dettagliata visualizza una posizione fissa anziché la posizione scelta dall'utente. L'host può classificare in ordine di priorità i widget con contenuto personalizzato rispetto a questo widget meteo di esempio. Impostare la proprietà su false quando si specifica contenuto personalizzato. Questa proprietà è stata aggiunta in SDK per app di Windows 1.6.6.

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 nel costruttore per la classe WidgetProvider.

Aggiungere #include <charconv> e #include <system_error> a WidgetProvider.cpp per la conversione dello stato salvato.

// WidgetProvider.cpp
WidgetProvider::WidgetProvider()
{
    auto runningWidgets = winrt::WidgetManager::GetDefault().GetWidgetInfos();
    for (auto widgetInfo : runningWidgets )
    {
        auto widgetContext = widgetInfo.WidgetContext();
        auto widgetId = widgetContext.Id();
        auto widgetName = widgetContext.DefinitionId();
        auto customState = widgetInfo.CustomState();
        CompactWidgetInfo runningWidgetInfo{ widgetId, widgetName };
        if (!customState.empty())
        {
            auto savedState = winrt::to_string(customState);
            auto [end, error] = std::from_chars(
                savedState.data(), savedState.data() + savedState.size(),
                runningWidgetInfo.customState);
            if (error != std::errc{} || end != savedState.data() + savedState.size())
            {
                runningWidgetInfo.customState = 0;
                OutputDebugStringW(L"Invalid saved widget count; resetting to 0.\n");
            }
        }
        std::scoped_lock lock(RunningWidgetsMutex);
        RunningWidgets.try_emplace(widgetId, runningWidgetInfo);
    }
}

Registrare una factory di classi che istanzierà WidgetProvider su richiesta

Aggiungere l'intestazione che definisce la classe WidgetProvider alle inclusioni nella parte superiore del file dell'app main.cpp.

// main.cpp
...
#include "WidgetProvider.h"

Dichiarare l'evento che attiverà l'uscita dell'app e la funzione SignalLocalServerShutdown che imposta l'evento. Incollare il codice seguente in main.cpp.

// main.cpp
wil::unique_event g_shudownEvent(wil::EventOptions::None);

void SignalLocalServerShutdown()
{
    g_shudownEvent.SetEvent();
}

Successivamente, sarà necessario creare un CLSID che verrà usato per identificare il provider di widget per l'attivazione COM. Generare un GUID in Visual Studio passando a Tools->Crea GUID. Selezionare l'opzione "static const GUID =" e fare clic su Copia, quindi incollare in main.cpp. Aggiornare la definizione GUID con la sintassi C++/WinRT seguente, impostando il nome della variabile GUID widget_provider_clsid. Lasciare la versione con commenti del GUID, perché sarà necessario questo formato in un secondo momento, quando si crea il pacchetto dell'app.

// main.cpp
...
// {80F4CB41-5758-4493-9180-4FB8D480E3F5}
static constexpr GUID widget_provider_clsid
{
    0x80f4cb41, 0x5758, 0x4493, { 0x91, 0x80, 0x4f, 0xb8, 0xd4, 0x80, 0xe3, 0xf5 }
};

Aggiungere la seguente definizione della classe factory a main.cpp. La factory usa winrt::no_module_lock e non mantiene un'istanza del provider, quindi la registrazione della factory non lascia acquisito il blocco del modulo del provider. Ogni attivazione crea un provider e restituisce l'interfaccia richiesta da COM. Il mutex della fabbrica serializza la costruzione; RunningWidgetsMutex protegge separatamente la mappa da costruttori e callback concorrenti. Le eccezioni vengono convertite in HRESULT all'interfaccia COM. CoWaitForMultipleObjects attende l'evento di arresto prima che l'app venga chiusa.

// main.cpp
template <typename T>
struct WidgetProviderClassFactory : winrt::implements<WidgetProviderClassFactory<T>, IClassFactory, winrt::no_module_lock>
{
    STDMETHODIMP CreateInstance(
        ::IUnknown* outer,
        GUID const& iid,
        void** result) noexcept final
    {
        if (!result)
        {
            return E_POINTER;
        }
        *result = nullptr;

        if (outer)
        {
            return CLASS_E_NOAGGREGATION;
        }

        try
        {
            std::scoped_lock lock(m_creationMutex);
            return winrt::make<T>().as(iid, result);
        }
        catch (...)
        {
            return winrt::to_hresult();
        }
    }

    STDMETHODIMP LockServer(BOOL) noexcept final
    {
        return S_OK;
    }

private:
    std::mutex m_creationMutex;
};

int main()
{
    winrt::init_apartment();
    wil::unique_com_class_object_cookie widgetProviderFactory;
    auto factory = winrt::make<WidgetProviderClassFactory<WidgetProvider>>();

    winrt::check_hresult(CoRegisterClassObject(
        widget_provider_clsid,
        factory.get(),
        CLSCTX_LOCAL_SERVER,
        REGCLS_MULTIPLEUSE,
        widgetProviderFactory.put()));

    DWORD index{};
    HANDLE events[] = { g_shudownEvent.get() };
    winrt::check_hresult(CoWaitForMultipleObjects(CWMO_DISPATCH_CALLS | CWMO_DISPATCH_WINDOW_MESSAGES,
        INFINITE,
        static_cast<ULONG>(std::size(events)), events, &index));

    return 0;
}

L'applicazione host può attivare e rilasciare il provider mentre i widget rimangono bloccati. Ripristinare lo stato salvato all'avvio del provider e sincronizzare l'accesso allo stato del widget condiviso se i callback vengono eseguiti simultaneamente. Per altre informazioni, vedere le osservazioni di IWidgetProvider.

Impacchetta l'app di fornitura di widget

Questa procedura dettagliata registra il provider di widget tramite il manifest del pacchetto MSIX. I passaggi seguenti creano il pacchetto dell'app e la registrano con Windows come provider di widget. Nell'esempio viene usata la distribuzione dipendente dal framework del runtime di SDK per app di Windows. Se si sceglie invece la distribuzione autonoma, seguire Distribuire app autonome, incluse le impostazioni per entrambi i progetti quando si usa un Windows Application Packaging Project.

Creare un progetto di packaging MSIX

In Esplora soluzioni fare clic con il pulsante destro del mouse sulla soluzione e scegliere Add->New Project... . Nella finestra di dialogo Aggiungi una nuova finestra di dialogo project selezionare il modello "Project di creazione pacchetti di 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. Fare quindi clic con il pulsante destro del mouse sul project ExampleWidgetProviderPackage e selezionare Add->Project reference. Selezionare il progetto ExampleWidgetProvider e fare clic su OK.

Aggiungere un riferimento al pacchetto SDK per app di Windows nel progetto di packaging

È necessario aggiungere un riferimento al pacchetto nuget SDK per app di Windows 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="2.5.1">
        <IncludeAssets>build</IncludeAssets>
    </PackageReference>  
</ItemGroup>

Nota

Il frammento di codice usa la versione 2.5.1, non la versione minima che ha introdotto le API del widget. Se è stata selezionata una versione stabile supportata successiva nel passaggio precedente, aggiornare il valore Version in modo che corrisponda.

Aggiornare il manifesto del pacchetto

In Esplora soluzioni fare clic con il pulsante destro del mouse sul file Package.appxmanifest e selezionare View Code 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 utilizzate. Aggiungere le seguenti definizioni di namespace all'elemento principale Pacchetto.

<!-- 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 sul ExampleWidgetProviderPackage e selezionare Add->New Folder. 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 le icone e gli screenshot desiderati, assicurati che i nomi delle immagini corrispondano a ciò che viene dopo Path=ProviderAssets\ in Package.appxmanifest o i widget non verranno visualizzati nell'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 il selettore widget.

Verifica del fornitore di widget

Assicurati 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, quindi fare clic con il pulsante destro del mouse su ExampleWidgetProviderPackage e scegliere Distribuisci. Apri la scheda Widget con tasto Windows + W e apri la selezione widget, visualizzata come Aggiungi widget o come pulsante + a seconda della versione della scheda. Trovare il widget Weather e Microsoft Counting Widget, aggiungerli alla bacheca e testarne le funzionalità.

Esegui il 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 -> Collega al 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 App Installato.
  2. Filtrare i pacchetti e trovare il pacchetto del provider di widget desiderato.
  3. Seleziona l'opzione e spunta la casella che dice Non avviare, ma esegui il debug del codice al momento dell'avvio.
  4. Fare clic su Allega.

Convertire l'applicazione console in un'applicazione Windows

Per convertire l'app console creata in questa procedura dettagliata in un'app Windows:

  1. Fare clic con il pulsante destro del mouse sul progetto ExampleWidgetProvider in Esplora soluzioni e selezionare Properties. Passare a Linker -> System e modificare SubSystem da "Console" a "Windows". Questa operazione può essere eseguita anche aggiungendo <SubSystem>Windows</SubSystem> al <Link>.. </Link> sezione del .vcxproj.
  2. In main.cpp, cambiare int main() in int WINAPI wWinMain(_In_ HINSTANCE /*hInstance*/, _In_opt_ HINSTANCE /*hPrevInstance*/, _In_ PWSTR pCmdLine, _In_ int /*nCmdShow*/).

A screenshot che mostra le proprietà del progetto del provider di widget C++ con il tipo di output impostato su Windows Application

Pubblicazione del widget

Dopo aver sviluppato e testato il widget, puoi pubblicare l'app nel Microsoft Store per consentire agli utenti di installare i widget nei loro dispositivi. Per istruzioni dettagliate per la pubblicazione di un'app, vedi Pubblica l'app nel Microsoft Store.

Collezione di Widget dello Store

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

Screenshot del Microsoft Store che mostra la raccolta di widget che consente agli utenti di scoprire le app che includono Windows Widgets.

Implementazione della personalizzazione dei widget

A partire da SDK per app di Windows 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 i modelli JSON visivi e di dati per la normale esperienza dei widget.
  2. L'utente fa clic sul pulsante Personalizza widget nel menu a tre punti.
  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 scheda Windows widget, per i widget compilati usando il SDK per app di Windows, 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 richiede SDK per app di Windows 1.4 o versione successiva. Questa procedura dettagliata usa Microsoft.WindowsAppSDK versione 2.5.1.

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

Aggiornare WidgetProvider.h

Per aggiungere il supporto per la personalizzazione al widget creato nei passaggi precedenti di questo articolo, è necessario aggiornare il file di intestazione per il provider di widget WidgetProvider.h.

Prima di tutto, aggiornare la definizione CompactWidgetInfo. Questa struct helper ci aiuta a tenere traccia dello stato corrente dei nostri 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.h
struct CompactWidgetInfo
{
    winrt::hstring widgetId;
    winrt::hstring widgetName;
    int customState = 0;
    bool isActive = false;
    bool inCustomization = false;
};

Aggiornare la dichiarazione WidgetProvider per implementare l'interfaccia IWidgetProvider2.

// WidgetProvider.h

struct WidgetProvider : winrt::implements<WidgetProvider, winrt::Microsoft::Windows::Widgets::Providers::IWidgetProvider, winrt::Microsoft::Windows::Widgets::Providers::IWidgetProvider2>

Aggiungere una dichiarazione per la callback OnCustomizationRequested dell'interfaccia IWidgetProvider2.

// WidgetProvider.h

void OnCustomizationRequested(winrt::Microsoft::Windows::Widgets::Providers::WidgetCustomizationRequestedArgs args);

Infine, 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 dalla personalizzazione" che segnalerà al provider di tornare al normale comportamento del widget.

// WidgetProvider.h
const std::string countWidgetCustomizationTemplate = R"(
{
    "type": "AdaptiveCard",
    "actions" : [
        {
            "type": "Action.Execute",
            "title" : "Reset counter",
            "verb": "reset"
            },
            {
            "type": "Action.Execute",
            "title": "Exit customization",
            "verb": "exitCustomization"
            }
    ],
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "version": "1.5"
})";

Aggiornare WidgetProvider.cpp

Aggiornare ora il file WidgetProvider.cpp per implementare il comportamento di personalizzazione del widget. 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.cpp
void WidgetProvider::OnCustomizationRequested(winrt::WidgetCustomizationRequestedArgs args)
{
    auto widgetId = args.WidgetContext().Id();

    std::optional<CompactWidgetInfo> widgetToUpdate;
    {
        std::scoped_lock lock(RunningWidgetsMutex);
        if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
        {
            iter->second.inCustomization = true;
            widgetToUpdate = iter->second;
        }
    }
    if (widgetToUpdate)
    {
        UpdateWidget(*widgetToUpdate);
    }
}

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.cpp
void WidgetProvider::UpdateWidget(CompactWidgetInfo const& localWidgetInfo)
{
    ...
    else if (localWidgetInfo.widgetName == L"Counting_Widget")
    {
        if (!localWidgetInfo.inCustomization)
        {
            std::wcout << L" - not in customization " << std::endl;
            templateJson = winrt::to_hstring(countWidgetTemplate);
		}
        else
        {
            std::wcout << L" - in customization " << std::endl;
            templateJson = winrt::to_hstring(countWidgetCustomizationTemplate);
		}
    }
    ...
    
    updateOptions.Template(templateJson);
    updateOptions.Data(dataJson);
    // !!  You can store some custom state in the widget service that you will be able to query at any time.
    updateOptions.CustomState(winrt::to_hstring(localWidgetInfo.customState));
    winrt::WidgetManager::GetDefault().UpdateWidget(updateOptions);
}

Quando gli utenti interagiscono con gli input nel modello di personalizzazione, chiama lo stesso gestore OnActionInvoked così come quando interagiscono con l'esperienza standard del widget. Per supportare la personalizzazione, cerchiamo 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.cpp
void WidgetProvider::OnActionInvoked(winrt::WidgetActionInvokedArgs actionInvokedArgs)
{
    auto verb = actionInvokedArgs.Verb();
    if (verb != L"inc" && verb != L"reset" && verb != L"exitCustomization")
    {
        return;
    }

    auto widgetId = actionInvokedArgs.WidgetContext().Id();
    std::optional<CompactWidgetInfo> widgetToUpdate;
    {
        std::scoped_lock lock(RunningWidgetsMutex);
        if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
        {
            auto& localWidgetInfo = iter->second;
            if (verb == L"inc")
            {
                localWidgetInfo.customState++;
            }
            else
            {
                if (verb == L"reset")
                {
                    localWidgetInfo.customState = 0;
                }
                localWidgetInfo.inCustomization = false;
            }
            widgetToUpdate = localWidgetInfo;
        }
    }
    if (widgetToUpdate)
    {
        UpdateWidget(*widgetToUpdate);
    }
}

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.