ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ...

96
Министерство образования и науки Российской Федерации Федеральное государственное бюджетное образовательное учреждение высшего профессионального образования «Владимирский государственный университет имени Александра Григорьевича и Николая Григорьевича Столетовых» И. Е. ЖИГАЛОВ И. А. НОВИКОВ ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ ГРАФИКИ Учебное пособие Владимир 2014

Transcript of ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ...

Page 1: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

Министерство образования и науки Российской Федерации

Федеральное государственное бюджетное образовательное учреждение высшего профессионального образования

«Владимирский государственный университет имени Александра Григорьевича и Николая Григорьевича Столетовых»

И. Е. ЖИГАЛОВ И. А. НОВИКОВ

ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ ГРАФИКИ

Учебное пособие

Владимир 2014

Page 2: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

2

УДК 681.3 ББК 32.973-018.2

Ж68 Рецензенты:

Доктор технических наук, профессор кафедры основ нанотехнологий и теоретической физики

Владимирского государственного университета имени Александра Григорьевича и Николая Григорьевича Столетовых

М. В. Руфицкий

Кандидат экономических наук, профессор зав. кафедрой инновационного предпринимательства

Московского государственного технического университета им. Н. Э. Баумана С. Р. Борисов

Печатается по решению редакционно-издательского совета ВлГУ

Жигалов, И. Е. Ж68 Программирование компьютерной графики : учеб. пособие /

И. Е. Жигалов, И. А. Новиков ; Владим. гос. ун-т им. А. Г. и Н. Г. Сто-летовых. – Владимир : Изд-во ВлГУ, 2014. − 96 с.

ISBN 978-5-9984-0437-5 Содержит теоретический материал и практические задания по темам, связанным с

освоением Visual C# и изучением графической библиотеки OpenGL. Предназначено для студентов направлений 230400 – Информационные сиcтемы и

технологии и 231000 – Программная инженерия при изучении дисциплины «Програм-мирование компьютерной графики».

Рекомендовано для формирования профессиональных компетенций в соответ-ствии с ФГОС 3-го поколения.

Ил. 57. Табл. 2. Библиогр.: 8 назв.

УДК 681.3 ББК 32.973-018.2

ISBN 978-5-9984-0437-5 © ВлГУ, 2014

Page 3: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

3

ВВЕДЕНИЕ

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

В пособии рассматриваются типичные для компьютерной графики способы и приемы, ориен-тированные на расчет узловых точек изображения в пространстве и формирование объектов на их осно-ве. Пособие помогает при изучении конкретных за-дач, связанных с получением компьютерных изоб-ражений на базе графических средств среды про-граммирования VisualC# и включает цикл работ по освоению библиотеки OpenGL и изучению алго-ритмов компьютерной графики.

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

Page 4: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

4

Тема 1. C#: РАЗРАБОТКА КОНСОЛЬНОГО ПРИЛОЖЕНИЯ Цель изучения темы. Освоение принципов программирования в сре-

де Microsoft Visual C# для операционной системы Windows с использова-нием Microsoft . NET Framework, получение практических навыков постро-ения консольных приложений в Visual C#.

1.1. О Microsoft .NET Framework Microsoft .NET Framework – это предложенная компанией Microsoft

программная технология, основной задачей которой является предоставле-ние разработчику набора удобных средств для разработки как простых программ, так и web-приложений.

Microsoft . NET Framework вобрал в себя все самое лучшее из совре-менных средств программирования: удобные возможности синтаксиса С++, удобства объектной модели Java, простота C++ Builder в плане по-строения оконных приложений и методов для доступа к переменным, без-опасный код.

Хотя скорости работы идентично реализованных алгоритмов на язы-ках C# и С++ различаются примерно в 2 раза (в пользу С++), при исполь-зовании unsafe (небезопасного) кода, C# практически догоняет по скорости выполнения С++, недобирая всего несколько процентов.

Подобно технологии Java (объектно-ориентированный язык програм-мирования, разработанный компанией Sun Microsystems) среда разработки .NET из исходного кода программы создаёт байт-код, предназначенный для исполнения виртуальной машиной − программной или аппаратной средой, исполняющей некоторый код (например, байт-код или машинный код реального процессора).

Входной язык этой машины в .NET называется MSIL (Microsoft Inter-mediate Language), или CIL (Common Intermediate Language), или просто IL. Применение байт-кода предназначено для получения кроссплатформенно-сти на уровне скомпилированного текста программы, а не только на уровне исходного как, например, в языке С. С помощью JIT-компилятора (just in time, компиляция на лету) непосредственно перед запуском сборки

Page 5: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

5

в среде исполнения CLR происходит преобразование байт-кода в машин-ные коды целевого процессора. Для native-сборки (компиляция в родной для ОС код) можно выполнить компиляцию с помощью утилиты NGen.exe, которая поставляется вместе с .Net Frаmework.

Часто разработчикам приходится писать программы на разных языках программирования. Библиотека классов .NeT Framerowk позволяет разра-ботчикам использовать один программный интерфейс для всех функцио-нальных средств CLR. Универсальность библиотеки классов .net − одна из ее сильнейших сторон.

1.2. Основы синтаксиса языка C# Синтаксис языка C# очень похож на синтаксис языка С++. В качестве

первого примера создадим традиционное приложение − Hello World. Все примеры будут приводиться на MS Visual Studio 2008, SP1 с русскоязыч-ным интерфейсом, который более понятен для начинающих программи-стов, хотя рекомендуется использовать английскую версию. Откройте ре-дактор MS Visual Studio. Заполнение кода в Visual Studio стало крайне удобным благодаря отлично реализованной системе подсказок, поэтому набор кода не должен составить никаких проблем.

Запустите MS Visual Studio, после чего создайте новый проект, используя меню «Файл», как показано на рис. 1.1 (Файл -> Создать -> Проект…):

Рис. 1.1

Page 6: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

6

Выберите проекты Visual C#. В шаблонах отметьте Консольное при-ложение и введите имя для нашего проекта: Hello World (рис. 1.2). По умолчанию все проекты сохраняются в папке «Мои документы» текущего пользователя \Visual Studio 2008\Projects.

Рис. 1.2

В открывшемся окне редактора будет код шаблона простейшего при-

ложения .net на языке C#. В него нам необходимо добавить две строки кода: первая будет выво-

