Créer des événements en C++/WinRTAuthor events in C++/WinRT

Cette rubrique vient compléter les explications fournies sur le composant Windows Runtime et l’application consommatrice, dont la génération vous est expliquée dans la rubrique Composants Windows Runtime avec C++/WinRT.This topic builds on the Windows Runtime component, and the consuming application, that the Windows Runtime components with C++/WinRT topic shows you how to build.

Voici les nouvelles fonctionnalités traitées dans cette rubrique.Here are the new features that this topic adds.

  • Mettez à jour la classe runtime du thermomètre pour déclencher un événement lorsque sa température passe en dessous de 0.Update the thermometer runtime class to raise an event when its temperature goes below freezing.
  • Mise à jour de l’application Core qui consomme la classe runtime du thermomètre pour qu’elle gère cet événement.Update the Core App that consumes the thermometer runtime class so that it handles that event.

Notes

Pour plus d’informations sur l’installation et l’utilisation de l’extension VSIX (Visual Studio Extension) C++/WinRT et du package NuGet (qui fournissent ensemble la prise en charge des modèles et des builds de projet), consultez Prise en charge de Visual Studio pour C++/WinRT.For info about installing and using the C++/WinRT Visual Studio Extension (VSIX) and the NuGet package (which together provide project template and build support), see Visual Studio support for C++/WinRT.

Important

Pour obtenir les principaux concepts et termes facilitant votre compréhension pour utiliser et créer des classes runtime avec C++/WinRT, voir Utiliser des API avec C++/WinRT et Créer des API avec C++/WinRT.For essential concepts and terms that support your understanding of how to consume and author runtime classes with C++/WinRT, see Consume APIs with C++/WinRT and Author APIs with C++/WinRT.

Créer ThermometerWRC et ThermometerCoreAppCreate ThermometerWRC and ThermometerCoreApp

Si vous souhaitez procéder aux mises à jour présentées dans cette rubrique pour pouvoir générer et exécuter le code, la première étape consiste à suivre la procédure pas à pas de la rubrique Composants Windows Runtime avec C++/WinRT.If you want to follow along with the updates shown in this topic, so that you can build and run the code, then the first step is to follow the walkthrough in the Windows Runtime components with C++/WinRT topic. De cette façon, vous disposerez du composant Windows Runtime ThermometerWRC et de l’application Core ThermometerCoreApp qui le consomme.By doing so, you'll have the ThermometerWRC Windows Runtime component, and the ThermometerCoreApp Core App that consumes it.

Mettre à jour ThermometerWRC pour déclencher un événementUpdate ThermometerWRC to raise an event

Mettez à jour Thermometer.idl de sorte qu’il se présente comme dans le listing ci-dessous.Update Thermometer.idl to look like the listing below. Voici comment déclarer un événement dont le type délégué est EventHandler avec, comme argument, un nombre à virgule flottante simple précision.This is how to declare an event whose delegate type is EventHandler with an argument of a single-precision floating-point number.

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

Enregistrez le fichier.Save the file. Dans son état actuel, le projet ne sera pas généré complètement. Effectuez cependant une génération dès à présent pour générer des versions mises à jour des fichiers stub \ThermometerWRC\ThermometerWRC\Generated Files\sources\Thermometer.h et Thermometer.cpp.The project won't build to completion in its current state, but perform a build now in any case to generate updated versions of the \ThermometerWRC\ThermometerWRC\Generated Files\sources\Thermometer.h and Thermometer.cpp stub files. Vous pouvez maintenant voir dans ces fichiers les implémentations stub de l’événement TemperatureIsBelowFreezing.Inside those files you can now see stub implementations of the TemperatureIsBelowFreezing event. Dans C++/WinRT, un événement déclaré dans le fichier IDL est implémenté comme un ensemble de fonctions surchargées (de la même manière qu'une propriété est implémentée comme une paire de fonctions Get et Set surchargées).In C++/WinRT, an IDL-declared event is implemented as a set of overloaded functions (similar to the way a property is implemented as a pair of overloaded get and set functions). Une surcharge prend un délégué à inscrire et retourne un jeton (winrt::event_token).One overload takes a delegate to be registered, and returns a token (a winrt::event_token). L’autre prend un jeton et révoque l’inscription du délégué associé.The other takes a token, and revokes the registration of the associated delegate.

Ouvrez à présent Thermometer.h et Thermometer.cpp, et mettez à jour l’implémentation de la classe runtime Thermometer.Now open Thermometer.h and Thermometer.cpp, and update the implementation of the Thermometer runtime class. Dans Thermometer.h, ajoutez les deux fonctions TemperatureIsBelowFreezing surchargées, ainsi qu’un membre de données d’événement privé à utiliser dans l’implémentation de ces fonctions.In Thermometer.h, add the two overloaded TemperatureIsBelowFreezing functions, as well as a private event data member to use in the implementation of those functions.

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

Comme vous pouvez le voir ci-dessus, un événement est représenté par le modèle struct winrt::event, paramétré par un type délégué particulier (qui peut lui-même être paramétré par un type args).As you can see above, an event is represented by the winrt::event struct template, parameterized by a particular delegate type (which itself can be parameterized by an args type).

