C++ CLI – улучшаем представление графического редактора

Рубрика: Без категории, Дата: 15 November, 2014, Автор:
Tags:

И так здорова ребятки!

В этом посту мы продолжаем дорабатывать наше приложение графический редактор разработанный в посте C++ CLI рисование в окне и доработанный чуток в посте Книга visual C++ решение задач глава 16 , там мы в нескольких последних задачах доработали наше приложение. На сейчас наше приложение имеет вид

CLI appПока оно по одной фигуре рисует и без сохранения, если мы заведем наше приложение за край области видимости или свернем, то то что мы нарисовали пропадет. Короче в этом посте мы улучшим наше представление.

Да ребятки у меня названия приложений может не совпадать и соответственно некоторые имена разные, но смысл тот же, просто у меня несколько проектов этой программы и я сейчас открыл другой проект. Это не существенно в принципе, потому что суть не меняется.

Реализация сохранения элементов в Sketch

Для начала нам нужно изменить наше старое приложения, а именно функцию Draw нашего класса Circle меняем следующим образом

virtual void Draw(Graphics^ g) override
{
	//перемещаем начало отсчета на position.X position.Y
	g->TranslateTransform(safe_cast<float>(position.X),
			  safe_cast<float>(position.Y));
	g->DrawEllipse(pen,0,0,width,height);
	//отменяем все настройки
	g->ResetTransform();
}

в комментариях все написано что делает та или иная функция, функция TranslateTransform устанавливает точку отсчета в позиции position.X, position.Y, а функция ResetTransform сбрасывает все установки.

Изменим функцию Draw для класса Line

//функция рисования линии
virtual void Draw(Graphics^ g) override
{
	//перемещаем начало отсчета системы координаты
	g->TranslateTransform(safe_cast<float>(position.X),
			  safe_cast<float>(position.Y));
	//Код для рисования самой линии
	g->DrawLine(pen,0,0,end.X-position.X,end.Y-position.Y);
	//сбрасываем настройки
	g->ResetTransform();
}

теперь функция DrawLine рисует линию относительно точки 0,0, после того как функция TranslateTransform перемещает исходную точку клиентской области в позицию position – точку начала линии.

Код функции Draw класса Rectangle очень похож на тот, который применялся в классе Circle

virtual void Draw(Graphics^ g) override
{
	//перемещаем начало отсчета на position.X position.Y
	g->TranslateTransform(safe_cast<float>(position.X),
			  safe_cast<float>(position.Y));
	g->DrawRectangle(pen,0,0,width,height);
	//отменяем все настройки
	g->ResetTransform();
}

И изменим функцию Draw класса Curve

Можете откомпилировать и все у вас будет работать, так же как и работало.

Теперь создадим класс Sketch. Добавьте новый заголовочный файл Sketch.h и от его определение

Показать »

//фалй Sketch.h
//Содержит определение эскиза

#pragma once
#include <cliext/list>
#include "Elements.h"

using namespace System;
using namespace cliext;

namespace CLRSketcher33//то пространство в котором находится ваш основной класс form
{
	public ref class Sketch
	{
	private:
		list<Element^>^ elements;

	public:
		Sketch()
		{
			elements=gcnew list<Element^>();
		}

		//Добавить элемент в эскиз
		Sketch^ operator+=(Element^ element)
		{
			elements->push_back(element);
			return this;
		}

		//удалить элемент из эскиза
		Sketch^ operator-=(Element^ element)
		{
			elements->remove(element);
			return this;
		}

		void Draw(Graphics^ g)
		{
			for each(Element^ element in elements)
				element->Draw(g);
		}
	};
}

подключим этот файл в основном файле Form1.h, добавим в класс form переменную член типа Sketch^ с именем sketch и в конструкторе инициируем нашу переменную

Form1(void)
	:elementType(ElementType::LINE)
	,color(Color::Black)
	, drawing(false)
	, firstPoint(0)	
	, Style(ElementStyle::SOLID)
	, sketch(gcnew Sketch())
