Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
В этом разделе показано, как зарегистрировать и отозвать делегаты обработки событий с помощью C++/WinRT. Вы можете обрабатывать событие с помощью любого стандартного объекта функции C++.
Замечание
Для получения информации об установке и использовании расширения Visual Studio C++/WinRT (VSIX) и пакета NuGet (которые вместе обеспечивают поддержку шаблона проекта и сборки), см. подраздел Поддержка Visual Studio для C++/WinRT.
Добавление обработчика событий с помощью Visual Studio
Удобный способ добавления обработчика событий в проект — использование пользовательского интерфейса конструктора XAML в Visual Studio. Откройте страницу XAML в конструкторе XAML, выберите элемент управления, событие которого требуется обрабатывать. На странице свойств для этого элемента управления щелкните значок молнии, чтобы получить список всех событий, инициируемых этим элементом управления. Затем дважды щелкните событие, которое требуется обрабатывать; например, OnClicked.
Дизайнер XAML добавляет в ваши исходные файлы соответствующий прототип функции обработчика событий (и реализацию-заглушку), которые готовы для замены на собственную реализацию.
Замечание
Как правило, обработчики событий не должны быть описаны в файле Midl (.idl
). Поэтому конструктор XAML не добавляет прототипы функций обработчика событий в файл Midl. Он добавляет их только в файлы .h
и .cpp
.
Регистрация делегата для обработки события
Простой пример — обработка события нажатия кнопки. Обычно для регистрации функции-члена для обработки события используется разметка XAML.
// 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"));
}
Код, представленный выше, взят из проекта Blank App (пустое приложение) на языке C++/WinRT в Visual Studio. Код myButton()
вызывает сгенерированную функцию доступа, которая возвращает Button, которую мы назвали myButton. Если изменить
Замечание
В этом случае источник событий (объект, который вызывает событие), является кнопкойс именем myButton. И получатель события (объект, обрабатывающий событие), является экземпляром MainPage. Дополнительные сведения см. в этом разделе об управлении временем существования источников событий и получателей событий.
Вместо выполнения этого декларативно в разметке, вы можете императивно зарегистрировать функцию-член для обработки события. Возможно, это не очевидно из приведенного ниже примера кода, но аргумент для вызова ButtonBase::Click является экземпляром делегата RoutedEventHandler . В этом случае мы используем перегрузку конструктора RoutedEventHandler, которая принимает объект и функцию указателя на член.
// MainPage.cpp
MainPage::MainPage()
{
InitializeComponent();
myButton().Click({ this, &MainPage::ClickHandler });
}
Это важно
При регистрации делегата приведенный выше пример кода передает необработанный этот указатель (указывающий на текущий объект). Чтобы узнать, как установить сильную или слабую ссылку на текущий объект, см. Если вы используете функцию-член в качестве делегата.
Ниже приведен пример, использующий статическую функцию-член; обратите внимание на более простой синтаксис.
// 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 */) { ... }
Существуют другие способы создания RoutedEventHandler. Ниже приведен блок синтаксиса, взятый из раздела документации для RoutedEventHandler (выберите C++/WinRT в раскрывающемся списке "Язык " в правом верхнем углу веб-страницы). Обратите внимание на различные конструкторы: один принимает лямбда-выражение; другой принимает свободную функцию; и еще один (тот, который мы использовали выше) принимает объект и указатель на функцию-член.
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;
};
Синтаксис оператора вызова функции также полезен для изучения. В нем сообщается, какими должны быть параметры вашего делегата. Как видно, в этом случае синтаксис оператора вызова функции соответствует параметрам нашего MainPage::ClickHandler.
Замечание
Для любого заданного события, чтобы выяснить подробности его делегата и параметры этого делегата, сначала перейдите к разделу документации самого события. Рассмотрим событие UIElement.KeyDown в качестве примера. Посетите этот раздел и выберите C++/WinRT в раскрывающемся списке язык. В блоке синтаксиса в начале раздела вы увидите это.
// Register
event_token KeyDown(KeyEventHandler const& handler) const;
Эта информация сообщает нам, что событие UIElement.KeyDown (тема, которую мы обсуждаем) имеет тип делегата KeyEventHandler, так как это тип, который вы передаете при регистрации делегата с этим типом события. Теперь перейдите по ссылке по теме к делегату типа KeyEventHandler. Здесь блок синтаксиса содержит оператор вызова функции. И, как упоминалось выше, это указывает, какими должны быть параметры вашего делегата.
void operator()(
winrt::Windows::Foundation::IInspectable const& sender,
winrt::Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e) const;
Как видно, делегат должен быть объявлен, чтобы принимать IInspectable в качестве отправителя и экземпляр класса KeyRoutedEventArgs в качестве аргумента.
Возьмем другой пример — рассмотрим событие Popup.Closed. Его тип делегата — EventHandler<IInspectable>. Таким образом, ваш делегат будет принимать IInspectable в качестве отправителя, и другой IInspectable (поскольку это параметр типа EventHandler) в качестве аргумента.
Если вы не выполняете много работы в обработчике событий, можно использовать лямбда-функцию вместо функции-члена. Опять же, это может быть не очевидно из приведенного ниже примера кода, но делегат RoutedEventHandler создается из лямбда-функции, которая, опять же, должна соответствовать синтаксису оператора вызова функции, который мы обсуждали выше.
MainPage::MainPage()
{
InitializeComponent();
myButton().Click([this](IInspectable const& /* sender */, RoutedEventArgs const& /* args */)
{
myButton().Content(box_value(L"Clicked"));
});
}
При создании делегата вы можете сделать его чуть более явным. Например, если вы хотите передавать его другим или использовать несколько раз.
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);
}
Отзыв зарегистрированного делегата
При регистрации делегата вам обычно возвращается маркер. Впоследствии этот токен можно использовать для отзыва делегата, что означает, что делегат снимается с регистрации из события и не вызывается, если событие произойдет снова.
Для простоты ни один из приведенных выше примеров кода не показал, как это сделать. Но следующий пример кода сохраняет маркер в члене частных данных структуры и отменяет его обработчик в деструкторе.
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;
};
Вместо строгой ссылки, как в приведенном выше примере, можно сохранить слабую ссылку на кнопку (см. статью "Сильные и слабые ссылки" в C++/WinRT).
Замечание
Когда источник событий вызывает события синхронно, вы можете отозвать обработчик и быть уверены, что больше не будете получать событий. Но для асинхронных событий даже после отзыва (и особенно при отмене в деструкторе) событие в полете может достичь объекта после начала деструкции. Для устранения проблемы можно найти место для отмены подписки до уничтожения, или, для более надежного решения, обратитесь к разделу "Безопасный доступ к этому указателю с делегатом обработки событий".
Кроме того, при регистрации делегата можно указать winrt::auto_revoke (это значение типа winrt::auto_revoke_t), чтобы запросить отзыв события (типа winrt::event_revoker). Отменяющий событие содержит слабую ссылку на источник событий (объект, который вызывает событие) для вас. Вы можете вручную отозвать событие, вызвав функцию-член event_revoker::revoke; однако обработчик событий вызывает эту функцию автоматически, когда выходит за пределы области видимости. Функция аннулировать проверяет, существует ли источник событий и, если это так, отзывает ваш делегат. В этом примере нет необходимости хранить источник событий и не требуется деструктор.
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;
};
Ниже приведен блок синтаксиса, взятый из раздела документации для события ButtonBase::Click . В нем показаны три различных функции регистрации и отзыва. Вы можете точно увидеть, какой тип отзыва событий необходимо объявить из третьей перегрузки. И вы можете передать одинаковые типы делегатов как в регистр, так и в аннулирование с перегрузками 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;
Замечание
В приведённом выше примере кода Button::Click_revoker
— это псевдоним типа для winrt::event_revoker<winrt::Windows::UI::Xaml::Controls::Primitives::IButtonBase>
. Аналогичный шаблон применяется ко всем событиям C++/WinRT. Каждое событие среды выполнения Windows имеет перегрузку функции отзыва, которая возвращает ревокер события, а тип этого ревокера является членом источника события. В качестве ещё одного примера, событие CoreWindow::SizeChanged имеет перегрузку функции регистрации, которая возвращает значение типа CoreWindow::SizeChanged_revoker.
Вы можете рассмотреть возможность отзыва обработчиков в сценарии навигации по страницам. Если вы неоднократно входите на страницу, а затем возвращаетесь назад, вы можете отключить обработчики событий при уходе со страницы. В качестве альтернативы, если вы используете тот же экземпляр страницы, проверьте значение маркера и регистрируйтесь только в случае, если оно еще не установлено (if (!m_token){ ... }
). Третий вариант — хранить обработчик отмены события на странице в качестве переменной данных. И четвертый вариант, как описано далее в этом разделе, заключается в том, чтобы записать сильную или слабую ссылку на этот объект в лямбда-функции.
Если не удалось зарегистрировать делегата автоматического отзыва
Если при регистрации делегата вы пытаетесь указать winrt::auto_revoke, и результатом этого является исключение winrt::hresult_no_interface, то это обычно означает, что источник событий не поддерживает слабые ссылки. Это распространенная ситуация в пространстве имен Windows.UI.Composition , например. В этой ситуации вы не можете использовать функцию автоматического отзыва. Вам придется вернуться к ручному отзыву обработчиков событий.
Типы делегатов для асинхронных действий и операций
В приведенных выше примерах используется тип делегата RoutedEventHandler , но есть, конечно, много других типов делегатов. Например, асинхронные действия и операции (с прогрессом и без него) имеют завершённые и/или ожидающие событий хода выполнения, которые нуждаются в делегатах соответствующего типа. Например, событие прогресса асинхронной операции, которая реализует IAsyncOperationWithProgress, требует делегата типа AsyncOperationProgressHandler. Ниже приведен пример кода для создания делегата этого типа с помощью лямбда-функции. В примере также показано, как создать делегат 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 };
}
Как говорится выше в комментарии "coroutine", вместо делегата, работающего с завершенными событиями асинхронных действий и операций, возможно, будет более естественно использовать корутины. Дополнительные сведения и примеры кода см. в разделе о параллельном выполнении и асинхронных операциях с C++/WinRT.
Замечание
Неправильно реализовывать более чем одного обработчика завершения для асинхронного действия или операции. Вы можете либо назначить одного делегата для завершенного события, либо co_await
его. Если у вас есть оба, в таком случае второй завершится ошибкой.
Если вы придерживаетесь делегатов вместо корутины, вы можете выбрать более простой синтаксис.
async_op_with_progress.Completed(
[](auto&& /*sender*/, AsyncStatus const /* args */)
{
// ...
});
Типы делегатов, возвращающие значение
Некоторые типы делегатов должны сами возвращать значение. Примером является ListViewItemToKeyHandler, который возвращает строку. Ниже приведен пример создания делегата этого типа (обратите внимание, что лямбда-функция возвращает значение).
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";
});
}
Безопасный доступ к этому указателю с делегатом обработки событий
Если вы обрабатываете событие с функцией-членом объекта или из лямбда-функции внутри функции-члена объекта, необходимо подумать о относительных времени существования получателя события (объект, обрабатывающий событие) и источнике событий (объект, вызывающий событие). Дополнительные сведения и примеры кода, см. в сильных и слабых ссылках на C++/WinRT.
Важные API
- структура маркеров winrt::auto_revoke_t
- winrt::implements::get_weak функция
- winrt::implements::get_strong функция