дить на экран сообщение “Hello World”, вторая ожидать строку, введенную пользователем, после которой программа завершится. Ожидание ввода нам необходимо, чтобы прочитать выведенные программой строки; иначе мы просто не успеем это сделать: программа выполнит код и завершится.

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace HelloWorld {

Page 7: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

7

class Program { static void Main(string[] args) { // выводим текст в консоль System.Console.Write("Hello world again"); // ждем ввода строки от пользователя, // после которого завершится выполнение программы String str = System.Console.ReadLine(); } } }

После набора кода необходимо нажать F5 и подтвердить необходи-мость компиляции.

Программа будет запущена, после чего вы сможете увидеть результат ее работы (рис. 1.3).

Рис. 1.3

Теперь остановимся подробнее на синтаксисе С#. Объектно-ориентированным языкам свойственно иметь две большие

категории типов: типы, которые присущи языку («базовые типы»), и клас-сы, которые программист может создать самостоятельно.

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

Page 8: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

8

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

В .net и С# нет такой проблемы, так как любая сущность CTS (Common Type System – уникальная система типов, принятая в .NET) является объек-том. Причем базовым классом служит System.Object.

Все типы в C# .NET делятся на РАЗМЕРНЫЕ и ССЫЛОЧНЫЕ. Размерные типы содержат реальные данные и, следовательно, не мо-

гут быть равны null. Именование типов очень напоминает С++: Int, long, char,

за исключением того, что для беззнаковых чисел упрощено именование: теперь для этого используется первый символ “u” перед названием типа, вместо слова «unsigned», использовавшегося в С++.

На практике, написать uint намного удобнее, чем unsigned int. Ссылочные типы крайне похожи на ссылки в С++. Правда, в отличие

от указателя в С++ ссылочный тип гарантирует, что ссылка указывает на объект заданного типа в памяти либо может быть равна null. Например:

string str = “This is a test”; т.е. в данном случае было выделено место в памяти и str содержит ссылку на него.

Массивы и классы в C# являются ссылочными типами. Типы и псевдонимы (с помощью которых, как правило, происходит

объявление) приведены в табл. 1.1. Таблица 1.1

СТS Тип Имя псевдонима в С# Описание

System. Object object Класс, базовый для всех типов (CTS)System. String string Строка System. SByte sbyt 8-разрядный байт (со знаком) System. Byte byte 8-разрядный байт (без знака) System. S16 short 16-разрядное число (со знаком) System. UM16 ushort 16-разрядное число (без знака) System. Int32 int 32-разрядное число (со знаком) System. UInt32

uint 32-разрядное число (без знака)

System. Int64 long 64-разрядное число (со знаком) System. UInt64 ulong 64-разрядное число (без знака) System.Char char 16-разрядный символ (Unicode)

Page 9: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

9

Окончание табл. 11 СТS Тип Имя псевдонима в С# Описание

System. Single float 32-разрядное число с плавающей точкой (стандарт IEEE)

System. Double double 64-разрядное число с плавающей точкой (стандарт IEEE)

System. Boolean bool Булевское значение (true/false) System. Decimal

decimal Данный 128-разрядный тип исполь-зуется в основном, когда требуется крайне высокая точность (до 28-го

знака) Теперь рассмотрим, как работают операторы выбора и оператор ветв-

ления, как реализуются циклы. Оператор выбора (условный оператор IF) Как известно, оператор выбора используется для последующего вы-

полнения или невыполнения некоторого оператора или группы операторов в зависимости от условия. Если предлагаемое условие истинно, то вложен-ный оператор, или блок кода, выполняется. Альтернативная ветвь, которая может присутствовать (а может и нет), выполнится, если условие ложно.

if (resault == 777) {

System.Console.WriteLine("Congratulations, you win!!!!"); // выведет: «Поздравляем, вы победили» } else {

// выведет: «Пожалуйста, попробуйте снова» System.Console.WriteLine("please, try again \n"); //выведет: «Мы уверены- вам повезет» System.Console.WriteLine("we are confident - you will be lucky");

}

Так же, как и в С++, можно использовать упрощенный синтаксис с

оператором «?»: (resault == 777) ? true : false ;

Page 10: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

10

Оператор ветвления Оператор ветвления может иметь большое количество ветвей, выбор

которых осуществляется с помощью значения управляющего выражения. Это очень удобный способ реализации кода, когда существует некий пара-метр, в зависимости от которого должны выполняться те или иные ветви кода. В C# он реализуется следующим образом:

switch ( value ) {

default: {

System.Console.WriteLine("для этого варианта действие не определено”); break;

} case 1 : {

System.Console.WriteLine("Цифра 1”); break;

} case 2 : {

System.Console.WriteLine("Цифра 2”); break;

} case 3 : {

System.Console.WriteLine("Цифра 3”); break;

} }

В языке С# существуют 4 вида циклов Циклы реализуются с помощью следующих зарезервированных слов:

while, do while , for , foreach . Рассмотрим каждый из них на примере. Переменные, объявленные в цикле (в том числе в заголовке цикла for

и foreach) не видны снаружи цикла (так же, как в стандартном C++). 1. Оператор цикла while Пока истинно управляющее условие, выполняется оператор (или те-

ло цикла).

Page 11: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

11

Пример: значение переменной a инициируется равным 100; затем, пока a больше 5, выполняется тело цикла − вывод а, затем его значение уменьша-ется на единицу.

int a = 100; while (a > 5) { System.Console.WriteLine(a); a--; }

2. Оператор цикла dowhile В данном случае тело цикла выполняется до проверки условия:

int a = 100; do { System.Console.WriteLine(a); a--; }while (a > 5)

3. Оператор цикла for Цикл for используют, как правило, когда число повторений известно

заранее, т.е. в задачах, связанных с перебором. Мы устанавливаем начало отсчета, условие остановки и тип изменения параметра.

Пример: при начальном значении a = 100 перебор значений a проис-ходит до тех пор, пока a больше пяти. После каждого выполнения тела цикла a уменьшается на единицу.

for (int a = 100; a > 5; a --) { System.Console.WriteLine(a); }

4. Оператор цикла foreach Этот цикл полезен, когда необходимо перебрать все элементы масси-

ва. Рассмотрим на примере: массив a состоит из 3 элементов. Цикл foreach перебирает все значения, имеющиеся в массиве, присваивая их переменной x. В теле цикла выполняется вывод этих значений.

Page 12: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

12

int[] a = new int[]{1,2,3}; // наш массив foreach (int x in a) { System.Console.WriteLine(x); }

Как видно из кода, работа с массивами в C# отличается от работы с массивами в С++.

Массивы Массивы претерпели изменения по сравнению с синтаксисом языка

С++. Массивы в .NET имеют тип System.Array. Они не копируются при

присваивании (т.е. это ссылки, а не значения). Также их можно использо-вать в циклах foreach. Свойство массива Length содержит общее число элементов массива, что очень удобно.

Одномерные массивы Начнем с одномерных массивов. Данный тип массива включает в себя

информацию о базовом типе элементов, которые может содержать массив, а также о количестве элементов в массиве (размерность массива). Нумера-ция элементов массивов так же, как и в языке С/C++, начинается с нуля.

Одномерные массивы определяются следующим образом: uint[] arr_name = new uint[100]; Здесь arr_name − имя массива, 100 − количество элементов в массиве.

Так как нумерация начинается с нуля, первый элемент доступен через об-ращение arr_name[0], последний − через arr_name [99].

Теперь рассмотрим три способа заполнения массива значениями.

int arr_name = new int[2]; arr_name[0] = 1; arr_name[1] = 2; int[] arr_name = new int[] {1,2}; int[] arr_name = {1,2};

Прямоугольные массивы Прямоугольный массив − это двумерный массив, например, размерно-

стью 4 × 2:

Page 13: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

13

int[,] arr_name_1 = new int[4,2]; int[,] arr_name_2 = {{0, 1, 2, 3}, {0, 1, 2}}; for(i = 0; i < arr_name_1.GetLength(0); i++) { for (j = 0; j < arr_name_1.GetLength(1); j++) { System.Console.WriteLine(arr_name_1[i,j]); } }

В данном коде мы рассмотрели пример вывода значений из двумерно-

го массива. Классы Следующим пунктом в освоении C# являются классы. Синтаксис

крайне прост, очень похож на синтаксис С++: Ключевое слово class, затем имя класса и перечисление его членов, за-

ключенное в фигурные скобки, − на первый взгляд, мало что изменилось. Рассмотрим пример

public class MyTestClass { public string SomeStringInformation; }

C# имеет 4 модификатора доступа к элементам и методам класса (табл. 1.2).

Таблица 1.2 Модификатор

доступа Описание

public Член класса доступен вне определения класса и иерархии произ-водных классов

protected Член класса невидим за пределами класса, и к нему могут обра-щаться только производные классы

private Член класса недоступен за пределами области видимости опреде-ляющего его класса, поэтому доступа к этим членам нет даже у производных классов

internal Член класса видим только в пределах текущей единицы компи-ляции. Модификатор доступа internals в плане ограничения до-ступа является гибридом public и protected, который зависит от местоположения кода

Page 14: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

14

Безусловно, классы − очень обширная тема. Мы рассмотрим ее на примере разработки класса и реализации консольной работы с ним.

1.3. Разработка класса и реализация консольного приложения Приведем пример создания небольшого класса в C#. В нем мы рас-

смотрим создание новых методов и переменных − членов класса, а также работу с классом.

Создадим новый класс, назовем его Man (человек). Наш класс будет схематично описывать персонажа компьютерной иг-

ры следующим образом (рис.1.4). Имя, возраст, здоровье и состоя-

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

Имя будет передаваться в класс во время вызова конструктора класса. Возраст и состояние здоровья будут генерироваться в конструкторе авто-матически, в дальнейшем они будут влиять на вывод текста в консоль при вызове доступных пользователю ме-тодов.

Создадим новое консольное при-ложение и назовем его ClassTesting.

После сгенерированного программой класса Program мы создадим свой класс, назвав его Man и снабдив необходимыми свойствами и мето-дами:

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ClassTesting {

class Program {

Рис. 1.4

Page 15: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

15

static void Main(string[] args) { }

} public class Man {

// конструктор класса (данная функция вызывается // при создании нового экземпляра класса) public Man(string _name) { Name = _name; isLife = true; } // закрытые члены, которые нельзя изменить // извне класса // строка, содержащая имя private string Name; // беззнаковое целое число, содержащее возраст private uint Age; // беззнаковое целое число, отражающее уровень здоровья private uint Health; // булево, означающее жив ли данный человечек private bool isLife; // заготовка функции "говорить" public void Talk() { } // заготовка функции "идти" public void Go() { } // функция, возвращающая показатель - жив ли данный человечек. public bool IsAlive() { // возвращаем значение, к которому мы не можем // обратиться напрямую извне класса, // так как оно имеет статус private return isLife; }

} }

Это заготовка для нашей программы. Мы добавили в код еще одну

функцию, о которой забыли в схеме нашего класса, − IsAlive.

Page 16: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

16

Данная функция будет возвращать значение переменной isLife, к ко-торой мы не можем получить доступ извне класса, так как она является за-крытой (private).

Теперь более тщательно займемся реализацией наших классов. Во-первых, улучшим конструктор класса: в нем будут генерироваться

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

Генерация случайного числа в C# Владение языками программирования не обходится без знания того,

как выполняется генерация случайных чисел. Сейчас мы рассмотрим, как выполняется генерация случайного числа в С#.

Для выполнения данной задачи сначала создадим новый экземпляр класса Random

Random rnd = new Random(); Теперь для того чтобы получить случайное число, достаточно исполь-

зовать созданный экземпляр класса, вызывая метод Next(); , например Age = rnd.Next(); При желании можно генерировать число в заданном диапазоне, указав

начальное и конечное значения. Модернизируем конструктор нашего класса так, чтобы он смог гене-

рировать каждому новому экземпляру случайные значения здоровья и воз-раста.

Теперь конструктор класса выглядит следующим образом:

// конструктор класса (данная функция вызывается // при создании нового экземпляра класса) public Man(string _name) {

// получаем имя человечка из входного параметра // конструктора класса Name = _name; // экземпляр жив isLife = true; // генерируем возраст от 15 до 50 Age = (uint)rnd.Next(15,50); // и здоровье, от 10 до 100% Health = (uint)rnd.Next(10, 100);

}

Page 17: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

17

Параметр (uint) перед экземпляром класса rnd означает, что значение случайного числа, которое вернет функция Next, будет приведено к типу uint (unsigned – т.е. беззнаковое int). Просто функция rnd возвращает зна-чение с типом int, и данное значение не может быть неявно преобразовано, поэтому необходимо сообщить компилятору о преобразовании.

После конструктора класса добавим объявление нашего экземпляра класса Random

// экземпляр класса Random // для генерации случайных чисел

private Random rnd = new Random();

Остается только модернизировать функции, предназначенные для пе-ремещения и разговора.

Сначала рассмотрим функцию разговора. Начало кода функции будет таким:

// заготовка функции "говорить" public void Talk() { // генерируем случайное число от 1 до 3 int random_talk = rnd.Next(1, 3); // объявляем переменную, в которой мы будем хранить строку

string tmp_str = "";

Как видим, здесь объявляется переменная random_talk, и сразу при объ-

явлении она получает значение – результат выполнения функции rnd.Next в диапазоне от 1 до 3.

В зависимости от этого значения при вызове функции Talk случайным образом будет происходить выбор: что же сейчас скажет наш объект?

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

Генерация строки будет выполняться с помощью оператора выбора switch:

// в зависимости от случайного значения random_talk switch (random_talk) { case 1: // если 1 - называем свое имя { // генерируем текст сообщения tmp_str = "Привет, меня зовут " + Name + ", рад познакомиться"; // завершаем оператор выбора break;

Page 18: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

18

} case 2: // возраст { // генерируем текст сообщения tmp_str = "Мне " + Age + ". А тебе?"; // завершаем оператор выбора break; } case 3: // говорим о своем здоровье { // в зависимости от параметра здоровья if (Health > 50) tmp_str = "Да я здоров как бык!"; else tmp_str = "Со здоровьем у меня неважно, дожить бы до " + (Age + 10).ToString(); // завершаем оператор выбора break; } }

Как видно из кода, в зависимости от значения переменной random_talk

выполняется ветвление кода и выбирается одна из веток для значения ran-dom_talk = 1 (case 1), random_talk = 2 (case 2), или random_talk = 3 (case 3).

И завершение функции: // выводим на консоль сгенерированное сообщение System.Console.WriteLine(tmp_str); }

Теперь рассмотрим код функции Go. Здесь все проще: сначала мы определяем, жив ли объект. Если нет, то

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

Код данной функции с подробными комментариями: // заготовка функции "идти" public void Go() { // если объект жив if (isLife == true) { // если показатель более 40 // считаем объект здоровым if (Health > 40)

Page 19: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

19

{ // генерируем строку текста string outString = Name + " мирно прогуливается по городу"; // выводим в консоль System.Console.WriteLine(outString); } else { // генерируем строку текста string outString = Name + " болен и не может гулять по городу"; // выводим в консоль System.Console.WriteLine(outString); } } else { // генерируем строку текста string outString = Name + " не может идти, он умер"; System.Console.WriteLine(outString); } }

Напоследок добавим функцию, которая «убивает» нашего человечка. Её можно впоследствии усовершенствовать для исключения возможность «убивать» человечка несколько раз. Код функции будет выглядеть следу-ющим образом:

public void Kill() {

// устанавливаем значение isLife (жив) // в false... isLife = false;

}

Вот и все. Если вы набрали весь код верно, то при компиляции (кла-виша F5) не должно возникнуть никаких проблем.

Теперь вернемся к коду главного класса (Program). Нам необходимо сделать так, чтобы консоль ожидала команды поль-

зователя: в зависимости от этих команд будут выполняться различные дей-ствия – создание новых экземпляров класса Man (генерирование новых че-ловечков) и выполнение функций, которые реализованы в классе Man.

Для этого мы создадим бесконечный цикл: в нем программа будет за-прашивать пользователя ввести строку. Затем строка будет сверяться с за-данным набором команд при помощи оператора выбора (switch).

Page 20: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

20

В зависимости от той или иной команды будут выполняться действия над нашим человечком. Команда exit будет завершать программу. Сначала посмотрим, как будут происходить ввод строки и выбор команды.

Первым делом объявим переменную user_command, в которой мы бу-дем хранить команду, введенную пользователем. Далее мы объявим пере-менную Infinity. До тех пор пока значение Infinity будет равно true, цикл будет выполняться. Изменение этой переменной произойдет, только если пользователь введет команду exit.

static void Main(string[] args) {

// переменная, которая будет хранить команду пользователя string user_command = ""; // бесконечный цикл bool Infinity = true; while (Infinity) // пока Infinity равно true {

// приглашение ввести команду System.Console.WriteLine("Пожалуйста, введите команду"); // получение строки (команды) от пользователя user_command = System.Console.ReadLine();

} }

Далее из кода видно, что мы начинаем наш цикл while. При старте вы-полнения кода из тела цикла сначала в консоль выводится строка с при-глашением ввести команду. Далее мы ожидаем ввода строки пользовате-лем. Введенная строка после нажатия клавиши enter будет записана в пе-ременную user_command.

Теперь с помощью операции ветвления мы создадим обработку стан-дартных команд exit и help.

Ветвление default будет выполняться в том случае, когда введенная строка не будет соответствовать ни одной существующей команде.

// обработка команды с помощью оператора ветвления switch (user_command) { // если user_command содержит строку exit case "exit": { Infinity = false;

// теперь цикл завершиться, и программа завершит свое выполнение break;

}

Page 21: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

21

// если user_command содержит строку help case "help": {

System.Console.WriteLine("Список команд:"); System.Console.WriteLine("---"); System.Console.WriteLine("create_man : команда создает человечка, (экземпляр

класса Man)"); System.Console.WriteLine("kill_man : команда убивает человечка"); System.Console.WriteLine("talk : команда заставляет человечка говорить (если

создан экземпляр класса)"); System.Console.WriteLine("go : команда заставляет человечка идти (если создан

экземпляр класса)"); System.Console.WriteLine("---"); System.Console.WriteLine("---"); break;

} // если команду определить не удалось default: {

System.Console.WriteLine("Ваша команда не определена, пожалуйста, повтори-те снова");

System.Console.WriteLine("Для вывода списка команд введите команду help"); System.Console.WriteLine("Для завершения программы введите команду exit"); break;

} }

Не забудьте закрыть все фигурные скобки (}) после операции ветвления. Если мы теперь откомпилируем проект (клавиша F5), то увидим окно

консоли с приглашением ввести команду.

Рис. 1.5

Page 22: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

22

Теперь нам осталось лишь реализовать обработку команд create_man, kill_man, talk и go.

После строки объявления переменной и перед началом цикла while добавьте объявления экземпляра класса Man.

// пустой (равный null) экземпляр класса Man Man SomeMan = null; Теперь рассмотрим код обработки команды create_man в теле опера-

тора ветвления (switch).

case "create_man": { // проверяем, создан ли уже какой-либо человечек if (SomeMan != null) { // человечек уже существует - "убиваем" его SomeMan.Kill(); } // просим ввести имя человечка System.Console.WriteLine("Пожалуйста, введите имя создаваемого человечка \n"); // получаем строку, введенную пользователем user_command = System.Console.ReadLine(); // создаем новый объект в памяти в качестве параметра // передаем имя человечка SomeMan = new Man(user_command); // сообщаем о создании System.Console.WriteLine("Человечек успешно создан \n"); break; }

Как видно из кода, с помощью оператора new мы создаем новый объ-ект в памяти.

Теперь он имеет имя, параметры, и мы можем выполнять другие ко-манды, осуществляющие работу с классом.

case "kill_man": {

// проверяем, что объект существует в памяти if (SomeMan != null) {

// вызываем функцию "смерти" SomeMan.Kill();

} break;

}

Page 23: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

23

Это код функции "смерти". Обратите внимание на то, что перед тем, как воспользоваться вызовом

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

Мы делаем это, проверяя, не равен ли наш объект (к примеру, Some-Man) null. Если не выполнить такую проверку, а потом в ходе работы про-граммы окажется, что, например, SomeMan = null, и мы попытаемся вы-звать любой из его методов (кроме конструктора класса – он как раз и со-здает объект в памяти), то в программе произойдет ошибка, а потом − об-работка исключения.

Для того чтобы, например, выполнить функцию kill( ), компилятор сначала получает адрес памяти, где сейчас хранится экземпляр нашего класса. SomeMan как раз должен хранить этот адрес, а он будет равен null. Следовательно, компилятор получит команду “по адресу null определить адрес функции Kill и выполнить ее”. У него это, конечно, не получится.

Нам остались команды talk и go. Они будут реализованы так же, как и команда kill: нам достаточно

проверить, что объект существует в памяти. Если он существует – выпол-нить метод. Если нет – сообщить об этом.

case "talk": { // проверяем, что объект существует в памяти if (SomeMan != null) { // вызываем функцию разговора SomeMan.Talk(); } else // иначе { System.Console.WriteLine("Человечек не создан. Команда не может быть выполне-на"); } break; } case "go": { // проверяем, что объект существует в памяти if (SomeMan != null) { // вызываем функцию передвижения SomeMan.Go();

Page 24: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

24

} else { System.Console.WriteLine("Человечек не создан. Команда не может быть выполне-на"); } break; }

Пример работы откомпилированной программы:

Рис. 1.6

Page 25: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

25

Протестировав программу, обнаружим, что человечек способен разго-варивать даже после «смерти». Сочтём это не за ошибку, а за интересную особенность программы.

Контрольные вопросы 1. Классы и объекты в С#. 2. Понятия объектно-ориентированного программирования: наследо-

вание, перегрузка и переопределение функций, конструктор и деструктор. 3. Структура и интерфейс пользователя рабочей среды VisualC#. 4. Основные объекты программы на VisualC#.

Тема 2. C#: РАЗРАБОТКА ОКОННОГО ПРИЛОЖЕНИЯ Цель изучения темы. Освоение работы с оконными элементами в сре-

де Microsoft Visual C# для операционной системы Windows с использова-нием Microsoft .net Framework. Получение практических навыков построе-ния оконных приложений в Visual C#.

