Delen via


Een widgetprovider implementeren in een C# Windows-app

Dit artikel begeleidt u bij het maken van een eenvoudige widgetprovider waarmee de IWidgetProvider-interface wordt geïmplementeerd. De methoden van deze interface worden aangeroepen door de widgethost om de gegevens aan te vragen die een widget definiëren of om de widgetprovider te laten reageren op een gebruikersactie op een widget. Widgetproviders kunnen één widget of meerdere widgets ondersteunen. In dit voorbeeld definiëren we twee verschillende widgets. Een widget is een gesimuleerde weerwidget die enkele van de opmaakopties illustreert die door het Adaptive Cards-framework worden geboden. De tweede widget demonstreert gebruikersacties en de aangepaste widgetstatusfunctie door een teller te onderhouden die wordt verhoogd wanneer de gebruiker op een knop klikt die wordt weergegeven in de widget.

Een schermopname van een eenvoudige weerwidget. De widget toont een aantal weergerelateerde afbeeldingen en gegevens, evenals een aantal diagnostische tekst die illustreert dat de sjabloon voor de middelgrote widget wordt weergegeven.

Een schermopname van een eenvoudige telwidget. De widget toont een tekenreeks met de numerieke waarde die moet worden verhoogd en een knop met het label 'Verhogen', evenals enkele diagnostische teksten die illustreren dat het sjabloon voor de kleine widget wordt weergegeven.

Deze voorbeeldcode in dit artikel is aangepast aan het windows App SDK-widgetsvoorbeeld. Zie Een widgetprovider implementeren in een win32-app (C++/WinRT)om een widgetprovider te implementeren met C++/WinRT.

Vereiste voorwaarden

  • Op uw apparaat moet de ontwikkelaarsmodus zijn ingeschakeld. Zie Instellingen voor ontwikkelaars voor meer informatie.
  • Visual Studio 2022 of hoger met de Universal Windows Platform-ontwikkeling workload. Zorg ervoor dat u het onderdeel voor C++ (v143) toevoegt vanuit de optionele vervolgkeuzelijst.

Een nieuwe C#-console-app maken

Maak in Visual Studio een nieuw project. Stel in het dialoogvenster Een nieuw project maken het taalfilter in op 'C#' en het platformfilter op Windows en selecteer vervolgens de console-app-projectsjabloon. Geef het nieuwe project de naam ExampleWidgetProvider. Stel de doelversie van .NET in op 8.0 wanneer u hierom wordt gevraagd.

Wanneer het project wordt geladen, klikt u in Solution Explorer met de rechtermuisknop op de projectnaam en selecteert u Eigenschappen. Op de pagina Algemeen, schuif omlaag naar Doelbesturingssysteem en selecteer "Windows". Selecteer onder Doelversie van het besturingssysteem versie 10.0.19041.0 of hoger.

Als u uw project wilt bijwerken ter ondersteuning van .NET 8.0, klikt u in Solution Explorer met de rechtermuisknop op de projectnaam en selecteert u Projectbestand bewerken. Voeg in PropertyGroup het volgende Element RuntimeIdentifiers toe.

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

Deze procedure maakt gebruik van een console-app die het consolevenster weergeeft wanneer de widget wordt geactiveerd om eenvoudige foutopsporing in te schakelen. Wanneer u klaar bent om uw widget provider app te publiceren, kunt u de consoletoepassing omzetten naar een Windows-toepassing door de stappen in Uw console-app omzetten naar een Windows-appte volgen.

Verwijzingen toevoegen aan de Windows App SDK

In dit voorbeeld wordt het meest recente stabiele NuGet-pakket voor Windows App SDK gebruikt. Klik in Solution Explorer met de rechtermuisknop op Afhankelijkheden en selecteer NuGet-pakketten beheren.... Selecteer in NuGet Package Manager het tabblad Bladeren en zoek naar Microsoft.WindowsAppSDK. Selecteer de meest recente stabiele versie in de vervolgkeuzelijst Versie en klik vervolgens op Installeren.

Een WidgetProvider-klasse toevoegen om widgetbewerkingen te verwerken

Klik in Visual Studio met de rechtermuisknop op het ExampleWidgetProvider-project in de Solution Explorer en selecteer Toevoegen->Klasse. Geef in het dialoogvenster Klasse toevoegen de klasse WidgetProvider een naam en klik op Toevoegen. Werk in het gegenereerde WidgetProvider.cs-bestand de klassedefinitie bij om aan te geven dat de IWidgetProvider-interface wordt geïmplementeerd.

