Bagikan melalui


Menerapkan penyedia widget di aplikasi win32 (C++/WinRT)

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.

Cuplikan layar widget cuaca sederhana. Widget menunjukkan beberapa grafik dan data terkait cuaca serta beberapa teks diagnostik yang menggambarkan bahwa templat untuk widget ukuran sedang ditampilkan.

Cuplikan layar widget penghitungan sederhana. Widget menunjukkan string yang berisi nilai numerik untuk ditambahkan dan tombol berlabel Kenaikan, serta beberapa teks diagnostik yang menggambarkan bahwa templat untuk widget ukuran kecil sedang ditampilkan.

Kode sampel dalam artikel ini diadaptasi dari Sampel Widget SDK Aplikasi Windows. Untuk menerapkan penyedia widget menggunakan C#, lihat Menerapkan penyedia widget di aplikasi win32 (C#).

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++/WinRT win32 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 Windows (C++/WinRT). Beri nama proyek baru "ExampleWidgetProvider". Saat diminta, atur versi Windows target untuk aplikasi ke versi 1809 atau yang lebih baru.

Menambahkan referensi ke paket NuGet Pustaka Implementasi SDK Aplikasi Windows dan Windows

Sampel ini menggunakan paket NuGet SDK Aplikasi Windows stabil terbaru. Di Penjelajah Solusi, klik kanan Referensi 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.

Sampel ini juga menggunakan paket NuGet Pustaka Implementasi Windows. Di Penjelajah Solusi, klik kanan Referensi dan pilih Kelola paket NuGet.... Di manajer paket NuGet, pilih tab Telusuri dan cari "Microsoft.Windows.ImplementationLibrary". Pilih versi terbaru di menu drop-down Versi lalu klik Instal.

Dalam file header yang telah dikommpilasikan sebelumnya, pch.h, tambahkan direktif yang disertakan berikut.

//pch.h 
#pragma once
#include <wil/cppwinrt.h>
#include <wil/resource.h>
...
#include <winrt/Microsoft.Windows.Widgets.Providers.h>

Catatan

Anda harus menyertakan header wil/cppwinrt.h terlebih dahulu, sebelum header WinRT.

Untuk menangani mematikan aplikasi penyedia widget dengan benar, kita perlu implementasi kustom winrt::get_module_lock. Kami telah mendeklarasikan metode SignalLocalServerShutdown yang akan didefinisikan dalam file main.cpp kami dan akan mengatur peristiwa yang memberi sinyal aplikasi untuk keluar. Tambahkan kode berikut ke file pch.h Anda, tepat di bawah #pragma once arahan, sebelum yang lain menyertakan.

//pch.h
#include <stdint.h>
#include <combaseapi.h>

// In .exe local servers the class object must not contribute to the module ref count, and use
// winrt::no_module_lock, the other objects must and this is the hook into the C++ WinRT ref counting system
// that enables this.
void SignalLocalServerShutdown();

namespace winrt
{
    inline auto get_module_lock() noexcept
    {
        struct service_lock
        {
            uint32_t operator++() noexcept
            {
                return ::CoAddRefServerProcess();
            }

            uint32_t operator--() noexcept
            {
                const auto ref = ::CoReleaseServerProcess();

                if (ref == 0)
                {
                    SignalLocalServerShutdown();
                }
                return ref;
            }
        };

        return service_lock{};
    }
}


#define WINRT_CUSTOM_MODULE_LOCK

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.

Mendeklarasikan kelas yang mengimplementasikan antarmuka IWidgetProvider

Antarmuka IWidgetProvider mendefinisikan metode yang akan dipanggil host widget untuk memulai operasi dengan penyedia widget. Ganti definisi kelas kosong dalam file WidgetProvider.h dengan kode berikut. Kode ini mendeklarasikan struktur yang mengimplementasikan antarmuka IWidgetProvider dan mendeklarasikan prototipe untuk metode antarmuka.

// WidgetProvider.h
struct WidgetProvider : winrt::implements<WidgetProvider, winrt::Microsoft::Windows::Widgets::Providers::IWidgetProvider>
{
    WidgetProvider();

