С++ CLI Диалоговые окна и элементы управления

Рубрика: Без категории, Дата: 28 February, 2015, Автор:

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

Продолжаем улучшать нашу программку разработанную в посте С++ CLI улучшаем представление графического редактора .

CLI applicationДобавление диалогового окна

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

В таком случае диалоговое окно фактически создается за счет добавления в проект формы и ее последующего превращения в диалоговое окно. Щелкните в окне проводника решения правой кнопкой мыши на своем проекте и выберите в контекстном меню пункт Add => New Item (Добавить => Новый элемент). В появившемся диалоговом окне выберите сначала в левой части раздел UI(Пользовательский интерфейс), а затем в правой части – компонент Windows Form (Форма Windows). Введите в качестве имени PenDialog.cs и щелкните на кнопке ОК.

CLI cs

Расширение .cs указывает на то что данное диалоговое окно предназначено для программы С++/CLI. Далее появится новое окно Design (Конструктор) вместе с добавленным только что диалоговым окном PenDialog.

cli design

В окне свойств этого диалогового окна измените значение свойства FormBorderStyle в группе Appearance (Внешний вид) на значение FixedDialog, а для свойств ControlBox, MinimizeBox и MaximizeBox в группе Window Style (Стиль окна) установите значение false. Затем измените значение свойств Text на Set Pen Width. В результате получится готовое модальное диалоговое окно для установки ширины пера, в которое можете начинать добавлять необходимые элементы управления.

Настройки диалогового окна

Чтобы позволить выбирать толщину пера, нужно использовать кнопки переключатели. Их следует разместить в групповом блоке, чтобы пользователь мог выбирать только один переключатель из всех. Для этого перетащите в только что созданное диалоговое окно элемент управления GroupBox из категории Containers (Контейнеры) окна Toolbox (Панель инструментов). Измените значение его свойства Text на Select Pen Width, а значение свойства name – на penWidthGroupBox. Отрегулируйте его размер, перетаскивания границы так, чтобы он хорошо вписывался в рамки диалогового окна. Перетащите в него шесть элементов управления RadioButton из окна Toolbox и разместите их в прямоугольном порядке. Вы заметите, что после размещения первого элемента управления RadioButton все остальные будут уже выравниваться для отображения автоматически. Измените значение свойства Text каждого из элементов управления RadioButton на Pen Width 1, Pen Width 2 и т.д. до Pen Width 6, а значение  свойства name – соответственно на penWidthButton1, penWidthButton2 и т.д. до penWidthButton6. Для переключателя penWidthButton1 измените значение свойства Checked на true.

Далее можете добавить в диалоговое окно две обычные кнопки и изменить значение их свойства Text на OK и Cancel соответственно, а значение свойства name – на penWidthOk и penWidthCancel. Затем вернитесь к окну свойств самого диалогового окна и установите для свойств AcceptButton и CancelButton значения penWidhtOk и penWidthCancel, выбрав их из списка в столбце значений. Удостоверьтесь в том что для свойства кнопок DialogResult  установлены значения ОК и Cancel, чтобы при закрытии диалогового окна щелчком на той или другой кнопке всегда возвращалось соответствующее значение DialogResult. После этого у вас должно получиться в окне конструктора диалоговое окно, подобно показанному ниже

CLI dialogКод для класса диалогового окна является частью проекта и определяется в файле заголовка PenDialog.h. Но в настоящий момент ни одного экземпляра класса PenDialog пока нигде не существует, поэтому, чтобы использовать данное диалоговое окно, его нужно создать. Поскольку сделать это необходимо в классе Form1, добавьте сначала в файл Form1.h директиву #include для файла заголовка PenDialog.h. Затем добавьте в класс Form1 новую закрытую переменную penDialog типа PenDialog^ и инициализируйте ее в конструкторе класса Form1 как gcnew PenDialog().

Далее понадобится какой-то способ для получения из объекта PenDialog информации о том, какой именно из переключателей был выбран. Один из возможных вариантов сделать эту информацию доступной – добавить в класс открытое свойство. Это может быть и доступное только для чтения свойство, так что от вас требуется лишь реализовать для свойства метод get(). Код, который создает конструктор форм Widnows (Windows From Designer), выделяется директивами #pragma region и #pragma endregion, и изменять его вручную или добавлять какой-нибудь код нельзя. Поэтому добавьте приведенный ниже код в класс PenDialog сразу же после директивы #pragma endregion.