Dans Thermometer.cpp, implémentez les deux fonctions TemperatureIsBelowFreezing surchargées.In Thermometer.cpp, implement the two overloaded TemperatureIsBelowFreezing functions.

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

Notes

Pour savoir ce qu’est un revoker d’événement automatique, consultez Révoquer un délégué inscrit.For details of what an auto event revoker is, see Revoke a registered delegate. L’implémentation d’un revoker d’événement automatique est gratuite pour votre événement.You get auto event revoker implementation for free for your event. En d’autres termes, vous n’avez pas besoin d’implémenter la surcharge pour le revoker d’événement puisque l’implémentation vous est fournie par la projection C++/WinRT.In other words, you don't need to implement the overload for the event revoker—that's provided for you by the C++/WinRT projection.

Les autres surcharges (inscription et révocation manuelle) ne sont pas intégrées à la projection.The other overloads (the registration and manual revocation overloads) are not baked into the projection. Vous pouvez donc les implémenter de manière optimale pour votre scénario.That's to give you the flexibility to implement them optimally for your scenario. Le fait d’appeler event::add et event::remove comme indiqué dans ces implémentations constitue une valeur par défaut efficace et concurrency/thread-safe.Calling event::add and event::remove as shown in these implementations is an efficient and concurrency/thread-safe default. Mais si vous avez un très grand nombre d’événements, vous pouvez ne pas souhaiter un champ d’événement pour chacun, mais plutôt opter pour un type d’implémentation fragmentée à la place.But if you have a very large number of events, then you may not want an event field for each, but rather opt for some kind of sparse implementation instead.

Vous pouvez également voir ci-dessus que l’implémentation de la fonction AdjustTemperature a été mise à jour pour déclencher l’événement TemperatureIsBelowFreezing quand la température passe en dessous de 0.You can also see above that the implementation of the AdjustTemperature function has been updated to raise the TemperatureIsBelowFreezing event if the temperature goes below freezing.

Mettre à jour ThermometerCoreApp pour gérer l’événementUpdate ThermometerCoreApp to handle the event

Dans le projet ThermometerCoreApp, dans App.cpp, apportez les modifications suivantes au code pour inscrire un gestionnaire d’événements, puis provoquez une température inférieure à 0.In the ThermometerCoreApp project, in App.cpp, make the following changes to the code to register an event handler, and then cause the temperature to go below freezing.

WINRT_ASSERT est une définition de macro, qui se développe en _ASSERTE.WINRT_ASSERT is a macro definition, and it expands to _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);
        ...
    }
    ...
};

Tenez compte de la modification de la méthode OnPointerPressed.Be aware of the change to the OnPointerPressed method. À présent, chaque fois que vous cliquez sur la fenêtre, vous enlevez 1 degré Fahrenheit à la température du thermomètre.Now, each time you click the window, you subtract 1 degree Fahrenheit from the thermometer's temperature. À présent, l’application gère l’événement qui est déclenché quand la température passe en dessous de 0.And now, the app is handling the event that's raised when the temperature goes below freezing. Pour démontrer que l’événement est déclenché comme prévu, insérez un point d’arrêt à l’intérieur de l’expression lambda qui gère l’événement TemperatureIsBelowFreezing, exécutez l’application, puis cliquez à l’intérieur de la fenêtre.To demonstrate that the event is being raised as expected, put a breakpoint inside the lambda expression that's handling the TemperatureIsBelowFreezing event, run the app, and click inside the window.

Délégués paramétrables dans une interface ABIParameterized delegates across an ABI

Si votre événement doit être accessible via une interface binaire d'application (ABI)—par exemple entre un composant et son application consommatrice—votre événement doit utiliser un type de délégué Windows Runtime.If your event must be accessible across an application binary interface (ABI)—such as between a component and its consuming application—then your event must use a Windows Runtime delegate type. L’exemple ci-dessus utilise le type de délégué Windows Runtime Windows::Foundation::EventHandler<T> .The example above uses the Windows::Foundation::EventHandler<T> Windows Runtime delegate type. TypedEventHandler<TSender, TResult> est un autre exemple de type de délégué Windows Runtime.TypedEventHandler<TSender, TResult> is another example of a Windows Runtime delegate type.

Comme les paramètres de type pour ces deux types de délégués doivent traverser l'ABI, ils doivent donc aussi être de type Windows Runtime.The type parameters for those two delegate types have to cross the ABI, so the type parameters must be Windows Runtime types, too. Cela inclut les classes runtime Windows et tierces ainsi que les types primitifs tels que les nombres et les chaînes.That includes Windows runtime classes, third-party runtime classes, and primitive types such as numbers and strings. Le compilateur vous aide en affichant une erreur « must be WinRT type » (doit être de type WinRT) si vous oubliez cette contrainte.The compiler helps you with a "must be WinRT type" error if you forget that constraint.

