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

Вносим изменения в главную форму сервера

Последний штрих в нашем сервере – изменение его главного окна. Сюда необходимо добавить менюшку. В студии откройте редактор формы.

Наведите мышь на Панель элементов (у меня она слева) и выберете там элемент MenuStrip.

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

И потом ещё дал им имена в Свойствах.

Теперь открываем код формы:

В самый верх кода добавляем один неймспейс. Нам потребуются работа с файлами, потому нам нужен будет IO.

using System.IO;

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

List<Plugin> connectedPlugins = new List<Plugin>();

Теперь проматываем вниз и добавляем туда метод создания и подключения плагина к серверу. Основную часть работы мы уже сделали в классе Plugin, потому сейчас мы будем использовать его функционал. Объявляем новый метод и в нём блок try-catch.

/// <summary>

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

/// </summary>

/// <param name="path">Путь к библиотеке.</param>

private void ConnectSinglePlugin(string path)

{

try

{

}

catch (Exception exc)

{

MessageBox.Show(this, exc.Message + "\r\n" + exc.StackTrace, "Ошибка подеключения плагина");

}

}

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

Plugin plugin = null;

try

{

plugin = new Plugin(path); // Вызываем конструктор

}

catch (FileNotFoundException) // Обрабатваем тот случай, когда файла внезапно не оказалось.

{

MessageBox.Show(this, "Плагин \r\n" + path + "\r\n не существует. Такие дела.", "Ошибка подключения плагина");

}

catch (CannotLoadDllAsPluginException exc)

{

// Обработаем случай, когда библиотеку не удалось загрузить как плагин.

DialogResult result = MessageBox.Show(this, "Не удалось загрузить библиотеку как плагин\r\n" + path + "\r\n" + exc.Message +

"\r\n\r\nУдалить плагин из директории плагинов?", "Ошибка загрузки плагина", MessageBoxButtons.YesNo);

if (result == DialogResult.Yes) // Предложим пользователю удалить плагин из директории плагинов, чтобы в следующий раз не видеть то же самое сообщение.

(new FileInfo(path)).Delete();

}

catch (Exception exc)

{

MessageBox.Show(this, exc.Message + "\r\n" + exc.StackTrace, "Ошибка подключения плагина"); // Обработчик наиболее общего исключения. Просто покажем стэк вызовов.

}

Далее, всё в том же блоке try-catch, подключим плагин через метод Connect. Обработаем ещё пачку исключений.

if (plugin != null) // Если плагин создался нормально.

{

IZigzagControl zigzag = new ZigzagControl(); // Создаём новый ZigzagControl

try

{

plugin.Connect(zigzag); // и отправляем его в плагин при подключении.

if (plugin.IsConnected) // Если подключение прошло успешно, то

{

}

else

((IDisposable)control).Dispose(); // В случае, если плагин не подключился, освобождаем экземпляр ZigzagControl.

}

catch (InternalPluginErrorException) // Обрабатываем ситуацию, когда в плагине случилась какая-то внутренняя ошибка.

{

((IDisposable)control).Dispose(); // В случае, если плагин не подключился, освобождаем экземпляр ZigzagControl.

DialogResult result = MessageBox.Show(this, "Не удалось загрузить библиотеку как плагин\r\n" + path + "\r\nПри попытке подключить плагин в нем произошла внутренняя ошибка.\r\nПлагин: " + path + "\r\nУдалить плагин из директории плагинов?", "Ошибка загрузки плагина", MessageBoxButtons.YesNo);

if (result == DialogResult.Yes) // Предложим пользователю удалить плагин из директории плагинов, чтобы в следующий раз не видеть то же самое сообщение.

File.Delete(path);

}

}

Наконец, если всё прошло хорошо и плагин подключился, заполняем нашу менюшку действиями плагина. Пишем в if:

int actionsCount = plugin.ActionsList.Count; // парсим действия, доступные из плагина.

ToolStripMenuItem pluginMenu = new ToolStripMenuItem(); // Создаём новый пункт меню.

pluginMenu.Name = "Plugin" + connectedPlugins.Count.ToString() + "_Menu";

pluginMenu.Text = plugin.Name; // Присваиваем тексту пункта меню имя плагина. В этот пункт мы будет запихивать все действия, доступные из плагина.

List<PluginToolStripMenuItem> pluginMenuItems = new List<PluginToolStripMenuItem>(); // Создаём список для этих действий.

for (int i = 0; i < actionsCount; i++) // И в цикле добавляем подпункты с действиями.

