События (C++/CX)

Тип среда выполнения Windows может объявлять события (то есть публиковать) и клиентский код в том же компоненте или в других компонентах может подписываться на эти события путем связывания методов, называемых обработчиками событий с событием. С одним событием можно связать несколько обработчиков событий. Когда публикующий объект создает событие, это вызывает срабатывание все обработчиков событий. Таким образом, подписывающийся класс может выполнять любые действия, зависящие от конкретной ситуации, когда публикующий объект создает событие. Событие имеет тип делегата, определяющего сигнатуру, которую должны иметь все обработчики событий, чтобы подписаться на данное событие.

Использование событий в компонентах Windows

Многие компоненты в среда выполнения Windows предоставляют события. Например, объект LightSensor инициирует событие ReadingChanged, когда изменяются показания датчика освещенности. При использовании в программе объекта LightSensor можно определить метод, который будет вызываться при возникновении события ReadingChanged. Метод может делать все, что вы хотите сделать; единственное требование заключается в том, что его подпись должна соответствовать подписи вызываемого делегата. Дополнительные сведения о создании обработчика событий делегата и подписке на событие см. в разделе "Делегаты".

Создание пользовательских событий

Объявление

Событие можно объявить в классе ссылки или интерфейсе, задав для него режим доступа public, internal (public/private), public protected, protected, private protected или private accessibility. При объявлении события компилятор автоматически создает объект, имеющий два метода доступа: add и remove. Когда подписывающиеся объекты регистрируют обработчики событий, объект события сохраняет их в коллекции. Когда происходит событие, объект события по очереди вызывает все обработчики в списке. Тривиальное событие (такое как в следующем примере) имеет неявно заданное резервное хранилище, а также неявно заданные методы доступа add и remove . Разработчик также может определять собственные методы доступа, аналогично тому как задаются пользовательские методы доступа get и set в случае свойств. Реализующий класс не может "вручную" перебирать список подписчиков на событие при возникновении тривиального события.

В следующем примере показан способ объявления и инициации события. Обратите внимание, что событие имеет тип делегата и объявлено с помощью символа "^".

namespace EventTest
{
    ref class Class1;
    public delegate void SomethingHappenedEventHandler(Class1^ sender, Platform::String^ s);

    public ref class Class1 sealed
    {
    public:
        Class1(){}
        event SomethingHappenedEventHandler^ SomethingHappened;
        void DoSomething()
        {
            //Do something....

            // ...then fire the event:
            SomethingHappened(this, L"Something happened.");
        }
    };
}

Использование

В следующем примере показано, как подписывающийся класс с помощью оператора += подписывается на событие и предоставляет обработчик событий, вызываемый при возникновении этого события. Обратите внимание, что указанная функция соответствует сигнатуре делегата, который определен на стороне издателя в пространстве имен EventTest .

namespace EventClient
{
    using namespace EventTest;
    namespace PC = Platform::Collections; //#include <collection.h>

    public ref class Subscriber sealed
    {
    public:
        Subscriber() : eventCount(0) 
        {
            // Instantiate the class that publishes the event.
            publisher= ref new EventTest::Class1();

            // Subscribe to the event and provide a handler function.
            publisher->SomethingHappened += 
                ref new EventTest::SomethingHappenedEventHandler(
                this,
                &Subscriber::MyEventHandler);
            eventLog = ref new PC::Map<int, Platform::String^>();
        }
        void SomeMethod()
        {            
            publisher->DoSomething();
        }

        void MyEventHandler(EventTest::Class1^ mc, Platform::String^ msg)
        {
            // Our custom action: log the event.
            eventLog->Insert(eventCount, msg);
            eventCount++;
        }

    private:
        PC::Map<int, Platform::String^>^ eventLog;
        int eventCount;
        EventTest::Class1^ publisher;
    };
}

Предупреждение

Как правило, для обработчика событий лучше использовать именованную функцию, а не лямбда-выражение, чтобы не пришлось внимательно следить за возникновением циклических ссылок. Именованная функция перехватывает указатель this с помощью слабой ссылки, в то время как лямбда-выражение перехватывает его с помощью строгой ссылки и создает циклическую ссылку. Дополнительные сведения см. в разделе Слабые ссылки и разрыв циклов (C++/CX).

Пользовательские методы добавления и удаления