// WidgetProvider.cs
internal class WidgetProvider : IWidgetProvider

Maak gereed voor het bijhouden van ingeschakelde widgets

Een widgetprovider kan één widget of meerdere widgets ondersteunen. Wanneer de widgethost een bewerking start met de widgetprovider, wordt er een id doorgegeven om de widget te identificeren die aan de bewerking is gekoppeld. Elke widget heeft ook een gekoppelde naam en een statuswaarde die kan worden gebruikt om aangepaste gegevens op te slaan. In dit voorbeeld declareren we een eenvoudige helperstructuur voor het opslaan van de id, naam en gegevens voor elke vastgemaakte widget. Widgets kunnen zich ook in een actieve status bevinden, die wordt besproken in de sectie Activeren en Deactiveren hieronder. Deze status wordt voor elke widget bijgehouden met een Booleaanse waarde. Voeg de volgende definitie toe aan het WidgetProvider.cs-bestand in de naamruimte ExampleWidgetProvider , maar buiten de widgetproviderklassedefinitie .

// WidgetProvider.cs

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

}

Voeg in de widgetproviderklassedefinitie in WidgetProvider.cs een lid toe voor de kaart die de lijst met ingeschakelde widgets onderhoudt, met behulp van de widget-id als de sleutel voor elke vermelding.

// WidgetProvider.cs

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

JSON-tekenreeksen voor widgetsjabloon declareren

In dit voorbeeld worden enkele statische tekenreeksen gede declareerd om de JSON-sjablonen voor elke widget te definiëren. Voor het gemak worden deze sjablonen opgeslagen in de lidvariabelen van de widgetproviderklasse . Als u een algemene opslag voor de sjablonen nodig hebt, kunnen deze worden opgenomen als onderdeel van het toepassingspakket: Toegang tot pakketbestanden. Zie Een widgetsjabloon maken met adaptieve kaartontwerper voor meer informatie over het maken van het JSON-document voor widgetsjablonen.

In de nieuwste release kunnen apps die Windows-widgets implementeren, de koptekst aanpassen die wordt weergegeven voor hun widget in het widgetsbord, waardoor de standaardpresentatie wordt overschreven. Zie Het koptekstgebied van de widget aanpassen voor meer informatie.

Opmerking

In de nieuwste release kunnen apps die Windows-widgets implementeren ervoor kiezen om de widgetinhoud te vullen met HTML die wordt geleverd vanaf een opgegeven URL in plaats van inhoud op te geven in de schema-indeling van adaptieve kaart in de JSON-nettolading die van de provider aan het widgetsbord wordt doorgegeven. Widget providers moeten nog steeds een JSON-payload van een Adaptive Card opgeven, dus de implementatiestappen in deze instructie zijn geschikt voor webwidgets. Zie Webwidgetproviders voor meer informatie.

// WidgetProvider.cs

// Class members of WidgetProvider
        const string weatherWidgetTemplate = """
{
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "type": "AdaptiveCard",
    "version": "1.0",
    "speak": "<s>The forecast for Seattle January 20 is mostly clear with a High of 51 degrees and Low of 40 degrees</s>",
    "backgroundImage": "https://messagecardplayground.azurewebsites.net/assets/Mostly%20Cloudy-Background.jpg",
    "body": [
        {
            "type": "TextBlock",
            "text": "Redmond, WA",
            "size": "large",
            "isSubtle": true,
            "wrap": true
        },
        {
            "type": "TextBlock",
            "text": "Mon, Nov 4, 2019 6:21 PM",
            "spacing": "none",
            "wrap": true
        },
        {
            "type": "ColumnSet",
            "columns": [
                {
                    "type": "Column",
                    "width": "auto",
                    "items": [
                        {
                            "type": "Image",
                            "url": "https://messagecardplayground.azurewebsites.net/assets/Mostly%20Cloudy-Square.png",
                            "size": "small",
                            "altText": "Mostly cloudy weather"
                        }
                    ]
                },
                {
                    "type": "Column",
                    "width": "auto",
                    "items": [
                        {
                            "type": "TextBlock",
                            "text": "46",
                            "size": "extraLarge",
                            "spacing": "none",
                            "wrap": true
                        }
                    ]
                },
                {
                    "type": "Column",
                    "width": "stretch",
                    "items": [
                        {
                            "type": "TextBlock",
                            "text": "°F",
                            "weight": "bolder",
                            "spacing": "small",
                            "wrap": true
                        }
                    ]
                },
                {
                    "type": "Column",
                    "width": "stretch",
                    "items": [
                        {
                            "type": "TextBlock",
                            "text": "Hi 50",
                            "horizontalAlignment": "left",
                            "wrap": true
                        },
                        {
                            "type": "TextBlock",
                            "text": "Lo 41",
                            "horizontalAlignment": "left",
                            "spacing": "none",
                            "wrap": true
                        }
                    ]
                }
            ]
        }
    ]
}
""";

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