{

PluginToolStripMenuItem pluginToolStripMenuItem = new PluginToolStripMenuItem(connectedPlugins.Count, i); // Создаём подпункт меню. Эти два аргумента – числовые идентификатор плагина и идентификатор действия. Они будут передаваться в событие PluginActionRequested через PluginToolStripMenuItemClickEventArgs.

pluginToolStripMenuItem.PluginActionRequested += new PluginToolStripMenuItemClickEventHandler(pluginToolStripMenuItem_PluginActionRequested); // Вешаем на него обработчик события нажатия -- он будет один на все подпункты.

pluginToolStripMenuItem.Name = "Plugin" + connectedPlugins.Count.ToString() + "_Action" + i.ToString(); // Задаём подпункту имя

pluginToolStripMenuItem.Text = plugin.ActionsList[i].ActionName; // Текст подпункта будет равен названию действия плагина.

pluginMenuItems.Add(pluginToolStripMenuItem);

}

pluginMenu.DropDownItems.AddRange(pluginMenuItems.ToArray()); // Добавляем подпункты-действия в пункт-плагин.

plugins_ToolStripMenuItem.DropDownItems.Add(pluginMenu);

connectedPlugins.Add(plugin); // Добавляем подключенный плагин к нашему списку.

ComServer.IncreasePluginsCount(); // Просим сервер учесть, что мы подключили один плагин.

Для контроля, весь метод целиком:

/// <summary>

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

/// </summary>

/// <param name="path">Путь к библиотеке.</param>

private void ConnectSinglePlugin(string path)

{

try

{

Plugin plugin = null;

try

{

plugin = new Plugin(path); // Вызываем конструктор

}

catch (FileNotFoundException) // Обрабатваем тот случай, когда файла внезапно не оказалось.

{

MessageBox.Show(this, "Плагин \r\n" + path + "\r\n не существует. Такие дела.", "Ошибка подключения плагина");

}

catch (CannotLoadDllAsPluginException exc)

{

// Обработаем случай, когда библиотеку не удалось загрузить как плагин.

DialogResult result = MessageBox.Show(this, "Не удалось загрузить библиотеку как плагин\r\n" + path + "\r\n" + exc.Message +

"\r\n\r\nУдалить плагин из директории плагинов?", "Ошибка загрузки плагина", MessageBoxButtons.YesNo);

if (result == DialogResult.Yes) // Предложим пользователю удалить плагин из директории плагинов, чтобы в следующий раз не видеть то же самое сообщение.

(new FileInfo(path)).Delete();

}

catch (Exception exc)

{

MessageBox.Show(this, exc.Message + "\r\n" + exc.StackTrace, "Ошибка подключения плагина"); // Обработчик наиболее общего исключения. Просто покажем стэк вызовов.

}

if (plugin != null) // Если плагин создался нормально.

{

IZigzagControl zigzag = new ZigzagControl(); // Создаём новый ZigzagControl

try

{

plugin.Connect(zigzag); // и отправляем его в плагин при подключении.

if (plugin.IsConnected) // Если подключение прошло успешно, то

{

int actionsCount = plugin.ActionsList.Count; // парсим действия, доступные из плагина.

//if (actionsCount == 0)

//{

// plugin.Disconnect();

// return;

//}

ToolStripMenuItem pluginMenu = new ToolStripMenuItem(); // Создаём новый пункт меню.

pluginMenu.Name = "Plugin" + connectedPlugins.Count.ToString() + "_Menu";

pluginMenu.Text = plugin.Name; // Присваиваем тексту пункта меню имя плагина. В этот пункт мы будет запихивать все действия, доступные из плагина.

List<PluginToolStripMenuItem> pluginMenuItems = new List<PluginToolStripMenuItem>(); // Создаём список для этих действий.

for (int i = 0; i < actionsCount; i++) // И в цикле добавляем подпункты с действиями.

{

// Создаём подпункт меню. Эти два аргумента – числовые идентификатор плагина и идентификатор действия. Они будут передаваться в событие PluginActionRequested через PluginToolStripMenuItemClickEventArgs.

PluginToolStripMenuItem pluginToolStripMenuItem = new PluginToolStripMenuItem(connectedPlugins.Count, i);

pluginToolStripMenuItem.PluginActionRequested += new PluginToolStripMenuItemClickEventHandler(pluginToolStripMenuItem_PluginActionRequested); // Вешаем на него обработчик события нажатия -- он будет один на все подпункты.

pluginToolStripMenuItem.Name = "Plugin" + connectedPlugins.Count.ToString() + "_Action" + i.ToString(); // Задаём подпункту имя

pluginToolStripMenuItem.Text = plugin.ActionsList[i].ActionName; // Текст подпункта будет равен названию действия плагина.

pluginMenuItems.Add(pluginToolStripMenuItem);

}

pluginMenu.DropDownItems.AddRange(pluginMenuItems.ToArray()); // Добавляем подпункты-действия в пункт-плагин.

plugins_ToolStripMenuItem.DropDownItems.Add(pluginMenu);

connectedPlugins.Add(plugin); // Добавляем подключенный плагин к нашему списку.

ComServer.IncreasePluginsCount(); // Просим сервер учесть, что мы подключили один плагин.

}

else

((IDisposable)control).Dispose(); // В случае, если плагин не подключился, освобождаем экземпляр ZigzagControl.

}

catch (InternalPluginErrorException) // Обрабатываем ситуацию, когда в плагине случилась какая-то внутренняя ошибка.

{

((IDisposable)control).Dispose(); // В случае, если плагин не подключился, освобождаем экземпляр ZigzagControl.

DialogResult result = MessageBox.Show(this, "Не удалось загрузить библиотеку как плагин\r\n" + path + "\r\nПри попытке подключить плагин в нем произошла внутренняя ошибка.\r\nПлагин: " + path +

"\r\nУдалить плагин из директории плагинов?", "Ошибка загрузки плагина", MessageBoxButtons.YesNo);

if (result == DialogResult.Yes) // Предложим пользователю удалить плагин из директории плагинов, чтобы в следующий раз не видеть то же самое сообщение.

File.Delete(path);

}

}

}

catch (Exception exc)

{

MessageBox.Show(this, exc.Message + "\r\n" + exc.StackTrace, "Ошибка подеключения плагина");

}

}

