Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Этот раздел основан на компоненте среда выполнения Windows и клиентском приложении, которые, как показано в разделе среда выполнения Windows components with C++/WinRT, можно создать.
Ниже приведены новые функции, которые добавляет этот раздел.
- Обновите класс среды выполнения термометра, чтобы вызвать событие, когда его температура идет ниже заморозки.
- Обновите основное приложение, которое использует класс среды выполнения термометра, чтобы он обрабатывал это событие.
Note
Сведения об установке и использовании расширения Visual Studio C++/WinRT (VSIX) и пакета NuGet (которые вместе предоставляют шаблон проекта и поддержку сборки) см. в разделе Поддержка C++/WinRT в Visual Studio.
Это важно
Основные понятия и термины, поддерживающие понимание использования и разработки классов среды выполнения с помощью C++/WinRT, см. в статье "Использование API с помощью C++/WinRT и разработки API с помощью C++/WinRT".
Создание ThermometerWRC и ThermometerCoreApp
Если вы хотите повторить описанные в этом разделе изменения, чтобы затем собрать и запустить код, то первым шагом нужно пройти пошаговое руководство в разделе Компоненты среда выполнения Windows с использованием C++/WinRT. Таким образом, у вас будет компонент ThermometerWRC среда выполнения Windows и приложение ThermometerCoreApp Core, которое использует его.
Обновите ThermometerWRC, чтобы генерировать событие
Обновите Thermometer.idl , чтобы выглядеть так, как показано ниже. Вот как объявить событие, тип делегата которого — EventHandler, с аргументом типа числа с плавающей запятой одинарной точности.
// Thermometer.idl
namespace ThermometerWRC
{
runtimeclass Thermometer
{
Thermometer();
void AdjustTemperature(Single deltaFahrenheit);
event Windows.Foundation.EventHandler<Single> TemperatureIsBelowFreezing;
};
}
Сохраните файл. Проект в текущем состоянии не соберётся полностью, но в любом случае выполните сборку сейчас, чтобы сгенерировать обновлённые версии файлов-заглушек \ThermometerWRC\ThermometerWRC\Generated Files\sources\Thermometer.h и Thermometer.cpp. В этих файлах теперь можно увидеть пустые реализации события TemperatureIsBelowFreezing. В C++/WinRT объявленное событие IDL реализуется как набор перегруженных функций (аналогично тому, как свойство реализуется как пара перегруженных функций получения и задания). Одна перегрузка принимает делегат для регистрации и возвращает маркер ( winrt::event_token). Другой метод принимает токен и аннулирует регистрацию соответствующего делегата.
Теперь откройте Thermometer.h и Thermometer.cpp, а затем обновите реализацию класса времени выполнения Thermometer. В Thermometer.h добавьте две перегруженные функции TemperatureIsBelowFreezing, а также закрытый член данных события для их реализации.
// 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;
...
};
}
...
Как видно выше, событие представлено шаблоном структуры winrt::event , параметризованным определенным типом делегата (который может быть параметризован типом args).
В Thermometer.cpp реализуйте две перегруженные функции TemperatureIsBelowFreezing.
// 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
Дополнительные сведения о том, что такое автоматический объект отмены события, см. в статье Отмена регистрации делегата. Вы получаете автоматическую реализацию отзыва событий бесплатно для вашего события. Другими словами, вам не нужно реализовать перегрузку для отзыва событий, которая предоставляется проекцией C++/WinRT.
Другие перегрузки (перегрузки для регистрации и ручного отзыва) не встроены в проекцию. Это позволяет реализовать их оптимально для вашего сценария. Вызов event::add и event::remove, как показано в этих реализациях, является эффективным и потокобезопасным вариантом по умолчанию. Но если у вас есть очень большое количество событий, то вам может не потребоваться поле событий для каждого, а вместо этого выбрать какую-то разреженную реализацию.
Вы также можете увидеть выше, что реализация функции AdjustTemperature была обновлена, чтобы вызвать событие TemperatureIsBelowFreezing , если температура идет ниже замораживания.
Обновление ThermometerCoreApp для обработки события
В проекте ThermometerCoreApp, в App.cpp, внесите следующие изменения в код, чтобы зарегистрировать обработчик события, а затем добиться того, чтобы температура опустилась ниже точки замерзания.
WINRT_ASSERT — это определение макроса, и оно расширяется до _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);
...
}
...
};
Помните об изменении метода OnPointerPressed . Теперь каждый раз, когда вы щелкаете окно, вычитаете 1 градус Фахренхейта из температуры термометра. И теперь приложение обрабатывает событие, которое возникает, когда температура идет ниже замораживания. Чтобы продемонстрировать, что событие вызывается должным образом, поместите точку останова в лямбда-выражение, обрабатывающее событие TemperatureIsBelowFreezing , запустите приложение и щелкните в окне.
Параметрические делегаты на уровне ABI
Если событие должно быть доступно в двоичном интерфейсе приложения (ABI), например между компонентом и его потребляющим приложением, то событие должно использовать тип делегата среда выполнения Windows. В примере выше используется тип делегата среды выполнения Windows Windows::Foundation::EventHandler<T>. TypedEventHandler<TSender, TResult> является другим примером типа делегата среда выполнения Windows.
Параметры типов этих двух типов делегатов должны передаваться через ABI, поэтому они тоже должны быть типами среда выполнения Windows. Это включает в себя Windows классы среды выполнения, классы сторонних сред выполнения и примитивные типы, такие как числа и строки. Компилятор выдаёт ошибку "T должен быть типом WinRT", если вы забыли об этом ограничении.
Ниже приведен пример в виде перечислений кода. Начните с проектов ThermometerWRC и ThermometerCoreApp , созданных ранее в этом разделе, и измените код в этих проектах, чтобы выглядеть как код в этих списках.
Эта первая позиция в списке относится к проекту ThermometerWRC. После редактирования ThermometerWRC.idl, как показано ниже, соберите проект, а затем скопируйте MyEventArgs.h и .cpp в проект (из папки Generated Files), как вы уже делали ранее с Thermometer.h и .cpp. Не забудьте удалить static_assert из обоих файлов.
// 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);
}
}
...
Это описание предназначено для проекта ThermometerCoreApp .
// 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.
});
}
...
Простые сигналы через ABI
Если вам не нужно передавать параметры или аргументы с событием, можно определить собственный простой среда выполнения Windows тип делегата. В приведенном ниже примере показана более простая версия класса среды выполнения Thermometer . Он объявляет тип делегата SignalDelegate , а затем использует его для создания события типа сигнала вместо события с параметром.
// 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);
...
}
...
};
Параметризованные делегаты, простые сигналы и обратные вызовы в проекте
Если вам нужны события, которые используются только внутри проекта Visual Studio, а не между двоичными модулями, и эти события не ограничены типами среда выполнения Windows, то вы по-прежнему можете использовать шаблон класса winrt::event<Delegate>. Просто используйте winrt::d elegate вместо фактического типа делегата среда выполнения Windows, так как winrt::d elegate также поддерживает параметры, отличные от среда выполнения Windows.
В приведенном ниже примере сначала показана подпись делегата, не принимающая никаких параметров (по сути, простой сигнал), а затем — подпись, принимающая строку.
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!");
Обратите внимание, что вы можете добавить в событие столько делегатов, сколько вы хотите. Однако есть некоторые издержки, связанные с событием. Если вам нужен простой обратный вызов с одним подписчиком, то вы можете использовать winrt::delegate<... T> сам по себе.
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!");
Если вы переносите из базы кода C++/CX, где события и делегаты используются внутри проекта, winrt::d elegate поможет вам реплицировать этот шаблон в C++/WinRT.
Отложенные события
Распространенный шаблон в среда выполнения Windows — это отложенное событие. Обработчик событий принимает отсрочку путем вызова метода GetDeferral аргумента события. Это указывает источнику событий, что действия после события должны быть отложены до завершения отсрочки. Это позволяет обработчику событий выполнять асинхронные действия в ответ на событие.
Шаблон структуры winrt::deferrable_event_args является вспомогательным классом для реализации (создания) шаблона отсрочки в среда выполнения Windows. Приведем пример.
// 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;
};
}
Вот как получатель события использует шаблон отложенных событий.
// 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();
});
Как реализатор (производитель) источника событий, вы наследуете свой класс аргументов события от winrt::deferrable_event_args. deferrable_event_args<T> реализует T::GetDeferral за вас. Он также предоставляет новый вспомогательный метод deferrable_event_args::wait_for_deferrals, который завершается после завершения всех невыполненных отложений (если не было отложений, то он завершается немедленно).
// 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;
}
Рекомендации по проектированию
Мы рекомендуем передавать события, а не делегаты в качестве параметров функции. Функция add объекта winrt::event является единственным исключением, поскольку в этом случае необходимо передать делегат. Причина этого руководства заключается в том, что делегаты могут принимать различные формы на разных языках среда выполнения Windows (с точки зрения поддержки одной регистрации клиента или нескольких). События, с их несколькими моделями подписчиков, представляют собой гораздо более предсказуемый и согласованный вариант.
Подпись делегата обработчика событий должна состоять из двух параметров: отправитель (IInspectable) и args (некоторый тип аргумента события, например RoutedEventArgs).
Обратите внимание, что эти рекомендации не обязательно применяются, если вы разрабатываете внутренний API. Хотя внутренние API часто становятся общедоступными с течением времени.
Связанные темы
Windows developer