Zpracování událostí pomocí delegátů v C++/WinRT

Důležité

Vyvíjíte pomocí sady Windows App SDK? Kód tohoto článku používá obory názvů UPW (Windows.UI.Xaml). Pokud váš projekt cílí na WinUI 3 (Windows App SDK), nahraďte Microsoft.UI.Xaml (a související Microsoft.UI.* obory názvů) po celou dobu. Úplný přehled mapování najdete v tématu Mapování rozhraní API UWP na Windows App SDK a další podrobnosti v průvodci migrací uživatelského rozhraní.

Toto téma ukazuje, jak zaregistrovat a odvolat delegáty zpracování událostí pomocí C++/WinRT. Událost můžete zpracovat pomocí libovolného standardního objektu podobného funkci jazyka C++.

Note

Informace o instalaci a použití rozšíření C++/WinRT Visual Studio (VSIX) a balíčku NuGet (které společně poskytují podporu šablony projektu a sestavení) najdete v tématu Visual Studio podpora pro C++/WinRT.

Použití sady Visual Studio k přidání obslužné rutiny události

Pohodlný způsob přidání obslužné rutiny události do projektu je pomocí uživatelského rozhraní Návrháře XAML v Visual Studio. Když máte otevřenou stránku XAML v Návrháři XAML, vyberte ovládací prvek, jehož událost chcete zpracovat. Na stránce vlastností tohoto ovládacího prvku klikněte na ikonu blesku a zobrazte seznam všech událostí, které jsou zdrojem tohoto ovládacího prvku. Potom poklikejte na událost, kterou chcete zpracovat; Například OnClicked.

Návrhář XAML přidá do zdrojových souborů odpovídající prototyp funkce obsluhy události (a prázdnou implementaci), které pak můžete nahradit vlastní implementací.

Note

Obslužné rutiny událostí obvykle nemusí být popsané v souboru Midl (.idl). Návrhář XAML tedy do souboru MIDL nepřidává prototypy funkcí pro obsluhu událostí. Přidá je jen do vašich souborů .h a .cpp.

Registrace delegáta pro zpracování události

Jednoduchým příkladem je zpracování události kliknutí na tlačítko. Obvykle se používá kód XAML k registraci členské funkce pro zpracování události, jako je tato.

// MainPage.xaml
<Button x:Name="myButton" Click="ClickHandler">Click Me</Button>
// MainPage.h
void ClickHandler(
    winrt::Windows::Foundation::IInspectable const& sender,
    winrt::Microsoft::UI::Xaml::RoutedEventArgs const& args);

// MainPage.cpp
void MainPage::ClickHandler(
    IInspectable const& /* sender */,
    RoutedEventArgs const& /* args */)
{
    myButton().Content(box_value(L"Clicked"));
}

Výše uvedený kód je převzat z projektu Prázdná aplikace, v balíčku (WinUI 3 v Desktopu) ve Visual Studiu. Kód myButton() volá vygenerovanou funkci přístupového objektu, která vrací tlačítko , které jsme pojmenovali myButton. Pokud změníte x:Name prvek Button , změní se také název vygenerované funkce přístupového objektu.

Note

V tomto případě je zdrojem události (objekt, který vyvolá událost) tlačítkos názvem myButton. A příjemce události (objekt zpracovávající událost) je instance MainPage. Další informace najdete dále v tomto tématu o správě životnosti zdrojů událostí a příjemců událostí.

Místo deklarativního zpracování v kódu můžete imperativní zaregistrovat členovou funkci pro zpracování události. Z následujícího příkladu kódu to nemusí být zřejmé, ale argumentem volání ButtonBase::Click je instance delegáta RoutedEventHandler. V tomto případě používáme přetížení konstruktoru RoutedEventHandler, které přijímá objekt a ukazatel na členskou funkci.

// MainPage.cpp
MainPage::MainPage()
{
    InitializeComponent();

    myButton().Click({ this, &MainPage::ClickHandler });
}

Důležité

Při registraci delegáta předá výše uvedený příklad kódu nezpracovaný tento ukazatel (odkazující na aktuální objekt). Informace o tom, jak vytvořit silnou nebo slabou referenci na aktuální objekt, najdete v části Používáte-li členskou funkci jako delegáta.

Tady je příklad, který používá statickou členskou funkci; poznamenejte si jednodušší syntaxi.

