Sdílet prostřednictvím


Implementace poskytovatele widgetu v aplikaci win32 (C++/WinRT)

Tento článek vás provede vytvořením jednoduchého zprostředkovatele widgetu, který implementuje rozhraní IWidgetProvider. Metody tohoto rozhraní jsou vyvolány hostitelem widgetu, aby požádal o data, která definují widget, nebo aby poskytovatel widgetu reagoval na akci uživatele na widgetu. Poskytovatelé widgetů můžou podporovat jeden widget nebo více widgetů. V tomto příkladu definujeme dva různé widgety. Jedním widgetem je widget napodobení počasí, který znázorňuje některé možnosti formátování poskytované architekturou Adaptivní karty. Druhý widget předvede akce uživatele a vlastní funkci stavu widgetu tím, že zachová čítač, který se zvýší, kdykoli uživatel klikne na tlačítko zobrazené na widgetu.

snímek obrazovky s jednoduchým widgetem počasí Widget zobrazuje některé grafické objekty a data související s počasím a také některé diagnostické texty, které ilustrují, že se zobrazuje šablona widgetu střední velikosti.

Snímek obrazovky s jednoduchým widgetem pro počítání. Widget zobrazuje řetězec obsahující číselnou hodnotu, která se má zvýšit, a tlačítko označené Přidat, a také nějaký diagnostický text ilustrující zobrazení šablony widgetu malé velikosti.

Tento vzorový kód v tomto článku je přizpůsoben z Ukázky widgetů sady Windows App SDK. Pokud chcete implementovat zprostředkovatele widgetu pomocí jazyka C#, podívejte se na téma Implementace zprostředkovatele widgetu v aplikaci win32 (C#).

Požadavky

  • Vaše zařízení musí mít povolený vývojářský režim. Další informace najdete v tématu Nastavení pro vývojáře.
  • Visual Studio 2022 nebo novější s vývojovou úlohou pro univerzální platformu Windows . Nezapomeňte přidat komponentu pro C++ (v143) z volitelného rozevíracího seznamu.

Vytvoření nové konzolové aplikace C++/WinRT win32

V sadě Visual Studio vytvořte nový projekt. V dialogovém okně Vytvořit nový projekt nastavte filtr jazyka na C++ a filtr platformy na Windows a pak vyberte šablonu projektu Konzolová aplikace systému Windows (C++/WinRT). Pojmenujte nový projekt ExampleWidgetProvider. Po zobrazení výzvy nastavte cílovou verzi Windows aplikace na verzi 1809 nebo novější.

Přidejte odkazy na balíčky NuGet Windows App SDK a Windows Implementation Library

Tato ukázka používá nejnovější stabilní balíček NuGet sady Windows App SDK. V okně Průzkumník řešení klikněte pravým tlačítkem na Reference a vyberte Spravovat balíčky NuGet.... Ve správci balíčků NuGet vyberte kartu Procházet a vyhledejte "Microsoft.WindowsAppSDK". V rozevíracím seznamu Verze vyberte nejnovější stabilní verzi a klikněte na Nainstalovat.

Tato ukázka také používá balíček NuGet knihovny implementace systému Windows. V průzkumníku řešení klikněte pravým tlačítkem na Reference a vyberte Spravovat balíčky NuGet.... Ve správci balíčků NuGet vyberte kartu Procházet a vyhledejte Microsoft.Windows.ImplementationLibrary. V rozevíracím seznamu Verze vyberte nejnovější verzi a potom klikněte na Nainstalovat.

Do souboru předkompilované hlavičky pch.h přidejte následující direktivy include.

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

Poznámka

Před všemi hlavičkami WinRT musíte nejprve zahrnout hlavičku wil/cppwinrt.h.

Abychom mohli správně vypnout aplikaci zprostředkovatele widgetů, potřebujeme vlastní implementaci winrt::get_module_lock. Předem deklarujeme metodu SignalLocalServerShutdown, která se definuje v našem souboru main.cpp a nastaví událost, která signalizuje ukončení aplikace. Do souboru pch.h přidejte následující kód těsně pod direktivu #pragma once, před ostatní include.

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

