Események létrehozása a C++/WinRT alkalmazásban

Ez a témakör arra a Windows-futtatókörnyezet-összetevőre és felhasználó alkalmazásra épül, amelyeknek a létrehozását a Windows-futtatókörnyezet-összetevők C++/WinRT használatával című témakör mutatja be.

Az alábbi új funkciókkal bővítjük a témakört.

  • Frissítse a hőmérő runtime osztályát, hogy eseményt váltson ki, amikor a hőmérséklete fagypont alá csökken.
  • Frissítse a hőmérő futásidejű osztályát használó Core-alkalmazást, hogy az kezelje az eseményt.

Note

A C++/WinRT Visual Studio bővítmény (VSIX) és a NuGet-csomag telepítésével és használatával kapcsolatos információkért (amelyek együttesen nyújtanak projektsablont és buildtámogatást), tekintse meg Visual Studio C++/WinRT támogatását.

Fontos

Azokat az alapvető fogalmakat és kifejezéseket, amelyek támogatják a futtatókörnyezeti osztályok C+++/WinRT használatával történő felhasználásának és szerkesztésének megértését, olvassa el az API-k használata c++/WinRT-lel és Szerzői API-k c++/WinRT használatával című témakört.

ThermometerWRC és ThermometerCoreApp létrehozása

Ha követni szeretné a jelen témakörben látható frissítéseket, hogy felépíthesse és futtathassa a kódot, az első lépés az, hogy kövesse a C++/WinRT témakör Windows-futtatókörnyezet összetevőinek útmutatóját. Ezzel rendelkezni fog a ThermometerWRC Windows-futtatókörnyezet összetevővel és az azt használó ThermometerCoreApp Core-alkalmazással.

HőmérőWRC frissítése esemény létrehozásához

Frissítse a(z) Thermometer.idl elemet úgy, hogy úgy nézzen ki, mint az alábbi lista. Így deklarálhat olyan eseményt, amelynek delegált típusa EventHandler , egy pontosságú lebegőpontos szám argumentumával.

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

Mentse a fájlt. A projekt a jelenlegi állapotában nem fordul le teljesen, de ennek ellenére most futtass egy buildet, hogy létrejöjjenek a \ThermometerWRC\ThermometerWRC\Generated Files\sources\Thermometer.h és Thermometer.cpp stubfájlok frissített verziói. Ezeken a fájlokon belül most már láthatja a TemperatureIsBelowFreezing esemény csonk implementációit. A C++/WinRT-ben az IDL-deklarált esemény túlterhelt függvények halmazaként van implementálva (hasonlóan ahhoz, ahogyan egy tulajdonság túlterhelt get és set függvények párjaként van implementálva). Egy túlterhelés egy meghatalmazott regisztrációját veszi igénybe, és egy jogkivonatot ( winrt::event_token) ad vissza. A másik fogad egy tokent, és visszavonja a hozzá tartozó delegált regisztrációját.

Most nyissa meg Thermometer.h és Thermometer.cppfrissítse a hőmérő futtatókörnyezeti osztály implementációját. Ebben Thermometer.hadja hozzá a két túlterhelt TemperatureIsBelowFreezing függvényt, valamint egy privát eseményadat-tagot a függvények implementálásához.

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

Ahogy fent látható, egy eseményt a winrt::event struct sablon jelöl, amelyet egy adott delegálási típus paraméterez (amely maga is paraméterezhető egy args-típussal).

In Thermometer.cpp, implementálja a két túlterhelt TemperatureIsBelowFreezing függvényt.

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

Az automatikus esemény-visszavonás részleteiért lásd: Regisztrált meghatalmazott visszavonása. Az esemény automatikus eseménymegvonási implementációját ingyenesen megkapja az eseményhez. Más szóval nem kell implementálnia az esemény-visszavonó túlterhelését – ezt a C++/WinRT-vetítés biztosítja Önnek.