// MainPage.h
static void ClickHandler(
    winrt::Windows::Foundation::IInspectable const& sender,
    winrt::Microsoft::UI::Xaml::RoutedEventArgs const& args);

// MainPage.cpp
MainPage::MainPage()
{
    InitializeComponent();

    myButton().Click( MainPage::ClickHandler );
}
void MainPage::ClickHandler(
    IInspectable const& /* sender */,
    RoutedEventArgs const& /* args */) { ... }

Existují další způsoby vytvoření RoutedEventHandler. Níže je blok syntaxe převzatý z tématu dokumentace pro RoutedEventHandler (v rozevíracím seznamu Jazyk v pravém horním rohu webové stránky zvolte C++/WinRT). Všimněte si různých konstruktorů: jeden přijímá lambdu, další samostatnou funkci a ještě další (ten, který jsme použili výše) přijímá objekt a ukazatel na členskou funkci.

struct RoutedEventHandler : winrt::Windows::Foundation::IUnknown
{
    RoutedEventHandler(std::nullptr_t = nullptr) noexcept;
    template <typename L> RoutedEventHandler(L lambda);
    template <typename F> RoutedEventHandler(F* function);
    template <typename O, typename M> RoutedEventHandler(O* object, M method);
    /* ... other constructors ... */
    void operator()(winrt::Windows::Foundation::IInspectable const& sender,
        winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e) const;
};

Je také užitečné podívat se na syntaxi operátoru volání funkce. Řekne vám, jaké parametry delegáta musí být. Jak vidíte, v tomto případě syntaxe operátoru volání funkce odpovídá parametrům naší MainPage::ClickHandler.

Note

Pokud chcete zjistit podrobnosti o delegátovi a parametry delegáta, přejděte nejprve do tématu dokumentace pro samotnou událost. Podívejme se na příklad události UIElement.KeyDown . Přejděte na toto téma a v rozevíracím seznamu Jazyk zvolte C++/WinRT. Na začátku tématu v bloku se syntaxí uvidíte toto.

// Register
event_token KeyDown(KeyEventHandler const& handler) const;

Tyto informace nám říkají, že událost UIElement.KeyDown (téma, které používáme) má typ delegáta KeyEventHandler, protože to je typ, který předáte při registraci delegáta s tímto typem události. Takže teď postupujte podle odkazu na téma s tímto typem delegáta KeyEventHandler . Blok syntaxe zde obsahuje operátor volání funkce. A jak už bylo zmíněno výše, to vám říká, jaké parametry musí mít váš delegát.

void operator()(
  winrt::Windows::Foundation::IInspectable const& sender,
  winrt::Microsoft::UI::Xaml::Input::KeyRoutedEventArgs const& e) const;

Jak vidíte, delegát musí být deklarován tak, aby jako odesílatele přijímal IInspectable a jako argumenty instanci třídy KeyRoutedEventArgs.

Uveďme si další příklad: podívejme se na událost Popup.Closed. Jeho typ delegáta je EventHandler<IInspectable>. Váš delegát tedy jako odesílatele vezme IInspectable a další IInspectable (protože to je parametr typu EventHandler ) jako args.

Pokud vaše obslužná funkce události neprovádí mnoho činností, můžete místo členské funkce použít lambda výraz. Z níže uvedeného příkladu kódu to nemusí být zřejmé, ale delegát RoutedEventHandler je vytvořen z funkce lambda, která musí znovu odpovídat syntaxi operátoru volání funkce, který jsme probrali výše.

MainPage::MainPage()
{
    InitializeComponent();

    myButton().Click([this](IInspectable const& /* sender */, RoutedEventArgs const& /* args */)
    {
        myButton().Content(box_value(L"Clicked"));
    });
}

Při vytváření delegáta můžete být o něco explicitnější. Pokud ho například chcete předat nebo ho použít vícekrát.

MainPage::MainPage()
{
    InitializeComponent();

    auto click_handler = [](IInspectable const& sender, RoutedEventArgs const& /* args */)
    {
        sender.as<winrt::Microsoft::UI::Xaml::Controls::Button>().Content(box_value(L"Clicked"));
    };
    myButton().Click(click_handler);
    AnotherButton().Click(click_handler);
}

Odvolání registrovaného delegáta

Když zaregistrujete delegáta, obvykle se vám vrátí token. Tento token můžete následně použít k odvolání svého delegáta; to znamená, že delegát je z události zrušený a nebude volán, pokud bude událost znovu vyvolána.