Vous trouverez ci-dessous un exemple sous forme de listes de code.Below is an example in the form of code listings. Commencez par les projets ThermometerWRC et ThermometerCoreApp que vous avez créés précédemment dans cette rubrique, puis modifiez le code de ces projets en reproduisant le code figurant dans ces listes.Begin with the ThermometerWRC and ThermometerCoreApp projects that you created earlier in this topic, and edit the code in those projects to look like the code in these listings.

La première liste concerne le projet ThermometerWRC.This first listing is for the ThermometerWRC project. Après avoir modifié ThermometerWRC.idl comme indiqué ci-dessous, générez le projet, puis copiez MyEventArgs.h et .cpp dans le projet (à partir du dossier Generated Files) comme vous l’avez fait précédemment avec Thermometer.h et .cpp.After editing ThermometerWRC.idl as shown below, build the project, and then copy MyEventArgs.h and .cpp into the project (from the Generated Files folder) just like you did earlier with Thermometer.h and .cpp.

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

Cette liste concerne le projet ThermometerCoreApp.This listing is for the ThermometerCoreApp project.

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

Signaux simples dans une interface ABISimple signals across an ABI

Si vous n'avez pas besoin de passer des paramètres ou des arguments avec votre événement, vous pouvez définir votre propre type de délégué Windows Runtime simple.If you don't need to pass any parameters or arguments with your event, then you can define your own simple Windows Runtime delegate type. L’exemple ci-dessous montre une version plus simple de la classe runtime Thermometer.The example below shows a simpler version of the Thermometer runtime class. Il déclare un type de délégué nommé SignalDelegate puis l'utilise pour déclencher un événement de type signal au lieu d'un événement avec un paramètre.It declares a delegate type named SignalDelegate and then it uses that to raise a signal-type event instead of an event with a parameter.

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

Délégués paramétrés, signaux simples et rappels au sein d'un projetParameterized delegates, simple signals, and callbacks within a project

Si vous avez besoin d’événements internes à votre projet Visual Studio (pas entre binaires), où ces événements ne sont pas limités aux types Windows Runtime, vous pouvez toujours utiliser le modèle de classe winrt::event<Delegate>.If you need events that are internal to your Visual Studio project (not across binaries), where those events are not limited to Windows Runtime types, then you can still use the winrt::event<Delegate> class template. Utilisez simplement winrt::delegate au lieu d’un type délégué Windows Runtime réel, car winrt::delegate prend également en charge les paramètres non-Windows Runtime.Simply use winrt::delegate instead of an actual Windows Runtime delegate type, since winrt::delegate also supports non Windows Runtime parameters.

L'exemple ci-dessous montre d'abord une signature de délégué qui ne prend aucun paramètre (essentiellement un simple signal), puis une signature qui prend une chaîne.The example below first shows a delegate signature that doesn't take any parameters (essentially a simple signal), and then one that takes a string.

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

Notez comment vous pouvez ajouter à l'événement autant de délégués que vous le souhaitez.Notice how you can add to the event as many subscribing delegates as you wish. Mais un événement implique une surcharge de travail.However, there is some overhead associated with an event. Si vous n'avez besoin que d'un simple rappel avec un seul délégué abonné, alors vous pouvez utiliser winrt::delegate<... T> seul.If all you need is a simple callback with only a single subscribing delegate, then you can use winrt::delegate<... T> on its own.

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

Si vous effectuez un portage à partir d'un code base C++/CX où les événements et les délégués sont utilisés en interne au sein d’un projet, winrt::delegate vous aidera à répliquer ce modèle en C++/WinRT.If you're porting from a C++/CX codebase where events and delegates are used internally within a project, then winrt::delegate will help you to replicate that pattern in C++/WinRT.

Recommandations en matière de conceptionDesign guidelines

Nous vous recommandons de passer les événements, et non les délégués, comme paramètres de fonction.We recommend that you pass events, and not delegates, as function parameters. La fonction add de winrt::event est la seule exception car vous devez passer un délégué dans ce cas.The add function of winrt::event is the one exception, because you must pass a delegate in that case. Cette directive s'explique par le fait que les délégués peuvent prendre différentes formes dans différents langages Windows Runtime (s’ils prennent en charge une seule ou plusieurs inscriptions de clients).The reason for this guideline is because delegates can take different forms across different Windows Runtime languages (in terms of whether they support one client registration, or multiple). Les événements, avec leur modèle à abonnés multiples, constituent une option beaucoup plus prévisible et cohérente.Events, with their multiple subscriber model, constitute a much more predictable and consistent option.

La signature d'un délégué de gestionnaire d'événement doit être composée de deux paramètres : sender (IInspectable) et args (un type d'argument d'événement, par exemple RoutedEventArgs).The signature for an event handler delegate should consist of two parameters: sender (IInspectable), and args (some event argument type, for example RoutedEventArgs).

Notez que ces instructions ne s’appliquent pas obligatoirement si vous concevez une API interne.Note that these guidelines don't necessarily apply if you're designing an internal API. Bien que les API internes finissent souvent par devenir publiques.Although, internal APIs often become public over time.