Přidání třídy WidgetProvider pro zpracování operací widgetu

V sadě Visual Studio klikněte pravým tlačítkem na projekt ExampleWidgetProvider v průzkumníku řešení a vyberte Přidat –>třída. V dialogovém okně Přidat třídu pojmenujte třídu WidgetProvider a klikněte na Přidat.

Deklarace třídy, která implementuje rozhraní IWidgetProvider

Rozhraní IWidgetProvider definuje metody, které hostitel widgetu vyvolá za účelem zahájení operací s poskytovatelem widgetu. Nahraďte definici prázdné třídy v souboru WidgetProvider.h následujícím kódem. Tento kód deklaruje strukturu, která implementuje IWidgetProvider rozhraní a deklaruje prototypy pro metody rozhraní.

// 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 */

    
};

Přidejte také privátní metodu UpdateWidget, což je pomocná metoda, která bude odesílat aktualizace od našeho poskytovatele do hostitele widgetu.

// WidgetProvider.h
private: 

void UpdateWidget(CompactWidgetInfo const& localWidgetInfo);

Příprava ke sledování povolených widgetů

Poskytovatel widgetů může podporovat jeden widget nebo více widgetů. Kdykoli hostitel widgetu zahájí operaci s poskytovatelem widgetu, předá ID k identifikaci widgetu přidruženého k operaci. Každý widget má také přidružený název a hodnotu stavu, kterou lze použít k ukládání vlastních dat. V tomto příkladu deklarujeme jednoduchou pomocnou strukturu pro uložení ID, názvu a dat pro každý připnutý widget. Widgety můžou být také v aktivním stavu, který je popsán v části Aktivovat a deaktivovat níže. Tento stav budeme sledovat pro každý widget s logickou hodnotou. Do souboru WidgetProvider.h přidejte následující definici nad deklaraci struktury WidgetProvider.

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

Uvnitř WidgetProvider deklarace ve WidgetProvider.h přidejte člena mapy, který bude udržovat seznam povolených widgetů pomocí ID widgetu jako klíče pro každou položku.

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

        

Deklarace řetězců JSON šablony widgetu

Tento příklad deklaruje některé statické řetězce, které definují šablony JSON pro každý widget. Pro usnadnění práce jsou tyto šablony uloženy v místních proměnných deklarovaných mimo definici třídy WidgetProvider. Pokud potřebujete obecné úložiště pro šablony – dají se zahrnout jako součást balíčku aplikace: přístup k souborům balíčků. Informace o vytvoření dokumentu JSON šablony widgetu najdete v tématu Vytvoření šablony widgetu pomocí návrháře adaptivních karet.

V nejnovější verzi mohou aplikace, které implementují widgety pro Windows, přizpůsobit záhlaví, jež se zobrazuje pro jejich widget na panelu widgetů, čímž mohou změnit jeho výchozí vzhled. Další informace naleznete v tématu Přizpůsobení oblasti záhlaví widgetu.

Poznámka

V nejnovější verzi se aplikace, které implementují widgety Windows, mohou rozhodnout naplnit obsah widgetu kódem HTML obsluhovaným ze zadané adresy URL místo poskytování obsahu ve formátu schématu adaptivní karty v datové části JSON předané poskytovatelem do panelu widgetů. Poskytovatelé widgetů musí stále poskytovat datovou část JSON adaptivní karty, takže kroky implementace v tomto návodu platí pro webové widgety. Další informace naleznete v tématu poskytovatelé webových widgetů.

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

Implementace metod IWidgetProvider

V následujících několika částech budeme implementovat metody IWidgetProvider rozhraní. Pomocná metoda UpdateWidget, která je volána v několika z těchto implementací metod, se zobrazí dále v tomto článku. Než se ponoříte do metod rozhraní, přidejte následující řádky do WidgetProvider.cpp, za direktivy include, aby se API poskytovatele widgetů přenesla do oboru názvů winrt a umožnila přístup k mapě, kterou jsme deklarovali v předchozím kroku.

