Vytváření událostí v C++/WinRT

Toto téma navazuje na komponentu prostředí Windows Runtime a klientskou aplikaci, jejichž vytvoření popisuje téma prostředí Windows Runtime components with C++/WinRT.

Tady jsou nové funkce, které toto téma přidává.

  • Aktualizujte běhovou třídu teploměru tak, aby vyvolala událost, když její teplota klesne pod bod mrazu.
  • Aktualizujte aplikaci Core, která využívá runtime třídu teploměru, aby tuto událost zpracovávala.

Note

Informace o instalaci a používání 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 jazyka C++/WinRT.

Důležité

Základní koncepty a termíny, které podporují pochopení toho, jak využívat a vytvářet třídy modulu runtime pomocí C++/WinRT, najdete v tématu Využívání rozhraní API s rozhraními C++/WinRT a Author API s C++/WinRT.

Vytvořte ThermometerWRC a ThermometerCoreApp

Pokud chcete postupovat podle aktualizací zobrazených v tomto tématu, abyste mohli sestavit a spustit kód, pak prvním krokem je postupovat podle návodu v prostředí Windows Runtime komponentách s tématem C++/WinRT. Uděláte to tak, že budete mít komponentu TeploměrWRC prostředí Windows Runtime a aplikaci TeploměrCoreApp Core, která ji spotřebovává.

Aktualizovat ThermometerWRC tak, aby vyvolal událost

Aktualizujte Thermometer.idl, aby odpovídal následujícímu výpisu. Tímto způsobem deklarujte událost, jejíž typ delegáta je EventHandler s argumentem čísla s plovoucí desetinnou čárkou s jednoduchou přesností.

// Thermometer.idl
namespace ThermometerWRC
{
    runtimeclass Thermometer
    {
        Thermometer();
        void AdjustTemperature(Single deltaFahrenheit);
        event Windows.Foundation.EventHandler<Single> TemperatureIsBelowFreezing;
    };
}

Uložte soubor. Projekt se ve svém aktuálním stavu nepodaří sestavit až do konce, ale přesto jej nyní sestavte, aby se vygenerovaly aktualizované verze souborů stub \ThermometerWRC\ThermometerWRC\Generated Files\sources\Thermometer.h a Thermometer.cpp. V těchto souborech nyní můžete vidět zástupné implementace události TemperatureIsBelowFreezing. V jazyce C++/WinRT se deklarovaná událost IDL implementuje jako sada přetížených funkcí (podobně jako vlastnost je implementována jako dvojice přetížených funkcí get a set). Jedna z přetížených verzí přijímá delegáta, který se má zaregistrovat, a vrací token (tj. winrt::event_token). Druhý z nich převezme token a zruší registraci příslušného delegáta.

Nyní otevřete Thermometer.h a Thermometer.cpp, a aktualizujte implementaci běhové třídy Thermometer. Do Thermometer.h přidejte dvě přetížené funkce TemperatureIsBelowFreezing a také soukromý datový člen události, který se použije při implementaci těchto funkcí.

// Thermometer.h
...
namespace winrt::ThermometerWRC::implementation
{
    struct Thermometer : ThermometerT<Thermometer>
    {
        ...
        winrt::event_token TemperatureIsBelowFreezing(Windows::Foundation::EventHandler<float> const& handler);
        void TemperatureIsBelowFreezing(winrt::event_token const& token) noexcept;

    private:
        winrt::event<Windows::Foundation::EventHandler<float>> m_temperatureIsBelowFreezingEvent;
        ...
    };
}
...

Jak vidíte výše, událost je reprezentována šablonou struktury winrt::event parametrizovaným konkrétním typem delegáta (který sám může být parametrizován typem args).

V Thermometer.cpp implementujte dvě přetížené funkce TemperatureIsBelowFreezing.

// Thermometer.cpp
...
namespace winrt::ThermometerWRC::implementation
{
    winrt::event_token Thermometer::TemperatureIsBelowFreezing(Windows::Foundation::EventHandler<float> const& handler)
    {
        return m_temperatureIsBelowFreezingEvent.add(handler);
    }