.......

Добавим код в обработчик события MouseUp так что бы он добавлял элементы в эскиз

private: System::Void Form1_MouseUp(System::Object^  sender, System::Windows::Forms::MouseEventArgs^  e) {
	 if(!drawing)
		 return;
	 if(tempElement)
	 {
		 sketch+=tempElement;
		 //Сохранение элемента в эскизе
		 tempElement=nullptr;
		 //делаем недействительной всю область
		 Invalidate();
	 }
	 drawing=false;
 }

И обеспечим рисование всего эскиза, для этого изменим функцию – обработчик события Paint

private: System::Void Form1_Paint(System::Object^  sender, System::Windows::Forms::PaintEventArgs^  e) {
	 Graphics^ g=e->Graphics;
		 sketch->Draw(g);
	 //Код рисования эскиза
	 if(tempElement!=nullptr)
	 {
		 tempElement->Draw(g);
	 }
				
 }

Все теперь компилируем нашу программу и запускаем и мы уже можем сворачивать-разворачивать наше окно или выводить-вводить его за область видимости и данные не будут удалятся, так как они хранятся в нашем эскизе Sketch

Реализация механизма подсветки элементов

Для этого добавьте в класс Element открытый член highlighted типа bool, содержащий информацию о том подсвечен элемент или нет

public:
property bool highlighted;

Так же добавьте в класс Element защищенный член, определяющий цвет подсветки элементов

Color highlighColor

Добавьте в класс Element открытый конструктор, чтобы инициализировать новые члены

Element()
	:highlightColor(Color::Magenta)
{highlighted=false;}

К описывающему прямоугольнику элемента необходим доступ извне, поэтому в класс Element необходимо добавить открытое свойство, чтобы предоставить его

property System::Drawing::Rectangle bound
{
	System::Drawing::Rectangle get(){return boundRect;}
}

это делает переменную boundRect доступной не подвергая опасности целостность класса, поскольку для свойства bound нет никакой функции set().

Теперь изменим реализацию функции Draw() в каждом из классов, производных от класса Element. Ниже показано, как эта функция должна выглядеть для класса Line.

virtual void Draw(Graphics^ g) override
{
	pen->Color=highlighted?highlightColor:color;
	//перемещаем начало отсчета системы координаты
	g->TranslateTransform(safe_cast<float>(position.X),
			  safe_cast<float>(position.Y));
	//Код для рисования самой линии
	g->DrawLine(pen,0,0,end.X-position.X,end.Y-position.Y);
	//сбрасываем настройки
	g->ResetTransform();
}

Сточка кода

pen->Color=highlighted?highlightColor:color;

Выбирает цвет в соответствии с состоянием переменной highlighted, в общем измените все остальные функции Draw по аналогии.

Поиск элемента для выделения

Добавим в класс Element открытую функцию Hit которая будет выяснять какой из элементов, если он есть находится под курсором

bool Hit(Point p)
{
	return boundRect.Contains(p);
}

 

Теперь можно добавить в класс Sketch следующую открытую функцию для определения того, находится ли какой-нибудь из элементов в эскизе под курсором.

//функция для поиска элементов под курсором
Element^ HitElement(Point p)
{
	for(auto riter=elements->rbegin();
		riter!=elements->rend();++riter)
	{
		if((*riter)->Hit(p))
			return *riter;
	}
	return nullptr;
}

 

Добавьте в класс Form1 защищенный член highlightedElement типа Element^ для фиксирования информации о текущем подсвечиваемом элементе и инициализируйте его в конструкторе класса Form1 со значением nullptr.

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

Показать »