Poznámka

Objekty předané do metod zpětného volání rozhraní IWidgetProvider jsou zaručeny jako platné pouze v rámci zpětného volání. Odkazy na tyto objekty byste neměli ukládat, protože jejich chování mimo kontext zpětného volání není definováno.

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

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

VytvořitWidget

Hostitel widgetu volá CreateWidget, když uživatel připnul některý z widgetů vaší aplikace do hostitele widgetu. Nejprve tato metoda získá ID a název přidružené widgetu a přidá novou instanci naší pomocné struktury, CompactWidgetInfo, do kolekce povolených widgetů. Dále odešleme počáteční šablonu a data widgetu, která je zapouzdřená v pomocné metodě UpdateWidget.

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

OdstranitWidget

Hostitel widgetu volá DeleteWidget, když uživatel odepnul některý z widgetů vaší aplikace od hostitele widgetu. Když k tomu dojde, odebereme přidružený widget ze seznamu povolených widgetů, abychom pro tento widget neposílali žádné další aktualizace.

// WidgetProvider.cpp
void WidgetProvider::DeleteWidget(winrt::hstring const& widgetId, winrt::hstring const& customState)
{
    RunningWidgets.erase(widgetId);
}

OnActionInvolat

Hostitel widgetu volá OnActionInvolat, když uživatel pracuje s akcí, kterou jste definovali v šabloně widgetu. U widgetu čítače použitého v tomto příkladu byla akce nastavena s slovesem a hodnotou "inc" v šabloně JSON pro widget. Kód zprostředkovatele widgetu použije tuto hodnotu příkazu k určení akce, která se má provést v reakci na interakci uživatele.

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

V metodě OnActionInvoked získat hodnotu slovesa kontrolou vlastnosti sloveso objektu WidgetActionInvokedArgs předaných do metody. Pokud je sloveso "inc", víme, že v našem vlastním stavu pro widget zvýšíme počet. Z WidgetActionInvokedArgszískejte objekt WidgetContext a potom WidgetId získat ID pro widget, který se aktualizuje. Vyhledejte položku v mapě povolených widgetů se zadaným ID a pak aktualizujte hodnotu vlastního stavu, která se používá k uložení počtu přírůstků. Nakonec aktualizujte obsah widgetu novou hodnotou pomocí UpdateWidget pomocné funkce.

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

Informace o syntaxi Action.Execute pro adaptivní karty najdete v tématu Action.Execute. Pro pokyny ohledně návrhu interakce widgetů si přečtěte Pokyny k návrhu interakce widgetu.

OnWidgetContextChanged

V aktuální verzi se OnWidgetContextChanged volá pouze v případě, že uživatel změní velikost připnutého widgetu. V závislosti na požadované velikosti můžete hostiteli widgetu vrátit jinou šablonu NEBO data JSON. Můžete také navrhnout JSON šablony tak, aby podporovala všechny dostupné velikosti pomocí podmíněného vykreslování na základě hodnoty host.widgetSize. Pokud pro změnu velikosti nepotřebujete odesílat novou šablonu nebo data, můžete pro účely telemetrie použít OnWidgetContextChanged.

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

Aktivace a deaktivace

Volá se metoda Activate, která informuje poskytovatele widgetu, že hostitel widgetu má v současné době zájem o příjem aktualizovaného obsahu od poskytovatele. Může to například znamenat, že uživatel aktuálně aktivně zobrazuje hostitele widgetu. Metoda Deactivate je volána k upozornění poskytovatele widgetu, že hostitel widgetu již nevyžaduje aktualizace obsahu. Tyto dvě metody definují okno, ve kterém má hostitel widgetu největší zájem zobrazit nejaktuálnější up-toobsah. Poskytovateléwidgetch služeb můžou kdykoliv odesílat aktualizace do widgetu, například v reakci na nabízené oznámení, ale stejně jako u jakéhokoli úkolu na pozadí, je důležité vyvážit up-to–date obsah s obavami o prostředky, jako je životnost baterie.