    /* IWidgetProvider required functions that need to be implemented */
    void CreateWidget(winrt::Microsoft::Windows::Widgets::Providers::WidgetContext widgetContext);
    void DeleteWidget(winrt::hstring const& widgetId, winrt::hstring const& customState);
    void OnActionInvoked(winrt::Microsoft::Windows::Widgets::Providers::WidgetActionInvokedArgs actionInvokedArgs);
    void OnWidgetContextChanged(winrt::Microsoft::Windows::Widgets::Providers::WidgetContextChangedArgs contextChangedArgs);
    void Activate(winrt::Microsoft::Windows::Widgets::Providers::WidgetContext widgetContext);
    void Deactivate(winrt::hstring widgetId);
    /* IWidgetProvider required functions that need to be implemented */

    
};

Selain itu, tambahkan metode privat, UpdateWidget, yang merupakan metode pembantu yang akan mengirim pembaruan dari penyedia kami ke host widget.

// WidgetProvider.h
private: 

void UpdateWidget(CompactWidgetInfo const& localWidgetInfo);

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.h, di atas deklarasi struktur WidgetProvider .

// WidgetProvider.h
struct CompactWidgetInfo
{
    winrt::hstring widgetId;
    winrt::hstring widgetName;
    int customState = 0;
    bool isActive = false;
};

Di dalam deklarasi WidgetProvider di WidgetProvider.h, tambahkan anggota untuk peta yang akan mempertahankan daftar widget yang diaktifkan, menggunakan ID widget sebagai kunci untuk setiap entri.