2.1. Основы Windows.Forms Windows.Forms используется в Microsoft .NET для создания приложе-

ний, снабженных графическим интерфейсом. Основывается он на .NET Framework class library и имеет намного более совершенную и удобную в работе модель программирования, чем, например, программные интерфей-сы Win32 API или MFC.

Если вам уже приходилось работать с Win32 API или MFC, то вам по-нравится то, насколько удобна и быстра разработка с использованием Windows.Forms. Написание оконных приложений с использованием Windows. Forms как намного удобнее, быстрее, так и на много качественнее, так как Windows.Forms устраняют многие ошибки Windows API. Код, который вы бу-дете писать, намного проще и компактнее (нет DDX переменных и макросов, как в MFC).

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

Page 26: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

26

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

Так как Windows.Forms должна включать сотни организованных клас-сов, чтобы обеспечивать все необходимые возможности разработчику, .NET Framework разбита на ряд иерархических разделов, имеющих свои имена. System является корневым разделом и предназначен для описания фундаментальных типов данных.

2.2. Создание оконного приложения в .net Разрабатываемое приложение будет представлять собой небольшую

форму, повторяющую идею известной программы: на форме будет содер-жаться вопрос «Вы стремитесь сделать мир лучше?». При попытке наве-дения указателя мыши на кнопку «Да, конечно!» окно будет убегать от нас. Нажатие на кнопку «Нет» не будет ничем ограничено.

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

Создайте новый проект, в качестве типа шаблона установите прило-жение Windows Forms, как показано на рис. 2.1.

Рис. 2.1

Page 27: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

27

Назовите проект RandWindow и нажмите кнопку ОК. Рабочее окно MS Visual Studio содержит следующие вспомогательные

окна (рис. 2.2). На рисунке цифрами отме-

чены: 1. Окно Toolbox (Панель

элементов управления, которые вы можете разместить на созда-ваемой форме).

2. Окно Solution Explorer (Обозреватель решений). Здесь вы сможете увидеть следующие узлы: Properties − настройки проекта, Links (Ссылки) − под-ключенные к проекту библио-теки, а также созданные и подключенные к проекту файлы исходных кодов (с расширением .cs) и подключенные к проекту формы (например, Form1).

3. Окно Class View (Окно классов, здесь представлены все созданные в программе классы).

4. Окно Properties (Свойства. Выбрав любой элемент управления или даже форму, вы сможете увидеть все параметры данного объекта, а также изменить значения, установленные в них по умолчанию).

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

Добавление новых элементов управления на форму После того как мы ввели имя проекта, установили необходимый шаб-

лон и нажали кнопку ОК, MS Visual Studio автоматически создаст каркас оконного приложения, после чего мы сможем добавить на него новые оконные элементы.

Для этого следует перетащить необходимый оконный элемент из окна инструментов (ToolBox).

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

Рис. 2.2

Page 28: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

28

В центре окна будет находиться надпись, которую мы создадим с по-мощью элемента Label.

Снизу будут расположены две кнопки. Немного растяните заготовку окна. Если вы нажмете на него правой

кнопкой, то откроется контекстное меню. В нем нажмите на пункте «Свой-ства», после чего вы сможете изучить различные параметры окна, которые вы можете изменить. На рис.2. 3 изображены самые необходимые свойства:

Рис. 2.3

Page 29: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

29

Немного растяните заготовку окна и добавьте все необходимые эле-менты. На рис. 2.4 вы можете увидеть их в окне ToolBox.

Рис. 2.4

Перейдите в свойства строки Label1, где измените текст на «Вы стре-митесь сделать мир лучше?».

Также измените тип шрифта; для этого найдите свойство Font (рис. 2.5).

Рис. 2.5

Затем установите тип шрифта Tahoma, ширину шрифта Bold и размер равный 16 (рис. 2.6).

Рис. 2.6

Page 30: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

30

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

образом (рис.2. 7).

Рис. 2.7

Теперь рассмотрим техническую часть работы нашей программы. 1. Сначала мы добавим обработчик события перемещения мыши и ре-

ализуем вывод ее координат x, y в два созданных поля ввода. 2. Далее мы создадим функции обработчики щелчка по каждой кла-

више мыши (особенно усердные пользователи все же смогут попасть по кнопке «Да, конечно!»).

3. Далее мы добавим код, реализующий случайное перемещение окна в том случае, если курсор приблизится к кнопке «Да, конечно!».

Определение перемещения указателя мыши по форме Щелкните непосредственно на части формы создаваемого приложе-

ния (НЕ на одном из элементов). Теперь перейдите к свойствам формы с помощью щелчка правой

кнопки мыши -> контекстное меню свойства. Теперь необходимо перейти к списку возможных событий, которые

может получать данное окно, для этого щелкните по кнопке «Event» (со-бытия), как показано на рис. 2.8.

Рис. 2.8

Page 31: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

31

Когда пользователь передвигает указатель мыши по нашему окну, опе-рационная система посылает сообщение программе с текущими координата-ми указателя. Они-то нам и нужны.

Чтобы назначить обработчик данного события, найдите строку MouseMove (рис. 2.9), после чего сделайте двойной щелчок в поле справа от нее – автоматически добавятся обработчик события движения мыши и функ-ция Form1_MouseMove в коде нашей программы.

Рис. 2.9

Добавьте в эту функцию две строки, чтобы ее код стал выглядеть сле-

дующим образом:

private void Form1_MouseMove(object sender, MouseEventArgs e) {

// переводит координату X в строку и записывает в поля ввода textBox1.Text = e.X.ToString(); // переводит координату Y в строку и записывает в поля ввода textBox2.Text = e.Y.ToString();

}

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

Объекты textBox1 и textBox2 − это экземпляры класса textbox, реали-зующие управление нашими элементами поля для ввода.

Член данных экземпляров Text позволяет установить текст в этих по-лях.

Таким образом, если теперь откомпилировать программу (F5), при перемещении указателя мыши по форме окна мы будем видеть координаты указателя (внутри формы), которые будут непрерывно изменяться.

Page 32: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

32

Теперь вернемся к заготовке нашей формы. Для это щелкните на соот-ветствующей закладке (Form1.cs [Конструктор]), как показано на рис. 2.10.

Рис. 2.10

Сделайте двойной щелчок по первой кнопке; Visual Studio автомати-чески добавит код обработки данной кнопки при нажатии.

Добавьте следующие строки кода:

private void button1_Click(object sender, EventArgs e) {

// Вывести сообщение с текстом "Вы усердны" MessageBox.Show( "Вы усердны!!"); // Завершить приложение Application.Exit();

}

Теперь снова вернитесь к конструктору и добавьте вторую кнопку также с помощью двойного щелчка по ней.

Она будет содержать следующий код:

private void button2_Click(object sender, EventArgs e) {

// Вывести сообщение, с текстом "Мы не сомневались в вашем безразличии" // второй параметр - заголовок окна сообщения "Внимание" // MessageBoxButtons.OK - тип размещаемой кнопки на форме сообщения // MessageBoxIcon.Information - тип сообщения - будет иметь иконку "информа-

ция" и соответствующий звуковой сигнал MessageBox.Show("Мы не сомневались в вашем безразличии","Внимание",

MessageBoxButtons.OK, MessageBoxIcon.Information); }

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

Теперь нам осталось только реализовать перемещение окна в тот мо-мент, когда мышь приближается к кнопке «Да, конечно».

Для этого мы добавим код в функцию private void Form1_MouseMove(object sender, MouseEventArgs e)

Принцип очень прост: получая координаты движения мыши, мы про-веряем, не входят ли они в квадрат, очерчивающий нашу кнопку с неболь-

Page 33: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

33

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

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

Также нам понадобится объявить несколько "рабочих" переменных, которые мы будем в дальнейшем использовать.

Добавление новых элементов управления на форму Класс Random в C# представляет собой генератор псевдослучайных

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

Random rnd = new Random(); Здесь мы объявили экземпляр класса Random (rnd), с помощью кото-

рого будем генерировать случайные числа. В дальнейшем мы будем ис-пользовать код вида rnd.Next(диапазон_генерации); или rnd.Next(от,до); для генерации случайного числа.

Объявим еще несколько переменных, часть которых сразу будет ини-циализирована.

Point tmp_location; int _w = System.Windows.Forms.SystemInformation.PrimaryMonitorSize.Width;

int _h = System.Windows.Forms.SystemInformation.PrimaryMonitorSize.Height;

Переменная tmp_location объявляется для того, чтобы в будущем вре-менно хранить текущее положение окна.

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

Определение разрешения экрана в C#.NET Чтобы определить разрешение экрана в C#.NET мы будем использо-

вать

System.Windows.Forms.SystemInformation.PrimaryMonitorSize

Переменные _h и _w будут хранить в себе размеры экрана пользовате-ля, который определяется при их инициализации.

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

Page 34: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

34

private void Form1_MouseMove(object sender, MouseEventArgs e) {

// переводим координату X в строку и записываем в поля ввода textBox1.Text = e.X.ToString(); // переводим координату Y в строку и записываем в поля ввода textBox2.Text = e.Y.ToString(); // если координата по оси X и по оси Y лежит в очерчиваемом вокруг кнопки "да,

конечно" квадрате if (e.X > 80 && e.X < 195 && e.Y > 100 && e.Y < 135) {

// запоминаем текущее положение окна tmp_location = this.Location; // генерируем перемещения по осям X и Y и прибавляем их к хранимому значе-

нию текущего положения окна // числа генерируются в диапазоне от -100 до 100. tmp_location.X += rnd.Next(-100, 100); tmp_location.Y += rnd.Next(-100, 100); // если окно вышло за пределы экрана по одной из осей if (tmp_location.X < 0 || tmp_location.X > (_w - this.Width / 2) || tmp_location.Y < 0

|| tmp_location.Y > (_h - this.Height / 2)) {

// новыми координатами станет центр окна tmp_location.X = _w / 2; tmp_location.Y = _h / 2;

} // обновляем положение окна на новое сгенерированное this.Location = tmp_location;

} }

Вот и все. Откомпилировав приложение, можно попробовать нажать на кнопку "Да, конечно". Это будет непросто.

2.3. Создание второго оконного приложения Целью данного пункта будет закрепление навыков работы с оконны-

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

Создайте новый проект, указав в качестве шаблона Windows Form Application и назовите second_application. Затем нажмите Oк.

Page 35: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

35

Добавление меню в приложение C#.net Здесь все просто: чтобы добавить меню в приложение, сначала от-

кройте окно Toolbox (Панель элементов). Здесь нам потребуется вкладка Menus

& Toolbars (Меню и панели инструментов). В нем нас интересует элемент MenuStrip (рис. 2.11).

Зажмите его левой кнопкой мыши и перетащите на форму. На форме отразится элемент меню, как показано на рис. 2.12.

Рис. 2.12

Теперь щелкнув на строке «введите здесь», вы можете ввести назва-ние меню: назовем его «File». При этом добавятся два дополнительных элемента – один снизу, который будет доступен при открытии меню, вто-рой − справа для создания новых разделов меню (рис. 2.13).

Рис. 2.13

Дайте название подэлементу меню File “Выход”, как показано на рис. 2.13. После этого назовите еще один раздел меню – «загрузить». В меню «за-грузить» добавьте подэлемент “загрузить изображение” и ему назначьте в

Рис. 2.11

Page 36: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

36

раскрывающемся меню два подэлемента : «В формате JPG» и «В формате PNG». Выглядеть меню будет следующим образом (рис. 2.14).

Рис. 2.14

Теперь рассмотрим, как добавить обработчик события меню. Для это-го перейдите к меню “Выход”. Сделайте двойной щелчок левой кнопкой мыши – MS Visual Studio автоматически создаст код функции обработчика и настроит событие обработки.

Перед нами откроется код функции обработчика:

private void выйтиToolStripMenuItem_Click(object sender, EventArgs e) { }

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

Новый код функции с комментариями:

private void выйтиToolStripMenuItem_Click(object sender, EventArgs e) {

// создаем переменную rsl, которая будет хранить результат вывода окна с вопро-сом

// (пользователь нажал одну из клавиш на окне - это и есть результат) // MessageBox будет создержать вопрос, а также кнопки Yes No и иконку Question

(Вопрос) DialogResult rsl = MessageBox.Show( "Вы действительно хотите выйти из прило-

жения?", "Внимание!", MessageBoxButtons.YesNo, MessageBoxIcon.Question); // если пользователь нажал кнопку "да" if (rsl == DialogResult.Yes) {

// выходим из приложения Application.Exit();

} }

Page 37: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

37

Теперь можно откомпилировать приложение и проверить работоспо-собность кнопки.

Часто панель управления (Toolbar) дублирует элементы меню для быстрого к ним доступа.

Сейчас мы рассмотрим, как создается панель управления (Toolbar) в C#.NET.

Нам снова нужно перейти к окну Toolbox (Панель инструментов), вкладке Menus & Toolbars. В этот раз мы выберем элемент ToolStrip (рис. 2.15).

Рис. 2.15

Перетащите элемент управления на окно и вы увидите, как вдоль его

верхней границы разместится панель ToolBar (рис. 2.16).

Рис. 2.16

Мы изменим положение привязки нашего ToolBar’a. Для этого щелк-

нем по нему правой кнопкой и в открывшемся контекстном меню выберем пункт "Свойства". Откроется окно свойств: здесь мы изменим привяз-ку на левую часть окна, внеся из-менения в параметр Dock, как по-казано на рис. 2.17.

Теперь увеличим размеры кнопок на toolbar’e. Для этого сна-чала необходимо в его свойствах установить параметр AutoSize рав-ным false. Теперь мы можем изме-

Рис. 2.17

Page 38: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

38

нить размеры самих кнопок: установим параметры Size - Width равными 44. Поле станет шире (рис. 2.18).

Теперь добавим три кнопки на наш ToolBar. Для этого щелкните по нему и в раскрывающемся списке эле-ментов, которые мы можем добавить, выберите элемент button (рис. 2.19).

Рис. 2.19

Повторите операцию, чтобы кнопок на панели стало две. Теперь по-очередно выберите каждую кнопку и в ее свойствах установите AutoSize, равный false. После это перейдите к полю Size и установите высоту, рав-ную 42. Теперь кнопки примут вид квадрата.