Kvůli jednoduchosti žádný z výše uvedených příkladů kódu neukazil, jak to udělat. Tento další příklad kódu však uloží token do soukromého datového členu struktury a v destruktoru zruší obslužnou rutinu tohoto tokenu.

struct Example : ExampleT<Example>
{
    Example(winrt::Microsoft::UI::Xaml::Controls::Button const& button) : m_button(button)
    {
        m_token = m_button.Click([this](IInspectable const&, RoutedEventArgs const&)
        {
            // ...
        });
    }
    ~Example()
    {
        m_button.Click(m_token);
    }

private:
    winrt::Microsoft::UI::Xaml::Controls::Button m_button;
    winrt::event_token m_token;
};

Místo silného odkazu, jako v příkladu výše, můžete uložit slabý odkaz na tlačítko (viz Silné a slabé odkazy v C++/WinRT).

Note

Když zdroj událostí vyvolá události synchronně, můžete odvolat obslužnou rutinu a mít jistotu, že nebudete dostávat žádné další události. Ale u asynchronních událostí, a to i po odvolání (a zejména při odvolání uvnitř destruktoru), může událost v letu dosáhnout objektu po zahájení destrukce. Nalezení místa pro odregistraci před zánikem může tento problém zmírnit; robustní řešení však najdete v části Bezpečný přístup k ukazateli this pomocí delegáta pro zpracování událostí.

Případně můžete při registraci delegáta zadat winrt::auto_revoke (což je hodnota typu winrt::auto_revoke_t) a požádat o odvolání události (typu winrt::event_revoker). Odvolávač události za vás uchovává slabý odkaz na zdroj události (objekt, který událost vyvolává). Registraci můžete ručně zrušit voláním členské funkce event_revoker::revoke; objekt event revoker však tuto funkci zavolá automaticky, když opustí rozsah platnosti. Funkce revoke ověří, zda zdroj události stále existuje, a pokud ano, zruší registraci vašeho delegáta. V tomto příkladu není nutné ukládat zdroj událostí a nepotřebujete destruktor.

struct Example : ExampleT<Example>
{
    Example(winrt::Microsoft::UI::Xaml::Controls::Button button)
    {
        m_event_revoker = button.Click(
            winrt::auto_revoke,
            [this](IInspectable const& /* sender */,
            RoutedEventArgs const& /* args */)
        {
            // ...
        });
    }

private:
    winrt::Microsoft::UI::Xaml::Controls::Button::Click_revoker m_event_revoker;
};

Níže je blok syntaxe převzatý z tématu dokumentace pro událost ButtonBase::Click . Zobrazuje tři různé funkce registrace a odvolání. Z třetího přetížení můžete přesně zjistit, jaký typ odvolávače události potřebujete deklarovat. A můžete předat stejné druhy delegátů jak do registru , tak do odvolání s event_revoker přetížení.

// Register
winrt::event_token Click(winrt::Microsoft::UI::Xaml::RoutedEventHandler const& handler) const;

// Revoke with event_token
void Click(winrt::event_token const& token) const;

// Revoke with event_revoker
Button::Click_revoker Click(winrt::auto_revoke_t,
    winrt::Microsoft::UI::Xaml::RoutedEventHandler const& handler) const;

Note

V příkladu kódu výše Button::Click_revoker je alias typu pro winrt::event_revoker<winrt::Microsoft::UI::Xaml::Controls::Primitives::IButtonBase>. Podobný vzor platí pro všechny události C++/WinRT. Každá událost prostředí Windows Runtime má přetíženou verzi funkce revoke, která vrací odvolávač události, přičemž typ tohoto odvolávače je členem třídy zdroje události. Pokud tedy chcete vzít další příklad, window::SizeChanged událost má přetížení registrační funkce, která vrací hodnotu typu Window::SizeChanged_revoker.

V případě navigace na stránce můžete zvážit odvolání obslužných rutin. Pokud opakovaně přejdete na stránku a pak se vrátíte zpět, můžete odvolat všechny obslužné rutiny, když přejdete mimo stránku. Případně pokud používáte stejnou instanci stránky, zkontrolujte hodnotu tokenu a zaregistrujte ji jenom v případě, že ještě není nastavená (if (!m_token){ ... }). Třetí možností je uložení odvolávání událostí na stránce jako datového člena. Čtvrtou možností, jak je popsáno dále v tomto tématu, je zachycení silného nebo slabého odkazu na tento objekt ve funkci lambda.