private: System::Void Form1_MouseMove(System::Object^  sender, System::Windows::Forms::MouseEventArgs^  e) {
	if(drawing)
	{
		if(tempElement)
			//Обьявить недействительной старую область элемента
			Invalidate(tempElement->bound);
		switch(elementType)
		{
		case ElementType::LINE:
			tempElement=gcnew Line(color,firstPoint,e->Location,Style);
			break;
		case ElementType::RECTANGLE:
			tempElement=gcnew Rectangle(color,firstPoint,e->Location,Style);
			break;
		case ElementType::CIRCLE:
			tempElement=gcnew Circle(color,firstPoint,e->Location,Style);
			break;
		case ElementType::CURVE:
			if(tempElement==nullptr)
				tempElement=gcnew Curve(color,firstPoint,e->Location,Style);
			else
				safe_cast<Curve^>(tempElement)->Add(e->Location);
			break;
		case ElementType::ELLIPSE:
			tempElement=gcnew Ellipse(color,firstPoint,e->Location,Style);
			break;
			//	MessageBox::Show("Hellow");
		}
		Invalidate();
	}
	else
	{
		//Найти элемент под курсором, если он есть
		Element^ element(sketch->HitElement(e->Location));
		//Если старый элемент совпадает с новым,
		//то ничего не делать
		if(highlightedElement==element)
			return;

		//Сброс любого выделенного элемента
		if(highlightedElement)
		{
			//Область элемента недействительна
			Invalidate(highlightedElement->bound);
			highlightedElement->highlighted=false;
			highlightedElement=nullptr;
		}
		//Поиск и установка нового выделенного элемента если он есть
		highlightedElement=element;
		if(highlightedElement)
		{
			highlightedElement->highlighted=true;
			//Область элемента не действительна
			Invalidate(highlightedElement->bound);
		}
		Update();//послать сообщение о необходимеости перерисовки
	}
 }

Можно уже компелировать и при наводке мышки на элемент он будет подсвечиваться, но увы он подсвечивается не правильно, для того что б подсвечивался правильно нужно увеличить описывающий прямоугольник для каждого элемента и так приступим, от новый код конструктора для Line

//конструктор
Line(Color color, Point start, Point end,ElementStyle aStyle)
{
	pen=gcnew Pen(color);
	pen->DashPattern=GetStyle(aStyle);
	this->color=color;
	position = start;
	this->end=end;
	boundRect=System::Drawing::Rectangle(
					Math::Min(position.X,end.X),
					Math::Min(position.Y,end.Y),
					Math::Abs(position.X-end.Y),
					Math::Abs(position.Y-end.Y));
			
	//корректировка ограничивающего прямоугольника элемента
	int penWidth(safe_cast<int>(pen->Width));//ширина пера как целое
	boundRect.Inflate(penWidth,penWidth);
}

Последние строчки также добавьте в классы Rectangle и Circle. Класс Curve должен быть изменен так как показано ниже

Curve(Color color, Point p1, Point p2, ElementStyle aStyle)
{
	pen=gcnew Pen(color);
	pen->DashPattern=GetStyle(aStyle);
	this->color=color;
	points=gcnew vector<Point>();
	position=p1;
	points->push_back(p2-Size(position));

	//Найдите минимальные и максимальные значения координат
	int minX=p1.X<p2.X?p1.X:p2.X;
	int minY=p1.Y<p2.Y?p1.Y:p2.Y;
	int maxX=p1.X>p2.X?p1.X:p2.X;
	int maxY=p1.Y>p2.Y?p1.Y:p2.Y;
	int width=Math::Max(2,maxX-minX);
	int height=Math::Max(2,maxY-minY);
	boundRect=System::Drawing::Rectangle(minX,minY,width,height);
	
	//корректировка ограничивающего прямоугольника элемента
	int penWidth(safe_cast<int>(pen->Width));//ширина пера как целое
	boundRect.Inflate(penWidth,penWidth);
}

Необходимо изменить так же функцию-член Add класса Curve

