ПОНЯТНО О 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).
Во дворце 40 залов. Компьютер
Рис. 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 возвращается к выполнению программы. Говорят, что управление возвращается к программе. Про значение функции говорят, что функция возвращает это значение в программу.
    Отличия функций от процедур. Вы видите, что объявление функции очень похоже на объявление процедуры. Но функция в отличие от процедуры обладает некоторыми свойствами переменной величины. Объявление функции отличается от объявления процедуры следующими элементами:
  • В заголовке функции мы пишем не  Sub (процедура), а Function (функция)

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

  • Внутри тела функции ей хотя бы раз должно быть присвоено какое-нибудь значение. Именно это значение функция и будет возвращать. Присвоение можно делать двумя способами:

  • Обычным оператором присваивания, в котором слева от знака равенства стоит имя функции, как если бы это была не функция, а обычная переменная (у нас этим занимается оператор  Площадь = Сторона1 * Сторона2).

  • При помощи специального оператора Return (у нас этим занимается оператор  Return  2 * Сторона1 + 2 * Сторона2).

  • В конечной строке мы пишем не  End Sub (конец процедуры), а End Function (конец функции)

  • Напомню еще раз, что обращение к функции отличается от обращения к процедуре. Если обращение к процедуре – самостоятельный оператор, то обращение к функции – это обычно составная часть выражения.
    Пример 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.
    IntelliSense
    Рис. 11.2
    Как только вы введете открывающую скобку, перед вами возникнет подсказка, приведенная на рисунке. Смысл ее не очень понятен, но в ее левом углу мы видим, что имеется семь вариантов написания этой функции, а мы сейчас смотрим на подсказку к 1-му варианту. Щелкая мышкой по стрелкам или с клавиатуры прокрутим список подсказок, пока не доберемся до чего-нибудь более подходящего (я имею в виду 3-й вариант – Рис. 11.3).
    IntelliSense
    Рис. 11.3
    В верхней части подсказки мы видим заголовок объявления функции.
    Abs (value As Integer) As Integer
    Это как раз то, что нужно, чтобы знать правильные типы функции и ее параметров. Функция имеет имя Abs.  Слово value – это имя первого параметра. Почему выбрано именно такое имя, не имеет значения. Если вы помните, вы можете для параметров выбирать любые имена. Вы видите, что в этом варианте параметр должен иметь тип Integer. Функция в этом случае, как видите, будет обязана иметь тот же самый тип Integer.

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


    Введите в качестве параметра какое- нибудь число, переменную или выражение и поставьте запятую. Подсказка снова изменяется, настроившись на третий параметр. И так далее.
    Подсказка в подсказке. Попробуем в качестве какого-нибудь параметра этой функции ввести выражение, содержащее другую функцию –  Math.Abs(-20) + 10. Как только мы откроем скобку после Abs, VB услужливо сменит подсказку для процедуры FillEllipse  на подсказку для функции Abs (см. Рис. 11.6).
    IntelliSense
    Рис. 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. После щелчка мыши по кнопке компьютер должен сделать следующее:


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

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

  • Мы предвидим, что размеры PictureBox придется изменять. Надо теперь заставить картинку принимать размеры PictureBox, установив по-другому то же самое свойство.

  • Поделить ширину формы на ее высоту, чтобы узнать ее «продолговатость». (Это надо бы пораньше, да ладно.)

  • Поделить ширину PictureBox на его высоту, чтобы узнать «продолговатость» картинки.

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

  • Если продолговатость картинки меньше, чем продолговатость формы, то в идеале при максимальном увеличении картинка упрется верхним и нижним краем в края формы, а слева и справа должно остаться одинаковое пространство. Здесь тоже нужно выполнить несколько аналогичных операторов.

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

    Растровая и векторная графика


    В компьютерной графике все картинки можно разделить на растровые
    и векторные. Соответственно говорят о двух типах графики – растровой
    и векторной, и о двух типах форматов графических файлов – растровом
    и векторном. К растровым форматам относятся форматы 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).
    Метод DrawImage и его варианты
    Рис. 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).
    Метод RotateFlip объекта Bitmap
    Рис. 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.
    Функция 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)
    Встроенный графический редактор VB
    Рис. 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.
    Рамка (GroupBox)
    Рис. 13.1
    Поместите GroupBox на форму и придайте ему довольно большие размеры. Получилась пустая рамка с заголовком GroupBox1. Затем возьмите из Toolbox и поместите внутрь этой рамки несколько разных элементов управления. А теперь передвиньте рамку по форме, ухватившись мышкой за ее границу. Вы видите, что все объекты внутри рамки передвинулись вместе с ней. Вот в этом и состоит удобство рамки: объекты, помещенные вами внутрь нее, она считает «своими» и таскает по форме вслед за собой. Посмотрите на значения свойств X и Y любого объекта внутри рамки. Теперь это координаты не относительно формы, а относительно рамки.
    В режиме проектирования или в коде вы можете изменять значения свойств рамки X и Y. Объекты, объединенные в рамке, будут при этом перемещаться вместе с ней. Вы можете изменять значения свойств рамки Visible, Enabled,. При этом точно так же будут меняться значения этих свойств у всех объектов, объединенных в рамке. Это полезно, например, тогда, когда у вас на форме слишком много объектов и все они не умещаются на ней. Выход есть, если не все они нужны одновременно. Просто разделите их между наложенными друг на друга рамками и в каждый момент времени делайте видимой только одну из них. Замечу, что для этого можно пользоваться и вкладками.
    Поменяв свойства рамки BackColor, ForeColor, Font, вы тем самым меняете эти свойства и у входящих в нее объектов (кроме тех, где эти свойства уже были изменены). Это же касается и формы, которая тоже является контейнером. Поэкспериментируйте.
    Вы можете мышкой переместить в рамку уже размещенный на форме объект. Оказавшись внутри границ рамки, он становится «своим» и разъезжает вместе с рамкой. А вот когда вы попытаетесь обнять новой или старой рамкой уже существующий объект, у вас ничего не получится. Оказавшись внутри границ рамки, он останется «чужим». Убедитесь в этом, сдвинув рамку в сторону. Все уехали, объект остался.
    Вставьте внутрь одной рамки другую, а в нее – несколько объектов. Подвигайте внешнюю рамку. Внутренняя рамка вместе со всеми своими объектами движется за ней. Все логично. Подвигайте теперь внутреннюю рамку внутри внешней. Вы видите, что внутренняя рамка ведет себя, как и следовало ожидать: для внешней рамки она «подчиненный», а для объектов внутри себя – «начальник».


    Панель (Panel)


    Панель во многом похожа на рамку. Интересующее нас отличие состоит в том, что если элементы управления не умещаются на панели, то ей можно придать полосы прокрутки, благодаря которым любой ее элемент управления может быть помещен в поле зрения (Рис. 13.2).
    Панель (Panel)
    Рис. 13.2
    Проделайте эксперимент. Поместите на форме большую панель. Разместите по всей ее поверхности несколько элементов управления. Затем уменьшите размеры панели. Многие элементы управления уйдут из поля зрения. Придайте панели полосы прокрутки, установив в True ее свойство AutoScroll. Покрутите полосы. Это очень экономит пространство на форме.


    Вкладка (TabControl)


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


    Рамка (GroupBox), панель (Panel) и вкладка (TabControl)


    Эти три элемента управления относятся к контейнерам – элементам управления, которые могут содержать на своей поверхности другие элементы управления. К контейнерам относится и форма.


    Постановка задачи


    Создать Будильник-секундомер следующего вида (см. Рис. 13.6).
    Постановка задачи
    Рис. 13.6
    Если вы ставите задачу для серьезного проекта, то прежде всего вам нужно с максимальной подробностью описать, что должен делать ваш проект с точки зрения пользователя (а не программиста!).  я начну с того же, но ввиду очевидности того, что изображено на рисунке, я поясню только то, чего не видно или может быть неправильно понято:
  • На верхнем циферблате должно отображаться текущее время суток (системное время Windows) в часах, минутах и секундах

  • Под ним – дата и день недели

  • Время будильника пользователь устанавливает, вручную редактируя (изменяя с клавиатуры) содержимое циферблата будильника

  • При нажатии на кнопку «Выключить будильник» надпись на кнопке меняется на  «Включить будильник», а надпись над циферблатом меняется на «Будильник отключен»

  • При срабатывании будильника раздается какая-нибудь продолжительная мелодия, которая замолкает при нажатии на кнопку «Выключить звонок»

  • Секундомер измеряет время с точностью до 0.01 сек. На картинке вы видите секундомер в момент паузы. Цифры на секундомере замерли. Если нажать на ПУСК, то отсчет времени продолжится с 1 минуты 27,97 сек, которые вы видите на картинке, а надпись на кнопке сменится на ПАУЗА. Если снова нажать на кнопку, цифры на секундомере снова замрут.

  • При нажатии на кнопку «Обнулить» секундомер останавливается и сбрасывается в ноль. На циферблате – 00:00.00.



  • Недоработки проекта


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

  • На циферблате вы видите «11 Сентябрь 2003 г.». А хотелось бы «11 сентября 2003 года». Это касается всех 12 месяцев.

  • Хорошо бы иметь меню со сменой мелодий, цветов, шрифтов и т.п.

  • Когда вы вручную устанавливаете время на циферблате будильника, то есть вводите текст в текстовое поле, вы должны быть очень внимательны, чтобы не нарушить формат строки: цифра-цифра-двоеточие-цифра-цифра-двоеточие-цифра-цифра. Например, нельзя вводить 8:30:00 вместо 08:30:00. И никаких пробелов! Иначе будильник не сработает. Происходит это потому, что программа сравнивает строку на циферблате часов со строкой на циферблате будильника. На циферблате же часов время имеет формат HH:mm:ss. В чем состоит проблема для программиста? Проблема в том, что пользователь не любит напрягаться и быть внимательным. Программист должен холить и лелеять пользователя, он должен сделать так, чтобы будильник «съел» все, что пользователь введет в циферблат, и тем не менее сработал правильно. В разумных пределах, конечно. Или же будильник просто не должен воспринимать неправильные вводимые символы, тогда пользователь волей-неволей введет все правильно. И то, и другое вы сможете делать позже.

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



  • Таймер и моделирование


    Теперь о роли таймера в компьютерном конструировании реальных механизмов. Заглянем-ка еще раз в процедуру таймера часов:
        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 способов заставить компьютер многократно выполнять код:
  • Операторы Do …. Loop

  • Операторы For

  • Устаревший оператор Goto

  • Использование таймера

  • Рекурсия


  • Тип 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).
    Невидимый код в окне кода – Windows Form Designer generated code
    Рис. 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
        Private Sub InitializeComponent()
            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 параметр – это собственно строка текста, которую вы хотите написать.

  • 2 параметр – это шрифт, который мы выбираем для написания строки. Пока я еще не пояснил вам, как в коде свободно и независимо управлять шрифтом, поэтому мы будем пользоваться шрифтами, установленными в режиме проектирования. Так, мы только что в режиме проектирования установили свойства Button5.Font и TextBox1.Font. Свойство Font имеется и у формы (Me.Font), и у других элементов управления. Мы можем в коде свободно пользоваться этими шрифтами, как готовыми перьями или кистями. В процедуре вы видите, что для написания строк на форме и в графическом поле я использовал шрифт кнопки Button5, для написания строки в текстовом поле я использовал шрифт формы, а для написания строки на кнопке – шрифт текстового поля.

  • 3 параметр – кисть. Да, буквы мы пишем кистью, а не пером. Цвет кисти выбираем по вкусу.

  • 4 и 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. Вот и весь ответ.

    Что делать? Способов несколько.
  • Можно объявить переменные  x1, y1, x2, y2  не как Integer, а как Single. Это поможет в случае с делением, так как деление Single на Integer дает Single. А в случае со сложением не поможет, так как сложение Single и Double дает Double.

  • Можно изменять надлежащим образом тип литералов, ставя в их конец символы типа. Так,  x1 + 0.8F   вполне удовлетворит VB.

  • Можно применять функции преобразования типов. Положение спасет, например:

  •         Граф.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 Это означает, что если s         a = 6
            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. Компьютер должен:
  • Если a<0, сказать «Число отрицательно».

  • Если a=0, сказать «Вы ввели нуль».

  • Если a>100, сказать «Число большое».

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

  • В любом случае после всего этого компьютер должен сказать «До свидания».
    Вот программа:
        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=1   Else k=0
    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 пикселей от пушки.
    Логический тип данных Boolean
    Рис. 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 стоит не переменная, а выражение, поэтому с перечисленными в вариантах значениями будет сравниваться число 10, полученное как 3*3+1.

  • В качестве значений вариантов тоже могут стоять выражения, как, например, у нас –  4*a-3.

  • Значений вариантов довольно много, все они перечислены через запятые.



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

  • Конструкция  9 To 12. Она обозначает то же, что и  9 <= a*a+1 <= 12, и служит в нашем случае для сокращения записи.

  • Конструкция Is < 0. Она обозначает то же, что и  a*a+1 < 0.  Слово Is, таким образом, служит заменителем выражения a*a+1 и используется для сокращения. Итак, Select Case не только сравнивает значения на равенство, но и проверяет неравенства.

  • Здесь отсутствует вариант Case Else. Это значит, что если бы в нашей программе оператор Select Case не выбрал ни один из своих вариантов, то, не найдя также Case Else, он завершил бы свою работу, так ничего и не сделав.

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


    Разницы две:
  • Между While и Until. Здесь соображения удобства. Что вам удобнее: указывать компьютеру, когда цикл нужно продолжать (f <= 9) или когда его нужно заканчивать (f > 9)?

  • В том, куда поставить условие – после Do или после Loop. Здесь разница не только в удобстве. В первом случае можно придумать такое условие, когда тело цикла не выполнится ни разу. Например,

  •         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 осуществляют три оператора:
  • Однострочный If

  • Многострочный If

  • Оператор выбора Select Case

  • Их применению в программировании на 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 (отладка).

    

        Программирование: Языки - Технологии - Разработка