De IWidgetProvider-methoden implementeren

In de volgende secties implementeren we de methoden van de IWidgetProvider-interface . De helpermethode UpdateWidget die in verschillende van deze methodeimplementaties wordt aangeroepen, wordt verderop in dit artikel weergegeven.

Opmerking

Objecten die worden doorgegeven aan de callback-methoden van de IWidgetProvider-interface , zijn alleen gegarandeerd geldig in de callback. U mag geen verwijzingen naar deze objecten opslaan omdat hun gedrag buiten de context van de callback niet is gedefinieerd.

CreateWidget

De widget host roept CreateWidget aan wanneer de gebruiker een van de widgets van uw app in de widget host heeft geplaatst. Eerst haalt deze methode de id en naam van de gekoppelde widget op en voegt een nieuw exemplaar van onze helperstructuur, CompactWidgetInfo, toe aan de verzameling ingeschakelde widgets. Vervolgens verzenden we de eerste sjabloon en gegevens voor de widget, die is ingekapseld in de Helper-methode 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);
}

Verwijder Widget

De widgethost roept DeleteWidget aan wanneer de gebruiker een van de widgets van je app heeft losgemaakt van de widgethost. Wanneer dit gebeurt, verwijderen we de bijbehorende widget uit onze lijst met ingeschakelde widgets, zodat we geen verdere updates voor die widget verzenden.

// WidgetProvider.cs

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

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

In dit voorbeeld wordt naast het verwijderen van de widget met de opgegeven widget uit de lijst met ingeschakelde widgets ook gecontroleerd of de lijst nu leeg is. Als dat het geval is, stellen we een gebeurtenis in die later wordt gebruikt om de app toe te staan af te sluiten wanneer er geen widgets zijn ingeschakeld. Voeg binnen uw klassedefinitie de declaratie van de ManualResetEvent en een publieke toegangsfunctie toe.

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

public static ManualResetEvent GetEmptyWidgetListEvent()
{
    return emptyWidgetListEvent;
}

OnActionInvoked

De widgethost roept OnActionInvoked aan wanneer de gebruiker communiceert met een actie die u hebt gedefinieerd in uw widgetsjabloon. Voor de tellerwidget die in dit voorbeeld wordt gebruikt, is een actie gedeclareerd met een werkwoord en een waarde van "inc" in de JSON-sjabloon voor de widget. De code van de widgetprovider gebruikt deze werkwoordwaarde om te bepalen welke actie moet worden ondernomen als reactie op de interactie van de gebruiker.

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

Haal in de methode OnActionInvoked de waarde van het werkwoord op door de Verb-eigenschap van de WidgetActionInvokedArgs, die aan de methode is doorgegeven, te controleren. Als het werkwoord 'inc' is, weten we dat we het aantal in de aangepaste status voor de widget gaan verhogen. Haal in de WidgetActionInvokedArgshet WidgetContext-object en vervolgens de WidgetId om de ID van de widget die wordt bijgewerkt op te halen. Zoek de vermelding in de kaart met ingeschakelde widgets met de opgegeven id en werk vervolgens de aangepaste statuswaarde bij die wordt gebruikt om het aantal stappen op te slaan. Werk ten slotte de widgetinhoud bij met de nieuwe waarde met de helperfunctie 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);
            }
        }
}

Zie Action.Execute voor meer informatie over de syntaxis action.execute voor adaptieve kaarten. Zie de richtlijnen voor het ontwerpen van widgetinteractie voor aanwijzingen over het ontwerpen van interactie voor widgets.

OnWidgetContextChanged

