Модификация приложения WindowsForms: функциональность векторного редактора
Для начала определим все необходимые для работы приложения класса внутри самого приложения.
Первый класс с именем файла DrawObject.csстанет основным классом для рисования любых объектов. На его основе будут созданы классы для рисования конкретных объектов типа линий или прямоугольников. Класс будет содержать все возможные функции, события и свойства, необходимы для работы с графическим объектом. А именно этот класс будет определять функция рисования объекта (Draw), выделения, перемещения, изменения размеров и определения количества ключевых точек. Что такое ключевая точка? Для линии это точки на концах линии, которые «подсвечиваются» небольшими прямоугольниками. Если выделить линию и навести мышь на такую точку, курсор будет изменён (зависит от типа точки и объекта рисования). Нажатие на ключевую точку обеспечивает операцию изменения размеров объекта либо перемещения (зависит от объекта рисования). Любой объект во время выделения мышью подсвечивается ключевыми точками. Например, нарисованный и выделенный прямоугольник имеет 8 ключевых точек:
Код файла DrawObject.cs:[искомый код можно найти в приложении к данной лабораторной работе в (описания можно того или иного приложения можно посмотреть в пунтке № 8 протокола работы), а именно необходимо открыть Приложение № 2 (Файлы классов Draw...)].
Класс GraphicsPropertiesнапрямую связан с дочерней формойLWP15Properties. Обработаем форму LWP15Properties и проинициализируем все необходимые свойства и события.
Откроем код формы (выделим LWP15Properies.cs ->нажмём правую кнопку мыши ->Перейти к коду (F7)). Найдём:
public LWP15Properties()
{
InitializeComponent();
}
Добавимпосле:
privateGraphicsProperties properties;
privateconststring undefined = "Незадано";
privateconstint maxWidth = 10;
publicGraphicsProperties Properties
{
get { return properties; }
set { properties = value; }
}
В самое начало формы добавим код:
using System.Globalization;
Найдём:
namespace LWP15Draw
{
public partialclassLWP15Properties : Form
{
Изменимна:
namespace LWP15Draw
{
partialclassLWP15Properties : Form
{
Проинициализируем первое событие формы LWP15Properties: Load со следующим кодом:
privatevoid LWP15Properties_Load(object sender, EventArgs e)
{
InitControls();
SetColor();
SetPenWidth();
}
Вспомогательные функции добавим сразу после предыдущего фрагмента кода:
privatevoid InitControls()
{
for (int i = 1; i <= maxWidth; i++)
{
CB_PenWidth.Items.Add(i.ToString(CultureInfo.InvariantCulture));
}
}
privatevoid SetColor()
{
if (properties.Color.HasValue) L_Color.BackColor = properties.Color.Value;
else L_Color.Text = undefined;
}
privatevoid SetPenWidth()
{
if (properties.PenWidth.HasValue)
{
int penWidth = properties.PenWidth.Value;
if (penWidth < 1) penWidth = 1;
if (penWidth > maxWidth) penWidth = maxWidth;
label2.Text = penWidth.ToString(CultureInfo.InvariantCulture);
CB_PenWidth.SelectedIndex = penWidth - 1;
}
else { label2.Text = undefined; }
}
privatevoid ReadValues()
{
if (CB_PenWidth.Text != undefined) { properties.PenWidth = CB_PenWidth.SelectedIndex + 1; }
if (L_Color.Text.Length == 0) { properties.Color = L_Color.BackColor; }
}
СобытиеSelectedIndexChangedдляComboBoxэтойформы:
privatevoid CB_PenWidth_SelectedIndexChanged(object sender, EventArgs e)
{
int width = CB_PenWidth.SelectedIndex + 1;
L_PenWidth.Text = width.ToString(CultureInfo.InvariantCulture);
}
СобытиеClickнажатиякнопкиB_SelectColor:
privatevoid B_SelectColor_Click(object sender, EventArgs e)
{
ColorDialog dlg = newColorDialog();
dlg.Color = L_Color.BackColor;
if (dlg.ShowDialog(this) == DialogResult.OK)
{
L_Color.BackColor = dlg.Color;
L_Color.Text = "";
}
}
СобытиеClickнажатиякнопкиB_OK:
privatevoid B_OK_Click(object sender, EventArgs e)
{
ReadValues();
this.DialogResult = DialogResult.OK;
}
Форма готова. Если возникнут ошибки, перепроверяем имена элементов управления.
Следующим классом станет класс, отвечающий за работу со списком графических объектов документа. Данный класс будет реализовывать массив всех графических объектов, нарисованных в документе.
Логика работы с объектами после рисования будет такой:
Каждый новый объект помещается вначале Z-порядка (списка расположения «под курсором»). Таким образом, нарисовав в одном и том же месте несколько объектов и нажав инструментов «Выделение» на этом же месте «по умолчанию» будет выделен последний нарисованный объект. Если изменить положение объекта в Z-списке, отправив объект «назад» («Переместить назад»), наверху окажется предыдущий нарисованный объект. Сам же объект, который мы отправили «назад», будет расположен в конце Z-порядка и будет выделен, если удалить из области все другие объекты.
Файл для класса назовём GraphicsList.cs, код файла будет таким:[искомый код можно найти в приложении к данной лабораторной работе в (описания можно того или иного приложения можно посмотреть в пунтке № 8 протокола работы), а именно необходимо открыть Приложение № 3 (Файлы классов Graphics...)].
Игнорируем возникшие ошибки (3 штуки, исправим позже добавлением других классов).
Также добавим вспомогательный класс для передачи параметров: GraphicsProperties(файл GraphicsProperties.cs)со следующим кодом: [искомый код можно найти в приложении к данной лабораторной работе в (описания можно того или иного приложения можно посмотреть в пунтке № 8 протокола работы), а именно необходимо открыть Приложение № 3 (Файлы классов Graphics...)].
Добавляем новый класс, являющийся базовым для команд типа «Отменить» и «Вернуть» («Undo» и «Redo»). Имя файла класса будет Command.cs, код файла вставляем следующий:[искомый код можно найти в приложении к данной лабораторной работе в (описания можно того или иного приложения можно посмотреть в пунтке № 8 протокола работы), а именно необходимо открыть Приложение № 4 (Файлы классов Command...)].
Следующий класс (CommandChangeState.cs) станет основным классом для поддержки команд перемещений, изменения размеров и изменения параметров объекта рисования для операций «Отменить» и «Вернуть». Код файла будет следующим:[искомый код можно найти в приложении к данной лабораторной работе в (описания можно того или иного приложения можно посмотреть в пунтке № 8 протокола работы), а именно необходимо открыть Приложение № 4 (Файлы классов Command...)].
Класс CommandAdd(CommandAdd.cs), отвечающий за команду добавления объекта для операций «Отменить» и «Вернуть»: [искомый код можно найти в приложении к данной лабораторной работе в (описания можно того или иного приложения можно посмотреть в пунтке № 8 протокола работы), а именно необходимо открыть Приложение № 4 (Файлы классов Command...)].
Непосредственно реализация команд «Отменить» и «Вернуть» будет выполнена следующим классом UndoManager (файл UndoManager.cs) с кодом:[искомый код можно найти в приложении к данной лабораторной работе в (описания можно того или иного приложения можно посмотреть в пунтке № 8 протокола работы), а именно необходимо открыть Приложение № 5 (Прочие файлы)].
Класс CommandDelete(CommandDelete.cs), отвечающий за команду удаления выделенного объекта для операций «Отменить» и «Вернуть»: [искомый код можно найти в приложении к данной лабораторной работе в (описания можно того или иного приложения можно посмотреть в пунтке № 8 протокола работы), а именно необходимо открыть Приложение № 4 (Файлы классов Command...)].
Класс CommandDeleteAll(CommandDeleteAll.cs), отвечающий за команду удаления всех объектов для операций «Отменить» и «Вернуть»: [искомый код можно найти в приложении к данной лабораторной работе в (описания можно того или иного приложения можно посмотреть в пунтке № 8 протокола работы), а именно необходимо открыть Приложение № 4 (Файлы классов Command...)].
Теперь перейдём к классам, поддерживающим рисование графических объектов. Первый и самый примитивный графический объект это линия. Класс на основе DrawObject: DrawLine (DrawLine.cs) реализует всё необходимое для поддержки рисования объекта линии, выделения нарисованного объекта, изменения положения и перемещения объекта линии. Линия рисуется через два три события (нажатие левой кнопки мыши, перемещения и отжатия левой кнопки мыши). Объект имеет де ключевые точки (точка начала и точка конца линии). Сериализуется в файл путём указания следующих параметров: точка начала и точка конца линии, а также строки записей для сохранения в файле: Startи End. Перемещение линии обеспечивается созданием для линии небольшой линейной области (толщина в 7 пикселей от центра линии). Нажатие левой кнопки мыши в этой области приведёт к выделению объекта и активации возможности перемещение, изменения. Изменение размеров линии осуществляется в ключевых точках.
Код файла для реализации класса:[искомый код можно найти в приложении к данной лабораторной работе в (описания можно того или иного приложения можно посмотреть в пунтке № 8 протокола работы), а именно необходимо открыть Приложение № 2 (Файлы классов Draw...)].
Объект получает параметры из инструмента «Линия» следующим обрахом:
privatePoint startPoint;
privatePoint endPoint;
public DrawLine(int x1, int y1, int x2, int y2) : base()
{
startPoint.X = x1;
startPoint.Y = y1;
endPoint.X = x2;
endPoint.Y = y2;
Initialize();
}
Рисует линию так:
///<summary>
/// Главная функция рисования линии на форме
///</summary>
publicoverridevoid Draw(Graphics g)
{
g.SmoothingMode = SmoothingMode.AntiAlias;
Pen pen = newPen(Color, PenWidth);
g.DrawLine(pen, startPoint.X, startPoint.Y, endPoint.X, endPoint.Y);
pen.Dispose();
}
Прямоугольник. Класс на основе DrawObject: DrawRectangle (DrawRectangle.cs) реализует всё необходимое для поддержки рисования объекта прямоугольника, выделения нарисованного объекта, изменения положения и перемещения объекта прямоугольника. Объект имеет воесь ключевых точек (угловые точки, точки в центре линий). Сериализуется в файл путём указания следующих параметров: область прямоугольника, а также строки записей для сохранения в файле: Rect. Прямоугольник является цельным объектом, область для перемещения которого ограничена стронами прямоугольника. Для перемещения, как и в случае с линией нужно выделить объект (в любом месте прямоугольника) и зажать левую кнопку мыши.
Код файла для реализации класса:[искомый код можно найти в приложении к данной лабораторной работе в (описания можно того или иного приложения можно посмотреть в пунтке № 8 протокола работы), а именно необходимо открыть Приложение № 2 (Файлы классов Draw...)].
Нормализация нужна для правильного определения направления рисования (первое нажатие в левом уголу, движение мыши в правый нижний угол и прочее) и получение положительных значений координат для прямоугольника.
Эллипс. Класс на основе DrawRectangle: DrawEllipse (DrawEllipse.cs) реализует всё необходимое для поддержки рисования объекта эллипса, выделения нарисованного объекта, изменения положения и перемещения объекта эллипса. Объект имеет воесь ключевых точек (угловые точки, точки в центре линий). Сериализуется в файл путём указания следующих параметров: область прямоугольника, а также строки записей для сохранения в файле: Rect. Прямоугольник является цельным объектом, область для перемещения которого ограничена стронами прямоугольника. Для перемещения, как и в случае с линией нужно выделить объект (в любом месте прямоугольника) и зажать левую кнопку мыши.
Код файла для реализации класса:[искомый код можно найти в приложении к данной лабораторной работе в (описания можно того или иного приложения можно посмотреть в пунтке № 8 протокола работы), а именно необходимо открыть Приложение № 2 (Файлы классов Draw...)].
Объект получает следующие параметры из инструмента «Эллипс»:
public DrawEllipse(int x, int y, int width, int height) : base()
{
Rectangle = newRectangle(x, y, width, height);
Initialize();
}
Рисование эллипса реализовано так:
///<summary>
/// Главная функция рисования эллипса на форме
///</summary>
///<param name="g"></param>
publicoverridevoid Draw(Graphics g)
{
Pen pen = newPen(Color, PenWidth);
g.DrawEllipse(pen, DrawRectangle.GetNormalizedRectangle(Rectangle));
pen.Dispose();
}
Карандаш: последний объект рисования. Класс на основе DrawLine: DrawPolygon (DrawPolygon.cs) реализует всё необходимое для поддержки рисования объекта непрерывной линии с малым расстоянием между точками, выделения нарисованного объекта, изменения положения и перемещения объекта карандаша. Объект имеет столько ключевых точек, сколько нарисовано «кусков» + 1 точка). Сериализуется в файл путём указания следующих параметров: точкаидлина(для одного участка), а также строки записей для сохранения в файле: Pointи Length. Карандаш не является цельным объектом (даже в случае замыкания линий). Для перемещения, как и в случае с линией нужно выделить объект (в любом месте нарисоанной ломаной) и зажать левую кнопку мыши. Путь для выделения создаётся также как и в случае с линией, но для каждого участка. Параметр расстояния (до ближайшей точки) регулируется так (будущий файл ToolPolygon.cs):
privateconstint minDistance = 15*15; // Дистанция между ключевыми точками
Кусок кода событиея перемещения мыши во время рисования:
Point point = newPoint(e.X, e.Y);
int distance = (e.X - lastX)*(e.X - lastX) + (e.Y - lastY)*(e.Y - lastY);
if (distance < minDistance)
{
// Если расстояние между последними двумя точками меньше минимального -
// перемещаем последнюю точку
newPolygon.MoveHandleTo(point, newPolygon.HandleCount);
}
else
{
// Добавляем новую точку
newPolygon.AddPoint(point);
lastX = e.X;
lastY = e.Y;
}
Код файла для реализации класса:[искомый код можно найти в приложении к данной лабораторной работе в (описания можно того или иного приложения можно посмотреть в пунтке № 8 протокола работы), а именно необходимо открыть Приложение № 2 (Файлы классов Draw...)].
Последние 7 классов относятся к классам реализации инструмента рисования, и именно они инициализируются до вызова класса рисования объекта. Экземпляр класса инструмента передаёт в экземпляр класса рисования все параметры. Первый класс из цепочки является абстрактным базовым классом для реализации работы инструментов. Реализует три простых события действия с мышью (нажатие, перемещение и снятие нажатия). Имя: Tool.cs (класс Tool). Код файла с классом: [искомый код можно найти в приложении к данной лабораторной работе в (описания можно того или иного приложения можно посмотреть в пунтке № 8 протокола работы), а именно необходимо открыть Приложение № 6 (Файлы классов Tool...)].
ToolObject.cs (класс ToolObject). Это базовый класс для реализации остальных объектов инструментов.Реализует свойства курсора для инструмента, а также функцию добавления нового объекта в список объектов документа:
///<summary>
/// Добавление нового объекта в область рисования.
/// Функция вызывается когда пользователь нажимает ЛКМ на области рисования,
/// и один из полученных ToolObject-инструментов активен.
///</summary>
///<param name="drawArea"></param>
///<param name="o"></param>
protectedvoid AddNewObject(DrawArea drawArea, DrawObject o)
{
drawArea.GraphicsList.UnselectAll();
o.Selected = true;
drawArea.GraphicsList.Add(o);
drawArea.Capture = true;
drawArea.Refresh();
drawArea.SetDirty();
}
Код файла с классом: [искомый код можно найти в приложении к данной лабораторной работе в (описания можно того или иного приложения можно посмотреть в пунтке № 8 протокола работы), а именно необходимо открыть Приложение № 6 (Файлы классов Tool...)].
ToolLine.cs (класс ToolLine). Это базовый класс для реализации инструмента рисования «Линия». Передаёт параметры объекту рисования.Реализует свойства курсора для инструмента, а также функцию добавления нового объекта линии в список объектов документа.
Пример события нажатия ЛКМ в области DrawAreaс созданием экземпляра класса DrawLineи передачей параметров нажатия:
publicoverridevoid OnMouseDown(DrawArea drawArea, MouseEventArgs e)
{
AddNewObject(drawArea, newDrawLine(e.X, e.Y, e.X + 1, e.Y + 1));
}
Код файла с классом: [искомый код можно найти в приложении к данной лабораторной работе в (описания можно того или иного приложения можно посмотреть в пунтке № 8 протокола работы), а именно необходимо открыть Приложение № 6 (Файлы классов Tool...)].
ToolRectangle.cs (класс ToolRectnagle). Это базовый класс для реализации инструмента рисования «Прямоугольник». Передаёт параметры объекту рисования.Реализует свойства курсора для инструмента, а также функцию добавления нового объекта прямоугольника в список объектов документа. Событиенажатиякнопкимыши:
publicoverridevoid OnMouseDown(DrawArea drawArea, MouseEventArgs e)
{
AddNewObject(drawArea, newDrawRectangle(e.X, e.Y, 1, 1));
}
Код файла с классом: [искомый код можно найти в приложении к данной лабораторной работе в (описания можно того или иного приложения можно посмотреть в пунтке № 8 протокола работы), а именно необходимо открыть Приложение № 6 (Файлы классов Tool...)].
ToolEllipse.cs (класс ToolEllipse). Это базовый класс для реализации инструмента рисования «Эллипс». Передаёт параметры объекту рисования.Реализует свойства курсора для инструмента, а также функцию добавления нового объекта эллипса в список объектов документа. Событиенажатиякнопкимыши:
publicoverridevoid OnMouseDown(DrawArea drawArea, MouseEventArgs e)
{
AddNewObject(drawArea, newDrawEllipse(e.X, e.Y, 1, 1));
}
Код файла с классом: [искомый код можно найти в приложении к данной лабораторной работе в (описания можно того или иного приложения можно посмотреть в пунтке № 8 протокола работы), а именно необходимо открыть Приложение № 6 (Файлы классов Tool...)].
ToolPolygon.cs (класс ToolPolygon). Это базовый класс для реализации инструмента рисования «Карандаш». Передаёт параметры объекту рисования.Реализует свойства курсора для инструмента, а также функцию добавления нового объекта непрерывной линии в список объектов документа.
Код файла с классом: [искомый код можно найти в приложении к данной лабораторной работе в (описания можно того или иного приложения можно посмотреть в пунтке № 8 протокола работы), а именно необходимо открыть Приложение № 6 (Файлы классов Tool...)].
ToolPointer.cs (класс ToolPointer). Это базовый класс для реализации инструмента рисования «Выделение». Передаёт параметры объекту рисования.Реализует события связанные с выделение объектов в области рисования, работает с перемещением объекта, изменением размеров объекта и чистым выделением объект(-ов). Этот инструмент также управляет пунктирным прямоугольником выделения:
if (selectMode == SelectionMode.NetSelection)
{
// Удаляем прямоугольник предыдущего выделения
ControlPaint.DrawReversibleFrame(
drawArea.RectangleToScreen(DrawRectangle.GetNormalizedRectangle(startPoint, oldPoint)),
Color.Black,
FrameStyle.Dashed);
// Рисуем прямоугольник нового выделения
ControlPaint.DrawReversibleFrame(
drawArea.RectangleToScreen(DrawRectangle.GetNormalizedRectangle(startPoint, point)),
Color.Black,
FrameStyle.Dashed);
return;
}
Код файла с классом: [искомый код можно найти в приложении к данной лабораторной работе в (описания можно того или иного приложения можно посмотреть в пунтке № 8 протокола работы), а именно необходимо открыть Приложение № 6 (Файлы классов Tool...)].
На этом формирование классов закончено. Теперь можно перейти к реализации функциональности непосредственно для главной формы и элемента, в котором будем рисовать (DrawArea).
Первым делом «добьём» форму LWP15About. Событие Loadдля формы будет таким:
privatevoid LWP15About_Load(object sender, EventArgs e)
{
this.Text = "Опрограмме " + Application.ProductName;
L_About.Text =
"Программа: " + Application.ProductName + "\n" +
"Версия: " + Application.ProductVersion;
}
СобытиеClickкнопкиB_OK:
privatevoid B_OK_Click(object sender, EventArgs e)
{
this.Close();
}
Перейдём к DrawArea.cs. Это основной элемент, в котором происходит «рисование». Любые действия мышью совершаемые в фокусе элемента перехватываются инструментами Tool... и реализуют то или иной действие в зависимости от активного инструмента и действия с мышью. Сам элемент будет растягиваться до строки состояния внизу, границ с боку формы и панели инструментов сверху. Абсолютное значение размеров будет влиять лишь на доступную для размещения объектов область. Эти размеры будут важны лишь при создании растрового изображения (об этом в конце данного материала) на основе графики в элементе.
Откроем код элемента DrawArea.csи заменим все директивы usingв начале файла следующим кодом:
#regionДирективы Using
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using LWP15Tools; // Подключаембиблиотекуклассов
#endregion
Подключаем к проекту ссылку на нашу библиотеку LWP15tools: жмём правую кнопку мыши на имени проекта LWP15Draw, затем Добавить ссылку...:
Рис. 6. 1. Добавить ссылку: вкладка Проекты
В открывшемся окне переходим на вкладку Проекты и выбираем LWP15Tools, жмём ОК.
В коде находим:
namespaceLWP15Draw
{
publicpartialclassDrawArea : UserControl
{
public DrawArea()
{
InitializeComponent();
}
}
Заменяемна (последнююфигурную скобку в файле не трогаем):
namespace LWP15Draw
{
partialclassDrawArea : UserControl
{
#regionКонструктор
public DrawArea()
{
InitializeComponent();
}
#endregion
#regionПеречисления
publicenumDrawToolType
{
Pointer, Rectangle, Ellipse, Line, Polygon, NumberOfDrawTools
};
#endregion
#regionЧлены
privateGraphicsList graphicsList; // Списокобъектоврисования
privateDrawToolType activeTool; // Активныйинструментрисования
privateTool[] tools; // Массивинструментов
privateLWP15Main owner;
privateDocManager docManager;
privateContextMenuStrip m_ContextMenu;
privateUndoManager undoManager;
#endregion
#regionСвойства
///<summary>
/// Ссылка на владельца формы
///</summary>
publicLWP15Main Owner
{
get { return owner; }
set { owner = value; }
}
///<summary>
///Ссылкана DocManager
///</summary>
publicDocManager DocManager
{
get { return docManager; }
set
{
docManager = value;
}
}
///<summary>
///Активныйинструментрисования
///</summary>
publicDrawToolType ActiveTool
{
get { return activeTool; }
set { activeTool = value; }
}
///<summary>
///Списокобъектоврисования
///</summary>
publicGraphicsList GraphicsList
{
get { return graphicsList; }
set
{
graphicsList = value;
undoManager = newUndoManager(graphicsList);
}
}
///<summary>
/// true - если операция отмены возможна
///</summary>
publicbool CanUndo
{
get
{
if (undoManager != null) { return undoManager.CanUndo; }
returnfalse;
}
}
///<summary>
/// true - если операция возврата возможна
///</summary>
publicbool CanRedo
{
get
{
if (undoManager != null) { return undoManager.CanRedo; }
returnfalse;
}
}
#endregion
#regionПрочиефункции
///<summary>
///Инициализация
///</summary>
///<param name="owner"></param>
///<param name="docManager"></param>
publicvoid Initialize(LWP15Main owner, DocManager docManager)
{
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer, true);
// Сохраняем ссылку на владельца формы
this.Owner = owner;
this.DocManager = docManager;
// Устанавливаем инструмент по умолчанию
activeTool = DrawToolType.Pointer;
// Создаём список графических объектов
graphicsList = newGraphicsList();
// Создаём экземпляр UndoManager для текущего файла
undoManager = newUndoManager(graphicsList);
// Создаёммассивинструментоврисования
tools = newTool[(int)DrawToolType.NumberOfDrawTools];
tools[(int)DrawToolType.Pointer] = newToolPointer();
tools[(int)DrawToolType.Rectangle] = newToolRectangle();
tools[(int)DrawToolType.Ellipse] = newToolEllipse();
tools[(int)DrawToolType.Line] = newToolLine();
tools[(int)DrawToolType.Polygon] = newToolPolygon();
}
///<summary>
/// Добавления команды в историю
///</summary>
publicvoid AddCommandToHistory(Command command)
{
undoManager.AddCommandToHistory(command);
}
///<summary>
///Очисткаистории
///</summary>
publicvoid ClearHistory()
{
undoManager.ClearHistory();
}
///<summary>
///Отменить
///</summary>
publicvoid Undo()
{
undoManager.Undo();
Refresh();
}
///<summary>
///Вернуть
///</summary>
publicvoid Redo()
{
undoManager.Redo();
Refresh();
}
///<summary>
/// Устанавливаем флаг "грязный" (файл был изменён после последней операции сохранения)
///</summary>
publicvoid SetDirty()
{
DocManager.Dirty = true;
}
///<summary>
/// Обработчик нажатия правой кнопки мышки
///</summary>
///<param name="e"></param>
privatevoid OnContextMenu(MouseEventArgs e)
{
// Измененяем текущий выбор при необходимости
Point point = newPoint(e.X, e.Y);
int n = GraphicsList.Count;
DrawObject o = null;
for (int i = 0; i < n; i++)
{
if (GraphicsList[i].HitTest(point) == 0)
{
o = GraphicsList[i];
break;
}
}
if (o != null)
{
if (!o.Selected) GraphicsList.UnselectAll();
// Выбор объекта произведён
o.Selected = true;
}
else
{
GraphicsList.UnselectAll();
}
Refresh(); // В случае изменения выбора
// Выводин контекстное меню (всплывающее).
// Элементы меню вставлены из строки меня, главного элемента "Правка"
m_ContextMenu = newContextMenuStrip();
int nItems = owner.ContextParent.DropDownItems.Count;
// Получаем элементы меню "Правка" и перемещаем их на контекстное-всплывающее меню.
// Так как каждый шаг уменьшает количество элементов, читая их в обратном порядке.
// Чтобы получить элементы в прямом порядке, вставим каждый из них в начало
for (int i = nItems - 1; i >= 0; i--)
{
m_ContextMenu.Items.Insert(0, owner.ContextParent.DropDownItems[i]);
}
// Выводит контекстное меню для владельца формы, а также обрабатывает элементы выбора.
// Преобразует координаты точки в этом окне к координатам владельца
point.X += this.Left;
point.Y += this.Top;
m_ContextMenu.Show(owner, point);
Owner.SetStateOfControls(); // Включение/выключение элементов меню
// Контекстное меню вызвано, но меню "Правка" владельца теперь пусто.
// Подписываемся на событие закрытия контекстного меню и восстанавливаем там элементы
m_ContextMenu.Closed += delegate(object sender, ToolStripDropDownClosedEventArgs args)
{
if (m_ContextMenu != null)
{
nItems = m_ContextMenu.Items.Count;
for (int k = nItems - 1; k >= 0; k--) { owner.ContextParent.DropDownItems.Insert(0, m_ContextMenu.Items[k]); }
}
};
}
#endregion
}
По очереди инициализируем для элемента DrawAreaследующие события с кодом. Вначале идёт событие Paint:
#region Обработчики событий
///<summary>
/// Рисование графического объекта и группировка прямоугольника выделения (опционально)
///</summary>
privatevoid DrawArea_Paint(object sender, PaintEventArgs e)
{
SolidBrush brush = newSolidBrush(Color.FromArgb(255, 255, 255));
e.Graphics.FillRectangle(brush, this.ClientRectangle);
if (graphicsList != null) { graphicsList.Draw(e.Graphics); }
//DrawNetSelection(e.Graphics);
brush.Dispose();
}
СобытиеMouseDown, MouseMoveиMouseUpдляDrawArea:
///<summary>
/// Событие MouseDown для DrawArea
/// ЛКМ: перехватывается активным инструментом
/// ПКМ: перехватывается и описано в данном классе
///</summary>
privatevoid DrawArea_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left) tools[(int)activeTool].OnMouseDown(this, e);
elseif (e.Button == MouseButtons.Right) OnContextMenu(e);
}
///<summary>
/// Событие MouseMove для DrawArea
/// Перемещение без нажатия кнопок или с нажатие левой кнопки мыши перехватываетсмя активным инструментом
///</summary>
privatevoid DrawArea_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left || e.Button == MouseButtons.None) tools[(int)activeTool].OnMouseMove(this, e);
elsethis.Cursor = Cursors.Default;
}
///<summary>
/// Событие MouseUp для DrawArea
/// ЛКМ: перехватывается активным инструментом
///</summary>
privatevoid DrawArea_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left) tools[(int)activeTool].OnMouseUp(this, e);
}
#endregion
Переходим к главной форме LWP15Main. Переписываем директивы using:
#regionДирективыUsing
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Runtime.Serialization;
using System.Diagnostics;
using System.Security;
using Microsoft.Win32;
using LWP15Tools;
#endregion
Находим:
publicpartialclassLWP15Main : Form
{
Заменяемна:
partialclassLWP15Main : Form
{
#regionЧлены
privateDrawArea drawArea;
privateDocManager docManager;
privateDragDropManager dragDropManager;
privateMruManager mruManager;
privatePersistWindowState persistState;
privatestring argumentFile = ""; // Имяфайлаизкоманднойстроки
conststring registryPath = "Software\\LWP15Draw";
#endregion
#regionСвойства
///<summary>
///Имяфайлаизкоманднойстроки
///</summary>
publicstring ArgumentFile
{
get { return argumentFile; }
set { argumentFile = value; }
}
///<summary>
///Получаемссылкунаэлементстрокименю"Правка".
/// Используется при вызове контекстного-всплывающего меню в классе DrawArea
///</summary>
///<value></value>
publicToolStripMenuItem ContextParent
{
get { returnправкаToolStripMenuItem; }
}
#endregion
#regionКонструктор
public LWP15Main()
{
InitializeComponent();
persistState = newPersistWindowState(registryPath, this);
}
#endregion
#regionОбработчикисобытийдля DocManager
///<summary>
/// Загрузка документа из потока поставляемая DocManager
///</summary>
///<param name="sender"></param>
///<param name="args"></param>
privatevoid docManager_LoadEvent(object sender, SerializationEventArgs e)
{
// DocManager просит загрузить документ в поставляемый поток
try
{
drawArea.GraphicsList = (GraphicsList)e.Formatter.Deserialize(e.SerializationStream);
}
catch (ArgumentNullException ex) { HandleLoadException(ex, e); }
catch (SerializationException ex) { HandleLoadException(ex, e); }
catch (SecurityException ex) { HandleLoadException(ex, e); }
}
///<summary>
/// Сохранение документа в поток поставляемый DocManager
///</summary>
///<param name="sender"></param>
///<param name="args"></param>
privatevoid docManager_SaveEvent(object sender, SerializationEventArgs e)
{
// DocManager просит сохранить документ в поставляемый поток
try
{
e.Formatter.Serialize(e.SerializationStream, drawArea.GraphicsList);
}
catch (ArgumentNullException ex) { HandleSaveException(ex, e); }
catch (SerializationException ex) { HandleSaveException(ex, e); }
catch (SecurityException ex) { HandleSaveException(ex, e); }
}
#endregion
Теперь поочерёдно создаём события строки меню (двойное нажатие на элементе меню для инициализации события Click). В качестве кнопок будут выступать все подпункты (дочерние пункты) меню кроме подпункта «Последние файлы» меню «Файл». Блок с кодом событий Clickвсей строки меню будет таким:
#regionОбработчикисобытийстрокименю
privatevoidсоздатьToolStripMenuItem_Click(object sender, EventArgs e)
{
CommandNew();
}
privatevoidоткрытьToolStripMenuItem_Click(object sender, EventArgs e)
{
CommandOpen();
}
privatevoidсохранитьToolStripMenuItem_Click(object sender, EventArgs e)
{
CommandSave();
}
privatevoidсохранитькакToolStripMenuItem_Click(object sender, EventArgs e)
{
CommandSaveAs();
}
privatevoidвыходToolStripMenuItem_Click(object sender, EventArgs e)
{
this.Close();
}
privatevoidотменитьToolStripMenuItem_Click(object sender, EventArgs e)
{
CommandUndo();
}
privatevoidвернутьToolStripMenuItem1_Click(object sender, EventArgs e)
{
CommandRedo();
}
privatevoidвыделитьвсеToolStripMenuItem_Click(object sender, EventArgs e)
{
drawArea.GraphicsList.SelectAll();
drawArea.Refresh();
}
privatevoidснятьВыделениеToolStripMenuItem_Click(object sender, EventArgs e)
{
drawArea.GraphicsList.UnselectAll();
drawArea.Refresh();
}
privatevoidудалитьToolStripMenuItem_Click(object sender, EventArgs e)
{
CommandDelete command = newCommandDelete(drawArea.GraphicsList);
if (drawArea.GraphicsList.DeleteSelection())
{
drawArea.SetDirty();
drawArea.Refresh();
drawArea.AddCommandToHistory(command);
}
}
privatevoidудалитьВсеToolStripMenuItem_Click(object sender, EventArgs e)
{
CommandDeleteAll command = newCommandDeleteAll(drawArea.GraphicsList);
if (drawArea.GraphicsList.Clear())
{
drawArea.SetDirty();
drawArea.Refresh();
drawArea.AddCommandToHistory(command);
}
}
privatevoidпереместитьНазадToolStripMenuItem_Click(object sender, EventArgs e)
{
if (drawArea.GraphicsList.MoveSelectionToBack())
{
drawArea.SetDirty();
drawArea.Refresh();
}
}
privatevoidпереместитьВпередToolStripMenuItem_Click(object sender, EventArgs e)
{
if (drawArea.GraphicsList.MoveSelectionToFront())
{
drawArea.SetDirty();
drawArea.Refresh();
}
}
privatevoidпараметрыToolStripMenuItem_Click(object sender, EventArgs e)
{
if (drawArea.GraphicsList.ShowPropertiesDialog(drawArea))
{
drawArea.SetDirty();
drawArea.Refresh();
}
}
privatevoidвыделениеToolStripMenuItem_Click(object sender, EventArgs e)
{
CommandPointer();
}
privatevoidкарандашToolStripMenuItem_Click(object sender, EventArgs e)
{
CommandPolygon();
}
privatevoidлинияToolStripMenuItem_Click(object sender, EventArgs e)
{
CommandLine();
}
privatevoidэллипсToolStripMenuItem_Click(object sender, EventArgs e)
{
CommandEllipse();
}
privatevoidпрямоугольникToolStripMenuItem_Click(object sender, EventArgs e)
{
CommandRectnagle();
}
privatevoidопрограммеToolStripMenuItem_Click(object sender, EventArgs e)
{
CommandAbout();
}
#endregion
Тожесамое проделываем для строки с иконками (панелью инструментов). Двойное нажатие на иконку инициализирует событие Click:
#regionОбработчикисобытийпанелиинструментов
privatevoidсоздатьToolStripButton_Click(object sender, EventArgs e)
{
CommandNew();
}
privatevoidоткрытьToolStripButton_Click(object sender, EventArgs e)
{
CommandOpen();
}
privatevoidсохранитьToolStripButton_Click(object sender, EventArgs e)
{
CommandSave();
}
privatevoidвыделениеToolStripButton_Click(object sender, EventArgs e)
{
CommandPointer();
}
privatevoidкарандашToolStripButton_Click(object sender, EventArgs e)
{
CommandPolygon();
}
privatevoidлинияToolStripButton_Click(object sender, EventArgs e)
{
CommandLine();
}
privatevoidэллипсToolStripButton_Click(object sender, EventArgs e)
{
CommandEllipse();
}
privatevoidпрямоугольникToolStripButton_Click(object sender, EventArgs e)
{
CommandRectangle();
}
privatevoidотменитьToolStripButton_Click(object sender, EventArgs e)
{
CommandUndo();
}
privatevoidвернутьToolStripButton_Click(object sender, EventArgs e)
{
CommandRedo();
}
privatevoidсправкаToolStripButton_Click(object sender, EventArgs e)
{
CommandAbout();
}
#endregion
Инициализируем три события для главной формы. Событие Loadформы LWP15Main:
#regionОбработчикисобытий
privatevoid LWP15Main_Load(object sender, EventArgs e)
{
toolStripStatusLabel.Text = "Готовкработе";
// Создаёмобластьрисования
drawArea = newDrawArea();
drawArea.Location = new System.Drawing.Point(0, 0);
drawArea.Size = new System.Drawing.Size(10, 10);
drawArea.Owner = this;
this.Controls.Add(drawArea);
// Вспомогательные объекты (DocManager и прочие)
InitializeHelperObjects();
drawArea.Initialize(this, docManager);
ResizeDrawArea();
LoadSettingsFromRegistry();
// Submit to Idle event to set controls state at idle time
Application.Idle += delegate(object o, EventArgs a)
{
SetStateOfControls();
};
// Открытый файл передаётся в командную строку
if (ArgumentFile.Length > 0) OpenDocument(ArgumentFile);
// Подписываемся на событие DropDownOpened для каждого всплывающего меню
foreach (ToolStripItem item in menuStrip1.Items)
{
if (item.GetType() == typeof(ToolStripMenuItem)) { ((ToolStripMenuItem)item).DropDownOpened += LWP15Main_DropDownOpened; }
}
}
Событие Resize:
///<summary>
/// Изменение размеров DrawArea, Когда меняются размеры формы
///</summary>
privatevoid LWP15Main_Resize(object sender, EventArgs e)
{
if (this.WindowState != FormWindowState.Minimized && drawArea != null) { ResizeDrawArea(); }
}
Событие FormClosing:
///<summary>
/// Событие закрытия формы
///</summary>
privatevoid LWP15Main_FormClosing(object sender, FormClosingEventArgs e)
{
if (e.CloseReason == CloseReason.UserClosing)
{
if (!docManager.CloseDocument()) e.Cancel = true;
}
SaveSettingsToRegistry();
}
Последнее событие формы без инициализации (просто вставляем код после последнего события):
///<summary>
/// Всплывающая строка меню ("Файл", "Правка" и прочее) открыто
///</summary>
///<param name="sender"></param>
///<param name="e"></param>
void LWP15Main_DropDownOpened(object sender, EventArgs e)
{
// Устанавливаем активный инструмент на "Выделение".
// Педотвращает редкий сбой, когда выбран ино<