Aktivovat a Deaktivovat jsou volány pro každý widget zvlášť. Tento příklad sleduje aktivní stav jednotlivých widgetů v CompactWidgetInfo pomocné struktury. V metodě Activate voláme metodu pomocníka UpdateWidget pro aktualizaci widgetu. Všimněte si, že časové období mezi Aktivovat a Deaktivovat může být malé, proto doporučujeme, abyste se pokusili co nejrychleji aktualizovat cestu kódu widgetu.

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

Aktualizace widgetu

Definujte metodu pomocné rutiny UpdateWidget pro aktualizaci povoleného widgetu. V tomto příkladu zkontrolujeme název widgetu v CompactWidgetInfo pomocné struktury předané metodě a pak nastavíme odpovídající šablonu a data JSON na základě toho, který widget se aktualizuje. WidgetUpdateRequestOptions je inicializován šablonou, daty a vlastním stavem pro aktualizovaný widget. Pro zavolání WidgetManager::GetDefault pro získání instance třídy WidgetManager a poté zavolejte UpdateWidget, abyste odeslali aktualizovaná data widgetu hostiteli widgetu.

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

Inicializace seznamu povolených widgetů při spuštění

Když je náš poskytovatel widgetu poprvé inicializován, je vhodné se zeptat WidgetManager pokud existují nějaké spuštěné widgety, které náš poskytovatel aktuálně obsluhuje. Pomůže obnovit aplikaci do předchozího stavu v případě restartování počítače nebo chybového ukončení poskytovatele. Pro volání WidgetManager::GetDefault, aby se získala výchozí instance správce widgetů pro aplikaci. Potom zavolejte GetWidgetInfos, které vrátí pole objektů WidgetInfo. Zkopírujte ID widgetu, názvy a vlastní stav do pomocné struktury CompactWidgetInfo a uložte ho do RunningWidgets členské proměnné. Do konstruktoru pro WidgetProvider třídy vložte následující kód.

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

Registrace objektu pro vytváření tříd, který vytvoří instanci WidgetProvideru na vyžádání

Přidejte hlavičku, která definuje třídu WidgetProvider do zahrnutí v horní části souboru main.cpp vaší aplikace. Také budeme zahrnovat mutex zde.

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

Událost, která aktivuje ukončení naší aplikace, a funkce SignalLocalServerShutdown, která tuto událost nastaví. Do main.cpp vložte následující kód.

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

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

Dále budete muset vytvořit CLSID, který se použije k identifikaci vašeho poskytovatele widgetu pro aktivaci modelu COM. Vygenerujte GUID v sadě Visual Studio tak, že přejdete na Tools->Vytvořit GUID. Vyberte možnost Static const GUID =, klikněte na Kopírovat a vložte ji do main.cpp. Aktualizujte definici GUID pomocí následující syntaxe C++/WinRT a nastavte název proměnné GUID widget_provider_clsid. Při balení aplikace ponechte komentářovanou verzi identifikátoru GUID, protože tento formát budete později potřebovat.

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

Do main.cpppřidejte následující definici objektu pro vytváření tříd . Většinou se jedná o často používaný kód, který není specifický pro implementace zprostředkovatele widgetů. Všimněte si, že CoWaitForMultipleObjects čeká na aktivaci události vypnutí před ukončením aplikace.

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

Zabalte svou aplikaci poskytovatele widgetů

V aktuální verzi je možné jako poskytovatele widgetů zaregistrovat jenom zabalené aplikace. Následující kroky vás provedou procesem zabalení aplikace a aktualizací manifestu aplikace pro registraci aplikace v operačním systému jako poskytovatele widgetu.

Vytvoření projektu balení MSIX

V Průzkumníku řešeníklikněte pravým tlačítkem na řešení a vyberte Přidat–>Nový projekt.... V dialogovém okně Přidat nový projekt vyberte šablonu Projekt Windows Application Packaging a klikněte na Další. Nastavte název projektu na ExampleWidgetProviderPackage a klikněte na Vytvořit. Po zobrazení výzvy nastavte cílovou verzi na verzi 1809 nebo novější a klikněte na OK. Dále klikněte pravým tlačítkem myši na projekt s názvem ExampleWidgetProviderPackage a vyberte Add->Project reference. Vyberte projekt ExampleWidgetProvider a klikněte na tlačítko OK.

