Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Lab3.docx
Скачиваний:
0
Добавлен:
01.05.2025
Размер:
3.22 Mб
Скачать

Реализация формы второго плагина

Начинаем с того, что откроем заголовочный файл формы.

Там будет что-то вроде этого:

Добавим в секцию инклудов ещё один. Как и в других плагинах, мы собираемся передавать в форму ZigzagControl, да и вообще использовать много типов с сервера, потому подключим сгенерированный заголовочный файл нашего сервера.

#include "ZigzagServer_TLB.h"

Сейчас наша форма не имеет методов с модификаторами private и public. Объявим их, заменив всю область с имющимися модификаторами следующим кодом:

private:

TPicture* __fastcall TMainForm::GetImage(__int64 currentTime,

long* pointsX,

long* pointsY,

__int64* pointsTime,

long pointsCount);

void __fastcall TMainForm::RefreshTrackbarStatus();

public: // User declarations

__fastcall TMainForm(TComponent* Owner);

void __fastcall RefreshInfo(IZigzagControlDisp ZigzagControl);

void __fastcall PointListChanged();

GetImage будет возвращать изображение ломаной в зависимости от момента времени, RefreshTrackbarStatus будет обновлять таймлайновый трекбар, RefreshInfo будет обновлять информацию о точках на форме, загружая эту информацию с сервера. Всё так же, как и в третьем плагине.

Небольшое лирическое отступления на тему особенностей работы с COM-классами в Borland C++ Builder

IZigzagControlDisp – это автоматически сгенерированный билдером класс для поддержки диспинтерфейса IZigzagControl. Так как все интерфейсы нашего сервера унаследованны от IDispatch, то все они имеют подобные обёртки в коде, который так любезно сгенерировал для нас билдер. Достаточно просто получить указатель на объект на сервере и привязаться к нему через этот класс-обёртчик, и можно уже запросто обращаться к методам и свойствам данного интерфейса.

Продемонстрирую работу с этими классами. У нас на сервере есть COM-класс ZigzagServer.PluginAction. Чтобы получить новый экземпляр этого класса через COM, достаточно сделать вот так:

IPluginActionDisp pluginActionDisp;

HRESULT hr = pluginActionDisp.Bind(StringToOleStr("ZigzagServer.PluginAction")); // Создадим действие плагина и привяжем его к имени COM-класса.

if (hr != S_OK)

{

*pRetVal = hr;

return Error(StringToOleStr("Ошибка при получении экземпляра класса ZigzagServer.PluginAction"), CLSID_PluginAction, hr);

}

HRESULT – это тип данных, в котором издревле в винде хранятся коды ошибок, если что. S_OK – это константа, равная 0. То есть, если ошибок не было, то большинство методов тут возвращают 0. Про StringToOleStr написано тут. Привязкой к классу таким образом создаётся новый экземпляр посредством вызова конструктора класса без параметров.

Посмотрим как получать доступ к уже созданным экземплярам. Наш метод плагина Connect должен иметь дело с экземпляром класса ZigzagControl. Сохраняем его мы у себя черезвычайно просто:

Connect(LPUNKNOWN IZigzagControl, LPUNKNOWN* IPluginConnectionInfo, long* pRetVal)