Таким образом, на панели разместятся 3 кнопки, как показано на рис. 2.20.

Теперь назначим изображения для данных картинок. В качестве изоб-ражений можно использовать все современные форматы, в том числе и png24 с поддержкой прозрачности.

Мы будем использовать 3 следующих изображения.

Изображение для кнопки 1 (будет назначено кнопке, отвечающей за

открытие дополнительного диалогового окна).

Изображение для кнопки 2 (будет назначено кнопке, отвечающей за

открытие, загрузку файлов jpg).

Рис. 2.18

Рис. 2.20

Page 39: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

39

Изображение для кнопки 3 (будет назначено кнопке, отвечающей за

открытие, загрузку файлов png).

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

жений, 1-я кнопка будет предназначена для вызова окна с отображением картинки, которую мы загрузили.

Теперь для установки изображений необходимо перейти в свойства кар-тинки, после чего мы установим значение параметра ImageScaling, равным none, чтобы изображение не масштабировалось. Теперь в параметре Image мы можем выбрать изображение для загрузки, как показано на рис. 2.21.

Рис. 2.21

В процессе выбора откроется окно, показанное на рис. 2.22.

Рис. 2.22

Page 40: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

40

Теперь щелкните на кнопке Import и выберите необходимый рисунок. Аналогично повторите с другими рисунками. В результате вы получите 3

красивые кнопки, как показано на рис. 2.23. Для того чтобы создать обработчики нажатий на

кнопки этого ToolBox’а, достаточно совершить двой-ной щелчок мыши на каждом из них – MS Visual Studio автоматически сгенерирует код обработчика события и заготовки функций.

В будущем мы добавим вызов необходимых нам функций из этого обработчика.

Теперь мы разместим на форме элемент PictureBox и настроим размеры окна, чтобы оно выглядело следу-ющим образом (рис. 2.24).

Рис. 2.24

В свойствах добавленного элемента PictureBox установите параметр

SizeMode, равный StretchImage. Теперь, когда мы реализуем загрузку изоб-

Рис. 2.23

Page 41: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

41

ражения, оно будет масштабироваться под размеры нашего элемента PictureBox.

Создание окна выбора файла в C#.net Чтобы пользователь мог выбирать файл для загрузки через стандарт-

ное в windows окно загрузки файлов, мы выполним следующие действия. Перейдите к окну ToolBox (Панель элементов). Теперь перетащите элемент управления OpenFileDialog (рис. 2.25) на

форму.

Рис. 2.25

Местоположение, в которое вы перетащите элемент, не важно – он добавится в поле под окном к другим специфическим объектам (рис. 2.26).

Как видно из рис. 2.26, к дополнительным элементам, уже установ-ленным на наше окно (меню и ToolBox), добавился еще и элемент openFileDialog1.

Теперь мы реализуем код открытия окна выбора файла с необходи-мыми нам параметрами.

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

Код сгенерированных функций выглядит следующим образом:

// обработка кнопки меню "загрузка - в формате jpg" private void вФорматеJPGToolStripMenuItem_Click(object sender, EventArgs e) { } // обработка кнопки меню "загрузка - в формате png" private void вФорматеPNGToolStripMenuItem_Click(object sender, EventArgs e) { } // обработка кнопки № 2 на панели private void toolStripButton2_Click(object sender, EventArgs e) {

Рис. 2.26

Page 42: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

42

} // обработка кнопки № 3 на панели private void toolStripButton3_Click(object sender, EventArgs e) { }

Открытие окна выбора файла и загрузка изображения в C#.net Теперь напишем следующую функцию LoadImage.

Image MemForImage; // функция загрузки изображения private void LoadImage(bool jpg) { // директория, которая будет выбрана как начальная в окне для выбора файла openFileDialog1.InitialDirectory = "c:\\"; // если будем выбирать jpg файлы if (jpg) { // устанавливаем формат файлов для загрузки - jpg openFileDialog1.Filter = "image (JPEG) files (*.jpg)|*.jpg|All files (*.*)|*.*"; } else { // устанавливаем формат файлов для загрузки - png openFileDialog1.Filter = "image (PNG) files (*.png)|*.png|All files (*.*)|*.*"; } // если открытие окна выбора файла завершилось выбором файла и нажатием кнопки ОК if(openFileDialog1.ShowDialog() == DialogResult.OK) { try // безопасная попытка { // пытаемся загрузить файл с именем openFileDialog1.FileName - выбранный пользо-вателем файл. MemForImage = Image.FromFile(openFileDialog1.FileName); // устанавливаем картинку в поле элемента PictureBox pictureBox1.Image = MemForImage; } catch (Exception ex) // если попытка загрузки не удалась { // выводим сообщение с причиной ошибки MessageBox.Show( "Не удалось загрузить файл: " + ex.Message); } } }

Page 43: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

43

Функция LoadImage в качестве входного параметра будет получать флаг о том, какой фильтр для выбора файлов необходимо выбрать. Далее вызывается окно выбора файла, и если оно при закрытии возвращает ре-зультат DialogResult.OK, то мы пытаемся загрузить и установить выбран-ную картинку в поле PictureBox. Конструкция try catch необходима нам здесь по следующей причине: если загрузка прошла неудачно, то мы полу-чим сообщение об ошибке, но на выполнение программы это не повлияет, и мы сможем продолжить ее выполнение.

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

// обработка кнопки меню "загрузка - в формате jpg" private void вФорматеJPGToolStripMenuItem_Click(object sender, EventArgs e) { // загружаем jpg файлы LoadImage(true); } // обработка кнопки меню "загрузка - в формате png" private void вФорматеPNGToolStripMenuItem_Click(object sender, EventArgs e) { // загружаем png файлы LoadImage(false); } // обработка кнопки № 2 на панели private void toolStripButton2_Click(object sender, EventArgs e) { // загружаем jpg файлы LoadImage(true); } // обработка кнопки № 3 на панели private void toolStripButton3_Click(object sender, EventArgs e) { // загружаем png файлы LoadImage(false); }

Если теперь откомпилировать приложение (F5), то можно попробо-

вать загрузить изображение. Обратите внимание, что загрузка png24 с аль-фа-каналом тоже работает (рис. 2.27).

Page 44: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

44

Как видите, наше изображение масштабируется под размер элемента PictureBox. Поэтому сейчас мы доба-вим в проект еще одну форму, на ко-торой мы будем отображать картин-ку в ее истинном размере.

Добавление и вызов допол-

нительного диалогового окна Для этого перейдите к окну

Solution Explorer (Проводник реше-ний), после чего щелкните на назва-нии проекта правой кнопкой мыши

и в открывшемся контекстном меню выберите Add->Form, как показано на рис. 2.28.

Рис. 2.28

Рис. 2.27

Page 45: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

45

В открывшемся окне введите название класса, который будет отвечать за генерацию нашего вспомогательного окна – Preview.cs (рис. 2.29).

Рис. 2.29

Теперь измените размер окна так, как показано на рис. 2.30. Затем на

форме разместите элемент panel. Внутри элемента panel разместите эле-мент pictureBox (см. рис. 2.30).

Рис. 2.30

Page 46: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

46

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

Перейдите к свойствам элемента panel и установите значение пара-метра AutoScroll, равное true. Теперь в том случае, если размер изображе-ния будет превышать размер элемента panel1, будут появляться вертикаль-ная и горизонтальная полосы прокрутки, с помощью которых можно будет просмотреть изображение.

Теперь рассмотрим исходный код, необходимый для реализации дан-ных возможностей.

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

Он будет выглядеть следующим образом:

// кнопка активации дополнительного диалогового окна private void toolStripButton1_Click(object sender, EventArgs e) { // создаем новый экземпляр класса Preview, // отвечающего за работу с нашей дополнительной формой // в качестве параметра мы передаем наше загруженное изображение Form PreView = new Preview(MemForImage); // затем мы вызываем диалоговое окно PreView.ShowDialog(); }

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

Перейдите к окну Preview, после чего сделайте двойной щелчок левой клавишей мыши на нем (НЕ на размещенных на нем объектах).

Откроется для редактирования функция

private void Preview_Load(object sender, EventArgs e)

Но мы сначала изменим код конструктора класса, теперь он будет вы-глядеть следующим образом:

Page 47: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

47

public partial class Preview : Form { // объект Image для хранения изображения Image ToView; // модифицируем конструктор окна таким образом, чтобы он получал // в качестве параметра изображение для отображения public Preview(Image view) { // получаем изображение ToView = view; InitializeComponent(); }

Теперь отредактируем код функции Preview_Load. Он будет выгля-

деть следующим образом: // эта функция выполнится при загрузке окна private void Preview_Load(object sender, EventArgs e) {

// если объект, хранящий изображение неравен null if (ToView != null) {

// устанавливаем новые размеры элемента pictureBox1, //равные ширине (ToView.Width) и высоте (ToView.Height) изображения pictureBox1.Size = new Size(ToView.Width, ToView.Height); // устанавливаем изображение для отображения в элементе pictureBox1 pictureBox1.Image = ToView;

} }

В заключение самостоятельно добавьте обработчик нажатия кнопки

"Закрыть". Функция закрытия будет выглядеть следующим образом: // обработчик кнопки "Закрыть" private void button1_Click(object sender, EventArgs e) { // закрываем диалоговое окно Close(); }

Теперь если будет загружено большое изображение, его отображение

в дополнительном окне будет снабжено полосами прокрутки (рис. 2.31).

Page 48: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

48

Рис. 2.31

Откомпилируйте приложение (F5), чтобы проверить его. Контрольные вопросы 1. Назначение Windows.Forms. 2. Порядок разработки оконного приложения в VisualC#. 3. Создание элементов оконного интерфейса в VisualC#.

Тема 3. МНОГОПОТОЧНЫЕ ВЫЧИСЛЕНИЯ

Цель изучения темы. Освоение средств организации многопоточных вычислений в C#.

3.1. Многопоточное программирование в C# Понимание задач и методов организации многопоточного программиро-

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

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

Особенностью многопоточной программы является то, что она может со-стоять из нескольких блоков, каждый из которых выполняет свою часть по-

Page 49: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

49

ставленной задачи. Таким образом, блоки выполняются параллельно. Такая часть программы называется потоком. Среда .NET Framework, в свою очередь, содержит ряд классов, предназначенный для гибкой реализации многопоточ-ных приложений, причем встроенная поддержка многопоточности в C# позво-ляет свести к минимуму проблемы, встречавшиеся в процессе создания мно-гопоточных приложений в некоторых других языках программирования.

Многопоточность может быть ориентирована на потоки и процессы. Основная разница состоит в том, что процесс фактически является отдель-но выполняемой программой, т.е. здесь многопоточность основана на том, что выполняются две программы и более.

Поток (по-английски thread, буквально можно перевести как «нить») – это управляемая единица кода, выполняемая в адресном пространстве по-родившего его потока. Используя многопоточность, мы можем реализо-вать нашу программу таким образом, чтобы один поток просчитывал гра-фику в сцене, визуализировал ее и обновлял окно, а другой − в это же са-мое время просчитывал физические законы, которые происходят в сцене.

Другой пример: программа должна заниматься просчетом математи-ческих алгоритмов. Если вычисление одного уравнения занимает 10 − 15 с, то окно приложения не будет отвечать на запросы операционной системы («зависнет» на 10 − 15 с), пока вычисление не будет завершено, так как программа выполняет строки кода последовательно.

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

Потоки могут как выполняться, так и ожидать выполнения (быть вре-менно приостановленными, заблокированными). Поток может затем возоб-новиться или просто завершиться. Все это – возможные состояния потока.

Многопоточность в среде .NET Framework реализована следующим об-разом: существуют два типа потоков − высокоприоритетный и низкоприори-тетный.

Высокоприоритетный (foreground) поток в отличие от низкоприори-тетного (или фонового − background) назначается как тип потока по умол-чанию. Он не будет остановлен, если все высокоприоритетные потоки в его процессе будут остановлены.

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

Page 50: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

50

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

Классы C#, отвечающие за поддержку многопоточного программиро-вания, определены в пространстве имен System.Threading.

3.2. Базовые методы работы с потоками в C# .net Рассмотрим процесс создания потоков на примере разработки кон-

сольной программы. При старте программы будут запускаться на выпол-нение 4 потока, каждый из которых будет выводить свой номер в окно консоли.

Важными здесь являются базовые методы работы с потоками: созда-ние потоков, запуск, ожидание завершения.

При создании потоков мы установим для них различные приоритеты. Тогда некоторым потокам будет выделяться больше квантов времени про-цессора, и они будут доминировать при выводе своего номера в окно. Мы сможем это наблюдать в ходе работы программы.

Создадим новый проект и назовем его Thread_Step_1. В качестве шаб-лона выберем консольное приложение.

Сгенерированный код первоначально выглядит следующим образом: using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Thread_Step_1 {

class Program {

static void Main(string[] args) { }

} }

Для работы с потоками необходимо подключить пространство имен System.Threading.

Добавим строку using System.Threading после строки using System.Text. Непосредственно перед функцией Main мы добавим функцию

WriteString, которая будет отвечать за вывод символов, назначенных данному потоку (мы будем назначать номер потока) на экран. В качестве параметра функция будет получать объект _Data, который впоследствии будет преобра-зован в строку в коде функции.

Page 51: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

51

Код функции будет выглядеть следующим образом:

static void WriteString(object _Data) {

//для получения строки используем преобразование типов: // приводим переменную _Data к типу string и записываем // в переменную str_for_out string str_for_out = (string) _Data; // теперь поток 1 тысячу раз выведет полученную строку (свой номер) for (int i = 0; i <= 1000; i++) Console.Write(str_for_out);

}

Теперь перейдем к коду функции Main. Здесь мы реализуем следую-щий код:

Сначала создадим 4 потока, каждому из которых укажем, что они бу-дут выполнять функцию WriteString.

Далее мы назначим потокам приоритеты. После этого мы запустим все четыре потока, передав в качестве пара-

метра их номера. Далее нам останется только дождаться завершения всех четырех по-

токов и ожидать ввода пользователем какого-либо символа, чтобы завер-шить выполнение программы.

Код функции Main будет выглядеть следующим образом:

static void Main(string[] args) //точка входа в программу { //создаем 4 потока, в качестве параметров передаем имя Выполняемой функции Thread th_1 = new Thread(WriteString); Thread th_2 = new Thread(WriteString); Thread th_3 = new Thread(WriteString); Thread th_4 = new Thread(WriteString); //расставляем приоритеты для потоков th_1.Priority = ThreadPriority.Highest; // самый высокий th_2.Priority = ThreadPriority.BelowNormal; // выше среднего th_3.Priority = ThreadPriority.Normal; // средний th_4.Priority = ThreadPriority.Lowest; // низкий

// запускаем каждый поток, в качестве параметра передаем номер потока th_1.Start( "1"); th_2.Start( "2"); th_3.Start( "3"); th_4.Start( "4"); Console.WriteLine( "все потоки запущены\n\n"); //Ждем завершения каждого потока th_1.Join();

Page 52: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

52

th_2.Join(); th_3.Join(); th_4.Join(); // прочитать символ: пока пользователь не нажмет клавишу, // программа не завершится (чтобы можно было успеть посмотреть результат) Console.ReadKey();

}

Откомпилируйте и запустите программу (F5). Пример результата работы программы можно увидеть на рисунке. При каждом запуске программы выводимый на экран результат будет

отличаться от предыдущего. Если запустить созданное приложение не из среды Visual Studio (без отладки), а из операционной системы, то до выво-да сообщения «Все потоки запущены» по несколько раз может успеть вы-полниться назначенная функция каждого потока.

Page 53: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

53

Как видно из рисунка, к моменту запуска последнего 4-го потока 1-й поток, имеющий Highest приоритет, уже успел полностью выполниться. Последний же 4-й поток с низшим приоритетом, к тому же запущенный последним, выполнится безусловно после всех других потоков.

Контрольные вопросы 1. Сущность многопоточности. 2. Многопоточность в C# .NET. 3. Базовые методы работы с потоками в C# .NET.

Тема 4. ВВЕДЕНИЕ В OPENGL Цель изучения темы. Изучение принципов применения библиотеки

OpenGL при разработке приложений в C#. 4.1. OpenGL OpenGL означает Open Graphics Library, что переводится как «откры-

тая графическая библиотека». Другими словами, OpenGL − это некая спецификация, включающая в

себя несколько сотен функций. Она определяет независимый от языка про-граммирования кросс-платформенный программный интерфейс, с помо-щью которого программист может создавать приложения, использующие двухмерную и трехмерную компьютерную графику. Первая базовая версия OpenGL появилась в 1992 году, она была разработана компанией Silicon Graphics Inc, занимающейся разработками в области трехмерной компью-терной графики.

В библиотеку заложен механизм расширений, благодаря которому производители аппаратного обеспечения (например, производители ви-деокарт) могли выпускать расширения OpenGL для поддержки новых спе-цифических возможностей, не включенных в текущую версию библиотеки. Благодаря этому программисты могли сразу использовать эти новые воз-можности в отличие от библиотеки Microsoft Direct3D (в этом случае им бы пришлось ждать выхода новой версии DirectX).

Библиотеки OpenGL и DirectX являются конкурентами на платформе MS Windows. Microsoft продвигает свою библиотеку DirectX и стремится замедлить развитие библиотеки OpenGL, что ослабило бы графическую систему конкурирующих ОС, где используется исключительно библиотека OpenGL для реализации вывода всей графики.

Page 54: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

54

Мы будем учиться визуализации компьютерной графики именно с применением этой библиотеки. Однако прямой поддержки данной библио-теки в .NET Framework нет, поэтому мы будем использовать библиотеку Tao Framework.

4.2. TAO Framework Tao Framework – это свободно распространяемая библиотека с откры-

тым исходным кодом, предназначенная для быстрой и удобной разработки кросс-платформенного мультимедийного программного обеспечения в среде .NET Framework и Mono (см. приложение).

На сегодняшний день Tao Framework − оптимальный путь для исполь-зования библиотеки OpenGL при разработке приложений в среде .NET на языке C#.

В состав библиотеки на данный момент входят все современные сред-ства, которые могут понадобиться в ходе разработки мультимедиа про-граммного обеспечения: реализация библиотеки OpenGL, реализация биб-лиотеки FreeGlut, содержащей все самые новые функции этой библиотеки, библиотека DevIL (легшая в основу стандарта OpenIL − Open Image Library) и многие другие.

Самые интересные библиотеки, включенные в Tao Framework:

• OpenGL 2.1.0.12 − свободно распространяемый аппаратно-программный интерфейс для визуализации 2D- и 3D-графики.

• FreeGLUT 2.4.0.2 – библиотека с открытым исходным кодом, яв-ляющаяся альтернативой библиотеке GLUT (OpenGL Utility Toolkit).

• DevIL 1.6.8.3 (она же OpenIL) – кросс-платформенная библиотека, реализующая программный интерфейс для работы с изображениями. На дан-ный момент библиотека поддерживает работу с изображениями 43 форматов для чтения и 17 форматов для записи.

• Cg 2.0.0.0 – язык высокого уровня, созданный для программирова-ния текстурных и вершинных шейдеров.

• OpenAL 1.1.0.1 – свободно распространяемый аппаратно-программный интерфейс для обработки аудиоданных. (В том числе 3D-звука и EAX эффек-тов).

• PhysFS 1.0.1.2 – библиотека для работы с вводом/выводом файло-вой системы, а также различного вида архивами на основе собственного API.

Page 55: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

55

• SDL 1.2.13.0 – кросс-платформенная мультимедийная библиотека, активно использующаяся для написания мультимедийных приложений в операционной системе GNU/Linux.

• ODE 0.9.0.0 − свободно распространяемый физический программ-ный интерфейс, главной особенностью которого является реализация си-стемы динамики абсолютно твёрдого тела и системы обнаружения столк-новений.

• FreeType 2.3.5.0 – библиотека, реализующая растеризацию шриф-тов. Данная библиотека используется в X11 − оконной системе, которая обеспечивает все стандартные инструменты и протоколы для построения GUI (графического интерфейса пользователя) в UNIX подобных операци-онных системах.

• FFmpeg 0.4.9.0 − набор свободно распространяемых библиотек с открытым исходным кодом. Данные мультимедийные библиотеки позво-ляют работать с аудио- и видеоданными в различных форматах.

Таким образом, библиотека Tao Framework является мощным и удоб-ным свободно распространяемым инструментом для решения любых мультимедийных задач, преимущественно кросс-платформенного характе-ра. Работая с данной библиотекой, разработчики могут использовать базу алгоритмов и реализованных за многие годы методов, что сокращает время разработки программных продуктов.

4.3. Инициализация OpenGL в C# Проверим работоспособность библиотеки Tao с использованием те-

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

Создание проекта и подключение библиотеки Tao OpenGL в С# Сначала создайте новый проект, в качестве шаблона установив при-

ложение Windows Forms. Назовите его Tao-OpenGL-Initialization-Test. Дождитесь, пока MS Visual Studio закончит генерацию кода шаблона.

Теперь перейдите к окну Solution Explorer (Проводник решений). Здесь нас интересует узел Links, который отображает связи с библиотеками, необхо-димыми для работы нашего приложения (рис. 4.1).

Page 56: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

56

Рис. 4.1

Назовите главное окно «Тестирование инициализации OpenGL в

С#.NET». (Свойства окна, параметр Text). Щелкните по этому узлу правой клавишей мыши, после чего в от-

крывшемся контекстном меню выберите «Добавить ссылку» (“Add Link”), как показано на рис. 4.2.

Рис. 4.2

В открывшемся окне «Добавить ссылку» перейдите к закладке "Об-зор". После этого перейдите к директории, в которую была установлена биб-лиотека Tao Framework. (По умолчанию “C:\\Program Files\Tao Framework”).

Page 57: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

57

Нам потребуется папка bin, в ней хранятся необходимые нам библиотеки. Перейдите в папку bin и выберите три библиотеки, как показано на рис. 4.3.

Tao.OpenGL.dll отвечает за реализацию библиотеки OpenGL. Tao.FreeGlut.dll отвечает за реализацию функций библиотеки Glut. Мы

будем ее использовать для инициализации рендера, а также для других целей. Tao.Platform.Windows.dll отвечает за поддержку элементов для визуа-

лизации на платформе Windows.

Рис. 4.3

На рис. 4.4 мы видим все

добавившиеся библиотеки в узле «Ссылки» (Links).

Теперь перейдите к ис-ходному коду окна. Для рабо-ты с нашими библиотеками необходимо подключить со-ответствующие пространства имен:

Рис. 4.4

Page 58: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

58

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; // для работы с библиотекой OpenGL using Tao.OpenGl; // для работы с библиотекой FreeGLUT using Tao.FreeGlut; // для работы с элементом управления SimpleOpenGLControl using Tao.Platform.Windows;

Теперь вернитесь к конструктору диалогового окна и перейдите к ок-

ну Toolbox (панель элементов). Щелкните правой кнопкой на вкладке «Общие» и в раскрывшемся контекстном меню выберите пункт «Выбрать элементы» (Choose Items), как показано на рис. 4.5.

Рис. 4.5

В открывшемся окне найдите элемент SimpleOpenGLControl и устано-

вите возле него галочку, как показано на рис. 4.6. Затем нажмите ОК.

Page 59: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

59

Рис. 4.6

Теперь данный элемент станет доступным для размещения на форме приложения. Перетащите элемент на форму и разместите так, как показано на рис. 4.7. Справа от размещенного элемента установите 2 кнопки – «Ви-зуализировать» и «Выйти».

Рис. 4.7

Page 60: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

60

Теперь выделите элемент simpleOpenGlControl1, расположенный на форме и перейдите к его свойствам. Измените параметр name на значение

“AnT”. Далее элементы simpleOpenGl Control мы будем называть AnT (рис. 4.8).

Инициализация OpenGL в C#.NET Теперь необходимо инициализи-

ровать работу OpenGl. Сначала в конструкторе класса

мы должны инициализировать работу элемента AnT:

public Form1() {

InitializeComponent(); AnT.InitializeContexts();

}

Снова перейдите к конструктору и сделайте двойной щелчок левой клавишей мыши на форме – создастся функция обработчик события за-грузки формы.

В ней мы поместим код инициализации OpenGl. Подробное описание данного кода будет рассмотрено в следующих темах, а сейчас мы только протестируем работу библиотек OpenGL и FreeGLUT.

private void Form1_Load(object sender, EventArgs e) { // инициализация Glut Glut.glutInit(); Glut.glutInitDisplayMode (Glut.GLUT_RGB | Glut.GLUT_DOUBLE | Glut.GLUT_DEPTH); // очистка окна Gl.glClearColor(255, 255, 255, 1); // установка порта вывода в соответствии с размерами элемента anT Gl.glViewport(0, 0, AnT.Width, AnT.Height); // настройка проекции

Gl.glMatrixMode(Gl.GL_PROJECTION); Gl.glLoadIdentity(); Glu.gluPerspective(45, (float)AnT.Width / (float)AnT.Height, 0.1, 200); Gl.glMatrixMode(Gl.GL_MODELVIEW); Gl.glLoadIdentity();

Рис. 4.8

Page 61: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

61

// настройка параметров OpenGL для визуализации Gl.glEnable(Gl.GL_COLOR_MATERIAL); Gl.glEnable(Gl.GL_DEPTH_TEST); Gl.glEnable(Gl.GL_LIGHTING); Gl.glEnable(Gl.GL_LIGHT0);

}

На окне мы создали 2 кнопки. Обработчик кнопки "Выйти" будет вы-глядеть следующим образом:

//обработчик кнопки "выйти" private void button2_Click(object sender, EventArgs e) {

Application.Exit(); }

Обработчик кнопки «Визуализировать» будет содержать код, реали-зующий визуализацию сеточного каркаса сферы (за отрисовку трехмерной сферы будет отвечать библиотека FreeGLUT). Код, который будет разме-щен в данной функции, отвечает за разные технические аспекты визуали-зации, с которыми мы познакомимся в следующих темах.

Код функции: // обработчик кнопки "визуализировать" private void button1_Click(object sender, EventArgs e) {

Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT); Gl.glLoadIdentity(); Gl.glColor3ub(255, 0, 0); Gl.glPushMatrix(); Gl.glTranslated(0,0,-6); Gl.glRotated(45, 1, 1, 0); // рисуем сферу с помощью библиотеки FreeGLUT Glut.glutWireSphere(2, 32, 32); Gl.glPopMatrix(); Gl.glFlush(); AnT.Invalidate();

}

Откомпилируйте и запустите приложение. Если при компиляции приложения (по кнопке F5) Microsoft Visual

Studio выдает ошибку с сообщением о том, что не найдена какая-либо биб-лиотека OpenGL (например, freeglut.dll), необходимо в настройках опера-ционной системы в переменную среды PATH добавить каталог C:\Program

Page 62: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

62

Files\ TaoFramework\lib. При невозможности это сделать, допустимо из ка-талога C:\Program Files\TaoFramework\lib скопировать все файлы необхо-димых библиотек (в нашем случае − freeglut.dll) в каталог, уже включен-ный в переменную среды, например C:\WINDOWS\system32.

Результат работы приложения показан на рис. 4.9. Если вы правильно набрали исходные коды и выполнили все описанные действия, то после нажатия на кнопке «Визуализировать» вы увидите аналогичную визуали-зацию сферы.

Рис. 4.9

Итак, мы протестировали работоспособность библиотеки Tao, иници-

ализировав библиотеку OpenGL в C#.NET. Контрольные вопросы 1. Сущность и назначение OpenGL. 2. Назначение Tao Framework. 3. Порядок установки и подключения библиотек TAO. 4. Поддержка OpenGL в VisualC#. 5. Инициализация OpenGL в C#.

Page 63: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

63

Тема 5. ИНИЦИАЛИЗАЦИЯ OPENGL Цель изучения темы. Изучение принципов инициализации OpenGL и

визуализации объектов при разработке приложений в C#. 5.1. Инициализация OpenGL в C# Этот пунк посвящен начальной инициализации OpenGL, которая

предшествует визуализации любой трехмерной сцены: будет рассмотрен код приложения и объяснено, каким образом происходят инициализация OpenGL и визуализация объектов.

Инициализация OpenGl После того как объект SimpleOpenGLControl прошел инициализацию,

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

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

