ПОНЯТНО О Visual Basic NET (том 2)
Во дворце 40 залов. Компьютер
Во дворце 40 залов. Компьютер запрашивает длину, ширину и высоту каждого зала. Вычислить площадь пола и объем каждого зала.
Сначала напишем фрагмент для одного зала:
Dlina = InputBox("Введите длину") 'Начало фрагмента
Shirina = InputBox("Введите ширину")
Visota = InputBox("Введите высоту")
S = Dlina * Shirina 'Площадь пола
V = S * Visota 'Объем
Debug.WriteLine("Площадь пола = " & S & " Объем зала = " & V) 'Конец фрагмента
Для решения задачи этот фрагмент нужно выполнить 40 раз, для чего вполне естественно вложить его внутрь оператора For:
Dim Dlina, Shirina, Visota, S, V As Double
Dim i As Integer
For i = 1 To 40
Dlina = InputBox("Введите длину") 'Начало фрагмента
Shirina = InputBox("Введите ширину")
Visota = InputBox("Введите высоту")
S = Dlina * Shirina 'Площадь пола
V = S * Visota 'Объем
Debug.WriteLine("Площадь пола = " & S & " Объем зала = " & V) 'Конец фрагмента
Next
Полужирным шрифтом я выделил новые по сравнению с предыдущим фрагментом строки.
Чтобы программа подходила для любого числа залов, нужно вместо строки
For i = 1 To 40
написать две строки:
N = InputBox("Сколько залов во дворце?")
For i = 1 To N
Задание 44.
Построить диаграмму численности населения городов (см. Рис. 10.1).

Рис. 10.1
Подробнее: Компьютер запрашивает количество городов на диаграмме. Затем спрашивает название города и число его жителей, после чего строит первый столбец диаграммы с надписью. Затем запрашивает данные о втором городе, строит второй столбец и так далее. Неплохо бы под столбцом указывать число жителей.
Роль ошибок в программе
Пусть во дворце три зала размерами 20*15*4, 30*20*5 и 10*5*3. В этом случае, выполняя программу предыдущего подраздела, мы вводим N=3 и оператор For выполняет цикл три раза.
Мы знаем, что по ошибочной программе компьютер выдает ошибочные результаты. Попробуйте угадать результаты, если в программе мы вместо V=S*visota напишем V=S+visota. Ответ:
Площадь пола = 300 Объем зала = 304
Площадь пола = 600 Объем зала = 605
Площадь пола = 50 Объем зала = 53
Если же вы случайно вместо For i=1 To N напишете For i=2 To N и не заметите этого, то результаты будут такими:
Площадь пола = 300 Объем зала = 1200
Площадь пола = 600 Объем зала = 3000
На этом программа закончит работу и не спросит размеров третьего зала. Вам не кажется странным, что она посчитала 1 и 2 залы, а не 2 и 3? Если кажется, то учтите, что вы ничего не знаете об ошибке в программе, а компьютер не говорит вам, размеры какого по счету зала нужно вводить.
Задание 45.
Определите без компьютера, что он напечатает, если
А. Строку For i=1 To N поместить на три строки ниже, а именно – перед строкой S=Dlina*Shirina
Б. Поменять местами строки Debug.WriteLine и Next
Если задания не получаются, введите программы в компьютер и используйте пошаговый режим.
Вычисления в цикле
Вычисления в цикле встречаются очень часто и поэтому, несмотря на то, что никакой особой техники тут не требуется, я рассмотрю пример.
Счетчики
Счетчик - это переменная величина, в которой вы что-нибудь подсчитываете. Для чего нужны счетчики? Ну хотя бы для того, чтобы подсчитать количество жизней главного персонажа в компьютерной игре.
Задача 1: В компьютер с клавиатуры вводятся числа. Компьютер после ввода каждого числа должен печатать, сколько среди них уже введено положительных.
Фрагмент, решающий задачу:
Dim a, c As Integer
c = 0 'Обнуляем счетчик
Do
a = InputBox("Введите очередное число")
If a > 0 Then c = c + 1
Debug.WriteLine("Введено положительных чисел - " & c)
Loop
Пояснения: Нам уже приходилось сталкиваться со счетчиком. Это был счетчик циклов. Тогда мы просто придумали некую переменную, которую и назвали счетчиком циклов. Здесь мы тоже придумали переменную c. Она у нас выполняет роль счетчика положительных чисел. Сердце счетчика – оператор c=c+1. Именно он в нужный момент увеличивает счетчик на 1. Но и без части If a>0 Then тоже никак нельзя. Если бы ее не было, то c подсчитывал бы все числа без разбору, то есть был бы обыкновенным счетчиком циклов. В нашем же фрагменте увеличение с на 1 выполняется не всегда, а лишь при положительном а.
Обязательно покрутите программу в пошаговом режиме.
В сложных программах не забывайте обнулять счетчик перед входом в цикл, а не то он начнет считать вам не с нуля, а бог знает с чего. А это нехорошо. Как бы вам понравилось, если бы таксист в начале поездки не обнулил счетчик?
Задача 2: В предыдущем фрагменте значения счетчика печатаются при каждом выполнении цикла. Изменим задачу: В компьютер вводится ровно 200 чисел. Компьютер должен подсчитать и в конце один раз напечатать, сколько среди них положительных.
Программа:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Dim a, c, i As Integer
For i = 1 To 200
a = InputBox("Введите очередное число")
If a > 0 Then c = c + 1
Next i
Debug.WriteLine("Из них положительных - " & c)
End Sub
Пояснения: Путь рассуждений здесь тот же, что и в первой задаче. В результате применения оператора For тело цикла выполняется ровно 200 раз, благодаря чему счетчик с накапливает нужное значение. Оператор Debug.WriteLine выполняется только один раз и печатает последнее накопленное значение, потому что в ячейке с в момент печати будет находиться именно оно..
Задание 46.
«Ошибки». Что будет, если
А. Вместо c=0 написать c=10
Б. Вместо c=c+1
написать c=c+2
В. Строки Next и Debug.WriteLine поменять местами
Г. Строки c=0 и For поменять местами
Д. Строки For и InputBox поменять местами
Задача 3: А в следующей программе мы используем уже два счетчика. Изменим задачу: В компьютер вводится ровно 200 чисел. Компьютер должен подсчитать и один раз напечатать, сколько среди них положительных чисел и сколько нулей.
Программа:
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
Dim a, i, c_полож, c_нулей As Integer
c_полож = 0 'Обнуляем счетчик положительных чисел
c_нулей = 0 'Обнуляем счетчик нулей
For i = 1 To 200
a = InputBox("Введите очередное число")
If a > 0 Then c_полож += 1
If a = 0 Then c_нулей += 1
Next i
Debug.WriteLine("Из них положительных - " & c_полож & " нулей - " & c_нулей)
End Sub
Задача 4: Подсчитывать можно не только числа, но и строки и данные любых других типов. Как, например, узнать, насколько Лев Толстой любил слово «добро»? Для этого можно подсчитать, сколько раз встречается это слово в его произведениях. Решим похожую задачу:
В компьютер один за другим вводятся произвольные символы. Ввод заканчивается символом "/". Подсчитать, какой процент от общего числа введенных символов составляют символ «W» и символ «:» по отдельности.
Здесь мы организуем три счетчика одновременно: сW – для подсчета букв W, сDv – для подсчета двоеточий, а также i – счетчик общего числа введенных символов, кроме «/».
Программа:
Private Sub Button4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button4.Click
Dim i, cW, cDv As Integer
Dim simvol As String
i = 0 : cW = 0 : cDv = 0 'Обнуляем все три счетчика
Do
simvol = InputBox("Введи символ")
If simvol <> "/" Then i = i + 1 'Если это не /, то "посчитай" его
Select Case simvol
Case "W" 'Если это W, то
cW = cW + 1 'увеличь счетчик символов W
Case ":" 'Если это : , то
cDv = cDv + 1 'увеличь счетчик символов :
Case "/" 'Если это /, то
Exit Do 'завершай работу цикла
End Select
Loop
Debug.WriteLine("Символов W было " & Format(cW / i, "P") & " Двоеточий было " & Format(cDv / i, "P"))
End Sub
Печатаются результаты в таком виде:
Символов W было 48,39% Двоеточий было 3,23%
Здесь cW/i дает долю символов W в общем числе символов, а формат "P" преобразовывает эту долю в проценты.
Задание 47.
В компьютер вводится N чисел. Подсчитать из них по-отдельности количество положительных, отрицательных и тех, что превышают число 10.
Задание 48.
В компьютер вводятся пары целых чисел. Подсчитать, сколько среди них пар, дающих в сумме число 12. Подсчет закончить вводом пары нулей.
Задание 49.
«Считаем звезды». Возьмите решенную нами задачу о звездном небе и подсчитайте заодно, сколько звезд из 100 попадет в левую часть неба (конкретнее, сколько звезд отстоят от левого края формы не более, чем на половину ее ширины). Пусть компьютер тут же ответит и на вопрос: где больше звезд – в левом верхнем квадрате неба размером 100 на 100 или в левом нижнем квадрате того же размера (см. Рис. 10.2)? Указание: Вам придется использовать переменные величины – координаты звезд.

Рис. 10.2
Сумматоры
Сумматор – это переменная величина, в которой вы подсчитываете сумму чего-либо. Для чего нужны сумматоры? Ну хотя бы для того, чтобы подсчитать общее количество золота, которое вы нашли в нескольких кладах в компьютерной игре.
Если вы поняли идею счетчика, то понять идею сумматора будет нетрудно. Посмотрим, как работает следующий фрагмент:
s = 0 'Обнуляем сумматор. Это не менее важно, чем обнулить счетчик
Do
a = InputBox("Введите очередное число")
s = s + a 'Увеличиваем сумматор
Debug.WriteLine("Сумма= " & s)
Loop
В ячейке s накапливается сумма вводимых чисел a, поэтому назовем эту ячейку сумматором. Отличие сумматора от счетчика в том, что счетчик увеличивается на 1 оператором c=c+1, а сумматор – на суммируемое число оператором s=s+a.
Задача: В компьютер вводится N чисел. Вычислить и один раз напечатать их сумму.
Программа:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Dim a, s, i, N As Integer
N = InputBox("Сколько чисел будем складывать?")
s = 0
For i = 1 To N
a = InputBox("Введите очередное число")
s = s + a
Next i
Debug.WriteLine("Сумма равна " & s)
End Sub
Задание 50.
«Ошибки». Пусть N=2, a =5 и 3. Тогда по вышеприведенной программе VB напечатает 8. Определите без компьютера, что он напечатает, если:
А. Вместо s=0 написать s=10.
Б. Вместо s=s+a написать s=s+a+1.
В. Строки Next и Debug.WriteLine поменять местами.
Г. Строки s=0 и For поменять местами.
Д. Строки For и a=InputBox поменять местами.
Е. Строки s=s+a и Next поменять местами.
Ж. Вместо For i=1 To N написать For i=2 To N.
Задание 51.
Во дворце 40 залов. Известны длина и ширина каждого зала. Вычислить площадь пола всего дворца.
Задание 52.
Вычислить средний балл учеников вашего класса по физике. Указание: Средний балл находится делением суммы баллов на число оценок.
Задание 53.
Вычислить произведение N произвольных чисел. Подсказка: Несмотря на то, что произведение – не сумма, эта программа будет отличаться от программы суммирования всего лишь одним числом и одним значком, а структура обеих программ совершенно одинакова.
Для тренировки определите без компьютера,
Для тренировки определите без компьютера, что напечатает следующий фрагмент:
a = 9
For i = 1 To 6
If i * i = a Then
For k = 5 To 6
Debug.Write(k)
If 3 > 2 Then a = 16
Next k
Else
Debug.WriteLine(2004)
End If
Next i
Здесь внутрь For i вложен If i*i=a , внутрь которого вложен For k, внутрь которого в свою очередь вложен If 3>2. Обратите внимание на различную величину отступов от левого края листа.
Ответ:
2004
2004
56562004
2004
Вложенные циклы – «Таблица умножения»
Вложенные циклы или «цикл внутри цикла» – весьма распространенная конструкция при программировании.
Поставим себе задачу – вычислить и напечатать таблицу умножения (см. Рис. 10.3).

Рис. 10.3
Сразу же рисовать прямоугольники и писать на форме нам будет сложно, поэтому пока будем печатать таблицу в окне Output при помощи оператора Debug.Write.
Начнем с малого – пусть нужно напечатать
1*1=1
Вот фрагмент программы:
Фрагмент 1
a = 1
b = 1
proizv = a * b
Debug.Write (a & "*" & b & "=" & proizv)
Здесь в операторе Debug.WriteLine 5 элементов:
* сомножитель a
* символ знака умножения "*"
* сомножитель b
* символ "="
* произведение proizv
Я намеренно ввел во фрагмент максимально возможное число переменных, так как предвижу, что фрагмент станет сердцем более сложной программы для вычисления всей таблицы, где сомножители меняются.
Печатаем строку. Попробуем заставить компьютер напечатать первый ряд таблицы:
1*1=1 1*2=2 1*3=3 1*4=4 1*5=5 1*6=6 1*7=7 1*8=8 1*9=9 1*10=10
Замечаем, что здесь нам нужно решить 10 элементарных задач на вычисление произведения, первую из которых решает фрагмент 1. Все они очень похожи и различаются лишь значением второго сомножителя. Таким образом, для решения каждой из 10 задач подошел бы наш фрагмент 1, если бы в нем в операторе b=1 вместо единицы стояло нужное число. В данном случае идеально подходит оператор For:
Фрагмент 2
a = 1
For b = 1 To 10
proizv = a * b
Debug.Write(a & "*" & b & "=" & proizv & " ")
Next
Обратите внимание, что для пробела между столбцами будущей таблицы в оператор Debug.Write добавился 6-й элемент – " ".
Прокрутите программу в пошаговом режиме.
6*1=6 6*2=12 6*3=18 6*4=24 6*5=30 6*6=36 6*7=42 6*8=48 6*9=54 6*10=60
7*1=7 7*2=14 7*3=21 7*4=28 7*5=35 7*6=42 7*7=49 7*8=56 7*9=63 7*10=70
8*1=8 8*2=16 8*3=24 8*4=32 8*5=40 8*6=48 8*7=56 8*8=64 8*9=72 8*10=80
9*1=9 9*2=18 9*3=27 9*4=36 9*5=45 9*6=54 9*7=63 9*8=72 9*9=81 9*10=90
10*1=10 10*2=20 10*3=30 10*4=40 10*5=50 10*6=60 10*7=70 10*8=80 10*9=90 10*10=100
Как видите, печатает фрагмент 4 плохо. Аккуратных столбцов не получается. А нам и не очень хотелось. Задачу создания красивой таблицы умножения я вынес в Задание 72.
В целом программа иллюстрирует идею вложенных циклов, когда один, внутренний, цикл вложен внутрь другого, внешнего. У нас тело внешнего цикла выполняется 10 раз, а тело внутреннего – 100 раз, так как на каждое выполнение внешнего цикла он выполняется 10 раз.
Нельзя. Бессмысленно и нельзя переменные внешнего и внутреннего цикла называть одним именем. Так, следующий фрагмент
Dim i, a As Integer
For i = 1 To 10
For i = 1 To 10
a = 0
Next
Next
не пройдет. Переменная i во внутреннем цикле будет подчеркнута. Вообще, не рекомендуется внутри цикла как-то менять ее переменную.
Задание 54.
А. Распечатать все возможные сочетания из двух цифр – первая цифра может быть любой от 3 до 8, вторая - любой от 0 до 7. Например, 36, 44, 80.
Б. Распечатать все возможные сочетания из четырех цифр, каждая из которых может принимать значения 1,2,3. Например, 2123, 3312, 1111. Получилось? А теперь подсчитайте количество таких сочетаний.
В. Подсчитать из них количество неубывающих сочетаний, то есть таких, где каждая следующая цифра не меньше предыдущей – 1123, 1223, 2222 и т.п., но не 3322. Распечатать все такие сочетания.
Вложенные циклы – «Небоскреб»
Мы видим, что ввод в программу переменных величин вместо чисел делает программу более гибкой и способной к развитию. Попробуем в следующей программе использовать переменные пошире.
Задача. Нарисовать небоскреб (см. Рис. 10.4) с заданным числом этажей и подъездов (условимся: один столбец окон – один подъезд).

Рис. 10.4
Начнем с малого – пусть нужно нарисовать одно единственное окно:
Фрагмент 1
Ширина = 5 'Ширина окна
Высота = 10 'Высота окна
y = 30 'Вертикальная координата окна
x = 20 'Горизонтальная координата окна
Граф.DrawRectangle(Pens.Black, x, y, Ширина, Высота)
Пусть этот фрагмент рисует левое верхнее окно небоскреба.
Рисуем этаж. Попробуем заставить компьютер нарисовать верхний этаж. Кстати, мы с вами ведь уже рисовали «квадратную трубу» в 8.5? Это то же самое.
Замечаем, что здесь нам нужно решить несколько элементарных задач на рисование окна, первую из которых решает фрагмент 1. Все задачи очень похожи и различаются лишь значением координаты x. Таким образом, для решения каждой из этих задач подошла бы последняя строчка фрагмента 1, если бы в ней x равнялся не 20, а нужному числу. В данном случае идеально подходит оператор For:
Фрагмент 2
Зазор_х = 4 'Зазор между окнами на этаже
Ширина = 5 : Высота = 10
П = InputBox("Введите количество подъездов")
y = 30
x = 20
For j = 1 To П
Граф.DrawRectangle(Pens.Black, x, y, Ширина, Высота)
x = x + Ширина + Зазор_х 'Вычисляем горизонтальную координату следующего окна
Next
Рисуем весь небоскреб. Следующая ступень усложнения – последняя – нарисовать все этажи небоскреба. Для этого 5 нижних строчек фрагмента 2 должны быть выполнены заданное число раз, каждый раз – с новым значением y. Чтобы этого достичь, «обнимем» эти строчки оператором For:
Программа
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
Зазор_х = 4
Зазор_у = 6 'Зазор между окнами соседних этажей
Ширина = 5 : Высота = 10
Э = InputBox("Введите количество этажей")
П = InputBox("Введите количество подъездов")
y = 30
For i = 1 To Э
x = 20
For j = 1 To П
Граф.DrawRectangle(Pens.Black, x, y, Ширина, Высота)
x = x + Ширина + Зазор_х
Next j
y = y + Высота + Зазор_у 'Вычисляем вертикальную координату следующего окна
Next i
Граф.DrawRectangle(Pens.Black, 8, 15, x, y) 'Рисуем контур небоскреба
End Sub
Задание 55.
«Ковер» (Рис. 10.5).

Рис. 10.5
Ковер сделан из пересекающихся окружностей. Если центры соседних окружностей отстоят друг от друга на одинаковое расстояние как по горизонтали, так и по вертикали, и если удачно подобраны размеры, то ковер у вас получится красивым и с аккуратными краями. Дополнение А: Если ковер получился, сделайте, чтобы у него был вырезан правый верхний угол. Дополнение Б: Если и это получилось, сделайте, чтобы у него был вырезан вдобавок и квадрат посередине.
Задание 56.
«Шахматная доска». Нарисуйте шахматную доску (Рис. 10.6).

Рис. 10.6
Указание: Здесь основные трудности возникнут при раскраске клеток в шахматном порядке. У Волчёнкова (См. Список литературы) я встретил следующую идею относительно того, как закрашивать клетки: Те клетки, у которых сумма номеров строки и столбца четная, закрашивать одним цветом, остальные – другим.
Задание 57.
«Таблица умножения». Нарисовать на форме таблицу умножения, как на Рис. 10.3.
Вложенные операторы
Реальная процедура на VB может представлять собой сложную мозаику из циклических и разветвляющихся частей, вложенных друг в друга. Мы уже видели в 7.5.1 как в оператор ветвления был вложен оператор ветвления, а в 10.2.1. – как в оператор цикла был вложен оператор ветвления. В свою очередь в них могут быть вложены другие операторы цикла или ветвления, и так до бесконечности. Вам нужно привыкнуть разбираться в этой матрешке.
Поиск максимума и минимума
Ищем максимальное число.
Задача программисту: Найти максимальное из вводимых в компьютер чисел.
Задача рыбаку: Принести домой самую большую из выловленных рыб.
Решение рыбака: Рыбак приготовил для самой большой рыбы пустое ведро с водой. Первую пойманную рыбу рыбак не глядя бросает в это ведро. Каждую следующую рыбу он сравнивает с той, что в ведре. Если она больше, то он бросает ее в ведро, а ту, что была там раньше, выпускает в реку.
Решение программиста: Программист приготовил для самого большого числа ячейку и придумал ей название, скажем, max. Первое число программист не глядя вводит в эту ячейку. Каждое следующее число (назовем его chislo) он сравнивает с max. Если оно больше, то он присваивает переменной max значение этого числа.
Напишем программу для определения максимального из 10 вводимых чисел:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim max, chislo, i As Integer
max = InputBox("Введите число") 'первую рыбу - в ведро
For i = 2 To 10 'ловим остальных рыб:
chislo = InputBox("Введите число") 'поймали очередную рыбу
If chislo > max Then max = chislo 'и если она больше той, что в ведре, бросаем ее в ведро
Next i
Debug.WriteLine(max) 'несем самую большую рыбу домой
End Sub
В этой программе переменная max исполняла роль «памяти». Она сохраняла в себе нужное значение и передавала его из цикла в цикл, благодаря чему каждый следующий цикл «знал», с чем сравнивать очередное число. Эта идея – использовать переменные для запоминания нужной информации на всем процессе выполнения программы – весьма продуктивна и широко используется программистами.
Примечание: Не путайте изобретенную нами переменную max со стандартной функцией Math.Max, которая нужна немножко для другого.
Задание 58.
В нашей программе функция InputBox("Введите число") встречается два раза. Не всем нравится такая избыточность. А. Попробуйте избавится от одной из них, усложнив If. Б. Можно избавиться и по-другому: заранее присвоив переменной max очень маленькое число, такое, что все числа заведомо больше него.
Ищем порядковый номер максимального числа. Дополним нашу программу, чтобы она искала также порядковый номер максимального числа из N заданных чисел, для чего организуем переменную Номер_макс_числа:
Private Sub Button4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button4.Click
Dim max, chislo, i, N, Номер_макс_числа As Integer
N = InputBox("Сколько чисел?")
max = InputBox("Введите число")
Номер_макс_числа = 1
For i = 2 To N
chislo = InputBox("Введите число")
If chislo > max Then
max = chislo
Номер_макс_числа = i
End If
Next i
Debug.WriteLine(max)
Debug.WriteLine(Номер_макс_числа)
End Sub
Задание 59.
Найти из N чисел минимальное. Каким по порядку было введено минимальное число?
Задание 60.
У вас есть результаты забега на 100 метров (в секундах). Правда ли, что результат самого быстрого бегуна отличается от результата самого медленного больше, чем на 0,4 сек.?
Задание 61.
На небе 10 звезд. Напечатайте координаты самой правой звезды.
Понятие о процедурах пользователя
Рассмотрим бессмысленную программу:
Public Class Form1
Inherits System.Windows.Forms.Form
Windows Form Designer generated code
Dim a, b, d, f As Integer
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
d = 100
a = 2
b = 30
Debug.WriteLine(a + b)
Debug.WriteLine(a * b + 1)
Debug.WriteLine(a ^ b - 20)
d = 9 - b
f = 0
a = 2
b = 30
Debug.WriteLine(a + b)
Debug.WriteLine(a * b + 1)
Debug.WriteLine(a ^ b - 20)
d = a + 1
a = 2
b = 30
Debug.WriteLine(a + b)
Debug.WriteLine(a * b + 1)
Debug.WriteLine(a ^ b - 20)
f = f - a
End Sub
End Class
Предположим, эта программа раздражает нас не своей бессмысленностью, а своей длиной. Как сократить ее?
Замечаем, что программа содержит 3 одинаковых фрагмента (в тексте я их выделил полужирным курсивом):
a = 2
b = 30
Debug.WriteLine(a + b)
Debug.WriteLine(a * b + 1)
Debug.WriteLine(a ^ b - 20)
В этом случае программисты всего мира сокращают программу так. Они придумывают повторяющемуся фрагменту произвольное имя, например,
Печать_разных_чисел
Затем они вписывают в окно кода этот фрагмент отдельно, снабдив его заголовком
Sub Печать_разных_чисел()
и конечной строкой
End Sub
Получается вот что:
Sub Печать_разных_чисел()
a = 2
b = 30
Debug.WriteLine(a + b)
Debug.WriteLine(a * b + 1)
Debug.WriteLine(a ^ b - 20)
End Sub
Они называют все это процедурой пользователя, после чего имеют право во всем окне кода вместо полного текста фрагмента писать только его имя Печать_разных_чисел. Посмотрите на получившуюся программу целиком:
Public Class Form1
Inherits System.Windows.Forms.Form
Windows Form Designer generated code
Dim a, b, d, f As Integer
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
d = 100
Печать_разных_чисел()
d = 9 - b
f = 0
Печать_разных_чисел()
d = a + 1
Печать_разных_чисел()
f = f - a
End Sub
Sub Печать_разных_чисел()
a = 2
b = 30
Debug.WriteLine(a + b)
Debug.WriteLine(a * b + 1)
Debug.WriteLine(a ^ b - 20)
End Sub
End Class
Получившаяся программа выполняет абсолютно то же самое, что и исходная.
Как видите, программа получилась короче, а если бы в исходной программе фрагмент встретился не 3 раза, а больше (как часто и бывает), то укорочение было бы еще заметнее.
Все операторы, из которых состоит процедура, без заголовка и конечной строки, будем называть телом процедуры. А вместе с ними – объявлением процедуры.
Слово Печать_разных_чисел используется внутри процедуры Sub Button1_Click, как настоящий оператор, и выполняется, как настоящий оператор. Суть его выполнения в том, что когда VB в процессе выполнения программы натыкается на оператор Печать_разных_чисел, он ищет в программе объявление процедуры с именем Печать_разных_чисел и начинает выполнять тело этой процедуры. Этот процесс называется вызовом процедуры или обращением к процедуре. Говорят также, что управление передается процедуре. После выполнения тела процедуры VB возвращается к выполнению программы. Говорят, что управление возвращается к программе.
Вы обязательно должны проверить то, что я только что сказал, и собственными глазами убедиться в правдивости моих слов. Для этого обязательно выполните эту программу в пошаговом режиме! Это самое необходимое из того, что вы должны сделать. При этом обязательно же обратите внимание вот на что:
Порядок исполнения операторов показывает желтая полоса. Она перескакивает с оператора Печать_разных_чисел() на строку Sub Печать_разных_чисел(), после чего пробегает по всем 5 операторам тела процедуры. Дойдя до конечной строки End Sub, она возвращается на тот же оператор Печать_разных_чисел(), с которого и «прыгала», после чего переходит на оператор, следующий сразу же за этим самым оператором (в первый раз в нашем случае это оператор d=9-b). Вы просто обязаны усвоить этот порядок на всю жизнь.
Два вида процедур. Вот у вас в окне кода две процедуры. Одна из них – Button1_Click и вы знаете, когда она выполняется – когда нажимается кнопка Button1. Она принадлежит к числу привычных нам процедур обработки событий. А когда выполняется другая – Печать_разных_чисел? Только тогда (и если), когда (и если) в других процедурах компьютер наткнется на оператор Печать_разных_чисел(). А если этот оператор мы забыли там написать, то процедура Печать_разных_чисел не выполнится никогда.
Вы усвоили, что когда завершает свою работу процедура пользователя, VB возвращается в процедуру, которая ее вызвала. А куда он возвращается, когда завершает свою работу процедура обработки событий? А никуда. Просто ждет ваших дальнейших действий, вызывающих события, например, нажатий на кнопку.
Пример процедуры пользователя
Я пояснил применение процедуры пользователя на бессмысленном примере. А теперь – пример программы, имеющей реальный смысл. Если вы хорошо поймете его по тексту, то проверять на компьютере не обязательно.
Мы с вами уже написали процедуры, которые рисуют человечка, паровозик, звездное небо, ковер и т.п. Пусть теперь мы хотим создать проект – альбом рисунков, создаваемых нашими процедурами. В нем столько кнопок, сколько рисунков. По нажатии на каждую кнопку что-нибудь рисуется. Пусть для краткости в нашем проекте при нажатии на первую кнопку рисуется пушка (кружочек-колесо и палочка-ствол), а при нажатии на вторую – Буратино (кружочек-голова и палочка-нос). Добавим еще одну кнопку – для стирания. Назовем ее «Стираем». Вообще-то, для стирания достаточно процедуры из одного оператора:
Private Sub Стираем_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles Стираем.Click
Граф.Clear(Color.White)
End Sub
но нам бы хотелось, чтобы это было не просто стирание, а что-нибудь более экстравагантное, причем с музыкальным сопровождением. Посмотрите, что у нас получилось:
1 вариант программы:
'Процедура стирания:
Private Sub Стираем_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles Стираем.Click
Beep() 'Короткий звуковой сигнал
'Постепенно стираем рисунок, не спеша заполняя форму белыми эллипсами:
For i = 1 To 10000
Граф.FillEllipse(Brushes.White, Me.Width * Rnd(), Me.Height * Rnd(), 10, 50)
Next
Граф.Clear(Color.White) 'Окончательное стирание, так как эллипсов не хватает
Плеер.FileName = "D:\WINNT\Media\tada.wav" 'Играем мелодию
End Sub
'Рисуем пушку:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Граф.DrawEllipse(Pens.Black, 100, 100, 200, 200) 'Колесо
Граф.DrawLine(Pens.Black, 20, 300, 350, 20) 'Ствол
End Sub
'Рисуем Буратино:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Граф.DrawEllipse(Pens.Black, 100, 100, 200, 200) 'Голова
Граф.DrawLine(Pens.Black, 300, 200, 400, 200) 'Нос
End Sub
2 вариант программы. Все хорошо. Мы обошлись без процедур пользователя. Но теперь мы хотим улучшить проект. Пусть нам лень каждый раз перед нажатием на кнопку рисунка нажимать на кнопку стирания. Давайте сэкономим одно нажатие и сделаем так, чтобы при нажатии на кнопку любого рисунка сначала происходило стирание, а уж потом рисование. Для этого достаточно было бы операторы процедуры стирания дописать наверх каждой процедуры рисования:
'Рисуем пушку:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim i As Integer
Beep()
For i = 1 To 10000
Граф.FillEllipse(Brushes.White, Me.Width * Rnd(), Me.Height * Rnd(), 10, 50)
Next
Граф.Clear(Color.White)
Плеер.FileName = "D:\WINNT\Media\tada.wav"
Граф.DrawEllipse(Pens.Black, 100, 100, 200, 200) 'Колесо
Граф.DrawLine(Pens.Black, 20, 300, 350, 20) 'Ствол
End Sub
'Рисуем Буратино:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Dim i As Integer
Beep()
For i = 1 To 10000
Граф.FillEllipse(Brushes.White, Me.Width * Rnd(), Me.Height * Rnd(), 10, 50)
Next
Граф.Clear(Color.White)
Плеер.FileName = "D:\WINNT\Media\tada.wav"
Граф.DrawEllipse(Pens.Black, 100, 100, 200, 200) 'Голова
Граф.DrawLine(Pens.Black, 300, 200, 400, 200) 'Нос
End Sub
3 вариант программы. Результат достигнут. Но за счет удлинения программы. Как бы убить двух зайцев: и программу не удлинять и лишних кнопок не нажимать? Очень просто – так же, как мы это делали в бессмысленной программе, применив процедуру пользователя:
'Процедура пользователя для стирания рисунка:
Sub Стирание_старого_рисунка()
Dim i As Integer
Beep()
For i = 1 To 10000
Граф.FillEllipse(Brushes.White, Me.Width * Rnd(), Me.Height * Rnd(), 10, 50)
Next
Граф.Clear(Color.White)
Плеер.FileName = "D:\WINNT\Media\tada.wav"
End Sub
'Рисуем пушку:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Стирание_ старого_рисунка()
Граф.DrawEllipse(Pens.Black, 100, 100, 200, 200) 'Колесо
Граф.DrawLine(Pens.Black, 20, 300, 350, 20) 'Ствол
End Sub
'Рисуем Буратино:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Стирание_ старого_рисунка()
Граф.DrawEllipse(Pens.Black, 100, 100, 200, 200) 'Голова
Граф.DrawLine(Pens.Black, 300, 200, 400, 200) 'Нос
End Sub
Как видите, здесь в каждой из двух процедур рисования мы заменили длинный фрагмент стирания оператором Стирание_старого_рисунка(), который обращается к одноименной процедуре пользователя.
Процедуры пользователя улучшают читабельность. Итак, процедуры пользователя укорачивают программы. Но не только это. Они еще делают их более «читабельными», то есть более понятными при чтении, когда нужно быстро схватить общий смысл процедуры. Вообразите, что эта программа чужая и вы ее читаете впервые. Сравните в этом аспекте текст процедуры рисования пушки из 2 и 3 вариантов программы. Очевидно, что вы быстрее разберетесь, что делает эта процедура, именно в 3 варианте. Но этого не произойдет, если вы неудачно назовете процедуру пользователя. Если вы вместо имени Стирание_старого_рисунка употребите имя Крутая_процедурка, то не о какой понятности и читабельности не может быть и речи.
Call. Есть еще один способ обратиться к процедуре. Вместо оператора
Стирание_старого_рисунка()
можно написать оператор
Call Стирание_старого_рисунка()
Смысл этих двух операторов совершенно одинаков. Вторым способом часто пользовались раньше. С английского слово «Call» переводится «Вызов».
Понятие о процедурах с параметрами
Вернемся к началу. Изменим слегка нашу бессмысленную программу из 11.1.1:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
d = 100
a = 2
b = 30
Debug.WriteLine(a + b)
Debug.WriteLine(a * b + 1)
Debug.WriteLine(a ^ b - 20)
d = 9 - b
f = 0
a = 2
b = 8
Debug.WriteLine(a + b)
Debug.WriteLine(a * b + 999)
Debug.WriteLine(a ^ b - 20)
d = a + 1
a = 2
b = -4
Debug.WriteLine(a + b)
Debug.WriteLine(a * b + 73)
Debug.WriteLine(a ^ b - 20)
f = f - a
End Sub
Мы замечаем здесь те же 3 фрагмента, но они уже чуть-чуть неодинаковые (неодинаковые места я выделил полужирным курсивом). Наша задача прежняя – укоротить программу. Спрашивается, как сделать это в усложнившихся условиях?
Посмотрим повнимательнее, в чем изменения. Мы видим, что они коснулись двух мест фрагментов: в операторе b= число каждый раз разное и в операторе Debug.WriteLine(a*b+ тоже каждый раз разное число.
Действуем следующим образом. Придумываем одно имя для чисел в одном месте и другое имя для чисел в другом месте. Пусть мы придумали имена Параметр1 и Параметр2. Теперь создадим процедуру пользователя примерно так же, как мы делали это в 11.1.1, но с некоторыми изменениями.
Прежде всего, в теле процедуры вместо непослушных чисел записываем имена переменных Параметр1 и Параметр2. Но раз есть переменные, их нужно объявлять. А объявляются они специальным образом в заголовке процедуры. Окончательно получается вот что:
Sub Печать_разных_чисел (ByVal Параметр1 As Integer, ByVal Параметр2 As Integer)
a = 2
b = Параметр1
Debug.WriteLine(a + b)
Debug.WriteLine(a * b + Параметр2)
Debug.WriteLine(a ^ b - 20)
End Sub
Здесь полужирным шрифтом я выделил новые для вас конструкции. Вместо Dim мы пишем ByVal. На смысле слова ByVal я остановлюсь позже.
Теперь о том, как вызывать эту процедуру. Вызывается она просто:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
d = 100
Печать_разных_чисел(30, 1)
d = 9 - b
f = 0
Печать_разных_чисел(8, 999)
d = a + 1
Печать_разных_чисел(-4, 73)
f = f - a
End Sub
Вы видите, что в скобках оператора вызова указываются два числа. Это как раз те числа, которые нужно подставить во фрагмент на место переменных Параметр1 и Параметр2. Вот и все отличие.
Мы достигли поставленной цели и укоротили программу путем некоторого усложнения процедуры пользователя и ее вызова.
Переменные, объявленные в скобках заголовка процедуры, называются параметрами процедуры.
В качестве значений параметров в обращениях к процедурам можно писать не только константы, но и переменные, и выражения. Например, вместо
Печать_разных_чисел(30, 1)
можно было написать
Dim m = 30
Печать_разных_чисел (m, m - 29)
Вообще, вы наверное уже привыкли, что в VB там, где можно писать числа, чаще всего можно писать и переменные, и выражения. Это же относится и к другим литералам, например, строкам.
Параметры разных типов. Параметры могут иметь не только числовой, но и строковый и многие другие типы. Пример:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Печатаем_3_раза("Кто там? - Это почтальон Печкин!")
Печатаем_3_раза("Дядя Федор")
End Sub
Private Sub Печатаем_3_раза(ByVal Что_нибудь As String)
Debug.WriteLine(Что_нибудь)
Debug.WriteLine(Что_нибудь)
Debug.WriteLine(Что_нибудь)
End Sub
Здесь вы видите процедуру пользователя Печатаем_3_раза и ее параметр – строковую переменную с именем Что_нибудь. При нажатии на кнопку программа начинает работать и печатает следующий текст:
Кто там? - Это почтальон Печкин!
Кто там? - Это почтальон Печкин!
Кто там? - Это почтальон Печкин!
Дядя Федор
Дядя Федор
Дядя Федор
Пример процедуры с параметрами
После бессмысленного примера процедур с параметрами хотелось бы рассмотреть реальный пример. Вернемся ко 2 варианту нашей программы из 11.1.2 об альбоме рисунков и стирании старого рисунка. Изменим его слегка, чтобы перед каждым рисунком стирание шло по-своему. Пусть у каждой картинки будет свое время стирания и своя музыка.
'Рисуем пушку:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim i As Integer
Beep()
For i = 1 To 10000
Граф.FillEllipse(Brushes.White, Me.Width * Rnd(), Me.Height * Rnd(), 10, 50)
Next
Граф.Clear(Color.White)
Плеер.FileName = "D:\WINNT\Media\tada.wav"
Граф.DrawEllipse(Pens.Black, 100, 100, 200, 200) 'Колесо
Граф.DrawLine(Pens.Black, 20, 300, 350, 20) 'Ствол
End Sub
'Рисуем Буратино:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Dim i As Integer
Beep()
For i = 1 To 2000
Граф.FillEllipse(Brushes.White, Me.Width * Rnd(), Me.Height * Rnd(), 10, 50)
Next
Граф.Clear(Color.White)
Плеер.FileName = "D:\WINNT\Media\chimes.wav"
Граф.DrawEllipse(Pens.Black, 100, 100, 200, 200) 'Голова
Граф.DrawLine(Pens.Black, 300, 200, 400, 200) 'Нос
End Sub
Мы замечаем здесь тот же фрагмент о стирании, но он уже в обеих процедурах чуть-чуть неодинаковый. Наша задача прежняя – укоротить программу. Спрашивается, как сделать это в усложнившихся условиях? Точно так же, как мы поступили в 11.1.3 с бессмысленной программой.
Посмотрим повнимательнее, в чем неодинаковость. Мы видим, что она имеет место в двух местах фрагмента: в операторе For i=1 To (число каждый раз разное) и в операторе Плеер.FileName = (указываются разные звуковые файлы).
Придумываем имя для числа – Число_эллипсов. Придумываем имя для строки с именем файла – Звуковой_файл.
В теле процедуры вместо изменчивых числа и строки записываем имена переменных Число_эллипсов и Звуковой_файл. Затем объявляем их в заголовке процедуры, причем Звуковой_файл объявляем, конечно, как String. В скобках операторов вызова указываем число и строку. Это как раз те число и строка, которые нужно подставить во фрагмент на место переменных Число_эллипсов и Звуковой_файл.
Получается вот что:
'Рисуем пушку:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Стирание_старого_рисунка(10000, "D:\WINNT\Media\tada.wav")
Граф.DrawEllipse(Pens.Black, 100, 100, 200, 200) 'Колесо
Граф.DrawLine(Pens.Black, 20, 300, 350, 20) 'Ствол
End Sub
'Рисуем Буратино:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Стирание_старого_рисунка(2000, "D:\WINNT\Media\chimes.wav")
Граф.DrawEllipse(Pens.Black, 100, 100, 200, 200) 'Голова
Граф.DrawLine(Pens.Black, 300, 200, 400, 200) 'Нос
End Sub
Sub Стирание_старого_рисунка(ByVal Число_эллипсов As Integer, ByVal Звуковой_файл As String)
Dim i As Integer
Beep()
For i = 1 To Число_эллипсов
Граф.FillEllipse(Brushes.White, Me.Width * Rnd(), Me.Height * Rnd(), 10, 50)
Next
Граф.Clear(Color.White)
Плеер.FileName = Звуковой_файл
End Sub
Мы видим, что параметры могут иметь разные типы.
Теперь мы понимаем, зачем в конце заголовков процедур и в других операторах ставится пара скобок (). Это для параметров, буде они объявятся.
Вызов процедур из процедуры пользователя
Вызываемая процедура сама в процессе своей работы может вызвать какую-нибудь другую процедуру. Та – тоже. И так далее. Потренируемся. Определим без компьютера, что напечатает программа:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Write(1)
A()
Write(2)
B()
Write(3)
End Sub
Sub C()
Write(7)
End Sub
Sub B()
Write(5)
C()
Write(6)
End Sub
Sub A()
Write(4)
End Sub
Мы видим здесь 4 процедуры с именами Button1_Click, C, B, A. Работать программа начинает по щелчку по кнопке Button1 с выполнения процедуры Button1_Click. В процессе своей работы эта процедура вызовет по очереди процедуры A и B. Порядок записи объявлений процедур в окне кода безразличен и не влияет на порядок их вызова. Поглядите на тело процедуры B. В процессе своей работы эта процедура вызовет процедуру C. Процедура пользователя вызвала процедуру пользователя. Это не только допустимо, но и приветствуется. Помните только, что когда вызываемая процедура C свое отработала, компьютер возвращается туда, откуда она была вызвана, то есть внутрь процедуры B, после чего выполняется следующий оператор, в нашем случае Write(6).
Вряд ли вам с непривычки удастся без компьютера угадать правильный ответ. Тогда непременно программу – в компьютер и – пошаговый режим. Желтая полоска будет скакать по программе туда-сюда. Перед каждым нажатием на F11 вы обязаны предсказать, куда она прыгнет! Не сможете – нет смысла читать книгу дальше.
Ответ:
1425763
Вы спросите: зачем все это нужно? Это не просто нужно, без этого при программировании не обойтись.
Операторы Stop, End и Exit Sub
До сих пор мы писали процедуры, которые выполняли свою работу до конца и заканчивали ее, как положено, то есть только на операторе End Sub, не раньше. Существуют ли операторы, которые подобно операторам выхода из цикла Exit Do и Exit For заставляют компьютер покинуть процедуру, не доходя до ее конца? Такие операторы существуют.
End. Оператор End заставляет VB завершить работу не только процедуры, а всего проекта. Пример: программа
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Write(1)
Write(2)
End
Write(3)
End Sub
напечатает
12
после чего End завершает режим работы и проект переходит в режим проектирования.
Потренируйтесь. Определите без компьютера, что напечатает программа:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Write(2) : A() : Write(3) : End : Write(4)
End Sub
Sub A()
Write(6) : End : Write(7)
End Sub
Ответ:
26
Помните, что при выполнении оператора End и при завершении работы проекта кнопкой Stop Debugging на панели инструментов событие формы Closed не наступает.
Stop. Ненавистник пошагового режима мог бы мечтать о таком способе отладки: «Хорошо бы существовал специальный оператор паузы, чтобы наткнувшись на него, компьютер приостанавливал выполнение программы, а мы могли бы спокойно посмотреть на результаты и подумать». Такой оператор есть, это Stop.
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Write(1)
Write(2)
Stop
Write(3)
End Sub
Здесь программа напечатает 12 и сделает паузу. После щелчка по Start она допечатает 3 и закончит выполнение процедуры.
Механика работы оператора Stop проста: наткнувшись на него, компьютер переходит в режим прерывания. Фактически оператор Stop равносилен установке точки прерывания. Как только Stop выполнился, вы можете делать все, что можно делать в режиме прерывания, например узнавать значения переменных. Чтобы продолжить выполнение программы, вы щелкаете по кнопке Start. Тогда он продолжает работу с того места, где остановился. А если хотите, можете вместо Start нажать F11 и тем самым продолжить выполнение программы в пошаговом режиме.
Exit Sub. Оператор Exit Sub не такой решительный, как End. Он не выбрасывает VB из режима работы, а просто заставляет компьютер выйти из процедуры, в которой он выполнился. Если он выполнился в вызываемой процедуре, то VB возвращается, как положено, в процедуру, ее вызвавшую, на положенное место. Если он выполнился в процедуре обработки события, то VB просто завершает работу этой процедуры.
Пример:
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
Write(2) : B() : Write(3) : Exit Sub : Write(4)
End Sub
Sub B()
Write(6) : Exit Sub : Write(7)
End Sub
Эта программа напечатает
263
Задание 62.
Вот вам программа с процедурами. Вам нужно, не запуская ее, записать на бумажке весь разговор, который ведут герои «Трех мушкетеров».
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
WriteLine("Я, король Франции, спрашиваю вас - кто вы такие? Вот ты - кто такой?")
ATOS()
WriteLine("А ты, толстяк, кто такой?")
PORTOS()
WriteLine("А ты что отмалчиваешься, усатый?")
DARTANIAN()
WriteLine("Анна! Иди-ка сюда!!!")
Exit Sub
WriteLine("Аудиенция закончена, прощайте!")
End Sub
Sub ATOS()
WriteLine("Я - Атос")
End Sub
Sub ARAMIS()
WriteLine("Это так же верно, как то, что я - Арамис!")
End Sub
Sub PORTOS()
WriteLine("А я Портос! Я правильно говорю, Арамис?")
ARAMIS()
WriteLine("Он не врет, ваше величество! Я Портос, а он Арамис.")
End Sub
Sub DARTANIAN()
WriteLine("А я все думаю, ваше величество - куда девались подвески королевы?")
Exit Sub
WriteLine("Интересно, что ответит король?")
PORTOS()
End Sub
Сверьте с ответом. Если не сходится, запустите программу в пошаговом режиме.
Задание 63.
Среди графических методов VB нет методов «крестик» и «треугольник». Вы можете возместить этот недостаток, написав две соответствующие процедуры с тремя параметрами: координата_х, координата_у, размер. Если не получается, то прочтите следующий раздел и тогда получится обязательно.
Процедуры
До сих пор мы с вами имели дело только с процедурами, задающими реакцию компьютера на те или иные события (нажатие на кнопку, загрузка формы и т.д.). Если вспомнить пример программы из 1.1, то это процедуры типа "Что делать, если …". Обязательно перечитайте 1.1, если вы забыли, что это такое, так как настала пора познакомить вас с другими процедурами – процедурами типа "Как…". Будем называть их процедурами пользователя.
Задание на проект
Задание на проект. Нарисовать парк под луной (см. Рис. 11.1):

Рис. 11.1
Мы создадим программу, которая широко использует процедуры пользователя с параметрами. Я разберу с вами эту задачу не столько для того, чтобы у нас получился правильный рисунок, сколько для того, чтобы вы привыкли в правильном порядке работать с большими задачами. Поэтому не нужно стараться сделать все по-своему, чтобы только побыстрее выполнить задание. Старайтесь уловить порядок работы. Ведь это будет наш первый реальный проект с взаимодействующими процедурами.
Пытаемся угадать, где тут будут процедуры пользователя. На рисунке мы видим несколько деревьев. Каждое из них придется рисовать. Если бы мы решили действовать по старинке, то в программе у нас присутствовало бы несколько десятков почти одинаковых фрагментов, рисующих деревья в разных местах. Кошмар! Мы, конечно, будем вместо этого создавать процедуру пользователя Дерево. Так, одну процедуру угадали. Аналогично угадываем процедуру Фонарь. С остальным неясно: звездное небо, месяц … Ладно, угадывание остальных процедур отложим. Займемся деревом.
Проповедь для язычников. Начинающим программистам не хочется писать процедуры пользователя, как не хочется им писать и длинные имена. «Ну и лопухи же эти так-называемые профессиональные программисты, что осложняют себе жизнь этой морокой!» – думают они – «Наши программы отлично работают и безо всего этого». Верно, работают. Потому что программы коротенькие. Когда они станут длинными, то будут напоминать винегрет, и тогда все такие подрастающие программисты дружно зарыдают: "Мамочка, мы ни в чем не можем разобраться! Почему ты нас в детстве не научила слушаться взрослых?!"
Процедуры должны быть короткими. Запомните еще одно хорошее правило: Размеры любой процедуры не должны превышать одного экрана монитора. Если превышают, то даже если в ней нет повторяющихся фрагментов, все равно разбейте ее по смыслу на два-три фрагмента и каждый сделайте процедурой. Ваша программа будет гораздо легче читаться.
Два способа программирования
Основываясь на опыте составления этой программы и на опыте мирового программирования, можно сказать, что существуют два способа программирования:
Способ «сверху вниз», когда сначала вся задача дробится на части и каждая часть программируется по-отдельности. Если среди частей попадаются слишком крупные, они тоже дробятся на все более мелкие части до тех пор, пока не станет ясно, как любую из этих мелких частей запрограммировать. В нашем примере мы в основном так и поступали.
Можно программировать и по способу «снизу вверх», когда программист сначала составляет список самых мелких частей, из которых состоит задача, программирует их, а потом собирает из них более крупные части. Когда мы в нашем примере начали работу с программирования процедур Дерево и Фонарь, мы пользовались именно этим способом.
Из сказанного ясно, что при программировании можно пользоваться комбинацией этих способов. Что бы вы ни программировали в будущем, начинать вы будете с выбора одного из этих способов или их комбинации в нужной пропорции.
Задание 64.
Попробуйте придумать какую-нибудь картинку и запрограммировать ее. Надо только, чтобы на ней присутствовали одинаковые предметы разной величины. Пусть это будет атака космических кораблей. Или длинный стол, накрытый на 24 персоны (вид сверху-сбоку в перспективе). Или какой-нибудь орнамент. Или батальон на марше.
Задание это важно выполнить. Потому что у вас нет пока опыта самостоятельного создания больших проектов с процедурами пользователя и вы не скоро его еще наберетесь.
От чисел – к переменным
Дерево. Создайте новый проект. Создайте в нем единственную кнопку Рисуем. При нажатии на эту кнопку будет рисоваться весь пейзаж целиком. Значит в процедуре обработки нажатия на эту кнопку будет много обращений к процедурам пользователя, рисующим те или иные элементы пейзажа. Одна из них – Дерево. Создайте ее и попробуйте в ней нарисовать дерево. Пока безо всяких переменных. Вот что получилось у меня:
Public Class Form1
Inherits System.Windows.Forms.Form
Windows Form Designer generated code
Dim Гр As Graphics
Private Sub Рисуем_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Рисуем.Click
Гр = Me.CreateGraphics
Дерево()
'Здесь будет много вызовов других процедур:
' . . . . . . . . .
End Sub
Sub Дерево()
Гр.FillEllipse(Brushes.Green, 200, 100, 20, 60) 'Зеленая крона
Гр.DrawEllipse(Pens.Black, 200, 100, 20, 60) 'Черный контур для зеленой кроны
Гр.DrawLine(Pens.Black, 200 + 10, 100 + 60, 200 + 10, 100 + 60 + 15) 'Ствол
End Sub
End Class
Дерево получилось примерно такое, как на рисунке. Смысл чисел такой. 200 и 100 определяют местоположение дерева. Я их взял наобум. 20 и 60 – размер кроны. В строке процедуры, рисующей ствол, я написал много плюсов. Это чтобы было понятней, откуда взялись числа. Ведь, чтобы ствол находился на нужном месте, их величина должна зависеть от положения и размеров кроны. Так вот. 10 – это половинка от 20, иначе бы ствол был смещен влево или вправо. 15 – длина ствола.
Внимательно разберитесь в смысле чисел, иначе дальнейшее изложение будет непонятным.
Числа – плохо, переменные – хорошо. Забудем пока о грядущих параметрах, подумаем о вреде чисел.
Предположим, вам захотелось нарисовать дерево поправее. Для этого вам нужно в процедуре в четырех местах поменять число 200 на 250. Вот то-то и неудобно, что в четырех, а не в одном. Слишком много труда. В нашей программе это, конечно, пустяк, а вот в больших и сложных программах одна и та же величина может встречаться сотни раз, и чтобы ее изменить, придется вносить сотни исправлений.
Посмотрим, как нам помогут переменные величины. Придумаем переменную величину с именем x и везде заменим 200 на x. Вот как выглядит та же процедура, но с использованием переменной величины:
Sub Дерево()
Dim x As Single = 200
Гр.FillEllipse(Brushes.Green, x, 100, 20, 60)
Гр.DrawEllipse(Pens.Black, x, 100, 20, 60)
Гр.DrawLine(Pens.Black, x + 10, 100 + 60, x + 10, 100 + 60 + 15)
End Sub
Теперь для того, чтобы изменить горизонтальное положение дерева, достаточно заменить число 200 только в одном месте.
Избавляемся ото всех чисел. То, что мы сделали, настолько мне нравится, что я хочу заменить все числа переменными. Вот что у меня получилось:
Sub Дерево()
Dim x As Single = 200
Dim y As Single = 100
Dim Ширина_кроны As Single = 20
Dim Высота_кроны As Single = 60
Dim Длина_ствола As Single = 15
Гр.FillEllipse(Brushes.Green, x, y, Ширина_кроны, Высота_кроны)
Гр.DrawEllipse(Pens.Black, x, y, Ширина_кроны, Высота_кроны)
Гр.DrawLine(Pens.Black, x + Ширина_кроны / 2, y + Высота_кроны, _
x + Ширина_кроны / 2, y + Высота_кроны + Длина_ствола)
End Sub
Как видите, для этого хватило 5 переменных. Обратите внимание, что вместо 10 я написал Ширина_кроны/2. Можно было бы для 10 придумать шестую переменную, но слишком увеличивать число переменных тоже ни к чему. А главное – тогда, если бы мы захотели изменить ширину кроны, нам бы пришлось менять значение и шестой переменной тоже, иначе бы ствол был смещен влево или вправо. А это лишний труд.
Вы спросите: а зачем было придумывать переменную для числа 15? Ведь оно встречается только один раз. А на перспективу! Ведь в будущем вы захотите сделать дерево более красивым и сложным. Процедура усложнится и, глядишь, длина ствола будет фигурировать в нескольких местах.
Вторая причина, по которой мы используем переменные вместо всех чисел, состоит в том, что с ними программа, становясь длиннее, тем не менее становится понятнее, так как имена переменным мы придумываем, исходя из их смысла.
И третья причина – из переменных легко получаются параметры. Об этом мы сейчас и поговорим.
Для переменных я выбрал тип Single. Почему – я объяснил в 6.2.8. .
От переменных – к параметрам
Мы видим, что на рисунке деревья находятся в разных местах и имеют разный размер. Значит мы должны создать параметры, управляющие местоположением и размером дерева. Начнем с местоположения.
Параметры местоположения дерева. Очевидно, положением дерева на форме управляют только две переменные: x – по горизонтали и y – по вертикали. Превратим их в параметры. Превратить переменную в параметр – это значит просто переписать ее объявление из тела процедуры в заголовок процедуры:
Private Sub Рисуем_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Рисуем.Click
Гр = Me.CreateGraphics
Дерево(50, 20)
Дерево(150, 40)
End Sub
Sub Дерево(ByVal x As Single, ByVal y As Single)
Dim Ширина_кроны As Single = 20
Dim Высота_кроны As Single = 60
Dim Длина_ствола As Single = 15
Гр.FillEllipse(Brushes.Green, x, y, Ширина_кроны, Высота_кроны)
Гр.DrawEllipse(Pens.Black, x, y, Ширина_кроны, Высота_кроны)
Гр.DrawLine(Pens.Black, x + Ширина_кроны / 2, y + Высота_кроны, _
x + Ширина_кроны / 2, y + Высота_кроны + Длина_ствола)
End Sub
Теперь, щелкнув по кнопке, вы увидите два дерева в разных местах.
Уменьшаем число переменных. Размерами дерева в процедуре управляют три переменные: Ширина_кроны, Высота_кроны и Длина_ствола. Очень гибко, но многовато переменных. Хорошо бы вместо трех иметь одну переменную, определяющую размер дерева.
Чтобы это сделать, придумаем математическую зависимость между тремя переменными. Посмотрим на их значения: 20, 60 и 15. Мы видим, что ширина кроны меньше высоты в 3 раза, а длина ствола – в 4 раза. Зададим новую переменную Размер. Пусть ее величина будет равна высоте кроны. Посмотрите, как упростится теперь наша процедура:
Sub Дерево(ByVal x As Single, ByVal y As Single)
Dim Размер As Single = 60
Гр.FillEllipse(Brushes.Green, x, y, Размер / 3, Размер)
Гр.DrawEllipse(Pens.Black, x, y, Размер / 3, Размер)
Гр.DrawLine(Pens.Black, x + Размер / 6, y + Размер, x + Размер / 6, y + Размер + Размер / 4)
End Sub
Благодаря запрограммированной нами связи между шириной и высотой кроны и длиной ствола, при изменении значения переменной Размер пропорции между частями дерева не изменятся.
Параметр размера дерева. А теперь превратим переменную Размер в параметр:
Private Sub Рисуем_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Рисуем.Click
Гр = Me.CreateGraphics
Дерево(50, 20, 200)
Дерево(150, 50, 100)
Дерево(200, 70, 40)
End Sub
Sub Дерево(ByVal x As Single, ByVal y As Single, ByVal Размер
As Single)
Гр.FillEllipse(Brushes.Green, x, y, Размер / 3, Размер)
Гр.DrawEllipse(Pens.Black, x, y, Размер / 3, Размер)
Гр.DrawLine(Pens.Black, x + Размер / 6, y + Размер, x + Размер / 6, y + 5 * Размер / 4)
End Sub
Теперь, щелкнув по кнопке, вы увидите три дерева в разных местах и разных размеров.
Процедура «Фонарь». Процедуру Фонарь в качестве упражнения сделайте сами. Параметры пусть будут такие же, как и у дерева. Вот что получилось у меня:
Sub Фонарь(ByVal x As Single, ByVal y As Single, ByVal Размер As Single)
Гр.FillRectangle(Brushes.Gold, x, y, Размер / 30, Размер) 'Столб
Гр.DrawRectangle(Pens.Black, x, y, Размер / 30, Размер) 'Черный контур для столба
Гр.FillPie(Brushes.DarkOrange, x + Размер / 30, y, Размер / 5, Размер / 10, 180, 180) 'Плафон
Гр.DrawPie(Pens.Black, x + Размер / 30, y, Размер / 5, Размер / 10, 180, 180) 'Черный контур для плафона
End Sub
Вам не кажется, что, несмотря на наши старания, чисел в процедуре осталось многовато? В общем-то, да. В принципе, всех их надо было превратить в переменные. Но я не захотел затемнять изложение излишними подробностями.
А теперь забегите немного вперед и напишите в теле процедуры Рисуем_Click пять операторов для рисования трех одиночных деревьев и двух одиночных фонарей над прудом примерно в тех местах и тех размеров, что вы видите на рисунке. Получилось? – Прекрасно! А теперь уберите из проекта этих пятерых выскочек, они нарушают стройный и размеренный шаг нашей бронированной армии. Но не уничтожайте их, переместите куда-нибудь или закомментируйте: они вам еще пригодятся.
Делим задачу на части
То, что мы сейчас делали – это учились писать процедуры с параметрами. Но мы еще не приступали к программированию нашего проекта как целого. Процедура Рисуем_Click у нас практически пуста. И вот теперь мы приступаем к планомерной осаде этой крепости.
Когда Наполеон встречал превосходящего по силам врага, он бил его армию по частям. Когда программисту нужно решить сложную задачу, он делит ее на части и программирует каждую часть по-отдельности.
Легко сказать – делит. А как ее разделить, если все в задаче перепутано и взаимосвязано. Здесь, конечно, помогают программистский опыт и мастерство. А если их пока еще нет? Тогда можно руководствоваться соображениями здравого смысла и общей «понятности» разбиения на части. В нашем случае разбиение напрашивается само собой: посмотри, из каких крупных кусков сделан пейзаж – на то и дели! Причем нужно примириться с тем, что мы можем пока и не представлять, как каждый из этих кусков запрограммировать.
Перечислим, что мы видим на рисунке:
Так разделил бы любой прохожий с улицы, ничего не ведающий о программировании. Удивитесь ли вы, если я скажу, что мнение этого прохожего я посчитаю истиной в последней инстанции и именно такие процедуры пользователя обязуюсь создать? Вот как примерно будет выглядеть в окончательном виде главная процедура рисования:
Private Sub Рисуем_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Рисуем.Click
Гр = Me.CreateGraphics
Звездное_небо()
Месяц()
Земля()
Пруд()
Три_одиночных_дерева()
Два_одиночных_фонаря()
Ряд_деревьев()
Ряд_фонарей()
Аллея()
End Sub
Почему я послушался прохожего? Потому что он делил по принципу: как проще и очевидней. А это великий принцип. Будем и мы ему следовать. Потому что:
Понятная программа – правильная программа!
Не изящная, не замысловатая, не сверхкороткая, а именно понятная! Заставляйте компьютер думать так, как привычно человеку, а не наоборот. Потому что наоборот гены не позволят.
Пока я у всех этих процедур не указал параметров. Но если вдруг выяснится, что параметры нужны, мы их, конечно, создадим.
Почему я выбрал именно такой порядок рисования? Можно было бы выбрать и любой другой, только надо помнить, что фигуры, нарисованные, раньше, загораживаются фигурами, нарисованными позже, поэтому одиночные деревья, например, нельзя рисовать раньше пруда.
Программируем части по-отдельности
Ну что ж. Начинаем программировать отдельные части. В каком порядке? Все равно. Но логичнее в том порядке, в каком они появляются на рисунке, а значит в порядке сверху-вниз в теле главной процедуры рисования. Следовательно, начинаем со звездного неба.
Вы спросите, а как же наши процедуры Дерево и Фонарь, над которыми мы так долго трудились? Неужели они не понадобятся? Понадобятся в свое время, причем в полном объеме.
Звездное небо. Прежде чем рисовать звездное небо, надо задать, на каком уровне по вертикали кончается небо и начинается земля. Если мы зададим этот уровень числом, то наверняка это число встретится также при рисовании на горизонте деревьев и фонарей, а может быть и в других процедурах. Так что надо задать это число переменной величиной, а чтобы этой переменной могли пользоваться все процедуры, объявим ее не в теле процедуры, а вне процедур.
Поскольку звездное небо вы уже раньше рисовали, то дальнейшие пояснения не требуются. Вот какое у меня получилось на этом этапе окно кода:
Public Class Form1
Inherits System.Windows.Forms.Form
Windows Form Designer generated code
Dim Гр As Graphics
Dim Уровень_горизонта As Single = 200
Private Sub Рисуем_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Рисуем.Click
Гр = Me.CreateGraphics
Звездное_небо()
End Sub
Sub Звездное_небо()
Dim i, Размер_звезды As Single
Гр.FillRectangle(Brushes.Black, 0, 0, Me.Width, Уровень_горизонта) 'Черный прямоугольник неба
For i = 1 To 100 'Рисуем сто звезд
Размер_звезды = 5 * Rnd()
Гр.FillEllipse(Brushes.White, _
Me.Width * Rnd(), Уровень_горизонта * Rnd(), Размер_звезды, Размер_звезды)
Гр.FillEllipse(Brushes.Yellow, _
Me.Width * Rnd(), Уровень_горизонта * Rnd(), Размер_звезды, Размер_звезды)
Гр.FillEllipse(Brushes.LightBlue, _
Me.Width * Rnd(), Уровень_горизонта * Rnd(), Размер_звезды, Размер_звезды)
Гр.FillEllipse(Brushes.LightPink, _
Me.Width * Rnd(), Уровень_горизонта * Rnd(), Размер_звезды, Размер_звезды)
Next i
End Sub
End Class
Небо – это просто большой черный прямоугольник во всю ширину формы выше уровня горизонта. Мы не видим его краев, так как они совпадают с краями формы или выходят за них После нажатия на кнопку рисования мы увидим черный прямоугольник неба, заполненный разноцветными звездами.
Серп молодого месяца или «В час по чайной ложке»
Если вы видели солнечное или лунное затмения, то знаете, что картинка солнца или луны в этих случаях тоже напоминает месяц, не правда ли? Во всяком случае, у нас принцип получения изображения месяца будет такой же, как при затмении: рисуем желтый круг, а поверх него с небольшим смещением – черный круг. Получается серпик толщиной в это смещение. Программируем:
Процедура Месяц небольшая, но и ее так сразу не напишешь. Начинаем с того, что записываем в окно кода ее заготовку:
Sub Месяц()
End Sub
Затем увеличиваем на один оператор главную процедуру рисования:
Private Sub Рисуем_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Рисуем.Click
Гр = Me.CreateGraphics
Звездное_небо()
Месяц()
End Sub
Запускаем проект. Ничего нового вдобавок к звездному небу, конечно, не рисуется, но мы хотя бы убеждаемся, что VB ни на что не жалуется, значит ошибок нет.
Теперь записываем в процедуру Месяц оператор, рисующий желтый круг. Вряд ли вы уже «на ты» с переменными, поэтому пишите его, не стесняясь – в числах. Запускайте проект и проверяйте, хорошо ли рисуется круг. Если все в порядке, записывайте в процедуру Месяц оператор, рисующий черный круг. Запускайте – проверяйте. Если все в порядке, переходите к замене чисел на переменные. Запускайте – проверяйте. Идея такая: выполнив маленький кусочек работы, обязательно нужно запустить проект и проверить, как он работает. Нужно это для того, чтобы обнаружить ошибку как можно раньше. Представьте ситуацию: ваш проект работал хорошо, потом вы дописали в него 10 строк, запустили – он стал работать плохо. Ясно, что виновата одна из 10 строк, но какая? Попробуйте найти ошибку в 10 строках! А если там не одна ошибка? А теперь другая ситуация: строк не 10, а 1. Ясно, что искать ошибку в 10 раз легче. Вот так: «Тише едешь …».
Вот какая у меня в конце концов получилась процедура:
Sub Месяц()
Dim x As Single = 100
Dim y As Single = 30
Dim Размер_месяца As Single = 50
Dim Толщина_серпа As Single = 10
Гр.FillEllipse(Brushes.Yellow, x, y, Размер_месяца, Размер_месяца) 'Желтый круг
Гр.FillEllipse(Brushes.Black, x - Толщина_серпа, y, Размер_месяца, Размер_месяца) 'Черный круг
End Sub
Земля, пруд, три дерева и два фонаря
Пора переходить к процедуре Земля. Мы будем работать над ней и над другими процедурами точно так же, как и над процедурой Месяц. То есть, сначала запишем в окно кода ее заготовку, затем увеличим на один оператор главную процедуру рисования и затем постепенно будем создавать процедуру Земля, постоянно проверяя результат. Таким же манером мы будем действовать и в дальнейшем, пока проект не будет готов.
Земля. Это большой зеленый прямоугольник ниже уровня горизонта:
Sub Земля()
Гр.FillRectangle(Brushes.LightGreen, 0, Уровень_горизонта, Me.Width, Me.Height)
End Sub
Пруд. Это эллипс черного цвета с белой окантовкой:
Sub Пруд()
Dim x As Single = 400
Dim y As Single = 300
Dim Размер As Single = 200
Гр.FillEllipse(Brushes.Black, x, y, Размер, Размер / 3) 'Вода
Гр.DrawEllipse(Pens.White, x, y, Размер, Размер / 3) 'Берег
End Sub
Обратите внимание, как легко превратить эту процедуру в процедуру с параметрами. Тогда легко будет разместить в парке несколько прудов. В будущем вы сможете делать отражение звезд в пруду.
Три одиночных дерева и два одиночных фонаря. Вот когда понадобились наши Дерево и Фонарь:
Sub Три_одиночных_дерева()
Дерево(420, 240, 50)
Дерево(600, 260, 70)
Дерево(430, 260, 100)
End Sub
Sub Два_одиночных_фонаря()
Фонарь(480, 230, 60)
Фонарь(540, 260, 140)
End Sub
Ряд деревьев
Ряд деревьев – 1 версия. Нарисовать ряд деревьев с помощью процедуры Дерево – все равно, что нарисовать ряд окружностей с помощью процедуры DrawEllipse, что мы делали в 8.5. Вот как выглядит процедура, рисующая подходящий ряд из пары десятков деревьев на горизонте:
Sub Ряд_деревьев()
Dim x As Single = 400
Dim y As Single = Уровень_горизонта - 30
Dim i As Integer
For i = 1 To 20
Дерево(x, y, 30)
x = x + 15
Next
End Sub
Числа 400, 30, 30, 15 я подобрал так, чтобы картинка была более-менее похожа на заданную.
Кстати, вы видите, что в нашей программе мы обращаемся из процедуры пользователя Ряд_деревьев к процедуре пользователя Дерево.
Ряд деревьев – 2 версия. На нашем рисунке присутствуют еще два ряда деревьев. Они чем-то похожи, а чем-то непохожи на наш ряд. Нам нужно решить: будем ли мы, когда дело дойдет до их рисования, скромно создавать для них новые процедуры или же прямо сейчас смело сконструируем единую процедуру с такими хитрыми параметрами, чтобы она могла нарисовать любой из трех рядов деревьев на нашем рисунке. Нет ни малейшего сомнения, что мы выбираем второе.
Эта процедура получится небольшим усложнением 1 версии. Попробуем ее переписать так, чтобы получился левый из двух рядов в аллее. Рисовать нужно начинать с самого дальнего дерева, иначе дальние загородят ближних. Если в первой версии у нас от дерева к дереву менялась только горизонтальная координата x, то здесь у нас должны меняться и вертикальная координата y (увеличиваться), и Размер (увеличиваться). Причем, если в 1 версии x рос, то здесь он должен уменьшаться:.
Sub Ряд_деревьев_2_версия()
Dim x As Single = 220
Dim y As Single = Уровень_горизонта - 30
Dim Размер As Single = 30
Dim i As Integer
For i = 1 To 20
Дерево(x, y, Размер)
x = x - 22
y = y + 20
Размер = Размер + 10
Next
End Sub
Временно допишите в процедуру рисования вызов этой процедуры и проверьте, как она работает. Есть ли у вас уверенность, что меняя только числа в процедуре, вы сможете рисовать любые нужные на рисунке ряды деревьев? У меня есть. Действительно, ряд деревьев на горизонте получается при помощи этой процедуры изменением четырех чисел в следующих строках:
Dim x As Single = 400
x = x + 15
y = y + 0
Размер = Размер + 0
А раз так, то пора числа и переменные превращать в параметры.
Ряд деревьев – окончательная версия. Посмотрим, от каких переменных и чисел зависит вид ряда деревьев. Начнем просматривать нашу процедуру сверху вниз:
Dim x As Single = 220
Dim y As Single = Уровень_горизонта - 30
Dim Размер As Single = 30
Эти три строки определяют положение и размер самого первого дерева ряда. Конечно же, все три переменные должны стать параметрами.
For i = 1 To 20
Эта строка задает число деревьев в ряду. Конечно, мы захотим иметь возможность рисовать ряды с разным числом деревьев. Число 20 превращаем в параметр.
x = x - 22
y = y + 20
Размер = Размер + 10
Три числа: -22, 20, 10 определяют направление ряда деревьев, расстояние между деревьями и увеличение размеров от дерева к дереву. Без них не обойтись. Еще три параметра.
Итого 7 параметров. Все они нужны, ничего не попишешь. Превращаем нашу процедуру в процедуру с параметрами:
Sub Ряд_деревьев(ByVal x As Single, ByVal y As Single, ByVal Размер As Single, ByVal Число_деревьев _
As Integer, ByVal Шаг_по_гориз As Single, ByVal Шаг_по_вертик As Single, ByVal Увеличение As Single)
Dim i As Integer
For i = 1 To Число_деревьев
Дерево(x, y, Размер)
x = x + Шаг_по_гориз
y = y + Шаг_по_вертик
Размер = Размер + Увеличение
Next
End Sub
Вы спросите: почему в строках
x = x + Шаг_по_гориз
y = y + Шаг_по_вертик
стоят плюсы, ведь часто там должны быть и минусы? Отвечаю: Плюсы трогать не будем, а нужного результата будем добиваться отрицательным значением шага. Например, вот как будет выглядеть вызов процедуры для рисования левого из двух рядов в аллее:
Ряд_деревьев (220, Уровень_горизонта - 30, 30, 20, -22, 20, 10)
А вот как – для ряда деревьев на горизонте:
Ряд_деревьев (400, Уровень_горизонта - 30, 30, 20, 15, 0, 0)
У процедуры есть недостаток: расстояния между соседними деревьями вдали и вблизи одинаковы, что не соответствует законам перспективы. Вы можете исправить это положение, сделав шаги зависящими от размера.
Ряд фонарей и аллея
Ряд фонарей. Эта процедура строится совершенно аналогично процедуре Ряд_деревьев. Создайте ее самостоятельно. Вот что получилось у меня:
Sub Ряд_фонарей(ByVal x As Single, ByVal y As Single, ByVal Размер As Single, ByVal Число_фонарей _
As Integer, ByVal Шаг_по_гориз As Single, ByVal Шаг_по_вертик As Single, ByVal Увеличение As Single)
Dim i As Integer
For i = 1 To Число_фонарей
Фонарь(x, y, Размер)
x = x + Шаг_по_гориз
y = y + Шаг_по_вертик
Размер = Размер + Увеличение
Next
End Sub
Процедуры Ряд_деревьев и Ряд_фонарей до смешного одинаковы. Это значит, что если как следует подумать, процедуру Ряд_фонарей не надо было выдумывать, а надо было просто скопировать Ряд_деревьев и немного изменить.
Одинаковость наводит еще на одну мысль: а что, если сделать единую процедуру для рисования ряда деревьев или фонарей? Что именно рисовать, решают еще один строковый параметр и оператор If внутри процедуры. Но это упражнение я оставляю для энтузиастов.
Теперь добавьте в главную процедуру рисования вызов процедуры рисования ряда фонарей на горизонте:
Ряд_фонарей (20, Уровень_горизонта - 30, 70, 6, 25, 0, 0) 'на горизонте
Кстати, ваше окно кода сильно выросло и доставляет вам неудобство тем, что для поиска нужной процедуры вам приходится его долго прокручивать вверх-вниз. Хотите избавиться от лишней прокрутки – перечтите 6.1.3 и щелкните по крохотным минусикам у заголовков ненужных пока процедур. Их тела скроются из вида, освободив дефицитное место. Чтобы их вернуть, щелкайте по плюсикам.
Аллея. Мы прекрасно понимаем, что вместо создания и вызова процедуры Аллея можно бы просто написать два вызова процедуры Ряд_деревьев и один вызов процедуры Ряд_фонарей. И мороки было бы меньше. Все это верно. Но в этом случае мы ослушались бы прохожего, а этого нельзя делать, потому что мы нарушили бы великий принцип простоты и наглядности. К тому же, кто знает, может быть в будущем наш парк украсится несколькими аллеями? А?
Пояснять процедуру Аллея излишне. Вот она:
Sub Аллея()
Ряд_деревьев(220, Уровень_горизонта - 30, 30, 20, -22, 20, 10) 'левый
Ряд_деревьев(240, Уровень_горизонта - 30, 30, 20, 12, 20, 10) 'правый
Ряд_фонарей(220, Уровень_горизонта, 70, 20, -36, 64, 30)
End Sub
Обратите внимание, что значение первого параметра во всех трех обращениях почти одинаково. Это наводит на мысль превратить его в параметр процедуры Аллея. Вот так:
Sub Аллея(ByVal x As Single)
Ряд_деревьев(x, Уровень_горизонта - 30, 30, 20, -22, 20, 10) 'левый
Ряд_деревьев(x + 20, Уровень_горизонта - 30, 30, 20, 12, 20, 10) 'правый
Ряд_фонарей(x, Уровень_горизонта, 70, 20, -36, 64, 30)
End Sub
А вот окончательный вид главной процедуры рисования парка:
Private Sub Рисуем_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Рисуем.Click
Гр = Me.CreateGraphics
Звездное_небо()
Месяц()
Земля()
Пруд()
Три_одиночных_дерева()
Два_одиночных_фонаря()
Ряд_деревьев(400, Уровень_горизонта - 30, 30, 20, 15, 0, 0) 'на горизонте
Ряд_фонарей(20, Уровень_горизонта - 30, 70, 6, 25, 0, 0) 'на горизонте
Аллея(220)
End Sub
Проект завершен.
Меняя параметр процедуры Аллея, мы сдвигаем всю аллею целиком влево-вправо. Ощущение могущества! Чем хорошо программирование: минимальные усилия, а результат меняется колоссально. Если бы мы рисовали вручную, сколько времени нам понадобилось бы, чтобы перерисовать всю аллею?
Попробуйте как следует понизить уровень горизонта. Вся земля сдвинулась, а пруд и окружающие его деревья и фонари остались на месте. В результате чего очутились на небе. Н-да-а … Это потому, что при их рисовании мы уровень горизонта не учитывали.
Проект «Парк под луной»
Теперь вы достаточно знаете о процедурах пользователя, чтобы они стали для вас удобными кирпичиками для постройки программ. Уже сейчас вы готовы составлять с их помощью более качественные программы, чем раньше. Однако, в моих примерах я писал процедуры пользователя не сразу. Сначала я для большей понятности писал почти одинаковые фрагменты, а уж от них шел к процедурам пользователя. В реальном программировании никто не пишет одинаковых фрагментов перед тем, как написать процедуру пользователя. Ее пишут сразу. И именно этому мы сейчас будем учиться.
Создание, инициализация и уничтожение переменных
Перед тем, как начать работать, переменная величина автоматически, независимо от желания программиста, инициализируется, то есть ей присваивается некоторое начальное значение. Поясним на примере.
Создайте проект для изучения переменных и в нем такую бессмысленную процедуру:.
Private Sub Button4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button4.Click
Dim b As Integer
b = 2
Dim a As Integer = 5
a = b
Dim c As Integer
c = b
Dim d As String = "Привет"
End Sub
Запустите проект в пошаговом режиме. Имейте в виду, что пока вы еще не нажали на кнопку Button4 (то есть пока процедура не выполняется), переменные в процедуре не имеют никаких значений. Но как только желтая полоса встала на заголовок процедуры в знак того, что процедура начала выполняться, немедленно произошла инициализация: VB нашел все операторы Dim внутри процедуры и всем числовым переменным согласно указанным в них типам (но не значениям, приведенным после знака равенства!) присвоил значение 0, а строковым – Nothing, что означает «Ничто». Проверьте.
А теперь потихоньку переводите желтую полосу от оператора к оператору, следя за значениями переменных и обращая внимание вот на что: Поскольку операторы Dim компьютер выполнил в самом начале, теперь он через них просто перепрыгивает, однако, если в операторе Dim есть присваивание, то оно, конечно, выполняется.
Оператор Dim может быть записан в любом месте процедуры, однако не ниже появления «своей» переменной в теле процедуры. Например, ошибкой было бы поменять местами первые две строки тела нашей процедуры. Из-за этого программисты обычно собирают операторы Dim в верхней части процедуры.
Когда желтая полоса уйдет со строки End Sub в знак того, что процедура закончила выполняться, переменные потеряют свои значения.
Закон такой:
Переменная, объявленная внутри процедуры, не существует в те периоды времени, когда эта процедура не выполняется. Она создается в тот момент, когда VB начинает выполнять эту процедуру. На уровне «железа» это выражается в том, что в этот момент под переменную отводится ячейка памяти. Тут же переменная инициализируется. Пока процедура работает, переменная живет и работает тоже. Когда VB выходит из этой процедуры, ячейка уничтожается вместе с переменной и ее значением.
Если переменная встречается в теле процедуры, но не объявлена в операторе Dim, то она просто не создается и VB выдает ошибку.
Этот закон касается только переменных, объявленных внутри процедуры. Но есть и другие переменные, о которых чуть позже.
Области видимости переменных
Шапка-невидимка. Введите в окно кода такой код:
Public Class Form1
Inherits System.Windows.Forms.Form
Windows Form Designer generated code
Dim a As Integer = 5
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim b As Integer = 4
a = b
End Sub
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
a = b
End Sub
End Class
VB подчеркнул во второй из двух процедур переменную b. Ошибка. При наведении мышки на ошибку VB выдал подсказку: Name 'b' is not declared, что означает «Имя b не объявлено». Как не объявлено?! Ведь в первой процедуре мы b объявили! В чем дело? Дело в так называемых областях видимости. Закон такой:
Переменная, объявленная внутри процедуры, не видна снаружи, в том числе и из других процедур.
Происходит вот что: Когда первая процедура выполняется, ее переменная b живет и здравствует, но второй процедуре от этого никакой выгоды нет, так как она сама в это время спит летаргическим сном и ждет своей очереди. Когда же мы ее будим щелчком по кнопке Button2, то оказывается, что первая процедура уже заснула, а значит и ее переменная b уничтожена. Уничтожена – не уничтожена! Какая разница, если это все равно чужая переменная, то есть принадлежащая другой процедуре?!
Умный VB предвидит эту ситуацию и подчеркивает переменную b во второй процедуре, намекая, что неплохо бы ее там объявить. Ну что ж, объявим, добавив оператор во вторую процедуру:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Dim b As Integer = 1
a = b
End Sub
Теперь все в порядке и все работает.
Стеклянные стены. Вопрос: а почему VB не жаловался на переменную a? Ведь мы ее не объявляли ни в одной процедуре. Ответ: а потому что мы ее объявили вне процедур. Закон такой:
Переменная, объявленная вне процедур, видна изо всех процедур окна кода.
Создается и инициализируется такая переменная раньше тех, что объявлены в процедурах, а уничтожается позже.
Тезки. Еще один вопрос, «философский»: правда ли, что переменная b из первой процедуры и переменная b из второй процедуры – это одна и та же переменная, время от времени рождающаяся и умирающая, или же это «тезки» – две разные переменные, только с одним именем? Ответ однозначный: верно второе. В доказательство рассмотрим ситуацию, когда обе процедуры работают одновременно:
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
Dim b As Integer
b = 11
Процедура()
Debug.WriteLine(b)
b = 4
End Sub
Sub Процедура()
Dim b As Integer
b = 8
Debug.WriteLine(b)
End Sub
После щелчка по кнопке Button3 компьютер напечатает вот что:
8
11
Запустите проект в пошаговом режиме. После выполнения оператора b=11 компьютер прыгает на процедуру пользователя, создает ее переменную b и начинает процедуру выполнять. Обратите внимание, что выполнение первой процедуры при этом не завершено, а значит и переменная b этой процедуры не уничтожена. К сожалению, пошаговый режим не показывает нам разницу значений этих переменных, однако оператор Debug.WriteLine(b) делает это безошибочно. Первой выполняется печать в процедуре пользователя и, естественно, печатается 8. Затем компьютер возвращается в первую процедуру, прямо к выполнению ее оператора Debug.WriteLine(b). Если бы переменная b была одна на обе процедуры, то конечно была бы напечатана еще раз 8. Однако у каждой из процедур своя переменная, а чужая вообще не видна, поэтому оператор Debug.WriteLine(b) спокойно печатает свою переменную b, которая равна 11.
Итак:
Переменные с одинаковыми именами, объявленные в разных процедурах, являются разными переменными. Не путайте.
Терминология. Подводим итоги:
Мы обнаружили две области видимости переменных: конкретная процедура или все окно кода.
Если переменные объявлены внутри процедуры, то они и использованы могут быть только внутри нее. Их зона видимости – эта конкретная процедура. Называют такие переменные локальными переменными.
Параметры процедуры тоже являются локальными переменными в этой процедуре, так как они объявлены именно в ее заголовке.
Если переменные объявлены вне процедур, то они могут быть использованы во всем окне кода внутри любой процедуры. Называют такие переменные модульными переменными или переменными уровня модуля. О причине такой терминологии поговорим попозже (21.9).
Тезки из разных областей. Теперь вопрос относительно модульной переменной: что будет, если нам захотелось назвать одинаковыми именами локальную переменную и переменную уровня модуля? Проверим:
Public Class Form1
Inherits System.Windows.Forms.Form
Windows Form Designer generated code
Dim a As Integer = 5
Private Sub Button5_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button5.Click
Dim a As Integer = 44
Debug.WriteLine(a)
End Sub
End Class
Здесь компьютер напечатает
44
Спрашивается: что это было – одна и та же переменная или разные? А если разные, то почему предпочли локальную, ведь из процедуры видны обе? Или нет? Ответом будет закон «Своя рубашка ближе к телу»: Если к вам в гости пришли Петя и Коля, а у вас в доме уже живет член семьи Коля, то вы пришедшего Колю не пускаете на порог. А с Петей все нормально. В переводе на язык программистов:
Локальная и модульная переменные с одинаковыми именами являются разными переменными, причем модульная переменная не видна из процедуры, где объявлена локальная переменная с тем же именем.
Говорят: локальная переменная затеняет в своей процедуре модульную с тем же именем.
Тщательно пережевывайте пищу. Если вы поняли все, что я только что объяснял, то без компьютера определите, что напечатает программа:
Public Class Form1
Inherits System.Windows.Forms.Form
Windows Form Designer generated code
Dim x As Integer 'x - модульная переменная
Dim z As Integer 'z - модульная переменная
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
x = 1 : z = 2 'x и z - модульные переменные
B()
Debug.WriteLine(x & " " & z) 'x и z - модульные переменные
End Sub
Private Sub B()
Dim x As Integer 'x - локальная переменная
Dim y As Integer 'y - локальная переменная
x = 20 : y = 30 : z = 40
End Sub
End Class
Ответ приведен в конце подраздела.
Если с ответом не сходится, то значит вы поняли не все, и вот вам пояснение:
Оператор Debug.WriteLine(x & " " & z) находится снаружи процедуры В, и поэтому локальная переменная х=20, объявленная внутри В, из него не видна. Зато прекрасно видна модульная переменная х=1, которую он и печатает. Переменная же z не объявлена внутри В, поэтому она является модульной переменной, и оператор z=40 с полным правом меняет ее значение с 2 на 40.
А теперь приведу длинное пояснение «ближе к железу»:
Переменная х, объявленная снаружи процедуры, это совсем другая переменная, чем х, объявленная в процедуре, и помещаются эти переменные в разных местах памяти. Поэтому и не могут друг друга испортить. Вы можете вообразить, что это переменные с разными именами xмод и xлок.
Переменная, объявленная снаружи процедуры, видна из любой процедуры окна кода и каждая процедура может ее испортить. Однако, когда процедура натыкается на переменную x, объявленную внутри самой этой процедуры, то она, благодаря описанному выше механизму, работает только с ней и не трогает переменную x, объявленную снаружи.
Вот порядок выполнения программы:
В оперативной памяти VB отводит ячейки под Х мод и Z мод.
Процедура Button1_Click начинает выполняться с присвоения значений Х мод = 1 и Z мод = 2. Почему мод, а не лок? Потому что переменные с именами X и Z в процедуре Button1_Click не объявлены, а значит волей-неволей процедуре приходится пользоваться модульными переменными.
Вызывается процедура В. При этом в оперативной памяти отводится место под Х лок и У лок.
Присваиваются значения Х лок = 20, У лок = 30 и Z мод = 40.
Программа выходит из процедуры В. При этом исчезают переменные Х лок (20) и У лок (30). А Z мод (40) остается.
Компьютер печатает Х мод = 1 и Z мод = 40.
Таким образом программа напечатает 1 и 40.
Зачем нужны разные области видимости
Зачем такие ограничения и неудобства? Какой во всем этом смысл? Поговорим об этом.
Создание разных областей видимости для разных переменных является способом повышения надежности больших программ и понижения вероятности запутаться при их написании. Программы, создаваемые сегодня профессиональными программистами, очень велики – десятки и сотни тысяч строк. Таково, например, большинство игровых программ. Естественно, один человек не может достаточно быстро создать такую программу, поэтому пишется она обычно группой программистов. Для этого программа делится на части, и каждый программист пишет свою часть. И все равно, в каждой части присутствуют десятки и сотни процедур с десятками и сотнями переменных, в которых человеку легко запутаться. Ну а уж когда все эти части соединяют вместе, начинается кавардак!
Рассмотрим бессмысленную «сложную» программу, не использующую локальных переменных, и покажем, как она из-за этого уязвима. Пусть в ней присутствуют сотни процедур. Рассмотрим две из них:
Dim x As Integer
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
x = 2 * 2
B()
Debug.WriteLine(x)
End Sub
Private Sub B()
x = 5 * 5
Debug.WriteLine(x)
End Sub
Автор первой процедуры хочет, чтобы человечество узнало, сколько будет дважды два, для чего и написал первую и третью строчку своей процедуры. Между ними вклинилась вторая строчка – вызов процедуры B. Для чего она нужна, автор и сам не знает, но начальство потребовало, чтобы она была вставлена именно в это место процедуры в интересах проекта. Ну и ладно, жалко что ли?
Автор второй процедуры сообщает человечеству результат умножения 5 на 5.
И все было бы хорошо, и все были бы довольны, если бы авторов не угораздило назвать переменные одинаковыми именами. Начальство ожидало, что сначала компьютер напечатает 25, а потом 4, но на самом деле, как легко видеть, будет напечатано
25
25
Для защиты от таких ошибок директор проекта, не использующего локальных переменных, должен внимательно следить, чтобы разные процедуры не использовали переменных с одинаковыми именами. Но для больших программ этот контроль очень трудоемок и неудобен. Для того, чтобы избежать его, в современных языках программирования и разработан механизм локальных переменных. Если программист знает, что его данные нигде, кроме как в родной процедуре, не нужны, он объявляет соответствующие переменные любыми именами внутри
процедуры, ничуть не заботясь, что переменные с такими же именами встречаются в других местах программы. И все нормально работает:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim x As Integer
x = 2 * 2
B()
Debug.WriteLine(x)
End Sub
Private Sub B()
Dim x As Integer
x = 5 * 5
Debug.WriteLine(x)
End Sub
Данная программа к удовольствию директора напечатает
25
4
Таким образом, вы видите, что смысл механизма локальных переменных – в увеличении надежности программирования. Я настоятельно советую:
Те переменные, которые наверняка не понадобятся вне процедуры, делайте локальными.
Область видимости – блок
Блоком называется последовательность всех выполняющихся друг за другом операторов. Если вы помните, эти операторы легко узнать, так как у них одинаковый отступ от левого края окна. Блоки могут входить внутрь других блоков. Рассмотрим бессмысленную программу:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Dim a, b, c As Integer
b = 6
For a = 5 To 6
b = 8
c = 2 * 2
Next
a = 1
If a > 0 Then
a = 7
c = 5 * 5
a = 8
End If
a = 3
End Sub
Все тело процедуры есть блок из 5 операторов:
b = 6
For . . .
a = 1
If . . .
a = 3
Внутрь этого блока входят два блока. Один есть содержимое оператора For:
b = 8
c = 2 * 2
Другой – содержимое оператора If:
a = 7
c = 5 * 5
a = 8
Если бы внутрь этих двух блоков входили сложные операторы, то операторы, из которых состоят эти сложные операторы, тоже составляли бы блоки, входящие в упомянутые. Матрешка.
Оказывается, целесообразно иметь области видимости меньшие, чем процедура. Это блоки.
Зачем они могут понадобиться? Ну, например, мы видим, что в нашем примере переменная c в каждом блоке делает «важные» дела, причем в каждом блоке свои. Мы опасаемся, как бы при дальнейшей доработке программы значения переменной c из одного блока при нашей невнимательности не повредили бы значения переменной c из другого блока. Что делать для спокойствия? Уберем объявление c из начала процедуры и объявим переменную c внутри каждого блока:
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
Dim a, b As Integer
b = 6
For a = 5 To 6
Dim c As Integer
b = 8
c = 2 * 2
Next
a = 1
If a > 0 Then
Dim c As Integer
a = 7
c = 5 * 5
a = 8
End If
a = 3
End Sub
Теперь мы имеем в процедуре две совершенно независимые переменные, имеющие одинаковое имя c. Каждая из них создается и инициализируется в момент, когда компьютер заходит в ее блок. Она живет и работает, пока работает блок. Когда же компьютер выходит из блока, переменная уничтожается вместе со своим значением.
Только не вздумайте объявлять переменную, имеющую то же имя, в начале процедуры, то есть в ее главном блоке. VB не допускает, чтобы одноименные переменные объявлялись в охватываемом и охватывающем блоках.
У констант тоже существует область видимости блок.
Статические переменные
Исчезновение значения локальной переменной при выходе из процедуры – это хорошо, но не всегда удобно. Пусть мы хотим подсчитывать количество нажатий на кнопку Button1. Приведенная ниже процедура почти подходит, но она не будет накапливать счетчик, так как при каждом вызове процедуры счетчик обнуляется в результате инициализации:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim Счетчик As Integer
Счетчик = Счетчик + 1
TextBox1.Text = Счетчик
End Sub
Делать счетчик модульной переменной не хочется, никому он кроме нас не нужен. Чтобы счетчик не обнулялся, объявим его статической переменной:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Static Счетчик As Integer
Счетчик = Счетчик + 1
TextBox1.Text = Счетчик
End Sub
Статическая переменная объявляется не словом Dim, а словом Static. Это обычная локальная переменная, но с одним отличием: ее значение не уничтожается после выхода из процедуры и доживает до следующего ее вызова.
Теперь все в порядке. Счетчик накапливается. А для других процедур переменная невидима.
Пока об областях видимости нам достаточно. Более подробную информацию вы найдете в 21.9.
Области видимости переменных
Материал следующих трех разделов сложен для восприятия и для новичка по большей части непонятно, зачем он вообще нужен. Ничего впечатляющего мы здесь не создадим. Зато следующие за ними три главы – настоящий сказочный сон. Для тех же, кто малодушно перепрыгнет через эти три раздела, он может превратиться в кошмар непонимания.
Да, для уверенного плавания по океану VB этот материал необходим! Не усвоивший его дальше будет тонуть в неожиданных местах. Итак, наберите побольше кислорода и терпения. Идем вглубь.
Передача параметров по ссылке и по значению
Рассмотрим задачу: Известны стороны двух прямоугольников. Нужно вычислить площадь и периметр каждого прямоугольника, а затем напечатать периметр того, чья площадь больше.
Внимательно разберитесь в приведенной ниже программе. На ее примере мы пройдем путь от процедур к функциям.
Поскольку прямоугольника два, мы уже предвидим, что будет выгодно создать процедуру, вычисляющую площадь и периметр прямоугольника. Попробуем сделать это по старинке:
Dim A1, B1, S1, P1 As Integer 'Две стороны, площадь и периметр 1 прямоугольника
Dim A2, B2, S2, P2 As Integer 'Две стороны, площадь и периметр 2 прямоугольника
Dim Площадь As Integer 'площадь, вычисленная процедурой
Dim Периметр As Integer 'периметр, вычисленный процедурой
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
'Работаем с 1 прямоугольником:
A1 = 10 : B1 = 50
Прямоугольник(A1, B1)
P1 = Периметр : S1 = Площадь
'Работаем со 2 прямоугольником:
A2 = 20 : B2 = 30
Прямоугольник(A2, B2)
P2 = Периметр : S2 = Площадь
'Анализируем:
If S1 > S2 Then Debug.WriteLine(P1) Else Debug.WriteLine(P2)
End Sub
Sub Прямоугольник(ByVal Сторона1 As Integer, ByVal Сторона2 As Integer)
Площадь = Сторона1 * Сторона2
Периметр = 2 * Сторона1 + 2 * Сторона2
End Sub
В учебных целях я здесь написал
A1 = 10 : B1 = 50
Прямоугольник(A1, B1)
хотя короче было бы написать
Прямоугольник(10, 50)
В нашей программе нас явно раздражает необходимость писать строки:
Dim Площадь As Integer 'площадь, вычисленная процедурой
Dim Периметр As Integer 'периметр, вычисленный процедурой
P1 = Периметр : S1 = Площадь
P2 = Периметр : S2 = Площадь
К тому же пришлось объявлять слишком много модульных переменных, что снижает безопасность проекта. Нельзя ли как-то укоротить и улучшить программу? Можно. Для этого необходимо добавить в заголовок процедуры один параметр для площади и другой – для периметра. Вот более короткий вариант программы:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim A1, B1, S1, P1 As Integer 'Две стороны, площадь и периметр 1 прямоугольника
Dim A2, B2, S2, P2 As Integer 'Две стороны, площадь и периметр 2 прямоугольника
A1 = 10 : B1 = 50
Прямоугольник(A1, B1, S1, P1)
A2 = 20 : B2 = 30
Прямоугольник(A2, B2, S2, P2)
If S1 > S2 Then Debug.WriteLine(P1) Else Debug.WriteLine(P2)
End Sub
Sub Прямоугольник(ByVal Сторона1 As Integer, ByVal Сторона2 As Integer, _
ByRef Площадь As Integer, ByRef Периметр As Integer)
Площадь = Сторона1 * Сторона2
Периметр = 2 * Сторона1 + 2 * Сторона2
End Sub
Пояснения. До этого момента мы без пояснений писали в заголовке процедуры перед каждым параметром слово ByVal (по значению). Оно рекомендуется для исходных данных, по которым процедура вычисляет результаты. Ну а вот если процедура эти результаты должна «сообщить внешнему миру», чтобы они использовались где-то вне процедуры (что мы и видим в нашем случае), тогда эти результаты нужно включить в число параметров процедуры и предварить словом ByRef (по ссылке).
Вам нужно очень точно понять, что происходит в компьютере при вызове и работе такой процедуры. Разберем момент выполнения вызывающего оператора
Прямоугольник(A1, B1, S1, P1)
Лучше всего это делать в пошаговом режиме. К этому моменту в ячейках A1 и B1 уже находятся числа 10 и 50. А в ячейках S1 и P1 пока пусто, точнее – нули.
Но вот желтая полоса прыгает на заголовок процедуры Прямоугольник, управление передалось этой процедуре. В этот славный момент создаются все ее локальные переменные: параметры Сторона1, Сторона2, Площадь, Периметр. И VB требует, чтобы они тут же получили свои значения от вызывающего оператора Прямоугольник (A1, B1, S1, P1). Причем в том порядке, в каком они приведены в скобках. Поэтому Сторона1 получает свое значение от переменной A1, а значит становится равной 10. Сторона2 получает 50 от B1. А Площадь и Периметр получают по нулю от S1 и P1. Кстати, вы можете сказать, что этим двум и не надо было получать никаких нулей, все равно процедура их правильно бы вычислила. Верно, но такая ненужная здесь механика может пригодиться в других программах.
Смысл. А сейчас вам нужно понять разницу между тем, как процедура работает с параметрами по значению и по ссылке. Представьте себе, что весь проект – это здание школы, процедуры – это различные классы в школе. В классах развешено по нескольку классных досок. Каждая доска – это ячейка под локальную переменную этой процедуры или под ее параметр. Работу каждой процедуры осуществляет учитель с мелом и тряпкой, который сидит в классе.
Теперь будьте внимательны. Что происходит при вызове и работе процедуры Прямоугольник? В классе, отведенном под процедуру Button1_Click, в этот момент на досках A1, B1, S1, P1 написаны числа: соответственно 10, 50, 0, 0. Для определенности назовем комнатой класс, отведенный под процедуру Прямоугольник. В момент вызова мы видим, что в комнатке на доски, помеченные значком ByVal, автоматически переписываются числа с соответствующих досок класса. То есть, на доску Сторона1 переписывается число 10, а на доску Сторона2 – 50. Но вот странная вещь: вместо досок, помеченных значком ByRef, в комнатке дыры. Вместо доски Площадь – дыра с надписью Площадь, которая ведет в класс прямо к доске S1, и учитель может просунуть в дыру свою длинную руку и писать прямо на доске S1 в классе! Абсолютно то же самое с досками Периметр и P1. Поэтому в рассматриваемый момент с досок S1 и P1 в комнатушку ничего не переписывается. В этом нет нужды, так как учитель все равно имеет полный доступ к этим двум доскам. Через дыры он видит, что там – нули.
Итак, учитель может распоряжаться 4 досками, 2 – в своей комнатке и 2 – в классе. Посмотрим, что происходит, когда учитель выполняет оператор
Площадь = Сторона1 * Сторона2
Он смотрит на доски своей комнатки, видит там 10 и 50, перемножает их, затем просовывает руку в дыру с надписью Площадь и записывает результат 500 на доску S1 в классе. Говорят: переменная Площадь является ссылкой на переменную S1 или что переменная Площадь ссылается
на переменную S1.
Мы привыкли, что в ячейках памяти хранятся числа или строки. А что же хранится в «дырявой» ячейке Площадь? Говорят, что в ней хранится ссылка на ячейку S1 или, еще говорят, – хранится адрес ячейки S1.
Если учитель во время работы что-нибудь напишет на доске со значком ByVal, то бесполезно ждать, что в классе об этом когда-нибудь узнают. Обратного переписывания с досок ByVal в комнатушке на соответствующие доски в классе никогда не происходит! Это значит, что если мы нечаянно пометим параметр Периметр не словом ByRef, а словом ByVal, то все усилия процедуры по вычислению периметра окажутся бесполезными: никто никогда снаружи не узнает вычисленного значения периметра. Проверьте и увидите, что программа в этом случае печатает периметр равный нулю.
Теперь скажем то же самое, но только другими словами: при помощи компьютерной терминологии. Говорят, что ByVal обеспечивает передачу параметров по значению, а ByRef – передачу параметров по ссылке. При передаче параметров по значению процедура работает не с теми ячейками, из которых передается информация (A1, B1), а с собственными ячейками (Сторона1, Сторона2), куда информация из A1, B1 копируется при обращении к процедуре. Процедура может как угодно менять информацию в своих ячейках Сторона1 и Сторона2, на чужих ячейках A1 и B1 это никак не скажется. Поэтому обычно никто из программистов и не старается этого делать.
При передаче параметров по ссылке процедура работает не с собственными ячейками (Площадь, Периметр), а непосредственно с теми ячейками, на которые они ссылаются (S1, P1). Это опасно, ведь процедура получает доступ к локальным переменным другой процедуры, а это не приветствуется, ведь эти переменные становятся беззащитными против ошибок в вызываемой процедуре. Поэтому программисты стараются внимательно следить, чтобы ненароком не записать в чужие переменные что-нибудь не то. И опасное слово ByRef употребляют только тогда, когда хотят передать вызывающей процедуре важные сведения, а в остальных случаях используют безопасное ByVal.
Вот пример, как неоправданное использование ByRef довело нас до беды:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Dim A1, B1, S1, P1 As Integer 'Две стороны, площадь и периметр прямоугольника
A1 = 10 : B1 = 50
Прямоугольник_опасный(A1, B1, S1, P1)
Debug.WriteLine(A1 & " " & B1 & " " & S1 & " " & P1)
End Sub
Sub Прямоугольник_опасный(ByRef Сторона1 As Integer, ByVal Сторона2 As Integer, _
ByRef Площадь As Integer, ByRef Периметр As Integer)
Площадь = Сторона1 * Сторона2
Периметр = 2 * Сторона1 + 2 * Сторона2
Сторона1 = 999
End Sub
Пояснения. Автор процедуры Прямоугольник_опасный для каких-то своих нужд написал оператор Сторона1 = 999. И все бы ничего, это его право, но он шапкозакидательски написал в заголовке ByRef Сторона1. В результате ни в чем не виноватая процедура Button2_Click вместо того, чтобы напечатать
10 50 500 120
напечатала
999 50 500 120
Достаточно в приведенной программе вместо ByRef Сторона1 снова написать ByVal Сторона1, и все опять будет нормально. Таким образом, передача параметров по значению – еще один способ повысить надежность программирования.
Важное исключение. Слово ByVal теряет свою способность к защите по отношению к массивам и объектам. Ни массивов, ни объектов мы еще не проходили. О причинах невозможности защиты поговорим в 27.2.4.
Далее. В вызывающем операторе на месте параметров, вызываемых по значению, могут стоять не только переменные, но и литералы, и выражения:
Прямоугольник(10, A1+40, S1, P1)
Разница только в том, что при вызове процедуры выражения сначала вычисляются, после чего вычисленные значения, как и положено, посылаются в ячейки параметров процедуры (Сторона1 и Сторона2).
А вот на месте параметров, вызываемых по ссылке, могут стоять только переменные! Никаких литералов и выражений!
Из механики работы параметров вытекает очень удобный факт: Когда мы пишем процедуру, нам не нужно заботиться о том, какие имена переменных будут использованы при обращении к процедуре, мы просто даем параметру любое пришедшее в голову подходящее имя. И наоборот, когда мы пишем обращение к процедуре, нам не нужно заботиться о том, какие имена имеют параметры в заголовке процедуры. В частности, мы можем использовать в обращении переменные, имеющие такие же имена, что и соответствующие параметры. Эффект затенения не даст им перепутаться.
Из чего состоит тело процедуры. Выражения
Тело процедуры состоит из операторов Dim и остальных операторов. Остальные операторы выполняются по порядку.
Из чего состоят операторы? Они могут включать в себя блоки других операторов, как это делают, например, операторы If и Do. Они могут включать в себя выражения.
Мы знаем с вами уже три вида выражений: арифметические (их значение – число), строковые (их значение – текстовая строка) и логические (они принимают одно из двух значений: True или False).
Выражение, стоящее отдельно, само по себе, встречаться не может. Выражения всегда являются частью операторов. Выражения мы встречаем справа (не слева!) от знака равенства в операторе присваивания, в условиях тех же операторов If и Do, внутри скобок при обращении к методам и функциям.
В простейшем случае выражение – это литерал:
| Фрагмент | Где тут выражения | ||
| a = 0 | 0 | ||
| b = -7 | -7 | ||
| s = "Привет" | "Привет" |
В простом случае выражение – это переменная:
| a = c | c | ||
| w = s | s – строковое выражение |
В обычном случае выражение – это литералы и переменные, соединенные знаками арифметических и других операций. В выражения входят скобки, а также математические и другие функции:
| d = a + 1 | Здесь три выражения: a, 1 и a+1 | ||
| w = s & "ик" | Здесь три выражения: s, "ик" и s & "ик" | ||
| Debug.WriteLine(b - 3) | Здесь три выражения: b, 3 и b-3 | ||
| b = Len(w) | Здесь два выражения: w и Len(w) | ||
| b = a * (Len(w) – Rnd()) | Выражение a * (Len(w) – Rnd()) и в нем 5 выражений | ||
| If c + 9 > a Then | Логическое выражение c+9>a, состоящее из двух арифметических |
Функции
Философия. Мы с вами уже сталкивались со стандартными функциями (например, Len(w), Abs(c+200) и др.). Стандартная функция – это некая скрытая программа, которая принимает свои параметры, указанные в скобках, в качестве исходных данных (аргументов), что-то делает с ними и в результате получает одну величину, которая и является значением функции. Много примеров функций вы найдете в 5.4.1 и по всей книге.
Когда мы видим оператор
b = a * (Len(w) – Abs(c+200))
то говорим, что при выполнении этого оператора компьютер обращается к функциям Len и Abs. А само упоминание этих функций в тексте оператора называем обращениями к этим функциям, чтобы отличить их от объявлений функций, которые являются солидными программами для вычисления значений этих функций, только скрытыми от нас. Здесь полная аналогия с обращением к процедуре пользователя. Обращение к процедуре – это коротенький оператор, а объявление процедуры может состоять из многих строк.
Обратите внимание, что обращения к стандартным функциям встречаются обычно в выражениях. Редко кто пишет отдельные операторы такого вида:
Len(w)
Abs(c + 20)
Да это и понятно. Ведь в этом случае неизвестно, как использовать вычисленную величину функции. Действительно, ну вот мы вычислили длину строки, а куда ее девать, как приспособить для какого-нибудь дела? Поэтому пишут так:
b = Len(w)
Здесь все понятно: мы в дальнейшем можем использовать, переменную b, которая несет в себе вычисленную величину функции, как хотим. Или, скажем, так:
Debug.WriteLine(Abs(c + 20))
Здесь тоже от абсолютной величины польза была: мы ее напечатали.
Сейчас я хочу, чтобы мы подошли к осознанию необходимости и удобства обладания собственными функциями – функциями пользователя. Функции пользователя вы создаете тогда, когда вам недостаточно функций из библиотеки классов .NET Framework. Например, вы хотите иметь функцию, аргументами которой были бы длины двух сторон прямоугольника, а значением – периметр этого прямоугольника. Но позвольте! – скажете вы – Мы только что создали процедуру, которая делает именно это, да еще и площадь вычисляет! Зачем нам нужна еще какая-то функция? А затем, что функция в данном случае удобнее и естественнее в использовании. Вы увидите, что удобнее создать и использовать две функции: Периметр и Площадь, чем одну процедуру. И не забывайте, кстати, что единую функцию Периметр_Площадь создать нельзя, так как функция может иметь только одно значение.
Пример 1. Вспомним задачу из 11.4.1. : «Известны стороны двух прямоугольников. Нужно напечатать периметр того прямоугольника, чья площадь больше.» Приведу целиком программу решения этой задачи, но уже с использованием не процедуры, как в 11.4.1. , а функций:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim A1, B1 As Integer 'Две стороны 1 прямоугольника
Dim A2, B2 As Integer 'Две стороны 2 прямоугольника
A1 = 10 : B1 = 50
A2 = 20 : B2 = 30
If Площадь(A1, B1) > Площадь(A2, B2) Then WriteLine(Периметр(A1, B1)) _
Else WriteLine(Периметр(A2, B2))
End Sub
Function Периметр(ByVal Сторона1 As Integer, ByVal Сторона2 As Integer) As Integer
Return 2 * Сторона1 + 2 * Сторона2
End Function
Function Площадь(ByVal Сторона1 As Integer, ByVal Сторона2 As Integer) As Integer
Площадь = Сторона1 * Сторона2
End Function
Пояснения. Оператор
If Площадь(A1, B1) > Площадь(A2, B2) Then WriteLine(Периметр(A1, B1)) _
Else WriteLine(Периметр(A2, B2))
содержит в себе два обращения к функции Площадь и два обращения к функции Периметр. Он хорош своей естественностью и понятностью. Действительно, он практически повторяет условие задачи:
«ЕСЛИ площадь первого прямоугольника больше площади второго,
ТО печатай периметр первого прямоугольника,
ИНАЧЕ печатай периметр второго».
Терминология. Как же устроены наши функции? Сначала о терминологии, которая очень похожа на терминологию процедур.
Все операторы, из которых состоит функция, без заголовка и конечной строки, будем называть телом функции. А вместе с этими строками – объявлением функции. Как видите, тела наших функций очень коротенькие, всего из одного оператора. Но операторов может быть сколько угодно, как и в процедуре.
Когда VB в процессе выполнения программы натыкается на функцию Периметр, он ищет в программе объявление функции с именем Периметр и начинает выполнять тело этой функции. Этот процесс называется вызовом функции или
обращением к функции . Говорят также, что управление передается функции. После выполнения тела функции VB возвращается к выполнению программы. Говорят, что управление возвращается к программе. Про значение функции говорят, что функция возвращает это значение в программу.
Отличия функций от процедур. Вы видите, что объявление функции очень похоже на объявление процедуры. Но функция в отличие от процедуры обладает некоторыми свойствами переменной величины. Объявление функции отличается от объявления процедуры следующими элементами:
Напомню еще раз, что обращение к функции отличается от обращения к процедуре. Если обращение к процедуре – самостоятельный оператор, то обращение к функции – это обычно составная часть выражения.
Пример 2. Рассмотрим другой пример функции. Ее параметр (аргумент) – строка. Значение функции – та же строка, повторенная столько раз, сколько символов (букв) в исходной строке. Например, если параметр – «уж», то функция – «ужуж», параметр – «Вена», функция – «ВенаВенаВенаВена». Если длина параметра превышает 8 символов, значение функции таково – «Вы задали слишком длинное слово».
Вот программа:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim S As String
S = TextBox1.Text
TextBox1.Text = Размноженное_слово(S)
End Sub
Function Размноженное_слово( ByVal Slovo As String) As String
Dim i, Длина_слова As Integer
Длина_слова = Len(Slovo)
If Длина_слова > 8 Then
Return "Вы задали слишком длинное слово"
Else
Размноженное_слово = ""
For i = 1 To Длина_слова
Размноженное_слово = Размноженное_слово & Slovo
Next
End If
End Function
Пояснения. Здесь мы пишем в текстовом поле слово и щелчком по кнопке тут же получаем в том же текстовом поле результат – размноженное слово.
Посмотрим на объявление функции. В заголовке мы видим, что как параметр функции, так и сама функция имеют тип String. Процедуры и функции, как вы уже видели, могут содержать кроме параметров также и обычные локальные переменные (в нашем случае это i и Длина_слова). Тело функции состоит из нескольких операторов.
Механика работы этой функции проста. Переменная Размноженное_слово накапливает в себе слова, как сумматор сумму. Роль плюса играет знак &. На каждой итерации второй из двух операторов присваивания удлиняет Размноженное_слово на Slovo. А итераций столько, сколько букв в слове. «Обнуляет сумматор» оператор
Размноженное_слово = ""
Роль нуля исполняет пустая строка "" длиной в 0 символов.
Для возврата значений применяется в одном месте оператор Return и в двух местах – оператор присваивания Размноженное_слово = . . .. Не запутаемся ли? – Значение одно, операторов – три. Спрашивается, какой из этих операторов в действительности вернет значение? Чтобы ответить на этот вопрос, вам нужно знать, что
между операторами Return и присваивания, используемыми для возврата значения функции, имеется существенное различие. А именно: наткнувшись на оператор Return, компьютер выполняет его и на этом немедленно прекращает выполнение функции, возвращаясь в программу, ее вызвавшую. Ничего подобного при выполнении оператора присваивания не происходит – компьютер продолжает выполнять тело функции дальше.
Если вы внимательно разберете текст этой или любой другой функции, то увидите, что вернет значение оператор, последний по времени выполнения (а не в порядке записи). Какой именно? Все зависит от длины слова. Если оно длиннее 8 букв, то значение вернет оператор Return. Если его длина – от 1 до 8 букв, то второй из двух операторов присваивания. Если вы ничего не введете в текстовое поле, то – первый.
Обязательно прогоните программу в пошаговом режиме. Вам будет любопытно наблюдать, как постепенно удлиняется Размноженное_слово.
Побочный эффект. В теле функции можно писать сколько угодно любых операторов. А почему бы в таком случае не попытаться убить двух зайцев и не вычислять в теле функции Площадь еще и периметр? Ну и что, что он не будет возвращаемым значением функции! Зато тогда можно было бы обойтись одной функцией. Попробуем:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim A1, B1, П1 As Integer 'Две стороны и периметр 1 прямоугольника
Dim A2, B2, П2 As Integer 'Две стороны и периметр 2 прямоугольника
A1 = 10 : B1 = 50
A2 = 20 : B2 = 30
If Площадь(A1, B1, П1) > Площадь(A2, B2, П2) Then WriteLine(П1) Else WriteLine(П2)
End Sub
Function Площадь(ByVal Сторона1 As Integer, ByVal Сторона2 As Integer, ByRef Периметр As Integer) _
As Integer
Площадь = Сторона1 * Сторона2
Периметр = 2 * Сторона1 + 2 * Сторона2
End Function
Что мы выиграли и что проиграли? Сэкономили две строчки кода, но пришлось вводить дополнительные переменные и параметр. А самое главное – проиграли в простоте, единообразии и понятности. Функция получилась довольно нелепая.
Если функция кроме возвращаемого значения вычисляет что-то еще побочное, что мы используем в программе, то говорят, что функция имеет побочный эффект. Побочные эффекты довольно часто используются в программировании. Например, среди функций из библиотеки классов .NET Framework довольно много таких, возвращаемое значение которых никого не интересует, а интересует именно побочный эффект. Поэтому обращение к функции разрешено писать не только в выражениях, но и отдельным оператором. Например, так:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Dim A1, B1, П1 As Integer 'Две стороны и периметр прямоугольника
A1 = 10 : B1 = 50
Площадь(A1, B1, П1)
Debug.WriteLine(П1)
Math.Abs(-20)
End Sub
Как я уже упоминал, от площади в этом примере толк есть, а от абсолютной величины – нет.
Задание 65.
Напишите функцию с двумя строковыми параметрами. Функция должна выдать длину той строки, которая короче.
Константы
Вспомним процедуру рисования дерева из 11.2.2.:
Sub Дерево()
Dim x As Single = 200
Dim y As Single = 100
Dim Ширина_кроны As Single = 20
Dim Высота_кроны As Single = 60
Dim Длина_ствола As Single = 15
Гр.FillEllipse(Brushes.Green, x, y, Ширина_кроны, Высота_кроны)
Гр.DrawEllipse(Pens.Black, x, y, Ширина_кроны, Высота_кроны)
Гр.DrawLine(Pens.Black, x + Ширина_кроны / 2, y + Высота_кроны, _
x + Ширина_кроны / 2, y + Высота_кроны + Длина_ствола)
End Sub
При помощи пяти переменных мы можем управлять положением, размерами и формой дерева. Предположим, приходит ваш начальник, смотрит на дерево и говорит: «Хорошее дерево, особенно мне нравится ширина кроны. Приказываю, чтобы отныне все деревья, нарисованные этой процедурой, имели только такую ширину кроны и никакую другую, несмотря на дальнейшее развитие и усложнение процедуры.»
Как надежно выполнить приказ начальника? Ширина кроны задается оператором
Dim Ширина_кроны As Single = 20
Если не трогать этот оператор, можно ли быть уверенным, что ширина кроны в будущем не изменится? Нельзя, так как в будущем мы можем по забывчивости дописать в процедуру операторы типа Ширина_кроны = . . . Как бороться с забывчивостью? VB предлагает средство – так называемые константы. Константа – это переменная, которой мы задаем значение при объявлении, и которая неспособна это значение в дальнейшем менять. Чтобы отличить константу от обычной переменной, в операторе объявления мы вместо Dim пишем Const:
Const Ширина_кроны As Single = 20
Отныне, если мы попытаемся дописать в процедуру операторы, имеющие возможность изменить значение константы, типа Ширина_кроны =… , VB выдаст сообщение об ошибке.
Таким образом, при помощи объявления констант мы повышаем надежность программирования.
Имейте в виду, что термином «константа» до последнего времени часто обозначали два близких понятия: с одной стороны собственно константы, а с другой стороны литералы. Я буду стараться избегать возможной путаницы и литералы буду называть литералами. Литералы мы уже проходили. Напомню, что литералами называются те конкретные значения величин, которые мы видим в программе. Например, во фрагменте
a = 1 + 0.25
b = "Амазонка"
Debug.WriteLine("Волга")
If a > 3 Then . . .
литералы это 1 0.25 "Амазонка" "Волга" 3.
Кроме упомянутых выше констант, которые вы создаете сами, существует еще большое число констант, определенных в библиотеке классов .NET Framework. Объявлять их не надо, ими можно сразу пользоваться, если знать их имена и смысл. Например, такими константами являются математические константы PI и E, входящие в класс Math. Каждая из таких констант имеет конкретное значение (например, PI = 3.14159265358979323846). Мы могли бы вместо слова PI просто писать конкретное число 3.14159265358979323846, но названия запоминаются легче и записываются короче, чем числа.
Функции
Процедуры и функции – важное средство сделать вашу программу надежнее и понятнее.
Прежде чем перейти к функциям, нам нужно поподробнее поговорить о параметрах. До сих пор мы использовали параметры для того, чтобы сообщить процедуре значения исходных данных для ее работы, например, положение и размеры дерева, а уж процедура по этим данным рисует дерево нужных размеров и в нужном месте. Однако бывает необходимо, чтобы и процедура нам что-то сообщила.
Переменные объектного типа
В 6.1.2 мы видели, что переменные могут иметь объектный тип. Проиллюстрируем это на примере. Создайте проект и поместите на форму метку Label1 и кнопку. Введите такой код:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Dim A As Object
A = Label1
A.text = "Я метка"
A = Me
A.text = "Я форма"
A = Me.CreateGraphics
A.DrawLine(Pens.Black, 20, 20, 200, 200)
End Sub
Пояснения. Слово Object в операторе Dim A As Object говорит о том, что переменная A может иметь своим значением любой объект.
Следующий оператор A = Label1 присваивает переменной A значение существующего объекта – метки Label1. Это значит, что теперь все равно, как написать:
Label1.Text = "Я метка"
или
A.text = "Я метка"
Эффект будет одинаковый.
Получается, что через переменную A вы получили доступ ко всем свойствам и другим возможностям метки, доступным через имя Label1.
Обратите внимание, что когда вы набираете точку после буквы A, никакого привычного нам вразумительного списка свойств и методов не появляется и слово text приходится полностью набирать вручную. Дело в том, что состав этого списка определяется типом объекта, после которого вы набираете точку. Тип Object настолько универсален, что практически не существует свойств и методов, присущих одновременно всем объектам VB.
В следующем операторе A = Me ветреная и непостоянная A изменяет метке и принимает в качестве своего значения форму. С тем же результатом всевластия над возможностями формы. О метке забыто.
Оператор A = Me.CreateGraphics присваивает переменной A значение объекта класса Graphics. В результате через переменную A вы получили доступ ко всем возможностям рисования.
Тип Object – самый общий объектный тип. Есть более узкие объектные типы. Например, Control. Переменная, объявленная этим типом, уже не может быть любым объектом, но может, например, принимать значение любого элемента управления.
Типы Label и Button – еще более узкие типы. Так, переменная, объявленная типом Button, может быть любой кнопкой, но только кнопкой. Говорят, что эта переменная стала объектом класса Button.
Параметры объектного типа
В качестве параметров мы пока использовали переменные числовых и строкового типов. Параметр – это переменная. Переменная может иметь объектный тип. Значит и параметр может иметь объектный тип.
Как это понимать и какая от этого польза? Рассмотрим два примера.
Пример 1. Параметр типа Control. Для задания размеров элемента управления требуется два оператора – один для ширины, другой – для высоты. Предположим, вы хотите обойтись одним. Особого смысла в этом нет, но для иллюстрации подойдет. Создайте проект и поместите на форму метку и две кнопки. Введите такой код:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Настраиваем_размер(Button2, 80, 30)
Настраиваем_размер(Label1, 100, 120)
End Sub
Sub Настраиваем_размер(ByVal Элемент_упр As Control, ByVal Ширина As Integer, ByVal Высота As Integer)
Элемент_упр.Width = Ширина
Элемент_упр.Height = Высота
End Sub
Пояснения. Оператор
Настраиваем_размер(Button2, 80, 30)
делает ширину кнопки Button2 равной 80, а высоту – 30. А оператор
Настраиваем_размер(Label1, 100, 100)
делает ширину метки Label1 равной 100, а высоту – 120. Мы добились того, чего хотели. Как это у нас получилось?
Оба приведенные оператора являются вызовами процедуры пользователя Настраиваем_размер. Посмотрите на ее заголовок. Первый параметр – Элемент_упр – имеет тип Control – объектный тип элементов управления. Это значит, что он может, например, принимать значение любого элемента управления на форме.
Обязательно зайдите в пошаговый режим и обратите внимание, что переменная Элемент_упр, как и положено параметру, инициализируется, несмотря на то, что она не обычная, а объектная. Пока мы не зашли в процедуру Настраиваем_размер, она, естественно, не инициализирована и подсказка просто сообщает, что она объявлена, как мы и задали, типом System.Windows.Forms.Control. Здесь System.Windows.Forms – пространство имен, в которое входит класс Control. Но как только VB заходит в процедуру, переменная Элемент_упр становится объектом класса System.Windows.Forms.Button. При втором обращении – System.Windows.Forms.Label.
Пример 2. Параметр типа Graphics. Вам уже предлагалось в Задание 78 написать процедуру пользователя для рисования крестика. У той процедуры были привычные нам параметры: координаты и размер крестика. Сейчас же нам интересен другой параметр. Мы хотим посредством него управлять тем, на поверхности какого элемента управления (или на форме) рисовать крестик. Создайте проект и поместите на форму метку и кнопку. Чтобы не отвлекаться, забудем о параметрах для координат и размера крестика. Введите такой код:
Dim Графика_для_формы As Graphics
Dim Графика_для_метки As Graphics
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Графика_для_формы = Me.CreateGraphics
Графика_для_метки = Label1.CreateGraphics
Рисуем_крестик(Графика_для_формы)
Рисуем_крестик(Графика_для_метки)
End Sub
Sub Рисуем_крестик(ByVal Гр As Graphics)
Гр.DrawLine(Pens.Blue, 100, 110, 120, 110)
Гр.DrawLine(Pens.Blue, 110, 100, 110, 120)
End Sub
Пояснения. Крестики рисуются на форме и на метке. Как видите, здесь параметром процедуры пользователя является объект класса Graphics. Без этого параметра мы не смогли бы пользоваться процедурой Рисуем_крестик для рисования крестика на разных элементах управления. Получилось бы только на одном.
Пример 3. Функция объектного типа. Создайте проект с кнопкой, меткой и текстовым полем. Пусть нам наскучило обращаться к элементам управления, как положено, по именам, а «желаем» по номерам. Пронумеруем их в уме как-нибудь, например, кнопка – 1, метка – 2, текстовое поле – 3. Пусть возникла задача напечатать, на сколько кнопка превосходит по вертикальному размеру текстовое поле. В соответствии с нашим желанием нам не хочется писать так:
Debug.WriteLine(Button1.Height - TextBox1.Height)
а хочется писать что-нибудь вроде этого:
Debug.WriteLine(Элемент(1).Height - Элемент(3).Height)
Можем ли мы так сделать? Можем. Достаточно написать функцию:
Function Элемент( ByVal Номер As Integer) As Control
Select Case Номер
Case 1
Return Button1
Case 2
Return Label1
Case 3
Return TextBox1
End Select
End Function
Пояснения. Функция может возвращать значение почти любого типа, а не только простого. В том числе и объект. Наша функция Элемент имеет объектный тип Control. Это значит, что в зависимости от значения своего параметра Номер она может принять значение не числа и не строки, к чему мы привыкли, а элемента управления: Button1 или Label1, или TextBox1. Вы можете в обращении мысленно заменить Элемент(1) на Button1, а Элемент(3) – на TextBox1, тогда вам будет легче привыкнуть к записи. Самое приятное, что когда мы в обращении ставим точку после Элемент(1), то всплывает список компонентов. Это происходит потому, что VB знает, что функция имеет тип Control.
Функция Элемент дает нам замечательное преимущество работать с пронумерованными элементами управления в цикле. Например, вот как можно находить суммарную ширину элементов управления:
For i = 1 To 3
s = s + Элемент(i).Width
Next
В заключение признаюсь, что создатели VB давно уже поняли прелесть нумерации элементов управления и воплотили ее стандартными средствами – при помощи так называемых коллекций (см. 16.2).
Соответствие типов
Мы с вами уже привыкли, что сочиняя программу, нужно немножко заботиться о соответствии типов. Соответствие типов должно выполняться не только для параметров методов, но и в операторе присваивания, где тип выражения справа от знака равенства должен соответствовать типу переменной слева от него. Поэтому, например, следующий фрагмент VB выполнит гладко и без малейших вопросов:
Dim a As Integer
Dim b As Integer = 99
a = b + 2
А вот при попытке выполнить следующий фрагмент
Dim a As Integer
Dim s As String = "Привет"
a = s
VB выдаст ошибку:
Cast from string "Привет" to type 'Integer' is not valid.
Переводится сообщение так:
«Преобразование строки "Привет" в тип Integer незаконно».
Действительно, в какое число можно превратить строчку текста? Нонсенс.
А зачем VB вообще что-то преобразовывал? А затем, что есть такое правило выполнения оператора присваивания:
Вычислив в операторе присваивания выражение справа от знака равенства, VB смотрит, совпадает ли его тип с типом переменной слева от него. Если совпадает – хорошо, а если не совпадает, то пытается преобразовать его тип к нужному (тому, что слева). Если удается – хорошо, а если из соображений правильности, точности, безопасности или из каких-то других своих соображений VB посчитает, что такое преобразование незаконно или ненужно, он выдает сообщение об ошибке.
Рассмотрим фрагмент:
Dim a As Integer
Dim b As Double = 1
a = b + 0.48
Он будет выполнен гладко, несмотря на то, что справа – Double, а слева – Integer. VB не возражает в данном случае преобразовать дробное число в целое. Несмотря на колоссальную потерю в точности (1 вместо 1.48), никаких сообщений или предупреждений мы не увидим. Это программист сам виноват, что объявил переменную целым типом.
Так же гладко выполнится и следующий фрагмент:
Dim a As Integer = 99
Dim s As String
s = a
Здесь VB преобразует число 99 в строку “99”.
Соответствие типов требуется и при вызове процедур и функций с параметрами.
При обращении к процедуре VB смотрит, совпадает ли тип параметра в обращении к процедуре с типом соответствующего параметра в заголовке объявления процедуры. Если совпадает – хорошо, а если не совпадает, то пытается преобразовать его тип к нужному (тому, что в объявлении). Если удается – хорошо, а если нет, VB выдает сообщение об ошибке.
В подтверждение этих слов достаточно вспомнить Методы, «придирчивые» к типу параметров (6.2.8).
Чтобы не было проблем, старайтесь объявлять переменные и параметры, между которыми должно соблюдаться соответствие, одинаковыми типами.
Соответствие объектных типов
Все, что сказано в предыдущем подразделе о соответствии типов, полностью относится и к тому случаю, когда эти типы объектные.
При попытке ввести в окно кода следующий код
Dim A As Graphics
A = Label1
VB подчеркнет ошибку и подскажет:
Value of type 'System.Windows.Forms.Label' cannot be converted to 'System.Drawing.Graphics'
Переводится подсказка так:
«Значение типа Label не может быть преобразовано в тип Graphics».
Действительно, мы объявили переменную A объектным типом Graphics, а сами присвоили ей значение хоть и объектного типа Label, но не имеющего никакого отношения к графике. «Не садись не в свои сани».
Точно такая же подсказка, но уже по поводу параметров, появится, если мы к процедуре из 11.5.2:
Sub Рисуем_крестик(ByVal Гр As Graphics)
Гр.DrawLine(Pens.Blue, 100, 110, 120, 110)
Гр.DrawLine(Pens.Blue, 110, 100, 110, 120)
End Sub
обратимся с глупой ошибкой:
Рисуем_крестик(Label1)
а не как положено:
Рисуем_крестик(Графика_для_метки)
Неопределенные параметры, произвольное число параметров
VB позволяет создавать процедуры и функции с неопределенными параметрами, обращаться к которым можно, например, так:
Проц (5, , , 8, 3)
Ясно, что здесь не указаны значения двух параметров.
VB также позволяет создавать процедуры и функции с произвольным числом параметров. Про то, как и зачем это делается, вы можете прочесть во многих учебниках по VB, в частности, в книге Гарнаева (стр. 133) (см. Список литературы).
Что такое методы
В 2.1.2 я сказал, что классы состоят в основном из методов, свойств и событий. Когда мы ставим точку после имени класса или полученного из него объекта, перед нами возникает список компонентов (members) этого класса, то есть тех же методов, свойств и кое-чего другого.
С другой стороны, мы знаем, что любой стандартный класс – это программа, и поэтому, несмотря на то, что ее текста мы не видим, можете быть уверены: он включает в себя все то, что мы сами привыкли писать в окне кода: процедуры, функции, переменные, параметры, константы и пр.
Вы видите, что здесь терминология несколько другая. Как соотносятся методы, свойства, события с одной стороны с процедурами, функциями, переменными с другой? Нам сейчас нужно хоть немножко увязать терминологию списка компонентов и терминологию программы. Мы исходим из того, что встретиться в этом списке может только то, что встречается в тексте программы этого класса.
Расшифруем чуть-чуть понятие метода. Методы делятся на две категории:
Методы, возвращающие значение – это функции (Function). Мы недавно научились их писать.
Методы, не возвращающие значения – это привычные нам процедуры (Sub).
В дальнейшем мы будем писать собственные методы и они тоже будут или процедурами, или функциями. Поэтому в дальнейшем я часто буду называть процедуры и функции методами.
Что такое свойства (Property)? Пока мы можем думать о них, как о переменных.
В классах встречаются и константы (Const).
О событиях (Event) поговорим гораздо попозже 22.13.
Очевидно, что у методов, возвращающих значение, у свойств, у констант имеются значения, а значит они принадлежат к тому или иному типу.
Все, что я сказал сейчас относительно классов, относится частично и к структурам, и к модулям, и к другим объектам, входящим в состав пространств имен.
Пользуемся подсказкой, чтобы узнать объектные типы
Для новичка функции, переменные, параметры объектного типа непривычны. И мы будем привыкать к ним постепенно, по мере надобности. А чтобы не отвыкнуть, не успев привыкнуть, нам нужно научиться для начала обеспечивать совместимость, быстро узнавать забытый тип и т.д. Ведь классов, их свойств и методов (да еще и параметров этих методов) в библиотеке классов .NET Framework огромное количество, всех не упомнишь. И если VB жалуется, например, на несоответствие типу параметра для забытого нами метода какого-то класса, то мы должны хотя бы иметь возможность где-то узнать, что это за тип.
Рассмотрим на простейшем примере, как для этого пользоваться подсказкой. Пусть мы, например, хотим покрасить метку в красный цвет, но забыли, как это делается. В результате мы не знаем, как правильно написать – так:
Label1.BackColor = Color.Red
так:
Label1.BackColor = Brushes.Red
или так:
Label1.BackColor = Pens.Red
VB путем подчеркивания вежливо подсказывает нам, что два последних варианта неверны. Но мы не понимаем, почему. И хотим знать, чтобы не ошибаться в дальнейшем. Такое желание достойно уважения и его необходимо удовлетворить.
Источник многих ошибок – несоответствие типов. Поэтому нам для начала нужно точно знать тип каждого элемента в написанных строках. Потренируемся. Начнем с правильной строки.
Label1.BackColor = Color.Red. Поставим мышь на слово Label1. Всплывает подсказка:
Friend Dim WithEvents Label1 As System.Windows.Forms.Label
Что это такое? Не обращаем внимания на слова Friend и WithEvents. Остается
Dim Label1 As System.Windows.Forms.Label
Это не что иное, как объявление метки. Мы его не делали, VB сделал его сам в скрытой части окна кода (см. 6.1.3). Мы видим, что метка Label1 имеет тип System.Windows.Forms.Label, то есть принадлежит классу Label, входящему в пространство имен System.Windows.Forms.
Поставим мышь на слово BackColor. Всплывает подсказка:
Public Overridable Overloads Property BackColor() As System.Drawing.Color
Не обращаем внимания на слова Public Overridable Overloads. Остается
Property BackColor() As System.Drawing.Color
Слово Property означает «свойство». Это не что иное, как подсказка о типе свойства BackColor. Мы видим, что свойство метки BackColor имеет тип System.Drawing.Color, то есть является структурой Color, входящей в пространство имен System.Drawing.
Не задавайте мне сейчас вопросов, вроде «Как может цвет быть какой-то там структурой?» или других вопросов о «смысле». Вам даже не нужно понимать, похожа ли структура на объект. Понимание таких вещей придет к нам позднее. Сейчас нам важно уяснить следующее: Некоторый элемент (в нашем случае – свойство BackColor) может принадлежать
одному классу или объекту (Label1), а его значение может иметь тип совсем другого класса, объекта или чего-нибудь еще (структура Color). Всегда помните эту разницу. Это нормально. Это в порядке вещей.
Поставим мышь на слово Color. Всплывает подсказка:
Structure Color
Здесь все понятно.
Далее. У структуры Color есть свойства и одно из них стоит после точки. Поставим мышь на слово Red. Всплывает подсказка:
Public Shared Overloads ReadOnly Property Red() As System.Drawing.Color
Не обращаем внимания на слова Public Shared Overloads ReadOnly. Получается
Property Red() As System.Drawing.Color
Мы видим, что Red – это свойство структуры Color и оно же имеет тип Color, то есть тип той же самой структуры. А почему бы и нет, Семен Семеныч Семенов? Опять же, пока важно не это.
Оператор присваивания Label1.BackColor = Color.Red присваивает свойству BackColor объекта Label1 свойство Red структуры Color. Мы только что убедились, что типы обоих свойств совпадают, а это значит, что совместимость типов соблюдена и присвоение прошло без осложнений.
Label1.BackColor = Brushes.Red. Теперь посмотрим, к чему придирается VB в операторе Label1.BackColor = Brushes.Red. Что касается левой части оператора, то там все то же, что и в правильной строке. Посмотрим на подчеркнутую правую часть.
Поставим на нее мышь. Всплывает подсказка, относящаяся ко всей подчеркнутой части ошибочного оператора и в ней мы скоро разберемся. Но нам сначала хотелось бы узнать, что это за Brushes такие. Предположим, что мы это забыли.
Чтобы вспомнить, мы можем воспользоваться средствами помощи или Object Browser, как мы это делали в 6.2.8. Я поступлю проще – напишу какой-нибудь правильный оператор, в котором выражение Brushes.Red не будет подчеркнуто, и поставлю мышь на слово Brushes. Всплывает подсказка:
Class Brushes
Все ясно. Это класс с именем Brushes.
Поставим мышь на слово Red. Всплывает подсказка:
Public Shared Overloads ReadOnly Property Red() As System.Drawing.Brush
Она единственным (самым правым) словом отличается от аналогичной подсказки для свойства Red структуры Color. Но это слово решает все.
Мы видим, что в данном случае Red – это свойство класса Brushes, и оно имеет тип Brush. Класс Brushes и класс Brush – это разные классы и вы не должны их путать, несмотря на то, что их похожее написание провоцирует спекуляции на тему некоей смысловой связи этих классов. Чтобы правильно пользоваться обоими классами, нет никакой необходимости вникать в наличие или отсутствие этой связи.
Мы видим, что свойство Red принадлежит классу Brushes, а само имеет значение совсем другого типа – класса Brush. Тоже в порядке вещей. Привыкайте.
Главный вывод таков. Неправильный оператор присваивания пытается присвоить свойству BackColor, имеющему тип Color, свойство Red, имеющее тип Brush. Типы не совпадают и VB бессилен их совместить.
Поставим мышь на подчеркнутую правую часть. Всплывает подсказка
Value of type 'System.Drawing.Brush' cannot be converted to 'System.Drawing.Color'
Переводится подсказка так:
«Значение типа Brush не может быть преобразовано в тип Color».
Как видите, для понимания причин несовместимости нам совсем не понадобилось изучать класс Brush и даже знакомиться с ним.
Label1.BackColor = Pens.Red. Аналогично можете убедиться в неправильности третьей строки, где свойство Red знакомого нам класса Pens имеет неведомый нам тип Pen, то есть его значение принадлежит классу Pen.
Получается не очень логичная с точки зрения здравого смысла вещь: Красный – он и в Африке красный. Зачем же тогда свойство Red разных классов имеет разный тип? Поймете, когда будете писать свои классы.
Параметры методов
В предыдущем подразделе мы разбирались в причинах несовместимости объектных типов в операторе присваивания. Сейчас мы рассмотрим несовместимость объектных типов в параметрах.
Вспомним еще раз Методы, «придирчивые» к типу параметров (6.2.8). Проделаем еще раз то, что мы делали там. Пусть мы, например, забыли параметры метода DrawLine. В результате мы не знаем, как правильно написать – так:
Граф.DrawLine(Pens.Red, 0, 0, 100, 100)
так:
Граф.DrawLine(Brushes.Red, 0, 0, 100, 100)
или так:
Граф.DrawLine(Color.Red, 0, 0, 100, 100)
VB путем подчеркивания подсказывает нам, что два последних варианта неверны. Но нам надо знать, почему. Мы грешим на несовместимость типов и решаем проверить, каким типам принадлежат первый параметр в обращении к методу и первый параметр в объявлении метода.
Граф.DrawLine(Pens.Red, 0, 0, 100, 100). Рассмотрим сначала правильную строку. Поставим мышь на слово Red. Подсказка говорит нам, что свойство Red знакомого нам класса Pens имеет неведомый нам тип Pen, то есть его значение принадлежит классу Pen. Хорошо. Неважно, что класс неведомый, важно, чтобы тип первого параметра тоже был Pen и в объявлении метода.
Как добраться до объявления? Предлагаю 3 способа. Первые два заключаются в использовании системы помощи VB и Object Browser. Я разобрал эти способы в 6.2.8 и тех, кто их совсем забыл, отсылаю туда. Остальным напомню.
1 способ. Поставьте текстовый курсор на название метода DrawLine в окне кода и нажмите клавишу F1. Перед вами возникнет окно помощи, в котором вы увидите заголовки нескольких вариантов метода – процедуры Sub DrawLine. Во всех вариантах вы увидите, что тип интересующего нас первого параметра – Pen. Не путайте с Pens. Все в порядке.
2 способ. Найдите в Object Browser класс Graphics и выделите. В правой панели вы увидите его свойства и методы. Среди них вы найдете и 4 варианта метода DrawLine с указанием параметров.
3 способ я рассмотрю в следующем подразделе.
Граф.DrawLine(Brushes.Red, 0, 0, 100, 100). Теперь рассмотрим вторую строку, ошибочную. Поставим на нее мышь. Перед нами возникает длинная подсказка об ошибке:
Overload resolution failed because no accessible 'DrawLine' can be called with these arguments:
'Public Overloads Sub DrawLine(pen As System.Drawing.Pen, x1 As Integer, y1 As Integer, x2 As Integer, y2 As Integer)': Value of type 'System.Drawing.Brush' cannot be converted to 'System.Drawing.Pen'.
'Public Overloads Sub DrawLine(pen As System.Drawing.Pen, x1 As Single, y1 As Single, x2 As Single, y2 As Single)': Value of type 'System.Drawing.Brush' cannot be converted to 'System.Drawing.Pen'.
Переводится она так:
«Ошибка произошла потому, что ни один из вариантов процедуры DrawLine не может быть вызван с такими аргументами (параметрами):
В варианте процедуры 'Public Overloads Sub DrawLine(pen As System.Drawing.Pen, x1 As Integer, y1 As Integer, x2 As Integer, y2 As Integer)' значение типа 'System.Drawing.Brush' не может быть преобразовано в 'System.Drawing.Pen'.
В варианте процедуры 'Public Overloads Sub DrawLine(pen As System.Drawing.Pen, x1 As Single, y1 As Single, x2 As Single, y2 As Single)' значение типа 'System.Drawing.Brush' не может быть преобразовано в 'System.Drawing.Pen'.»
Мне кажется, здесь все ясно. Мы ошибочно попытались написать в обращении первым параметром Brushes.Red. Но VB выдал ошибку, поскольку тип свойства Red класса Brushes есть Brush, а для первого параметра требуется Pen.
Аналогичное сообщение вы получите для третьей строки.
IntelliSense
У VB есть средство IntelliSense, помогающее вводить программный текст в окно кода. Его основная цель – сократить время написания операторов, по ходу ввода предлагая программисту подсказки, правильные варианты написания текста и дописывая за него слова.
Перечислю основные возможности IntelliSense:
Список компонентов. Мы с вами уже давно и широко пользуемся этой возможностью. Как только мы ставим точку после имени объекта, перед нами возникает прокручивающийся список всех подходящих компонентов этого объекта, из которого мы мышкой или с клавиатуры выбираем нужный. Когда нужный компонент выбран, рядом с ним возникает подсказка, поясняющая его смысл и значение. Мы нажимаем клавишу Tab или Enter – и компонент появляется в тексте, что избавляет нас от необходимости вводить его вручную.
Подсказка по параметрам – пример 1. Она, непрошенная, сопровождает вас во время ввода параметров процедур и функций. Безусловно, вам она уже, наверное, надоела, а некоторые, возможно, в ней уже разобрались и извлекают из нее пользу. Это, кстати, и есть третий способ добраться до объявления процедуры или функции, упомянутый в предыдущем подразделе. Пришла пора разобрать эту подсказку. Разберем ее сначала на примере вызова функции вычисления абсолютной величины.
Введите текст, приведенный на Рис. 11.2.

Рис. 11.2
Как только вы введете открывающую скобку, перед вами возникнет подсказка, приведенная на рисунке. Смысл ее не очень понятен, но в ее левом углу мы видим, что имеется семь вариантов написания этой функции, а мы сейчас смотрим на подсказку к 1-му варианту. Щелкая мышкой по стрелкам или с клавиатуры прокрутим список подсказок, пока не доберемся до чего-нибудь более подходящего (я имею в виду 3-й вариант – Рис. 11.3).

Рис. 11.3
В верхней части подсказки мы видим заголовок объявления функции.
Abs (value As Integer) As Integer
Это как раз то, что нужно, чтобы знать правильные типы функции и ее параметров. Функция имеет имя Abs. Слово value – это имя первого параметра. Почему выбрано именно такое имя, не имеет значения. Если вы помните, вы можете для параметров выбирать любые имена. Вы видите, что в этом варианте параметр должен иметь тип Integer. Функция в этом случае, как видите, будет обязана иметь тот же самый тип Integer.
В нижней части подсказки мы видим пояснение для параметра value: Говорится, что это число в диапазоне таком-то (далее идет пояснение диапазона для профессионалов, нам это не нужно, так как мы и без того знаем точное значение этого диапазона).
Введите в качестве параметра какое-нибудь число, переменную или выражение и закройте скобку. Подсказка исчезнет.
Подсказка по параметрам – пример 2. Мы хотим нарисовать залитый эллипс при помощи функции FillEllipse. Введите текст, приведенный на Рис. 11.4 и выберите 2-й вариант подсказки.

Рис. 11.4
Внутри скобок мы видим 4 запятые. Значит, параметров – 5. Первый из них такой:
brush As System.Drawing.Brush
Слово brush – это имя первого параметра. Начинающего на первых порах может дезориентировать тот факт, что выбрано именно такое имя. Ведь оно совпадает с именем класса Brush. Сказано же: разные вещи называй по-разному! Но опытные программисты знают, что значение и роль какого-нибудь элемента в программной строке зависит не только от его имени, но и от положения в этой строке. Поэтому и не возражают против таких «тезок». Ну, а новичкам приходится привыкать.
Вы видите, что первый параметр должен иметь тип System.Drawing.Brush. Это значит, что он должен быть объектом класса Brush, принадлежащего пространству имен System.Drawing.
Внизу подсказки мы видим пояснение параметра brush: Говорится, что этот объект определяет характеристики заливки.
В дальнейшем мы сами будем создавать кисти. А сейчас воспользуемся готовым объектом – Brushes.Blue (см. Рис. 11.5). Он имеет подходящий тип.

Рис. 11.5
Как только мы ввели запятую после Brushes.Blue, подсказка изменилась. Теперь полужирным выделен не первый параметр, который мы только что ввели, а второй, который нужно вводить. Вообще правило такое: полужирным выделяется тот параметр, на котором стоит текстовый курсор. И подсказка внизу – для него же. Это удобно.
Имя второго параметра – x. Его тип в этом варианте – Single. Читаем внизу пояснение: x – координата верхнего левого угла воображаемого прямоугольника, описанного вокруг эллипса.
Введите в качестве параметра какое- нибудь число, переменную или выражение и поставьте запятую. Подсказка снова изменяется, настроившись на третий параметр. И так далее.
Подсказка в подсказке. Попробуем в качестве какого-нибудь параметра этой функции ввести выражение, содержащее другую функцию – Math.Abs(-20) + 10. Как только мы откроем скобку после Abs, VB услужливо сменит подсказку для процедуры FillEllipse на подсказку для функции Abs (см. Рис. 11.6).

Рис. 11.6
Когда вы закроете скобку после функции Abs, вернется старая подсказка. Правда, у меня не сохранялся номер варианта старой подсказки.
Всплывающая подсказка. Ей мы широко пользовались. Если поставить мышь на любое имя в окне кода, всплывет подсказка о типе элемента с этим именем.
Дописываю слово. Начните писать какое-нибудь имя, например, имя пространства имен Microsoft. Написав Mic, вы устали и хотите, чтобы VB вместо вас дописал это слово. Нажмите комбинацию клавиш Alt-стрелка направо или Ctrl-пробел – и VB допишет или предложит варианты.
Выделение парных скобок. Когда вы пишете длинное выражение со многими вложенными скобками, бывает трудно глазами отыскать пару для данной скобки, что затрудняет правильный ввод выражений. VB приходит вам на помощь и в момент ввода скобки выделяет полужирным ее и ее пару.
Переменные и параметры объектного типа
Я уже говорил, что объекты пронизывают весь VB. Нам пора привыкать к ним, как к значениям переменных величин и параметров.
Точка
Точка – это структура типа Point, принадлежащего пространству имен System.Drawing. Давайте создадим Точку.
Что представляет собой структура Точка? Мы знаем, что точка-пиксель на плоскости (на экране) задается парой координат (X и Y). Так вот: основное назначение структуры Point в том и состоит, чтобы хранить эти две координаты точки и удобно их выдавать программисту по первому требованию. Поставим текстовый курсор на слово Point и нажмем F1. Сработает система помощи и на экране вы увидите небогатый список свойств и методов структуры Point. Среди них – X и Y. Остальные нас не интересуют. Щелкнем по свойству X – и увидим, что это свойство имеет тип Integer.
Для создания структуры Point достаточно написать:
Dim Точка As Point
Вопрос: если мы создали структуру Точка и нигде не задавали ей значения X и Y, то чему они равны? Посмотрим:
Dim Точка As Point
Точка.X = 20
Debug.WriteLine(Точка)
Кстати, еще одна новость – Debug.WriteLine(Точка). До сих пор мы при помощи Debug.WriteLine печатали числа и строки. А как распечатается структура? Не вылетит ли с экрана точка и не погонится ли за нами?
Вот что будет напечатано:
{X=20,Y=0}
VB решил, что распечатка структуры – это распечатка в фигурных скобках ее основных свойств. Можно вообразить, что Точка Point – это просто пара чисел. Попробуйте для интереса распечатать объекты: кнопку и форму.
Итак, X=20, Y=0. Этого следовало ожидать. Каждое из свойств структуры при ее инициализации инициализируется в соответствии со своим типом данных.
Кроме Точки типа Point в VB имеется Точка типа PointF, которая отличается от первой тем, что ее координаты имеют тип не Integer, а Single.
Кому нужна Точка? Это выяснится чуть позже.
Размер
Размер – это структура типа Size или SizeF, принадлежащего пространству имен System.Drawing. Размер предназначен для задания ширины и высоты прямоугольной области.
Структура Размер аналогична структуре Точка, только вместо свойств X и Y у нее имеются свойства Width и Height.
Прямоугольник
Прямоугольник – это структура типа Rectangle, принадлежащего пространству имен System.Drawing. Прямоугольник определяется парой координат своего левого верхнего угла (X и Y), шириной (Width) и высотой (Height).
Dim Прямоугольник As Rectangle
Прямоугольник.X = 20
Прямоугольник.Width = 80
Debug.WriteLine(Прямоугольник)
Этот фрагмент напечатает:
{X=20,Y=0,Width=80,Height=0}
Кроме этих основных свойств у Прямоугольника есть еще несколько приятных дополнительных, с которыми вас может познакомить система помощи.
Также у Прямоугольника есть несколько интересных методов, с одним из которых (Inflate) мы познакомимся.
Кроме Прямоугольника типа Rectangle в VB имеется Прямоугольник типа RectangleF, который отличается от первого тем, что его координаты и размеры имеют тип не Integer, а Single.
Использование Точки и Прямоугольника в графических методах
Просмотрите еще раз варианты графических методов для рисования. Вы найдете много таких, в которых параметры обязаны иметь типы Point, PointF, Rectangle, RectangleF.
Вот пример рисования линии, прямоугольника и эллипса вариантами графических методов с использованием Точек и Прямоугольников:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim Гр As Graphics = Me.CreateGraphics
'Объявляем точки и прямоугольники:
Dim Т1, Т2 As Point
Dim П1, П2 As Rectangle
'Задаем координаты и размеры:
Т1.X = 50 : Т1.Y = 20
Т2.X = 200 : Т2.Y = 80
П1.X = 120 : П1.Y = 100 : П1.Width = 150 : П1.Height = 30
П2.X = 220 : П2.Y = 200 : П2.Width = 180 : П2.Height = 40
'Рисуем с использованием Точек и Прямоугольников:
Гр.DrawLine(Pens.Black, Т1, Т2)
Гр.DrawRectangle(Pens.Black, П1)
Гр.DrawEllipse(Pens.Black, П2)
End Sub
Пояснения: Как видите, заранее необходимо создать все нужные Точки и Прямоугольники и придать им нужные вам координаты и размеры, а затем уже можно рисовать с использованием подходящих вариантов графических методов. Здесь в методе DrawLine Точки Т1 и Т2 – начальная и конечная точки отрезка. Метод DrawRectangle рисует Прямоугольник П1. А метод DrawEllipse рисует эллипс, вписанный в Прямоугольник П2.
Inflate. Познакомимся с методом Прямоугольника Inflate. Этот метод расширяет (сужает) Прямоугольник во все стороны на заданные размеры. Так, оператор
П1.Inflate(-20, 10)
сузит Прямоугольник П1 на 20 пикселей в обе стороны по горизонтали и расширит на 10 пикселей в обе стороны по вертикали.
Разберитесь в программе:
Dim Гр As Graphics
Dim П1, П2 As Rectangle
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Гр = Me.CreateGraphics
П1.X = 200 : П1.Y = 150 : П1.Width = 150 : П1.Height = 30
П2.X = 550 : П2.Y = 150 : П2.Width = 200 : П2.Height = 10
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
П1.Inflate(30, 10) 'Изменяем размеры Прямоугольника в памяти
Гр.DrawRectangle(Pens.Black, П1) 'Рисуем измененный Прямоугольник
End Sub
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
П2.Inflate(-10, 10) 'Изменяем размеры Прямоугольника в памяти
Гр.DrawRectangle(Pens.Black, П2) 'Рисуем измененный Прямоугольник
End Sub
Пояснения: Оператор П1.Inflate(30, 10) расширяет Прямоугольник П1 на 30 пикселей в обе стороны по горизонтали и на 10 пикселей в обе стороны по вертикали. Оператор П2.Inflate(-10, 10) сужает Прямоугольник П2 на 10 пикселей в обе стороны по горизонтали и расширяет на 10 пикселей в обе стороны по вертикали.
В результате нескольких нажатий на кнопку Button1 вы увидите фигуру в левой части Рис. 12.1, а в результате нескольких нажатий на кнопку Button2 – в правой.

Рис. 12.1
Задание 66.
«Круги на воде или радиоволны». Нарисуйте в цикле десяток концентрических окружностей, то есть окружностей разного радиуса, но имеющих общий центр.
Задание 67.
«Компакт-диск» и «Летающая тарелка». Если диаметр самого маленького «круга на воде» будет порядка 100, а самого большого – во весь экран, и если диаметры соседних окружностей будут различаться на 2-4 пикселя, то на экране вы увидите привлекательный «компакт-диск». Сделайте его белым или золотым на черном фоне. Если получилось, то сделайте ему внутренний и наружный ободки другого цвета. А теперь «положите» диск, то есть нарисуйте его не из окружностей, а из эллипсов, сжатых по вертикали. Получится «летающая тарелка».
Задание 68.
Меняя вместе с диаметром еще и вертикальную координату, вы получите «коническую башню».
Точки и прямоугольники
Многие графические методы требуют в качестве параметров так называемые «Точки», «Размеры» и «Прямоугольники». Эти элементы VB похожи по смыслу на одноименные зрительные образы, но это не одно и то же. Я буду писать их с заглавной буквы. Точка – это структура типа System.Drawing.Point. Размер – это структура типа System.Drawing.Size. Прямоугольник – это структура типа System.Drawing.Rectangle. Не путайте его с прямоугольником, нарисованным на экране, который никакой структурой не является, а представляет собой просто светящиеся пиксели. Наши Точка, Размер и Прямоугольник – это структуры, которые, подобно объекту класса Graphics, живут в памяти компьютера невидимо.
Что такое структура? Пока нам достаточно знать, что структура – это один из видов объектов (в широком смысле) VB, который наряду с классами, модулями и перечислениями входит в состав пространств имен. У структуры, также как и у класса, могут быть свойства и методы. Структуры похожи на классы, но чуть-чуть «не дотягивают» до них. Со структурами мы будем знакомиться постепенно.
Создаем собственные перья. Конструктор
Создаем перо. До этого момента для рисования линий мы пользовались стандартными перьями из класса Pens. Там можно было выбирать цвет пера и больше ничего. Мы же хотим управлять также толщиной, стилем и другими свойствами линий. Для этого существует специальный класс Pen, входящий в пространство имен System.Drawing (не путать с классом Pens). Но пользоваться им напрямую нельзя. Нам нужно сначала создать из класса Pen объекты-перья подобно тому, как из класса Button в 6.1.2 мы создавали объекты-кнопки.
Конструктор. В классах для создания из них экземпляров-объектов существует специальная процедура с именем New. Называется она конструктором. Вы можете объявить и создать объект тремя способами записи. Самый длинный:
Dim Перо As Pen 'Объявляем объект
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Перо = New Pen(Color.Red, 20) 'Создаем объект
End Sub
При создании пера при помощи слова New необходимо в скобках указывать параметры. Я выбрал вариант с указанием двух параметров: цвета пера и толщины. В приведенном фрагменте создано красное перо толщины 20.
Откуда мы знаем, какие нужно указывать параметры, и нужно ли их указывать вообще? Об этом вам скажет подсказка по параметрам, которая возникнет, как только вы откроете скобку после слова Pen. Вы выберете вариант по вкусу и укажете параметры.
О процедуре New и вариантах ее параметров вы можете также узнать при помощи клавиши F1 или Object Browser.
Как видите, обращение к конструктору грамматически несколько отличается от общепринятого обращения к процедуре.
В приведенном варианте при запуске проекта объект только объявляется, а создается позже, когда мы нажмем на кнопку. Этот способ хорош тогда, когда вы хотите по ходу работы проекта то создавать, то уничтожать объект.
Вот способ покороче:
Dim Перо As Pen = New Pen(Color.Red, 20) 'Объявляем и создаем объект
Здесь объект и объявляется, и создается одновременно.

Рис. 12.3
Для этого создайте проект с тремя кнопками и меткой и введите следующую программу:
Dim Граф_формы, Граф_метки As Graphics
Dim Перо1, Перо2 As Pen
'Создаем графические объекты и перья:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Граф_формы = Me.CreateGraphics
Граф_метки = Label1.CreateGraphics
'Создаем перо 1:
Перо1 = New Pen(Color.Blue, 10)
Перо1.DashStyle = Drawing.Drawing2D.DashStyle.Dash
'Создаем перо 2:
Перо2 = New Pen(Color.Red, 20)
Перо2.StartCap = Drawing.Drawing2D.LineCap.RoundAnchor
Перо2.EndCap = Drawing.Drawing2D.LineCap.ArrowAnchor
End Sub
'Рисуем пером 1:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Граф_формы.DrawEllipse(Перо1, 100, 50, 200, 100) 'Рисуем пером 1 эллипс на форме
Граф_метки.DrawPie(Перо1, 10, 10, 150, 150, 0, 270) 'Рисуем пером 1 сектор на метке
End Sub
'Рисуем пером 2:
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
Граф_формы.DrawLine(Перо2, 50, 50, 330, 150) 'Рисуем пером 2 стрелку на форме
Граф_метки.DrawRectangle(Перо2, 50, 50, 70, 100) 'Рисуем пером 2 прямоугольник на метке
End Sub
Пояснения. Здесь мы объявили оба пера вне процедур, чтобы ими можно было пользоваться из каждой процедуры.
При создании пера 1 мы задали его свойство DashStyle (стиль штриховки). Как только в окне кода в операторе
Перо1.DashStyle = Drawing.Drawing2D.DashStyle.Dash
вы введете с клавиатуры знак равенства, перед вами развернется список возможных стилей штриховки (Рис. 12.4), из которых вы можете выбирать.

Рис. 12.4
Вот их узоры:
|
Custom |
Определяется программистом |
|
Dash |
- - - - - - - - |
|
DashDot |
- . - . - . - . |
|
DashDotDot |
- . . - . . - . . - . . |
|
Dot |
. . . . . . . . |
|
Solid |
________ |
Каждый из стилей является одним из 6 значений перечисления DashStyle, входящего в пространство имен Drawing.Drawing2D. Мы выбрали штриховку Dash и таким штрихованным пером начертили эллипс и сектор.
При создании пера 2 мы задали его свойства StartCap («Колпак» на начало линии) и EndCap («Колпак» на конец линии). Процесс их выбора аналогичен процессу выбора свойства DashStyle. В качестве колпаков может выступить стрелка (ArrowAnchor), кружок (RoundAnchor) и ряд других фигур и очертаний. Эти фигуры и очертания являются значениями перечисления LineCap, входящего в пространство имен Drawing.Drawing2D. Пером 2 мы начертили стрелку и прямоугольник. Обратите внимание, что прямоугольник получился без «колпаков». Наверное потому, что у линии прямоугольника нет ни начала, ни конца.
У пера есть и другие любопытные свойства, но объем книги не позволяет мне на них останавливаться. Если вам интересно, найдите в Object Browser список свойств класса Pen. Выделите интересующее вас свойство и нажмите клавишу F1. Если вы знаете английский, то попробуйте разобраться в возникшем окне помощи. Правда, разобраться как следует даже при знании английского начинающему будет трудновато.
Создаем собственные кисти
До этого момента для рисования закрашенных фигур мы пользовались стандартными кистями из класса Brushes. Однако, собственные кисти гораздо богаче возможностями, чем стандартные. Собственные объекты-кисти, как мы уже предвидим, нужно будет создавать из специального класса, подобно тому, как мы создавали из класса Pen объекты-перья. Что же это за класс? Он не один, их несколько, точнее – 5. Каждый из них обеспечивает создание кистей определенного вида. Наряду с термином «кисть» мы будем употреблять термин заливка с тем же смыслом, поэтому можно сказать, что каждый класс обеспечивает фигурам заливку (закраску) определенного вида.
Вот эти классы с указанием пространств имен:
| System.Drawing.SolidBrush | Обычная сплошная простая заливка | ||
| System.Drawing.Drawing2D.LinearGradientBrush | Линейный градиент, то есть плавный переход между двумя цветами | ||
| System.Drawing.Drawing2D.PathGradientBrush | Нелинейный градиент | ||
| System.Drawing.TextureBrush | Текстурная заливка | ||
| System.Drawing.Drawing2D.HatchBrush | Штрихованная заливка |
Существует также специальный класс Brush (не путать с Brushes), но от него объекты-кисти образовывать нельзя, у него другая роль.
Сплошная кисть – SolidBrush. Собственная сплошная кисть нам не очень интересна, так как не богаче стандартной из класса Brushes. Покажу все же, как ее создавать:
Dim Кисть As SolidBrush
= New SolidBrush(Color.Blue)
Теперь все равно, что писать в программе: Кисть или Brushes.Blue.
Создав однажды Кисть, вы затем свободно можете менять ее цвет, задавая ее свойство Color:
Кисть.Color = Color.Brown
Градиентная кисть – LinearGradientBrush. Поставим задачу нарисовать маленький эллипс, такой, как в правой части Рис. 12.5.

Рис. 12.5
Вы видите, что его заливка не однородная, а меняется от красного к желтому. Для этого используется так называемая градиентная заливка (кисть) или точнее – линейная градиентная заливка (LinearGradientBrush). Вот как она работает. Чтобы было удобнее объяснять, я нарисовал еще и большой полосатый эллипс, нарисованный той же кистью.
Зададим на поверхности произвольную точку, где цвет должен быть абсолютно красным. Назовем ее Т1. Зададим другую произвольную точку, где цвет должен быть абсолютно желтым. Назовем ее Т2. Мысленно соединим две эти точки отрезком прямой (на рисунке этот отрезок показан черной линией). Вдоль этого отрезка цвет постепенно меняется от абсолютно красного к абсолютно желтому. Вся заливаемая поверхность покрыта этим изменяющимся цветом так, что любая воображаемая прямая, перпендикулярная данному отрезку, состоит из точек одного цвета. Способ заливки хорошо виден на рисунке: как только кисть дойдет до абсолютно желтого цвета, она вновь начинает с красного. Получается матрас.
Произвольно задавая положение точек Т1 и Т2, мы можем как угодно регулировать направление градиента (изменения) цвета и расстояние между полосами матраса.
Вот какая программа понадобилась для этого рисунка:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim Гр As Graphics = Me.CreateGraphics
Dim Т1 As New Point(220, 50)
Dim Т2 As New Point(350, 150)
Dim П As New Rectangle(10, 30, 530, 200)
'Создаем градиентную кисть:
Dim Кисть_град As New System.Drawing.Drawing2D.LinearGradientBrush(Т1, Т2, Color.Red, Color.Yellow)
Гр.FillEllipse(Кисть_град, П) 'Рисуем большой эллипс
Гр.DrawLine(Pens.Black, Т1, Т2) 'Рисуем отрезок
Гр.FillEllipse(Кисть_град, 450, 10, 180, 50) 'Рисуем маленький эллипс
End Sub
Пояснения. Здесь я использовал простейший вариант создания градиентной кисти с 4 параметрами: первая точка, вторая точка, один цвет, другой цвет. Есть и другие, например, с указанием угла.
Штрихованная кисть – HatchBrush. Фигуры можно заливать не только цветом, но и разными штриховками, например, такой, как на Рис. 12.6.

Рис. 12.6
Для этого используется так называемая штрихованная кисть (HatchBrush).
Прежде чем писать программу, позаботимся об экономии чернил. Поскольку HatchBrush принадлежит к пространству имен с очень длинным именем System.Drawing.Drawing2D, сделаем так, чтобы VB не утомлял нас им. Для этого (вспомните 2.2.3) самой первой строкой в окне кода напишем
Imports System.Drawing.Drawing2D
С учетом этой строки вот какой фрагмент понадобился для этого рисунка:
Dim Кисть_штрих As New HatchBrush(HatchStyle.BackwardDiagonal, Color.Blue, Color.Yellow)
Гр.FillEllipse(Кисть_штрих, 10, 50, 530, 200)
Пояснения. Самый первый параметр конструктора штрихованной кисти – HatchStyle – тип штриховки. На выбор вам будет предложено несколько десятков типов. Второй параметр – цвет штриха, третий (в 1 варианте конструктора он не указывается) – цвет фона.
Рассмотрение текстурной кисти отложим до 12.6. Кисть с нелинейным градиентом рассматривать не будем.
Шрифты
До сих пор мы писали на поверхности формы и элементов управления только теми шрифтами, что задавали в режиме проектирования (3.2.2 и 6.2.6). Покажем, как создавать собственные шрифты программным путем, в коде. Делается это аналогично созданию собственных перьев и кистей. Шрифт – это объект класса Font, входящего в пространство имен System.Drawing. Создадим один какой-нибудь шрифт:
Dim Строгий_шрифт As New Font("Arial", 20, FontStyle.Bold)
Пояснения. Из десятка вариантов конструктора я выбрал вариант с 3 параметрами:
Первый параметр – взятое в кавычки название (гарнитура) одного из установленных на вашем компьютере шрифтов (Arial). Гарнитура определяет рисунок букв. Гарнитуры можно посмотреть, зайдя в режиме проектирования в свойство Font любого элемента управления. На вашем компьютере установлены шрифты гарнитур «Arial», «Times», «Courier» и как минимум нескольких других.
Второй параметр – размер шрифта (20). На единицах измерения размера шрифта я не останавливаюсь.
Третий параметр – начертание (стиль) шрифта (FontStyle). VB предложит вам на выбор несколько начертаний:
| Regular | обычный | ||
| Bold | полужирный | ||
| Italic | курсив | ||
| Underline | подчеркнутый | ||
| Strikeout | перечеркнутый |
Создадим проект с кнопками и меткой. Пусть при нажатии на первую кнопку пишется слово «Всегда!» на форме, а при нажатии на вторую – слова «Привет!» и «издалека» на метке (см. Рис. 12.7).

Рис. 12.7
Для этого понадобится такая программа:
Dim Строгий_шрифт As New Font("Arial", 20, FontStyle.Bold)
Dim Красивый_шрифт As New Font("Times", 80, FontStyle.Bold Or FontStyle.Italic)
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim Граф As Graphics = Me.CreateGraphics
Граф.DrawString("Всегда!", Строгий_шрифт, Brushes.Black, 0, 50)
End Sub
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Dim Гр As Graphics = Label1.CreateGraphics
Dim Т1 As New Point(40, 20)
Dim Т2 As New Point(20, 40)
Dim Кисть As New Drawing2D.LinearGradientBrush(Т1, Т2, Color.Blue, Color.Yellow)
Гр.DrawString("Привет!", Красивый_шрифт, Кисть, 0, 0)
Гр.DrawString("издалека", Строгий_шрифт, Brushes.Black, 300, 100)
End Sub
Пояснения. Стиль для красивого шрифта мы указали так:
FontStyle.Bold Or FontStyle.Italic
Это значит, что мы хотим, чтобы он был одновременно и полужирным, и курсивом. Если вы помните, то при помощи логической операции Or мы точно так же в 7.8 задавали вид и поведение окна MsgBox. Несколько непривычное для нас применение логической операции Or.
Чтобы слово «Привет!» получилось полосатым, как на рисунке, я пишу это слово градиентной кистью, градиент для которой я определяю при помощи Точек Т1 и Т2.
Точки я задаю немного по-другому, чем делал это раньше. Я использую конструктор. У структур, как и у классов, есть конструкторы. Два параметра у конструктора Точки – это ее главные свойства X и Y. Их я и указал.
Задание 69.
Начертите график функции y = (sin x)/x так, как он выглядит на Рис. 12.8. Координатные оси должны быть со стрелками. Указания: Максимальное значение Y равно 1, максимальное значение X на рисунке примерно равно 30. Задание выполняется по тому же принципу, что и Задание 47. Подробные пояснения смотрите в ответе.

Рис. 12.8
Собственные перья, кисти и шрифты
Собственные перья, кисти и шрифты получатся у вас гораздо богаче, чем стандартные, которыми вы пользовались до этого.
Картинка, как свойство Image элемента управления
Картинки можно размещать на многих элементах управления. Рассмотрим PictureBox. Для размещения картинки в PictureBox мы должны установить его свойство Image. В режиме проектирования это делается совершенно так же, как мы это делали в 3.6.
У элемента PictureBox имеется свойство SizeMode, которое управляет положением картинки на элементе. Вот его значения:
| Normal | Левый верхний угол картинки совпадает с левым верхним углом PictureBox. Если картинка больше PictureBox, то выступающие ее части не видны. | ||
| CenterImage | Картинка размещается в центре PictureBox. Если картинка больше PictureBox, то выступающие ее части не видны. | ||
| AutoSize | PictureBox автоматически изменяет свои размеры так, чтобы они равнялись размерам картинки. | ||
| StretchImage | Картинка автоматически изменяет свои размеры так, чтобы они равнялись размерам PictureBox. |
У PictureBox есть также свойство BackgroundImage. Это картинка, которая мозаикой покрывает поверхность элемента на заднем плане. Она становится видна, если Image покрывает не всю поверхность элемента.
Свойство Image элементу управления можно устанавливать и в коде. Для этого быстрее всего использовать метод FromFile, принадлежащий классу Image. (Пусть вас не путает то, что свойство и класс имеют одинаковые имена. Для VB это в порядке вещей.) Если вы хотите, чтобы картинка оказалась на элементе управления, необходимо, чтобы файл с этой картинкой уже хранился на дисках вашего компьютера. Если вы не можете найти ни одного файла с картинкой, то подскажу, что в папке Windows вы всегда отыщете картинки – обои рабочего стола или в крайнем случае найдете что-нибудь по адресу
Program Files\Microsoft Visual Studio .NET\Common7\Graphics
Рекомендую предварительно скопировать файлы картинок в папку bin вашего проекта. Тогда обращаться к ним можно будет просто по имени, без необходимости указания полного адреса. Кстати, и картинки всегда будут при вас.
Вот оператор, устанавливающий свойство Image элементу PictureBox:
PictureBox1.Image
= Image.FromFile("Earth.JPG")
Здесь слева от знака равенства – свойство Image, справа – класс Image. Обратите внимание, что в кавычках я указал только имя файла Earth.JPG. Это стало возможным потому, что я скопировал файл в папку bin проекта. Иначе пришлось бы указать полный адрес: "D:\Фотографии\ Earth.JPG".
Элементы управления вполне могут заимствовать друг у друга картинки:
Button2.Image = PictureBox1.Image
Если мы хотим, чтобы на элементе управления картинки больше не было, мы пишем:
Button2.Image = Nothing
Слово Nothing означает «Ничто», то есть картинки никакой нет.
Задание 70.
Вы профессиональный продавец автомобилей. Вы приезжаете к покупателю, достаете портативный компьютер, на экране – несколько десятков кнопок, на каждой – маленькая фотография одного из продаваемых автомобилей. Покупатель говорит: «Вот этот покажите, пожалуйста». Вы нажимаете эту кнопку и на экране возникает та же фотография, но увеличенная.
Помощь: Если вы собираетесь в качестве кнопок использовать элементы управления Button, то вам придется предварительно позаботиться об уменьшении фотографий до размеров кнопок. Но можно и избежать такой потери времени. Ведь кнопками могут служить объекты PictureBox! Потому что у объекта PictureBox тоже есть событие Click! Создайте на форме несколько маленьких PictureBox и один большой и в маленькие впишите фото. По щелчку мыши по маленькому PictureBox большой PictureBox пусть копирует в себя его картинку.
Необязательное усложнение для тех, кто не боится системы координат: Если у вас фотографии имеют одинаковые размеры и пропорции, то все хорошо и ничего дополнительно делать не нужно. Проблемы возникают тогда, когда размеры и пропорции исходных фото разные: одни фото большие и продолговатые по горизонтали, другие – по вертикали, третьи маленькие квадратные. Во-первых, проблемы возникают уже с кнопками, потому что, чтобы фото на них не были искажены или обрезаны, они сами должны иметь разную продолговатость. Но Бог с ними, с кнопками, нам хочется, чтобы хоть большие-то картинки располагались на экране симметрично как по горизонтали, так и по вертикали, и имели максимально возможный размер. Вот этой цели я и хочу достигнуть. Для этого придется использовать оператор ветвления, а также свойства, задающие размер и местоположение объектов. Проверьте, чтобы форма была распахнута на весь экран. Как это сделать, я объяснял в 3.4.3. После щелчка мыши по кнопке компьютер должен сделать следующее:
Таким образом, при щелчке по кнопке на форме возникает фото максимальных размеров и по центру.
Растровая и векторная графика
В компьютерной графике все картинки можно разделить на растровые
и векторные. Соответственно говорят о двух типах графики – растровой
и векторной, и о двух типах форматов графических файлов – растровом
и векторном. К растровым форматам относятся форматы BMP, JPEG, GIF, TIFF, PNG, Icon и др., к векторным форматам относятся форматы WMF, EMF и др.
Упомянутые два вида графики различаются своим поведением, поэтому неплохо бы знать их различия. Посмотрите на Рис. 12.9.
![]() | ![]() | ||
![]() | ![]() |
Рис. 12.9
Слева – фотография (формат BMP), справа – рисунок из коллекции Microsoft (формат WMF). Фотография – типичный представитель растровой графики, рисунок из коллекции Microsoft – типичный представитель векторной графики. Ниже вы видите увеличенные фрагменты фотографии и рисунка соответственно.
Чем больше мы увеличиваем фотографию, тем больше становятся зернистыми и грубыми ее мелкие элементы. Это вполне естественно, то же самое мы видим, рассматривая в мощную лупу фотографию на бумаге. При дальнейшем увеличении на экране нам будет казаться, что фотография состоит из цветных квадратов. Эти квадраты – не что иное, как бывшие пиксели.
С рисунком же ничего подобного не происходит. Сколько бы мы его ни увеличивали, все линии рисунка остаются ясными и четкими. Причина вот в чем. Обратите внимание, что в отличие от фотографии, где цвет плавно изменяется от одного места фотографии к другому, векторный рисунок состоит из одноцветных областей разной формы с резким переходом между областями. Если точно задать линии контура всех областей и цвет заливки каждой области, то весь рисунок будет задан.
В файлах векторной графики хранится не собственно рисунок, сделанный из пикселей, как в случае с фотографией, а точное математическое описание рисунка: описание координат всех линий, из которых сделаны контуры областей, и цветов заливки этих областей. Видимого рисунка, как такового, нет, одни описания. Это как если бы вы попросили свою знакомую прислать вам свою фотографию, а она вместо этого прислала письмо: «Глаза у меня серые, ушки маленькие» и так далее. Но для компьютера такого описания достаточно. Если фотографию компьютер направляет на экран прямо так, как она есть, пиксель за пикселем, то рисунок на экране компьютер по описанию рисует. И если вы, работая с векторным рисунком, сместили его на экране, увеличили или как-то по-другому изменили, то компьютер его заново мгновенно перерисовывает. Никто ему не помешает при любом увеличении рисовать четко, ведь описания математически точные.
Рисуем картинки
С этого момента мы будем говорить о работе с картинками в коде.
Устанавливать элементам управления свойство Image быстро и просто. Но для осуществления всего богатства работы с картинками, о которых я говорил, нужны и другие способы.
Объекты. В VB имеются специальные объекты для работы с картинками. Для нас главный из них – объект класса Bitmap. Он может работать со всеми упомянутыми форматами файлов. Имеются еще класс Metafile, учитывающий специфику работы с файлами векторной графики, и класс Icon, учитывающий специфику работы с иконками. Существует еще упомянутый выше класс Image, для которого классы Bitmap и Metafile являются «наследниками». Поэтому считается, что объекты классов Bitmap и Metafile принадлежат не только своим классам, но и типу Image (о наследовании читайте в 22.7.).
Объект класса Bitmap находится в оперативной памяти и невидим для нас. Его главное дело – получить картинку из файла и хранить ее. Картинка, естественно, тоже будет невидима. В памяти она может быть по нашему желанию подвергнута разнообразной обработке, а затем, когда нам нужно, она из объекта Bitmap попадает на поверхность формы или элемента управления, где мы ее и увидим.
Способ 1. Вот самый простой способ поместить картинку из файла в Bitmap, а оттуда безо всяких преобразований – на элемент управления:
Private Sub Button5_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button5.Click
Dim Картинка As New Bitmap("Spacescape.JPG")
PictureBox1.Image = Картинка
End Sub
Пояснения. Первый оператор создает объект Картинка класса Bitmap и тут же при помощи параметра конструктора помещает в него картинку из файла Spacescape.JPG. Следующий оператор просто присваивает эту картинку свойству Image элемента управления PictureBox1. О свойстве Image мы уже говорили.
Способ 2:
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
Dim Картинка As New Bitmap("Spacescape.JPG")
Dim Гр As Graphics = PictureBox2.CreateGraphics
Гр.DrawImage(Картинка, 0, 0)
End Sub
Здесь картинка не присваивается свойству Image элемента управления PictureBox2, а рисуется
на этом элементе. Метод DrawImage объекта Гр рисует содержимое объекта Картинка на элементе, в результате чего мы видим на нем картинку. Левый верхний угол картинки имеет на элементе управления координаты 0, 0.
Аналогично картинки можно рисовать и на форме, и на других элементах управления. Оба способа на первый взгляд дают один и тот же результат, но на самом деле между ними есть существенные различия, которые вы постепенно поймете. Самое бросающееся в глаза различие – нарисованная картинка стирается, как только элемент управления, на котором она нарисована, оказывается загорожен другими окнами или задвинут вами за край экрана. Картинка же, являющаяся свойством Image – не стирается.
У конструктора Bitmap есть много вариантов. Мы рассмотрим их позже, а сейчас поговорим поподробнее о методе DrawImage.
Несколько картинок на форме. Вот программа, рисующая на форме три картинки разных форматов:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim Гр As Graphics = Me.CreateGraphics
'Создаем объекты Bitmap из графических файлов:
Dim Фото As New Bitmap("Spacescape.JPG")
Dim Векторный_рисунок As New Bitmap("DIME.WMF")
Dim Иконка As New Bitmap("FACE.ICO")
'Переносим картинки с объектов Bitmap на форму:
Гр.DrawImage(Фото, 0, 0)
Гр.DrawImage(Векторный_рисунок, 300, 50)
Гр.DrawImage(Иконка, 480, 50)
End Sub
Вот результат работы этой программы (Рис. 12.10).

Рис. 12.10
Левый верхний угол космического пейзажа имеет на форме координаты 0, 0. Обратите внимание, что рисунки векторной графики и иконки вовсе не обязаны иметь прямоугольную форму. Тем не менее, мы можем вообразить прямоугольники, описанные вокруг них. Поэтому можно сказать, что левый верхний угол векторного рисунка монеты имеет на форме координаты 300, 50. Маленькая иконка в виде круглой рожицы видна наверху монеты. Ее координаты – 480, 50.
Метафайлы и иконки. Вот для сведения программка, делающая то же самое с монетой и рожицей, но уже с использованием объектов не класса Bitmap, а классов Metafile и Icon:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Dim Гр As Graphics = Me.CreateGraphics
'Работаем с метафайлом:
Dim Векторный_рисунок As New System.Drawing.Imaging.Metafile("DIME.WMF")
Гр.DrawImage(Векторный_рисунок, 300, 50)
'Работаем с иконкой:
Dim Иконка As New Icon("FACE.ICO")
Гр.DrawIcon(Иконка, 480, 50)
End Sub
Как видите, если классы Image, Bitmap и Icon принадлежат пространству имен System.Drawing, то класс Metafile принадлежит пространству имен System.Drawing.Imaging. Для рисования иконок есть свой метод – DrawIcon. В дальнейшем я не буду рассматривать специфику классов Metafile и Icon:
Размер и разрешение картинок
Принцип получения изображения на экране монитора. Этот принцип иллюстрируется Рис. 12.11 и подробно рассмотрен в Приложении 1 (Устройства вывода – Монитор).

Рис. 12.11
Если он вам незнаком, то обязательно изучите его, иначе дальнейший материал вам будет непонятен.
Размер и разрешение. Как попадают фотографии на компьютерный диск? Со сканера, с цифрового фотоаппарата, из Интернета, с телевизора или видеомагнитофона и некоторыми другими путями. В любом случае изображение сохраняется в графическом файле одного из растровых форматов (BMP, JPEG и др.). Это означает, что изображение представляется в файле в виде мозаики мельчайших пикселей. В файле указывается ширина картинки в пикселях – это то количество пикселей, на которые разбита картинка по горизонтали. Аналогично указывается высота.
А откуда файл узнал это количество? Оно определяется аппаратурой, получившей фото для компьютера. Например, обычный сканер может различать 300 точек (пикселей) на один дюйм (около двух с половиной сантиметров) ширины. Это число называется разрешением (Resolution) по горизонтали. Раз так, то ширина фото в пикселях для сканера получается умножением разрешения сканера на ширину бумажной фотографии в дюймах. То же относится и к высоте. В цифровых фотоаппаратах свои цифры. Там заранее известны ширина и высота в пикселях.
Итак, в файлах растровых форматов кроме ширины и высоты картинки в пикселях указываются также разрешение по горизонтали и вертикали.
Такой вопрос: Современные мониторы не способны обеспечить такой маленький размер пикселя, чтобы на дюйме их умещалось целых 300. Зачем же тогда сканерам работать с таким разрешением? Ну, во-первых существует еще печать на бумаге, где такое разрешение уже достигнуто и превышено. Во-вторых, на экране мы можем изображение и увеличивать и тогда лишние пиксели пригодятся.
Тогда еще один вопрос: Пусть бумажная фотография имела ширину 5 дюймов. Следовательно, после сканера с разрешением 300 ширина картинки составит 1500 пикселей. Если я захочу увидеть ее на экране, то она не уместится, так как мой экран настроен на ширину 1280 пикселей. Что делать? На это я могу сказать, что многие программы показывают на экране картинки, исходя из их размеров не в пикселях, а в дюймах, и часто предоставляют пользователю выбор желаемого разрешения экрана. Кстати, программы этого раздела показывали картинки на экране именно исходя из их размеров в дюймах.
Размер и разрешение в объекте Bitmap. Свойства Width и Height объекта Bitmap – это его ширина и высота, то есть ширина и высота картинки, хранящейся в нем, в пикселях. Чтобы узнать, например, ширину объекта Фото, достаточно написать оператор
MsgBox (Фото.Width)
У меня ширина объекта Фото равнялась 1528. Разрешение равнялось 300, отсюда и размер на экране составлял около 5 дюймов.
Откуда взялись эти цифры – 1528 и 300? Объект Фото взял их из файла Spacescape.JPG, из которого он был создан оператором
Dim Фото As New Bitmap("Spacescape.JPG")
Управляем размерами и разрешением. Рассмотрим следующую программу:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim Гр As Graphics = Me.CreateGraphics
'Работаем с оригинальным фото:
Dim Фото As New Bitmap("Spacescape.JPG")
Debug.WriteLine(Фото.Size) : Debug.WriteLine(Фото.HorizontalResolution)
Гр.DrawImage(Фото, 0, 0)
'Работаем с уменьшенным фото:
Dim Фото1 As New Bitmap(Фото, 50, 40)
Debug.WriteLine(Фото1.Size) : Debug.WriteLine(Фото1.HorizontalResolution)
Гр.DrawImage(Фото1, 500, 0)
'Увеличиваем видимые размеры фото за счет уменьшения разрешения:
Фото1.SetResolution(10, 10)
Debug.WriteLine(Фото1.Size) : Debug.WriteLine(Фото1.HorizontalResolution)
Гр.DrawImage(Фото1, 570, 0)
End Sub
Пояснения. Программа состоит из трех последовательных частей. Первая часть рисует левую из трех фотографий на Рис. 12.12. Кроме этого, она печатает две строчки в окне Output:
{Width=1528, Height=1212}
300

Рис. 12.12
Здесь первая строка – это размеры (Size) объекта Фото. Вторая строка – разрешение по горизонтали (HorizontalResolution) объекта Фото.
Умножим для интереса 1528 на 1212. Получается, что наше фото состоит почти из двух миллионов пикселей. Многовато. А ведь каждый пиксель требует со стороны компьютера внимания и времени! Не удивительно, что при активной работе с такими качественными фотографиями компьютер начинает подтормаживать.
Давайте, чтобы не напрягать компьютер, прикажем разбить картинку на гораздо меньшее число пикселей. Скажем, 50 на 40. Я специально взял слишком мало, чтобы разница бросалась в глаза. К сожалению, объект класса Bitmap не позволяет после своего рождения менять свои размеры. Поэтому мы из старого объекта Фото создадим новый объект Фото1, причем воспользуемся тем, что при рождении объекта класса Bitmap его конструктор позволяет задавать размеры. Это я и сделал при помощи строки
Dim Фото1 As New Bitmap(Фото, 50, 40)
В данном варианте конструктора объект получает свое разрешение (Resolution) не от старого объекта, а приобретает стандартное в компьютерном мире разрешение для экрана монитора, равное 96. Это значит, что картинка на экране будет показана «пиксель в пиксель», то есть займет на экране 50 пикселей по горизонтали и 40 пикселей по вертикали. Что мы и видим на рисунке (маленькая картинка между двумя большими).
Посмотрим, что напечатала вторая часть нашей программы:
{Width=50, Height=40}
96
Нам такой размер не нравится. Нам хочется побольше. Пожалуйста. Для этого достаточно изменить разрешение объекта. У нас в одном дюйме умещается 96 пикселей. Сделаем, чтобы умещалось 10 по горизонтали и 10 по вертикали:
Фото1.SetResolution(10, 10)
Вот что печатает третья часть нашей программы:
{Width=50, Height=40}
10
Рисует она правую картинку из трех.
Вы видите, что пиксели (квадратики) получились настолько большие, что их легко заметить. Вся картинка получилась из-за этого очень грубой и зернистой. За что боролись! Вариант 500 на 400 был бы наилучшим выходом. И число пикселей уменьшилось бы на порядок, и потери качества мы бы не заметили.
Метод DrawImage и его варианты
Вот программа:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim Гр As Graphics = Me.CreateGraphics
Dim Фото As New Bitmap("Spacescape.JPG")
Dim Т As New Point(30, 10)
Dim П1 As New Rectangle(550, 10, 150, 130)
Dim П2 As New Rectangle(550, 160, 200, 70)
Dim П3 As New Rectangle(550, 250, 200, 150)
Dim П4 As New Rectangle(800, 170, 400, 300)
Гр.DrawImage(Фото, Т) 'Точка Т задает левый верхний угол картинки
Гр.DrawImage(Фото, П1) 'Втискиваем картинку в прямоугольник П1
Гр.DrawImage(Фото, П2) 'Втискиваем картинку в прямоугольник П2
'Вырезаем из картинки прямоугольник П4 и втискиваем его в прямоугольник П3:
Гр.DrawImage(Фото, П3, П4, GraphicsUnit.Pixel)
End Sub
Вот результат ее работы (Рис. 12.13).

Рис. 12.13
Пояснения. Я уже говорил, что у структур, как и у классов, есть конструкторы. Четыре параметра у конструктора прямоугольника – это его главные свойства X, Y, Width и Height.
Из трех десятков вариантов DrawImage я использовал три. Оператор
Гр.DrawImage(Фото, Т) 'Точка Т задает левый верхний угол картинки
рисует ту картинку, что на рисунке слева, с левым верхним углом в указанной точке.
Операторы
Гр.DrawImage(Фото, П1) 'Втискиваем картинку в прямоугольник П1
Гр.DrawImage(Фото, П2) 'Втискиваем картинку в прямоугольник П2
рисуют две картинки справа сверху. Они позволяют как угодно увеличивать, уменьшать, растягивать и сплющивать картинку, потому что картинка обязана уместиться в указанный вами прямоугольник.
Оператор
Гр.DrawImage(Фото, П3, П4, GraphicsUnit.Pixel)
вырезает из картинки прямоугольник П4 и умещает его в прямоугольник П3 на форме. Будьте внимательны насчет единиц измерения размеров в прямоугольнике П4. Прежде всего, четвертым параметром метода мы указали GraphicsUnit.Pixel. Это значит, что единицей измерения для прямоугольника П4 мы выбрали пиксель в объекте Фото. Не путайте пиксели на форме и экране с пикселями в невидимых объектах Bitmap. Не удивительно, что прямоугольник П4 я задаю с очень большими величинами параметров, ведь измеряются они в этих самых невидимых пикселях.
Метод RotateFlip объекта Bitmap
Метод RotateFlip позволяет поворачивать (Rotate) и зеркально отражать (Flip) картинку в памяти.
Вот программа:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim Гр As Graphics = Me.CreateGraphics
Dim Фото As New Bitmap("Spacescape.JPG")
Фото.RotateFlip(RotateFlipType.Rotate90FlipNone) 'Повернули по часовой на 90 град, не отражали
Гр.DrawImage(Фото, 0, 0)
Фото.RotateFlip(RotateFlipType.RotateNoneFlipY) 'Отразили по вертикали, не поворачивали
Гр.DrawImage(Фото, 400, 0)
End Sub
Вот результат ее работы (Рис. 12.14).

Рис. 12.14
Пояснения. У метода RotateFlip всего один параметр. Как только вы раскроете скобку, VB предложит вам все 16 возможных значений этого параметра. Они являются значениями перечисления RotateFlipType. В смысле всех 16 легко разобраться на 4 нижеприведенных примерах. Нужно только знать, что Rotate переводится «вращай по часовой стрелке», Flip переводится «зеркально отражай», None переводится «не надо»:
| RotateNoneFlipX | Вращать не надо, отражай по горизонтали | ||
| Rotate180FlipNone | Вращай на 180 градусов, отражать не надо | ||
| Rotate90FlipY | Вращай на 90 градусов, после чего отрази по вертикали | ||
| Rotate270FlipXY | Вращай на 270 градусов, после чего отрази по горизонтали и вертикали |
Посмотрим на программу. Оператор
Фото.RotateFlip(RotateFlipType.Rotate90FlipNone) 'Повернули по часовой на 90 град, не отражали
поворачивает картинку в объекте Фото. Она так и остается там повернутой. Но мы этого пока не видим. Чтобы увидеть ее на форме, применяем следующий оператор:
Гр.DrawImage(Фото, 0, 0)
Это и есть картинка в левой части Рис. 12.14. Она действительно повернута по сравнению с исходной ориентацией, которую мы можем видеть на Рис. 12.13. Далее оператор
Фото.RotateFlip(RotateFlipType.RotateNoneFlipY) 'Отразили по вертикали, не поворачивали
отражает по вертикали уже повернутую картинку в объекте Фото, после чего следующий оператор показывает ее нам в правой части рисунка..
Метод Save объекта Bitmap
Сохраняем картинку в файл. Метод Save сохраняет картинку из объекта Bitmap в файл. Это может понадобиться тогда, когда картинка как-то изменена. Вот программа, которая загружает картинку из файла Spacescape.JPG, поворачивает ее и сохраняет в повернутом виде в файле Spacescape-1.JPG.
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Dim Фото As New Bitmap("Spacescape.JPG")
Фото.RotateFlip(RotateFlipType.Rotate90FlipNone) 'Повернули по часовой на 90 град, не отражали
Фото.Save("Spacescape-1.JPG") 'Сохранили измененную картинку в файле
End Sub
Преобразовываем формат графического файла. У метода Save – несколько вариантов. Один из них позволяет указывать формат сохраняемого файла. Поэтому в VB есть возможность преобразовать формат графического файла. Вот, например, как просто преобразовать файл из формата WMF в формат BMP:
Dim Картинка As New Bitmap("DIME.WMF")
Картинка.Save("DIME.BMP", System.Drawing.Imaging.ImageFormat.Bmp)
Первая строка загружает файл DIME.WMF в объект Картинка класса Bitmap. Вторая строка сохраняет эту картинку в файле DIME.BMP. Однако не расширение BMP, указанное нами, приказало компьютеру изменить формат файла. Это приказал сделать второй параметр метода – свойство Bmp класса ImageFormat, принадлежащего пространству имен System.Drawing.Imaging. Кроме свойства Bmp у этого класса есть еще десяток свойств, соответствующих разным форматам графических файлов. Однако не для всех из них преобразование этим методом осуществимо или же осуществимо так просто.
Работа с картинками
VB может не только сам рисовать линии, кружочки и другие фигуры. Он может работать и с готовыми картинками, доставшимися со стороны, то есть с рисунками, нарисованными в графических редакторах, и с фотографиями. Чтобы VB мог с ними работать, они должны храниться на диске в виде файлов следующих популярных графических форматов:
| BMP | JPEG | GIF | TIFF | PNG | Icon | WMF | EMF | Exif |
Кроме этого, в VS есть собственный простенький графический редактор, позволяющий рисовать иконки (пиктограммы, значки), курсоры и обычные рисунки.
Что значит, что VB работает с фотографиями и рисунками? Это не значит, что он сам «фотографирует» или рисует, как художник. Это значит, что он может уже готовые картинки из файлов показывать в любом месте экрана и всячески их преобразовывать, например, увеличивать, уменьшать, растягивать, сплющивать во все стороны, обрезать, двигать по экрану, быстро сменять разные картинки, добиваясь эффекта анимации, изменять цвета и т.п.
Однако этого вполне достаточно, чтобы обеспечить вашему проекту привлекательный внешний вид или, скажем, использовать его для создания игр.
Рисуем в памяти
Программисты знают, что в оперативной памяти те или иные действия происходят быстрее, чем на поверхности экрана. Так, увеличить на единицу переменную в памяти быстрее, чем увеличить на единицу число в текстовом поле. То же относится и к графике. Рисование фигур и преобразование картинок непосредственно на экране происходит медленнее, чем те же действия в памяти. Поэтому, когда дело касается рисования большого числа фигур и сложных манипуляций с картинками, целесообразно все эти действия проделать в памяти, а человеку показать только конечный результат.
Но что значит «рисовать в памяти»? Пока мы умеем в памяти только хранить картинку в объекте Bitmap и там ее немножко преобразовывать. А как в памяти нарисовать, например, кружочек? Давайте посмотрим.
VB позволяет рассматривать объект Bitmap как некую невидимую поверхность, для которой можно создать объект класса Graphics точно так же, как мы создаем объект класса Graphics для формы или, скажем, метки. Как только объект класса Graphics для объекта Bitmap создан, вы получаете возможность пользоваться всеми его методами и, значит, рисовать на невидимой поверхности невидимыми перьями и кистями все, что вашей душе угодно, и помещать туда любые картинки. После того, как вы будете удовлетворены этим «нарядом голого короля», вы можете одним оператором поместить его на всеобщее обозрение на экран.
Вот программа:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim Картинка As New Bitmap(600, 400) 'Создаем пустую поверхность 600 на 400
Dim Гр As Graphics = Graphics.FromImage(Картинка) 'Создаем над ней объект класса Graphics
'Рисуем на поверхности:
Dim Векторный_рисунок As New Bitmap("DIME.WMF")
Гр.DrawImage(Векторный_рисунок, 10, 10)
Dim П As New RectangleF(300, 250, 160, 40)
Гр.FillRectangle(Brushes.White, П)
Гр.DrawString("Это монета", Me.Font, Brushes.Black, П)
'Переносим на форму то, что нарисовано на поверхности:
Dim Граф As Graphics = Me.CreateGraphics
Граф.DrawImage(Картинка, 0, 0)
End Sub
Пояснения. Результат работы этой программы – Рис. 12.15, содержащий картинку, фигуру (белый прямоугольник) и текст.

Рис. 12.15
Все эти элементы были сначала нарисованы в памяти в объекте Bitmap, потом одним оператором содержимое объекта Bitmap было помещено на форму. Рассмотрим программу поподробнее, оператор за оператором.
Dim Картинка As New Bitmap(600, 400) 'Создаем пустую поверхность 600 на 400
Здесь мы сталкиваемся с еще одним вариантом конструктора объекта Bitmap. Объект создается пустой, без картинки. Можно представить его, как чистую поверхность размером 600 на 400, готовую к тому, чтобы на ней что-нибудь нарисовали.
Dim Гр As Graphics = Graphics.FromImage(Картинка) 'Создаем над ней объект класса Graphics
Эта строка создает для объекта Картинка объект класса Graphics. Но, как видите, способ создания здесь не такой, как, скажем, для формы. Там мы использовали функцию CreateGraphics формы, здесь же мы используем функцию FromImage класса Graphics. Именно класса, а не объекта. Различие в смысле для вас прояснится позже.
Объект класса Graphics создан и вы получаете возможность пользоваться всеми его методами так, как если бы вы рисовали на форме или элементе управления. Следующие пять строк процедуры наносят на невидимую поверхность объекта Bitmap по очереди картинку монеты, белый прямоугольник и текст.
Последние две строки процедуры озабочены переносом содержимого объекта Bitmap на форму. Для этого создается объект класса Graphics для формы и перенос привычно осуществляется методом DrawImage этого объекта.
Перерисовка картинок, фигур и текста
Вы, наверное, давно уже обратили внимание на такой факт. Если форма с нарисованными фигурами, картинками или написанным текстом скрывается из глаз, закрытая другими окнами, то когда мы вновь ее добываем из-под других окон, то видим, что нарисованные картинки, фигуры и текст стерлись. Если мы часть формы затащили за край экрана, то стирается все, что было нарисовано и написано на этой скрывшейся за край экрана части. Если мы мышью уменьшили размеры формы, а потом снова увеличили, то на поверхности, обнажившейся после увеличения, все стерто. В общем, стирается все то, что пропадает с глаз.
Но если картинка находится на элементе управления в качестве его свойства Image, то она не стирается. Стирается лишь то, что нарисовано.
Чтобы стирания нарисованных вещей не происходило, в Visual Basic 6.0 мы просто в режиме проектирования устанавливали у формы свойство AutoRedraw, и могли больше об этом не думать. Однако VB слишком сложен и мощен для такого «детского» решения.
Посмотрим, что нужно делать в VB.
Событие Paint. В каком случае мы можем заметить, что на форме что-то стерто? Наверное, тогда и только тогда, когда в поле нашего зрения на экране появляется участок формы (пусть даже самый маленький), который был до этого по той или иной причине скрыт (и поэтому с него все было стерто).
Задача VB – создать у нас иллюзию, что ничего не стирается. Для этого путь один – как только такой участок со стертой информацией возникает в поле зрения, тут же его снова зарисовывать «как было», чтобы человек не успевал ничего заметить. Однако, сам VB зарисовывать ничего не собирается, он предоставляет позаботиться об этом программисту. Для чего в помощь ему VB отряжает событие Paint. Это событие наступает как раз тогда, когда в поле зрения на экране появляется участок формы (пусть самый ничтожный), который был до этого скрыт. Если вы, например, медленно вытаскиваете форму из-за края экрана или медленно увеличиваете ее высоту или ширину, то каждую ничтожную долю секунды в поле зрения появляются «стертые» участки, а значит, наступает событие Paint. Получается, что в данном случае событие Paint наступает много раз в секунду.
Раз есть событие, значит есть и процедура-обработчик этого события. Все, что должен сделать программист, это получить в окне кода обычным способом заготовку этой процедуры и записать в нее операторы рисования всех фигур и всего текста на форме, которые он хочет предохранить от стирания. По принципу: если забор в одном месте запачкался – перекрашивай весь забор.
Создайте проект с кнопкой. Введите в окно кода такой текст:
Dim Гр As Graphics = Me.CreateGraphics
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Гр.DrawEllipse(Pens.Black, 20, 20, 80, 100)
End Sub
Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) _
Handles MyBase.Paint
Гр.DrawRectangle(Pens.Black, 10, 10, 200, 100)
End Sub
Пояснения. При нажатии на кнопку рисуется кружок. Чтобы создать заготовку процедуры-обработчика события Paint, выберите, если вы работаете в Visual Studio .NET 2003, в левом поле окна кода Form1 Events, а если в Visual Studio .NET – Base Class Events., а в правом – Paint. Введите в эту процедуру оператор рисования квадрата.
Запустите проект. Вы видите, что квадрат уже на форме, несмотря на то, что никаких кнопок вы не нажимали. Произошло это потому, что событие Paint уже успело сработать при появлении формы на экране. Щелкните по кнопке Button1. Появился еще и кружок. Теперь задвиньте форму частично за левый край экрана и снова выдвиньте. Вы увидите, что часть кружка стерта, а квадрат как будто и не стирался (см. Рис. 12.16).

Рис. 12.16
На самом деле он прекрасно стерся, но при выдвижении формы из-за края экрана много раз в секунду наступало событие Paint и он каждый раз рисовался снова. Причем рисовался настолько быстро, что человеческий глаз не успевал этого заметить.
Итак, мы видим, что власть над тем, каким фигурам стираться, а каким нет, находится полностью в руках программиста. Поэтому, если вы хотите, чтобы фигуры не стирались, помещайте операторы для их рисования в обработчик события Paint.
Событие Paint имеется не только у формы, но и у элементов управления. Можете проверить его, например, на метке.
Мы можем искусственно вызвать событие Paint и, значит, перерисовку поверхности формы или элемента управления, применяя их метод Refresh. Например, так:
Me.Refresh()
Кроме обработки события Paint VB предлагает и другие способы перерисовки, но мы на них останавливаться не будем.
Image не стирается. Очевиден такой способ борьбы со стиранием. Мы рисуем все, что нам нужно, в памяти на объекте Bitmap, а затем присваиваем получившийся Bitmap свойству Image элемента управления.
Текстурная кисть
Текстурной кистью называют такой способ заливки фигур, когда они вместо цвета или штрихового узора заполняются бесконечно повторяющейся картинкой, взятой вами из графического файла (см. Рис. 12.17).

Рис. 12.17
На этом рисунке эллипс заполнен квадратной картинкой, повторяющейся на нем полтора десятка раз. Создает такую заливку следующая программа:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Dim Текстура As New Bitmap("Дверь.jpg")
Dim Кисть_текстурная As New TextureBrush(Текстура)
Dim Гр As Graphics = Me.CreateGraphics
Гр.FillEllipse(Кисть_текстурная, 10, 50, 1200, 900)
End Sub
Пояснения. Конструктор текстурной кисти (вторая строка) может сконструировать ее только из объекта типа Image, содержащего подходящую картинку. Поэтому первой строкой я создал такой объект с именем Текстура. Последние две строки рисуют на форме эллипс, заполненный этой текстурой.
Большинство вариантов конструктора позволяют использовать для кисти не всю картинку, а вырезанный из нее прямоугольник. Если вы знаете размер картинки в пикселях, то можете этим воспользоваться:
Dim Текстура As New Bitmap("Дверь.jpg")
Dim П As New Rectangle(200, 100, 40, 70)
Dim Кисть_текстурная As New TextureBrush(Текстура, П)
Здесь я заранее знаю размер картинки в объекте Bitmap – 256 на 256. Из нее я вырезал прямоугольник с левым верхним углом в точке (200, 100) относительно левого верхнего угла картинки и с размерами 40 на 70. Этот прямоугольный фрагмент и стал новой картинкой для кисти. Если вы «залезете» вырезаемым прямоугольником за край исходной картинки, VB выдаст ошибку.
Несколько вариантов конструктора позволяют управлять ориентацией картинки в текстурной заливке и создавать простейшие комбинации с использованием этой картинки в разных ориентациях (см. например, Рис. 12.18).

Рис. 12.18
Для этого используется параметр конструктора WrapMode. Это перечисление из пространства имен System.Drawing.Drawing2D. Оно имеет несколько значений, определяющих форму комбинации. Одно из них – TileFlipXY – использовано в программе, создающей этот рисунок
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
Dim Картинка As New Bitmap("DIME.WMF")
Dim Текстура As New Bitmap(Картинка, 100, 100)
Dim Кисть_текстурн As New TextureBrush(Текстура, System.Drawing.Drawing2D.WrapMode.TileFlipXY)
Dim Гр As Graphics = Me.CreateGraphics
Гр.FillEllipse(Кисть_текстурн, 10, 10, 900, 600)
End Sub
Пояснения. Здесь я сначала, как положено, создал объект Bitmap для картинки, но поскольку картинка получилась слишком большая, я следующим оператором уменьшил ее до размеров 100 на 100. Следующий оператор из полученной картинки создает текстурную кисть с комбинацией TileFlipXY.
Системные цвета
Задаем системные цвета вручную. Попробуйте в режиме проектирования настроить цвет кнопки. Для этого в окне свойств зайдите в ее свойство BackColor, а в нем – в закладку System (см. Рис. 12.19) и выберите какой-нибудь цвет.

Рис. 12.19
В этой закладке представлены так называемые системные цвета. Это те цвета, на которые настроены элементы вашей Windows: окна, их заголовки, цвет текста в заголовках, меню и т.д. Например, цвет кнопок и многих других рельефных элементов в Windows называется Control (у меня на компьютере он серый), а цвет рабочего стола, когда на нем нет обоев, называется Desktop (у меня он темно-голубой). Эти цвета мы и видим на рисунке. Вы, наверное, уже обратили внимание, что и VS вслед за Windows всегда предлагает вам цвет Control в качестве цвета формы, кнопок и других элементов управления.
Итак, системные цвета VS берет от Windows. Проверим. Выйдем из VS в Windows. Перенастроим цветовую гамму Windows. Сейчас она у вас, скорее всего, стандартная, серенькая или синеватая. Выберем что-нибудь поярче. Для этого щелкнем правой клавишей мыши по рабочему столу Windows и в контекстном меню выберем Свойства, а там – закладку Оформление. В возникшем окне перенастроим цветовую схему Windows. Обратите внимание, что весь цветовой облик Windows после этого изменился, цвета всех ее элементов стали другими.
Попробуйте теперь в Windows открыть какое-нибудь солидное приложение, например, Word. Вы увидите, что привычные цвета элементов Word тоже изменились в согласии с выбранной в Windows цветовой схемой. Вернитесь в VS, в ваш проект. Вы видите, что изменился цвет кнопки и цвет формы. Снова зайдите в окно свойств, в свойство BackColor, а в нем – в закладку System. Обратите внимание, что все цвета здесь изменились в согласии с выбранной в Windows цветовой схемой. Попробуйте открыть какой-нибудь старый проект – цвета формы и элементов управления изменились и в нем.
Таким образом, изменение цветовой схемы Windows влияет на все ее приложения. Хорошо это или плохо? Как посмотреть. В любом случае программист должен иметь возможность сам решать, должны ли цвета элементов в его приложении зависеть от настройки цветовой схемы Windows.
Как решать? Просто. Если речь идет о выборе цвета в режиме проектирования, то выбрав цвет из закладки System, вы тем самым разрешаете ему меняться в зависимости от настройки цветовой схемы Windows на компьютере пользователя. Если же вы выбираете цвет из закладок Custom или Web, то запрещаете.
Если вы не используете в своем проекте системных цветов, цвета вашего приложения на любом компьютере будут одинаковы независимо от настроек Windows.
Опасайтесь делать цвета одних элементов проекта системными, а других – несистемными. Например, сделав цвет кнопки системным, а именно Desktop, а цвет текста на этой кнопке несистемным, а именно белым, вы сильно рискуете, потому что если пользователю вздумается сделать рабочий стол своего Windows белым, он ничего не сможет прочесть на вашей кнопке.
И вообще – цветовые схемы Windows составляли профессиональные дизайнеры. Если ваше приложение пользуется только системными цветами, то есть хороший шанс, что его цветовое решение будет достаточно гармоничным. Если ваше приложение пользуется только несистемными цветами, то такой шанс тоже есть, если у вас была пятерка по рисованию. Если же приложение пользуется и системными и несистемными цветами, то есть вероятность, что на чужом компьютере оно будет напоминать помесь попугая с вороной.
Задаем системные цвета в коде. До сих пор в коде мы с вами пользовались несистемными цветами, которые VB предоставлял нам в структуре Color и в классах Pens и Brushes пространства имен System.Drawing. Системные же цвета лежат соответственно в классах SystemColors, SystemPens и SystemBrushes пространства имен System.Drawing. Там вы найдете пару десятков примерно тех же цветов, что и в закладке System свойства BackColor. Из класса SystemPens вы берете готовые перья, из класса SystemBrushes вы берете готовые кисти, из класса SystemColors вы берете цвета для всех прочих нужд. Вот примеры использования системных цветов:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Button2.BackColor = SystemColors.ActiveCaption
Button2.ForeColor = SystemColors.ActiveCaptionText
Dim Гр As Graphics = Me.CreateGraphics
Гр.DrawEllipse(SystemPens.MenuText, 100, 0, 300, 200)
Гр.FillEllipse(SystemBrushes.ControlLightLight, 80, 80, 140, 180)
Dim Перо As New Pen(SystemColors.ControlDark)
Гр.DrawEllipse(Перо, 10, 50, 230, 150)
Dim Кисть As New SolidBrush(SystemColors.ControlDark)
Гр.FillEllipse(Кисть, 50, 50, 200, 100)
End Sub
Private Sub Button4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button4.Click
Dim Гр As Graphics = Me.CreateGraphics
Гр.Clear(SystemColors.Control)
End Sub
Функция FromArgb
В VB существует 16 миллионов цветов с лишним (точнее - 16777216)! Их имен мы не знаем, да имен этих и не существует. Тем не менее, мы должны научиться управлять этими цветами. Чтобы навести порядок в этой массе цветов, VB предлагает функцию FromArgb. Она принадлежит структуре Color. Суть ее вот в чем.
Вспомним, что любую краску можно получить, смешав в определенной пропорции красную (Red), зеленую (Green) и синюю (Blue) краски. В VB каждой краски в смесь можно положить от 0 до 255 единиц. Функция FromArgb как раз и смешивает эти краски. Пусть мы хотим покрасить форму краской, в которую мы положили и смешали 200 единиц красной, 40 единиц зеленой и 250 единиц синей краски. Для этого пишем такой оператор:
Me.BackColor = Color.FromArgb(200, 40, 250)
Здесь мы использовали вариант функции FromArgb с тремя параметрами.
Примеры:
| FromArgb (255, 0, 0) | Красный цвет | ||
| FromArgb (0, 255, 0) | Зеленый цвет | ||
| FromArgb (0, 0, 255) | Синий цвет | ||
| FromArgb (255, 255, 0) | Желтый цвет |
Вы видите, что желтый цвет – это смесь красного с зеленым.
Чем меньше каждой краски мы положим, тем темнее будет цвет, чем больше – тем светлее:
| FromArgb (70, 90, 88) | Темный цвет (потому что числа маленькие) | ||
| FromArgb (210, 250, 202) | Светлый цвет (потому что числа большие) | ||
| FromArgb (0, 0, 0) | Черный цвет | ||
| FromArgb (255, 255, 255) | Белый цвет |
Если каждой краски положить поровну, получится серый цвет:
| FromArgb (90, 90, 90) | Темно-серый цвет | ||
| FromArgb (220, 220, 220) | Светло-серый цвет |
Вот программа, которая красит форму, рисует и заливает эллипс, как на Рис. 12.20:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim Цвет1, Цвет2 As Color
Цвет1 = Color.FromArgb(250, 230, 252)
Цвет2 = Color.FromArgb(220, 170, 240)
Me.BackColor = Цвет1 'Красим форму
Dim Перо As New Pen(Color.FromArgb(140, 120, 90), 40)
Dim Кисть As New SolidBrush(Цвет2)
Dim Гр As Graphics = Me.CreateGraphics
'Рисуем и заливаем эллипс:
Гр.DrawEllipse(Перо, 50, 30, 300, 200)
Гр.FillEllipse(Кисть, 50, 30, 300, 200)
End Sub
Здесь цвет формы, пера и кисти выбран при помощи функции FromArgb.

Рис. 12.20
Прозрачность
Функция FromArgb позволяет управлять прозрачностью цвета. Для этого используется ее вариант не с тремя, а с четырьмя параметрами. Второй, третий и четвертый параметры имеют привычный нам смысл количества красной, зеленой и синей краски. А вот первый параметр определяет прозрачность цвета. Если он равен 255, то цвет полностью непрозрачен, а если 0 – то полностью прозрачен (невидим).
Вот программа:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Dim Гр As Graphics = Me.CreateGraphics
Dim Цвет1, Цвет2, Цвет3, Цвет4 As Color
Цвет1 = Color.FromArgb(255, 50, 130, 152) 'Совершенно непрозрачный цвет
Цвет2 = Color.FromArgb(200, 50, 130, 152) 'Немного прозрачный цвет
Цвет3 = Color.FromArgb(150, 50, 130, 152) 'Более прозрачный цвет
Цвет4 = Color.FromArgb(100, 50, 130, 152) 'Еще более прозрачный цвет
Dim Перо As New Pen(Color.Black, 40) 'Черное перо для горизонтальной линии
Dim Перо1 As New Pen(Цвет1, 40)
Dim Перо2 As New Pen(Цвет2, 40)
Dim Перо3 As New Pen(Цвет3, 40)
Dim Перо4 As New Pen(Цвет4, 40)
Гр.DrawLine(Перо, 30, 100, 300, 100) 'Чертим горизонтальную линию
Гр.DrawLine(Перо1, 80, 60, 80, 200) 'Чертим левую вертикальную линию
Гр.DrawLine(Перо2, 130, 60, 130, 200) 'Чертим вторую слева вертикальную линию
Гр.DrawLine(Перо3, 180, 60, 180, 200) 'Чертим третью слева вертикальную линию
Гр.DrawLine(Перо4, 230, 60, 230, 200) 'Чертим правую вертикальную линию
End Sub
Результат ее работы вы видите на Рис. 12.21. Четыре вертикальные линии одного и того же цвета (50, 130, 152), но разной прозрачности нарисованы на фоне черной горизонтальной линии.

Рис. 12.21
Попробуйте несколько раз нажать на кнопку. Сможете объяснить результат?
Задание 71.
«Атака абстракциониста». На экране рисуются один за другим в быстром темпе залитые случайными цветами эллипсы или прямоугольники случайных размеров и местоположения. Получается очень ярко и живописно.
Задание 72.
Попробуйте из картинки в левой части Рис. 12.22 сделать картинку в правой.

Рис. 12.22
Указание: Для этого заполните пространство фотографии белыми концентрическими эллипсами с разной прозрачностью.
Задание 73.
Представьте себе куб, собранный из множества кубиков. Его высота – 256 кубиков, ширина и толщина – тоже по 256 кубиков. Получается ровно 16777216 кубиков – по числу цветов в VB. Каждый кубик покрашен в свой цвет. Цвета не повторяются. Система раскраски такая. Слева направо растет от 0 до 255 красная составляющая в цвете кубиков, сверху вниз – зеленая, от нас вдаль – синяя. Так что самый левый верхний ближний кубик получается абсолютно черным, а самый правый нижний дальний кубик – абсолютно белым. Сразу все кубики видеть мы, конечно, не можем, но мы можем делать срез куба в любом месте параллельно любой из его граней, в результате чего на срезе будем видеть квадрат, состоящий из 256*256 разноцветных квадратиков. Вот эту задачу среза я бы и хотел вам предложить. Программа просит пользователя выбрать один из трех основных цветов (это удобно сделать через меню) и его насыщенность (число от 0 до 255). Этим определяется место среза. Затем программа чертит на форме этот разноцветный срез. Конечно, квадратики получатся очень маленькими, но это ничего.
Указание: Используйте процедуру с двумя параметрами: выбранный пользователем цвет (один из трех) и его насыщенность.
Кстати, догадайтесь, из каких цветов составлена главная диагональ куба, проведенная между двумя упомянутыми мной кубиками.
Как узнать цвет точки на фотографии
GetPixel и свойства R, G, B. Задача: У вас есть фотография морского пляжа и вы хотите узнать, какого цвета зонтик вот у этой дамы слева.
Решение: Сначала вы заносите фото в компьютер и создаете объект Bitmap с этим фото. Затем, чтобы не было разницы между пикселями экрана и пикселями объекта, создаете еще один объект Bitmap с заданными размерами, как мы это делали в 12.3.4. Для наглядности нарисуйте это фото на форме. Затем вам нужно узнать координаты хоть какой-нибудь точки на зонтике. Я думаю, вы сами догадаетесь, как это сделать. Совершенно верно, методом «тыка». Здесь вам поможет маленькая окружность, которую вы рисуете на фото и координаты которой вы наугад подбираете так, чтобы попасть в зонтик. Как только вы увидите, что окружность оказалась прямо на зонтике, можно приступать к определению цвета в ее центре. Для этого вы используете функцию GetPixel объекта Bitmap и свойства R, G, B структуры Color:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Dim Гр As Graphics = Me.CreateGraphics
Dim Фото As New Bitmap("Spacescape.JPG")
Dim Фото1 As New Bitmap(Фото, 500, 400)
Dim Цвет_точки As Color
Dim x, y, Красный, Зеленый, Синий As Integer
Гр.DrawImage(Фото1, 0, 0)
x = InputBox("Введите горизонтальную координату точки")
y = InputBox("Введите вертикальную координату точки")
Гр.DrawEllipse(New Pen(Color.Yellow, 3), x - 5, y - 5, 10, 10) 'Рисуем маленькую окружность
Цвет_точки = Фото1.GetPixel(x, y) 'Определяем цвет заданной точки
Красный = Цвет_точки.R 'Количество красного
Зеленый = Цвет_точки.G 'Количество зеленого
Синий = Цвет_точки.B 'Количество синего
Debug.WriteLine(Цвет_точки)
Debug.WriteLine(Красный)
Debug.WriteLine(Зеленый)
Debug.WriteLine(Синий)
End Sub
Пояснения: Функция GetPixel
объекта Bitmap определяет цвет пикселя с заданными координатами в объекте Bitmap. Она возвращает значение типа Color. Свойства R, G, B структуры Color являются целыми числами в диапазоне 0-255 и имеют значение количества в ней красного, зеленого и синего цвета.
Программа выдаст результаты такого вида:
Color [A=255, R=209, G=183, B=98]
209
183
98
Вам придется удовлетвориться тем, что вы узнаете содержание основных цветов в нужной точке рисунка. Имейте в виду, что программа находит цвет одной точки на зонтике, а не всего зонтика. Если зонтик хоть чуть-чуть пестрый, разные точки на зонтике имеют разный цвет и результат будет дезориентирующим. Часто пестрый узор настолько мелок, что издалека он кажется вам однородным цветом, и лишь большое увеличение позволяет обнаружить пестроту.
У структуры Color имеется также свойство A, определяющее прозрачность в том смысле, о котором мы только что говорили.
SetPixel. Вы можете не только узнавать цвет пикселя, но и рисовать в любом месте объекта Bitmap пиксель нужного цвета. Для этого вы используете функцию SetPixel объекта Bitmap. Так, в условиях предыдущей задачи оператор
Фото1.SetPixel(x, y, Color.Blue)
поставил бы синюю точку в центр окружности. Только не забывайте после этого нарисовать содержимое объекта на экране, а то вы эту точку не увидите.
Вот фрагмент, рисующий на фотографии синий треугольник из пикселей:
x0 = InputBox("Введите горизонтальную координату точки")
y0 = InputBox("Введите вертикальную координату точки")
For y = y0 To y0 + 100
For x = x0 To y
Фото1.SetPixel(x, y, Color.Blue)
Next
Next
Гр.DrawImage(Фото1, 0, 0)
Задание 74.
Определить цвет заданной точки на картинке и выдать одно из трех сообщений:
Перспективы. Распознав цвет точки на картинке, вы сделали первый шаг к решению великой и не решенной до сих пор наукой задачи распознавания зрительных образов. Пожалуй, вы уже сейчас в силах написать программу, которая в большинстве случаев правильно отличит фотографию песчаной пустыни от фотографии океана. Но знаете ли вы, что не родился еще гений, способный написать программу, надежно отличающую хотя бы фотографию собаки от фотографии кошки? Потому что здесь дело не столько в цвете, сколько в форме. А это уже гораздо сложнее.
Решение задачи распознавания образов – ключ к осуществлению величайшей и самой дерзкой мечты ученых – созданию искусственного интеллекта, электронного разума, равного человеческому или превосходящего его.
Работа с цветом
До сих пор мы с вами пользовались цветами, которые VB предоставлял нам в структуре Color и в классах Pens и Brushes. Это полторы сотни цветов со своими именами. Но есть и другие цвета. О них мы сейчас и поговорим.
Преобразования системы координат
Я уже вам говорил, что начало системы координат для элемента управления находится в его верхнем левом углу, причем ось Y направлена вниз. У объекта класса Graphics есть методы, позволяющие изменять положение начала координат, направление осей, их масштаб, вращать систему координат.
Сдвиг, растяжение, сжатие, смена направления на обратное. Поставим задачу поменять компьютерную систему координат на привычную нам по школе, когда ось Y направлена вверх и начало координат лежит посредине тетрадного листа. Создадим для этого программу:
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
Dim Гр As Graphics = Me.CreateGraphics
Гр.ScaleTransform(1, -1) 'Меняем направление оси Y
Гр.TranslateTransform(300, -100) 'Смещаем начало координат
Гр.DrawLine(Pens.Black, 0, 0, 150, 70)
Гр.DrawString("Новое начало координат", Me.Font, Brushes.Black, 0, 0)
Гр.DrawString("Точка 150, 70", Me.Font, Brushes.Black, 150, 70)
End Sub
Пояснения: Для изменения направления оси Y оператор
Гр.ScaleTransform(1, -1) 'Меняем направление оси Y
использует процедуру ScaleTransform, которая позволяет растягивать или сжимать обе оси координат и менять их направление. Первый параметр относится к оси X, второй – к оси Y. Знак минус означает, что ось меняет направление на обратное. Если бы первым параметром мы вместо 1 написали 3, то все размеры по оси X увеличились бы в 3 раза, то есть любая картинка или фигура троекратно растянулась бы по горизонтали.
Для смещения начала координат из верхнего левого угла приблизительно в середину формы использован оператор
Гр.TranslateTransform(300, -100) 'Смещаем начало координат
Первый параметр процедуры TranslateTransform смещает начало координат направо на 300 пикселей, второй – вниз на 100 пикселей. Не перепутайте: именно вниз, а не вверх, так как теперь, после того, как ось Y стала смотреть вверх, отрицательные величины означают направление вниз, а не вверх.
Следующий оператор для иллюстрации рисует (см. Рис. 12.23 с перевернутым текстом) отрезок от начала координат (0, 0) до точки (150, 70), причем обратите внимание, что начало координат теперь лежит где-то посредине формы, а точка (150, 70) находится правее и выше начала координат, что привычно для математика, но не для компьютерщика.

Рис. 12.23
Все бы хорошо, но когда мы после такого преобразования системы координат рисуем объекты, имеющие «форму», такие, как текст или фотографии, они, естественно, получаются перевернутыми, что и видно на рисунке, где изображен текст "Новое начало координат" и "Точка 150, 70".
Упражнение. Выполните Задание 84 о построении графика функции y = (Sin x)/x (см. Рис. 12.8) с использованием преобразования системы координат. Указание: После того, как вы начертите оси координат и сделаете все надписи, сместите начало координат в точку пересечения осей координат, поменяйте направление оси ординат и растяните ось x примерно в 10, а ось y – в 100 раз. Тогда для вычерчивания графика вам не придется преобразовывать числа x и y в координаты на экране.
Вращение. Мы можем и вращать систему координат. Рассмотрим программу:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim Гр As Graphics = Me.CreateGraphics
Dim Фото As New Bitmap("Spacescape.JPG")
Dim П1 As New Rectangle(400, 0, 150, 130)
Гр.DrawEllipse(Pens.Black, 100, 0, 250, 40)
Гр.DrawImage(Фото, П1)
Гр.RotateTransform(20) 'Вращаем систему координат вокруг ее начала на 20 градусов
Гр.DrawEllipse(Pens.Black, 100, 0, 250, 40)
Гр.DrawImage(Фото, П1)
End Sub
Результат ее работы вы видите на Рис. 12.24.

Рис. 12.24
Пояснения: Четвертая и пятая строки процедуры
Гр.DrawEllipse(Pens.Black, 100, 0, 250, 40)
Гр.DrawImage(Фото, П1)
рисуют верхний эллипс и верхнюю фотографию. Следующая строка
Гр.RotateTransform(20) ' Вращаем систему координат вокруг ее начала на 20 градусов
незримо поворачивает систему координат вокруг ее начала (а оно находится в левом верхнем углу формы) по часовой стрелке на 20 градусов. Делает это процедура RotateTransform.
Седьмая и восьмая строки процедуры
Гр.DrawEllipse(Pens.Black, 100, 0, 250, 40)
Гр.DrawImage(Фото, П1)
являются копиями четвертой и пятой строк, но несмотря на это, рисуют эллипс и фотографию в другом месте, так как система координат уже повернута.
Вы можете вращать систему координат вокруг любой точки формы, предварительно сместив туда начало координат процедурой TranslateTransform.
Задание 75.
Заставьте картинку (например, монетку с Рис. 12.15) непрерывно вращаться. Указание: Сместите начало координат примерно в середину формы и нарисуйте монетку так, чтобы ее центр был как можно ближе к новому началу координат. Затем немного поверните систему координат и снова нарисуйте монетку. И так далее.
Встроенный графический редактор VB
Если вы хотите с удобством нарисовать иконку или другую картинку, чтобы к тому же не приходилось специально заботиться о прозрачном цвете, используйте встроенный графический редактор VB (Image Editor).
Пусть вы хотите нарисовать иконку в виде флажка. Ваши действия: В режиме проектирования выберите Project ® Add New Item ® в возникшем окне Add New Item выбираете Icon File, даете ему имя, затем ® Open. Перед вами в окне проекта возникает окно графического редактора (Рис. 12.25)

Рис. 12.25
В окне Solution Explorer вы видите, что в папку проекта добавилась иконка – файл с расширением ico. В середине окна вы видите серое окошко размером 32х32 пикселя, в котором вы и будете рисовать флажок. Чуть левее вы видите тот же флажок в уменьшенном виде. Для рисования вам предоставляется панель инструментов со стандартным набором инструментов графического редактора и палитра цветов (и то и другое вы видите на рисунке). Если вам не хватает этих цветов, сделайте двойной щелчок по любому цвету и выберите нужный. Левый верхний цвет в палитре – прозрачный.
Зайдя в меню Image, вы сможете преобразовывать рисунок и выбирать размер в пикселях нового рисунка.
Вы можете нарисовать и картинку в формате BMP, выбрав в окне Add New Item пункт Bitmap File,. Вы можете нарисовать курсор, выбрав в окне Add New Item пункт Cursor File,
Чтобы редактировать в графическом редакторе нарисованную ранее картинку, дважды щелкните по ней в окне Solution Explorer.
Материализация привидений. Добудьте где-нибудь файл с фотографией мрачного замка. Нарисуйте в Image Editor привидение на прозрачном фоне. Напишите программу, по которой привидение постепенно возникает и растворяется на фоне замка. Это непросто и не обязательно.
Ненадолго расстаемся с графикой. Некоторые сложные фигуры требуют для своего рисования знания массивов, поэтому я перенес дальнейшее изучение графики в Глава 17. .
Переменные и литералы типа DateTime
Когда вы пишете в окне кода программу, в ней встречаются литералы: числа, строки, а теперь вы должны научиться писать в программе литерал даты и времени суток. Чтобы VB понял, что перед ним число, вы просто пишете цифры, и он понимает. Чтобы VB понял, что перед ним строка, вы пишете ряд букв и берете его в двойные кавычки, и VB понимает, что это строка. Чтобы VB понял, что перед ним дата или время суток, вы правильно записываете дату и время и заключаете их между значками #, и он понимает, что это литерал типа DateTime. Вот пример правильной записи: #2/16/2002#. Перед вами не что иное, как 16 февраля 2002 года. Как правильно записывать дату и время в других случаях, вы поймете из других примеров:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Debug.WriteLine(#6/25/2001#) '25 июня 2001 года
Debug.WriteLine(#2:22:57 PM#) '2 часа 22 минуты 57 секунд после полудня (PM)
Debug.WriteLine(#2/28/1998 10:45:00 PM#) '10 часов 45 минут вечера 28 февраля 1998 года
Dim D, T, DT As DateTime
D = #12/25/2044#
T = #2:00:32 AM# '2 часа 00 минут 32 секунды до полудня (AM)
DT = #1/15/2156 11:59:42 PM#
Debug.WriteLine(D)
Debug.WriteLine(T)
Debug.WriteLine(DT)
End Sub
Эта процедура напечатает такие результаты:
25.06.2001 0:00:00
01.01.0001 14:22:57
28.02.1998 22:45:00
25.12.2044 0:00:00
01.01.0001 2:00:32
15.01.2156 23:59:42
Пояснения: Как видите, в окне кода мы обязаны писать дату и время по-американски, то есть месяц писать раньше числа и разделять все это косыми чертами, а в обозначении времени суток VB заставляет вас указывать, до или после полудня было дело. А вот результаты по этим непривычным данным печатаются все равно по-нашему, вернее, так, как настроена Windows (а у большинства она настроена на Россию).
Вы можете задавать переменную типа DateTime и в виде строки.
D = "5/6/2003 10:45:12 PM"
В этом случае, если дата и время в строке записаны правильно, VB автоматически преобразует строку в тип DateTime. А вот если вы захотите задавать дату или время при помощи оператора
D = InputBox("Введите дату")
или из текстового поля, то вводить их по-американски нельзя и значки # тоже нельзя ставить.
Переменная типа DateTime включает в себя всегда и дату, и время суток. Но при задании значения такой переменной вы не обязаны задавать и то и другое одновременно. Если вы не задали время суток, то, как видите из результатов, считается, что временем суток данной переменной является 0:00:00, то есть полночь – начальный момент суток. Если вы не задали дату, то считается, что датой в данной переменной является 01.01.0001, то есть первый день нашей эры.
Что делать, если я хочу в результатах видеть только дату, а не время, или наоборот – только время, а не дату? Об этом чуть позже.
Кроме типа DateTime со временем в VB имеет дело тип TimeSpan. Его сфера компетенции – не столько конкретные даты и моменты времени, сколько протяженность промежутков времени. Мы не будем его рассматривать.
Свойства и методы структуры DateTime
Чтобы понять, что от типа DateTime есть какая-то польза, решим пару задач.
Задача 1. 29 мая 2004 года мне выдали задание и сказали, чтобы я уложился в 50 дней. Какого числа наступит крайний срок сдачи задания?
Программа:
Private Sub Button6_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button6.Click
Dim Сегодня, Крайний_срок As DateTime
Сегодня = #5/29/2004#
Крайний_срок = Сегодня.AddDays(50)
MsgBox(Крайний_срок)
End Sub
В окне MsgBox мы видим результат: 18.07.2004. То есть 18 июля 2004 года – крайний срок сдачи задания. Здесь я использовал метод AddDays («Добавить дни») структуры DateTime.
Ту же самую программу можно записать короче:
Dim Сегодня As DateTime = #5/29/2004#
MsgBox(Сегодня.AddDays(50))
И еще короче:
MsgBox(#5/29/2004#.AddDays(50))
Задача 2. В отпуск вы отправляетесь 1 июля 2005 года. Какой это день недели?
Программа:
Dim Начало_отпуска As DateTime = #7/1/2005#
MsgBox(Начало_отпуска.DayOfWeek)
Здесь я использовал метод DayOfWeek («День недели») структуры DateTime. В окне MsgBox мы видим результат: 5. Это пятый день недели – пятница. Кстати, воскресенье в VB – не 7-й день, а нулевой. Это потому, что у американцев неделя начинается с воскресенья, а не с понедельника, как у нас.
Перечень свойств и методов структуры DateTime. Не всех, но популярных. С пояснениями.
Пусть мы задали переменную:
Dim D As DateTime = #2/14/2005 11:41:39 PM#
Ее значение мы будем использовать в этом подразделе, как исходный материал.
Оператор
Debug.WriteLine(D.Date)
напечатает значение
14.02.2005 0:00:00
Мы видим, что свойство Date возвращает значение переменной с обнуленной составляющей времени суток, оставив только дату. Тип значения – Date. Заносим вышесказанное в таблицу:
| Свойство или метод | Значение | Тип значения | Пояснения | ||||
| Date | 14.02.2005 0:00:00 | Date | Функция обнуляет в значении переменной составляющую времени суток, оставив только дату |
|
ToUniversalTime |
14.02.2005 20:41:39 |
DateTime |
Если вы имеете в виду, что в переменной D задано время вашего пояса, то эта функция показывает время GMT. |
|
ToLocalTime |
15.02.2005 2:41:39 |
DateTime |
Наоборот: Если вы имеете в виду, что в переменной D задано время GMT, то эта функция показывает, сколько время в этот момент было в вашем поясе |
Следующие методы преобразуют значение из типа DateTime в тип String. После этого его удобно просматривать и к нему можно применять методы работы со строками.
|
ToString |
14.02.2005 23:41:39 |
String |
Просто преобразование без изменений |
|
ToLongDateString |
14 Февраль 2005 г. |
String |
Выделяется дата |
|
ToShortDateString |
14.02.2005 |
String |
Выделяется дата |
|
ToLongTimeString |
23:41:39 |
String |
Выделяется время |
|
ToShortTimeString |
23:41 |
String |
Выделяется время |
Следующие методы и свойства принадлежат структуре DateTime и для их использования не нужно создавать переменную типа DateTime. То есть, можно писать, например, просто
Debug.WriteLine(DateTime.Now)
|
DaysInMonth(1996, 2) |
29 |
Integer |
Сколько дней во 2 месяце 1996 года |
|
IsLeapYear(2004) |
True |
Boolean |
Правда ли, что 2004 год – високосный. |
|
Now |
23.08.2003 17:42:10 |
Date |
Дата и время на момент выполнения этого оператора, то есть текущие показания часов вашего компьютера |
|
Today |
23.08.2003 0:00:00 |
Date |
То же самое, только без времени суток |
Свойства и методы модуля DateAndTime
Полезные средства для работы с датами и временем унаследованы от Visual Basic 6.0. Вы можете воспользоваться ими, как свойствами и методами модуля DateAndTime пространства имен Microsoft.VisualBasic[‡]. Не путайте модуль DateAndTime со структурой DateTime. Любая переменная или литерал типа даты и времени является экземпляром структуры DateTime, и чтобы воспользоваться ее свойством или методом, вы просто пишете после имени переменной или литерала точку и за ней название свойства или метода, как мы делали это в предыдущем подразделе. Модуль же DateAndTime (как и все модули) не имеет права поставлять нам свои экземпляры для хранения переменных и литералов. Он поставляет свои свойства и методы в виде функций, параметрами которых и будут наши переменные и литералы. Вот пример:
Пусть D1 = #2/14/2009 4:45:07 PM#, D2 = #2/16/2009 11:32:43 AM#. Тогда
| Свойство или метод | Значение | Тип значения | Пояснения | ||||
| DateDiff("h", D1, D2) | 42 | Long | Количество часов, прошедших с момента D1 до момента D2 |
На то, что нас интересуют именно часы, указывает параметр "h". Если вас в этой или других функциях интересуют минуты или другие единицы времени, воспользуйтесь следующей таблицей:
Значения строкового параметра для функций работы с датами:
| Строковый параметр | Смысл | ||
| yyyy | Год | ||
| q | Квартал в году | ||
| m | Номер месяца в году | ||
| y | Номер дня в году | ||
| d | Номер дня в месяце | ||
| w | Номер дня в неделе (с 1 по 7, а не с 0 по 6). Неделя начинается с воскресенья | ||
| ww | Номер недели в году | ||
| h | Час в сутках | ||
| n | Минута в часе | ||
| s | Секунда в минуте |
Функция DatePart работает аналогично ряду свойств структуры DateTime .
Пусть D = # 2/14/2009 4:45:07 PM # (это суббота). Тогда
| DatePart("m", D) | 2 | Integer | Функция выделяет из даты номер месяца в году | ||||
| DatePart("w", D) | 7 | Integer | Поскольку неделя начинается с воскресенья, то воскресенье имеет №1, а суббота – №7. | ||||
| DatePart("w", D, vbMonday) | 6 | Integer | А здесь мы попросили функцию считать первым днем недели понедельник (vbMonday) и поэтому результат она выдала привычный для нас – 6-й день - суббота |
Форматирование даты и времени
В 5.4.7 вы уже сталкивались с форматированием чисел. Форматирование нужно для того, чтобы представить информацию в удобном для пользователя виде. Осуществляет форматирование функция Format. У нее два аргумента (параметра): первый – что форматировать, второй – как форматировать. Функция возвращает значение типа String.
Готовые форматы. Рассмотрим программу:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim t As DateTime = #8/10/2004 4:42:39 PM#
WriteLine(Format(t, "General Date")) '10.08.2004 16:42:39
WriteLine(Format(t, "Long Date")) '10 Август 2004 г.
WriteLine(Format(t, "Short Date")) '10.08.2004
WriteLine(Format(t, "Long Time")) '16:42:39
WriteLine(Format(t, "Short Time")) '16:42
WriteLine(Format(t, "f")) '10 Август 2004 г. 16:42
WriteLine(Format(t, "F")) '10 Август 2004 г. 16:42:39
WriteLine(Format(t, "g")) '10.08.2004 16:42
WriteLine(Format(t, "y")) 'Август 2004 г.
End Sub
Пояснения: Здесь первый аргумент – t. Второй – строка. Содержание строки определяет вид результата. То, что печатают операторы программы, написано в комментариях.
Форматы, определяемые программистом. Мы рассмотрели здесь наиболее популярные из готовых форматов, которые нам предоставляет VB. Но форматы можно конструировать и самому. Рассмотрим программу:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Dim t As DateTime = #8/5/2004 7:02:09 PM#
WriteLine(Format(t, "fff")) '000 - доли секунды (тысячные, потому что 3 буквы f)
WriteLine(Format(t, "%s")) '9 - секунды
WriteLine(Format(t, "ss")) '09 - секунды
WriteLine(Format(t, "%m")) '2 - минуты
WriteLine(Format(t, "mm")) '02 - минуты
WriteLine(Format(t, "%h")) '7 - часы до и после полудня
WriteLine(Format(t, "hh")) '07 - часы до и после полудня
WriteLine(Format(t, "%H")) '19 - часы
WriteLine(Format(#3:02:09 AM#, "HH")) '03 - часы
WriteLine(Format(t, "%d")) '5 - число месяца
WriteLine(Format(t, "dd")) '05 - число месяца
WriteLine(Format(t, "ddd")) 'Чт - день недели
WriteLine(Format(t, "dddd")) 'четверг - день недели
WriteLine(Format(t, "%M")) '8 - месяц
WriteLine(Format(t, "MM")) '08 - месяц
WriteLine(Format(t, "MMM")) 'авг - месяц
WriteLine(Format(t, "MMMM")) 'Август - месяц
WriteLine(Format(t, "%y")) '4 - год
WriteLine(Format(t, "yy")) '04 - год
WriteLine(Format(t, "yyyy")) '2004 - год
End Sub
Пояснения: В комментариях вы видите результаты работы операторов программы с пояснениями. Знак процента употребляется тогда, когда в кавычках стоит один единственный символ форматирования, чтобы не спутать его с символом готового формата.
Мы рассмотрели здесь, как выделять из переменной нужные нам элементарные части даты или времени. Теперь посмотрим, как их комбинировать, чтобы получить вразумительную строку:
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
Dim t As DateTime = #8/5/2004 7:02:09 PM#
WriteLine(Format(t, "dd-MM-yy")) '05-08-04
WriteLine(Format(t, "d MMM yyyy")) '5 авг 2004
WriteLine(Format(t, "Настал yyyy год.")) 'Настал 2004 год.
WriteLine(Format(t, "d/MM/yy")) '5.08.04
WriteLine(Format(t, "HH:m:ss")) '19:2:09
WriteLine(Format(t, "HH,m.ss")) '19,2.09
WriteLine(Format(t, "HH часов d MMM")) '19 часов 5 авг
WriteLine(Format(t, "Было %h часов вечера и m минуты")) 'Было 7 часов вечера и 2 минуты
End Sub
Пояснения: Если мы при разделении элементов даты пользуемся косой чертой, то в результате разделителем становится символ, на который настроена Windows (в России – точка, в США – косая черта). То же самое можно сказать о двоеточии для разделения элементов времени суток. Но мы можем для разделения сравнительно свободно пользоваться и другими символами: черточками, пробелами, точками, запятыми, буквами русского алфавита и др.
Задание 76.
Напишите программу, которая, ничего у вас не спрашивая, печатает, какое число будет через 52 недели.
Задание 77.
Напишите программу, которая, спросив у вас дату рождения и не спрашивая, какое сегодня число, печатает, сколько секунд вы живете на белом свете.
Задание 78.
Напишите программу, которая, спросив у вас дату рождения и не спрашивая, какое сегодня число и был ли у вас в этом году день рождения, печатает, сколько дней вам осталось до следующего дня рождения.
Задание 79.
Я знаю, что високосных годов ученым не хватает. Поэтому, не то где-то раз в много лет вклинивается лишний високосный год, не то иногда где-то в каком-то месяце бывает лишний день. Не знаю. Может быть VB подскажет?
Тип данных DateTime (Date)
Вы пока знакомы с числовыми, строковым и логическим типами данных. Тип даты и времени суток – DateTime тоже в каком-то смысле числовой, но специфический. Если, например, в нем к 0:40 прибавить 0:40, то получится, сами понимаете, не 0:80, а 1:20.
Тип DateTime представляет структуру
DateTime пространства имен System. Вы можете пользоваться или им, или эквивалентным ему типом Date, который достался нам в наследство от Visual Basic 6.0. Я не буду делать между ними различия. Во всяком случае VB распространяет на данные типа Date все свойства и методы структуры DateTime.
Таймер
Суть работы таймера. Создайте новый проект. Возьмите из Toolbox элемент управления Timer и поместите его на форму. Однако, подобно элементу управления Меню, таймер расположится не на форме, а под ней, на дизайнере компонентов. Установите его свойство Enabeled в True, а свойство Interval равным 10000. Сделайте двойной щелчок по таймеру. В появившуюся заготовку процедуры впишите одну строчку:
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
Debug.WriteLine("Процедура сработала")
End Sub
Запустите проект. Подождите немного. Через 10 секунд в окне Output появится строчка "Процедура сработала". Еще через 10 секунд появится такая же строчка, через 10 секунд еще одна, и так далее.
Если бы мы установили Interval равным 5000, то строчки появлялись бы каждые 5 с, а если равным 500, то – каждые 0,5 с. Получается, что свойство Interval задает промежуток времени в миллисекундах (мс – тысячных долях секунды).
Вывод: Таймер Timer1 – объект, вся работа которого заключается в том, чтобы через каждые Interval миллисекунд создавать событие (импульс), которое запускает процедуру Timer1_Tick.
Цикл без цикла. В процедуре Timer1_Tick мы можем написать все, что угодно. Например:
Dim i As Long = 1
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
Debug.WriteLine("Процедура сработала " & i & "-й раз")
i = i + 1
End Sub
Установите Interval в 1000. Запустите проект. Вы увидите через каждую секунду возникающие строчки:
Процедура сработала 1-й раз
Процедура сработала 2-й раз
Процедура сработала 3-й раз
Процедура сработала 4-й раз
Мы видим, что у нас заработал цикл, несмотря на то, что операторов цикла мы не писали! Чуть позже вы догадаетесь, как вовремя делать выход из цикла.
Свойство таймера Enabled. Установим свойство таймера Enabled (по-русски – «в рабочем состоянии») в False. Запустим проект. Никаких строк не появляется. Значит таймер выдает импульсы только тогда, когда свойство Enabled установлено в True.
Добавим в проект две кнопки и к ним две процедуры:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Timer1.Enabled = True
End Sub
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Timer1.Enabled = False
End Sub
Запустите проект. Вы увидите, что новые строчки появляются только после щелчка по первой кнопке, а после щелчка по второй прекращают появляться.
«Холостые» импульсы. Что будет, если процедура Timer1_Tick еще не успела закончить работу, а от таймера уже пришел новый импульс? Проверим. Установим Interval равным 100, то есть импульсы от таймера должны посылаться примерно 10 раз в секунду. Напишем процедуру Timer1_Tick, которая очень долго работает:
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
Debug.WriteLine("Процедура таймера начала работу")
For i = 1 To 200000000 : Next
Debug.WriteLine("Процедура таймера закончила работу")
End Sub
Здесь долго работает пустой цикл
For i = 1 To 200000000 : Next
Несмотря на то, что делать ему нечего (тело у цикла отсутствует), у него много времени уходит на то, чтобы просто досчитать до 200 миллионов. На моем компьютере это занимает секунд пять, на вашем, наверное, по-другому. Это неважно, а важно то, что за эти пять секунд в процедуру врежется порядка 50 импульсов от таймера. И все как об стену горох – процедура на них не реагирует. Пока не завершит свою работу, как положено, оператором End Sub. После этого первый же следующий импульс, которому повезло, снова ее запускает. Вы увидите, что на экране не спеша, где-то раз в пять секунд появляется очередная пара строчек.
Точность таймера. Проведя эксперименты на компьютере Celeron 400, я обнаружил, что таймер выдает импульсы от интервала 10 мс и выше. То есть минимальное время между импульсами = 10 мс. Задавать интервал меньше 10 бесполезно. Однако, это все-таки гораздо лучше, чем в VB 6.0, где это время равнялось 1/18 доле секунды.
Задание 80.
Запрограммируйте с помощью таймера печать чисел от 100 до 110 через 1 секунду.
Несколько таймеров. Таймеров в проекте может быть несколько. Все они работают независимо друг от друга и каждый имеет свою собственную процедуру, которая реагирует только на его импульсы.
Поместите в проект два таймера. Интервал первого задайте равным 1000, второго – 3000. Вот программа:
Dim i As Long = 1
Dim j As Long = 1
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
Debug.WriteLine("Первый таймер сработал " & i & "-й раз")
i = i + 1
End Sub
Private Sub Timer2_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer2.Tick
Debug.WriteLine("Второй таймер сработал " & j & "-й раз")
j = j + 1
End Sub
А вот результаты:
Первый таймер сработал 1-й раз
Второй таймер сработал 1-й раз
Первый таймер сработал 2-й раз
Первый таймер сработал 3-й раз
Первый таймер сработал 4-й раз
Второй таймер сработал 2-й раз
Первый таймер сработал 5-й раз
Первый таймер сработал 6-й раз
Первый таймер сработал 7-й раз
Второй таймер сработал 3-й раз
Как видите, второй таймер срабатывает в 3 раза реже первого.
Перечисления
С перечислениями мы с вами сталкивались не раз. Каждый раз под перечислением понимался список значений какого-нибудь свойства или параметра, или чего-нибудь другого. Например, в 12.3.6мы использовали перечисление RotateFlipType в качестве параметра метода. У этого перечисления 16 значений, из которых можно выбирать любое.
Все перечисления, с которыми мы сталкивались, были созданы заранее профессиональными программистами, включены ими в библиотеку классов .NET Framework и достались нам готовыми к употреблению. Нам осталось только с удобством выбирать значения из всплывающего списка. Однако в реальном программировании часто встречаются ситуации, когда необходимо иметь перечисление, еще никем не созданное.
Пример 1. Пусть вам хотелось бы с удобством пользоваться списком своих любимых футбольных команд:
Real, MU, Вперед, Milan
VB позволяет вам самому создать такое (и вообще, какое угодно) перечисление и пользоваться им. Вот что нужно для этого сделать.
Сначала в верхней части окна кода, вне процедур, пишем такой текст:
Enum Мои_команды
Real
MU
Вперед
Milan
End Enum
Это не процедура, хоть и похожа. Слово Enum означает, что это перечисление. Мы придумали ему имя Мои_команды. Слова End Enum означают конец перечисления.
Это и не переменная. Вы не можете написать
Мои_команды = Вперед
То, что мы создали, это новый тип. Тип Мои_команды. Впервые мы не просто пользуемся типом, а создаем тип.
Чтобы извлечь выгоду из этого типа и воспользоваться перечислением, мы должны создать переменную данного типа. Делается это обычным образом:
Dim Команда As Мои_команды
Переменная Команда имеет право принимать только одно из четырех значений, перечисленных в объявлении типа.
Пользоваться этим перечислением так же удобно, как и остальными. Например, когда вы наберете текст
Команда =
VB любезно распахнет перед вами список из всех четырех значений перечисления. Правда, перед каждым значением он поставит точку и имя типа, что кажется несколько громоздким. Получится вот что:
Команда = Мои_команды.Milan
Теперь вы можете как угодно работать с переменной. Например, так:
If Команда = Мои_команды.Real Then MsgBox("Это не команда, а сборная мира!")
Пример 2. Все значения перечисления пронумерованы, начиная с нуля. Поэтому при работе с ними удобно применять операторы цикла. Проиллюстрирую работу с переменными перечислимого типа на примере перечисления дней недели:
Public Class Form1
Inherits System.Windows.Forms.Form
Windows Form Designer generated code
Enum Дни
понедельник
вторник
среда
четверг
пятница
суббота
воскресенье
End Enum
Dim День As Дни
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
День = 0
Debug.WriteLine(День) 'понедельник
День = 1
Debug.WriteLine(День) 'вторник
День = Дни.понедельник
Debug.WriteLine(День) 'понедельник
День = День + 2
Debug.WriteLine(День) 'среда
День = Дни.среда
Do Until День = Дни.воскресенье
День = День + 1
Debug.WriteLine(День) 'четверг пятница суббота воскресенье
Loop
For День = Дни.вторник To Дни.четверг
Debug.WriteLine(День) 'вторник среда четверг
Next
For День = Дни.воскресенье To Дни.понедельник Step -2
Debug.WriteLine(День) 'воскресенье пятница среда понедельник
Next
End Sub
End Class
Пояснения: То, что напечатают операторы этой программы, вы видите в комментариях. Поскольку все значения перечисления пронумерованы, то, например, все равно, как написать:
День = 1
или
День = Дни.вторник
Пример 3. В этом примере вы увидите, как можно анализировать с пользой для дела переменные перечислимого типа. Предполагается, что, как в предыдущем примере, определены тип Дни и переменная День.
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
День = InputBox("Введите номер дня недели")
Select Case День - 1 ' Чтобы понедельник был не нулевым, а первым
Case Дни.понедельник To Дни.среда
MsgBox("Начало недели")
Case Дни.четверг, Дни.пятница
MsgBox("Середина недели")
Case Дни.суббота, Дни.воскресенье
MsgBox("Отдых")
Case Is > Дни.воскресенье, Is < Дни.понедельник
MsgBox("Таких дней не бывает")
End Select
End Sub
Пояснения: Обратите внимание, что вводить день недели привычным образом буквами просто так не удастся, так как информация, вводимая в текстовое поле окна InputBox имеет тип String, который автоматически не преобразуется в перечислимый. Поэтому я выбрал ввод номера дня недели.
Настоящую пользу из собственных перечислений вы начнете извлекать в проекте «Будильник-секундомер» и когда изучите массивы.
Рамка (GroupBox)
Познакомимся с элементом управления GroupBox, который понадобится нам в проекте «Будильник-секундомер». Рамки являются удобным средством объединения элементов управления по смыслу. Пример вы видите на Рис. 13.1.

Рис. 13.1
Поместите GroupBox на форму и придайте ему довольно большие размеры. Получилась пустая рамка с заголовком GroupBox1. Затем возьмите из Toolbox и поместите внутрь этой рамки несколько разных элементов управления. А теперь передвиньте рамку по форме, ухватившись мышкой за ее границу. Вы видите, что все объекты внутри рамки передвинулись вместе с ней. Вот в этом и состоит удобство рамки: объекты, помещенные вами внутрь нее, она считает «своими» и таскает по форме вслед за собой. Посмотрите на значения свойств X и Y любого объекта внутри рамки. Теперь это координаты не относительно формы, а относительно рамки.
В режиме проектирования или в коде вы можете изменять значения свойств рамки X и Y. Объекты, объединенные в рамке, будут при этом перемещаться вместе с ней. Вы можете изменять значения свойств рамки Visible, Enabled,. При этом точно так же будут меняться значения этих свойств у всех объектов, объединенных в рамке. Это полезно, например, тогда, когда у вас на форме слишком много объектов и все они не умещаются на ней. Выход есть, если не все они нужны одновременно. Просто разделите их между наложенными друг на друга рамками и в каждый момент времени делайте видимой только одну из них. Замечу, что для этого можно пользоваться и вкладками.
Поменяв свойства рамки BackColor, ForeColor, Font, вы тем самым меняете эти свойства и у входящих в нее объектов (кроме тех, где эти свойства уже были изменены). Это же касается и формы, которая тоже является контейнером. Поэкспериментируйте.
Вы можете мышкой переместить в рамку уже размещенный на форме объект. Оказавшись внутри границ рамки, он становится «своим» и разъезжает вместе с рамкой. А вот когда вы попытаетесь обнять новой или старой рамкой уже существующий объект, у вас ничего не получится. Оказавшись внутри границ рамки, он останется «чужим». Убедитесь в этом, сдвинув рамку в сторону. Все уехали, объект остался.
Вставьте внутрь одной рамки другую, а в нее – несколько объектов. Подвигайте внешнюю рамку. Внутренняя рамка вместе со всеми своими объектами движется за ней. Все логично. Подвигайте теперь внутреннюю рамку внутри внешней. Вы видите, что внутренняя рамка ведет себя, как и следовало ожидать: для внешней рамки она «подчиненный», а для объектов внутри себя – «начальник».
Панель (Panel)
Панель во многом похожа на рамку. Интересующее нас отличие состоит в том, что если элементы управления не умещаются на панели, то ей можно придать полосы прокрутки, благодаря которым любой ее элемент управления может быть помещен в поле зрения (Рис. 13.2).

Рис. 13.2
Проделайте эксперимент. Поместите на форме большую панель. Разместите по всей ее поверхности несколько элементов управления. Затем уменьшите размеры панели. Многие элементы управления уйдут из поля зрения. Придайте панели полосы прокрутки, установив в True ее свойство AutoScroll. Покрутите полосы. Это очень экономит пространство на форме.
Вкладка (TabControl)
Вкладки вы видели не раз во многих приложениях Windows. Например, в VB вы можете видеть их в Tools ® Customize. Пример вкладки вы видите и на Рис. 13.3.

Рис. 13.3
Вкладка на этом рисунке состоит из трех страниц (TabPage). (В других подразделах книги я буду называть страницы закладками, так как этот термин более употребителен.) На каждой странице вы можете помещать любые элементы управления: кнопки, метки и пр. Я поместил на странице пейзажей несколько графических полей (PictureBox) с пейзажами. Если вы щелкнете по слову «Портреты», увидите страницу портретов. Страницы можно снабдить полосами прокрутки, благодаря чему на любой странице можно поместить огромное число элементов управления.
Сразу же бросается в глаза основное преимущество вкладки – она радикально экономит место на форме.
Создадим проект с вкладкой. Поместите на форму элемент управления TabControl. Обратите внимание, что перетаскивать вкладку мышкой можно только, ухватившись за ее бордюр. Зайдите в окне свойств в ее свойство TabPages и в возникшем окне Редактора вкладок щелкните несколько раз по кнопке Add (на Рис. 13.4 было щелкнуто 3 раза).

Рис. 13.4
В результате вкладка на форме приобретет такой вид, как на Рис. 13.5.

Рис. 13.5
В левой части Редактора вкладок вы видите список страниц вкладки. В правой части Редактора вкладок вы видите свойства той страницы вкладки, которая выделена. Свойства довольно обычные. Вы можете поменять имя, текст (название) страницы, ее цвет, бордюр и пр. При свойстве AutoScroll, установленном в True, на странице при необходимости будут возникать полосы прокрутки.
Пощелкав по названию страницы, выдвиньте ее на передний план. Щелкнув затем по пространству страницы, вы добьетесь, что выделена будет уже не вся вкладка, а только эта одна страница. Соответственно в окне свойств среды VB появятся свойства не вкладки, а этой страницы.
Помещайте теперь на эту страницу любые элементы управления. Затем выделяйте другую страницу и помещайте элементы управления на нее. Всеми этими элементами управления можно пользоваться обычным образом, как будто бы они расположены на форме.
Рамка (GroupBox), панель (Panel) и вкладка (TabControl)
Эти три элемента управления относятся к контейнерам – элементам управления, которые могут содержать на своей поверхности другие элементы управления. К контейнерам относится и форма.
Постановка задачи
Создать Будильник-секундомер следующего вида (см. Рис. 13.6).

Рис. 13.6
Если вы ставите задачу для серьезного проекта, то прежде всего вам нужно с максимальной подробностью описать, что должен делать ваш проект с точки зрения пользователя (а не программиста!). я начну с того же, но ввиду очевидности того, что изображено на рисунке, я поясню только то, чего не видно или может быть неправильно понято:
Недоработки проекта
Будильник готов. Вы можете установить на нем время звонка и свернуть окно, чтобы он не мешал работать. Продолжайте делать на компьютере другие дела. В свернутом виде будильник не спит. В нужный момент вы услышите звонок, и будильник, чтобы привлечь ваше внимание, развернется на весь экран.
Проект работает на первый взгляд нормально, я многократно проверял и часы, и будильник, и секундомер. Но несмотря на это, в нем вполне возможны ошибки и недоработки. Вот те ошибки и недоработки, которые я сумел заметить, но не стал исправлять, предоставив это вам:
Таймер и моделирование
Теперь о роли таймера в компьютерном конструировании реальных механизмов. Заглянем-ка еще раз в процедуру таймера часов:
Private Sub Таймер_часов_Tick(ByVal sender As Object, ByVal e As EventArgs) Handles Таймер_часов.Tick
Dim Время_на_часах As String
Время_на_часах = Format(Now, "HH:mm:ss")
Циферблат_часов.Text = Время_на_часах
If Время_на_часах = "00:00:00" Then Смена_даты_и_дня_недели()
If Будильник_установлен And Время_на_часах = Циферблат_будильника.Text Then Включить_звонок()
End Sub
Вы видите, что эта процедура является главным мотором, приводящим в движение весь механизм часов с будильником, главным штабом, планирующим всю их работу. Таймер, как сердце, несколько раз в секунду посылает импульсы, каждый из которых заставляет выполнится эту процедуру. А что за операторы составляют тело процедуры? Это как раз главные операторы проекта, которые, выполняясь, в свою очередь управляют выполнением вспомогательных процедур Смена_даты_и_дня_недели и Включить_звонок. За один импульс таймера механизм совершает весь цикл своего функционирования с начала до конца и выполняет всю работу, которую положено выполнить: время на часах обновлено; если подошел момент, то именно на этом импульсе заменены дата и день недели; и если подошел момент, то именно на этом же импульсе отдан приказ на включение звонка будильника. На следующем импульсе все повторяется. И так беспрерывно, бесконечно.
Этот принцип применения таймера подойдет для компьютерного конструирования бесчисленного множества других механизмов и аппаратов: холодильника, синтезатора, автоматической метеостанции, беспилотного космического аппарата и пр. и пр. Нужно только знать принцип их работы – и вы сможете создать проект, «вживую» показывающий их деятельность. Это называется моделированием, а сама программа называется моделью.
Вот мы с вами создали модель будильника, которая настолько хороша, что работает не хуже оригинала. Однако, мы не копировали работу реальных
шестеренок из бабушкиных ходиков, нам это было не нужно. Таким образом, надо понимать, что мы создали модель только внешнего, а не внутреннего поведения механического будильника. Внутреннее поведение у нашего будильника совсем другое, мы попросту брали готовое время у Windows, и все. Чтобы смоделировать внутреннюю работу реальных механических часов с учетом движения и зацепления каждой шестеренки, нужен гораздо более сложный проект. Спрашивается, кому он нужен? Наверное, тем, кто конструирует часы для любителей старины.
Моделировать можно и работу механизмов, управляемых человеком: автомобиля, корабля, самолета и др. В этом случае роль человека будете исполнять вы, щелкая мышкой по созданным вами кнопкам и рычагам управления. Кстати, вы только что создали такой проект. Ведь будильник – это тоже механизм, работающий под управлением человека, щелкающего по кнопкам будильника.
Вы можете пойти дальше и смоделировать на компьютере поведение человека. Тогда вам не придется самому нажимать на кнопки и рычаги, это будет делать человек на экране.
Задание 81.
«Время в разных странах».Это задание – на любителя. Его делать не нужно, если вы собираетесь выполнить следующее Задание 97 «Шахматные часы».
Усовершенствуйте часы. Пусть они по вашему желанию показывают время в любом из нескольких десятков городов мира. Пользователю достаточно ввести название города в текстовое поле. Для программирования вам нужно самим знать поясное время в других городах. Для этого можете сделать двойной щелчок мышкой по индикатору времени на панели задач Windows и в открывшемся окне загляните в список Time Zone. Основой программы можете сделать большой оператор Select Case. Будильник же не должен обращать внимание на чужое время на циферблате, он все равно должен звонить по нашему местному времени.
Задание 82.
«Шахматные часы». Это задание большое, но его нужно сделать обязательно. Это будет ваш первый опыт создания настоящего проекта.
По шахматным правилам шахматист не может думать над ходом бесконечно. На обдумывание первых 40 ходов ему дается, скажем, суммарное время 2,5 часа. При этом все равно, сколько времени он обдумывает каждый ход. Таким образом, партия, дошедшая до 41 хода, не может продолжаться дольше 5 часов. На следующие ходы тоже отводится какой-то лимит времени. Чтобы шахматист мог следить за тем, сколько времени осталось на обдумывание ему и противнику, существуют специальные шахматные часы. Они представляют собой единый корпус с двумя циферблатами – счетчиками времени, двумя кнопками и двумя флажками. Перед началом партии шахматистов А и В каждый счетчик показывает 2,5 часа. Пусть шахматисту А выпало начинать. Как только партия начинается, судья запускает счетчик А, который начинает обратный отсчет времени, уменьшая свои показания. Таким образом, в каждый момент партии счетчик показывает, сколько времени осталось шахматисту на обдумывание. Пока работает счетчик А, счетчик В, естественно, стоит. Как только шахматист А сделал ход, он тут же нажимает кнопку А, которая останавливает его счетчик и запускает счетчик В. За обдумывание принимается шахматист В. Сделав ход, он нажимает кнопку В, которая останавливает его счетчик и запускает счетчик А. И так далее. Если шахматист просрочит время, его флажок падает – он проиграл.
Для удобства шахматистов добавьте к часам счетчик ходов.
Делим проект на части
Нельзя создавать сразу весь проект одновременно. То есть было бы фатальной ошибкой сразу все нарисовать и пытаться все сразу программировать. Не пытайтесь ломать весь веник сразу, ломайте по прутикам. Когда Наполеон видел, что его армия слишком мала, чтобы сразу одолеть противника, он весь удар сосредотачивал на одной какой-то части вражеской армии и, разбив ее, нападал на следующую.
Надо разделить проект на части. Это не всегда легко. Некоторые проекты, особенно игры, представляют на первый взгляд единое неразрывное целое, так что нужен некоторый опыт, чтобы увидеть, «из каких кубиков построен домик». В нашем случае все более-менее просто. Три части просматриваются сразу, это:
Отлично! За какую из этих частей браться вначале? Для ответа на этот вопрос нужно сначала решить, зависят ли друг от друга отдельные части. Здесь очень пригодился бы некоторый опыт программирования. А если его нет, подойдет житейский опыт. Действительно, если бы вы мастерили будильник-секундомер из шестеренок, что бы в нем от чего зависело? Ну, ясно, что будильник не сработал бы без часов, ведь он должен чувствовать, когда время на циферблатах часов и будильника совпадает. Значит, будильник от часов зависит. А вот часы ходят и без будильника, значит они от него не зависят. Секундомер же, видимо, представляет собой полностью независимую часть со своим механизмом.
Итак, проект распался на две независимые части:
Какой частью заняться вначале? Дело вкуса. Часы с будильником попроще, поэтому начнем с них. Ну а между ними что выбрать вначале – часы или будильник? Здесь сомнения неуместны –
раньше нужно делать ту часть, которая не зависит от других частей
– это часы.
Итак, мы разделили проект на части и определили порядок выполнения частей:
1.Часы
2.Будильник
3.Секундомер
Беремся за часы. И тут опять пошла дележка. Чем раньше заниматься – временем суток (будем называть это просто часами), датой или днем недели? Шестеренки смутно подсказывают нам, что дата переключается в полночь, а значит момент переключения зависит от часов. Значит дату будем делать позже часов. А день недели, ясно, определяется датой.
Итак, окончательная последовательность такая:
1.Часы (время суток)
2.Дата
3.День недели
4.Будильник
5.Секундомер
Лирическое отступление (утешение): Если вам не удалось разделить ваш проект на части, или вы чувствуете, что разделили неправильно, это не значит, что нужно от проекта отказываться. Программируйте напропалую! В процессе программирования отдельные части постепенно (не без мучений и многократных досадных переделок) встанут на свои места.
Ну что ж, «Задачи ясны, цели определены, за работу, товарищи!», как говорили при социализме.
Делаем часы
Три рамки. Создаем новый проект. Первым делом нужно придумать, из каких элементов управления мы будем его строить. Поглядев на Рис. 13.6, мы видим, что все элементы управления, какими бы они ни были, пространственно и по смыслу разделены на три четкие группы: часы, будильник, секундомер. Напрашиваются три рамки (GroupBox). Поместим их на форму и дадим имена:
Для красоты покрасьте рамки, придав значения их свойству BackColor. На рисунке вы видите, что кроме этого вокруг рамок градиентной кистью нарисован бордюр. Но этим мы займемся потом. И вообще, если вы хотите в смысле графики сделать что-нибудь выдающееся, и украсить часы необыкновенными шедеврами, то на здоровье, пожалуйста!. Но не сейчас. Потом. Когда все заработает. Иначе потонете в подробностях и не откопаете в золотых лепесточках нужную вам стальную шестеренку.
Циферблат часов. Берем элемент управления метку и размещаем в рамке Часы. Это наш циферблат часов. В режиме проектирования я придал ему белый цвет, большой красивый шрифт и выровнял текст по центру.
Дадим метке имя Циферблат_часов. Еще раз предупреждаю: не надо сокращенных имен типа Циф_час. Сэкономив сейчас копейку, вы потом потеряете рубль.
Время на циферблате. Пришла пора программировать. Прежде всего нужна идея, как сделать так, чтобы часы показывали текущее время. Мы знаем, что есть функция Now, значение которой в любой момент равно текущей дате и времени суток. Стоит выполнить оператор
Циферблат_часов.Text = Format(Now, "HH:mm:ss")
и на часах появится текущее время суток. Попробуйте. Для этого временно создайте кнопку и поместите этот оператор в процедуру обработки щелчка по этой кнопке. Щелкайте по этой кнопке время от времени.
Получилось? Прекрасно! Но как сделать, чтобы время на циферблате менялось само? Нужно, чтобы этот оператор выполнялся не реже раза в секунду. Может быть, использовать оператор цикла? Но я должен вас предупредить, что на этом пути вас ждут разнообразные трудности, в суть которых не так-то легко вникнуть. Назову только одну. Вот, предположим, у вас заработал оператор цикла для часов. Тут вам захотелось включить секундомер. Значит, нужно, чтобы заработал другой оператор цикла – для секундомера. Значит, нужно выходить из цикла для часов. Так что же – часам останавливаться? Более глубоко я вникну в эту проблему в 27.3, а сейчас идем дальше.
Ввиду вышеизложенного программисты для таких дел используют таймеры. В нашем случае удобно использовать два таймера: для часов свой, для секундомера свой.
Выбросим кнопку и сотрем ее процедуру. Поместим на форму таймер и дадим ему имя Таймер_часов. Зададим ему пока интервал = 2500. Напишем такую программу:
Private Sub Таймер_часов_Tick(ByVal sender As Object, ByVal e As EventArgs) Handles Таймер_часов.Tick
Циферблат_часов.Text = Format(Now, "HH:mm:ss")
End Sub
Запустите проект. Вы увидите, что часы показывают правильное время, но обновляется оно раз в две с половиной секунды. Зададим интервал = 100. В этом случае процедура будет выполняться около 10 раз в секунду и не пропустит момента смены системного времени. Проверьте.
Ну что, все! Часы готовы.
Занимаемся датой
Занимаемся датой. Размещаем в рамке метку для даты. Даем метке имя Циферблат_даты. Чтобы там появилась дата в нужном нам виде, достаточно выполнить оператор
Циферблат_даты.Text = Format(Now, "Long Date")
Если его поместить в ту же процедуру таймера часов, то задача будет решена. Но мне жаль компьютер. 24 часа в сутки по 10 раз в секунду он будет спрашивать у Windows, какое нынче число, и выводить в текстовое поле одну и ту же дату, хотя делать это нужно только два раза – при запуске проекта и в полночь. Здесь еще и вопрос экономии: бегая, как белка в колесе, компьютер тратит ресурсы (силы), нужные в это же время для какого-нибудь другого дела, для того же секундомера хотя бы. Давайте облегчим жизнь компьютеру. Вспоминаем: при запуске проекта вырабатывается событие Form1_Load, а полночь – это когда показания часов равны нулю. Ага. Дописываем программу:
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
'Это чтобы дата появлялась на циферблате при запуске проекта:
Циферблат_даты.Text = Format(Now, "Long Date")
End Sub
Private Sub Таймер_часов_Tick(ByVal sender As Object, ByVal e As EventArgs) Handles Таймер_часов.Tick
Циферблат_часов.Text = Format(Now, "HH:mm:ss")
'Это чтобы дата менялась в полночь:
If Format(Now, "HH:mm:ss") = "00:00:00" Then Циферблат_даты.Text = Format(Now, "Long Date")
End Sub
Чтобы проверить, как работает проект, переставьте системные часы Windows на "Двенадцать без пяти" и подождите «полночи». Только потом не забудьте переставить обратно.
Наводим правильный стиль. Все работает, но мы начинаем допускать погрешности против правильного стиля программирования, которые в будущем могут выйти нам боком. Они и сейчас уже выходят. Придирчивый читатель даже видит, в каком месте. Действительно, никакой особой экономии ресурсов мы не добились, два раза подряд в одной процедуре Таймер_часов_Tick опрашивая компьютер функцией Format(Now, "HH:mm:ss"). А вот и два лекарства:
Первое. Показания часов напрашиваются быть переменной величиной. Ведь они нам еще понадобятся и для будильника. Мы их анализируем и еще будем анализировать, а это лучше делать с переменной величиной, а не со свойством Циферблат_часов.Text или функцией Format(Now, "HH:mm:ss"). Поэтому придумаем переменную Время_на_часах, объявим ее, как имеющую тип String и будем пользоваться только ей.
Второе. Как при запуске проекта, так и в полночь нам придется менять не только дату, но и день недели. Я предвижу повторяющийся фрагмент как минимум из двух операторов (пока это только один оператор Циферблат_даты.Text = Format(Now, "Long Date")). Поэтому оформим его, как процедуру с именем Смена_даты_и_дня_недели.
С учетом вышесказанного перепишем программу:
Dim Время_на_часах As String
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Смена_даты_и_дня_недели()
End Sub
Private Sub Таймер_часов_Tick(ByVal sender As Object, ByVal e As EventArgs)Handles Таймер_часов.Tick
Время_на_часах = Format(Now, "HH:mm:ss")
Циферблат_часов.Text = Время_на_часах
If Время_на_часах = "00:00:00" Then Смена_даты_и_дня_недели()
End Sub
Sub Смена_даты_и_дня_недели()
Циферблат_даты.Text = Format(Now, "Long Date")
End Sub
В этот момент вы совершенно искренне и с большим чувством можете сказать: «Ну зачем были все эти усложнения? Ведь все и так работало!» В ответ на это я могу только отослать вас к началу раздела.
Занимаемся днем недели
Нам не хочется трудиться, размещая в рамке метку, а потом еще и настраивая ее для дня недели. Замечаем, что она такая же, как для даты. Скопируем ее, как это принято в Windows, щелкнув по ней правой клавишей мыши и выбрав в контекстном меню Copy, а затем щелкнув правой клавишей мыши в рамке Часы и выбрав в контекстном меню Paste.
Даем метке имя Циферблат_дня_недели. Чтобы там появился правильный день недели, нужно выполнить оператор
Циферблат_дня_недели.Text = Format(Now, "dddd")
Раз мы организовали процедуру Смена_даты_и_дня_недели, то нам нет нужды вспоминать, в каких местах проекта оператор должен менять день недели. Вставляем его в эту процедуру:
Sub Смена_даты_и_дня_недели()
Циферблат_даты.Text = Format(Now, "Long Date")
Циферблат_дня_недели.Text = Format(Now, "dddd")
End Sub
Итак, часы с датой и днем недели готовы.
Делаем будильник
Поместим в рамку с именем Будильник все объекты, необходимые для будильника:
В циферблат занесем в режиме проектирования текст 12:00:00. Просто для того, «чтобы было». В качестве циферблата мы выбрали текстовое поле, а не метку. А это для того, чтобы иметь возможность в режиме работы вручную устанавливать время будильника.
Заставляем будильник звучать. Давайте пока забудем обо всех этих кнопках, метках и прочих подробностях, а возьмем быка за рога и заставим будильник при совпадении времени на часах и на будильнике исполнять какую-нибудь мелодию.
Вспомните, как мы занимались музыкой в «Плеере». Поместите на форму Windows Media Player и дайте ему имя Звонок. Сделайте его невидимым. Для настройки звонка достаточно трех операторов в процедуре Form1_Load:
Звонок.AutoStart = False 'Чтобы не запускался слишком рано
Звонок.PlayCount = 0 'Чтобы закончив играть, начинал снова
Звонок.FileName = "Mozart's Symphony No. 40.RMI"
Эта троица, несмотря на то, что встретится в программе скорее всего только один раз, в глазах программиста просится стать процедурой. Здесь принцип такой:
Если группа операторов (или даже один сложный оператор) представляет собой некое единое целое в том смысле, что решает какую-то свою отдельную задачу, то оформляйте ее процедурой.
Это улучшит понятность и читабельность программы – один из важнейших факторов ее качества.
Запустит воспроизведение мелодии оператор Звонок.Play(), но он должен выполниться только тогда, когда время на циферблате часов совпадет со временем на циферблате будильника.
Вот как дополнится теперь наш проект (я показываю только те процедуры, которых коснулись дополнения):
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Настройка_звонка()
Смена_даты_и_дня_недели()
End Sub
Private Sub Таймер_часов_Tick( ByVal sender As Object, ByVal e As EventArgs) Handles Таймер_часов.Tick
Время_на_часах = Format(Now, "HH:mm:ss")
Циферблат_часов.Text = Время_на_часах
If Время_на_часах = "00:00:00" Then Смена_даты_и_дня_недели()
If Время_на_часах = Циферблат_будильника.Text Then Звонок.Play()
End Sub
Sub Настройка_звонка()
Звонок.AutoStart = False 'Чтобы не запускался слишком рано
Звонок.PlayCount = 0 'Чтобы закончив играть, начинал снова
Звонок.FileName = "Mozart's Symphony No. 40.RMI"
End Sub
Запустите проект и установите время на циферблате будильника. Дождитесь, когда будильник зазвонит. Завершите работу проекта, не дожидаясь конца мелодии.
Два состояния будильника. Пора заняться кнопками и меткой. Подумаем вот о чем. Во время работы будильник в каждый момент времени может находиться в одном из двух состояний: «Установлен» или «Не установлен». Состояние «Установлен» означает, что вы хотите, чтобы будильник, когда подойдет его срок, звонил. Состояние «Не установлен» означает, что вы не хотите, чтобы будильник звонил, даже когда подойдет его срок. Компьютер должен в любой момент времени знать, в каком именно состоянии находится будильник, чтобы принять решение – звонить или не звонить. В таких случаях, когда компьютер должен в любой момент что-то знать или помнить, программист всегда придумывает переменную величину, дает ей подходящее имя и тип и организует хранение в этой переменной той самой информации, которую нужно помнить.
Придумаем переменную и дадим ей имя Будильник_установлен. Каким типом ее объявить? Для этого надо понять, а какие значения будет принимать эта переменная? По смыслу задачи значений два – «да» и «нет». Можно придать ей тип String, но хорошая практика программирования диктует тип Boolean. Вы скоро привыкнете к нему и поймете, что он удобнее.
Кнопки и метка будильника. Теперь попробуем вообразить, что должно происходить при включении будильника:
В соответствии с этим пишем процедуру:
Sub Включить_будильник()
Будильник_установлен = True
Метка_будильника.Text = "Будильник установлен на"
Кнопка_включения_выключения_будильника.Text = "Выключить будильник"
End Sub
Теперь попробуем сообразить, что должно происходить при выключении будильника:
В соответствии с этим пишем процедуру:
Sub Выключить_будильник()
Будильник_установлен = False
Метка_будильника.Text = "Будильник отключен"
Кнопка_включения_выключения_будильника.Text = "Включить будильник"
End Sub
Теперь нам удивительно легко написать процедуру щелчка по кнопке включения-выключения будильника:
Private Sub Кнопка_включения_выключения_будильника_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Кнопка_включения_выключения_будильника.Click
If Будильник_установлен Then Выключить_будильник() Else Включить_будильник()
End Sub
Обратите внимание, что благодаря удачному выбору имен и структуры программы язык программы по своей понятности приближается к естественному, человеческому.
Напомню, что поскольку переменная Будильник_установлен имеет логическое значение True или False, ее можно в одиночку использовать в качестве условия оператора If. Поэтому совсем не обязательно было писать
If Будильник_установлен = True Then ….
Процедуру щелчка по кнопке выключения звонка приведу без пояснений:
Private Sub Кнопка_выключения_звонка_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Кнопка_выключения_звонка.Click
Звонок.Stop()
End Sub
В последних четырех процедурах заключена вся механика работы кнопок и метки. Теперь надо позаботиться о том, чтобы будильник звенел только тогда, когда включен. Для этого вместо оператора
If Время_на_часах = Циферблат_будильника.Text Then Звонок.Play()
мы пишем оператор
If Будильник_установлен And Время_на_часах = Циферблат_будильника.Text Then Звонок.Play()
Теперь нужно решить, должен ли будильник после запуска проекта быть включен или выключен. Можно решить и так и эдак, на работоспособность проекта это никак не повлияет. Я предпочитаю, чтобы он был выключен, поэтому пишу в процедуру Form1_Load оператор Выключить_будильник().
Программа часов с будильником целиком. Часы с будильником готовы. Приведу полностью их программу:
Public Class Form1
Inherits System.Windows.Forms.Form
Windows Form Designer generated code
Dim Время_на_часах As String
Dim Будильник_установлен As Boolean
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs)Handles MyBase.Load
Настройка_звонка()
Выключить_будильник()
Смена_даты_и_дня_недели()
End Sub
Private Sub Таймер_часов_Tick(ByVal sender As Object, ByVal e As EventArgs) Handles Таймер_часов.Tick
Время_на_часах = Format(Now, "HH:mm:ss")
Циферблат_часов.Text = Время_на_часах
If Время_на_часах = "00:00:00" Then Смена_даты_и_дня_недели()
If Будильник_установлен And Время_на_часах = Циферблат_будильника.Text Then Звонок.Play()
End Sub
Sub Настройка_звонка()
Звонок.AutoStart = False
Звонок.PlayCount = 0
Звонок.FileName = "Mozart's Symphony No. 40.RMI"
End Sub
Sub Смена_даты_и_дня_недели()
Циферблат_даты.Text = Format(Now, "Long Date")
Циферблат_дня_недели.Text = Format(Now, "dddd")
End Sub
Sub Включить_будильник()
Будильник_установлен = True
Метка_будильника.Text = "Будильник установлен на:"
Кнопка_включения_выключения_будильника.Text = "Выключить будильник"
End Sub
Sub Выключить_будильник()
Будильник_установлен = False
Метка_будильника.Text = "Будильник отключен"
Кнопка_включения_выключения_будильника.Text = "Включить будильник"
End Sub
Private Sub Кнопка_включения_выключения_будильника_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Кнопка_включения_выключения_будильника.Click
If Будильник_установлен Then Выключить_будильник() Else Включить_будильник()
End Sub
Private Sub Кнопка_выключения_звонка_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Кнопка_выключения_звонка.Click
Звонок.Stop()
End Sub
End Class
Еще раз о стиле. Вы, наверное, заметили, что если после запуска проекта вы заставляли будильник звенеть несколько раз, то каждый раз он начинал мелодию не с начала, а с того места, где вы звонок выключили. Мне это все равно, но если вам это не нравится, то вы должны включать звонок не одним оператором
Звонок.Play()
а парой операторов:
Звонок. CurrentPosition = 0
Звонок.Play()
Но в этом случае вместо
If Будильник_установлен And Время_на_часах = Циферблат_будильника.Text Then Звонок.Play()
вам придется писать
If Будильник_установлен And Время_на_часах = Циферблат_будильника.Text Then
Звонок.CurrentPosition = 0
Звонок.Play()
End If
Вы видите, что оператор включения звонка теряет прозрачность и краткость. Выход один: оформлять эту пару операторов, как процедуру Включить_звонок. Тогда можно будет написать
If Будильник_установлен And Время_на_часах = Циферблат_будильника.Text Then Включить_звонок()
Вот теперь оператор включения звонка приобрел элегантность и максимальную понятность. Теперь никто не обвинит нас в корявости стиля. К тому же, если нам в будущем захочется как-то улучшить включение звонка, то все изменения мы сможем делать внутри процедуры Включить_звонок, не трогая сам оператор.
Делаем секундомер
Размести в проекте второй таймер, дав ему имя Таймер_секундомера. Поместим в рамку с именем Секундомер элементы управления для секундомера и дадим им имена:
а также не забудем метку с текстом «Секундомер».
Таймер нужен секундомеру для того же, для чего и часам, а именно – чтобы вовремя менялись цифры на циферблате, а собственный
таймер предпочтительно (но в нашем случае не обязательно) иметь для того, чтобы не зависеть от часов. Поскольку нам нужно отслеживать сотые доли секунды, установим ему интервал меньше 10. Когда секундомер считает, таймер секундомера должен работать:
Таймер_секундомера.Enabled = True
а когда он в паузе или в нуле, таймер может и отдохнуть:
Таймер_секундомера.Enabled = False
Режимы работы секундомера. Если будильник в каждый момент времени может находиться в одном из двух состояний (режимов) – установлен или не установлен, то секундомер – в трех: считает, стоит в паузе, стоит в нуле. Придумаем переменную и дадим ей имя Режим _секундомера. Объявить ее типом Boolean явно недостаточно, ведь в типе Boolean всего два возможных значения, а нам нужно три. Здесь напрашивается перечисление:
Enum Режим
считает
пауза
в_нуле
End Enum
Dim Режим_секундомера As Режим
Для каждого режима нам придется организовать свою переменную для отсчета секунд на секундомере:
Dim Секунды_на_секундомере As Double
Dim Секунды_при_запуске_секундомера As Double
Dim Секунды_на_паузе_секундомера As Double
Обратите внимание, что эти переменные, хоть и имеют смысл времени, объявлены, как дробные числовые, а не как Date. Почему так получилось и почему переменных три, а не одна, сейчас попытаюсь пояснить.
Для отсчета времени на секундомере мы будем использовать известную вам функцию
Timer класса DateAndTime (не путайте с элементом управления Timer), потому что она выдает секунды с дробной частью (нас устроят сотые доли секунды – какой же секундомер без сотых долей). Поскольку она выдает число секунд, прошедших с полуночи, а нам нужно число секунд, прошедших с момента запуска секундомера, да еще с учетом того, что во время паузы на секундомере уже стояли какие-то показания, нам придется поразмыслить, как это сделать.
Засечем в момент пуска секундомера значение функции Timer оператором
Секунды_при_запуске_секундомера = DateAndTime.Timer
Тогда в каждый момент времени после запуска секундомера выражение
DateAndTime.Timer - Секунды_при_запуске_секундомера
как раз и будет равняться числу секунд, прошедших с момента запуска секундомера. А если нам удастся правильно засечь Секунды_на_паузе_секундомера (имеющие смысл секунд, прошедших с момента пуска до паузы), то дело решит оператор
Секунды_на_секундомере = DateAndTime.Timer - Секунды_при_запуске_секундомера _
+ Секунды_на_паузе_секундомера
Если поместить его в процедуру таймера, то он будет оперативно выдавать нужные для циферблата секунды.
Внешний вид показаний секундомера. Переменная Секунды_на_секундомере – это дробное число типа Double. Например, такое – 65.28194. Но на секундомере эти самые цифры, сами понимаете, должны выглядеть по-другому – 00:01:05.28. Для этого нам нужно как-то преобразовать число секунд в стандартный формат времени. Организуем переменную и константу:
Dim Время_на_секундомере As Date
Const Полночь As Date = #12:00:00 AM#
Здесь время #12:00:00 AM# обозначает, как ни странно, полночь по-американски.
Дело решает пара операторов:
Время_на_секундомере = Полночь.AddSeconds(Секунды_на_секундомере)
Циферблат_секундомера.Text = Format(Время_на_секундомере, "mm:ss.ff")
Кнопки секундомера. Теперь нам нужно подумать о том, что должно происходить во время нажатия на кнопки секундомера. Наши размышления должны течь примерно в том же русле, что и размышления о кнопках и метке будильника. Никакой новой идеологии по сравнению с будильником вы не обнаружите. По аналогии с будильником вы, наверное, должны придти к выводу о необходимости создания трех процедур:
Sub Секундомер_обнулить()
Sub Секундомер_остановить()
Sub Секундомер_запустить()
Я думаю, эти размышления вам полезно будет довести до конца самостоятельно. А проверить себя вы сможете, заглянув в окончательный текст программы ниже. Все процедуры, касающиеся будильника собраны вместе и вы их легко найдете.
Еще о режиме. Самые дотошные из вас, разобравшись в программе, скажут мне, что нечего было огород городить – создавать перечисление, когда можно было обойтись логической переменной Секундомер_считает. Верно. Но неправильно. Потому что нужно оставлять простор для дальнейшего развития проекта. Например, вы можете в будущем захотеть, чтобы во время паузы цифры на секундомере мигали, а в нуле – нет.
Рисуем бордюры вокруг рамок
Взгляните еще раз на рисунок. Три рамки – три бордюра. Но это не бордюры. Просто я нарисовал на форме три залитых градиентной кистью прямоугольника. Каждый – за своей рамкой, чуть побольше ее по размерам. Рамки непрозрачные и поэтому от прямоугольников мы видим только их края.
Поскольку бордюров три, я создал процедуру Бордюр_вокруг, которая рисует бордюр вокруг любого элемента управления. Вот ее заголовок:
Sub Бордюр_вокруг(ByVal Объект As Control, ByVal Цвет As Color, ByVal Толщина As Single)
а тело ее вы можете видеть ниже, в полном тексте программы. В процедуре три параметра. Параметр Объект имеет тип Control. Это как раз тот самый любой элемент управления. Параметр Толщина – толщина бордюра. Параметр Цвет определяет первый из двух цветов градиентной кисти. Когда вы разберетесь в тексте процедуры, вы увидите, что я мог бы включить в число параметров и второй цвет, и координаты точек, определяющих градиент. Но я не стал этого делать, чтобы не затемнять изложение. А вы вполне можете попробовать.
Чтобы бордюры не стирались при случайном загораживании наших часов другими окнами, я включил обращения к процедуре Бордюр_вокруг в процедуру Form1_Paint. Получилось очень изящно и прозрачно:
Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) _
Handles MyBase.Paint
Бордюр_вокруг(Часы, Color.Blue, 20)
Бордюр_вокруг(Будильник, Color.Red, 20)
Бордюр_вокруг(Секундомер, Color.Green, 20)
End Sub
Косая граница градиента для всех трех бордюров получилась единой потому, что координаты точек, определяющих градиент, едины для всех трех бордюров.
Полный текст программы «Будильник-секундомер»
Привожу полный текст программы «Будильник-секундомер». В том, что касается будильника, я добавил упоминавшуюся мной процедуру Включить_звонок, причем включил в нее для привлечения внимания пользователя разворачивание будильника на весь экран. Везде, где можно, я объявления переменных перенес из верхней части окна кода внутрь процедур.
Public Class Form1
Inherits System.Windows.Forms.Form
Windows Form Designer generated code
Enum Режим
считает
пауза
в_нуле
End Enum
Dim Режим_секундомера As Режим
Dim Будильник_установлен As Boolean
Dim Секунды_на_секундомере As Double
Dim Секунды_при_запуске_секундомера As Double
Dim Секунды_на_паузе_секундомера As Double
'НАЧАЛЬНАЯ УСТАНОВКА МЕХАНИЗМА
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Настройка_звонка()
Выключить_будильник()
Смена_даты_и_дня_недели()
Секундомер_обнулить()
End Sub
'ПРОЦЕДУРЫ РАБОТЫ ЧАСОВ И БУДИЛЬНИКА
Private Sub Таймер_часов_Tick(ByVal sender As Object, ByVal e As EventArgs) Handles Таймер_часов.Tick
Dim Время_на_часах As String
Время_на_часах = Format(Now, "HH:mm:ss")
Циферблат_часов.Text = Время_на_часах
If Время_на_часах = "00:00:00" Then Смена_даты_и_дня_недели()
If Будильник_установлен And Время_на_часах = Циферблат_будильника.Text Then Включить_звонок()
End Sub
Sub Смена_даты_и_дня_недели()
Циферблат_даты.Text = Format(Now, "Long Date")
Циферблат_дня_недели.Text = Format(Now, "dddd")
End Sub
Private Sub Кнопка_включения_выключения_будильника_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Кнопка_включения_выключения_будильника.Click
If Будильник_установлен Then Выключить_будильник() Else Включить_будильник()
End Sub
Sub Включить_будильник()
Будильник_установлен = True
Метка_будильника.Text = "Будильник установлен на"
Кнопка_включения_выключения_будильника.Text = "Выключить будильник"
End Sub
Sub Выключить_будильник()
Будильник_установлен = False
Метка_будильника.Text = "Будильник отключен"
Кнопка_включения_выключения_будильника.Text = "Включить будильник"
End Sub
Sub Настройка_звонка()
Звонок.AutoStart = False
Звонок.PlayCount = 0
Звонок.FileName = "Mozart's Symphony No. 40.RMI"
End Sub
Sub Включить_звонок()
Me.WindowState = FormWindowState.Maximized
Звонок.CurrentPosition = 0
Звонок.Play()
End Sub
Private Sub Кнопка_выключения_звонка_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Кнопка_выключения_звонка.Click
Звонок.Stop()
End Sub
'ПРОЦЕДУРЫ РАБОТЫ СЕКУНДОМЕРА
Private Sub Таймер_секундомера_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles Таймер_секундомера.Tick
Dim Время_на_секундомере As Date
Const Полночь As Date = #12:00:00 AM#
Секунды_на_секундомере = DateAndTime.Timer - Секунды_при_запуске_секундомера + _
Секунды_на_паузе_секундомера
Время_на_секундомере = Полночь.AddSeconds(Секунды_на_секундомере)
Циферблат_секундомера.Text = Format(Время_на_секундомере, "mm:ss.ff")
End Sub
Private Sub Кнопка_пуска_паузы_секундомера_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Кнопка_пуска_паузы_секундомера.Click
If Режим_секундомера <> Режим_секундомера.считает Then Секундомер_запустить() _
Else Секундомер_остановить()
End Sub
Sub Секундомер_запустить()
Секунды_при_запуске_секундомера = DateAndTime.Timer
Режим_секундомера = Режим.считает
Таймер_секундомера.Enabled = True
Кнопка_пуска_паузы_секундомера.Text = "ПАУЗА"
End Sub
Sub Секундомер_остановить()
Секунды_на_паузе_секундомера = Секунды_на_секундомере
Режим_секундомера = Режим.пауза
Таймер_секундомера.Enabled = False
Кнопка_пуска_паузы_секундомера.Text = "ПУСК"
End Sub
Private Sub Кнопка_обнуления_секундомера_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Кнопка_обнуления_секундомера.Click
Секундомер_обнулить()
End Sub
Sub Секундомер_обнулить()
Секунды_на_паузе_секундомера = 0
Циферблат_секундомера.Text = "00:00.00"
Режим_секундомера = Режим.в_нуле
Таймер_секундомера.Enabled = False
Кнопка_пуска_паузы_секундомера.Text = "ПУСК"
End Sub
'ПРОЦЕДУРЫ ПОСТРОЕНИЯ БОРДЮРОВ
Private Sub Form1_Paint( ByVal sender As Object, ByVal e As PaintEventArgs) Handles MyBase.Paint
Бордюр_вокруг(Часы, Color.Blue, 20)
Бордюр_вокруг(Будильник, Color.Red, 20)
Бордюр_вокруг(Секундомер, Color.Green, 20)
End Sub
Sub Бордюр_вокруг(ByVal Объект As Control, ByVal Цвет As Color, ByVal Толщина As Single)
Dim Гр As Graphics = Me.CreateGraphics
Dim Т1 As New Point(0, 0)
Dim Т2 As New Point(430, 430)
Dim Кисть_град As New System.Drawing.Drawing2D.LinearGradientBrush(Т1, Т2, Цвет, Color.Yellow)
'Бордюр - залитый градиентом прямоугольник:
Гр.FillRectangle(Кисть_град, Объект.Left - Толщина, Объект.Top - Толщина, _
Объект.Width + 2 * Толщина, Объект.Height + 2 * Толщина)
'Черный ободок вокруг бордюра:
Гр.DrawRectangle(Pens.Black, Объект.Left - Толщина, Объект.Top - Толщина, _
Объект.Width + 2 * Толщина, Объект.Height + 2 * Толщина)
End Sub
End Class
Проект «Будильник-секундомер»
Вы узнали о VB вполне достаточно, чтобы рваться в бой. Наверняка у вас в голове зреет идея создать некий элегантный и одновременно вполне «убойный» проект строчек на 200, способный поразить в самое сердце ваших друзей и, что не менее важно, подруг. Однако, спешу вам сказать, что вы торопитесь. Ибо! Друзья и подруги может быть и понесут вас на руках до ближайшего кафе-мороженого, но человек, мало-мальски разбирающийся в программировании, бросив скучающий взгляд на ваше жалкое детище, спросит: «Юноша, в каком году вы заканчивали церковно-приходскую школу?» И не потому, что проект работает плохо, он вполне может проделывать на экране что-нибудь любопытное. Но программа! Программа! Она не выдерживает никакой критики. В ней невозможно разобраться! 200 строк вы осилили, а сколько вы возились? Неделю? А веди могли все сделать за день! Сколько у вас процедур? 8? А нужно было 40! И так далее. Когда придет пора делать проект из 1000 строк, вы не сможете его одолеть никогда! Так что не воображайте, что любая программа, которая выдает правильный результат – это хорошая программа.
Что же делать? Все очень просто. Вы ни в чем не виноваты. Ведь я пока только провозгласил вам, что программу надо писать правильно. Например, писать много маленьких процедур. Но ведь не научил
еще, как это делать. А учиться надо на примерах.
Пришла пора создать проект, «правильно» составленный из процедур. Проекты «Калькулятор» и «Плеер», написанный нами ранее, не подходит в качестве учебного пособия, потому что логика их работы слишком проста: нажал кнопку – выполнилась соответствующая процедура и все. Проект «Парк под луной» хорош, но он все-таки учебный и поэтому тоже упрощенный. Нужна более реальная задача. И «Будильник-секундомер» в этом смысле подходит идеально.
Для ее решения мы создадим небольшую программу примерно из 100 строк, включающую 17 процедур. Поскольку создаваемая программа представляет для многих из вас неведомую страну, я буду применять метод изложения «за ручку», который уже применял в 1.3. Повторяйте за мной все, что я буду делать.
Начнем с постановки задачи.
Суть анимации
Суть анимации лучше всего объяснять, используя методы рисования.
В начале Глава 8. я объяснил идею создания иллюзии движения картинок по экрану. Там же мы двигали по форме объекты – элементы управления. Попробуем заставить двигаться по экрану не объекты, а нарисованные нами геометрические фигуры. Пусть слева направо должна двигаться окружность. Для этого мы должны сначала нарисовать ее слева и быстро стереть, для чего нарисовать ее на том же месте, но цветом фона. Несмотря на то, что мы окружность быстро стерли, она успеет мелькнуть на экране, и глаз это заметит. Затем нужно нарисовать и стереть такую же окружность чуть правее, затем еще правее и т.д.
Ввиду причин, упомянутых в 13.5.3, откажемся от операторов цикла. Будем использовать таймеры. Создадим проект. Поместим в него таймер. Установим его интервал в 30. Вот программа:
Dim x As Integer = 100 'Координаты окружности
Dim y As Integer = 150
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
Dim Граф As Graphics = Me.CreateGraphics
Dim Черное_перо As Pen = New Pen(Color.Black, 5)
Dim Перо_цвета_фона As Pen = New Pen(Me.BackColor, 5)
Dim i As Integer
Граф.DrawEllipse(Черное_перо, x, y, 20, 20) 'Рисуем окружность
For i = 1 To 5000000 : Next 'Пустой цикл для задания паузы
Граф.DrawEllipse(Перо_цвета_фона, x, y, 20, 20) 'Стираем окружность
x = x + 1 'Перемещаемся немного направо
End Sub
Пояснения: Когда вы попробуете выполнить эту программу на компьютере, изображение движущейся окружности может получиться некачественным – окружность в процессе движения будет мерцать и пульсировать. Это связано с разверткой электронно-лучевой трубки вашего монитора. Если создать маленькую паузу между рисованием и стиранием окружности, нежелательные эффекты уменьшатся. Пауза нужна для того, чтобы окружность не слишком быстро исчезала с экрана. Эту паузу я создаю пустым циклом. Поэкспериментируйте с диаметром, толщиной окружности, продолжительностью паузы или шагом движения по горизонтали. Последние две величины и интервал таймера определяют скорость движения.
Задание 83.
Пусть по экрану движется «вагон» – прямоугольник и два кружочка.
Движем объекты
Мы больше не будем заниматься анимацией при помощи графических методов, потому что элементы управления двигать гораздо приятнее. Чем самим стараться создавать иллюзию движения окружности, рисуя и стирая ее, лучше нарисовать ее один раз на каком-нибудь элементе управления и затем двигать сам элемент.
Поместим на форму кнопку, объект PictureBox и таймер. Вот программа:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim Граф As Graphics = PictureBox1.CreateGraphics
Dim Черное_перо As Pen = New Pen(Color.Black, 5)
Граф.DrawEllipse(Черное_перо, 3, 3, 20, 20) 'Рисуем один раз окружность
Timer1.Enabled = True 'Включаем движение
End Sub
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
PictureBox1.Left = PictureBox1.Left + 1 'Перемещаем PictureBox немного направо
End Sub
Пояснения: На объекте PictureBox1 по нажатию на кнопку рисуется наша окружность и включается таймер, движущий PictureBox1 направо. Обратите внимание, что в отличие от предыдущей программы окружность движется спокойно, не мерцая. Дело в том, что VB создает иллюзию движения гораздо искуснее, чем это умеем делать мы с вами. А ведь тоже рисует и стирает.
Вместо нарисованной примитивной окружности мы могли бы придать элементу PictureBox1 какую-нибудь картинку или фото (присвоить свойству Image или же нарисовать на поверхности PictureBox1 методом DrawImage).. И тогда бы двигалась вся картинка.
Задание 84.
Нарисуйте в графическом редакторе Paint два самолетика на белом фоне и сохраните их изображения. Придайте форме и двум объектам PictureBox на ней белый цвет. Поместите самолетики на объекты PictureBox. Пусть они летят один за другим справа налево.
Задание 85.
Пусть пять кнопок движутся одновременно в разных направлениях: вправо, влево, вверх, вниз, наискосок.
Отскок от края формы. Заставим какой-нибудь объект двигаться направо, а затем самостоятельно отскочить от правого края формы:
Dim Шаг As Integer = 2
Dim x As Integer = 0
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
Button1.Left = x
x = x + Шаг
If x > Width Then Шаг = -2 'Если объект улетел за правый край формы, то лететь обратно
End Sub
Пояснения: Я написал Width, а не Me.Width, но VB не пожаловался, так как считает, что если «хозяин» свойства не указан, то в данном случае «хозяином» является форма.
Задание 86.
Заставьте объект бесконечно двигаться, отскакивая от правого и левого краев формы.
Задание 87.
«Биллиардный шар». Нарисуйте «биллиардный стол» – большой прямоугольник. Шар под углом летает по столу, отскакивая от его краев по закону отражения (Рис. 13.7). Попав в «лузу» (любой из четырех углов стола), он останавливается. Объектом здесь удобно взять маленький PictureBox с загруженной иконкой в виде шарика (подходящие иконки есть в папке VS).

Рис. 13.7
Указание: В переводе на компьютерный язык слова «по закону отражения» означают вот что. Обозначим dx шаг по горизонтали, dy – по вертикали. Если они оба отличаются от нуля, шарик летит наискосок Удаpившись о левый или пpавый боpт, шаpик меняет гоpизонтальную составляющую скоpости на пpотивоположную: dx = -dx. Аналогично, удаpившись о верхний или нижний боpт, шаpик меняет вертикальную составляющую скоpости на пpотивоположную: dy = -dy.
Задание 88.
«Часы со стрелками». Если вы в ладах с градусной мерой угла, сделайте часы со стрелками: часовой, минутной, секундной. Задача упростится, если вы выберете в качестве стрелок тоненькие сектора окружностей.
Задание 89.
Изобразите полет камня, брошенного с башни, для Задание 47. Камнем может служить PictureBox с подходящей загруженной иконкой. Необходимо, чтобы время полета равнялось реальному времени полета камня.
Задание 90.
Сделайте игру: Пушка на экране стреляет в цель ядрами. С какого выстрела она поразит противника? Между пушкой и целью расположена небольшая гора. Перед началом игры случайно задается горизонтальная координата цели. Затем рисуется картинка (Рис. 13.8).

Рис. 13.8
Перед каждым выстрелом компьютер отображает в текстовом поле номер выстрела и запрашивает у человека стартовую скорость ядра v и угол a наклона ствола пушки к земле. Затем летит ядро. Полет ядра подчиняется двум уравнениям: s=v*t*cosa и h=v*t*sina – 9.81*t2/2 (см. предыдущее задание). Считается, что цель поражена, если ядро ее коснулось, не коснувшись горы. Вы можете запрограммировать автоматическое определение попадания в цель. Указание: Для этого нужно в момент, когда ядро при падении пересекло уровень земли, сравнить горизонтальные координаты ядра и цели. Если они достаточно близки, то фиксируйте попадание. Определение прикосновения к горе – чуть более хлопотное занятие, но идея та же.
Движем» свойства объектов
Реклама убивает мысль! Сделаем антирекламный ролик: Из черной глубины экрана на нас наплывают, увеличиваясь, красные слова «Реклама убивает мысль!»
Растянем на всю форму черную метку. Придадим ее свойству Text значение нужного текста, а свойству ForeColor – красный цвет. Программа:
Dim Размер_шрифта As Integer = 10
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
Dim Шрифт As New Font("Times", Размер_шрифта)
Label1.Font = Шрифт
Размер_шрифта = Размер_шрифта + 1
End Sub
Задание 91.
Пусть текст также плавно меняет свой цвет.
Zoom. Пусть теперь экран монитора – это передний иллюминатор космического корабля. Мы приближаемся к планете, она вырастает в иллюминаторе. Чтобы создать такой эффект, распахните черную форму на весь экран. Придайте фото планеты объекту PictureBox. Установив в True его свойство StrechImage, увеличивайте размеры PictureBox:
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
PictureBox1.Width = PictureBox1.Width + 1
PictureBox1.Height = PictureBox1.Height + 1
End Sub
Подобным образом можно оживлять многие объекты, меняя любые их свойства, имеющие численное значение.
А теперь создадим два мультфильма: «Летающая тарелка» и «Человечек».
Мультфильм «Летающая тарелка»
Богатство графических методов и свойств объектов VB позволяет делать мультфильмы разными способами. Я выберу те, что ведут к цели проще и быстрее.
Покажите своим друзьям фильм, снятый «секретной видеокамерой»: В небе над вашим домом летит летающая тарелка.
Для этого вам понадобится ввести в компьютер фотографию, на которой были бы видны ваш дом и небо. Если такой фотографии у вас нет, возьмите любой графический файл из тех, что есть на вашем диске – для тренировки сойдет.
Затем зайдите в любой графический редактор, например, в Paint, настройте очень маленький размер листа, на котором будете рисовать, и на белом фоне нарисуйте во весь этот маленький лист летающую тарелку. Сохраните ее изображение в файл в формате BMP или другом каком-нибудь, не искажающем цвета пикселей (например, для рисунков очень хорош формат PNG, он и цвета пикселей не искажает и размеры файла сильно уменьшает). Формат JPEG не подойдет, потому что он цвета пикселей чуть-чуть, почти незаметно для глаза, но искажает. Почему нельзя искажать даже чуть-чуть, скажу чуть-чуть позже. JPEG, однако, хорош для фотографий, потому что он сильно уменьшает размеры файла.
Создайте проект с формой, таймером и PictureBox. Будем писать программу, постепенно ее улучшая.
1 версия (плохая):
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Me.BackgroundImage = Image.FromFile("Ландшафт.jpg")
PictureBox1.Image = Image.FromFile("Тарелка.bmp")
End Sub
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
PictureBox1.Left = PictureBox1.Left + 1
End Sub
Результаты работы программы вы видите на Рис. 13.9. Тарелка действительно летит по небу слева направо. Плохо то, что летит она в обрамлении большого серого и маленького белого прямоугольников. Большой серый прямоугольник – это наш PictureBox. Займемся им во 2 версии программы, а маленьким белым – в 3 версии.

Рис. 13.9
2 версия (получше). Для избавления от серого прямоугольника сделаем 3 вещи:
Избавим PictureBox от бордюра:
PictureBox1.BorderStyle = BorderStyle.None
Чтобы избавиться от серого цвета, сделаем PictureBox «прозрачным». Для этого или в режиме проектирования установим его свойству BackColor значение Transparent (в закладке Web), или напишем в коде:
PictureBox1.BackColor = Color.Transparent
Помогло. А теперь замечаем, что движение чуть притормаживает. Происходит это от того, что PictureBox слишком велик и двигать его компьютеру в нашем случае трудно. Заставим PictureBox сжаться до размеров тарелки:
PictureBox1.SizeMode = PictureBoxSizeMode.AutoSize
Вот программа:
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Me.BackgroundImage = Image.FromFile("Ландшафт.jpg")
PictureBox1.Image = Image.FromFile("Тарелка.bmp")
PictureBox1.BorderStyle = BorderStyle.None
PictureBox1.BackColor = Color.Transparent
PictureBox1.SizeMode = PictureBoxSizeMode.AutoSize
End Sub
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
PictureBox1.Left = PictureBox1.Left + 1
End Sub
Результаты работы программы вы видите на Рис. 13.10.

Рис. 13.10
3 версия (нормальная). Белый прямоугольник вокруг тарелки достался нам в наследство от графического редактора. Ведь сохранилась в файле не только сама тарелка, но весь прямоугольный лист, на котором ее рисовали. А лист-то был белый.
Мы имеем возможность, работая с объектом Bitmap, объявить любой цвет прозрачным. Поэтому для избавления от белого прямоугольника сделаем 3 вещи: Образуем из нашей тарелки объект Bitmap, объявим белый цвет прозрачным и присвоим Bitmap свойству Image нашего PictureBox. Вот программа:
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Me.BackgroundImage = Image.FromFile("Ландшафт.jpg")
Dim Тарелка As New Bitmap("Тарелка.bmp")
Тарелка.MakeTransparent(Color.White)
PictureBox1.Image = Тарелка
PictureBox1.BorderStyle = BorderStyle.None
PictureBox1.BackColor = Color.Transparent
PictureBox1.SizeMode = PictureBoxSizeMode.AutoSize
End Sub
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
PictureBox1.Left = PictureBox1.Left + 1
End Sub
Здесь я полужирным выделил три новых оператора. Использован метод MakeTransparent объекта Bitmap, который делает прозрачным цвет, указанный в качестве параметра. Удовлетворительные результаты работы программы вы видите на Рис. 13.11.

Рис. 13.11
Вопрос: Что делать, если тарелка нарисована в графическом редакторе не на белом фоне, а на другом, причем, что за цвет у фона, неизвестно? Не беда. Выберите на картинке пиксель, который наверняка имеет этот самый цвет фона и узнайте его цвет при помощи функции GetPixel, как мы это делали в 12.7.4. А затем используйте его в качестве параметра метода MakeTransparent:
Dim Цвет As Color = Тарелка.GetPixel(1, 1)
Тарелка.MakeTransparent(Цвет)
Чем плох JPEG. Сейчас становится ясно, почему нельзя было сохранять тарелку в формате JPEG. Дело в том, что этот формат платит за свою компактность некоторым искажением мельчайших деталей фотографии. При этом однородный фон вполне может превратиться в пестрый набор пикселей, имеющих очень близкие, но все-таки не абсолютно одинаковые цвета. Для глаза незаметно, но компьютер не проведешь! Он видит, что белый фон на рисунке тарелки не абсолютно белый, вернее – не везде абсолютно белый. Ведь что такое белый цвет для компьютера? Это когда все три составляющие цвета (красная, зеленая и синяя) равны 255. JPEG же может какие-нибудь из них для некоторых пикселей сделать, например, 254. И все! Компьютер отказывается считать этот цвет белым, а значит и не делает его прозрачным. Белая окантовка тарелки местами остается.
Задание 92.
Пусть тарелка, полетав туда-сюда, совершит посадку. Указание: Для этого вы можете организовать счетчик импульсов таймера и в зависимости от его величины при помощи операторов выбора выбирать в процедуре Timer1_Tick то или иное направление полета тарелки.
Пусть тарелка время от времени выстреливает вниз лазерным лучом. Указание: Для этого вы можете использовать летающий вместе с тарелкой высокий узкий PictureBox и менять его фон с прозрачного на белый и обратно или делать PictureBox иногда видимым.
Мультфильм «Человечек»
Сейчас мы с вами создадим простейший мультфильм, в котором человечек шагает среди египетских пирамид (Рис. 13.12).

Рис. 13.12
Когда мы снимаем видеокамерой идущего человека, то на разных кадрах у него разное положение ног. Все остальные части тела более-менее неподвижны. Значит, если мы будем достаточно быстро менять в поле зрения кадры с разным положением ног, то создастся иллюзия ходьбы. В нашем случае для создания приемлемой иллюзии нам достаточно смены трех кадров (см. Рис. 13.13).

Рис. 13.13
Зайдите в графический редактор Paint или какой-нибудь другой и на белом фоне нарисуйте первый кадр примерно так же, как вы рисовали летающую тарелку. Вы можете сделать человечка гораздо красивее и подробнее, чем это сделал я. Если вы отлично работаете в солидном графическом редакторе, то можете даже взять фотографию вашего знакомого, только не забудьте выкрасить белой краской все, что выходит за контуры его фигуры. Сохраните человечка под именем Кадр1. Теперь, не стирая человечка, измените ему положение ног и сохранитесь как Кадр2 (не Save, а Save as…). Аналогично создайте и сохраните Кадр3. У вас получилось три файла.
Теперь создайте проект в VB. Придайте форме картинку пирамид или чего-нибудь другого подходящего, например, улицы. Поместите на форму объект PictureBox. Создайте в коде из упомянутых 3 кадров 3 объекта Bitmap. Наша идея – придавать объекту PictureBox по очереди с достаточной скоростью картинки из этих 3 объектов. Тогда мы увидим, что человечек в PictureBox передвигает на месте ногами. Менять кадры нужно в такой последовательности: 1-2-3-2-1-2-3-2-1-2-3-2-1-2-…. Если при этом объект PictureBox будет еще и двигаться налево по форме, то и получится ходьба.
Давайте-ка для лучшего понимания мысленно разобьем эту последовательность на одинаковые участки: 1-2-3-2----1-2-3-2-----1-2-3-2- …. Длина участка получилась равной 4. Поместите на форму таймер и придайте ему интервал 100 (потом, если движение будет слишком быстрым или медленным, вы его измените). Наша задача – сделать так, чтобы при каждом выполнении процедуры таймера мы видели следующий очередной кадр из приведенной мной последовательности. Для этого я организовал переменную N и заставил ее пробегать значения 0-1-2-3-----0-1-2-3-----0-1-2-….Не путайте эту последовательность с предыдущей. Как видите, длину участка на ней я подобрал тоже равной 4. Вот программа:
Dim Кадр1 As New Bitmap("Кадр1.png")
Dim Кадр2 As New Bitmap("Кадр2.png")
Dim Кадр3 As New Bitmap("Кадр3.png")
Dim N As Integer = 0
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Me.BackgroundImage = Image.FromFile("Пирамиды.jpg")
Кадр1.MakeTransparent(Color.White)
Кадр2.MakeTransparent(Color.White)
Кадр3.MakeTransparent(Color.White)
PictureBox1.BorderStyle = BorderStyle.None
PictureBox1.BackColor = Color.Transparent
PictureBox1.SizeMode = PictureBoxSizeMode.AutoSize
End Sub
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
Select Case N
Case 0 : PictureBox1.Image = Кадр1
Case 1 : PictureBox1.Image = Кадр2
Case 2 : PictureBox1.Image = Кадр3
Case 3 : PictureBox1.Image = Кадр2
End Select
N = N + 1 'Увеличиваем N на 1
If N = 4 Then N = 0 'После 3 должен идти 0, а не 4
PictureBox1.Left = PictureBox1.Left - 10
End Sub
Пару операторов
N = N + 1 'Увеличиваем N на 1
If N = 4 Then N = 0 'После 3 должен идти 0, а не 4
можно заменить одним изящным оператором
N = (N + 1) Mod 4
Задание 93.
Добавьте в проект три кнопки. По одной человечек идет направо, по другой налево, по третьей останавливается. Подсказка: Рисовать новые кадры в графическом редакторе не надо.
Задание 94.
«Улыбка». Для тех, кто умеет рисовать. Попросите у своей знакомой ее фотографию, где она снята с серьезным выражением лица. Введите фото в компьютер. Сделайте в Paint, а лучше в PhotoShop еще два-три кадра этого фото, аккуратно понемножку приподнимая уголки губ на изображении. Подумайте, в какой последовательности нужно показывать кадры, чтобы улыбка постепенно возникала и исчезала.
Анимация
Анимация означает придание неподвижному предмету движения. Еще одно значение слова анимация – мультфильм.
Фокус у элементов управления
Что такое фокус? Создайте проект. Поместите на форму сначала два текстовых поля, а затем две кнопки. Не наоборот. Запустите проект. Щелкните по одной кнопке, по другой, по одному полю, по другому. Ничего, конечно, не происходит, но обратите внимание вот на что. Если последний раз вы щелкнули по кнопке, то она имеет несколько другой, чем у других кнопок, «бывший нажатый» вид. Это от того, что рамочка вокруг нее стала чуть потолще. А если последний раз вы щелкнули по текстовому полю, то именно в нем мигает курсор, а в других текстовых полях не мигает. VB как бы показывает вам, каким из элементов управления вы пользовались последним. Можно сказать, что из множества разнородных объектов на форме один какой-то выделяется, то есть находится в фокусе. Говорят, что в этом случае объект обладает фокусом.
На вашей форме 4 объекта. В фокусе всегда только один из них.
События приобретения и потери фокуса. У объектов, способных иметь фокус, имеются два события: Enter, которое возникает в момент приобретения объектом фокуса, и Leave, которое возникает в момент потери фокуса.
Запишите в окно кода такие процедуры (если вы забыли, как получать заготовки процедур, перечитайте 3.11):
Private Sub Button1_Enter(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Enter
Debug.WriteLine("Кнопка 1 получила фокус")
End Sub
Private Sub Button2_Enter(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button2.Enter
Debug.WriteLine("Кнопка 2 получила фокус")
End Sub
Private Sub TextBox1_Enter(ByVal sender As Object, ByVal e As System.EventArgs) Handles TextBox1.Enter
Debug.WriteLine("Текстовое поле 1 получило фокус")
End Sub
Private Sub Button1_Leave(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Leave
Debug.WriteLine("Кнопка 1 утратила фокус")
End Sub
Private Sub Button2_Leave(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button2.Leave
Debug.WriteLine("Кнопка 2 утратила фокус")
End Sub
Private Sub TextBox1_Leave( ByVal sender As Object, ByVal e As System.EventArgs) Handles TextBox1.Leave
Debug.WriteLine("Текстовое поле 1 утратило фокус")
End Sub
Я не стал писать процедуры для второго текстового поля.
Запустите проект. Обратите внимание, что первое текстовое поле уже в фокусе, о чем в окне Output вы уже имеете сообщение от вашей программы:
Текстовое поле 1 получило фокус
Щелкните по второму текстовому полю. В окне Output появляется:
Текстовое поле 1 утратило фокус
Щелкните по второй кнопке. В окне Output появляется:
Кнопка 2 получила фокус
Щелкните по первому текстовому полю. В окне Output появляется:
Кнопка 2 утратила фокус
Текстовое поле 1 получило фокус
Именно в таком порядке, а не наоборот. Пощелкайте по объектам. Понаблюдайте, что появляется при этом в окне Output и в какой последовательности.
Придаем фокус в коде. Фокус можно переводить на объект и программным путем. Дополним наш проект процедурой:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
TextBox1.Focus()
Button2.Focus()
End Sub
Запустите проект. Щелкните по первой кнопке. Фокус, вопреки ожиданиям, переместился не на нее, а на вторую кнопку. А в окне Output появляется:
Текстовое поле 1 утратило фокус
Кнопка 1 получила фокус
Кнопка 1 утратила фокус
Текстовое поле 1 получило фокус
Текстовое поле 1 утратило фокус
Кнопка 2 получила фокус
Произошло это благодаря методу Focus. Дело его простое – переводить фокус на своего хозяина. Как видите, один щелчок по кнопке может приводить сразу к нескольким событиям, следующим друг за другом в строго определенном порядке.
Не все объекты, получившие фокус, проявляют это зримо. Например, метка. Если фокус на ней, мы этого никак не заметим.
Если сломалась мышь. Фокус нужен хотя бы для того, чтобы компьютер в каждый момент времени знал, какой из элементов управления должен реагировать на клавиатуру. Если вы набираете на клавиатуре текст, то вводиться он будет только в то текстовое поле, которое обладает фокусом. Если фокус находится на кнопке, то вы можете нажимать на нее не только мышкой, но и клавишей Enter.
Переводить фокус с одного объекта на другой вы можете вручную клавишей Tab. При этом фокус останавливается только на тех объектах, для которых это имеет смысл. Так, метка упомянутым способом фокуса не получит. Порядок пробегания определяется численным значением свойства TabIndex каждого элемента управления. Его вы можете видеть в окне свойств. Значение 0 получает элемент управления, первым появившийся на форме при проектировании, 1 – второй и т.д. Фокус переходит от объекта к объекту в порядке возрастания этого значения. Так, в предыдущем примере, создавая проект, я на пустой форме первым разместил текстовое поле 1, затем – текстовое поле 2. Поэтому при запуске проекта в фокусе сразу же оказалось текстовое поле 1, а после нажатия Tab – текстовое поле 2.
Вы можете изменить этот порядок, изменив некоторым объектам значения свойства TabIndex. С несколько большим комфортом вы сможете изменить их, зайдя во View ® Tab Order и пощелкав мышкой по элементам управления. Только не забудьте потом выйти тем же путем: View ® Tab Order.
Вы можете запретить фокусу останавливаться на объекте при нажатии Tab, установив в False его свойство TabStop.
Между кнопками фокус можно перемещать и клавишами перемещения курсора.
Основные события, связанные с мышью
Создадим проект с кнопкой. Зайдем в окно кода и заглянем в события, связанные с формой. Среди них отыщем события, относящиеся к мыши. Нас интересуют такие: Click (щелчок), DoubleClick (двойной щелчок), MouseDown (нажали клавишу мыши), MouseUp (отпустили клавишу мыши), MouseEnter (мышь появилась над формой), MouseLeave (мышь покинула форму), MouseMove (сдвинули мышь).
Проверим работу этих событий, для чего введем такой код:
Private Sub Form1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Click
Debug.WriteLine("Сработало событие Click")
End Sub
Private Sub Form1_DoubleClick(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.DoubleClick
Debug.WriteLine("Сработало событие DoubleClick")
End Sub
Private Sub Form1_MouseDown(ByVal sender As Object, ByVal e As MouseEventArgs) _
Handles MyBase.MouseDown
Debug.WriteLine("Сработало событие MouseDown")
End Sub
Private Sub Form1_MouseUp(ByVal sender As Object, ByVal e As MouseEventArgs) Handles MyBase.MouseUp
Debug.WriteLine("Сработало событие MouseUp")
End Sub
Private Sub Form1_MouseEnter(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.MouseEnter
Debug.WriteLine("Сработало событие MouseEnter")
End Sub
Private Sub Form1_MouseLeave(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.MouseLeave
Debug.WriteLine("Сработало событие MouseLeave")
End Sub
Private Sub Form1_MouseMove(ByVal sender As Object, ByVal e As MouseEventArgs) _
Handles MyBase.MouseMove
Debug.WriteLine("Сработало событие MouseMove")
End Sub
Запустите проект. Сделайте окно Output повыше размером. Перемещая мышь и щелкая разными ее кнопками, а также перемещая мышь при нажатых кнопках, наблюдайте в окне Output порядок наступления событий. Обратите внимание, что вне формы и над кнопкой события не срабатывают. Вот необходимые пояснения:
Подробности событий мыши. Класс MouseEventArgs
Мы с вами бесчисленное количество раз писали процедуру
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
и ни разу не заглядывали внутрь скобок. А видим мы там два параметра процедуры: один с именем sender, другой – e. Первый имеет тип Object и обозначает объект, с которым случилось событие, в нашем случае – Button1. Второй имеет тип EventArgs. Это некий общий тип для второго параметра и на нем мы останавливаться не будем. Рассмотрим лучше заголовки процедур для событий MouseDown, MouseUp и MouseMove. Например:
Private Sub Form1_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) _
Handles MyBase.MouseDown
События эти похожи и параметры у всех трех одинаковые. Мы уже сейчас можем извлечь пользу из параметра e, являющегося объектом класса MouseEventArgs. Польза от него та, что он содержит информацию о событии, например, какая кнопка мыши была нажата и какие у мыши в этот момент были координаты. Информация эта находится в нескольких свойствах объекта MouseEventArgs. Приведу те, что нас интересуют:
| Свойства | Смысл | Тип значения | |||
| X, Y | Координаты острия курсора мыши в момент события | Integer | |||
| Button | Какая именно из кнопок мыши была нажата | Перечисление MouseButtons со значениями: Left (левая), Right (правая), Middle (средняя), None (никакая) и пара значений для пятикнопочной мыши. | |||
| Clicks | Равняется 0 (если не было щелчка), 1 (если был одинарный щелчок) или 2 (если был двойной щелчок). | Integer |
Кроме этого класс MouseEventArgs поддерживает работу с колесом мыши.
Для того, чтобы понять и проверить смысл этих свойств, прочтите (чтобы понять) и запустите (чтобы проверить) такую программу:
Private Sub Form1_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) _
Handles MyBase.MouseDown
Debug.WriteLine(e.X & " " & e.Y) 'Печатаем координаты события
Debug.WriteLine(e.Button.ToString) 'Печатаем английское название кнопки
If e.Button = MouseButtons.Left Then Debug.WriteLine("Нажата левая кнопка мыши")
If e.Button = MouseButtons.Right Then Debug.WriteLine("Нажата правая кнопка мыши")
If e.Button = MouseButtons.Middle Then Debug.WriteLine("Нажата средняя кнопка мыши")
End Sub
Private Sub Form1_MouseUp( ByVal sender As Object, ByVal e As MouseEventArgs) Handles MyBase.MouseUp
Debug.WriteLine(e.X & " " & e.Y) 'Печатаем координаты события
Debug.WriteLine(e.Button.ToString) 'Печатаем английское название кнопки
If e.Button = MouseButtons.Left Then Debug.WriteLine("Отпущена левая кнопка мыши")
If e.Button = MouseButtons.Right Then Debug.WriteLine("Отпущена правая кнопка мыши")
If e.Button = MouseButtons.Middle Then Debug.WriteLine("Отпущена средняя кнопка мыши")
End Sub
Private Sub Form1_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) _
Handles MyBase.MouseMove
Debug.WriteLine(e.X & " " & e.Y) 'Печатаем координаты события
Debug.WriteLine(e.Button.ToString) 'Печатаем английское название кнопки
If e.Button = MouseButtons.Left Then Debug.WriteLine("Удерживается нажатой левая кнопка мыши")
If e.Button = MouseButtons.Right Then Debug.WriteLine("Удерживается нажатой правая кнопка мыши")
If e.Button = MouseButtons.Middle Then Debug.WriteLine("Удерживается нажатой средняя кнопка мыши")
If e.Button = MouseButtons.None Then Debug.WriteLine("Не нажата ни одна кнопка мыши")
End Sub
Пояснения: Здесь я использовал свойственный объектам метод ToString, который возвращает в виде вразумительной строки имя или значение объекта.
Пощелкайте в разных местах формы всеми тремя кнопками мыши. Повозите мышь над формой, удерживая нажатой ту или иную кнопку. В окне Output вы будете наблюдать последовательность сообщений примерно такого вида:
264 24
None
Не нажата ни одна кнопка мыши
264 24
Left
Нажата левая кнопка мыши
337 83
Left
Удерживается нажатой левая кнопка мыши
337 83
Left
Отпущена левая кнопка мыши
341 83
None
Не нажата ни одна кнопка мыши
Пояснения здесь излишни.
Итоги. В тонкости я, конечно, не вникал, но полученной информации о мыши вполне достаточно для решения реальных задач.
Две задачи: Глаз-ватерпас и Мышка-карандаш
Глаз-ватерпас. Создадим программу на определение точности глаза и руки: Поместим на форму кнопку. При нажатии кнопки возникает и тут же исчезает в случайном месте формы маленькая окружность. Вы должны поточнее щелкнуть мышкой там, где она мелькнула (как можно ближе к ее центру). После щелчка происходит вот что: становится видимой исчезнувшая окружность, а на месте, где вы щелкнули, тоже возникает окружность (раза в два поменьше той первой, чтобы вы на глаз могли их различить). Это позволяет вам наглядно определить их близость. Кроме этого, компьютер сообщает вам, на каком точно расстоянии от центра исчезнувшей окружности было острие мышиного курсора во время щелчка.
Здесь подойдет событие MouseDown, так как оно сообщает координаты мыши во время щелчка. Вот программа для поставленной задачи:
Dim X_кружка As Integer
Dim Y_кружка As Integer
Dim Граф As Graphics = Me.CreateGraphics
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim i As Integer
Randomize()
X_кружка = 200 * Rnd() 'Определяем координаты кружка (центра кружка)
Y_кружка = 200 * Rnd()
Граф.DrawEllipse(Pens.Black, X_кружка - 10, Y_кружка - 10, 20, 20) 'Чертим кружок
For i = 1 To 3000000 : Next 'Пауза, чтобы мы успели заметить кружок
Граф.Clear(Me.BackColor) 'Очищаем форму от кружка
End Sub
Private Sub Form1_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) _
Handles MyBase.MouseDown
Граф.DrawEllipse(Pens.Black, X_кружка - 10, Y_кружка - 10, 20, 20) 'Кружок снова возникает
Граф.DrawEllipse(Pens.Black, e.X - 5, e.Y - 5, 10, 10) 'Чертим кружочек на месте щелчка
Dim Расстояние_до_кружка As Double = Math.Sqrt((e.X - X_кружка) ^ 2 + (e.Y - Y_кружка) ^ 2)
MsgBox("Промах на " & Math.Round(Расстояние_до_кружка) & " пикс")
Граф.Clear(Me.BackColor) 'Очищаем форму от кружков
End Sub
Пояснение того, как вычислялось расстояние ( для тех, кто знает теорему Пифагора): Мысленно соедините отрезком прямой центр первой окружности и точку щелчка. Проведите из концов отрезка вертикальные и горизонтальные линии. Вы легко увидите прямоугольный треугольник. Искомым расстоянием будет гипотенуза этого прямоугольного треугольника. Нетрудно заметить, что горизонтальный катет равен e.X - X_кружка, а вертикальный равен e.Y - Y_кружка (знак я не учитываю). Теорема Пифагора гласит, что квадрат гипотенузы равен сумме квадратов катетов. Отсюда, гипотенуза равна корню квадратному из суммы квадратов катетов (каковая формула и записана в программе).
Вы можете превратить эту программу в игру. Создайте сумматор расстояний, чтобы игрок видел свой накапливающийся суммарный результат. Предоставьте игроку возможность сделать, скажем, ровно 10 щелчков, после чего игра заканчивается. Цель – набрать наименьшую сумму в конце игры.
Мышка-карандаш. Вот программа, превращающая мышку в карандаш:
Private Sub Form1_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) _
Handles MyBase.MouseMove
Dim Граф As Graphics = Me.CreateGraphics
Граф.FillEllipse(Brushes.Black, e.X, e.Y, 5, 5)
End Sub
Запустите ее и медленно ведите мышкой по форме. За мышкой остается нарисованный след из множества черных кружочков диаметра 5. Кружочки рисуются так близко друг к другу, что образуют непрерывную линию. Если мышку передвигать побыстрее, то след будет прерывистым, потому что, хоть событие MouseMove наступает и часто, но все же не бесконечно часто. В 20.9.2 мы решим проблему непрерывности, хотя, собственно говоря, вы и сейчас можете это сделать.
Задание 96.
Сделайте так, чтобы мышь-карандаш рисовала только при нажатой левой клавише, что более привычно для всех пользователей компьютера. Сделайте так, чтобы при щелчке по правой клавише толщина линии возрастала на 1. Если сможете, сделайте так, чтобы при двойном щелчке по правой клавише толщина линии убывала на 1.
Работа с мышью
В 3.11 мы познакомились с некоторыми событиями, возникающими при работе с мышью. Пришла пора расширить и углубить наше знакомство.
Событие KeyPress. Класс KeyPressEventArgs. Структура Char
Создайте проект из одной формы, без единого элемента управления. Зайдите в окно кода и выберите для формы событие KeyPress. В появившуюся заготовку процедуры запишите следующий код:
Private Sub Form1_KeyPress(ByVal sender As Object, ByVal e As KeyPressEventArgs) Handles MyBase.KeyPress
Debug.Write(e.KeyChar)
End Sub
Запустите проект, пощелкайте по клавишам клавиатуры и понаблюдайте за результатами в окне Output. Каждый раз, как вы нажимаете на алфавитно-цифровую клавишу (то есть набираете на клавиатуре букву, цифру, знак препинания или другой символ), набранный символ появляется в окне Output. Попробуйте ввести заглавные буквы, русские буквы. Все получается:
qwertyQWERTY12345,.!фывапроФЫВАПРО()"№;%:?*
Более того, нажатие клавиш пробела, ввода и табуляции вызывает привычный эффект. Впечатление такое, что окно Output превратилось в окно текстового редактора.
Нажатие функциональных (F1 – F12) и многих управляющих клавиш не вызывает события KeyPress. (Управляющие клавиши – это те, что сосредоточены в основном в левом конце клавиатуры и в правой ее части левее дополнительной цифровой клавиатуры.)
Из программы видно, что символы, выводимые в окно Output, являются значениями свойства KeyChar объекта e, принадлежащего классу KeyPressEventArgs. Строго говоря, значение свойства KeyChar есть структура Char, обладающая рядом полезных методов, действие некоторых из которых видно из следующей программы:
Private Sub Form1_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) _
Handles MyBase.KeyPress
Dim Символ As Char = e.KeyChar
Debug.WriteLine("Набран символ " & Символ)
Debug.WriteLine("Это цифра? " & Char.IsDigit(Символ))
Debug.WriteLine("Это буква? " & Char.IsLetter(Символ))
Debug.WriteLine("Правда ли, что это буква или цифра? " & Char.IsLetterOrDigit(Символ))
Debug.WriteLine("Это строчная буква? " & Char.IsLower(Символ))
Debug.WriteLine("Это заглавная буква? " & Char.IsUpper(Символ))
Debug.WriteLine("Это знак препинания? " & Char.IsPunctuation(Символ))
Debug.WriteLine("Это пробел? " & Char.IsSeparator(Символ))
Debug.WriteLine("Превратить в строчную " & Char.ToLower(Символ))
Debug.WriteLine("Превратить в заглавную " & Char.ToUpper(Символ))
End Sub
Вот как отреагирует эта программа на ввод заглавной русской буквы «Ж»:
Набран символ Ж
Это цифра? False
Это буква? True
Правда ли, что это буква или цифра? True
Это строчная буква? False
Это заглавная буква? True
Это знак препинания? False
Это пробел? False
Превратить в строчную ж
Превратить в заглавную Ж
Анализировать вводимые с клавиатуры символы для управления компьютером можно, например, такими операторами:
If Символ = ":" Then Debug.WriteLine("Набрано двоеточие")
If Char.IsDigit(Символ) Then Debug.WriteLine("Набрана цифра")
События KeyDown и KeyUp. Класс KeyEventArgs
KeyDown и KeyUp. Создайте проект из одной формы, без элементов управления. Введите такой код:
Private Sub Form1_KeyPress(ByVal sender As Object, ByVal e As KeyPressEventArgs) Handles MyBase.KeyPress
Debug.WriteLine("Сработал KeyPress")
End Sub
Private Sub Form1_KeyDown(ByVal sender As Object, ByVal e As KeyEventArgs) Handles MyBase.KeyDown
Debug.WriteLine("Сработал KeyDown")
End Sub
Private Sub Form1_KeyUp(ByVal sender As Object, ByVal e As KeyEventArgs) Handles MyBase.KeyUp
Debug.WriteLine("Сработал KeyUp")
End Sub
Запустите проект, пощелкайте по клавишам клавиатуры и понаблюдайте за результатами в окне Output. Удерживайте нажатыми разные клавиши и тоже наблюдайте.
Выводы: При нажатии почти любой клавиши клавиатуры срабатывает событие KeyDown. Если при этом нажатие приводит к вводу символа, то сразу за ним срабатывает событие KeyPress. Если вы удерживаете клавишу нажатой, эта парочка срабатывает многократно и часто. При отпускании клавиши однократно срабатывает событие KeyUp. Я не рассматриваю ситуацию, когда одновременно удерживаются нажатыми две и более клавиш.
KeyCode. Какие именно клавиши клавиатуры были нажаты или отпущены, вам подскажет свойство KeyCode объекта e, принадлежащего классу KeyEventArgs. Введите такой код:
Private Sub Form1_KeyDown(ByVal sender As Object, ByVal e KeyEventArgs) Handles MyBase.KeyDown
Debug.WriteLine(e.KeyCode.ToString)
End Sub
Пояснения: Здесь я использовал свойственный объектам метод ToString, который возвращает в виде вразумительной строки имя или значение объекта.
Запустите проект, пройдитесь по всем клавишам клавиатуры (кроме тех, конечно, что выключают компьютер). Вот что вы увидите:
| Печать в окне Output | Какая клавиша была нажата | ||
| D L F1 Tab Enter Left Space D1 D2 Escape ControlKey ShiftKey Menu | D L F1 Tab Enter Стрелка влево Пробел Цифра 1 Цифра 2 Esc Ctrl Shift Alt |
И так далее. Пройдитесь по клавишам дополнительной цифровой клавиатуры в правой части клавиатуры при включенном и выключенном индикаторе NumLock.
Свойства Control, Alt, Shift. VB позволяет определить, какие из трех клавиш Ctrl, Alt, Shift удерживались нажатыми в момент срабатывания события KeyDown или KeyUp. Дополните процедуру:
Private Sub Form1_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) _
Handles MyBase.KeyDown
Debug.WriteLine(e.KeyCode.ToString & " " & e.Control
& " " & e.Alt & " " & e.Shift)
End Sub
Здесь я использовал свойства Control, Alt, Shift объекта e. Их смысл будет ясен из распечатки в окне Output. Запустите проект, нажмите и удерживайте нажатыми клавиши Alt и Shift (но не Ctrl), после чего щелкните по клавише W. Вот что вы увидите последней напечатанной строкой в окне Output:
W False True True
Все сказанное относится и к событию KeyUp.
Анализировать нажатия клавиш для управления компьютером можно, например, такими операторами:
If e.KeyCode = Keys.F2 Then Debug.WriteLine("Нажата клавиша F2")
If e.Shift Then Debug.WriteLine("Нажата клавиша Shift")
If e.Control And e.KeyCode
= Keys.Right Then Debug.WriteLine("Нажата стрелка направо при нажатой кл Ctrl")
Перечисление Keys включает в себя коды клавиш клавиатуры.
События клавиатуры у элементов управления. События, связанные с клавиатурой, имеются у многих элементов управления. Поместим на форму, к примеру, пару кнопок и текстовое поле. Вдобавок к процедуре Form1_KeyDown напишем три процедуры:
Private Sub Button1_KeyDown …
Private Sub Button2_KeyDown …
Private Sub TextBox1_KeyDown …
Запустим программу и щелкнем по какой-нибудь клавише. Какая из четырех процедур сработает? Та, чей элемент управления находится в фокусе. А поскольку один какой-нибудь элемент на форме всегда находится в фокусе, до процедуры Form1_KeyDown дело никак не доходит. Это не всегда бывает удобно. И против этого в VB есть специальный прием: свойство формы KeyPreview устанавливается в True. Это означает приказ компьютеру при нажатии на клавишу сначала вызывать событие формы, а уж потом другого объекта. Так, если в фокусе находится кнопка 2, то при нажатии на клавишу клавиатуры сначала выполняется процедура Form1_KeyDown, а сразу за ней – Button2_KeyDown. Сказанное касается большинства клавиш и элементов управления, но не всех.
«Телефонный номер». Разберем пример использования событий клавиатуры. Пусть на форме имеется текстовое поле, которое мы предназначили для ввода телефонных номеров. Сделаем текстовое поле умным – запретим ему воспринимать символы за исключением цифр, пробелов, черточек и точек. Если пользователь попытается ввести неподходящий символ, должно появляться сообщение об ошибке, а сам символ в поле появляться не должен. После окончания ввода можно нажать клавишу Enter, пусть при этом выводится сообщение «Ввод закончен».
Вот код:
Private Sub TextBox1_KeyPress(ByVal sender As Object, ByVal e As KeyPressEventArgs) _
Handles TextBox1.KeyPress
Dim С As Char = e.KeyChar 'Вводимый символ
If Char.IsDigit(С) Or Char.IsSeparator(С) Or С = "-" Or С = "." Then Exit Sub
Beep()
MsgBox("В телефонном номере можно употреблять только цифры, пробелы, черточки и точки!")
e.Handled
= True
End Sub
Private Sub TextBox1_KeyDown(ByVal sender As Object, ByVal e As KeyEventArgs) Handles TextBox1.KeyDown
If e.KeyCode = Keys.Enter Then MsgBox("Ввод закончен")
End Sub
Пояснения: Используются события KeyPress и KeyDown текстового поля. Событие KeyDown используется для обработки нажатия на клавишу Enter. Оператор If в первой процедуре предназначен для того, чтобы не создавать проблем цифрам, пробелам, черточкам и точкам. Ведь выход из процедуры (Exit Sub) и есть выход без проблем. Если символ не принадлежит к упомянутым, то выхода из процедуры не происходит и выполняются последние три строки процедуры: раздается сигнал, затем появляется сообщение, а напоследок оператор e.Handled = True заставляет компьютер считать, что неосуществленная стандартная обработка события (в частности – появление символа в текстовом поле) якобы уже произошла, а значит и символу незачем появляться в текстовом поле.
Задание 97.
«Управляемый объект». На клавиатуре имеются четыре клавиши со стрелками для управления курсором. Поместите на форму какой-нибудь элемент управления. При щелчке по любой клавише управления курсором элемент управления должен двигаться с постоянной скоростью в указанном на клавише направлении.
Задание 98.
«Светофор». Нарисуйте светофор: прямоугольник и три круга. При наборе на клавиатуре символа R светофор должен загораться красным светом, G – зеленым, Y - желтым.
Задание 99.
«Зенитка». Вверху справа налево медленно движется вражеский самолет. В подходящий момент вы нажатием любой клавиши запускаете снизу вверх зенитный снаряд. При попадании компьютер выдает соответствующее сообщение.
Не шали! Поставьте при помощи событий клавиатуры преграду тем шалунам, кто в нашем калькуляторе хочет ввести с клавиатуры в текстовое поле для результата любые символы. А потом вспомните, что это можно было сделать, просто превратив текстовое поле в метку или придав текстовому полю свойство ReadOnly.
Работа с клавиатурой
Познакомимся с событиями, связанными с клавиатурой. Их три: KeyDown (клавиша нажата), KeyUp (клавиша отпущена) и KeyPress.(ввели с клавиатуры символ).
Постановка задачи
Поставим задачу сделать игру, где наш миниатюрный гоночный автомобиль будет под управлением мыши или клавиатуры нестись от старта до финиша.
На Рис. 14.1 вы видите внешний вид игры (о красоте я не заботился, красоту вы всегда сможете добавить по вкусу):





Рис. 14.1
Процесс игры таков. После загрузки проекта вы нажимаете на кнопку Начинаем сначала. На форме появляется белое квадратное поле для гонки со случайно расположенными газонами. Машина стоит на старте. Нажатием на клавишу пробела вы даете газ и машина со старта набирает скорость. Ваша цель – любым путем побыстрее добраться до финиша. На белом асфальте вы можете газовать до предельной скорости (я выбрал ее значение равным 15). Если же ненароком попадете на газон, то на газоне ваша скорость будет очень мала (я выбрал 1). Поэтому имеет смысл по газонам ездить пореже. Направление движения может быть только горизонтальное и вертикальное, наискосок машина не движется. Выбор направления (руль) – это клавиши со стрелками. Тормоз – клавиша Ctrl. В ограждение въезжать нельзя – катастрофа. Когда приедете на финиш, посмотрите на счетчик времени и увидите ваш результат. Снова нажимайте на кнопку Начинаем сначала. Теперь расположение газонов будет другим. Сажайте за клавиатуру приятеля и смотрите, не покажет ли он время лучше, чем у вас. Можете посмотреть, кто из вас покажет лучшее время из 10 заездов.
Не удивляйтесь, что скорость на спидометре не равна пути, деленному на время. Ведь это мгновенная скорость (то есть настоящая скорость в данное мгновение), а не средняя скорость автомобиля (см. Физика, 9 класс).
Все эти правила я старался сделать как можно проще, чтобы не усложнять проект. Разобравшись в проекте, вы всегда сможете дописать процедуры, делающие процесс гонки более для вас привлекательным.
Для проекта я выбрал вариант игры с одним автомобилем. Я вам намекну, как модифицировать проект, чтобы получилась игра с двумя автомобилями. Однако помните, что для грамотного создания игры с несколькими автомобилями вам нужно будет подняться на новый уровень программирования – научиться создавать собственные классы объектов.
Делим проект на части
Создадим новый проект. Прежде чем что-либо с ним делать, мысленно разделим задачу, как положено, на части (по возможности, конечно). Мы раньше уже делили задачу на части, когда создавали будильник. Сейчас вам очень полезно перечитать тот материал. И проглядеть ту программу.
Можно заметить, что в нашем проекте нет таких независимых частей, как в будильнике. Тем не менее, удобно разделить создание проекта на 3 последовательные части:
1. Рисование всех элементов поля в результате нажатия на кнопку Начинаем сначала. Этим будет заниматься одна группа процедур.
2. Затем управление машиной во время гонки. Этим будет заниматься другая группа процедур. Мы полностью задействуем идею использования таймера так, как я ее изложил в 13.5.11. На каждом импульсе таймера автомобиль должен будет проделывать весь цикл своего функционирования.
3. И наконец – определение поведения машины на газоне, на финише и при столкновении с препятствием. Сюда же я отношу организацию счетчиков на пульте управления (время, скорость, путь) и выдачу сообщений.
Первая часть – рисуем поле для гонки
Что рисовать.
1. Сначала рисуем сам квадрат поля
2. Затем старт и финиш
3. Затем газоны
Код первой части. Разместите на форме кнопку. Дайте ей имя Кнопка_начинай_сначала. Приведу работающую версию первой части программы, а вслед за ней – пояснения.
Const Отступ As Short = 20 'Имеется в виду отступ поля от края формы слева и сверху
Const Размер_поля As Short = 500 'Поле - квадрат, это сторона квадрата
Const Высота_формы As Short = Размер_поля + 4 * Отступ 'Оставляем отступ снизу
Const Ширина_формы As Short = Размер_поля + 10 * Отступ 'Оставляем справа место для кнопок и меток
Const Размер_старта As Short = 40 'Старт - квадрат, это сторона квадрата
Const Размер_финиша As Short = 40 'Финиш - квадрат, это сторона квадрата
Dim X_старта, Y_старта, X_финиша, Y_финиша As Short 'Координаты старта и финиша
'Создаем пустую поверхность величиной с форму:
Dim Картинка As New Bitmap(Ширина_формы, Высота_формы)
Dim Гр As Graphics = Graphics.FromImage(Картинка) 'Создаем над ней объект класса Graphics
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Me.Width = Ширина_формы
Me.Height = Высота_формы
Randomize() 'Нам придется рисовать случайные газоны
End Sub
Private Sub Кнопка_начинай_сначала_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles Кнопка_начинай_сначала.Click
Гр.Clear(Color.LightGray) 'Стираем светло-серым цветом предыдущую картинку, если была
Рисуем_поле()
Рисуем_старт()
Рисуем_финиш()
Рисуем_газоны()
Me.BackgroundImage = Картинка 'Придаем полю картинку, нарисованную в памяти
Будем действовать, как сказано в 12.4. Организуем в памяти «пустой лист для рисования». Для этого предназначены строки 8-10.
Загружаем форму. Содержимое процедуры Form1_Load ясно без пояснений. В эту процедуру программисты всегда вставляют то, что должно быть сделано один раз за все время выполнения проекта, причем в самом начале. После загрузки формы мы ничего на ней не видим, кроме кнопки. Эта процедура у нас еще будет расти.
Кнопка_начинай_сначала. Процедура Кнопка_начинай_сначала_Click начинается с того, что мы заполняем картинку в памяти светло-серым цветом. Это цвет ограждения в наших гонках. Дальше идет обращение к процедуре Рисуем_поле, которая просто рисует на этом сером фоне белый квадрат поля для гонки. Дальше идет обращение к трем процедурам рисования старта, финиша и газонов. Картинка в памяти готова. Предпоследняя строка процедуры делает эту картинку значением свойства формы BackgroundImage, то есть она становится «фоном» этой формы. Я мог поступить по-другому и перенести эту картинку на поверхность формы методом DrawImage класса Graphics, но в этом случае мне не удалось бы привычными средствами сделать PictureBox с изображением машины прозрачным, ведь так называемая «прозрачность» работает только по отношению к BackgroundImage. К тому же я предпочел не заботиться о перерисовке. Запустив проект, растяните форму и вы увидите, что картинка покрывает форму мозаикой. Последняя строка процедуры «проявляет» новый «фон» на форме, перерисовав ее оператором Me.Refresh().
Рисуем поле, старт и финиш. Содержимое соответствующих процедур очевидно. В формулах, определяющих координаты старта и финиша, вы легко разберетесь самостоятельно.
Рисуем газоны. Здесь в цикле рисуется 30 газонов, в каждой итерации по одному. Вы видите, что некоторые константы и переменные я объявил внутри процедуры. Так поступают, когда знают, что их значения нигде, кроме как в этой процедуре, не нужны. Значения чисел и вид формул для X_газона и Y_газона я выбрал так, чтобы газоны получались не слишком большие и не накладывались ни на ограждения, ни на старт с финишем. К тому же слишком большие газоны сольются в один большой газон и между ними нельзя будет проехать. Вид формул не принципиален, и если вам не хочется разбираться в них, можете их просто списать или придумать свои.
Результат. После ввода всего вышеприведенного кода у вас при каждом нажатии на кнопку Начинаем сначала должны рисоваться поле, старт, финиш и новая конфигурация газонов.
Вторая часть – управляем машиной
Если с первой частью все было относительно просто, то про вторую стоит поговорить подробнее. Фактически, нам нужно будет создавать автомобиль, как раньше мы создавали будильник. Не имея программистского опыта, мы попытаемся использовать житейский опыт касательно того, как автомобиль устроен. Причем применительно к задачам проекта. Так, цвет сиденья в этом смысле нам не очень важен. А важно нам управлять скоростью и направлением движения (этим в обычном автомобиле занимаются руль и педали газа и тормоза). А еще важно, чтобы автомобиль чувствовал, «куда он въехал» (газон, ограждение, финиш) и вел себя соответственно (этим мы займемся в третьей части).
Для начала поместим на форму маленький PictureBox. Это ваша машина. Так и назовем его – Машина. Поставим задачу управлять им с клавиатуры как написано в задании на игру:
Направление движения может быть только горизонтальным и вертикальным, наискосок машина не движется. Выбор направления – это клавиши со стрелками. Тормоз – клавиша Ctrl. Газ – клавиша пробела. Щелчок по клавише газа или тормоза увеличивает или уменьшает скорость на какое-то значение.
Загляните в Задание 112. Там мы уже решали задачу движения объекта по форме. Если вы ее не решили, то решите или на худой конец загляните в ответ и разберитесь в нем.
Код второй части. Приведу код, который заведует управлением машиной. Этим кодом нужно дополнить первую часть программы, чтобы получилась работающая версия проекта. Вслед за кодом я привожу пояснения. Из процедур, входящих в первую часть, я здесь перепишу только две: Form1_Load и Кнопка_начинай_сначала_Click.
Dim Цвет_фона As Color = Color.White 'Цвет фона при рисовании машины
Dim Исходная_машина As New Bitmap("Машина.BMP") 'Создаем исходное изображение машины
'Определяем 4 рабочих изображения машины:
Dim Машина_налево, Машина_вверх, Машина_направо, Машина_вниз As Bitmap
Dim x, y As Short 'Горизонтальная и вертикальная координаты автомобиля
Dim Шаг As Short 'Шаг численно равен перемещению автомобиля по форме на каждом такте таймера
Dim Газ As Boolean = False 'Нажимаем ли мы на газ
Dim Тормоз As Boolean = False 'Нажимаем ли мы на тормоз
Enum типРуль 'Куда едем
вверх
влево
вниз
вправо
End Enum
Dim Руль As типРуль
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
'……………здесь расположены уже знакомые нам предыдущие строки процедуры……………
Создаем_изображения_машины()
Машина.BackColor = Color.Transparent
Me.KeyPreview = True 'Чтобы машина отзывалась на клавиатуру
txtВремя.ReadOnly = True
End Sub
Private Sub Кнопка_начинай_сначала_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles Кнопка_начинай_сначала.Click
'……………здесь расположены уже знакомые нам предыдущие строки процедуры……………
Ставим_машину_на_старт()
txtВремя.Focus()
End Sub
Sub Создаем_изображения_машины()
Создаем_изображение(Машина_налево, RotateFlipType.RotateNoneFlipNone)
Создаем_изображение(Машина_вверх, RotateFlipType.Rotate90FlipNone)
Создаем_изображение(Машина_направо, RotateFlipType.Rotate180FlipNone)
Создаем_изображение(Машина_вниз, RotateFlipType.Rotate270FlipNone)
End Sub
Sub Создаем_изображение(ByRef Автомобиль As Bitmap, ByVal Поворот As RotateFlipType)
Автомобиль = New Bitmap(Исходная_машина) 'Порождаем новый Bitmap из исходного
Автомобиль.RotateFlip(Поворот) 'Поворачиваем его
Автомобиль.MakeTransparent(Цвет_фона) 'Делаем фон автомобиля прозрачным
End Sub
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
Изменяем_скорость()
Выбираем_куда_ехать_и_делаем_шаг()
Машина.Left = x : Машина.Top = y 'Передвигаем PictureBox
End Sub
Sub Ставим_машину_на_старт()
x = X_старта : y = Y_старта ' Координаты машины приравниваются координатам точки старта
Шаг = 0 'На старте стоим, а не едем
Руль = типРуль.влево 'Это чтобы машина знала, что когда стартуем, нужно ехать влево
Машина.Image = Машина_налево 'Ориентируем машину налево
End Sub
Sub Изменяем_скорость()
If Газ Then Шаг = Шаг + 1
If Тормоз Then
Шаг = Шаг – 2 'Потому, что тормоз действует быстрее газа
'В результате быстрого торможения скорость может стать отрицательной, что и предотвращается:
If Шаг < 0 Then Шаг = 0
End If
'Чтобы во время набора скорости и торможения приходилось без перерыва жать на педаль:
Газ = False : Тормоз = False
End Sub
Sub Выбираем_куда_ехать_и_делаем_шаг()
Select Case Руль
Case типРуль.вверх : Машина.Image = Машина_вверх : y = y - Шаг
Case типРуль.вниз : Машина.Image = Машина_вниз : y = y + Шаг
Case типРуль.влево : Машина.Image = Машина_налево : x = x - Шаг
Case типРуль.вправо : Машина.Image = Машина_направо : x = x + Шаг
End Select
End Sub
'Обработка события - нажатия клавиши на клавиатуре для управления автомобилем:
Private Sub Form1_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) _
Handles MyBase.KeyDown
Select Case e.KeyCode
Case Keys.Left : Руль = типРуль.влево
Case Keys.Right : Руль = типРуль.вправо
Case Keys.Up : Руль = типРуль.вверх
Case Keys.Down : Руль = типРуль.вниз
Case Keys.Space : Газ = True
Case Keys.ControlKey : Тормоз = True
End Select
End Sub
Создаем изображения машины. Пустой PictureBox передвигать по форме неинтересно, поэтому рисуем в каком-нибудь графическом редакторе маленькую машинку (вид сверху) точно так же, как мы рисовали летающую тарелку для мультфильма. Сохраняем ее.
Во время гонок машина может ехать в 4 направлениях: влево, вправо, вверх, вниз. Значит, для придания естественности игре (чтобы машина не ехала «боком»), мы должны иметь 4 ориентации машины, между которыми будем в нужные моменты переключаться. Поэтому нам нужны 4 одинаковых изображения машины, повернутые в разные стороны. Для этого вовсе не нужно рисовать 4 рисунка и сохранять 4 файла (хотя и это можно, конечно). Вспомним, что метод RotateFlip умеет поворачивать картинки под прямыми углами. Его и используем.
Обращение к процедуре Создаем_изображения_машины мы находим, естественно, в процедуре Form1_Load, так как создать эти изображения достаточно один раз. Здесь же мы видим, как следующая строка делает прозрачным фон элемента управления Машина. Тело процедуры Создаем_изображения_машины, как видите, состоит из 4 операторов, каждый из которых создает одно из 4 изображений. Каждый из этих операторов есть в свою очередь обращение к процедуре Создаем_изображение. Рассмотрим ее получше.
В верхней части окна кода мы видим строки:
Dim Исходная_машина As New Bitmap("Машина.BMP") 'Создаем исходное изображение машины
'Определяем 4 рабочих изображения машины:
Dim Машина_налево, Машина_вверх, Машина_направо, Машина_вниз As Bitmap
Первая из них на основе нарисованной нами машины создает объект Исходная_машина типа Bitmap, который послужит исходным материалом для упомянутых 4 изображений. Каждое из них в дальнейшем получится поворотом картинки Исходная_машина на нужный угол. Тут же объявляются (но не создаются) объекты типа Bitmap для этих 4 изображений.
У процедуры Создаем_изображение два параметра: Первый из них (Автомобиль) как раз и является тем изображением, которое создает процедура. Он имеет тип Bitmap и обозначен словом ByRef, а не ByVal, так как в процессе работы изображение и создается и меняется. Второй параметр (Поворот) имеет тип перечисления RotateFlipType, которое как раз и используется при повороте картинок. Когда я рисовал машину, я нарисовал ее глядящей влево, отсюда становятся понятны значения второго параметра в обращении к процедурам.
Для формирования требуемого изображения машины нужно проделать 3 вещи:
Именно эти 3 вещи делают 3 оператора процедуры. Взгляните на них. Если вы не привыкли еще к параметрам объектного типа или вообще не понимаете текста этих операторов, я «перепишу» их по-другому. Например, для случая выполнения 2-го оператора процедуры Создаем_изображения_машины я просто для наглядности подставлю в них вместо параметров их значения:
Машина_вверх = New Bitmap(Исходная_машина) 'Порождаем новый Bitmap из исходного
Машина_вверх.RotateFlip(RotateFlipType.Rotate90FlipNone) 'Поворачиваем его
Машина_вверх.MakeTransparent(Цвет_фона) 'Делаем фон автомобиля прозрачным
Управляем машиной. Поместим на форму таймер. Настроим его интервал в пределах от 10 до 100 (по вкусу) На каждом импульсе таймера автомобиль должен будет проделать весь цикл своего функционирования. Из этого цикла нам для нормального передвижения машины достаточно пока озаботиться тремя вещами. Автомобиль должен:
Эти три вещи как раз и выполняются операторами, которые мы видим в процедуре Timer1_Tick. Разберемся в них, но сначала заглянем в верхнюю части окна кода, где мы видим объявления переменных x, y, Шаг, Газ, Тормоз и Руль. Зачем нам нужны последние три переменные? Нельзя ли попроще: управлять движением объектов с клавиатуры безо всяких переменных. Попытаться можно, и программа поначалу получится короче. Но с ростом сложности проекта будут расти неудобства. Например, машина не будет знать направления своего движения, а без этого трудно будет запрограммировать отскок от ограждения.
С учетом вышесказанного проглядите процедуру Form1_KeyDown. Она проста и в комментариях не нуждается.
События, связанные с клавиатурой, имеются у многих элементов управления. Почему мы выбрали события формы? Выбор какого-нибудь элемента управления был бы для нашей игры неудобен. Если мы для программирования реакции автомобиля на нажатия клавиш выберем, например, процедуру Private Sub Button1_KeyDown, то во время гонки мы не сможем щелкать по другим кнопкам, кроме Button1, так как иначе Button1 выйдет из фокуса и автомобиль перестанет реагировать на клавиши.
Изменяем скорость. Теперь посмотрим, как регулируется скорость. Для этого заглянем в процедуру Изменяем_скорость. Действие ее полностью определяется значением переменных Газ и Тормоз. Если это газ, то на данном такте таймера шаг, а значит и скорость, возрастет на 1. Если тормоз – упадет на 2 (потому что тормоз обычно действует сильнее газа). Отрицательный шаг в результате торможения означал бы задний ход, что неестественно, поэтому в программе это предотвращается.
Как видите, значения переменных Газ и Тормоз в конце процедуры принудительно приравниваются False. Это значит, что щелчок по клавише газа или тормоза приводит только к однократному увеличению или уменьшению скорости. Ведь на следующем тике таймера переменные Газ и Тормоз будут иметь значение False, а значит процедура не приведут к изменению шага. Значит на следующем такте таймера скорость не изменится. Чтобы она изменилась, нам нужно еще раз нажать на клавишу газа или тормоза. Обычно поступают по-другому – просто удерживают клавишу нажатой, при этом событие KeyDown возникает несколько раз в секунду и скорость меняется достаточно быстро. Это соответствует механике реального автомобиля – чтобы набирать скорость, нужно непрерывно и усиленно нажимать на педаль газа, а чтобы тормозить – тормоза.
Выбираем куда ехать и делаем шаг. Именно эта процедура задает ориентацию машины и направление движения. Загляните в нее. Ее дело – чувствовать одно из 4 значений переменной Руль и в соответствии с этим значением делать две вещи: поворачивать машину в нужном направлении и менять в этом направлении ее координату. Само же изображение автомобиля на экране прыгнет на указанную координату мгновением позже, на последней строке процедуры Timer1_Tick.
В этой процедуре каждая строка оператора Select Case присваивает свойству Image нашего элемента управления Машина значение одного из 4 изображений машины, а именно как раз того, которого требует нажатие клавиши со стрелкой на клавиатуре. При этом в выбранном направлении изменяется и координата x или y. В результате мы видим, что автомобиль глядит в ту же сторону, куда он едет.
Последняя строка процедуры Timer1_Tick перемещает элемент управления Машина на экране в соответствии с вычисленными координатами x и y.
Ставим машину на старт. Когда мы нажимаем кнопку Начинаем сначала, машина, где бы она ни была и что бы ни делала, должна прыгнуть на старт и замереть, глядя влево. Именно это поведение обеспечивают все пять операторов процедуры Ставим_машину_на_старт. И именно поэтому обращение к этой процедуре включено в процедуру Кнопка_начинай_сначала_Click.
Чтобы машина реагировала на клавиатуру. Я уже говорил ранее, что поскольку один какой-нибудь элемент управления на форме всегда находится в фокусе, до процедуры Form1_KeyDown дело не дойдет. Это значит, что наша машина не будет реагировать на клавиши. Для борьбы с этим в процедуре Form1_Load свойство формы KeyPreview установлено в True. Это означает приказ компьютеру при нажатии на клавишу вызывать событие формы, а уж потом другого объекта. Но этого недостаточно. И вот почему. При запуске проекта фокус автоматически устанавливается на кнопку «Начинаем сначала». Первое нажатие во время гонки на клавишу со стрелкой приводит не к выбору направления машиной, а к перескакиванию фокуса с кнопки на другой объект формы. Если же вы вместо этого нажимаете на пробел, то машина дает газ, но радоваться тоже рано. Кнопки воспринимают пробел и Enter, как приказ на нажатие, и поэтому кнопка «Начинаем сначала» тут же сама собой нажимается и получается, что немедленно после старта газоны перерисовываются, а машина снова прыгает на старт.
Чтобы прекратить это безобразие, поместим на форму текстовое поле. Позже оно нам пригодиться для отображения счетчика времени, а сейчас у нас другая забота: увести от вредной кнопки фокус куда-нибудь подальше. Текстовое поле ведет себя смирно, назовем его txtВремя и включим в процедуру оператор, уводящий фокус с кнопки на это поле:
txtВремя.Focus()
Помогло. При нажатии клавиш со стрелками фокус не покидает текстовое поле. Но осталась одна шероховатость. Если в текстовом поле есть текст, то при нажатии клавиш со стрелками текстовый курсор будет слоняться по текстовому полю влево-вправо, а при нажатии пробела – вставлять в текст пробел. Чтобы от этого избавиться и чтобы предотвратить в дальнейшем возможность нечаянно с клавиатуры испортить показания счетчика времени, помещаем в процедуру Form1_Load оператор
txtВремя.ReadOnly = True
Это означает, что содержимое текстового поля можно только читать, но вручную не менять. Изменения возможны только программным путем.
Теперь все нормально.
Результат. После ввода всего вышеприведенного кода у вас при каждом нажатии на кнопку Начинаем сначала должны рисоваться поле, старт, финиш и новая конфигурация газонов, машина должна нормально вставать на старт, а при нажатии нужных клавиш должна нормально ускоряться, тормозить и ездить по всей форме во всех направлениях, не разбирая пока, где асфальт, где преграды и все остальное.
Третья часть – Поведение машины, организация счетчиков и пр.
Я уже говорил, что на каждом импульсе таймера автомобиль должен проделать весь цикл своего функционирования. Из этого цикла нам осталось рассмотреть два дела. Автомобиль должен:
Код третьей части. Приведу последнюю порцию кода в нашем проекте. Этим кодом нужно дополнить первые две части программы, чтобы получилась работающая версия проекта.
Dim Шаг, Время, Путь As Integer
'Чтобы секундомер судьи запускался автоматически, когда мы стартуем:
Dim Секундомер_запущен As Boolean
Private Sub Кнопка_начинай_сначала_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles Кнопка_начинай_сначала.Click
'……………здесь расположены уже знакомые нам предыдущие строки процедуры……………
Me.KeyPreview = True 'чтобы машина слушалась руля и педалей
Секундомер_запущен = False 'Мы еще не стартовали, секундомер не пущен,
Шаг = 0 : Время = 0 : Путь = 0 'на приборах - нули
lbl_Сообщение.Text = "" 'Пока никакого сообщения нет
End Sub
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
Определяем_где_мы()
Изменяем_скорость()
Выбираем_куда_ехать_и_делаем_шаг()
Машина.Left = x : Машина.Top = y
Отображаем_информацию()
End Sub
Sub Определяем_где_мы()
Dim Цвет_под_автомобилем As Color = Картинка.GetPixel(x, y)
If Цвет_под_автомобилем.ToArgb = Color.Green.ToArgb And Not Шаг < 1 _
Then Шаг = 1 'На газоне скорость мала
If Цвет_под_автомобилем.ToArgb = Color.Red.ToArgb _
Then Приехали() : lbl_Сообщение.Text = "Финиш!"
If Цвет_под_автомобилем.ToArgb = Color.LightGray.ToArgb _
Then Приехали() : lbl_Сообщение.Text = "Врезались в ограждение!"
End Sub
Sub Приехали()
Шаг = 0 'Раз приехали, надо остановиться, …
Секундомер_запущен = False 'выключить секундомер и сделать так, …
Me.KeyPreview = False ' чтобы машина НЕ слушалась руля и педалей
End Sub
Sub Изменяем_скорость()
Dim Максимальная_скорость As Short = 15 'Быстрее мотор не тянет
If Газ And Шаг < Максимальная_скорость Then Шаг = Шаг + 1
'……………здесь расположены уже знакомые нам последующие строки процедуры………
End Sub
Private Sub Отображаем_информацию()
'Счетчик времени запускается только тогда, когда мы стартуем:
If Секундомер_запущен Then Время = Время + 1 'Время – число импульсов таймера.
txtВремя.Text = "Время = " & Время 'Показания секундомера
lbl_Скорость.Text = "Скорость = " & Шаг 'Показания спидометра – скорость (Шаг)
Путь = Путь + Шаг 'Путь – это сумма шагов
lbl_Путь.Text = "Путь = " & Путь 'Показания спидометра - путь
End Sub
Private Sub Form1_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) _
Handles MyBase.KeyDown
Секундомер_запущен = True 'Секундомер запускается от нажатия на любую клавишу
Select Case e.KeyCode
'……………здесь расположены уже знакомые нам строки процедуры……………
End Select
End Sub
Поведение машины на газоне, на ограждении, на финише. Разместите на форме метку, в которую будут помещаться сообщения о том, что мы находимся на финише или врезались в ограждение. Назовите ее lbl_Сообщение.
Рассмотрим главную процедуру проекта – Timer1_Tick. В соответствии с двумя дополнительными делами, которые должен выполнить автомобиль, в процедуру добавились два оператора:
Определяем_где_мы()
'……………
Отображаем_информацию()
Поговорим о процедуре Определяем_где_мы. Ее задача – задать реакцию автомобиля на три ситуации: попадание на газон, на финиш и на ограждение. Метод, при помощи которого автомобиль определяет, где он, самый простой – GetPixel. Его мы разобрали в 12.7.4. Поскольку газон я рисовал зеленым, финиш – красным , а ограждение – светло-серым (LightGray), то метод GetPixel выдает на них разные результаты. Операторы If эти результаты анализируют и задают реакцию автомобиля. Как видите, на газон машина реагирует только установкой скорости = 1 (если она уже не была меньше 1), на два других цвета она выполняет процедуру Приехали и выдает подходящее сообщение в метку lbl_Сообщение.
Немного о грамматике. Мы не могли записать
If Цвет_под_автомобилем = Color.Green
так как VB не может устанавливать с помощью знака равенства равенство таких объектов, как цвет. Пришлось предварительно методом ToArgb преобразовывать цвета к специальному представлению ARGB, которое допускает подобное сравнение:
If Цвет_под_автомобилем.ToArgb = Color.Green.ToArgb ……
Цель процедуры Приехали ясна: остановить машину, заблокировать руль и педали и остановить секундомер, чтобы мы могли поглядеть на результат. Раз так, то при следующем нажатии на кнопку Начинаем сначала руль и педали должны быть разблокированы, для чего мы переносим оператор
Me.KeyPreview = True 'чтобы машина слушалась руля и педалей
из процедуры Form1_Load в процедуру Кнопка_начинай_сначала_Click.
Организация счетчиков. В дополнение к текстовому полю txtВремя, в котором отображается текущее время пробега, разместите на форме метку lbl_Скорость, в которой будет отображаться текущая скорость (Шаг) машины, и метку lbl_Путь, в которой будет отображаться путь, пройденный машиной.
Вычислением этих трех величин и их отображением занимается процедура Отображаем_информацию. Она выполняется на каждом тике таймера. Заглянем в нее. Содержание ее очевидно. В пояснении нуждаются только моменты запуска и остановки секундомера. Когда мы нажимаем на кнопку Начинаем сначала, все три упомянутые величины устанавливаются в 0, а переменная Секундомер_запущен – в False (см. текст процедуры Кнопка_начинай_сначала_Click). Поэтому после нажатия на кнопку секундомер стоит на нуле. Как только мы нажимаем клавишу газа или какую-нибудь другую, секундомер запускается (см. текст процедуры Form1_KeyDown). Останавливается он после этого только в том случае, если машина «приехала» или мы нажали на кнопку Начинаем сначала.
Упомяну еще, что переменная Время является не временем в секундах, а количеством тактов таймера, прошедших со старта.
Максимальная скорость. При большой скорости машина делает огромные шаги от одного такта таймера к другому. А ведь цвет под собой она ощущает не непрерывно, а только на тактах таймера. Значит она может перепрыгнуть через тоненький газон, не заметив его. То же относится к финишу и к ограждению. Если шаг превысит размер финиша, то машина и его может перепрыгнуть, не заметив. Машина может перепрыгнуть через тонкое ограждение и упереться в край формы, при этом VB выдаст ошибку (кстати – почему?). Значит, нужно ограничить скорость машины. Поскольку размер финиша равен 40, а минимальная толщина ограждения у нас равна 20, то я выбрал максимальный шаг = 15. Простая механика ограничения приведена в процедуре Изменяем_скорость. А через газоны пусть перепрыгивает!
Резюме. Ну вот, кажется, и все пояснения. Организовывая переменные для хранения информации о состоянии машины и создавая много маленьких процедур с удачными длинными названиями, мы сделали программу более читабельной и открытой для усовершенствования. Попробуйте обойтись малым числом длинных процедур – и вы увидите, что программа, может быть и став покороче (а вполне может быть и нет), потеряла четкость, понятность и легкость модификации.
Недоработки проекта
Ошибок в проекте я не вижу (при отладке я выловил все, что заметил, но это не значит, что их там больше нет!). Замеченные же мной недоработки вызваны в основном нежеланием увеличивать размер кода и заключаются в следующем:
1. Сразу же после загрузки проекта, но до нажатия на кнопку Начинаем сначала, машина торчит в верхнем левом углу формы. Это непорядок. Бороться с этим можно по-разному. Сделайте, например, машину невидимой до нажатия на кнопку Начинаем сначала.
2. Координатами (х, у) машины считается ее левый верхний угол, а не центр. Это приводит к некоторой неестественной асимметрии поведения машины при поворотах и переезде с цвета на цвет. Причина этого – слишком простая строка в процедуре Timer1_Tick:
Машина.Left = x : Машина.Top = y 'Передвигаем PictureBox
Немного повозившись, вы сможете исправить положение, усложнив формулы в этой строке. Также нужно сделать, чтобы машина стояла не в углу старта, а посредине.
3. Сам Бог велел добавить в проект управление машиной с помощью мыши. Например, щелчок мышью в стороне от машины или сзади вызывает поворот машины в соответствующую сторону. Щелчок спереди – газ, щелчок на правую клавишу мыши в любой точке формы – тормоз. Для этого вам нужно будет написать процедуру Form1_MouseDown, придавая в ней нужные значения переменным Руль, Газ и Тормоз аналогично тому, как это делает процедура Form1_KeyDown. Вам придется поразмыслить, как правильно сравнивать координаты машины и мыши, чтобы добиться нужного результата.
Задание 100.
Исправьте недоработки проекта.
Гонки двух автомобилей
Если вы хотите играть вместе с другом, вам нужны гонки двух машин. Несмотря на то, что ничего нового вам придумывать не придется, размер программы вырастет. Вам придется иметь по два экземпляра переменных, описывающих поведение каждой машины. Например, вместо переменной Руль вам придется иметь две переменные – Руль1 и Руль2. Чтобы не писать по два экземпляра процедур, задающих поведение каждой машины, вам нужно будет снабдить эти процедуры параметрами. Процедуру Form1_KeyDown вам придется дополнить новыми клавишами для управления второй машиной. При этом нужно учесть, что удерживать клавиши нажатыми будет теперь нельзя без усложнения программы. А если у вас гонки 10 автомобилей? Проблемы нарастают.
VB конечно же предлагает универсальное решение этой проблемы. Он, как и все серьезные и мощные языки программирования, является объектно-ориентированным языком. В частности, имеется в виду, что он позволяет программисту создавать собственные классы объектов. Научившись создавать собственные классы, вы сможете создать класс «автомобиль» и пользоваться каким угодно числом экземпляров этого класса. Программу поведения автомобиля вы пишете один раз и только для одного автомобиля. Остальные автомобили будут автоматически пользоваться ей же. Как все это делается, описывается в Глава 22. , но вам пока рано читать тот материал.
Задания на проекты
Нельзя научиться кататься на велосипеде, только следя за тем, как катаются другие. Нужно самому сесть на велосипед. Вы не научитесь создавать проекты, только разбирая их создание по книжке. Нужно создать свой собственный проект. В Задание 97 вы уже создавали небольшой проект «Шахматные часы». Пришла пора создать проект посложнее и побольше размером. Я предложу вам на выбор один из двух.
Торпедная атака. Начинается все красивой заставкой с названием игры. По щелчку мыши заставка пропадает. Компьютер спрашивает ваше имя, чтобы во время игры оно горело красивыми яркими буквами в углу экрана. В меню вы можете посмотреть правила игры, сведения об авторе, выбрать игру со звуком или без. Но вот игра началась. На экране морской пейзаж (нарисованный вами или взятый готовым). Вы – капитан подводной лодки и смотрите в перископ. Вдали экрана по горизонтали плывет вражеский корабль. У нижнего края экрана находится ваш торпедный аппарат. Вы целитесь, с клавиатуры или мышкой поворачивая торпедный аппарат. В подходящий момент времени вы нажимаете клавишу – и торпеда плывет вдаль, уменьшаясь по мере удаления. Если вы попали, то экран озаряется вспышкой от взрыва, на мгновение виден и сам взрыв, раздается коротенькая торжествующая мелодия, на экране – коротенький поздравительный текст, счетчик подбитых кораблей на экране увеличивается на 1. Если не попали, то зрительные и звуковые эффекты, сами понимаете – совсем другие. В любом случае уменьшается на 1 счетчик оставшихся торпед. Затем плывет следующий корабль. И так далее. Когда торпеды у вас кончатся (скажем, их было 10), игра заканчивается. Программа анализирует ваши успехи и в зависимости от них выдает на экран текст, скажем «Мазила!», если вы не попали ни разу из 10, или «Профи!», если вы попали 8 раз. Затем спрашивает, будете ли вы играть еще.
Помощь: Как компьютер определит, попала торпеда в корабль или не попала? Нужно в тот момент, когда торпеда доплывет до линии движения корабля, сравнить горизонтальные координаты корабля и торпеды, и если они достаточно близки, считать, что попала.
Улучшение. Если у всех кораблей будет одинаковая скорость, то попадать будет слишком просто, а значит и играть неинтересно. Сделайте скорость кораблей случайной в разумных пределах. Причем не нужно менять скорость одного и того же корабля в процессе движения. Пусть она остается постоянной, а то все будет зависеть не от вашего мастерства, а от везения. Различаются скорости только разных кораблей.
Пусть игрок в меню сможет выбирать из нескольких уровней трудности. Трудность удобнее всего увеличивать, уменьшая размеры корабля, то есть требуемую величину близости координат корабля и торпеды при определении попадания.
Теннис с компьютером. На экране теннисный корт, вид сверху (см. Рис. 14.2).

Рис. 14.2
Слева – большая ракетка компьютера, справа – маленькая ваша. Сверху и снизу – края корта. Мяч летает по корту, отскакивая от его краев по закону отражения (что такое «по закону отражения», объяснено в Задание 102). Ракетка компьютера совершает равномерные, медленные, бессмысленные возвратно-поступательные движения вверх-вниз, отскакивая от краев корта. Движением вашей ракетки управляете вы. Или с клавиатуры, или мышкой. Вы можете двигать ракетку вверх или вниз с постоянной скоростью, или останавливать ее. Влево-вправо ракетки не движутся. Наткнувшись на ракетки, мяч отскакивает. Если ракетка неподвижна, то мяч отскакивает по закону отражения. Если ракетка движется, то на направление отскока мяча немного влияет направление движения ракетки. Цель – не пропустить мяч за пределы корта. Если мяч улетит направо – очко проиграли вы, налево – проиграл компьютер.
Красивая заставка, меню, счет партий, подведение итогов, зрительные и звуковые эффекты должны быть не хуже, чем у «Торпедной атаки».
Помощь: Как компьютер определит, попал мяч в ракетку или не попал? Нужно в тот момент, когда мяч долетит до линии движения ракетки, сравнить вертикальную координату мяча с вертикальными координатами верхнего и нижнего краев ракетки, и если мяч находится между краями ракетки, считать, что попал.
Улучшение. Пусть игрок сможет в меню выбирать из нескольких уровней трудности. Трудность удобнее всего увеличивать, увеличивая скорость мяча.
Может возникнуть не совсем естественная ситуация, когда скорость мяча в результате ударов о ракетки приобретет слишком большую вертикальную составляющую. Тогда мяч будет в основном биться между верхним и нижним краями корта, лишь изредка попадая на ракетки. Возможно, имеет смысл вертикальную составляющую скорости ограничить.
Чтобы избежать бесконечной нудной перекидки мяча, можно при отскоке от движущейся ракетки увеличивать горизонтальную составляющую скорости на очень маленькое случайное число. Тогда скорость мяча от удара к удару будет постепенно возрастать, и скучно не будет.
Другие проекты. Вам совсем не обязательно выполнять именно одно из двух предложенных мною заданий. Если вам хочется создать проект на другую тему – на здоровье! Но помните, задуманный вами проект, легкий на вид, на деле может оказаться слишком сложным для выполнения и вам придется его бросить. Или наоборот – слишком простым, и вы не получите от его выполнения большой пользы.
_______________ _ _________________
Мы с вами закончили первый из двух циклов знакомства с Visual Basic.NET. Если вам удался последний проект и он работает не хуже, чем указано в задании (пусть даже и без звука), если вы при этом умело применяли переменные величины и давали им длинные имена, если у вас в проекте много маленьких (а не мало больших) процедур, то все в порядке: у вас должна появиться уверенность, что теперь вы можете создавать проекты любого размера и любой сложности, а значит цель этого цикла достигнута. Я вас поздравляю – вам присваивается звание «Программист-любитель III ранга»!
Проект – Гонки (игра)
Мы с вами создали уже два правильно написанных больших проекта: «Парк под луной» и «Будильник». И этого, конечно, мало. У нас нет опыта создания проектов с движением объектов по форме, да к тому же под управлением мыши и клавиатуры. Поэтому я решил создать с вами такой проект (строк на 150). На этот раз это будет игра.
Переменные с индексами
В основе массивов лежит понятие индекса. В математике широко применяются так называемые индексированные переменные. На бумаге они записываются так:
x1 x2 b8 yi yi-6 z i j z i+1 j
а читаются соответственно так: икс первое, икс второе, бэ восьмое, игрек итое, игрек и минус шестое, зет итое житое, зет и плюс первое житое. Все эти маленькие подстрочные цифры и выражения называются индексами. Поскольку в алфавите VB нет подстрочных букв и цифр, то те же индексированные переменные в VB приходится обозначать так:
X(1) X(2) B(8) Y(i) Y(i-6) Z(i,j) Z(i+1, j)
Числа Фибоначчи. Зачем математикам нужны индексированные переменные? Ну, их удобно применять хотя бы при операциях над числовыми рядами. Числовой ряд – это просто несколько чисел, выстроенных по порядку одно за другим. Чисел в ряду может быть много и даже бесконечно много.
Возьмем, например, бесконечный ряд чисел Фибоначчи:
1 1 2 3 5 8 13 21 34 .....
Попробуйте догадаться, по какому закону образуются эти числа. Если вы сами не догадались, то я подскажу: каждое из чисел, начиная с третьего, является суммой двух предыдущих. А теперь попробуем записать это утверждение с помощью языка математики. Для этого обозначим каждое из чисел Фибоначчи индексированной переменной таким образом:
Первое число Фибоначчи обозначим так: f(1),
Второе число Фибоначчи обозначим так: f(2)
и т.д.
Тогда можно записать, что
f(1)=1 f(2)=1 f(3)=2 f(4)=3 f(5)=5 f(6)=8 ......
Очевидно, что
f(3)=f(1)+f(2),
f(4)=f(2)+f(3),
f(5)=f(3)+f(4)
и т.д.
Как математически одной формулой записать тот факт, что каждое
из чисел является суммой двух предыдущих? Математики в индексном виде записывают это так:
f(i)=f(i-2)+f(i-1).
Для пояснения этой формулы подставим вместо i любое число, например, 6. Тогда получится:
f(6)=f(6-2)+f(6-1)
или упрощая:
f(6)=f(4)+f(5),
что соответствует определению чисел Фибоначчи.
Какое бы i, большее 2, мы не подставляли, получается правильное равенство. Значит, формула верна сама по себе.
Задание 101.
Запишите на бумаге в индексном виде, как получается из предыдущего числа ряда последующее:
1) 14 18 22 26 .....
2) 6 12 24 48 ....
3) 3 5 9 17 33 65 ....
Еще примеры. Вот еще примеры, когда математики предпочитают использовать индексы. Пусть мы на протяжении года каждый день раз в сутки измеряли температуру за окном. Тогда вполне естественно обозначить через t(1) температуру первого дня года, t(2) – второго, ..... , t(365) – последнего. Пусть 35 спортсменов прыгали в высоту. Тогда через h(1) можно обозначить высоту, взятую первым прыгуном, h(2) – вторым и т.д.
Пока мы только немного привыкли к индексам, но никакой выгоды от них не получили. Выгода – в следующем разделе.
Простая сортировка
Задача: Задан массив из 100 произвольных положительных чисел. Отсортировать его по возрастанию.
Идея решения: Отсутствует. Тогда поговорим об идее поиска идеи. Если мы не можем придумать, как запрограммировать задачу, нужно подробно представить себе, в каком порядке мы решали бы ее вручную, без компьютера. Как бы мы сами сортировали 100 чисел, записанных на бумаге? Мы сделали бы вот что. Запаслись карандашом, ластиком и другим, пустым листом бумаги из 100 клеток. Затем нашли бы в исходном массиве максимальное число и записали его в самую правую клетку, а в исходном массиве на его месте записали бы число, меньшее самого маленького в массиве (в нашем случае подойдет 0). Затем нашли бы в изменившемся исходном массиве новое максимальное число и записали его на второе справа место, а на его место в исходном массиве – 0. И так далее.
Вот программа, воплощающая эту идею для 10 чисел:
'Вспомогательная функция для поиска максимума в массиве m размера N+1. Она выдает значение
'максимального элемента (maximum) и заодно мы узнаем номер этого элемента (Nomer_max):
Function maximum(ByVal m() As Integer, ByVal N As Integer, ByRef Nomer_max As Integer) As Integer
Dim i, max As Integer
max = m(0) : Nomer_max = 0 'max - "временный" максимум
For i = 1 To N
If max < m(i) Then
max = m(i)
Nomer_max = i
End If
maximum = max
Next
End Function
'Основная процедура сортировки исходного массива mass_ish размера N+1 в результирующий - mass_rez:
Sub sortirovka(ByVal mass_ish() As Integer, ByVal N As Integer, ByVal mass_rez() As Integer)
Dim i, Nom_max As Integer
For i = 0 To N
mass_rez(N - i) = maximum(mass_ish, N, Nom_max) 'Пишем "в правую клетку"
mass_ish(Nom_max) = 0 'Ноль - на старое место
Next
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim massiv_ishodn() As Integer = {41, 8, 17, 82, 20, 2, 30, 12, 6, 9} 'Это исходный массив
Dim N As Integer = massiv_ishodn.Length - 1 'Это его размер без 1
Dim massiv_rezult(N) As Integer 'Это наш пустой лист бумаги
sortirovka(massiv_ishodn, N, massiv_rezult) 'Сортируем массив
Dim i As Integer
For i = 0 To N
Debug.WriteLine(massiv_rezult(i)) 'Распечатываем отсортированный массив
Next
End Sub
Примечания: В программе вам полезно разобраться самому. Обратите внимание, что в заголовке функции в одном месте вместо ByVal употребляется ByRef. Разберитесь, почему. Обратите внимание, что функция maximum, кроме того, что сама имеет значение максимального элемента массива, выдает еще и порядковый номер максимального элемента – Nomer_max. Это, как я уже говорил, называется побочным эффектом функции.
Метод пузырька
Методов сортировки много. Приведенный метод – самый примитивный. Мало того, что нам пришлось расходовать память на второй массив, нам для выполнения сортировки массива из 100 элементов понадобилось бы около 100*100=10000 операций сравнения элементов массива между собой.
Существуют методы гораздо более эффективные. Приведу один из них – метод пузырька. Представьте себе тонкую вертикальную трубку с водой. Запустим снизу пузырек воздуха. Он поднимется до самого верха и остановится. Затем пустим еще один. Он поднимется наверх и остановится сразу же под первым. Затем запустим третий и так далее все сто пузырьков.
А теперь представим, что это не трубка, а наш исходный массив, а вместо пузырьков поднимаются максимальные элементы, сначала самый максимальный, потом - следующий по величине и т.д.
Вот алгоритм: Сравним первый элемент массива со вторым, и если второй больше, то это хорошо, так как они расположены в порядке возрастания. Мы ничего не делаем. А вот если первый больше, то меняем местами первый и второй элементы. В этом вся соль метода. Затем повторяем это со вторым и третьим элементами – если третий больше, то ничего не делаем, а если второй больше, то меняем местами второй и третий элементы. Затем повторяем все это с третьим и четвертым элементами и так далее. Где-то по пути мы встретим максимальный элемент, и он, согласно нашей механике постоянно меняясь местами с вышестоящими соседями, как пузырек, поднимется у нас до самого верха.
Теперь, когда мы знаем, что элемент номер 100 у нас самый большой, нам предстоит решить задачу сортировки для массива из остальных 99 элементов. Метод тот же. Запускаем второй пузырек и так далее.
Метод пузырька не требует второго массива, да и сравнений здесь в два раза меньше.
Вот программа:
'Сортировка массива mass размером N+1:
Sub puziryok(ByVal mass() As Integer, ByVal N As Integer)
Dim i, c, m As Integer
For m = N To 1 Step -1 'Всего пузырьков - 9
For i = 0 To m - 1 'i увеличивается - пузырек ползет вверх
If mass(i) > mass(i + 1) Then 'Стоит ли обмениваться значениями
c = mass(i) 'Три оператора для обмена значений
mass(i) = mass(i + 1) 'двух элементов с помощью
mass(i + 1) = c 'транзитного элемента c
End If
Next i
Next m
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim massiv() As Integer = {41, 8, 17, 82, 20, 2, 30, 12, 6, 9} 'Это массив
Dim N As Integer = massiv.Length - 1 'Это его размер без 1
puziryok(massiv, N) 'Сортируем массив
Dim i As Integer
For i = 0 To N
Debug.WriteLine(massiv(i)) 'Распечатываем отсортированный массив
Next
End Sub
В заключение скажу, что существуют методы гораздо более эффективные, чем даже метод пузырька.
Задание 108.
Задача для ученого-диетолога: Определить связь между ростом и весом боксеров-профессионалов. Для этого диетолог измерил рост и вес нескольких сотен боксеров и записал результаты измерений в длинную таблицу:
|
Фамилия |
Рост |
Вес |
|
Иванов |
173 |
71 |
|
Петров |
182 |
93 |
|
Сидоров |
169 |
62 |
|
Николаев |
175 |
70 |
|
……………. |
………………. |
…………….. |
Для более наглядного представления о характере связи между ростом и весом необходимо упорядочить эту таблицу по весу:
|
Фамилия |
Рост |
Вес |
|
Сидоров |
169 |
62 |
|
Николаев |
175 |
70 |
|
Иванов |
173 |
71 |
|
Петров |
182 |
93 |
Для проведения упорядочивания приглашается программист, то есть вы. Считайте, что вам заданы три исходных массива.
Сортировка
Здесь вы не узнаете ничего нового о собственно языке VB. Будем совершенствовать универсальную технику программирования.
Пусть имеется ряд чисел: 8 2 5 4. Под сортировкой понимают их упорядочивание по возрастанию (2 4 5 8) или убыванию (8 5 4 2). Сортировать можно и строки (как слова в словаре).
Сортировка – очень распространенная вещь в самых разных программах, в частности – в системах управления базами данных. Несмотря на то, что класс Array имеет специальный метод для сортировки массива, этого явно недостаточно. Вы должны уметь сортировать сами.
Основы работы с одномерными массивами
Пример. Рассмотрим на примере простой задачи, как VB управляется с массивами. Предположим, в зоопарке живут три удава. Известна длина каждого удава в сантиметрах (500, 400 и 600). Какая длина получится у трех удавов, вытянутых в линию?
Обозначим длину первого удава – dlina(1), второго – dlina(2), третьего – dlina(3). Прикажем VB отвести под эту индексированную переменную массив ячеек в памяти. Делается это так:
Dim dlina (3) As Integer
Здесь 3 - верхняя граница индекса. В целом эту строку можно перевести так: Отвести в памяти под переменную dlina ряд ячеек типа Integer, пронумерованных от 0 до
3.
Почему от 0? Нам не нужно от 0! Нам нужно от 1! – Ну, это уже ваши проблемы – отвечает VB – я могу только от 0! – Ну, что ж – отвечаем мы – в конце концов, пусть будет от 0. В конце концов, это означает всего лишь то, что в памяти будет отведена лишняя ячейка для значения dlina(0). А мы ее просто не будем использовать, и все! И никаких значений туда не будем записывать. И никаких проблем.
Вот программа полностью:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim dlina(3) As Integer
Dim summa As Integer
dlina(1) = 500
dlina(2) = 400
dlina(3) = 600
'В этот момент в трех ячейках памяти уже находятся числа
'и с ними можно выполнять арифметические действия
summa = dlina(1) + dlina(2) + dlina(3)
Debug.WriteLine(summa)
End Sub
А теперь запустите отладочный пошаговый режим выполнения программы и загляните в окне Locals в значения dlina(1), dlina(2), dlina(3), summa. Заметьте на будущее, что желтая полоса не перескакивает через оператор
Dim dlina(3) As Integer
и плюсик в окне Locals у переменной dlina появляется только после его выполнения. Жмите на плюсик – и перед вами значения всех 4 элементов массива.
Инициализация массива. В предыдущем примере мы задавали значения элементам массива простым присвоением. Можно сделать это короче:
Мощь одномерных массивов
Внутри скобок мы можем писать не только числа, но и переменные, и выражения. От этого программа сразу станет с непривычки менее понятной, однако приобретет необыкновенную мощь.
Привыкаем к переменным и выражениям в качестве индексов. Сначала мы должны привыкнуть к переменным и выражениям в качестве индексов. Вот три эквивалентных варианта программы, распечатывающей длины удавов. Вы должны без компьютера убедиться, что все три варианта распечатают одно и то же.
Private Sub Button6_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button6.Click
Dim dlina() As Integer = {0, 500, 400, 600}
Debug.WriteLine(dlina(1))
Debug.WriteLine(dlina(2))
Debug.WriteLine(dlina(3))
End Sub
Private Sub Button7_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button7.Click
Dim dlina() As Integer = {0, 500, 400, 600}
Dim i As Integer
i = 1
Debug.WriteLine(dlina(i))
i = 2
Debug.WriteLine(dlina(i))
i = i + 1
Debug.WriteLine(dlina(i))
End Sub
Private Sub Button8_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button8.Click
Dim dlina() As Integer = {0, 500, 400, 600}
Dim i As Integer = 1
Dim k As Integer = 10
Debug.WriteLine(dlina(i))
Debug.WriteLine(dlina(k - 8))
i = 2
k = 1
Debug.WriteLine(dlina(k + i))
End Sub
Мощи здесь пока не видно, увидите чуть позже.
Вопрос: Продолжаем привыкать. Не подходя к компьютеру, ответьте, что напечатает следующая процедура:
Private Sub Button5_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button5.Click
Dim a(100), i As Integer
i = 2 : a(3) = 10 : a(i) = 100 : a(i + 6) = a(i) + a(5 - i) : i = 0 : Debug.WriteLine(a(i + 3) + a(2) + a(i + 8))
End Sub
Ответ: 220
Хорошо бы вы получили правильный ответ самостоятельно. Если не смогли получить, значит вы не понимаете чего-то важного или просто невнимательны. В этом случае читайте пояснение:
Первая ступенька: i = 2 ® a(3) = 10 ® a(2) = 100 ® a(2 + 6) = a(2) + a(5 - 2) ® i = 0 ® Debug.WriteLine( a(0 + 3) + a(2) + a(0 + 8))
Вторая ступенька: i = 2 ® a(3) = 10 ® a(2) = 100 ® a(8) = a(2) + a(3) ® i = 0 ® Debug.WriteLine( a(3) + a(2) + a(8))
Третья ступенька: i = 2 ® a(3) = 10 ® a(2) = 100 ® a(8) = 110 ® i = 0 ® Debug.WriteLine( 10 + 100 + 110)
Цикл – мощное оружие при работе с массивами. Теперь решим задачу про суммарную длину удавов в предположении, что удавов не 3, а 1000:
Private Sub Button9_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button9.Click
Dim Число_удавов As Integer = 1000
'Верхняя граница индекса может быть выражена переменной величиной:
Dim dlina(Число_удавов) As Integer
Dim summa, i As Integer
'Вводим с клавиатуры длины тысячи удавов, хоть это и утомительно и никто так не делает.
'Здесь на первом выполнении цикла i=1 и поэтому компьютер вводит число в ячейку dlina(1),
'на втором - i=2 и поэтому компьютер вводит число в ячейку dlina(2) и т.д.
For i = 1 To Число_удавов
dlina(i) = InputBox("Введите длину " & i & "-го удава")
Next
'Определяем суммарную длину тысячи удавов:
summa = 0
For i = 1 To Число_удавов
summa = summa + dlina(i)
Next
Debug.WriteLine(summa)
End Sub
Отлаживая эту программу, возьмите, конечно, вместо числа 1000 какое-нибудь маленькое число.
Пример. Продолжаем привыкать к индексам. Решим еще одну задачу. Дан ряд из 10 произвольных чисел: a(1), a(2), ... , a(10). Подсчитать и напечатать суммы каждой из восьми троек стоящих рядом чисел.
первая тройка: a(1)+a(2)+a(3)
вторая тройка: a(2)+a(3)+a(4)
третья тройка: a(3)+a(4)+a(5)
......
восьмая тройка: a(8)+a(9)+a(10)
Вот программа:
Private Sub Button10_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button10.Click
Dim a() As Integer = {0, 5, 3, 4, 0, 20, 10, 23, 2, 9, 1000}
Dim i As Integer
For i = 1 To 8
Debug.WriteLine(a(i) + a(i + 1) + a(i + 2))
Next
End Sub
Вот что она напечатает:
12
7
24
30
53
35
34
1011
Следующие два задания очень важны, так как это ваши первые задания по массивам.
Задание 102.
Напишите с использованием массива программу вычисления среднегодовой температуры. Подсчитайте также количество теплых дней в году (когда температура выше 20 градусов). Узнайте, каким по порядку идет самый жаркий день. (Для отладки в компьютере годом можно считать неделю.)
Задание 103.
Вычислить и распечатать первые 139 чисел Фибоначчи.
Одномерные массивы
Одна из типичных задач программирования формулируется примерно так. Имеется большое количество данных, например, тех же температур или высот. С этими данными компьютер должен что-нибудь сделать, например, вычислить среднегодовую температуру, количество морозных дней, максимальную взятую высоту и т.п. Раньше мы уже вычисляли подобные вещи, и при этом данные вводили в компьютер с клавиатуры одно за другим. При этом всегда получалось, что они вводятся в одну и ту же ячейку памяти (см. Глава 10. ). Однако, программистская практика показывает, что удобно, а часто и необходимо иметь данные в оперативной памяти сразу все, а не по очереди. Тогда для задачи, скажем, про температуру нам понадобится 365 ячеек. Эти 365 ячеек мы и назовем массивом. Итак, массивом можно назвать ряд ячеек памяти, отведенных для хранения значений индексированной переменной. На вопрос о том, как большое количество значений оказывается в памяти, отвечу, что обычно они вводятся из файла (19.2).
Двумерные массивы
Поясним суть двумерных массивов на простом примере. Пусть на целом ряде метеостанций, расположенных в разных точках земного шара, в течение многих дней измеряли температуру воздуха. Показания термометров свели в таблицу. Ограничимся для экономии места тремя станциями и четырьмя днями.
| 1-й день | 2-й день | 3-й день | 4-й день | ||||||
| Метеостанция 1 | -8 | -14 | -19 | -18 | |||||
| Метеостанция 2 | 25 | 28 | 26 | 20 | |||||
| Метеостанция 3 | 11 | 18 | 20 | 25 |
Требуется (в порядке возрастания трудности):
1) Распечатать температуру на 2-й метеостанции за 4?й день и на 3-й метеостанции за 1?й день.
2) Распечатать показания термометров всех метеостанций за 2?й день
3) Определить среднюю температуру на 3-й метеостанции
4) Распечатать всю таблицу
5) Распечатать, в какие дни и на каких метеостанциях температура была в диапазоне 24-26 градусов тепла
Для этого обозначим показания термометров при помощи индексированной переменной с двумя индексами по следующей схеме:
| t(1,1) | t(1,2) | t(1,3) | t(1,4) | ||||
| t(2,1) | t(2,2) | t(2,3) | t(2,4) | ||||
| t(3,1) | t(3,2) | t(3,3) | t(3,4) |
Первый индекс в скобках обозначает номер строки (метеостанция), второй – номер столбца (день) прямоугольной таблицы. Такую таблицу математики называют матрицей.
В памяти отводим массив из ячеек типа Integer под значения индексированной переменной t. Будем называть его двумерным массивом:
Dim t (3, 4) As Integer
Аналогично одномерному массиву, первый индекс здесь будет меняться не от 1 до 3, а от 0 до 3, а второй – от 0 до 4. Таким образом в памяти будет отведен следующий массив ячеек:
| t(0,0) | t(0,1) | t(0,2) | t(0,3) | t(0,4) | |||||
| t(1,0) | t(1,1) | t(1,2) | t(1,3) | t(1,4) | |||||
| t(2,0) | t(2,1) | t(2,2) | t(2,3) | t(2,4) | |||||
| t(3,0) | t(3,1) | t(3,2) | t(3,3) | t(3,4) |
Мы просто не будем пользоваться верхней строкой и левым столбцом.
Программа:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim t(3, 4) As Integer
Dim i, j, s As Integer
' Зададим значения элементов массива примитивным присваиванием:
t(1, 1) = -8 : t(1, 2) = -14 : t(1, 3) = -19 : t(1, 4) = -18
t(2, 1) = 25 : t(2, 2) = 28 : t(2, 3) = 26 : t(2, 4) = 20
t(3, 1) = 11 : t(3, 2) = 18 : t(3, 3) = 20 : t(3, 4) = 25
'Выполняем 1 пункт задания:
Debug.WriteLine(t(2, 4) & " " & t(3, 1))
'А теперь распечатаем второй столбец массива (2 пункт задания):
Debug.Write("Второй столбец: ")
For i = 1 To 3 : Debug.Write(t(i, 2) & " ") : Next
Debug.WriteLine("") 'Переводим строку
'Определим среднее значение элементов третьей строки (3 пункт задания):
i = 3
s = 0
For j = 1 To 4 : s = s + t(i, j) : Next
Debug.WriteLine("Средняя температура равна " & s / 4)
'Распечатаем всю таблицу (4 пункт задания):
For i = 1 To 3
For j = 1 To 4
Debug.Write(t(i, j) & " ")
Next j
Debug.WriteLine("") 'Переводим строку
Next i
'Распечатаем станции и дни с температурой 24-26 градусов (5 пункт задания):
For i = 1 To 3
For j = 1 To 4
If t(i, j) >= 24 And t(i, j) <= 26 Then Debug.WriteLine("Станция " & i & " день " & j)
Next j
Next i
End Sub
Вот что напечатает эта программа:
20 11
Второй столбец: -14 28 18
Средняя температура равна 18,5
-8 -14 -19 -18
25 28 26 20
11 18 20 25
Станция 2 день 1
Станция 2 день 3
Станция 3 день 4
Инициализация двумерного массива. Вместо фрагмента, объявляющего массив и задающего его значения присваиванием:
Dim t(3, 4) As Integer
t(1, 1) = -8 : t(1, 2) = -14 : t(1, 3) = -19 : t(1, 4) = -18
t(2, 1) = 25 : t(2, 2) = 28 : t(2, 3) = 26 : t(2, 4) = 20
t(3, 1) = 11 : t(3, 2) = 18 : t(3, 3) = 20 : t(3, 4) = 25
можно использовать более короткую запись:
Dim t ( , ) As Integer = { {99, 99, 99, 99, 99} , {99, -8, -14, -19, -18} , {99, 25, 28, 26, 20} , {99, 11, 18, 20, 25} }
Разберемся в ней. Верхние границы индексов не указываются, но запятая в круглых скобках остается, чтобы было понятно, что массив двумерный, а не одномерный. Чисел в фигурных скобках нужно столько, чтобы заполнить столбцы 0,1,2,3,4 и строки 0,1,2,3. Каждую строку берем в свои фигурные скобки и отделяем от других строк запятой. Весь массив снаружи «обнимается» дополнительной парой фигурных скобок. Ненужные нам нулевую строку и нулевой столбец я заполнил числами 99, а мог бы и любыми другими, так как в программе мы их не используем.
Задание 104.
Вычислить разницу между максимальной и минимальной температурой во всей таблице.
Какие бывают массивы
Массивы бывают не только числовые, но и строковые и типа Date и многие прочие. Например:
Dim s(50) As String
Это означает, что в каждой из 51 ячеек должно находиться не число, а произвольная строка. А объявление
Dim DT(10) As Date
означает, что в каждой из 11 ячеек должна находиться дата.
Пример. Вот элементарный пример использования строкового массива:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim s(50) As String
s(21) = "Привет"
s(22) = s(21) + " всем!!!"
Debug.WriteLine(s(22))
Debug.WriteLine(Len(s(21)))
End Sub
Вот что напечатает эта программа:
Привет всем!!!
6
Пример. Вот пример работы с массивами других типов:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Dim b(30, 6) As Boolean
Dim DT(10) As Date
b(2, 3) = False
b(5, 0) = Not b(2, 3)
Debug.WriteLine (b(5, 0))
DT(2) = #1/15/2003 11:59:42 PM#
DT(0) = DT(2).AddDays(10)
If b(5, 0) Then Debug.WriteLine (DT(0))
End Sub
Вот что напечатает эта программа:
True
25.01.2003 23:59:42
Еще пример:
Enum типРуль
вверх
влево
вниз
вправо
End Enum
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
Dim Руль(300) As типРуль
Руль(200) = типРуль.влево
Руль(220) = Руль(200) + 1
Debug.WriteLine(Руль(220))
End Sub
Вот что напечатает эта программа:
вниз
Бывают массивы, состоящие из структур, объектов, но о них мы поговорим позже.
Многомерные массивы. Массивы могут быть одномерные, двумерные, трехмерные, четырехмерные и т.д.:
| Dim a (10) As Integer | -одномерный массив | 11 ячеек | |||
| Dim a (10, 5) As Integer | -двумерный массив | 66 ячеек (11*6) | |||
| Dim a (9, 4, 1) As Integer | -трехмерный массив | 100 ячеек (10*5*2) | |||
| Dim a (9, 4, 1, 2) As Integer | -четырехмерный массив | 300 ячеек (10*5*2*3) |
Использование массивов при программировании игр
Идеология. Есть ли польза от массивов при программировании игр? Вопрос праздный. Массивы необходимы и для шахмат, и для шашек, и для морского боя, и для крестиков-ноликов, и для многих других игр, в особенности для тех, где игра проходит на прямоугольном поле, расчерченном на квадраты. Возьмем для примера игру против компьютера в крестики-нолики на поле размером 3 на 3. Компьютеру приходится здесь рисовать на экране большие клетки, а в них – нолики (кружочки) после ваших ходов и крестики (пересекающиеся косые линии) после своих. Но этого умения недостаточно. Компьютеру ведь еще надо соображать, куда ставить крестики. А для этого нужно как минимум знать, где уже стоят крестики и нолики. А откуда он это знает? Если знание об этом хранится только на экране, то это очень неудобно, так как анализировать информацию о пикселях экрана трудно. Гораздо разумнее заранее организовать массив Dim a (3, 3) As Integer и записывать туда в нужные места нолики после ходов человека и, скажем, единички после ходов компьютера. Сразу же после записи в элемент массива нуля или единицы программа должна рисовать в соответствующем месте экрана кружок или крестик. Мыслить компьютер мог бы при помощи примерно таких операторов –
If a(1,1)=0 And a(1,2)=0 Then a(1,3)=1
Это очевидный защитный ход компьютера – на два кружочка в ряду он ставит в тот же ряд крестик.
Итак, сделаем вывод, что массив в памяти компьютера и поле для игры на экране в любой момент времени соответствуют друг другу, но компьютеру удобнее глядеть не на экран, а в память.
Проиллюстрируем идею использования массивов в играх подробнее, на специально придуманном примитивном примере (типа морского боя, но гораздо проще).
![]() |
Задание на создание игры: Играют друг против друга два человека на квадратном поле размером 2 на 2:
Компьютер в игре участвует только как судья, а не как игрок.
Правила: Сначала первый игрок тайком от второго сообщает компьютеру, в каких двух клетках находятся его одноклеточные корабли (например, в правой верхней и правой нижней). Затем второй сообщает компьютеру, в какие клетки он производит два выстрела (тоже, конечно, наугад – например, в левую верхнюю и правую нижнюю). Если подбиты оба корабля – он выиграл, если подбит один – ничья, если ни одного – выиграл первый игрок.
Сначала запрограммируем эту игру без графики, а потом с графикой.
Поле боя должно быть показано на экране только один раз – после двух выстрелов, причем, если без графики, то в виде распечатки из 4 букв:
м к
о х
Здесь я использовал такие обозначения:
о - корабля здесь нет и сюда не стреляли
к - неподбитый корабль
х - подбитый корабль
м - мимо (стреляли и промахнулись)
Других вариантов быть не может. Вы видите, что приведенная распечатка отражает результат расстановки кораблей и выстрелов, описанных мной в качестве примера.
Вот программа без графики:
Dim a(2, 2) As String 'Поле боя
Dim i, j, Подбито As Integer
'Главная процедура:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
a(1, 1) = "о" : a(1, 2) = "о" 'Поначалу на поле боя кораблей нет
a(2, 1) = "о" : a(2, 2) = "о"
Устанавливаем_корабль(1)
Устанавливаем_корабль(2)
Подбито = 0 'Пока не стреляли
Выстрел(1)
Выстрел(2)
Показываем_поле_боя()
Debug.WriteLine(Подбито) 'Показываем исход битвы - количество подбитых кораблей
End Sub
Sub Устанавливаем_корабль(ByVal Номер_корабля As Integer)
i = InputBox("Первый игрок, назовите номер строки для корабля " & Номер_корабля)
j = InputBox("Первый игрок, назовите номер столбца для корабля " & Номер_корабля)
a(i, j) = "к"
End Sub
Sub Выстрел(ByVal Номер_выстрела As Integer)
i = InputBox("Второй игрок, назовите номер строки для выстрела " & Номер_выстрела)
j = InputBox("Второй игрок, назовите номер столбца для выстрела " & Номер_выстрела)
If a(i, j) = "к" Then 'Если попал, то
a(i, j) = "х" 'ставим крестик
Подбито = Подбито + 1 'и увеличиваем счетчик подбитых кораблей
ElseIf a(i, j) = "о" Then 'иначе если промахнулся, то
a(i, j) = "м" 'ставим м
End If
End Sub
Sub Показываем_поле_боя()
For i = 1 To 2
For j = 1 To 2
Debug.Write(a(i, j))
Next j
Debug.WriteLine("")
Next i
End Sub
Пояснения: Вы видите, что в качестве массива, представляющего поле для игры, я выбрал строковый массив
Dim a(2, 2) As String 'Поле боя
Теперь прочтите главную процедуру. Убедитесь, что она правильно отражает основной порядок действий в процессе игры. Затем разберитесь в процедурах Устанавливаем_корабль и Выстрел. Они с параметрами. Наконец, разберитесь в процедуре Показываем_поле_боя.
Программа не объявляет итогов боя, а всего лишь печатает количество подбитых кораблей. Определение и объявление победителя оставляю вам.
Обратите внимание, что игра мгновенно переделывается из игры на поле 2 на 2 в игру на поле, скажем, 30 на 30, простой заменой числа 2 в тексте программы на число 30. Этой возможностью мы наслаждаемся только благодаря использованию массива! В этом случае, конечно, придется в цикле записать во все клеточки поля букву «о». А если мы хотим при этом иметь больше двух кораблей и двух выстрелов, нам придется в главной процедуре обратиться в цикле к процедуре Устанавливаем_корабль и в цикле к процедуре Выстрел, что очень просто.
Если бы мы пренебрегли секретностью, то могли бы показывать поле боя после каждого хода игроков. Для этого достаточно в конец процедур Устанавливаем_корабль и Выстрел включить строку
Показываем_поле_боя()
Программа с графикой. Придумаем для простоты такую графику. Поле состоит из 4 цветных квадратов. Вот возможные цвета:
Голубой квадрат - корабля здесь нет и сюда не стреляли
Серый квадрат - неподбитый корабль
Красный квадрат - подбитый корабль
Зеленый квадрат - мимо (стреляли и промахнулись)
Замечательно, что от добавлении графики программа абсолютно не изменится за исключением единственной процедуры Показываем_поле_боя. Причем и в ней-то вся структура цикла останется неизменной. По большому счету выкинем только строку
Debug.WriteLine("")
как нужную только для текстового вывода, а строку, печатающую очередную букву из четырех:
Debug.Write(a(i, j))
заменим фрагментом, рисующим очередной квадрат из четырех. Вот новая процедура Показываем_поле_боя:
Sub Показываем_поле_боя()
Dim Размер As Integer = 100 'Размер квадрата
Dim Гр As Graphics = Me.CreateGraphics
Dim Кисть_для_воды As New SolidBrush(Color.LightBlue)
Dim Кисть_для_корабля As New SolidBrush(Color.Gray)
Dim Кисть_для_попадания As New SolidBrush(Color.Red)
Dim Кисть_для_промаха As New SolidBrush(Color.Green)
Dim Кисть As SolidBrush 'Текущая кисть для квадрата
Гр.Clear(Color.White) 'Стираем поле, нарисованное после предыдущего хода
For i = 1 To 2
For j = 1 To 2
Select Case a(i, j) 'Выбираем кисть для очередного квадрата
Case "о" : Кисть = Кисть_для_воды
Case "к" : Кисть = Кисть_для_корабля
Case "х" : Кисть = Кисть_для_попадания
Case "м" : Кисть = Кисть_для_промаха
End Select 'Рисуем очередной квадрат:
Гр.FillRectangle(Кисть, Размер * j, Размер * i, Размер, Размер)
Next j
Next i
End Sub
Пояснения: Предположим, наша процедура работает после каждого хода игроков. Мы могли бы написать ее так, чтобы после каждого очередного хода (поставили корабль или выстрелили) компьютер перерисовывал только тот квадрат, о котором шла речь. Остальные ведь остались неизменными – чего их перерисовывать? В этом случае компьютеру пришлось бы «меньше трудиться». Но я пошел по более простому и универсальному пути – после каждого хода все поле со всеми квадратами стирается и рисуется заново согласно содержимому массива a.
Кстати, строка
Гр.Clear(Color.White) 'Стираем поле, нарисованное после предыдущего хода
в нашем случае излишня. Ведь мы все равно заново перерисовываем все квадраты поля, поэтому предварительно стирать их не имеет смысла.
Крестики-нолики 3х3 – советы. В принципе вы уже готовы к программированию игры против компьютера в обычные крестики-нолики 3х3. Всю техническую сторону дела мы прошли. Остается логика, то есть объяснение компьютеру, куда ставить крестики. И вот с логикой-то у нас будет проблема. Вы скажете: Какая проблема? – ведь клеточек всего 9 штук! Один из возможных операторов уже написан:
If a(1,1)=0 And a(1,2)=0 Then a(1,3)=1
Напишу еще пару десятков подобных операторов – и дело с концом! – А пару сотен не хотите?! Вы только попробуйте перебрать все возможные варианты расстановки крестиков, ноликов и пустых клеток! Их вообще несколько тысяч. В этом случае, чтобы сократить программу, нужно применять в качестве индексов переменные величины и ломать голову над тем, какие писать процедуры, ветвления и циклы.
Поэтому любителям игр рекомендую для тренировки запрограммировать крестики-нолики не против компьютера, а как игру человека с человеком, где компьютер – лишь судья. Если получится, вот тогда можно замахнуться и на большее. Но и здесь идите постепенно. Рекомендую написать большую процедуру для правильной простановки крестика в произвольном одномерном
массиве из трех клеток. У этой процедуры будет три параметра – по числу клеток. Затем заметьте, что в реальной игре 3х3 вас интересует только 8 рядов: 3 по горизонтали, 3 по вертикали и 2 по диагонали. Значит у вас будет 8 обращений к этой процедуре. Дальше думайте сами. Все это совсем не просто.
Массивы как объекты
Оказывается, массив – это объект. Объект класса Array пространства имен System. Как?! – скажете вы, – мы до сих пор прекрасно работали с массивами и, как говорится, «ни сном, ни духом»! Мы нигде не писали New, не пользовались свойствами и методами массивов. – Что ж, верно, многим программистам вполне можно работать с массивами и не подозревать, что это объекты. Авторы VB замаскировали этот факт (как мне кажется), чтобы не пугать программистов, переходящих с Visual Basic 6.0 на VB. Массивы-объекты рождаются в вашей программе «нечувствительно» для вас безо всякого New.
И все же, вот как можно создать массив при помощи New:
Dim a() As Integer = New
Integer() {8, 1, 4, 3}
Нам будут полезны некоторые свойства и методы массивов (см. процедуру):
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
Dim a() As Integer = {80, 60, 50, 90, 40, 20, 50, 70}
Dim t(,) As Integer = {{99, 99, 99, 99, 99}, {99, -8, -14, -19, -18}, {99, 25, 28, 26, 20}, {99, 11, 18, 20, 25}}
Debug.WriteLine(a.Length) 'Длина массива a (число элементов) = 8
Debug.WriteLine(t.Length) 'Длина массива t (число элементов) = 20
Debug.WriteLine(t.GetUpperBound(0)) 'Число строк (макс. индекс первого измерения) - 1 = 3
Debug.WriteLine(t.GetUpperBound(1)) 'Число столбцов (макс. индекс второго измерения) - 1 = 4
'Ищется первое вхождение числа 50 в одномерный массив a и находится его индекс (2):
Debug.WriteLine(Array.IndexOf(a, 50))
'Ищется последнее вхождение числа 50 в одномерный массив a и находится его индекс (6):
Debug.WriteLine(Array.LastIndexOf(a, 50))
Debug.WriteLine(Array.IndexOf(a, 55)) 'Ищется число 55 в массиве a и не находится (-1)
Array.Reverse(a) 'Все элементы массива a меняют порядок на обратный = {70, 50, 20, 40, 90, 50, 60, 80}
Array.Sort(a) 'Все элементы массива a сортируются по возрастанию = {20, 40, 50, 50, 60, 70, 80, 90}
Array.Clear(a, 4, 3) 'Обнуляется 3 элемента массива a, начиная с индекса 4= {20, 40, 50, 50, 0, 0, 0, 90}
End Sub
Из приведенных методов некоторые имеют несколько вариантов, которые я здесь не привожу.
Замечание. Учитывая, что массив – это объект, я призываю вас до поры не присваивать массив массиву целиком, без индексов, например, вот так:
Dim a() As Integer = {8, 1, 5, 2}
Dim b() As Integer
b = a
Присвоение, конечно, состоится, но совсем не такое, как вы ждали. Оно может привести к неожиданным для начинающих последствиям. Вот к каким, например. Продолжу фрагмент:
a(2) = 99
Debug.WriteLine(b(2))
Напечатается 99, а не 5, потому что массив – это объект. Почему? Расскажу позднее, в 27.2.
Массивы как параметры
До этого момента параметр процедуры или функции был для нас каким-то одним данным: это или одно число, или одна строка, или один объект. Но параметр может быть и массивом.
Задача: Имеется два массива, по три числа в каждом. Напечатать сумму элементов каждого массива. Использовать функцию sum, единственным параметром которой является суммируемый массив.
Программа:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim a() As Integer = {4, 10, 20}
Dim b() As Integer = {100, 40, 50}
Debug.WriteLine(sum(a))
Debug.WriteLine(sum(b))
End Sub
Function sum(ByVal c() As Integer) As Integer
sum = c(0) + c(1) + c(2)
End Function
Выполните программу в пошаговом режиме, глядя в окно Locals. Понаблюдайте, как массив c принимает в себя значения элементов массивов a и b.
Задание 105.
В школе два класса. В каждом – два-три десятка учеников. Каждый ученик получил отметку на экзамене по физике. Определить, какой из двух классов учится ровнее (будем считать, что ровнее учится тот класс, в котором разница между самой высокой и самой низкой отметкой меньше).
Указание: Создать функции Минимум(c), Максимум(c) и Разница(c).
Задание 106.
На двух метеостанциях (A и B) в течение года измерялась температура. Соответственно созданы два массива чисел длиной 365. Затем оказалось, что на обеих станциях термометры были испорчены: на станции A термометр все время показывал температуру на 2 градуса выше настоящей, а на станции B – на 3 градуса ниже. Написать процедуру с двумя параметрами, которая исправляет один произвольный массив и с ее помощью исправить оба массива. Один параметр процедуры – величина поправки, другой – массив температур.
Массивы элементов управления
Задача: Выстроить вдоль нижней кромки формы на равных расстояниях друг от друга 40 элементов управления PictureBox – улыбающихся рожиц, так, как это сделано на Рис. 15.1.

Рис. 15.1
При нажатии кнопки эти рожицы должны прыгнуть вверх, так, как на Рис. 15.2.

Рис. 15.2
Трудности: По-старинке нам нужно вручную поместить на форму 40 элементов управления PictureBox и загрузить в них картинку рожицы, что само по себе утомительно. Расположить их вручную на равных расстояниях без помощи VS тоже нелегко. VB дает возможность изящно преодолеть все эти трудности программным путем.
Первый способ – использование коллекции. Поместите вручную на форму 40 элементов PictureBox. Поскольку все они автоматически входят в коллекцию элементов управления формы (загляните вперед, в 16.2), вы можете запрограммировать их правильное расположение и прыжки. Но вручную тащить на форму или копировать 40 элементов не хочется. Поэтому изберем второй способ.
Второй способ – использование массива. В 6.1.2 мы не тащили вручную на форму элемент управления, а создавали его программным путем. Перечитайте тот раздел. Здесь мы поступим совершенно аналогично, только создадим не один элемент управления, а массив из 40 элементов управления. Вот программа:
Dim Рожица(40) As PictureBox
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim i As Integer
For i = 1 To 40
Рожица(i) = New PictureBox 'Создаем очередной объект
Рожица(i).SizeMode = PictureBoxSizeMode.AutoSize
Рожица(i).Image = Image.FromFile("FACE02.ICO")
'Помещаем на свои места все объекты массива:
Рожица(i).Top = Me.Height - 50
Рожица(i).Left = 20 * i
Me.Controls.Add(Рожица(i)) 'Добавляем новую рожицу в коллекцию
Next
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim i As Integer
For i = 1 To 40
Рожица(i).Top = Рожица(i).Top - i 'Прыжок всех объектов
Next
End Sub
Пояснения: Как обычно, первой выполняется процедура Form1_Load, которая все создает и расставляет. Оператор
Рожица(i).SizeMode = PictureBoxSizeMode.AutoSize
понадобился для того, чтобы каждый PictureBox уменьшился до размеров рожицы.
Затем процедура Button1_Click обеспечивает прыжок всех элементов управления, каждого на свою высоту.
Как только элемент управления создан, вы можете как угодно задавать и менять его свойства, обращаясь к нему по индексу. Например, оператор
Рожица(38).Visible = False
делает невидимой 38-ю рожицу.
Обратите внимание, что пока я для простоты создаю элементы управления без возможности обработки свойственных им событий. Так, на созданную кнопку нажимать бесполезно. Как обрабатывать события элементов управления, созданных в коде, я написал в 22.13.
Индукция. Рекурсия
Понятие рекурсии – замечательное, сложное и необходимое понятие для программиста. В первый раз, однако, вы можете прочесть этот раздел «по диагонали», уяснив только общую идею и не вникая в объяснения. В дальнейшем, при необходимости, вы вернетесь к нему.
Здесь мне никуда не уйти от классического примера о факториале. Факториалом целого положительного числа N называется произведение всех целых чисел от 1 до N. Например, факториал пяти равен 1*2*3*4*5, то есть 120. Обратите внимание, что факториал единицы по определению считается равным 1.
Все понятно. Однако, существует еще один, совершенно ужасный способ определения, что такое факториал. Этот способ определения называется индуктивным. Вот он:
«Факториал единицы равен 1. Факториал любого целого положительного числа N, большего единицы, равен числу N, умноженному на факториал числа N-1.»
Если вам уже все ясно, значит вы – профессор математики. Для обычных смертных поясню. Возьмем какое-нибудь конкретное N, например, 100. Тогда ужасное определение будет звучать проще: Факториал числа 100 равен числу 100, умноженному на факториал числа 99.
Вроде, все правильно. Ну и что из того? Какая нам выгода, что правильно? Как нам из этого правильного определения узнать, чему равен какой-нибудь конкретный факториал, скажем, факториал трех? Для того, чтобы узнать это, будем рассуждать также совершенно чудовищным образом:
Смотрю в определение: Факториал трех равен 3 умножить на факториал двух. Не знаю, чему равен факториал двух. Поэтому спускаюсь на ступеньку ниже.
Смотрю в определение: Факториал двух равен 2 умножить на факториал единицы. Не знаю, сколько это. Спускаюсь еще на ступеньку.
Смотрю в определение: Факториал единицы равен 1. Вот, наконец-то – впервые узнал конкретное число. Значит можно подниматься обратно.
Поднимаюсь на одну ступеньку. Я на этой ступеньке уже бывал. Вспоминаю: Факториал двух равен 2 умножить на факториал единицы, то есть на 1, полученную нами ступенькой ниже. Получается 2. Хорошо.
Поднимаюсь еще на ступеньку. Я на этой ступеньке тоже бывал. Вспоминаю: Факториал трех равен 3 умножить на факториал двух, то есть на 2, полученную нами ступенькой ниже. Получается 6. Задача решена!
Рассуждая таким образом, можно вычислить факториал любого числа. Этот способ рассуждения называется рекурсивным. Ну как он вам?
:
Какое отношение все это имеет к компьютерам? Дело в том, что рекурсивный способ рассуждений реализован во многих языках программирования, в том числе – и в VB. Значит, этим языкам должен быть понятен и индуктивный способ написания программ.
Обозначим кратко факториал числа N, как Factorial(N), и снова повторим наш индуктивный способ определения:
Если N=1, то Factorial(N) = 1.
Если N>1, то Factorial(N) вычисляется умножением N на Factorial(N-1).
В соответствии с этим определением, ни минуты не сомневаясь, напишем на VB функцию Factorial для вычисления факториала:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Debug.WriteLine(Factorial(3))
End Sub
Function Factorial(ByVal N As Integer) As Long
If N = 1 Then Factorial = 1
If N > 1 Then Factorial = N * Factorial(N - 1)
End Function
Обратите внимание, как удивительно соответствует код процедуры последнему варианту нашего индуктивного способа определения. Это практически дословный перевод определения с русского на английский язык. Единственное маленькое несоответствие – это употребление слева от знака равенства слова Factorial вместо Factorial(N). Но это требование грамматики, не меняющее смысла.
Что самое удивительное – функция работает! Несмотря на то, что в программе нигде не употребляется оператор цикла. А как же получается повторение? Вся соль программы в том, что функция Factorial вместо цикла включает в себя вызов самой себя – Factorial(N-1).
Что же происходит в компьютере во время выполнения программы? Механизм происходящего необычен, но в точности соответствует нашему путешествию по ступенькам. «Мыслей много, дела мало J». Запустите проект в отладочном режиме и обязательно понаблюдайте за окнами Locals и Call Stack:
Все начинается с того, что мы щелкаем по кнопке и VB пробует выполнить строку Debug.WriteLine(Factorial(3)). Для этого он вызывает функцию Factorial. Выполнение функции начинается с того, что в памяти отводится место для всех параметров функции и ее локальных переменных (в нашем случае это единственный параметр N). Затем число 3 подставляется на место параметра N, то есть в память в ячейку N посылается 3. Затем выполняется тело функции. Так как 3>1, то VB пытается выполнить умножение 3 * Factorial(3-1) и сталкивается с необходимостью знать значение функции Factorial(2), для чего вызывает ее, то есть отправляется ее выполнять, недовыполнив Factorial(3), но предварительно запомнив, куда возвращаться.
Спускаюсь на ступеньку ниже, выполнять Factorial(2). В другом месте памяти отводится место для N. Это уже другое N, чем в Factorial(3), путать их нельзя! В эту ячейку N посылается 2 (а в той продолжает сидеть 3). Затем выполняется тело функции. Пусть вас не смущает, что VB второй раз выполняет тело функции, не закончив его выполнять в первый раз. Так как 2>1, VB пытается выполнить умножение 2 * Factorial(2-1) и сталкивается с необходимостью знать значение функции Factorial(1), для чего, недовыполнив Factorial(2),вызывает ее. Итак, уже два недовыполненных обращения к одной и той же функции ждут своего часа на довыполнение.
Спускаюсь еще на ступеньку, выполнять Factorial(1). В другом месте памяти отводится место еще для одного N. В эту ячейку N посылается 1. Затем выполняется тело функции. Так как 1=1, то VB вычисляет Factorial=1. Вот – впервые конкретное число. Затем VB пытается выполнить следующую строку if N>1 then Factorial = N * Factorial(N-1). Поскольку нельзя сказать, что 1>1, то строка не выполняется и выполнение тела функции закончено. Значит можно подниматься.
Поднимаюсь на одну ступеньку. VB возвращается внутрь тела функции (той, где N=2), имея в кармане в качестве результата единицу, и успешно выполняет умножение – Factorial = 2*1 = 2.
Поднимаюсь еще на ступеньку. VB возвращается внутрь тела функции (той, где N=3), имея в кармане в качестве результата двойку, и успешно выполняет умножение – Factorial = 3*2 = 6. Задача решена.
Все произошло так, как если бы в окне кода было не одно объявление функции Factorial , а три. Из главной процедуры мы обратились к первому, из него – ко второму, а из того – к третьему. Выполнив третье, компьютер вернулся во второе, выполнив второе – в первое, выполнив первое – в главную процедуру.
Здесь мы рассмотрели рекурсию на примере функции. Однако обращаться к самой себе может с тем же успехом и процедура. Итак, рекурсией
в программировании называется вызов метода из тела самого метода.
Пути рекурсии не всегда бывают такими короткими. Часто функция А вызывает, например, процедуру В, которая вызывает процедуру С, которая в свою очередь вызывает функцию А. Круг замкнулся – рекурсия есть!
Чем хорош рекурсивный стиль программирования? В нашей программе о факториале мы как бы и не программировали вовсе, а просто объяснили компьютеру, что такое факториал. Как бы перешли на новый уровень общения с компьютером: вместо программирования – постановка задачи.
Чем плох рекурсивный стиль программирования? Если мы для решения той же задачи напишем программу не с рекурсией, а с обычным циклом, то такая программа будет выполняться быстрее и потребует меньше памяти.
Как преимущества, так и недостатки рекурсии вы поймете на примере выполнения следующего задания:
Задание 107.
Напишите рекурсивную функцию fib для вычисления чисел Фибоначчи. В любом случае загляните в ответ.
Оглядимся вокруг. Итак, мы изучили 5 способов заставить компьютер многократно выполнять код:
Тип Object
Переменные типа Object. Типы данных, которые мы проходили до этого, строгие. Если уж мы написали
Dim a As Integer
то переменная a не может быть ни строкой, ни дробным числом и ничем другим неположенным, она может быть только целым числом, и точка! Но есть и «добрые» типы данных. Самый добрый тип – это Object. Мы уже говорили о нем в 11.5.1. Там переменная типа Object принимала значения различных элементов управления. Рассмотрим теперь такую процедуру:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim a As Object
a = Button1
a = 77
a = "кот"
End Sub
Как видите, здесь переменная типа Object принимает по-очереди значения таких разных вещей, как элемент управления, целое число и строка. И ничего плохого не происходит, VB не жалуется. Тип Object – это тип-хамелеон. Он не возражает, если объявленная им переменная будет иметь значение любого типа.
Выполните проект в пошаговом режиме, наблюдая за значением переменной в окне Locals.
Вместо
Dim a As Object
разрешается писать
Dim a
В этом случае VB все равно будет считать, что переменная a объявлена, как Object.
Зачем нужны строгие типы? Я уже говорил ранее, что они во многих случаях не позволяют программисту и компьютеру делать глупости и получать недостоверные результаты.
А зачем нужны добрые типы? Какой толк от типа Object? – Вот какой. Иногда бывает так, что заботясь о безопасности и объявляя тип каждой переменной, мы наталкиваемся во время выполнения программы на сообщения об ошибке вроде Type mismatch (несовпадение типов) или на другие подобные сообщения, о которых я говорил, например, в 11.5.3. Это значит, что выполняя операции над данными разных типов и преобразовывая один в другой, VB не захотел преступать строгие рамки безопасности и потерпел крах. Не всегда у начинающего программиста хватает знаний, чтобы разобраться в причинах краха. В этом случае, если вы не прочь немножко рискнуть, объявите данные типом Object. Он, хоть и допускает значения любого типа, все-таки присматривает, чтобы выполнение программы при этом проходило по возможности гладко и без неприятностей для программиста. Теперь вероятность сообщений об ошибке снизится, хотя несколько увеличится вероятность получения недостоверного результата.
Массивы типа Object. Массив – это набор однотипных элементов. Если вы объявили массив некоторого типа, то он обязан состоять из элементов только этого типа. Например, каждый из десяти элементов массива, объявленного как
Dim a(9) As Integer
обязан быть целым числом. Ни один из них не имеет права быть строкой или дробным числом.
Имеется целый ряд задач, где хотелось бы, чтобы элементы в наборе имели разный тип. Этого можно достичь хотя бы при помощи массива, если объявить массив, как вы уже догадались, типом Object:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Dim a(3) As Object
a(1) = 32
a(2) = "кошка"
a(3) = #8:56:00 AM#
a(3) = "поварешка"
End Sub
Выполните проект в пошаговом режиме, наблюдая за значениями элементов массива в окне Locals.
Вместо
Dim a(3) As Object
разрешается писать
Dim a(3)
И все же, когда дело касается желания иметь набор из элементов разных типов, программисты не хотят использовать массивы типа Object. Они предпочитают так называемые коллекции и структуры. О них сейчас и пойдет речь.
Создание коллекции, методы коллекции
Я рассмотрю распространенный случай, когда в коллекцию входят только элементы управления.
Задача: На вашей форме имеется несколько десятков кнопок, меток и текстовых полей, которые делают свое дело, скажем, помогают обрабатывать банковскую информацию. Глядя на них, можно сказать кто принес деньги, сколько принес, в долларах или рублях, сколько денег у него стало, сколько всего в банке денег и т.д. Вам нужно, чтобы иногда, в некоторые моменты выполнения программы, такие-то и такие-то кнопки, метки и текстовые поля из самых разных мест формы (всего, скажем, дюжина объектов) меняли цвет. Ну, скажем, для того, чтобы привлечь к себе ваше внимание. А в какие-нибудь другие моменты эта же самая дюжина должна делать что-нибудь другое, например, все кнопки из этой дюжины должны быть деактивированы, чтобы на них случайно не нажали. И так далее. В общем, эта дюжина иногда должна вести себя по-другому, чем остальные элементы управления.
Решение: Создадим проект. Для простоты разместим на форме только шесть элементов управления: Label1, TextBox1, Button1, Label2, TextBox2, Button2. Пусть в нашу «дюжину» входят из них только Label2, TextBox2, Button2.
Мы могли бы поместить нашу «дюжину» в рамку (GroupBox). Но рамка не обладает такой функциональностью, как коллекция. К тому же она требует пространственной отграниченности входящих в нее элементов управления от остальных.
Программным путем создадим из наших объектов коллекцию. Для этого сначала придумаем ей имя Моя_коллекция. Коллекция – это объект, экземпляр класса Collection, и создается она соответствующим привычным образом:.
Dim Моя_коллекция As New Collection 'Объявляем и создаем коллекцию
Коллекция объявлена, но пока она пуста, в ней нет ни одного элемента. Теперь будем по очереди добавлять объекты в коллекцию. Смотрим программу и пояснения к ней:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim Моя_коллекция As New Collection 'Объявляем и создаем коллекцию
Моя_коллекция.Add(Label2) 'Добавляем в коллекцию 1-й элемент
Моя_коллекция.Add(TextBox2, "Текстик") 'Добавляем в коллекцию 2-й элемент
Моя_коллекция.Add(Button2, "Кнопочка") 'Добавляем в коллекцию 3-й элемент
Debug.WriteLine(Моя_коллекция.Count) 'Печатается 3 - число элементов в коллекции
Debug.WriteLine(Моя_коллекция(2)) 'Печатаются сведения об объекте TextBox2
Debug.WriteLine(Моя_коллекция(1).Text) 'Печатается "Label2"
Моя_коллекция(3).Height = 80 'Задается высота кнопки Button2
Моя_коллекция("Текстик").Width = 100 'Задается ширина поля TextBox2
Dim i As Integer
For i = 2 To 3 'ДЛЯ 2 и 3 объекта
Debug.WriteLine(Моя_коллекция(i).Top) 'распечатывается вертикальная координата
Next
Dim Мой_объект As Control
For Each
Мой_объект In Моя_коллекция 'ДЛЯ КАЖДОГО объекта В
коллекции:
Мой_объект.BackColor = Color.Yellow 'задается желтый цвет
Next
For Each Мой_объект In Моя_коллекция 'ДЛЯ КАЖДОГО объекта В коллекции:
If TypeName(Мой_объект) = "Button" Then 'если имя типа объекта - Button, то
Мой_объект.Enabled = False 'деактивировать ее
End If
Next
Моя_коллекция.Remove(2) 'Удаляем из коллекции 2-й элемент
Моя_коллекция.Remove("Кнопочка") 'Удаляем из коллекции кнопку
End Sub
Пояснения: У всех коллекций, как экземпляров класса Collection, есть несколько методов, три из них (Add, Remove, Count) я сейчас поясню.
При помощи метода Add мы добавляем в коллекцию элементы. При этом обязательно указывается имя элемента. Также удобно через запятую указать произвольную строку – ключ элемента, по которому можно к нему при желании обращаться, что мы и сделали в двух местах ("Текстик" и "Кнопочка"). Элементы, добавляемые в коллекцию, получают номера в том порядке, в котором их добавляли, начиная с 1 (а не с 0). Обращаться к отдельным элементам коллекции можно по полученному номеру (индексу) прямо как к элементу массива, а можно и по ключу, там, где он задан.
Как видите, здесь для добавления каждого элемента мы использовали отдельный оператор, хотя есть ситуации, когда можно это делать и в цикле.
Метод Count просто сообщает число элементов в коллекции.
Строки процедуры с 6 по 13 иллюстрируют обращение к элементам коллекции по индексу или по ключу, в том числе в цикле.
Чтобы удалить отдельный элемент из коллекции (не с формы), используется метод Remove с указанием индекса или ключа элемента.
Очевидно, что работа с коллекциями напоминает работу с одномерными массивами, в чем-то побогаче ее, а в чем-то и менее удобна.
В следующем подразделе мы продолжим пояснения нашего примера.
Оператор цикла For Each
Работа For Each с коллекциями. Для коллекций удобно применять специальную разновидность оператора цикла – For Each. Для этого необходимо придумать имя переменной цикла, значение которой будет пробегать все элементы коллекции. Мы придумали имя Мой_объект. Надо объявить переменную цикла так, чтобы ее тип подходил для всех элементов коллекции. Поскольку в нашей коллекции одни только элементы управления, мы объявили так:
Dim Мой_объект As Control
хотя могли и так:
Dim Мой_объект As Object
Ведь тип Object всеяден. Но чем более конкретный тип мы указываем, тем удобнее работать.
Когда цикл выполняется в первый раз, Мой_объект «равняется» одному элементу коллекции, во второй раз – другому и т.д., пока элементы не будут исчерпаны. В остальном синтаксис и порядок выполнения оператора For Each такой же, как и у привычного нам For. К нему также можно применять оператор Exit For.
Мы использовали функцию TypeName, чтобы выбрать из всех элементов коллекции элементы только данного типа. Пояснять ее я не буду, используйте дальше по аналогии.
Работа For Each с массивами. Оператор For Each никто не запрещал использовать и при работе с массивами. Вот пример:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Dim a() As Integer = {10, 11, 12, 13, 14}
Dim Элемент As Integer
For Each Элемент In a
Debug.WriteLine(Элемент)
Next
End Sub
Напечатает эта процедура вот что:
10
11
12
13
14
Коллекции, принадлежащие контейнерам
Мы знакомы с такими объектами, как форма, рамка, панель, вкладка. Каждый из них может включать в себя другие элементы управления. Поэтому такие объекты называются контейнерами. У контейнера есть собственная коллекция, в которую автоматически заносятся элементы управления, входящие в контейнер. Поэтому специально заботиться о добавлении их в контейнер не нужно. Коллекция формы называется Controls. Коллекция рамки GroupBox1 является ее свойством Controls, поэтому к ней можно обращаться так: GroupBox1.Controls. К коллекции панели Panel1 можно обращаться так: Panel1.Controls и т.д. Пример:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim Объект As Control
For Each Объект In Controls
Debug.WriteLine(Объект.Text)
Next
For Each Объект In Panel1.Controls
Debug.WriteLine(Объект.Text)
Next
End Sub
Во вкладках элементы управления принадлежат страницам вкладки. У каждой страницы – своя коллекция элементов управления. Пусть во вкладке TabControl1 имеется три страницы: TabPage1, TabPage2 и TabPage3. Тогда фрагмент
For Each Объект In TabPage2.Controls
Debug.WriteLine(Объект)
Next
распечатает элементы управления, находящиеся на 2-й странице. Сами страницы составляют коллекцию, принадлежащую вкладке. Поэтому фрагмент
For Each Объект In TabControl1.Controls
Debug.WriteLine(Объект.Name)
Next
распечатает имена всех страниц вкладки:
TabPage1
TabPage2
TabPage3
Получается коллекция в коллекции. Матрешка. Поэтому сработает такой оператор:
Debug.WriteLine(TabControl1.Controls(1).Controls(2))
Здесь распечатывается элемент управления, стоящий под номером 2 в коллекции элементов управления, принадлежащей некой странице. А страница эта стоит под номером 1 в коллекции страниц, принадлежащей вкладке TabControl1.
Коллекции
Коллекция представляет собой набор элементов одного или разных типов. Обычно коллекции составляют из элементов управления, других объектов. Коллекция и сама является объектом (а почему бы и нет? – «коробки в коробке»). Коллекции всеядны, они позволяют иметь в одном наборе и кнопку, и метку, и строку, и переменную величину типа Integer.
Структуры
Со структурами мы с вами знакомы слабо. Несколько слов я сказал о них в 12.1. А именно, я сказал, что структура – это один из видов объектов (в широком смысле), который наряду с классами, модулями и перечислениями входит в состав пространств имен. У структуры, также как и у класса, могут быть свойства и методы. Нам известны структуры Точка, Прямоугольник и др.
В Паскале структурам соответствуют записи, в Visual Basic 6.0 структурам соответствует пользовательский тип данных, в Си структуры тоже называются структурами.
Все знакомые нам структуры достались нам готовыми, входящими в библиотеку классов .NET Framework Сейчас же мы займемся созданием собственных структур. Для нас структура будет пока не вместилищем методов и свойств, как мы привыкли, а всего лишь местом для хранения набора разнотипных данных. Вы скажете, что для этого мы уже пользовались коллекцией. Но у коллекции и у структуры немножко разные области применения. В дальнейшем вы почувствуете разницу между ними.
Рассмотрим пример.
Задание. Вы хотите занести в компьютер информацию о ваших любимых компьютерных играх (хотя бы для того, чтобы затем как-то ее анализировать, например, определить, какая игра занимает больше места на диске). Для простоты ограничим информацию об игре тремя элементами:
Также для экономии ограничимся двумя играми. Ваша программа должна будет занести в память информацию об обеих играх, а затем выполнить три задания:
Ваши действия. Для начала нужно создать структуру для размещения информации об игре. Нам уже приходилось конструировать перечисления (см. 13.3). Там мы сначала создавали тип
перечисления, а затем объявляли этим типом переменные. Здесь действуем аналогично – придумываем сначала имя новому типу структуры (типИгра), причем придумываем также имена и типы всем элементам информации об игре, которые мы хотим хранить в структуре, после чего пишем:
Structure типИгра
Dim Название As String
Dim Объем As Integer
Dim Графика_хорошая As Boolean
End Structure
Слово Structure как раз и обозначает структуру.
Тип определен. Теперь VB знает, что входит в информацию об игре и сколько места в памяти она займет. Можно объявлять переменные, то есть отводить место в памяти:
Dim Игра1, Игра2 As типИгра 'Отводим в памяти место под информацию о двух играх
Вот полный текст программы:
'Создаем тип структуры. Его нельзя объявлять в процедуре:
Structure типИгра
Dim Название As String
Dim Объем As Integer
Dim Графика_хорошая As Boolean
End Structure
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim Игра1, Игра2 As типИгра 'Отводим в памяти место под информацию о двух играх
'Заносим в память компьютера информацию об играх:
Игра1.Название = "StarC" : Игра2.Название = "Heroes III"
Игра1.Объем = 90 : Игра2.Объем = 200
Игра1.Графика_хорошая = False : Игра2.Графика_хорошая = True
Debug.WriteLine(Игра1.Название) 'Выполняем 1 задание
Debug.WriteLine(Игра1.Объем + Игра2.Объем) 'Выполняем 2 задание
'Выполняем 3 задание:
If Игра2.Графика_хорошая Then Debug.WriteLine("Хорошая графика") _
Else Debug.WriteLine("Плохая графика")
End Sub
Обратите внимание, что имя элемента структуры отделяется от имени переменной точкой. Нам это привычно по работе со структурами Размер, Прямоугольник и др., когда имя такого элемента структуры, как свойство, тоже отделялось от имени переменной точкой.
Массивы и структуры отличаются от простых типов данных, таких как Integer или String. Если переменная простого типа содержит в каждый момент времени только одно значение (число, строку, дату и т.п.), то массив или структура в каждый момент времени содержат по нескольку значений. Поэтому такие типы называют составными (composite).
Можно писать операторы такого вида:
Игра2 = Игра1 'Игре 2 присваиваются все элементы 1-й игры
В этом случае всем элементам игры 2 присваиваются значения соответствующих элементов игры 1.
Базы данных. Выстроенную подобным образом в памяти информацию о чем-либо часто называют базой данных. Всю информацию об одной переменной (в нашем случае об одной игре) называют записью в этой базе данных. Программу, которая извлекает информацию из базы данных, сортирует записи и производит другую обработку информации в базе данных, называют системой управления базой данных. В нашем примере роль системы управления базой данных играет процедура Button1_Click.
Базы данных являются настолько распространенным средством хранения информации, что в VB есть специальные мощные инструменты для работы с ними. Мы рассмотрим их в Глава 24. .
Массивы структур: Вы хотите создать базу данных о 30 играх. Для этого достаточно вместо очень длинного объявления
Dim Игра1, Игра2, …………… Игра30 As типИгра
объявить массив:
Dim Игра(30) As типИгра
Теперь можно использовать операторы такого вида:
Игра(16).Название = "KU4" 'название 16-й игры
Игра(25).Объем = 400 'объем 25-й игры
Debug.WriteLine(Игра(8).Графика_хорошая)
Игра(29) = Игра(12) '29-й игре присваиваются все элементы 12-й игры
Структура в структуре: Элементы структуры могут иметь не только простой тип, но и составной, в том числе быть массивом, структурой или объектом.
Пусть мы хотим иметь более подробную, чем в предыдущем примере, информацию о графике. Для этого организуем отдельную структуру:
Structure типГрафика
Dim Хорошая As Boolean
Dim Число_цветов As Integer
Dim Максимальное_разрешение As String
End Structure
А поскольку информация о графике является составной частью информации об игре, вставим переменную созданного типа в качестве элемента в структуру игры:
Structure типИгра
Dim Название As String
Dim Объем As Integer
Dim Графика As типГрафика
End Structure
Теперь мы можем писать такие операторы:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim Игра(30) As типИгра
Игра(8).Графика.Хорошая = True
Игра(14).Графика.Число_цветов = 65000000
Игра(29).Графика.Максимальное_разрешение = "1280х960"
Debug.WriteLine(Игра(14).Графика.Число_цветов)
End Sub
Заключение. Вы увидели, что составные элементы структуры сами могут включать в себя структуры. Они могут вкладываться друг в друга, как матрешки, до бесконечности. Так можно создать иерархии данных практически в любой области знаний. Примеры же о массивах и объектах в качестве элементов структур я не буду рассматривать, так как они кажутся мне несколько сложноватыми для начинающих.
Задание 109.
Создайте базу данных о себе, ближайших родственниках или друзьях. О каждом должно быть известно:
Массивы не используйте. Программа должна:
Задание 110.
Создайте базу данных о своих книжках. О каждой книжке должно быть известно:
Обязательно используйте массив не меньше, чем из 5 структур. Программа должна:
Оператор With
Когда мы используем длинные имена структур или объектов или глубоко вложенные друг в друга конструкции, обращение к элементам таких структур или объектов занимает в окне кода довольно много места. Например:
Игра(29).Графика.Хорошая = True
Игра(29).Графика.Число_цветов = 65000000
Игра(29).Графика.Максимальное_разрешение = "1280х960"
Оператор With позволяет сократить запись. Вот фрагмент, равносильный предыдущему:
With Игра(29).Графика
. Хорошая = True
. Число_цветов = 65000000
. Максимальное_разрешение = "1280х960"
End With
Как видите, оператор With не производит никаких действий. Он просто позволяет перенести в свой заголовок одинаковую левую часть из всех обращений к элементам объекта или структуры, за счет чего эти обращения могут быть записаны в укороченном виде.
Понятие объекта, как экземпляра класса
Трудности. До сих пор мы с вами успешно пользовались свойствами и методами различных объектов, не встречая препятствий. Например, методом WriteLine класса Debug или методом Hide объекта Button1. Или свойством Width объекта Button1. Однако существуют объекты, их свойства и методы, которые не позволяют пользоваться ими напрямую, как мы делали до этого.
Попробуем, например, в окне кода изменить размер шрифта для текстового поля. Для этого напишем казалось бы правильный оператор:
TextBox1.Font.Size = 14
Здесь Font – шрифт, а Size – размер. Однако, VB подчеркивает его, а когда мы ставим на него мышь, выдает подсказку Property ‘Size’ is ‘ReadOnly’, что означает «Свойство Size предназначено только для чтения», то есть мы можем его значение видеть, но менять не можем. Почему?
Еще один пример. Попробуем начертить на форме круг. Я знаю, что для этого подходит метод DrawEllipse класса Graphics. Я пробую записать оператор
Graphics.DrawEllipse
но VB после того, как я поставил точку за словом Graphics, мне не предлагает метода DrawEllipse. Вручную писать его тоже бесполезно. Почему не предлагает?
Преодоление. В обоих случаях выход – в создании объектов, являющихся так называемыми экземплярами класса. Но не думайте, что экземпляры класса – это некий аспирин, который пьют только во время болезни. Объекты – экземпляры класса – это мощное и важное средство всего объектного программирования, то, без чего оно потеряло бы весь свой смысл.
Что такое объект – экземпляр класса? Вы видели, как дети в песочнице делают куличики из формочки? Так вот: формочка – это класс, куличик – это экземпляр класса. Формочка – одна, куличиков из нее – много. Или еще: штампуем алюминиевые тарелки. Штамп – это класс, тарелки – экземпляры класса.
Вот более точная аналогия. Скульптор долго создавал и наконец создал статую. Это оригинал статуи. Он один такой. Затем на заводе при помощи простейших приспособлений изготавливают сколько угодно точных копий этой статуи. Оригинал статуи – это класс. Копии – экземпляры класса.
Понятие объекта именно как экземпляра класса настолько вошло в традицию, что когда программист слышит слово «объект», то первое, что он представляет при этом – экземпляр класса. В дальнейшем изложении я тоже буду следовать этой традиции и употреблять термин «объект» именно в этом смысле. Однако нужно помнить, что в программировании слово «объект» используют также и в широком смысле: объектами называют и классы, и структуры и пространства имен и др. Я постараюсь избегать такого употребления.
А сейчас вы увидите и «пощупаете» объекты – экземпляры класса.
Создаем объекты из класса
Вручную. Создайте проект. Разместите на форме несколько кнопок. Знаете, что вы сейчас сделали? Догадайтесь. Ну конечно же, вы создали несколько объектов из класса! Класс – это Button, вот он – в облике кнопки Button в окне Toolbox. А вот созданные вами объекты в облике кнопок на форме – Button1, Button2, Button3. Это – экземпляры класса Button.
Вы видите, что при своем рождении объекты-кнопки одинаковы. Они во многом являются копиями своего класса. Во многом, но не во всем. После рождения вы можете изменять свойства объектов, например, одну кнопку растянуть и покрасить в желтый цвет, другую – уменьшить и покрасить в синий цвет. Однако, яблочко от яблони недалеко падает, поэтому кнопка останется кнопкой, сколько бы вы ни меняли ее внешний вид.
Аналогично вы можете «отштамповать» сколько угодно объектов из класса TextBox и других.
В коде. Объекты из класса можно создавать не только вручную в режиме проектирования, как мы только что сделали, но и запрограммировав это действие в окне кода. Напишите такой код для кнопки, скажем, Button2:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Dim Кнопка1 As Button
Кнопка1 = New Button()
Кнопка1.Text = "Кнопка №1"
Debug.WriteLine(Кнопка1.Width)
Me.Controls.Add(Кнопка1)
Dim Кнопка2 = New Button()
Кнопка2.Text = "Кнопка №2"
Me.Controls.Add(Кнопка2)
Кнопка2.Location = New Point(0, 50)
End Sub
Запустите проект. Нажмите кнопку Button2 – у вас на форме появятся еще две кнопки. Теперь выполните проект в пошаговом режиме, одновременно читая пояснения.
Пояснения:
Создаваемый объект нужно объявлять. Так, объявляя числовую переменную, вы придумывали ей имя и указывали, чтобы она принадлежала типу, скажем, Double, поэтому писали оператор
Dim Глубина As Double
Согласно этому оператору VB отводил в памяти место под переменную типа Double.
Объявляя объект, вы тоже придумываете ему имя и указываете, чтобы он принадлежал типу, скажем, Button, поэтому пишете оператор
Dim Кнопка1 As Button
Button – это один из многих типов (классов) объектов – элементов управления.
Глубина – это переменная величина, имеющая тип Double, являющийся числом.
Кнопка1 – это переменная величина, имеющая тип Button, являющийся классом.
Говорят, что переменная Кнопка1 имеет объектный тип. Это значит, что ее значением является не число и не строка, а объект – экземпляр класса. Пусть вас не удивляет, что такая сложная вещь, как объект, может быть значением такой простой вещи, как переменная величина. В конце концов, значением переменной величины Город_где_я_сейчас_нахожусь является такая грандиозная вещь, как Москва.
Итак, после выполнения первой строки в процедуре компьютер знает, что переменная Кнопка1 будет кнопкой, но новая кнопка пока еще не создана. Чтобы убедиться в этом, поставьте мышь в окне кода на слово Кнопка1 – всплывет подсказка Кнопка1=Nothing. Слово Nothing по-английски – «Ничто».
Чтобы создать кнопку, вы выполняете оператор
Кнопка1 = New Button()
Слово New вместе с Button означает, что создается НОВЫЙ объект класса Button. В этот момент и «штампуется тарелка». Чтобы убедиться в этом, поставьте мышь на слово Кнопка1 – всплывет подсказка Кнопка1={System.Windows.Forms.Button}. Это означает, что Кнопка1 стала объектом класса Button пространства имен System.Windows.Forms. VB отводит в памяти место под объект класса Button.
Объект создан, но создан только в памяти компьютера, на поверхности формы он пока не виден. Тем не менее, мы уже можем менять его свойства и узнавать их значение:
Кнопка1.Text = "Кнопка №1"
Debug.WriteLine(Кнопка1.Width)
Чтобы кнопка появилась на форме, форма должна «принять ее в свое лоно», добавить (Add) ее в коллекцию своих элементов управления (Controls):
Me.Controls.Add(Кнопка2)
Подробнее о коллекциях см. в 16.2.
(В пошаговом режиме созданная кнопка будет реально видна только после завершения процедуры.)
Создадим теперь из того же класса Button еще одну кнопку. Для краткости оператор объявления и оператор создания объекта объединяем в один:
Dim Кнопка2 = New Button()
Далее аналогично:
Кнопка2.Text = "Кнопка №2"
Me.Controls.Add(Кнопка2)
По умолчанию все кнопки появляются на форме в ее левом верхнем углу, и чтобы новая кнопка не загородила старую, я сдвину ее на 50 пикселей вниз:
Кнопка2.Location = New Point(0, 50)
Смысл этой строки вы поймете позже, а сейчас не будем отвлекаться.
Наши новые кнопки выглядят вполне прилично, но ничего пока не умеют делать. Как научить их работать, написано в 22.13.
Мы сейчас создавали элементы управления. Однако классов в VB гораздо больше, чем элементов управления. Как создавать объекты из них? Это делается в коде и совершенно аналогично тому, как мы делали это только что.
Невидимый код в окне кода – Windows Form Designer generated code
Итак, мы знаем два способа создания объекта из класса: «ручной» и «в коде». Однако первого из них на самом деле нет J, это все одна видимость, обман.
Чтобы проверить это, создайте новый проект (только совсем новый, а не скопированный из старого). Мы в режиме проектирования. Перед нами – квадрат формы Form1. Добавьте кнопку. Запускаем проект – на экране окно с кнопкой – копия формы. Приложение работает: мы можем перемещать окно, изменять его размер, нажимать кнопку и т.д. Парадокс вот в чем. Помните основной принцип работы компьютера? – Компьютер пальцем о палец не ударит без программы, то есть без кода. Но раз приложение работает, значит существует код, управляющий этой работой. Но ведь мы его не писали! Где же он?
Мы делаем единственный возможный вывод: код присутствует, только мы его не видим. Когда вы при помощи Toolbox размещаете на форме кнопку, VB автоматически и невидимо для вас пишет в окне кода программный текст подобный тому, что мы писали в предыдущем подразделе. Он-то, этот невидимый код, и делает все дело – создает кнопку. А все эти щелчки по Toolbox и по форме – так только, для нашего удобства и удовольствия.
Более того. В режиме проектирования форму на экран мы специально не помещали, она сама там появилась, вернее, нам ее подсунул услужливый VB. Форма – такой же объект, как и кнопка. Значит в окне кода должен также присутствовать невидимый код, создающий Form1? Так оно и есть.
Посмотрим на этот невидимый код. Перейдем в окно кода. Вот что мы видим (Рис. 6.1).

Рис. 6.1
Пока немного. Но это код видимый. А нам нужен невидимый. Для этого щелкните по плюсику в малюсеньком квадратике слева от строки Windows Form Designer generated code. Строка эта означает «Код, автоматически создаваемый дизайнером форм Windows». Вы увидите этот невидимый код. Здесь я привожу его, выкинув комментарии и пустые строки:
#Region " Windows Form Designer generated code "
Public Sub New()
MyBase.New()
InitializeComponent()
End Sub
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub
Private components As System.ComponentModel.IContainer
Friend WithEvents Button1 As System.Windows.Forms.Button
Me.Button1 = New System.Windows.Forms.Button
Me.SuspendLayout()
Me.Button1.Location = New System.Drawing.Point(24, 24)
Me.Button1.Name = "Button1"
Me.Button1.TabIndex = 0
Me.Button1.Text = "Button1"
Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Me.ClientSize = New System.Drawing.Size(192, 77)
Me.Controls.Add(Me.Button1)
Me.Name = "Form1"
Me.Text = "Form1"
Me.ResumeLayout(False)
End Sub
#End Region
Вы видите здесь три непонятного вида процедуры и еще кое-что. Разбираться в этом сложном коде мы не будем. Но на некоторые моменты обратим внимание.
Некоторые операторы почти понятны. Я их выделил полужирным шрифтом Например,
Me.Button1.Text = "Button1"
Вы даже можете в качестве эксперимента изменить в нем текст на кнопке. Но вообще менять что-то в операторах Windows Form Designer generated code не рекомендую. Если вам нужно изменить какие-то свойства формы и элементов управления, делайте это привычным образом через окно свойств. Вы увидите, что при этом изменится и код в Windows Form Designer generated code. А теперь посмотрите, как он дополнится, когда вы при помощи Toolbox разместите на форме еще и текстовое поле с меткой.
В заголовках процедур, в строке #Region и в других местах мы видим минусики в квадратиках. Щелкая по ним, мы скрываем из вида части кода (например, тела процедур), а минусики превращаются в плюсики. Все, как в проводнике Windows. Сделано это для удобства, для экономии места на экране. Щелкнув по минусику в строке #Region, мы скроем весь невидимый код. Пощелкайте.
Итак, мы сейчас увидели код, создающий форму и элементы управления. А где код, управляющий их поведением? Например, тем, что края формы можно таскать мышкой, что кнопка при нажатии слегка меняет вид, что в текстовом поле при нажатии клавиш клавиатуры появляются буквы? Этот код нам не увидеть, он запрятан в классах Form, Button и TextBox. Получается, что класс состоит из кода, управляющего поведением объектов этого класса.
Удобство визуального программирования
Мы, в принципе, показали, что при создании проекта можем в режиме проектирования не пользоваться окном свойств, окном Toolbox и даже вообще не заходить в закладку Form1.vb [design]. Вместо этого достаточно написать правильный код в окне кода и, запустив проект, увидеть правильно работающую форму со всеми нужными элементами управления. Но такой код писать долго и скучно, а для начинающего – еще и непонятно. Удобство визуального программирования в том и состоит, что VB освобождает вас от написания этого кода, заменив его удобными манипуляциями с окном свойств, окном Toolbox и другими окнами и панелями инструментов. А код автоматически создает сам, да еще и прячет его, чтобы не утомлять глаза программиста.
В режиме проектирования VB создает иллюзию существования на экране формы и элементов управления и даже позволяет менять их свойства, создавая видимость того, что у них изменились размеры, цвет и т.д. Но не забывайте, что это одна видимость. никакой формы и элементов управления в режиме проектирования нет. Существуют лишь их удобные видимые образы, как две капли воды похожие на оригиналы. И любая ваша манипуляция в режиме проектирования ничуть не меняет оригинал (которого нет), а меняет лишь невидимый код и видимый образ. Ведь код в окне кода, и видимый, и невидимый, выполняется не в режиме проектирования, а позже – после запуска проекта на выполнение, поэтому и настоящая форма, и настоящие элементы управления по командам кода порождаются и начинают жить реальной жизнью только после запуска проекта, в режиме работы.
Класс – это программа
Вы конечно же знаете общий принцип работы компьютера, заключающийся в том, что все, что компьютер ни делает, он делает по программе. Это значит, что если вы что-то видите на экране или на экране что-то происходит, значит кто-то когда-то написал программу, чтобы это появилось на экране и работало.
Где находится программа, которая приказывает компьютеру в режиме работы при нажатии на кнопку Button1 показывать ее немного «вдавленной»? Она невидимо для нас находится в классе Button (или в одном из его родительских классов, о которых мы с вами поговорим позже). Там же находятся и другие программы, управляющие поведением кнопки. Каждая из кнопок, рожденных из класса Button, управляется одинаковыми программами, а именно теми программами, которые находятся в этом классе.
Где находится программа, которая приказывает компьютеру в режиме работы во время ввода информации в текстовое поле TextBox1 при нажатии на клавиатуре буквы «Ш» показывать на экране в этом поле именно эту букву? Она находится в классе TextBox. Там же находятся и другие программы, управляющие поведением текстового поля. Каждое из текстовых полей, рожденных из класса TextBox, управляется одинаковыми программами, а именно теми программами, которые находятся в этом классе.
То же самое относится и к другим элементам управления и к форме.
Вообще, можно сказать, что класс – это программа. Класс входит в пространство имен, а пространство имен входит в сборку библиотеки классов .NET Framework, расположенную в файле (см. 4.2.4). До сих пор мы пользовались классами, не видя их кода, и дальше мы тоже его не увидим. За ненадобностью. Или потому, что авторы кода не хотят делиться своей интеллектуальной собственностью.
Невидимые объекты
В этой главе мы будем создавать объекты – экземпляры классов из библиотеки классов .NET Framework, которые дают нам возможность рисовать. В дальнейшем мы будем создавать уже собственные классы и их экземпляры. Программист – скульптор. Он долго пишет длинную программу – класс. А затем парой строчек кода он может создать сколько угодно объектов – экземпляров этого класса.
Форма и элементы управления – «видимые» объекты. Почему эти объекты видимы? Потому что в VS содержится и в режиме работы постоянно выполняется программа, которая рисует эти объекты. Как только мы меняем внешний вид объекта, например, размер формы, эта программа автоматически его перерисовывает на экране в соответствии с внесенными изменениями.
Но мы-то уже знаем, что вся механика, все особенности поведения объекта содержатся не в этой простой программе рисования объекта на экране, а в том коде, из которого состоит класс данного объекта. Если убрать эту программу рисования, объект останется, только будет невидим. Вы спросите: какой от него в таком случае толк? А вот, например, мы пользовались проигрывателем Windows Media Player. При воспроизведении музыки мы могли сделать его невидимым и тем не менее музыку слышали. Значит польза была и от невидимого объекта. Или вот хотя бы класс Math, позволяющий нам пользоваться математическими функциями. Он невидим за ненадобностью какой бы то ни было видимости, а польза от него несомненна.
В VB и вообще в Windows присутствует огромное количество невидимых, но очень полезных объектов. Как с ними общаться, если они невидимы? Очень просто:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim a As Double = InputBox("Введите число")
Dim N As Integer = Math.Round(a)
MsgBox(N)
End Sub
В рассмотренном примере мы сообщили невидимому классу Math значение переменной a, задав его при помощи InputBox, а результат увидели при помощи MsgBox. Но чтобы воспользоваться объектом-невидимкой, мы должны знать механику его работы. В случае с классом Math это означает знать его функции. Впрочем, то же верно и по отношению к видимым объектам.
Таким образом, невидимые объекты общаются с нами через текстовые поля, кнопки и другие стандартные элементы VB. А есть невидимые объекты, которые с нами вообще не общаются, а общаются только с другими объектами, помогая им работать. Мы о них можем ничего и не знать.
Объекты – экземпляры класса
Работа с графикой в VB основывается на знании так называемых графических объектов. Поэтому данный раздел будет посвящен не графике, а более близкому знакомству с объектами. Не перепрыгивайте через него: чего хорошего, если вы будете рисовать, не понимая, как вы это делаете?
Класс Graphics
На чем мы можем рисовать в VB? На форме, на кнопке, на текстовом поле, на графическом поле PictureBox, на других элементах управления. Но ведь в коде классов формы и элементов управления нет программ рисования. Вы можете убедиться в этом, поставив точку после слова Button1 и просмотрев открывшийся список. Вы не найдете там ничего, что позволяет рисовать. Для обеспечения рисования в VB имеется специальный класс Graphics, расположенный в пространстве имен System.Drawing. Его код и содержит программы для рисования фигур и для другой работы с графической информацией.
Зайдите в Object Browser (см. 4.2.4) и найдите класс Graphics. Щелкните по нему – в правой панели вы увидите свойства и методы этого класса. Среди них много методов рисования фигур – хотя бы все те, что начинаются на Draw. Прекрасно. Однако, если вы в окне кода напишете слово Graphics и поставите точку, то в открывшемся списке вы этих методов не увидите. Значит, так просто их применять нельзя.
Все богатство возможностей класса рисования Graphics нам станет доступно лишь тогда, когда мы из этого класса создадим объект. Объект, как вы знаете, это «копия», экземпляр класса, обладающий всеми нужными его возможностями. Объект этот невидимый, но нам это безразлично, так как все равно рисовать он будет не «на себе», а на форме или элементе управления.
Существует еще такое требование: форме и каждому элементу управления – свой объект класса Graphics. Это значит, что если мы собираемся рисовать на форме, двух кнопках и на метке, то нам придется создавать 4 объекта класса Graphics. Сделано это потому, что одному объекту рисовать сразу на нескольких разных предметах трудновато, а удобнее настроиться на какой-то один.
Первая нарисованная линия
Задача: Нарисовать на форме отрезок прямой линии синего цвета между двумя точками. Координаты на форме первой точки: x=50, y=20. Координаты второй точки: x=200, y=100.
Создайте проект. Вспомните систему координат (3.5). Прикиньте глазами, где примерно должен проходить отрезок. Разместите на форме кнопку, при щелчке по которой будет нарисован отрезок.
Начинаем с того, что объявляем объект класса Graphics:
Dim Графика_для_формы As Graphics
Теперь нужно этот объект создать. Пока мы знаем, что объекты создаются при помощи слова New. Однако для объектов класса Graphics мы воспользуемся другим способом. У формы и элементов управления есть специальный метод CreateGraphics, при помощи которого каждый элемент управления или форма создает свой собственный, персональный объект класса Graphics.
Графика_для_формы = Me.CreateGraphics
Объект создан. Поскольку он создан методом CreateGraphics, принадлежащим форме, то он сможет рисовать только на ней.
Теперь можно рисовать. Для этого воспользуемся методом DrawLine объекта Графика_для_формы:
Графика_для_формы.DrawLine(Pens.Blue, 50, 20, 200, 100)
Четыре числа в качестве аргументов метода DrawLine – не что иное, как координаты двух точек, между которыми проводится отрезок. А вот про аргумент Pens.Blue поговорим подробнее. Он определяет цвет линии. Blue – это синий цвет. А при чем тут Pens? Поясняю.
Мы уже привыкли, что VB ведет себя словно старый бюрократ. Помните – когда мы хотели кнопку сделать красной, он заставлял вместо Red писать Color.Red (см. 2.1.3)? Это означает, что цвет Red принадлежит в качестве свойства структуре Color. Употребляя аналогию, можно сказать: краски для того, чтобы покрасить форму или элемент управления, мы должны брать из «коробки для красок», которую зовут Color.
Линии фигур VB чертит пером. Существует специальный класс Pens («Перья»), содержащий несколько десятков перьев различных цветов. Запись Pens.Blue как раз и означает взятое из этой «коробки для перьев» перо синего цвета. Попробуйте в качестве первого аргумента метода DrawLine указать не Pens.Blue, а Color.Blue – у вас ничего не получится: цвета одинаковые, да «коробки» разные.
В дальнейшем я более солидно обосную, где и какие выражения можно или нельзя писать. Вот текст процедуры целиком:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim Графика_для_формы As Graphics
Графика_для_формы = Me.CreateGraphics
Графика_для_формы.DrawLine(Pens.Blue, 50, 20, 200, 100)
End Sub
Запустите проект, нажмите кнопку. Вот что вы увидите (Рис. 6.2).

Рис. 6.2
Слева сверху первая крайняя точка отрезка, справа снизу – вторая.
Рисуем отрезки, прямоугольники, круги, эллипсы
Поставим задачу нарисовать на форме 7 фигур, как изображено на Рис. 6.3. Числа на рисунке обозначают горизонтальную и вертикальную координаты на форме.

Рис. 6.3
Оба отрезка прямых будем рисовать методом DrawLine.
Прямоугольник и квадрат будем рисовать методом DrawRectangle.
Окружность и эллипсы (сплющенные окружности) будем рисовать методом DrawEllipse.
Вот программа, решающая задачу:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim Граф As Graphics = Me.CreateGraphics
'Левый отрезок: 0, 400 - координаты одной из точек отрезка, 300, 0 – другой:
Граф.DrawLine(Pens.Black, 0, 400, 300, 0)
'Правый отрезок:
Граф.DrawLine(Pens.Black, 400, 300, 600, 400)
'Прямоугольник: '400, 300 - координаты левого верхнего угла прям-ка, 200 - ширина пр-ка, 100 - высота
Граф.DrawRectangle(Pens.Black, 400, 300, 200, 100)
'Эллипс, вписанный в этот прямоугольник - параметры те же, что и у прямоугольника:
Граф.DrawEllipse(Pens.Black, 400, 300, 200, 100)
Граф.DrawRectangle(Pens.Black, 200, 300, 100, 100) 'Квалрат
Граф.DrawEllipse(Pens.Black, 400, 0, 200, 200) 'Круг
Граф.DrawEllipse(Pens.Black, 0, 0, 200, 400) 'Высокий эллипс
End Sub
Не забывайте, что после ввода каждого очередного оператора проект нужно запускать и проверять, как он работает.
Пояснения:
Внутри процедуры для рождения объекта класса Graphics достаточно вместо двух операторов
Dim Граф As Graphics
Граф = Me.CreateGraphics
написать один
Dim Граф As Graphics = Me.CreateGraphics
Примечание. В дальнейших примерах я для краткости часто буду опускать операторы, порождающие объект Граф, поэтому, столкнувшись невзначай с именем Граф, знайте, что это всего лишь объект класса Graphics.
Будем называть величины, указанные в скобках за именем метода, параметрами метода. Сравните их с координатами и размерами фигур на рисунке, а также прочитайте комментарии к коду и дальнейшие пояснения.
Отрезки прямых рисуются методом DrawLine. Мы знаем, что отрезок прямой можно построить, если известно положение двух его крайних точек. Они-то и задаются в обращении к методу. Первая пара параметров вслед за указанием пера – координаты одной точки (любой из двух), вторая пара – другой. Первое число в каждой паре - горизонтальная координата, второе число - вертикальная.
Прямоугольники рисуются методом DrawRectangle. Прямоугольник можно построить, если известно положение его верхнего левого угла, ширина и высота. Четыре параметра в скобках вслед за указанием пера как раз и определяют эти величины.
Квадрат, как известно, тоже прямоугольник, поэтому чертится тем же методом.
Эллипсы рисуются методом DrawEllipse. Вокруг каждого эллипса можно описать прямоугольник. В правой нижней части рисунка вы как раз и видите эллипс с описанным вокруг него прямоугольником. Если вы хотите начертить эллипс, вообразите прямоугольник, описанный вокруг него, и параметрами для метода DrawEllipse укажите параметры для рисования этого воображаемого прямоугольника.
Круг – это эллипс, у которого одинаковы ширина и высота, поэтому чертится тем же методом.
Координаты в методах могут быть и отрицательными. В результате вся фигура или ее часть оказывается левей или выше формы и поэтому не видна. Координаты в методах могут быть и слишком большими положительными. В результате вся фигура или ее часть оказывается правей или ниже формы и поэтому не видна.
Задание 1.
Нарисуйте человечка и паровозик, как на Рис. 6.4. Велосипед можно не рисовать. Старайтесь, чтобы у вас вышла полная копия того, что вы видите на рисунке.

Рис. 6.4
Рисуем дуги, сектора и закрашенные фигуры
Поставим задачу нарисовать на форме 8 фигур, таких, как на Рис. 6.5.

Рис. 6.5
Вот программа, решающая задачу:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim Граф As Graphics = Me.CreateGraphics
'Закрашенный прямоугольник
Граф.FillRectangle(Brushes.Yellow, 50, 50, 150, 100)
'Закрашенный эллипс
Граф.FillEllipse(Brushes.Yellow, 250, 50, 150, 100)
'Дуга величиной 60 градусов, начиная от 30 градуса:
Граф.DrawArc(Pens.Black, 50, 150, 100, 100, 30, 60)
'Дуга величиной 270 градусов, начиная от 0 градуса:
Граф.DrawArc(Pens.Black, 50, 300, 100, 100, 0, 270)
'Сектор величиной 60 градусов, начиная от 30 градуса:
Граф.DrawPie(Pens.Black, 200, 150, 100, 100, 30, 60)
'Сектор величиной 270 градусов, начиная от 0 градуса:
Граф.DrawPie(Pens.Black, 200, 300, 100, 100, 0, 270)
'Закрашенный сектор величиной 60 градусов, начиная от 30 градуса:
Граф.FillPie(Brushes.Yellow, 350, 150, 100, 100, 30, 60)
'Закрашенный сектор величиной 270 градусов, начиная от 0 градуса:
Граф.FillPie(Brushes.Yellow, 350, 300, 100, 100, 0, 270)
End Sub
Пояснения: Аналогично тому, как для рисования линий фигур вы должны выбрать перо, для рисования закрашенных фигур вы должны выбрать кисть. Подобно тому, как существует класс Pens, содержащий несколько десятков перьев различных цветов, существует и специальный класс Brushes, содержащий несколько десятков кистей различных цветов. Запись Brushes.Blue означает взятую кисть синего цвета.
Обращение к методам рисования закрашенных фигур отличается от обращения к методам рисования таких же незакрашенных фигур только тем, что в скобках вы вместо пера указываете кисть.
Рассмотрим верхние две фигуры рисунка.
Закрашенный прямоугольник рисуется методом FillRectangle.
Закрашенный эллипс рисуется методом FillEllipse.
Что касается дуг и секторов, то если вы не знакомы с градусной мерой угла, вам придется туговато. Напомню для тех, кто ее забыл: разрежьте круглый торт от центра на 360 одинаковых кусков, тогда остренький уголок каждого куска называется градусом.
Обратите внимание, что все три фигуры в нижнем ряду имеют одну и ту же градусную меру. То же можно сказать и про три фигуры в среднем ряду.
Две дуги (на рисунке они слева) рисуются методом DrawArc. Под дугой понимается дуга (кусок) эллипса (окружности). Поэтому первые пять параметров метода имеют тот же смысл, что и первые пять параметров метода DrawEllipse. Но добавляются еще два параметра, задающие в градусах размер и положение дуги на эллипсе (или окружности). На Рис. 6.6 поясняется смысл этих параметров. Конкретно, показана верхняя из двух дуг. Для простоты я выбрал дугу окружности. Углы измеряются по часовой стрелке от направления направо (на восток). Первый из двух параметров указывает начало дуги (30о), второй – длину дуги (60о).

Рис. 6.6
Два сектора (на рисунке они в центре) рисуются методом DrawPie. Все параметры имеют тот же смысл, что и в DrawArc.
Два закрашенные сектора (на рисунке они справа) рисуются методом FillPie. Отличается он от DrawPie только наличием кисти вместо пера.
Задание 2.
Нарисуйте цветочек, как на Рис. 6.7.

Рис. 6.7
Подсказка: Нарисуйте в одном месте два прямоугольника: сначала закрашенный, а потом – незакрашенный.
Следите пока, чтобы параметры не были дробными. О причинах такой осторожности я расскажу еще.
Рисуем на нескольких элементах управления
Создадим такой проект (Рис. 6.8).

Рис. 6.8
Слева мы видим текстовое поле (TextBox1), посредине – графическое поле (PictureBox1), справа – кнопки (начиная с Button1 наверху и кончая Button7 внизу). Смысл кнопок во многом ясен из надписей на них.
Вот программа. Ниже – пояснения.
Public Class Form1
Inherits System.Windows.Forms.Form
Windows Form Designer generated code
'Объявляем графические объекты для формы и элементов управления:
Dim Граф_для_формы As Graphics
Dim Граф_для_текстов_поля As Graphics
Dim Граф_для_графич_поля As Graphics
Dim Граф_для_кнопки As Graphics
'Создаем графические объекты для формы и элементов управления:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Граф_для_формы = Me.CreateGraphics
Граф_для_текстов_поля = TextBox1.CreateGraphics
Граф_для_графич_поля = PictureBox1.CreateGraphics
Граф_для_кнопки = Button5.CreateGraphics
End Sub
'Рисуем на форме:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Граф_для_формы.DrawLine(Pens.Black, 0, 0, 3000, 900)
End Sub
'Рисуем на текстовом поле:
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
Граф_для_текстов_поля.DrawRectangle(Pens.Black, 20, 20, 200, 120)
End Sub
'Рисуем на графическом поле:
Private Sub Button4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button4.Click
Граф_для_графич_поля.FillPie(Brushes.Red, 30, 30, 80, 150, 90, 330)
End Sub
'Рисуем на кнопке:
Private Sub Button5_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button5.Click
Граф_для_кнопки.DrawEllipse(Pens.Black, 10, 10, 30, 30)
End Sub
'Стираем с текстового поля:
Private Sub Button6_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button6.Click
Граф_для_текстов_поля.Clear(Color.White)
End Sub
' Уничтожаем графические объекты для формы и элементов управления:
Private Sub Button7_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button7.Click
Граф_для_формы.Dispose()
Граф_для_текстов_поля.Dispose()
Граф_для_графич_поля.Dispose()
Граф_для_кнопки.Dispose()
End Sub
End Class
Пояснения. Прежде всего, я объявил графические объекты вне процедур, а не внутри. По той простой причине, что использую каждый графический объект не в одной, а в нескольких процедурах. Иначе графический объект, объявленный внутри одной процедуры, не был бы «виден» из других (подробнее о видимости переменных рассказано в 11.3).
Кнопка Button1 создает все графические объекты. Все остальные кнопки заставляют эти графические объекты что-нибудь сделать. Попробуйте, запустив проект, начать работу с нажатия не верхней кнопки, а какой-нибудь другой – VB выдаст ошибку. Действительно: нельзя заниматься графическими работами с нерожденными графическими объектами.
Смысл кнопок с Button2 по Button5 очевиден: каждая из них рисует то, что вы видите на рисунке.
Кнопка Button6 стирает. Для стирания всего, что нарисовано на объекте, существует метод Clear. В нем нужно указать цвет, которым все стирается. Этот цвет является свойством структуры Color. Я выбрал белый цвет. Можно было выбрать любой.
Если вы захотите методом Clear стереть что-нибудь на форме или другом элементе управления, то перед вами встанет вопрос, а какой цвет у этого объекта? Заглянув в окно свойств, вы увидите скорее всего, что это цвет Control. Но в структуре Color такого цвета нет! Что же делать? – Или заранее покрасить форму или элемент управления в другой цвет или почитать о системных цветах в 12.7.1.
Кнопка Button7 уничтожает все графические объекты. Каждый созданный объект, даже если он невидим, расходует ресурсы компьютера или, как нынче говорят, «напрягает» компьютер. Хорошим тоном у программистов является заботиться о компьютере и не перенапрягать его. Поэтому, когда ясно, что объект отработал свое и больше не понадобится, его уничтожают методом Dispose.
Замечания. Структура Color предлагает нам полторы сотни цветов, а окно свойств – гораздо большее количество. В 12.7 я расскажу вам, как пользоваться в коде несколькими миллионами цветов.
Обратите внимание, что рисунок в текстовом окне стирается и тогда, когда вы вручную начнете стирать в нем текст. А рисунок с кнопки стирается тогда, когда вы щелкнете по другой кнопке или по текстовому полю. Если вы, таская форму по экрану, затащите часть ее за край экрана, то все, нарисованное в этой части, сотрется. Если же форма полностью пропадет из вида, то сотрется все. О причинах этого явления и о том, как с ним бороться, написано в 12.5
Подробности о работе объекта класса Graphics. Когда объект класса Graphics создается методом CreateGraphics, он в момент создания запоминает размеры поверхности, на которой будет рисовать. Пусть, например, это форма. Размеры поверхности он запоминает как размеры формы в момент своего создания. Если же мы впоследствии изменим размеры формы, размеры поверхности рисования от этого не изменятся, что может нам не понравиться.
Это легко проверить. Запустите проект. Щелкните по первой кнопке. Объект порожден. А теперь растяните форму и щелкните по второй кнопке. На форме рисуется идущий наискосок отрезок прямой. Судя по координатам, он должен был быть очень длинным, однако он рисуется только в пределах размеров формы, какой она была при щелчке по первой кнопке.
Если вы не хотите задумываться об этой проблеме, объявляйте и создавайте графический объект прямо в той процедуре, которая рисует, как я и делал в этой книге в большинстве программ.
С учетом вышесказанного не советую создавать графический объект для формы в процедуре Form1_Load, то есть когда форма еще не появилась на экране.
Пишем
На форме и элементах управления можно не только рисовать, но и писать, то есть изображать на их поверхности текст. Вы скажете: это не новость, мы уже делали это, устанавливая свойство Text для текстового поля, метки и кнопки. Но это другое. Здесь мы будем обладать свободой писать текст в любой точке поверхности объекта и у нас будет гораздо больше возможностей для его красивого оформления (см., например, рисунки в 12.2.3). Здесь текст будет не задаваться, как мы это делали раньше, а рисоваться
методами объекта класса Graphics, как мы рисуем линии, кружочки и пр.
Продолжим работу над предыдущим проектом. Добавим в него кнопку Button8 «Пишем». По нажатии на нее на поверхности формы и элементов управления должно появиться четыре слова приветствия: "Привет!", "Здравствуй!", "Салют!", "Hello!" (см. Рис. 6.9).

Рис. 6.9
Начнем с того, что в режиме проектирования изменим для кнопки Button5 свойство Font (шрифт): сделаем шрифт пожирней и побольше. Вы видите результат этого изменения на рисунке в виде текста «Рисую на себе». Аналогично немного увеличьте и сделайте курсивным шрифт текстового поля TextBox1. Но это все не то – это все вспомогательные действия в режиме проектирования. Нас интересует рисование текста в коде.
Рисованием текста занимается все тот же объект класса Graphics. Для этого он использует свой метод DrawString («Нарисуй строку»). Как будто бы текст – это та же фигура, только поизвилистей.
Добавьте в проект следующую процедуру:
'Пишем на форме и элементах управления:
Private Sub Button8_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button8.Click
Граф_для_формы.DrawString("Привет!", Button5.Font, Brushes.Red, 70, 280)
Граф_для_текстов_поля.DrawString("Здравствуй!", Me.Font, Brushes.Brown, 50, 100)
Граф_для_графич_поля.DrawString("Салют!", Button5.Font, Brushes.White, 35, 70)
Граф_для_кнопки.DrawString("Hello!", TextBox1.Font, Brushes.Blue, 0, 0)
End Sub
Разберемся, что здесь написано.
Форма и каждый элемент управления пишут на себе своими собственными объектами класса Graphics. В том варианте метода DrawString, который я использую, в скобках вы видите 5 параметров, разделенных запятыми:
Пишем строковые выражения. Текстовая строка, являющаяся 1 параметром метода, может быть строковым выражением. Например, фрагмент
Dim a As Integer = 100
Dim s As String = " попугаев"
Граф.DrawString(a & " разноцветных" & s, Me.Font, Brushes.Black, 0, 0)
напишет на форме: 100 разноцветных попугаев
Свободное управление в коде параметрами шрифта отложим до 12.2.3.
Переменные и выражения вместо чисел
В качестве параметров графических (да и других) методов можно употреблять не только числа, как мы делали до этого, но и переменные, и выражения. Приведу два фрагмента, которые рисуют в одном и том же месте один и тот же прямоугольник:
Граф.DrawRectangle(Pens.Black, 50, 20, 200, 100)
и
Dim a As Integer = 50
Dim b As Integer = 20
Граф.DrawRectangle(Pens.Black, a, b, 10 * b, a + a)
Следите только пока, чтобы не было дробных чисел.
Вообще, в будущем, объясняя какой-нибудь новый оператор, я часто буду для простоты ограничиваться коротенькими примерами его записи, как в первом фрагменте. Вы должны знать, что почти везде на месте числа может стоять числовая переменная или арифметическое выражение, на месте строки – строковая переменная или строковое выражение. И вообще, вместо литерала данного типа может стоять переменная или выражение этого типа.
Задача: Построить треугольник по трем его вершинам. Конкретнее: даны координаты трех точек на форме, начертить между этими точками три отрезка прямых. Координаты вводить в 6 текстовых полей. Проект должен выглядеть так, как на Рис. 6.10 (пока не обращайте внимания на правый треугольник).

Рис. 6.10
Придумаем имена переменным – координатам точек: x1, y1, x2, y2, x3, y3.
Создадим 6 текстовых полей для ввода координат и к ним 6 меток. Для удобства переименуем все 6 текстовых полей так: txtX1, txtY1, txtX2, txtY2, txtX3, txtY3. Мы не стали их именовать точно такими же именами, как переменные величины, чтобы имена не перепутались. В таких случаях среди программистов принято начинать имя объектов с приставки (префикса). У нас этим префиксом стал txt.
Вот программа:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim x1, y1, x2, y2, x3, y3 As Integer
Dim Граф As Graphics = Me.CreateGraphics
'Присваиваем переменным значения из текстовых полей:
x1 = txtX1.Text : y1 = txtY1.Text
x2 = txtX2.Text : y2 = txtY2.Text
x3 = txtX3.Text : y3 = txtY3.Text
'Рисуем треугольник:
Граф.DrawLine(Pens.Black, x1, y1, x2, y2) 'Отрезок между 1 и 2 точками
Граф.DrawLine(Pens.Black, x2, y2, x3, y3) 'Отрезок между 2 и 3 точками
Граф.DrawLine(Pens.Black, x3, y3, x1, y1) 'Отрезок между 3 и 1 точками
End Sub
Разобрались? А теперь усложним задачу: Нарисовать еще один треугольник, точно такой же, но расположенный на 100 пикселей правее и на 30 выше. Для этого в процедуру достаточно добавить такой фрагмент:
'Рисуем правый треугольник:
Граф.DrawLine(Pens.Black, x1 + 100, y1 - 30, x2 + 100, y2 - 30)
Граф.DrawLine(Pens.Black, x2 + 100, y2 - 30, x3 + 100, y3 - 30)
Граф.DrawLine(Pens.Black, x3 + 100, y3 - 30, x1 + 100, y1 - 30)
Здесь каждая координата x увеличена на 100, а каждая координата y – уменьшена на 30, чем и достигается нужный результат.
Попробуйте придавать некоторым точкам координаты, выходящие за пределы формы, а именно большие или отрицательные, и вы увидите на форме только части треугольников.
Замечание: неплохо бы предусмотреть кнопку для стирания с формы.
Во многих будущих проектах вам придется использовать в графических методах переменные. Не всем это будет легко и понятно. Но стараться надо. Без переменных – никуда. И все же, если у вас не будет получаться – не отчаивайтесь: в 8.5 я объясню переход от чисел к переменным более подробно. Так что можете оставить неполучившиеся графические задания на потом. Но не заглядывайте в 8.5 раньше времени, материал этот пока вам будет непонятен.
Примечание: Старайтесь пока, чтобы во время рисования или перед тем ничто не заслоняло поверхность, на которой производится рисование, а то рисунка может не получиться. Особенно любят мешаться под ногами окна InputBox. Вы их сдвигайте подальше от формы, чтобы InputBox форму не заслонял. Потом мы научимся справляться с такими проблемами.
Задание 3.
Вспомним Задание 12: В самом углу прямоугольного двора стоит прямоугольный дом. Подсчитать площадь дома, свободную площадь двора и длину забора. В углу, где дом, забора, естественно, нет. Размеры дома и двора вводим при помощи InputBox. Все числа целые.
Потребуем теперь начертить двор, дом и забор заданных размеров, а результаты с пояснениями написать на форме. Советы: Двор и дом чертите залитыми прямоугольниками разного цвета, а забор – незалитым. Программирование будет легким, если верхние левые углы двора, дома и забора будут находиться в одной и той же точке, например, x=20, y=80.
Методы, «придирчивые» к типу параметров
Попробуйте в предыдущем примере о треугольнике в одном из методов DrawLine в качестве параметра задать дробное число, скажем так:
Граф.DrawLine(Pens.Black, x1 / 3, y1, x2, y2)
или так:
Граф.DrawLine(Pens.Black, x1 + 0.8, y1, x2, y2)
Немедленно VB подчеркнет оператор и выдаст подсказку об ошибке, в которой вы увидите слова Double, Integer и Single. В чем дело? Давайте разберемся.
Причина в том, что каждый метод четко определяет типы своих параметров и не любит другие типы. Как нам узнать нужные?
Первый способ. Поставьте текстовый курсор на название метода в окне кода (пусть это будет DrawLine) и нажмите клавишу F1. Перед вами возникнет окно помощи, в котором вы чаще всего найдете следующий список (Рис. 6.11).

Рис. 6.11
Заголовок означает «Список вариантов метода». Не обращайте пока внимания на слова Overloads Public Sub в начале каждой из 4 интересующих нас строк. Далее в каждой строке идет слово DrawLine, а за ним в скобках – список параметров метода, причем в списке приведены именно типы параметров. Почему строк 4, а не одна? Потому что существует 4 варианта этого метода. Каждый из них чертит одни и те же линии, а отличаются варианты параметрами. В типах верхних двух вариантов мы пока даже не можем разобраться, а вот нижние два для нас имеют смысл. Тип первого параметра – Pen. Мы этот тип пока не проходили, но согласимся. Он нас пока не интересует. Остальные четыре параметра – это, как мы знаем, координаты точек. Мы видим, что они должны иметь тип или Integer, или Single.
Второй способ. Найдите в Object Browser класс Graphics и выделите. В правой панели вы увидите его свойства и методы. Среди них вы найдете и 4 варианта метода DrawLine с указанием параметров (см. Рис. 6.12).

Рис. 6.12
Есть и другие способы.
Причина ошибки. Типы параметров мы узнали. Почему VB выдает ошибку? Потому что результат деления x1/3 имеет, как мы узнали в 5.4.6, тип Double. И результат сложения x1+0.8 тоже имеет тип Double. А нужен Integer или Single. Вот и весь ответ.
Что делать? Способов несколько.
Граф.DrawLine(Pens.Black, CSng(x1 + 0.8), y1, x2, y2)
Задание 4.
1. Подготовительная задача. Начертить цилиндр радиуса 100 и высотой 200, такой, как на Рис. 6.13. Высота эллипсов, изображающих основания цилиндра, должна быть в два раза меньше их ширины. Переменными можно не пользоваться.
2. Основная задача. Даны радиус и высота цилиндра. Вычислить объем цилиндра и полную площадь его поверхности. Начертить (это нелегко) цилиндр данного радиуса и высоты. Радиус и высоту вводить в два текстовых поля. Объем и площадь выводить на поверхность формы методом DrawString с 3 знаками после запятой и с текстовыми пояснениями. Высота эллипсов, изображающих основания цилиндра, должна быть в два раза меньше их ширины (см. Рис. 6.13).

Рис. 6.13
Ненадолго расстаемся. Того, чему мы научились, нам вполне хватит для рисования в последующих главах любопытных вещей. Кроме методов рисования рассмотренных нами фигур существуют еще методы рисования многоугольников, кривых Безье, сплайнов и некоторых других. Кроме того, перья, кисти и шрифты гораздо более богаты, чем рассмотренные нами. Мы пока только чуть-чуть прикоснулись к графическим возможностям VB. Дальнейшие возможности графики будут изложены в Глава 12. и Глава 17. . Но не советую прямо сейчас туда отправляться. Будет непонятно.
Графические объекты. Рисуем и пишем.
Изображения на форму и элементы управления можно наносить двумя способами. Можно наносить на них фото или другие изображения, взятые из графических файлов, как мы делали в 3.6 и будем еще делать в 12.3. А можно рисовать на них линии и другие фигуры программным способом, как мы будем делать в этой главе.
Что такое выбор (ветвление)
У начинающего программиста интерес должен вызывать такой вопрос: как компьютер думает, как он принимает решения, как он выбирает, какое действие из нескольких возможных нужно выполнить в данный момент? Ведь до сих пор компьютер выполнял все, что ему приказывали, не рассуждая. Попробую ответить.
Возьмем игру в воздушный бой. Предположим, самолет на экране летит на автопилоте. Это значит, он должен самостоятельно поддерживать высоту полета между 3000 м и 3200 м. Для этого ему достаточно периодически определять высоту, и если она меньше 3000, набирать высоту, а если больше 3200 – снижаться. Естественно, этот выбор делает программа для игры в воздушный бой, сам по себе компьютер ничего выбрать не может. В программе заранее пишутся процедуры для набора высоты и для снижения, а выбор между ними делает специальная команда (оператор) выбора, имеющаяся в каждом языке программирования. Вот алгоритм выбора между набором высоты и снижением:
1.Определи высоту.
2.Если высота < 3000, то выполняй процедуру НАБОР ВЫСОТЫ.
3.Если высота > 3200, то выполняй процедуру СНИЖЕНИЕ.
4.Продолжай полет.
Здесь команды 2, 3 – команды выбора. В общем случае команда выбора содержит условие, от которого зависит, будет ли выполняться какая-нибудь команда или группа команд. Это условие может быть самым разным: нажата или нет любая клавиша, нажата или нет конкретная клавиша, был ли щелчок мышью над таким-то объектом, больше ли одно число другого, правда ли, что с клавиатуры введено такое-то слово и т.д. В нашем случае условие – это высота < 3000 или высота > 3200.
Напишем для примера примитивный алгоритм, позволяющий имитировать вежливое общение компьютера с человеком при включении компьютера:
1.Покажи на мониторе текст "Здравствуйте, я – компьютер, а вас как зовут?"
2.Жди ответа с клавиатуры.
3.Если на клавиатуре человек набрал "Петя" или "Вася", то покажи на мониторе текст "Рад встретиться со старым другом!", иначе покажи на мониторе текст "Рад познакомиться!"
4.Покажи на мониторе текст "Чем сегодня будем заниматься – программировать или играть?"
5.Жди ответа с клавиатуры.
6.Если . . . . . . . . . . . . . .
. . . . . . . . . . . . .
Выбор называют ветвлением по аналогии с разветвляющимся деревом (когда мы забираемся на дерево, мы время от времени делаем выбор, по какой из нескольких веток забираться дальше).
Идею разветвления в программе я изложил. Как видите, команды ветвления довольно просты. Как же с помощью таких простых команд запрограммировать сложное поведение компьютера? Ответ вы найдете в материале этой главы. Добавлю только, что вся мыслительная деятельность во всех
программах (в том числе и в той, что выиграла в шахматы у чемпиона мира Каспарова) осуществляется при помощи таких вот простых команд ветвления (выбора). Только их там много.
Разбираем оператор If на примерах
Выучим сначала три английских слова:
| If | читается "иф" | переводится "если" | |||
| Then | читается "зэн" | переводится "то" | |||
| Else | читается "элз" | переводится "иначе" |
Теперь приведем пример записи нового для вас оператора:
If a=28 Then Debug.WriteLine(f) Else k=44
Переводится он так:
ЕСЛИ a=28 ТО пиши f ИНАЧЕ присвой k значение 44
Другими словами, мы предлагаем компьютеру прежде, чем что-то делать, сначала подумать, правда ли, что a=28 , и если правда, то выполнить оператор Debug.WriteLine(f), в противном случае выполнить оператор k=44. Таким образом, мы с вами впервые написали оператор, при выполнении которого компьютер не просто выполняет, что приказано, а сначала думает и делает выбор (пока одного из двух).
Мы видим, что оператор If включает в себя другие операторы, которые выполняются или не выполняются в зависимости от какого-то условия. Тем не менее, вся эта запись считается одним оператором If. Чтобы привыкнуть к оператору If, рассмотрим пару задач.
Задача 1. Компьютер должен перемножить два числа - 167 и 121. Если их произведение превышает 20000, то компьютер должен напечатать текст ПРОИЗВЕДЕНИЕ БОЛЬШОЕ, иначе текст ПРОИЗВЕДЕНИЕ МАЛЕНЬКОЕ. После этого компьютер в любом случае должен напечатать само произведение.
Программа:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim a, b, y As Integer
a = 167
b = 121
y = a * b
If y > 20000 Then Debug.WriteLine("ПРОИЗВЕДЕНИЕ БОЛЬШОЕ") _
Else Debug.WriteLine("ПРОИЗВЕДЕНИЕ МАЛЕНЬКОЕ")
Debug.WriteLine(y)
End Sub
Пояснение: В процедуре 5 операторов, не считая оператора Dim, последний из них – Debug.WriteLine(y). Поскольку все 5 операторов выполняются по порядку, то он выполнится обязательно. Оператор If у нас – однострочный, пусть вас не вводит в заблуждение то, что он занимает физически две строки, ведь в конце первой из них я поставил знак переноса.
Обязательно выполните эту программу в пошаговом режиме. Обратите внимание, что подсветка после If y > 20000 Then перескакивает на Debug.WriteLine ("ПРОИЗВЕДЕНИЕ БОЛЬШОЕ"), а затем на Debug.WriteLine(y).
Теперь замените в программе a = 167 на a = 1 и снова выполните программу в пошаговом режиме. Обратите внимание, что теперь подсветка после If y > 20000 Then перескакивает на Debug.WriteLine("ПРОИЗВЕДЕНИЕ МАЛЕНЬКОЕ"), а затем уже на Debug.WriteLine(y).
Задача 2. В компьютер вводятся два произвольных положительных числа – длины сторон двух кубиков. Компьютер должен подсчитать объем одного из них – того, что больше по размеру.
Обозначим a1 - сторону одного кубика, a2 - сторону другого, bol - сторону большего кубика, V - объем кубика. Приведем три варианта программы:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim a1, a2 As Double
a1 = InputBox("Введите сторону одного кубика")
a2 = InputBox("Введите сторону другого кубика")
If a1 > a2 Then Debug.WriteLine(a1 * a1 * a1) Else Debug.WriteLine(a2 * a2 * a2)
End Sub
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Dim a1, a2, V As Double
a1 = InputBox("Введите сторону одного кубика")
a2 = InputBox("Введите сторону другого кубика")
If a1 > a2 Then V = a1 ^ 3 Else V = a2 ^ 3
Debug.WriteLine(V)
End Sub
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
Dim a1, a2, bol As Double
a1 = InputBox("Введите сторону одного кубика")
a2 = InputBox("Введите сторону другого кубика")
If a1 > a2 Then bol = a1 Else bol = a2
Debug.WriteLine(bol ^ 3)
End Sub
Каждый из вариантов полезен и должен быть вами понят. Вы должны убедиться, что одна и та же задача может решаться разными программами. Если возникают трудности в понимании, то используйте пошаговый режим и следите за значениями переменных. Для каждого варианта пошаговый режим используйте два раза – когда больше первый кубик и когда больше второй кубик.
If без Else. Оператор If можно записывать и без части Else. Например,
If s
If a > 5 Then a = a + 10
Debug.WriteLine(a)
напечатает 16, а фрагмент
a = 2
If a > 5 Then a = a + 10
Debug.WriteLine(a)
напечатает 2.
Еще один пример: Пусть в компьютер вводится слово. Компьютер должен просто распечатать его. Однако, если введенным словом будет «колхозник», то компьютер должен напечатать вместо него слово «фермер».
Вот как будет выглядеть наша программа-«цензор»:
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
Dim Slovo As String
Slovo = InputBox("Введите слово")
If Slovo = "колхозник" Then Slovo = "фермер"
Debug.WriteLine(Slovo)
End Sub
Несколько операторов после Then и Else. До сих пор мы после Then и после Else писали только по одному оператору. А если нужно больше?
Задача: Если a не равно 4, выполнить операторы b=3 и Debug.WriteLine(b), а в противном случае - операторы b=0, a=b+5 и с=0.
Вот оператор, решающий эту задачу:
If a <> 4 Then b = 3 : Button4.Width = 50 Else b = 0 : a = b + 5 : c = 0
Как видите, после Then и Else можно писать по нескольку операторов, разделенных двоеточиями. Однако, чаще для этого применяют многострочный If.
Правила записи однострочного оператора If
Любой оператор VB нужно записывать по определенным грамматическим правилам, в противном случае VB выдает сообщение об ошибке. У каждого человеческого языка есть своя грамматика, включающая в себя правила, по которым должны выстраиваться в цепочку слова и другие элементы языка, чтобы получилось правильное предложение. Совокупность этих правил образует часть грамматики, называемую синтаксисом. В языках программирования тоже есть предложения. Такими предложениями являются операторы. Поэтому у языка программирования тоже есть свой синтаксис, определяющий правила, по которым записываются операторы языка и из операторов составляется программа. После того, как человек запускает программу на выполнение, любая порядочная среда программирования прежде, чем действительно выполнять ее, сначала проверит, нет ли в ней синтаксических ошибок, и если есть, то программу выполнять не будет, а выдаст сообщение, указывающее человеку, в чем ошибка. А VB проверяет программу еще на стадии ввода кода.
У VB есть две формы оператора If: однострочная и многострочная. Пока мы пользовались только однострочным If и поэтому приведем правило записи только для него. Приведем это правило в виде так называемой синтаксической схемы:
If условие Then операторы Else операторы
Как понимать эту схему? Ее следует понимать, как образец, шаблон записи оператора, указывающий порядок, в котором оператор записывается из отдельных слов. Слова, которые в схеме я записал жирными буквами, при вводе оператора вы просто вводите один к одному. Вместо слов, которые в схеме записаны курсивом, нужно при записи оператора подставить то, что они означают. Поясним, что обозначают эти слова.
| операторы | любой оператор VB или группа операторов, разделенных двоеточиями | ||
| условие | пока под условием будем понимать два арифметических или строковых выражения, соединенных знаком сравнения | ||
| знак сравнения | знаков сравнения шесть: > больше >= больше или равно = равно < меньше <= меньше или равно <> не равно |
Пример: If 5*a+4 <= a*b Then Beep Else a=b+5
Здесь
Beep - один оператор,
a=b+5 - другой оператор,
5*a+4 <= a*b - условие,
5*a+4 - одно выражение,
a*b - другое выражение,
<= - знак сравнения.
Вы уже видели, что однострочный оператор If можно записывать в краткой форме. Вот синтаксическая схема для этой формы:
If условие Then операторы
Таким образом, это уже вторая синтаксическая схема, касающаяся одного оператора. Удобно же весь синтаксис оператора иметь перед глазами в одной схеме. Соединим две схемы в одну. Вот эта схема:
Синтаксическая схема однострочного оператора If:
If условие Then операторы [ Else операторы ]
Квадратные скобки означают, что их содержимое можно писать, а можно и не писать в операторе.
Полезное замечание: Вычисляя выражения, стоящие в условии оператора If, VB не записывает их значения в память. Например, после выполнения фрагмента
b=6 : If b+1>0 Then s=20
в ячейке b будет храниться 6, а не 7. То же относится и к выражениям из оператора Debug.WriteLine. Например:
b=6 : Debug.WriteLine ( b+1)
И здесь тоже в ячейке b останется храниться 6, а не 7. И вообще, информация в ячейках памяти не меняется при вычислении выражений без присваивания.
Еще примеры и задания
Разберите еще несколько примеров работы оператора If:
| ФРАГМЕНТ ПРОГРАММЫ | ПЕЧАТЬ | ||
| a=10 If a>2 Then Debug.WriteLine("!!!") Else Debug.WriteLine("!") | !!! | ||
| a=4 If a>5 Then a=a+10 Else a=a-1 Debug.WriteLine(a) | 3 | ||
| s=6 If s-8<>0 Then s=2*s Debug.WriteLine(s) | 12 | ||
| s=6 If s<0 Then s=2*s s=s+1 Debug.WriteLine(s) | 7 |
Задание 5.
Определить без компьютера, какие 3 числа напечатает следующий фрагмент:
k = 20 : k = k + 10 : If k + 10 <> 30 Then k = 8 Else k = k - 1
Debug.WriteLine(k)
k = 20 : k = k + 10 : If k + 10 = 30 Then k = 8 Else k = k - 1
Debug.WriteLine(k)
p = 1 : If p > 0 Then p = p + 5
If p > 6 Then p = p + 1
Debug.WriteLine(p)
Задание 6.
В компьютер вводятся два числа. Если первое больше второго, то напечатать их сумму, иначе – произведение. После этого компьютер должен напечатать текст ЗАДАЧА РЕШЕНА.
Задание 7.
В компьютер вводятся длины трех отрезков. Компьютер должен ответить на вопрос, правда ли, что первый отрезок слишком велик, чтобы образовать с другими двумя отрезками треугольник. Указание: Для этого его длина должна быть больше или равна сумме длин двух других отрезков.
В Задание 31 вам будет предложено определить, возможен ли треугольник из этих отрезков, а затем в Задание 35 – нарисовать треугольник по трем сторонам, если он возможен.
Задание 8.
Дракон каждый год отращивает по три головы, но после того, как ему исполнится 100 лет – только по две. Сколько голов и глаз у дракона, которому N лет?
Анализируем свойства объектов. Если в выражения, входящие в условие оператора If, включить свойства объектов, то вы можете заставить компьютер с ними работать. Например, компьютеру нужно определить, видимо или невидимо в данный момент текстовое поле TextBox1. Делает это такой оператор:
If TextBox1.Visible = True Then MsgBox("Видимо") Else MsgBox("Невидимо")
Таким образом, мы расширили понятие условия в операторе If, включив в него сравнение свойства с его значениями. Вы можете писать, например, так:
If TextBox1.Width < 50 Then …
Не все свойства позволяют делать такие сравнения.
Задание 9.
Если кнопка расположена на форме с вашей точки зрения слишком высоко, пусть при нажатии на нее она опустится на 20 пикселей.
Условный оператор If или как компьютер делает выбор
Теперь посмотрим, как писать разветвляющиеся программы на VB.
Функции Rnd и Randomize
Запустите такую программу:
Dim p As Double
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
p = Rnd()
Debug.WriteLine(p)
End Sub
Вот результат:
0,705547511577606
Это случайное число из диапазона от 0 до 1. Вырабатывает это число функция Rnd.
Щелкнем несколько раз по кнопке. Получим серию случайных чисел:
0,705547511577606
0,533424019813538
0,579518616199493
0,289562463760376
0,301948010921478
Завершим работу программы и снова ее запустим. Пощелкаем по кнопке. Получим ту же серию:
0,705547511577606
0,533424019813538
0,579518616199493
0,289562463760376
0,301948010921478
Выходит, что числа хоть и случайные, но после каждого запуска одинаковые. Не очень-то, получается, случайные. Как сделать их разными от запуска к запуску? Добавим кнопку и к ней процедуру с оператором Randomize:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Randomize()
End Sub
После выполнения оператора Randomize числа будут и случайными и разными от запуска к запуску.
| Как получить случайное число из диапазона от 0 до 20? | Так – p = 20 * Rnd. | ||
| А из диапазона от 6 до 7? | Так – p = 6 + Rnd. | ||
| А из диапазона от 200 до 210? | Так – p = 200 + 10 * Rnd. |
Проверьте.
Как получить случайное целое число из диапазона от 200 до 210? Так –
p = Fix (200 + 11 * Rnd)
Функция Fix «отрезает» дробную часть у числа. Подумайте, почему я написал 11, а не 10. Если не можете додуматься, вычисляйте эту формулу по этапам:
s = 11 * Rnd()
t = 200 + s
p = Fix(t)
Debug.WriteLine(s)
Debug.WriteLine(t)
Debug.WriteLine(p)
Запуская этот фрагмент, наблюдайте за значениями s, t и p. Дождитесь, когда s перевалит за 10. Вы сразу все поймете. Если бы я в формуле вместо 11 написал 10, число p равное 210 никогда бы нам не встретилось.
Проект «Звездное небо».
Создайте новый проект с кнопкой. Покрасьте форму в цвет ночного неба. Пусть по щелчку по кнопке на форме рисуется маленькая белая окружность, такая маленькая, что неотличима от точки:
Граф.DrawEllipse(Pens.White, 100, 100, 3, 3)
Поставим задачу нарисовать такую звездочку в случайном месте формы. Посмотрим в окне свойств, чему равны размеры формы. Пусть они равны 500 по горизонтали и 400 по вертикали. Тогда дело решает следующий оператор:
Граф.DrawEllipse(Pens.White, 500 * Rnd(), 400 * Rnd(), 3, 3)
Пощелкайте по кнопке. С каждым щелчком на небе будет зажигаться новая звездочка (Рис. 7.1).

Рис. 7.1
Задание 10.
Создайте проект «Звездное небо в окне» (см. Рис. 7.2).

Рис. 7.2
Задание 11.
«Ловля кузнечика или измеритель шустрости». Создайте проект с большой формой и одной очень маленькой кнопкой. При нажатии на кнопку она должна прыгать в случайное место формы. Вы щелкнули по кнопке – она прыгнула, вы снова поскорее щелкнули – она снова прыгнула, и так далее. Старайтесь щелкать как можно чаще. Можете засечь, сколько раз вам удалось щелкнуть за 1 минуту. Побеждает тот, у кого за 1 минуту кнопка прыгнет наибольшее число раз. (В дальнейшем вы сможете научить компьютер, чтобы он сам засекал время и сам подсчитывал количество нажатий. Кстати, попробуйте опередить книгу и сами организуйте подсчет. В этом вам поможет оператор вида k=k+1.)
Указание: Чтобы кнопка прыгнула в случайное место формы, вам достаточно задать случайные значения двум свойствам кнопки - Left и Top. При этом вы должны добиться, чтобы кнопка не «упрыгивала» с формы. Подсказка: Чтобы можно было играть на форме любых размеров, вам может понадобиться такая, например, случайная величина – Me.Width * Rnd. Только имейте в виду, что размеры формы больше размеров ее рабочего пространства на размеры заголовка и бордюров. Поэтому указанную формулу надо немного подкорректировать в сторону уменьшения.
Задание 12.
«Угадай число» или «Экстрасенс ли вы». Это ваша первая простейшая игра с компьютером. Компьютер загадывает число – 0 или 1. Ваше дело – отгадать. А дело компьютера – сказать «Угадал» или «Не угадал». Некоторые экстрасенсы утверждают, что благодаря сверхчувственному контакту с компьютером они могут из 100 раз угадать 80.
Программа готова? Настройтесь на сверхчувственный контакт! Пуск!
Указание: Здесь вам нужно получить целое число из диапазона от 0 до 1. Получается оно абсолютно по той же методе, что и целое число из диапазона от 200 до 210.
Случайные величины
Без случайных величин компьютер всегда бы, как робот, на одинаковые действия человека реагировал одинаково. Но тогда невозможны были бы игры.
Разбираем многострочный If на примерах
Вспомним недавнюю задачу: Если a не равно 4, выполнить операторы b=3 и Debug.WriteLine(b), а в противном случае - операторы b=0, a=b+5 и с=0. Вот однострочный оператор If, решающий эту задачу:
If a <> 4 Then b = 3 : Debug.WriteLine(b) Else b = 0 : a = b + 5 :
c = 0
Однако, часто количество операторов после Then и Else бывает гораздо большим, да и сами эти операторы бывают гораздо более сложными и длинными. В этом случае строка становится неудобочитаемой, да и вообще не умещается на ширине экрана. Для таких случаев создан многострочный (или блочный) оператор If. Вот как решается наша задача с его помощью:
If a <> 4 Then
b = 3
Debug.WriteLine(b)
Else
b = 0
a = b + 5
c = 0
End If
Конструкция End If означает просто, что в этом месте оператор If заканчивается.
Часть Else может и отсутствовать. Например,
If a <> 4 Then
b = 3
Debug.WriteLine(b)
End If
Самое замечательное в блочном If то, что здесь можно одно за другим проверять несколько условий. Проиллюстрирую на примерах.
Задача: В компьютер вводится число a. Компьютер должен:
В любом случае после всего этого компьютер должен сказать «До свидания».
Вот программа:
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
a = InputBox("Введите число")
If a < 0 Then
MsgBox("Число отрицательно")
ElseIf a = 0 Then
MsgBox("Вы ввели нуль")
ElseIf a > 100 Then
MsgBox("Число большое")
Else
Debug.WriteLine(a ^ 2)
End If
MsgBox("До свидания!")
End Sub
А вот перевод приведенного оператора If на русский:
ЕСЛИ a < 0 ТО
MsgBox("Число отрицательно")
ИНАЧЕ ЕСЛИ a = 0 ТО
MsgBox("Вы ввели нуль")
ИНАЧЕ ЕСЛИ a > 100 ТО
MsgBox("Число большое")
ИНАЧЕ
Debug.WriteLine(a ^ 2)
КОНЕЦ ОПЕРАТОРА
ElseIf переводят так – «иначе если».
Многострочный If выполняется так: Сначала проверяется первое условие (a < 0). Если оно не выполняется, то VB переходит к проверке второго условия(a = 0), третьего и так далее. Наткнувшись наконец на условие, которое выполняется, VB выполняет операторы, стоящие после его Then и на этом заканчивает работу, даже если ниже есть условия, которые тоже выполняются. Если не выполняется ни одно из условий, VB выполняет операторы, стоящие за Else. Выполнив многострочный If, компьютер переходит к выполнению следующего оператора (у нас это MsgBox("До свидания!")).
Проверьте работу этого оператора в пошаговом режиме 4 раза: для отрицательного, нулевого, положительного и большого положительного значения a. Это нужно для того, чтобы убедиться, что каждая из 4 ветвей оператора работает нормально.
Обратите внимание, что вам нигде не пришлось объяснять компьютеру, что «в остальных случаях» означает на самом деле «между 0 и 100». Все получилось само собой.
Правила записи многострочного If
Вот синтаксис многострочного оператора If:
If условие Then
операторы
операторы
…………….
[ ElseIf условие Then
операторы
операторы
……………. ]
……………………….….
[ Else
операторы
операторы
……………. ]
End If
Ветвей ElseIf может быть сколько угодно или совсем не быть. Ветвь Else может присутствовать, а может отсутствовать. Если она есть, то одна и стоит последней.
Имейте в виду, что у вас нет права и вы не сможете, экономя место по вертикали экрана, объединять строки многострочного оператора If, например, так:
If условие Then операторы ElseIf операторы
и переносить слова Then, ElseIf и другие со своего законного места, например, так:
If
условие
Then
операторы
ElseIf
операторы
Кое в чем вам могут помочь двоеточия.
Задание 13.
Определите без компьютера, что напечатает данная процедура:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim a As Integer = 0
If 3 > 2 Then
a = a + 1
a = a + 2
ElseIf 3 > 2 Then
a = a + 4
End If
a = a + 10
If 3 < 2 Then
a = a + 20
ElseIf 3 > 2 Then
a = a + 40
a = a + 100
Else
a = a + 200
End If
a = a + 500
If 3 < 2 Then
a = a + 1000
ElseIf 3 < 2 Then
a = a + 2000
Else
a = a + 4000
End If
a = a + 8000
Debug.WriteLine(a)
End Sub
Если у вас не сошлось с ответом – это катастрофа, значит вы чего-то не понимаете. В этом случае запустите процедуру в пошаговом режиме, который вам все объяснит.
Задание 14.
Компьютер спрашивает пользователя, как его зовут, а затем приветствует его в соответствии с именем: Колю – «Привет», Васю – «Здорово», Джона – «Hi», а остальных – «Здравствуйте». Для Васи, кроме этого, он красит форму в зеленый цвет.
Задание 15.
Видоизменить диалог с компьютером, начатый в 5.6.2. Пусть компьютер, выяснив в разговоре имя и возраст человека, дальнейшую беседу ведет по двум вариантам. Если возраст больше 17, то компьютер должен задать вопрос: «В каком институте ты учишься?» и получив ответ, глубокомысленно заметить «Хороший институт». Если же возраст меньше или равен 17, то соответственно – «В какой школе ты учишься?» и «Неплохая школа». После этого, каков бы ни был вариант, компьютер должен попрощаться: «До следующей встречи!». Если хотите, можете запрограммировать продолжение разговора.
Ступенчатая запись программы
Возьмем любую программу и посмотрим, как она записана. Конкретнее – обратим внимание на отступы от левого края листа в записи каждой строки. Не будем рассматривать самые верхние и нижнюю строчки в окне кода, смысла которых мы пока не понимаем. Вот пример бессмысленной программы:
Dim a, b, c
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
a = InputBox("Введите число")
If a > 4 Then
b = 3
Debug.WriteLine(b)
Else
b = 0
a = b + 5
c = 0
End If
b = 5
MsgBox("До свидания!")
End Sub
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
a = 0
b = 0
End Sub
Операторы Dim вне процедур, а также строки начала и конца процедур записываются с одинаковым минимальным отступом. Мы видим, что верхняя процедура состоит из четырех операторов: а=, If, b= и MsgBox. Все они выполняются по порядку, один за другим, поэтому каждый из них записан с одинаковым отступом. Если оператор сложный, то есть включает в себя другие операторы (мы знаем пока один такой оператор - If), то составляющие его операторы записываются еще правее. Так, у нас операторы b=0, a=b+5 и с=0 входят в состав оператора If и должны выполняться по порядку один за другим, поэтому их отступ слева одинаков и больше, чем у If.
Сделано все это для удобства чтения программы, для того, чтобы глаз мог сразу же уловить структуру программы, а именно, из каких частей состоит как сама программа, так и каждый из элементов, ее составляющих. Впрочем, вам с первого взгляда может показаться, что такая запись, наоборот, неудобна для чтения. Однако, заметьте, что она принята во всем мире и глаза профессиональных программистов привыкли именно к ней. Настолько привыкли, что программа, записанная без соблюдения ступенчатого стиля, вызывает раздражение. VB не позволяет нарушать ступенчатый стиль, а вот более ранние версии Бейсика позволяли, и по его отсутствию сразу же можно было определить, что программу писал любитель.
Конечно, допустимы и некоторые отклонения от ступенчатого стиля. Например, как я уже говорил, несколько коротких похожих операторов вполне можно записать в одну строку:
a=0 : b=0 : c=0 : f=4
Этим мы экономим дефицитное место по вертикали экрана или листа бумаги.
Вложенные операторы If
Согласно синтаксической схеме оператора If, после Then и Else могут стоять любые операторы VB, а значит и еще один или несколько If.
Решим задачу: В компьютер вводится число (пусть для конкретности это будет дальность какого-нибудь выстрела). Если оно находится в интервале от 28 до 30, то напечатать текст ПОПАЛ, иначе – НЕ ПОПАЛ.
Сначала составим алгоритм: Введи число. Если оно меньше 28, то печатай НЕ ПОПАЛ, в противном случае надо еще подумать. А о чем же думать? А вот о чем: Если число меньше 30, то печатай ПОПАЛ, иначе печатай НЕ ПОПАЛ.
А теперь по составленному алгоритму напишем программу:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim a As Double = InputBox("Введите дальность выстрела")
If a < 28 Then
MsgBox("НЕ ПОПАЛ")
Else
If a < 30 Then MsgBox("ПОПАЛ") Else MsgBox("НЕ ПОПАЛ")
End If
End Sub
Здесь оператор If a < 30 входит в состав оператора If a < 28. Говорят, что он вложен в него. Естественно, вложенный If вполне и сам может быть многострочным и содержать другие If, вложенные в него. И так далее.
Некоторые программы с вложенными If можно достаточно просто переписать без вложенных If, применяя ветви ElseIf. Вот как мы сделаем это с нашей программой:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Dim a As Double = InputBox("Введите дальность выстрела")
If a < 28 Then
MsgBox("НЕ ПОПАЛ")
ElseIf a < 30 Then
MsgBox("ПОПАЛ")
Else
MsgBox("НЕ ПОПАЛ")
End If
End Sub
Логические операции And, Or, Not
Применение большого числа вложенных If создает довольно громоздкую, трудную для понимания программу. Применение вместо них ветвей ElseIf не всегда делает программу понятнее. Поэтому в VB есть возможность записывать программы короче и понятнее, используя так называемые логические операции. Что это такое, разберем на примерах.
Сформулируем последнюю задачу так: Если число больше 28 и одновременно меньше 30, то печатай ПОПАЛ, в противном случае – НЕ ПОПАЛ.
Обратите внимание, насколько эта формулировка короче и понятнее прежнего алгоритма. Упрощение достигнуто благодаря применению союза «и». В языках программирования в роли этого союза выступает логическая операция And (по-русски – логическая операция И). Вот как с ее помощью записывается наша программа:
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
Dim a As Double = InputBox("Введите дальность выстрела")
If a > 28 And a < 30 Then MsgBox("ПОПАЛ") Else MsgBox("НЕ ПОПАЛ")
End Sub
Как видите, намного короче.
Обратите внимание, что условие в операторе If уже не такое простое, как мы описывали раньше, а стало таким:
a > 28 And a < 30
Оно состоит из двух условий, между которыми стоит знак логической операции And .
Знак логической операции And, поставленный между двумя условиями, говорит о том, что должны выполняться сразу оба эти условия.
Разберем еще один пример.
Задача «Разборчивая принцесса». В прихожей у принцессы – длинная очередь женихов. Принцессе нравятся только голубоглазые маленького роста. Устав принимать женихов и отбирать из них подходящих, принцесса вместо себя поставила компьютер, написав для него программу, которая говорит OK тем, у кого цвет глаз голубой и одновременно рост меньше 140. Остальным программа говорит BYE.
Вот эта программа:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim Tsvet As String = InputBox("Каков цвет ваших глаз?")
Dim Rost As Integer = InputBox("Введите ваш рост в сантиметрах")
If Tsvet = "Голубой" And Rost < 140 Then MsgBox("OK ") Else MsgBox("BYE")
End Sub
Оператор If в данном примере можно прочесть так – если цвет глаз голубой И рост меньше 140 сантиметров, то говори OK , иначе говори BYE.
Поэтому наш оператор If ответит BYE и высоким голубоглазым, и высоким неголубоглазым, и маленьким неголубоглазым. И лишь маленьким голубоглазым он ответит OK . В общем, And – строгая операция.
Примеры:
|
ФРАГМЕНТ |
РЕЗУЛЬТАТ |
|
a=8: b=6: If a>b And b>1 Then k=1 Else k=0 |
k=1 |
|
a=8: b=6: If a>b And b>7 Then k=1 Else k=0 |
k=0 |
|
If 8>2 And 3>5 Then k=1 Else k=0 |
k=0 |
|
If c>d And c |
k=0 |
Задача «Неразборчивая принцесса». Неразборчивой принцессе нравятся все маленькие независимо от цвета глаз и все голубоглазые независимо от роста. Программа неразборчивой принцессы будет отличаться от программы разборчивой одним единственным знаком логической операции:
If Tsvet = "Голубой" Or Rost < 140 Then MsgBox("OK ") Else MsgBox("BYE")
Оператор If в данном примере можно прочесть так – если цвет глаз голубой ИЛИ рост меньше 140 сантиметров, то говори OK , иначе говори BYE.
Здесь мы употребили логическую операцию Or (по-русски – логическую операцию ИЛИ).
Поставленный между двумя условиями, знак Or говорит о том, что достаточно, если будет выполняться хотя бы одно из них.
Поэтому теперь оператор If ответит OK и высоким голубоглазым и маленьким голубоглазым и маленьким неголубоглазым. И лишь высоким неголубоглазым он ответит BYE. В общем, Or – добрая операция.
Примеры:
|
ФРАГМЕНТ |
РЕЗУЛЬТАТ |
|
a=8: b=6: If a>b Or b>7 Then k=1 Else k=0 |
k=1 |
|
a=8: b=6: If a7 Then k=1 Else k=0 |
k=0 |
|
If 1>2 Or 5<4 Then k=1 Else k=0 |
k=0 |
Несколько And и Or. Знаками And и Or можно объединять не только два, а сколько угодно условий. Например:
If a>2 Or x=b Or c<>1 Then k=99 Else k=33
Здесь оператор k=99 выполнится, если верно хотя бы одно из трех условий, и лишь когда все три неверны, будет выполняться оператор k=33. Еще один пример:
If a>2 And x=b And c<>1 Then k=99 Else k=33
Здесь наоборот: оператор k=99 выполнится только тогда, когда верны все три условия, а когда хотя бы одно из них неверно, будет выполняться оператор k=33.
Not. Кроме логических операций And и Or применяется еще логическая операция Not (по-русски – НЕ). Запись
If Not a>b Then...
переводится так –
ЕСЛИ НЕПРАВДА, ЧТО a больше b, ТО....
Вот фрагмент:
a=2: b=3: If Not a>b Then k=1 Else k=0
Здесь выполнится оператор k=1, так как неправда, что 2>3.
Знак логической операции Not, поставленный перед условием, говорит о том, что это условие не должно выполняться.
Задание 16.
Усложним Задание 22 из 7.2.3 о треугольнике: В компьютер вводятся длины трех отрезков. Компьютер должен ответить на вопрос, можно или нельзя из этих отрезков образовать треугольник. Указание: Для этого каждый отрезок должен быть меньше суммы длин двух других отрезков. Напишите 3 варианта программы: без использования логических операций, с использованием логических операций Or, с использованием логических операций And.
Задание 17.
Человек вводит с клавиатуры строку, смысл которой – приветствие при встрече. Компьютер тоже должен ответить приветствием. Отвечать нужно в соответствии со следующей таблицей:
|
ПРИВЕТСТВИЕ ЧЕЛОВЕКА |
ОТВЕТ КОМПЬЮТЕРА |
|
Привет |
Привет |
|
Здравствуйте |
Здравствуйте |
|
Добрый день |
Салют |
|
Приветик |
Салют |
|
Салют |
Салют |
|
Здравия желаю |
Вольно |
|
Любое другое приветствие |
Я вас не понимаю |
Используя логические операции, постарайтесь уложиться в один оператор If с 4 ветвями.
Логические выражения
Выражения
a>28
a > 28 And a < 30
Tsvet ="Голубой"
Tsvet ="Голубой" Or Rost<140
a>b-2 Or 5*x+1=Sqrt(b) Or c<>10-y
имеют ту общую черту, что про каждое из них можно сказать, верно оно или нет в каждый момент времени. Такие выражения называются логическими выражениями. Если логическое выражение верно, то говорят, что оно имеет значение True (Истина). Если логическое выражение неверно, то говорят, что оно имеет значение False (Ложь). Любое логическое выражение может стоять в качестве условия в операторе If.
Логические выражения могут быть и более сложными, чем приведенные – содержать одновременно операции And, Or, Not. Например, такое выражение:
a>2 And Not b>3 Or s>8
Чтобы его понять, нужно знать порядок логических действий. В арифметике сначала выполняется умножение, потом сложение. В логических выражениях сначала выполняется Not, затем And, затем Or. Для облегчения понимания не возбраняется расставлять скобки:
(a>2 And (Not b>3)) Or s>8
Это выражение равносильно предыдущему. По-русски его смысл можно выразить так: оно верно тогда, когда или s больше 8 или одновременно a больше 2 и b не больше 3.
Скобки можно расставлять и чтобы изменить порядок действий:
a>2 And Not (b>3 Or s>8)
По-русски смысл этого выражения можно выразить так: оно верно тогда, когда a больше 2 и одновременно неправда, что или b больше 3 или s больше 8.
Логический тип данных Boolean
До этого момента мы с вами были знакомы с двумя видами переменных и выражений: арифметическими (их значение – число) и строковыми (их значение – текстовая строка).
Теперь мы с вами познакомились с логическими выражениями. Существуют и широко применяются в программировании также и логические переменные. Это переменные, которые, как и логические выражения, могут принимать только одно из двух значений: True или False.
Раз есть переменная, должен быть и тип. Объявляются логические переменные так:
Dim a, b As Boolean
Тип данных Boolean (булевский тип) назван так по имени прадедушки логической алгебры, которого звали Буль.
Теперь вы можете писать так:
a = True
или
If b Then …
Зачем это нужно и какая выгода от таких «скучных» переменных, выяснится позже, когда вы будете программировать реальные проекты, например – «Будильник-секундомер» (13.5).
Задание 18.
«Замысловатая принцесса». Определите без компьютера, кто нравится принцессе, по фрагменту из ее программы:
If Tsvet = "Черный" And (Rost < 180 Or Rost > 184) Then MsgBox("OK ") Else MsgBox("BYE")
Задание 19.
Усложним нашу задачу про ПОПАЛ – НЕ ПОПАЛ: Целей для нашего выстрела две. Одна находится в диапазоне дальности 28-30, другая – в диапазоне 65-70. Человек вводит в компьютер число – дальность выстрела. Если снаряд попал в цель, то есть число находится в интервале от 28 до 30 или от 65 до 70, то нужно выдать сообщение ПОПАЛ. Если снаряд упал на расстоянии ближе 10 к любой из целей, то нужно выдать сообщение БЛИЗКО. Кроме этого предусмотрите варианты ПЕРЕЛЕТ, НЕДОЛЕТ, МЕЖДУ ЦЕЛЯМИ и НЕ БЕЙ ПО СВОИМ. Нарисуйте схему выстрела (Рис. 7.3). Горизонтальная линия – земля. Два синих прямоугольника в заданном месте и заданных размеров – это цели. Черный квадратик слева – пушка. Вся картина должна появляться на форме до ввода дальности выстрела, после ввода должен появляться красный. кружочек – место попадания снаряда. Перед рисованием вам придется выбрать масштаб: скажем, 1 к 10. Это значит, что, например, первая цель будет прямоугольником, занимающим пространство на расстоянии от 280 до 300 пикселей от пушки.

Рис. 7.3
Задание 20.
Это задание трудное и необязательное, и поэтому очень полезное. Оно является добавлением к Задание 31 о треугольнике и предназначено для тех, кто чувствует себя уверенно в программировании и геометрии:
В компьютер вводятся длины трех отрезков. Если из этих отрезков можно построить треугольник, постройте его. Или ответьте, что нельзя.
Подсказка: Придется находить координаты вершин треугольника, а для этого надо, возможно, искать высоту треугольника при помощи формулы Герона или же теоремы Пифагора с решением уравнений. Кроме этого рекомендую занести в три дополнительные отдельные переменные размеры самого длинного отрезка и двух других отрезков.
Вложенные операторы If. Логические операции и выражения
Для решения самых сложных логических задач компьютеру вполне достаточно тех операторов If, которые мы разобрали, при условии, что мы будем вкладывать операторы If один в другой.. А применение так называемых логических операций кроме того и значительно упрощает программу.
Оператор варианта Select Case
Суть оператора. У авторов языков программирования есть похвальное стремление сделать язык попроще, попонятнее. Они с ужасом взирают на многочисленные «иначе если» и логические операции и стараются, где можно, от них избавиться. Возьмем, например, такую задачу: Компьютер спрашивает школьника, какую он получил отметку по физике, и реагирует на нее подходящим текстом. Вот программа без нововведений, использующая If:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim Otmetka As Integer = InputBox("Какую отметку ты получил по физике?")
If Otmetka = 1 Or Otmetka = 2 Then
MsgBox("Кошмар!")
ElseIf Otmetka = 3 Then
MsgBox("Неважно")
ElseIf Otmetka = 4 Then
MsgBox("Неплохо")
ElseIf Otmetka = 5 Then
MsgBox("Молодец!")
Else
MsgBox("Таких отметок не бывает")
End If
End Sub
Здесь может вызвать раздражение слишком часто встречающееся имя Otmetka, а также то, что и в такой простой задаче не обошлось без логических операций. Хорошо бы программу можно было писать попроще, например, так (по-русски):
Выбери вариант отметки
Вариант 1, 2
говори "Кошмар!"
Вариант 3
говори "Неважно"
Вариант 4
говори "Неплохо"
Вариант 5
говори "Молодец!"
Вариант остальное
говори "Таких отметок не бывает"
Конец выбора
И такой оператор варианта был придуман и назван Select Case, что и означает в переводе ВЫБЕРИ ВАРИАНТ. Теперь я просто-напросто переписываю русский вариант программы по-английски:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Dim Otmetka As Integer = InputBox("Какую отметку ты получил по физике?")
Select Case Otmetka
Case 1, 2
MsgBox("Кошмар!")
Case 3
MsgBox("Неважно")
Case 4
MsgBox("Неплохо")
Case 5
MsgBox("Молодец!")
Case Else
MsgBox("Таких отметок не бывает")
End Select
End Sub
Логика работы Select Case абсолютно такая же, как и у многострочного If. В процессе исполнения оператора компьютер сравнивает значение переменной Otmetka по очереди со всеми значениями, перечисленными в вариантах, сверху вниз. Наткнувшись на совпадающее значение, он выполняет операторы, стоящие в этом варианте. На этом исполнение оператора Select Case завершается. Если же совпадающего значения так и не нашлось, то выполняются операторы, стоящие в варианте Case Else (в нашей программе он полезен на тот случай, если ученик – угрюмый мечтатель и вводит число 6).
Возможности оператора. Оператор Select Case предоставляет более широкие возможности, чем рассмотренные в только что приведенном примере. Проиллюстрируем их:
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
Dim a, k As Double
a = 3
Select Case a * a + 1
Case 8, 4 * a - 3, 26
k = 0
Debug.WriteLine(k)
Debug.WriteLine(a)
Case 7, 9 To 12
k = 1
Debug.WriteLine(k)
Case Is < 0, 2, 4, 2.57 To 1.8 + a, 44, 68, Is > 100 + a
k = 3
End Select
End Sub
Эта программа напечатает 1. Здесь мы видим несколько новых для нас элементов:
Недостаток оператора. Чем платим за удобство Select Case по сравнению с If? Что может If такого, чего не может Select Case? Самое главное – условия в If могут быть совершенно произвольны, а в Select Case мы привязаны к a*a+1.
Синтаксис оператора Select Case:
Select Case проверяемое выражение
[Case значение, значение……
[операторы
операторы
……………...]]
[Case значение, значение……
[операторы
операторы
……………...]]
…………………….……………….
[Case Else
[операторы
операторы
……………...]]
End Select
Здесь значение – это:
- выражение
- выражение To выражение
- Is знак сравнения выражение
Выражения могут быть не только числовые, но и строковые. Пример:
Private Sub Button4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button4.Click
Dim a As String = "Дом"
Select Case a + a
Case "Домик"
Debug.WriteLine(44)
Case "ДомДом"
Debug.WriteLine(99)
End Select
End Sub
Здесь будет напечатано 99.
Задание 21.
Ученик вводит с клавиатуры букву русского алфавита. Компьютер должен сказать, какой звук обозначает это буква – гласный, согласный звонкий, согласный глухой или какой-нибудь другой (можно и НЕ ЗНАЮ). Подсказка: Буква – это строка из одного символа.
Задание 22.
Необязательное. Если у вас есть микрофон и вы умеете записывать свой голос в файл, то попробуйте усовершенствовать изученные нами диалоги с компьютером. Пусть компьютер подает вам реплики голосом. Для этого вам заранее придется записать на диск все реплики компьютера в виде звуковых файлов и при помощи операторов Select Case выбирать между ними в зависимости от реплик человека с клавиатуры.
Последний маленький шажок – сделать так, чтобы компьютер правильно реагировал на ваши реплики, подаваемые не с клавиатуры, а с микрофона. Но наука пока не в силах сделать этот маленький шажок.
Проверка ввода чисел в текстовое поле
Давайте запустим наш калькулятор в том виде, который он получил в 5.4.8:
Dim Чис1 As Double 'Переменная, содержащая число из текстового поля Число1
Dim Чис2 As Double 'Переменная, содержащая число из текстового поля Число2
Dim Рез As Double 'Переменная-результат, предназначенный для текстового поля Результат
Private Sub Кл_сложения_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles Кл_сложения.Click
Чис1 = Число1.Text 'Значения исходных данных переходят из текстовых полей в переменные
Чис2 = Число2.Text
Рез = Чис1 + Чис2 'Обработка переменных для получения результата
Результат.Text = Рез 'Значение результата переходит из переменной в текстовое поле
End Sub
Попробуем ввести в верхнее текстовое поле вместо числа какую-нибудь ерунду, например, Куку. Щелкнем по кнопке сложения. VB выдает сообщение об ошибке из-за несовпадения типов числовой переменной Чис1 и вводимой текстовой информации. Все верно.
Теперь подумаем – хорошо ли это, что VB реагирует на ваши неправильные действия собственными сообщениями об ошибке? Для вас, которые уже что-то умеют в программировании и делают калькулятор исключительно для собственной пользы и удовольствия, это может быть и хорошо. Ну а представьте, что вы решили похвастаться своим калькулятором перед подружкой, которая знакома с программированием только понаслышке, и она, работая с калькулятором, допустила такую же ошибку – ввела буквы вместо цифр! Что она подумает, увидев неожиданно возникшее окно с непонятными английскими словами? Вы думаете, она поймет, что это сообщение об ошибке? Скорее всего она подумает, что это сломался компьютер. И на какие кнопки, скажите на милость, ей теперь нажимать? "Ну и гадость твой калькулятор!" – скажет она и будет права.
Та же проблема стоит и перед всеми профессиональными программистами, которые создают свои продукты для пользователей, не обязанных ничего знать о программировании. Программист обязан сделать так, чтобы на все неправильные действия пользователя реагировал не VB своими непонятными сообщениями, а сама программа, причем понятно, по-русски, и тут же давала возможность исправиться.
Для этого в VB должны быть средства. И они там есть. Что нам нужно сейчас? Нам нужно средство, которое определяло бы, число ли записано в текстовом поле или же все, что угодно, но только не число. Этим занимается функция IsNumeric. Ее аргумент может иметь любой тип, в том числе и строковый. Функция анализирует строку и если видит, что она состоит из цифр, знака минус и запятой (точки) таким образом, что получается правильное число (и больше ничего в строке нет), то функция получает значение True, что означает "Истина", в противном случае - False, что означает "Ложь". Проверим:
Оператор Debug.WriteLine( IsNumeric("КУ-КУ")) печатает False.
Оператор Debug.WriteLine( IsNumeric("-67,3")) печатает True.
Раз так, то функцию IsNumeric можно включать в логические выражения и использовать в качестве условия оператора If. Например,
If IsNumeric("-67,3") Then MsgBox ("Это число") Else MsgBox ("Это не число")
что означает: "Если -67,3 – число, то …" Получается, что совсем не обязательно писать
If IsNumeric("-67,3") = True Then ….
Алгоритм работы калькулятора с проверкой звучит примерно так: Если в текстовом поле Число1 введено число и в текстовом поле Число2 введено число, то делай все, что положено, иначе выдавай сообщение «Вводите только числа». Процедура сложения с учетом этого алгоритма будет выглядеть так:
Private Sub Кл_сложения_Click(ByVal sender As Object, ByVal e As EventArgs) Handles Кл_сложения.Click
If IsNumeric(Число1.Text) And IsNumeric(Число2.Text) Then
Чис1 = Число1.Text
Чис2 = Число2.Text
Результат.Text = Чис1 + Чис2
Else
MsgBox("Вводите только числа")
End If
End Sub
Здесь я немного сэкономил на переменной Рез. Поскольку мы ее никак не обрабатываем, я обошелся без нее. Процедуры для остальных арифметических действий пишутся аналогично.
Кстати, функция IsNumeric – это метод. А где его хозяин?
Запрет деления на ноль
Вторая ошибка, на которую реагирует VB, это деление на ноль. От этой реакции мы избавимся, если запретим компьютеру делить на ноль, записав вместо оператора
Результат.Text = Чис1 / Чис2
такой:
If Чис2 <> 0 Then Результат.Text = Чис1 / Чис2 Else MsgBox ("На ноль делить нельзя")
Вот как будет выглядеть теперь процедура деления:
Private Sub Кл_деления_Click(ByVal sender As Object, ByVal e As EventArgs) Handles Кл_деления.Click
If IsNumeric(Число1.Text) And IsNumeric(Число2.Text) Then
Чис1 = Число1.Text
Чис2 = Число2.Text
If Чис2 <> 0 Then Результат.Text = Чис1 / Чис2 Else MsgBox("На ноль делить нельзя")
Else
MsgBox("Вводите только числа")
End If
End Sub
Как видите, здесь в состав многострочного If входит однострочный.
Ставим пароль на калькулятор
Ваш калькулятор стал достаточно надежен и удобен. Теперь его не стыдно показать друзьям. Ну а защищаться от врагов будем паролем.
Наша задача – сделать так, чтобы при попытке запустить калькулятор на экране появлялось приглашение ввести пароль, известный только вам. При попытке ввода неправильного пароля, программа должна заканчивать свою работу.
Поскольку приглашение на ввод пароля должно появляться раньше появления калькулятора на экране, то программируем его в процедуре Form1_Load. Предварительно выдумаем сам пароль, например, «калям».
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim Parol As String 'Переменная-пароль
Parol = InputBox("Введите пароль")
If Parol <> "калям" Then MsgBox("Пароль неверный!") : End
End Sub
Новый для вас оператор End делает всего одну вещь – вызывает завершение программы.
Запустите проект и проверьте, как он работает.
То же самое можно было бы запрограммировать короче и без использования переменной:
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
If InputBox("Введите пароль") <> "калям" Then MsgBox("Пароль неверный!") : End
End Sub
Вообще, переменные нужны, если необходима неоднократная обработка какой-то информации: сложение чего-то, затем сравнение этого чего-то с чем-то другим и т.д. В нашем же случае пароль нужен всего один раз, так что можно обойтись и без переменной.
Вы скажете: Кто угодно перед запуском моей программы посмотрит в ее текст и сразу же увидит пароль. Совершенно верно. Существует много способов сделать пароль в тексте программы неудобочитаемым (например, соединив его из нескольких переменных, каждая из которых – коротенькая строчка), но самый лучший способ – не показывайте посторонним текст программы, а запускайте исполняемый файл (он находится в папке BIN вашего проекта).
Усовершенствуем пароль. Вы можете использовать пароль и более тонко. Например, пусть враг запустил ваш калькулятор. Он запустился, враг ликует. Но недолго, потому что видит, что кнопки сложения и вычитания неработоспособны. Для этого в процедуре Form1_Load предусмотрите их деактивацию при помощи установки в False их свойства Enabled. Для ввода пароля вы в этом случае используете не InputBox, а дополнительное текстовое поле (TextBox1) и кнопку (Button1) с текстом ОК. Введя пароль, вы щелкаете по кнопке ОК и кнопки сложения и вычитания становятся работоспособными.
Сделаем также так, чтобы в случае ввода пароля в текстовое поле там для секретности появлялись не буквы пароля, а звездочки (как это обычно принято) или любой другой символ. В этом случае никто из-за вашей спины не сможет пароль подсмотреть. Для этого достаточно в режиме проектирования или в коде до ввода пароля установить свойство текстового поля PasswordChar, выбрав в качестве его значения любой символ (в том числе и звездочку):
Textbox1.PasswordChar = "Ж"
Вот нужные две процедуры:
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
TextBox1.PasswordChar = "*"
Кл_сложения.Enabled = False
Кл_вычитания.Enabled = False
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
If TextBox1.Text = "калям" Then
Кл_сложения.Enabled = True
Кл_вычитания.Enabled = True
Else
MsgBox("Пароль неверный!")
End If
End Sub
Улучшаем калькулятор
Наш калькулятор пока не предохранен от попыток выполнения арифметических действий над текстом вместо чисел и от деления на ноль. Предохраним его от этого, а также научимся ставить пароль на проект.
Функция MsgBox
Простое окно MsgBox. В 3.7 я вкратце описал, как пользоваться окном MsgBox. Здесь я более подробно разберу его действие. Обеспечивает его появление на экране функция MsgBox. Если мы выполним оператор
MsgBox("Привет!")
то увидим на экране такое окно (Рис. 7.4).

Рис. 7.4
Обратите внимание, что кнопка ОК выделена (активна). Это значит, что вместо того, чтобы щелкать по ней мышью, можно просто на клавиатуре нажать клавишу Enter – результат будет тот же.
Точно такое же окно мы увидим, если выполним оператор
y = MsgBox("Привет!")
Поскольку MsgBox, как и всякая функция, имеет значение, оно и будет присвоено переменной y. Что это за значение и зачем оно нужно, мы чуть ниже выясним.
Более сложное окно MsgBox. Рассмотрим следующее окно MsgBox (Рис. 7.5).

Рис. 7.5
Оно отличается от предыдущего количеством кнопок, картинкой и текстом в заголовке. Также активна у нас кнопка No. Задается окно такой функцией MsgBox:
MsgBox ("Хотите знать, сколько будет дважды два?", MsgBoxStyle.YesNoCancel Or _
MsgBoxStyle.Information Or MsgBoxStyle.DefaultButton2, "Ответьте на вопрос")
У функции – 3 параметра, разделенные запятыми:
Первый параметр – текст сообщения «Хотите знать, сколько будет дважды два?».
Третий параметр – текст заголовка «Ответьте на вопрос». Если этот параметр вами не указан, то в заголовок выносится название вашего проекта.
Второй параметр у нас такой:
MsgBoxStyle.YesNoCancel Or MsgBoxStyle.Information Or MsgBoxStyle.DefaultButton2
Он определяет количество и названия кнопок (у нас – MsgBoxStyle.YesNoCancel), внешний вид и поведение окна (у нас – MsgBoxStyle.Information), а также то, какая кнопка из трех будет активной (у нас – MsgBoxStyle.DefaultButton2).
Вам не нужно запоминать их названий. VB сам предложит их вам на выбор во всплывающем списке, как только вы при вводе этой строки в окно кода введете запятую после первого параметра.
Составные части второго параметра отделяются друг от друга знаком логической функции Or. Не буду пояснять, почему. Составных частей может быть одна, две и больше. Они являются значениями перечисления MsgBoxStyle. Смысл трех из них ясен из таблицы:
|
Кнопки |
|
|
OKOnly |
Кнопка OK |
|
OKCancel |
Кнопки OK, Cancel (отменить) |
|
YesNo |
Кнопки Yes, No |
|
YesNoCancel |
Кнопки Yes, No, Cancel |
|
RetryCancel |
Кнопки Retry (попробовать еще раз), Cancel |
|
AbortRetryIgnore |
Кнопки Abort (отменить), Retry, Ignore (игнорировать) |
|
Картинки |
|
|
Critical |
Х – крест |
|
Question |
? – вопрос |
|
Exclamation |
! – восклицание |
|
Information |
i – информация |
|
Какая кнопка активна |
|
|
DefaultButton1 |
1-я |
|
DefaultButton2 |
2-я |
|
DefaultButton3 |
3-я |
Есть еще несколько любопытных составных частей, на которых я не останавливаюсь.
Если второй и третий параметры функции не указаны, окно имеет такой вид, как на Рис. 7.4.
Нажимаем на разные кнопки. Мы можем, если хотим, задавать действия, которые должны выполниться при нажатии той или иной кнопки в окне MsgBox. Для этого существует перечисление MsgBoxResult. Его значения повторяют названия кнопок в окне: OK, Yes, No, Cancel, Abort, Retry, Ignore. При нажатии на кнопку функция MsgBox принимает соответствующее значение, а значит, мы можем его анализировать оператором If.
Вот пример обработки нажатия на кнопки для нашего окна:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim y = MsgBox("Хотите знать, сколько будет дважды два?", _
MsgBoxStyle.YesNoCancel Or MsgBoxStyle.Information Or MsgBoxStyle.DefaultButton2, _
"Ответьте на вопрос")
If y = MsgBoxResult.Yes Then
Debug.WriteLine("Четыре")
ElseIf y = MsgBoxResult.No Then
Debug.WriteLine("Я вижу, Вы не хотите этого знать!")
ElseIf y = MsgBoxResult.Cancel Then
Debug.WriteLine("Вы, к сожалению, отказались отвечать, нажав кнопку Cancel или крестик")
End If
End Sub
Имейте в виду, что нажатие на крестик в окне MsgBox вызывает ту же реакцию, что и нажатие кнопки Cancel.
Названия кнопок сами по себе не играют никакой роли, их действие, как вы только что убедились, полностью определяется кодом, который вы напишете в процедуре. Однако в среде пользователей и программистов уже утвердились некоторые привычки, которые вам небесполезно знать:
OK означает просто принять сообщение к сведению, Cancel
– отменить намечавшееся действие, Abort – прекратить неудавшуюся попытку (например, когда ваша процедура занимается распечаткой документа на принтере и приходит сообщение «Принтер не готов»), Retry – повторить неудавшуюся попытку, Ignore – проигнорировать предупреждение и продолжать, как ни в чем не бывало, Yes-No – ответить да или нет на вопрос, содержащийся в сообщении, и предпринять соответствующие действия.
Цикл с GoTo. Метки
Посмотрим, как осуществить цикл в VB. Предположим, мы хотим, чтобы компьютер бесконечно повторял выполнение следующего фрагмента:
Debug.Write("Это ")
Debug.Write("тело ")
Debug.WriteLine("цикла")
в результате чего в окне Output мы бы увидели:
Это тело цикла
Это тело цикла
Это тело цикла
Это тело цикла
. . . . . . . .
Если бы операторы VB можно было писать по-русски, то для достижения нашей цели было бы естественно воспользоваться такой конструкцией:
метка m1: Debug.Write("Это ")
Debug.Write("тело ")
Debug.WriteLine("цикла")
иди к оператору, помеченному меткой m1
Здесь мы видим новый для нас «оператор» ИДИ, который выполняется после оператора Debug.WriteLine("цикла") и единственная работа которого заключается в том, чтобы заставить компьютер перескочить к выполнению оператора Debug.Write( "Это "), помеченного меткой m1.
А вот как этот фрагмент выглядит реально на VB (не запускайте его пока):
m1: Debug.Write("Это ")
Debug.Write("тело ")
Debug.WriteLine("цикла")
GoTo m1
Здесь GoTo m1 – оператор безусловного перехода, переводится «иди к», m1: – метка. Метка - это произвольное имя или произвольное не слишком большое целое положительное число. Метка перед оператором должна заканчиваться двоеточием.
Оператор GoTo можно писать в любых местах процедуры и метку можно ставить перед любым оператором процедуры, заставляя компьютер таким образом перескакивать внутри процедуры откуда угодно куда угодно. Правда, в сложных процедурах и внутри сложных операторов эта свобода перескакивания существенно ограничивается, так что я не советую вам врываться снаружи внутрь вложенных операторов, а вот изнутри наружу – пожалуйста. Между процедурами скакать нельзя.
А теперь запустите эту программу, но только в пошаговом режиме. Посмотрите, как заполняется окно Output. Чтобы оно заполнялось быстрее, нажмите клавишу F11 и не отпускайте.
Группа операторов, выполняющихся многократно, называется телом цикла. У нас это все 4 оператора.
Теперь запустите эту программу обычным
Теперь запустите эту программу обычным образом (не в пошаговом режиме). Через некоторое время перед вами должен встать жизненно важный вопрос – когда же она остановится? А никогда! Вы видите, что окно Output лихорадочно заполняется текстом, а белка в колесе и не думает уставать.
Вы обнаружите, что кнопки и другие элементы управления на форме не отзываются на нажатие мыши, и вообще, до программы «не докричишься». Любопытно, что так «глохнет» любая нормальная программа в процессе выполнения операторов кода. И все ваши прежние программы тоже так «глохли». Но вы этого не замечали по той простой причине, что весь код их процедур выполнялся очень быстро. Не успели вы нажать на кнопку, а все уже выполнилось и снова программа реагирует на ваши действия.
В нормальной программе, написанной без ошибок, исключены ситуации, когда код выполняется бесконечно или на протяжении слишком долгого времени. Если же вы допустили ошибку и в программе выполняется бесконечный цикл, то возникает как раз такая ситуация. Вы вечно будете смотреть на экран, по которому бесконечно бегут непонятные числа или слова или рисуются бесконечные вереницы графических фигур, а возможно и ничего не происходит – все зависит от характера программы. Говорят, что программа зациклилась.
Для прерывания работы программы, в том числе и зациклившейся, вы нажимаете кнопку Stop. Можно щелкнуть и по крестику на форме, но в этом случае VB подвергнет вас небольшому допросу.
Примеры
Все нижеприведенные примеры прогоните в обычном и в пошаговом режиме.
Пример 1. Определите без компьютера, что напечатает фрагмент:
Dim a = 100
GoTo 8
a = a + 40
Dim k = 7
Debug.Write(a)
8: a = a + 2
k = k + 10
Debug.Write(a)
Этот фрагмент напечатает 102. Операторы выполняются в такой последовательности:
Dim a = 100
GoTo 8
a = a + 2
k = k + 10
Debug.Write(a)
А операторы
a = a + 40
Dim k = 7
Debug.Write(a)
выполнены не будут вообще, несмотря на то, что написаны. Цикла здесь нет.
Пример 2. Печатать числа 0, 1, 2, 3 . . . и так без конца.
Вот программа:
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
Dim n As Long = 0
m1: Debug.Write(n & " ")
n = n + 1
GoTo m1
End Sub
Здесь в оператор Debug.Write(n & " ") вставлена печать пробела, чтобы как-то отделить друг от друга числа в одной строке.
Обратите внимание, что когда компьютер, скажем, в 24-й раз выполняет тело цикла, значение n как раз равно 24. Значит, зная в любой момент значение n, мы можем сказать, в какой раз выполняется тело цикла (еще говорят: какая итерация цикла выполняется). Переменная, обладающая таким свойством, называется счетчиком циклов.
Пример 3. Выводить числа 0, 1, 2, 3 . . . , но не на печать, а в элемент управления Label (что весьма приятно наблюдать).
Вот программа:
Private Sub Button4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button4.Click
Dim n As Long = 0
m1: Label1.Text = n
Label1.Refresh()
n = n + 1
GoTo m1
End Sub
Здесь новостью для нас является оператор Label1.Refresh(), который можно перевести так – «перерисовать, обновить, освежить Label1». Без него вы не увидели бы в Label1 никаких новых чисел, информация в ней просто не обновлялась бы. Метод Refresh подходит в некоторых случаях и к другим элементам управления.
Пример 4. Печатать числа 200, 205, 210, 215 . . . и так без конца.
Вот программа:
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
Dim n As Long = 200
m1: Debug.Write(n & " ")
n = n + 5
GoTo m1
End Sub
Задание 23.
Определить без компьютера, что будет печатать программа:
Private Sub Button4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button4.Click
Dim n As Integer = 10
Dim k As Integer = 0
Debug.WriteLine("Считаем зайцев")
met5: Debug.Write(n)
n = n + k
GoTo m1
n = n + 1
m1: Debug.WriteLine(" зайцев")
k = k + 1
GoTo met5
Debug.Write("Посчитали зайцев")
End Sub
Если не можете определить – посмотрите в пошаговом режиме.
Движение объектов по экрану
Вам уже приходилось заставлять кнопки прыгать по экрану. Попробуем добиться плавного движения объекта. Создайте проект с большой формой и добавьте в него маленький элемент управления PictureBox. Поместите его в левой части формы. Придайте ему картинку (свойство Image). Лучше всего, пока вы еще не умеете работать с изображениями, в качестве картинки взять один из файлов значков (иконок), находящихся по адресу
Program Files\Microsoft Visual Studio .NET\Common7\Graphics\icons
Напишем программу, которая двигала бы картинку плавно направо:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
m: PictureBox1.Left += 2 ’Значение PictureBox1.Left увеличивается на 2 пикселя
GoTo m
End Sub
Если движение получилось слишком медленным, то прибавьте шаг – напишите 5 вместо 2. Если слишком быстрым, то уменьшите – 1.
Чтобы сделать движение еще более медленным, вам придется сделать шаг меньшим 1. Но в данной программе это не сработает, так как значения свойства Left имеют тип Integer и поэтому все равно будут округляться или до 1, или до 0. Придется использовать переменную величину дробного типа:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Dim x As Double
x = PictureBox1.Left 'Компьютер узнает, откуда начинать движение
m: PictureBox1.Left = x 'Изображение встает на место, указанное гориз. координатой
x += 0.0001 'Компьютер увеличивает в уме горизонтальную координату
GoTo m
End Sub
Не удивляйтесь, что вам не пришлось в цикле рисовать и стирать объект. Когда дело касается элемента управления, заботы о перерисовке берет на себя VB. А вот когда вы сами будете рисовать различные фигуры на форме и попытаетесь их двигать, тогда вам придется их и рисовать и стирать.
Задание 24.
Напишите 4 процедуры, в которых заставьте картинку или кнопку двигаться налево, вниз, вверх, наискосок.
Что дальше? Пока мы никак не можем влиять на полученное движение. Только можем останавливать программу. Как с помощью мыши или клавиатуры влиять на движение во время движения? Как хотя бы запрограммировать остановку в нужном месте? Об этом позже.
Оператор перехода GoTo. Цикл. Метки
Оператор GoTo является одним из самых низкоуровневых средств современных языков программирования и на практике им почти не пользуются. Но сам компьютер «думает» именно на низком уровне, и поэтому настоящий программист должен быть с GoTo «на ты».
Выход из цикла с помощью If
Бесконечный цикл. Начнем с привычного. Задача: При помощи цикла печатать такие текст и числа:
Начало счета 3 5 7 9 11 13 15 . . .
Для краткости я во многих программах не буду показывать строки объявлений, заголовков и завершения процедур, а также строку, импортирующую класс Debug. Так что останется одна «суть». Не обращайте также пока внимания на непонятные слова (Do…Loop) в заголовках таблиц, эти слова понадобятся чуть позже. Вот вариант программы. Я назвал его нулевым:
| 0 ВАРИАНТ (Do …. Loop) | |
| Write("Начало счета ") f = 3 m: Write(f & " ") f = f + 2 GoTo m |
Выход из цикла. Теперь вопрос: как нам запрограммировать завершение работы цикла, чтобы он не выполнялся бесконечно? Для этого нужно применить оператор GoTo внутри оператора If.
Задача: При помощи цикла напечатать такие текст и числа:
Начало счета 3 5 7 9 11 13 15 . . . . 329 331 333 Конец счета
Для удобства отладки изменим условие задачи – напечатать:
Начало счета 3 5 7 9 Конец счета
Вы уже достаточно опытны в программировании, чтобы догадаться, что программа для измененного условия будет копией программы для исходного, за исключением одного числа.
Вот 4 варианта программы. Первый – самый простой, а остальные нам понадобятся в дальнейшем. Все 4 варианта делают одно и то же, они очень похожи, но чем-то и отличаются. Вот в этом отличии вам и надо разобраться, иначе не будет понятен дальнейший материал.
Создайте проект с 4 кнопками и выполните в пошаговом режиме все 4 варианта:
| 1 ВАРИАНТ (Do …. Loop While) | 2 ВАРИАНТ (Do …. Loop Until) | ||
| Write("Начало счета ") f = 3 m: Write(f & " ") f = f + 2 If f <= 9 Then GoTo m Write("Конец счета") | Write("Начало счета ") f = 3 m1: Write(f & " ") f = f + 2 If f > 9 Then GoTo m2 Else GoTo m1 m2: Write("Конец счета") |
Вот в каком порядке выполняются операторы программы 1 варианта:
Write("Начало счета") f=3 Write(f & " ") {печатается3} f=f+2 {f становится равным 5} If f<=9 Then GoTo m Write(f& " ") {печ. 5} f=f+2 {f = 7} If f<=9 Then GoTo m Write(f & " ") {печ. 7} f=f+2 {f = 9} If f<=9 Then GoTo m Write(f & " ") {печ. 9} f=f+2 {f = 11} If f<=9 Then GoTo m Write("Конец счета")
Здесь оператор GoTo m выполняется три раза. На четвертый раз условие f<=9 оказывается ложным и поэтому выполняется не GoTo m, а следующий за If оператор Write("Конец счета"), то есть программа выходит из цикла и завершает свою работу.
|
3 ВАРИАНТ (Do While …. Loop) |
4 ВАРИАНТ (Do Until …. Loop) |
|
Write("Начало счета ") f = 3 m1: If f <= 9 Then GoTo m3 Else GoTo m2 m3: Write(f & " ") f = f + 2 GoTo m1 m2: Write("Конец счета") |
Write("Начало счета ") f = 3 m1: If f > 9 Then GoTo m2 Else GoTo m3 m3: Write(f & " ") f = f + 2 GoTo m1 m2: Write("Конец счета") |
Задача: Напечатать пары чисел - 0 1000 1 999 2 998 . . . . . . 1000 0.
Программа:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim f, s As Integer
f = 0
m: s = 1000 - f
Write(f & " " & s & " ")
f = f + 1
If f <= 1000 Then GoTo m
End Sub
Задание 25.
А. Напечатать 1 2 3 4 . . . 99 100
Б. Напечатать 100 99 . . . 3 2 1
В. Напечатать 1 2 3 4 . . . 99 100 99 . . . 3 2 1
Задача «Таблицы Брадиса» или «Таблицы логарифмов». В те далекие времена, когда калькуляторов еще не было, были такие напечатанные в виде брошюрок таблицы для школьников и студентов, по которым они могли быстро посмотреть численные значения квадратов, логарифмов и других математических функций. Поставим задачу вычислить и напечатать таблицы Брадиса. На первый раз достаточно вычислить и напечатать с 6 десятичными знаками квадраты и логарифмы чисел 0.001 0.002 0.003 . . . 0.999 1.000.
Программа:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Dim a, Квадрат, Логарифм As Decimal
a = 0.001
m: Квадрат = a ^ 2
Логарифм = Math.Log(a)
Debug.WriteLine(Format(a, "0.000") & " " & Format(Квадрат, "0.000000") _
& " " & Format(Логарифм, "0.000000"))
a = a + 0.001
If a <= 1 Then GoTo m
End Sub
Почему я объявил a как Decimal, а не Double? Причина в незначительных погрешностях, которые допускает компьютер при действиях с десятичными дробями (о чем я уже писал). На моем компьютере при многократном прибавлении 0.001 значение a типа Double на некотором этапе переставало быть точным. Конкретнее, у меня получалось вот что:
0,682 + 0,001 = 0,683000000000001
Вследствие этого, при дальнейшем нарастании а последнее сложение было таким:
0,999000000000001 + 0,001 = 1,000000000000001
Легко видеть, что в этом случае для a=1 задание не выполняется, так как неправда, что
1,000000000000001 <= 1
Тип же Decimal, как я уже писал, обеспечивает абсолютную точность сложений и вычитаний.
Можно было обойтись и типом Double, но в этом случае оператор If пришлось бы чуть-чуть изменить, например, так:
If a < 1.00001 Then GoTo m
Задание 26.
Для х=2700, 900, 300, 100 . . . и т.д. вычислять и печатать y=x/4 + 20 и z=2y+0.23 до тех пор, пока yz не станет меньше 1/х.
Задание 27.
Пусть движущееся изображение, описанное в 8.1.4, через некоторое время остановится.
Задание 28.
Изображение, пройдя немного слева направо, поворачивает вниз и, пройдя немного, через некоторое время останавливается.
Оператор Do …. Loop
Попытаемся составить с использованием 0 варианта оператора Do программу решения задачи о бесконечной печати чисел 3 5 7 … из предыдущего подраздела. Для того, чтобы точно определить работу этого варианта оператора Do, приведем ее параллельно с 0 вариантом программы решения этой задачи из того же подраздела. Работают эти параллельные варианты абсолютно одинаково. При этом объяснением любого оператора в правом столбце является оператор, стоящий в той же строчке в левом столбце.
| 0 ВАРИАНТ | 0 ВАРИАНТ ОПЕРАТОРА Do | ||
| Write("Начало счета ") | Write("Начало счета ") | ||
| f = 3 | f = 3 | ||
| m: | Do | ||
| Write(f & " ") | Write(f & " ") | ||
| f = f + 2 | f = f + 2 | ||
| GoTo m | Loop |
Do можно перевести, как «Делай», а понимать следует просто как метку.
Loop можно перевести, как «Петля» или «Возврат назад», а понимать следует так: «Возвращайся к метке Do».
Порядок работы обеих программ совершенно одинаков, так что можно считать слово Do заменой метки m:, а слово Loop считать заменой оператора GoTo m. Обе программы бесконечно печатают 3 5 7 9 11 …..
Общий смысл оператора Do
такой: выполни по порядку сверху вниз все операторы между словами Do и Loop, затем выполни их по порядку еще раз и еще раз и так далее.
Толку в 0 варианте оператора Do мы из-за зацикливания видим мало (пока!).
Синтаксис оператора Do …. Loop:
Do
операторы
операторы
…………….
Loop
Строки операторов между Do и Loop называются телом цикла.
Оператор Do …. Loop While
Добавьте в ваш проект еще 4 кнопки и выполните в пошаговом режиме программы с вариантами оператора Do 1 – 4, которые я привел ниже. Вы увидите, что все 4 варианта делают одно и то же и они очень похожи. Вопрос о том, зачем нужно целых 4 похожих варианта, рассмотрим чуть позже. Уверяю, они все нужны.
Составим с использованием 1 варианта оператора Do программу решения задачи о печати чисел 3 5 7 9 из предыдущего подраздела. Для того, чтобы точно определить работу этого варианта оператора Do, приведем ее параллельно с 1 вариантом программы решения этой задачи из того же подраздела. Объяснением любого оператора в правом столбце является оператор, стоящий в той же строчке в левом столбце.
| 1 ВАРИАНТ | 1 ВАРИАНТ ОПЕРАТОРА Do | ||
| Write("Начало счета ") | Write("Начало счета ") | ||
| f = 3 | f = 3 | ||
| m: | Do | ||
| Write(f & " ") | Write(f & " ") | ||
| f = f + 2 | f = f + 2 | ||
| If f <= 9 Then GoTo m | Loop While f <= 9 | ||
| Write("Конец счета") | Write("Конец счета") |
While переводится «Пока». Значит, Loop While f <= 9 понимать следует так: «Возвращайся к метке Do, пока f<=9».
Порядок работы обеих программ совершенно одинаков, так что можно считать слово Do заменой метки m:, а конструкцию Loop While f <= 9 считать заменой оператора If f <= 9 Then GoTo m.
Синтаксис оператора Do …. Loop While:
Do
операторы
операторы
…………….
Loop While условие продолжения
работы цикла
Оператор Do …. Loop Until
| 2 ВАРИАНТ | 2 ВАРИАНТ ОПЕРАТОРА Do | ||
| Write("Начало счета ") | Write("Начало счета ") | ||
| f = 3 | f = 3 | ||
| m1: | Do | ||
| Write(f & " ") | Write(f & " ") | ||
| f = f + 2 | f = f + 2 | ||
| If f > 9 Then GoTo m2 Else GoTo m1 | Loop Until f > 9 | ||
| m2: Write("Конец счета") | Write("Конец счета") |
Until переводится «До тех пор, пока не».
Значит, Loop Until f > 9 понимать следует так: «Возвращайся к метке Do до тех пор, пока не выполнится условие f > 9».
Синтаксис оператора Do …. Loop Until:
Do
операторы
операторы
…………….
Loop Until условие завершения
работы цикла
Оператор Do While …. Loop
| 3 ВАРИАНТ | 3 ВАРИАНТ ОПЕРАТОРА Do | ||
| Write("Начало счета ") | Write("Начало счета ") | ||
| f = 3 | f = 3 | ||
| m1: If f <= 9 Then GoTo m3 Else GoTo m2 | Do While f <= 9 | ||
| m3: Write(f & " ") | Write(f & " ") | ||
| f = f + 2 | f = f + 2 | ||
| GoTo m1 | Loop | ||
| m2: Write("Конец счета") | Write("Конец счета") |
Do While f <= 9 понимать следует так: «Пока f <= 9, выполняй нижестоящие операторы вплоть до Loop».
Синтаксис оператора Do While …. Loop:
Do While условие продолжения работы цикла
операторы
операторы
…………….
Loop
Оператор Do Until …. Loop
| 4 ВАРИАНТ | 4 ВАРИАНТ ОПЕРАТОРА Do | ||
| Write("Начало счета ") | Write("Начало счета ") | ||
| f = 3 | f = 3 | ||
| m1: If f > 9 Then GoTo m2 Else GoTo m3 | Do Until f > 9 | ||
| m3: Write(f & " ") | Write(f & " ") | ||
| f = f + 2 | f = f + 2 | ||
| GoTo m1 | Loop | ||
| m2: Write("Конец счета") | Write("Конец счета") |
Do Until f > 9 понимать следует так: «Выполняй нижестоящие операторы вплоть до Loop, до тех пор, пока не выполнится условие f > 9».
Синтаксис оператора Do Until …. Loop:
Do Until условие завершения работы цикла
операторы
операторы
…………….
Loop
Разница между вариантами операторов Do
Разницы две:
Dim f As Integer = 3
Do Until f > 0
Debug.Write(f)
f = f - 10
Loop
Во втором случае, каково бы ни было условие, тело цикла хотя бы раз, да выполнится, потому что добраться до стоящего внизу условия можно только пройдя все стоящие выше него операторы.
Часто эти отличия для начинающих малосущественны, поэтому пока выбирайте вариант по вкусу.
Типичная ошибка начинающих – небрежное обращение со знаками сравнения. Многие не видят большой разницы в том, как записать – While f<=9 или While f<9, а затем, «недополучив» результат, удивляются, куда он делся. Если вы не понимаете, куда, попробуйте ошибочный вариант программы с While f<9 выполнить в пошаговом режиме.
Примеры и задания
Задание 29.
Выполнить с использованием оператора Do задачу из 8.2: Напечатать пары чисел – 0 1000 1 999 2 998 . . . . . . 1000 0. Напишите два варианта программы с использованием 1 и 2 вариантов оператора Do.
Задание 30.
Выполнить с использованием оператора Do Задание 43: Изображение, пройдя немного слева направо, поворачивает вниз и, пройдя немного, через некоторое время останавливается. Используйте 3 и 4 варианты оператора Do.
Задача: Компьютер предлагает человеку ввести слово, после чего распечатывает это слово, снабдив его восклицательным знаком. Затем снова предлагает ввести слово и так до тех пор, пока человек не введет слово «Хватит». Распечатав его с восклицательным знаком, компьютер отвечает «Хватит так хватит» и заканчивает работу.
Придумаем строковую переменную, в которую человек будет с клавиатуры вводить слово. Назовем ее Slovo. Выберем подходящий вариант оператора Do, это будет 2-й вариант (а 3-й и 4-й здесь вообще не подойдут), и пишем программу:
Dim Slovo As String
Do
Slovo = InputBox("Введите слово")
Debug.WriteLine(Slovo & "!")
Loop Until Slovo = "Хватит"
Debug.WriteLine("Хватит так хватит")
Задание 31.
Усложним эту задачу. Пусть компьютер перед распечаткой каждого слова ставит его порядковый номер. И еще: если слово длинней 10 букв, компьютер должен добавить – «Тяжелая жизнь.».
Задание 32.
«Полет камня». Это непростое задание разделим на два этапа:
Вычислительная часть. Если камень бросить горизонтально со 100-метровой башни со скоростью v=20м/с, то его расстояние от башни по горизонтали s будет выражаться формулой s=vt, где t – время полета камня в секундах. Высота над землей h будет выражаться формулой h=100-9.81t2/2. Требуется вычислять и печатать t, s и h для значений t = 0, 0.2, 0.4, 0.6 и так далее до тех пор, пока камень не упадет на землю.
Графическая часть (выполнять только в том случае, если получилась вычислительная). Нарисовать землю, башню и траекторию камня (Рис. 8.1). Указание: Траектория – серия кружочков. За одну итерацию цикла рисуется один кружочек. Высота башни – 100 пикселей. И в остальном тоже выберем масштаб – 1 метр – 1 пиксель, что удобно. Горизонтальная координата кружка на форме – это s с небольшим сдвигом вправо, так как бросаем не от левого края формы. Вертикальная координата кружка на форме – это 100-h с небольшим сдвигом вниз, так как бросаем не от верхнего края формы. Переменная h взята со знаком минус потому, что вертикальная ось в компьютерной системе координат направлена вниз.

Рис. 8.1
В Задание 104 мы изобразим полет камня в реальном времени.
Оператор Exit Do
Оператор Exit Do нужен для того, чтобы выходить из цикла не в начале тела цикла, как в вариантах 3-4, не в конце, как в вариантах 1-2, а в середине. Добавим, например, Exit Do в тело цикла одного из вариантов предыдущей программы:
Write("Начало счета ")
f = 3
Do
Write(f & " ")
Exit Do
f = f + 2
Loop While f <= 9
Write("Конец счета")
Вот результат работы этой программы:
Начало счета 3 Конец счета
Как видите, Exit Do – это всего лишь приказ перейти к оператору, следующему за словом Loop. Толк от Exit Do будет тогда, когда его поместят внутрь оператора ветвления:
Write("Начало счета ")
f = 3
Do
Write(f & " ")
If f >= 5 Then Exit Do
f = f + 2
Loop While f <= 9
Write("Конец счета")
Вот результат работы этой программы:
Начало счета 3 5 Конец счета
Пока в Exit Do особого смысла вы не видите. А увидите вы его в следующей задаче, где не подойдет ни один из вариантов 1 – 4 оператора Do, а подойдет Exit Do в совокупности с 0 вариантом.
Задача. Выполнить с использованием Do и Exit Do Задание 41: Для х=2700, 900, 300, 100 .. . и т.д. вычислять и печатать y=x/4+20 и z=2y+0.23 до тех пор, пока yz не станет меньше 1/х.
Загляните в ответ – как решена задача без Do. А вот как – с Do.
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
Dim x, y, z As Double
x = 2700
Do
y = x / 4 + 20
z = 2 * y + 0.23
If y * z < 1 / x Then Exit Do
Debug.WriteLine(Format(x, "0.000000") & " " & Format(y, "0.000000") & " " & Format(z, "0.000000"))
x = x / 3
Loop
End Sub
Как видите, очень похоже. Обратите внимание, что если вы захотите обойтись без Exit Do и использовать один из вариантов 1 – 4 оператора Do, то у вас при использовании 1-2 вариантов «новый» x в условии y*z<1/x будет сравниваться со «старыми» y и z (в нашем конкретном случае это не приведет к катастрофе, но все равно это нехорошо). А использование 3-4 вариантов вообще невозможно без неоправданного усложнения программы.
Задание 33.
Решить чуть измененную задачу про «Хватит так хватит» из предыдущего раздела: Компьютер предлагает человеку ввести слово, после чего распечатывает это слово, снабдив его восклицательным знаком. Затем снова предлагает ввести слово и так до тех пор, пока человек не введет слово «Хватит». Его он не распечатывает вообще, а отвечает «Хватит так хватит» и заканчивает работу. Указание: Используйте Exit Do в совокупности с 0 вариантом Do. Остальные варианты не подойдут или подойдут плохо.
Оператор цикла While …End While
Имеется оператор цикла While …… End While, привычный для других языков и для предыдущих версий Бейсика. Вот вам его синтаксис:
While условие продолжения работы цикла
операторы
операторы
…………….
End While
А смысл – абсолютно тот же, что и у Do While …. Loop
Операторы цикла Do
Циклы настолько широко применяются в программах, что у программистов давным-давно появилась потребность написать специальный оператор цикла, не использующий оператор GoTo. Последний неудобен хотя бы тем, что у программистов, пишущих большие программы, много времени и внимания уходит на поиск взглядом меток в тексте программы. К тому же GoTo нарушает стройную идеологию так называемого «структурного программирования», когда порядок действий задается не скачками из одной части программы в другую, а цепочкой вложенных друг в друга операторов. В общем, нынче широко использовать GoTo так же неприлично, как не объявлять переменные.
Операторы цикла в VB делятся на 2 вида: Do и For. Операторы вида Do встречаются в 5 вариантах:
| 0 ВАРИАНТ | 1 ВАРИАНТ | 2 ВАРИАНТ | 3 ВАРИАНТ | 4 ВАРИАНТ | |||||
| Do …. Loop | Do …. Loop While | Do …. Loop Until | Do While …. Loop | Do Until …. Loop |
Операторы вида For встречаются в 2 вариантах. О них – в следующем разделе.
Объясняю For на примерах
Задача 1: 200 раз напечатать слово ФУТБОЛ.
Очевидно, нам нужно написать циклическую программу, где цикл выполнялся бы 200 раз. Попробуем сначала решить задачу при помощи оператора GoTo. Для выхода из цикла оператор GoTo нужно включить в состав оператора If. Кроме этого нужна переменная, меняющая свое значение от одного выполнения цикла к следующему. Удобнее всего, если эта переменная будет менять свое значение на 1, начиная с 0 или 1. Напомним, что переменная, организованная таким образом, является счетчиком циклов (См. 8.1.3). Придумаем этой величине какое-нибудь имя, скажем i. Задачу решает такой причудливый фрагмент:
i = 1
m1: If i > 200 Then GoTo m2
Debug.Write("ФУТБОЛ ")
i = i + 1
GoTo m1
m2:
Здесь i вначале равно 1, но к каждому следующему выполнению цикла оно увеличивается на 1. В первый раз выполняя оператор If, компьютер проверяет условие 1>200 и найдя его ложным, выполняет следующий оператор – Debug.Write("ФУТБОЛ "). Во второй раз проверяется условие 2>200 ит.д. В 200-й раз компьютер проверяет условие 200>200 и найдя его ложным, в 200-й раз выполняет оператор Debug.Write("ФУТБОЛ "). В 201-й раз компьютер проверяет условие 201>200 и найдя его истинным, выходит из цикла, перейдя к метке m2. Все. ФУТБОЛ напечатан ровно 200 раз.
В нашем примере «полезную» работу выполняет только одна строка из пяти – Debug.Write("ФУТБОЛ "). Остальные строки заняты тем, что обеспечивают выполнение «полезной» строки ровно 200 раз. Чтобы избежать такого перекоса в сторону «обслуживающего персонала» и для вящей краткости, придумали оператор For и вместо верхних 2 строк:
i = 1
m1: If i > 200 Then GoTo m2
стали писать 1 строку:
For i = 1 To 200
а вместо нижних 2 строк:
i = i + 1
GoTo m1
тоже стали писать 1 строку:
Next i
смысл программы оставляя тем же самым.
Получилось вот что:
For i = 1 To 200
Debug.Write("ФУТБОЛ ")
Next i
Как видите, гораздо короче.
Слово For переводится «Для». Слово To переводится «до». Конструкция For i=1 To 200 понимается так: Для i, изменяющегося от 1 до
200, выполняй операторы, стоящие ниже вплоть до слова Next.
Слово Next говорит о том, что надо увеличивать i на 1 и возвращаться к началу цикла. При первом выполнении цикла i будет равно 1, при втором – 2, и так далее. При последнем – 200. Переменная i называется переменной цикла.
Задача 2. В предыдущем конкретном случае сами по себе значения i не были важны, нужный результат мы бы получили и с оператором
For i = 501 To 700
А вот где значения переменной цикла будут важны: Распечатать пары чисел – 101 1010 102 1020 103 1030 ……. 110 1100
Вот программа:
For a = 101 To 110
b = 10 * a
Debug.WriteLine(a & " " & b)
Next a
Шаг цикла
До сих пор переменная цикла менялась с шагом 1. Но оказывается шаг можно задавать любой и тогда оператор For удобно использовать вместо Do – программы получаются короче.
Пусть нужно распечатать числа 600, 605, 610, 615 . . . 900. Вот программа:
Dim a As Integer
For a = 600 To 900 Step 5
Debug.WriteLine(a)
Next a
Здесь Step 5 означает «Шаг 5».
Пусть нужно распечатать числа 3.00, 3.02, 3.04 . . . 5.00. Вот программа:
Dim a As Decimal
For a = 3 To 5 Step 0.02
Debug.WriteLine(Format(a, "0.00"))
Next a
Как видите, шаг может быть дробным. Тип переменной цикла я выбрал Decimal, если бы я выбрал Double, то при многократном прибавлении шага значение a стало бы чуть-чуть неточным.
Пусть нужно распечатать числа 5.00, 4.98, 4.96 . . . 3.00. Вот программа:
Dim a As Decimal
For a = 5 To 3 Step -0.02
Debug.WriteLine(Format(a, "0.00"))
Next a
Как видите, шаг может быть отрицательным.
Сравниваем For и Do. Таким образом, оператор For удобнее оператора Do в двух случаях: когда цикл нужно выполнить определенное количество раз и когда нам известны начальное и конечное значения переменной цикла и шаг. Однако за компактность и удобство For платит бедностью возможностей. Do гораздо более гибок, чем For.
Синтаксис и работа оператора For
Синтаксис оператора For:
For переменная цикла = выражение1 To выражение2 [ Step выражение3
]
операторы
операторы
……………..
Next [ переменная цикла ]
Переменная цикла должна иметь числовой тип. Обычно назначают Integer. Допустимы также нечисловые типы, для которых имеют смысл операции сложения и сравнения. В качестве примера приведу перечисления (см. 13.3).
После Next вы можете писать, а можете и не писать переменную цикла, но в программах, где много пар For и Next, я рекомендую это делать для легкости чтения программы, чтобы вам удобней было разобраться, для какого именно For написан данный конкретный Next.
Вместо чисел в операторе For можно писать переменные и выражения. Пример записи:
For I = s To 2*s+1 Step k*10
Если шаг не указан, он считается равным 1, то есть переменная на каждой итерации увеличивается на 1. Если же мы хотим уменьшать ее на 1, нам придется явно указать Step -1.
Работа оператора For
при положительном (или нулевом) шаге:
Прежде всего вычисляется выражение1, и переменной цикла (пусть это будет i) присваивается его значение. Затем вычисляется выражение2 и сравнивается с i. Если i> выражения2, то оператор For завершает свою работу, так ничего и не сделав. В противном случае выполняются операторы, стоящие между строками For и Next. После их выполнения значение i увеличивается на значение выражения3 (или при его отсутствии на 1) и снова сравнивается с выражением2. Если i > выражения2, то оператор For завершает свою работу, иначе снова выполняются операторы, снова i увеличивается и т.д.
при отрицательном шаге:
Прежде всего вычисляется выражение1, и переменной цикла (пусть это будет i) присваивается его значение. Затем вычисляется выражение2 и сравнивается с i. Если i < выражения2, то оператор For завершает свою работу, так ничего и не сделав. В противном случае выполняются операторы, стоящие между строками For и Next. После их выполнения значение i уменьшается на значение модуля выражения3 и снова сравнивается с выражением2. Если i < выражения2, то оператор For завершает свою работу, иначе снова выполняются операторы, снова i уменьшается и т.д.
Примечание: В VB 2003 в отличие от VB вы можете объявлять переменную цикла не заранее (оператором Dim), а непосредственно в заголовке цикла:
For переменная цикла As тип = выражение1 To …….
В этом случае областью видимости переменной цикла является блок оператора For (см. 11.3.4).
Оператор Exit For
Оператор Exit For – еще один способ выхода из цикла For. Применяется он совершенно аналогично оператору Exit Do, описанному в 8.3.8. .
Задание 34.
Напечатать с помощью оператора For:
Прямой счет: -5 -4 -3 -2 -1 0 1 2 3 4 5 Обратный счет: 5 4 3 2 1 0 -1 -2 -3 -4 -5 Конец счета
Мыльные пузыри» и другие шалости
Пришло время пожинать плоды ученья. В этом разделе вы получите несколько заданий на рисование красочных и пестрых картинок. Все они основаны на использовании циклов и случайных величин.
Разноцветное звездное небо. Рассмотрим пример. Вспомните задачу о звездном небе, которую мы решали в 7.3.2. Тогда за одно нажатие на кнопку рисовалась одна звезда. Оператор, рисующий одну белую звезду размером в 3 пикселя, выглядел так:
Граф.DrawEllipse(Pens.White, 500 * Rnd(), 400 * Rnd(), 3, 3)
Поставим задачу за одно нажатие на кнопку нарисовать «тыщу» звезд. Тем, кто знает операторы цикла, сделать это очень просто:
Dim i As Integer
For i = 1 To 1000
Граф.DrawEllipse(Pens.White, 500 * Rnd(), 400 * Rnd(), 3, 3)
Next
Впечатляет. Но мне не нравится, что звезда внутри «пустая», так как сделана из окружности, а не из круга. Заменим оператор рисования:
Граф.FillEllipse(Brushes.White, 500 * Rnd(), 400 * Rnd(), 3, 3)
Добьемся теперь, чтобы звездное небо рисовалось во всю форму:
Граф.FillEllipse(Brushes.White, Me.Width * Rnd(), Me.Height * Rnd(), 3, 3)
Картина звездного неба получится абсолютно реальной и изумительно красочной, если звезды будут разноцветные и разных размеров. Займемся сначала размером звезды. Пусть он будет случайным, причем самые крупные звезды имеют размер = 5:
Dim i, Размер_звезды As Integer
For i = 1 To 1000
Размер_звезды = 5 * Rnd()
Граф.FillEllipse(Brushes.White, Me.Width * Rnd(), Me.Height * Rnd(), Размер_звезды, Размер_звезды)
Next
Как по-вашему: почему я не написал вместо
Размер_звезды = 5 * Rnd()
Граф.FillEllipse(Brushes.White, Me.Width * Rnd(), Me.Height * Rnd(), Размер_звезды, Размер_звезды)
попроще:
Граф.FillEllipse(Brushes.White, Me.Width * Rnd(), Me.Height * Rnd(), 5 * Rnd(), 5 * Rnd())
Если не догадались, попробуйте и сразу увидите, почему. Звезды потеряют форму круга и станут разнокалиберными эллипсами.
Мы пока не готовы сделать цвет звезды случайным, поэтому поступим просто: выберем несколько любимых звездных цветов (скажем, белый, желтый, голубой и красный) и напишем в цикле соответствующее количество операторов рисования:
For i = 1 To 200
Размер_звезды = 5 * Rnd()
Граф.FillEllipse(Brushes.White, Me.Width * Rnd(), Me.Height * Rnd(), Размер_звезды, Размер_звезды)
Граф.FillEllipse(Brushes.Yellow, Me.Width * Rnd(), Me.Height * Rnd(), Размер_звезды, Размер_звезды)
Граф.FillEllipse(Brushes.LightBlue, Me.Width * Rnd(), Me.Height * Rnd(), Размер_звезды, Размер_звезды)
Граф.FillEllipse(Brushes.LightPink, Me.Width * Rnd(), Me.Height * Rnd(), Размер_звезды, Размер_звезды)
Next
Напоминаю, что сколько бы раз вы ни запускали программу с указанным фрагментом, картина созвездий на экране будет абсолютно одинакова. Если вам нужно, чтобы от запуска к запуску набор значений случайной величины менялся (а значит и созвездия), употребите разик до использования функции Rnd функцию Randomize.
Как замедлить работу компьютера. Пустой цикл. Если вы эстет (а я эстет), то вам захочется, чтобы звезды на небе зажигались помедленнее. Для этого достаточно, чтобы после рисования очередной звезды перед рисованием новой возникала небольшая пауза. Вообще-то, для этих целей используют таймер. Но поскольку вы с ним еще не знакомы, вставьте для замедления внутрь цикла оператор, который, ничего не изменяя на экране, будет выполняться достаточно долго. Обычно для этого используют «пустой цикл»:
For j = 1 To 1000000 : Next
Пока компьютер будет бестолку считать до миллиона, пройдет некая значительная доля секунды:
Dim i, j, Размер_звезды As Integer
For i = 1 To 20
Размер_звезды = 5 * Rnd()
Граф.FillEllipse(Brushes.White, Me.Width * Rnd(), Me.Height * Rnd(), Размер_звезды, Размер_звезды)
Граф.FillEllipse(Brushes.Yellow, Me.Width * Rnd(), Me.Height * Rnd(), Размер_звезды, Размер_звезды)
Граф.FillEllipse(Brushes.LightBlue, Me.Width * Rnd(), Me.Height * Rnd(), Размер_звезды, Размер_звезды)
Граф.FillEllipse(Brushes.LightPink, Me.Width * Rnd(), Me.Height * Rnd(), Размер_звезды, Размер_звезды)
For j = 1 To 10000000
: Next j 'Пустой цикл
Next i
Здесь маленькая пауза возникает после рисования каждой четверки звезд.
Задание 35.
“Дождь в луже”. Поместите на форму PictureBox. Покрасьте его в цвет воды. Сделайте ему рамочку. Все это в режиме проектирования. Это наша лужа или, скорее, бассейн. Нарисуйте на нем в случайных местах штук 20 эллипсов, сжатых по вертикали раза в два. Результат – на Рис. 8.2.

Рис. 8.2
Задание 36.
«Мыльные пузыри». Разноцветные окружности случайных радиусов на темном фоне.
Задание 37.
«Сноп света в глаза». То есть пучок лучей, выходящих из одной точки. Реализуется множеством случайных разноцветных отрезков прямых, причем одна точка всех отрезков не случайна, а находится в центре формы. Хорошо смотрится на черном фоне.
Задание 38.
«Стог сена». Множество случайных разноцветных отрезков прямых преимущественно желтоватых оттенков, причем одна точка любого отрезка находится в случайной точке левой трети стога, другая – в случайной точке правой. Размер стога – 600 на 600.
Оператор цикла For
Я говорил, что имеются две разновидности оператора For. Здесь я рассмотрю только одну. Вторая будет разобрана в 16.2.2.
Вспомните программу печати чисел 3 5 7 9:
f = 3
Do
Write(f & " ")
f = f + 2
Loop While f <= 9
Здесь было выполнено 4 итерации цикла Do или, как еще говорят, цикл выполнился 4 раза, или, как говорят уже не очень правильно, было выполнено 4 цикла. Но обычно, когда мы пишем операторы Do, нам совсем не интересно знать, сколько итераций будет сделано.
Тем не менее, существует множество задач, для решения которых цикл нужно выполнить именно определенное количество раз. В этом случае вместо оператора Do удобнее использовать оператор цикла For.
Используем в рисовании переменные величины
Я уже потихоньку использовал в рисовании переменные величины, но без особых пояснений, и, главное, в недостаточной степени. Переменные должны почти полностью вытеснить числа из текста программ, это неизбежно.
Если вы нарисовали человечка и паровоз, то наверное согласитесь, что для этого вам пришлось основательно потрудиться, хотя сам рисунок получился не слишком сложным, в нем всего-то порядка двух десятков элементов.
Как заставить VB короткой программой рисовать множество элементов, сплетая их в красивые узоры? Ответ: применять циклы, используя в обращениях к графическим методам вместо чисел переменные величины и арифметические выражения. Поучимся.
Задача: Нарисовать горизонтальный ряд окружностей диаметром 20 на расстоянии 100 от верхнего края формы и с такими горизонтальными координатами 50, 80, 110, 140, ¼ , 290.

Как видим, центры соседних окружностей отстоят друг от друга на 30. Вот примитивный фрагмент, решающий эту задачу:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim Граф As Graphics = Me.CreateGraphics
Граф.DrawEllipse(Pens.Black, 50, 100, 20, 20)
Граф.DrawEllipse(Pens.Black, 80, 100, 20, 20)
Граф.DrawEllipse(Pens.Black, 110, 100, 20, 20)
Граф.DrawEllipse(Pens.Black, 140, 100, 20, 20)
Граф.DrawEllipse(Pens.Black, 170, 100, 20, 20)
Граф.DrawEllipse(Pens.Black, 200, 100, 20, 20)
Граф.DrawEllipse(Pens.Black, 230, 100, 20, 20)
Граф.DrawEllipse(Pens.Black, 260, 100, 20, 20)
Граф.DrawEllipse(Pens.Black, 290, 100, 20, 20)
End Sub
При вводе этой программы вас будет раздражать необходимость вводить много раз почти одно и то же. Воспользуйтесь копированием, которое объяснено в Приложении 2.
Мы видим, что здесь VB 9 раз выполняет один и тот же метод DrawEllipse, причем при каждом следующем обращении второй параметр метода вырастает на 30.
Придумаем для этого параметра переменную величину, например, х. Немного изменим программу:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Dim Граф As Graphics = Me.CreateGraphics
Dim x As Integer = 50
Граф.DrawEllipse(Pens.Black, x, 100, 20, 20) : x = x + 30
Граф.DrawEllipse(Pens.Black, x, 100, 20, 20) : x = x + 30
Граф.DrawEllipse(Pens.Black, x, 100, 20, 20) : x = x + 30
Граф.DrawEllipse(Pens.Black, x, 100, 20, 20) : x = x + 30
Граф.DrawEllipse(Pens.Black, x, 100, 20, 20) : x = x + 30
Граф.DrawEllipse(Pens.Black, x, 100, 20, 20) : x = x + 30
Граф.DrawEllipse(Pens.Black, x, 100, 20, 20) : x = x + 30
Граф.DrawEllipse(Pens.Black, x, 100, 20, 20) : x = x + 30
Граф.DrawEllipse(Pens.Black, x, 100, 20, 20) : x = x + 30
End Sub
В последней строке оператор x = x + 30 я написал только для красоты, от него в ней нет никакой пользы, хотя и вреда тоже особого нет.
Эта программа рисует абсолютно то же самое, что и предыдущая, но она проще нее, так как не пришлось самим вычислять координаты.
Что мы видим? Мы видим, что программа состоит из 9 совершенно одинаковых фрагментов. Это прямое приглашение применить цикл:
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
Dim Граф As Graphics = Me.CreateGraphics
Dim x As Integer= 50
Do Until x > 290
Граф.DrawEllipse(Pens.Black, x, 100, 20, 20) : x = x + 30
Loop
End Sub
Эта программа также рисует абсолютно то же самое, что и две предыдущие, но она существенно короче. Здесь я объявил переменную x, как целую. Иначе при многократном прибавлении 30 могло бы оказаться, что результат равен не 290, а, скажем, 290.0000067 (Такой же случай я рассматривал в 8.2). А это значит, что последняя окружность не была бы нарисована. Можно было бы перестраховаться по-другому: вместо Do Until x > 290 написать Do Until x > 291.
Кое-чему мы научились. Когда у вас возникнут проблемы с использованием переменных величин в графических (и не только графических) задачах, постарайтесь пройти по ступенькам, по которым только-что прошел я: от чисел – к одинаковым фрагментам, а от них – к циклу.
Задание 39.
«Труба». А. Попробуйте уменьшить расстояние между центрами окружностей, не изменяя их радиуса, нарисовав их плотнее, чтобы они пересекались, еще плотнее, пока они не образуют «трубу».
Б. Удлините трубу налево и направо до краев формы.
В. Увеличьте толщину трубы.
Ряд окружностей наискосок. Заставим окружности вести себя посложнее. Например, расположим их не по горизонтали, а примерно по диагонали формы в направлении от левого верхнего угла вправо вниз.
Начнем с первой ступеньки:
Граф.DrawEllipse(Pens.Black, 0, 0, 20, 20)
Граф.DrawEllipse(Pens.Black, 30, 20, 20, 20)
Граф.DrawEllipse(Pens.Black, 60, 40, 20, 20)
Граф.DrawEllipse(Pens.Black, 90, 60, 20, 20)
На второй ступеньке организуем две переменные: горизонтальную координату x и вертикальную координату у – и заставим их изменяться:
x = 0
y = 0
Граф.DrawEllipse(Pens.Black, x, y, 20, 20) : x = x + 30 : y = y + 20
Граф.DrawEllipse(Pens.Black, x, y, 20, 20) : x = x + 30 : y = y + 20
И наконец мы на последней ступеньке:
Dim x, y As Integer
x = 0
y = 0
Do Until x > 290
Граф.DrawEllipse(Pens.Black, x, y, 20, 20)
x = x + 30
y = y + 20
Loop
Задания. Теперь вы достаточно подготовлены для того, чтобы выполнить следующие задания:
Задание 40.
«Две очереди трассирующими ночью». Мы «перечеркнули» форму окружностями наискосок из левого верхнего угла приблизительно в правый нижний. Пусть теперь это будут очень маленькие кружочки на черной форме. Получилась «очередь трассирующими». Вслед за этим перечеркните форму очередью также и из левого нижнего угла, чтобы на экране получились две пересекающиеся очереди.
Задание 41.
Нарисуйте «трубу» наискосок. Получилось? Теперь попробуйте вместе с координатами x и у менять также и диаметр окружности. Если вы будете понемножку его увеличивать, начиная с 0, то получите «трубопровод», уходящий в бесконечность.
Задание 42.
Начертите ряд квадратов. Получилось? Теперь сделайте «квадратный трубопровод».
Задание 43.
А. Разлинуйте экран в линейку.
Б. А теперь в клетку.
В. А теперь в косую линейку.
Алфавитный указатель
"................................................................... 154
#.................................................................. 362
&......................................................... 172, 492
*................................................................... 154
/.................................................................... 154
^................................................................... 154
+.................................................................. 492
<.................................................................. 201
<=................................................................ 201
<>................................................................ 201
=.................................................................. 201
>.................................................................. 201
>=................................................................ 201
A
A.................................................................. 357
Abort........................................................... 751
AboutBox..................................................... 87
Abs................................................................ 59
Access........................................................ 668
ActiveMdiChild......................................... 746
Add........................................... 178, 458, 484
Add project.................................................. 39
Add Reference......................................... 712
AddArc....................................................... 468
AddDays.................................................... 363
AddEllipse................................................. 468
AddHandler...................................... 639, 642
AddressOf................................................. 639
AddSeconds............................................. 365
AddTicks.................................................... 365
AddYears................................................... 364
ADO.NET................................................... 668
Alias............................................................ 718
Align........................................................... 118
Alt................................................................ 414
Anchor.......................................................... 71
And............................................................. 211
AND............................................................ 698
Appearance............................ 474, 477, 542
Append............................................. 502, 512
Application................................................ 717
AppWinStyle............................................. 719
Array........................................................... 445
Asc.............................................................. 495
ASCII символы......................................... 495
AscW.......................................................... 494
ASP. NET Web Application..................... 662
Assembler................................................. 759
Auto Hide................................................... 109
AutoPopDelay.......................................... 489
AutoScroll......................................... 375, 377
AutoSize.................................................... 336
B
B.................................................................. 356
BackColor.................................... 49, 69, 552
BackgroundImage............................ 76, 336
BackSpace....................................... 784, 785
Beep.................................................... 54, 718
BETWEEN................................................. 698
Binary......................................................... 512
BinaryReader............................................ 515
BinaryWriter.............................................. 515
Bitmap........................................................ 339
Bold............................................................... 66
Bold Italic..................................................... 66
Boolean..................................................... 214
BorderStyle.................................................. 70
break.......................................................... 149
Breakpoint Properties............................. 252
Breakpoints...................................... 147, 251
Bring to Front............................................ 118
Brushes...................................................... 187
Build..................................................... 94, 705
Busy............................................................ 653
Button.................................................. 32, 409
ButtonClick................................................ 543
Buttons....................................................... 540
ByRef.......................................................... 303
Byte.................................................... 157, 158
ByVal.......................................................... 303
C
Call............................................................. 275
Call Stack.................................................. 255
Cancel........................................................ 142
CapsLock.................................................. 784
Caption...................................................... 686
Catch.......................................................... 522
CBool......................................................... 163
CByte.......................................................... 162
CChar......................................................... 163
CDate......................................................... 163
CDbl........................................................... 163
CDec.......................................................... 163
CD-R........................................................... 770
CD-ROM.................................................... 770
CD-RW....................................................... 770
Ceiling........................................................ 155
Cells........................................................... 717
CenterImage............................................. 336
Changed.................................................... 650
Char................................................... 412, 494
CharacterCasing........................................ 73
Chars.......................................................... 496
CheckBox.................................................. 473
CheckBoxes.............................................. 544
Checked........................................... 474, 476
CheckedChanged................................... 475
CheckedIndices.............................. 485, 550
CheckedItems................................. 485, 550
CheckedListBox.............................. 483, 485
CheckState................................................ 474
Child........................................................... 552
Chr.............................................................. 495
Chr(10)....................................................... 508
Chr(13)....................................................... 508
ChrW.......................................................... 494
CInt............................................................. 162
Class Library............................................. 711
Class View................................................ 731
Clear....................... 190, 445, 484, 550, 697
Click..................................... 36, 89, 407, 408
Clicks.......................................................... 409
CLng........................................................... 163
Close........................................ 500, 501, 515
Close Solution................................... 38, 116
Closed.......................................................... 92
CloseFigure.............................................. 468
CLR............................................................... 18
CObj........................................................... 163
Code............................................................. 35
Collection.................................................. 457
Color....................... 329, 331, 356, 532, 558
ColorDepth................................................ 539
ColorDialog............................................... 558
Columns........................................... 544, 546
ColumnWidth............................................ 482
COM........................................................... 714
Combo простой...................................... 483
Combo раскрывающийся..................... 483
ComboBox................................................ 482
Command Window-Immediate............ 254
Comment Selection................................ 117
Common Language Runtime.................. 18
Compile..................................................... 726
Component Designer................................ 79
Connection................................................ 677
Console..................................................... 570
Console Application................................ 569
Const................................................. 310, 586
CONSTRAINT........................................... 700
Contains........................................... 484, 549
Contents........................................... 123, 724
ContextMenu............................................ 490
Continue.................................................... 149
Control..................................... 312, 414, 703
Controls............................................ 178, 459
Copy.................................. 43, 116, 519, 781
Cos............................................................. 156
Count................................................. 458, 484
COUNTER................................................. 700
CREATE TABLE....................................... 700
Created...................................................... 649
CreateDirectory........................................ 519
CreateObject............................................. 717
CShort........................................................ 162
CSng.......................................................... 163
CStr............................................................. 163
CurrentDirectory....................................... 520
CurrentPosition........................................... 87
Cursor........................................................... 71
Cut............................................... 43, 116, 781
D
DashStyle.................................................. 330
DataColumn............................................. 690
DataGrid.................................. 686, 691, 692
DataGridTableStyle................................. 693
DataSet.................................... 684, 685, 690
DataSource............................................... 686
DataTable.................................................. 690
Date................................................... 362, 364
DateAdd..................................................... 367
DateAndTime........................................... 366
DateDiff..................................................... 366
DatePart..................................................... 366
DateSerial................................................. 367
DateString................................................. 367
DateTime.......................................... 362, 363
DateTimePicker....................................... 487
DateValue................................................. 367
Day............................................................. 364
DayOfWeek............................................... 364
DayOfYear................................................. 364
DaysInMonth............................................. 365
Debug........................................................ 255
Debug.WriteLine............................... 55, 147
Decimal............................................ 157, 160
Declare...................................................... 718
Delete....................... 43, 116, 519, 782, 785
DELETE..................................................... 699
Deleted...................................................... 649
Delphi......................................................... 759
DESC......................................................... 683
design........................................................... 33
Designer...................................................... 31
Details........................................................ 544
DialogResult............................................. 531
Dim........................................... 143, 144, 586
Directory.................................................... 518
Dispose...................................................... 190
DLL............................................................. 705
Do............................................. 232, 233, 236
Do …. Loop............................................... 232
Do …. Loop Until...................................... 234
Do …. Loop While.................................... 233
Do Until …. Loop...................................... 235
Do While …. Loop.................................... 235
Dock..................................................... 71, 542
Dockable................................................... 108
DocumentComplete................................ 653
DoEvents................................................... 747
Double................... 144, 157, 159, 160, 161
DoubleClick..................................... 407, 408
DrawArc..................................................... 187
DrawBezier............................................... 466
DrawClosedCurve................................... 467
DrawCurve................................................ 466
DrawEllipse............................................... 185
DrawImage...................................... 339, 344
Drawing..................................................... 182
DrawLine.......................................... 183, 185
DrawLines................................................. 464
DrawPath................................................... 468
DrawPie..................................................... 187
DrawPolygon............................................ 465
DrawRectangle......................................... 185
DrawRectangles....................................... 464
DrawString................................................ 191
DROP TABLE............................................ 701
DropDown Combo.................................. 483
DropDownButton..................................... 541
DropDownList........................................... 482
DropDownStyle........................................ 482
Duration....................................................... 87
DVD-ROM.................................................. 770
Dynamic Help........................................... 123
E
e......................................................... 155, 637
E.................................................................. 155
Edit.............................................................. 116
Else............................................................. 198
ElseIf.......................................................... 208
Enabled........................................................ 71
EnableRaisingEvents.............................. 650
End..................................... 46, 221, 279, 280
End Function............................................. 308
End If.......................................................... 208
End Sub.............................................. 36, 271
EndCap...................................................... 331
EndsWith................................................... 496
Enter........................................................... 405
Enum.......................................................... 372
Environment.................................... 119, 520
EOF............................................................. 513
Event.......................................................... 641
EventArgs.................................................. 409
Excel........................................................... 714
Exception................................................... 522
Exists.......................................................... 519
Exit....................................................... 38, 116
Exit Do........................................................ 238
Exit For....................................................... 243
Exit Sub...................................................... 280
Exp.............................................................. 155
Expand....................................................... 552
ExpandAll.................................................. 552
F
False........................................................... 214
File..................................................... 116, 518
File System Object................................... 498
FileAttributes............................................. 519
FileClose.......................................... 510, 512
FileGet........................................................ 511
FileMode.................................................... 515
FileName............................................ 85, 528
FileOpen........................................... 510, 512
FilePut............................................... 510, 512
FileStream................................................. 514
FileSystem................................................ 498
FileSystemWatcher................................. 649
Fill............................................. 684, 687, 697
FillEllipse................................................... 187
FillPath....................................................... 468
FillPie......................................................... 187
FillPolygon....................................... 465, 467
FillRectangle............................................. 187
FillRectangles........................................... 465
Filter................................................... 529, 649
Find............................................................. 117
Fix............................................................... 155
FlatStyle....................................................... 70
Flip.............................................................. 345
Floating...................................................... 108
Floor........................................................... 155
Focus.......................................................... 406
Font............................................. 66, 333, 532
Font Style..................................................... 66
Fonts and Colors...................................... 119
FontStyle.................................................... 333
For............................................. 239, 240, 242
For Each................................................... 458
ForeColor............................................ 70, 552
Format............................................... 117, 164
FormBorderStyle........................................ 70
Friend......................................................... 586
FROM......................................................... 683
FromArgb......................................... 353, 354
FromFile.............................................. 77, 336
FromImage............................................... 348
FullPath...................................................... 650
Function..................................................... 308
G
G.................................................................. 356
Get.............................................................. 600
GetAttributes............................................. 519
GetChar..................................................... 492
GetCreationTime..................................... 519
GetCurrentDirectory................................ 520
GetDirectories........................................... 520
GetFiles...................................................... 521
GetItemChecked...................................... 486
GetLastAccessTime................................ 519
GetLastWriteTime.................................... 519
GetLogicalDrives..................................... 521
GetNodeCount......................................... 552
GetParent.................................................. 520
GetPixel..................................................... 356
GetUpperBound....................................... 445
GoTo........................................................... 226
Graphics.................................................... 182
GraphicsPath............................................ 467
GraphicsUnit............................................. 345
Grid Pane.................................................. 680
Grid Size.................................................... 121
GridColumnStyles................................... 693
GridLines................................................... 544
GroupBox.................................................. 374
H
Handled..................................................... 415
Handles............................................... 89, 637
HatchBrush...................................... 331, 332
HatchStyle................................................. 333
Height........................................... 70, 75, 342
Help.......................................... 122, 721, 727
HelpKeyword on HelpProvider1........... 727
HelpNamespace...................................... 727
HelpNavigator on HelpProvider1......... 727
HelpProvider............................................. 727
Hide..................................................... 48, 109
HideSelection.................................. 535, 550
HorizontalResolution............................... 342
Hour............................................................ 364
HScrollBar................................................. 477
HTML.......................................................... 652
HTML Help Workshop............................. 722
HTML-документ....................................... 659
Hue............................................................. 559
I
Icon............................................. 73, 339, 341
If......................................................... 198, 210
If блочный................................................. 206
If вложенные............................................ 210
If многострочный.................................... 206
If однострочный...................................... 200
Image....................... 77, 336, 339, 349, 477
Image Editor............................................. 360
ImageAlign.................................................. 77
ImageIndex............................................... 546
ImageList.......................................... 538, 540
Images....................................................... 538
ImageSize................................................. 539
Imports......................................................... 60
In................................................................. 457
IncludeSubdirectories............................. 649
Indeterminate........................................... 474
Index.................................................. 123, 726
IndexOf........................... 445, 484, 496, 543
Inflate......................................................... 326
Inherits....................................................... 606
InitialDelay................................................ 489
Input............................................................ 512
InputBox..................................................... 141
InputString................................................. 513
Insert......................................... 484, 496, 785
INSERT...................................................... 698
InStr............................................................ 493
Int................................................................ 658
Integer...................................... 143, 157, 158
IntelliSense............................................... 320
Internet....................................................... 771
Internet Explorer....................................... 652
Invalidate................................................... 472
Is......................................................... 217, 543
IsDate......................................................... 367
IsDigit......................................................... 412
IsLeapYear................................................ 365
IsLetter....................................................... 412
IsLetterOrDigit........................................... 412
IsLower...................................................... 412
IsMdiContainer......................................... 745
IsNumeric.................................................. 219
IsPunctuation............................................ 412
IsSeparator................................................ 412
IsUpper...................................................... 412
Italic............................................................... 66
Item............................................................. 692
Items......................................... 480, 483, 545
J
Java............................................................ 759
JPEG.......................................................... 401
K
KeyChar..................................................... 412
KeyCode.................................................... 413
KeyDown.......................................... 411, 413
KeyEventArgs........................................... 413
KeyPress................................................... 411
KeyPressEventArgs................................. 412
KeyPreview...................................... 414, 424
Keys............................................................ 414
KeyUp............................................... 411, 413
L
Label............................................................. 65
LabelEdit................................................... 547
LargeChange........................................... 477
LargeImageList........................................ 550
LastIndexOf...................................... 445, 496
Lcase.......................................................... 493
Leave......................................................... 405
Left....................................... 74, 75, 409, 492
Len....................................................... 60, 492
Length............................................... 445, 496
Let............................................................... 137
Lib............................................................... 718
LinearGradientBrush...................... 331, 332
LineCap..................................................... 331
LineInput.................................................... 513
LinkClicked............................................... 491
LinkLabel................................................... 490
LinkVisited................................................. 491
LISP............................................................ 759
ListBox....................................................... 480
ListView..................................................... 544
ListViewItem............................................. 545
Load.............................................................. 92
LoadFile..................................................... 536
Locals......................................................... 250
Lock Controls............................................ 118
Locked.......................................................... 70
LOF............................................................. 513
Log.............................................................. 155
Log10......................................................... 156
Logo........................................................... 759
Long.................................................. 157, 158
Loop........................................................... 233
Lowercase................................................. 117
LTrim.......................................................... 493
Luminosity................................................. 559
M
Main............................................................ 568
MainMenu.................................................... 79
MakeTransparent..................................... 401
MappingName......................................... 693
Math..................................................... 59, 155
Max............................................................. 155
MaximizeBox.............................................. 73
Maximum.................................................. 477
MaximumSize............................................. 73
MDI............................................................. 744
MdiChildren.............................................. 747
MdiParent.................................................. 746
Media Player............................................... 83
Members............................................. 55, 123
Message.................................................... 523
Metafile............................................. 339, 341
Microsoft...................................................... 54
Microsoft Chart Control........................... 565
Microsoft Multimedia Control 6.0..... 84, 88
Microsoft WebBrowser............................ 652
Mid.............................................................. 492
Middle........................................................ 409
Min.............................................................. 155
MinimizeBox............................................... 73
Minimum................................................... 477
MinimumSize.............................................. 73
Minute........................................................ 364
Mod............................................................. 154
Module....................................................... 566
Month......................................................... 364
MonthCalendar........................................ 487
MonthName.............................................. 367
MouseDown..................................... 407, 409
MouseEnter........................................ 90, 407
MouseEventArgs...................................... 409
MouseLeave............................................. 407
MouseMove............................ 407, 408, 411
MouseUp................................................... 407
Move........................................................... 519
MsgBox...................................................... 222
MsgBox........................................................ 78
MsgBoxResult........................................... 224
MsgBoxStyle............................................. 223
MultiColumn............................................. 481
Multiline........................................................ 72
MultiSelect................................................. 549
MustInherit................................................. 610
MustOverride............................................ 611
MyBase............................................... 92, 610
MyClass..................................................... 610
N
Name................................................... 64, 541
Namespace.............................................. 730
Navigate.................................................... 653
NET Framework......................................... 18
New.................. 39, 116, 178, 328, 578, 602
New Item................................................... 577
Next............................................................ 240
Nodes................................................ 551, 552
None........................................................... 410
Normal....................................................... 336
Not.............................................................. 213
Nothing............................................. 336, 740
Now............................................................ 365
NumericUpDown..................................... 488
O
Object....................................... 311, 455, 742
Object Browser......................................... 111
OldFullPath............................................... 649
OleDbCommand..................................... 697
OleDbConnection.................................... 684
OleDbDataAdapter.................................. 684
OnClick...................................................... 658
OnPaint...................................................... 472
Opacity......................................................... 73
Open........................................... 38, 116, 717
Open File..................................................... 41
Open Project............................................... 39
OpenFileDialog........................................ 527
OpenMode....................................... 510, 512
Option Explicit........................................... 145
Options....................................................... 118
Or....................................................... 212, 334
Order.......................................................... 118
ORDER BY................................................. 683
Orientation........................................ 478, 480
Output......................................... 56, 139, 512
Overloading.............................................. 612
Overridable............................................... 609
Overrides................................................... 609
P
Page Setup............................................... 116
Paint........................................................... 348
Panel.......................................................... 375
Parent......................................................... 552
Pascal........................................................ 759
PasswordChar.......................................... 221
Paste........................................... 43, 116, 781
Path............................................................ 649
PathGradientBrush.................................. 331
PC Speaker............................................... 768
Peek........................................................... 503
PeekChar.................................................. 518
Pen............................................................. 327
Pens........................................................... 183
PerformStep............................................. 480
PI................................................................. 155
PictureBox................................................. 336
Play............................................................... 85
PlayCount.................................................... 87
Point.................................................. 324, 464
PointF......................................................... 325
PRIMARY KEY........................................... 700
Print................................................... 116, 513
PrintLine.................................................... 513
Priority........................................................ 750
Private........................................................ 586
Private Const............................................ 586
Process...................................................... 720
ProgressBar.............................................. 479
Project............................................... 117, 576
Project Explorer........................................ 574
Prolog......................................................... 759
Properties Window..................................... 63
Property..................................................... 600
Protected................................................... 607
Providers................................................... 677
Public......................................................... 586
PushButton................................................ 541
Q
Query Builder............................................ 695
Quick Watch.............................................. 254
R
R.................................................................. 356
RadioButton.............................................. 476
RaiseEvent................................................ 641
Random............................................ 510, 512
Randomize................................................ 203
Rate............................................................... 87
Read........................................................... 502
ReadBoolean............................................ 518
ReadByte................................................... 515
ReadDouble.............................................. 518
ReadLine.......................................... 501, 570
ReadOnly............................ 72, 87, 415, 600
ReadString................................................ 518
ReadToEnd............................................... 507
Recent Projects........................................ 116
Rectangle.................................................. 325
RectangleF................................................ 325
Redo........................................................... 116
Reference Types...................................... 741
Refresh...................................................... 349
Region класс............................................ 469
Region свойство...................................... 469
Regular......................................................... 66
Remove................. 458, 484, 496, 548, 552
RemoveAt.................................................. 484
Rename.............................................. 43, 781
Renamed................................................... 649
RenamedEventArgs................................ 649
Replace............................................. 117, 496
Resize........................................................ 472
Resolution................................................. 341
Resume..................................................... 751
Return......................................................... 308
Reverse...................................................... 445
RichTextBox.............................................. 533
RichTextBoxStreamType........................ 537
Right.................................................. 409, 492
Rnd.................................................... 156, 203
Rotate......................................................... 345
RotateFlip.................................................. 345
RotateTransform...................................... 360
Round......................................................... 155
Rows........................................................... 690
RTrim.......................................................... 493
run................................................................. 34
Run To Cursor.......................................... 251
S
Saturation.................................................. 559
Save........................................................... 346
Save All............................................... 38, 116
SaveFile..................................................... 536
SaveFileDialog......................................... 526
ScaleTransform....................................... 358
Scroll.......................................................... 478
Scrollable.................................................. 550
ScrollBars.................................................... 72
Scrolling..................................................... 480
Search........................................................ 123
Second....................................................... 364
Seek.................................................. 512, 515
SeekOrigin................................................ 515
SELECT................................... 683, 694, 698
Select Case............................................... 216
SelectCommand...................................... 697
SelectedIndex........................................... 484
SelectedIndexChanged.......................... 484
SelectedIndices........................................ 549
SelectedItem.................................... 481, 484
SelectedItems........................................... 549
SelectedNode........................................... 552
SelectedText............................................. 536
SelectionAlignment................................. 535
SelectionBullet......................................... 535
SelectionColor.......................................... 535
SelectionFont............................................ 535
SelectionMode......................................... 486
Send to Back............................................. 118
sender........................................................ 637
Separator................................................... 541
Server Explorer........................................ 676
Set............................................................... 600
SET............................................................. 699
SetAttributes.............................................. 519
SetLastAccessTime................................. 520
SetLastWriteTime.................................... 519
SetPixel...................................................... 357
SetResolution........................................... 343
SetToolTip................................................. 489
Shadows.................................................... 610
Shared....................................................... 598
Shell........................................................... 719
Shift................................................... 414, 784
Short.................................................. 157, 158
Shortcut........................................................ 82
Show............................................................. 50
Show Grid.................................................. 121
Show Start Page...................................... 123
ShowColor................................................ 532
ShowDialog..................................... 527, 558
ShowHelp.................................................. 727
ShowHelpIndex........................................ 727
ShowInTaskbar........................................... 73
Sibling........................................................ 552
Sign............................................................ 155
Simple Combo......................................... 483
Sin............................................................... 156
Single....................................... 157, 159, 160
Size............................................... 66, 70, 325
SizeF.......................................................... 325
SizeMode.................................................. 336
Sleep.......................................................... 751
SmallChange........................................... 477
SmallImageList........................................ 544
SnapToGrid............................................... 121
SolidBrush................................................. 331
Solution...................................................... 575
Solution Explorer...................... 95, 109, 574
Sort............................................................. 445
Sorted......................................................... 484
Sorting........................................................ 550
Split.................................................... 121, 497
Splitter........................................................ 565
SQL.................................................... 684, 698
Sqrt............................................................. 155
Start...................................................... 33, 720
Start Page.................................................. 115
StartCap..................................................... 331
StartPosition................................................ 73
StartsWith.................................................. 496
Startup object............................................ 568
Static.................................................. 301, 586
StatusBar................................................... 565
Step................................................... 241, 480
Stop............................................. 87, 280, 653
Stop Debugging......................................... 34
Str................................................................ 493
StreamReader.......................................... 500
StreamWriter............................................. 499
StretchImage............................................ 336
String........................................ 172, 492, 496
Strings................................................. 60, 492
Structure.................................................... 460
Style............................................................ 541
Sub....................................................... 36, 271
SubItems................................................... 546
Substring................................................... 496
Suspend.................................................... 751
System......................................................... 54
System.IO.................................................. 499
SystemBrushes........................................ 353
SystemColors........................................... 353
SystemPens.............................................. 353
T
Tab Order.................................................. 407
TabControl................................................ 376
TabIndex.................................................... 406
TableName............................................... 693
Tables........................................................ 690
TableStyles............................................... 693
TabPage.................................................... 376
TabStop..................................................... 407
Tan.............................................................. 156
Text............................................. 64, 484, 546
TextAlign............................................. 71, 542
TextBox........................................................ 32
TextChanged.............................................. 92
TextureBrush................................... 331, 350
Then........................................................... 198
Thread........................................................ 750
ThreadPriority........................................... 751
ThreeState................................................. 474
TickFrequency.......................................... 478
Ticks........................................................... 365
TickStyle.................................................... 479
Timer.......................................................... 367
TimeSerial................................................. 367
TimeSpan.................................................. 363
TimeString................................................. 367
TimeValue................................................. 367
To....................................................... 217, 240
ToCharArray............................................. 496
Today......................................................... 365
ToggleButton............................................ 541
ToLocalTime............................................. 365
ToLongDateString................................... 365
ToLongTimeString................................... 365
ToLower..................................................... 412
Toolbar....................................................... 539
Toolbars..................................................... 124
Toolbox............................................... 32, 110
Tools........................................................... 118
ToolTip....................................................... 488
Top......................................................... 74, 75
TOP............................................................. 698
ToShortTimeString.................................. 365
ToString............................................ 365, 410
ToUniversalTime..................................... 365
ToUpper..................................................... 412
TrackBar.................................................... 478
TranslateTransform................................. 359
TransparentColor..................................... 539
TreeNode.................................................. 552
TreeView.......................................... 544, 550
Trim............................................................ 493
True............................................................ 214
Try............................................................... 522
TypeName................................................ 458
U
Ucase......................................................... 493
Unchecked................................................ 474
Uncomment Selection............................ 117
Undo........................................................... 116
Unicode-символы................................... 494
Until............................................................ 234
Update.............................................. 684, 687
UPDATE.................................................... 699
Uppercase................................................. 117
User Control Designer............................ 704
UserControl............................................... 704
V
Val........................................................ 67, 493
Value.... 477, 480, 487, 488, 601, 658, 717
Value Types.............................................. 741
ValueChanged................................ 487, 488
VALUES..................................................... 699
VB.................................................................. 15
vbNewLine................................................ 508
VBScript..................................................... 657
View................................................... 117, 544
View Code................................................. 575
View Designer.......................................... 575
Visible........................................................... 71
Visual Basic.............................................. 759
Visual Basic .NET....................................... 15
Visual C# .NET........................................... 18
Visual C++................................................. 759
Visual C++ .NET......................................... 18
Visual J# .NET............................................ 18
Visual Studio .NET........................ 18, 26, 95
Volume......................................................... 87
VS.................................................................. 26
VScrollBar................................................. 477
W
Watch......................................................... 252
WebApplication........................................ 662
Web-приложение................................... 662
Web-сервер.............................................. 651
Web-страница......................................... 654
Web-форма.............................................. 663
WeekdayName........................................ 367
WHERE...................................................... 683
While.......................................................... 234
While …End While................................... 239
Width.................................... 70, 75, 329, 342
Window...................................................... 121
Windows API............................................. 717
Windows Form Designer generated code 179
WindowState............................................... 73
With............................................................. 463
WithEvents....................................... 636, 640
Word Wrap.......................................... 94, 117
WordWrap.................................................... 72
Workbooks................................................ 717
Worksheet................................................. 717
WrapMode................................................. 351
Write................................ 502, 512, 515, 570
WriteLine................................. 500, 512, 570
WriteOnly................................................... 602
X
X........................................................... 74, 409
XML............................................................ 688
Y
Y............................................................ 74, 409
Year............................................................ 364
Z
ZoomFactor............................................... 536
А
абсолютная величина.......................... 155
абстрактные классы.............................. 610
адаптер..................................................... 685
адрес................................................ 304, 782
активный объект....................................... 62
алгоритм................................................... 756
алфавитно-цифровая клавиша......... 412
анимация.................................................. 395
арифметическое выражение.............. 138
Ассемблер................................................ 759
Б
база данных................................... 461, 666
байт.......................................... 763, 772, 773
библиотека динамической компоновки 705
библиотека классов............................... 711
библиотека классов .NET Framework 18, 714
бит..................................................... 513, 773
блок............................................................ 299
браузер..................................................... 652
булевский тип.......................................... 214
буфер обмена......................................... 788
Бэйсик.......................................................... 19
В
ввод............................................................ 498
векторная графика................................ 338
верхняя граница индекса.................... 434
вершина.................................................... 552
ветвление........................................ 197, 198
ветка.......................................................... 552
видео............................................................ 83
видеоадаптер......................................... 767
видеокарта............................................... 767
визуальное программирование......... 180
винчестер................................................. 761
вкладка...................................................... 376
вложенные циклы......................... 262, 265
возврат каретки...................................... 507
возврат управления.............................. 764
выбор................................................ 197, 198
выборка..................................................... 684
вывод......................................................... 498
выделение фрагмента......................... 787
выделенный канал................................ 771
выделенный объект................................ 62
вызов программы................................... 764
вызов процедуры................................... 272
выражение...................................... 138, 305
выход из VS................................................ 38
выход из цикла........................................ 230
Г
гарнитура................................................. 333
главное меню............................................. 79
глобальная область видимости......... 586
градиентная заливка............................ 332
грамматика............................................... 728
графика................................... 176, 324, 464
графический путь................................... 467
графический редактор VB................... 360
графическое поле.................................. 182
Д
данные............................................. 157, 763
движение.................................................. 229
двумерные массивы.............................. 437
действия арифметики.......................... 154
делегаты................................................... 643
деление.................................................... 154
Дельфи...................................................... 759
дерево....................................................... 779
десятичные дроби................................. 155
Джава......................................................... 759
диалоговое окно выбора цвета.......... 558
диалоговое окно настройки шрифта 531
диалоговые окна открытия и сохранения файла 526
дизайнер компонентов........................... 79
динамические компоненты объектов 599
дискета...................................................... 769
дисковод................................................... 769
дисплей..................................................... 766
дозапись................................................... 502
документ................................................... 777
дополнительная цифровая клавиатура 414
дорожка..................................................... 782
доступ к локальным дискам................. 659
дочерняя вершина................................ 552
дуга............................................................. 187
Е
если............................................................ 198
Ж
жесткий диск............................................ 761
З
заголовок процедуры............................ 271
загрузка............................................ 498, 764
закладка.................................................... 376
закладка alphabetic.................................. 63
закладка categorized................................ 63
закрытие проекта..................................... 38
заливка...................................................... 331
запись...................................... 461, 498, 666
запрос.............................................. 667, 673
запятая.............................................. 68, 155
затенение................................................ 587
зацикливание.......................................... 226
звук............................................................... 83
звуковая карта........................................ 768
звуковой адаптер................................... 768
зерно......................................................... 767
знак сравнения....................................... 201
значение................................................... 137
И
И.................................................................. 211
идеология .NET......................................... 18
ИЛИ............................................................ 212
имя.............................................. 64, 151, 777
иначе......................................................... 198
индекс...................................... 432, 458, 496
индексированные переменные......... 432
индикатор процесса.............................. 479
индукция................................................... 448
инициализация....................................... 294
инициализация двумерного массива 439
инициализация массива...................... 434
инкапсуляция.......................................... 591
инсталляционный пакет...................... 125
инсталляция..................................... 95, 125
инсталляция Visual Studio .NET........... 95
инструкция............................................... 760
Интернет.................................................. 651
интерфейсы............................................ 608
исключение.............................................. 521
Истина....................................................... 214
исходные данные.................................. 763
итерация.................................................. 228
К
календари................................................ 487
каталог...................................................... 777
кисть........................................................... 187
клавиатура............................................... 411
клавиши перемещения курсора........ 785
класс........................................ 181, 577, 733
ключ............................................................ 458
ключевое поле........................................ 670
ключевые слова...................................... 152
книга........................................................... 716
кнопка.......................................................... 32
кнопка возврата...................................... 788
кнопка максимизации.............................. 73
кнопка минимизации............................... 73
кнопка отмены........................................ 788
код................................................................. 35
кодовая страницы 1251....................... 505
коллекция................................................. 456
команда..................................................... 760
комментарии.............................................. 93
компакт-диски.......................................... 770
компиляция..................................... 705, 760
компонент................................................... 55
компоненты объекта............................. 123
компьютер................................................ 756
консольное приложение...................... 569
константы................................................. 310
конструктор..................................... 328, 602
конструктор запросов........................... 680
конструктор таблицы............................ 670
конструктор форм.................................... 33
конструктор элементов управления пользователя 704
контейнер................................................. 459
контекстное меню......................... 489, 775
копирование................................... 781, 788
копирование файла.............................. 518
копирование файлов и папок............... 43
корень........................................................ 552
корень квадратный................................ 155
кривая Безье........................................... 466
круг............................................................. 184
Л
лазерные диски...................................... 770
Лисп........................................................... 759
лист............................................................ 716
логарифм.................................................. 155
логические выражения......................... 214
логические операции............................ 211
логические переменные...................... 214
логический диск...................................... 782
Лого............................................................ 759
Ложь........................................................... 214
локальные переменные.............. 296, 586
локальный диск....................................... 659
ломаная.................................................... 464
М
максимум.................................................. 268
маркер......................................................... 32
маска.......................................................... 529
массив.............................................. 432, 433
массивы как объекты............................ 445
массивы как параметры....................... 446
массивы многомерные......................... 441
массивы одномерные........................... 433
массивы структур................................... 461
массивы элементов управления....... 446
мастер....................................................... 133
мастер установки................................... 125
математика.............................................. 153
математические функции.................... 155
машинный язык...................................... 757
медиаплеер............................................... 83
меню............................................................. 79
меню «Пуск»............................................ 774
метка................................................... 65, 226
метка с гиперссылкой........................... 490
метод пузырька...................................... 453
методы.................... 48, 194, 315, 316, 319
микрофон................................................. 766
минимум.................................................... 268
многозадачность.................................... 765
многоугольник......................................... 465
множественный выбор......................... 486
моделирование...................................... 394
модель....................................................... 394
модем........................................................ 771
модуль............................ 155, 567, 569, 733
модульные переменные............. 296, 586
монитор.................................................... 766
мусор......................................................... 740
мышь.......................................................... 407
Н
наследник................................................. 605
наследование......................................... 605
наследование элементов управления 643
настройка среды.................................... 118
насыщенность........................................ 559
научный формат.................................... 164
НЕ............................................................... 213
О
области видимости....................... 293, 585
обработчик события............................. 635
обращение к процедуре...................... 272
обращение к функции.................. 306, 307
объект................................................. 25, 591
объектное программирование........... 591
объектный тип........................................ 178
объекты.................................................... 176
объявление.................................... 140, 167
объявление процедуры....................... 272
объявление функции............................ 307
окно............................................................ 775
окно Output................................................. 56
окно дизайнера компонентов............. 708
окно кода..................................................... 35
окно свойств............................................... 62
округление............................................... 155
оперативная память.. 761, 762, 763, 768
оператор............................................ 24, 760
оператор безусловного перехода.... 226
оператор варианта............................... 215
оператор присваивания.... 137, 139, 146
оператор цикла...................................... 232
операционная система........................ 764
ОС............................................................... 764
остаток от деления............................... 154
открытие проекта..................................... 38
открытие файла..................................... 499
отладка........................................ 25, 95, 249
отрезок...................................................... 184
оттенок...................................................... 559
ошибки....................................... 43, 257, 521
ошибки выполнения................................ 44
ошибки компиляции................................. 46
П
память.............................................. 761, 763
панель....................................................... 375
панель задач........................................... 774
панель инструментов............ 33, 123, 539
папка.......................................................... 777
параметры............................. 185, 276, 319
пароль....................................................... 221
Паскаль....................................................... 19
перевод строки....................................... 507
перегрузка................................................ 612
передача параметров по значению. 304
передача параметров по ссылке...... 304
передача управления........................... 764
переименовывание........................ 43, 781
переключатель....................................... 476
переменная цикла................................. 240
переменные величины 137, 149, 245, 283
переменные уровня модуля............... 296
перемещение файлов и папок.... 43, 781
перемещение фрагментов.................. 788
перенос оператора................................. 93
перенос файла....................................... 518
переопределение.................................. 609
перечисления......................................... 371
перо........................................................... 183
персональный компьютер................... 760
пиксель.................................... 757, 766, 772
плавающие окна..................................... 108
платформа .NET....................................... 18
побочный эффект.................................. 309
подсказка.................................................. 488
поле.................................................. 594, 666
ползунок................................................... 478
полиморфизм.......................................... 608
полосы прокрутки......................... 477, 779
пользователь.......................................... 756
потоки............................................... 747, 749
пошаговый режим.................................. 149
преобразование типов................ 161, 501
привязанные окна................................. 108
привязка................................................... 686
приложение............................................... 25
приложение Windows.............................. 25
принтер..................................................... 768
принтер лазерный................................. 768
принтер матричный.............................. 768
принтер струйный................................. 768
принцип инкапсуляции......................... 593
пробел....................................................... 784
провайдер................................................ 651
проводник................................................. 778
программа................................ 24, 157, 756
программист............................................ 756
продукт........................................................ 25
проект.......................................................... 25
проект - создание..................................... 39
прозрачность.......................................... 354
прозрачный цвет...................................... 77
Пролог....................................................... 759
пространство имен......................... 53, 728
простые типы данных.......................... 461
процедуры......................................... 24, 270
процедуры обработки событий......... 272
процедуры пользователя.................... 271
процедуры с параметрами................. 275
процессор................................................ 761
прямоугольник...................... 184, 325, 464
путь............................................................ 782
Р
рабочий лист........................................... 717
рабочий стол........................................... 774
радиокнопка............................................ 476
разветвляющиеся программы........... 197
размер....................................................... 325
размер картинок..................................... 341
размер шрифта........................................ 66
разрешение.................................... 341, 767
разрешение картинок........................... 341
рамка......................................................... 374
растровая графика................................ 338
расширение............................................. 778
регистр верхний..................................... 784
регистр нижний....................................... 784
регистр русский...................................... 784
Редактор вершин дерева.................... 551
Редактор коллекции картинок............ 538
Редактор коллекции кнопок................ 540
Редактор коллекции подэлементов списка 546
Редактор коллекции столбцов........... 544
Редактор коллекции элементов списка 545
Редактор строковых коллекций......... 480
режим вставки......................................... 785
режим замещения................................. 785
режим прерывания................................ 148
режим проектирования.......................... 33
режим работы........................................... 34
результат................................................. 763
рекурсия................................................... 448
решение................................................... 575
родитель................................................... 605
родительская вершина........................ 552
С
сборка............................................... 112, 711
свойства............................................. 69, 600
свойства для чтения-записи............... 600
свойства текстового поля...................... 72
свойства только для записи................ 601
свойства только для чтения................ 600
свойства формы....................................... 73
свойство...................................................... 34
свойство объекта................................... 592
связанные таблицы.............................. 667
сектор........................................................ 187
сетевая карта.......................................... 770
сетка............................................................. 34
сеть глобальная..................................... 770
сеть компьютерная................................ 770
сеть локальная....................................... 770
Си.................................................................. 19
символы........................................... 494, 771
символы типа.......................................... 161
символы формата.................................. 165
синтаксис......................................... 200, 735
синтаксическая схема........................... 200
система координат................... 74, 75, 358
система управления базами данных 461, 666
системные цвета.................................... 351
системный блок...................................... 760
сканер........................................................ 766
скобки........................................................ 154
случайное число..................................... 156
случайные величины............................ 203
смысловые ошибки.................................. 43
собственный класс................................ 577
события.............................. 24, 88, 635, 641
соединение.............................................. 677
создание папок......................................... 42
создание файлов............................ 42, 518
соответствие типов............................... 315
сортировка............................................... 452
составные типы данных...................... 461
сохранение.............................................. 505
сохранение проекта................................ 37
спецификаторы формата.................... 165
список........................................................ 480
список раскрывающийся...................... 482
список с флажками................................ 483
сплайн....................................................... 466
справочная система.............................. 721
среда визуальной разработки программ. 25
ссылка.............................................. 304, 739
ссылочные типы............................ 737, 741
стандартный модуль............................. 567
стартовая страница....................... 26, 115
стартовое меню...................................... 774
статические компоненты объектов.. 598
статические переменные.................... 301
степень - возведение............................ 154
страница................................................... 376
строка состояния................................... 565
строки............................................... 172, 492
строковые выражения.......................... 173
строковые литералы............................. 173
строковые переменные....................... 171
структуры........................................ 460, 733
ступенчатая запись программы........ 209
СУБД.......................................................... 666
сумматор.................................................. 261
сценарий.................................................. 657
счетчик............................................. 258, 488
счетчик циклов........................................ 228
съемные носители информации....... 769
Т
таймер....................................................... 369
тактовая частота.................................... 761
тег............................................................... 655
текстовое поле.......................................... 32
текстовый курсор................................... 783
текстовый редактор.............................. 782
текстурная кисть.................................... 350
текущая папка......................................... 520
тело процедуры...................... 36, 272, 307
тело цикла....................................... 226, 233
тик............................................................... 365
тип.............................................................. 143
тип выражения....................................... 163
типы данных............................................ 157
точка........................................... 68, 155, 324
точки прерывания......................... 147, 251
трансляция.............................................. 760
У
удаление файлов и папок.... 43, 518, 782
удаление фрагмента............................ 788
узел............................................................ 651
указатель.................................................. 739
умножение............................................... 154
умолчание.................................................. 64
управляющие клавиши......................... 412
условие..................................................... 201
условный оператор............................... 198
установка.................................................. 125
устройства ввода................................... 765
устройства вывода....................... 765, 766
устройства компьютера....................... 765
Ф
файл................................................. 498, 777
файлы нетипизированные................. 513
файлы с последовательным доступом 499
файлы с произвольным доступом 512, 514
файлы текстовые................................... 499
файлы типизированные...................... 510
фильтр...................................................... 529
флажок...................................................... 473
флэш-память........................................... 770
фокус......................................................... 405
форма............................... 31, 566, 743, 744
форма фигурная.................................... 469
форматирование.......................... 163, 368
фото............................................................. 76
функции........................................... 301, 306
функции пользователя......................... 306
функции преобразования типов....... 162
функциональные клавиши.................. 412
Ц
цвет объекта.............................................. 69
цвет текста................................................. 70
целая часть числа.................................. 155
целое число.................................... 143, 144
целочисленное деление...................... 154
цикл............................................................ 225
циклические программы....................... 225
Ч
числа Фибоначчи.................................... 432
числовые литералы.............................. 156
чтение....................................................... 498
Ш
шаблон...................................................... 529
шаг цикла.................................................. 241
шина.......................................................... 761
шрифты.............................................. 66, 333
штрихованая кисть................................ 332
Э
экземпляры класса................................ 176
экспоненциальный формат................ 164
элемент управления пользователя. 704
элементы управления.......... 32, 473, 525
эллипс....................................................... 184
Я
Ява............................................................. 759
язык программирования...................... 758
яркость...................................................... 559
ячейка............................................... 146, 147
Специально для http://all-ebooks.com
[†] В этой главе я кое- где буду применять термины, которые еще не объяснял и буду объяснять лишь позже. Вы можете спокойно не обращать на них внимания. Они нужны только тем, кто их уже понимает.
[‡] Модулем он стал только в VB 2003. В предыдущей версии VB он был классом, но для начинающего программиста это несущественно.
Часть III. Программирование на VB – второй уровень
Если вам кажется, что вы уже все можете, то вы правы и неправы. Правы потому, что вы можете написать сколь угодно большую и сложную программу, разбив ее на разумное число процедур. Неправы потому, что ваша способность манипулировать данными разных типов еще очень ограничена. А без нее вам не поддадутся многие «умные» задачи. Например, не познакомившись с так называемыми массивами, вы не сможете запрограммировать игру в крестики-нолики или морской бой. Не освоив работу со строками, вы не сможете решить задачу о мало-мальски серьезной шифровке и дешифровке секретных сообщений. Не умея работать с файлами, вы не сможете сохраняться в созданных вами играх. Вам нужно освоить объектное программирование, познакомиться с базами данных, с работой в Интернете и с другими вещами.
Если в предыдущей части главной целью было научить вас программировать сложные проекты для работы с простыми данными, то теперь главная цель – программировать простые проекты для работы со сложными данными. В этой части я предполагаю, что предыдущую часть вы прочитали и задания в основном выполнили.
Типичные приемы программирования
Каждому программисту известны такие понятия, как счетчик, сумматор, минимум, максимум, вложенные циклы и другие подобные, составляющие основу элементарной техники программирования. Без них не обходится ни одна реальная программа, и если мы хотим идти дальше, то нам без них тоже не обойтись. В этой главе я не буду вводить новых операторов VB, а вместо этого покажу, как программировать типичные задачи, в том числе и те, что используют упомянутые понятия.
Процедуры и функции
Понятие процедур и функций точно так же, как и понятие объектов – основополагающее в современном программировании. В этой главе вы разберетесь в процедурах и функциях и научитесь писать собственные процедуры и функции.
Работа с таймером, временем, датами
Мы с вами пока не умеем управлять временем. А это нам необходимо для работы с движением, анимацией, а также для решения других полезных и интересных задач, приведенных в этой главе. Собственно, все игры с одновременным движением персонажей делаются с помощью таймера. Венец главы – создание вашего первого не учебного, а реального проекта «Будильник-секундомер». Вы ведь видели часы в правой части панели задач Windows? Если на них поставить мышь, они покажут дату. Давайте сделаем что-нибудь получше, а именно – большие, красивые часы-будильник (можно даже во весь экран), а заодно и с секундомером. Для этого нам придется сначала познакомиться с новым типом данных – типом даты и времени суток. Также в этой главе вы изучите перечисления и освоите новые элементы управления.
Работа с мышью и клавиатурой
До сих пор в режиме работы проекта мы пользовались мышкой только для того, чтобы примитивно нажимать на кнопки, а клавиатурой – только для ввода текста в текстовые поля. Однако, возможности мыши и клавиатуры в VB гораздо шире. VB позволяет делать мышью и клавиатурой все те вещи, которые мы привыкли делать ими в любых графических и текстовых редакторах, играх и других приложениях Windows. В частности, мы можем с их помощью управлять поведением и движением объектов на форме, рисовать мышью линии, рамочки, изменять традиционное действие клавиш и так далее.
Прежде чем говорить о мыши и клавиатуре, поговорим о фокусе.
Массивы, рекурсия, сортировка
Массивы – одно из главных средств хранения в памяти компьютера больших объемов информации.
Для того, чтобы понять массивы, нужно обладать некоторой культурой математического мышления. Если этот материал покажется вам трудным, не поддавайтесь искушению пропустить его. Настоящего программирования без массивов не бывает, да и большая часть дальнейшего материала без массивов не будет понятна.
Рекурсия – это вызов процедуры из тела самой процедуры. Она лежит в основе рекурсивного стиля программирования, очень интересного и плодотворного стиля для определенного круга задач.
Сортировка – это процесс упорядочивания элементов массива. Очень популярная и распространенная задача программирования.
Начнем с массивов.
Разные звери в одном ковчеге
В предыдущей главе я рассматривал массивы – данные, состоящие из многих элементов. В этой главе я продолжу рассматривать данные, состоящие из нескольких элементов. Но если массивы – это одинаковые солдатики, стоящие стройными рядами в своих строгих батальонах, то те данные, что мы рассмотрим в этой главе, больше напоминают цыганский табор.
Разветвляющиеся программы
Выбор (ветвление) в VB осуществляют три оператора:
Их применению в программировании на VB и посвящена эта глава.
Циклические программы
Идею цикла я поясню на примере движения объектов по экрану. Возьмем игру в воздушный бой. Самолетик по экрану должен двигаться. Но в списках операторов большинства языков программирования, используемых профессиональными программистами для создания игр, нет команды движения. Здесь нужно задаться вопросом – а что такое движение? Рассмотрим иллюзию движения, возникающую на экране кинотеатра. Если вы держали в руках кинопленку фильма, изображающего, скажем, движение автомобиля, то должны были обратить внимание, что она состоит из множества неподвижных слайдов-кадров, на каждом следующем из которых автомобиль находится чуть-чуть в другом месте, чем на предыдущем. Показывая эти кадры один за другим с большой скоростью, мы создаем иллюзию движения автомобиля. Точно так же поступают с созданием иллюзии движения на экране компьютера. Запишем алгоритм движения самолетика по экрану слева направо:
1.Зададим в уме компьютера позицию самолетика в левой части экрана.
2.Нарисуем в задуманном месте самолетик.
3.Сотрем его.
4.Изменим в уме компьютера позицию самолетика на миллиметр правее.
5.Перейдем к команде 2.
Каждая из приведенных команд алгоритма легко программируется на большинстве языков. Любой компьютер, выполнив очередную команду, автоматически переходит к выполнению следующей. Так, выполнив команду 2, компьютер всегда перейдет к выполнению команды 3. Однако, если мы захотим, то можем заставить компьютер изменить этот порядок, что мы и сделали в команде 5. Из-за нее компьютер выполняет команды в таком порядке: 1-2-3-4-5-2-3-4-5-2-3-…. Напомню, что, выполнив команду 2, компьютер всегда перейдет к выполнению команды 3, независимо от того, какую команду он выполнял перед командой 2 – 1-ю или 5-ю. Таким образом, многократно выполняется последовательность команд 2-3-4-5. Такая многократно выполняемая последовательность называется циклом. Можно сказать, что цикл – это одно из средств заставить компьютер долго работать при помощи короткой программы.
В коротком промежутке времени после выполнения команды 2 самолетик будет появляться на экране и на команде 3 исчезать, но этого достаточно, чтобы человеческий глаз его заметил. Благодаря циклу самолетик будет мелькать каждый раз в новом месте, а поскольку смена «кадров» будет очень быстрой, нам будет казаться, что происходит плавное движение самолетика.
Не нужно думать, что циклы применяются лишь при движении. Они пронизывают все программирование. Ведь со 2-й по 5-ю команды мы можем писать команды любого назначения: и вычислительные, и по работе с текстом и так далее.
Отладка программы
Мы уже добрались до программ, требующих для своей отладки более разнообразных средств, чем простой пошаговый режим. С ними мы сейчас и познакомимся. Кстати, зачастую они и более удобны.
Сначала перечитайте, пожалуйста, материал про сообщения об ошибках в 1.3.8 и про пошаговый способ в 5.3.2.
Приступим. На Рис. 9.1 вы видите в окне кода в процессе отладки некую бессмысленную процедуру Button1_Click и несколько отладочных окон. Вот эта процедура:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Debug.WriteLine("Начало работы")
Dim a As Integer = 20
Dim b As Integer = 3
Do Until b > a
Debug.WriteLine(a & " " & b)
a = a - 2
b = b + 1
Loop
Debug.WriteLine("Конец работы")
End Sub
Легко видеть, что выполнившись с начала до конца, эта процедура печатает в окне Output такую информацию:
Начало работы
20 3
18 4
16 5
14 6
12 7
10 8
Конец работы
Вообразим, что нас такая печать не устроила, мы создавали процедуру для того, чтобы было напечатано
Начало работы
20 3
18 4
Конец работы
Вообразим, что мы не можем сразу понять, как изменить программу, чтобы получилось то, что нам нужно. Ясно, что в программе где-то что-то надо исправить. Чтобы разобраться в случившемся и найти ошибку, мы затеяли отладку.
Что же мы делаем? Прежде всего мы обычным способом помещаем на экран окно Output. Затем мы ставим точку прерывания на заголовке процедуры и запускаем проект. Затем нажимаем на кнопку Button1 и проект прерывается на заголовке процедуры. Ну вот, теперь мы находимся в режиме прерывания. Теперь можно отлаживать.
Многие окна, используемые в отладке, доступны или работают лишь в режиме прерывания. Поэтому, прежде, чем помещать их на экран, я и запустил, а затем прервал проект, чтобы в этом самом режиме прерывания оказаться.

Рис. 9.1
Окно Locals. Идем дальше. При помощи Debug ® Windows ® Locals мы вызываем на экран окно Locals (см. рисунок). Назначение окна Locals – показывать в режиме прерывания значения локальных переменных и других объектов выполняемой процедуры. (Локальные переменные – это переменные, объявленные внутри процедуры[†]). Столбец Name в окне Locals означает имя переменной или объекта. Столбец Value показывает нам их значение. К тому же в столбце Type вы можете видеть информацию о типе переменных и объектов.
После того, как проект прервался на заголовке процедуры, мы запускаем клавишей F11 пошаговый режим выполнения проекта (если VS настроена на профиль Visual Basic 6, то это будет клавиша F8). После нескольких нажатий на F11 окна Locals и Output принимают тот вид, что вы видите на рисунке. Правда, мы и без окна Locals в любой момент можем узнать значения a и b, просто поместив на них мышиный курсор. Однако применение окна Locals избавляет нас и от этих мизерных трудов.
Щелкните по плюсику слева от Me. Форма расскажет вам о текущих значениях своих свойств. Такие же плюсики вы видите у других объектов. Если в вашей процедуре имеются сложные переменные, например, массивы или структуры, щелкнув по плюсику рядом с ними, вы увидите значения всех их составных частей.
Если вы перейдете к выполнению другой процедуры, то в окне Locals будут видны уже именно ее локальные переменные.
Точки прерывания (Breakpoints). Когда программа большая или циклов много, жать на F11 приходится слишком часто. Но ведь и нужды-то в этом нет. Обычно вам интересно останавливаться не на каждой строке программы, а только на некоторых подозрительных, остальные можно выполнять и без остановки. VS позволяет вам задать такие строки – точки прерывания
(Breakpoints). Щелкните по вертикальной серой полосе в левой части окна кода против строки, на которой хотите прерывать выполнение программы. На полосе появится черная точка и вся строка будет выделена черным (все это описано в 5.3.2).
Щелкните также против всех нужных вам строк. Все они станут черными. Запустите проект обычным образом (кнопка Start или клавиша F5). Дойдя до первой же точки прерывания, проект перейдет в режим прерывания. Продолжайте его работу клавишей F5. Проект будет останавливаться только на точках прерывания. Убираются точки прерывания так же, как и ставятся – щелчком мыши.
«Беги до курсора» – Run To Cursor. Это еще один удобный способ остановки в нужной вам строке. Щелкните правой клавишей мыши по нужной строке и в возникшем контекстном меню выберите команду Run To Cursor. При этом происходит вот что. Щелчок правой клавишей мыши ставит текстовый курсор в нужную строку, а команда Run To Cursor запускает выполнение. Программа будет выполняться до тех пор, пока не наткнется на строку с курсором. А теперь щелкните правой клавишей мыши в другой строке и в возникшем контекстном меню снова выберите команду Run To Cursor. Программа продолжит работу с того места, где остановилась, и будет выполняться до тех пор, пока не наткнется на строку с курсором. И так далее.
Переключаемся между способами прерывания. В процессе выполнения программы вы можете достаточно свободно переключаться между разными способами прерывания. Ставьте и убирайте точки прерывания, нажимайте то F11, то F5, бегите временами до курсора. VB будет вас слушаться. Это удобно.
Если вы работаете в пошаговом режиме в какой-то процедуре, которая вызывает много других процедур, вам не интересных, то работайте не клавишей F11, а клавишей F10. При этом все другие процедуры будут проскакиваться мгновенно.
Окна Watch. Вообразим, что окно Locals не заставило нас поумнеть. Нам все кажется, что цикл должен был бы прерваться уже при значении b=5, а почему не прервался – непонятно. Чтобы разобраться, что к чему, нам хотелось бы в любой момент времени с удобством присматривать, правда или неправда, что b=5. Также нам хотелось бы в любой момент знать, правда или неправда, что b>a. То есть нам хотелось бы, чтобы вычислялись булевские выражения b=5 и b>a и результаты в виде True или False были видны в окне. Окно Locals такой возможности не дает. Более широкие возможности – у окна Watch. Вызовем его так: Debug ® Windows ® Watch ® Watch1 (можно было вызвать и другие три окна Watch). Типичный вид этого окна при отладке другой программы вы можете видеть на Рис. 9.2.

Рис. 9.2
Его вид не отличается от окна Locals. Отличие – в возможностях. В столбец Name мы можем вводить имена каких угодно (а не только локальных) переменных и объектов. А также мы можем вводить туда какие угодно выражения. В столбце Value мы видим значения этих переменных и выражений. Если вы щелкнете по плюсику слева от Button1, то кнопка расскажет вам о текущих значениях своих свойств. Если в вашем проекте имеются сложные переменные, например, массивы или структуры, введите их имена в столбец Name и, щелкнув по плюсику рядом с ними, вы увидите значения всех их составных частей.
В нашем случае я ввел в окно Watch1 оба наших выражения: b=5 и b>a. На Рис. 9.1 вы видите значения этих выражений в тот момент выполнения проекта, когда b равно 5 и a равно 16.
Настройка точек прерывания. Окно Watch помогает неплохо. Но мы устали жать на F11. Чтобы не уставать, мы ставим точку прерывания на одну из строк в цикле, скажем, на Loop, и жмем уже на F5, а не на F11. Теперь выполнение останавливается реже – один раз на итерацию (выполнение цикла). Стало легче. Но это когда итераций мало. А если их штук 500? 500 раз нажимать на F5 – тоже не подарок. В этом случае вам захочется найти более умные и быстрые средства отладки. И они есть. Рассмотрим их применительно к нашему случаю.
Хорошо бы наша программа исполнялась в своем обычном сверхбыстром режиме и проскакивала нашу точку прерывания без остановки, но каждый раз, однако, проверяя, выполняется ли некое условие, скажем, b=5. Оно по нашим подозрениям поначалу равно False, но затем в какой-то момент выполнения вроде бы станет равным True. Вот этот самый момент мы и хотим, чтобы VB поймал и тут же остановил программу.
Итак, нам нужен совсем другой способ остановки – хоть и на нужной строке, но не всегда, а только при выполнении заданного условия. Для этого точку прерывания, установленную на данной строке, нужно настроить.
Для настройки точки прерывания завершим выполнение проекта и перейдем в режим проектирования. Щелкнем правой клавишей мыши по точке прерывания и в возникшем контекстном меню выберем Breakpoint Properties. Возникнет окно настройки свойств точки прерывания (см. Рис. 9.3).

Рис. 9.3
В этом окне нас сейчас интересуют только кнопки Condition и Hit Count.
Condition. Нажав кнопку Condition, мы видим окно настройки условия для прерывания (Рис. 9.4).

Рис. 9.4
Вводим в поле наше условие (b=5). Затем – ОК. Теперь компьютер остановится на точке прерывания только тогда, когда будет выполнено условие. Этот момент остановки вы и видите на Рис. 9.1.
Под полем для условия вы видите переключатель на два положения. Нижнее положение приказывает прерваться не при выполнении условия, а при любом изменении значения выражения, записанного в поле.
Hit Count. Нажав кнопку Hit Count, мы видим окно настройки счетчика прерываний (Рис. 9.5).

Рис. 9.5
Его дело – останавливать компьютер на точке прерывания не всегда, когда позволяет условие, а еще реже. Как именно реже, определяет список из 4 способов. Важную роль играет число, которое вы вводите в поле справа. На рисунке введено число 3 и мы для определенности будем пояснять относительно него. Вот 4 способа:
|
break always |
Останавливай всегда, когда позволяет условие, а если условия нет – то вообще всегда. |
|
break when the hit count is equal to |
Останавливай на 3-й раз и больше никогда |
|
break when the hit count is a multiple of |
Останавливай через каждые два раза на 3-й |
|
break when the hit count is greater than or equal to |
Останавливай на 3-й раз и после этого всегда |
Окно Quick Watch. Кроме этих окон вы видите на Рис. 9.1 еще одно окно – Quick Watch. Оно понадобилось вот для чего. Иногда в процессе работы в режиме прерывания вам захочется узнать, чему равно в настоящий момент значение того или иного выражения из процедуры, например a - 2. Вы, конечно, можете включить это выражение в окно Watch, но быстрее поступить так: Выделите нужное выражение в окне кода, как это сделано на рисунке, затем щелкните по выделенному выражению правой клавишей мыши и в возникшем контекстном меню выберите команду Quick Watch. Окно появляется уже с нужной информацией. Если вы захотите ввести это выражение в окно Watch, щелкните кнопку Add Watch.
Окно Command Window-Immediate позволяет в режиме прерывания вычислять выражения, узнавать значения переменных и свойств объектов проекта, и, что самое пикантное, принудительно менять их.
Вызывается оно так: Debug ® Windows ® Immediate. Возникает окно с заголовком Command Window-Immediate. Не спутайте его с окном, имеющим заголовок Command Window, которое вызывается по-другому (View ® Other Windows ® Command Window) и не совсем вам подходит.
Пользуются этим окном так. Нужно в режиме прерывания вводить в него команды (вручную или скопировав из окна кода) и нажимать Enter. Тут же, в следующей строке вы будете видеть результат. Вот пример содержимого окна Command Window-Immediate (вводимые вами команды я выделил полужирным шрифтом):
?a
14
?a+10
24
?a<33
True
Debug.WriteLine(a & " " & b)
14 6
a=1000
?a
1000
? textbox1.text
"TextBox1"
textbox1.text="Привет!"
? textbox1.text
"Привет!"
textbox1.BackColor=color.LightSeaGreen
Пояснения: Знак вопроса означает запрос значения переменной, свойства или выражения. Так, команда
?a
означает запрос на значение переменной a и получает результат 14.
Команда
Debug.WriteLine(a & " " & b)
скопирована из окна кода и дала ожидаемый результат:
14 6
Команда
a=1000
принудительно меняет значение переменной a. То есть происходит прямое и неприкрытое вмешательство в святая-святых – работу программы. Но польза от этого для отладки есть. (Это еще ничего, предыдущие версии Visual Basic разрешали менять даже операторы программы в процессе выполнения. Представляете?! И тоже, кстати, не без пользы.)
Команда
? textbox1.text
узнает значение свойства объекта.
Команды
textbox1.text="Привет!"
и
textbox1.BackColor=color.LightSeaGreen
меняют свойства объекта.
Окно Command Window-Immediate не дает вам привычной свободы окна кода. Скорее оно похоже на командную строку с историей. Для навигации по окну вы можете пользоваться мышкой и на клавиатуре стрелками вверх-вниз. Нажатие клавиши Enter на любой промежуточной строке в этом окне отправляет эту строку вниз. Стрелки вверх-вниз не движут курсор, а «вспоминают» команды. Ничего, привыкнуть можно.
Окно Call Stack. Когда в программе содержится много взаимодействующих процедур, вам при отладке может быть совершенно необходимо разобраться в последовательности их выполнения. В этом случае вам поможет окно Call Stack, которое и покажет вам эту последовательность. Вызывается оно так: Debug ® Windows ® Call Stack.
Все описанные в этом разделе возможности отладки, а также некоторые другие, не описанные мной, приведены в меню Debug (отладка).
Программирование: Языки - Технологии - Разработка
- Программирование
- Технологии программирования
- Разработка программ
- Работа с данными
- Методы программирования
- IDE интерфейс
- Графический интерфейс
- Программирование интерфейсов
- Отладка программ
- Тестирование программ
- Программирование на Delphi
- Программирование в ActionScript
- Assembler
- Basic
- Pascal
- Perl
- VBA
- VRML
- XML
- Ada
- Lisp
- Python
- UML
- Форт
- Языки программирования