Přidání odkazu na balíček sady Windows App SDK do projektu balení

Do projektu balíčku MSIX musíte přidat odkaz na balíček NuGet sady Windows App SDK. V Průzkumníku řešenídvojklikněte na projekt ExampleWidgetProviderPackage a otevřete soubor ExampleWidgetProviderPackage.wapproj. Do elementu Project přidejte následující kód XML.

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

Poznámka

Ujistěte se, že verze zadaná v elementu PackageReference odpovídá nejnovější stabilní verzi, na kterou jste odkazovali v předchozím kroku.

Pokud je v počítači už nainstalovaná správná verze sady Windows App SDK a nechcete do balíčku zabalit modul runtime sady SDK, můžete určit závislost balíčku v souboru Package.appxmanifest pro projekt 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>
...

Aktualizace manifestu balíčku

Ve Průzkumníku řešení klikněte pravým tlačítkem na soubor Package.appxmanifest a vyberte možnost Zobrazit kód, abyste otevřeli soubor XML manifestu. Dále je potřeba přidat deklarace oboru názvů pro některá rozšíření balíčků aplikací, které budeme používat. Do elementu nejvyšší úrovně Package přidejte definice následujícího oboru názvů.

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

Uvnitř elementu Application vytvořte nový prázdný prvek s názvem Extensions. Ujistěte se, že toto přijde po uzavírací značce pro uap:VisualElements.

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

    </Extensions>
</Application>

První rozšíření, které potřebujeme přidat, je rozšíření ComServer. Tím se zaregistruje vstupní bod spustitelného souboru v operačním systému. Toto rozšíření je ekvivalentem zabalené aplikace pro registraci serveru COM nastavením klíče registru a není specifické pro poskytovatele widgetů. Přidejte následující prvek com:Extension jako podřízený prvek Extensions. Změňte identifikátor GUID v atributu Id elementu com:Class na identifikátor GUID, který jste vygenerovali v předchozím kroku.

<!-- Package.appxmanifest -->
<Extensions>
    <com:Extension Category="windows.comServer">
        <com:ComServer>
            <com:ExeServer Executable="ExampleWidgetProvider\ExampleWidgetProvider.exe" DisplayName="ExampleWidgetProvider">
                <com:Class Id="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" DisplayName="ExampleWidgetProvider" />
            </com:ExeServer>
        </com:ComServer>
    </com:Extension>
</Extensions>

Dále přidejte rozšíření, které zaregistruje aplikaci jako poskytovatele widgetu. V následujícím úseku kódu vložte prvek uap3:Extension jako podřízený prvek elementu Extensions. Nezapomeňte nahradit atribut ClassId elementu COM identifikátorem GUID, který jste použili v předchozích krocích.

<!-- Package.appxmanifest -->
<Extensions>
    ...
    <uap3:Extension Category="windows.appExtension">
        <uap3:AppExtension Name="com.microsoft.windows.widgets" DisplayName="WidgetTestApp" Id="ContosoWidgetApp" PublicFolder="Public">
            <uap3:Properties>
                <WidgetProvider>
                    <ProviderIcons>
                        <Icon Path="Images\StoreLogo.png" />
                    </ProviderIcons>
                    <Activation>
                        <!-- Apps exports COM interface which implements IWidgetProvider -->
                        <CreateInstance ClassId="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" />
                    </Activation>

                    <TrustedPackageFamilyNames>
                        <TrustedPackageFamilyName>Microsoft.MicrosoftEdge.Stable_8wekyb3d8bbwe</TrustedPackageFamilyName>
                    </TrustedPackageFamilyNames>

                    <Definitions>
                        <Definition Id="Weather_Widget"
                            DisplayName="Weather Widget"
                            Description="Weather Widget Description"
                            AllowMultiple="true">
                            <Capabilities>
                                <Capability>
                                    <Size Name="small" />
                                </Capability>
                                <Capability>
                                    <Size Name="medium" />
                                </Capability>
                                <Capability>
                                    <Size Name="large" />
                                </Capability>
                            </Capabilities>
                            <ThemeResources>
                                <Icons>
                                    <Icon Path="ProviderAssets\Weather_Icon.png" />
                                </Icons>
                                <Screenshots>
                                    <Screenshot Path="ProviderAssets\Weather_Screenshot.png" DisplayAltText="For accessibility" />
                                </Screenshots>
                                <!-- DarkMode and LightMode are optional -->
                                <DarkMode />
                                <LightMode />
                            </ThemeResources>
                        </Definition>
                        <Definition Id="Counting_Widget"
                                DisplayName="Microsoft Counting Widget"
                                Description="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>