In de huidige release wordt OnWidgetContextChanged alleen aangeroepen wanneer de gebruiker de grootte van een vastgemaakte widget wijzigt. U kunt ervoor kiezen om een andere JSON-sjabloon/-gegevens te retourneren aan de widgethost, afhankelijk van de grootte die wordt aangevraagd. U kunt de sjabloon-JSON ook ontwerpen ter ondersteuning van alle beschikbare grootten met behulp van voorwaardelijke rendering op basis van de waarde van host.widgetSize. Als u geen nieuwe sjabloon of gegevens hoeft te verzenden om rekening te houden met de groottewijziging, kunt u de OnWidgetContextChanged gebruiken voor telemetriedoeleinden.

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

Activeren en deactiveren

De methode Activate wordt aangeroepen om de widgetprovider op de hoogte te stellen dat de widgethost momenteel geïnteresseerd is in het ontvangen van bijgewerkte inhoud van de provider. Het kan bijvoorbeeld betekenen dat de gebruiker momenteel actief de widgethost bekijkt. De methode Deactiveren wordt aangeroepen om de widgetprovider op de hoogte te stellen dat de widgethost geen inhoudsupdates meer aanvraagt. Met deze twee methoden wordt een venster gedefinieerd waarin de widget-host vooral geïnteresseerd is in het tonen van de meest actuele up-to-gegevensinhoud. Widgetproviders kunnen op elk gewenst moment updates naar de widget verzenden, zoals in reactie op een pushmelding, maar net als bij elke achtergrondtaak is het belangrijk om de balans op te lossen met up-to-datuminhoud met resourceproblemen zoals de levensduur van de batterij.

Activeren en deactiveren worden per widget aangeroepen. In dit voorbeeld wordt de actieve status van elke widget bijgehouden in de Helper-struct CompactWidgetInfo . In de methode Activate roepen we de Helper-methode UpdateWidget aan om onze widget bij te werken. Houd er rekening mee dat het tijdvenster tussen Activeren en Deactiveren klein kan zijn, dus het is raadzaam dat u het codepad voor het bijwerken van de widget zo snel mogelijk probeert te maken.

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

Een widget bijwerken

Definieer de helpermethode UpdateWidget om een ingeschakelde widget bij te werken. In dit voorbeeld controleren we de naam van de widget in de Helper-struct CompactWidgetInfo die is doorgegeven aan de methode en stellen we vervolgens de juiste sjabloon en gegevens-JSON in op basis van welke widget wordt bijgewerkt. Een WidgetUpdateRequestOptions wordt geïnitialiseerd met de sjabloon, gegevens en aangepaste status voor de widget die wordt bijgewerkt. WidgetManager aanroepen::GetDefault om een exemplaar van de WidgetManager-klasse op te halen en vervolgens UpdateWidget aan te roepen om de bijgewerkte widgetgegevens naar de widgethost te verzenden.

// WidgetProvider.cs

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

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

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

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

Initialiseer de lijst met ingeschakelde widgets bij het opstarten

Wanneer onze widgetprovider voor het eerst wordt geïnitialiseerd, is het een goed idee om WidgetManager te vragen of er actieve widgets zijn die momenteel door onze provider worden geleverd. Het helpt bij het herstellen van de app naar de vorige status in het geval van het opnieuw opstarten van de computer of het vastlopen van de provider. Roep WidgetManager.GetDefault aan om de standaardwidgetbeheerderinstantie voor de app op te halen. Roep vervolgens GetWidgetInfos aan, die een matrix met WidgetInfo-objecten retourneert. Kopieer de widget-id's, namen en aangepaste status in de helper struct CompactWidgetInfo en sla deze op in de variabele RunningWidgets-lid . Plak de volgende code in de klassedefinitie voor de widgetproviderklasse .

// 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 };
            try
            {
                // If we had any save state (in this case we might have some state saved for Counting widget)
                // convert string to required type if needed.
                int count = Convert.ToInt32(customState.ToString());
                runningWidgetInfo.customState = count;
            }
            catch
            {

            }
            RunningWidgets[widgetId] = runningWidgetInfo;
        }
    }
}

Een klassefabriek implementeren die WidgetProvider op aanvraag instantieert

Om de widgethost te laten communiceren met onze widgetprovider, moeten we CoRegisterClassObject aanroepen. Voor deze functie moeten we een implementatie maken van de IClassFactory waarmee een klasseobject wordt gemaakt voor onze WidgetProvider-klasse . We zullen onze klassefaciliteit in een zelfstandige hulpklasse implementeren.