    void Thermometer::TemperatureIsBelowFreezing(winrt::event_token const& token) noexcept
    {
        m_temperatureIsBelowFreezingEvent.remove(token);
    }

    void Thermometer::AdjustTemperature(float deltaFahrenheit)
    {
        m_temperatureFahrenheit += deltaFahrenheit;
        if (m_temperatureFahrenheit < 32.f) m_temperatureIsBelowFreezingEvent(*this, m_temperatureFahrenheit);
    }
}

Note

Podrobnosti o tom, co je automatický odvolávač událostí, naleznete v tématu Odvolání registrovaného delegáta. Implementaci automatického odvolávání událostí získáte zdarma pro vaši událost. Jinými slovy, nemusíte implementovat přetížení pro odvolávání událostí – to vám poskytne projekce C++/WinRT.

Ostatní přetížení (přetížení pro registraci a ruční odvolání) nejsou zahrnuta do projekce. To vám poskytne flexibilitu při optimální implementaci pro váš scénář. Volání event::add a event::remove, jak je ukázáno v těchto implementacích, je efektivní a ve výchozím nastavení bezpečné z hlediska souběhu i přístupu z více vláken. Pokud ale máte velmi velký počet událostí, možná nebudete chtít pole události pro každou z nich, ale raději zvolit nějaký druh řídké implementace.

Můžete také vidět výše, že implementace funkce AdjustTemperature byla aktualizována tak, aby vyvolala temperatureIsBelowFreezing událost, pokud teplota klesne pod mraz.

Aktualizace aplikace TeploměrCoreApp pro zpracování události

V projektu ThermometerCoreApp v souboru App.cpp proveďte v kódu následující změny, abyste zaregistrovali obslužnou rutinu události, a potom zajistěte, aby teplota klesla pod bod mrazu.

WINRT_ASSERT je definice makra a rozbalí se na _ASSERTE.

struct App : implements<App, IFrameworkViewSource, IFrameworkView>
{
    winrt::event_token m_eventToken;
    ...
    
    void Initialize(CoreApplicationView const &)
    {
        m_eventToken = m_thermometer.TemperatureIsBelowFreezing([](const auto &, float temperatureFahrenheit)
        {
            WINRT_ASSERT(temperatureFahrenheit < 32.f); // Put a breakpoint here.
        });
    }
    ...

    void Uninitialize()
    {
        m_thermometer.TemperatureIsBelowFreezing(m_eventToken);
    }
    ...
    
    void OnPointerPressed(IInspectable const &, PointerEventArgs const & args)
    {
        m_thermometer.AdjustTemperature(-1.f);
        ...
    }
    ...
};

Uvědomte si změnu metody OnPointerPressed . Když teď kliknete na okno, odečtete 1 stupeň Fahrenheita od teploty teploměru. Aplikace nyní zpracovává událost, která je vyvolána, když teplota klesne pod bod mrazu. Abychom si ukázali, že událost je vyvolána podle očekávání, vložte zarážku do výrazu lambda, který zpracovává událost TemperatureIsBelowFreezing , spusťte aplikaci a klikněte do okna.

Parametrizované delegáty napříč ABI

Pokud musí být vaše událost přístupná v binárním rozhraní aplikace (ABI), například mezi komponentou a její aplikací, musí událost používat typ delegáta prostředí Windows Runtime. Výše uvedený příklad používá typ delegáta Windows::Foundation::EventHandler<T> prostředí Windows Runtime. TypedEventHandler<TSender, TResult> je dalším příkladem typu delegáta prostředí Windows Runtime.

Parametry typu těchto dvou typů delegátů musí procházet přes ABI, takže i tyto parametry typu musí být typy prostředí Windows Runtime. To zahrnuje třídy modulu runtime Windows, třídy modulu runtime třetích stran a primitivní typy, jako jsou čísla a řetězce. Kompilátor vám pomůže s chybou typu T musí být typu WinRT, pokud toto omezení zapomenete.

