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.
En este tema se muestra cómo registrar y revocar delegados de control de eventos mediante de C++/WinRT. Puede controlar un evento mediante cualquier objeto de tipo función de C++ estándar.
Nota:
Para obtener información sobre cómo instalar y usar la extensión de Visual Studio (VSIX) de C++/WinRT y el paquete NuGet (que juntos proporcionan soporte para plantillas de proyecto y compilación), consulte compatibilidad de Visual Studio 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 mediante 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 agrega el prototipo de función de controlador de eventos adecuado (y una implementación de código auxiliar) a los archivos de origen, listos para que usted los reemplace con su propia implementación.
Nota:
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 agrega los 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 para gestionar el evento, como se muestra a continuación.
// MainPage.xaml
<Button x:Name="myButton" Click="ClickHandler">Click Me</Button>
// MainPage.h
void ClickHandler(
winrt::Windows::Foundation::IInspectable const& sender,
winrt::Windows::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 toma del proyecto Aplicación en blanco (C++/WinRT) en Visual Studio. El código x:Name
de ese elemento Button, cambiará también el nombre de la función accesora generada.
Nota:
En este caso, el origen del evento (el objeto que genera el evento) es el Button 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 cómo gestionar el ciclo de vida de los orígenes y destinatarios de eventos.
En lugar de hacerlo de forma declarativa en el código, puede registrar imperativamente una función miembro para controlar 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, usamos la sobrecarga del constructor RoutedEventHandler que toma un objeto y un puntero a una función miembro.
// MainPage.cpp
MainPage::MainPage()
{
InitializeComponent();
myButton().Click({ this, &MainPage::ClickHandler });
}
Importante
Al registrar el delegado, el ejemplo de código anterior pasa un sin procesar este puntero (que apunta al objeto actual). Para aprender a establecer una referencia fuerte o débil al objeto actual, véase si se 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::Windows::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 formas de construir un RoutedEventHandler. A continuación se muestra el bloque de sintaxis tomado del tema de documentación de RoutedEventHandler (elija de C++/WinRT en la lista desplegable Language en la esquina superior derecha de la página web). Observe los distintos constructores: uno toma una expresión lambda; otra función libre; y otra (la que hemos usado anteriormente) toma un objeto y una función de puntero a 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::Windows::UI::Xaml::RoutedEventArgs const& e) const;
};
La sintaxis del operador de llamada de función también es útil de observar. 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.
Nota:
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. Vamos a tomar el evento UIElement.KeyDown como ejemplo. Visite ese tema y elija C++/WinRT en la lista desplegable Language. En el bloque de sintaxis al principio 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 que estamos tratando) tiene un tipo delegado de KeyEventHandler, ya que es el tipo que se debe pasar al registrar un delegado con este tipo de evento. Por lo tanto, siga ahora el vínculo del tema a ese delegado KeyEventHandler tipo. Aquí, el bloque de sintaxis contiene un operador de llamada de función. Y, como se mencionó anteriormente, le indica cuáles deben ser los parámetros de su delegado.
void operator()(
winrt::Windows::Foundation::IInspectable const& sender,
winrt::Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e) const;
Como puede ver, el delegado debe declararse para tomar 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, tu delegado tomará un IInspectable como remitente y otro IInspectable (ya que ese es el parámetro de tipo del 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 un RoutedEventHandler delegado se está construyendo 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"));
});
}
Puede elegir ser un poco más explícito al construir su delegado. Por ejemplo, si quieres 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::Windows::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 revocar su delegado; lo que significa que el delegado se elimina del registro del evento y no se llamará si vuelve a generarse el evento.
Por motivos de simplicidad, ninguno de los ejemplos de código anteriores mostró cómo hacerlo. Pero este siguiente ejemplo de código almacena el token en el miembro de datos privado del struct y revoca su controlador en el destructor.
struct Example : ExampleT<Example>
{
Example(winrt::Windows::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::Windows::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).
Nota:
Cuando un origen de eventos genera sus eventos de forma sincrónica, puede revocar el controlador y estar seguro de que no recibirá más eventos. Pero en el caso de eventos asincrónicos, incluso después de revocar (y especialmente al hacerlo dentro del destructor), es posible que un evento en curso llegue al objeto después de que haya comenzado su destrucción. Encontrar un lugar para cancelar la suscripción antes de la destrucción podría mitigar el problema o para una solución sólida, consulte Acceso seguro al este puntero de 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 usted. Puede revocar manualmente llamando a la función miembro event_revoker::revoke; pero el revocador de eventos llama automáticamente a esa función cuando sale de su ámbito. La función revocar 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::Windows::UI::Xaml::Controls::Button button)
{
m_event_revoker = button.Click(
winrt::auto_revoke,
[this](IInspectable const& /* sender */,
RoutedEventArgs const& /* args */)
{
// ...
});
}
private:
winrt::Windows::UI::Xaml::Controls::Button::Click_revoker m_event_revoker;
};
A continuación se muestra el bloque de sintaxis tomado del tema de documentación para el evento ButtonBase::Click. Muestra las tres funciones de registro y revocación diferentes. Puede ver exactamente qué tipo de revocador de eventos necesita declarar a partir de la tercera sobrecarga. Además, puede pasar los mismos tipos de delegados tanto para registrar como para revocar usando las sobrecargas de event_revoker.
// Register
winrt::event_token Click(winrt::Windows::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::Windows::UI::Xaml::RoutedEventHandler const& handler) const;
Nota:
En el ejemplo de código anterior, Button::Click_revoker
es un alias de tipo para winrt::event_revoker<winrt::Windows::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 de revocación que devuelve un revocador de eventos, y el tipo de ese revocador es miembro del origen del evento. Así que, tomando otro ejemplo, el evento CoreWindow::SizeChanged tiene una sobrecarga de función de registro que devuelve un valor de tipo CoreWindow::SizeChanged_revoker.
Podrías considerar revocar los controladores en un escenario de navegación de páginas. Si navega repetidamente a una página y luego retrocede, podría revocar cualquier manejador al salir de la página. Alternativamente, si está utilizando la misma instancia de página, compruebe el valor de su token y registre el token solo si aún no se ha establecido previamente (if (!m_token){ ... }
). Una tercera opción es almacenar un revocador de eventos en la página como miembro de datos. Y una cuarta opción, como se describe más adelante en este tema, es capturar una referencia fuerte o débil a este objeto en tu función lambda.
Si el delegado de revocación automática no se puede registrar
Si intenta especificar winrt::auto_revoke al registrar un delegado y el resultado es una excepción winrt::hresult_no_interface, normalmente significa que el origen del evento no admite referencias débiles. Es una situación común en el espacio de nombres Windows.UI.Composition, por ejemplo. En esta situación, no puede usar la característica de revocación automática. Tendrás que recurrir a revocar manualmente tus controladores de eventos.
Delegar tipos para acciones y operaciones asincrónicas
Los ejemplos anteriores usan el RoutedEventHandler tipo de delegado, pero por supuesto hay muchos otros tipos de delegados. Por ejemplo, las acciones y operaciones asincrónicas (con y sin progreso) tienen eventos de finalización y/o de progreso que esperan delegados del tipo correspondiente. Por ejemplo, el evento de progreso de una operación asincrónica con progreso (que es todo lo que implementa 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 "corrutina" anterior, en lugar de usar un delegado con los eventos completados de acciones y operaciones asincrónicas, probablemente te resulte más natural usar corrutinas. Para obtener más información y ejemplos de código, consulte simultaneidad y operaciones asincrónicas con C++/WinRT.
Nota:
No es correcto implementar más de un manejador de finalización para una acción u operación asincrónica. Puede tener un único delegado para el evento completado o puede usar co_await
para ello. Si tiene ambos, entonces el segundo fallará.
Si te apegas a los delegados en lugar de una corrutina, puedes optar por una sintaxis más sencilla.
async_op_with_progress.Completed(
[](auto&& /*sender*/, AsyncStatus const /* args */)
{
// ...
});
Tipos de delegado que devuelven un valor
Algunos tipos de delegado deben 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::Windows::UI::Xaml::Controls;
winrt::hstring f(ListView listview)
{
return ListViewPersistenceHelper::GetRelativeScrollPosition(listview, [](IInspectable const& item)
{
return L"key for item goes here";
});
}
Acceder de manera segura al puntero del con un delegado de manejo de eventos
Si maneja un evento con la función de miembro de un objeto, o desde una función lambda dentro de la función de miembro de un objeto, debe considerar los tiempos de vida relativos del receptor del evento (el objeto que maneja el evento) y la fuente del evento (el objeto que genera el evento). Para obtener más información y ejemplos de código, consulta referencias fuertes y débiles en C++/WinRT.
API importantes
- estructura de marcador winrt::auto_revoke_t
- función winrt::implements::get_weak
- función get_strong de winrt::implements