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 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.
Il codice di esempio in questo articolo è adattato da Esempio Widget Windows App SDK. Per implementare un provider di widget con C#, vedere Implementare un provider di widget in un'app win32 (C#).
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 win32 C++/WinRT
In Visual Studio creare un nuovo progetto. Nella finestra di dialogo Crea un nuovo progetto impostare il filtro del linguaggio su "C++" e il filtro della piattaforma su Windows, quindi selezionare il modello di progetto Applicazione console di Windows (C++/WinRT). Assegnare il nome al nuovo progetto "ExampleWidgetProvider". Quando richiesto, impostare la versione di Windows di destinazione per l'app su versione 1809 o versione successiva.
Aggiungere riferimenti ai pacchetti NuGet libreria di implementazione Windows e 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 Riferimenti 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.
Questo esempio usa anche il pacchetto NuGet libreria di implementazione Windows. In Esplora soluzioni fare clic con il pulsante destro del mouse su Riferimenti e selezionare Gestisci pacchetti NuGet.... Nella gestione pacchetti NuGet, selezionare la scheda Sfoglia e cercare "Microsoft.Windows.ImplementationLibrary". 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 e successivamente 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 dell'inclusione dell'altro.
//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 Aggiungi->Classe. 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 definizione seguente al file WidgetProvider.h sopra la dichiarazione di strutt. 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 <unordered_map>
struct WidgetProvider : winrt::implements<WidgetProvider, winrt::Microsoft::Windows::Widgets::Providers::IWidgetProvider>
{
...
private:
...
static std::unordered_map<winrt::hstring, CompactWidgetInfo> RunningWidgets;
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 widget, vedere Creare un modello di widget con Adaptive Card Designer (Progettazione di Schede adattive).
// WidgetProvider.h
const std::string weatherWidgetTemplate = R"(
{
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.0",
"speak": "<s>The forecast for Seattle January 20 is mostly clear with a High of 51 degrees and Low of 40 degrees</s>",
"backgroundImage": "https://messagecardplayground.azurewebsites.net/assets/Mostly%20Cloudy-Background.jpg",
"body": [
{
"type": "TextBlock",
"text": "Redmond, WA",
"size": "large",
"isSubtle": true,
"wrap": true
},
{
"type": "TextBlock",
"text": "Mon, Nov 4, 2019 6:21 PM",
"spacing": "none",
"wrap": true
},
{
"type": "ColumnSet",
"columns": [
{
"type": "Column",
"width": "auto",
"items": [
{
"type": "Image",
"url": "https://messagecardplayground.azurewebsites.net/assets/Mostly%20Cloudy-Square.png",
"size": "small",
"altText": "Mostly cloudy weather"
}
]
},
{
"type": "Column",
"width": "auto",
"items": [
{
"type": "TextBlock",
"text": "46",
"size": "extraLarge",
"spacing": "none",
"wrap": true
}
]
},
{
"type": "Column",
"width": "stretch",
"items": [
{
"type": "TextBlock",
"text": "°F",
"weight": "bolder",
"spacing": "small",
"wrap": true
}
]
},
{
"type": "Column",
"width": "stretch",
"items": [
{
"type": "TextBlock",
"text": "Hi 50",
"horizontalAlignment": "left",
"wrap": true
},
{
"type": "TextBlock",
"text": "Lo 41",
"horizontalAlignment": "left",
"spacing": "none",
"wrap": true
}
]
}
]
}
]
})";
const std::string countWidgetTemplate = R"(
{
"type": "AdaptiveCard",
"body": [
{
"type": "TextBlock",
"text": "You have clicked the button ${count} times"
},
{
"text":"Rendering Only if Medium",
"type":"TextBlock",
"$when":"${$host.widgetSize==\"medium\"}"
},
{
"text":"Rendering Only if Small",
"type":"TextBlock",
"$when":"${$host.widgetSize==\"small\"}"
},
{
"text":"Rendering Only if Large",
"type":"TextBlock",
"$when":"${$host.widgetSize==\"large\"}"
}
],
"actions": [
{
"type": "Action.Execute",
"title": "Increment",
"verb": "inc"
}
],
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.5"
})";
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 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.
// WidgetProvider.cpp
namespace winrt
{
using namespace Microsoft::Windows::Widgets::Providers;
}
std::unordered_map<winrt::hstring, CompactWidgetInfo> WidgetProvider::RunningWidgets{};
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.cpp
void WidgetProvider::CreateWidget(winrt::WidgetContext widgetContext)
{
auto widgetId = widgetContext.Id();
auto widgetName = widgetContext.DefinitionId();
CompactWidgetInfo runningWidgetInfo{ widgetId, widgetName };
RunningWidgets[widgetId] = runningWidgetInfo;
// Update the widget
UpdateWidget(runningWidgetInfo);
}
DeleteWidget
L'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)
{
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 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.cpp
void WidgetProvider::OnActionInvoked(winrt::WidgetActionInvokedArgs actionInvokedArgs)
{
auto verb = actionInvokedArgs.Verb();
if (verb == L"inc")
{
auto widgetId = actionInvokedArgs.WidgetContext().Id();
// If you need to use some data that was passed in after
// Action was invoked, you can get it from the args:
auto data = actionInvokedArgs.Data();
if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
{
auto& localWidgetInfo = iter->second;
// Increment the count
localWidgetInfo.customState++;
UpdateWidget(localWidgetInfo);
}
}
}
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.cpp
void WidgetProvider::OnWidgetContextChanged(winrt::WidgetContextChangedArgs contextChangedArgs)
{
auto widgetContext = contextChangedArgs.WidgetContext();
auto widgetId = widgetContext.Id();
auto widgetSize = widgetContext.Size();
if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
{
auto localWidgetInfo = iter->second;
UpdateWidget(localWidgetInfo);
}
}
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();
if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
{
auto& localWidgetInfo = iter->second;
localWidgetInfo.isActive = true;
UpdateWidget(localWidgetInfo);
}
}
void WidgetProvider::Deactivate(winrt::hstring widgetId)
{
if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
{
auto& localWidgetInfo = iter->second;
localWidgetInfo.isActive = false;
}
}
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);
// 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);
}
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.
// WidgetProvider.cpp
WidgetProvider::WidgetProvider()
{
auto runningWidgets = winrt::WidgetManager::GetDefault().GetWidgetInfos();
for (auto widgetInfo : runningWidgets )
{
auto widgetContext = widgetInfo.WidgetContext();
auto widgetId = widgetContext.Id();
auto widgetName = widgetContext.DefinitionId();
auto customState = widgetInfo.CustomState();
if (RunningWidgets.find(widgetId) == RunningWidgets.end())
{
CompactWidgetInfo runningWidgetInfo{ widgetId, widgetName };
try
{
// If we had any save state (in this case we might have some state saved for Counting widget)
// convert string to required type if needed.
int count = std::stoi(winrt::to_string(customState));
runningWidgetInfo.customState = count;
}
catch (...)
{
}
RunningWidgets[widgetId] = runningWidgetInfo;
}
}
}
Registrare una class factory che creerà un'istanza di WidgetProvider su richiesta
Aggiungere l'intestazione che definisce la classe WidgetProvider alle inclusioni nella parte superiore del file dell'app main.cpp
. Qui verrà incluso anche mutex.
// main.cpp
...
#include "WidgetProvider.h"
#include <mutex>
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 Strumenti->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 definizione della class factory seguente main.cpp
. Si tratta principalmente di codice boilerplate non specifico per le implementazioni del provider di widget. Nota: CoWaitForMultipleObjects attende che l'evento di arresto venga attivato prima dell'uscita dall'app.
// main.cpp
template <typename T>
struct SingletonClassFactory : winrt::implements<SingletonClassFactory<T>, IClassFactory>
{
STDMETHODIMP CreateInstance(
::IUnknown* outer,
GUID const& iid,
void** result) noexcept final
{
*result = nullptr;
std::unique_lock lock(mutex);
if (outer)
{
return CLASS_E_NOAGGREGATION;
}
if (!instance)
{
instance = winrt::make<WidgetProvider>();
}
return instance.as(iid, result);
}
STDMETHODIMP LockServer(BOOL) noexcept final
{
return S_OK;
}
private:
T instance{ nullptr };
std::mutex mutex;
};
int main()
{
winrt::init_apartment();
wil::unique_com_class_object_cookie widgetProviderFactory;
auto factory = winrt::make<SingletonClassFactory<winrt::Microsoft::Windows::Widgets::Providers::IWidgetProvider>>();
winrt::check_hresult(CoRegisterClassObject(
widget_provider_clsid,
factory.get(),
CLSCTX_LOCAL_SERVER,
REGCLS_MULTIPLEUSE,
widgetProviderFactory.put()));
DWORD index{};
HANDLE events[] = { g_shudownEvent.get() };
winrt::check_hresult(CoWaitForMultipleObjects(CWMO_DISPATCH_CALLS | CWMO_DISPATCH_WINDOW_MESSAGES,
INFINITE,
static_cast<ULONG>(std::size(events)), events, &index));
return 0;
}
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:
- In Visual Studio fare clic su Debug -> Connetti a processo.
- Filtrare i processi e trovare l'applicazione del provider di widget desiderata.
- Collegare il debugger.
Per collegare automaticamente il debugger al processo all'avvio iniziale:
- In Visual Studio fare clic su Debug -> Altre destinazioni di debug -> Debug pacchetto applicazione installato.
- Filtrare i pacchetti e trovare il pacchetto del provider di widget desiderato.
- Selezionarlo e selezionare la casella Non avviare, ma eseguire il debug del codice all'avvio.
- 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 seleziona Proprietà. Passare a Linker -> Sistema e modificare SubSystem da "Console" a "Windows". A tale scopo, è anche possibile aggiungere <Sottosistema>Windows</Sottosistema> a <Collegamento>..</Collegamento> sezione di .vcxproj.
- In main.cpp passare
int main()
aint WINAPI wWinMain(_In_ HINSTANCE /*hInstance*/, _In_opt_ HINSTANCE /*hPrevInstance*/, _In_ PWSTR pCmdLine, _In_ int /*nCmdShow*/)
.
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.
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.
I passaggi seguenti riepilogano il processo di personalizzazione dei widget.
- 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.
- L'utente fa clic sul pulsante Personalizza widget nel menu con i puntini di sospensione.
- Il widget genera l'evento OnCustomizationRequested nel provider di widget per indicare che l'utente ha richiesto l'esperienza di personalizzazione del widget.
- 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.
- 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.
- 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.
- 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">
...
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 strutt. helper consente di tenere traccia dello 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.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 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 da 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();
if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
{
auto& localWidgetInfo = iter->second;
localWidgetInfo.inCustomization = true;
UpdateWidget(localWidgetInfo);
}
}
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, 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.cpp
void WidgetProvider::OnActionInvoked(winrt::WidgetActionInvokedArgs actionInvokedArgs)
{
auto verb = actionInvokedArgs.Verb();
if (verb == L"inc")
{
auto widgetId = actionInvokedArgs.WidgetContext().Id();
// If you need to use some data that was passed in after
// Action was invoked, you can get it from the args:
auto data = actionInvokedArgs.Data();
if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
{
auto& localWidgetInfo = iter->second;
// Increment the count
localWidgetInfo.customState++;
UpdateWidget(localWidgetInfo);
}
}
else if (verb == L"reset")
{
auto widgetId = actionInvokedArgs.WidgetContext().Id();
auto data = actionInvokedArgs.Data();
if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
{
auto& localWidgetInfo = iter->second;
// Reset the count
localWidgetInfo.customState = 0;
localWidgetInfo.inCustomization = false;
UpdateWidget(localWidgetInfo);
}
}
else if (verb == L"exitCustomization")
{
auto widgetId = actionInvokedArgs.WidgetContext().Id();
// If you need to use some data that was passed in after
// Action was invoked, you can get it from the args:
auto data = actionInvokedArgs.Data();
if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
{
auto& localWidgetInfo = iter->second;
// Stop sending the customization template
localWidgetInfo.inCustomization = false;
UpdateWidget(localWidgetInfo);
}
}
}
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.
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.