Níže je příklad ve formě výpisů kódu. Začněte s projekty TeploměrWRC a TeploměrCoreApp , které jste vytvořili dříve v tomto tématu, a upravte kód v těchto projektech tak, aby vypadal jako kód v těchto výpisech.

Tento první výpis je určen pro projekt TeploměrWRC . Po úpravách ThermometerWRC.idl , jak je znázorněno níže, sestavte projekt a pak ho zkopírujte MyEventArgs.h.cpp a vložte do projektu (ze Generated Files složky), stejně jako jste to udělali dříve s Thermometer.h a .cpp. Nezapomeňte odstranit static_assert z obou souborů.

// ThermometerWRC.idl
namespace ThermometerWRC
{
    [default_interface]
    runtimeclass MyEventArgs
    {
        Single TemperatureFahrenheit{ get; };
    }

    [default_interface]
    runtimeclass Thermometer
    {
        ...
        event Windows.Foundation.EventHandler<ThermometerWRC.MyEventArgs> TemperatureIsBelowFreezing;
        ...
    };
}

// MyEventArgs.h
#pragma once
#include "MyEventArgs.g.h"

namespace winrt::ThermometerWRC::implementation
{
    struct MyEventArgs : MyEventArgsT<MyEventArgs>
    {
        MyEventArgs() = default;
        MyEventArgs(float temperatureFahrenheit);
        float TemperatureFahrenheit();

    private:
        float m_temperatureFahrenheit{ 0.f };
    };
}

// MyEventArgs.cpp
#include "pch.h"
#include "MyEventArgs.h"
#include "MyEventArgs.g.cpp"

namespace winrt::ThermometerWRC::implementation
{
    MyEventArgs::MyEventArgs(float temperatureFahrenheit) : m_temperatureFahrenheit(temperatureFahrenheit)
    {
    }

    float MyEventArgs::TemperatureFahrenheit()
    {
        return m_temperatureFahrenheit;
    }
}

// Thermometer.h
...
struct Thermometer : ThermometerT<Thermometer>
{
...
    winrt::event_token TemperatureIsBelowFreezing(Windows::Foundation::EventHandler<ThermometerWRC::MyEventArgs> const& handler);
...
private:
    winrt::event<Windows::Foundation::EventHandler<ThermometerWRC::MyEventArgs>> m_temperatureIsBelowFreezingEvent;
...
}
...

// Thermometer.cpp
#include "MyEventArgs.h"
...
winrt::event_token Thermometer::TemperatureIsBelowFreezing(Windows::Foundation::EventHandler<ThermometerWRC::MyEventArgs> const& handler) { ... }
...
void Thermometer::AdjustTemperature(float deltaFahrenheit)
{
    m_temperatureFahrenheit += deltaFahrenheit;

    if (m_temperatureFahrenheit < 32.f)
    {
        auto args = winrt::make_self<winrt::ThermometerWRC::implementation::MyEventArgs>(m_temperatureFahrenheit);
        m_temperatureIsBelowFreezingEvent(*this, *args);
    }
}
...

Tento výpis je určený pro projekt TeploměrCoreApp .

// App.cpp
...
void Initialize(CoreApplicationView const&)
{
    m_eventToken = m_thermometer.TemperatureIsBelowFreezing([](const auto&, ThermometerWRC::MyEventArgs args)
    {
        float degrees = args.TemperatureFahrenheit();
        WINRT_ASSERT(degrees < 32.f); // Put a breakpoint here.
    });
}
...

Jednoduché signály napříč ABI

Pokud u události nepotřebujete předávat žádné parametry nebo argumenty, můžete definovat vlastní jednoduchý typ delegáta prostředí Windows Runtime. Následující příklad ukazuje jednodušší verzi třídy runtime Thermometer. Deklaruje typ delegáta s názvem SignalDelegate a pak používá k vyvolání události typu signálu místo události s parametrem.

// ThermometerWRC.idl
namespace ThermometerWRC
{
    delegate void SignalDelegate();