A többi túlterhelés (a regisztráció és a manuális visszavonási túlterhelések) nem kerülnek be az előrejelzésbe. Így rugalmasan implementálhatja őket a forgatókönyvéhez optimálisan. A event::add és a event::remove meghívása az ilyen implementációkban bemutatott módon hatékony és konkurens/szálbiztos alapértelmezett megoldás. Ha azonban nagyon sok eseménye van, akkor előfordulhat, hogy nem szeretne egy eseménymezőt mindegyikhez, hanem inkább valamilyen ritkább megvalósítást választ.

A fentiekben azt is láthatja, hogy az AdjustTemperature függvény implementációja frissült a TemperatureIsBelowFreezing esemény megemelése érdekében, ha a hőmérséklet fagyás alatt van.

A ThermometerCoreApp frissítése az esemény kezeléséhez

A ThermometerCoreApp projektben, a(z) App.cpp fájlban végezze el az alábbi módosításokat a kódban egy eseménykezelő regisztrálásához, majd idézze elő, hogy a hőmérséklet fagypont alá csökkenjen.

WINRT_ASSERT egy makródefiníció, és _ASSERTE kifejezésre bontódik ki.

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

Vegye figyelembe az OnPointerPressed metódus módosítását. Mostantól minden alkalommal, amikor az ablakra kattint, levon 1 Fahrenheit-fokot a hőmérő hőmérsékletéből. Most pedig az alkalmazás kezeli azt az eseményt, amely akkor merül fel, amikor a hőmérséklet fagypont alá csökken. Annak bemutatásához, hogy az esemény a várt módon történik, helyezzen el egy töréspontot a TemperatureIsBelowFreezing eseményt kezelő lambda kifejezésbe, futtassa az alkalmazást, és kattintson az ablakban.

Paraméterezett delegáltak ABI-kon átívelően

Ha az eseménynek elérhetőnek kell lennie egy alkalmazás bináris interfészén (ABI), például egy összetevő és annak használó alkalmazása között, akkor az eseménynek Windows-futtatókörnyezet delegált típust kell használnia. A fenti példa a Windows::Foundation::EventHandler<T> Windows-futtatókörnyezet delegálttípust használja. TypedEventHandler<A TSender, TResult> egy másik példa egy Windows-futtatókörnyezet delegált típusra.

A két delegált típus típusparamétereinek át kell haladnia az ABI-n, így a típusparamétereknek Windows-futtatókörnyezet típusoknak is meg kell lenniük. Ez magában foglalja Windows futtatókörnyezeti osztályokat, külső futtatókörnyezeti osztályokat és primitív típusokat, például számokat és sztringeket. A fordító egy "A T-nek WinRT-típusnak kell lennie" hibával segít, ha elfelejted ezt a megkötést.

Az alábbiakban egy példa látható kódlisták formájában. Kezdje a témakör korábbi részében létrehozott ThermometerWRC - és ThermometerCoreApp-projektekkel , és szerkessze az ezekben a projektekben lévő kódot, hogy az a listaelemekben szereplő kódhoz hasonlóan nézzen ki.

Ez az első lista a ThermometerWRC projekthez tartozik. Miután az alábbiak szerint szerkesztette a(z) ThermometerWRC.idl elemet, fordítsa le a projektet, majd másolja be a(z) MyEventArgs.h és .cpp elemet a projektbe (a Generated Files mappából), ugyanúgy, ahogyan korábban a(z) Thermometer.h és .cpp elemmel tette. Ne felejtse el törölni a static_assert elemet mindkét fájlból.

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

Ez a lista a ThermometerCoreApp projekthez tartozik.

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

Egyszerű jelek ABI-n keresztül

Ha nem kell paramétereket vagy argumentumokat átadnia az eseményhez, akkor megadhatja saját egyszerű Windows-futtatókörnyezet delegált típusát. Az alábbi példa a Hőmérő futtatókörnyezet osztály egyszerűbb verzióját mutatja be. Deklarál egy SignalDelegate nevű delegálttípust, majd ezzel a paraméterrel rendelkező esemény helyett jel típusú eseményt hoz létre.

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

