Модификация приложения Windows Forms : функциональность векторного редактора
Для начала определим все необходимые для работы приложения класса внутри самого приложения.
Первый класс с именем файла DrawObject . cs станет основным классом для рисования любых объектов. На его основе будут созданы классы для рисования конкретных объектов типа линий или прямоугольников. Класс будет содержать все возможные функции, события и свойства, необходимы для работы с графическим объектом. А именно этот класс будет определять функция рисования объекта (Draw), выделения, перемещения, изменения размеров и определения количества ключевых точек. Что такое ключевая точка? Для линии это точки на концах линии, которые «подсвечиваются» небольшими прямоугольниками. Если выделить линию и навести мышь на такую точку, курсор будет изменён (зависит от типа точки и объекта рисования). Нажатие на ключевую точку обеспечивает операцию изменения размеров объекта либо перемещения (зависит от объекта рисования). Любой объект во время выделения мышью подсвечивается ключевыми точками. Например, нарисованный и выделенный прямоугольник имеет 8 ключевых точек:
Код файла DrawObject . cs: [искомый код можно найти в приложении к данной лабораторной работе в (описания можно того или иного приложения можно посмотреть в пунтке № 8 протокола работы), а именно необходимо открыть Приложение № 2 (Файлы классов Draw ...)].
Класс GraphicsProperties напрямую связан с дочерней формойLWP 15 Properties. Обработаем форму LWP 15 Properties и проинициализируем все необходимые свойства и события.
|
|
Откроем код формы (выделим LWP 15 Properies . cs -> нажмём правую кнопку мыши -> Перейти к коду (F7)). Найдём:
public LWP15Properties()
{
InitializeComponent();
}
Добавим после:
private GraphicsProperties properties;
private const string undefined = "Не задано";
private const int maxWidth = 10;
public GraphicsProperties Properties
{
get { return properties; }
set { properties = value; }
}
В самое начало формы добавим код:
using System.Globalization;
Найдём:
namespace LWP15Draw
{
public partial class LWP15Properties : Form
{
Изменим на:
namespace LWP15Draw
{
partial class LWP15Properties : Form
{
Проинициализируем первое событие формы LWP 15 Properties: Load со следующим кодом:
private void LWP15Properties_Load(object sender, EventArgs e)
{
InitControls();
SetColor();
SetPenWidth();
}
Вспомогательные функции добавим сразу после предыдущего фрагмента кода:
private void InitControls()
{
for (int i = 1; i <= maxWidth; i++)
{
CB_PenWidth.Items.Add(i.ToString(CultureInfo.InvariantCulture));
}
}
private void SetColor()
{
if (properties.Color.HasValue) L_Color.BackColor = properties.Color.Value;
else L_Color.Text = undefined;
}
private void 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; }
}
private void 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 этой формы:
private void CB_PenWidth_SelectedIndexChanged(object sender, EventArgs e)
{
int width = CB_PenWidth.SelectedIndex + 1;
L_PenWidth.Text = width.ToString(CultureInfo.InvariantCulture);
}
Событие Click нажатия кнопки B_SelectColor:
private void B_SelectColor_Click(object sender, EventArgs e)
{
ColorDialog dlg = new ColorDialog();
dlg.Color = L_Color.BackColor;
if (dlg.ShowDialog(this) == DialogResult.OK)
{
L_Color.BackColor = dlg.Color;
L_Color.Text = "";
}
}
Событие Click нажатия кнопки B_OK:
private void 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 ...)].
Объект получает параметры из инструмента «Линия» следующим обрахом:
private Point startPoint;
private Point 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>
public override void Draw(Graphics g)
{
g.SmoothingMode = SmoothingMode.AntiAlias;
Pen pen = new Pen(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 = new Rectangle(x, y, width, height);
Initialize();
}
Рисование эллипса реализовано так:
/// <summary>
/// Главная функция рисования эллипса на форме
/// </summary>
/// <param name="g"></param>
public override void Draw(Graphics g)
{
Pen pen = new Pen(Color, PenWidth);
g.DrawEllipse(pen, DrawRectangle.GetNormalizedRectangle(Rectangle));
pen.Dispose();
}
Карандаш: последний объект рисования. Класс на основе DrawLine: DrawPolygon (DrawPolygon . cs) реализует всё необходимое для поддержки рисования объекта непрерывной линии с малым расстоянием между точками, выделения нарисованного объекта, изменения положения и перемещения объекта карандаша. Объект имеет столько ключевых точек, сколько нарисовано «кусков» + 1 точка). Сериализуется в файл путём указания следующих параметров: точка и длина (для одного участка), а также строки записей для сохранения в файле: Point и Length. Карандаш не является цельным объектом (даже в случае замыкания линий). Для перемещения, как и в случае с линией нужно выделить объект (в любом месте нарисоанной ломаной) и зажать левую кнопку мыши. Путь для выделения создаётся также как и в случае с линией, но для каждого участка. Параметр расстояния (до ближайшей точки) регулируется так (будущий файл ToolPolygon . cs):
private const int minDistance = 15*15; // Дистанция между ключевыми точками
Кусок кода событиея перемещения мыши во время рисования:
Point point = new Point(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>
protected void 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 и передачей параметров нажатия:
public override void OnMouseDown(DrawArea drawArea, MouseEventArgs e)
{
AddNewObject(drawArea, new DrawLine(e.X, e.Y, e.X + 1, e.Y + 1));
}
Код файла с классом: [искомый код можно найти в приложении к данной лабораторной работе в (описания можно того или иного приложения можно посмотреть в пунтке № 8 протокола работы), а именно необходимо открыть Приложение № 6 (Файлы классов Tool ...)].
ToolRectangle . cs (класс ToolRectnagle). Это базовый класс для реализации инструмента рисования «Прямоугольник». Передаёт параметры объекту рисования. Реализует свойства курсора для инструмента, а также функцию добавления нового объекта прямоугольника в список объектов документа. Событие нажатия кнопки мыши:
public override void OnMouseDown(DrawArea drawArea, MouseEventArgs e)
{
AddNewObject(drawArea, new DrawRectangle(e.X, e.Y, 1, 1));
}
Код файла с классом: [искомый код можно найти в приложении к данной лабораторной работе в (описания можно того или иного приложения можно посмотреть в пунтке № 8 протокола работы), а именно необходимо открыть Приложение № 6 (Файлы классов Tool ...)].
ToolEllipse . cs (класс ToolEllipse). Это базовый класс для реализации инструмента рисования «Эллипс». Передаёт параметры объекту рисования. Реализует свойства курсора для инструмента, а также функцию добавления нового объекта эллипса в список объектов документа. Событие нажатия кнопки мыши:
public override void OnMouseDown(DrawArea drawArea, MouseEventArgs e)
{
AddNewObject(drawArea, new DrawEllipse(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).
Первым делом «добьём» форму LWP 15 About. Событие Load для формы будет таким:
private void LWP15About_Load(object sender, EventArgs e)
{
this.Text = "О программе " + Application.ProductName;
L_About.Text =
"Программа: " + Application.ProductName + "\n" +
"Версия: " + Application.ProductVersion;
}
Событие Click кнопки B_OK:
private void 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
Подключаем к проекту ссылку на нашу библиотеку LWP 15 tools: жмём правую кнопку мыши на имени проекта LWP 15 Draw, затем Добавить ссылку...:
Рис. 6. 1. Добавить ссылку: вкладка Проекты
В открывшемся окне переходим на вкладку Проекты и выбираем LWP 15 Tools, жмём ОК.
В коде находим:
namespace LWP15Draw
{
public partial class DrawArea : UserControl
{
public DrawArea()
{
InitializeComponent();
}
}
Заменяем на (последнюю фигурную скобку в файле не трогаем):
namespace LWP15Draw
{
partial class DrawArea : UserControl
{
#region Конструктор
public DrawArea()
{
InitializeComponent();
}
#endregion
#region Перечисления
public enum DrawToolType
{
Pointer, Rectangle, Ellipse, Line, Polygon, NumberOfDrawTools
};
#endregion
#region Члены
private GraphicsList graphicsList; // Список объектов рисования
private DrawToolType activeTool; // Активный инструмент рисования
private Tool[] tools; // Массив инструментов
private LWP15Main owner;
private DocManager docManager;
private ContextMenuStrip m_ContextMenu;
private UndoManager undoManager;
#endregion
#region Свойства
/// <summary>
/// Ссылка на владельца формы
/// </summary>
public LWP15Main Owner
{
get { return owner; }
set { owner = value; }
}
/// <summary>
/// Ссылка на DocManager
/// </summary>
public DocManager DocManager
{
get { return docManager; }
set
{
docManager = value;
}
}
/// <summary>
/// Активный инструмент рисования
/// </summary>
public DrawToolType ActiveTool
{
get { return activeTool; }
set { activeTool = value; }
}
/// <summary>
/// Список объектов рисования
/// </summary>
public GraphicsList GraphicsList
{
get { return graphicsList; }
set
{
graphicsList = value;
undoManager = new UndoManager(graphicsList);
}
}
/// <summary>
/// true - если операция отмены возможна
/// </summary>
public bool CanUndo
{
get
{
if (undoManager != null) { return undoManager.CanUndo; }
return false;
}
}
/// <summary>
/// true - если операция возврата возможна
/// </summary>
public bool CanRedo
{
get
{
if (undoManager != null) { return undoManager.CanRedo; }
return false;
}
}
#endregion
#region Прочие функции
/// <summary>
/// Инициализация
/// </summary>
/// <param name="owner"></param>
/// <param name="docManager"></param>
public void Initialize(LWP15Main owner, DocManager docManager)
{
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer, true);
// Сохраняем ссылку на владельца формы
this.Owner = owner;
this.DocManager = docManager;
// Устанавливаем инструмент по умолчанию
activeTool = DrawToolType.Pointer;
// Создаём список графических объектов
graphicsList = new GraphicsList();
// Создаём экземпляр UndoManager для текущего файла
undoManager = new UndoManager(graphicsList);
// Создаём массив инструментов рисования
tools = new Tool[(int)DrawToolType.NumberOfDrawTools];
tools[(int)DrawToolType.Pointer] = new ToolPointer();
tools[(int)DrawToolType.Rectangle] = new ToolRectangle();
tools[(int)DrawToolType.Ellipse] = new ToolEllipse();
tools[(int)DrawToolType.Line] = new ToolLine();
tools[(int)DrawToolType.Polygon] = new ToolPolygon();
}
/// <summary>
/// Добавления команды в историю
/// </summary>
public void AddCommandToHistory(Command command)
{
undoManager.AddCommandToHistory(command);
}
/// <summary>
/// Очистка истории
/// </summary>
public void ClearHistory()
{
undoManager.ClearHistory();
}
/// <summary>
/// Отменить
/// </summary>
public void Undo()
{
undoManager.Undo();
Refresh();
}
/// <summary>
/// Вернуть
/// </summary>
public void Redo()
{
undoManager.Redo();
Refresh();
}
/// <summary>
/// Устанавливаем флаг "грязный" (файл был изменён после последней операции сохранения)
/// </summary>
public void SetDirty()
{
DocManager.Dirty = true;
}
/// <summary>
/// Обработчик нажатия правой кнопки мышки
/// </summary>
/// <param name="e"></param>
private void OnContextMenu(MouseEventArgs e)
{
// Измененяем текущий выбор при необходимости
Point point = new Point(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 = new ContextMenuStrip();
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>
private void DrawArea_Paint(object sender, PaintEventArgs e)
{
SolidBrush brush = new SolidBrush(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>
private void DrawArea_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left) tools[(int)activeTool].OnMouseDown(this, e);
else if (e.Button == MouseButtons.Right) OnContextMenu(e);
}
/// <summary>
/// Событие MouseMove для DrawArea
/// Перемещение без нажатия кнопок или с нажатие левой кнопки мыши перехватываетсмя активным инструментом
/// </summary>
private void DrawArea_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left || e.Button == MouseButtons.None) tools[(int)activeTool].OnMouseMove(this, e);
else this.Cursor = Cursors.Default;
}
/// <summary>
/// Событие MouseUp для DrawArea
/// ЛКМ: перехватывается активным инструментом
/// </summary>
private void DrawArea_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left) tools[(int)activeTool].OnMouseUp(this, e);
}
#endregion
Переходим к главной форме LWP 15 Main. Переписываем директивы 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
Находим:
public partial class LWP15Main : Form
{
Заменяем на:
partial class LWP15Main : Form
{
#region Члены
private DrawArea drawArea;
private DocManager docManager;
private DragDropManager dragDropManager;
private MruManager mruManager;
private PersistWindowState persistState;
private string argumentFile = ""; // Имя файла из командной строки
const string registryPath = "Software\\LWP15Draw";
#endregion
#region Свойства
/// <summary>
/// Имя файла из командной строки
/// </summary>
public string ArgumentFile
{
get { return argumentFile; }
set { argumentFile = value; }
}
/// <summary>
/// Получаем ссылку на элемент строки меню "Правка".
/// Используется при вызове контекстного-всплывающего меню в классе DrawArea
/// </summary>
/// <value></value>
public ToolStripMenuItem ContextParent
{
get { return правкаToolStripMenuItem; }
}
#endregion
#region Конструктор
public LWP15Main()
{
InitializeComponent();
persistState = new PersistWindowState(registryPath, this);
}
#endregion
#region Обработчики событий для DocManager
/// <summary>
/// Загрузка документа из потока поставляемая DocManager
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
private void 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>
private void 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 Обработчики событий строки меню
private void создатьToolStripMenuItem_Click(object sender, EventArgs e)
{
CommandNew();
}
private void открытьToolStripMenuItem_Click(object sender, EventArgs e)
{
CommandOpen();
}
private void сохранитьToolStripMenuItem_Click(object sender, EventArgs e)
{
CommandSave();
}
private void сохранитькакToolStripMenuItem_Click(object sender, EventArgs e)
{
CommandSaveAs();
}
private void выходToolStripMenuItem_Click(object sender, EventArgs e)
{
this.Close();
}
private void отменитьToolStripMenuItem_Click(object sender, EventArgs e)
{
CommandUndo();
}
private void вернутьToolStripMenuItem1_Click(object sender, EventArgs e)
{
CommandRedo();
}
private void выделитьвсеToolStripMenuItem_Click(object sender, EventArgs e)
{
drawArea.GraphicsList.SelectAll();
drawArea.Refresh();
}
private void снятьВыделениеToolStripMenuItem_Click(object sender, EventArgs e)
{
drawArea.GraphicsList.UnselectAll();
drawArea.Refresh();
}
private void удалитьToolStripMenuItem_Click(object sender, EventArgs e)
{
CommandDelete command = new CommandDelete(drawArea.GraphicsList);
if (drawArea.GraphicsList.DeleteSelection())
{
drawArea.SetDirty();
drawArea.Refresh();
drawArea.AddCommandToHistory(command);
}
}
private void удалитьВсеToolStripMenuItem_Click(object sender, EventArgs e)
{
CommandDeleteAll command = new CommandDeleteAll(drawArea.GraphicsList);
if (drawArea.GraphicsList.Clear())
{
drawArea.SetDirty();
drawArea.Refresh();
drawArea.AddCommandToHistory(command);
}
}
private void переместитьНазадToolStripMenuItem_Click(object sender, EventArgs e)
{
if (drawArea.GraphicsList.MoveSelectionToBack())
{
drawArea.SetDirty();
drawArea.Refresh();
}
}
private void переместитьВпередToolStripMenuItem_Click(object sender, EventArgs e)
{
if (drawArea.GraphicsList.MoveSelectionToFront())
{
drawArea.SetDirty();
drawArea.Refresh();
}
}
private void параметрыToolStripMenuItem_Click(object sender, EventArgs e)
{
if (drawArea.GraphicsList.ShowPropertiesDialog(drawArea))
{
drawArea.SetDirty();
drawArea.Refresh();
}
}
private void выделениеToolStripMenuItem_Click(object sender, EventArgs e)
{
CommandPointer();
}
private void карандашToolStripMenuItem_Click(object sender, EventArgs e)
{
CommandPolygon();
}
private void линияToolStripMenuItem_Click(object sender, EventArgs e)
{
CommandLine();
}
private void эллипсToolStripMenuItem_Click(object sender, EventArgs e)
{
CommandEllipse();
}
private void прямоугольникToolStripMenuItem_Click(object sender, EventArgs e)
{
CommandRectnagle();
}
private void опрограммеToolStripMenuItem_Click(object sender, EventArgs e)
{
CommandAbout();
}
#endregion
Тоже самое проделываем для строки с иконками (панелью инструментов). Двойное нажатие на иконку инициализирует событие Click:
#region Обработчики событий панели инструментов
private void создатьToolStripButton_Click(object sender, EventArgs e)
{
CommandNew();
}
private void открытьToolStripButton_Click(object sender, EventArgs e)
{
CommandOpen();
}
private void сохранитьToolStripButton_Click(object sender, EventArgs e)
{
CommandSave();
}
private void выделениеToolStripButton_Click(object sender, EventArgs e)
{
CommandPointer();
}
private void карандашToolStripButton_Click(object sender, EventArgs e)
{
CommandPolygon();
}
private void линияToolStripButton_Click(object sender, EventArgs e)
{
CommandLine();
}
private void эллипсToolStripButton_Click(object sender, EventArgs e)
{
CommandEllipse();
}
private void прямоугольникToolStripButton_Click(object sender, EventArgs e)
{
CommandRectangle();
}
private void отменитьToolStripButton_Click(object sender, EventArgs e)
{
CommandUndo();
}
private void вернутьToolStripButton_Click(object sender, EventArgs e)
{
CommandRedo();
}
private void справкаToolStripButton_Click(object sender, EventArgs e)
{
CommandAbout();
}
#endregion
Инициализируем три события для главной формы. Событие Load формы LWP 15 Main:
#region Обработчики событий
private void LWP15Main_Load(object sender, EventArgs e)
{
toolStripStatusLabel.Text = "Готов к работе";
// Создаём область рисования
drawArea = new DrawArea();
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>
private void LWP15Main_Resize(object sender, EventArgs e)
{
if (this.WindowState != FormWindowState.Minimized && drawArea != null) { ResizeDrawArea(); }
}
Событие FormClosing:
/// <summary>
/// Событие закрытия формы
/// </summary>
private void 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)
{
// Устанавливаем активный инструмент на "Выделение".
// Педотвращает редкий сбой, когда выбран иной инструмент, пользователь открывает главное менюuser opens
// и после нажатия в DrawArea событие MouseDown не вызывается и MouseMove работает неправильно
drawArea.ActiveTool = DrawArea.DrawToolType.Pointer;
}
#endregion
Наконец добавляем последний участок кода сразу же после предыдущего:
#region Прочие функции
/// <summary>
/// Установка состояния элементов управления.
/// Функция вызывается при простое
/// </summary>
public void SetStateOfControls()
{
// Выбор активного инструмента
выделениеToolStripButton.Checked = (drawArea.ActiveTool == DrawArea.DrawToolType.Pointer);
прямоугольникToolStripButton.Checked = (drawArea.ActiveTool == DrawArea.DrawToolType.Rectangle);
эллипсToolStripButton.Checked = (drawArea.ActiveTool == DrawArea.DrawToolType.Ellipse);
линияToolStripButton.Checked = (drawArea.ActiveTool == DrawArea.DrawToolType.Line);
карандашToolStripButton.Checked = (drawArea.ActiveTool == DrawArea.DrawToolType.Polygon);
выделениеToolStripMenuItem.Checked = (drawArea.ActiveTool == DrawArea.DrawToolType.Pointer);
прямоугольникToolStripMenuItem.Checked = (drawArea.ActiveTool == DrawArea.DrawToolType.Rectangle);
эллипсToolStripMenuItem.Checked = (drawArea.ActiveTool == DrawArea.DrawToolType.Ellipse);
линияToolStripMenuItem.Checked = (drawArea.ActiveTool == DrawArea.DrawToolType.Line);
карандашToolStripMenuItem.Checked = (drawArea.ActiveTool == DrawArea.DrawToolType.Polygon);
bool objects = (drawArea.GraphicsList.Count > 0);
bool selectedObjects = (drawArea.GraphicsList.SelectionCount > 0);
// Операции с файлом
сохранитьToolStripMenuItem.Enabled = objects;
сохранитьToolStripButton.Enabled = objects;
сохранитьКакToolStripMenuItem.Enabled = objects;
// Операции правки
удалитьToolStripMenuItem.Enabled = selectedObjects;
удалитьВсеToolStripMenuItem.Enabled = objects;
выделитьВсеToolStripMenuItem.Enabled = objects;
снятьВыделениеToolStripMenuItem.Enabled = objects;
переместитьВпередToolStripMenuItem.Enabled = selectedObjects;
переместитьНазадToolStripMenuItem.Enabled = selectedObjects;
параметрыToolStripMenuItem.Enabled = selectedObjects;
// "Отменить"
отменитьToolStripMenuItem.Enabled = drawArea.CanUndo;
отменитьToolStripButton.Enabled = drawArea.CanUndo;
// "Вернуть"
вернутьToolStripMenuItem.Enabled = drawArea.CanRedo;
вернутьToolStripButton.Enabled = drawArea.CanRedo;
}
/// <summary>
/// Установка области рисования формы на основе пространство свободной области,
/// за исключением панели инструментов, строки состояния и строки меню
/// </summary>
private void ResizeDrawArea()
{
Rectangle rect = this.ClientRectangle;
drawArea.Left = rect.Left;
drawArea.Top = rect.Top + menuStrip1.Height + toolStrip1.Height;
drawArea.Width = rect.Width;
drawArea.Height = rect.Height - menuStrip1.Height - toolStrip1.Height - statusStrip1.Height;
}
/// <summary>
/// Инициализация вспомогательных объектов из библиотеки классов LWP15Tools.
/// </summary>
private void InitializeHelperObjects()
{
// DocManager
DocManagerData data = new DocManagerData();
data.FormOwner = this;
data.UpdateTitle = true;
data.FileDialogFilter = "Файлы LWP15Draw (*.lwp)|*.lwp|Все файлы (*.*)|*.*";
data.NewDocName = "New.lwp";
data.RegistryPath = registryPath;
docManager = new DocManager(data);
docManager.RegisterFileType("lwp", "lwpfile", "Файл LWP15Draw");
// Подписываемся на события класса DocManager
docManager.SaveEvent += docManager_SaveEvent;
docManager.LoadEvent += docManager_LoadEvent;
// Делаем "встроенные подписки" с помощью анонимных методов
docManager.OpenEvent += delegate(object sender, OpenFileEventArgs e)
{
// Обновляем список последних файлов
if (e.Succeeded) mruManager.Add(e.FileName);
else mruManager.Remove(e.FileName);
};
docManager.DocChangedEvent += delegate(object o, EventArgs e)
{
try
{
drawArea.Refresh();
drawArea.ClearHistory();
}
catch { }
};
docManager.ClearEvent += delegate(object o, EventArgs e)
{
if (drawArea.GraphicsList != null)
{
drawArea.GraphicsList.Clear();
drawArea.ClearHistory();
drawArea.Refresh();
}
};
docManager.NewDocument();
// DragDropManager
dragDropManager = new DragDropManager(this);
dragDropManager.FileDroppedEvent += delegate(object sender, FileDroppedEventArgs e)
{
OpenDocument(e.FileArray.GetValue(0).ToString());
};
// MruManager
mruManager = new MruManager();
mruManager.Initialize(
this, // Владелец формы (this)
последниеФайлыToolStripMenuItem, // Элемент меню последних исользованных файлов
файлToolStripMenuItem, // Родительский элемент ("Файл")
registryPath); // Путь в системном реестре для хранения списка последних файлов
mruManager.MruOpenEvent += delegate(object sender, MruFileOpenEventArgs e)
{
OpenDocument(e.FileName);
};
}
/// <summary>
/// Обрабатываем исключение из функции docManager_LoadEvent
/// </summary>
/// <param name="ex"></param>
/// <param name="fileName"></param>
private void HandleLoadException(Exception ex, SerializationEventArgs e)
{
MessageBox.Show(this,
"Операция открытия файла завершилась неудачей. Имя файла: " + e.FileName + "\n" +
"Причина: " + ex.Message,
Application.ProductName);
e.Error = true;
}
/// <summary>
/// Обрабатываем исключение из функции docManager_SaveEvent
/// </summary>
/// <param name="ex"></param>
/// <param name="fileName"></param>
private void HandleSaveException(Exception ex, SerializationEventArgs e)
{
MessageBox.Show(this,
"Операция сохранения файла завершилась неудачей. Имя файла: " + e.FileName + "\n" +
"Причина: " + ex.Message,
Application.ProductName);
e.Error = true;
}
/// <summary>
/// Открытие документа.
/// Используется для открытия файла переданного в командной строке или "переброшенного" в окно
/// </summary>
/// <param name="file"></param>
public void OpenDocument(string file)
{
docManager.OpenDocument(file);
}
/// <summary>
/// Загрузка настроек приложения из системного реестра
/// </summary>
void LoadSettingsFromRegistry()
{
try
{
RegistryKey key = Registry.CurrentUser.CreateSubKey(registryPath);
DrawObject.LastUsedColor = Color.FromArgb((int)key.GetValue("Color", Color.Black.ToArgb()));
DrawObject.LastUsedPenWidth = (int)key.GetValue("Width", 1);
}
catch (ArgumentNullException ex) { HandleRegistryException(ex); }
catch (SecurityException ex) { HandleRegistryException(ex); }
catch (ArgumentException ex) { HandleRegistryException(ex); }
catch (ObjectDisposedException ex) { HandleRegistryException(ex); }
catch (UnauthorizedAccessException ex) { HandleRegistryException(ex); }
}
/// <summary>
/// Сохранение настроек приложения в системный реестр
/// </summary>
void SaveSettingsToRegistry()
{
try
{
RegistryKey key = Registry.CurrentUser.CreateSubKey(registryPath);
key.SetValue("Color", DrawObject.LastUsedColor.ToArgb());
key.SetValue("Width", DrawObject.LastUsedPenWidth);
}
catch (SecurityException ex) { HandleRegistryException(ex); }
catch (ArgumentException ex) { HandleRegistryException(ex); }
catch (ObjectDisposedException ex) { HandleRegistryException(ex); }
catch (UnauthorizedAccessException ex) { HandleRegistryException(ex); }
}
private void HandleRegistryException(Exception ex)
{
Trace.WriteLine("Выполнение операции с системны реестром завершилась неудачей: " + ex.Message);
}
/// <summary>
/// Выделение
/// </summary>
private void CommandPointer()
{
drawArea.ActiveTool = DrawArea.DrawToolType.Pointer;
toolStripStatusLabel.Text = "Выбран инструмент \"Выделение\"";
}
/// <summary>
/// Прямоугольник
/// </summary>
private void CommandRectangle()
{
drawArea.ActiveTool = DrawArea.DrawToolType.Rectangle;
toolStripStatusLabel.Text = "Выбран инструмент \"Прямоугольник\"";
}
/// <summary>
/// Эллипс
/// </summary>
private void CommandEllipse()
{
drawArea.ActiveTool = DrawArea.DrawToolType.Ellipse;
toolStripStatusLabel.Text = "Выбран инструмент \"Эллипс\"";
}
/// <summary>
/// Линия
/// </summary>
private void CommandLine()
{
drawArea.ActiveTool = DrawArea.DrawToolType.Line;
toolStripStatusLabel.Text = "Выбран инструмент \"Линия\"";
}
/// <summary>
/// Карандаш
/// </summary>
private void CommandPolygon()
{
drawArea.ActiveTool = DrawArea.DrawToolType.Polygon;
toolStripStatusLabel.Text = "Выбран инструмент \"Карандаш\"";
}
/// <summary>
/// Диалог "О программе..."
/// </summary>
private void CommandAbout()
{
LWP15About frm = new LWP15About();
frm.ShowDialog(this);
toolStripStatusLabel.Text = "Вызвана форма \"О программе\"";
}
/// <summary>
/// Создать
/// </summary>
private void CommandNew()
{
docManager.NewDocument();
toolStripStatusLabel.Text = "Создан новый документ";
}
/// <summary>
/// Открыть
/// </summary>
private void CommandOpen()
{
docManager.OpenDocument("");
toolStripStatusLabel.Text = "Документ открыт";
}
/// <summary>
/// Сохранить
/// </summary>
private void CommandSave()
{
docManager.SaveDocument(DocManager.SaveType.Save);
toolStripStatusLabel.Text = "Документ сохранён";
}
/// <summary>
/// Сохранить как
/// </summary>
private void CommandSaveAs()
{
docManager.SaveDocument(DocManager.SaveType.SaveAs);
toolStripStatusLabel.Text = "Документ сохранён";
}
/// <summary>
/// Отменить
/// </summary>
private void CommandUndo()
{
drawArea.Undo();
toolStripStatusLabel.Text = "Произведена операция \"Отменить\"";
}
/// <summary>
/// Вернуть
/// </summary>
private void CommandRedo()
{
drawArea.Redo();
toolStripStatusLabel.Text = "Произведена операция \"Вернуть\"";
}
#endregion
Почти всё. Наше приложение способно создавать свои собственные документы (файлы *. lwp). А как быть, если необходимо сохранить рисунок в нормальном графическом формате? Всё достаточно просто. Нужно сохранить содержимое элемента управления DrawArea в одно из желаемых форматов растрового изображения.
Для реализации этой функции создаём в меню «Файл» новую кнопку «Сохранить как изображение...»:
Свойства такие:
( Name ): | сохранитьКакИзображениеToolStripMenuItem |
Text: | Сохранить как &изображение... |
Image: | Импортируем иконку |
Событие Click по кнопке меню «Сохранить как изображение...»:
#region Добавленная функция экспорта DrawArea в изображение
private Rectangle drawAreaRect;
private string fileName;
private string strFilExtn;
private void сохранитьКакИзображениеToolStripMenuItem_Click(object sender, EventArgs e)
{
SaveFileDialog SFD_Picture = new SaveFileDialog();
SFD_Picture.Title = "Сохранить ка изображение";
SFD_Picture.OverwritePrompt = true;
SFD_Picture.CheckPathExists = true;
SFD_Picture.Filter = "Изображение в формате PNG|*.png|" + "Изображение в формате JPEG|*.jpg|" + "Изображение в формате BMP|*.bmp|" + "Изображение в формате GIF|*.gif|" + "Изображение в формате TIF|*.tif";
drawAreaRect = new Rectangle();
drawAreaRect.Width = drawArea.Size.Width;
drawAreaRect.Height = drawArea.Size.Height;
Bitmap drawAreaBitmap = new Bitmap(drawArea.Size.Width, drawArea.Size.Height);
drawArea.DrawToBitmap(drawAreaBitmap, drawAreaRect);
// Если выбрали, сохраняем
if (SFD_Picture.ShowDialog() == DialogResult.OK)
{
// Получаем имя файла из диалога
fileName = SFD_Picture.FileName;
// Получаем расширения файла из диалога
strFilExtn = fileName.Remove(0, fileName.Length - 3);
// Сохраняем файл в выбранном расширении
switch (strFilExtn)
{
case "bmp":
drawAreaBitmap.Save(fileName, System.Drawing.Imaging.ImageFormat.Bmp);
break;
case "jpg":
drawAreaBitmap.Save(fileName, System.Drawing.Imaging.ImageFormat.Jpeg);
break;
case "gif":
drawAreaBitmap.Save(fileName, System.Drawing.Imaging.ImageFormat.Gif);
break;
case "tif":
drawAreaBitmap.Save(fileName, System.Drawing.Imaging.ImageFormat.Tiff);
break;
case "png":
drawAreaBitmap.Save(fileName, System.Drawing.Imaging.ImageFormat.Png);
break;
default:
break;
}
toolStripStatusLabel.Text = "Изображение " + System.IO.Path.GetFileName(SFD_Picture.FileName) + " успешно сохранено! Полный путь: " + SFD_Picture.FileName;
}
SFD_Picture.Dispose();
drawAreaBitmap.Dispose();
}
#endregion
Хорошо, а если, например нам потребуется вставить изображение, да ещё и изменить его размеры? Тоже не проблема. Выполним полноценное расширение возможностей нашего приложения добавление нового полноценного объекта рисования — изображения. Изображение будет добавлять на форму через диалог выбора файла. По умолчанию у него будет чёрная прямоугольная рамка с толщиной пера в один пиксель. После добавления изображения, цвет и толщину пера рамки можно будет поменять (как и у любого другого объекта). Изображение будет полноценно сериализовываться в файле наравне с остальными объектами. Во время рисования, размер изображения будет автоматически подстраиваться под нарисованную рамку. Вставленное изображение можно будет переместить и изменить его размер.
Первое что нужно сделать, это добавить новый класс для рисования объекта изображения. Назовём его DrawImage (файл DrawImage . cs). За основу возьмём класс DrawRectangle, так как он наиболее походит нам для реализации нового класса.
Код класс рисования будет таким:
using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Globalization;
using System.Runtime.Serialization;
using System.Windows.Forms;
namespace LWP15Draw
{
/// <summary>
/// Рисование графического объекта "Изображение"
/// </summary>
class DrawImage : LWP15Draw.DrawObject
{
private Rectangle rectangle;
private Bitmap _image;
private Bitmap _originalImage; // Оригинальное изображение
public Bitmap TheImage
{
get { return _image; }
set
{
_originalImage = value;
ResizeImage(rectangle.Width, rectangle.Height);
}
}
private const string entryRectangle = "Rect";
private const string entryImage = "Image";
private const string entryImageOriginal = "OriginalImage";
/// <summary>
/// Клонирование данного экземпляра
/// </summary>
public override DrawObject Clone()
{
DrawImage drawImage = new DrawImage();
drawImage._image = _image;
drawImage._originalImage = _originalImage;
drawImage.rectangle = rectangle;
FillDrawObjectFields(drawImage);
return drawImage;
}
protected Rectangle Rectangle
{
get { return rectangle; }
set { rectangle = value; }
}
public DrawImage()
{
SetRectangle(0, 0, 1, 1);
Initialize();
}
public DrawImage(int x, int y)
{
rectangle.X = x;
rectangle.Y = y;
rectangle.Width = 1;
rectangle.Height = 1;
Initialize();
// Устанавливаем перо по умолчанию для каждого нового изображения
Color = Color.Black;
PenWidth = 0;
}
public DrawImage(int x, int y, Bitmap image)
{
rectangle.X = x;
rectangle.Y = y;
_image = (Bitmap)image.Clone();
SetRectangle(rectangle.X, rectangle.Y, image.Width, image.Height);
Initialize();
}
/// <summary>
/// лавная функция рисования изображения на форме
/// </summary>
/// <param name="g"></param>
public override void Draw(Graphics g)
{
Pen pen = new Pen(Color, PenWidth);
if (_image == null)
{
g.DrawRectangle(pen, rectangle);
}
else
{
g.DrawImage(_image, new Point(rectangle.X, rectangle.Y));
g.DrawRectangle(pen, rectangle);
}
}
protected void SetRectangle(int x, int y, int width, int height)
{
rectangle.X = x;
rectangle.Y = y;
rectangle.Width = width;
rectangle.Height = height;
}
public override int HandleCount
{
get { return 8; }
}
/// <summary>
/// Получение ключевых точек изображения
/// </summary>
/// <param name="handleNumber"></param>
/// <returns></returns>
public override Point GetHandle(int handleNumber)
{
int x, y, xCenter, yCenter;
xCenter = rectangle.X + rectangle.Width / 2;
yCenter = rectangle.Y + rectangle.Height / 2;
x = rectangle.X;
y = rectangle.Y;
switch (handleNumber)
{
case 1:
x = rectangle.X;
y = rectangle.Y;
break;
case 2:
x = xCenter;
y = rectangle.Y;
break;
case 3:
x = rectangle.Right;
y = rectangle.Y;
break;
case 4:
x = rectangle.Right;
y = yCenter;
break;
case 5:
x = rectangle.Right;
y = rectangle.Bottom;
break;
case 6:
x = xCenter;
y = rectangle.Bottom;
break;
case 7:
x = rectangle.X;
y = rectangle.Bottom;
break;
case 8:
x = rectangle.X;
y = yCenter;
break;
}
return new Point(x, y);
}
/// <summary>
/// Проверка нажатия.
/// Возвращаемые параметры: -1 - нет нажатия
/// 0 - нажатие где угодно
/// > 1 - обработка ключевой точки
/// </summary>
/// <param name="point"></param>
/// <returns></returns>
public override int HitTest(Point point)
{
if (Selected)
{
for (int i = 1; i <= HandleCount; i++)
{
if (GetHandleRectangle(i).Contains(point)) return i;
}
}
if (PointInObject(point)) return 0;
return -1;
}
protected override bool PointInObject(Point point)
{
return rectangle.Contains(point);
}
/// <summary>
/// Получение курсора для ключевых точек
/// </summary>
/// <param name="handleNumber"></param>
/// <returns></returns>
public override Cursor GetHandleCursor(int handleNumber)
{
switch (handleNumber)
{
case 1:
return Cursors.SizeNWSE;
case 2:
return Cursors.SizeNS;
case 3:
return Cursors.SizeNESW;
case 4:
return Cursors.SizeWE;
case 5:
return Cursors.SizeNWSE;
case 6:
return Cursors.SizeNS;
case 7:
return Cursors.SizeNESW;
case 8:
return Cursors.SizeWE;
default:
return Cursors.Default;
}
}
/// <summary>
/// Изменение размеров объекта изображения
/// </summary>
/// <param name="point"></param>
/// <param name="handleNumber"></param>
public override void MoveHandleTo(Point point, int handleNumber)
{
int left = Rectangle.Left;
int top = Rectangle.Top;
int right = Rectangle.Right;
int bottom = Rectangle.Bottom;
switch (handleNumber)
{
case 1:
left = point.X;
top = point.Y;
break;
case 2:
top = point.Y;
break;
case 3:
right = point.X;
top = point.Y;
break;
case 4:
right = point.X;
break;
case 5:
right = point.X;
bottom = point.Y;
break;
case 6:
bottom = point.Y;
break;
case 7:
left = point.X;
bottom = point.Y;
break;
case 8:
left = point.X;
break;
}
Dirty = true;
SetRectangle(left, top, right - left, bottom - top);
ResizeImage(rectangle.Width, rectangle.Height);
}
protected void ResizeImage(int width, int height)
{
if (_originalImage != null)
{
Bitmap b = new Bitmap(_originalImage, new Size(width, height));
_image = (Bitmap)b.Clone();
b.Dispose();
}
}
public override bool IntersectsWith(Rectangle rectangle)
{
return Rectangle.IntersectsWith(rectangle);
}
/// <summary>
/// Перемещение объекта изображения
/// </summary>
/// <param name="deltaX"></param>
/// <param name="deltaY"></param>
public override void Move(int deltaX, int deltaY)
{
rectangle.X += deltaX;
rectangle.Y += deltaY;
Dirty = true;
}
public override void Dump()
{
base.Dump();
Trace.WriteLine("rectangle.X = " + rectangle.X.ToString(CultureInfo.InvariantCulture));
Trace.WriteLine("rectangle.Y = " + rectangle.Y.ToString(CultureInfo.InvariantCulture));
Trace.WriteLine("rectangle.Width = " + rectangle.Width.ToString(CultureInfo.InvariantCulture));
Trace.WriteLine("rectangle.Height = " + rectangle.Height.ToString(CultureInfo.InvariantCulture));
}
/// <summary>
/// Нормализация объекта прямоугольника
/// </summary>
public override void Normalize()
{
rectangle = DrawRectangle.GetNormalizedRectangle(rectangle);
}
/// <summary>
/// Сохранение объекта изображения в потоке сериализации
/// </summary>
/// <param name="info"></param>
/// <param name="orderNumber"></param>
public override void SaveToStream(SerializationInfo info, int orderNumber)
{
info.AddValue(
String.Format(CultureInfo.InvariantCulture,
"{0}{1}", entryRectangle, orderNumber), rectangle);
info.AddValue(
String.Format(CultureInfo.InvariantCulture,
"{0}{1}",
entryImage, orderNumber), _image);
info.AddValue(
String.Format(CultureInfo.InvariantCulture,
"{0}{1}",
entryImageOriginal, orderNumber), _originalImage);
base.SaveToStream(info, orderNumber);
}
/// <summary>
/// Загрузка объекта изображения из потока сериализации
/// </summary>
/// <param name="info"></param>
/// <param name="orderNumber"></param>
public override void LoadFromStream(SerializationInfo info, int orderNumber)
{
rectangle = (Rectangle)info.GetValue(
String.Format(CultureInfo.InvariantCulture,
"{0}{1}", entryRectangle, orderNumber),
typeof(Rectangle));
_image = (Bitmap)info.GetValue(
String.Format(CultureInfo.InvariantCulture,
"{0}{1}", entryImage, orderNumber),
typeof(Bitmap));
_originalImage = (Bitmap)info.GetValue(
String.Format(CultureInfo.InvariantCulture,
"{0}{1}", entryImageOriginal, orderNumber),
typeof(Bitmap));
base.LoadFromStream(info, orderNumber);
}
#region Вспомогательные функции
public static Rectangle GetNormalizedRectangle(int x1, int y1, int x2, int y2)
{
if (x2 < x1)
{
int tmp = x2;
x2 = x1;
x1 = tmp;
}
if (y2 < y1)
{
int tmp = y2;
y2 = y1;
y1 = tmp;
}
return new Rectangle(x1, y1, x2 - x1, y2 - y1);
}
public static Rectangle GetNormalizedRectangle(Point p1, Point p2)
{
return GetNormalizedRectangle(p1.X, p1.Y, p2.X, p2.Y);
}
public static Rectangle GetNormalizedRectangle(Rectangle r)
{
return GetNormalizedRectangle(r.X, r.Y, r.X + r.Width, r.Y + r.Height);
}
#endregion
}
}
Второй класс нам понадобится для реализации инструмента для рисования изображения. ToolImage (ToolImage.cs) выглядит так:
using System;
using System.Drawing;
using System.Windows.Forms;
namespace LWP15Draw
{
/// <summary>
/// Изображение
/// </summary>
internal class ToolImage : ToolObject
{
public ToolImage()
{
Cursor = new Cursor(GetType(), "C_Image.cur");
}
public override void OnMouseDown(DrawArea drawArea, MouseEventArgs e)
{
AddNewObject(drawArea, new DrawImage(e.X, e.Y));
}
public override void OnMouseMove(DrawArea drawArea, MouseEventArgs e)
{
drawArea.Cursor = Cursor;
if (e.Button == MouseButtons.Left)
{
Point point = new Point(e.X, e.Y);
drawArea.GraphicsList[0].MoveHandleTo(point, 5);
drawArea.Refresh();
}
}
public override void OnMouseUp(DrawArea drawArea, MouseEventArgs e)
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.Title = "Вставка изображения";
ofd.Filter = "Изображение в формате PNG|*.png|" + "Изображение в формате JPEG|*.jpg|" + "Изображение в формате BMP|*.bmp|" + "Изображение в формате GIF|*.gif|" + "Изображение в формате TIF|*.tif|" + "Изображения в формате ICO|*.ico|" + "Все файлы|*.*";
ofd.InitialDirectory = Environment.SpecialFolder.MyPictures.ToString();
if (ofd.ShowDialog() == DialogResult.OK)
{
((DrawImage)drawArea.GraphicsList[0]).TheImage = (Bitmap)Bitmap.FromFile(ofd.FileName);
}
ofd.Dispose();
base.OnMouseUp(drawArea, e);
}
}
}
Курсор под инструмент такой:
Не забываем изменить свойство для файла курсора: «Действие при построении» меняем на «Внедрённый ресурс». Добавляем кнопку на панель инструментов сразу после кнопки «Прямоугольник», изображение «по умолчанию» не трогаем:
( Name ): | изображениеToolStripButton |
Text: | &Изображение |
ToolTipText: | Изображение |
В меню «Рисование» добавим новый пункт «Изображение» со следующими свойствами:
( Name ): | изображениеToolStripMenuItem |
Text: | &Изображение |
Image: | Импортируем иконку |
В файле DrawObject . cs находим:
private const string entryPenWidth = "PenWidth";
Добавляем после:
// Прочее
private bool dirty;
В том же файле DrawObject . cs находим:
/// <summary>
/// ID объекта
/// </summary>
public int ID
{
get { return id; }
set { id = value; }
}
Добавляем после:
/// <summary>
/// true когда объект изменён
/// </summary>
public bool Dirty
{
get { return dirty; }
set { dirty = value; }
}
В файле DrawArea.cs находим:
public enum DrawToolType
{
Pointer, Polygon, Line, Ellipse, Rectangle, NumberOfDrawTools
};
Заменяем на:
public enum DrawToolType
{
Pointer, Polygon, Line, Ellipse, Rectangle, Image, NumberOfDrawTools
};
В том же файле DrawArea . cs находим:
tools[(int)DrawToolType.Ellipse] = new ToolEllipse();
tools[(int)DrawToolType.Rectangle] = new ToolRectangle();
Добавляем после:
tools[(int)DrawToolType.Image] = new ToolImage();
На главной форме инициализируем события двух кнопок «Изображение» в строке меню и на панели инструментов с иконками, а также добавляем общую функцию для двух обработчиков Click:
#region Добавленный инструмент вставки изображения
private void изображениеToolStripButton_Click(object sender, EventArgs e)
{
CommandImage();
}
private void изображениеToolStripMenuItem_Click(object sender, EventArgs e)
{
CommandImage();
}
/// <summary>
/// Изображение
/// </summary>
private void CommandImage()
{
drawArea.ActiveTool = DrawArea.DrawToolType.Image;
toolStripStatusLabel.Text = "Выбран инструмент \"Изображение\"";
}
#endregion
Готово. Можно компилировать и проверять работоспособность.
Завершающая часть
Компилируем приложение (Release) и запускаем. Рисуем что хотим, при помощи всех доступных инструментов, смотрим что получилось в окне приложения:
Рис. 7. 1. Модифицированное приложение Windows Forms: рисуем
Сохраняем документ («Файл» -> «Сохранить» или «Сохранить как») как Test . lwp, закрываем приложение, вновь запускаем, далее открываем ранее сохранённый файл и смотрим список последних файлов («Файл»):
Рис. 7. 2. Модифицированное приложение Windows Forms: открываем ранее сохранённый файл и просматриваем список последних открытых файлов
Выполняем «Файл» -> «Сохранить как изображение....». Сохраняем документ в формате *. gif (Рис. 7. 3):
Рис. 7. 3. Результат работы приложения: изображение Test . gif, открытое в стандартном редакторе изображений Windows 7
Перетаскиваем объекты в документе Test . lwp, меняем их размеры и свойства:
Рис. 7. 4. Модифицированное приложение Windows Forms: меняем свойства объектов в открытом документе Test . lwp
Рис. 7. 5. Модифицированное приложение Windows Forms: перетаскиваем файл Test . lwp на форму из проводника Windows (в результате файл откроется)
Дата добавления: 2019-09-13; просмотров: 198; Мы поможем в написании вашей работы! |
Мы поможем в написании ваших работ!