Podrobné popisy a informace o formátu pro všechny tyto prvky naleznete v tématu Formát XML manifestu balíčku poskytovatele widgetu.

Přidání ikon a dalších obrázků do projektu balení

V Průzkumníku řešeníklikněte pravým tlačítkem na ExampleWidgetProviderPackage a vyberte Přidat>Nový adresář. Pojmenujte tuto složku ProviderAssets, protože se používá v Package.appxmanifest z předchozího kroku. Tady uložíme naše ikony a snímky obrazovek pro naše widgety. Po přidání požadovaných ikon a snímků obrazovky se ujistěte, že názvy obrázků odpovídají tomu, co následuje po Path=ProviderAssets\ ve vašem Package.appxmanifest nebo se widgety nezobrazí v hostiteli widgetu.

Informace o požadavcích na návrh obrázků snímků obrazovky a konvencích vytváření názvů pro lokalizované snímky obrazovky najdete v tématu Integrace s nástrojem pro výběr widgetu.

Testování poskytovatele widgetů

Ujistěte se, že jste vybrali architekturu, která odpovídá vašemu vývojovému počítači, z rozevíracího seznamu platformy řešení , například x64. V Průzkumníku řešeníklikněte pravým tlačítkem na řešení a vyberte Sestavit řešení. Jakmile je to hotovo, klikněte pravým tlačítkem na ExampleWidgetProviderPackage a vyberte Nasadit. V aktuální verzi je jediným podporovaným hostitelem widgetů panel widgetů. Pokud chcete zobrazit widgety, budete muset otevřít panel widgetů a vybrat Přidat widgety v pravém horním rohu. Posuňte se do dolní části dostupných widgetů a měli byste vidět napodobení widgetu počasí a widgetu Microsoft Counting Widget vytvořené v tomto kurzu. Kliknutím na widgety je připnete na panel widgetů a otestujete jejich funkčnost.

Ladění poskytovatele widgetů

Po připnutí widgetů spustí platforma widgetů aplikaci poskytovatele widgetů, aby mohla přijímat a odesílat relevantní informace o widgetu. Pokud chcete ladit spuštěný widget, můžete buď připojit ladicí program ke spuštěné aplikaci zprostředkovatele widgetů, nebo můžete nastavit visual Studio tak, aby po spuštění automaticky spustilo ladění procesu zprostředkovatele widgetu.

Chcete-li se připojit ke spuštěném procesu:

  1. V sadě Visual Studio klikněte na Ladění –> Připojit k procesu.
  2. Vyfiltrujte procesy a najděte požadovanou aplikaci zprostředkovatele widgetu.
  3. Připojte ladicí program.

Pokud chcete automaticky připojit ladicí program k procesu při počátečním spuštění:

  1. Ve Visual Studiu klikněte na Ladění -> Další cíle ladění -> Ladění nainstalovaného balíčku aplikace.
  2. Vyfiltrujte balíčky a najděte požadovaný balíček zprostředkovatele widgetu.
  3. Zaškrtněte ho a zaškrtněte políčko Nespouštět, ale při spuštění ladit můj kód.
  4. Klepněte na tlačítko Připojit.

Převod konzolové aplikace na aplikaci pro Windows