//Добавьте точку кривой
void Add(Point p)
{
	points->push_back(p-Size(position));

	//Изменение ограничивающего прямоугольника, так чтобы он вмещал
	//новую точку
	int penWidth(safe_cast<int>(pen->Width));//ширина пера как целое
	boundRect.Inflate(-penWidth,-penWidth);//Уменьшить ширину пера

	if(p.X<boundRect.X)
	{
		boundRect.Width=boundRect.Right-p.X;
		boundRect.X=p.X;
	}
	else if(p.X>boundRect.Right)
		boundRect.Width=p.X-boundRect.Left;

	if(p.Y<boundRect.Y)
	{
		boundRect.Height=boundRect.Bottom-p.Y;
		boundRect.Y=p.Y;
	}
	else if(p.Y>boundRect.Bottom)
		boundRect.Height=p.Y-boundRect.Top;
	boundRect.Inflate(penWidth,penWidth);//Увеличить ширину пера
}

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

Создание контекстных меню

Для программ CLI контекстные меню создаются интерактивным образом с помощью конструктора форм за счет перетаскивания из окна Toolbox (Панель инструментов) в форму элемента управления ContextMenuStrip.

Берем элемент из меню свойства и вставляем его на нашу форму

CLI properties

 

 

CLI form

меню можно выбрать в конструкторе форм, в низу там целый список меню. И так идем дальше, добавим в меню элементы, у вас должно получиться так как на рисунке ниже

CLI contextMenu

Переименуйте пункты меню для Send to Back, Delete и Move на sendToBackContextMenuItem, deleteContextMenuItem, moveContextMenuItem. Для остальных пунктов Line, Rectangle и т.д. на lineContextMenuItem, rectangleContextMenuItem и т.д. Разделитель переименуйте на contaxtSeparator

Обработчики Click нужно создать только три для пунктов меню Move, Delete и Send to Back, для всех остальных выбираем старые обработчики.

CLI click

Контроль отображения пунктов в раскрывающемся списке контекстного меню, в том или ином случае, можно обеспечить в обработчике событий Opening элемента управления ContextMenuStrip1 (он у нас так называется). Выберите свойства события и добавьте обработчик Opening

CLI opening

Заполним обработчик Opening

private: System::Void contextMenuStrip1_Opening(System::Object^  sender, System::ComponentModel::CancelEventArgs^  e) {
	 contextMenuStrip1->Items->Clear();//Удаление существующих элементов
	 if(highlightedElement)
	 {
		contextMenuStrip1->Items->Add(moveContextMenuItem);
		contextMenuStrip1->Items->Add(deleteContextMenuItem);
		contextMenuStrip1->Items->Add(sendToBackMenuItem);
	 }
	 else
	 {
		 contextMenuStrip1->Items->Add(lineContextMenuItem);
		 contextMenuStrip1->Items->Add(rectangleContextMenuItem);
		 contextMenuStrip1->Items->Add(circleContextMenuItem);
		 contextMenuStrip1->Items->Add(curveContextMenuItem);
		 contextMenuStrip1->Items->Add(contextSeparator);
		 contextMenuStrip1->Items->Add(blackContextMenuItem);
		 contextMenuStrip1->Items->Add(redContextMenuItem);
		 contextMenuStrip1->Items->Add(greenContextMenuItem);
		 contextMenuStrip1->Items->Add(blueContextMenuItem);

		 //установка отметок для пунктов меню
		 LineContextMenuItem->Checked=elementType==ElementType::LINE;
		 rectangleContextMenuItem->Checked=elementType==ElementType::RECTANGLE;
		 circleContextMenuItem->Checked=elementType==ElementType::CIRCLE;
		 curveContextMenuItem->Checked=elementType==ElementType::CURVE;
		 blackContextMenuItem->Checked=color==Color::Black;
		 redContextMenuItem->Checked=color==Color::Red;
		 greenContextMenuItem->Checked=color==Color::Green;
		 blueContextMenuItem->Checked=color==Color::Blue;
	 }
 }

затем зайдите в свойства главного окна и в пункте contextMenuStrip выберете наше созданное меню

CLI contextMenu add

все можете откомпелировать и у вас появится возможность вызывать меню выбора элемента и подсветки, если вы кликните на пустом месте