// WidgetProvider.h
#include <unordered_map>
struct WidgetProvider : winrt::implements<WidgetProvider, winrt::Microsoft::Windows::Widgets::Providers::IWidgetProvider>
{
...
    private:
        ...
        static std::unordered_map<winrt::hstring, CompactWidgetInfo> RunningWidgets;

        

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 lokal yang dideklarasikan di luar definisi 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 Menyesuaikan area header widget.

Catatan

Dimulai dengan Windows Build [TBD - Build number], 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.h
const std::string weatherWidgetTemplate = R"(
{
    "$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 std::string countWidgetTemplate = R"(
{                                                                     
    "type": "AdaptiveCard",                                         
    "body": [                                                         
        {                                                               
            "type": "TextBlock",                                    
            "text": "You have clicked the button ${count} times"    
        },
        {
             "text":"Rendering Only if Medium",
             "type":"TextBlock",
             "$when":"${$host.widgetSize==\"medium\"}"
        },
        {
             "text":"Rendering Only if Small",
             "type":"TextBlock",
             "$when":"${$host.widgetSize==\"small\"}"
        },
        {
         "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. Sebelum menyelami metode antarmuka, tambahkan baris berikut ke WidgetProvider.cpp, setelah direktif sertakan, untuk menarik API penyedia widget ke namespace winrt dan izinkan akses ke peta yang kami deklarasikan di langkah sebelumnya.

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.

// WidgetProvider.cpp
namespace winrt
{
    using namespace Microsoft::Windows::Widgets::Providers;
}

std::unordered_map<winrt::hstring, CompactWidgetInfo> WidgetProvider::RunningWidgets{};

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.cpp
void WidgetProvider::CreateWidget(winrt::WidgetContext widgetContext)
{
    auto widgetId = widgetContext.Id();
    auto widgetName = widgetContext.DefinitionId();
    CompactWidgetInfo runningWidgetInfo{ widgetId, 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.cpp
void WidgetProvider::DeleteWidget(winrt::hstring const& widgetId, winrt::hstring const& customState)
{
    RunningWidgets.erase(widgetId);
}

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.cpp
void WidgetProvider::OnActionInvoked(winrt::WidgetActionInvokedArgs actionInvokedArgs)
{
    auto verb = actionInvokedArgs.Verb();
    if (verb == L"inc")
    {
        auto 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:
        auto data = actionInvokedArgs.Data();
        if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
        {
            auto& localWidgetInfo = iter->second;
            // 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.cpp
void WidgetProvider::OnWidgetContextChanged(winrt::WidgetContextChangedArgs contextChangedArgs)
{
    auto widgetContext = contextChangedArgs.WidgetContext();
    auto widgetId = widgetContext.Id();
    auto widgetSize = widgetContext.Size();
    if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
    {
        auto localWidgetInfo = iter->second;

        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.

void WidgetProvider::Activate(winrt::Microsoft::Windows::Widgets::Providers::WidgetContext widgetContext)
{
    auto widgetId = widgetContext.Id();

    if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
    {
        auto& localWidgetInfo = iter->second;
        localWidgetInfo.isActive = true;

        UpdateWidget(localWidgetInfo);
    }
}

void WidgetProvider::Deactivate(winrt::hstring widgetId)
{
    if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
    {
        auto& localWidgetInfo = iter->second;
        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.cpp
void WidgetProvider::UpdateWidget(CompactWidgetInfo const& localWidgetInfo)
{
    winrt::WidgetUpdateRequestOptions updateOptions{ localWidgetInfo.widgetId };

    winrt::hstring templateJson;
    if (localWidgetInfo.widgetName == L"Weather_Widget")
    {
        templateJson = winrt::to_hstring(weatherWidgetTemplate);
    }
    else if (localWidgetInfo.widgetName == L"Counting_Widget")
    {
        templateJson = winrt::to_hstring(countWidgetTemplate);
    }

    winrt::hstring dataJson;
    if (localWidgetInfo.widgetName == L"Weather_Widget")
    {
        dataJson = L"{}";
    }
    else if (localWidgetInfo.widgetName == L"Counting_Widget")
    {
        dataJson = L"{ \"count\": " + winrt::to_hstring(localWidgetInfo.customState) + L" }";
    }

    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(winrt::to_hstring(localWidgetInfo.customState));
    winrt::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 konstruktor untuk kelas WidgetProvider .

// WidgetProvider.cpp
WidgetProvider::WidgetProvider()
{
    auto runningWidgets = winrt::WidgetManager::GetDefault().GetWidgetInfos();
    for (auto widgetInfo : runningWidgets )
    {
        auto widgetContext = widgetInfo.WidgetContext();
        auto widgetId = widgetContext.Id();
        auto widgetName = widgetContext.DefinitionId();
        auto customState = widgetInfo.CustomState();
        if (RunningWidgets.find(widgetId) == RunningWidgets.end())
        {
            CompactWidgetInfo runningWidgetInfo{ widgetId, 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 = std::stoi(winrt::to_string(customState));
                runningWidgetInfo.customState = count;
            }
            catch (...)
            {

            }
            RunningWidgets[widgetId] = runningWidgetInfo;
        }
    }
}

Mendaftarkan pabrik kelas yang akan membuat widgetProvider berdasarkan permintaan

Tambahkan header yang menentukan kelas WidgetProvider ke menyertakan di bagian atas file aplikasi main.cpp Anda. Kami juga akan menyertakan mutex di sini.

// main.cpp
...
#include "WidgetProvider.h"
#include <mutex>

Deklarasikan peristiwa yang akan memicu aplikasi kami untuk keluar dan fungsi SignalLocalServerShutdown yang akan mengatur peristiwa. Tempelkan kode berikut di main.cpp.

// main.cpp
wil::unique_event g_shudownEvent(wil::EventOptions::None);

void SignalLocalServerShutdown()
{
    g_shudownEvent.SetEvent();
}

Selanjutnya, Anda harus membuat CLSID yang akan digunakan untuk mengidentifikasi penyedia widget Anda untuk aktivasi COM. Buat GUID di Visual Studio dengan membuka ALAT-Buat> GUID. Pilih opsi "static const GUID =" dan klik Salin lalu tempelkan ke dalam main.cpp. Perbarui definisi GUID dengan sintaks C++/WinRT berikut, atur nama variabel GUID widget_provider_clsid. Biarkan versi GUID yang dikomentari karena Anda akan memerlukan format ini nanti, saat mengemas aplikasi Anda.

// main.cpp
...
// {80F4CB41-5758-4493-9180-4FB8D480E3F5}
static constexpr GUID widget_provider_clsid
{
    0x80f4cb41, 0x5758, 0x4493, { 0x91, 0x80, 0x4f, 0xb8, 0xd4, 0x80, 0xe3, 0xf5 }
};

Tambahkan definisi pabrik kelas berikut ke main.cpp. Ini sebagian besar kode boilerplate yang tidak spesifik untuk implementasi penyedia widget. Perhatikan bahwa CoWaitForMultipleObjects menunggu peristiwa pematian kami dipicu sebelum aplikasi keluar.

// main.cpp
template <typename T>
struct SingletonClassFactory : winrt::implements<SingletonClassFactory<T>, IClassFactory>
{
    STDMETHODIMP CreateInstance(
        ::IUnknown* outer,
        GUID const& iid,
        void** result) noexcept final
    {
        *result = nullptr;

        std::unique_lock lock(mutex);

        if (outer)
        {
            return CLASS_E_NOAGGREGATION;
        }

        if (!instance)
        {
            instance = winrt::make<WidgetProvider>();
        }

        return instance.as(iid, result);
    }

    STDMETHODIMP LockServer(BOOL) noexcept final
    {
        return S_OK;
    }

private:
    T instance{ nullptr };
    std::mutex mutex;
};

int main()
{
    winrt::init_apartment();
    wil::unique_com_class_object_cookie widgetProviderFactory;
    auto factory = winrt::make<SingletonClassFactory<winrt::Microsoft::Windows::Widgets::Providers::IWidgetProvider>>();

    winrt::check_hresult(CoRegisterClassObject(
        widget_provider_clsid,
        factory.get(),
        CLSCTX_LOCAL_SERVER,
        REGCLS_MULTIPLEUSE,
        widgetProviderFactory.put()));

    DWORD index{};
    HANDLE events[] = { g_shudownEvent.get() };
    winrt::check_hresult(CoWaitForMultipleObjects(CWMO_DISPATCH_CALLS | CWMO_DISPATCH_WINDOW_MESSAGES,
        INFINITE,
        static_cast<ULONG>(std::size(events)), events, &index));

    return 0;
}

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:

  1. Di Visual Studio klik Debug -> Lampirkan ke proses.
  2. Filter proses dan temukan aplikasi penyedia widget yang Anda inginkan.
  3. Lampirkan debugger.

Untuk melampirkan debugger secara otomatis ke proses saat awalnya dimulai:

  1. Di Visual Studio klik Debug -> Target Debug Lainnya -> Debug Paket Aplikasi terinstal.
  2. Filter paket dan temukan paket penyedia widget yang Anda inginkan.
  3. Pilih dan centang kotak yang mengatakan Jangan luncurkan, tetapi debug kode saya saat dimulai.
  4. Klik Lampirkan.

Mengonversi aplikasi konsol Anda ke aplikasi Windows

Untuk mengonversi aplikasi konsol yang dibuat dalam panduan ini ke aplikasi Windows:

  1. Klik kanan pada proyek ExampleWidgetProvider di Penjelajah Solusi dan pilih Properti. Navigasi ke Linker -> Sistem dan ubah SubSistem dari "Konsol" ke "Windows". Ini juga dapat dilakukan dengan menambahkan <SubSistem>Windows</SubSystem> ke <Link>..</Tautkan> bagian dari .vcxproj.
  2. Di main.cpp, ubah int main() ke int WINAPI wWinMain(_In_ HINSTANCE /*hInstance*/, _In_opt_ HINSTANCE /*hPrevInstance*/, _In_ PWSTR pCmdLine, _In_ int /*nCmdShow*/).

Cuplikan layar memperlihatkan properti proyek penyedia widget C++ dengan jenis output yang diatur ke 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.

Cuplikan layar Microsoft Store memperlihatkan koleksi widget yang memungkinkan pengguna menemukan aplikasi yang menampilkan Widget Windows.

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 .

Cuplikan layar memperlihatkan widget dengan dialog kustomisasi ditampilkan.

Langkah-langkah berikut meringkas proses untuk kustomisasi widget.

  1. Dalam operasi normal, penyedia widget merespons permintaan dari host widget dengan templat JSON visual dan data untuk pengalaman widget reguler.
  2. Pengguna mengklik tombol Sesuaikan widget di menu elipsis.
  3. Widget meningkatkan peristiwa OnCustomizationRequested pada penyedia widget untuk menunjukkan bahwa pengguna telah meminta pengalaman penyesuaian widget.
  4. 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.
  5. Saat dalam mode kustomisasi, penyedia widget menerima peristiwa OnActionInvoked saat pengguna berinteraksi dengan UI kustomisasi dan menyesuaikan konfigurasi dan perilaku internalnya berdasarkan tindakan pengguna.
  6. Ketika tindakan yang terkait dengan peristiwa OnActionInvoked adalah tindakan "kustomisasi keluar" yang ditentukan aplikasi, penyedia widget mengatur ulang bendera internal untuk menunjukkan bahwa itu tidak lagi dalam mode kustomisasi dan melanjutkan pengiriman templat visual dan data JSON untuk pengalaman widget reguler, mencerminkan perubahan yang diminta selama penyesuaian.
  7. 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 Definisi eleent untuk widget dan atur ke true.

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

Memperbarui WidgetProvider.h

Untuk menambahkan dukungan kustomisasi ke widget yang dibuat di langkah-langkah sebelumnya dalam artikel ini, kita perlu memperbarui file header untuk penyedia widget kita, WidgetProvider.h.

Pertama, perbarui definisi CompactWidgetInfo . Struktur pembantu ini membantu kami 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.h
struct CompactWidgetInfo
{
    winrt::hstring widgetId;
    winrt::hstring widgetName;
    int customState = 0;
    bool isActive = false;
    bool inCustomization = false;
};

Perbarui deklarasi WidgetProvider untuk mengimplementasikan antarmuka IWidgetProvider2.

// WidgetProvider.h

struct WidgetProvider : winrt::implements<WidgetProvider, winrt::Microsoft::Windows::Widgets::Providers::IWidgetProvider, winrt::Microsoft::Windows::Widgets::Providers::IWidgetProvider2>

Tambahkan deklarasi untuk panggilan balik OnCustomizationRequested antarmuka IWidgetProvider2 .

// WidgetProvider.h

void OnCustomizationRequested(winrt::Microsoft::Windows::Widgets::Providers::WidgetCustomizationRequestedArgs args);

Terakhir, 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.

// WidgetProvider.h
const std::string countWidgetCustomizationTemplate = R"(
{
    "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"
})";

Memperbarui WidgetProvider.cpp

Sekarang perbarui file WidgetProvider.cpp untuk mengimplementasikan perilaku kustomisasi widget. 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.cpp
void WidgetProvider::OnCustomizationRequested(winrt::WidgetCustomizationRequestedArgs args)
{
    auto widgetId = args.WidgetContext().Id();

    if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
    {
        auto& localWidgetInfo = iter->second;
        localWidgetInfo.inCustomization = true;

        UpdateWidget(localWidgetInfo);
    }
}

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.cpp
void WidgetProvider::UpdateWidget(CompactWidgetInfo const& localWidgetInfo)
{
    ...
    else if (localWidgetInfo.widgetName == L"Counting_Widget")
    {
        if (!localWidgetInfo.inCustomization)
        {
            std::wcout << L" - not in customization " << std::endl;
            templateJson = winrt::to_hstring(countWidgetTemplate);
		}
        else
        {
            std::wcout << L" - in customization " << std::endl;
            templateJson = winrt::to_hstring(countWidgetCustomizationTemplate);
		}
    }
    ...
    
    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(winrt::to_hstring(localWidgetInfo.customState));
    winrt::WidgetManager::GetDefault().UpdateWidget(updateOptions);
}

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.cpp
void WidgetProvider::OnActionInvoked(winrt::WidgetActionInvokedArgs actionInvokedArgs)
{
    auto verb = actionInvokedArgs.Verb();
    if (verb == L"inc")
    {
        auto 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:
        auto data = actionInvokedArgs.Data();
        if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
        {
            auto& localWidgetInfo = iter->second;
            // Increment the count
            localWidgetInfo.customState++;
            UpdateWidget(localWidgetInfo);
        }
    }
    else if (verb == L"reset") 
    {
        auto widgetId = actionInvokedArgs.WidgetContext().Id();
        auto data = actionInvokedArgs.Data();
        if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
        {
            auto& localWidgetInfo = iter->second;
            // Reset the count
            localWidgetInfo.customState = 0;
            localWidgetInfo.inCustomization = false;
            UpdateWidget(localWidgetInfo);
        }
    }
    else if (verb == L"exitCustomization")
    {
        auto 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:
        auto data = actionInvokedArgs.Data();
        if (const auto iter = RunningWidgets.find(widgetId); iter != RunningWidgets.end())
        {
            auto& localWidgetInfo = iter->second;
            // 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.

Cuplikan layar yang menunjukkan UI kustomisasi widget.

Klik tombol Reset penghitung untuk mengatur ulang penghitung ke 0. Klik tombol Keluar dari kustomisasi untuk kembali ke perilaku reguler widget Anda.