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 la aplicación que lo usa, que en el tema Componentes de Windows Runtime con C++/WinRT se muestra cómo compilar.
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 toma un delegado que se va a registrar y devuelve un token (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 sobrecargadas TemperatureIsBelowFreezing, así como un miembro de datos de evento privado 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. Es decir, no es necesario implementar la sobrecarga para el revocador de eventos, ya que la proyección de C++/WinRT la proporciona automáticamente.
Las otras sobrecargas (las sobrecargas de revocación manual y registro) no se incluyen 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 haga clic en la ventana, resta 1 grado de 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 se genera el evento según lo esperado, coloca un punto de interrupción en la expresión lambda que controla el evento TemperatureIsBelowFreezing, ejecuta la aplicación y haz clic dentro de la ventana.
Delegados con parámetros mediante 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 delegado Windows::Foundation::EventHandler<T> de Windows Runtime. TypedEventHandler<TSender, TResult> es otro ejemplo de un tipo de delegado Windows Runtime.
Los parámetros de tipo de esos dos tipos delegados deben cruzar la ABI, por lo que los parámetros deben ser también 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 le ayuda con el error "T debe ser de tipo WinRT" si olvida esta 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 devoluciones de llamadas dentro de 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 devolución de llamada simple con un solo delegado de suscripción, puedes usar winrt::delegate<. T> por sí misma.
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 evento 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, deriva la clase de argumentos del 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
Te recomendamos que pases eventos, 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.