Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Artikel ini memancang Anda membuat penyedia widget sederhana yang mengimplementasikan antarmuka IWidgetProvider . Metode antarmuka ini dipanggil oleh host widget untuk meminta data yang menentukan widget atau membiarkan penyedia widget merespons tindakan pengguna pada widget. Penyedia widget dapat mendukung satu widget atau beberapa widget. Dalam contoh ini, kita akan menentukan dua widget yang berbeda. Satu widget adalah widget cuaca tiruan yang menggambarkan beberapa opsi pemformatan yang disediakan oleh kerangka kerja Kartu Adaptif. Widget kedua akan menunjukkan tindakan pengguna dan fitur status widget kustom dengan mempertahankan penghitung yang bertambah setiap kali pengguna mengklik tombol yang ditampilkan pada widget.
Kode sampel dalam artikel ini diadaptasi dari Sampel Widget SDK Aplikasi Windows. Untuk menerapkan penyedia widget menggunakan C++/WinRT, lihat Menerapkan penyedia widget di aplikasi win32 (C++/WinRT).
Prasyarat
- Perangkat Anda harus mengaktifkan mode pengembang. Untuk informasi selengkapnya, lihat Mengaktifkan perangkat Anda untuk pengembangan.
- Visual Studio 2022 atau yang lebih baru dengan beban kerja pengembangan Platform Windows Universal. Pastikan untuk menambahkan komponen untuk C++ (v143) dari dropdown opsional.
Membuat aplikasi konsol C# baru
Di Visual Studio, buat proyek baru. Dalam dialog Buat proyek baru, atur filter bahasa ke "C#" dan filter platform ke Windows, lalu pilih templat proyek Aplikasi Konsol. Beri nama proyek baru "ExampleWidgetProvider". Saat diminta, atur versi .NET target ke 8.0.
Saat proyek dimuat, di Penjelajah Solusi klik kanan nama proyek dan pilih Properti. Pada halaman Umum , gulir ke bawah ke OS Target dan pilih "Windows". Di bawah Versi OS Target, pilih versi 10.0.19041.0 atau yang lebih baru.
Untuk memperbarui proyek Anda untuk mendukung .NET 8.0, di Penjelajah Solusi klik kanan nama proyek dan pilih Edit File Proyek. Di dalam PropertyGroup, tambahkan elemen RuntimeIdentifiers berikut.
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
Perhatikan bahwa panduan ini menggunakan aplikasi konsol yang menampilkan jendela konsol saat widget diaktifkan untuk mengaktifkan penelusuran kesalahan yang mudah. Ketika Anda siap untuk menerbitkan aplikasi penyedia widget, Anda dapat mengonversi aplikasi konsol ke aplikasi Windows dengan mengikuti langkah-langkah di Mengonversi aplikasi konsol Anda ke aplikasi Windows.
Menambahkan referensi ke SDK Aplikasi Windows
Sampel ini menggunakan paket NuGet SDK Aplikasi Windows stabil terbaru. Di Penjelajah Solusi, klik kanan Dependensi dan pilih Kelola paket NuGet.... Di manajer paket NuGet, pilih tab Telusuri dan cari "Microsoft.WindowsAppSDK". Pilih versi stabil terbaru di menu drop-down Versi lalu klik Instal.
Menambahkan kelas WidgetProvider untuk menangani operasi widget
Di Visual Studio, klik ExampleWidgetProvider
kanan proyek di Penjelajah Solusi dan pilih Add-Class>.
Dalam dialog Tambahkan kelas, beri nama kelas "WidgetProvider" dan klik Tambahkan. Dalam file WidgetProvider.cs yang dihasilkan, perbarui definisi kelas untuk menunjukkan bahwa file tersebut mengimplementasikan antarmuka IWidgetProvider .
// WidgetProvider.cs
internal class WidgetProvider : IWidgetProvider
Bersiap untuk melacak widget yang diaktifkan
Penyedia widget dapat mendukung satu widget atau beberapa widget. Setiap kali host widget memulai operasi dengan penyedia widget, host widget meneruskan ID untuk mengidentifikasi widget yang terkait dengan operasi. Setiap widget juga memiliki nama terkait dan nilai status yang dapat digunakan untuk menyimpan data kustom. Untuk contoh ini, kami akan mendeklarasikan struktur pembantu sederhana untuk menyimpan ID, nama, dan data untuk setiap widget yang disematkan. Widget juga dapat dalam keadaan aktif, yang dibahas di bagian Aktifkan dan Nonaktifkan di bawah ini, dan kami akan melacak status ini untuk setiap widget dengan nilai boolean. Tambahkan definisi berikut ke file WidgetProvider.cs, di dalam namespace Layanan ExampleWidgetProvider , tetapi di luar definisi kelas WidgetProvider .
// WidgetProvider.cs
public class CompactWidgetInfo
{
public string? widgetId { get; set; }
public string? widgetName { get; set; }
public int customState = 0;
public bool isActive = false;
}
Di dalam definisi kelas WidgetProvider dalam WidgetProvider.cs, tambahkan anggota untuk peta yang akan mempertahankan daftar widget yang diaktifkan, menggunakan ID widget sebagai kunci untuk setiap entri.
// WidgetProvider.cs
// Class member of WidgetProvider
public static Dictionary<string, CompactWidgetInfo> RunningWidgets = new Dictionary<string, CompactWidgetInfo>();
Mendeklarasikan string JSON templat widget
Contoh ini akan mendeklarasikan beberapa string statis untuk menentukan templat JSON untuk setiap widget. Untuk kenyamanan, templat ini disimpan dalam variabel anggota kelas WidgetProvider . Jika Anda memerlukan penyimpanan umum untuk templat - mereka dapat disertakan sebagai bagian dari paket aplikasi: Mengakses File Paket. Untuk informasi tentang membuat dokumen JSON templat widget, lihat Membuat templat widget dengan Perancang Kartu Adaptif.
Dalam rilis terbaru, aplikasi yang mengimplementasikan widget Windows dapat menyesuaikan header yang ditampilkan untuk widget mereka di Papan Widget, mengganti presentasi default. Untuk informasi selengkapnya, lihat Kustomisasi area header widget.
Catatan
Dalam rilis terbaru, aplikasi yang mengimplementasikan widget Windows dapat memilih untuk mengisi konten widget dengan HTML yang disajikan dari URL tertentu alih-alih menyediakan konten dalam format skema Kartu Adaptif dalam payload JSON yang diteruskan dari penyedia ke Papan Widget. Penyedia widget masih harus menyediakan payload JSON Kartu Adaptif, sehingga langkah-langkah implementasi dalam panduan ini berlaku untuk widget web. Untuk informasi selengkapnya, lihat penyedia widget Web .
// WidgetProvider.cs
// Class members of WidgetProvider
const string weatherWidgetTemplate = """
{
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.0",
"speak": "<s>The forecast for Seattle January 20 is mostly clear with a High of 51 degrees and Low of 40 degrees</s>",
"backgroundImage": "https://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"
}
""";
Menerapkan metode IWidgetProvider
Di beberapa bagian berikutnya, kita akan menerapkan metode antarmuka IWidgetProvider . Metode pembantu UpdateWidget yang dipanggil dalam beberapa implementasi metode ini akan ditampilkan nanti dalam artikel ini.
Catatan
Objek yang diteruskan ke metode panggilan balik antarmuka IWidgetProvider hanya dijamin valid dalam panggilan balik. Anda tidak boleh menyimpan referensi ke objek ini karena perilakunya di luar konteks panggilan balik tidak terdefinisi.
Buat Widget
Host widget memanggil CreateWidget ketika pengguna telah menyematkan salah satu widget aplikasi Anda di host widget. Pertama, metode ini mendapatkan ID dan nama widget terkait dan menambahkan instans baru dari struktur pembantu kami, CompactWidgetInfo, ke koleksi widget yang diaktifkan. Selanjutnya, kami mengirim templat dan data awal untuk widget, yang dienkapsulasi dalam metode pembantu 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);
}
DeleteWidget
Host widget memanggil DeleteWidget ketika pengguna telah membuka salah satu widget aplikasi Anda dari host widget. Ketika ini terjadi, kami akan menghapus widget terkait dari daftar widget yang diaktifkan sehingga kami tidak mengirim pembaruan lebih lanjut untuk widget tersebut.
// WidgetProvider.cs
public void DeleteWidget(string widgetId, string customState)
{
RunningWidgets.Remove(widgetId);
if(RunningWidgets.Count == 0)
{
emptyWidgetListEvent.Set();
}
}
Untuk contoh ini, selain menghapus widget dengan yang ditentukan dari daftar widget yang diaktifkan, kami juga memeriksa untuk melihat apakah daftar sekarang kosong, dan jika demikian, kami mengatur peristiwa yang akan digunakan nanti untuk memungkinkan aplikasi keluar ketika tidak ada widget yang diaktifkan. Di dalam definisi kelas Anda, tambahkan deklarasi ManualResetEvent dan fungsi aksesor publik.
// WidgetProvider.cs
static ManualResetEvent emptyWidgetListEvent = new ManualResetEvent(false);
public static ManualResetEvent GetEmptyWidgetListEvent()
{
return emptyWidgetListEvent;
}
OnActionInvoked
Host widget memanggil OnActionInvoked saat pengguna berinteraksi dengan tindakan yang Anda tentukan dalam templat widget Anda. Untuk widget penghitung yang digunakan dalam contoh ini, tindakan dideklarasikan dengan nilai kata kerja "inc" dalam templat JSON untuk widget. Kode penyedia widget akan menggunakan nilai kata kerja ini untuk menentukan tindakan apa yang harus diambil sebagai respons terhadap interaksi pengguna.
...
"actions": [
{
"type": "Action.Execute",
"title": "Increment",
"verb": "inc"
}
],
...
Dalam metode OnActionInvoked, dapatkan nilai kata kerja dengan memeriksa properti Kata Kerja widgetActionInvokedArgs yang diteruskan ke metode . Jika kata kerja adalah "inc", maka kita tahu kita akan menaikkan jumlah dalam status kustom untuk widget. Dari WidgetActionInvokedArgs, dapatkan objek WidgetContext lalu WidgetId untuk mendapatkan ID untuk widget yang sedang diperbarui. Temukan entri di peta widget yang diaktifkan dengan ID yang ditentukan lalu perbarui nilai status kustom yang digunakan untuk menyimpan jumlah kenaikan. Terakhir, perbarui konten widget dengan nilai baru dengan fungsi pembantu 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);
}
}
}
Untuk informasi tentang sintaks Action.Execute untuk Kartu Adaptif, lihat Action.Execute. Untuk panduan tentang merancang interaksi untuk widget, lihat Panduan desain interaksi widget
OnWidgetContextChanged (ContextWidgetBerubah)
Dalam rilis saat ini, OnWidgetContextChanged hanya dipanggil ketika pengguna mengubah ukuran widget yang disematkan. Anda dapat memilih untuk mengembalikan templat/data JSON yang berbeda ke host widget tergantung pada ukuran apa yang diminta. Anda juga dapat merancang JSON templat untuk mendukung semua ukuran yang tersedia menggunakan penyajian bersyarar berdasarkan nilai host.widgetSize. Jika Anda tidak perlu mengirim templat atau data baru untuk memperhitungkan perubahan ukuran, Anda dapat menggunakan OnWidgetContextChanged untuk tujuan telemetri.
// 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);
}
}
Mengaktifkan dan Menonaktifkan
Metode Aktifkan dipanggil untuk memberi tahu penyedia widget bahwa host widget saat ini tertarik untuk menerima konten yang diperbarui dari penyedia. Misalnya, itu bisa berarti bahwa pengguna saat ini secara aktif melihat host widget. Metode Nonaktifkan dipanggil untuk memberi tahu penyedia widget bahwa host widget tidak lagi meminta pembaruan konten. Kedua metode ini menentukan jendela di mana host widget paling tertarik untuk menampilkan konten terbaru. Penyedia widget dapat mengirim pembaruan ke widget kapan saja, seperti sebagai respons terhadap pemberitahuan push, tetapi seperti halnya tugas latar belakang apa pun, penting untuk menyeimbangkan penyediaan konten terbaru dengan masalah sumber daya seperti masa pakai baterai.
Aktifkan dan Nonaktifkan dipanggil berdasarkan per widget. Contoh ini melacak status aktif setiap widget di struktur pembantu CompactWidgetInfo . Dalam metode Aktifkan, kami memanggil metode pembantu UpdateWidget untuk memperbarui widget kami. Perhatikan bahwa jendela waktu antara Aktifkan dan Nonaktifkan mungkin kecil, jadi disarankan agar Anda mencoba membuat jalur kode pembaruan widget secepat mungkin.
// 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;
}
}
Memperbarui widget
Tentukan metode pembantu UpdateWidget untuk memperbarui widget yang diaktifkan. Dalam contoh ini, kami memeriksa nama widget di struktur pembantu CompactWidgetInfo yang diteruskan ke metode , lalu mengatur templat dan data JSON yang sesuai berdasarkan widget mana yang sedang diperbarui. WidgetUpdateRequestOptions diinisialisasi dengan templat, data, dan status kustom untuk widget yang sedang diperbarui. Panggil WidgetManager::GetDefault untuk mendapatkan instans kelas WidgetManager lalu panggil UpdateWidget untuk mengirim data widget yang diperbarui ke host widget.
// WidgetProvider.cs
void UpdateWidget(CompactWidgetInfo localWidgetInfo)
{
WidgetUpdateRequestOptions updateOptions = new WidgetUpdateRequestOptions(localWidgetInfo.widgetId);
string? templateJson = null;
if (localWidgetInfo.widgetName == "Weather_Widget")
{
templateJson = weatherWidgetTemplate.ToString();
}
else if (localWidgetInfo.widgetName == "Counting_Widget")
{
templateJson = countWidgetTemplate.ToString();
}
string? dataJson = null;
if (localWidgetInfo.widgetName == "Weather_Widget")
{
dataJson = "{}";
}
else if (localWidgetInfo.widgetName == "Counting_Widget")
{
dataJson = "{ \"count\": " + localWidgetInfo.customState.ToString() + " }";
}
updateOptions.Template = templateJson;
updateOptions.Data = dataJson;
// 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);
}
Menginisialisasi daftar widget yang diaktifkan saat startup
Ketika penyedia widget kami pertama kali diinisialisasi, ada baiknya untuk bertanya kepada WidgetManager apakah ada widget yang sedang berjalan yang saat ini dilayani oleh penyedia kami. Ini akan membantu memulihkan aplikasi ke status sebelumnya jika komputer dimulai ulang atau penyedia crash. Panggil WidgetManager.GetDefault untuk mendapatkan instans manajer widget default untuk aplikasi. Kemudian panggil GetWidgetInfos, yang mengembalikan array objek WidgetInfo . Salin ID widget, nama, dan status kustom ke dalam struktur pembantu CompactWidgetInfo dan simpan ke variabel anggota RunningWidgets . Tempelkan kode berikut ke dalam definisi kelas untuk kelas 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 };
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;
}
}
}
Menerapkan pabrik kelas yang akan membuat widgetProvider berdasarkan permintaan
Agar host widget dapat berkomunikasi dengan penyedia widget kami, kita harus memanggil CoRegisterClassObject. Fungsi ini mengharuskan kita untuk membuat implementasi IClassFactory yang akan membuat objek kelas untuk kelas WidgetProvider kita. Kami akan menerapkan pabrik kelas kami di kelas pembantu mandiri.
Di Visual Studio, klik ExampleWidgetProvider
kanan proyek di Penjelajah Solusi dan pilih Add-Class>.
Dalam dialog Tambahkan kelas, beri nama kelas "FactoryHelper" dan klik Tambahkan.
Ganti konten file FactoryHelper.cs dengan kode berikut. Kode ini mendefinisikan antarmuka IClassFactory dan mengimplementasikan dua metode, CreateInstance dan LockServer. Kode ini adalah boilerplate khas untuk menerapkan pabrik kelas dan tidak spesifik untuk fungsionalitas penyedia widget kecuali bahwa kami menunjukkan bahwa objek kelas yang dibuat mengimplementasikan antarmuka 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;
}
}
Membuat GUID yang mewakili CLSID untuk penyedia widget Anda
Selanjutnya, Anda perlu membuat GUID yang mewakili CLSID yang akan digunakan untuk mengidentifikasi penyedia widget Anda untuk aktivasi COM. Nilai yang sama juga akan digunakan saat mengemas aplikasi Anda. Buat GUID di Visual Studio dengan membuka ALAT-Buat> GUID. Pilih opsi format registri dan klik Salin lalu tempelkan ke dalam file teks sehingga Anda dapat menyalinnya nanti.
Mendaftarkan objek kelas penyedia widget dengan OLE
Dalam file Program.cs untuk executable kami, kami akan memanggil CoRegisterClassObject untuk mendaftarkan penyedia widget kami dengan OLE, sehingga host widget dapat berinteraksi dengannya. Ganti konten Program.cs dengan kode berikut. Kode ini mengimpor fungsi CoRegisterClassObject dan memanggilnya, meneruskan antarmuka WidgetProviderFactory yang kami tentukan pada langkah sebelumnya. Pastikan untuk memperbarui deklarasi variabel CLSID_Factory untuk menggunakan GUID yang Anda buat di langkah sebelumnya.
// 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);
}
Perhatikan bahwa contoh kode ini mengimpor fungsi GetConsoleWindow untuk menentukan apakah aplikasi berjalan sebagai aplikasi konsol, perilaku default untuk panduan ini. Jika fungsi mengembalikan pointer yang valid, kami menulis informasi debug ke konsol. Jika tidak, aplikasi berjalan sebagai aplikasi Windows. Dalam hal ini, kita menunggu peristiwa yang kita tetapkan dalam metode DeleteWidget ketika daftar widget yang diaktifkan kosong, dan kita keluar dari aplikasi. Untuk informasi tentang mengonversi contoh aplikasi konsol ke aplikasi Windows, lihat Mengonversi aplikasi konsol Anda ke aplikasi Windows.
Mengemas aplikasi penyedia widget Anda
Dalam rilis saat ini, hanya aplikasi paket yang dapat didaftarkan sebagai penyedia widget. Langkah-langkah berikut akan membawa Anda melalui proses pengemasan aplikasi Anda dan memperbarui manifes aplikasi untuk mendaftarkan aplikasi Anda dengan OS sebagai penyedia widget.
Membuat proyek pengemasan MSIX
Di Penjelajah Solusi, klik kanan solusi Anda dan pilih Tambahkan> Proyek Baru.... Dalam dialog Tambahkan proyek baru, pilih templat "Proyek Pengemasan Aplikasi Windows" dan klik Berikutnya. Atur nama proyek ke "ExampleWidgetProviderPackage" dan klik Buat. Saat diminta, atur versi target ke versi 1809 atau yang lebih baru dan klik OK. Selanjutnya, klik kanan proyek ExampleWidgetProviderPackage dan pilih > Add-Project. Pilih proyek ExampleWidgetProvider dan klik OK.
Menambahkan referensi paket SDK Aplikasi Windows ke proyek pengemasan
Anda perlu menambahkan referensi ke paket nuget SDK Aplikasi Windows ke proyek kemasan MSIX. Di Penjelajah Solusi, klik dua kali proyek ExampleWidgetProviderPackage untuk membuka file ExampleWidgetProviderPackage.wapproj. Tambahkan xml berikut di dalam elemen Project .
<!--ExampleWidgetProviderPackage.wapproj-->
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.2.221116.1">
<IncludeAssets>build</IncludeAssets>
</PackageReference>
</ItemGroup>
Catatan
Pastikan Versi yang ditentukan dalam elemen PackageReference cocok dengan versi stabil terbaru yang Anda referensikan di langkah sebelumnya.
Jika versi SDK Aplikasi Windows yang benar sudah diinstal di komputer dan Anda tidak ingin membundel runtime SDK dalam paket Anda, Anda dapat menentukan dependensi paket dalam file Package.appxmanifest untuk proyek 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>
...
Memperbarui manifes paket
Di Penjelajah Solusi klik Package.appxmanifest
kanan file dan pilih Tampilkan Kode untuk membuka file xml manifes. Selanjutnya Anda perlu menambahkan beberapa deklarasi namespace layanan untuk ekstensi paket aplikasi yang akan kami gunakan. Tambahkan definisi namespace berikut ke elemen Paket tingkat atas.
<!-- Package.appmanifest -->
<Package
...
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
Di dalam elemen Aplikasi, buat elemen kosong baru bernama Extensions. Pastikan ini muncul setelah tag penutup untuk uap:VisualElements.
<!-- Package.appxmanifest -->
<Application>
...
<Extensions>
</Extensions>
</Application>
Ekstensi pertama yang perlu kita tambahkan adalah ekstensi ComServer . Ini mendaftarkan titik masuk dari executable dengan OS. Ekstensi ini adalah aplikasi paket yang setara dengan mendaftarkan server COM dengan mengatur kunci registri, dan tidak khusus untuk penyedia widget. Tambahkan elemen com:Extension berikut sebagai turunan dari elemen Extensions. Ubah GUID di atribut Id elemen com:Class ke GUID yang Anda buat di langkah sebelumnya.
<!-- 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>
Selanjutnya, tambahkan ekstensi yang mendaftarkan aplikasi sebagai penyedia widget. Tempelkan elemen uap3:Extension dalam cuplikan kode berikut, sebagai turunan dari elemen Extensions. Pastikan untuk mengganti atribut ClassId elemen COM dengan GUID yang Anda gunakan di langkah sebelumnya.
<!-- 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>
Untuk deskripsi terperinci dan informasi format untuk semua elemen ini, lihat Format XML manifes paket penyedia widget.
Menambahkan ikon dan gambar lain ke proyek kemasan Anda
Di Penjelajah Solusi, klik kanan ExampleWidgetProviderPackage Anda dan pilih Tambahkan> Folder Baru. Beri nama folder ini ProviderAssets karena inilah yang digunakan di Package.appxmanifest
dari langkah sebelumnya. Di sinilah kami akan menyimpan Ikon dan Cuplikan Layar untuk widget kami. Setelah Anda menambahkan Ikon dan Cuplikan Layar yang Anda inginkan, pastikan nama gambar cocok dengan apa yang muncul setelah Path=ProviderAssets\ di widget Anda Package.appxmanifest
atau tidak akan muncul di host widget.
Untuk informasi tentang persyaratan desain untuk gambar cuplikan layar dan konvensi penamaan untuk cuplikan layar yang dilokalkan, lihat Mengintegrasikan dengan pemilih widget.
Menguji penyedia widget Anda
Pastikan Anda telah memilih arsitektur yang cocok dengan komputer pengembangan Anda dari menu drop-down Platform Solusi, misalnya "x64". Di Penjelajah Solusi, klik kanan solusi Anda dan pilih Bangun Solusi. Setelah ini selesai, klik kanan ExampleWidgetProviderPackage Anda dan pilih Sebarkan. Dalam rilis saat ini, satu-satunya host widget yang didukung adalah Papan Widget. Untuk melihat widget, Anda harus membuka Papan Widget dan memilih Tambahkan widget di kanan atas. Gulir ke bagian bawah widget yang tersedia dan Anda akan melihat Widget Cuaca tiruan dan Widget Penghitungan Microsoft yang dibuat dalam tutorial ini. Klik widget untuk menyematkannya ke papan widget Anda dan menguji fungsionalitasnya.
Men-debug penyedia widget Anda
Setelah Anda menyematkan widget, Platform Widget akan memulai aplikasi penyedia widget Anda untuk menerima dan mengirim informasi yang relevan tentang widget. Untuk men-debug widget yang sedang berjalan, Anda dapat melampirkan debugger ke aplikasi penyedia widget yang sedang berjalan atau Anda dapat mengatur Visual Studio untuk secara otomatis mulai men-debug proses penyedia widget setelah dimulai.
Untuk melampirkan ke proses yang sedang berjalan:
- Di Visual Studio klik Debug -> Lampirkan ke proses.
- Filter proses dan temukan aplikasi penyedia widget yang Anda inginkan.
- Lampirkan debugger.
Untuk melampirkan debugger secara otomatis ke proses saat awalnya dimulai:
- Di Visual Studio klik Debug -> Target Debug Lainnya -> Debug Paket Aplikasi terinstal.
- Filter paket dan temukan paket penyedia widget yang Anda inginkan.
- Pilih dan centang kotak yang mengatakan Jangan luncurkan, tetapi debug kode saya saat dimulai.
- Klik Lampirkan.
Mengonversi aplikasi konsol Anda ke aplikasi Windows
Untuk mengonversi aplikasi konsol yang dibuat dalam panduan ini ke aplikasi Windows, klik kanan proyek ExampleWidgetProvider di Penjelajah Solusi dan pilih Properti. Di bawah Aplikasi-Umum> ubah jenis Output dari "Aplikasi Konsol" menjadi "Aplikasi Windows".
Menerbitkan widget Anda
Setelah mengembangkan dan menguji widget, Anda dapat menerbitkan aplikasi di Microsoft Store agar pengguna dapat menginstal widget di perangkat mereka. Untuk panduan langkah demi langkah untuk menerbitkan aplikasi, lihat Menerbitkan aplikasi Anda di Microsoft Store.
Koleksi Toko widget
Setelah aplikasi Anda diterbitkan di Microsoft Store, Anda dapat meminta aplikasi Anda untuk disertakan dalam widget Store Collection yang membantu pengguna menemukan aplikasi yang menampilkan Widget Windows. Untuk mengirimkan permintaan Anda, lihat Mengirimkan informasi Widget Anda untuk tambahan ke Koleksi Toko.
Menerapkan kustomisasi widget
Dimulai dengan SDK Aplikasi Windows 1.4, widget dapat mendukung kustomisasi pengguna. Saat fitur ini diimplementasikan, opsi Sesuaikan widget ditambahkan ke menu elipsis di atas opsi Unpin widget .
Langkah-langkah berikut meringkas proses untuk kustomisasi widget.
- Dalam operasi normal, penyedia widget menanggapi permintaan dari host widget dengan templat dan payload data untuk pengalaman widget reguler.
- Pengguna mengklik tombol Sesuaikan widget di menu elipsis.
- Widget meningkatkan peristiwa OnCustomizationRequested pada penyedia widget untuk menunjukkan bahwa pengguna telah meminta pengalaman penyesuaian widget.
- Penyedia widget mengatur bendera internal untuk menunjukkan bahwa widget berada dalam mode kustomisasi. Saat dalam mode kustomisasi, penyedia widget mengirimkan templat JSON untuk UI kustomisasi widget alih-alih UI widget reguler.
- Saat dalam mode kustomisasi, penyedia widget menerima peristiwa OnActionInvoked saat pengguna berinteraksi dengan UI kustomisasi dan menyesuaikan konfigurasi dan perilaku internalnya berdasarkan tindakan pengguna.
- Ketika tindakan yang terkait dengan peristiwa
OnActionInvoked adalah tindakan "kustomisasi keluar" yang ditentukan aplikasi, penyedia widget mengatur ulang bendera internalnya untuk menunjukkan bahwa peristiwa tersebut tidak lagi dalam mode kustomisasi dan melanjutkan pengiriman templat JSON visual dan data untuk pengalaman widget reguler, mencerminkan perubahan yang diminta selama penyesuaian. Pengguna dapat mengakhiri pengalaman penyesuaian tanpa mengklik tindakan keluar dari kustomisasi yang ditentukan oleh aplikasi. Dalam hal ini, IWidgetProviderAnalytics.OnAnalyticsInfoReported akan dinaikkan, dan WidgetAnalyticsInfoReportedArgs.AnalyticsJson akan memiliki interaksi Kind dari "exitCustomization". - Penyedia widget mempertahankan opsi kustomisasi ke disk atau cloud sehingga perubahan dipertahankan di antara pemanggilan penyedia widget.
Catatan
Ada bug yang diketahui dengan Windows Widget Board, untuk widget yang dibangun menggunakan SDK Aplikasi Windows, yang menyebabkan menu elipsis menjadi tidak responsif setelah kartu kustomisasi ditampilkan.
Dalam skenario kustomisasi Widget umum, pengguna akan memilih data apa yang ditampilkan pada widget atau menyesuaikan presentasi visual widget. Untuk kesederhanaan, contoh di bagian ini akan menambahkan perilaku kustomisasi yang memungkinkan pengguna untuk mengatur ulang penghitung widget penghitungan yang diterapkan pada langkah-langkah sebelumnya.
Catatan
Kustomisasi widget hanya didukung di SDK Aplikasi Windows 1.4 dan yang lebih baru. Pastikan Anda memperbarui referensi dalam proyek Anda ke versi terbaru paket Nuget.
Perbarui manifes paket untuk mendeklarasikan dukungan kustomisasi
Untuk memberi tahu host widget bahwa widget mendukung kustomisasi, tambahkan atribut IsCustomizable ke elemen Definisi untuk widget dan atur ke true.
...
<Definition Id="Counting_Widget"
DisplayName="Microsoft Counting Widget"
Description="CONFIG counting widget description"
IsCustomizable="true">
...
Melacak kapan widget berada dalam mode kustomisasi
Contoh dalam artikel ini menggunakan struct pembantu CompactWidgetInfo untuk melacak status widget aktif kami saat ini. Tambahkan bidang inCustomization, yang akan digunakan untuk melacak ketika host widget mengharapkan kami untuk mengirim templat json kustomisasi kami daripada templat widget biasa.
// 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;
}
Menerapkan IWidgetProvider2
Fitur kustomisasi widget diekspos melalui antarmuka IWidgetProvider2 . Perbarui definisi kelas WidgetProvider untuk mengimplementasikan antarmuka ini.
// WidgetProvider.cs
internal class WidgetProvider : IWidgetProvider, IWidgetProvider2
Tambahkan implementasi untuk panggilan balik OnCustomizationRequested antarmuka IWidgetProvider2 . Metode ini menggunakan pola yang sama dengan panggilan balik lain yang telah kami gunakan. Kami mendapatkan ID untuk widget yang akan disesuaikan dari WidgetContext dan menemukan struct pembantu CompactWidgetInfo yang terkait dengan widget tersebut dan mengatur bidang inCustomization ke 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);
}
}
Sekarang, deklarasikan variabel string yang menentukan templat JSON untuk UI kustomisasi widget. Untuk contoh ini, kami memiliki tombol "Reset counter" dan tombol "Exit customization" yang akan memberi sinyal kepada penyedia kami untuk kembali ke perilaku widget reguler. Tempatkan definisi ini di samping definisi templat lainnya.
// 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""
}";
Mengirim templat kustomisasi di UpdateWidget
Selanjutnya, kita akan memperbarui metode pembantu UpdateWidget yang mengirim data dan templat JSON visual ke host widget. Ketika kami memperbarui widget penghitungan, kami mengirim templat widget reguler atau templat kustomisasi tergantung pada nilai bidang inCustomization . Untuk brevity, kode yang tidak relevan dengan kustomisasi dihilangkan dalam cuplikan kode ini.
// 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);
}
Menanggapi tindakan kustomisasi
Ketika pengguna berinteraksi dengan input dalam templat kustomisasi kami, pengguna memanggil handler OnActionInvoked yang sama seperti ketika pengguna berinteraksi dengan pengalaman widget reguler. Untuk mendukung kustomisasi, kami mencari kata kerja "reset" dan "exitCustomization" dari templat JSON kustomisasi kami. Jika tindakannya adalah untuk tombol "Reset penghitung", kami mengatur ulang penghitung yang disimpan di bidang customState dari struktur pembantu kami ke 0. Jika tindakannya adalah untuk tombol "Kustomisasi Keluar", kami mengatur bidang inCustomization ke false sehingga ketika kami memanggil UpdateWidget, metode pembantu kami akan mengirim templat JSON reguler dan bukan templat kustomisasi.
// 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);
}
}
}
Sekarang, saat Anda menyebarkan widget, Anda akan melihat tombol Sesuaikan widget di menu elipsis. Mengklik tombol kustomisasi akan menampilkan templat kustomisasi Anda.
Klik tombol Reset penghitung untuk mengatur ulang penghitung ke 0. Klik tombol Keluar dari kustomisasi untuk kembali ke perilaku reguler widget Anda.
Windows developer