public: property float PenWidth
{
	float get()
	{
		if(penWidthButton1->Checked)
			return 1.0f;
		if(penWidthButton2->Checked)
			return 2.0f;
		if(penWidthButton3->Checked)
			return 3.0f;
		if(penWidthButton4->Checked)
			return 4.0f;
		if(penWidthButton5->Checked)
			return 5.0f;
		return 6.0f;
	}
}

Класс System::Drawing::Pen определяет перо, где ширина пера – значение с плавающей запятой; следовательно, свойство PenWidth должно иметь тип float.

Теперь, когда диалоговое окно готово и у класса Form1 имеется ссылающийся на него объект, необходим механизм для его открытия. Еще одна кнопка в панели инструментов в окне Form1 является вполне подходящим для этого вариантом.

Отображение диалогового окна

Необходимо найти растровый рисунок, который мог бы отображаться на этой новой кнопке в панели инструментов. Для этого переключитесь в режим Resource View (Представление ресурсов), щелкните правой кнопкой мыши на папке Bitmap (Растровый рисунок) и в появившемся контекстном меню выберите пункт Insert Bitmap (Вставить растровый рисунок). Установите для свойства Height и Width вставляемого рисунка значение 16, для свойства Filename – значение penwidth.bmp, а для свойства ID – значение IDB_PENWIDTH. Теперь можете создать любой растровый рисунок для представления толщины линии: здесь используется рисунок с изображением рисующего линию пера.

В открытом для файла Form1.h окне конструктора добавьте в панель инструментов разделитель со следующей за ним новой кнопкой. Измените значение свойства name на penWidthButton, а значение свойства ToolTipText – на Change pen width. Укажите в качестве рисунка для этой новой кнопки файл penwidth.bmp, а затем создайте для нее обработчик событий Click, дважды щелкнув в окне ее свойств на событии Click. Измените имя функции обработчика на penWidthButton_Click, после чего добавьте в новый обработчик событий следующий код, отображающий диалоговое окно установки толщины пера.

private: System::Void penWidthButton_Click(System::Object^  sender, System::EventArgs^  e) {
	 if(penDialog->ShowDialog()==System::Widnows::Forms::DialogResult::OK)
	 {
		 //Установить толщину пера ...
	 }
 }

Здесь вызов функции ShowDialog() для объекта PenDialog приводит к отображению диалогового окна. После этого окно является модальным диалоговым окном, оно остается видимым до тех пор, пока щелчок на кнопке не закроет его. Функция ShowDialog() возвращает значение, представляющее собой одно из значений, доступных в перечислении System::Windows::Form::DialogResult:None, OK, Cancel, Abort, Retry, Ingore, Yes и No

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

Далее потребуется зафиксировать где-то в классе Form1 текущую толщину пера, поэтому добавьте в класс закрытую переменную penWidth типа float и инициализируйте ее в конструкторе значением 1.0f. После этого можно заменить комментарий в приведенном выше обработчике событий Click, как показано ниже.

penWidth=penDialog->PenWidth;

Благодаря этому текущая толщина пера будет приравниваться к значению, возвращаемому свойством PenWidth диалогового окна.

Установка толщины пера для рисования

Этот процесс подразумевает внесение изменений в каждый из производных классов элементов. Все эти изменения в принципе выглядят одинаково для каждого класса, поэтому здесь демонстрируется внесение изменений только в конструктор класса Line, во все остальные классы вы сможете внести изменения и сами.

Ниже показаны изменения, которые нужно внести в конструктор класса Line, чтобы обеспечить возможность использовать перо разной толщины.

//конструктор
Line(Color color, Point start, Point end,ElementStyle aStyle,float penWidth)
{
	pen=gcnew Pen(color,penWidth);
	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));
	
	//корректировка ограничивающего прямоугольника элемента
	boundRect.Inflate(safe_cast<int>(penWidth),
		safe_cast<int>(penWidth));
}

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

