Сегодня предлагаю посмотреть более внимательно на события, ну и конечно смотря на события, нам не уйти от делегатов.
В чем принципиальная разница между делегатами (не потомками класса 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.
За сим все... пойду пить чай ;)
Комментариев нет:
Отправить комментарий