Обработчик нажатия на пункт меню мы используем, но пока ещё не объявили. Исправим это – объявляем ниже.

void pluginToolStripMenuItem_PluginActionRequested(object sender, PluginToolStripMenuItemClickEventArgs e)

{

int result = connectedPlugins[e.PluginId].PerformAction(connectedPlugins[e.PluginId].ActionsList[e.ActionId].ActionId);

if (result != 0)

{

if (result == -100)

MessageBox.Show(this, "Выполнение данного действия вызвало внутреннюю ошибку плагина.", "Внутренняя ошибка плагина");

}

}

Я уже говорил, что мне не нравятся все этим «коды ошибок», может быть исправлю позже, пока оставляю это на вас. Что здесь происходит? У нас есть список подключенных плагинов. Индекс плагина в этом списке идентифицирует каждый отдельый плагин. Кроме того, у каждого действия плагина есть свой уникальный внутри этого плагина числовой код-идентфикатор, который задаётся самим плагином. Когда мы загружали плагины, мы создавали пункты меню с помощью нашего класса PluginToolStripMenuItem, в конструктор которому мы передавали индексы плагина и действий. Таким образом, когда мы тыкаем мышкой в какой-то пункт меню, он сгенерит событие с этими числами, а мы их используем в обработчике.

Теперь добавим метод подключения нового плагина к приложению. Допустим, что юзер хочет подключить плагин. Он указывает путь к библиотеке с плагином, и приложение должно будет теперь всегда подключать его, после каждого своего запуска. Для этого мы будем копировать библиотеку в каталог plugins, как и условились в задании. Пишем следующий метод:

private void AddNewPlugin(string path)

{

string filename = (new FileInfo(path)).Name; // Путь к подключаемому файлу.

string executablePath = Application.ExecutablePath; // Путь к исполняемому файлу сервера.

string exeDir = (new FileInfo(executablePath)).Directory.FullName; // Путь к директории сервера

if (!Directory.Exists(exeDir + "\\plugins")) // Проверяем наличие директории для плагинов

Directory.CreateDirectory(exeDir + "\\plugins"); // и создаём её в случае необходимости

File.Copy(path, exeDir + "\\plugins\\" + filename, true); // Копируем библиотеку с плагином в нашу диреторию.

string newPath = exeDir + "\\plugins\\" + filename; // Получаем путь к нашему плагину уже в нашей директории.

ConnectSinglePlugin((new FileInfo(newPath)).FullName); // Подключаем плагин.

}

Тут всё понятно из комментариев к коду. Есть одно но: если библиотеке нужны какие-то ещё дополнительные файлы, то всё плохо и необходимо просить юзера самого копировать их в каталог plugins. Хорошим решением будет обязать создателей плагинов делать инсталляторы на тот случай, если библиотеке нужны будут дополнительные файлы, либо учить плагины искать файлы в строго отведённых местах, а не в ближайшей директории.

Заюзаем наш последний метод и добавим обработчик к пункту меню «Connect plugin…»

