
- •Добавление к com-серверу поддержки событий
- •Создание модели взаимодействия приложения хоста с плагинами
- •Описание com-интерфейсов, связанных с поддержкой плагинов
- •Создание com-интерфейсов, связанных с поддержкой плагинов
- •Создание com-классов, связанных с поддержкой плагинов
- •Создание класса, отвечающего за взаимодействие с плагинами
- •Код класса Plugin.Cs
- •Внесение косметических изменений в серверную часть
- •Строим своё меню с плагинами и идентификаторами
- •Вносим изменения в главную форму сервера
- •Создание первого плагина
- •Импорт типов с сервера
- •Создание класса для хранения внутреннего представления точек
- •Создание форм редактирования данных о точках
- •Реализация форм редактирования данных о точках
- •Реализация класса первого плагина
- •Создание третьего плагина
- •Делаем наш плагин com-видимым
- •Создание главной формы плагина
- •Реализация класса третьего плагина
- •Создание инсталлятора для плагина
- •Создание второго плагина
- •Создание главной формы плагина
- •Создание библиотеки типов
- •Реализация формы второго плагина
- •Реализация класса второго плагина
- •Добавление метода GetComClassName
- •Data Execution Prevention и его отключение
- •Тестирование совместной работы клиента и плагинов
- •Исходный код
Создание третьего плагина
Да да, не второго, а третьего. Я предпочёл именно такой порядок описания по нескольким причинам. Во-первых, потому, что второй мы будем делать уже не в среде MS VS, а в среде Borland C++ Builder, что создаст дополнительные осложнения. Во-вторых, именно в таком порядке я их и реализовывал сам. В-третьих, второй плагин будет написан на С++ с ATL, что создаст некоторые трудности после привычки писать на C#. Второй плагин очень сильно похож на третий, потому педагогичнее будет начать с более простого третьего. Извиняюсь за неудобства, если что. Изначально в моём ТЗ они были именно в таком порядке, возможно следует в будущих редакциях изменить его для вашего комфорта.
Создайте ещё одно решение. Можно создать отдельный проект в том же решении, но мне было легче создать отдельное решение с проектом библиотеки. Если вдруг забыли как, то освежите память. Нам нужно точно такая же библиотека классов.
Создали? Отлично! На этот раз мы делаем плагин, который рисует «динамическую диаграмму». Это такая же диаграмма, только зависимая от времени. На формочке будет бегунок, прямо как в видеоплеере, которым мы будем указывать позикию проигрывания во времени, а на диаграмме будет отображаться зависимость ординаты последних 8 точек ломаной от времени их создания. Вот так это будет выглядеть:
На этот раз плагин будет использовать технологию COM. Я же постараюсь обойтись минимумом лишнего кода – будем использовать максимум готовых серверных классов.
Первый шаг такой же – добавим ссылку на наш сервер. Как добавить ссылку смотрите всё там же.
Вообще говоря, во многих си-подобных принято импортировать внешние библиотеки типов, .tlb-шные файлы, но в .NET такое никак не получится, если импортируемая сборка написана тоже на .NET, как в нашем случае. Потому приходится указывать непосредственно сборки. И точно так же, как и с первым плагином, мы переименовываем файл Plugin3 и его класс в ZigzagPlugin.
Делаем наш плагин com-видимым
Открываем свойства проекта плагина.
Переходим на вкладку подписывание, ставим галочку в чекбоксе «Подписать сборку», в листбоксе выбираем опцию «Создать».
В открывшемся окне введите любое понравившееся вам имя, можете ввести и логин с паролем, я же снял галочку и обошелся без этого.
Далее переходим обратно на вкладку «Приложение» и жмём на кнопку «Сведения о сборке…»
В открывшемся окне ставим галочку на пункте «Сделать сборку видимой для COM».
Создание главной формы плагина
Уже знакомыми движениями мыши создаём в проекте форму MainForm. Привожу код её файла MainForm.Designer.cs:
namespace Plugin3
{
partial class MainForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.pictureBox = new System.Windows.Forms.PictureBox();
this.refreshDatabutton = new System.Windows.Forms.Button();
this.play_button = new System.Windows.Forms.Button();
this.pause_button = new System.Windows.Forms.Button();
this.stop_button = new System.Windows.Forms.Button();
this.timeline_trackBar = new System.Windows.Forms.TrackBar();
((System.ComponentModel.ISupportInitialize)(this.pictureBox)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.timeline_trackBar)).BeginInit();
this.SuspendLayout();
//
// pictureBox
//
this.pictureBox.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.pictureBox.Location = new System.Drawing.Point(12, 12);
this.pictureBox.Name = "pictureBox";
this.pictureBox.Size = new System.Drawing.Size(900, 500);
this.pictureBox.TabIndex = 0;
this.pictureBox.TabStop = false;
//
// refreshDatabutton
//
this.refreshDatabutton.Location = new System.Drawing.Point(12, 518);
this.refreshDatabutton.Name = "refreshDatabutton";
this.refreshDatabutton.Size = new System.Drawing.Size(125, 23);
this.refreshDatabutton.TabIndex = 1;
this.refreshDatabutton.Text = "Обновить данные";
this.refreshDatabutton.UseVisualStyleBackColor = true;
this.refreshDatabutton.Click += new System.EventHandler(this.refreshDatabutton_Click);
//
// play_button
//
this.play_button.Location = new System.Drawing.Point(307, 518);
this.play_button.Name = "play_button";
this.play_button.Size = new System.Drawing.Size(58, 23);
this.play_button.TabIndex = 2;
this.play_button.Text = "Старт";
this.play_button.UseVisualStyleBackColor = true;
this.play_button.Click += new System.EventHandler(this.play_button_Click);
//
// pause_button
//
this.pause_button.Location = new System.Drawing.Point(371, 518);
this.pause_button.Name = "pause_button";
this.pause_button.Size = new System.Drawing.Size(58, 23);
this.pause_button.TabIndex = 3;
this.pause_button.Text = "Пауза";
this.pause_button.UseVisualStyleBackColor = true;
this.pause_button.Click += new System.EventHandler(this.pause_button_Click);
//
// stop_button
//
this.stop_button.Location = new System.Drawing.Point(435, 518);
this.stop_button.Name = "stop_button";
this.stop_button.Size = new System.Drawing.Size(58, 23);
this.stop_button.TabIndex = 4;
this.stop_button.Text = "Стоп";
this.stop_button.UseVisualStyleBackColor = true;
this.stop_button.Click += new System.EventHandler(this.stop_button_Click);
//
// timeline_trackBar
//
this.timeline_trackBar.LargeChange = 1;
this.timeline_trackBar.Location = new System.Drawing.Point(499, 518);
this.timeline_trackBar.Maximum = 100;
this.timeline_trackBar.Name = "timeline_trackBar";
this.timeline_trackBar.Size = new System.Drawing.Size(413, 45);
this.timeline_trackBar.TabIndex = 5;
this.timeline_trackBar.TickFrequency = 10;
this.timeline_trackBar.ValueChanged += new System.EventHandler(this.timeline_trackBar_ValueChanged);
//
// MainForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(923, 554);
this.Controls.Add(this.timeline_trackBar);
this.Controls.Add(this.stop_button);
this.Controls.Add(this.pause_button);
this.Controls.Add(this.play_button);
this.Controls.Add(this.refreshDatabutton);
this.Controls.Add(this.pictureBox);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
this.Name = "MainForm";
this.Text = "Динамическая диаграмма";
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.MainForm_FormClosing);
((System.ComponentModel.ISupportInitialize)(this.pictureBox)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.timeline_trackBar)).EndInit();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.PictureBox pictureBox;
private System.Windows.Forms.Button refreshDatabutton;
private System.Windows.Forms.Button play_button;
private System.Windows.Forms.Button pause_button;
private System.Windows.Forms.Button stop_button;
private System.Windows.Forms.TrackBar timeline_trackBar;
}
}
Копипастите его в ваш файл и получаете идентичную форму. Остаётся дописать обработчики событий. Займёмся этим сейчас.
Открываем файл кода формы.
Подключаем нужный нам неймспейс InteropService.
using System.Runtime.InteropServices;
Помечаем класс как COM-видимый.
/// <summary>
/// Форма динамической диаграммы, отображающей зависимость Y-координаты точек от времени их создания.
/// </summary>
[ComVisible(false)]
public partial class MainForm : Form
Начинаем добавлять необходимые переменные. Добавим переменные, задающих параметры пера и макимальное количество отображаемых точек.
/// <summary> Количество отображаемых на диаграмме последних созданных точек. </summary>
static readonly int pointCount = 8;
/// <summary> Толщина пера линий графика. </summary>
static readonly int width = 5;
/// <summary> Цвет графика. </summary>
static readonly Color color = Color.Green;
/// <summary> Перо графика. </summary>
Pen pen = new Pen(color, width);
Потом допишем переменные, отвечающие за таймбар и вообще за учёт времени.
/// <summary> Шаг времени во время проигрывания. </summary>
static readonly int timeInterval = 100;
Timer timer = new Timer(); // Таймер.
/// <summary> Верхняя граница времени, до которой осуществляется проигрывание. </summary>
long maxTime;
/// <summary> Текущее время от начала проигрывания. </summary>
long currentTime = 0;
bool isTick = false;
И затем – переменные, дающие доступ к серверу и хранящие точки ломаной.
/// <summary> Сервер. </summary>
object zigzagControl;
/// <summary> Список точек ломаной. </summary>
List<Point> points = new List<Point>();
/// <summary> Список времён их создания. </summary>
List<long> creationTime = new List<long>();
Переходим к методам. Первый метод, который мы реализуем, будет получать список точек с сервера.
//Для фреймворка 4.0 и выше
/// <summary>
/// Загружает данные о точках с сервера.
/// </summary>
/// <param name="zigzagControl">Экземпляр класса ZigzagControl.</param>
public void RefreshData(object zigzagControl)
{
dynamic control = zigzagControl; // Прикастим к динамику объект zigzagControl, хранящийся на форме.
control.Lock(); // Получим монопольный доступ к данным сервера.
try
{
dynamic pairs = control.TimePointsPairs; // Получим список пар Время-Точка с сервера. Адрес этого метода будет разрешен на этапе выполнения программы.
int count = pairs.Count; // Узначем количество точек
List<Point> points = new List<Point>(count); // Создаём списки точек и времён их создания. Заранее указываем ёмкость (= count) списков.
List<long> time = new List<long>(count);
for (int i = 0; i < count; i++) // В цикле считываем точки
{
dynamic pair = pairs.GetElementAt(i); // Получаем очередную пару с сервера.
long t = (long)pair.Left; // В левом элементе - значение времени.
dynamic point = pair.Right; // В правом - точка.
points.Add(new Point(point.X, point.Y)); // Добавляем точку и время в список.
time.Add(t);
point.Dispose(); // Подчищаем за собой. Это необходимо для корректного учёта выданных COM-объектов.
pair.Dispose(); // Для каждого COM-объекта сервера, который мы получили, мы вызываем Dispose в тот момент, когда он перестал нам быть нужен.
// Это касется объектов, которые хранятся в реестре как LocalServer32, а именно Pair, Point, ZigzagControl. У .NET отчего-то плохо с .exe серверами, потому учёт количества приходится вести вручную.
// Все остальные объекты, которые InprocServer32 (PluginAction и PluginConnectionInfo), можно не очищать, за ними среда исполнения следит сама.
}
this.points = points; // Сохраним списки в форме.
this.creationTime = time;
}
catch (Exception exc)
{ ShowExceptionMessage(exc); }
finally
{ control.ReleaseLock(); } // Освободим монопольный доступ к данным сервера.
if (creationTime.Count > 0) // Если список точек не пуст
{
maxTime = creationTime[creationTime.Count - 1]; // Найдём время создания последней точки
RefreshTrackbarStatus(); // и обновим трэкбар, отображающей временную шкалу.
}
else
timeline_trackBar.Enabled = false; // иначе запретим его трогать.
}
Здесь мы используем преимущество четвёртой версии фреймворка и будем юзать тип dynamic (habr). Он облегчает работу с рефлекшеном и обеспечивает лаконичную работу с поздним связыванием. Раньше для вызовов через reflection необходимо было писать громоздкие конструкции, сейчас же всё стало чинно и статно. Логика работы обновления точек точно такая же, как и в предыдущем плагине. Да она везде такая же будет, логику получения точек диктует сервер, а не клиент. В конце метода мы выясняем время создания последней точки и, используя это время, обновляем таймлайн трэкбар (timeline trackbar, бегунок времени). В случае, если в ломаной вообще нет точек, мы блокируем трэкбар для пользователя.
Вот вам для сравнения тот же метод, написанный для Framework 3.5 и ниже.
//Для фреймворка ниже чем 4.0
/// <summary>
/// Загружает данные о точках с сервера.
/// </summary>
/// <param name="zigzagControl">Экземпляр класса ZigzagControl.</param>
public void RefreshData(object zigzagControl)
{
// Этот метод написан здесь для демонстрации старого стиля использования Reflection-а.
// Используйте этот метод, если ваш проект рассчитан на версию фреймворка, которая ниже 4.0.
zigzagControl.GetType().GetMethod("Lock", Type.EmptyTypes).Invoke(zigzagControl, null);
try
{
object pairs = zigzagControl.GetType().GetProperty("TimePointsPairs").GetValue(zigzagControl, null); // Получим список пар Время-Точка с сервера.
//Левый параметр GetValue - экземпляр, для которого мы получаем свойство, правый - массив индексов (на случай, если мы хотим получить элемент массива).
int count = (int)pairs.GetType().GetProperty("Count").GetValue(pairs, null); // Узначем количество точек
List<Point> points = new List<Point>(count); // Создаём списки точек и времён их создания. Заранее указываем ёмкость (= count) списков.
List<long> time = new List<long>(count);
for (int i = 0; i < count; i++) // В цикле считываем точки
{
object pair = pairs.GetType().GetMethod("GetElementAt", new Type[1] { typeof(int) }).Invoke(pairs, new object[1] { i }); // Получаем очередную пару с сервера.
//Invoke принимает в качестве первого параметра объект, для которого будет вызван метод, в качестве второго - массив object-ов, которые передаются в вызываемый метод в качестве аргументов.
long t = (long)pair.GetType().GetProperty("Left").GetValue(pair, null); // В левом элементе - значение времени.
object point = pair.GetType().GetProperty("Right").GetValue(pair, null); // В правом - точка.
int x = (int)point.GetType().GetProperty("X").GetValue(point, null); // Получим координату X точки.
int y = (int)point.GetType().GetProperty("Y").GetValue(point, null); // Получим координату Y точки.
points.Add(new Point(x, y)); // Добавляем точку и время в список.
time.Add(t);
point.GetType().GetMethod("Dispose", Type.EmptyTypes).Invoke(pair, null); // Подчищаем за собой. Это необходимо для корректного учёта выданных COM-объектов.
pair.GetType().GetMethod("Dispose", Type.EmptyTypes).Invoke(pair, null); // Для каждого COM-объекта сервера, который мы получили, мы вызываем Dispose в тот момент, когда он перестал нам быть нужен.
// Это касется объектов, которые хранятся в реестре как LocalServer32, а именно Pair, Point, ZigzagControl. У .NET отчего-то плохо с .exe серверами, потому учёт количества приходится вести вручную.
// Все остальные объекты, которые InprocServer32 (PluginAction и PluginConnectionInfo), можно не очищать, за ними среда исполнения следит сама.
}
this.points = points; // Сохраним списки в форме.
this.creationTime = time;
}
catch (Exception exc)
{ ShowExceptionMessage(exc); }
finally
{ zigzagControl.GetType().GetMethod("ReleaseLock", Type.EmptyTypes).Invoke(zigzagControl, null); }
if (creationTime.Count > 0) // Если список точек не пуст
{
maxTime = creationTime[creationTime.Count - 1]; // Найдём время создания последней точки
RefreshTrackbarStatus(); // и обновим трэкбар, отображающей временную шкалу.
}
else
timeline_trackBar.Enabled = false; // иначе запретим его трогать.
}
Так, в этом методе мы использовали RefreshTrackbarStatus(), а я ведь ещё не написал его. Вот его код:
/// <summary>
/// Обновляет трэкбар, на котором находится бегунок времени воспроизведения.
/// </summary>
private void RefreshTrackbarStatus()
{
timeline_trackBar.Value = 0;
timeline_trackBar.Maximum = (int) maxTime / timeInterval + 1;
//Maximum принимает int, а время хранится в long. Потому вот тут такое вот грязное преобразование.
//Сейчас наследоваться от трэкбара и делать свой мне лень, оставляю это на вас.
if (creationTime[creationTime.Count - 1] < 1200) // Если с момента создания сервера до времени создания последней точки прошло не более 120 секунд,
timeline_trackBar.TickFrequency = 10; // то делаем частоту засечек поменьше, 1 засечка в секунду,
else
timeline_trackBar.TickFrequency = 600; // иначе - одна засечка в минуту.
}
Здесь мы выставляем максимальное значение трэкбара и расстояние между его засечками. Немного говнокодерски написал, все эти числа следовало бы вынести в константы, ну да ладно. Пора написать конструктор, конструктор сам себя не напишет:
/// <summary>
/// Конструктор формы динамической диаграммы, отображающей зависимость Y-координаты точек от времени их создания..
/// </summary>
/// <param name="zigzagControl">Переменная, дающая контроль над сервером.</param>
public MainForm(object zigzagControl)
{
InitializeComponent();
this.zigzagControl = zigzagControl;
RefreshData(zigzagControl); // Загрузим данные о точках с сервера.
timer.Interval = timeInterval; // Зададим таймеру интервал времени. Каждые timeInterval мс таймер будет генерировать событие Tick.
timer.Tick += new EventHandler(timer_Tick); // Повесим обработчик на это событие.
}
В конструкторе мы сохраняем объект ZigzagControl, грузим данные с сервера, задаём интервал между тиками таймера и вешаем обработчик на событие тика. Если вы не знаете, как работает таймер: таймер каждые interval милисекунд генерирует событие Tick.
Форма должна отображать график, потому нам непременно нужен метод, который будет его рисовать. Создадим его. Напоминаю, что нам нужно рисовать график только для тех точек, которые уже были к моменту времени currentTime, переданному в метод в качестве аргумента. Это ведь динамическая диаграмма. У нас тут какбе плеер.
/// <summary>
/// Получает изображение графика зависимости ординаты (Y) от времени создания для pointCount последних созданных точек на момент времени currentTime.
/// </summary>
/// <param name="currentTime">Время в мс от момента создания сервера, для которого будет рисоваться график. В 0 график будет пустым, в максимально возможном времени - на нём будут все созданные точки. </param>
/// <returns>Возвращает изображение диаграммы.</returns>
public Image GetCurrentImage(long currentTime)
{
}
Наполним его содержимым. Начнём с того, что проверим аргумент, не слишком ли он большой, не превышает ли он максимально доступное нам время – время создания последней точки.
if (currentTime > maxTime) // Прогнозами мы не занимаемся, и если время больше максимально известного нам, то мы будем рисовать график для максимального времени.
currentTime = maxTime;
Потом создадим белое изображение. Если точек в ломаной ещё нет, то можно его и возвращать.
Bitmap bitmap = new Bitmap(pictureBox.Width, pictureBox.Height); // Создаём битмап.
Graphics g = Graphics.FromImage((Image)bitmap); // Получаем объект Graphics, на которо можно рисовать.
g.Clear(Color.White); // Заливаем его белым.
if (points.Count == 0) // Если точек нет, то диаграмма готова)
return (Image)bitmap;
В задании мы условились, что будем отображать до 8 последних точек. Потому следующим нашим шагом станет выделение множества последних восьми точек ломаной. Если точек меньше, мы будем брать все имеющиеся, иначе брать последний интервал точек. И то следует рассматривать только те точки, которые были созданы к моменту времени currentTime.
// Найдём индекс последней созданной к моменту времени currentTime точки.
if (creationTime[lastKnownIndex] <= currentTime) // Если текущее время больше или равно времени создания последней точки
lastPointIndex = lastKnownIndex; // запоминаем индекс последней точки.
else
for (int i = 0; i < points.Count; i++) // Иначе пробежимся по списку времён создания точек (он же упорядочен)
if (creationTime[i] > currentTime) // и найдём ту точку, которая создана уже после currentTime.
{
lastPointIndex = i - 1; // искомым индексом будет i - 1, предыдущая точка.
break;
}
if (lastPointIndex == -1) // Такое возможно только в том случае, если к моменту currentTime на сервере точек создано не было.
return (Image)bitmap;
// Нам нужно отображать на графике не более pointCount последних точек. Получим список этих точек в зависимости от того, сколько точек уже было добавлено к этому моменту времени.
if (lastPointIndex <= pointCount - 1)
{
// Точек не хватет или как раз их количество равно pointCount -- записываем в список все известные нам точки.
lastPoints = points.GetRange(0, lastPointIndex + 1);
lastTime = creationTime.GetRange(0, lastPointIndex + 1);
}
else
{
// Точек больше, чем pointCount -- записываем в список только последние pointCount точек.
lastPoints = points.GetRange(lastPointIndex - pointCount + 1, pointCount);
lastTime = creationTime.GetRange(lastPointIndex - pointCount + 1, pointCount);
}
Итак, мы получили список нужных нам точек и времён их создания. Если такая точка только одна, то рисовать график мы не будем (хотя и могли бы поставить точку, но как-то не комильфо) и вернём белое изображение.
if (lastPoints.Count == 1) // По одной точке мы не будем рисовать диаграмму, возвращаем пустую.
return (Image)bitmap;
В противном случае – вычислим масштаб время/ширина по оси X и нарисуем график.
long start = lastTime[0]; // Находим время создания первой
long end = lastTime[lastTime.Count - 1]; // и последней точки.
long period = end - start; // Находим время.
float scale = (float)period / pictureBox.Width; // Получаем масштаб как отношение периода времени к ширине картинки.
for (int i = 0; i < lastPoints.Count - 1; i++) // В цикле рисуем по линиям диаграмму.
{
int x1 = (int)((lastTime[i] - start) / scale);
int x2 = (int)((lastTime[i + 1] - start) / scale);
g.DrawLine(pen, x1, lastPoints[i].Y, x2, lastPoints[i + 1].Y);
}
return (Image)bitmap; // Возвращаем изображение диаграммы.
Этот метод готов. Создадим группу методов-обработчиков событий нажатия кнопок.
/// <summary>
/// Обработчик нажатия кнопки запроса обновления данных с сервера.
/// </summary>
private void refreshDatabutton_Click(object sender, EventArgs e)
{
if (zigzagControl != null)
RefreshData(zigzagControl);
}
/// <summary>
/// Обработчик нажатия на кнопку Старт
/// </summary>
private void play_button_Click(object sender, EventArgs e)
{
timer.Start(); // Запускаем таймер.
}
/// <summary>
/// Обработчик нажатия на кнопку Пауза.
/// </summary>
private void pause_button_Click(object sender, EventArgs e)
{
timer.Stop(); // Останавливаем таймер.
}
/// <summary>
/// Обработчик нажатия на кнопку Стоп.
/// </summary>
private void stop_button_Click(object sender, EventArgs e)
{
timer.Stop(); // Останавливаем таймер.
timeline_trackBar.Value = 0; // Двигаем бегунок трэкбара в левое положение.
currentTime = 0;
}
Этот плагин тоже не будет реагировать на события. Не вижу смысла показывать, как это делать, так как показывал уже в самом начале этого руководства на примере клиента. После того, как мы нажимаем на кнопку Старт, запускается таймер, которые каждые 100мс генерирует событие. Напишем для него обработчик.
/// <summary>
/// Обработчике события тика таймера.
/// </summary>
void timer_Tick(object sender, EventArgs e)
{
if (currentTime == maxTime) // Проверим, не равно ли текущее время максимальному.
currentTime = 0; // Если да, то приравняем текущее время к нулю, чем зациклим воспроизведение.
else
currentTime += (long)timeInterval; // Иначе увеличим текущее время на заданный интервал.
if (currentTime > maxTime) // Если мы вдруг превысили текущее время,
currentTime = maxTime; // то приравняем его к максимальному.
isTick = true; // Установим флаг, показывающий, что действия происходят в обработчике события тика таймера.
timeline_trackBar.Value = (int)(currentTime / timeInterval); // Передвинем указатель на трэкбаре.
isTick = false;
}
Обратите внимание на нижние строчки. Там мы устанавливаем положение бегунка трэкбара. Но надо же ещё дать возможность пользователю выставлять это положение самому. Представьте, что у вас на руках видеопроигрыватель, у которого вы не можете управлять подобным бегунком. Когда пользователь перетягивает бегунок, необходимо менять значение переменной currentTime. Если же положение бегунка устанавливается таймером, то переменную менять не надо. Для этого и используется булевский флаг isTick. Вот обработчик изменения положения бегунка:
/// <summary>
/// Обработчик события изменения состояния бегунка трэкбара.
/// </summary>
private void timeline_trackBar_ValueChanged(object sender, EventArgs e)
{
try
{
if (!isTick) // Если это юзер перемещает мышкой бегунок, если это не движение по таймеру,
currentTime = (long)timeline_trackBar.Value * (long)timeInterval; // то изменим переменную с текущим временем.
pictureBox.Image = GetCurrentImage(currentTime); // Перерисуем график.
GC.Collect(); // Принудительно запустим Garbage Collector, а то изображения уж больно много места жрут.
}
catch (Exception exc)
{ ShowExceptionMessage(exc); }
}
Именно в этом обработчике-то мы и меняем изображение на форме.
И на сладкое я оставил пару мелких методов. Первый служит для отображения сообщений об исключении. Второй – обработчик начала закрытия формы, отменяет закрытие и вместо этого скрывает форму .
/// <summary>
/// Метод для отображения сообщения об исключении.
/// </summary>
/// <param name="exc">Возникшее исключение.</param>
void ShowExceptionMessage(Exception exc)
{
MessageBox.Show(this, exc.Message + "\r\n" + exc.StackTrace, "Exception occuredin plugin3");
}
/// <summary>
/// Обработчик события закрытия формы. Скрывает её вместо того, чтобы уничтожать.
/// </summary>
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
e.Cancel = true;
this.Hide();
}
Форма готова.