понедельник, 1 февраля 2010 г.

События и делегаты

Уроки с ребенком сделаны, жена ушла на маникюр, поговорим о позднем связывании?

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

В чем принципиальная разница между делегатами (не потомками класса Delegate, а переменными объявленными типа делегат) и событиями?
1. Делегат может быть переменной, полем. параметром. Событие - только поле.
2. Делегату можно присваивать (d = new MyHandler(...)), можно подписываться ( d += ...) и отписываться (d -= ...). Событие приемлет только подписывание и отписывание.
3. Делегат (если вы имеете к нему доступ) может быть вызван от куда угодно. Событие - только из класса в котором оно объявлено.

Ну и давайте посмотрим пример раскрывающий все эти отличия события от делегата.
С классом Random и компонентом Timer все знакомы? Так вот, давайте напишем компонент который будет вызывать  событие в случайный момент времени (в рамках заданного диапазона). Зачем он может быть нужен? Ну например при моделировании случайных процессов, проверки ключа защиты в случайный момент времени (для усложнения дебагинга)  и т.д.
Создадим библиотеку с CustomControl с компонентом RandomTimer
  public class RandomTimer : Control
  {
    static RandomTimer()
    {
      DefaultStyleKeyProperty.OverrideMetadata(typeof(RandomTimer), new FrameworkPropertyMetadata(typeof(RandomTimer)));
    }
  }


* This source code was highlighted with Source Code Highlighter.
Для решения поставленной задачи создадим свойство задающие интервал срабатывания, отдельный поток который будет это случайное срабатывание генерировать и событие, которое и будет происходить при срабатывании:
    /// <summary>
    /// Поток в котором и будет происходить генерация события
    /// </summary>
    Thread _worker = null;
    /// <summary>
    /// Генератор случайных чисел
    /// </summary>
    static Random _randomizer = new Random();
        public RandomTimer()
    {
      _worker = new Thread(new ThreadStart(MainMethod));
      _worker.Start();
    }
    /// <summary>
    /// Метод выполняемый в отдельном потоке
    /// </summary>
    private void MainMethod()
    {
      while (true)
      {
        int interval = _randomizer.Next(MinInterval, MaxInterval);
        Thread.Sleep(interval);
        OnTick();
      }
    }

    /// <summary>
    /// Минимальное время срабатывания
    /// </summary>
    public int MinInterval;
    /// <summary>
    /// Максимальное время срабатывания
    /// </summary>
    public int MaxInterval;
    /// <summary>
    /// Вклячает/выключает таймер
    /// </summary>
    public bool Enabled { get; set; }
    /// <summary>
    /// Событие срабатывания таймера
    /// </summary>
    public event EventHandler Tick;
    /// <summary>
    /// Вызывает событие Tick в потоке диспетчера текущего компонента
    /// </summary>
    private void OnTick()
    {
      if (this.Dispatcher.Thread == Thread.CurrentThread)
      {
        if (Tick != null)
        {
          Tick(this, new EventArgs());
        }
      }
      else
      {       
        this.Dispatcher.Invoke(new ThreadStart(OnTick), new object[] { });
      }
    }


* This source code was highlighted with Source Code Highlighter.
Давайте посмотрим по шагам, что нам позволяет достичь слово event в строке объявления Tick.
1. На событие нашего таймера могут подписываться методы разных объектов, но ни один из этих методов не может быть добавлен в событие присвоением. Только через +=. Таким образом никогда не произойдет ситуация когда два метода будут уже хранится в событии, а при добавлении третьего из-за ошибки первые два будут стерты.
2. У внешних по отношению к нашему компоненту объектов нет возможности вызвать Tick, его может вызывать только метод находящийся в нашем классе (OnTick, например). Если бы это было не так, то могла бы возникнуть ситуация, когда мы подписались на событие таймера и ожидаем что оно будет происходить не чаще чем в 1 раз в секунду (MinInterval = 1000). Второй объект работающий с этим же таймером, может захотеть вызывать событие чаще, но из-за ограничений event-ов у него ничего не получится.

Ну и пример, как это все можно применить, сделаем гирлянду!
Форма:
<Window x:Class="RandomTimer.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:my="clr-namespace:Lib;assembly=Lib"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="Window1" Height="300" Width="300">
  <Grid>
    <Grid.ColumnDefinitions>
      <ColumnDefinition></ColumnDefinition>
      <ColumnDefinition></ColumnDefinition>
      <ColumnDefinition></ColumnDefinition>
    </Grid.ColumnDefinitions>
    <Rectangle x:Name="rc1" Grid.Column="0"></Rectangle>
    <Rectangle x:Name="rc2" Grid.Column="1"></Rectangle>
    <Rectangle x:Name="rc3" Grid.Column="2"></Rectangle>
    <my:RandomTimer x:Name="rt1" Tick="rt1_Tick"></my:RandomTimer>
    <my:RandomTimer x:Name="rt2" Tick="rt2_Tick"></my:RandomTimer>
    <my:RandomTimer x:Name="rt3" Tick="rt3_Tick"></my:RandomTimer>
  </Grid>
</Window>


* This source code was highlighted with Source Code Highlighter.

Код:
  public partial class Window1 : Window
  {
    public Window1()
    {
      InitializeComponent();
      rt1.MinInterval = 200;
      rt1.MaxInterval = 500;
      rt1.Enabled = true;
      rt2.MinInterval = 400;
      rt2.MaxInterval = 700;
      rt2.Enabled = true;
      rt3.MinInterval = 600;
      rt3.MaxInterval = 900;
      rt3.Enabled = true;
    }

    SolidColorBrush red = new SolidColorBrush(Colors.Red);
    SolidColorBrush green = new SolidColorBrush(Colors.Green);
    SolidColorBrush blue = new SolidColorBrush(Colors.Blue);
    SolidColorBrush yellow = new SolidColorBrush(Colors.Yellow);
    SolidColorBrush magenta = new SolidColorBrush(Colors.Magenta);
    SolidColorBrush purple = new SolidColorBrush(Colors.Purple);

    private void rt1_Tick(object sender, EventArgs e)
    {
      if (rc1.Fill == red)
      {
        rc1.Fill = green;
      }
      else
      {
        rc1.Fill = red;
      }
    }

    private void rt2_Tick(object sender, EventArgs e)
    {
      if (rc2.Fill == blue)
      {
        rc2.Fill = yellow;
      }
      else
      {
        rc2.Fill = blue;
      }
    }

    private void rt3_Tick(object sender, EventArgs e)
    {
      if (rc3.Fill == magenta)
      {
        rc3.Fill = purple;
      }
      else
      {
        rc3.Fill = magenta;
      }
    }
  }


* This source code was highlighted with Source Code Highlighter.

За сим все... пойду пить чай ;)

Комментариев нет:

Отправить комментарий