private void Form1_Load(object sender, EventArgs e) {

// инициализация Glut Glut.glutInit(); Glut.glutInitDisplayMode(Glut.GLUT_RGB | Glut.GLUT_DOUBLE |

Glut.GLUT_DEPTH); // очистка окна Gl.glClearColor(255, 255, 255, 1); // установка порта вывода в соответствии с размерами элемента AnT Gl.glViewport(0, 0, AnT.Width, AnT.Height); // настройка проекции Gl.glMatrixMode(Gl.GL_PROJECTION); Gl.glLoadIdentity(); Glu.gluPerspective(45, (float)AnT.Width / (float)AnT.Height, 0.1, 200); Gl.glMatrixMode(Gl.GL_MODELVIEW); Gl.glLoadIdentity(); // настройка параметров OpenGL для визуализации Gl.glEnable(Gl.GL_DEPTH_TEST); Gl.glEnable(Gl.GL_LIGHTING); Gl.glEnable(Gl.GL_LIGHT0);

}

Здесь в первую очередь выполняется инициализация библиотеки Glut.

Page 64: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

64

Как видно из кода, для работы с функциями библиотеки OpenGL ис-пользуется класс Gl, находящийся в пространстве имен Tao.OpenGL.

Для работы с функциями библиотеки Glut используется класс Glut. Таким образом, по сравнению с использованием этих библиотек в

С++ мы всего лишь вызываем методы из классов соответствующих биб-лиотек, где они очень удобно описаны.

Мы обязательно должны вызвать функцию glutInit() перед тем, как начнем использовать любые другие функции данной библиотеки, так как эта функция производит инициализацию библиотеки Glut.

Далее мы вызываем функцию Glut.glutInitDisplayMode(Glut.GLUT_RGB | Glut.GLUT_DOUBLE |

Glut.GLUT_DEPTH); Эта функция устанавливает режим отображения. В нашем случае

устанавливается режим RGB для визуализации (GLUT_RGB – это псевдо-ним GLUT_RGBA, он устанавливает режим RGBA битовой маски окна). Далее мы устанавливаем двойную буферизацию окна, которая, как прави-ло, используется для устранения мерцания, возникающего в процессе быстрой перерисовки кадров несколько раз подряд. GLUT_DEPTH указы-вает при инициализации окна, будет ли в приложении использоваться бу-фер глубины.

После инициализации окна мы устанавливаем цвет очистки окна с помощью функции

Gl.glClearColor(255, 255, 255, 1); Перед дальнейшим изучением кода перечислим этапы создания сцены

в OpenGL: 1. Позиционирование объема видимости в пространстве (установка координат камеры).

2. Установка в данном пространстве модели (объекта), которая будет попадать в объем видимости нашей камеры.

3. Проецирование, которое определяет форму объема видимости. 4. В рамках порта просмотра мы получаем изображение объекта. Теперь нам необходимо определить значение порта вывода, устанав-

ливая значения в функции Gl.glViewport(0, 0, AnT.Width, AnT.Height); Здесь мы указываем библиотеке OpenGL на то, что вывод будет осу-

ществляться во всей области элемента AnT (элемент, расположенный на форме для визуализации в него сцены). Определяем тот самый порт про-смотра, в который последним шагом будет визуализироваться модель.

Page 65: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

65

После этого происходит настройка проекции. Для этого мы сначала вызываем функцию

Gl.glMatrixMode(Gl.GL_PROJECTION); Функция glMatrixMode предназначена для того, чтобы задавать мат-

ричный режим: будет определена матрица, над которой в дальнейшем бу-дут производиться операции. В нашем случае это GL_PROJECTION – мат-рица проекций.

Следующей командой мы очищаем матрицу с помощью функции glLoadIdentity (функция заменяет текущую матрицу на единичную). Далее мы устанавливаем тип текущей проекции с помощью функции gluPerspective. Gl.glLoadIdentity(); Glu.gluPerspective(45, (float)AnT.Width / (float)AnT.Height, 0.1, 200);

Функция gluPerspective определена в библиотеке GLU – OpenGL Utili-ty Library (GLU). Эта библиотека является надстройкой над библиотекой OpenGL, реализующей ряд более продвинутых функций. Она также явля-ется свободно распространяемой и поставляется вместе с библиотекой OpenGL.

Данная функция строит пирамиду охвата видимости, основываясь на угле визуального охвата, отношении сторон порта просмотра и установке ближней и дальней плоскости просмотра (рис. 5.1).

Рис. 5.1 Теперь, когда проекция определена, мы устанавливаем в качестве те-

кущей матрицы объектно-видовую матрицу и очищаем ее.

Page 66: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

66

Gl.glMatrixMode(Gl.GL_MODELVIEW); Gl.glLoadIdentity();

Теперь остается только включить некоторые опции, необходимые для корректной визуализации сцены. Это тест глубины, а также отображение цветов материалов. Gl.glEnable(Gl.GL_DEPTH_TEST); Gl.glEnable(Gl.GL_COLOR_MATERIAL);

Визуализация объектов Теперь рассмотрим функцию, визуализирующую трехмерную сферу. Код этой функции:

// обработчик кнопки "визуализировать" private void button1_Click(object sender, EventArgs e) {

Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT);

Gl.glLoadIdentity(); Gl.glPushMatrix(); Gl.glTranslated(0,0,-6); Gl.glRotated(45, 1, 1, 0); // рисуем сферу с помощью библиотеки FreeGLUT Glut.glutWireSphere(2, 32, 32); Gl.glPopMatrix(); Gl.glFlush(); AnT.Invalidate();

}

Когда пользователь нажимает на кнопку и вызывается данная функ-ция, первым делом производится очистка окна (так как до этого уже мог быть реализован какой-либо вывод; очистка экрана перед визуализацией кадра − это стандартный шаг).

Для этого используется функция glClear. В качестве параметра функ-ция получает наименования буферов, которые необходимо очистить; в нашем случае это буфер цвета и буфер глубины.

Далее мы очищаем объектно-видовую матрицу – таким образом каме-ра устанавливается в начало координат. Теперь мы можем совершить ее перемещение в пространстве.

Но перед этим мы вызываем функцию Gl.glColor3f(255, 0, 0);

Page 67: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

67

устанавливающую красный цвет для отрисовки (основывается на RGB со-ставляющих).

Займемся перемещением. Функция glPushMatrix помещает текущую матрицу в стек матриц, откуда в дальнейшем мы сможем ее вернуть с по-мощью функции glPopMatrix.

Таким способом мы осуществим перемещение отрисовываемого объ-екта в пространстве, не изменив саму матрицу, отвечающую за положение камеры (наблюдателя).

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

Сохранив матрицу в стеке, мы производим перемещение объекта на 6 единиц по оси Z, после чего выполняем поворот сцены на 45 градусов сра-зу по двум осям Gl.glTranslated(0,0,-6); Gl.glRotated(45, 1, 1, 0);

После этого мы выполняем рисование объекта в той области, куда мы переместились (сфера радиусом 2 в виде сетки). Сфера будет разбита на 32 меридиана и 32 параллели.

Для визуализации используется библиотека FreeGlut. // рисуем сферу с помощью библиотеки FreeGLUT Glut.glutWireSphere(2, 32, 32);

Возвращаем сохраненную в стеке матрицу Gl.glPopMatrix(); Дожидаемся, пока библиотека OpenGL завершит визуализацию этого

кадра Gl.glFlush();

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

AnT.Invalidate(); На этом визуализация объекта заканчивается. 5.2. Визуализация 2D-примитивов Процесс рисования всех объектов в компьютерной графике сводится к

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

Page 68: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

68

ены простейшие геометрические фигуры – полигоны. Полигоны могут со-здавать сетку, которая представляет в трехмерном пространстве какую-либо модель.

Полигон – это область в пространстве, которая ограничивается одной замкнутой ломаной линией, причем каждый отрезок данной ломаной ли-нии задается с помощью координат двух точек − начальной и конечной. Описанный таким образом полигон может оказаться очень сложным гео-метрическим объектом.

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

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

Для рисования примитивов в OpenGL мы будем использовать различ-ные режимы визуализации, но сначала рассмотрим, как происходит указа-ние вершин в OpenGl.

Для указания вершин используется специальная команда glVertex*() Данная функция имеет следующий формат: void glVertex{234}{ifd}(TYPE coords); Одной цифрой (2, 3 или 4) обозначается то количество координат, ко-

торыми будет задаваться вершина. В OpenGL точки задаются 4 координа-тами: x, y, z, r. Таким образом, положение точки описывается как x/r, y/r, z/r. Когда параметр r не указывается, он принимается равным единице. Ес-ли указываются точки в двухмерной системе координат, параметр z счита-ется равным нулю.

Символ i, f или d обозначает тип принимаемых значений. Например, если написать команду glVertex3f(,,) то в скобках должны последовать три переменных типа float. Для d – double, для i – int.

Вызов функции glVertex может привести к желаемому результату, только если он сделан между вызовами функций glBegin() и glEnd().

Для построения объекта из точек используются разные режимы, кото-рые описываются различными правилами объединения вершин в полигоны.

Такие режимы рисования возможны лишь с помощью перечисления точек между командами glBegin() и glEnd(), причем при вызове функции glBegin указывается, в каком режиме будут соединяться полигоны.

Значения параметров, которые может принимать glBegin: GL_POINTS − на месте каждой указанной с помощью команды

glVertex вершины рисуется точка.

Page 69: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

69

GL_LINES − каждые 2 вершины объединяются в отрезок. GL_LINE_STRIP − вершины соединяются последовательно, образуя

кривую линию. GL_LINE_LOOP − то же, что и GL_LINE_STRIP, но от последней точ-

ки будет идти отрезок к начальной точке. GL_TRIANGLES − каждые три вершины объединятся в треугольник. GL_TRIANGLE_STRIP − треугольники рисуются таким образом, что

последнее ребро треугольника является начальным ребром следующего. GL_TRIANGLE_FAN − то же, что и GL_TRIANGLE_STRIP, только из-

менен порядок следования вершин. GL_QUADS − каждые четыре вершины объединяются в квадрат. GL_QUAD_STRIP − вершины объединяются в квадраты, причем по-

следнее ребро квадрата является первым ребром следующего. GL_POLYGON − рисуется полигон на основе вершин; вершин не

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

Теперь попробуем нарисовать какой-либо рисунок. Для этого создайте приложение, основываясь на программе из п. 4.3 (а именно подключите необходимые ссылки к библиотекам Tao, подключите необходимые про-странства имен, расположите элементы на окне, а также назначьте необхо-димые обработчики событий и инициализируйте необходимые элементы).

Эта программа будет отличаться от программы, написанной в п. 4.3, лишь тем, что здесь будут использоваться другие проекции в функции Form1_Load и код, отвечающий за визуализацию в функции button1_Click.

Перейдя к свойствам формы, измените название окна на «Рисование 2D-примитивов».

Рассмотрим код функции Form1_Load. Теперь он будет выглядеть следующим образом.

private void Form1_Load(object sender, EventArgs e) { // инициализация Glut Glut.glutInit(); Glut.glutInitDisplayMode(Glut.GLUT_RGB | Glut.GLUT_DOUBLE | Glut.GLUT_DEPTH); // очистка окна Gl.glClearColor(255, 255, 255, 1); // установка порта вывода в соответствии с размерами элемента AnT

Page 70: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

70

Gl.glViewport(0, 0, AnT.Width, AnT.Height); // настройка проекции Gl.glMatrixMode(Gl.GL_PROJECTION); Gl.glLoadIdentity(); // теперь необходимо корректно настроить 2D ортогональную проекцию // в зависимости от того, какая сторона больше // мы немного варьируем то, как будут сконфигурированы настройки проекции if ((float)AnT.Width <= (float)AnT.Height) {

Glu.gluOrtho2D(0.0, 30.0 * (float)AnT.Height / (float)AnT.Width, 0.0, 30.0); } else {

Glu.gluOrtho2D(0.0, 30.0 * (float)AnT.Width / (float)AnT.Height, 0.0, 30.0); } Gl.glMatrixMode(Gl.GL_MODELVIEW); Gl.glLoadIdentity(); // настройка параметров OpenGL для визуализации Gl.glEnable(Gl.GL_DEPTH_TEST); Gl.glEnable(Gl.GL_COLOR_MATERIAL);

}

Как видно из кода, за настройку 2D ортогональной проекции отвечает функция gluOrtho2D, реализация которой предоставлена библиотекой Glu. Эта функция помещает начало координат в самый левый нижний квадрат, а камера (наблюдатель) в таком случае находится на оси Z; таким образом визуализируется графика в 2D-режиме.

Мы должны передать в качестве параметров координаты области ви-димости окна проекции: left, right, bottom и top. В нашем случае мы пере-даем параметры таким образом, чтобы исключить искажения, связанные с тем, что область вывода не квадратная, поэтому параметр top рассчитыва-ется как произведение числа 30 и отношения высоты элемента AnT (в ко-тором будет идти визуализация) к его ширине.

Если высота больше ширины, используется отношение ширины к вы-соте.

Теперь рассмотрим код функции button1_Click. В ней будет производиться очистка буфера цвета кадра, после чего

будет очищаться текущая объектно-видовая матрица. Затем мы будем в режиме рисования линий задавать координаты для того, чтобы нарисовать объект − букву А.

Page 71: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

71

После этого мы перерисовываем содержимое элемента AnT. Перед тем как нарисовать наш объект (букву А), попробуем просто

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