Изнутри у события имеются методы add(), remove() и raise(). Когда клиентский код подписывается на событие, вызывается метод add() и переданный делегат добавляется в список вызова события. Класс публикации вызывает событие, оно вызывает метод raise() и по очереди вызываются все делегаты в списке. Подписчик может удалить себя из списка делегата, в результате чего будет вызван метод remove() события. Компилятор предоставляет версии этих методов по умолчанию, если разработчик не определил их в своем коде; эти события называются тривиальными. Во многих случаях тривиальных событий бывает достаточно.

Для события можно определить пользовательские методы добавления, удаления и инициации, если требуется реализовать пользовательскую логику, выполняемую при добавлении или удалении подписчиков. Например, если имеется ресурсоемкий объект, который требуется только для уведомления о событиях, можно отложить его создание до момента, когда клиент подпишется на событие.

В следующем примере показано, как добавить пользовательские методы добавления, удаления и инициации для события.

namespace EventTest2
{
    ref class Class1;
    public delegate void SomethingHappenedEventHandler(Class1^ sender, Platform::String^ msg);

    public ref class Class1 sealed
    {
    public:
        Class1(){}
        event SomethingHappenedEventHandler^ SomethingHappened;
        void DoSomething(){/*...*/}
        void MethodThatFires()
        {
            // Fire before doing something...
            BeforeSomethingHappens(this, "Something's going to happen.");
            
            DoSomething();

            // ...then fire after doing something...
            SomethingHappened(this, L"Something happened.");
        }

        event SomethingHappenedEventHandler^ _InternalHandler;

        event SomethingHappenedEventHandler^ BeforeSomethingHappens
        {
            Windows::Foundation::EventRegistrationToken add(SomethingHappenedEventHandler^ handler)
            {
                // Add custom logic here:
                //....
                return _InternalHandler += handler;
            }

            void remove(Windows::Foundation::EventRegistrationToken token)
            {
                // Add custom logic here:
                //....
                _InternalHandler -= token;
            }

            void raise(Class1^ sender, Platform::String^ str)
            {

                // Add custom logic here:
                //....
                return _InternalHandler(sender, str);
            }
        }
    };
}

Удаление обработчика событий со стороны подписчика

В некоторых редких случаях может потребоваться удалить обработчик событий для события, на которое вы подписались ранее. Например, может потребоваться заменить его другим обработчиком событий или удалить некоторые удерживаемые им ресурсы. Чтобы удалить обработчик, необходим сохраненный токен EventRegistrationToken, возвращаемый операцией += . Можно использовать оператор -= с этим токеном для удаления обработчика событий. Однако исходный обработчик может по-прежнему вызываться даже после его удаления. Например, состояние гонки может возникнуть, когда источник событий получает список обработчиков и начинает вызывать их. Если обработчик событий удаляется во время этого, список становится устаревшим. Поэтому, если вы планируете удалить обработчик событий, создайте флаг члена. Задайте его, если событие удалено, а затем в обработчике событий проверка флаг и сразу же вернитесь, если оно задано. В следующем примере демонстрируется использование основного шаблона.

namespace EventClient2
{
    using namespace EventTest2;

    ref class Subscriber2 sealed
    {
    private:
        bool handlerIsActive; 
        Platform::String^ lastMessage;

        void TestMethod()
        {
            Class1^ c1 = ref new Class1();
            handlerIsActive = true;
            Windows::Foundation::EventRegistrationToken cookie =
                c1->SomethingHappened += 
                ref new EventTest2::SomethingHappenedEventHandler(this, &Subscriber2::MyEventHandler);
            c1->DoSomething();

            // Do some other work�..then remove the event handler and set the flag.
            handlerIsActive = false;
            c1->SomethingHappened -= cookie;           
        }

        void MyEventHandler(Class1^ mc, Platform::String^ msg)
        {
            if (!handlerIsActive)
                return;
            lastMessage = msg;
        }
    };
}

Замечания

С одним событием может быть связано несколько обработчиков. Источник события поочередно вызывает все обработчики событий из одного потока. Если приемник события выполняет блокировку в методе обработчика событий, он не позволяет источнику события вызывать другие обработчики для этого события.

Порядок, в котором источник события вызывает обработчики событий в приемниках события не является предопределенным и может отличаться от вызова к вызову.

См. также

Система типов
Делегаты
Справочник по языку C++/CX
Справочник по пространствам имен