CLI contextMenu item

и если не на пустом, а над выделенным элементом, то появится меню другое

CLI context menu

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

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

 

 

Реализация операции удаления элемента

для этого к пункту меню delete добавим обработчик и вот его код

private: System::Void deleteContextMenuItem_Click(System::Object^  sender, System::EventArgs^  e) {
	 if(highlightedElement)
	 {
		 sketch-=highlightedElement;//удалить выделенный элемент
		 Invalidate(highlightedElement->bound);
		 highlightedElement=nullptr;
		 Update();
	 }
 }

 

Реализация отправки элемента на задний план

так же само добавляем обработчик события для пункта меню Send to Back и реализуем ее функцию

private: System::Void sendToBackMenuItem_Click(System::Object^  sender, System::EventArgs^  e) {
	 if(highlightedElement)
	 {
		 sketch-=highlightedElement;//Удалить выделенный элемент
		 sketch->push_front(highlightedElement);//Затем снова добавить
							//его в начало
		 highlightedElement->highlighted=false;
		 Invalidate(highlightedElement->bound);
		 highlightedElement=nullptr;
		 Update();
	 }
}

и реализуем функцию push_front в классе Sketch, просто ее добавьте в этот класс, вот ее код

void push_front(Element^ element)
{
	elements->push_front(element);
}

Все откомпелируйте и попробуйте как отправляются элементы на задний план

 

Реализация перемещения элемента

Для идентификации возможных режимов добавим в файл Form1.h сразу же после перечисления ElementType соответствующий класс перечисления

enum class Mode{Normal,Move};

Теперь в класс Form1 нужно добавить закрытую переменную-член mode типа Mode. Сделайте это и инициализируйте ее значением Mode::Normal в конструкторе Form1

Создайте  обработчик пункта Move и от его код

private: System::Void moveContextMenuItem_Click(System::Object^  sender, System::EventArgs^  e) {
	 mode=Mode::Move;
 }

 

Измените обработчик MouseDown на следующий

private: System::Void Form1_MouseDown(System::Object^  sender, System::Windows::Forms::MouseEventArgs^  e) {
	if(e->Button==System::Windows::Forms::MouseButtons::Left)
	{
		if(mode==Mode::Normal)
			drawing=true;

		firstPoint=e->Location;
	}
 }

Добавьте в класс Element открытую функцию Move

void Move(int dx, int dy)
{
	position.Offset(dx,dy);
	boundRect.X+=dx;
	boundRect.Y+=dy;
}

 

изменим обработчик MouseMove

Показать »

private: System::Void Form1_MouseMove(System::Object^  sender, System::Windows::Forms::MouseEventArgs^  e) {
	if(drawing)
	{
		if(tempElement)
			//Обьявить недействительной старую область элемента
			Invalidate(tempElement->bound);
		switch(elementType)
		{
		case ElementType::LINE:
			tempElement=gcnew Line(color,firstPoint,e->Location,Style);
			break;
		case ElementType::RECTANGLE:
			tempElement=gcnew Rectangle(color,firstPoint,e->Location,Style);
			break;
		case ElementType::CIRCLE:
			tempElement=gcnew Circle(color,firstPoint,e->Location,Style);
			break;
		case ElementType::CURVE:
			if(tempElement==nullptr)
				tempElement=gcnew Curve(color,firstPoint,e->Location,Style);
			else
				safe_cast<Curve^>(tempElement)->Add(e->Location);
			break;
		case ElementType::ELLIPSE:
			tempElement=gcnew Ellipse(color,firstPoint,e->Location,Style);
			break;
			//	MessageBox::Show("Hellow");
		}
		Invalidate();
	}
	else if(mode==Mode::Normal)
	{
		//Найти элемент под курсором, если он есть
		Element^ element(sketch->HitElement(e->Location));
		//Если старый элемент совпадает с новым,
		//то ничего не делать
		if(highlightedElement==element)
			return;
			//Сброс любого выделенного элемента
		if(highlightedElement)
		{
			//Область элемента недействительна
			Invalidate(highlightedElement->bound);
			highlightedElement->highlighted=false;
			highlightedElement=nullptr;
		}

		//Поиск и установка нового выделенного элемента если он есть
		highlightedElement=element;
		if(highlightedElement)
		{
			highlightedElement->highlighted=true;
			//Область элемента не действительна
			Invalidate(highlightedElement->bound);
		}
		Update();//послать сообщение о необходимеости перерисовки
	}
	else if(mode==Mode::Move&&
		e->Button==System::Windows::Forms::MouseButtons::Left)
	{//перемещение выделенного элемента
		if(highlightedElement)
		{
			//область перед перемещением
			Invalidate(highlightedElement->bound);
			highlightedElement->Move(e->X-firstPoint.X,
						e->Y-firstPoint.Y);
			firstPoint=e->Location;
			//Область после перемещения
			Invalidate(highlightedElement->bound);
			Update();
		}
	}
 }