Изменение списка параметров в конструкторах классов элементов требует внесения соответствующих изменений и в обработчики событий 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,penWidth);
			break;
		case ElementType::RECTANGLE:
			tempElement=gcnew Rectangle(color,firstPoint,e->Location,Style,penWidth);
			break;
		case ElementType::CIRCLE:
			tempElement=gcnew Circle(color,firstPoint,e->Location,Style,penWidth);
			break;
		case ElementType::CURVE:
			if(tempElement==nullptr)
				tempElement=gcnew Curve(color,firstPoint,e->Location,Style,penWidth);
			else
				safe_cast<Curve^>(tempElement)->Add(e->Location);
			break;
		case ElementType::ELLIPSE:
			tempElement=gcnew Ellipse(color,firstPoint,e->Location,Style,penWidth);
			break;
			//	MessageBox::Show("Hellow");
		}
		Invalidate();
	}
        //остальная часть кода такая же, как и раньше.
}

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

CLI AppИспользование элемента управления раскрывающегося списка

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

Установите для параметра DropDownStyle значение DropDownList; это делается для того, чтобы значение можно было выбирать только из списка. Установка значения DropDown сделает возможным ввод в раскрывающемся списке любого желаемого значения. Измените значение свойства name на что-нибудь более подходящее, например lineStyleComboBox. Чтобы добавить конкретный набор элементов для отображения в раскрывающемся списке, выберите свойство Items, отыщите в столбце его значений значение Collection, а затем щелкните на расположенной справа от него кнопке с изображением многоточия. В открывшемся диалоговом окне String Collection Editor (Редактор строковых коллекций) введите желаемые строки

CLI editor string colection

Эти строки представляют собой именно те записи, которые и будут отображаться в раскрывающемся списке, и индексируются они от 0 до 4.

Поскольку предлагаемый по умолчанию размер раскрывающегося списка шире, чем требуется, измените значение свойства Size на 150,25. Еще можете изменить значение свойства FlatStyle на Standard, чтобы он лучше выглядел, а также установить для свойства ToolTipText значение Choose line style.

В настоящее время при первом отображении в раскрывающемся списке ничего нет, но это легко исправить, добавив в конструктор класса Form1, после комментария //TODO, следующую строку кода.

lineStyleComboBox->SelectedIndex=0;

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

Текущий стиль линии необходимо где-то хранить, поэтому добавьте в класс Form1 закрытую переменную-член lineStyle типа System::Drawing::Drawing2D::DashStyle. Это тип перечисления, используемый классом Pen для определения стиля рисуемой линии. Если добавить директиву using для пространства имен System::Drawing::Drawing2D, то можно будет определять тип переменной lineStyle как DashStyle.

В перечислении DashStyle определены члены Solid, Dash, Dot, DashDot, DashDotDot и Castom. Значение первых пяти очевидно. Стиль Custom позволяет определить стиль линии как практически любую последовательность линейных сегментов и промежутков необходимых длин. Стиль линии определяется в свойстве DashPattern объекта Pen. Для определения шаблона используется массивом значение типа float, которые определяют длины линейных сегментов и пробелов.

array<float>^ pattern ={5.0f, 3.0f};

pen->DashPattern=pattern;

Этот код определяет для объекта пера пунктирную линию, состоящую из сегментов длиной 5.0f, разделенных пробелами длиной 3.0f. Указанный шаблон повторяется по длине всей линии. Вполне очевидно, что определяя большее количество элементов в массиве, вы можете определить шаблоны любой сложности.

Чтобы узнать, когда необходимо изменить значение переменной-члена lineStyle класса Form1, следует отследить, когда изменится значение раскрывающегося списка. Самый простой способ подразумевает добавление обработчика события SelectedIndexChanged объекта ComboBox. Добавьте этот обработчик в окне Properties и реализуйте его так как показано ниже.

 

private: System::Void lineStyleCombaBox_SelectedIndexChanged(System::Object^  sender, System::EventArgs^  e) {
	 switch(lineStyleComboBox->SelectedIndex)
	 {
	 case 1:
		 lineStyle=DashStyle::Dash;
		 break;
	 case 2:
		 lineStyle=DashStyle::Dot;
		 break;
	 case 3:
		 lineStyle=DashStyle::DashDot;
		 break;
	 case 4:
		 lineStyle=DashStyle::DashDotDot;
		 break;
	 default:
		 lineStyle=DashStyle::Solid;
		 break;
	 }
 }