Klik in Visual Studio met de rechtermuisknop op het ExampleWidgetProvider-project in de Solution Explorer en selecteer Toevoegen->Klasse. Geef in het dialoogvenster Klasse toevoegen de klasse De naam FactoryHelper en klik op Toevoegen.

Vervang de inhoud van het bestand FactoryHelper.cs door de volgende code. Deze code definieert de IClassFactory-interface en implementeert het twee methoden, CreateInstance en LockServer. Deze code is typisch standaard voor het implementeren van een klassefactory en is niet specifiek voor de functionaliteit van een widgetprovider, behalve dat we aangeven dat het klasseobject dat wordt gemaakt, de interface IWidgetProvider implementeert.

// FactoryHelper.cs

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

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

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

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

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

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

            return 0;
        }

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

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

    }
}

Een GUID maken die de CLSID voor uw widgetprovider vertegenwoordigt

Vervolgens moet u een GUID maken die de CLSID vertegenwoordigt die wordt gebruikt om uw widgetprovider te identificeren voor COM-activering. Dezelfde waarde wordt ook gebruikt bij het verpakken van uw app. Genereer een GUID in Visual Studio door naar Hulpmiddelen te gaan en>GUID makente selecteren. Selecteer de optie registerindeling en klik op Kopiëren en plak deze in een tekstbestand, zodat u deze later kunt kopiëren.

Het klasseobject van de widgetprovider registreren bij OLE

In het Program.cs-bestand voor ons uitvoerbare bestand roepen we CoRegisterClassObject aan om onze widgetprovider te registreren bij OLE, zodat de widgethost ermee kan communiceren. Vervang de inhoud van Program.cs door de volgende code. Met deze code wordt de functie CoRegisterClassObject geïmporteerd en aangeroepen, waarbij de WidgetProviderFactory-interface wordt doorgegeven die we in een vorige stap hebben gedefinieerd. Zorg ervoor dat u de declaratie van de CLSID_Factory variabele bijwerkt om de GUID te gebruiken die u in de vorige stap hebt gegenereerd.

// Program.cs

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

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

[DllImport("ole32.dll")]

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

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

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

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

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

    CoRevokeClassObject(cookie);
}

In dit codevoorbeeld wordt de functie GetConsoleWindow geïmporteerd om te bepalen of de app wordt uitgevoerd als consoletoepassing, het standaardgedrag voor dit scenario. Als de functie een geldige aanwijzer retourneert, schrijven we foutopsporingsgegevens naar de console. Anders wordt de app uitgevoerd als een Windows-app. In dat geval wachten we op de gebeurtenis die we hebben ingesteld in de Methode DeleteWidget wanneer de lijst met ingeschakelde widgets leeg is en de app wordt afgesloten. Zie Uw console-app converteren naar een Windows-app voor meer informatie over het converteren van de voorbeeldconsole-app naar een Windows-app.

Verpak uw widgetprovider-app

In de huidige release kunnen alleen verpakte apps worden geregistreerd als widgetproviders. Met de volgende stappen wordt u begeleid bij het verpakken van uw app en het bijwerken van het app-manifest om uw app te registreren bij het besturingssysteem als widgetprovider.

Een MSIX-pakketproject maken

Klik in Solution Explorer met de rechtermuisknop op uw oplossing en selecteer Add-New> Project.... Selecteer in het dialoogvenster Een nieuw project toevoegen de sjabloon Windows Application Packaging Project en klik op Volgende. Stel de projectnaam in op "ExampleWidgetProviderPackage" en klik op Creëren. Wanneer u hierom wordt gevraagd, stelt u de doelversie in op versie 1809 of hoger en klikt u op OK. Klik vervolgens met de rechtermuisknop op het project ExampleWidgetProviderPackage en selecteer >een projectverwijzingtoevoegen. Selecteer het project ExampleWidgetProvider en klik op OK.

Windows App SDK-pakketreferentie toevoegen aan het pakketproject

U moet een verwijzing toevoegen naar het NuGet-pakket van de Windows App SDK aan het MSIX-verpakkingsproject. Dubbelklik in Solution Explorer op het project ExampleWidgetProviderPackage om het bestand ExampleWidgetProviderPackage.wapproj te openen. Voeg de volgende XML toe in het Project-element .

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

Opmerking

Zorg ervoor dat de versie die is opgegeven in het element PackageReference overeenkomt met de meest recente stabiele versie waarnaar u in de vorige stap hebt verwezen.