private void button1_Click(object sender, EventArgs e) { // очищаем буфер цвета и глубины Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT); // очищаем текущую матрицу Gl.glLoadIdentity(); // устанавливаем текущий цвет - красный Gl.glColor3f(255, 0, 0); // активируем режим рисования линий на основе // последовательного соединения всех вершин в отрезки Gl.glBegin(Gl.GL_LINE_STRIP); // первая вершина будет находиться в начале координат Gl.glVertex2d(0, 0); // теперь в зависимости от того, как была определена проекция, if ((float)AnT.Width <= (float)AnT.Height) { // рисуем вторую вершину в противоположенном углу Gl.glVertex2d(30.0f * (float)AnT.Height / (float)AnT.Width, 30); } else { // рисуем вторую вершину в противоположенном углу Gl.glVertex2d(30.0f * (float)AnT.Width / (float)AnT.Height, 30); } // завершаем режим рисования Gl.glEnd(); // дожидаемся конца визуализации кадра Gl.glFlush(); // посылаем сигнал перерисовки элемента AnT. AnT.Invalidate(); }

Теперь если откомпилировать проект и запустить приложение (F5), а

затем нажать на кнопку «визуализировать», мы увидим следующий ре-зультат (рис. 5.2).

Page 72: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

72

Рис. 5.2

Теперь заменим код

Gl.glBegin(Gl.GL_LINE_STRIP); // первая вершина будет находиться в начале координат Gl.glVertex2d(0, 0); // теперь в зависимости от того, как была определена проекция if ((float)AnT.Width <= (float)AnT.Height) {

// рисуем вторую вершину в противоположенном углу Gl.glVertex2d(30.0f * (float)AnT.Height / (float)AnT.Width, 30);

} else {

// рисуем вторую вершину в противоположенном углу Gl.glVertex2d(30.0f * (float)AnT.Width / (float)AnT.Height, 30);

} // завершаем режим рисования Gl.glEnd();

на код, который будет рисовать букву А. Нам необходимо правильно указать координаты вершин. Букву будем

рисовать с помощью двух линий.

Page 73: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

73

Форма объекта и координаты соответствующих вершин представлены на рис. 5.3.

Рис. 5.3

Код рисования буквы А (буква будет рисоваться двумя линиями)

// активируем режим рисования линий на основе // последовательного соединения всех вершин отрезками Gl.glBegin(Gl.GL_LINE_LOOP); // первая вершина будет находиться в начале координат Gl.glVertex2d(8, 7); Gl.glVertex2d(15, 27); Gl.glVertex2d(17, 27); Gl.glVertex2d(23, 7); Gl.glVertex2d(21, 7); Gl.glVertex2d(19, 14); Gl.glVertex2d(12.5, 14); Gl.glVertex2d(10, 7); Gl.glEnd();

Page 74: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

74

// вторая линия Gl.glBegin(Gl.GL_LINE_LOOP); Gl.glVertex2d(18.5, 16); Gl.glVertex2d(16, 25); Gl.glVertex2d(13.2, 16); Gl.glEnd();

Пример нарисованной в программе буквы представлен на рис. 5.4.

Рис. 5.4

5.3. Визуализация графика функции Рассмотрим процесс создания программы, задачей которой будет ви-

зуализация графика заданной функции с анимационной демонстрацией изменения значения функции на графике.

Это будет реализовано следующим образом: по графику двигается красная точка, принимающая значения y для заданного x в нашем графике (по всей видимой области).

Кроме того, возле курсора будут визуализироваться его координаты (рис. 5.5).

Page 75: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

75

Рис. 5.5

Создайте основу приложения,

как это было описано в п. 4.3, но не добавляйте кнопки «Визуализиро-вать» и «Закрыть», ограничьтесь элементом SimpleOpenGLControl.

Окно должно иметь форму, представленную на рис. 5.5.

Добавьте в проект один таймер. Для того чтобы добавить этот объ-ект, перейдите к окну ToolBox, вы-берите в свитке «Компоненты» эле-мент Timer (рис. 5.6) и перетащите его на форму.

Рис. 5.6

Page 76: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

76

Место, куда вы перетащите этот элемент, не важно, поскольку по-следний не занимает его на форме и отобразится в полоске таких же («не занимающих место») объектов под формой.

Щелкните по элементу и перейдите в его свойства. Смените параметр name в свойствах таймера на значение PointInGrap,

а параметр Interval − на значение 30 (раз в 30 мс будет вызываться событие OnTimer).

Теперь щелкните по значку таймера на форме двойным щелчком ле-вой клавиши мыши. Создастся функция PointInGrap_Tick, отвечающая за обработку события OnTimer. Отсюда мы будем затем вызывать функцию Draw.

Теперь, когда основа приложения создана, мы перейдем к исходному коду.

Он будет основан на семи функциях, которые мы сейчас рассмотрим, но сначала перед кодом функции-конструктора класса добавьте инициали-зацию следующих переменных:

// размеры окна double ScreenW, ScreenH; // отношения сторон окна визуализации // для корректного перевода координат мыши в координаты, // принятые в программе private float devX; private float devY; // массив, который будет хранить значения x,y точек графика private float[,] GrapValuesArray; // количество элементов в массиве private int elements_count = 0; // флаг, означающий, что массив со значениями координат графика пока еще не за-полнен private bool not_calculate = true; // номер ячейки массива, из которой будут взяты координаты для красной точки // для визуализации текущего кадра private int pointPosition = 0; // вспомогательные переменные для построения линий от курсора мыши к коорди-натным осям float lineX, lineY; // текущие координаты курсора мыши float Mcoord_X = 0, Mcoord_Y = 0;

Перед каждой переменной в комментарии приведено ее назначение. Теперь вернемся к нашим семи функциям.

Page 77: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

77

Первая функция – это обработчик события загрузки формы − Form1_Load. Здесь при загрузке приложения будет произведена инициали-зация OpenGL для последующей визуализации.

Инициализацию OpenGL с двухмерной проекцией мы рассматривали ранее. Код этой функции с более подробными комментариями:

private void Form1_Load(object sender, EventArgs e) {

// инициализация библиотеки glut Glut.glutInit(); // инициализация режима экрана Glut.glutInitDisplayMode(Glut.GLUT_RGB | Glut.GLUT_DOUBLE); // установка цвета очистки экрана (RGBA) Gl.glClearColor(255, 255, 255, 1); // установка порта вывода Gl.glViewport(0, 0, AnT.Width, AnT.Height); // активация проекционной матрицы Gl.glMatrixMode(Gl.GL_PROJECTION); // очистка матрицы Gl.glLoadIdentity(); // определение параметров настройки проекции в зависимости от размеров сторон

элемента AnT. if ((float)AnT.Width <= (float)AnT.Height) {

ScreenW = 30.0; ScreenH = 30.0 * (float)AnT.Height / (float)AnT.Width; Glu.gluOrtho2D(0.0, ScreenW, 0.0, ScreenH);

} else {

ScreenW = 30.0 * (float)AnT.Width / (float)AnT.Height; ScreenH = 30.0; Glu.gluOrtho2D(0.0, 30.0 * (float)AnT.Width / (float)AnT.Height, 0.0, 30.0);

} // сохранение коэффициентов, которые нам необходимы для перевода координат

указателя в оконной системе, в координаты // принятые в нашей OpenGL сцене devX = (float)ScreenW / (float)AnT.Width; devY = (float)ScreenH / (float)AnT.Height; // установка объектно-видовой матрицы Gl.glMatrixMode(Gl.GL_MODELVIEW); // старт счетчика, отвечающего за вызов функции визуализации сцены PointInGrap.Start();

} Теперь обратимся к функции PointInGrap_Tick. Эта функция вызыва-

ется с задержкой в 30 мс.

Page 78: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

78

В ней определяется, из какого элемента массива с координатами графика мы возьмем координаты, которые используем для рисования красной точки.

Отсюда также вызывается функция Draw, отвечающая за визуализацию. Код этой функции:

// функция обработчик события таймера private void PointInGrap_Tick(object sender, EventArgs e) {

// если мы дошли до последнего элемента массива if (pointPosition == elements_count-1) pointPosition = 0; // переходим к начальному элементу // функция визуализации Draw(); // переход к следующему элементу массива pointPosition++;

}

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

Начнем с функции AnT_MouseMove. Эта функция добавляется созданием события MouseMove для элемен-

та SimpleOpnGLControl (AnT). Событие создается аналогично тому, как мы его создавали в п. 2.2. Только в данном случае мы переходим к свойствам элемента AnT и уже в них переходим во вкладку Event, добавляем событие MouseMove.

В данной функции мы производим сохранение текущих координат мыши, чтобы в будущем использовать их при визуализации графика, а также производим вычисление размеров двух красных линий, которые бу-дут по нормалям соединять координаты указателя мыши с координатными осями (рис. 5.5).

Код этой функции выглядит следующим образом: // обработка движения мыши над элементом AnT private void AnT_MouseMove(object sender, MouseEventArgs e) {

// сохраняем координаты мыши Mcoord_X = e.X; Mcoord_Y = e.Y; // вычисляем параметры для будущей дорисовки линий от указателя мыши к коор-

динатным осям lineX = devX * e.X; lineY = (float)(ScreenH - devY * e.Y);

}

Page 79: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

79

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

Эта функция устанавливает координаты вывода растровых символов в соответствии с координатами, переданными в параметрах x и y, а затем в цикле перебирает все символы из указанной в параметре строки текста. Каждый символ визуализируется с помощью функции glutBitmapCharacter. В этой функции указывается шрифт для вывода и переменная типа char для визуализации.

Код функции выглядит следующим образом: // функция визуализации текста private void PrintText2D(float x, float y, string text) {

// устанавливаем позицию вывода растровых символов // в переданных координатах x и y Gl.glRasterPos2f(x, y); // в цикле foreach перебираем значения из массива text, // который содержит значение строки для визуализации foreach (char char_for_draw in text) {

// визуализируем символ с помощью функции glutBitmapCharacter, // используя шрифт GLUT_BITMAP_9_BY_15 Glut.glutBitmapCharacter(Glut.GLUT_BITMAP_9_BY_15, char_for_draw);

} }

Следующая функция, которая нам понадобится, − это функция, вы-

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

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

Код этой функции с комментариями: // функция, производящая вычисления координат графика // и заносящая их в массив GrapValuesArray private void functionCalculation() {

Page 80: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

80

// определение локальных переменных X и Y float x = 0, y = 0; // инициализация массива, который будет хранить значение 300 точек // из которых будет состоять график GrapValuesArray = new float[300, 2]; // счетчик элементов массива elements_count = 0; // вычисления всех значений y для x, принадлежащего промежутку от -15 до 15, с

шагом в 0.01f for (x = -15; x < 15; x += 0.1f) {

// вычисление y для текущего x // по формуле y = (float)Math.Sin(x)*3 + 1; // эта строка задает формулу, описывающую график функции для нашего урав-

нения y = f(x). y = (float)Math.Sin(x)*3 + 1; // запись координаты x GrapValuesArray[elements_count, 0] = x; // запись координаты y GrapValuesArray[elements_count, 1] = y; // подсчет элементов elements_count++;

} // изменяем флаг, сигнализировавший о том, что координаты графика не вычисле-

ны not_calculate = false;

}

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

че функции визуализации сцены, вынесем визуализацию графика в от-дельную функцию (чтобы не загромождать функцию визуализации сцены).

В этой функции сначала будет проверен флаг, сигнализирующий о том, что координаты графика вычислены и занесены в массив (переменная not_calculate). В том случае если флаг указывает, что просчета значений еще не было, вызывается функция, которая посчитает значения координат точек графика и заполнит ими массив.

Далее реализуется проход циклом for по массиву значений координат точек графика и их визуализация, причем мы визуализируем не точки, а соединяем эти точки линией, основываясь на значении координат точек (вершины линии).

Page 81: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

81

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

Исходный код данной функции: // визуализация графика private void DrawDiagram() {

// проверка флага, сигнализирующего о том, что координаты графика вычислены if (not_calculate) {

// если нет, то вызываем функцию вычисления координат графика functionCalculation();

} // стартуем отрисовку в режиме визуализации точек // объединяемых в линии (GL_LINE_STRIP) Gl.glBegin(Gl.GL_LINE_STRIP); // рисуем начальную точку Gl.glVertex2d(GrapValuesArray[0, 0], GrapValuesArray[0, 1]); // проходим по массиву с координатами вычисленных точек for (int ax = 1; ax < elements_count; ax+=2) {

// передаем в OpenGL информацию о вершине, участвующей в построении ли-ний

Gl.glVertex2d(GrapValuesArray[ax, 0], GrapValuesArray[ax, 1]); } // завершаем режим рисования Gl.glEnd(); // устанавливаем размер точек, равный 5 пикселям Gl.glPointSize(5); // устанавливаем текущий цвет - красный Gl.glColor3f(255, 0, 0); // активируем режим вывода точек (GL_POINTS) Gl.glBegin(Gl.GL_POINTS); // выводим красную точку, используя ту ячейку массива, до которой мы дошли

(вычисляется в функии-обработчике событий таймера) Gl.glVertex2d(GrapValuesArray[pointPosition, 0], GrapValuesArray[pointPosition, 1]); // завершаем режим рисования Gl.glEnd(); // устанавливаем размер точек, равный единице Gl.glPointSize(1);

}

Теперь нам осталось рассмотреть последнюю функцию – Draw. В ней визуализируется координатная сетка под графиком, координат-

ные оси и буквы для их обозначений, а также вызывается функция рисова-

Page 82: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

82

ния графика и выводятся координаты мыши с линиями, соединяющими указатель мыши и оси координат.

Код этой функции выглядит следующим образом.

// функция, управляющая визуализацией сцены private void Draw() { // очистка буфера цвета и буфера глубины Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT); // очистка текущей матрицы Gl.glLoadIdentity(); // установка черного цвета Gl.glColor3f(0, 0, 0); // помещаем состояние матрицы в стек матриц Gl.glPushMatrix(); // выполняем перемещение в пространстве по осям X и Y Gl.glTranslated(15, 15, 0); // активируем режим рисования (указанные далее точки будут выводиться как точки GL_POINTS) Gl.glBegin(Gl.GL_POINTS); // с помощью прохода двумя циклами создаем сетку из точек for (int ax = -15; ax < 15; ax++) { for (int bx = -15; bx < 15; bx++) { // вывод точки Gl.glVertex2d(ax, bx); } } // завершение режима рисования примитивов Gl.glEnd(); // активируем режим рисования, каждые две точки, последовательно вызванные ко-мандой glVertex, объединяются в линию Gl.glBegin(Gl.GL_LINES); // рисуем координатные оси и стрелки на них Gl.glVertex2d(0, -15); Gl.glVertex2d(0, 15); Gl.glVertex2d(-15, 0); Gl.glVertex2d(15, 0); // вертикальная стрелка Gl.glVertex2d(0, 15); Gl.glVertex2d(0.1, 14.5);

Page 83: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

83

