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.
Important
¿Construyendo con la SDK de Aplicaciones para Windows? El código de este artículo usa los espacios de nombres de UWP (Windows.UI.Xaml). Si su proyecto está dirigido a WinUI 3 (SDK de aplicaciones para Windows), sustituya Microsoft.UI.Xaml (y los espacios de nombres relacionados Microsoft.UI.*) en todo el texto. Consulta Correspondencia entre las API de UWP y el SDK de aplicaciones de Windows para ver la correspondencia completa y la guía de migración de la interfaz de usuario para consultar más detalles.
En este tema se muestra cómo registrar y revocar delegados de control de eventos mediante C++/WinRT. Puede controlar un evento mediante cualquier objeto de tipo función de C++ estándar.
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 compilación y la plantilla de proyecto), consulta Visual Studio compatibilidad con C++/WinRT.
Uso de Visual Studio para agregar un controlador de eventos
Una forma cómoda de agregar un controlador de eventos al proyecto es usar la interfaz de usuario (UI) del Diseñador XAML en Visual Studio. Con la página XAML abierta en el Diseñador XAML, selecciona el control cuyo evento quieres controlar. En la página de propiedades de ese control, haga clic en el icono de rayo para enumerar todos los eventos de origen de ese control. A continuación, haga doble clic en el evento que desea controlar; por ejemplo, OnClicked.
El Diseñador XAML añade a sus archivos de origen el prototipo adecuado de la función controladora de eventos (y una implementación básica), para que la sustituya por su propia implementación.
Note
Normalmente, los controladores de eventos no necesitan describirse en el archivo Midl (.idl). Por lo tanto, el Diseñador XAML no agrega prototipos de función de controlador de eventos al archivo Midl. Solo los agrega a tus archivos .h y .cpp.
Registro de un delegado para controlar un evento
Un ejemplo sencillo es controlar el evento click de un botón. Es habitual usar el marcado XAML para registrar una función miembro con el fin de controlar el evento, como este.
// MainPage.xaml
<Button x:Name="myButton" Click="ClickHandler">Click Me</Button>
// MainPage.h
void ClickHandler(
winrt::Windows::Foundation::IInspectable const& sender,
winrt::Microsoft::UI::Xaml::RoutedEventArgs const& args);
// MainPage.cpp
void MainPage::ClickHandler(
IInspectable const& /* sender */,
RoutedEventArgs const& /* args */)
{
myButton().Content(box_value(L"Clicked"));
}
El código anterior se ha tomado del proyecto Aplicación en blanco, empaquetada (WinUI 3 de escritorio) en Visual Studio. El código myButton() llama a una función de acceso generada, que devuelve el Button al que llamamos myButton. Si cambias el x:Name de ese elemento Button, también cambia el nombre de la función de acceso generada.
Note
En este caso, el origen del evento (el objeto que genera el evento) es el botón denominado myButton. Y el destinatario del evento (el objeto que controla el evento) es una instancia de MainPage. Hay más información más adelante en este tema sobre la administración de la duración de los orígenes de eventos y los destinatarios de eventos.
En lugar de hacerlo de forma declarativa en el lenguaje de marcado, puede registrar de forma imperativa una función miembro para gestionar un evento. Es posible que no sea obvio en el ejemplo de código siguiente, pero el argumento de la llamada ButtonBase::Click es una instancia del delegado RoutedEventHandler . En este caso, usaremos la sobrecarga de constructor RoutedEventHandler que toma un objeto y un puntero a función miembro.
// MainPage.cpp
MainPage::MainPage()
{
InitializeComponent();
myButton().Click({ this, &MainPage::ClickHandler });
}
Important
Al registrar el delegado, el ejemplo de código anterior pasa sin formato el puntero this (que apunta al objeto actual). Para obtener información sobre cómo establecer una referencia fuerte o débil al objeto actual, consulte Si usa una función miembro como delegado.
Este es un ejemplo que usa una función miembro estática; tenga en cuenta la sintaxis más sencilla.
// MainPage.h
static void ClickHandler(
winrt::Windows::Foundation::IInspectable const& sender,
winrt::Microsoft::UI::Xaml::RoutedEventArgs const& args);
// MainPage.cpp
MainPage::MainPage()
{
InitializeComponent();
myButton().Click( MainPage::ClickHandler );
}
void MainPage::ClickHandler(
IInspectable const& /* sender */,
RoutedEventArgs const& /* args */) { ... }
Hay otras maneras de construir un RoutedEventHandler. A continuación se muestra el bloque de sintaxis tomado del tema de documentación de RoutedEventHandler (elija C++/WinRT en la lista desplegable Lenguaje en la esquina superior derecha de la página web). Ten en cuenta los diversos constructores: uno toma una expresión lambda, otro una función libre y otro (el que hemos usado anteriormente) toma un objeto y un puntero a función miembro.
struct RoutedEventHandler : winrt::Windows::Foundation::IUnknown
{
RoutedEventHandler(std::nullptr_t = nullptr) noexcept;
template <typename L> RoutedEventHandler(L lambda);
template <typename F> RoutedEventHandler(F* function);
template <typename O, typename M> RoutedEventHandler(O* object, M method);
/* ... other constructors ... */
void operator()(winrt::Windows::Foundation::IInspectable const& sender,
winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e) const;
};
También es útil ver la sintaxis del operador de la llamada de la función. Te indica cuáles deben ser los parámetros de tu delegado. Como puede ver, en este caso, la sintaxis del operador de llamada de función coincide con los parámetros de nuestro MainPage::ClickHandler.
Note
Para cualquier evento determinado, para averiguar los detalles de su delegado y los parámetros del delegado, vaya primero al tema de documentación del propio evento. Tomemos el evento UIElement.KeyDown como ejemplo. Visite ese tema y elija C++/WinRT en la lista desplegable Lenguaje . En el bloque de sintaxis al comienzo del tema, verás esto.
// Register
event_token KeyDown(KeyEventHandler const& handler) const;
Esa información nos indica que el evento UIElement.KeyDown (el tema en el que estamos) tiene un tipo de delegado de KeyEventHandler, ya que es el tipo que se pasa al registrar un delegado con este tipo de evento. Por lo tanto, sigue ahora el vínculo en el tema a ese tipo de delegado KeyEventHandler. Aquí, el bloque de sintaxis contiene un operador de llamada de función. Y, como se mencionó anteriormente, eso te indica qué parámetros debe tener tu delegado.
void operator()(
winrt::Windows::Foundation::IInspectable const& sender,
winrt::Microsoft::UI::Xaml::Input::KeyRoutedEventArgs const& e) const;
Como puedes ver, el delegado necesita que se declare para que tome un IInspectable como remitente y una instancia de la clase KeyRoutedEventArgs como argumentos.
Para obtener otro ejemplo, echemos un vistazo al evento Popup.Closed. Su tipo de delegado es EventHandler<IInspectable>. Por lo tanto, el delegado tomará un IInspectable como remitente y otro IInspectable (porque es el tipo de parámetro de EventHandler) como argumentos.
Si no está haciendo mucho trabajo en el controlador de eventos, puede usar una función lambda en lugar de una función miembro. De nuevo, puede que no sea obvio en el ejemplo de código siguiente, pero se está construyendo un delegado RoutedEventHandler a partir de una función lambda que, de nuevo, debe coincidir con la sintaxis del operador de llamada de función que hemos descrito anteriormente.
MainPage::MainPage()
{
InitializeComponent();
myButton().Click([this](IInspectable const& /* sender */, RoutedEventArgs const& /* args */)
{
myButton().Content(box_value(L"Clicked"));
});
}
Puedes ser un poco más explícito cuando construyas tu delegado. Por ejemplo, si desea pasarlo de un lado a otro, o usarlo más de una vez.
MainPage::MainPage()
{
InitializeComponent();
auto click_handler = [](IInspectable const& sender, RoutedEventArgs const& /* args */)
{
sender.as<winrt::Microsoft::UI::Xaml::Controls::Button>().Content(box_value(L"Clicked"));
};
myButton().Click(click_handler);
AnotherButton().Click(click_handler);
}
Revocar un delegado registrado
Al registrar un delegado, normalmente se devuelve un token. Posteriormente, puede usar ese token para anular el registro del delegado; es decir, el delegado deja de estar registrado en el evento y no se volverá a invocar si el evento se desencadena de nuevo.
Por motivos de simplicidad, ninguno de los ejemplos de código anteriores mostró cómo hacerlo. Pero el siguiente ejemplo de código almacena el token en el miembro de datos privados de la estructura y revoca su controlador en el destructor.
struct Example : ExampleT<Example>
{
Example(winrt::Microsoft::UI::Xaml::Controls::Button const& button) : m_button(button)
{
m_token = m_button.Click([this](IInspectable const&, RoutedEventArgs const&)
{
// ...
});
}
~Example()
{
m_button.Click(m_token);
}
private:
winrt::Microsoft::UI::Xaml::Controls::Button m_button;
winrt::event_token m_token;
};
En lugar de una referencia segura, como en el ejemplo anterior, puedes almacenar una referencia débil al botón (consulta Referencias fuertes y débiles en C++/WinRT).
Note
Cuando un origen de eventos genera sus eventos sincrónicamente, puedes revocar el controlador y estar seguro de que no recibirás más eventos. Pero para los eventos asincrónicos, incluso después de la revocación (y especialmente al revocar dentro del destructor), un evento en curso podría alcanzar el objeto después de que se haya iniciado la destrucción. Encontrar un lugar para cancelar la suscripción antes de que se destruya podría mitigar el problema o, si desea una solución más sólida, consulte Acceder de forma segura al puntero this con un delegado de control de eventos.
Como alternativa, al registrar un delegado, puede especificar winrt::auto_revoke (que es un valor de tipo winrt::auto_revoke_t) para solicitar un revocador de eventos (de tipo winrt::event_revoker). El revocador de eventos mantiene una referencia débil al origen del evento (el objeto que genera el evento) para ti. Puedes revocar manualmente mediante una llamada a la función miembro event_revoker::revoke, pero el revocador de eventos llama a la propia función automáticamente cuando sale del ámbito. La función revoke comprueba si el origen del evento sigue existiendo y, si es así, revoca su delegado. En este ejemplo, no es necesario almacenar el origen del evento y no es necesario un destructor.
struct Example : ExampleT<Example>
{
Example(winrt::Microsoft::UI::Xaml::Controls::Button button)
{
m_event_revoker = button.Click(
winrt::auto_revoke,
[this](IInspectable const& /* sender */,
RoutedEventArgs const& /* args */)
{
// ...
});
}
private:
winrt::Microsoft::UI::Xaml::Controls::Button::Click_revoker m_event_revoker;
};
A continuación se muestra el bloque de sintaxis tomado del tema de documentación del evento ButtonBase::Click . Muestra las tres funciones de registro y revocación diferentes. Puedes ver exactamente qué tipo de revocador de eventos tienes que declarar desde la tercera sobrecarga. Y puede pasar los mismos tipos de delegados a register y a las cargas de trabajo de revoke with event_revoker.
// Register
winrt::event_token Click(winrt::Microsoft::UI::Xaml::RoutedEventHandler const& handler) const;
// Revoke with event_token
void Click(winrt::event_token const& token) const;
// Revoke with event_revoker
Button::Click_revoker Click(winrt::auto_revoke_t,
winrt::Microsoft::UI::Xaml::RoutedEventHandler const& handler) const;
Note
En el ejemplo de código anterior, Button::Click_revoker es un alias de tipo para winrt::event_revoker<winrt::Microsoft::UI::Xaml::Controls::Primitives::IButtonBase>. Un patrón similar se aplica a todos los eventos de C++/WinRT. Cada evento de Windows Runtime tiene una sobrecarga de función revoke que devuelve un revocador de eventos y ese tipo de revocador es un miembro del origen del evento. Por lo tanto, para tomar otro ejemplo, el evento Window::SizeChanged tiene una sobrecarga de función de registro que devuelve un valor de tipo Window::SizeChanged_revoker.
Puede que tengas que considerar la posibilidad de revocar controladores en un escenario de navegación de páginas. Si vas a navegar varias veces por una página y luego vas a volver atrás, podrías revocar los controladores cuando salgas de la página. Como alternativa, si reutilizas la misma instancia de página, comprueba el valor de tu token y regístralo solo si todavía no se ha establecido (if (!m_token){ ... }). Una tercera opción es almacenar un revocador de eventos en la página como un miembro de datos. Y una cuarta opción, como se explica más adelante en este tema, es capturar una referencia fuerte o una débil al objeto this en la función lambda.
Si el delegado de revocación automática no se registra
Si intentas especificar winrt::auto_revoke al registrar un delegado y el resultado es una excepción winrt::hresult_no_interface, eso generalmente significa que el origen del evento no admite referencias poco seguras. Esa es una situación común en el espacio de nombres Microsoft.UI.Composition, por ejemplo. En esta situación, no puede usar la característica de revocación automática. Tendrás que recurrir a la revocación manual de tus controladores de eventos.
Delegar tipos para acciones y operaciones asincrónicas
En los ejemplos anteriores se usa el tipo de delegado RoutedEventHandler , pero hay muchos otros tipos de delegados. Por ejemplo, las acciones y operaciones asincrónicas (con y sin progreso) tienen eventos completados y en progreso que esperan delegados del correspondiente tipo. Por ejemplo, el evento de progreso de una operación asincrónica con progreso (que es cualquier cosa que implemente IAsyncOperationWithProgress) requiere un delegado de tipo AsyncOperationProgressHandler. Este es un ejemplo de código de creación de un delegado de ese tipo mediante una función lambda. En el ejemplo también se muestra cómo crear un delegado AsyncOperationWithProgressCompletedHandler .
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Web.Syndication.h>
using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Web::Syndication;
void ProcessFeedAsync()
{
Uri rssFeedUri{ L"https://blogs.windows.com/feed" };
SyndicationClient syndicationClient;
auto async_op_with_progress = syndicationClient.RetrieveFeedAsync(rssFeedUri);
async_op_with_progress.Progress(
[](
IAsyncOperationWithProgress<SyndicationFeed,
RetrievalProgress> const& /* sender */,
RetrievalProgress const& args)
{
uint32_t bytes_retrieved = args.BytesRetrieved;
// use bytes_retrieved;
});
async_op_with_progress.Completed(
[](
IAsyncOperationWithProgress<SyndicationFeed,
RetrievalProgress> const& sender,
AsyncStatus const /* asyncStatus */)
{
SyndicationFeed syndicationFeed = sender.GetResults();
// use syndicationFeed;
});
// or (but this function must then be a coroutine, and return IAsyncAction)
// SyndicationFeed syndicationFeed{ co_await async_op_with_progress };
}
Como sugiere el comentario de corrutina anterior, en lugar de usar un delegado con los eventos completados de acciones y operaciones asincrónicas, posiblemente te resulte más natural usar las corrutinas. Para obtener más información y ejemplos de código, consulte Operaciones asincrónicas y simultaneidad con C++/WinRT.
Note
No es correcto implementar más de un controlador de finalización para una acción o operación asincrónica. Puedes tener un solo delegado para su evento completado o bien puedes aplicar co_await. Si tiene ambos, el segundo fallará.
Si continúas con delegados en lugar de con una corrutina, podrás optar por una sintaxis más sencilla.
async_op_with_progress.Completed(
[](auto&& /*sender*/, AsyncStatus const /* args */)
{
// ...
});
Tipos delegados que devuelven un valor
Algunos tipos de delegados deben, por sí mismos, devolver un valor. Un ejemplo es ListViewItemToKeyHandler, que devuelve una cadena. Este es un ejemplo de creación de un delegado de ese tipo (tenga en cuenta que la función lambda devuelve un valor).
using namespace winrt::Microsoft::UI::Xaml::Controls;
winrt::hstring f(ListView listview)
{
return ListViewPersistenceHelper::GetRelativeScrollPosition(listview, [](IInspectable const& item)
{
return L"key for item goes here";
});
}
Acceso seguro al puntero this mediante un delegado de control de eventos
Si maneja un evento mediante una función miembro de un objeto, o desde una función lambda dentro de la función miembro de un objeto, entonces debe considerar los tiempos de vida relativos del receptor del evento (el objeto que maneja el evento) y del origen del evento (el objeto que genera el evento). Para obtener más información y ejemplos de código, consulta Referencias seguras y débiles en C++/WinRT.
API importantes
- estructura de marcador winrt::auto_revoke_t
- función winrt::implements::get_weak
- función winrt::implements::get_strong