{

IZigzagControlDisp zigzagDisp; // Врапер над экземпляром класса ZigzagControl.

// Попытаемся привязать автоматически сгенерированный борландом враппер над ZigzagControl к COM-классу с сервера.

HRESULT hr = zigzagDisp.Bind(IZigzagControl);

// В hr получим результат действия.

if (hr != S_OK) // Сравним его с нулём.

{

*pRetVal = hr; // Если он не равен нулю, то вернём его код в качестве кода ошибки.

return Error(StringToOleStr("Ошибка при попытке привязаться к интерфейсу IZigzagControl"),

IID_IZigzagControl, hr);

}

Здесь продемонстрировано начало реализации метода Connect нашего класса плагина. IZigzagControl в данном методе – это имя аргумента, если что. Если вы хотите прикастить вашу переменную к другому интерфейсу, то делается это тоже через Bind. Вот, например, кусок кода с уничтожением точки и пары:

IPairDisp pair;

IPointDisp point;

IDisposableDisp disposable; // Создаём враппер для IDisposable.

disposable.Bind(point); // Привязываемся к нему по очереди точкой и парой.

disposable.Dispose(); // Освобождаем экземпляры.

disposable.Bind(pair);

disposable.Dispose();

Такие дела, тут всё тоже довольно просто. Лирическое отступление закончено, вернёмся к нашей форме. Перейдём к файлу .cpp:

Будем добавлять в него код сверху вниз, начиная со строки после

TMainForm *MainForm;

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

IZigzagControlDisp zigzagControl; // Переменная, которая даёт управление сервером.

Добавим переменные, отвечающие за перо и изображение на форме.

const int penWidth = 5; // Толщина пера, которым рисуется линия.

const TColor penColor = clBlack; // Цвет пера.

TPicture* lastPicture; // Последнее изображение.

Добавим переменные, в которых будут храниться данные о времени.

const timeInterval = 100; // Интервал времени между тиками.

__int64 maxTime; // Верхняя граница времени, до которой осуществляется проигрывание.

__int64 currentTime; // Текущее время от начала проигрывания.

Далее выделим переменные под хранение данных о точках. Путь это будут обычные массивы.

long pointsCount; // Текущее количество точек в ломаной.

long* pointsX; // Массив координат X точек.

long* pointsY; // Массив координат Y точек.

__int64* pointsTime; // Массив времён создания точек.

И напоследок добавим переменные-флаги – те же самые, что были в форме третьего плагина.

bool isOpened = false; // Открыто ли окно.

bool isChanged = false; // Флаг, которые обращается в true в том случае, если на список точек на сервере изменился.

bool isTick = false; // Происходит ли в данный момент обработка события тика.

Обратите внимание на разницу в типах. Шарповскому int здесь соответствует long, шарповскому long – __int64.

Заменим конструктор класса окна.

//---------------------------------------------------------------------------

// Конструктор главного окна

__fastcall TMainForm::TMainForm(TComponent* Owner)

: TForm(Owner)

{

timer->Interval = timeInterval; // Задаём интервал между тиками.

}

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

//---------------------------------------------------------------------------

//Заставляет окно получить новые данные о точках ломаной с сервера.

void __fastcall TMainForm::RefreshInfo(IZigzagControlDisp ZigzagControl)

{

bool timerState = timer->Enabled; // Сохраним состояние таймера

timer->Enabled = false; // и выключим его.

zigzagControl.Bind(ZigzagControl); // Привяжемся к управлению сервером, чем инициализируем враппер zigzagControl.

isOpened = true; // При создании формы эта переменная станет равной true.

HRESULT hr;

ILockerDisp locker;

locker.Bind(zigzagControl);

locker.Lock(); // Получим монопольный доступ к данным сервера.

try

{

}

catch (...)

{ }

locker.ReleaseLock(); // Освободим монопольный доступ к данным сервера.

if (hr != S_OK)

{

ShowMessage("Во время обновления данных с сервера возникла ошибка: " + IntToHex((__int64)hr, 8));

return;

}

if (pointsCount > 0) // Если список точек не пуст

{

timeline_trackBar->Enabled = true; // Разрешаем юзеру трогать трэкбар, изображающий у нас прогресс таймлайн проигрывания.

maxTime = pointsTime[pointsCount - 1]; // Находим время создания последней точки и запоминаем его.

RefreshTrackbarStatus(); // Обновляем шкалу делений на трэкбаре.

}

else

timeline_trackBar->Enabled = false; // Если же список точек пуст, то и нечего юзеру его трогать.

timer->Enabled = timerState; // Возвращаем исходное состояние таймера.

}

Сравните метод с аналогичным. Давайте посмотрим, что здесь происходит. Вполне возможно, что во время обновления у нас происходит воспроизведение. Я не хотел здесь париться с внедрением потокобезопасного кода, и чтобы мы случайно не обновили что-то, что в данный момент читается в другом потоке и выводится на экран, я просто останавливаю воспроизведение, предварительно запомнив состояние воспроизведения в timerState.

В следующей паре строк я привязал переданную в класс переменную к локальной (фактически я скопировал значение и сделал приведение типов). Потом я установил флаг isOpened, который нам понадобится чуть позже. Далее я получил ссылку на интерфейс ILocker через его класс-обёртку. Это позволило мне получить монопольный доступ к данным сервера.

В блоке try-catch предполагается копирование точек в массивы. Если случается какая-то ошибка, то из этого блока происходит выход с исключением. Если hr не равен нулю, то мы освободим монопольный доступ, форма покажет сообщение об ошибке и метод прекратит работу.

Далее всё точно так же, как и в озвученном выше аналогичном методе. Напишем содержимое блока try-catch:

IList* pairs = zigzagControl.TimePointsPairs; // Получим список пар Время-Точка.

pointsCount = (long)pairs->Count; // Узнаем количество элементов в этом списке.

//Инициализируем массивы для точек.

pointsX = new long[pointsCount];

pointsY = new long[pointsCount];

pointsTime = new __int64[pointsCount];

for (long i = 0; i < pointsCount; i++) // Заполним в цикле массивы.

{

IPairDisp pair;

tagVARIANT varPair = pairs->GetElementAt(i); //Получим следующую пару.

hr = pair.Bind(varPair.punkVal);

if (hr != S_OK)

throw hr; //Если возникла какая-то ошибка, выйдем из цикла.

tagVARIANT varTime = pair.get_Left(); //Аналогичная история с левым и правым значением пары - они тоже возвращают Variant.

__int64 time = varTime.llVal; //Мы знаем, что время у нас __int64, что соответствует long long-у, потому получаем к нему доступ через llVal (long long Value).

IPointDisp point;

hr = point.Bind(pair->get_Right().punkVal); // Точка же - это интерфейс, потому получаем указатель на IUnknown.

if (hr != S_OK)

throw hr; //Если возникла какая-то ошибка, выйдем из цикла.

pointsX[i] = point.X; // Добавляем данные новой точки в массив.

pointsY[i] = point.Y;

pointsTime[i] = time;

IDisposableDisp disposable; // Создаём враппер для IDisposable.

disposable.Bind(point); // Привязываемся к нему по очереди точкой и парой.

disposable.Dispose(); // Освобождаем экземпляры.

disposable.Bind(pair);

disposable.Dispose();

}

Здесь мы видим новый тип – tagVARIANT. В описании интерфейса IList метод GetElementAt помечен как [return: MarshalAs(UnmanagedType.Struct)]. Это значит, что он должен возвращать значение вариантного типа. Про вариантный тип лучше почитать здесь, если вы не знаете, что это такое. Грубо говоря, в переменных вариантного типа можно хранить значение _почти_ любого типа: int, float, void*, и т.д. Используется в основном в COM, а также в Бейсике, где все переменные имеют вариантный тип, если не объявлены явным образом.

Итак, мы получили значение пары в нашу переменную вариантного типа:

tagVARIANT varPair = pairs->GetElementAt(i); //Получим следующую пару.

Дальше мы прикастим её содержимое к IUnknown*, используя свойство punkVal класса tagVARIANT (он имеет кучу свойств для преобразования к широкому множеству типов, введите «varPair.» и полюбуйтесь), а сразу после этого привяжем к ней враппер пары:

hr = pair.Bind(varPair.punkVal);

Как-то так этот вариантный тип и работает. Посмотрим ещё раз на метод GetElementAt интерфейса IPair сервера.

/// <summary>

/// Получает элемент списка по его индексу.

/// </summary>

/// <param name="index">Индекс элемента в списке.</param>

/// <returns>Возвращает элемент списка.</returns>

[return: MarshalAs(UnmanagedType.Struct)]

object GetElementAt([In] int index);

Всякий раз, когда вы на этапе проектирования сервера не можете точно сказать, объекты какого типа будут передаваться или возвращаться через проектируемое вами действие, следует использовать вариантный тип, который в шарпе маршалится через UnmanagedType.Struct.

Итак, метод обновления списка точек целиком:

//---------------------------------------------------------------------------

//Заставляет окно получить новые данные о точках ломаной с сервера.

void __fastcall TMainForm::RefreshInfo(IZigzagControlDisp ZigzagControl)

{

bool timerState = timer->Enabled; // Сохраним состояние таймера

timer->Enabled = false; // и выключим его.

zigzagControl.Bind(ZigzagControl); // Привяжемся к управлению сервером, чем инициализируем враппер zigzagControl.

isOpened = true; // При создании формы эта переменная станет равной true.

HRESULT hr;

ILockerDisp locker;

locker.Bind(zigzagControl);

locker.Lock(); // Получим монопольный доступ к данным сервера.

try

{

IList* pairs = zigzagControl.TimePointsPairs; // Получим список пар Время-Точка.

pointsCount = (long)pairs->Count; // Узнаем количество элементов в этом списке.

//Инициализируем массивы для точек.

pointsX = new long[pointsCount];

pointsY = new long[pointsCount];

pointsTime = new __int64[pointsCount];

for (long i = 0; i < pointsCount; i++) // Заполним в цикле массивы.

{

IPairDisp pair;

tagVARIANT varPair = pairs->GetElementAt(i); //Получим следующую пару.

hr = pair.Bind(varPair.punkVal);

if (hr != S_OK)

throw hr; //Если возникла какая-то ошибка, выйдем из цикла.

tagVARIANT varTime = pair.get_Left(); //Аналогичная история с левым и правым значением пары - они тоже возвращают Variant.

__int64 time = varTime.llVal; //Мы знаем, что время у нас __int64, что соответствует long long-у, потому получаем к нему доступ через llVal (long long Value).

IPointDisp point;

hr = point.Bind(pair->get_Right().punkVal); // Точка же - это интерфейс, потому получаем указатель на IUnknown.

if (hr != S_OK)

throw hr; //Если возникла какая-то ошибка, выйдем из цикла.

pointsX[i] = point.X; // Добавляем данные новой точки в массив.

pointsY[i] = point.Y;

pointsTime[i] = time;

IDisposableDisp disposable; // Создаём враппер для IDisposable.

disposable.Bind(point); // Привязываемся к нему по очереди точкой и парой.

disposable.Dispose(); // Освобождаем экземпляры.

disposable.Bind(pair);

disposable.Dispose();

}

}

catch (...)

{ }

locker.ReleaseLock(); // Освободим монопольный доступ к данным сервера.

if (hr != S_OK)

{

ShowMessage("Во время обновления данных с сервера возникла ошибка: " + IntToHex((__int64)hr, 8));

return;

}

if (pointsCount > 0) // Если список точек не пуст

{

timeline_trackBar->Enabled = true; // Разрешаем юзеру трогать трэкбар, изображающий у нас прогресс таймлайн проигрывания.

maxTime = pointsTime[pointsCount - 1]; // Находим время создания последней точки и запоминаем его.

RefreshTrackbarStatus(); // Обновляем шкалу делений на трэкбаре.

}

else

timeline_trackBar->Enabled = false; // Если же список точек пуст, то и нечего юзеру его трогать.

timer->Enabled = timerState; // Возвращаем исходное состояние таймера.

}

Теперь закодим метод, который рисует изображение ломаной. Он будет даже несколько проще, чем метод отрисовки в прошлом плагине.

Начнём вот с такого варианта, в котором сделана некоторая подготовительная работа.

//---------------------------------------------------------------------------

// Метод получения изображения ломаной в зависимости от временеи.

// В метод передаётся момент времени currentTime и данные о точках - кол-во, координаты, момент времени их создания.

// Метод возвращает изображение ломаной на момент времени currentTime, со всеми точками, которые к тому времени были созданы.

TPicture* __fastcall TMainForm::GetImage(__int64 currentTime,

long* pointsX,

long* pointsY,

__int64* pointsTime,

long pointsCount)

{

TPicture* result = new TPicture; // Создаём картинку

result->Bitmap->Height = Image->Height; // Устанавливаем её высоту и ширину

result->Bitmap->Width = Image->Width;

TCanvas* canvas = result->Bitmap->Canvas; // Получаем канву, на которой можно программно рисовать.

canvas->Pen->Style = psSolid; // Устанавливаем параметры пера - сплошное, чёрное, толщину.

canvas->Pen->Color = clBlack;

canvas->Pen->Width = penWidth;

canvas->Brush->Color = clWhite; // Устанавливаем параметры пера - сплошное, белое.

canvas->Brush->Style = bsSolid;

canvas->FillRect(TRect(0, 0, Image->Width, Image->Height)); // Заливаем прямоугольник канвы белым, используя наши параметры пера.

}

Тут всё понятно по комментариям. Дальше, как и в прошлый раз, нам нужно найти подмножество точек ломаной, созданных к моменту времени currentTime. Эту задачу мы решаем точно так же, как и в прошлый раз – перебираем до тех пор, пока время создания следующей точки не превысит currentTime.

if (pointsTime[lastKnownIndex] <= currentTime) // Если текущее время больше или равно времени создания последней точки

lastPointIndex = lastKnownIndex; // запоминаем индекс последней точки.

else

for (int i = 0; i < pointsCount; i++) // Пробежимся по списку времён создания точек (он же упорядочен)

if (pointsTime[i] >= currentTime) // и найдём ту точку, которая создана уже после currentTime.

{

lastPointIndex = i - 1; // искомым индексом будет i - 1, предыдущая точка

break;

}

Затем, если точек к тому моменту создано не было, возвращаем пустое изображение. Если точка одна, ставим на её месте небольшой круг и так же возвращаем изображение.

if (lastPointIndex == -1) // К этому моменту времени точек в ломаной ещё не было.

return result; // Возвращаем пустое белое изображение.

if (lastPointIndex == 0) // Если к этому моменту времени была создана только одна точка,

{

canvas->Ellipse(pointsX[0] - penWidth / 2, // то ставим её там, рисуя окружность маленького радиуса,

pointsY[0]- penWidth / 2,

pointsX[0] + penWidth / 2,

pointsY[0] + penWidth / 2);

return result; // и возвращаем изображение.

}

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

canvas->MoveTo(pointsX[0], pointsY[0]); // Если точек всё-таки много, то придётся рисовать линии. Переходим в координаты первой точки

for (int i = 0; i <= lastPointIndex; i++) // и в цикле

canvas->LineTo(pointsX[i], pointsY[i]); // рисуем линии к каждой последующей точке.

return result; // Изображение ломаной готово, возвращаем его.

Метод готов. Последующие методы являются практически идентичными аналогами методов из третьего плагина, начиная вот остюда. Можете открыть вторую копию этого документа и сравнивать, например.

Обработчики событий нажатия на кнопки окна:

//---------------------------------------------------------------------------

// Обработчик события нажатия на кнопку Старт.

void __fastcall TMainForm::start_buttonClick(TObject *Sender)

{

timer->Enabled = true; // Включаем таймер.

}

//---------------------------------------------------------------------------

// Обработчик события нажатия на кнопку Пауза.

void __fastcall TMainForm::pause_buttonClick(TObject *Sender)

{

timer->Enabled = false; // Выключаем таймер.

}

//---------------------------------------------------------------------------

// Обработчик события нажатия на кнопку Стоп.

void __fastcall TMainForm::stop_buttonClick(TObject *Sender)

{

timer->Enabled = false; // Выключаем таймер.

timeline_trackBar->Position = 0; // Возвращаем бегунок трэкбара в крайнее левое положение.

currentTime = 0; // Текущее время обращаем в 0.

}

Обработчик события тика таймера:

//---------------------------------------------------------------------------

// Обработчик события тика таймера.

void __fastcall TMainForm::timerTimer(TObject *Sender)

{

isTick = true; // Устанавливаем флаг того, что обрабатывается событие тика таймера.

if (currentTime == maxTime) // Проверим, не равно ли текущее время максимальному.

currentTime = 0; // Если да, то приравняем текущее время к нулю, чем зациклим воспроизведение.

else

currentTime += timeInterval; // Иначе увеличим текущее время на заданный интервал.

if (currentTime > maxTime) // Если мы вдруг превысили текущее время,

currentTime = maxTime; // то приравняем его к максимальному.

timeline_trackBar->Position = (int)(currentTime / timeInterval); // Передвинем указатель на трэкбаре.

if (isChanged) // Если вдруг на сервере изменился список точек ломаной, загружаем новые данные.

{

RefreshInfo(zigzagControl);

isChanged = false; // После сбрасываем флаг наличия новых данных в false.

}

isTick = false;

}

Он немного отличается от подобного метода третьего плагина. Я решил, что наш второй плагин будет обрабатывать события сервера. Если добавить обработку событий COM-сервера в плагинах на управляемом коде было очень легко, то тут пришлось немного погуглить. Но об этом – тоже чуть позже. В конце обработчика события тика таймера мы проверяем состояние переменной isChanged, сигнализирующей о том, что на сервере изменился список точек. Если список точек изменился, загружаем его. Всё так вот просто, да.

Следующие три метода полностью идентичны аналогичным трём методам из третьего плагина, не буду на них заострять внимание:

//---------------------------------------------------------------------------

// Обработчик события изменения положения трэкбара

void __fastcall TMainForm::timeline_trackBarChange(TObject *Sender)

{

if (!isTick) // Если данный метод не вызван обработчиком события тика таймера

currentTime = (__int64)timeline_trackBar->Position * (long)timeInterval; // изменяем текущее время -- это юзер передвинул мышкой бегунок трэкбара.

lastPicture->Free(); // Освобождаем память, занимаемую последним созданным изображением. Сборщика мусора, знаете ли, здесь нет. Не удивлюсь, если у меня где-то что-то утекает.

lastPicture = GetImage(currentTime, pointsX, pointsY, pointsTime, pointsCount); // Получаем новое изображение

Image->Picture = lastPicture; // и помещаем его в окно.

}

//---------------------------------------------------------------------------

void __fastcall TMainForm::RefreshTrackbarStatus()

{

timeline_trackBar->Position = 0; // Задаём минимальное и максимальное положение на трэкбаре

timeline_trackBar->Max = maxTime / timeInterval + 1;

if (maxTime < 1200) // Если с момента создания сервера до времени создания последней точки прошло не более 120 секунд,

timeline_trackBar->Frequency = 10; // то делаем частоту засечек поменьше, 1 засечка в секунду,

else

timeline_trackBar->Frequency = 600; // иначе - одна засечка в минуту.

}

//---------------------------------------------------------------------------

// Обработчик события начала закрытия окна.

void __fastcall TMainForm::FormCloseQuery(TObject *Sender, bool &CanClose)

{

timer->Enabled = false; // Выключаем таймер.

CanClose = false; // Отменяем закрытие.

Hide(); // Прячем окно.

}

А, чуть не забыл про один ньюанс: в методе-обработчике события изменения положения бегунка на трэкбаре мы получаем новое изображение и выводим его на форму. Но тут вам не уютненький C# и здесь нет сборщика мусора, а изображения занимают в памяти очень много, потому старое изображение перед получением нового надо из памяти удалить.

И последний метод формы будет в зависимости от состояния открытости формы и статуса проигрывания либо обновлять данные точек ломаной сразу же (если воспроизведение не идёт), либо устанавливать флаг isChanged, чтобы данные обновились в следующем тике. Этот метод будет вызываться извне в ZigzagPlugin обработчиком события изменения списка точек на сервере.

//---------------------------------------------------------------------------

// Метод, вызываемый обработчиком события изменения точек ломаной.

// То есть, когда на сервере меняются точки ломаной, вызывается этот метод.

void __fastcall TMainForm::PointListChanged()

{

if(isOpened) // Проверим, а были ли открыта наша форма, стоит ли вообще обновлять точки, или можно проигнорить событие.

{

if (timer->Enabled) // Если таймер сейчас работает, то установим флаг изменения точек.

isChanged = true; // В следующий раз обработчик события тика обновит точки, загрузив их с сервера.

else

RefreshInfo(zigzagControl); // Таймер выключен, можно не заботится о потокобезопасности и грузить точки.

}

}

Форма готова.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]