И последнее, что нужно сделать для того чтобы обеспечить возможность перемещения элементов, – изменить обработчик событий MouseUp, как показано ниже

Все компелируем и пытаемся перемещать, но увы линию не правильно перемещает.

CLI lineЗделайте функцию Move в классе Element виртуальной и переопределите ее для класса Line

virtual void Move(int dx, int dy) override
{
	end.Offset(dx,dy);
	Element::Move(dx,dy);
}

CLI lineвсе компелируем и смотрим что у нас получилось, короче все фигуры рисуются нормально кроме линии

CLI one lineэто я нарисовал одну линию и попытался ее переместить и у меня получилось две линии, просто половинка выделилась, а остальная часть не выделилась так и осталась, хотя я рисовал такую большую линию, непонятно что за фигня, щас попытаюсь разобраться. Да я выше ошибся с описывающим четырехугольником, там вмесно X я написал Y, после того как я исправил все стало нормально работать. Как я и говорил вся проблема была в описывающем четырехугольнике.

Скину я вам некоторые файлы

sketch.h »

//фалй Sketch.h
//Содержит определение эскиза

#pragma once
#include <cliext/list>
#include "Elements.h"

using namespace System;
using namespace cliext;

namespace CLRSketcher33//то пространство в котором находится ваш основной класс form
{
	public ref class Sketch
	{
	private:
		list<Element^>^ elements;

	public:
		Sketch()
		{
			elements=gcnew list<Element^>();
		}

		//Добавить элемент в эскиз
		Sketch^ operator+=(Element^ element)
		{
			elements->push_back(element);
			return this;
		}

		//удалить элемент из эскиза
		Sketch^ operator-=(Element^ element)
		{
			elements->remove(element);
			return this;
		}

		void Draw(Graphics^ g)
		{
			for each(Element^ element in elements)
				element->Draw(g);
		}

		//функция для поиска элементов под курсором
		Element^ HitElement(Point p)
		{
			for(auto riter=elements->rbegin();
				riter!=elements->rend();++riter)
			{
				if((*riter)->Hit(p))
					return *riter;
			}
			return nullptr;
		}

		void push_front(Element^ element)
		{
			elements->push_front(element);
		}
	};
}

Elements.h »

//Elements.h
//Определяет типы элементов
#pragma once
using namespace CLRSketcher33;
#include <cliext/vector>
using cliext::vector;

using namespace System;
using namespace System::Drawing;