Als de juiste versie van de Windows App SDK al op de computer is geïnstalleerd en u de SDK-runtime niet in uw pakket wilt bundelen, kunt u de pakketafhankelijkheid opgeven in het bestand Package.appxmanifest voor het project 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>
...

Het pakketmanifest bijwerken

Klik in Solution Explorer met de rechtermuisknop op het Package.appxmanifest bestand en selecteer Code weergeven om het XML-manifestbestand te openen. Vervolgens moet u enkele naamruimtedeclaraties toevoegen voor de app-pakketextensies die we gaan gebruiken. Voeg de volgende naamruimtedefinities toe aan het element Package op het hoogste niveau.

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

Maak in het toepassingselement een nieuw leeg element met de naam Extensies. Zorg ervoor dat dit komt na de afsluitende tag voor uap:VisualElements.

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

    </Extensions>
</Application>

De eerste extensie die we moeten toevoegen, is de ComServer-extensie . Hiermee wordt het toegangspunt van het uitvoerbare bestand geregistreerd bij het besturingssysteem. Deze extensie is het pakket-app-equivalent van het registreren van een COM-server door een registersleutel in te stellen en is niet specifiek voor widgetproviders. Voeg het volgende com:Extension-element toe als onderliggend element van het element Extensies . Wijzig de GUID in het kenmerk -id van het element com:Class naar de GUID die u hebt gegenereerd in een vorige stap.

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

Voeg vervolgens de extensie toe die de app registreert als widgetprovider. Plak het element uap3:Extension in het volgende codefragment als een onderliggend element van het element Extensions . Vervang het kenmerk ClassId van het COM-element door de GUID die u in de vorige stappen hebt gebruikt.

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

Zie de widgetprovider pakketmanifest XML-indeling voor gedetailleerde beschrijvingen en indelingen voor al deze elementen.

Pictogrammen en andere afbeeldingen toevoegen aan uw verpakkingsproject

Klik in Solution Explorer-met de rechtermuisknop op uw ExampleWidgetProviderPackage en selecteer Add->New Folder. Noem deze map ProviderAssets, omdat dit is wat is gebruikt in de Package.appxmanifest vorige stap. Hier slaan we onze pictogrammen en schermopnamen op voor onze widgets. Nadat u de gewenste pictogrammen en schermopnamen hebt toegevoegd, moet u ervoor zorgen dat de namen van de afbeeldingen overeenkomen met wat er na Path=ProviderAssets\ komt in uw specifieke bestand, anders zullen de widgets niet worden getoond in de widgethost.

Zie Integreren met de widgetkiezer voor informatie over de ontwerpvereisten voor schermafbeeldingen en de naamconventies voor gelokaliseerde schermopnamen.

Uw widgetprovider testen

Zorg ervoor dat u de architectuur hebt geselecteerd die overeenkomt met uw ontwikkelcomputer in de vervolgkeuzelijst Oplossingsplatformen , bijvoorbeeld 'x64'. Klik in Solution Explorer met de rechtermuisknop op uw oplossing en selecteer Oplossing bouwen. Zodra dit is gebeurd, klikt u met de rechtermuisknop op uw ExampleWidgetProviderPackage en selecteert u Implementeren. In de huidige release is de enige ondersteunde widgethost het widgetsbord. Als u de widgets wilt zien, moet u het widgetsbord openen en widgets in de rechterbovenhoek selecteren. Scroll naar de onderkant van de beschikbare widgets-lijst en u zou de mockup Weather Widget en Microsoft Counting Widget moeten zien die in deze tutorial zijn gemaakt. Klik op de widgets om ze aan het widgetsbord vast te maken en hun functionaliteit te testen.

Foutopsporing voor uw widgetprovider

Nadat u uw widgets hebt vastgemaakt, start het widgetplatform uw widgetprovidertoepassing om relevante informatie over de widget te ontvangen en te verzenden. Als u fouten wilt opsporen in de actieve widget, kunt u een foutopsporingsprogramma toevoegen aan de toepassing van de actieve widgetprovider of u kunt Visual Studio instellen om automatisch de foutopsporing van het widgetproviderproces te starten zodra deze is gestart.

Om u aan het lopende proces te koppelen:

  1. Klik in Visual Studio op Debuggen -> Koppelen aan proces.
  2. Filter de processen en zoek de gewenste widgetprovidertoepassing.
  3. Voeg het foutopsporingsprogramma toe.

