Опубликован: 02.12.2009 | Уровень: специалист | Доступ: свободно | ВУЗ: Тверской государственный университет
Лекция 8:

Классы с событиями

< Лекция 7 || Лекция 8: 12345 || Лекция 9 >

Класс sender

Рассмотрим, как устроен в нашем примере класс, создающий события. Начнем определение класса с задания его свойств и конструктора:

/// <summary>
   /// Класс, создающий событие.
    /// Потомок класса ArrayList.
   /// </summary> 
   public class ListWithChangedEvent: ArrayList 
   {      
    string name;    //имя объекта   
      public event ChangedEventHandler Changed;   //событие
    bool permit;  //результат обработки события
      /// <summary> 
         /// Конструктор
       /// </summary>
       /// <param name="name">имя объекта</param>
       public ListWithChangedEvent(string name)
        { this.name = name; }        
        public string Name
        { get { return name; } }

Первое свойство задает имя объекта, чтобы обработчики могли узнать, кто послал сообщение. Второе свойство описывает событие Changed. Оно открыто, что позволяет присоединять к нему обработчиков событий. Третье свойство задает суммарный итог, сформированный на основании результатов работы всех обработчиков события.

Хороший стиль требует задания в классе процедуры On, включающей событие. Так и поступим:

/// <summary>
/// Процедура On, включающая событие
/// </summary>
/// <param name="args">аргументы события</param> 
protected virtual void OnChanged(ChangedEventArgs args) 
{
   if (Changed != null)
      Changed(this, args);
}

Процедура OnChanged соответствует ранее описанному образцу. Если список обработчиков не пуст, то зажигается событие - посылается сообщение всем обработчикам события. Синтаксически конструкция Changed(this, args) - это вызов списка методов, поскольку Changed - объект функционального типа, к которому прикреплен список последовательно работающих методов.

Если в списке аргументов args есть выходные аргументы, то для решения проблемы коллизии совместной работы обработчиков посылку сообщения обработчикам события следует устроить более сложным образом:

protected virtual void OnChanged(ChangedEventArgs args) 
   {
     int countYes = 0, countNo = 0;
     if (Changed != null)
     {                
        foreach (ChangedEventHandler del in Changed.GetInvocationList())
        {
             del(this, args);
             if (args.Permit) countYes++;
             else countNo++;
        }
        permit = (countYes >= countNo); 
     }
   }

Метод GetInvocationList позволяет получить список обработчиков события, а цикл foreach - вызывать один обработчик за другим. После того, как обработчик завершится, можно понять, каково значение сформированного выходного аргумента события. В данном примере окончательное решение принимается по большинству голосов. Счетчики countYes и countNo считают голоса "за" и "против". Представленное здесь решение демонстрирует корректный способ работы с событиями, когда обработчикам необходимо передавать входные и выходные аргументы.

Перейдем теперь к рассмотрению того, как в нашем классе возникают события. Наш класс, являясь наследником класса ArrayList, наследует все его методы. Переопределим методы, изменяющие элементы:

  • метод Add, добавляющий новый элемент в конец списка;
  • индексатор this, дающий доступ к элементу списка по индексу;
  • метод Clear, производящий чистку списка.
// Переопределяемые методы, вызывающие событие Changed
      public override int Add(object value) 
      {
         int index = -1;
         ChangedEventArgs evargs = 
                new ChangedEventArgs(name, value);
         OnChanged(evargs);
         if (permit) 
            index = base.Add(value);         
         return index;
      }

Обратите внимание на схему включения события в процедуре Add. Вначале создается объект evargs - аргументы события, который передается методу OnChanged. Этот метод поочередно вызовет обработчики события и сформирует итоговый результат их работы. Анализ переменной permit позволяет установить, получено ли разрешение на изменение значения. При истинности значения этой переменной вызывается родительский метод Add, осуществляющий изменение значения. Аналогично устроены и другие методы, в которых возникает событие Changed.

public override void Clear() 
{
   ChangedEventArgs evargs = 
    new ChangedEventArgs(name, 0);
   OnChanged(evargs);
   base.Clear();
}

public override object this[int index] 
{
   set 
   {
      ChangedEventArgs evargs = 
              new ChangedEventArgs(name, value);
      OnChanged(evargs);
          if (permit) 
         base[index] = value;         
   }
   get   {return(base[index]);}
}

Это достаточно типичная схема организации класса с событиями.

Классы receiver

Построим два класса, объекты которых способны получать и обрабатывать событие Changed, возникающее у объектов класса ListWithChangedEvent. Вначале разберемся с устройством одного из этих классов, названного Receiver1. Вот его код:

class Receiver1 
   {
      private ListWithChangedEvent list;
        /// <summary>
        /// Конструктор 
        /// Присоединяет обработчик к событию
        /// </summary>
        /// <param name="list">объект, посылающий сообщение</param>
      public Receiver1(ListWithChangedEvent list) 
      {
         this.list = list;
         // Присоединяет обработчик к событию.
            list.Changed += new ChangedEventHandler(ListChanged);
      }         
      public void OffConnect() 
      {
         // Отсоединяет обработчик  
         list.Changed -= new ChangedEventHandler(ListChanged);
      }
   }//class Receiver1

Класс Receiver1 является примером класса, чьи объекты настроены на прием сообщения от одного объекта класса ListWithChangedEvent. Такой объект передается конструктору класса, который и выполняет всю необходимую работу, присоединяя обработчик события к событию переданного конструктору объекта, так что вызов конструктора класса Receiver1 приводит к созданию объекта, способного принимать и обрабатывать сообщения от объекта, задающего список.

Давайте подробнее рассмотрим устройство обработчика события:

private void ListChanged(object sender, ChangedEventArgs args) 
  {     
    Console.WriteLine("Сообщение послал {0}, " + 
    "элемент = {1}. " + " Сообщение получил: Receiver1 - ",
    args.Name, args.Item);
    if(args.Permit = (int)args.Item < 10)     
        Console.WriteLine("Изменения разрешаю");
    else Console.WriteLine("Изменения не разрешаю");
      }

С входными аргументами все просто. Они используются для формирования сообщения и формирования принимаемого решения. Но стоит подробнее разобраться, как в данном конкретном случае формируется выходной аргумент. Свое решение "Разрешить или не разрешить изменение" обработчик принимает в зависимости от величины элемента ( item ). Свое решение обработчик события принимает независимо, без оглядки на методы, совместно с ним обрабатывающими то же событие.

Класс Receiver2 в отличие от класса Receiver1 позволяет слушать и обрабатывать сообщения нескольких объектов класса sender. С конструктора класса снимается задача связывания события с обработчиком события. Ему не нужно теперь передавать объект класса sender. У класса появляется специальный метод OnConnect, которому передается объект класса sender. Присоединение обработчика события к событию объекта sender выполняется при каждом вызове метода OnConnect.

События, приходящие от разных объектов, могут обрабатываться одним обработчиком класса receiver. Возможно существование в классе набора обработчиков событий, чтобы разные объекты могли иметь и разные методы, обрабатывающие приходящее сообщение о возникшем событии. Так строятся инерфейсные классы, где у каждой командной кнопки свой обработчик события. В нашем примере предусмотрен один обработчик для всех объектов одного класса.

class Receiver2
    {       
        void ListChanged(object sender, ChangedEventArgs args)
        {
            Console.WriteLine("Сообщение послал {0}, " +
            "элемент = {1}. " + " Сообщение получил: Receiver2 - ",
            args.Name, args.Item);
            if (args.Permit =(int)args.Item < 20)
                Console.WriteLine("Изменения разрешаю");
            else Console.WriteLine("Изменения не разрешаю");
        }
        public void OnConnect(ListWithChangedEvent list)
        {
            list.Changed += new ChangedEventHandler(ListChanged);
            //list.Changed = new ChangedEventHandler(ListChanged);
        }
        public void OffConnect(ListWithChangedEvent list)
        {
            list.Changed -= new ChangedEventHandler(ListChanged);
            //list.Changed = null;
        }
    }//class Receiver2

Классы созданы, теперь осталось создать объекты и заставить их взаимодействовать, чтобы одни создавали события, а другие их обрабатывали. Эту часть работы будет выполнять тестирующая процедура класса Testing:

public void TestChangeList()
{
   // Создаются два объекта, вырабатывающие события
   ListWithChangedEvent list1 = new ListWithChangedEvent("list1");
      ListWithChangedEvent list2 = new ListWithChangedEvent("list2");

   // Создаются два объекта классов Receiver1 и Receiver2,
   //способные обрабатывать события класса ListWithChangedEvent
   Receiver1 receiver1 = new Receiver1(list1);
   Receiver2 receiver2 = new Receiver2();
      receiver2.OnConnect(list1);
   receiver2.OnConnect(list2);

// Работа с объектами, приводящая к появлению событий
   Random rnd = new Random();         
   list1.Add(rnd.Next(20)); list1.Add(rnd.Next(20));
      list1.Add(33); list1[1] = 17;   
   list2.Add(10);   list2[0] = 25;   
      list2.Clear();

   //Отсоединение обработчика событий
   receiver1.OffConnect();
   list1.Add(21);
      list1.Clear();
}

В тестирующей процедуре моделируется процесс работы с объектами, посылающими сообщения о событиях и принимающими эти сообщения. Два созданных объекта list1 и list2 посылают сообщения о событиях всякий раз, когда в список добавляется новый элемент или изменяется значение существующего элемента. Два созданных объекта receiver1 и receiver2 получают приходящие сообщения. Первый из них получает сообщения только от объекта list1, второй - от двух объектов list1 и list2. В некоторых ситуациях оба получателя сообщений "дают добро" на изменение элемента, в других ситуациях оба запрещают изменения.

В заключение взгляните на результаты работы этой процедуры.

События в мире объектов list

Рис. 7.3. События в мире объектов list
< Лекция 7 || Лекция 8: 12345 || Лекция 9 >
Федор Антонов
Федор Антонов

Здравствуйте!

Записался на ваш курс, но не понимаю как произвести оплату.

Надо ли писать заявление и, если да, то куда отправлять?

как я получу диплом о профессиональной переподготовке?

Илья Ардов
Илья Ардов

Добрый день!

Я записан на программу. Куда высылать договор и диплом?