Московский государственный технический университет имени Н.Э.Баумана Учебное пособие Н.Ю.Рязанова, К.Л.Тассов, М.В.Филиппов Программирование на С++ в среде Visual Studio CLR Windows Forms Москва Издательство МГТУ имени Н.Э.Баумана 2015 Введение Проведение занятий по информатике во 2-м семестре 1-ого курса для студентов, изучающих язык С, предполагает использование среды Windows Form Application, являющейся составной частью Visual Studio C++ . Это мощная система визуального проектирования, предназначенна для создания программ, работающих под управлением операционной системы Windows. В основе Windows Form Application лежит объектно-ориентированный язык C++. Эта среда позволяет разрабатывать приложения любой степени сложности для решения задач различных предметных областей с графическим интерфейсом, выполняемых под управлением ОС Windows. CLR Windows Form Application является средой визуального программирования. Она позволяет разработчику конструировать интерфейс приложения, используя стандартные визуальные компоненты среды. Однако, в настоящее время крайне мало литературы на русском языке, посвященной разработке программ в Windows Form Application. Поэтому 1-ый и 2-ой параграфы пособия посвящены рассмотрению особенностей разработки проектов в этой среде и описанию основных ее компонентов. В 3-ем параграфе подробно рассмотрены различные проекты, самостоятельное изучение которых позволит разрабатывать подобные приложения. 1. Создание проекта в среде Windows Form Интегрированная среда CLR разработки программ Windows Form Application, входящая в состав Visual Studio 2008 - 2012 – это среда, в которой есть все необходимые инструменты для проектирования, запуска и отладки программ, написанных на языке С++. Она включает в себя средства построения оконного интерфейса приложений, текстовый редактор, отладчик, редактор изображений, средства для работы с базами данных. Таким образом, среда Windows Form Application представляет разработчику достаточно полный набор инструментов для создания широкого спектра приложений. 2 Приложение, разработанное в среде Windows Form Application, называется проектом. Проект включает в себя информацию об интерфейсе программы и программные коды обработчиков различных событий. 1.1. Описание среды Windows Form Для запуска Windows Form необходимо выбрать пиктограмму Microsoft Visual Studio 2008 из меню ПускПрограммыMicrosoft Visual Studio 2008. После этого на экране появляется стандартное окно разработки программных продуктов, в верхней части которого расположена строка с командами главного меню. Далее необходимо выбрать FileNew и появившемся всплывающем меню выбрать пункт Project. В появившейся панели New Project в окне Project Type выбрать CLR, в окне Template – Windows Form Application и задать имя проекта (Name), как показано на Рис.1 Рис.1 3 После нажатия клавиши OK на экране появляется панель седы разработки приложения, представленная на Рис.2. На этой панели представлены три основные окна: - Главное окно; - Окно формы Form1; - Окно сведений об объекте Properties. Главное окно расположено в верхней части экрана. Это окно включает основное ме- ню и панель инструментов Основное меню находится под заголовком окна и содержит все команды среды Windows Form. Панель инструментов расположена в левой части главного окна ниже основного меню. Она содержит кнопки, дублирующие наиболее часто используемые команды основного меню, что позволяет повысить эффективность работы в среде Windows Form. Окно формы предназначено для визуального проектирования внешнего интерфейса приложения. Форма имеет те же свойства, что и любое другое окно Windows. По умолчанию поле формы имеет серый цвет ( рис.2 ). Каждое окно имеет полосу заголовка, в которой по умолчанию находится заголовок Form1.Заголовок может быть изменен разработчиком. В примере на рис.2 - Example1 - Microsoft Visual Studio. В правом верхнем углу находятся кнопки свертывания, развертывания и закрытия окна. 4 Рис.2 Окно сведений об объекте Propereties используется для задания свойств компонентов и обработчиков событий. Окно имеет несколько страниц, которые открываются с помощью пиктограмм, расположенных в верхней части окна. Наиболее важными являются вкладка с перечнем свойств Properties (пиктограмма ) и вкладка событий Events ( пиктограмма ). На вкладке Properties находится таблица свойств объекта. В левом столбце таблицы приведен список свойств активного объекта (в том числе и формы), а в правом столбце для каждого свойства указано его значение. Некоторые из этих значений могут быть пустыми. Значения свойств можно задавать или изменять непосредственно в таблице. Например, свойство Text, описывающее заголовок формы, можно изменить на «Пример». Можно задавать свойства компонентов и при написании обработчиков событий, о чем будет сказано ниже. 5 На вкладке Events указаны все события, на которые может реагировать активный объект. Для того чтобы связать выбранное событие с некоторой подпрограммой обработки события, надо дважды щелкнуть левой кнопкой мыши на пустом окне списка. После того откроется окно редактора кодов, в котором будет создана заготовка для подпрограммы обработки выбранного события. 1.2. Разработка проекта Основным элементом проекта является форма, в которой размещаются компоненты самой среды. Форма представляет экземпляр класса Form. Одновременно с формой среда автоматически создает различные файлы. Однако не все файлы, которые создает среда разработки, необходимы для разработки проекта. В таблице 1 приведен перечень файлов, которые непосредственно используются при создании приложений. Таблица 1. Имя файла Описание Файл, объединяющий все элементы одного или нескольких про- Solname.sin Projname.vcproj ектов, в одно общее решение. Файл, содержащий специфическую информацию проекта. Файл, содержащий текст программы, описывающий класс фор- Form1.h Stdafx.h, Stdafx.cpp мы. Файлы предварительной компиляции проекта Name.cpp Файл, содержащий функцию main() проекта Readme.txt Файл, в котором дано описание некоторых файлов проекта Пустая форма, которая появляется на экране после загрузки среды Windows Form, представляет собой окно дизайнера формы. Дизайнер формы позволяет выбирать из предоставляемого средой разработки набора различные компоненты на вкладке ToolBox, вставлять или удалять их, выделять компоненты и т.п ( рис.3). 6 Рис.3 Для размещения компонента на форме необходимо выбрать нужный компонент на вкладке Toolbox, щелкнуть по нему мышью, переместить курсор в нужное место формы и снова щелкнуть мышью. В результате значок выбранного компонента появится в форме. На рис.3 показана форма с размещенной на ней кнопкой управления Button1. Аналогичным образом можно разместить в форме и другие компоненты. Такой компонент, как кнопка позволяет инициировать в разрабатываемом проекте различные действия. Для этого необходимо создать обработчик события – button1_Click(). Простейший способ создания такого типа обработчика события - кликнуть два раза по изображению кнопки Button1. В результате будет 7 осуществлен автоматический переход в текстовый редактор проекта непосредственно на текст автоматически сгенерированного инструментальной средой обработчика: private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e){} Рис.4 В проекте могут быть созданы самые разные обработчики событий. Например, для того, чтобы различные компоненты формы реагировали, на щелчок мыши, необходимо связать это действие с функцией-обработчиком события с программой-обработчиком этого события. Общим способом создания такого обработчика является следующая последовательность действий: для выбранного элемента формы, например Label1, открыть вкладку Properties и кликом по иконке перейти на вкладку «События». На вкладке выбрать 8 MouseClick. и дважды щелкнуть мышью в окошке рядом. В результате в текст проекта будет добавлен обработчик: private: System::Void label1_MouseClick(System::Object^ System::Windows::Forms::MouseEventArgs^ e) { } Если просто кликнуть по изображению sender, Label1 на форме, то будет сгенерирован обработчик: private: System::Void label1_Click(System::Object^ e) { } sender, System::EventArgs^ Результатом всех перечисленных действий буде следующий листинг проекта: #pragma once namespace click { using namespace System; using namespace System::ComponentModel; using namespace System::Collections; using namespace System::Windows::Forms; using namespace System::Data; using namespace System::Drawing; /// <summary> /// Summary for Form1 /// /// WARNING: If you change the name of this class, you will need to change the /// 'Resource File Name' property for the managed resource compiler tool /// associated with all .resx files this class depends on. Otherwise, /// the designers will not be able to interact properly with localized /// resources associated with this form. /// </summary> public ref class Form1 : public System::Windows::Forms::Form { public: Form1(void) { InitializeComponent(); // //TODO: Add the constructor code here // } protected: /// <summary> /// Clean up any resources being used. /// </summary> ~Form1() { if (components) { delete components; } } private: System::Windows::Forms::Label^ label1; private: System::Windows::Forms::Button^ button1; protected: private: /// <summary> 9 /// Required designer variable. /// </summary> System::ComponentModel::Container ^components; #pragma region Windows Form Designer generated code /// <summary> /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// </summary> void InitializeComponent(void) { this->label1 = (gcnew System::Windows::Forms::Label()); this->button1 = (gcnew System::Windows::Forms::Button()); this->SuspendLayout(); // // label1 // this->label1->AutoSize = true; this->label1->Location = System::Drawing::Point(132, 158); this->label1->Name = L"label1"; this->label1->Size = System::Drawing::Size(35, 13); this->label1->TabIndex = 0; this->label1->Text = L"label1"; this->label1->Click += gcnew System::EventHandler(this, &Form1::label1_Click); this->label1->MouseClick += gcnew System::Windows::Forms::MouseEventHandler(this, &Form1::label1_MouseClick); // // button1 // this->button1->Location = System::Drawing::Point(92, 89); this->button1->Name = L"button1"; this->button1->Size = System::Drawing::Size(75, 23); this->button1->TabIndex = 1; this->button1->Text = L"button1"; this->button1->UseVisualStyleBackColor = true; this->button1->Click += gcnew System::EventHandler(this, &Form1::button1_Click); // // Form1 // this->AutoScaleDimensions = System::Drawing::SizeF(6, 13); this->AutoScaleMode = System::Windows::Forms::AutoScaleMode::Font; this->ClientSize = System::Drawing::Size(292, 266); this->Controls->Add(this->button1); this->Controls->Add(this->label1); this->Name = L"Form1"; this->Text = L"Form1"; this->ResumeLayout(false); this->PerformLayout(); } #pragma endregion private: System::Void label1_MouseClick(System::Object^ sender, System::Windows::Forms::MouseEventArgs^ e) { label1->Text="KKKKKKKKKKKKK"; } private: System::Void label1_Click(System::Object^ sender, System::EventArgs^ e) { } private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) { } }; } Листинг 1 10 Модуль содержит описание класса Form1 (public ref class Form1), объектом которого является форма. В самом конце этого класса и размещаются заготовки обработчиков событий. Например функция с заголовочной частью, но с пустым телом: private: System::Void button1_Click(System::Object^ e) {} sender, System::EventArgs^ Внутри созданной заготовки между фигурными скобками записывается код, который будет выполнен как реакция на событие Button1_Click. Например, при клике мышью по Label1 на экран будет выведен текст KKKKKKKKKK, так как в теле обработчика label1_MouseClick() записано присваиване: label1->Text="KKKKKKKKKKKKK"; 1.3 Сохранение, сборка и выполнение проекта Перед последующей обработкой проекта его целесообразно сохранить. Для этого необходимо выполнить команду FileSave All. В результате проект будет сохранен в папке с именем Example1, которое мы присвоили ему в начале создания. Теперь можно выполнить компиляцию и сборку проекта. Эти операции производятся командой BuildBuild Solution. Информация о процессе сборки появляется в окне Output. Если сборка завершилась успешно, то появится сообщение: ========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ========== В противном случае выдается лист с перечнем ошибок, который будет подробно описан позже. В процессе сборки в папке Example1 появятся дополнительные файлы: - example1.obj – содержит объектный код программы; - example1.exe – исполнительный модуль(приложение). Для выполнения приложения нужно выбрать команду DebugStart Debbuging или нажать функциональную клавишу F5. Результат выполнения рассмотренного приложения представлен на Рис. 5 11 Рис.5 2. Примеры использования визуальных компонентов среды Windows Form 2.1. Визуальные компоненты Label, TextBox и Button Начнем изучение визуальных компонент с простого примера вычисления суммы бесконечного ряда вида S 1 X X 2 / 2! X 3 / 3!... X N / N!... с заданной погрешностью E>0. Для решения поставленной задачи на форме нужно разместить два компонента редактирования текстов TextBox. Щелкнем левой клавишей мыши на компоненте TextBox в палитре Toolbox, а затем переместим курсор на форму и снова щелкнем левой клавишей мыши.. В результате на форме появится компонент TextBox. Компонент TextBox. Основное свойство этого компонента - Text. Оно представляет поле, в котором можно вводить и редактировать различные строки базового типа Среды разработки CLR - String^. String – это класс, предназначенный для работы со строками. String ^ - управляемый указатель, описывающий строку символов. Эта строка имеет ряд отличий от типа в ANSY/ISO C char* . В частности строки класса String можно присваивать. Следует отметить, что любые числовые данные, введенные в поле Text, также являются строкой символов. Поэтому необходимо выполнить их преобразование в числовую 12 форму. Наиболее простой способ – применение методов специального класса Convert. Например, для преобразования строкового представления числа к целому и вещественному типам: String ^str; Int number = Convert::ToInt32(str); double num = Convert::Double(str); Размеры компонента TextBox можно менять, используя маркеры выделения (квадра- тики, на левой и правой границах компонент). Для этого нужно установить курсор мыши на одном из маркеров, нажать левую клавишу мыши и, удерживая ее нажатой, изменить положение границы компонента. Можно изменить и расположение курсора на форме. Для этого нужно установить на нем курсор мыши, нажать ее левую клавишу и, удерживая ее нажатой, переместить компонент. Разместим на форме три компонента TextBox, расположив их в левой части формы в столбик. Один будет использован для ввода переменной X, другой - для ввода погрешности E, третий – для вывода результата работы приложения. Компонент Label. Кроме компонентов TextBox окно формы должно содержать пояснительный текст рядом с полем редактирования. Такой текст, расположенный на форме, может быть установлен с помощью компонента Label (метка). Основное свойство метки – Text, котрое тоже имеет тип String ^. На любой компонент Формы и в поле самой Формы можно выводить только строки типа String. Поле Text метки может быть изменен текст в окне Properties или непосредственно в проекте в функции InitializeComponent() в строках, относящихся к соответствующей метке в строке. Например: this->label1->Text = L”Точность”; Введем в форму три метки, расположив их над полями компонентов TextBox и поменяем в поле Text: button1 – на «Введите погрешность Е», button2 – на «Введите Х» , button3 – на «Сумма». 13 Компонент Button. Кнопка Button – простейший и наиболее часто используемый элементов управления. Основное свойство кнопки, сообщающее пользователю ее назначение, это – Text. В названии кнопки можно предусмотреть использование клавиш ускоренного доступа, выделяя для этого один из символов вставкой перед ним амперсанта &. В этом случае пользователь может вместо щелчка на кнопке нажать клавишу Alt cсовместно с клавишей выделенного символа. Добавим на форму три кнопки, которые расположим одна под другой справа от TextBox. Свойству Text первой из них зададим значение «Ввод погрешности», для второй – «Ввод Х», а для третьей – «Сумма». Вид формы приложения для вычисления площади треугольника представлен на рис.6. Рис.6 После создания интерфейса необходимо написать программу, которая выполнит все запланированные действия – позволит осуществить ввод исходных данных и вычислит результат. Каждое из указанных действий осуществляется щелчком правой клавиши мыши на соответствующей кнопке и называется событием. В Windows Form у стандартных событий есть стандартные имена. Они находятся на вкладке событий (Events) окна Properties. Например, событие, соответствующее одному щелчку левой клавишей мыши, имеет имя Click. 14 Реакцией на событие должно быть некоторое действие. Так, реакцией на событие Click, произошедшее при нажатии кнопки «Сумма», должно быть вычисление суммы ряда по введенным ранее исходным данным и отображение его в окне «Сумма». Реализация этих реакций осуществляется с помощью специальных функций, называемых обработчиками событий. Для написания кода обработчика необходимо на вкладке Events окна Properties выбрать событие Click и дважды щелкнуть в ячейке справа от него. В результате открывается окно редактора кода с макетом функции-обработчика события, который для кнопки «Ввод погрешности» имеет следующий вид: private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) {} Имя функции образуется автоматически как комбинация двух частей, разделенных символом ‘_’. Первая часть – имя командной кнопки Button1, а вторая - имя события – Click. Аналогичным образом формируются макеты обработчиков событий для кнопок «Ввод Х» и «Сумма». В окне редактора кода между операторными скобками {} следует написать инструкции языка С++, реализующие обработку событий. Код функций – обработчиков для всех трех кнопок представлен ниже: private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) { eps=Convert::ToDouble(textBox1->Text); } private: System::Void button2_Click(System::Object^ sender, System::EventArgs^ e) { x = Convert::ToDouble(textBox2->Text); } private: System::Void button3_Click(System::Object^ sender, System::EventArgs^ e) { s=1; float a=1; int n=1; while(fabs(a)>=eps) { a=a*x/n; s=s+a; n++; 15 } textBox3->Text=Convert::ToString(s); } При написании программы следует обратить внимание на использование функции ToDouble() класса Convert для перевода данных из типа String^ в тип float перед вычислением суммы и функции ToString() для обратной операции float -> String ^ для отображения результата в окне редактора. Для выполнения созданного приложения нужно выбрать сначала команду меню Build -> Build Solution и выполнить компиляцию и сборку программы. В случае успешного завершения данных операций выполнить команду Debug->Start Debugging. Результат работы приложения показан на Рис.7. Рис.7 Рассмотрим еще несколько часто используемых объектов формы. 2.2. Визуальные компоненты ListBox, ComboBox, RadioButton и GroupBox. Список ListBox. Компонент ListBox предназначен для хранения перечня текстовых элементов, например, списка фамилий. Он дает возможность пользователю выбирать элементы из списка, проводить его сортировку и т.д. 16 Основное свойство компонента ListBox – Items. Оно представляет коллекцию строк и имеет следующие основные методы: - Add(s) – добавление элемента s в конец списка; - Clear() – удаление элементов из списка (очистка списка); - Insert(n,s) – вставка элемента s в список под номером n, n – целое число (первый элемент списка имеет номер 0); - Remove(s) – удаление элемента s из списка; - Count – количество элементов в списке (на единицу больше индекса последней строки списка); - Items[n] – возвращает строку списка с номером n, n – целое число. Формирование списка строк можно выполнить двумя способами. Первый способ предусматривает создание первоначального списка на этапе проектирования приложения. Для этого нужно выделить компонент ListBox на форме, в окне Properties выбрать свойство Items и щелкнуть на кнопке с тремя точками. Откроется диалоговое окно String Collection Editor (редактор списка строк), в котором каждую фамилию следует размещать в отдельной строке, заканчивая набор строки нажатием клавиши <Enter>. Второй способ позволяет организовать список непосредственно в процессе выполнения приложения. Для этого необходимо использовать окно редактирования TextBox и кнопку управления. Каждая фамилия вводится в окно редактирования и нажатием кнопки перемещается в окно ListBox. Соответствующий фрагмент программы, реализующий указанную процедуру, расположен ниже. ListBox1->Items->Add(textBox1->Text); textBox1->Text = “”; textBox1->Focus(); Рассмотрим еще несколько полезных свойств, которые используются при работе со списками. Свойство SelectedIndex дает возможность выбирать нужные строки из списка. Для этого необходимо выделить строку в списке щелчком клавиши мыши. При этом значение свойства SelectedIndex станет равным номеру выбранной строки (напомним, что первая 17 строка имеет номер 0). В случае отсутствия выбора SelectedIndex = -1. Ниже приведен фрагмент программы, иллюстрирующий операцию выбора и вывода на экран строки списка. If (ListBox1->ItemIndex < 0) MessageBox::Show (“Выбор не сделан”); Else MessageBox::Show (“Ваш выбор” + “:” + Convert::ToString( listBox1->Items[listBox1->SelectedIndex])); Свойство Sorted позволяет выполнить сортировку списка строк по алфавиту. Для этого необходимо задать значение этого свойства равным true. Выпадающий список ComboBox. Этот компонент является комбинацией редактируемого поля TextBox и списка ListBox: в форме он представляется в виде редактируемого поля с треугольной кнопкой справа. По умолчанию компонент ComboBox появляется в виде однострокового окна типа TextBox (см. Рис.8, слева). Нажатие на эту кнопку осуществляет вывод данных в виде выпадающего списка типа списка ListBox (Рис.8 , справа). Из этого списка можно выбирать элементы, которые затем можно редактировать в редактируемом поле (аналог поля TextBox). Рис.8 Основные свойства компонента ComboBox – коллекция строк Items, имеющая те же методы, что и в компоненте ListBox, а также свойство Text, содержащее значение поля редактируемого элемента, извлеченного из списка. Компонент RadioButton (радиокнопка). Этот компонент используется как флажокпереключатель для предоставления возможности выбора одного из нескольких вариантов работы. На рис.9 представлен примерный вид 2-х компонентов: Рис.9 Оба представленных на Рис. 9 компонента выключены. Для включения одного из них необходимо щелкнуть левой клавишей мыши на кружочке, в котором сразу же появится точ18 ка. Следует отметить, что в группе компонентов RadioButton может быть включен только один из них, а остальные будут автоматически выключены. Основное свойство компонента – Checked, принимающее дав значения: true – компонент включен, false – выключен. Компонент GroupBox. Компонент представляет контейнер, который используется для разделения переключателей типа RadioButton на различные группы. В результате включение компонента RadioButton одной группы не повлияет на состояние такой же кнопки в другой группе. Таким образом, если необходимо, чтобы кнопки RadioButton обрабатывали несколько непересекающихся ситуаций, то такие кнопки нужно разместить в разных контейнерах GroupBox. Рассмотрим пример создания приложения, иллюстрирующий работу визуальных компонент, описанных выше в разделе 3.2. На Рис.10 представлена основная форма этого приложения. С помощью кнопки «Ввод» в расположенном ниже ее окне вводится список из нескольких фамилий, который затем упорядочивается либо по алфавиту, либо в обратном порядке (кнопка «Сортировка») . Полученные списки отображаются в окне ListBox (слева) и в окне ComboBox (справа ). Нажатие кнопки «Выбор» позволяет перенести отмеченную мышью строку в списке ListBox в окно редактирования. Рис.10 Компонент GroupBox с заголовком «Вид сортировки», включает две радиокнопки, задающими способ сортировки. На Рис.10 выбран способ «По алфавиту», о чем свидетельствует наличие точки в соответствующей радиокнопке). 19 Ниже представлен текст функций-обработчиков событий. Обработчик кнопки «Ввод»: private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) { listBox1->Items->Add(textBox1->Text); comboBox1->Items->Add(textBox1->Text); textBox1->Text=""; textBox1->Focus(); } Обработчик кнопки «Сортировка»: private: System::Void button2_Click(System::Object^ sender, System::EventArgs^ e) { int i,n,n1; String ^s,^s1; if(radioButton1->Checked) { listBox1->Sorted=true; comboBox1->Sorted=true; } if(radioButton2->Checked) { listBox1->Sorted=true; listBox1->Sorted=false; comboBox1->Sorted=true; comboBox1->Sorted=false; n=listBox1->Items->Count; for(i=0;i<n;i++) { s=listBox1->Items[0]->ToString(); listBox1->Items->Remove(listBox1->Items[0]); listBox1->Items->Insert(n-1-i,s); s1=comboBox1->Items[0]->ToString(); comboBox1->Items->Remove(comboBox1->Items[0]); comboBox1->Items->Insert(n-i-1,s1); } } } Обработчик кнопки «Выбор»: private: System::Void button3_Click(System::Object^ sender, System::EventArgs^ e) { if(listBox1->SelectedIndex<0) MessageBox::Show("Выбор не сделан"); else { MessageBox::Show("Ваш выбор"+":"+ Convert::ToString( listBox1->Items[listBox1->SelectedIndex])); textBox1->Text=Convert::ToString( listBox1->Items[listBox1->SelectedIndex]); } } Обработчик кнопки «Выход»: private: System::Void button4_Click(System::Object^ sender, System::EventArgs^ e) 20 { Close(); } 2.3. Матрица ячеек DataGridView Компонент DataGridView представляет собой таблицу, содержащую строки. Этот компонент используется для отображения таблиц и матриц. В зависимости от значений свойств компонент DataGridView может использоваться как для ввода, так и для вывода информации. Таблица имеет две полосы прокрутки, что позволяет выводить большие массивы данных. Заголовки столбцов таблицы фиксируются и их положение не меняется при вертикальной прокрутке. Компонент DataGridView находится в палитре Toolbox в разделе Data. После размещения его на форме необходимо сначала задать структуру будущей таблицы. Для этого необходимо щелкнуть мышью стрелку в верхней части компонента для входа в меню проектирования компонента (Рис.11) Рис.11 Следует выбрать пункт Add Column и в открывшимся диалоговом окне задать в окошке Header Text заголовки столбцов таблицы. Идентификаторы столбцов (name) как пра21 вило оставляют без изменений. В примере введены следующие заголовки: Фамилия, Экзамен1, Экзамен2, Экзамен 3. Пункт меню Edit Columns позволяет перейти к редактору столбцов, показанному на Рис.12. Рис.12 Чаще всего изменяют ширину столбца Width. Для редактирования нужного столбца необходимо выбрать его номер в окне Selected Columns, перейти в окно Column Properties, выбрать соответствующую опцию и внести изменения. Пункты менюEnable Adding, Enable Editing и Enable Deleting разрешают (наличие «галочки») или запрещают производить добавление, редактирование и удаление столбцов таблицы. Целесообразно также изменить имя компонента в окне свойств, например, вместо dataGridView1 использовать более короткое имя dGV1. Это облегчит написание кодов обработчиков событий. Количество строк таблицы задается на этапе выполнения приложения либо в конструкторе формы, либо в одном из обработчиков событий с помощью следующей команды: dGV1->RowCount=5; 22 Также в программе можно добавлять или удалять новые столбцы и строки. Для добавления используется команда dGV1->Columns->Add(st1,st2); где st1, st2 – строки типа String ^, задающие идетификатор и заголовок столбца, соответственно. Удаление столбца производится инструкцией dGV1->Columns->Remove(st1); Добавление строки можно осуществить командой dGV1->Rows->Add(); а удаление с помощью инструкции dGV1->Rows->RemoveAt(n); где n – идекс удаляемой строки. Информацию в ячейки таблицы можно заносить двумя способами. Первый наиболее простой способ – непосредственное заполнение таблицы вручную после запуска приложения на выполнение. Второй – заполнение таблицы программным путем. В этом случае для занесения информации в ячейку таблицы с индексами i,j используется следующая инструкция: dGV1->Rows[i]->Cells[j]->Value=inf; где inf – вводимое числовое или строковое значение. Следует отметить, что значения индексов i,j начинаются с нуля. Аналогичной инструкцией производится считывание информации из таблицы с учетом ее типа. Ниже приведены примера чтения из таблицы, соответственно, вещественных и строковых данных: double B = Convert::ToDouble(dGV1->Rows[i]->Cells[j]->Value); String ^ st = Convert::ToString(dGV1->Rows[i]->Cells[j]->Value). Рассмотрим пример работы с матрицей ячеек, в котором реализуется обработка сводной экзаменационной ведомости. Таблица содержит пять столбцов со следующими заголовками: «Фамилия», «Экзамен1», «Экзамен2», «Экзамен3», «Экзамен4». В новую таблицу необходимо перенести данные о студентах , средний балл которых не менее 4. 23 1. В форме, которой дано название «Успеваемость», формируем две таблицы: исходную и результатов отбора. (Рис.13). Кнопка «Ввод» предназначена для задания количества студентов в ведомости. Кнопка «Выбор» инициирует отбор студентов из исходной таблицы по заданному критерию и перенос сведений о них в таблицу результатов отбора. Кнопка «Выход» обеспечивает прекращение работы. Рис.13 3. Напишем обработчики для обеих кнопок. Текст этих обработчиков представлен ниже. Обработчик кнопки «Ввод»: private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) { n = Convert::ToInt32(textBox1->Text); dataGridView1->RowCount= n; dataGridView2->RowCount= n; } Целая переменная n должна быть описана как член класса Form1. Обработчик кнопки «Выбор»: private: System::Void button3_Click(System::Object^ sender, System::EventArgs^ e) { float sr; int k=0; int j; float *a = (float*)calloc(n,sizeof(float)); for(int i=0;i<n;i++) 24 { sr=0; for( j=1;j<5;j++) { a[j-1]=Convert::ToDouble(dataGridView1->Rows[i]->Cells[j]->Value); sr=sr+a[j-1]; } sr=sr/4; if(sr>=4) { for( j=0;j<5;j++) dataGridView2->Rows[k]->Cells[j]->Value=dataGridView1->Rows[i]->Cells[j]>Value; dataGridView2->Rows[k]->Cells[5]->Value=sr; k=k+1; } } } Обработчик кнопки «Выход»: private: System::Void button2_Click(System::Object^ sender, System::EventArgs^ e) { Close(); } На рис.14 представлен пример выполнения программы для заданного списка студентов. Рис.14 3. Материалы для самостоятельной проработки 3.1. Краткое введение в объектно-ориентированное программирование (ООП) 25 Концепция ООП. С++ CLR, классы, методы, свойства, события, обработчики событий. Windows form application, структура класса form, компоненты среды визуального программирования. Объект (object, instance) – конкретная реализация абстрактного типа (класса), обладающая характеристиками состояния (state), поведения (behavior), и индивидуальности (identity). Состояние – один из возможных вариантов условий существования объектов. Поведение – описание объекта в терминах изменения его состояния и передачи сообщений в процессе воздействия или под действием других объектов. Индивидуальность – сущность объекта, отличающая его от всех других объектов. На основе возможных состояний определяется жизненный цикл объекта. Для описания поведения объектов используют модель состояний Мура, которая состоит: 1. Из множества состояний. Каждое состояние представляет стадию в жизненном цикле типичного экземпляра объекта. 2. Из множества событий. Каждое событие представляет инцидент или указание на то, что происходит эволюционирование. 3. Из правил перехода. Правило перехода определяет, какое новое состояние достигается, когда с объектом в данном состоянии происходит некоторое событие. 4. Из действий. Действие – деятельность или операция, которые должны быть выполнены над объектом, чтобы он достиг состояния. Как правило, одно действие связывается с каждым состоянием. Все объекты, как правило, относят к следующим категориям: реальные объекты – абстракции фактического существования некоторых предметов в физическом мире (дом, стул и т.д.); роли – абстракции цели или назначения человека, части оборудования или организации (студент, преподаватель, сливной бачок, дипломат, избиратель); инциденты – абстракции чего-то происшедшего или случившегося (наводнение, скачок напряжения, выборы); взаимодействия – объекты, получаемые из отношений между другими объектами (перекресток, порог, договор, взятка, долг); спецификации – используются для представления правил, стандартов или критериев качества (правила дорожного движения, распорядок дня, расписание). Отношения между объектами: 1. Отношение использования (старшинства). 26 Каждый объект, включенный в отношения использования, может играть следующие три роли: Воздействие – объект может воздействовать на другие объекты, но сам никогда не подвержен воздействию других объектов (в определенном смысле соответствует понятию активный объект). Исполнение – объект в этом случае может только подвергаться управлению со стороны других объектов, но сам никогда не выступает в роли воздействующего объекта (пассивный объект). Посредничество – такой объект может выступать как в роли воздействующего, так и в роли исполнителя (как правило, объект-посредник создается для выполнения операций в интересах какого-либо активного объекта или другого посредника). 2. Отношение включения. Между отношением включения и использования существует взаимная связь. Включение одних объектов в другие предпочтительнее в том плане, что при этом уменьшается число объектов, с которыми вы непосредственно работаете. С другой стороны, использование одних объектов другими имеет преимущество: не возникает сильной зависимости между объектами, как в случае включения. Классы. Класс – это такая абстракция множества предметов реального мира, когда: все предметы в этом множестве – объекты (экземпляры) имеют одни и те же характеристики; все экземпляры подчинены и согласовываются с одним и тем же набором правил и линий поведения. Отношения между классами: наследование; использование; наполнение; метаклассы. В классах определяются поля (характеристики объекта или класса) и методы (функ- ции объекта или класса, которые используются для работы с полями). Свойство объекта – это способ доступа к его внутреннему состоянию, имитирующий переменную некоторого типа. Обращение к свойству объекта выглядит так же, как и обращение к полю, но в действительности приводит к вызову специальных функций свойства get (при получении значения) или set (при задании значения). 27 События позволяют классу или объекту уведомлять другие классы или объекты о возникновении каких-либо ситуаций. В классах создаются специальные методы для обработки событий – обработчики событий. Для того, чтобы метод вызывался при возникновении события, на него надо этот метод подписать. Домены. Домен – отдельный реальный, гипотетический или абстрактный мир, населенный отчетливым набором объектов, которые ведут себя в соответствии с характерными для домена правилами и линиями поведения. Каждый домен образует отдельное и связное единое целое. Что касается классов, то: Класс определяется в одном единственном домене. Классы в домене требуют существования других классов в том же домене. Классы в одном домене не требуют существования классов в другом домене. При создании проекта под Windows form application создается заголовочный файл, со- держащий заготовку класса Form1. Структура класса Form1: public ref class Form1 : public Form { public: // Конструктор Form1(void) { … } protected: // Деструктор ~Form1() { … } private: // Атрибуты класса … // Метод, инициализирующий компоненты void InitializeComponent(void) { … } // Обработчики событий System::Void exit_Click(System::Object^ sender, Sytem::EventArgs^ e) { Close(); 28 } … }; Класс Form1 может содержать различные компоненты, которые делятся на визуализируемые и невизуализируемые. К визуализируемым относятся такие компоненты как Button, TextBox, EditgridView. Каждый компонент обрабатывает события, на которые можно подписать свои обработчики событий, которые размещаются в том классе формы, которая включает этот компонент. Также каждый компонент обладает рядом свойств, меняя которые мы можем изменить внешний вид компонента: сделать его невидимым или неактивным, поменять содержимое компонента. 3.2 Динамические структуры данных Односвязные списки – стек и очередь. Реализация стека и очереди. Динамическая структура данных состоит из узлов, включающих в себя информационную часть (данное, ради которого создается узел) и ссылочную часть (указатели на себе подобных). Динамические структуры данных классифицируются на списки, деревья и графы. Списки бывают односвязными и двусвязными. Узел односвязных списков содержит один указатель на следующий узел. Односвязные списки подразделяются на стеки, очереди, деки, циклические списки и списки прямого доступа. Двусвязные списки реализуются только как списки прямого доступа. На языке С узел односвязного списка можно задать структурой, содержащей, например, поле данных info и поле типа указатель на Node next: struct Node { double info; Node* next; }; Стек Логика стека - первым пришел, последним ушел. Для работы со стеком нам нужен только один указатель на первый узел (вершина стека): struct Stack { Node* top; }; Вся работа выполняется только с этим элементом. Мы добавляем новые элементы в вершину стека и удаляем элементы из вершины. Можно выделить следующие операции над стеком: 29 1. Инициализация стека: Stack initStack() { Stack S = {0}; return S; } 2. Добавление элемента в стек: void push(Stack &S, double inf) { Node* work = new Node; work->info = inf; work->next = S.top; S.top = work; } 3. Удаление элемента из стека: bool pop(double& inf, Stack& S) { if(!S.top) return false; Node* work = S.top; S.top = S.top->next; inf = work->info; delete work; return true; } 4. Очистка стека: void clear(Stack& S) { while(S.top) { Node* work = S.top; S.top = S.top->next; delete work; } } 5. Получение данного из вершины стека: bool get(double& inf, const Stack& S) { if(!S.top) return false; inf = S.top->info; return true; } 30 6. Проверка состояния стека: bool empty(const Stack& S) { return !S.top; } Очередь Для работы с очередью необходимы два указателя: на первый (голову) и последний (хвост) узлы. Добавление нового элемента списка происходит в «хвост» списка, а удаление – из «головы». struct Queue { Node* top, * bottom; }; Для работы с очередью можно выделить такие же операции, как и над стеком. Будет отличаться только реализация. 3.3 Итераторы Итераторы - односвязные списки прямого доступа. Реализация Итератора. Обратная польская запись - алгоритм Дейкстры. Списки можно рассматривать не только как хранилище данных, но и как последовательность данных одного типа. Для организации доступа к таким данным используют указатели на узлы списка и при необходимости перемещают их по списку. В принципе, для односвязного списка можно организовать большинство алгоритмов обработки массива: поиск, удаление, вставка, группировка и сортировка. Удобство списков заключается в том, что нет необходимости заранее выделять память нужного размера и операции по вставке и удалению какого-либо узла, менее затратные. Мы заранее не знаем, со сколькими узлами нам потребуется одновременно работать, то есть сколько потребуется указателей. Это зависит от алгоритма обработки. Поэтому вспомогательный указатель лучше выделить в отдельную структуру и создавать переменные структуры по мере необходимости. Такую структуру именуют Iterator. struct List { Node* top, * bottom; }; struct Iterator { Node* current; }; 31 Для работы со списком прямого доступа с использованием итераторов можно выделить следующие операции: 1. Инициализация списка: List initList() { List L = {0, 0}; return L; } 2. Добавление элемента в голову списка: void push(List &L, double inf) { Node *work = new Node; work->info = inf; work->next = L.top; if(!L.top) L.bottom = work; L.top = work; } 3. Удаление элемента из головы списка: bool pop(double& inf, List& L) { if(!L.top) return false; Node* work = L.top; L.top = L.top->next; inf = work->info; delete work; return true; } 4. Очистка списка: void clear(List& L) { while(L.top) { Node* work = L.top; L.top = L.top->next; delete work; } L.bottom = 0; } 5. Получение данного из головы списка: bool get(double& inf, const List& L) 32 { return L.top ? (inf = L.top->info, true) : false; } 6. Проверка состояния списка: bool empty(const List& L) { return !L.top; } 7. Установка итератора в голову списка: Iterator begin(List& L) { Iterator I = {L.top}; return I; } 8. Установка итератора в хвост списка: Iterator end(List& L) { Iterator I= {L.bottom}; return I; } 9. Перемещение итератора на следующий узел: bool next(Iterator& I) { return I.current ? (I.current = I.current->next, true) : false; } 10. Добавление узла после узла, на который установлен итератор: bool add(List &L, Iterator &I, double inf) { if(!I.current && L.top) return false; Node* work = new Node; work->info = inf; if(!L.top) { work->next = 0; L.top = L.bottom = I.current = work; } else if(!I.current->next) { work->next = 0; L.bottom = I.current->next = work; } else { work->next = I.current->next; 33 I.current->next = work; } return true; } 11. Удаление узла, расположенного после узла, на который установлен итератор: bool del(double& inf, List &L, const Iterator &I) { if(!L.top || !I.current || !I.current->next) return false; Node* work = I.current->next; inf = work->info; I.current->next = work->next; if(!work->next) { L.bottom = I.current; } delete work; return true; } 12. Получение информационной части узла, на который установлен итератор: double get(const Iterator &I) { return I.current ? I.current->info : 0.; } 13. Получение информационной части узла следующего за тем, на который установлен итератор; double getNext(const Iterator &I) { return I.current && I.current->next ? I.current->next->info : 0.; } 14. Установлен ли итератор на список; bool check(const Iterator &I) { return I.current; } 15. Сравнение итераторов; bool operator ==(const Iterator& I1, const Iterator& I2) { return I1.current == I2.current; } bool operator !=(const Iterator& I1, const Iterator& I2) { 34 return I1.current != I2.current; } 16. Замена информационной части узла, на который установлен итератор: bool set(Iterator& I, double inf) { if(!I.current) return false; I.current->info = inf; return true; } 17. Замена информационной части узла, следующего за тем, на который установлен итератор: bool setNext(Iterator& I, double inf) { if(!I.current || !I.current->next) return false; I.current->next->info = inf; return true; } Обратная польская запись Для вычисления выражений используется обратная польская запись или обратная польская нотация. Она была разработана австралийским философом и специалистом в области теории вычислительных машин Чарльзом Хэмблином в середине 1950-х на основе польской нотации, которая была предложена в 1920 году польским математиком Яном Лукасевичем. Отличительной особенностью обратной польской нотации является то, что операнды расположены перед знаком операции. В общем виде запись выглядит следующим образом: Выражение состоит из последовательности операндов и операторов. Выражение читается слева направо. Когда в выражении встречается оператор, выполняется соответствующая операция над двумя или одним (в зависимости от арности оператора) последними встретившимися перед ним операндами в порядке их записи. Результат операции заменяет в выражении последовательность её операндов и оператора, после чего выражение вычисляется дальше по тому же правилу. Результатом вычисления выражения становится результат последней вычисленной операции. Эдсгер Дейкстра предложил алгоритм для преобразования выражений из инфиксной нотации в обратную польскую нотацию. Алгоритм: 35 1. Пока в выражении есть лексемы: a. Берем очередную лексему; b. Если лексема операнд, добавляем в выходную строку; c. Если лексема является функцией, помещаем его в стек; d. Если – оператор и приоритет его меньше, либо равен приоритету оператора, находящегося на вершине стека, выталкиваем верхние операторы из стека в выходную строку до тех пор, пока не встретится оператор с меньшим приоритетом, или открывающая круглая скобка, или стек станет пустым и помещаем его в стек; e. Если оператор и приоритет его больше приоритета оператора на вершине стека, то просто помещаем его в стек; f. Если лексема является открывающей скобкой, помещаем ее в стек; g. Если символ является закрывающей скобкой, то до тех пор, пока верхним элементом стека не станет открывающая скобка, выталкиваем элементы из стека в выходную строку. При этом открывающая скобка удаляется из стека, но в выходную строку не добавляется. Если после этого на вершине стека оказывается функция, выталкиваем ее в выходную строку. 2. Когда входное выражение закончилось, выталкиваем все символы из стека в выходную строку. Мы получили выражение в обратной польской записи. 3.4. Примеры с использованием списка прямого доступа и итераторов. Рассмотрим несколько примеров с использование итераторов. Вывод списка: void printList(const List& L) { for(Iterator I = begin(L); check(I); next(I)) printf(“%7.3lf ”, get(I)); printf(“\n”); } Добавление элементов в список так, чтобы они располагались в порядке возрастания: int insertSort(List &L, double inf) { int Pos = 0; Iterator I = begin(L); if(!check(I) || get(I) >= inf) push(L, inf); else 36 { for(Pos = 1; I != end(L) && getNext(I) < inf; next(I), Pos++); add(L, I, inf); } return Pos; } Удаление повторяющихся элементов: int delElem(List &L) { int Count = 0; for(Iterator I1 = begin(L); I1 != end(L); next(I)) for(Iterator I2 = I1; I2 != end(L); ) if(get(I1) == getNext(I2)) { double temp; del(temp, L, I2); Count++; } else next(I2); return Count; } Сортировка элементов списка: void sortList(List &L) { for(Iterator I1 = begin(L); I1 != end(L); next(I)) { double temp1 = get(I1); for(Iterator I2 = I1; next(I2), check(I2); ) if(temp1 > get(I2)) { temp1 = get(I2); set(I2, get(I1)); set(I1, temp1); } } } 3.5. Графика. Пример построения и вывода на экран графика функции. Статистическая обработка данных и графическое представление результатов обработки в виде гистограммы и круговой диаграммы. 37 Компоненты графики. На любом визуализируемом компоненте можно рисовать, но лучше использовать стандартный компонент pictureBox. Для рисования на компонентах используется класс Graphics, который содержит методы для рисования. Все методы рисования можно разделить на две группы: те, которые используют ручку Pen, и те, которые используют кисточку SolidBrush. Те методы, которые используют Pen, начинаются со слова Draw, а те, которые SolidBrush со слова Fill. Методы, использующие Pen: Рисование линии: void DrawLine(Pen^ pen, PointF p1, PointF p2). Рисование ломаной линии: void DrawLines(Pen^ pen, array<PointF>^ points). Рисование замкнутой области: void DrawPolygon(Pen^ pen, array<PointF>^ points). Рисование прямоугольника: void DrawRectangle(Pen^ pen, Rectangle rect). Рисование эллипса void DrawEllipse(Pen^ pen, RectangleF rect). Рисование дуги эллипса void DrawArc(Pen^ pen, RectangleF rect, float startAngle, float sweepAngle). Рисование сектора эллипса void DrawPie(Pen^ pen, RectangleF rect, float startAngle, float sweepAngle). Методы, использующие SolidBrush: Рисование замкнутой области: void FillPolygon(Brush^ brush, array<PointF>^ points). Рисование прямоугольника void FillRectangle(Brush^ brush, Rectangle rect). Рисование эллипса void FillEllipse(Brush^ brush, RectangleF rect). Рисование сектора эллипса void FillPie(Brush^ brush, RectangleF rect, float startAngle, float sweepAngle). Для того, чтобы что-нибудь нарисовать, надо создать объект класса Graphics и необ- ходимые инструменты: ручки и кисточки. 38 Объект Graphics необходимо связать c полем для рисования. Если просто рисовать на поле компонента, то это нигде не будет сохраняться, и при свертывании окна или при перекрытии компонента рисунок будет потерян. Поэтому необходимо выделять память под поле Image компонента и связывать объект класса Grapics c ним. Поскольку в этом случае мы будем рисовать на Image, то необходимо периодически обновлять поле компонента. Для этого используется метод Invalidate. График функции. Для вывода графика функции на компонент нам необходимо задать функцию, интервал изменения аргумента функции, размеры поля компонента, для вывода графика функции. Если предполагается воспроизводить график на PictureBox, то это будет его ширина и высота. Для того, чтобы разместить график функции на поле выбранного компонента, необходимо выполнить преобразование координат, в которых задана функция ( такие координаты принято называть мировыми ), в экранные координаты или, точнее, в координаты выбранной для постороения графика компоненты Среды. Аргумент функции изменяется в задавемом пользователем интервале [a,b]. На заданном интервале функия изменяется в пределах от Ymin до Ymax. Интервал [a,b] надо сопоставить с шириной окна, в поле которого буде выводится график функции, а интервал [Ymin,Ymax] сопоставить с высотой окна. Для построения графика функции удобно использовать класс Rectangle и его свойства Width и Height, а затем связать объект Rectangle Rec с pictureBox1->ClientRectangle. Удобно сначала сформировать массив точек PointF, а затем вывести изображение графика функции на экран, используя метод DrawLines(). Для нахождения максимального и минимального значения функции на заданном интервале a,b можно использовать функции maxY() и minY(), возвращающие значение типа double. double maxY(double A, double B, double h, double (*Pf)(double)) { double Ymax = Pf(A); for(double X = A + h; X < B + h/2; X += h) if(Ymax < Pf(X)) Ymax = Pf(X); return Ymax; } double minY(double A, double B, double h, double (*Pf)(double)) { double Ymin = Pf(A); 39 for(double X = A + h; X < B + h/2; X += h) if(Ymin > Pf(X)) Ymin = Pf(X); return Ymin; } Для формирования массива точек графика в экранных координатах можно использовать функцию DrawGraph(), которая возвращает указатель на массив типа Point. Для выполнения преобразования координат необходимо найти масштабные коэффициенты по осям X и Y: масштаб по оси X – Mx определяется как отношение количества точек экрана по оси Х к интервалу изменения аргумента выводимой на экран функции; масштаб по оси Y – mY определяется как отношение размера экрана по оси Y (Rec.Height - Rec.Y) к (Ymax – Ymin). array<Point>^ DrawGraph(Rectangle Rec, double A, double B, double (*Pf)(double)) { int Count = Rec.Width - Rec.X + 1; double h = (B - A)/(Count - 1); double Ymax = maxY(A, B, h, Pf); double Ymin = minY(A, B, h, Pf); double Mx = 1/h; double My = (Rec.Height - Rec.Y)/(Ymax - Ymin); array<Point>^ mas = gcnew array<Point>(Count); double X = A; for(int i = 0; i < Count; i++, X += h) mas[i] = Point(Rec.X + Mx*(X - A), Rec.Height - My*(Pf(X) - Ymin)); return mas; } Вызов функции DrawGraph() осуществляется из gr->DrawLines(). В примере показана передача функции DrawGraph() функции Sin(х), заданной на интервале [0., 12.56]. private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) { Pen ^pen = gcnew Pen(Color::Black); Graphics^ gr = pBox->CreateGraphics(); gr->Clear(Color::White); gr->DrawLines(pen, DrawGraph(pictureBox1->ClientRectangle, 0., 12.56, Math::Sin)); } 40 Для построения гистограммы можно использовать массив целых чисел. Так же, как и при построении графика, надо вычислить масштаб по X и по Y: Mx = W/Length, где Length – размерность массива; My = H/ElemMax, Где ElemMax – значение максимального элемента массива. Для построения круговой гистограммы можно использовать тот же массив. Сумма элементов массива соответствует полному кругу 2π. Необходимо определить угол, приходящийся на каждый элемент массива, разделив значение элемента массива на сумму элементов массива и умножив на 2π. 3.6. Реализация «движения» по однородному и сложному фону. Для организации перемещения фигуры по однородному фону можно выделить следующие действия: 1. Восстановление фона ( заливка области, занимаемой фигурой, цветом фона ); 2. Пересчет координат – сдвиг, поворот и масштабирование на плоскости; 3. Рисование фигуры с новыми координатами; 4. Задержка по времени; 5. Переход по условию на пункт 1. Для организации движения фигуры по сложному фону необходимо перерисовывать весь фон или сохранять часть фона (под фигурой) в буфере, а затем после того, как фигура перемещается на новую позицию, восстанавливать его. В этом случае последовательность действий будет следующая: 1. Восстановление части фона из буфера; 2. Пересчет координат – сдвиг, поворот и масштабирование на плоскости; 3. Запоминание части фона (под фигурой) в буфере; 4. Рисование фигуры; 5. Задержка по времени; 6. Переход по условию на пункт 1. Для реализации данной последовательности дей ствий необходимо использовать класс Bitmap. Напишем функции для сохранения и восстановления части фона. Сохранение: Bitmap^ pictureToBitmap(PictureBox^ pBox, Rectangle Section) { Bitmap^ Bmp = gcnew Bitmap(Section.Width, Section.Height); 41 Graphics^ Gt = Graphics::FromImage(Bmp); Gt->DrawImage(pBox->Image, 0, 0, Section, GraphicsUnit::Pixel); return Bmp; } Восстановление: void bitmapToPicture(PictureBox^ pBox, Point P, Bitmap^ Bmp) { Graphics^ G = Graphics::FromImage(pBox->Image); G->CompositingMode = Drawing2D::CompositingMode::SourceCopy; G->DrawImage(Bmp, P); pBox->Invalidate(); } Последовательность действий для организации движения можно реализовать в обработчике события на тик компонента таймер: System::Void timer_Tick(System::Object^ sender, System::EventArgs^ e) { bitmapToPicture(pBox, Point(X, Y), B); moveFig(fig, X, Y); Rectangle Section(X, Y, W, H); B = pictureToBitmap(pBox, Section); drawFig(pBox, fig, X, Y); } 3.7. Сложное движение каркасных моделей. Любое сложное движений в трехмерном пространстве можно представить в виде последовательных переноса, поворота и масштабирования ( приближение или отдаление от объекта ). А для получения изображения оъекта на экране применяется преобразование проецирования. Каркасную модель можно представить в виде набора точек и матрицы связанности. Для выполнения поворота и масштабирования фигуры или каркасной модели необходима точка, относительно которой будут выполняться эти действия (базовая точка). Удобно задать координаты вершин фигуры относительно этой точки. А при рисовании выполнять смещение на базовую точку. Перенос фигуры сведётся к смещению базовой точки. Все операции по трехмерному преобразованию фигуры можно свести к умножению вектора на матрицу. Матрицы для поворота запишутся так: 1. Относительно оси X: 42 0 0 1 0 cos( ) sin( ) 0 sin( ) cos( ) 0 0 0 0 0 0 1 2. Относительно оси Y: cos( ) 0 sin( ) 0 0 sin( ) 0 1 0 0 0 cos( ) 0 0 0 1 3. Относительно оси Z: cos( ) sin( ) sin( ) cos( ) 0 0 0 0 0 0 0 0 1 0 0 1 Матрица переноса: 1 0 0 X 0 0 1 0 0 1 Y Z 0 0 0 1 Матрица масштабирования: k 0 0 0 0 0 0 k 0 0 0 k 0 0 0 1 где k – коэффициент масштабирования. Матрица для плоскопараллельного проецирования: 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 Структура данных для хранения фигуры запишется так: value struct Figure { Point3D Base; array<Point3D, 1> ^Vertices; array<int, 2> ^Edges; int Count; }; // базовая точка // массив вершин // матрица связанности // количество вершин 43 где Point3D: value struct Point3D { float X; float Y; float Z; Point3D(float x, float y, float z) {X = x; Y = y; Z = z;} }; Функция рисования фигуры: void DrawFig(PictureBox^ pBox, Figure %fig) { Pen^ P = gcnew Pen(Color::Red); Graphics^ G = Graphics::FromImage(pBox->Image); for(int i = 0; i < fig.Count - 1; ++i) for(int j = i + 1; j < fig.Count; ++j) if(fig.Edges[i, j]) { G->DrawLine(P, fig.Base.X + fig.Vertices[i].X, fig.Base.Y + fig.Vertices[i].Y, fig.Base.X + fig.Vertices[j].X, fig.Base.Y + fig.Vertices[j].Y); } pBox->Invalidate(); } Структура данных для матрицы поворота: value struct Matrix3D { array<float, 2> ^M; }; Функция инициализации матрицы поворота относительно оси Х: Matrix3D Mx(float a) { array<float, 2> ^M = { {1, 0, 0, 0}, {0, Math::Cos(a), -Math::Sin(a), 0}, {0, Math::Sin(a), Math::Cos(a), 0}, {0, 0, 0, 1}}; Matrix3D tmp = {M}; return tmp; } Функции инициализации матриц поворота относительно остальных осей и матрицы масштабирования записываются аналогично. 44 Запишем функцию умножения вектора на матрицу: Point3D Multiply(Point3D Pt, Matrix3D Ma) { Point3D p; p.X = Pt.X * Ma.M[0, 0] + Pt.Y * Ma.M[0, 1] + Pt.Z * Ma.M[0, 2]; p.Y = Pt.X * Ma.M[1, 0] + Pt.Y * Ma.M[1, 1] + Pt.Z * Ma.M[1, 2]; p.Z = Pt.X * Ma.M[2, 0] + Pt.Y * Ma.M[2, 1] + Pt.Z * Ma.M[2, 2]; return p; } Выполнение операции поворота фигуры можно выделить в самостоятельную функцию: void Rotate(Figure %fig, float a, float b, float c) { if(a != 0.0) { Matrix3D Mxa = Mx(a); for(int i = 0; i < fig.Count; ++i) fig.Vertices[i] = Multiply(fig.Vertices[i], Mxa); } if(b != 0.0) { Matrix3D Myb = My(b); for(int i = 0; i < fig.Count; ++i) fig.Vertices[i] = Multiply(fig.Vertices[i], Myb); } if(c != 0.0) { Matrix3D Mzc = Mz(c); for(int i = 0; i < fig.Count; ++i) fig.Vertices[i] = Multiply(fig.Vertices[i], Mzc); } } где a, b и c – углы поворота относительно осей X, Y и Z соответственно. 4. Типовые задания для лабораторных работ 1. Разработка программы «Калькулятор» Программа должна выполнять следующие операции: - ввод чисел с помощью цифровых кнопок (объект Button) и их отображение в окне TextBox; - многократное выполнение основных арифметических действий над числами (сложение, вычитание, умножение и деление) и отображение результата; - очистка окна редактирования перед выполнением новых вычислений. 2. Разработка программы вычисления определенного интеграла с заданной погрешностью. 45 Программа должна выполнять следующие действия: - ввод исходных данных (нижний и верхний пределы интегрирования, погрешность) в соответствующих окнах TextBox; - выбор метода интегрирования (метод прямоугольников, трапеций, Симпсона) с помощью объектов CroupBox и RadioButton; - выбор подинтегральной функции с помощью объекта ComboBox. 3. Программа упорядочения сводной экзаменационной ведомости в соответствии с заданным критерием. Экзаменационная ведомость представляется в виде таблицы (объект DataGridView), столбцы которой имеют следующие наименования: фамилия, группа. экзамен1, экзамен2, экзамен3. Программа должна выполнять следующие действия: - ввод данных в таблицу вручную и из файла; - сортировка данных по алфавиту или в порядке возрастания среднего бала в зависимости от выбора пункта меню; - коррекция таблицы вручную. Для выполнения указанных действий организовать меню. 4.Разработка программы выполнения операций над односвязным списком общего вида. Программа должна выполнять следующие операции: - создание с информационным полем, заданным преподавателем; - добавление, поиск, вставка и удаление элементов; - очистка списка. Отображение информационных полей списка производится в объекте ListBox. Для выполнения указанных действий организовать меню. 5. Разработка программы построения секторной диаграммы и гистограммы. Построение диаграммы (гистограммы) производится в объекте PictureBox. Программа должна выполнять следующие действия: - ввод процентного содержания каждого сектора (столбца), кроме последнего, процентное содержание которого должно определяться автоматически, т.к. сумма всех процентных данных должна быть равна 100%; - построение диаграммы или гистограммы в зависимости от выбора пункта меню; - очистка содержимого объекта PictureBox. Для выполнения указанных действий организовать меню. 6. Разработка программы построения графика функции на координатной сетке. Построение графика производится в объекте PictureBox. Программа должна выполнять следующие действия: 46 - выбор функции с помощью объекта ComboBox; - осей координат и координатной сетки; - указание значений, соответствующих линиям сетки на осях Х и Y; - построение графика выбранной функции. Для выполнения указанных действий организовать меню. 7. Разработка программы преобразования заданной фигуры на плоскости. Программа должна выполнять следующие действия: - рисование раскрашенной фигуры, заданной преподавателем; - выполнение стандартных преобразований фигуры (параллельный перенос, поворот на заданный угол, вращение). Для выполнения указанных действий организовать меню. 8. Разработка программы моделирования одного из трех видов движения: - поступательное движение раскрашенного круга по квадратной рамке; - вращение раскрашенного круга по или против часовой стрелке по окружности заданного радиуса; - периодическое сжатие и растяжение раскрашенного квадрата относительно его центра. Для выполнения указанных действий организовать меню. 9. Разработка программы моделирования движения по сложному фону. Программа описывает движение раскрашенной фигуры по графику заданной функции. Необходимо предусмотреть кнопку «Пуск/стоп» для запуска и остановки движения. 10. Разработка программы преобразования куба в пространстве с использованием каркасной модели. Программа должна выполнять следующие действия: - построение куба; - выполнение одного из преобразований (параллельный перенос, поворот, масштабирование, проецирование) Для выполнения указанных действий организовать меню. Данная задача предлагается для «сильных» студентов по желанию. 5. Контрольные вопросы 1. Для чего предназначено окно формы? 2. Какую информацию содержит окно Properties? 3. Какие основные файлы входят в состав проекта? 4. Как создать шаблон обработчика события? 5. Для чег предназначены визуальные компоненты ListBox и ComboBox? 47 6. Какую возможность предоставляет свойство SelectedIndex компонента ListBox ? 7. Для чего используется компонент GroupBox? 8. Как задать структуру таблицы, создаваемой с помощью компонента DataGridView? 9. Какие существуют способы заполнения ячеек таблицы на основе компонента DataGridView? Заключение Учебное пособие содержит подробное описание визуальной среды программирования CLR Windows Form Application. Демонстрирует на большом числе примеров приемы работы с визуальными компонентами среды. Показывает разные типы компонентов и описывает соответствующие им способы действия. Большое внимание уделено написанию обработчиков событий, реализующих основную функциональности программного обеспечения. В результате изучения пособия и реализации отдельных заданий студент получит навыки конструирования интерфейса приложения, используя стандартные визуальные компоненты среды, и разработки ПО на основе событийной синхронизации. Список рекомендуемой литературы 1. Г.Шилдт. Самоучитель С++. – БХВ-Петербург, 2003, 688 с. 2. Б.И.Пахомов. С/С++ и MS Visual Studio 2008 для начинающих. БХВ-Петербург, 2009, 624 с. 3. А.Хортон. Visual Studio 2005: Базовый курс. М.: Вильямс, 2007, 1154 с. 48 Оглавление Введение …………………………………………………………………………………………. 3 1. Создание проекта в среде Windows Form …………………………………………………….3 1.1 Описание среды Windows Form …………………………………………………………4 1.2 Разработка проекта …………………………………………………………….……….6 1.3 Сохранение, сборка и выполнение проекта ……………………………………….....10 2. Визуальные компоненты среды Windows Form ……………………………………………..11 2.1 Визуальные компоненты Label, TextBox и Button ……………………………………11 2.2 Визуальные компоненты ListBox, ComboBox, RadioButton и GroupBox …………...15 2.3 Матрица ячеек DataGridView …………………………………………………………..20 3. Материалы для самостоятельной проработки……………………………………………......25 3.1 Краткое введение в объектно-ориентированное программирование (ООП)………...25 49 3.2 Динамические структуры данных………………………………………………………29 3.3 Итераторы...................……………………………………………………………………32 3.4 Примеры с использованием списка прямого доступа и итераторов.…......................37 3.5 Графика……………………….........……………………………………………………..38 3.6 Реализация «движения» по однородному и сложному фону.......................................42 3.7 Сложное движение каркасных моделей.....................................................................….43 4. Типовые задания для лабораторных работ………………………………………………46 Список рекомендованной литературы …………………………………………………………..49 50