Gl.glVertex2d(0, 15); Gl.glVertex2d(-0.1, 14.5); // горизонтальная стрелка Gl.glVertex2d(15, 0); Gl.glVertex2d(14.5, 0.1); Gl.glVertex2d(15, 0); Gl.glVertex2d(14.5, -0.1); // завершаем режим рисования Gl.glEnd(); // выводим подписи осей "x" и "y" PrintText2D(15.5f, 0, "x"); PrintText2D(0.5f, 14.5f, "y"); // вызываем функцию рисования графика DrawDiagram(); // возвращаем матрицу из стека Gl.glPopMatrix(); // выводим текст со значением координат возле курсора PrintText2D(devX * Mcoord_X + 0.2f, (float)ScreenH - devY * Mcoord_Y + 0.4f, "[ x: " + (devX * Mcoord_X - 15).ToString() + " ; y: " + ((float)ScreenH - devY * Mcoord_Y - 15).ToString() + "]"); // устанавливаем красный цвет Gl.glColor3f(255, 0, 0); // включаем режим рисования линий, для того чтобы нарисовать // линии от курсора мыши к координатным осям Gl.glBegin(Gl.GL_LINES); Gl.glVertex2d(lineX, 15); Gl.glVertex2d(lineX, lineY); Gl.glVertex2d(15, lineY); Gl.glVertex2d(lineX, lineY); Gl.glEnd(); // дожидаемся завершения визуализации кадра Gl.glFlush(); // сигнал для обновления элемента, реализующего визуализацию AnT.Invalidate(); }

В этой функции также нет ничего сложного, необходимо только разо-

браться с координатным методом построения примитивов. Если какая-либо часть кода остается непонятной, измените параметры в коде и по-смотрите на результат этих изменений, чтобы сделать вывод о работе про-граммы.

Page 84: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

84

5.4. Вывод 2D цветового треугольника Рассмотрим программу отрисовки двухмерного треугольника, каждой

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

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

Создадим новый проект и реализуем в нем систему визуализации, ос-нованную на объекте SimpleOpenGLControl и подключении ссылок к биб-лиотекам Tao, как это было рассмотрено в п. 4.3. Настройки инициализа-ции OpenGL (код функции Form1_Load будут соответствовать коду, пред-ставленному в предыдущем пункте).

Приведем еще раз код функции-обработчика события Form1_Load: private void Form1_Load(object sender, EventArgs e) {

// инициализация библиотеки GLUT Glut.glutInit(); // инициализация режима окна Glut.glutInitDisplayMode(Glut.GLUT_RGB | Glut.GLUT_DOUBLE |

Glut.GLUT_DEPTH); // устанавливаем цвет очистки окна Gl.glClearColor(255, 255, 255, 1); // устанавливаем порт вывода, основываясь на размерах элемента управления AnT Gl.glViewport(0, 0, AnT.Width, AnT.Height); // устанавливаем проекционную матрицу Gl.glMatrixMode(Gl.GL_PROJECTION); // очищаем ее Gl.glLoadIdentity(); // теперь необходимо корректно настроить 2D ортогональную проекцию // в зависимости от того, какая сторона больше, // мы немного варьируем конфигурацией настройки проекции if (AnT.Width <= AnT.Height) Glu.gluOrtho2D (0.0, 30.0, 0.0, 30.0 * (float)AnT.Height / (float)AnT.Width); else Glu.gluOrtho2D(0.0, 30.0 * (float)AnT.Width / (float)AnT.Height, 0.0, 30.0); // переходим к объектно-видовой матрице Gl.glMatrixMode(Gl.GL_MODELVIEW);

}

Помимо этого нам понадобится добавить на окно программы допол-нительные элементы управления: 3 элемента TrackBar (вы можете найти

Page 85: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

85

их в Панели элементов (ToolBox) в конце самого первого свитка («Все формы Windows Forms»)) и три элемента Label: они будут находиться над элементами TrackBar и содержать названия коэффициентов, которыми бу-дут управлять данные TrackBar’ы.

Под каждым элементом TrackBar будет находиться по одному допол-нительному элементу label для вывода текущего значения параметра, ко-торым управляет данный ползунок.

(Сверху будут находиться элементы label1, label2, label3, а снизу label4, label5, label6).

Разместите все элементы, как показано на рис. 5.7. Для того чтобы эле-мент TrackBar стал вертикальным, необходимо в его свойствах указать пара-метр Orientation равным Vertical.

Рис. 5.7

В свойствах TrackBar’ов необходимо указать диапазон значений: Minimum 0 Maximum 1000 TickFrequensy 10 (количество позиций между отметками).

Page 86: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

86

Для первого TrackBar’а установите значение (Value), равное 1000, для остальных – нуль.

Выполните двойной щелчок левой клавишей мыши по каждому TrackBar’у, чтобы создать функции-обработчики события перемещения пол-зунка пользователем.

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

double a = 1, b = 0, c = 0; Теперь реализуем функцию, отвечающую за рисование. Назовем ее

Draw. Эта функция будет вызываться при нажатии на кнопку «Визуализи-ровать» и при перемещении управляющих ползунков на TrackBar’ах, так как при этом будут изменяться коэффициенты a, b, c и наша сцена будет нуждаться в повторной визуализации.

Код функции с комментариями: // функия Draw private void Draw() {

// очищаем буфер цвета Gl.glClear(Gl.GL_COLOR_BUFFER_BIT); // активируем рисование в режиме GL_TRIANGLES, при котором три заданные // с помощью функции glVertex2d или glVertex3d вершины // будут объединяться в полигон (треугольник) Gl.glBegin(Gl.GL_TRIANGLES); // устанавливаем параметр цвета, основанный на параметрах a b c Gl.glColor3d(0.0 + a, 0.0 + b, 0.0 + c); // рисуем вершину в координатах 5,5 Gl.glVertex2d(5.0, 5.0); // устанавливаем параметр цвета, основанный на параметрах с a b Gl.glColor3d(0.0 + c, 0.0 + a, 0.0 + b); // рисуем вершину в координатах 25,5 Gl.glVertex2d(25.0, 5.0); // устанавливаем параметр цвета, основанный на параметрах b c a Gl.glColor3d(0.0 + b, 0.0 + c, 0.0 + a); // рисуем вершину в координатах 25,5 Gl.glVertex2d(5.0, 25.0); // завершаем режим рисования примитивов Gl.glEnd(); // дожидаемся завершения визуализации кадра Gl.glFlush(); // обновляем изображение в элементе AnT AnT.Invalidate();

}

Page 87: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

87

Функции-обработчики перемещения ползунка в TrackBar’ах будут выглядеть следующим образом:

private void trackBar1_Scroll(object sender, EventArgs e) {

// генерация коэффициента a = (double)trackBar1.Value / 1000.0; // вывод значения коэффициента, управляемого данным ползунком. // (под TrackBar'ом) label4.Text = a.ToString();

} private void trackBar2_Scroll(object sender, EventArgs e) {

// генерация коэффициента b = (double)trackBar2.Value / 1000.0; // вывод значения коэффициента, управляемого данным ползунком. // (под TrackBar'ом) label5.Text = b.ToString();

} private void trackBar3_Scroll(object sender, EventArgs e) {

// генерация коэффициента c = (double)trackBar3.Value / 1000.0; // вывод значения коэффициента, управляемого данным ползунком. // (под TrackBar'ом) label6.Text = c.ToString();

}

Для корректной визуализации (например, в том случае, когда наше окно перекроет какое-либо другое) мы добавим в нашу форму объект − таймер. Он будет отсчи-тывать промежутки времени таким образом, чтобы примерно 40 раз в секунду перерисовывать содер-жимое нашего окна.

Для того чтобы добавить объект, перейдите к окну ToolBox, выберите в свитке «Компоненты» элемент Timer (рис. 5.8) и перета-щите его на форму.

Рис. 5.8

Page 88: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

88

Место, куда вы перетащите этот элемент, не важно: он не занимает его на форме и отобразится в полоске таких же («не занимающих место») объектов под формой.

Щелкните по нему и перейдите в его свойства. Смените параметр name на значение RenderTimer, а параметр Interval −

на значение 25 (раз в 25 мс будет вызываться событие OnTimer). Теперь щелкните по нему двойным щелчком левой клавиши мыши.

Создастся функция-обработчик события OnTimer. Отсюда мы будем вызы-вать функцию Draw:

private void RenderTimer_Tick(object sender, EventArgs e) {

// функция визуализации Draw();

}

Кнопка «Визуализировать» будет отвечать за активацию этого тайме-ра. Для этого будет использоваться функция Start:

// обработчик кнопки "Визуализировать" private void button1_Click(object sender, EventArgs e) {

// старт таймера, отвечающего за вызов функции, // визуализирующей кадр RenderTimer.Start();

}

И в заключение назначьте обработчик кнопке «Выйти»

// обработчик кнопки "Выйти" private void button2_Click(object sender, EventArgs e) {

// выход из приложения Application.Exit();

}

После компиляции и запуска программы мы сможем управлять раз-ложением спектра в треугольнике (рис. 5.9, 5.10).

Page 89: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

89

Рис. 5.9

Рис. 5.10

Контрольные вопросы 1. Порядок инициализации OpenGL в C#. 2. Способы визуализации 2D-примитивов в VisualC#.

Page 90: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

90

ПРИЛОЖЕНИЕ

Установка и подключение библиотек TAO

Рассмотрим установку Tao Framework на примере версии 2.1.0. Установка может не начаться по причине отсутствия . NET Framework

2.0. Однако если уже установлена MS Visual Studio 2008, то такой пробле-мы возникнуть не должно, так как она уже содержит .NET Framework 3.0, который устанавливается в ходе общей установки MS Visual Studio.

После запуска дистрибутива появится окно подтверждения установки (рис. П1). Подтвердите установку и следуйте вперед, нажимая кнопку "Да-лее", чтобы подтвердить все стандартно выставленные программой опции (рис. П2 – П6). На шестом шаге установки (рис. П7) обязательно оставьте указанные галочки.

Рис. П1

Page 91: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

91

Рис. П2

Рис. П3

Page 92: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

92

Рис. П4

Рис. П5

Page 93: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

93

Рис. П6

Рис. П7

После этого шага пройдет инсталляция файлов и Tao Framework гото-ва к работе.

Для использования библиотеки может понадобиться в настройках операционной системы в переменную среды PATH добавить каталог C:\Program Files\TaoFramework\lib.

Page 94: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

94

БИБЛИОГРАФИЧЕСКИЙ СПИСОК

1. Жигалов, И. Е. Компьютерная графика : курс лекций / И. Е. Жига-лов. – Владимир : Ред.-издат. комплекс ВлГУ, 2004. − 124 с.

2. Жигалов, И. Е. Программирование компьютерной графики : практи-кум / И. Е. Жигалов ; Владим. гос. ун-т. − Владимир, 2002. − 100 с.

3. Никулин, Е. А. Компьютерная геометрия и алгоритмы машинной графики / Е. А. Никулин. − СПб. : БХВ, 2003. – 560 с. − ISBN 5-94157-264-6.

4. Поляков, А. Ю. Методы и алгоритмы компьютерной графики в при-мерах на Visual C++ / А. Ю. Поляков. – СПб. : БХВ-Петербург, 2002. – 416 с. − ISBN 5-941571-36-4.

5. Порев, В. Н. Компьютерная графика / В. Н. Порев. – СПб. : БХВ-Петербург, 2002. − 432 с. − ISBN 5-94157-139-9.

6. Тарасов, И. А. Основы программирования в OрenGL / И. А. Тарасов. − М. : Телеком, 2000. − 188 с. − ISBN 5-935170-16-7.

7. Эйнджел, Э. Интерактивная компьютерная графика. Вводный курс на базе OрenGL / Э. Эйнджел. – М. : Вильямс, 2001. – 592 с. − ISBN 5-8459-0209-6.

8. Павловская, Т. А. С#. Программирование на языке высокого уровня : учеб. для вузов / Т. А. Павловская. − СПб. : Питер, 2009. − 432 с. − ISBN 978-5-91180-174-8.

Page 95: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

95

Оглавление Введение ............................................................................................................... 3

Тема 1. C#: РАЗРАБОТКА КОНСОЛЬНОГО ПРИЛОЖЕНИЯ ................. 4 1.1. О Microsoft .NET Framework ....................................................... 4 1.2. Основы синтаксиса языка C# ...................................................... 5 1.3. Разработка класса и реализация консольного приложения ... 14

Тема 2. C#: РАЗРАБОТКА ОКОННОГО ПРИЛОЖЕНИЯ ....................... 25 2.1. Основы Windows.Forms ............................................................. 25 2.2. Создание оконного приложения в .net ..................................... 26 2.3. Создание второго оконного приложения ................................ 34

Тема 3. МНОГОПОТОЧНЫЕ ВЫЧИСЛЕНИЯ ......................................... 48 3.1. Многопоточное программирование в C# ................................ 48 3.2. Базовые методы работы с потоками в C#.net .......................... 50

Тема 4. ВВЕДЕНИЕ В OPENGL .................................................................. 53 4.1. Open GL ....................................................................................... 53 4.2. TAO Framework .......................................................................... 54 4.3. Инициализация Open GL в С# .................................................. 55

Тема 5. ИНИЦИАЛИЗАЦИЯ OPENGL ....................................................... 63 5.1. Инициализация Open GL в С# .................................................. 63 5.2. Визуализация 2D-примитивов .................................................. 67 5.3. Визуализация графика функций ............................................... 74 5.4. Вывод 2D цветового треугольника .......................................... 84

Приложение ....................................................................................................... 90

Библиографический список ............................................................................. 94

Page 96: ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ …e.lib.vlsu.ru/bitstream/123456789/3453/1/01298.pdfзовании unsafe (небезопасного) кода, C# практически

96

Учебное издание

ЖИГАЛОВ Илья Евгеньевич НОВИКОВ Иван Андреевич

ПРОГРАММИРОВАНИЕ КОМПЬЮТЕРНОЙ ГРАФИКИ

Учебное пособие

Подписано в печать 14.03.14.

Формат 60х84/16. Усл. печ. л. 5,58. Тираж 75 экз. Заказ

Издательство Владимирского государственного университета

имени Александра Григорьевича и Николая Григорьевича Столетовых. 600000, Владимир, ул. Горького, 87.