Модификация приложения 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; Мы поможем в написании вашей работы!

Поделиться с друзьями:






Мы поможем в написании ваших работ!