    runtimeclass Thermometer
    {
        Thermometer();
        event ThermometerWRC.SignalDelegate SignalTemperatureIsBelowFreezing;
        void AdjustTemperature(Single value);
    };
}
// Thermometer.h
...
namespace winrt::ThermometerWRC::implementation
{
    struct Thermometer : ThermometerT<Thermometer>
    {
        ...

        winrt::event_token SignalTemperatureIsBelowFreezing(ThermometerWRC::SignalDelegate const& handler);
        void SignalTemperatureIsBelowFreezing(winrt::event_token const& token);
        void AdjustTemperature(float deltaFahrenheit);

    private:
        winrt::event<ThermometerWRC::SignalDelegate> m_signal;
        float m_temperatureFahrenheit{ 0.f };
    };
}
// Thermometer.cpp
...
namespace winrt::ThermometerWRC::implementation
{
    winrt::event_token Thermometer::SignalTemperatureIsBelowFreezing(ThermometerWRC::SignalDelegate const& handler)
    {
        return m_signal.add(handler);
    }

    void Thermometer::SignalTemperatureIsBelowFreezing(winrt::event_token const& token)
    {
        m_signal.remove(token);
    }

    void Thermometer::AdjustTemperature(float deltaFahrenheit)
    {
        m_temperatureFahrenheit += deltaFahrenheit;
        if (m_temperatureFahrenheit < 32.f)
        {
            m_signal();
        }
    }
}
// App.cpp
struct App : implements<App, IFrameworkViewSource, IFrameworkView>
{
    ThermometerWRC::Thermometer m_thermometer;
    winrt::event_token m_eventToken;
    ...
    
    void Initialize(CoreApplicationView const &)
    {
        m_eventToken = m_thermometer.SignalTemperatureIsBelowFreezing([] { /* ... */ });
    }
    ...

    void Uninitialize()
    {
        m_thermometer.SignalTemperatureIsBelowFreezing(m_eventToken);
    }
    ...

    void OnPointerPressed(IInspectable const &, PointerEventArgs const & args)
    {
        m_thermometer.AdjustTemperature(-1.f);
        ...
    }
    ...
};

Parametrizované delegáty, jednoduché signály a zpětná volání v rámci projektu

Pokud potřebujete události, které jsou interní pro váš projekt Visual Studio (ne napříč binárními soubory), kde tyto události nejsou omezené na prostředí Windows Runtime typy, můžete stále použít šablonu třídy winrt::event<Delegate>. Jednoduše použijte winrt::d elegate místo skutečného typu delegáta prostředí Windows Runtime, protože winrt::d elegate také podporuje parametry, které nejsou prostředí Windows Runtime.

Následující příklad nejprve ukazuje podpis delegáta, který nepřijímá žádné parametry (v podstatě jednoduchý signál) a pak ten, který přebírá řetězec.

winrt::event<winrt::delegate<>> signal;
signal.add([] { std::wcout << L"Hello, "; });
signal.add([] { std::wcout << L"World!" << std::endl; });
signal();

winrt::event<winrt::delegate<std::wstring>> log;
log.add([](std::wstring const& message) { std::wcout << message.c_str() << std::endl; });
log.add([](std::wstring const& message) { Persist(message); });
log(L"Hello, World!");

Všimněte si, že k události můžete přidat libovolný počet delegátů přihlášených k odběru. S událostí je však spojena určitá režie. Pokud potřebujete jen jednoduché zpětné volání pouze s jediným registrovaným delegátem, můžete použít winrt::delegate<... T> samostatně.

winrt::delegate<> signalCallback;
signalCallback = [] { std::wcout << L"Hello, World!" << std::endl; };
signalCallback();

winrt::delegate<std::wstring> logCallback;
logCallback = [](std::wstring const& message) { std::wcout << message.c_str() << std::endl; }f;
logCallback(L"Hello, World!");

Pokud portujete z základu kódu C++/CX, kde se události a delegáty používají interně v rámci projektu, pomůže vám winrt::d elegate tento vzor replikovat v jazyce C++/WinRT.