Als u het foutopsporingsprogramma automatisch wilt koppelen aan het proces wanneer het in eerste instantie wordt gestart:

  1. Klik in Visual Studio op Foutopsporing -> Andere foutopsporingsdoelen -> Gedebugged app-pakket.
  2. Filter de pakketten en zoek het gewenste widgetproviderpakket.
  3. Selecteer het en vink het vakje aan met de tekst 'Niet starten, maar debug mijn code wanneer het start.'
  4. Klik op ombij te voegen.

Uw console-app converteren naar een Windows-app

Als u de console-app wilt converteren die in dit scenario is gemaakt naar een Windows-app, klikt u met de rechtermuisknop op het project ExampleWidgetProvider in Solution Explorer en selecteert u Eigenschappen. Wijzig onder Toepassings-algemeen> het uitvoertype van 'Consoletoepassing' in 'Windows-toepassing'.

Een schermopname van de projecteigenschappen van de C#-widgetprovider met het uitvoertype ingesteld op Windows-toepassing

Uw widget publiceren

Nadat u uw widget hebt ontwikkeld en getest, kunt u uw app publiceren in de Microsoft Store, zodat gebruikers uw widgets op hun apparaten kunnen installeren. Zie Uw app publiceren in de Microsoft Store voor stapsgewijze instructies voor het publiceren van een app.

De widget winkelcollectie

Nadat uw app is gepubliceerd in de Microsoft Store, kunt u aanvragen dat uw app wordt opgenomen in de Store-verzameling widgets waarmee gebruikers apps met Windows Widgets kunnen detecteren. Als u uw aanvraag wilt indienen, raadpleegt u Uw widgetgegevens indienen voor toevoeging aan dewinkelcollectie.

Schermopname van de Microsoft Store met de widgetsverzameling waarmee gebruikers apps kunnen detecteren die Windows Widgets bevatten.

Het aanpassen van widgets implementeren

Vanaf Windows App SDK 1.4 kunnen widgets gebruikersaanpassing ondersteunen. Wanneer deze functie is geïmplementeerd, wordt er een Aanpassen widget optie toegevoegd aan het beletseltekenmenu boven de Losmaken widget optie.

Een schermopname van een widget waarin het aanpassingsdialoogvenster wordt weergegeven.

De volgende stappen geven een overzicht van het proces voor het aanpassen van widget's.

  1. In normale werking reageert de widgetprovider op aanvragen van de widgethost met de sjabloon en gegevenspayloads voor de reguliere widgetervaring.
  2. De gebruiker klikt op de knop Widget aanpassen in het menu met het beletselteken.
  3. De widget verhoogt de gebeurtenis OnCustomizationRequested op de widgetprovider om aan te geven dat de gebruiker de aanpassingservaring van de widget heeft aangevraagd.
  4. De widgetprovider stelt een interne vlag in om aan te geven dat de widget zich in de aanpassingsmodus bevindt. In de aanpassingsmodus verzendt de widgetprovider de JSON-sjablonen voor de gebruikersinterface voor widgetaanpassing in plaats van de reguliere widgetgebruikersinterface.
  5. In de aanpassingsmodus ontvangt de widgetprovider OnActionInvoked-gebeurtenissen wanneer de gebruiker communiceert met de aanpassingsgebruikersinterface en de interne configuratie en het gedrag ervan aanpast op basis van de acties van de gebruiker.
  6. Wanneer de actie die is gekoppeld aan de gebeurtenis OnActionInvoked de door de app gedefinieerde actie 'aanpassing afsluiten' is, stelt de widgetprovider de interne vlag opnieuw in om aan te geven dat deze zich niet meer in de aanpassingsmodus bevindt en het verzenden van de JSON-sjablonen voor visuals en gegevens hervat voor de normale widgetervaring, waarbij de wijzigingen worden weergegeven die tijdens de aanpassing zijn aangevraagd. Het is mogelijk dat de gebruiker de aanpassingservaring sluit zonder op de door de app gedefinieerde aanpassingsactie voor afsluiten te klikken. In dit geval wordt de IWidgetProviderAnalytics.OnAnalyticsInfoReported opgeroepen en heeft de WidgetAnalyticsInfoReportedArgs.AnalyticsJson een interactionKind van 'exitCustomization'.
  7. De widgetprovider behoudt de aanpassingsopties op schijf of de cloud, zodat de wijzigingen behouden blijven tussen aanroepen van de widgetprovider.