namespace CLRSketcher33
{
	public ref class Element abstract
	{
	protected:
		Point position;
		Color color;
		System::Drawing::Rectangle boundRect;
		Pen^ pen;//указатель на перо

	public:
		virtual void Draw(Graphics^ g) abstract;
		array<float>^ GetStyle(ElementStyle s)
		{
			switch(s)
			{
			case ElementStyle::SOLID:
				{
				array<float>^ b={10.0f};
				return b;
				break;
				}
			case ElementStyle::DASHER:
				{
				array<float>^ b={10.0f,10.0f};
				return b;
				break;
				}
			case ElementStyle::DOTTER:
				{
				array<float>^ b={1.0f,3.0f};
				return b;
				break;
				}
			default:
				exit(1);
			}
		}

	public:
		property bool highlighted;
		Color highlightColor;
	
		Element()
			:highlightColor(Color::Magenta)
		{highlighted=false;}
		
		property System::Drawing::Rectangle bound
		{
			System::Drawing::Rectangle get(){return boundRect;}
		}

		bool Hit(Point p)
		{
			return boundRect.Contains(p);
		}

		virtual void Move(int dx, int dy)
		{
			position.Offset(dx,dy);
			boundRect.X+=dx;
			boundRect.Y+=dy;
		}

	};

	public ref class Line : Element
	{
	protected:
		Point end;

	public:
		//конструктор
		Line(Color color, Point start, Point end,ElementStyle aStyle)
		{
			pen=gcnew Pen(color);
			pen->DashPattern=GetStyle(aStyle);
			this->color=color;
			position = start;
			this->end=end;
			boundRect=System::Drawing::Rectangle(
										Math::Min(position.X,end.X),
										Math::Min(position.Y,end.Y),
										Math::Abs(position.X-end.X),
										Math::Abs(position.Y-end.Y));
			
			//корректировка ограничивающего прямоугольника элемента
			int penWidth(safe_cast<int>(pen->Width));//ширина пера как целое
			boundRect.Inflate(penWidth,penWidth);
		}

		//функция рисования линии
		virtual void Draw(Graphics^ g) override
		{
			pen->Color=highlighted?highlightColor:color;
			//перемещаем начало отсчета системы координаты
			g->TranslateTransform(safe_cast<float>(position.X),
								  safe_cast<float>(position.Y));
			//Код для рисования самой линии
			g->DrawLine(pen,0,0,end.X-position.X,end.Y-position.Y);
			//сбрасываем настройки
			g->ResetTransform();
		}

		virtual void Move(int dx, int dy) override
		{
			end.Offset(dx,dy);
			Element::Move(dx,dy);
		}
	};

	public ref class Circle : Element
	{
	protected:
		int width;
		int height;
	public:
		Circle(Color color,Point start,Point end,ElementStyle aStyle)
		{
			pen=gcnew Pen(color);
			pen->DashPattern=GetStyle(aStyle);
			this->color=color;
			int radius=safe_cast<int>(Math::Sqrt(
				(start.X-end.X)*(start.X-end.X)+
				(start.Y-end.Y)*(start.Y-end.Y)));
			width=height=2*radius;
			position.X=start.X-radius;
			position.Y=start.Y-radius;
			//описывающий четырехугольник
			boundRect=System::Drawing::Rectangle(position,Size(width,height));
			
			int penWidth(safe_cast<int>(pen->Width));//ширина пера как целое
			boundRect.Inflate(penWidth,penWidth);
		}

		virtual void Draw(Graphics^ g) override
		{
			pen->Color=highlighted?highlightColor:color;
			//перемещаем начало отсчета на position.X position.Y
			g->TranslateTransform(safe_cast<float>(position.X),
								  safe_cast<float>(position.Y));			
			g->DrawEllipse(pen,0,0,width,height);
			//отменяем все настройки
			g->ResetTransform();
		}
	};

	public ref class Rectangle : Element
	{
	protected:
		int width;
		int height;

	public:
		Rectangle(Color color, Point p1, Point p2,ElementStyle aStyle)
		{
			pen=gcnew Pen(color);
			pen->DashPattern=GetStyle(aStyle);
			this->color=color;
			position=Point(Math::Min(p1.X,p2.X),Math::Min(p1.Y,p2.Y));
			width=Math::Abs(p1.X-p2.X);
			height=Math::Abs(p1.Y-p2.Y);
			boundRect=System::Drawing::Rectangle(position,Size(width,height));
			
			int penWidth(safe_cast<int>(pen->Width));//ширина пера как целое
			boundRect.Inflate(penWidth,penWidth);
		}

		virtual void Draw(Graphics^ g) override
		{
			pen->Color=highlighted?highlightColor:color;
			//перемещаем начало отсчета на position.X position.Y
			g->TranslateTransform(safe_cast<float>(position.X),
								  safe_cast<float>(position.Y));
			g->DrawRectangle(pen,0,0,width,height);
			//отменяем все настройки
			g->ResetTransform();
		}
	};


