Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Tento článek vás provede vytvořením jednoduchého zprostředkovatele widgetu, který implementuje rozhraní IWidgetProvider. Metody tohoto rozhraní jsou vyvolány hostitelem widgetu, aby požádal o data, která definují widget, nebo aby poskytovatel widgetu reagoval na akci uživatele na widgetu. Poskytovatelé widgetů můžou podporovat jeden widget nebo více widgetů. V tomto příkladu definujeme dva různé widgety. Jedním widgetem je widget pro napodobení počasí, který znázorňuje některé možnosti formátování poskytované architekturou Adaptivní karty. Druhý widget předvede akce uživatele a vlastní funkci stavu widgetu tím, že zachová čítač, který se zvýší, kdykoli uživatel klikne na tlačítko zobrazené na widgetu.
Tento ukázkový kód v tomto článku je upraven z ukázky widgetů Windows App SDK. Pokud chcete implementovat poskytovatele widgetu pomocí C++/WinRT, přečtěte si téma Implementace zprostředkovatele widgetu v aplikaci win32 (C++/WinRT).
Požadavky
- Zařízení s Windows 11, které je k dispozici pro testování, s panelem widgetů.
- Vaše zařízení musí mít povolený vývojářský režim. Další informace najdete v tématu Nastavení pro vývojáře.
- Visual Studio 2022 nebo novější s úlohou Vývoj aplikací WinUI.
Vytvoření nové konzolové aplikace jazyka C#
V Visual Studio vytvořte nový projekt. V dialogovém okně Vytvořit nový projekt nastavte filtr jazyka na C# a filtr platformy na Windows a pak vyberte šablonu projektu konzolové aplikace. Pojmenujte nový projekt ExampleWidgetProvider. Po zobrazení výzvy nastavte cílovou .NET verzi na 8.0.
Po načtení projektu klikněte v Průzkumník řešení pravým tlačítkem myši na název projektu a vyberte Properties. Na stránce General přejděte dolů na Target OS a vyberte Windows. V části Cílová verze operačního systému vyberte verzi 10.0.19041.0 nebo novější.
Pokud chcete aktualizovat project na podporu .NET 8.0, klikněte v Průzkumník řešení pravým tlačítkem myši na název project a vyberte Edit Project Soubor. Uvnitř PropertyGroup přidejte následující element RuntimeIdentifiers.
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
Všimněte si, že tento průvodce používá konzolovou aplikaci, která při aktivaci widgetu otevře konzolové okno, aby umožnila snadné ladění. Až budete připravení publikovat aplikaci zprostředkovatele widgetů, můžete konzolovou aplikaci převést na aplikaci Windows pomocí kroků v Konvertovat konzolovou aplikaci na aplikaci Windows.
Přidání odkazů na Windows App SDK
Tento návod používá Microsoft.WindowsAppSDK 2.5.1. V Průzkumník řešení klikněte pravým tlačítkem na Dependencies a vyberte Spravované balíčky NuGet... . Ve správci balíčků NuGet vyberte kartu Browse a vyhledejte "Microsoft". WindowsAppSDK" Vyberte verzi 2.5.1 nebo novější podporovanou stabilní verzi z kanálů verze Windows App SDK a klikněte na nainstalovat. Použijte stejnou verzi balíčku v projektech aplikace a vytváření balíčků. Jedná se o verzi použitou v návodu, nikoli minimální verzi, která zavedla rozhraní API widgetu.
Přidání třídy WidgetProvider pro zpracování operací widgetu
V Visual Studio klikněte pravým tlačítkem na projekt ExampleWidgetProvider v Průzkumník řešení a vyberte Add->Class. V dialogovém okně Přidat třídu pojmenujte třídu WidgetProvider a klikněte na Přidat. Ve vygenerovaném WidgetProvider.cs souboru aktualizujte definici třídy tak, aby indikovala, že implementuje IWidgetProvider rozhraní.
// WidgetProvider.cs
internal class WidgetProvider : IWidgetProvider
Příprava ke sledování povolených widgetů
Poskytovatel widgetů může podporovat jeden widget nebo více widgetů. Kdykoli hostitel widgetu zahájí operaci s poskytovatelem widgetu, předá ID k identifikaci widgetu přidruženého k operaci. Každý widget má také přidružený název a hodnotu stavu, kterou lze použít k ukládání vlastních dat. V tomto příkladu deklarujeme jednoduchou pomocnou strukturu pro uložení ID, názvu a dat pro každý připnutý widget. Widgety můžou být také v aktivním stavu, který je popsán v části Aktivovat a deaktivovat níže. Tento stav budeme sledovat pro každý widget s logickou hodnotou. Do souboru WidgetProvider.cs přidejte následující definici do oboru názvů ExampleWidgetProvider, ale mimo definici třídy WidgetProvider.
// WidgetProvider.cs
public class CompactWidgetInfo
{
public string? widgetId { get; set; }
public string? widgetName { get; set; }
public int customState = 0;
public bool isActive = false;
}
V definici třídy WidgetProvider v WidgetProvider.cs přidejte člena pro mapu, která bude udržovat seznam povolených widgetů pomocí ID widgetu jako klíč pro každou položku.
// WidgetProvider.cs
// Class member of WidgetProvider
public static Dictionary<string, CompactWidgetInfo> RunningWidgets = new Dictionary<string, CompactWidgetInfo>();
Deklarace řetězců JSON šablony widgetu
Tento příklad deklaruje některé statické řetězce, které definují šablony JSON pro každý widget. Pro usnadnění práce jsou tyto šablony uloženy v proměnných členů třídy WidgetProvider. Pokud potřebujete obecné úložiště pro šablony – dají se zahrnout jako součást balíčku aplikace: přístup k souborům balíčků. Informace o vytvoření dokumentu JSON šablony widgetu najdete v tématu Vytvoření šablony widgetu pomocí návrháře Adaptivní karty.
Aplikace, které implementují widgety Windows, můžou přizpůsobit záhlaví zobrazené pro jejich widgety v hostitelích, které podporují přizpůsobení hlaviček. Další informace naleznete v tématu Přizpůsobení oblasti záhlaví widgetu.
Poznámka:
Webový widget může zobrazit HTML obsah načítaný z adresy URL. Poskytovatel stále poskytuje datovou část JSON adaptivní karty k určení této adresy URL a stále implementuje IWidgetProvider. Rozhraní API pro prostředky a zasílání zpráv byla zavedena v Windows App SDK 1.6.3. Požadavky na nastavení a hostování najdete v tématu Poskytovatelé webových widgetů.
// 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"
}
""";
Implementace metod IWidgetProvider
V následujících několika částech budeme implementovat metody IWidgetProvider rozhraní. Pomocná metoda UpdateWidget, která je volána v několika z těchto implementací metod, se zobrazí dále v tomto článku.
Poznámka:
Objekty předané do metod zpětného volání rozhraní IWidgetProvider jsou zaručeně platné pouze v rámci samotného zpětného volání. Odkazy na tyto objekty byste neměli ukládat, protože jejich chování mimo kontext zpětného volání není definováno.
VytvořitWidget
Hostitel widgetu volá CreateWidget, když uživatel připnul některý z widgetů vaší aplikace do hostitele widgetu. Nejprve tato metoda získá ID a název přidružené widgetu a přidá novou instanci naší pomocné struktury, CompactWidgetInfo, do kolekce povolených widgetů. Dále odešleme počáteční šablonu a data widgetu, která je zapouzdřená v pomocné metodě 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);
}
Odstranit Widget
Hostitel widgetu volá DeleteWidget, když uživatel odepnul některý z widgetů vaší aplikace z widgetového hostitele. Když k tomu dojde, odebereme přidružený widget ze seznamu povolených widgetů, abychom pro tento widget neposílali žádné další aktualizace.
// WidgetProvider.cs
public void DeleteWidget(string widgetId, string customState)
{
RunningWidgets.Remove(widgetId);
if(RunningWidgets.Count == 0)
{
emptyWidgetListEvent.Set();
}
}
V tomto příkladu kromě odebrání widgetu se zadaným ze seznamu povolených widgetů také zkontrolujeme, jestli je seznam prázdný, a pokud ano, nastavíme událost, která se později použije k tomu, aby se aplikace ukončila, když nejsou povolené widgety. Do definice vaší třídy přidejte deklaraci ManualResetEvent a veřejnou přístupovou funkci.
// WidgetProvider.cs
static ManualResetEvent emptyWidgetListEvent = new ManualResetEvent(false);
public static ManualResetEvent GetEmptyWidgetListEvent()
{
return emptyWidgetListEvent;
}
OnActionInvoked
Hostitel widgetu volá OnActionInvolat, když uživatel pracuje s akcí, kterou jste definovali v šabloně widgetu. Pro widget čítače použitého v tomto příkladu byla akce deklarována se slovesem a hodnotou "inc" v JSON šabloně pro widget. Kód zprostředkovatele widgetu použije tuto hodnotu příkazu k určení akce, která se má provést v reakci na interakci uživatele.
...
"actions": [
{
"type": "Action.Execute",
"title": "Increment",
"verb": "inc"
}
],
...
V metodě OnActionInvoked získejte hodnotu slovesa kontrolou vlastnosti Verb objektu WidgetActionInvokedArgs předaného do metody. Pokud je sloveso "inc", víme, že zvýšíme počet v přizpůsobeném stavu widgetu. Z WidgetActionInvokedArgs získejte objekt WidgetContext a potom pomocí jeho vlastnosti Id identifikujte instanci widgetu, která se aktualizuje. Vyhledejte položku v mapě povolených widgetů se zadaným ID a pak aktualizujte hodnotu vlastního stavu, která se používá k uložení počtu přírůstků. Nakonec aktualizujte obsah widgetu novou hodnotou pomocí UpdateWidget pomocné funkce.
// 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);
}
}
}
Informace o syntaxi Action.Execute pro Adaptivní karty najdete v tématu Action.Execute. Pokyny k návrhu interakcí pro widgety najdete v článku Pokyny k návrhu interakce widgetů.
OnWidgetContextChanged
V aktuální verzi se OnWidgetContextChanged volá pouze v případě, že uživatel změní velikost připnutého widgetu. V závislosti na požadované velikosti můžete hostiteli widgetu vrátit jinou šablonu NEBO data JSON. Můžete také navrhnout JSON šablony tak, aby podporovala všechny dostupné velikosti pomocí podmíněného vykreslování na základě hodnoty host.widgetSize. Pokud pro změnu velikosti nepotřebujete odesílat novou šablonu nebo data, můžete pro účely telemetrie použít OnWidgetContextChanged.
// 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);
}
}
Aktivace a deaktivace
Volá se metoda Activate, která informuje poskytovatele widgetu, že hostitel widgetu má v současné době zájem o příjem aktualizovaného obsahu od poskytovatele. Může to například znamenat, že uživatel aktuálně aktivně zobrazuje hostitele widgetu. Metoda Deactivate je volána k upozornění poskytovatele widgetu, že hostitel widgetu již nevyžaduje aktualizace obsahu. Tyto dvě metody definují okno, ve kterém má hostitel widgetu největší zájem o zobrazení nejaktuálnějšího obsahu up-to. Poskytovatelé widgetů můžou kdykoliv odesílat aktualizace do widgetu, například v reakci na push oznámení, ale stejně jako u jakéhokoli úkolu na pozadí, je důležité vyvážit up-to–date obsah se zdroji, jako je životnost baterie.
Aktivovat a Deaktivovat jsou volány pro každý widget zvlášť. Tento příklad sleduje aktivní stav jednotlivých widgetů v CompactWidgetInfo pomocné struktury. V metodě Activate voláme metodu pomocníka UpdateWidget pro aktualizaci widgetu. Všimněte si, že časové období mezi Aktivovat a Deaktivovat může být malé, proto doporučujeme, abyste se pokusili co nejrychleji aktualizovat cestu kódu widgetu.
// 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;
}
}
Aktualizace widgetu
Definujte metodu pomocné rutiny UpdateWidget pro aktualizaci povoleného widgetu. V tomto příkladu zkontrolujeme název widgetu v CompactWidgetInfo pomocné struktury předané metodě a pak nastavíme odpovídající šablonu a data JSON na základě toho, který widget se aktualizuje. Možnost žádosti o aktualizaci widgetu je inicializována pomocí šablony, dat a vlastního stavu pro aktualizovaný widget. Zavolejte WidgetManager::GetDefault, abyste získali instanci třídy WidgetManager, a potom zavolejte UpdateWidget, abyste odeslali aktualizovaná data widgetu hostiteli widgetu.
// 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);
}
Nastavte WidgetUpdateRequestOptions.IsPlaceholderContent na true u obsahu, který ještě není přizpůsobený nebo použitelný. Ukázkový widget počasí zobrazuje pevné umístění, takže ho tento příklad označuje jako zástupný obsah. Hostitel může upřednostnit widgety s přizpůsobeným obsahem před tímto ukázkovým widgetem počasí. Nastavte vlastnost na false, když zadáváte přizpůsobený obsah. Tato vlastnost byla přidána v Windows App SDK 1.6.6.
Inicializace seznamu povolených widgetů při spuštění
Když je náš poskytovatel widgetu poprvé inicializován, je vhodné se zeptat WidgetManager pokud existují nějaké spuštěné widgety, které náš poskytovatel aktuálně obsluhuje. Pomůže obnovit aplikaci do předchozího stavu v případě restartování počítače nebo chybového ukončení poskytovatele. Chcete-li získat výchozí instanci správce widgetů pro aplikaci, volejte WidgetManager.GetDefault. Potom zavolejte GetWidgetInfos, které vrátí pole objektů WidgetInfo. Zkopírujte ID widgetu, názvy a vlastní stav do pomocné struktury CompactWidgetInfo a uložte ho do RunningWidgets členské proměnné. Vložte následující kód do definice třídy 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;
}
}
}
Implementace objektu pro vytváření tříd, která vytvoří instanci WidgetProvideru na žádost
Aby hostitel widgetu komunikoval s naším poskytovatelem widgetů, musíme volat CoRegisterClassObject. Tato funkce vyžaduje, abychom vytvořili implementaci IClassFactory , která vytvoří objekt třídy pro naši WidgetProvider třídy. Náš objekt pro vytváření tříd implementujeme do samostatné pomocné třídy.
V Visual Studio klikněte pravým tlačítkem na projekt ExampleWidgetProvider v Průzkumník řešení a vyberte Add->Class. V dialogovém okně Přidat třídu pojmenujte třídu FactoryHelper a klikněte na Přidat.
Obsah souboru FactoryHelper.cs nahraďte následujícím kódem. Tento kód definuje IClassFactory rozhraní a implementuje jeho dvě metody, CreateInstance a LockServer. Tento kód je typický často používaný pro implementaci objektu pro vytváření tříd a není specifický pro funkce zprostředkovatele widgetu s tím rozdílem, že označujeme, že objekt třídy, který se vytváří, implementuje IWidgetProvider rozhraní.
// 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;
}
}
Vytvořte identifikátor GUID, který představuje CLSID pro vašeho poskytovatele widgetu
Dále je potřeba vytvořit identifikátor GUID představující CLSID , který se použije k identifikaci vašeho poskytovatele widgetu pro aktivaci COM. Stejná hodnota se použije také při balení aplikace. Vygenerujte identifikátor GUID ve Visual Studio tak, že přejdete na Nástroje->Vytvořit GUID. Vyberte možnost formát registru a klikněte na Kopírovat a vložte ho do textového souboru, abyste ho mohli později zkopírovat.
Registrace objektu třídy zprostředkovatele widgetu v OLE
V souboru Program.cs pro náš spustitelný soubor zavoláme CoRegisterClassObject k registraci poskytovatele widgetu v OLE, aby s ním hostitel widgetu mohl pracovat. Obsah Program.cs nahraďte následujícím kódem. Tento kód importuje funkci CoRegisterClassObject a volá ji a předává WidgetProviderFactory rozhraní, které jsme definovali v předchozím kroku. Nezapomeňte aktualizovat deklaraci proměnné CLSID_Factory tak, aby používala identifikátor GUID, který jste vygenerovali v předchozím kroku.
// 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);
}
Všimněte si, že tento příklad kódu naimportuje funkci GetConsoleWindow , která určí, jestli je aplikace spuštěná jako konzolová aplikace, výchozí chování pro tento názorný postup. Pokud funkce vrátí platný ukazatel, zapíšeme do konzoly informace o ladění. Jinak je aplikace spuštěná jako Windows aplikace. V takovém případě počkáme na událost, kterou jsme nastavili v metodě DeleteWidget , když je seznam povolených widgetů prázdný a pak aplikaci ukončíme. Informace o převodu ukázkové konzolové aplikace na aplikaci Windows najdete v tématu Povertování konzolové aplikace na aplikaci Windows.
Poznámka:
Čekání na odstranění posledního widgetu je zjednodušená zásada životnosti pro tento příklad, nikoli požadavek IWidgetProvider. Hostitel může aktivovat a odpojit poskytovatele, zatímco widgety zůstanou připnuté. V produkčním poskytovateli spravujte životnost serveru COM nezávisle, obnovte uložený stav widgetu při spuštění a synchronizujte přístup ke sdílenému stavu, pokud se zpětné volání spouští souběžně. Podívejte se na poznámky IWidgetProvider.
Zabalte svou aplikaci poskytovatele widgetů
Tento názorný postup zaregistruje poskytovatele desktopových widgetů prostřednictvím manifestu balíčku MSIX a používá nasazení modulu runtime Windows App SDK závislé na frameworku. Následující kroky zabalí vaši aplikaci a zaregistrují poskytovatele aplikace. Pokud místo toho zvolíte samostatné nasazení, postupujte podle pokynů k nasazení samostatných aplikací, včetně nastavení obou projektů, když použijete Windows Application Packaging Project.
Vytvoření projektu balení MSIX
V Průzkumník řešení klikněte pravým tlačítkem na řešení a vyberte >. V dialogovém okně Přidat nový projekt vyberte šablonu "Windows Aplikační balíček" a klikněte na Další. Nastavte název projektu na ExampleWidgetProviderPackage a klikněte na Vytvořit. Po zobrazení výzvy nastavte cílovou verzi na verzi 1809 nebo novější a klikněte na OK. Potom klikněte pravým tlačítkem myši na project ExampleWidgetProviderPackage a vyberte Add->Project reference. Vyberte projekt ExampleWidgetProvider a klikněte na tlačítko OK.
Přidání odkazu na balíček Windows App SDK do projektu balení
Do projektu balení MSIX je potřeba přidat odkaz na balíček nuget Windows App SDK. V Průzkumník řešení poklikejte na projekt ExampleWidgetProviderPackage a otevřete soubor ExampleWidgetProviderPackage.wapproj. Do elementu Project přidejte následující kód XML.
<!--ExampleWidgetProviderPackage.wapproj-->
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" Version="2.5.1">
<IncludeAssets>build</IncludeAssets>
</PackageReference>
</ItemGroup>
Poznámka:
Fragment kódu používá verzi 2.5.1, nikoli minimální verzi, která zavedla rozhraní API widgetů. Pokud jste v předchozím kroku vybrali novější podporovanou stabilní verzi, aktualizujte hodnotu verze tak, aby odpovídala této verzi.
Aktualizace manifestu balíčku
V Průzkumník řešení klikněte pravým tlačítkem na soubor Package.appxmanifest a vyberte View Code a otevřete soubor XML manifestu. Dále je potřeba přidat deklarace oboru názvů pro některá rozšíření balíčků aplikací, které budeme používat. Do elementu nejvyšší úrovně Package přidejte následující definice oboru názvů.
<!-- Package.appmanifest -->
<Package
...
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
Uvnitř elementu Application vytvořte nový prázdný prvek s názvem Extensions. Dbejte na to, aby to přišlo po uzavírací značce uap:VisualElements.
<!-- Package.appxmanifest -->
<Application>
...
<Extensions>
</Extensions>
</Application>
První rozšíření, které potřebujeme přidat, je rozšíření ComServer. Tím se zaregistruje vstupní bod spustitelného souboru v operačním systému. Toto rozšíření je ekvivalentem zabalené aplikace pro registraci serveru COM nastavením klíče registru a není specifické pro poskytovatele widgetů. Přidejte následující prvek com:Extension jako podřízený prvek Extensions. Změňte GUID v atributu Id elementu com:Class na GUID, který jste vygenerovali v předchozím kroku.
<!-- 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>
Dále přidejte rozšíření, které zaregistruje aplikaci jako poskytovatele widgetu. V následujícím úseku kódu vložte prvek uap3:Extension jako podřízený prvek elementu Extensions. Nezapomeňte nahradit atribut ClassId elementu COM identifikátorem GUID, který jste použili v předchozích krocích.
<!-- 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>
Podrobné popisy a informace o formátu pro všechny tyto prvky naleznete v tématu Formát XML manifestu balíčku poskytovatele widgetu.
Přidání ikon a dalších obrázků do projektu balení
V Průzkumník řešení klikněte pravým tlačítkem na ExampleWidgetProviderPackage a vyberte Add->New Folder. Pojmenujte tuto složku ProviderAssets, protože se používá v Package.appxmanifest z předchozího kroku. Tady uložíme naše ikony a snímky obrazovek pro naše widgety. Po přidání požadovaných ikon a snímků obrazovky se ujistěte, že názvy obrázků odpovídají tomu, co následuje po Path=ProviderAssets\ ve vašem Package.appxmanifest nebo se widgety nezobrazí v hostiteli widgetu.
Informace o požadavcích na návrh obrázků snímků obrazovky a konvencích vytváření názvů pro lokalizované snímky obrazovky najdete v tématu Integrace s nástrojem pro výběr widgetu.
Testování poskytovatele widgetů
V rozevíracím seznamu Platformy řešení vyberte architekturu, která odpovídá vašemu vývojovému počítači, například x64. V Průzkumník řešení klikněte pravým tlačítkem myši na řešení a vyberte Sestavit řešení, potom klikněte pravým tlačítkem myši na ExampleWidgetProviderPackage a vyberte Nasadit. Ve Windows 11 otevřete panel Widgety pomocí Windows+W, otevřete nástroj pro výběr widgetu a najděte Weather Widget a Microsoft Counting Widget. Přidejte je na nástěnku a otestujte jejich funkce. Ovládací prvky pro výběr a jejich umístění se můžou lišit s aktualizacemi Windows a Panelu widgetů.
Návrhář Adaptivní karty neposkytuje hodnoty pro Windows $host, nevyvolává zpětné volání zprostředkovatele ani nevynucuje vykreslené hranice panelu widgetů. Dokončete tyto kontroly na Nástěnce widgetů:
- Ověřte, že se widget zobrazí ve výběru s očekávaným názvem, ikonou, snímkem obrazovky a popisem.
- Připněte widget a ověřte jeho počáteční šablonu a data.
- Změňte jeho velikost do všech velikostí uvedených v manifestu balíčku. Zkontrolujte, zda obsah není oříznutý, skrytý nebo nepřetéká v každé velikosti.
- Otestujte každou operaci Action.Execute, pokud je
$host.isUserContextAuthenticatedtrue. Pokud je hodnota false, skryjte nepodporované akce a poskytněte odpovídající náhradní akci; v tomto kontextu se podporuje pouze Action.OpenUrl. - Zastavte a restartujte proces poskytovatele a ověřte, že se každý připnutý widget obnoví z jeho přizpůsobeného stavu.
- Odepněte widget a potvrďte, že se volá DeleteWidget.
Pro projekt CLI-first v C#, který používá MSIX s jedním projektem místo samostatného projektu balení v tomto názorném postupu, umístěte Package.appxmanifest do projektu zprostředkovatele, nastavte EnableMsixTooling na true a použijte aplikace pro Windows Development CLI k sestavení a registraci vývojového balíčku:
Important
Rozhraní příkazového řádku pro vývoj aplikací Windows je aktuálně ve verzi veřejné zkušební verze. Funkce a příkazy se můžou před finální verzí změnit. Před použitím tohoto příkazu zkontrolujte výstup příkazu winapp run --help nainstalované verze.
winapp run ExampleWidgetProvider.csproj --configuration Release --arch x64 --no-launch
Příkaz zaregistruje balíček bez přímého spuštění aplikace. Otevřete panel Widgety, aby se server COM aktivoval prostřednictvím hostitele widgetů.
Ladění poskytovatele widgetů
Po připnutí widgetů spustí platforma widgetů aplikaci poskytovatele widgetů, aby mohla přijímat a odesílat relevantní informace o widgetu. Chcete-li ladit spuštěný widget, můžete buď připojit ladicí program ke spuštěné aplikaci zprostředkovatele widgetu, nebo můžete nastavit Visual Studio, aby se po spuštění automaticky spustil proces zprostředkovatele widgetu.
Chcete-li se připojit ke spuštěném procesu:
- V Visual Studio klikněte na Debug -> Připojit k procesu.
- Vyfiltrujte procesy a najděte požadovanou aplikaci zprostředkovatele widgetu.
- Připojte ladicí program.
Pokud chcete automaticky připojit ladicí program k procesu při počátečním spuštění:
- V Visual Studio klikněte na Debug -> Další cíle ladění -> Ladění nainstalovaného balíčku aplikace.
- Vyfiltrujte balíčky a najděte požadovaný balíček zprostředkovatele widgetu.
- Vyberte jej a zaškrtněte políčko s názvem Nespouštět, ale při spuštění ladit můj kód.
- Klepněte na tlačítko Připojit.
Převod konzolové aplikace na Windows aplikaci
Pokud chcete převést konzolovou aplikaci vytvořenou v tomto názorném postupu na aplikaci Windows, klikněte pravým tlačítkem na projekt ExampleWidgetProvider v Průzkumník řešení a vyberte Properties. V části Application->General změňte typ Output z konzolové aplikace na Windows Aplikace.
Při úpravě souboru projektu nastavte OutputType na WinExe:
<OutputType>WinExe</OutputType>
Před ověřením finální uživatelské zkušenosti nebo publikováním to udělejte. Poskytovatel konzoly může otevřít okno terminálu pokaždé, když hostitel widgetu aktivuje svůj server COM.
Publikování widgetu
Po vývoji a otestování widgetu můžete aplikaci publikovat na Microsoft Store, aby si uživatelé nainstalovali widgety do svých zařízení. Podrobné pokyny k publikování aplikace najdete v tématu Publikujte svou aplikaci v Microsoft Storu.
Kolekce obchodu s widgety
Po publikování vaší aplikace na Microsoft Store můžete požádat o její zahrnutí do kolekce widgetů v obchodě, která pomáhá uživatelům objevovat aplikace s funkcí Windows Widgety. Pokud chcete odeslat svou žádost, přečtěte si jak odeslat informace o widgetu pro přidání do kolekce obchodu.
Implementace přizpůsobení widgetu
Od Windows App SDK 1.4 můžou widgety podporovat přizpůsobení uživatelů. Při implementaci této funkce se do nabídky se třemi tečkami nad možností Odepnout widget přidá možnost Přizpůsobit widget.
Následující kroky shrnují proces přizpůsobení widgetu.
- V normálním provozu poskytovatel widgetu reaguje na požadavky z hostitele widgetu pomocí šablony a datových částí pro běžné prostředí widgetu.
- Uživatel klikne na tlačítko Přizpůsobit widget v nabídce s trojtečkou.
- Widget vyvolá událost OnCustomizationRequested poskytovateli widgetu, aby naznačil, že uživatel požádal o přizpůsobení widgetu.
- Poskytovatel widgetu nastaví interní příznak, který označuje, že widget je v režimu přizpůsobení. V režimu přizpůsobení odesílá poskytovatel widgetů šablony JSON pro uživatelské rozhraní pro přizpůsobení widgetu místo běžného uživatelského rozhraní widgetu.
- V režimu přizpůsobení obdrží poskytovatel widgetu OnActionInvoked události, jakmile uživatel pracuje s uživatelským rozhraním pro přizpůsobení a upravuje jeho interní konfiguraci a chování na základě akcí uživatele.
- Když je akce spojená s událostí OnActionInvoked akcí "ukončení přizpůsobení" definovanou aplikací, poskytovatel widgetu resetuje svůj interní příznak, aby označil, že už není v režimu přizpůsobení, a pokračuje v odesílání šablon vizuálního zobrazení a dat v JSON formátu pro běžné prostředí widgetu, což odráží změny požadované během přizpůsobení. Uživatel může zavřít prostředí pro přizpůsobení, aniž by klikl na aplikací určenou akci pro ukončení přizpůsobení. V tomto případě bude vyvolána IWidgetProviderAnalytics.OnAnalyticsInfoReported a WidgetAnalyticsInfoReportedArgs.AnalyticsJson bude mít druhInterakce "exitCustomization".
- Poskytovatel widgetu uchovává možnosti přizpůsobení disku nebo cloudu, aby se změny zachovaly mezi vyvoláním poskytovatele widgetu.
Poznámka:
U widgetů vytvořených pomocí Windows App SDK existuje známá Windows chyba, která způsobí, že nabídka se třemi tečkami přestane reagovat po zobrazení karty přizpůsobení.
V typických scénářích přizpůsobení widgetu uživatel zvolí, jaká data se ve widgetu zobrazí, nebo upraví vizuální prezentaci widgetu. Pro zjednodušení, příklad v této části zavede funkci, která uživateli umožní resetovat čítač počítacího widgetu, který byl implementován v předchozích krocích.
Poznámka:
Přizpůsobení widgetu vyžaduje Windows App SDK 1.4 nebo novější. Tento návod používá Microsoft. WindowsAppSDK verze 2.5.1.
Aktualizace manifestu balíčku pro deklaraci podpory přizpůsobení
Pokud chcete hostiteli widgetu dát vědět, že widget podporuje přizpůsobení, přidejte atribut IsCustomizable do elementu Definition widgetu a nastavte ho na true.
...
<Definition Id="Counting_Widget"
DisplayName="Microsoft Counting Widget"
Description="CONFIG counting widget description"
IsCustomizable="true">
...
Sledování, kdy je widget v režimu přizpůsobení
Příklad v tomto článku používá pomocné struktury CompactWidgetInfo ke sledování aktuálního stavu našich aktivních widgetů. Přidejte pole inCustomization, které se použije ke sledování, kdy hostitel widgetů očekává, že místo běžné šablony widgetu odešleme šablonu JSON vlastního nastavení.
// 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;
}
Implementujte IWidgetProvider2
Funkce přizpůsobení widgetu je zpřístupněna prostřednictvím rozhraní IWidgetProvider2 . Aktualizujte definici třídy WidgetProvider pro implementaci tohoto rozhraní.
// WidgetProvider.cs
internal class WidgetProvider : IWidgetProvider, IWidgetProvider2
Přidejte implementaci pro zpětné volání OnCustomizationRequested v rozhraní IWidgetProvider2. Tato metoda používá stejný vzor jako ostatní zpětné volání, které jsme použili. ID widgetu, který se má přizpůsobit, získáme z WidgetContextu a najdeme pomocnou strukturu CompactWidgetInfo přidruženou k danému widgetu a nastavíme pole inCustomization na hodnotu 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);
}
}
Teď deklarujte proměnnou řetězce, která definuje šablonu JSON pro uživatelské rozhraní pro přizpůsobení widgetu. V tomto příkladu máme tlačítko Resetovat čítač a tlačítko Ukončit vlastní nastavení, které signalizují, že se náš poskytovatel vrátí k běžnému chování widgetu. Tuto definici umístěte vedle ostatních definic šablon.
// 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""
}";
Odeslání šablony vlastního nastavení v nástroji UpdateWidget
V dalším kroku aktualizujeme naši UpdateWidget pomocnou metodu, která odesílá naše data a šablony JSON vizuálu hostiteli widgetů. Při aktualizaci widgetu pro počítání pošleme buď běžnou šablonu widgetu, nebo šablonu přizpůsobení v závislosti na hodnotě inCustomization pole. Pro stručnost není v tomto fragmentu kódu vynechán kód, který není relevantní pro přizpůsobení.
// 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);
}
Reakce na akce přizpůsobení
Když uživatelé pracují se vstupy v naší šabloně přizpůsobení, volá stejnou OnActionInvoked obslužnou rutinu, jako když uživatel pracuje s běžným prostředím widgetu. Abychom mohli podporovat přizpůsobení, vyhledáme příkazy "reset" a "exitCustomization" z naší šablony JSON pro přizpůsobení. Akce „Reset counter“ resetuje customState na hodnotu 0 a ukončí přizpůsobení. Akce „Ukončit přizpůsobení“ ukončí přizpůsobení bez resetování čítače. Obě nastaví inCustomization na false, aby UpdateWidget odeslala běžnou šablonu widgetu.
// 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);
}
}
}
Nyní, když nasadíte widget, měli byste v nabídce s elipsou vidět tlačítko Přizpůsobit widget. Kliknutím na tlačítko Přizpůsobit zobrazíte šablonu vlastního nastavení.
Kliknutím na tlačítko Resetovat počítadlo resetujete počítadlo na 0. Kliknutím na tlačítko Ukončit přizpůsobení se vrátíte k běžnému chování widgetu.
Windows developer