Этот код лишь присваивает переменной-члену lineStyle одно из значений перечисления DashStyle на основании значения свойства SelectedIndex объекта раскрывающегося списка.

Чтобы нарисовать элементы с различными стилями линий, необходимо добавить еще один параметр типа DashStyle в каждый из конструкторов элемента. Сначала добавьте следующую директиву using в файл Elements.h

using namespace System::Drawing::Drawing2D;

Измените конструктор класса Line, чтобы обеспечить рисование линий разными стилями.

//конструктор
Line(Color color, Point start, Point end,float penWidth,DashStyle style)
{
	pen=gcnew Pen(color,penWidth);
	pen->DashStyle=style;
	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));
	//корректировка ограничивающего прямоугольника элемента
	boundRect.Inflate(safe_cast<int>(penWidth),
		safe_cast<int>(penWidth));
}

Здесь только одна новая строка кода, которая присваивает свойству DashStyle объекта пера значение переданного конструктору последнего аргумента. Измените другие конструкторы класса элемента таким же образом.

Последнее что осталось сделать, – изменить вызовы конструктора в обработчике событий перемещения мышки в классе Form1

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,penWidth,lineStyle);
			break;
		case ElementType::RECTANGLE:
			tempElement=gcnew Rectangle(color,firstPoint,e->Location,penWidth,lineStyle);
			break;
		case ElementType::CIRCLE:
			tempElement=gcnew Circle(color,firstPoint,e->Location,penWidth,lineStyle);
			break;
		case ElementType::CURVE:
			if(tempElement==nullptr)
				tempElement=gcnew Curve(color,firstPoint,e->Location,penWidth,lineStyle);
			else
				safe_cast<Curve^>(tempElement)->Add(e->Location);
			break;
		case ElementType::ELLIPSE:
			tempElement=gcnew Ellipse(color,firstPoint,e->Location,penWidth,lineStyle);
			break;
			//	MessageBox::Show("Hellow");
		}
       }
//Остальная часть кода такая же, как и раньше...
}

Если перекомпилировать и запустить версию CLR приложения Sketcher, то раскрывающийся список позволит выбрать новый стиль линии для рисуемых элементов. Стили линий не будут работают с кривыми из-за способа их рисования, так как они представлют собой набор очень коротких сегментов линий. У других элементов линии со стилями будут выглядеть лучше, если ширина пера будет больше, чем принято по умолчанию.

CLI app test

Создание текстовых элементов

Рисование текста в версии CLR программы Sketcher будет выглядеть примерно так же, как в версии MFC. Выбор пункта меню Text или соответствующей кнопки панели инструментов будет приводить к установке режима рисования Text. Щелчок где-нибудь в форме в этом режиме будет, в свою очередь приводить к отображению модального окна, позволяющего вводить текст, а закрытие этого диалогового окна с помощью кнопки ОК – к отображению введенного текста в позиции курсора. Тема рисования текста является очень обширной, поэтому ее обсуждение здесь ограничивается рассмотрением лишь основных моментов.

Сначала добавьте перечислитель ТEXT в класс перечисления ElementType, а затем пункт Text в меню Element и соответствующую кнопку в панель управления. Измените для пункта меню значение свойства name на textToolStripMenuItem и создайте для него обработчик событий Click. Можете также добавить для него и соответствующей кнопки панели инструментов желаемое значение для свойства ToolTipText. Далее создайте для кнопки панели инструментов растровый рисунок, изображающий текстовый режим, и укажите, что для нее должен использоваться тот же обработчик событий Click, что и для соответствующего пункта меню. Сам обработчик событий Click реализуйте так, как показано ниже.

private: System::Void textToolStripMenuItem_Click(System::Object^  sender, System::EventArgs^  e) {
	elementType=ElementType::TEXT;
	//SetElementTypeButtonState();
 }

Эта функция просто приравнивает elementType к перечислителю ElementType, представляющему режим рисования текста.

Не забудте добавить пункт меню Text и в контекстное меню.

Рисование текста

Прежде чем версия CLR приложения Sketcher сможет рисовать текст, необходимо рассмотреть несколько вопросов. Начнем со способа рисования текста. В классе Graphics для рисования текста доступна функция DrawString(). У этой функции имеется несколько перегруженных версий, краткое описание которых приведено в таблице