Pokud se váš delegát automatického odvolání nezaregistruje

Pokud se pokusíte zadat winrt::auto_revoke při registraci delegáta a výsledkem je winrt::hresult_no_interface výjimka, to obvykle znamená, že zdroj události nepodporuje slabé odkazy. To je například běžná situace v oboru názvů Microsoft.UI.Composition. V takovém případě nemůžete použít funkci automatického odvolání. Budete se muset uchýlit k ručnímu odebrání obslužných funkcí událostí.

Typy delegátů pro asynchronní akce a operace

Výše uvedené příklady používají typ delegáta RoutedEventHandler , ale existuje samozřejmě mnoho dalších typů delegátů. Například asynchronní akce a operace (s průběhem a bez pokroku) se dokončily a/nebo průběhové události, které očekávají delegáty odpovídajícího typu. Například událost průběhu asynchronní operace s průběhem (což je vše, co implementuje IAsyncOperationWithProgress) vyžaduje delegáta typu AsyncOperationProgressHandler. Tady je příklad kódu pro vytvoření delegáta tohoto typu pomocí funkce lambda. Příklad také ukazuje, jak vytvořit AsyncOperationWithProgressCompletedHandler delegát.

#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Web.Syndication.h>

using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Web::Syndication;

void ProcessFeedAsync()
{
    Uri rssFeedUri{ L"https://blogs.windows.com/feed" };
    SyndicationClient syndicationClient;

    auto async_op_with_progress = syndicationClient.RetrieveFeedAsync(rssFeedUri);

    async_op_with_progress.Progress(
        [](
            IAsyncOperationWithProgress<SyndicationFeed,
            RetrievalProgress> const& /* sender */,
            RetrievalProgress const& args)
        {
            uint32_t bytes_retrieved = args.BytesRetrieved;
            // use bytes_retrieved;
        });

    async_op_with_progress.Completed(
        [](
            IAsyncOperationWithProgress<SyndicationFeed,
            RetrievalProgress> const& sender,
            AsyncStatus const /* asyncStatus */)
        {
            SyndicationFeed syndicationFeed = sender.GetResults();
            // use syndicationFeed;
        });

    // or (but this function must then be a coroutine, and return IAsyncAction)
    // SyndicationFeed syndicationFeed{ co_await async_op_with_progress };
}

Jak naznačuje výše uvedený komentář „coroutine“, místo použití delegáta s událostmi signalizujícími dokončení asynchronních akcí a operací pro vás bude pravděpodobně přirozenější používat korutiny. Podrobnosti a příklady kódu najdete v tématu Souběžnost a asynchronní operace s C++/WinRT.

Note

Není správné implementovat více než jednu obslužnou rutinu dokončení pro asynchronní akci nebo operaci. Můžete mít buď jednoho delegáta pro dokončenou událost, nebo můžete co_await . Pokud máte obě, druhý selže.

Pokud místo korutiny zůstanete u delegátů, můžete zvolit jednodušší syntaxi.

async_op_with_progress.Completed(
    [](auto&& /*sender*/, AsyncStatus const /* args */)
{
    // ...
});

Typy delegátů, které vracejí hodnotu

Některé typy delegátů musí samy vrátit hodnotu. Příkladem je ListViewItemToKeyHandler, který vrací řetězec. Tady je příklad vytvoření delegáta tohoto typu (všimněte si, že funkce lambda vrací hodnotu).

using namespace winrt::Microsoft::UI::Xaml::Controls;

winrt::hstring f(ListView listview)
{
    return ListViewPersistenceHelper::GetRelativeScrollPosition(listview, [](IInspectable const& item)
    {
        return L"key for item goes here";
    });
}

Bezpečný přístup k tomuto ukazateli pomocí delegáta zpracování událostí

Pokud zpracováváte událost pomocí členské funkce objektu nebo z funkce lambda uvnitř členské funkce objektu, musíte se zamyslet nad relativní životností příjemce události (objekt zpracovávající událost) a zdrojem události (objektem, který vyvolává událost). Další informace a příklady kódu najdete v tématu Silné a slabé odkazy v jazyce C++/WinRT.

Důležitá rozhraní API