Opmerking

Er is een bekende fout met het Windows Widget Board, voor widgets die zijn gebouwd met de Windows App SDK, waardoor het beletseltekenmenu niet meer reageert nadat de aanpassingskaart is weergegeven.

In typische scenario's voor het aanpassen van widget kiest de gebruiker welke gegevens worden weergegeven in de widget of past de visuele presentatie van de widget aan. Ter vereenvoudiging voegt het voorbeeld in deze sectie aanpassingsgedrag toe waarmee de gebruiker de teller van de telwidget die in de vorige stappen is geïmplementeerd, opnieuw kan instellen.

Opmerking

Widgetaanpassing wordt alleen ondersteund in Windows App SDK 1.4 en hoger. Zorg ervoor dat u de verwijzingen in uw project bijwerkt naar de nieuwste versie van het Nuget-pakket.

Het pakketmanifest bijwerken om aanpassingsondersteuning te declareren

Als u de widgethost wilt laten weten dat de widget aanpassing ondersteunt, voegt u het kenmerk IsCustomizable toe aan het element Definitie voor de widget en stelt u deze in op true.

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

Bijhouden wanneer een widget zich in de aanpassingsmodus bevindt

In het voorbeeld in dit artikel wordt de helper struct CompactWidgetInfo gebruikt om de huidige status van onze actieve widgets bij te houden. Voeg het veld inCustomisatie toe, dat wordt gebruikt om bij te houden wanneer de widgethost verwacht dat we onze JSON-sjabloon voor aanpassing verzenden in plaats van de reguliere widgetsjabloon.

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

IWidgetProvider2 implementeren

De widgetaanpassingsfunctie wordt weergegeven via de IWidgetProvider2-interface . Werk de widgetproviderklassedefinitie bij om deze interface te implementeren.

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

Voeg een implementatie toe voor de callback onCustomizationRequested van de IWidgetProvider2-interface . Deze methode maakt gebruik van hetzelfde patroon als de andere callbacks die we hebben gebruikt. We krijgen de id voor de widget die moet worden aangepast vanuit widgetContext en zoeken de Helper-struct compactWidgetInfo die aan die widget is gekoppeld en stel het veld inCustomisatie in op 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);
    }
}

Declareer nu een tekenreeksvariabele die de JSON-sjabloon definieert voor de ui voor het aanpassen van de widget. In dit voorbeeld hebben we de knop 'Teller opnieuw instellen' en de knop 'Aanpassing afsluiten,' waarmee onze provider wordt gesignaleerd om terug te keren naar het normale widgetgedrag. Plaats deze definitie naast de andere sjabloondefinities.

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

Aanpassingssjabloon verzenden in UpdateWidget

Vervolgens werken we de Helpermethode UpdateWidget bij waarmee onze gegevens en visuele JSON-sjablonen naar de widgethost worden verzonden. Wanneer we de widget tellen bijwerken, verzenden we de reguliere widgetsjabloon of de aanpassingssjabloon, afhankelijk van de waarde van het veld InCustomisatie . Voor kortheid wordt code die niet relevant is voor aanpassing weggelaten in dit codefragment.

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

Reageren op aanpassingsacties

Wanneer gebruikers interactie hebben met invoer in onze aanpassingssjabloon, wordt dezelfde OnActionInvoked-handler aanroepen als wanneer de gebruiker communiceert met de reguliere widgetervaring. Ter ondersteuning van aanpassingen zoeken we naar de werkwoorden 'reset' en 'exitCustomization' in onze JSON-sjabloon voor aanpassing. Als de actie voor de knop 'Teller opnieuw instellen' is, stellen we de teller in het veld customState van onze helper-struct opnieuw in op 0. Als de actie voor de knop Aangepaste modus afsluiten is, stellen we het inCustomization-veld in op onwaar, zodat wanneer we UpdateWidget aanroepen, onze helpermethode de reguliere JSON-sjablonen verzendt en niet de aanpassingssjabloon.

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

Wanneer u uw widget implementeert, ziet u nu de knop Widget aanpassen in het menu met weglatingstekens. Als u op de knop Aanpassen klikt, wordt uw aanpassingssjabloon weergegeven.

Een schermopname van de ui voor het aanpassen van widgets.

Klik op de knop Teller opnieuw instellen om de teller opnieuw in te stellen op 0. Klik op de knop Aanpassing afsluiten om terug te keren naar het normale gedrag van uw widget.