Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Este tema se basa en el componente de Windows Runtime y en la aplicación cliente que el tema Windows Runtime components with C++/WinRT muestra cómo crear.
Estas son las nuevas características que agrega este tema.
- Actualice la clase de tiempo de ejecución del termómetro para desencadenar un evento cuando su temperatura descienda por debajo del punto de congelación.
- Actualice la aplicación principal que consume la clase de tiempo de ejecución del termómetro para que controle ese evento.
Note
Para obtener información sobre cómo instalar y usar la extensión de Visual Studio de C++/WinRT (VSIX) y el paquete NuGet (que juntos proporcionan compatibilidad con la plantilla de proyecto y la compilación), consulta Visual Studio compatibilidad con C++/WinRT.
Important
Para conocer los conceptos y términos esenciales que admiten su comprensión de cómo consumir y crear clases en tiempo de ejecución con C++/WinRT, consulte Consumo de API con C++/WinRT y Creación de API con C++/WinRT.
Creación de ThermoWRC y ThermoCoreApp
Si desea seguir las actualizaciones que se muestran en este tema, para poder compilar y ejecutar el código, el primer paso es seguir el tutorial de los componentes de Windows Runtime con C++/WinRT. Al hacerlo, tendrás el componente Windows Runtime ThermometerWRC y la aplicación principal ThermometerCoreApp que lo utiliza.
Actualizar ThermometerWRC para desencadenar un evento
Actualice Thermometer.idl para que tenga un aspecto similar al de la lista siguiente. Así es como declarar un evento cuyo tipo de delegado es EventHandler con un argumento de un número de punto flotante de precisión única.
// Thermometer.idl
namespace ThermometerWRC
{
runtimeclass Thermometer
{
Thermometer();
void AdjustTemperature(Single deltaFahrenheit);
event Windows.Foundation.EventHandler<Single> TemperatureIsBelowFreezing;
};
}
Guarde el archivo. El proyecto no se compilará por completo en su estado actual, pero, aun así, realiza una compilación ahora para generar versiones actualizadas de los archivos stub \ThermometerWRC\ThermometerWRC\Generated Files\sources\Thermometer.h y Thermometer.cpp. En esos archivos ahora se pueden ver implementaciones ficticias del evento TemperatureIsBelowFreezing. En C++/WinRT, un evento declarado por IDL se implementa como un conjunto de funciones sobrecargadas (de forma similar a la forma en que se implementa una propiedad como un par de funciones get y set sobrecargadas). Una sobrecarga acepta un delegado para registrarlo y devuelve un token (un winrt::event_token). El otro toma un token y revoca el registro del delegado asociado.
Abra ahora Thermometer.h y Thermometer.cpp, y actualice la implementación de la clase de tiempo de ejecución Thermometer. En Thermometer.h, agregue las dos funciones TemperatureIsBelowFreezing sobrecargadas, así como un miembro de datos de eventos privados que se usará en la implementación de esas funciones.
// 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;
...
};
}
...
Como puede ver anteriormente, un evento se representa mediante la plantilla de estructura winrt::event , parametrizada por un tipo de delegado determinado (que puede parametrizarse mediante un tipo de argumentos).
En Thermometer.cpp, implemente las dos funciones TemperatureIsBelowFreezing sobrecargadas.
// 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
Para obtener más información sobre lo que es un revocador de eventos automático, consulte Revocación de un delegado registrado. Puede obtener la implementación del revocador de eventos automático de forma gratuita para su evento. En otras palabras, no es necesario implementar la sobrecarga para el revocador de eventos; esa la proporciona la proyección de C++/WinRT.
Las otras sobrecargas (las sobrecargas de registro y revocación manual) no están integradas en la proyección. Esto es para proporcionarle la flexibilidad de implementarlos de forma óptima para su escenario. Llamar a event::add y event::remove, como se muestra en estas implementaciones, es una opción predeterminada eficaz y segura para la concurrencia y los subprocesos. Pero si tiene un gran número de eventos, es posible que no desee un campo de evento para cada uno, sino que opte por algún tipo de implementación dispersa en su lugar.
También puede ver arriba que la implementación de la función AdjustTemperature se ha actualizado para aumentar el evento TemperatureIsBelowFreezing si la temperatura va por debajo de la congelación.
Actualizar ThermoCoreApp para controlar el evento
En el proyecto ThermometerCoreApp, en App.cpp, realice los siguientes cambios en el código para registrar un controlador de eventos y, a continuación, haga que la temperatura descienda por debajo del punto de congelación.
WINRT_ASSERT es una definición de macro y se expande a _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);
...
}
...
};
Tenga en cuenta el cambio en el método OnPointerPressed . Ahora, cada vez que hagas clic en la ventana, restas 1 grado Fahrenheit de la temperatura del termómetro. Y ahora, la aplicación controla el evento que se genera cuando la temperatura va por debajo de la congelación. Para demostrar que el evento se está generando según lo previsto, coloque un punto de interrupción dentro de la expresión lambda que controla el evento TemperatureIsBelowFreezing , ejecute la aplicación y haga clic dentro de la ventana.
Delegados parametrizados a través de una ABI
Si el evento debe ser accesible a través de una interfaz binaria de aplicación (ABI), por ejemplo entre un componente y la aplicación que lo consume, entonces debe usar un tipo de delegado de Windows Runtime. En el ejemplo anterior se usa el tipo de delegado de Windows Runtime Windows::Foundation::EventHandler<T>. TypedEventHandler<TSender, TResult> es otro ejemplo de un tipo de delegado Windows Runtime.
Los parámetros de tipo de esos dos tipos de delegado tienen que cruzar la ABI, por lo que los parámetros de tipo también deben ser tipos de Windows Runtime. Esto incluye clases en tiempo de ejecución de Windows, clases en tiempo de ejecución de terceros y tipos primitivos, como números y cadenas. El compilador te ayuda con un error "T debe ser un tipo WinRT" si olvidas esa restricción.
A continuación se muestra un ejemplo en forma de listados de código. Comience con los proyectos ThermoWRC y ThermoCoreApp que creó anteriormente en este tema y edite el código de esos proyectos para que tenga un aspecto similar al código de estas listas.
Este primer elemento de la lista es para el proyecto ThermometerWRC. Después de editar ThermometerWRC.idl como se muestra a continuación, compile el proyecto y, a continuación, copie MyEventArgs.h y .cpp en el proyecto (desde la Generated Files carpeta) como hizo anteriormente con Thermometer.h y .cpp. Recuerde eliminar el static_assert de ambos archivos.
// 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);
}
}
...
Esta lista es para el proyecto ThermoCoreApp .
// 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.
});
}
...
Señales simples en una ABI
Si no necesita pasar ningún parámetro ni argumento en el evento, puede definir su propio tipo de delegado simple de Windows Runtime. En el ejemplo siguiente se muestra una versión más sencilla de la clase de tiempo de ejecución Thermometer. Declara un tipo de delegado denominado SignalDelegate y, a continuación, lo usa para generar un evento de tipo señal en lugar de un evento con un parámetro .
// 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);
...
}
...
};
Delegados con parámetros, señales simples y funciones de devolución de llamada en un proyecto
Si necesita eventos internos a su proyecto de Visual Studio (no entre binarios), y esos eventos no están limitados a tipos de Windows Runtime, puede seguir utilizando la plantilla de clase winrt::event<Delegate>. Solo tiene que usar winrt::delegate en lugar de un tipo real de delegado de Windows Runtime, ya que winrt::delegate también admite parámetros que no son de Windows Runtime.
En el ejemplo siguiente se muestra primero una firma de delegado que no toma ningún parámetro (básicamente una señal simple) y, a continuación, una que toma una cadena.
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!");
Observe que puede añadir al evento tantos delegados suscritos como desee. Sin embargo, hay cierta sobrecarga asociada a un evento. Si todo lo que necesitas es una función de devolución de llamada sencilla con un único delegado suscrito, puedes usar winrt::delegate<... T> por sí solo.
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 vas a migrar desde un código base de C++/CX donde los eventos y delegados se usan internamente dentro de un proyecto, winrt::d elegate te ayudará a replicar ese patrón en C++/WinRT.
Eventos aplazables
Un patrón común en el Windows Runtime es el evento aplazable. Un controlador de eventos toma un aplazamiento llamando al método GetDeferral del argumento de evento. Al hacerlo, se indica al origen del evento que las actividades posteriores al evento se deben posponer hasta que se complete el aplazamiento. Esto permite que un controlador de eventos realice acciones asincrónicas en respuesta a un evento.
La plantilla de la estructura winrt::deferrable_event_args es una clase auxiliar para implementar (producir) el patrón de aplazamiento de Windows Runtime. Este es un ejemplo.
// 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;
};
}
Este es el modo en que el destinatario del evento consume el patrón de eventos aplazable.
// 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();
});
Como implementador (productor) del origen del evento, derive su clase de argumentos de evento de winrt::deferrable_event_args. < deferrable_event_argsT> implementa T::GetDeferral para usted. También expone un nuevo método auxiliar deferrable_event_args::wait_for_deferrals, que se completa cuando se han completado todos los aplazamientos pendientes (si no se han tomado aplazamientos, se completa inmediatamente).
// 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;
}
Directrices de diseño
Se recomienda pasar eventos y no delegados, como parámetros de función. La función add de winrt::event es la única excepción, ya que en ese caso hay que proporcionar un delegado. La razón de esta directriz es que los delegados pueden adoptar distintas formas en los diferentes lenguajes de Windows Runtime, según admitan un único registro de cliente o varios. Los eventos, con su modelo de varios suscriptores, constituyen una opción mucho más predecible y coherente.
La firma de un delegado de controlador de eventos debe constar de dos parámetros: sender (IInspectable) y args (algún tipo de argumento de evento, por ejemplo RoutedEventArgs).
Tenga en cuenta que estas directrices no se aplican necesariamente si va a diseñar una API interna. Aunque las API internas suelen convertirse en públicas a lo largo del tiempo.