Перегруженные версии функции DrawString() »

Функция Описание
DrawString(String^ str, Font^ font, Brush^ brush, PointF point) Рисует строку str в позиции point с использованием шрифта, указанного в аргументе font, и цвета, заданного аргументом brush. Под Windows::Drawing::PointF подразумевает точка, представленная с помощью координат типа float. В любой из версий этой функции в качестве аргумента для параметра PointF может использоваться объект Point
DrawString(String^ str, Font^ font, Brush^ brush, float X, float Y) Рисует строку str в позиции (X,Y) с использованием шрифта, указанного в аргументе font, и цвета, заданного аргумента brush. При выводе этой функции для указания координат также допускается использование аргументов типа int
DrawString(String^ str, Font^ font, Brush^ brush, RectangleF rect) Рисует строку str в пределах прямоугольника rect с использованием шрифта, указанного в аргументе font, и цвета, заданного аргументом brush. Под Windows::Drawing::RectabgleF подразумевается прямоугольник, позиция, ширина и высота которого указаны с помощью значения типа float. В любой из версий этой функции в качестве аргумента для параметра RectabgleF может использоваться объект Rectangle
DrawString(String^ str, Font^ font, Brush^ brush, PointF point, StringFormat^ format) Рисует строку str в позиции point с использованием шрифта, указанного в аргументе font, цвета, заданного аргументом brush, и форматирования строки, указанного аргументом format. Объект StringFormat определяет выравнивания и другие свойства форматирования строки
DrawString(String^ str, Font^ font, Brush^ brush, float X, float Y, StringFormat^ format) Рисует строку str в позиции (X,Y) с использованием шрифта, указанного в аргументе font, цвета, заданного аргументом brush, и форматирования строки, указанного аргументом format
DrawString(String^ str, Font^ font, Brush^ brush, RectangleF rect, StringFormat^ format) Рисует строку str в пределах прямоугольника rect с использованием шрифта указанного в аргументе font, цвета, заданного аргументом brush, и форматирования строки, указанного аргументом format

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

Создание шрифтов

Шрифт, предназначенный для рисования строки, задается с помощью объекта System::Drawing::Font, который определяет гарнитуру, стиль и размер прорисовываемых символов. Объект типа System::Drawing::FontFamily определяет группу шрифтов с определенной гарнитурой, например “Arial” или “Times New Roman”. Перечисление System::Drawing::FontStyle определяет возможные стили шрифтов, которые могут иметь вид одного из следующих значений: Regular, Bold, Italic, Underline или Strikeout

Создать объект Font можно следующим образом.

FontFamily^ family=gcnew FontFamily(L”Arial”);

System::Drawing::Font^ font=gcnew System::Drawing::Font(family,10, FontStyle::Bold, GraphicsUnit::Point);

Объект FontFamily создается за счет передачи имени шрифта конструктору. Аргументами для конструктора класса Font являются название семейства, которому принадлежит данный шрифт, размер этого шрифта, егоо стиль и какое-нибудь значение из перечисления GraphicUnit, определяющее единицы, в которых должен измеряться размер шрифта. Возможные значения перечисления приведены в таблице ниже

Значения перечисления GraphicUnit »

World Единицы измерения – мировая система координат
Display Единица измерения – единица измерения устройства отображения для мониторов пиксели и 1/100 дюйма для принтеров
Pixel Единица измерения – пиксель устройства
Point Единица измерения – пункт (1/72 дюйма)
Inch Единица измерения – дюйм
Document Единица измерения – единица документа, которая составляет 1/300 дюйма
Millimeter Единица измерения – миллиметр

Таким образом, приведенный выше фрагмент кода указывает, что использоваться должен шрифт Arial размером в 10 пунктов и с положирным начертанием.

Обратите внимание на то, что имя типа Font в этом фрагменте полностью квалифицировано из-за того, что объект формы в приложении Windows Forms происходдит от базового класса Form, свойство которого тоже имеет имя Font. Приложения Windows Forms поддерживают главным образом шрифты TruType, поэтому шрифты OpenType для них лучше не выбирать. В случае применения шрифта, который либо не поддерживается, либо не был установлен на данном компьютере, будет исползоваться шрифт Microsoft Sans Serif.

Создание кистей