Odložené události

Běžným vzorem v prostředí Windows Runtime je odložená událost. Obslužná rutina události získá odklad voláním metody GetDeferral argumentu události. Tím se zdroji události signalizuje, že činnosti po události by měly být odloženy, dokud nebude odklad dokončen. To umožňuje obsluze události provádět asynchronní akce v reakci na ni.

Šablona struktury winrt::deferrable_event_args je pomocná třída pro implementaci (poskytování) vzoru odložení ve prostředí Windows Runtime. Tady je příklad.

// Widget.idl
namespace Sample
{
    runtimeclass WidgetStartingEventArgs
    {
        Windows.Foundation.Deferral GetDeferral();
        Boolean Cancel;
    };

    runtimeclass Widget
    {
        event Windows.Foundation.TypedEventHandler<
            Widget, WidgetStartingEventArgs> Starting;
    };
}

// Widget.h
namespace winrt::Sample::implementation
{
    struct Widget : WidgetT<Widget>
    {
        Widget() = default;

        event_token Starting(Windows::Foundation::TypedEventHandler<
            Sample::Widget, Sample::WidgetStartingEventArgs> const& handler)
        {
            return m_starting.add(handler);
        }
        void Starting(event_token const& token) noexcept
        {
            m_starting.remove(token);
        }

    private:
        event<Windows::Foundation::TypedEventHandler<
            Sample::Widget, Sample::WidgetStartingEventArgs>> m_starting;
    };

    struct WidgetStartingEventArgs : WidgetStartingEventArgsT<WidgetStartingEventArgs>,
                                     deferrable_event_args<WidgetStartingEventArgs>
    //                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    {
        bool Cancel() const noexcept { return m_cancel; }
        void Cancel(bool value) noexcept { m_cancel = value; }
        bool m_cancel = false;
    };
}

Tady je postup, jak příjemce události využívá model odložené události.

// EventRecipient.h
widget.Starting([](auto sender, auto args) -> fire_and_forget
{
    auto deferral = args.GetDeferral();
    if (!co_await CanWidgetStartAsync(sender))
    {
        // Do not allow the widget to start.
        args.Cancel(true);
    }
    deferral.Complete();
});

Jako implementátor (producent) zdroje události odvodíte třídu argumentů události z winrt::deferrable_event_args. < deferrable_event_argsT> implementuje T::GetDeferral za vás. Také zpřístupňuje novou pomocnou metodu deferrable_event_args::wait_for_deferrals, která se dokončí ve chvíli, kdy se dokončí všechna dosud neuzavřená odložení (pokud nedošlo k žádnému odložení, dokončí se okamžitě).

// Widget.h
IAsyncOperation<bool> TryStartWidget(Widget const& widget)
{
    auto args = make_self<WidgetStartingEventArgs>();
    // Raise the event to let people know that the widget is starting
    // and give them a chance to prevent it.
    m_starting(widget, *args);
    // Wait for deferrals to complete.
    co_await args->wait_for_deferrals();
    // Use the results.
    bool started = false;
    if (!args->Cancel())
    {
        widget.InsertBattery();
        widget.FlipPowerSwitch();
        started = true;
    }
    co_return started;
}

Pokyny pro návrh

Jako parametry funkce doporučujeme předávat události, nikoli delegáty. Funkce addwinrt::event je jedinou výjimkou, protože v takovém případě musíte předat delegáta. Důvodem tohoto pokynu je, že delegáti mohou mít různé formy v různých prostředí Windows Runtime jazycích (pokud jde o to, zda podporují jednu registraci klienta nebo více). Události s modelem více odběratelů představují mnohem předvídatelnější a konzistentnější možnost.

Podpis delegáta obslužné rutiny události by se měl skládat ze dvou parametrů: sender (IInspectable) a args (nějaký typ argumentu události, například RoutedEventArgs).

Upozorňujeme, že tyto pokyny se nemusí nutně vztahovat, pokud navrhujete interní rozhraní API. Interní rozhraní API se ale v průběhu času často stávají veřejnými.