Пользовательские события и методы доступа к событиям в компонентах среда выполнения Windows
Поддержка .NET для компонентов среда выполнения Windows упрощает объявление компонентов событий, скрывая различия между шаблоном событий универсальная платформа Windows (UWP) и шаблоном событий .NET. Однако при объявлении пользовательских методов доступа к событиям в компоненте среда выполнения Windows необходимо следовать шаблону, используемому в UWP.
Регистрация событий
При регистрации для обработки события в UWP добавьте маркер. Чтобы отменить регистрацию, передайте этот маркер в средство доступа. Это означает, что для событий UWP добавляются и удаляются разные подписи от используемых вами методов доступа.
К счастью, компиляторы Visual Basic и C# упрощают этот процесс: при объявлении события с пользовательскими средствами доступа в компоненте среда выполнения Windows компиляторы автоматически используют шаблон UWP. Например, вы получите ошибку компилятора, если добавленный метод доступа не возвращает маркер. .NET предоставляет два типа для поддержки реализации:
- Структура EventRegistrationToken представляет токен.
- Класс EventRegistrationTokenTable<T> создает маркеры и поддерживает сопоставление между маркерами и обработчиками событий. Аргумент универсального типа — это тип аргумента события. Вы создаете экземпляр этого класса для каждого события, при первом регистрации обработчика событий для этого события.
В следующем коде события NumberChanged показан базовый шаблон событий UWP. В этом примере конструктор для объекта аргумента события NumberChangedEventArgs принимает один целочисленный параметр, представляющий измененное числовое значение.
Обратите внимание, что это тот же шаблон, что и компиляторы, используемые для обычных событий, объявленных в компоненте среда выполнения Windows.
private EventRegistrationTokenTable<EventHandler<NumberChangedEventArgs>>
m_NumberChangedTokenTable = null;
public event EventHandler<NumberChangedEventArgs> NumberChanged
{
add
{
return EventRegistrationTokenTable<EventHandler<NumberChangedEventArgs>>
.GetOrCreateEventRegistrationTokenTable(ref m_NumberChangedTokenTable)
.AddEventHandler(value);
}
remove
{
EventRegistrationTokenTable<EventHandler<NumberChangedEventArgs>>
.GetOrCreateEventRegistrationTokenTable(ref m_NumberChangedTokenTable)
.RemoveEventHandler(value);
}
}
internal void OnNumberChanged(int newValue)
{
EventHandler<NumberChangedEventArgs> temp =
EventRegistrationTokenTable<EventHandler<NumberChangedEventArgs>>
.GetOrCreateEventRegistrationTokenTable(ref m_NumberChangedTokenTable)
.InvocationList;
if (temp != null)
{
temp(this, new NumberChangedEventArgs(newValue));
}
}
Private m_NumberChangedTokenTable As _
EventRegistrationTokenTable(Of EventHandler(Of NumberChangedEventArgs))
Public Custom Event NumberChanged As EventHandler(Of NumberChangedEventArgs)
AddHandler(ByVal handler As EventHandler(Of NumberChangedEventArgs))
Return EventRegistrationTokenTable(Of EventHandler(Of NumberChangedEventArgs)).
GetOrCreateEventRegistrationTokenTable(m_NumberChangedTokenTable).
AddEventHandler(handler)
End AddHandler
RemoveHandler(ByVal token As EventRegistrationToken)
EventRegistrationTokenTable(Of EventHandler(Of NumberChangedEventArgs)).
GetOrCreateEventRegistrationTokenTable(m_NumberChangedTokenTable).
RemoveEventHandler(token)
End RemoveHandler
RaiseEvent(ByVal sender As Class1, ByVal args As NumberChangedEventArgs)
Dim temp As EventHandler(Of NumberChangedEventArgs) = _
EventRegistrationTokenTable(Of EventHandler(Of NumberChangedEventArgs)).
GetOrCreateEventRegistrationTokenTable(m_NumberChangedTokenTable).
InvocationList
If temp IsNot Nothing Then
temp(sender, args)
End If
End RaiseEvent
End Event
Статический метод GetOrCreateEventRegistrationTokenTable создает экземпляр события объекта EventRegistrationTokenTable<>. Передайте поле уровня класса, которое будет содержать экземпляр таблицы маркеров этому методу. Если поле пусто, метод создает таблицу, сохраняет ссылку на таблицу в поле и возвращает ссылку на таблицу. Если поле уже содержит ссылку на таблицу маркеров, метод просто возвращает такую ссылку.
Важно, чтобы обеспечить безопасность потоков, поле, которое содержит экземпляр события EventRegistrationTokenTable<T> , должно быть полем уровня класса. Если это поле уровня класса, метод GetOrCreateEventRegistrationTokenTable гарантирует, что при попытке нескольких потоков создать таблицу маркеров все потоки получают один и тот же экземпляр таблицы. Для данного события все вызовы метода GetOrCreateEventRegistrationTokenTable должны использовать одно и то же поле уровня класса.
Вызов метода GetOrCreateEventRegistrationTokenTable в методе удаления и в методе RaiseEvent (метод OnRaiseEvent в C#) гарантирует, что исключения не возникают, если эти методы вызываются перед добавлением делегатов обработчика событий.
Другие члены класса EventRegistrationTokenTable<T> , которые используются в шаблоне событий UWP, включают следующее:
Метод AddEventHandler создает маркер для делегата обработчика событий, сохраняет делегат в таблице, добавляет его в список вызовов и возвращает маркер.
Перегрузка метода RemoveEventHandler(EventRegistrationToken) удаляет делегат из таблицы и из списка вызовов.
Обратите внимание , что методы AddEventHandler и RemoveEventHandler(EventRegistrationToken) блокируют таблицу, чтобы обеспечить безопасность потоков.
Свойство InvocationList возвращает делегат, включающий все обработчики событий, которые в настоящее время зарегистрированы для обработки события. Используйте этот делегат, чтобы вызвать событие или использовать методы класса Делегата для вызова обработчиков по отдельности.
Обратите внимание , что рекомендуется следовать шаблону, приведенному ранее в этой статье, и копировать делегат во временную переменную перед вызовом. Это позволяет избежать состояния гонки, в котором один поток удаляет последний обработчик, уменьшая делегат до null непосредственно перед тем, как другой поток пытается вызвать делегат. Делегаты неизменяемы, поэтому копия по-прежнему действительна.
Поместите собственный код в методы доступа соответствующим образом. Если безопасность потоков является проблемой, необходимо указать собственную блокировку кода.
Пользователи C#: при написании пользовательских методов доступа к событиям UWP компилятор не предоставляет обычные сочетания клавиш syntactic. При использовании имени события в коде возникают ошибки.
Пользователи Visual Basic: в .NET событие — это просто делегат многоадресной рассылки, представляющий все зарегистрированные обработчики событий. Вызов события просто означает вызов делегата. Синтаксис Visual Basic обычно скрывает взаимодействие с делегатом, а компилятор копирует делегата перед вызовом, как описано в заметке о безопасности потока. При создании настраиваемого события в компоненте среда выполнения Windows необходимо напрямую справиться с делегатом. Это также означает, что можно, например, использовать метод MulticastDelegate.GetInvocationList для получения массива, содержащего отдельный делегат для каждого обработчика событий, если требуется вызвать обработчики отдельно.