Объект System::Drawing::Brush определяет цвет, используемый при рисовании строк заданным шрифтом, а также может применятся для указания цвета и текстуры, которые должны использоваться для заливки фигуры. Создавать объект Brush напрямую нельзя, потому что он является абстрактным классом. Поэтому кисти создаются с применением таких типов классов, как SolidBrush, TextureBrush и LinearGradientBrush, которые происходят от класса Brush. Объект SolidBrush представляет одноцветную кисть, объект TextureBrush – кисть, использующую рисунок для заливки внутренней области фигуры, а объект LinearGradientBrush – кисть, применяющую градиентную смесь, которая обычно состоит из двух цветов, но может также состоять из нескольких цветов. Для рисования текста используется объект SolidBrush, создаваемый следующим образом.

solidBrush^ brush = gcnew SolidBrush(Color::Red);

Здесь создается одноцветная кисть, которая будет рисовать текст красного цвета.

Выбор шрифта

Вы можете сохранить ссылку на текущий объект Font, который должен использоваться при создании элементов Text, за счет добавления в класс Form1 переменной типа System::Drawing::Font^ по имени textFont. Инициализируйте ее в конструкторе класса Form со свойством Font, которое представляет собой свойство формы, указывающее, какой шрифт должен использоваться для нее по умолчанию. Обязательно сделайте это в теле конструктора следом за комментарием //TODO:, поскольку при попытке инициализировать ее в списке инициализации программа выдаст ошибку, так как свойство Font еще не определено.

Далее можете добавить кнопку в панель инструментов для предоставления пользователю возможности выбирать желаемый шрифт для ввода текста, возможно, снабдив ее растровым рисунком с изображением буквы F(сокращенно от Find). Измените значение ее свойства name на что-то более подходящее, например fontTollStripButton, а затем добавьте для нее обработчик события Click.

В окне Toolbox имеется стандартное диалоговое окно для выбора шрифта, в котором отображаются все доступные в данной системе шрифты и которое позволяет выбирать для шрифта желаемый стиль и размер. Перейдите в открытое для файла Form1.h окно конструктора и перетащите из окна Toolbox в форму элемент управления FontDialog. Отвечать за его отображение может обработчик событий Click кнопки fontToolStripButton.

private: System::Void fontTollStripButton_Click(System::Object^  sender, System::EventArgs^  e) {
	 if(fontDialog1->ShowDialog()==System::Windows::Forms::DialogResult::OK)
	 {
		 textFont=fontDialog1->Font;
	 }
 }

Здесь диалоговое окно для выбора шрифта отображается вызовом функции ShowDialog(), точно так же, как это было и в случае диалогового окна для выбора толщины пера. Если эта функция возвращает значение DialogResult::OK, то это означает, что данное диалоговое окно было закрыто щелчком на кнопке ОК. В таком случае далее выбранный шрифт извлекается из свойства Font объекта диалогового окна и сохраняется в переменной textFont в объекте класса Form1. Если хотите, можете испробовать, как это работает. Диалоговое окно для выбора шрифта должно выглядеть примерно так, как показано на рис

CLI font dialog

Теперь необходим класс для представления элемента Text, поэтому и определим его.

Определение класса текстового элемента

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

public ref class TextElement : Element
{
protected:
	String^ text;
	SolidBrush^ brush;
	Font^ font;

public:
	TextElement(Color color, Point p, String^ text, Font^ font)
	{
		this->color=color;
		brush=gcnew SolidBrush(color);
		position=p;
		this->text=text;
		this->font=font;
		int height=font->Height;//Высота текстовой строки
		int width=static_cast<int>(font->Size*text->Length);//ширина
		boundRect=System::Drawing::Rectangle(position,Size(width,height));
		boundRect.Inflate(2,2);//Учет подстрочных элементов
	}

	virtual void Draw(Graphics^ g) override
	{
		brush->Color=highlighted?highlightColor:color;
		g->TranslateTransform(safe_cast<float>(position.X),
			safe_cast<float>(position.Y));
		g->DrawString(text,font,brush,Point(0,0));
		g->ResetTransform();
	}
};

Имя TextElement, а не просто Text здесь было выбрано для того, чтобы не возникало путаницы с членом Text класса Form. Объект TextElement обладает членами для сохранения строки текста, а также шрифта и кисти, которые должны применяться для рисования этого текста.