Инфа из области usability: если после нажатия на какой-либо элемент интерфейса появляется дополнительное окно, в винде принято завершать текст этого элемента многоточием.

Откроем редактор формы и дважды щёлкнем по этому пункту.

Появится обработчик события. В нём мы используем стандартный виндовый OpenFileDialog и попросим пользователя указать нам нужный файл.

/// <summary>

/// Обработчик клика по пункту меню "Connect plugin..."

/// </summary>

private void connectPlugin_ToolStripMenuItem_Click(object sender, EventArgs e)

{

OpenFileDialog ofd = new OpenFileDialog();

ofd.CheckFileExists = true;

//ofd.DefaultExt = ".dll";

ofd.Filter = "dll files (*.dll)|*.dll"; // Добавим фильтры - нам нужны только dll-ки.

ofd.FilterIndex = 1;

ofd.Multiselect = false;

ofd.RestoreDirectory = false;

ofd.Title = "Укажите dll-файл плагина";

if (ofd.ShowDialog() == DialogResult.OK)

AddNewPlugin(ofd.FileName);

}

Думаю, что тут всё прозрачно.

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

private void ConnectPlugins()

{

string exeDir = (new FileInfo(Application.ExecutablePath)).Directory.FullName; // Получим путь к директории сервера.

Directory.SetCurrentDirectory(exeDir); // Установим рабочий каталог сервера.

if (!Directory.Exists("plugins")) // Проверим наличие папки с плагинами и создадим её в случае отсутствия.

Directory.CreateDirectory("plugins");

FileInfo[] pluginDlls = (new DirectoryInfo("plugins")).GetFiles("*.dll", SearchOption.TopDirectoryOnly); // Найдём все файлы по маске *.dll в этом каталоге plugins.

foreach (FileInfo pluginDll in pluginDlls)

ConnectSinglePlugin(pluginDll.FullName); // В цикле попытаемся подключить каждую библиотеку.

}

ВАЖНО: если сервер был запущен средой COM по требованию клиента, то его начальный рабочий каталог - "[SysDrive]:\Windows\System32\"

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

private void DisconnectPlugins()

{

for (int i = 0; i < connectedPlugins.Count; i++)

if (connectedPlugins[i].IsConnected) // Если плагин подключен.

{

ComServer.DecreasePluginsCount(); // Сообщим сереру, что отключаем плагин.

connectedPlugins[i].Disconnect(); // Вызовем метод отключения плагина.

}

connectedPlugins.Clear(); // Очистим наш список подключенных плагинов.

}

Исправим конструктор класса MainForm и добавим туда вызов подключения плагинов. Конструктор теперь будет выглядеть вот так:

public MainForm()

{

InitializeComponent();

SetUACShields(); // Вешаем красивые изображения щитов на кнопки.

ComServer.InitServer();

control = new ZigzagControl();

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

ComServer.OnComReleased += new ComReleased(ComServer_OnComReleased);

ZigzagControl.Zigzag.PenChanged += new PenChangedDelegate(Zigzag_PenChanged);

ZigzagControl.Zigzag.PointListChanged += new PointListChangedDelegate(Zigzag_PointListChanged);

RefreshImage(); // Получаем картинку

ConnectPlugins(); // Подключаем все плагины из директории plugins

}

И нам осталось только отключить плагины в тот момент, когда форма будет закрываться. Перейдём к визуальному редактору формы, выберем форму (не какой-то её элемент, а саму форму, кликайте по заголовку окна). Затем переходим к окну свойств формы, кликаем по пиктограмме молнии, нам показывают список событий формы, ищем событие FormClosing и дважды кликаем в поле правее от него.

Создаётся обработчик закрытия формы. В нём мы делаем следующее:

/// <summary>

/// Обработчик события закрытия формы.

/// </summary>

private void MainForm_FormClosing(object sender, FormClosingEventArgs e)

{

DisconnectPlugins(); // Запустим отключение плагинов

Hide(); // Спрячем форму.

bool isReady = false;

while (!isReady) // Вежливо подождём, пока отключатся все плагины.

{

isReady = true;

for (int i = 0; i < connectedPlugins.Count; i++)

if (connectedPlugins[i].IsConnected)

isReady = false;

Application.DoEvents();

}

}

Теперь форма исправлена и готова к поддержке плагинов. Запустите приложение и убедитесь, что мы ничего не поломали. В случае чего, сверьтесь с кодом из примеров и найдите разницу. Для поиска разницы может использоваться, например, Araxis Merge или WinMerge. Запустите также клиент и побалуйтесь с ним.

Если всё хорошо, то нас можно поздравить, можно переходить к созданию плагинов.

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