Implementowanie dostawcy widżetu w aplikacji systemu Windows w języku C#
W tym artykule opisano proces tworzenia prostego dostawcy widżetów, który implementuje interfejs IWidgetProvider
Tego przykładowego kodu w tym artykule został dostosowany z przykładu widżetów zestawu SDK aplikacji systemu Windows . Aby zaimplementować dostawcę widżetu przy użyciu języka C++/WinRT, zobacz Implementowanie dostawcy widżetu w aplikacji win32 (C++/WinRT).
Warunki wstępne
- Urządzenie musi mieć włączony tryb dewelopera. Aby uzyskać więcej informacji, zobacz Włącz urządzenie do rozwoju.
- Program Visual Studio 2022 lub nowszy z obciążeniem programistycznym platformy uniwersalnej systemu Windows . Pamiętaj, aby dodać składnik dla języka C++ (wersja 143) z opcjonalnej listy rozwijanej.
Tworzenie nowej aplikacji konsolowej w języku C#
W programie Visual Studio utwórz nowy projekt. W oknie dialogowym Utwórz nowy projekt ustaw filtr języka na "C#" i filtr platformy na Windows, a następnie wybierz szablon projektu Aplikacja konsolowa. Nadaj nowej nazwie nowy projekt "ExampleWidgetProvider". Po wyświetleniu monitu ustaw docelową wersję platformy .NET na 8.0.
Po załadowaniu projektu w Eksploratorze rozwiązań kliknij prawym przyciskiem myszy nazwę projektu i wybierz pozycję właściwości . Na stronie Ogólne przewiń w dół do docelowego systemu operacyjnego i wybierz pozycję "Windows". W obszarze docelowej wersji systemu operacyjnegowybierz wersję 10.0.19041.0 lub nowszą.
Aby zaktualizować projekt do obsługi programu .NET 8.0, w eksploratorze rozwiązań
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
Należy pamiętać, że w tym przewodniku jest używana aplikacja konsolowa, która wyświetla okno konsoli po aktywowaniu widżetu, aby umożliwić łatwe debugowanie. Gdy wszystko będzie gotowe do opublikowania aplikacji dostawcy widżetów, możesz przekonwertować aplikację konsolową na aplikację systemu Windows, wykonując kroki opisane w Konwertowanie aplikacji konsolowej na aplikację systemu Windows.
Dodawanie odwołań do zestawu SDK aplikacji systemu Windows
W tym przykładzie użyto najnowszego stabilnego pakietu NuGet zestawu SDK aplikacji systemu Windows. W oknie Eksploratora rozwiązań kliknij prawym przyciskiem myszy na Zależności i wybierz Zarządzaj pakietami NuGet.... W Menedżerze pakietów NuGet wybierz kartę Przeglądaj i wyszukaj "Microsoft.WindowsAppSDK". Wybierz najnowszą stabilną wersję na liście rozwijanej Wersja , a następnie kliknij pozycję Zainstaluj.
Dodawanie klasy WidgetProvider do obsługi operacji widżetu
W programie Visual Studio kliknij prawym przyciskiem myszy projekt ExampleWidgetProvider
w eksploratorze rozwiązań i wybierz pozycję Dodaj>Klasa. W oknie dialogowym Dodaj klasę nadaj klasie nazwę "WidgetProvider" i kliknij przycisk Dodaj. W pliku WidgetProvider.cs, który został wygenerowany, zaktualizuj definicję klasy, aby wskazać, że implementuje interfejs IWidgetProvider.
// WidgetProvider.cs
internal class WidgetProvider : IWidgetProvider
Przygotowanie do śledzenia włączonych widżetów
Dostawca widżetu może obsługiwać jeden widżet lub wiele widżetów. Za każdym razem, gdy host widżetu inicjuje operację z dostawcą widżetu, przekazuje identyfikator w celu zidentyfikowania widżetu skojarzonego z operacją. Każdy widżet ma również skojarzoną nazwę oraz stan, który może służyć do przechowywania danych niestandardowych. W tym przykładzie zadeklarujemy prostą strukturę pomocnika do przechowywania identyfikatora, nazwy i danych dla każdego przypiętego widżetu. Widżety mogą być również w stanie aktywnym, który jest omówiony w sekcji Aktywuj i Dezaktywuj poniżej, a następnie prześledzimy ten stan dla każdego widżetu z wartością logiczną. Dodaj następującą definicję do pliku WidgetProvider.cs wewnątrz ExampleWidgetProvider przestrzeni nazw, ale poza definicją klasy WidgetProvider.
// WidgetProvider.cs
public class CompactWidgetInfo
{
public string? widgetId { get; set; }
public string? widgetName { get; set; }
public int customState = 0;
public bool isActive = false;
}
Wewnątrz definicji klasy WidgetProvider w WidgetProvider.cs dodaj element członkowski dla mapy, która będzie obsługiwać listę włączonych widżetów przy użyciu identyfikatora widżetu jako klucza dla każdego wpisu.
// WidgetProvider.cs
// Class member of WidgetProvider
public static Dictionary<string, CompactWidgetInfo> RunningWidgets = new Dictionary<string, CompactWidgetInfo>();
Deklarowanie ciągów JSON szablonu widżetu
W tym przykładzie zostaną zadeklarowane niektóre ciągi statyczne w celu zdefiniowania szablonów JSON dla każdego widżetu. Dla wygody te szablony są przechowywane w zmiennych składowych klasy WidgetProvider. Jeśli potrzebujesz ogólnego miejsca przechowywania dla szablonów, można je uwzględnić w pakiecie aplikacji: Uzyskiwanie Dostępu do Plików Pakietów. Aby uzyskać informacje na temat tworzenia dokumentu JSON szablonu widżetu, zobacz Tworzenie szablonu widżetu za pomocą projektanta kart adaptacyjnych.
W najnowszej wersji aplikacje, które implementują widżety systemu Windows, mogą dostosowywać nagłówek, który jest wyświetlany dla ich widżetów na Tablicy Widżetów, zastępując domyślną prezentację. Aby uzyskać więcej informacji, zobacz Dostosowywanie obszaru nagłówka widżetu.
Uwaga
W najnowszej wersji aplikacje implementujące widżety systemu Windows mogą zdecydować się na wypełnienie zawartości widżetu kodem HTML obsługiwanym z określonego adresu URL zamiast podawania zawartości w formacie schematu karty adaptacyjnej w ładunku JSON przekazanym od dostawcy do tablicy widżetów. Dostawcy widżetów muszą nadal dostarczać ładunek JSON karty adaptacyjnej, więc kroki implementacji w tym przewodniku mają zastosowanie do widżetów internetowych. Aby uzyskać więcej informacji, skontaktuj się z dostawcami widżetów internetowych.
// 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"
}
""";
Implementowanie metod IWidgetProvider
W kilku następnych sekcjach zaimplementujemy metody interfejsu IWidgetProvider. Metoda pomocnika UpdateWidget wywoływana w kilku z tych implementacji metod zostanie pokazana w dalszej części tego artykułu.
Notatka
Obiekty przekazywane do metod wywołania zwrotnego interfejsu IWidgetProvider są gwarantowane tylko jako prawidłowe w wywołaniu zwrotnym. Nie należy przechowywać odwołań do tych obiektów, ponieważ ich zachowanie poza kontekstem wywołania zwrotnego jest niezdefiniowane.
UtwórzWidget
Host widżetów wywołuje CreateWidget, gdy użytkownik przypiął jeden z widżetów aplikacji w hoście widżetów. Najpierw ta metoda pobiera identyfikator i nazwę skojarzonego widżetu i dodaje nowe wystąpienie naszej struktury pomocniczej, CompactWidgetInfo, do kolekcji włączonych widżetów. Następnie wysyłamy początkowy szablon i dane dla widżetu, który jest hermetyzowany w metodzie pomocnika 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);
}
UsuńWidget
Host widżetu wywołuje DeleteWidget, gdy użytkownik odpiął jeden z widżetów Twojej aplikacji z hosta widżetu. W takim przypadku usuniemy skojarzony widżet z naszej listy z włączonymi widżetami, abyśmy nie wysyłali żadnych dalszych aktualizacji tego widżetu.
// WidgetProvider.cs
public void DeleteWidget(string widgetId, string customState)
{
RunningWidgets.Remove(widgetId);
if(RunningWidgets.Count == 0)
{
emptyWidgetListEvent.Set();
}
}
W tym przykładzie, oprócz usuwania widżetu o określonym parametrze z listy włączonych widżetów, sprawdzamy również, czy lista jest teraz pusta, a jeśli tak, ustawiamy zdarzenie, które będzie użyte później, aby umożliwić aplikacji wyjście, gdy nie ma włączonych widżetów. Wewnątrz definicji klasy dodaj deklarację ManualResetEvent i publiczną funkcję dostępu.
// WidgetProvider.cs
static ManualResetEvent emptyWidgetListEvent = new ManualResetEvent(false);
public static ManualResetEvent GetEmptyWidgetListEvent()
{
return emptyWidgetListEvent;
}
OnActionInvoked
Host widżetu wywołuje OnActionInvoked, gdy użytkownik wchodzi w interakcję ze zdefiniowaną akcją w szablonie widżetu. W widżecie licznika użytym w tym przykładzie zadeklarowano akcję z czasownikiem i wartością "inc" w szablonie JSON dla widżetu. Kod dostawcy widżetu użyje tego czasownika i wartości, aby określić działanie w odpowiedzi na interakcję użytkownika.
...
"actions": [
{
"type": "Action.Execute",
"title": "Increment",
"verb": "inc"
}
],
...
Aby uzyskać wartość czasownika w metodzie OnActionInvoked, sprawdź właściwość czasownika argumentów WidgetActionInvokedArgs przekazanych do metody. Jeśli czasownik ma wartość "inc", wiemy, że będziemy zwiększać liczbę w stanie niestandardowym dla widżetu. Z WidgetActionInvokedArgspobierz obiekt WidgetContext, a następnie WidgetId, aby uzyskać identyfikator zaktualizowanego widżetu. Znajdź wpis na mapie z włączonymi widżetami z określonym identyfikatorem, a następnie zaktualizuj wartość stanu niestandardowego, która jest używana do przechowywania liczby przyrostów. Na koniec zaktualizuj zawartość widżetu przy użyciu nowej wartości za pomocą funkcji pomocnika 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);
}
}
}
Aby uzyskać informacje o składni Action.Execute dla kart adaptacyjnych, zobacz Action.Execute. Aby uzyskać wskazówki dotyczące projektowania interakcji dla widżetów, zobacz wskazówki dotyczące projektowania interakcji z widżetem
OnWidgetContextChanged
W bieżącej wersji OnWidgetContextChanged jest wywoływana tylko wtedy, gdy użytkownik zmienia rozmiar przypiętego widżetu. Możesz wybrać zwrócenie innego szablonu/danych JSON do hosta widżetu, w zależności od żądanego rozmiaru. Kod JSON szablonu można również zaprojektować tak, aby obsługiwał wszystkie dostępne rozmiary przy użyciu renderowania warunkowego na podstawie wartości host.widgetSize. Jeśli nie musisz wysyłać nowego szablonu lub danych w celu uwzględnienia zmiany rozmiaru, możesz użyć OnWidgetContextChanged dla celów telemetrii.
// 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);
}
}
Aktywowanie i dezaktywowanie
Metoda Activate jest wywoływana w celu powiadomienia dostawcy widżetu, że host widżetu jest obecnie zainteresowany otrzymywaniem zaktualizowanej zawartości od dostawcy. Na przykład może to oznaczać, że użytkownik obecnie aktywnie ogląda hosta widżetu. Metoda Dezaktywuj jest wywoływana w celu powiadomienia dostawcy widżetu o tym, że host widżetu nie żąda już aktualizacji zawartości. Te dwie metody definiują okno, w którym host widżetu jest najbardziej zainteresowany wyświetlaniem najbardziej up-to-date zawartości. Dostawcy widżetów mogą wysyłać aktualizacje do widżetu w dowolnym momencie, na przykład w odpowiedzi na powiadomienie wypychane, ale podobnie jak w przypadku dowolnego zadania w tle, ważne jest, aby zrównoważyć dostarczanie up-to-date zawartości z problemami dotyczącymi zasobów, takimi jak żywotność baterii.
Aktywowanie i Dezaktywowanie są wywoływane dla poszczególnych widżetów. Ten przykład śledzi status aktywności każdego widżetu w strukturze pomocniczej CompactWidgetInfo. W metodzie Activate wywołujemy metodę pomocnika UpdateWidget w celu zaktualizowania widżetu. Należy pamiętać, że okno czasowe między Aktywacja a Dezaktywacja może być małe, dlatego zaleca się jak najszybsze zaktualizowanie ścieżki aktualizacji kodu widżetu.
// 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;
}
}
Aktualizowanie widżetu
Zdefiniuj metodę pomocnika UpdateWidget w celu zaktualizowania włączonego widżetu. W tym przykładzie sprawdzamy nazwę widżetu w strukturze pomocnika CompactWidgetInfo przekazanej do metody, następnie ustawiamy odpowiedni szablon i dane JSON na podstawie tego, który widżet jest aktualizowany. WidgetUpdateRequestOptions jest inicjowany przy użyciu szablonu, danych i stanu niestandardowego dla aktualizowanego widżetu. Wywołaj WidgetManager::GetDefault, aby uzyskać wystąpienie klasy WidgetManager, a następnie wywołaj UpdateWidget, aby wysłać zaktualizowane dane widżetu do hosta widżetu.
// 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);
}
Inicjowanie listy włączonych widżetów podczas uruchamiania
Po pierwszym zainicjowaniu dostawcy widżetów warto zapytać WidgetManager, czy istnieją uruchomione widżety, które obecnie obsługuje nasz dostawca. Pomoże to odzyskać aplikację do poprzedniego stanu w przypadku ponownego uruchomienia komputera lub awarii dostawcy. Wywołaj WidgetManager.GetDefault, aby pobrać domyślne wystąpienie menedżera widżetów dla aplikacji. Następnie wywołaj GetWidgetInfos, która zwraca tablicę obiektów WidgetInfo. Skopiuj identyfikatory, nazwy i stan niestandardowy widżetu do struktury pomocniczej CompactWidgetInfo i zapisz je w zmiennej składowej RunningWidgets. Wklej następujący kod do definicji klasy 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 = widgetName, widgetName = widgetId };
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;
}
}
}
Zaimplementuj fabrykę klas, która utworzy instancję WidgetProvider na żądanie
Aby host widżetu komunikował się z naszym dostawcą widżetów, musimy wywołać CoRegisterClassObject. Ta funkcja wymaga utworzenia implementacji IClassFactory, która utworzy obiekt klasy dla naszej klasy WidgetProvider. Zaimplementujemy naszą fabrykę klas w samodzielnej klasie pomocniczej.
W programie Visual Studio kliknij prawym przyciskiem myszy projekt ExampleWidgetProvider
w Eksploratorze Rozwiązań i wybierz pozycję Dodaj ->Klasa. W oknie dialogowym Dodaj klasę nadaj klasie nazwę "FactoryHelper" i kliknij pozycję Dodaj.
Zastąp zawartość pliku FactoryHelper.cs następującym kodem. Ten kod definiuje interfejs IClassFactory i implementuje dwie metody, CreateInstance i LockServer. Ten kod jest typowy standardowy do implementowania fabryki klas i nie jest specyficzny dla funkcjonalności dostawcy widżetu, z wyjątkiem tego, że wskazujemy, że tworzony obiekt klasy implementuje interfejs IWidgetProvider.
// FactoryHelper.cs
using Microsoft.Windows.Widgets.Providers;
using System.Runtime.InteropServices;
using WinRT;
namespace COM
{
static class Guids
{
public const string IClassFactory = "00000001-0000-0000-C000-000000000046";
public const string IUnknown = "00000000-0000-0000-C000-000000000046";
}
///
/// IClassFactory declaration
///
[ComImport, ComVisible(false), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid(COM.Guids.IClassFactory)]
internal interface IClassFactory
{
[PreserveSig]
int CreateInstance(IntPtr pUnkOuter, ref Guid riid, out IntPtr ppvObject);
[PreserveSig]
int LockServer(bool fLock);
}
[ComVisible(true)]
class WidgetProviderFactory<T> : IClassFactory
where T : IWidgetProvider, new()
{
public int CreateInstance(IntPtr pUnkOuter, ref Guid riid, out IntPtr ppvObject)
{
ppvObject = IntPtr.Zero;
if (pUnkOuter != IntPtr.Zero)
{
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;
}
}
Utwórz identyfikator GUID reprezentujący CLSID dostawcy widżetu
Następnie należy utworzyć identyfikator GUID reprezentujący CLSID, który będzie używany do identyfikowania dostawcy widżetu na potrzeby aktywacji modelu COM. Ta sama wartość będzie również używana podczas pakowania aplikacji. Wygeneruj identyfikator GUID w programie Visual Studio, przechodząc do Tools—>Create GUID. Wybierz opcję formatu rejestru i kliknij Kopiuj, a następnie wklej to do pliku tekstowego, aby móc skopiować to później.
Rejestrowanie obiektu klasy dostawcy widżetu za pomocą obiektu OLE
W pliku Program.cs naszego pliku wykonywalnego wywołamy CoRegisterClassObject, aby zarejestrować dostawcę widżetu w OLE, by host widżetu mógł się z nim komunikować. Zastąp zawartość Program.cs następującym kodem. Ten kod importuje funkcję CoRegisterClassObject i ją wywołuje, przekazując interfejs WidgetProviderFactory, który zdefiniowaliśmy w poprzednim kroku. Pamiętaj, aby zaktualizować deklarację zmiennej CLSID_Factory, aby użyć identyfikatora GUID wygenerowanego w poprzednim 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);
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);
}
Należy pamiętać, że ten przykładowy kod importuje funkcję GetConsoleWindow w celu określenia, czy aplikacja jest uruchomiona jako aplikacja konsolowa, domyślne zachowanie tego przewodnika. Jeśli funkcja zwraca prawidłowy wskaźnik, zapisujemy informacje debugowania w konsoli programu . W przeciwnym razie aplikacja jest uruchomiona jako aplikacja systemu Windows. W takim przypadku czekamy na zdarzenie, które ustawiliśmy w metodzie DeleteWidget, gdy lista włączonych widżetów jest pusta, i wychodzimy z aplikacji. Aby uzyskać informacje na temat konwertowania przykładowej aplikacji konsolowej na aplikację systemu Windows, zobacz Konwertowanie aplikacji konsolowej na aplikację systemu Windows.
Spakuj aplikację dostawcy widżetów
W bieżącej wersji jako dostawcy widżetów mogą być rejestrowane tylko spakowane aplikacje. Poniższe kroki poprowadzą cię przez proces pakowania aplikacji i aktualizowania jej manifestu, aby zarejestrować aplikację w systemie operacyjnym jako dostawcę widżetów.
Stwórz projekt pakietu MSIX
W Eksploratorze rozwiązań
Dodaj odwołanie do pakietu SDK Aplikacji Windows do projektu tworzenia pakietów
Należy dodać odwołanie do pakietu NuGet zestawu SDK dla aplikacji Windows do projektu pakietowania MSIX. W eksploratorze rozwiązań kliknij dwukrotnie projekt ExampleWidgetProviderPackage, aby otworzyć plik ExampleWidgetProviderPackage.wapproj. Dodaj następujący kod XML wewnątrz elementu Project.
<!--ExampleWidgetProviderPackage.wapproj-->
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.2.221116.1">
<IncludeAssets>build</IncludeAssets>
</PackageReference>
</ItemGroup>
Notatka
Upewnij się, że wersja
Jeśli na komputerze jest już zainstalowana poprawna wersja zestawu SDK aplikacji systemu Windows i nie chcesz pakować środowiska uruchomieniowego zestawu SDK w pakiecie, możesz określić zależność pakietu w pliku Package.appxmanifest dla projektu 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>
...
Aktualizowanie manifestu pakietu
W Eksploratorze rozwiązań
<!-- Package.appmanifest -->
<Package
...
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
Wewnątrz elementu Application utwórz nowy pusty element o nazwie Extensions. Upewnij się, że jest to po tagu zamykającym dla uap:VisualElements.
<!-- Package.appxmanifest -->
<Application>
...
<Extensions>
</Extensions>
</Application>
Pierwszym rozszerzeniem, które musimy dodać, jest rozszerzenie ComServer. Spowoduje to zarejestrowanie punktu wejścia pliku wykonywalnego w systemie operacyjnym. To rozszerzenie jest odpowiednikiem aplikacji pakietowej umożliwiającej rejestrację serwera COM poprzez ustawienie klucza rejestru i nie jest specyficzne dla dostawców widżetów. Dodaj następujący element com:Extension jako element podrzędny elementu Extensions. Zmień identyfikator GUID w atrybucie Id elementu com:Class na identyfikator GUID wygenerowany w poprzednim 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>
Następnie dodaj rozszerzenie, które rejestruje aplikację jako dostawcę widżetu. Wklej element uap3:Extension w poniższym fragmencie kodu jako element podrzędny elementu Extensions. Pamiętaj, aby zastąpić atrybut ClassId elementu COM identyfikatorem GUID użytym w poprzednich krokach.
<!-- 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>
Aby uzyskać szczegółowe opisy i informacje o formacie dla wszystkich tych elementów, zobacz Format XML manifestu dostawcy widżetów.
Dodawanie ikon i innych obrazów do projektu pakowania
W eksploratorze rozwiązań kliknij prawym przyciskiem myszy ExampleWidgetProviderPackage i wybierz dodaj>nowy folder. Nadaj temu folderowi nazwę ProviderAssets, ponieważ użyto go w Package.appxmanifest
z poprzedniego kroku. W tym miejscu będziemy przechowywać nasze ikony i zrzuty ekranu dla naszych widżetów. Po dodaniu żądanych ikon i zrzutów ekranu upewnij się, że nazwy obrazów są zgodne z tym, co następuje po Path=ProviderAssets\ w Package.appxmanifest
lub widżety nie będą wyświetlane na hoście widżetu.
Aby uzyskać informacje o wymaganiach projektowych dotyczących obrazów zrzutów ekranu i konwencji nazewnictwa zlokalizowanych zrzutów ekranu, zobacz Integrowanie z selektorem widżetów.
Testowanie dostawcy widżetu
Upewnij się, że wybrano zgodną architekturę z listy rozwijanej Platformy rozwiązań, na przykład "x64", która odpowiada twojej maszynie deweloperskiej. W eksploratorze rozwiązań kliknij rozwiązanie prawym przyciskiem myszy i wybierz pozycję Build Solution. Po wykonaniu tej czynności kliknij prawym przyciskiem myszy ExampleWidgetProviderPackage i wybierz pozycję Wdróż. W bieżącej wersji jedynym obsługiwanym hostem widżetu jest Tablica widżetów. Aby wyświetlić widżety, musisz otworzyć tablicę widżetów i wybrać pozycję Dodaj widżety w prawym górnym rogu. Przewiń do dołu dostępnych widżetów i powinieneś zobaczyć przykładowy widżet pogody i widżet zliczania Microsoft, które zostały utworzone w tym samouczku. Kliknij widżety, aby przypiąć je do tablicy widżetów i przetestować ich funkcjonalność.
Debugowanie twojego dostawcy widżetów
Po przypięciu widżetów Platforma widżetów uruchomi aplikację twojego dostawcy widżetów, aby mogła otrzymywać i wysyłać istotne informacje dotyczące widżetu. Aby debugować uruchomiony widżet, możesz dołączyć debuger do uruchomionej aplikacji dostawcy widżetów lub skonfigurować program Visual Studio, aby automatycznie rozpocząć debugowanie procesu dostawcy widżetu po jego uruchomieniu.
Aby dołączyć do uruchomionego procesu:
- W programie Visual Studio kliknij pozycję debugowanie —> Dołącz do procesu.
- Przefiltruj procesy i znajdź żądaną aplikację dostawcy widżetów.
- Dołącz debuger.
Aby automatycznie dołączyć debuger do procesu po początkowym uruchomieniu:
- W programie Visual Studio kliknij pozycję debugowanie —> inne cele debugowania —> Debugowanie zainstalowanego pakietu aplikacji.
- Przefiltruj pakiety i znajdź żądany pakiet dostawcy widżetów.
- Zaznacz go i zaznacz pole wyboru, które mówi: "Nie uruchamiaj, ale debuguj mój kod, kiedy zostanie uruchomiony".
- Kliknij , aby dołączyć.
Konwertowanie aplikacji konsolowej na aplikację systemu Windows
Aby przekonwertować aplikację konsolową utworzoną w tym przewodniku do aplikacji systemu Windows, kliknij prawym przyciskiem myszy projekt ExampleWidgetProvider w eksploratorze rozwiązań i wybierz pozycję właściwości . W obszarze Application->Ogólne zmień typ danych wyjściowych z "Aplikacja konsolowa" na "Aplikacja systemu Windows".
Publikowanie widżetu
Po opracowaniu i przetestowaniu widżetu możesz opublikować aplikację w sklepie Microsoft Store, aby użytkownicy mogli instalować widżety na swoich urządzeniach. Aby uzyskać szczegółowe wskazówki dotyczące publikowania aplikacji, zobacz Publikowanie aplikacji w sklepie Microsoft Store.
Kolekcja sklepów widżetów
Po opublikowaniu aplikacji w sklepie Microsoft Store możesz poprosić o włączenie aplikacji do kolekcji sklepów widżetów, która ułatwia użytkownikom odnajdywanie aplikacji, które zawierają widżety systemu Windows. Aby przesłać żądanie, zobacz Prześlij informacje o widżecie do dodania do Kolekcji Sklepów.
Implementowanie dostosowywania widżetu
Począwszy od zestawu Windows App SDK 1.4, widżety mogą obsługiwać dostosowywanie użytkowników. Po zaimplementowaniu tej funkcji do menu z trzema kropkami zostanie dodana opcja Dostosuj widżet powyżej opcji Odepnij widżet.
Poniższe kroki zawierają podsumowanie procesu dostosowywania widżetu.
- W normalnej operacji dostawca widżetu odpowiada na żądania od hosta widżetu za pomocą szablonu i ładunków danych dla zwykłego środowiska widżetu.
- Użytkownik klika przycisk Dostosuj widżet w menu z trzema kropkami.
- Widżet zgłasza zdarzenie OnCustomizationRequested u dostawcy widżetu, aby wskazać, że użytkownik zażądał możliwości dostosowywania widżetu.
- Dostawca widżetu ustawia flagę wewnętrzną, aby wskazać, że widżet jest w trybie dostosowywania. W trybie dostosowywania dostawca widżetu wysyła szablony JSON dla interfejsu użytkownika dostosowywania widżetu zamiast zwykłego interfejsu użytkownika widżetu.
- W trybie personalizacji dostawca widżetu otrzymuje zdarzenia OnActionInvoked, gdy użytkownik wchodzi w interakcję z interfejsem użytkownika personalizacji i na podstawie tych działań dostosowuje swoją wewnętrzną konfigurację oraz zachowanie.
- Gdy akcja skojarzona z zdarzeniem OnActionInvoked to zdefiniowana przez aplikację akcja "zakończ dostosowywanie", dostawca widżetu resetuje flagę wewnętrzną, aby wskazać, że nie jest już w trybie dostosowywania i wznawia wysyłanie szablonów wizualizacji i danych JSON dla zwykłego środowiska widżetu, odzwierciedlając zmiany żądane podczas dostosowywania.
- Dostawca widżetu utrzymuje opcje dostosowywania na dysku lub w chmurze, aby zmiany zostały zachowane między wywołaniami dostawcy widżetu.
Notatka
Istnieje znana usterka dotycząca tablicy widżetów systemu Windows dla widżetów utworzonych przy użyciu zestawu SDK aplikacji systemu Windows, która powoduje, że menu wielokropka przestaje odpowiadać po wyświetlaniu karty dostosowywania.
W typowych scenariuszach dostosowywania widżetu użytkownik wybierze dane wyświetlane w widżecie lub dostosuje wizualną prezentację widżetu. Dla uproszczenia przykład w tej sekcji spowoduje dodanie zachowania dostosowywania, które umożliwia użytkownikowi zresetowanie licznika widżetu zliczania zaimplementowanego w poprzednich krokach.
Uwaga
Dostosowywanie widżetu jest obsługiwane tylko w zestawie Sdk aplikacji systemu Windows w wersji 1.4 lub nowszej. Pamiętaj, aby zaktualizować odwołania w projekcie do najnowszej wersji pakietu Nuget.
Zaktualizuj manifest pakietu, aby zadeklarować wsparcie dla dostosowań
Aby poinformować hosta widżetu, że widżet obsługuje dostosowywanie, dodaj atrybut IsCustomizable do elementu Definition dla widżetu i ustaw go na wartość true.
...
<Definition Id="Counting_Widget"
DisplayName="Microsoft Counting Widget"
Description="CONFIG counting widget description"
IsCustomizable="true">
...
Śledzenie, kiedy widżet jest w trybie dostosowywania
W przykładzie w tym artykule użyto struktury pomocniczej CompactWidgetInfo do śledzenia bieżącego stanu naszych aktywnych widżetów. Dodaj pole inCustomization, które będzie używane do śledzenia, kiedy host widżetu oczekuje od nas wysłania szablonu dostosowania json, a nie szablonu zwykłego widżetu.
// 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;
}
Implementowanie IWidgetProvider2
Funkcja dostosowywania widżetu jest uwidoczniona za pośrednictwem interfejsu IWidgetProvider2. Zaktualizuj definicję klasy widgetProvider , aby zaimplementować ten interfejs.
// WidgetProvider.cs
internal class WidgetProvider : IWidgetProvider, IWidgetProvider2
Dodaj implementację dla wywołania zwrotnego OnCustomizationRequested interfejsu IWidgetProvider2. Ta metoda używa tego samego wzorca co inne użyte wywołania zwrotne. Otrzymujemy identyfikator widżetu, który ma zostać dostosowany, z WidgetContext, znajdujemy strukturę pomocniczą CompactWidgetInfo skojarzoną z tym widżetem i ustawiamy pole inCustomization na prawda.
// 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);
}
}
Teraz zadeklaruj zmienną ciągu, która definiuje szablon JSON dla interfejsu użytkownika dostosowywania widżetu. W tym przykładzie mamy przycisk "Resetuj licznik" i przycisk "Zakończ dostosowywanie", który zasygnalizuje, że nasz dostawca powróci do normalnego zachowania widżetu. Umieść tę definicję obok innych definicji szablonu.
// 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""
}";
Wysyłanie szablonu dostosowywania w rozwiązaniu UpdateWidget
Następnie zaktualizujemy metodę pomocnika UpdateWidget, która wysyła nasze dane i wizualne szablony JSON do hosta widżetu. Podczas aktualizowania widżetu zliczania wysyłamy zwykły szablon widżetu lub szablon dostosowywania w zależności od wartości pola inCustomization. Dla zwięzłości, kod, który nie jest istotny dla dostosowywania, został pominięty w tym fragmencie kodu.
// 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);
}
Reagowanie na akcje dostosowywania
Gdy użytkownicy wchodzą w interakcję z danymi wejściowymi w naszym szablonie dostosowywania, wywołuje tę samą obsługę OnActionInvoked, jak w przypadku interakcji użytkownika ze zwykłym środowiskiem widżetu. Aby zapewnić obsługę personalizacji, szukamy czasowników „reset” i „exitCustomization” z naszego szablonu JSON. Jeśli akcja dotyczy przycisku "Resetuj licznik", zresetujemy licznik przechowywany w polu customState naszej struktury pomocniczej na wartość 0. Jeśli akcja dotyczy przycisku "Zakończ dostosowywanie", ustawimy pole inCustomization na false, tak aby podczas wywoływania UpdateWidgetnasza metoda pomocnika wysyłała zwykłe szablony JSON, a nie szablon dostosowywania.
// 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);
}
}
}
Teraz po wdrożeniu widżetu powinien zostać wyświetlony przycisk Dostosuj widżet w menu wielokropka. Kliknięcie przycisku dostosuj spowoduje wyświetlenie szablonu dostosowywania.
Kliknij przycisk Resetuj licznik, aby zresetować licznik do 0. Kliknij przycisk Zakończ dostosowywanie, aby powrócić do normalnego działania widżetu.