Определение ограничивающего прямоугольника для текстового элемента сопряжено с небольшими трудностями, причиной которых является зависимость от размера шрифта в пунктах и необходимость вычислять из него ширину и высоту. Высота вычисляется легко, поскольку у объекта font есть свойство Height, которое делает доступным информацию о междустрочном интервале шрифта, который является хорошей оценкой для высоты текстовой строки. Свойство Size возвращает размер шрифта (соответствующий ширине символа М). Таким образом, вы можете примерно оценить ширину прямоугольника строки, умножив размер em на количество символов строки. Я увеличил прямоугольник вдвое, так как междустрочные интервал не всегда соответствует описывающему прямоугольнику, поскольку он не учитывает подстрочные элементы, как у символов p и q.

Создание диалогового окна для ввода текста

При создании элемента TextElement понадобится вводить текст с клавиатуры, поэтому необходимо диалоговое окно, в котором это можно было бы делать. Чтобы создать такое окно, перейдите к проводнику решения и добавьте в версию CLR программы Sketcher новую форму, щелкнув правой кнопкой мыши на имени проекта и выбрав в контекстном меню пункт Add=>New Item… (Добавить=>Новый элемент). Затем введите в качестве имени TextDialog. Измените значение свойства Text формы на Create Text Element, а значения других свойства – так, как делали это для диалогового окна PenDialog. В частности, потребуется изменить значение свойства FormBorderStyle на FixedDialog и установить свойства MaximizeBox, MinimizeBox и ControlBox в состояние false.

Следующим шагом является добавление кнопок для закрытия диалогового окна. Поэтому добавьте в диалоговое окно TextDialog кнопки ОК и Cancel с соответствующими значениями в свойстве DialogResult. Измените значение их свойства name на textOkButton и textCancelButton соответственно. И наконец, установите для свойства AcceptButton диалогового окна TextDialog значение textOKButton, а для свойства CancelButton – значение textCancelButton.

Используйте элемент управления TextBox

Элемент управления TextBox позволяет вводить как одну, так и несколько строк текста, поэтому он, конечно, подходит для преследуемых здесь целей. Добавьте его в диалоговое окно TextDialog, перетащив из окна Toolbox в окно конструктора. По умолчанию он позволяет вводить только одну строку текста, и здесь предполагается, что именно это и требуется. Если нужно, чтобы он позволял вводить несколько строк текста, щелкните на отображаемой в окне конструктора справа от TextBox стрелке, направленной вниз, и установите значение Multiline. Свойство Text элемента управления TextBox предоставляет доступ к вводимым данным и делает их доступными в виде объекта String.

В идеале нужно, чтобы диалоговое окно TextDialog открывалось сразу же с наведенным фокусом на текстовом поле, позволяющем вводить текст. Тогда можно будет сразу же после открытия диалогового окна вводить текст и нажимать клавишу <Enter> для его закрытия так, будто бы был выполнен щелчок на кнопке ОК. То, на каком элементе управления будет изначально находится фокус, определяет устанавливаемый для элементов управления диалогового окна порядок перехода по клавише табуляции, который зависит того, какие значения указываются в свойстве TabIndex этих элементов. Указание для свойства TabIndex элемента управления TextBox значения 0, а для свойства TabIndex кнопок ОК и Cancel – значений 1 и 2 приведет к тому, что при отображении диалогового окна фокус изначально будет находится именно на текстовом поле. Порядок перехода по клавише табуляции также определяет последовательность переноса фокуса с одного элемента управления на другой при нажатии клавиши <Tab>. Чтобы увидеть, как выглядит порядок перехода по клавише табуляции в окне конструктора, выберите в главном меню пункт View=>Tab Order (Вид=>Порядок табуляции); выбор этого же пункта меню снова позволит убрать из вида порядок табуляции.

Элемент управления TextBox1 будет хранить вводимую строку, но, поскольку он является закрытым (private) членом объекта диалогового окна, получать к нему доступ напрямую нельзя. Вместо этого нужно добавить механизм для извлечения строки из объекта диалогового окна. Другой проблемой является то, что элемент управления TextBox1 будет сохранять вводимый текст и отображать его снова при следующем открытии диалогового окна. Вы вряд ли хотите, чтобы такое происходило, а это значит, что необходим какой-то способ для сбрасывания значения свойства Text у элемента управления TextBox1. Можно добавить в класс TextDialog открытое свойство, которое будет решать сразу обе задачи. Чтобы сделать это, вставьте в определении класса TextDialog сразу же следом за директивой #pragma endregion следующий код.