Převod konzolové aplikace vytvořené v tomto názorném postupu na aplikaci pro Windows:

  1. Klikněte pravým tlačítkem myši na projekt ExampleWidgetProvider v Průzkumník řešení a vyberte Vlastnosti. Přejděte na Linker –> Systém a změňte SubSystem z "Konzola" na "Windows". Tento úkon lze také provést přidáním <SubSystem>Windows</SubSystem> do části <Link>..</Link> souboru .vcxproj.
  2. V main.cpp změňte int main() na int WINAPI wWinMain(_In_ HINSTANCE /*hInstance*/, _In_opt_ HINSTANCE /*hPrevInstance*/, _In_ PWSTR pCmdLine, _In_ int /*nCmdShow*/).

Snímek obrazovky zobrazující vlastnosti projektu poskytovatele widgetů C++ s typem výstupu nastaveným na aplikaci systému Windows

Publikování widgetu

Po vývoji a otestování widgetu můžete aplikaci publikovat v Microsoft Storu, aby si uživatelé nainstalovali widgety na svá zařízení. Podrobné pokyny k publikování aplikace najdete v tématu Publikování aplikace v Microsoft Storu.

Kolekce úložiště widgetů

Po publikování aplikace v Microsoft Storu můžete požádat o zahrnutí aplikace do kolekce widgetů Store, která uživatelům pomáhá zjišťovat aplikace, které obsahují widgety pro Windows. Pokud chcete odeslat svou žádost, přečtěte si Odeslání informací o widgetu pro přidání do kolekce obchodu.

snímek obrazovky Microsoft Storu zobrazující kolekci widgetů, která uživatelům umožňuje zjišťovat aplikace, které obsahují widgety Windows.

Implementace přizpůsobení widgetu

Od sady Windows App SDK 1.4 můžou widgety podporovat přizpůsobení uživatelů. Při implementaci této funkce se do nabídky se třemi tečkami nad možností Odepnout widget přidá možnost Přizpůsobit widget.

snímek obrazovky znázorňující widget se zobrazeným dialogem přizpůsobení

Následující kroky shrnují proces přizpůsobení widgetu.

  1. V normálním provozu poskytovatel widgetu reaguje na požadavky z hostitele widgetu pomocí šablon JSON vizuálu a dat pro běžné prostředí widgetu.
  2. Uživatel klikne na tlačítko Přizpůsobit widget v nabídce s trojtečkou.
  3. Widget vyvolá událost OnCustomizationRequested poskytovateli widgetu, aby naznačil, že uživatel požádal o přizpůsobení widgetu.
  4. Poskytovatel widgetu nastaví interní příznak, který označuje, že widget je v režimu přizpůsobení. V režimu přizpůsobení odesílá poskytovatel widgetů šablony JSON pro uživatelské rozhraní pro přizpůsobení widgetu místo běžného uživatelského rozhraní widgetu.
  5. V režimu přizpůsobení obdrží poskytovatel widgetu OnActionInvoked události, jakmile uživatel pracuje s uživatelským rozhraním pro přizpůsobení a upravuje jeho interní konfiguraci a chování na základě akcí uživatele.
  6. Když je akce přiřazená k události OnActionInvoked definována aplikací jako „ukončení přizpůsobení,“ poskytovatel widgetů resetuje svůj interní příznak, aby označil, že již není v režimu přizpůsobení, a pokračuje v odesílání vizuálních a datových šablon JSON pro běžný provoz widgetů, což odráží změny požadované během přizpůsobení.
  7. Poskytovatel widgetu uchovává možnosti přizpůsobení disku nebo cloudu, aby se změny zachovaly mezi vyvoláním poskytovatele widgetu.

Poznámka

U widgetů vytvořených pomocí sady Windows App SDK existuje známá chyba, která způsobí, že nabídka se třemi tečkami přestane reagovat, jakmile se zobrazí karta přizpůsobení.

V typických scénářích přizpůsobení widgetu uživatel zvolí, jaká data se ve widgetu zobrazí, nebo upraví vizuální prezentaci widgetu. Pro zjednodušení, příklad v této části zavede funkci, která uživateli umožní resetovat čítač počítacího widgetu, který byl implementován v předchozích krocích.

