Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
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.
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++/WinRT, vedere Implementare un provider di widget in un'app win32 (C++/WinRT).
Prerequisiti
- Un dispositivo Windows 11 con la bacheca Widget 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.
Creare una nuova app console C#
In Visual Studio creare un nuovo progetto. Nella finestra di dialogo Creare un nuovo progetto impostare il filtro del linguaggio su "C#" e il filtro della piattaforma su Windows, quindi selezionare il modello di progetto App console. Assegnare il nome al nuovo progetto "ExampleWidgetProvider". Quando richiesto, impostare la versione .NET di destinazione su 8.0.
Quando il progetto viene caricato, in Esplora soluzioni fare clic con il pulsante destro del mouse sul nome del progetto e scegliere Proprietà. Nella pagina General scorrere verso il basso fino a Target OS e selezionare "Windows". In Versione del Sistema operativo di destinazione, selezionare la versione 10.0.19041.0 o successiva.
Per aggiornare il project per supportare .NET 8.0, in Esplora soluzioni fare clic con il pulsante destro del mouse sul nome project e scegliere Edit Project File. All'interno di PropertyGroup, aggiungere l'elemento RuntimeIdentifiers seguente.
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
Si noti che questa procedura dettagliata usa un'app console che visualizza la finestra della console quando il widget viene attivato per consentire un debug facilitato. Quando si è pronti per pubblicare l'app del provider di widget, è possibile convertire l'applicazione console in un'applicazione Windows seguendo i passaggi descritti in Converte l'app console in un'app Windows.
Aggiungere riferimenti alla SDK per app di Windows
Questa procedura dettagliata usa Microsoft. WindowsAppSDK 2.5.1. In Esplora soluzioni fare clic con il pulsante destro del mouse Dependencies 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 da SDK per app di Windows canali di rilascio e quindi fare clic su Installa. Usare la stessa versione del pacchetto nei progetti di app e creazione di pacchetti. Questa è la versione usata dalla procedura dettagliata, non la versione minima che ha introdotto le API del widget.
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. Nel file WidgetProvider.cs generato aggiornare la definizione della classe per indicare che implementa l'interfaccia IWidgetProvider.
// WidgetProvider.cs
internal class WidgetProvider : IWidgetProvider
Prepararsi a tenere traccia dei widget abilitati
Un provider di widget può supportare un singolo widget o più widget. Ogni volta che l'host del widget avvia un'operazione con il provider di widget, passa un ID per identificare il widget associato all'operazione. Ogni widget ha anche un nome associato e un valore di stato che può essere usato per archiviare dati personalizzati. Per questo esempio, si indicherà una semplice struttura helper per archiviare l'ID, il nome e i dati per ogni widget aggiunto. I widget possono anche trovarsi in uno stato attivo, come illustrato nella sezione Attivare e Disattivare riportata di seguito e verrà monitorato questo stato per ogni widget con un valore booleano. Aggiungere la definizione seguente al file WidgetProvider.cs, all'interno dello spazio dei nomi ExampleWidgetProvider, ma all'esterno della definizione della classe WidgetProvider.
// WidgetProvider.cs
public class CompactWidgetInfo
{
public string? widgetId { get; set; }
public string? widgetName { get; set; }
public int customState = 0;
public bool isActive = false;
}
All'interno della definizione della classe WidgetProvider in WidgetProvider.cs, aggiungere un membro per la mappa che manterrà l'elenco dei widget abilitati, usando l'ID widget come chiave per ogni voce.
// WidgetProvider.cs
// Class member of WidgetProvider
public static Dictionary<string, CompactWidgetInfo> RunningWidgets = new Dictionary<string, CompactWidgetInfo>();
Dichiarare le stringhe JSON del modello di widget
Questo esempio dichiara alcune stringhe statiche per definire i modelli JSON per ogni widget. Per praticità, questi modelli vengono archiviati nelle variabili membro della classe WidgetProvider. Se è necessaria una risorsa di archiviazione generale per i modelli, questi possono essere inclusi come parte del pacchetto dell'applicazione: Accesso ai file del pacchetto. Per informazioni sulla creazione del documento JSON del modello di widget, vedere Creare un modello di widget con Schede adattive Designer.
Le app che implementano Windows widget possono personalizzare l'intestazione visualizzata per il widget negli host che supportano la personalizzazione dell'intestazione. Per altre informazioni, vedere Personalizzare l'area dell'intestazione del widget.
Nota
Un widget Web può visualizzare html servito da un URL. Il provider fornisce ancora un payload JSON della scheda adattiva per specificare tale URL e implementa comunque IWidgetProvider. Le API di risorsa e messaggistica sono state introdotte in SDK per app di Windows 1.6.3. Per i requisiti di configurazione e host, vedere Provider di widget Web.
// WidgetProvider.cs
// Class members of WidgetProvider
const string weatherWidgetTemplate = """
{
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.0",
"speak": "<s>The forecast for Seattle January 20 is mostly clear with a High of 51 degrees and Low of 40 degrees</s>",
"backgroundImage": "https://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 string countWidgetTemplate = """
{
"type": "AdaptiveCard",
"body": [
{
"type": "TextBlock",
"text": "You have clicked the button ${count} times"
},
{
"text":"Rendering Only if Small",
"type":"TextBlock",
"$when":"${$host.widgetSize==\"small\"}"
},
{
"text":"Rendering Only if Medium",
"type":"TextBlock",
"$when":"${$host.widgetSize==\"medium\"}"
},
{
"text":"Rendering Only if Large",
"type":"TextBlock",
"$when":"${$host.widgetSize==\"large\"}"
}
],
"actions": [
{
"type": "Action.Execute",
"title": "Increment",
"verb": "inc"
}
],
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.5"
}
""";
Implementare i metodi IWidgetProvider
Nelle sezioni successive verranno implementati i metodi dell'interfaccia IWidgetProvider. Il metodo helper UpdateWidget chiamato in diverse implementazioni di questi metodi verrà illustrato più avanti in questo articolo.
Nota
Gli oggetti passati nei 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.
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.cs
public void CreateWidget(WidgetContext widgetContext)
{
var widgetId = widgetContext.Id; // To save RPC calls
var widgetName = widgetContext.DefinitionId;
CompactWidgetInfo runningWidgetInfo = new CompactWidgetInfo() { widgetId = widgetId, widgetName = widgetName };
RunningWidgets[widgetId] = runningWidgetInfo;
// Update the widget
UpdateWidget(runningWidgetInfo);
}
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.cs
public void DeleteWidget(string widgetId, string customState)
{
RunningWidgets.Remove(widgetId);
if(RunningWidgets.Count == 0)
{
emptyWidgetListEvent.Set();
}
}
Per questo esempio, oltre a rimuovere il widget con il valore specificato dall'elenco dei widget abilitati, viene controllato anche se l'elenco ora è vuoto e, in tal caso, viene impostato un evento che verrà usato in un secondo momento per consentire all'app di uscire quando non sono presenti widget abilitati. All'interno della definizione della classe aggiungere la dichiarazione di ManualResetEvent e una funzione di accesso pubblica.
// WidgetProvider.cs
static ManualResetEvent emptyWidgetListEvent = new ManualResetEvent(false);
public static ManualResetEvent GetEmptyWidgetListEvent()
{
return emptyWidgetListEvent;
}
OnActionInvoked
L'host del widget chiama OnActionInvoked quando l'utente interagisce con un'azione definita nel modello di widget. Per il widget del contatore usato in questo esempio, un'azione è stata dichiarata con un valore verbo "inc" nel modello JSON per il widget. Il codice del provider di widget userà questo valore verbo per determinare l'azione da intraprendere in risposta all'interazione dell'utente.
...
"actions": [
{
"type": "Action.Execute",
"title": "Increment",
"verb": "inc"
}
],
...
Nel metodo OnActionInvoked ottenere il valore del verbo controllando la proprietà Verbo di WidgetActionInvokedArgs passato nel metodo. Se il verbo è "inc", si viene a conoscenza che il conteggio verrà incrementato nello stato personalizzato per il widget. Da WidgetActionInvokedArgs ottenere l'oggetto WidgetContext e quindi la relativa proprietà Id per identificare l'istanza del widget che viene aggiornata. Trovare la voce nella mappa dei widget abilitati con l'ID specificato e quindi aggiornare il valore dello stato personalizzato usato per archiviare il numero di incrementi. Aggiornare infine il contenuto del widget con il nuovo valore con la funzione helper UpdateWidget.
// WidgetProvider.cs
public void OnActionInvoked(WidgetActionInvokedArgs actionInvokedArgs)
{
var verb = actionInvokedArgs.Verb;
if (verb == "inc")
{
var widgetId = actionInvokedArgs.WidgetContext.Id;
// If you need to use some data that was passed in after
// Action was invoked, you can get it from the args:
var data = actionInvokedArgs.Data;
if (RunningWidgets.ContainsKey(widgetId))
{
var localWidgetInfo = RunningWidgets[widgetId];
// Increment the count
localWidgetInfo.customState++;
UpdateWidget(localWidgetInfo);
}
}
}
Per informazioni sulla sintassi Action.Execute per Schede adattive, vedere Action.Execute. Per indicazioni sulla progettazione dell'interazione per i widget, vedere Linee guida per la progettazione dell'interazione per 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.cs
public void OnWidgetContextChanged(WidgetContextChangedArgs contextChangedArgs)
{
var widgetContext = contextChangedArgs.WidgetContext;
var widgetId = widgetContext.Id;
var widgetSize = widgetContext.Size;
if (RunningWidgets.ContainsKey(widgetId))
{
var localWidgetInfo = RunningWidgets[widgetId];
UpdateWidget(localWidgetInfo);
}
}
Attivare e disattivare
Il metodo Attivare viene chiamato per notificare al provider di widget che l'host del widget è attualmente interessato a ricevere contenuto aggiornato dal provider. Ad esempio, potrebbe significare che l'utente sta attualmente visualizzando attivamente l'host del widget. Il metodo Disattivare viene chiamato per notificare al provider di widget che l'host del widget non richiede più aggiornamenti del contenuto. Questi due metodi definiscono una finestra in cui l'host del widget è più interessato a visualizzare il contenuto maggiormente aggiornato. I provider di widget possono inviare aggiornamenti al widget in qualsiasi momento, ad esempio in risposta a una notifica push, ma come per qualsiasi attività in background, è importante bilanciare la fornitura di contenuto aggiornato con problemi di risorse come la durata della batteria.
Attivare e Disattivare vengono chiamate in base al widget. Questo esempio tiene traccia dello stato attivo di ogni widget in strutt. dell'helper CompactWidgetInfo. Nel metodo Attivare viene chiamato il metodo helper UpdateWidget per aggiornare il widget. Si noti che l'intervallo di tempo tra Attivare e Disattivare può essere ridotto, quindi è consigliabile provare a rendere il percorso del codice di aggiornamento del widget il più rapido possibile.
// WidgetProvider.cs
public void Activate(WidgetContext widgetContext)
{
var widgetId = widgetContext.Id;
if (RunningWidgets.ContainsKey(widgetId))
{
var localWidgetInfo = RunningWidgets[widgetId];
localWidgetInfo.isActive = true;
UpdateWidget(localWidgetInfo);
}
}
public void Deactivate(string widgetId)
{
if (RunningWidgets.ContainsKey(widgetId))
{
var localWidgetInfo = RunningWidgets[widgetId];
localWidgetInfo.isActive = false;
}
}
Aggiornare un widget
Definire il metodo helper UpdateWidget per aggiornare un widget abilitato. In questo esempio si controlla il nome del widget nella struttura helper CompactWidgetInfo passato nel metodo e quindi si imposta il modello appropriato e il JSON di dati in base al widget da aggiornare. WidgetUpdateRequestOptions viene inizializzato con il modello, i dati e lo stato personalizzato per il widget da aggiornare. Chiamare WidgetManager::GetDefault per ottenere un'istanza della classe WidgetManager e quindi chiamare UpdateWidget per inviare i dati del widget aggiornati all'host del widget.
// WidgetProvider.cs
void UpdateWidget(CompactWidgetInfo localWidgetInfo)
{
WidgetUpdateRequestOptions updateOptions = new WidgetUpdateRequestOptions(localWidgetInfo.widgetId);
string? templateJson = null;
if (localWidgetInfo.widgetName == "Weather_Widget")
{
templateJson = weatherWidgetTemplate.ToString();
}
else if (localWidgetInfo.widgetName == "Counting_Widget")
{
templateJson = countWidgetTemplate.ToString();
}
string? dataJson = null;
if (localWidgetInfo.widgetName == "Weather_Widget")
{
dataJson = "{}";
}
else if (localWidgetInfo.widgetName == "Counting_Widget")
{
dataJson = "{ \"count\": " + localWidgetInfo.customState.ToString() + " }";
}
updateOptions.Template = templateJson;
updateOptions.Data = dataJson;
updateOptions.IsPlaceholderContent = localWidgetInfo.widgetName == "Weather_Widget";
// You can store some custom state in the widget service that you will be able to query at any time.
updateOptions.CustomState= localWidgetInfo.customState.ToString();
WidgetManager.GetDefault().UpdateWidget(updateOptions);
}
Impostare WidgetUpdateRequestOptions.IsPlaceholderContent su true per il contenuto non ancora personalizzato o utilizzabile. Il widget meteo fittizio visualizza una posizione fissa, quindi questo esempio lo contrassegna come contenuto segnaposto. 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 nella definizione di classe per la classe WidgetProvider.
// WidgetProvider.cs
public WidgetProvider()
{
var runningWidgets = WidgetManager.GetDefault().GetWidgetInfos();
foreach (var widgetInfo in runningWidgets)
{
var widgetContext = widgetInfo.WidgetContext;
var widgetId = widgetContext.Id;
var widgetName = widgetContext.DefinitionId;
var customState = widgetInfo.CustomState;
if (!RunningWidgets.ContainsKey(widgetId))
{
CompactWidgetInfo runningWidgetInfo = new CompactWidgetInfo() { widgetId = widgetId, widgetName = widgetName };
if (!string.IsNullOrEmpty(customState))
{
if (int.TryParse(customState, out int count))
{
runningWidgetInfo.customState = count;
}
else
{
Console.Error.WriteLine($"Invalid saved count for widget {widgetId}; resetting to 0.");
}
}
RunningWidgets[widgetId] = runningWidgetInfo;
}
}
}
Implementare una class factory che creerà un'istanza di WidgetProvider su richiesta
Affinché l'host del widget comunichi con il provider di widget, è necessario chiamare CoRegisterClassObject. Questa funzione richiede di creare un'implementazione di IClassFactory che creerà un oggetto classe per la classe FeedProvider. La class factory verrà implementata in una classe helper autonoma.
In Visual Studio fare clic con il pulsante destro del mouse sul progetto ExampleWidgetProvider in Esplora soluzioni e selezionare Add->Class. Nella finestra di dialogo Aggiungi classe, assegnare alla classe il nome "FactoryHelper" e fare clic su Aggiungi.
Sostituire il contenuto del file FactoryHelper.cs con il codice seguente. Questo codice definisce l'interfaccia IClassFactory e implementa i due metodi, CreateInstance e LockServer. Questo codice è boilerplate tipico per l'implementazione di una class factory e non è specifico per la funzionalità di un provider di widget, ad eccezione del fatto che si indica che l'oggetto classe creato implementa l'interfaccia IWidgetProvider.
// FactoryHelper.cs
using Microsoft.Windows.Widgets.Providers;
using System.Runtime.InteropServices;
using WinRT;
namespace COM
{
static class Guids
{
public const string IClassFactory = "00000001-0000-0000-C000-000000000046";
public const string IUnknown = "00000000-0000-0000-C000-000000000046";
}
///
/// IClassFactory declaration
///
[ComImport, ComVisible(false), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid(COM.Guids.IClassFactory)]
internal interface IClassFactory
{
[PreserveSig]
int CreateInstance(IntPtr pUnkOuter, ref Guid riid, out IntPtr ppvObject);
[PreserveSig]
int LockServer(bool fLock);
}
[ComVisible(true)]
class WidgetProviderFactory<T> : IClassFactory
where T : IWidgetProvider, new()
{
public int CreateInstance(IntPtr pUnkOuter, ref Guid riid, out IntPtr ppvObject)
{
ppvObject = IntPtr.Zero;
if (pUnkOuter != IntPtr.Zero)
{
return CLASS_E_NOAGGREGATION;
}
IntPtr provider = IntPtr.Zero;
try
{
provider = MarshalInspectable<IWidgetProvider>.FromManaged(new T());
return Marshal.QueryInterface(provider, in riid, out ppvObject);
}
catch (Exception exception)
{
Console.Error.WriteLine(exception);
return Marshal.GetHRForException(exception);
}
finally
{
if (provider != IntPtr.Zero)
{
Marshal.Release(provider);
}
}
}
int IClassFactory.LockServer(bool fLock)
{
return 0;
}
private const int CLASS_E_NOAGGREGATION = -2147221232;
}
}
Creare un GUID che rappresenta il CLSID per il provider di widget
Successivamente, è necessario creare un GUID che rappresenta il CLSID che verrà usato per identificare il provider di widget per l'attivazione COM. Lo stesso valore verrà usato anche durante la creazione del pacchetto dell'app. Generare un GUID in Visual Studio passando a Tools->Crea GUID. Selezionare l'opzione del formato del registro di sistema e fare clic su Copia e quindi incollare in un file di testo in modo che sia possibile copiarla in un secondo momento.
Registrare l'oggetto della classe del fornitore di widget con OLE
Nel file Program.cs per l'eseguibile, viene chiamato CoRegisterClassObject per registrare il provider di widget con OLE, in modo che l'host del widget possa interagire con essa. Sostituire il contenuto di Program.cs con il codice seguente. Questo codice importa la funzione CoRegisterClassObject e la chiama, passando l'interfaccia WidgetProviderFactory definita in un passaggio precedente. Assicurarsi di aggiornare la dichiarazione di variabile CLSID_Factory per usare il GUID generato nel passaggio precedente.
// Program.cs
using System.Runtime.InteropServices;
using ComTypes = System.Runtime.InteropServices.ComTypes;
using Microsoft.Windows.Widgets;
using ExampleWidgetProvider;
using COM;
using System;
[DllImport("kernel32.dll")]
static extern IntPtr GetConsoleWindow();
[DllImport("ole32.dll")]
static extern int CoRegisterClassObject(
[MarshalAs(UnmanagedType.LPStruct)] Guid rclsid,
[MarshalAs(UnmanagedType.IUnknown)] object pUnk,
uint dwClsContext,
uint flags,
out uint lpdwRegister);
[DllImport("ole32.dll")] static extern int CoRevokeClassObject(uint dwRegister);
WinRT.ComWrappersSupport.InitializeComWrappers();
Guid CLSID_Factory = Guid.Parse("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX");
var factory = new WidgetProviderFactory<WidgetProvider>();
Marshal.ThrowExceptionForHR(
CoRegisterClassObject(CLSID_Factory, factory, 0x4, 0x1, out uint cookie));
try
{
if (GetConsoleWindow() != IntPtr.Zero)
{
Console.WriteLine("Registered successfully. Press ENTER to exit.");
Console.ReadLine();
}
else
{
WidgetProvider.GetEmptyWidgetListEvent().WaitOne();
}
}
finally
{
int revokeResult = CoRevokeClassObject(cookie);
if (revokeResult < 0)
{
System.Diagnostics.Trace.TraceError($"CoRevokeClassObject failed: 0x{revokeResult:X8}");
}
GC.KeepAlive(factory);
}
Si noti che questo esempio di codice importa la funzione GetConsoleWindow per determinare se l'app è in esecuzione come applicazione console, il comportamento predefinito per questa procedura dettagliata. Se la funzione restituisce un puntatore valido, le informazioni di debug vengono scritte nella console. In caso contrario, l'app viene eseguita come app Windows. In tal caso, aspettiamo l'evento impostato nel metodo DeleteWidget quando l'elenco dei widget abilitati è vuoto e quindi usciamo dall'app. Per informazioni sulla conversione dell'app console di esempio in un'app Windows, vedere Converte l'app console in un'app Windows.
Nota
L'attesa dell'eliminazione dell'ultimo widget è un criterio di durata semplificato per questo esempio, non un requisito di IWidgetProvider. L'host può attivare e rilasciare un provider mentre i widget rimangono bloccati. In un provider di produzione gestire la durata del server COM in modo indipendente, ripristinare lo stato del widget salvato all'avvio e sincronizzare l'accesso allo stato condiviso se i callback vengono eseguiti simultaneamente. Vedere le osservazioni di IWidgetProvider.
Impacchetta l'app di fornitura di widget
Questa procedura dettagliata registra il provider di widget desktop tramite un manifesto del pacchetto MSIX e usa la distribuzione dipendente dal framework del runtime SDK per app di Windows. La procedura seguente crea il pacchetto dell'app e registra il provider. 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 più recente nel passaggio precedente, aggiornare il valore Version in modo che corrisponda ad essa.
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="Counting 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
Selezionare l'architettura corrispondente al computer di sviluppo dall'elenco a discesa Solution Platforms, 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. In Windows 11, apri la scheda Widget con Windows+W, apri la selezione widget e trova Weather Widget e Microsoft Counting Widget. Aggiungerli alla bacheca e testarne le funzionalità. I controlli di selezione e il posizionamento possono variare con gli aggiornamenti di Windows e della Bacheca dei widget.
L'Schede adattive Designer non fornisce valori Windows $host, richiama i callback del tuo provider o impone i limiti di rendering del Widgets Board. Completa questi controlli nella bacheca Widget:
- Verificare che il widget venga visualizzato nella selezione con il nome, l'icona, lo screenshot e la descrizione previsti.
- Fissare il widget e verificare il modello e i dati iniziali.
- Ridimensionarlo a ogni dimensione dichiarata nel manifest del pacchetto. Verificare la presenza di contenuto ritagliato, nascosto o che fuoriesce a ogni dimensione.
- Testare ogni operazione Action.Execute quando
$host.isUserContextAuthenticatedè true. Quando è false, nascondere le azioni non supportate e fornire un fallback appropriato; in tale contesto è supportato solo Action.OpenUrl . - Arrestare e riavviare il processo del provider, quindi verificare che ogni widget fissato venga ripristinato dal proprio stato personalizzato.
- Sganciare il widget e verificare che DeleteWidget sia chiamato.
Per un progetto C# cli-first che usa MSIX a progetto singolo anziché il progetto di creazione di pacchetti separato in questa procedura dettagliata, posizionare Package.appxmanifest nel progetto del provider, impostare EnableMsixTooling su true e usare app di Windows Development CLI per compilare e registrare un pacchetto di sviluppo:
Importante
L'interfaccia della riga di comando app di Windows Development è attualmente disponibile in public preview. Le funzionalità e i comandi possono cambiare prima della versione finale. Controllare l'output winapp run --help della versione installata prima di usare questo comando.
winapp run ExampleWidgetProvider.csproj --configuration Release --arch x64 --no-launch
Il comando registra il pacchetto senza avviare direttamente l'app. Aprire la Bacheca widget per attivare il server COM tramite l'host del widget.
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:
- In Visual Studio fare clic su Debug -> Collega al 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 App Installato.
- Filtrare i pacchetti e trovare il pacchetto del provider di widget desiderato.
- Seleziona l'opzione e spunta la casella che dice Non avviare, ma esegui il debug del codice al momento dell'avvio.
- 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, fare clic con il pulsante destro del mouse sul progetto ExampleWidgetProvider in Esplora soluzioni e selezionare Properties. In Application->General modificare il tipo Output da "Applicazione console" a "applicazione Windows".
Per una modifica del file di progetto, impostare OutputType su WinExe:
<OutputType>WinExe</OutputType>
Eseguire questa operazione prima di convalidare l'esperienza utente finale o pubblicare. Un provider di console può aprire una finestra del terminale ogni volta che l'host del widget attiva il server COM.
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.
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.
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 il modello e i payload di dati per l'esperienza standard del widget.
- L'utente fa clic sul pulsante Personalizza widget nel menu a tre punti.
- 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 "personalizzazione dell'uscita" definita dall'app, il provider di widget reimposta il flag interno per indicare che non è 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. È possibile che l'utente chiuda l'esperienza di personalizzazione senza fare clic sull'azione di personalizzazione dell'uscita definita dall'app. In questo caso, verrà generato IWidgetProviderAnalytics.OnAnalyticsInfoReported e WidgetAnalyticsInfoReportedArgs.AnalyticsJson avrà un interactionKind di "exitCustomization".
- 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">
...
Registrare quando un widget è in modalità di personalizzazione
L'esempio in questo articolo usa la strutt. helper CompactWidgetInfo per registrare lo stato corrente dei widget attivi. Aggiungere il campo inCustomization che verrà usato per tenere traccia del momento in cui l'host del widget prevede di inviare il modello JSON di personalizzazione anziché il modello di widget normale.
// WidgetProvider.cs
public class CompactWidgetInfo
{
public string widgetId { get; set; }
public string widgetName { get; set; }
public int customState = 0;
public bool isActive = false;
public bool inCustomization = false;
}
Implementare IWidgetProvider2
La funzionalità di personalizzazione del widget viene esposta tramite l'interfaccia IWidgetProvider2. Aggiornare la definizione della classe WidgetProvider per implementare questa interfaccia.
// WidgetProvider.cs
internal class WidgetProvider : IWidgetProvider, IWidgetProvider2
Aggiungere un'implementazione per la callback OnCustomizationRequested dell'interfaccia IWidgetProvider2. Questo metodo usa lo stesso modello delle altre callback usate. Ottenere l'ID per il widget da personalizzare da WidgetContext e trovare la strutt. helper CompactWidgetInfo associata a tale widget e impostare il campo inCustomization su true.
// WidgetProvider.cs
public void OnCustomizationRequested(WidgetCustomizationRequestedArgs customizationInvokedArgs)
{
var widgetId = customizationInvokedArgs.WidgetContext.Id;
if (RunningWidgets.ContainsKey(widgetId))
{
var localWidgetInfo = RunningWidgets[widgetId];
localWidgetInfo.inCustomization = true;
UpdateWidget(localWidgetInfo);
}
}
A questo punto, indicare una variabile stringa che definisce il modello JSON per l'interfaccia utente di personalizzazione del widget. Per questo esempio, abbiamo un pulsante "Reimposta contatore" e un pulsante "Esci dalla personalizzazione" che segnalerà al provider di tornare al normale comportamento del widget. Inserire questa definizione accanto alle altre definizioni di modello.
// WidgetProvider.cs
const string countWidgetCustomizationTemplate = @"
{
""type"": ""AdaptiveCard"",
""actions"" : [
{
""type"": ""Action.Execute"",
""title"" : ""Reset counter"",
""verb"": ""reset""
},
{
""type"": ""Action.Execute"",
""title"": ""Exit customization"",
""verb"": ""exitCustomization""
}
],
""$schema"": ""http://adaptivecards.io/schemas/adaptive-card.json"",
""version"": ""1.5""
}";
Inviare un modello di personalizzazione in UpdateWidget
Si aggiornerà quindi il metodo helper UpdateWidget che invia i dati e i modelli JSON visivi all'host del widget. Quando si aggiorna il widget di conteggio, viene inviato il modello di widget regolare o il modello di personalizzazione a seconda del valore del campo inCustomization. Per brevità, il codice non rilevante per la personalizzazione viene omesso in questo frammento di codice.
// WidgetProvider.cs
void UpdateWidget(CompactWidgetInfo localWidgetInfo)
{
...
else if (localWidgetInfo.widgetName == "Counting_Widget")
{
if (!localWidgetInfo.inCustomization)
{
templateJson = countWidgetTemplate.ToString();
}
else
{
templateJson = countWidgetCustomizationTemplate.ToString();
}
}
...
updateOptions.Template = templateJson;
updateOptions.Data = dataJson;
// You can store some custom state in the widget service that you will be able to query at any time.
updateOptions.CustomState = localWidgetInfo.customState.ToString();
WidgetManager.GetDefault().UpdateWidget(updateOptions);
}
Rispondere alle azioni di personalizzazione
Quando gli utenti interagiscono con gli input nel modello di personalizzazione, 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. L'azione "Reimposta contatore" reimposta customState su 0 ed esce dalla personalizzazione. L'azione "Esci personalizzazione" esce dalla personalizzazione senza reimpostare il contatore. Impostare entrambi inCustomization su false in modo che UpdateWidget invii il modello di widget normale.
// WidgetProvider.cs
public void OnActionInvoked(WidgetActionInvokedArgs actionInvokedArgs)
{
var verb = actionInvokedArgs.Verb;
if (verb == "inc")
{
var widgetId = actionInvokedArgs.WidgetContext.Id;
// If you need to use some data that was passed in after
// Action was invoked, you can get it from the args:
var data = actionInvokedArgs.Data;
if (RunningWidgets.ContainsKey(widgetId))
{
var localWidgetInfo = RunningWidgets[widgetId];
// Increment the count
localWidgetInfo.customState++;
UpdateWidget(localWidgetInfo);
}
}
else if (verb == "reset")
{
var widgetId = actionInvokedArgs.WidgetContext.Id;
var data = actionInvokedArgs.Data;
if (RunningWidgets.ContainsKey(widgetId))
{
var localWidgetInfo = RunningWidgets[widgetId];
// Reset the count
localWidgetInfo.customState = 0;
localWidgetInfo.inCustomization = false;
UpdateWidget(localWidgetInfo);
}
}
else if (verb == "exitCustomization")
{
var widgetId = actionInvokedArgs.WidgetContext.Id;
var data = actionInvokedArgs.Data;
if (RunningWidgets.ContainsKey(widgetId))
{
var localWidgetInfo = RunningWidgets[widgetId];
// Stop sending the customization template
localWidgetInfo.inCustomization = false;
UpdateWidget(localWidgetInfo);
}
}
}
Ora, quando si distribuisce il widget, verrà visualizzato il pulsante Personalizza widget nel menu con i puntini di sospensione. Facendo clic sul pulsante Personalizza verrà visualizzato il modello di personalizzazione.
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.