//Свойство для доступа к тексту в поле редактирования
public:
property String^ TextString
{
	String^ get(){return textBox1->Text;}
	void set(String^ text){textBox1->Text=text;}
}

Здесь вызываемая для свойства TextString функция get() делает доступной ту строку, которая вводится, а функция set() позволяет ее сбрасывать.

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

//Установить шрифт поля редактирования
public: property System::Drawing::Font^ TextFont
{
	void set(System::Drawing::Font^ font){textBox1->Font=font;}
}

Это свойство только для записи, которое вы можете использовать для установки шрифта объекта поля редактирования textBox1.

Для помощи в создании текстовых элементов необходимы еще кое-какие дополнительные переменные-члены в классе Form1. Добавьте в файл Form1.h директиву #include для файла заголовка TextDialog.h, а затем член textDialog типа TextDialog^. Инициализируйте его вызовом gcnew TextDialog() в списке инициализации конструктора класса Form1.

Отображение диалогового окна и создание текстового элемента

Процесс создания текстового элемента отличается от процесса создания геометрических элементов, и поэтому для понимания последовательности событий в коде сначала посмотрим, как этот процесс выглядит в интерактивном режиме. Режим создания текстового элемента вступает в силу при выборе пункта меню Text или щелчка на соответствующей кнопке панели инструментов. Для создания элемента в форме в том месте, где должен размещаться левый верхний угол строки текста, осуществляется щелчок мышью. Это приводит к отображению диалогового окна TextDialog, в котором в текстовом поле вводится любой желаемый текст, после чего нажимается клавиша <Enter> для закрытия диалогового окна. Обработчик события MouseMove не принимает в этом процессе вообще никакого участия. Таким образом, получается, что весь процесс начинается со щелчка кнопкой мыши для указания позиции размещения элемента. Это означает, что отображение диалогового окна и создание текстового элемента должно осуществляться в обработчике событий MouseDown. Необходимый для этого код приведен ниже.

private: System::Void Form1_MouseDown(System::Object^  sender, System::Windows::Forms::MouseEventArgs^  e) {
	if(e->Button==System::Windows::Forms::MouseButtons::Left)
	{
		firstPoint=e->Location;
		if(mode==Mode::Normal)
		{
			if(elementType==ElementType::TEXT)
			{
				//Сброс строки текстового поля
				textDialog->TextString=L"";
				//Установить шрифт для поля редактирования
				textDialog->TextFont=textFont;
				if(textDialog->ShowDialog()==System::Windows::Forms::DialogResult::OK)
				{
					tempElement=gcnew TextElement(color,firstPoint,
						textDialog->TextString,textFont);
					sketch+=tempElement;
					//Область текстового элемента
					Invalidate(tempElement->bound);
					tempElement=nullptr;
					Update();
				}
				drawing=false;
			}
			else
			{
				drawing=true;
			}
		}
	}
 }

В этом коде сначала указывается, что текстовые элементы должны создаваться только при условии, если член elementType формы имеет значение ElementType::TEXT, а mode – значение Mode::Normal; второе условие необходимо во избежание отображения диалогового окна, когда оно находится в режиме перемещения. когда условие if истинно, первым делом осуществляется сброс значения свойства Text для элемента управления TextBox1 в пустую строку за счет установки последней в качестве значения для свойства TextString диалогового окна. Далее, после вызова в выражении условия if функции ShawDialog, отображается диалоговое окно. Если функция ShowDialog() возвращает значение DialogResult::OK, то осуществляется извлечение строки из диалогового окна, создание объекта TextElement и его добавление в эскиз. После этого область, занимается новым элементом, становится недействительной и отображается заново за счет вызова функции Update(). Значение указателя tempElement по завершении всего этого тоже сбрасывается в nullptr. И наконец, переменная drawing устанавливается в состояние false для предотвращения попыток создать элемент обработчиком MouseMove.

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

CLI test application

на этом все! The end!

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

 

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


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

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