Paraméteres meghatalmazottak, egyszerű jelek és visszahívások egy projekten belül

Ha olyan eseményekre van szüksége, amelyek a Visual Studio projekten belül vannak (nem bináris fájlok között), ahol ezek az események nem korlátozódnak Windows-futtatókörnyezet típusokra, akkor továbbra is használhatja a winrt::event<Delegate> osztálysablont. Egyszerűen használja a winrt::d elegate parancsot a tényleges Windows-futtatókörnyezet delegált típus helyett, mivel a winrt::d elegate a nem Windows-futtatókörnyezet paramétereket is támogatja.

Az alábbi példa először egy olyan delegált szignatúráját mutatja be, amely nem fogad paramétereket (lényegében egy egyszerű jelzés), majd egy olyat, amely egy karakterláncot vesz át paraméterként.

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!");

Figyelje meg, hogyan adhat hozzá az eseményhez annyi feliratkozó meghatalmazottat, amennyit csak szeretne. Egy esemény azonban bizonyos többletterheléssel jár. Ha mindössze egy egyszerű visszahívásra van szüksége egyetlen feliratkozó delegálttal, akkor használhatja önmagában a winrt::delegate<... T> típust.

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!");

Ha olyan C++/CX kódbázisból portoz, ahol az eseményeket és a meghatalmazottakat a projekten belül használják, akkor a winrt::d elegate segítségével replikálhatja ezt a mintát a C++/WinRT fájlban.

Elhalasztható események

A Windows-futtatókörnyezet gyakori mintája a késleltethető esemény. Az eseménykezelő halasztást alkalmaz az eseményargumentum GetDeferral metódusának meghívásával. Ez azt jelzi az eseményforrásnak, hogy az esemény utáni tevékenységeket el kell halasztani a halasztás befejezéséig. Így az eseménykezelő aszinkron műveleteket hajthat végre egy eseményre válaszul.

A winrt::deferrable_event_args struktúrasablon egy segédosztály a Windows-futtatókörnyezet halasztási mintájának megvalósításához (előállításához). Íme egy példa.

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

Az esemény címzettje így használja fel a késleltethető eseménymintát.

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

Az eseményforrás implementátoraként (előállítójaként) az eseményargumentumok osztályát a winrt::deferrable_event_args osztályból származtatja. deferrable_event_args<T> megvalósítja a T::GetDeferral metódust az Ön számára. Emellett elérhetővé tesz egy új segédmetódust, a(z) deferrable_event_args::wait_for_deferrals metódust, amely akkor fejeződik be, amikor az összes függőben lévő halasztás befejeződött (ha nem kértek halasztást, akkor azonnal befejeződik).

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

Tervezési irányelvek

Azt javasoljuk, hogy ne delegáltakat, hanem eseményeket adjon át függvényparaméterként. A winrt::eventadd függvénye az egyetlen kivétel, mert ebben az esetben delegáltat kell átadnia. Ennek az útmutatónak az az oka, hogy a meghatalmazottak különböző űrlapokat készíthetnek különböző Windows-futtatókörnyezet nyelvek között (attól függetlenül, hogy támogatnak-e egy ügyfélregisztrációt vagy több nyelvet). A több előfizetői modellel rendelkező események sokkal kiszámíthatóbb és konzisztensebb lehetőséget jelentenek.

Az eseménykezelő delegáltjának aláírásának két paraméterből kell állnia: a feladóból (IInspectable) és az argsből (valamilyen eseményargumentumtípusból, például RoutedEventArgs).

Vegye figyelembe, hogy ezek az irányelvek nem feltétlenül érvényesek, ha belső API-t tervez. Bár a belső API-k idővel gyakran nyilvánossá válnak.