Poznámka

Přizpůsobení widgetu se podporuje jenom v sadě Windows App SDK 1.4 a novějších verzích. Nezapomeňte aktualizovat odkazy v projektu na nejnovější verzi balíčku NuGet.

Aktualizace manifestu balíčku pro deklaraci podpory přizpůsobení

Pokud chcete hostiteli widgetu dát vědět, že widget podporuje přizpůsobení, přidejte atribut IsCustomizable do Definice eleent widgetu a nastavte ho na true.

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

Aktualizace WidgetProvider.h

Pokud chcete přidat podporu přizpůsobení widgetu vytvořeného v předchozích krocích tohoto článku, budeme muset aktualizovat hlavičkový soubor pro našeho poskytovatele widgetu WidgetProvider.h.

Nejprve aktualizujte definici CompactWidgetInfo. Tato pomocná struktura nám pomáhá sledovat aktuální stav aktivních widgetů. Přidejte pole inCustomization, které se použije ke sledování, kdy hostitel widgetů očekává, že místo běžné šablony widgetu odešleme šablonu JSON vlastního nastavení.

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

Aktualizujte deklaraci WidgetProvider tak, aby implementovala rozhraní IWidgetProvider2.

// WidgetProvider.h

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

Přidejte deklaraci pro OnCustomizationRequested zpětné volání rozhraní IWidgetProvider2.

// WidgetProvider.h

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

Nakonec deklarujte proměnnou řetězce, která definuje šablonu JSON pro uživatelské rozhraní pro přizpůsobení widgetu. V tomto příkladu máme tlačítko Resetovat čítač a tlačítko Ukončit vlastní nastavení, které signalizují, že se náš poskytovatel vrátí k běžnému chování widgetu.

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

Aktualizace WidgetProvider.cpp

Teď aktualizujte soubor WidgetProvider.cpp tak, aby implementoval chování přizpůsobení widgetu. Tato metoda používá stejný vzor jako ostatní zpětné volání, které jsme použili. ID widgetu, který se má přizpůsobit, získáme z WidgetContextu a najdeme pomocnou strukturu CompactWidgetInfo přidruženou k danému widgetu a nastavíme pole inCustomization na hodnotu true.

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

V dalším kroku aktualizujeme naši UpdateWidget pomocnou metodu, která odesílá naše data a šablony JSON vizuálu hostiteli widgetů. Při aktualizaci widgetu pro počítání pošleme buď běžnou šablonu widgetu, nebo šablonu přizpůsobení v závislosti na hodnotě inCustomization pole. Pro stručnost není v tomto fragmentu kódu vynechán kód, který není relevantní pro přizpůsobení.

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

Když uživatelé pracují se vstupy v naší šabloně přizpůsobení, volá stejnou OnActionInvoked obslužnou rutinu, jako když uživatel pracuje s běžným prostředím widgetu. Abychom mohli podporovat přizpůsobení, vyhledáme příkazy "reset" a "exitCustomization" z naší šablony JSON pro přizpůsobení. Pokud se jedná o akci pro tlačítko „Resetovat čítač“, resetujeme čítač uložený v poli customState naší pomocné struktury na 0. Pokud je akce pro tlačítko "Ukončit přizpůsobení", nastavíme inCustomization pole false tak, aby při volání UpdateWidget, naše pomocná metoda odešle běžné šablony JSON, a ne šablonu přizpůsobení.

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

Nyní, když nasadíte widget, měli byste v nabídce s elipsou vidět tlačítko Přizpůsobit widget. Kliknutím na tlačítko Přizpůsobit zobrazíte šablonu vlastního nastavení.

Snímek obrazovky s uživatelským rozhraním pro přizpůsobení widgetů

Kliknutím na tlačítko Resetovat počítadlo resetujete počítadlo na 0. Kliknutím na tlačítko Ukončit přizpůsobení se vrátíte k běžnému chování widgetu.