	public ref class Curve : Element
	{
	private:
		vector<Point>^ points;

	public:
		Curve(Color color, Point p1, Point p2, ElementStyle aStyle)
		{
			pen=gcnew Pen(color);
			pen->DashPattern=GetStyle(aStyle);
			this->color=color;
			points=gcnew vector<Point>();
			position=p1;
			points->push_back(p2-Size(position));

			//Найдите минимальные и максимальные значения координат
			int minX=p1.X<p2.X?p1.X:p2.X;
			int minY=p1.Y<p2.Y?p1.Y:p2.Y;
			int maxX=p1.X>p2.X?p1.X:p2.X;
			int maxY=p1.Y>p2.Y?p1.Y:p2.Y;
			int width=Math::Max(2,maxX-minX);
			int height=Math::Max(2,maxY-minY);
			boundRect=System::Drawing::Rectangle(minX,minY,width,height);
			
			//корректировка ограничивающего прямоугольника элемента
			int penWidth(safe_cast<int>(pen->Width));//ширина пера как целое
			boundRect.Inflate(penWidth,penWidth);
		}
		
		//Добавьте точку кривой
		void Add(Point p)
		{
			points->push_back(p-Size(position));

			//Изменение ограничивающего прямоугольника, так чтобы он вмещал
			//новую точку
			int penWidth(safe_cast<int>(pen->Width));//ширина пера как целое
			boundRect.Inflate(-penWidth,-penWidth);//Уменьшить ширину пера

			if(p.X<boundRect.X)
			{
				boundRect.Width=boundRect.Right-p.X;
				boundRect.X=p.X;
			}
			else if(p.X>boundRect.Right)
				boundRect.Width=p.X-boundRect.Left;

			if(p.Y<boundRect.Y)
			{
				boundRect.Height=boundRect.Bottom-p.Y;
				boundRect.Y=p.Y;
			}
			else if(p.Y>boundRect.Bottom)
				boundRect.Height=p.Y-boundRect.Top;
			boundRect.Inflate(penWidth,penWidth);//Увеличить ширину пера
		}

		virtual void Draw(Graphics^ g) override
		{
			pen->Color=highlighted?highlightColor:color;
			//устанавливаем начало отсчета координат
			g->TranslateTransform(safe_cast<float>(position.X),
								  safe_cast<float>(position.Y));

			Point previous(0,0);
			for each(Point p in points)
			{
				g->DrawLine(pen,previous,p);
				previous=p;
			}
			//сбрасываем настройки
			g->ResetTransform();
		}
	};

	public ref class Ellipse : Element
	{
		protected:
		int width;
		int height;
	public:
		Ellipse(Color color,Point start,Point end,ElementStyle aStyle)
		{
			pen=gcnew Pen(color);
			pen->DashPattern=GetStyle(aStyle);
			this->color=color;
		
			position=start;//позиция
			height=Math::Abs(end.Y-start.Y);
			width=Math::Abs(end.X-start.X);
		
			//описывающий четырехугольник
			boundRect=System::Drawing::Rectangle(position,Size(width,height));
		}

		virtual void Draw(Graphics^ g) override
		{
			g->DrawEllipse(pen,position.X,position.Y,width,height);
		}
	};
};

ну и сама программа скрин как я перемещаю элементы

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

 

[youtube]https://www.youtube.com/watch?v=TyouOtKDV_s[/youtube]

 

Комментарии:


Оставить комментарий

Your email address will not be published. Required fields are marked *