ПОНЯТНО О Visual Basic NET (том 3)

         

Изменение содержимого текстового файла


Если вы хотите как-то изменить содержимое текстового файла на диске, то вам придется делать это в 3 приема:

полностью загрузить информацию из файла в память

изменить информацию в памяти, как вам надо

то, что получилось, целиком записать обратно в файл.

Задача 7: Текстовый файл Оружие.txt состоит из пары десятков названий оружия, применяемого в игре:

Меч

Винтовка

Лазерный пистолет

Бластер

и так далее.

Игру модифицировали – бластер заменили скорчером. Требуется сделать эту замену и в файле.

Решение: Вот программа:

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        'Подготавливаем массив строк для хранения информации из  файла:

        Dim Оружие(50) As String                            'Размер берем с запасом

        'СЧИТЫВАЕМ ИНФОРМАЦИЮ ИЗ ФАЙЛА:

        Dim Чтение As New System.IO.StreamReader("D:\Dinamo\Оружие.txt")

        Dim i As Integer = 1                                      'Счетчик строк  файла

        'Считываем все строки  файла:

        Do While Чтение.Peek() <> -1

            Оружие(i) = Чтение.ReadLine



            i = i + 1

        Loop

        Чтение.Close()                                             'Закрываем файл

        'ОБРАБАТЫВАЕМ ИНФОРМАЦИЮ В ОПЕРАТИВНОЙ ПАМЯТИ:

        Dim Число_строк_в_файле As Integer = i - 1

        For i = 1 To Число_строк_в_файле

            If Оружие(i) = "Бластер" Then Оружие(i) = "Скорчер"

        Next

        'ЗАПОЛНЯЕМ ФАЙЛ ОБРАБОТАННОЙ ИНФОРМАЦИЕЙ:

        Dim Запись As New System.IO.StreamWriter("D:\Dinamo\Оружие.txt")

        For i = 1 To Число_строк_в_файле

            Запись.WriteLine(Оружие(i))

        Next

        Запись.Close()                                              'Закрываем файл

End Sub

Заполнение файла вручную: Текстовый файл вы можете заполнять вручную. Для этого выйдите из VS в Windows, создайте текстовый файл (выполнив в нужной папке Файл ® Создать ® Текстовый документ) и дайте ему нужное имя. Чтобы заглянуть в него, щелкните по нему дважды мышкой, он откроется программой Notepad (Блокнот). Теперь вы можете заполнять его текстом, вводя его с клавиатуры. Только имейте в виду, что если вы заполняете его русскими буквами (кириллицей), объект StreamReader может его и не прочесть. В этом случае для чтения из VB используйте конструктор этого объекта с двумя параметрами:


Dim Чтение As New IO.StreamReader("D:\Dinamo\Оружие.txt", System.Text.ASCIIEncoding.GetEncoding(1251))

Здесь 1251 – номер так называемой кодовой страницы, содержащей буквы кириллицы. Остальное – без пояснений.

Задание 11.          

В мэрии нашего городка хранится файл со списком всех его жителей. Данные в нем расположены в следующем порядке:

Кривоклыков Леонид Львович

21.10.1959  00:00:00

Дерпт Клотильда Генриховна

14.02.1936  00:00:00

Похожий Семен Семенович

30.12.1940  00:00:00

И так далее. После строки с фамилией, именем и отчеством горожанина идет строка с датой его рождения. Раз в неделю файл обновляется. В эту неделю из города уехали двое:

Янзаев Федор Карпович

Кропоткин Владлен Фотиевич

Еще один –

Страшный Гектор Васильевич

поменял фамилию на «Благодатный».

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

Задание 12.          

Те, кому понравилась эта задача, могут сделать реальную систему в помощь мэрии или, хотя бы, школьному библиотекарю. Поместите на форму список типа ListBox. Создайте код, который заполняет его из файла фамилиями (или, скажем, названиями книжек). При выборе элемента списка полные данные об этом элементе (дата рождения, пол или, скажем, число страниц, автор) появляются в нескольких текстовых полях. Имеется несколько кнопок: «Исправить», «Удалить», «Обновить файл» и т.п. Обязательным в этом задании я считаю только заполнение списка из файла. Остальное можете отложить на потом.


Изменяем в коде содержимое таблицы, создаем и стираем таблицы


SQL-оператор SELECT не меняет содержимое базы данных, а только делает выборку. В этом подразделе мы на примерах познакомимся с SQL-операторами, которые изменяют содержимое таблицы базы данных, а также создают таблицы и удаляют их. Это операторы INSERT, DELETE, UPDATE, CREATE TABLE, DROP TABLE. Для их испытания вполне можно использовать наш последний проект. В текстовое поле вы вводите SQL-оператор и нажимаете кнопку Выполнить запрос. В исходной базе данных происходят изменения. Однако, в нижней сетке вы не увидите ничего. Чтобы наблюдать изменения, нужно нажать кнопку  Из базы – на экран  и посмотреть в верхнюю сетку.

Добавляем записи в таблицу. Оператор INSERT. Следующий оператор приказывает вставить в таблицу роман Толстого «Воскресение» объемом в 700 страниц:

INSERT INTO   Книги (Avtor, Nazvanie, Kol_str)

VALUES   ('Толстой', 'Воскресение', 700)

Точный смысл:

ВСТАВИТЬ В

таблицу Книги новую запись. В этой записи полям Avtor, Nazvanie, Kol_str, придать соответственно

ЗНАЧЕНИЯ

«Толстой», «Воскресение», 700.

Поля, которым не придано значение, остаются пустыми (в сетке мы увидим значение (null) ). Обратите внимание, что строковые константы берутся не в двойные, а в одинарные кавычки.

Вместо констант можно писать выражения. Пример:

INSERT INTO Книги (Avtor, Nazvanie, Kol_str)

VALUES ('Толстой', 'Воскресение', 400+300)

Удаляем записи из таблицы. Оператор DELETE. Следующий оператор приказывает стереть из таблицы все тонкие книги Толстого:

DELETE FROM   Книги

WHERE   (Kol_str<100) And (Avtor='Толстой')

Точный смысл:

СТЕРЕТЬ ИЗ

таблицы Книги  все записи,

ГДЕ число страниц меньше 100 и автор – Толстой.

Если часть WHERE не писать, будут стерты все записи таблицы:

DELETE FROM Книги

Обратите внимание, что в верхней сетке при нажатии на кнопку Из базы – на экран  записи «не стираются». Создается ошибочное впечатление, что они не стираются и в самой базе данных. Оно создается потому, что указанные SQL-операторы выполняют изменения именно в базе данных, а не в DataSet. Адаптер, отправляя при нажатии на верхнюю кнопку из измененного файла в DataSet, скажем, 5 записей (а в DataSet их все еще 7), обновляет в DataSet только эти 5 записей, остальные не трогая. Чтобы избежать такого впечатления, нужно перед оператором заполнения DataSet


        OleDbDataAdapter1.Fill(DataSet11)

выполнить оператор, опустошающий DataSet:

        DataSet11.Книги.Clear()

Изменяем записи в таблице. Оператор UPDATE. Следующий оператор во всей таблице меняет Толстого на Льва Толстого:

UPDATE   Книги

SET   Avtor='Лев Толстой'

WHERE   Avtor='Толстой'

Точный смысл:

ИЗМЕНИТЬ

таблицу Книги, а именно:

УСТАНОВИТЬ

полю Avtor значение «Лев Толстой» везде,

ГДЕ значением поля Avtor является «Толстой»

Следующий оператор все тонкие книги  делает толстыми и антикварными:

UPDATE   Книги

SET   Kol_str= [Kol_str]+500,  Data=#1/1/1600#

WHERE   Kol_str<100

точнее, во всех записях, где число страниц меньше 100, оно увеличивается на 500 и устанавливается дата – 1 января 1600 года.

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

Создаем таблицу. Оператор CREATE TABLE. Следующий оператор создает в нашей базе данных вдобавок к таблице «Книги» новую таблицу с двумя строковыми полями, без ключевого поля:

CREATE TABLE Сотрудники (Фамилия STRING, Отдел INTEGER)

Точный смысл:

СОЗДАТЬ ТАБЛИЦУ

 с именем Сотрудники  и с такими полями: Фамилия типа STRING и Отдел типа INTEGER

Типы данных в базах. Следующий оператор иллюстрирует основные типы полей, встречающиеся в таблицах баз данных, совместимых с Access:

CREATE TABLE Книги2 (Avtor STRING, Nazvanie STRING,  Data DATETIME,  Kol_str INTEGER, Качество LOGICAL, Цена CURRENCY, Рейтинг DOUBLE)

Пояснения: Здесь я создаю таблицу Книги2 с 7 полями. Описания полей приведены в скобках и разделены запятыми. Первое поле – Avtor, второе – Nazvanie и так далее.

В базах данных типы STRING, INTEGER, SINGLE, DOUBLE, DATETIME означают примерно то же самое, что и одноименные типы VB. Вместо Boolean пишем LOGICAL. При этом «Истина» и «Ложь»  могут изображаться числами -1 и 0, или флажком  (установленным или неустановленным) Можно пользоваться числовым типом CURRENCY – «Деньги». При этом число 48,2 может изображаться как 48,20р. Можете попробовать и другие простые типы VB, но не все они поддерживаются и не все под привычными именами.



Создание ключевого поля. Следующий оператор иллюстрирует создание ключа:

CREATE TABLE Книги3 (Avtor STRING, Nazvanie STRING,  Код INTEGER CONSTRAINT Ключ PRIMARY KEY)

Пояснения: Здесь я создаю таблицу Книги3 с 3 полями. Последнее поле Код я сделал ключевым. Чтобы создать ключевое поле с именем Код, я использовал выражение

Код  INTEGER CONSTRAINT  Ключ  PRIMARY KEY

Здесь INTEGER – тип поля, это целые числа. Слово CONSTRAINT  означает, что на значения в этом поле накладываются какие-то ограничения, они не могут быть произвольными целыми числами. Слова PRIMARY KEY конкретизируют эти ограничения. Имеется в виду, что поле является ключом (так называемым первичным ключом), а значит, значения не могут повторяться. Если вы случайно попытаетесь ввести туда число, которое уже встречалось, VB или Access не даст вам это сделать. Слово Ключ – это произвольное имя ключа, в простейшем случае оно вам не понадобится.

Очень удобно, когда ключевое поле само собою заполняется числами 1, 2, 3 … Для этого вместо INTEGER нужно выбрать тип COUNTER:

Код  COUNTER CONSTRAINT  Ключ  PRIMARY KEY

В этом случае уже нельзя самостоятельно вводить числа в это поле.

Ключевым можно сделать любое поле, если вы уверены, что значения в нем не повторяются. Например:

CREATE TABLE Книги4 (Avtor STRING, Nazvanie STRING CONSTRAINT Ключ PRIMARY KEY)

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

Уничтожаем таблицу. Оператор DROP TABLE. Следующий оператор уничтожает в базе данных таблицу Сотрудники:

DROP TABLE Сотрудники


Язык SQL Работаем с панелью SQL Pane конструктора запросов


Переведу на русский язык содержание панели SQL Pane с Рис. 24.19:

Содержание панели SQL Pane:

SELECT     Avtor AS Автор, Nazvanie AS Название, Data, Kol_str AS [Число страниц]

FROM         Книги

WHERE     (Kol_str > 100) AND (Data < #1/1/1970#)

ORDER BY Data DESC, Avtor

Перевод:

ВЫБРАТЬ Avtor КАК Автор, Nazvanie КАК Название, Data, Kol_str КАК [Число страниц]

ИЗ               Книги

ГДЕ             (Kol_str > 100) И (Data < #1/1/1970#)

ОТСОРТИРОВАТЬ Data ПО УБЫВАНИЮ, Avtor

Пересказ:

ПОКАЗАТЬ ИЗ ТАБЛИЦЫ Книги ТАКИЕ ПОЛЯ: Avtor ПОД ПСЕВДОНИМОМ Автор, Nazvanie ПОД ПСЕВДОНИМОМ Название, Data, Kol_str ПОД ПСЕВДОНИМОМ [Число страниц]

ПРИЧЕМ ПОКАЗАТЬ ТОЛЬКО ТЕ ЗАПИСИ, ГДЕ   (Kol_str > 100) И (Data < #1/1/1970#)

ЗАПИСИ ПРИ ЭТОМ ДОЛЖНЫ БЫТЬ ОТСОРТИРОВАНЫ СНАЧАЛА ПО ПОЛЮ Data ПО УБЫВАНИЮ, А ЗАТЕМ ПО ПОЛЮ Avtor

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

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

Содержание панели SQL Pane есть оператор специального языка SQL. Это очень распространенный язык для профессиональной работы с базами данных.

Мы познакомились на примере с оператором SELECT. Он используется для выборки (выбора) из таблицы нужных нам записей. Его части WHERE и ORDER BY могут отсутствовать. Я думаю, что этого примера достаточно, чтобы вы могли делать простые выборки самостоятельно. С другими операторами языка SQL мы познакомимся в 24.5.7и 24.5.8.



Языки программирования


Полвека назад языков программирования не было. Был лишь машинный язык. Главный недостаток команд машинного языка, как вы уже знаете, состоит в том, что действия, вызываемые этими командами, очень мелки. Поэтому программа выполнения даже очень простого задания будет состоять из большого числа команд. Это все равно, что строить дом не из кирпичей, а из косточек домино – построить можно, но слишком долго и утомительно. С другой стороны орнамент из кирпичей на этом доме получится плохой, грубый, а из косточек домино – гораздо более богатый и тонкий. Об этом не забывают поклонники языка Ассемблер (см. ниже).

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

Нарисовать кружочек заданного размера в заданном месте экрана

Нарисовать прямоугольник заданного размера и формы в заданном месте экрана

Нарисовать отрезок прямой

Написать на экране заданное слово, заданный текст

Запомнить слово или текст, введенные с клавиатуры

Вычислить математическую формулу

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

Наряду с созданием крупных процедур разрабатывались правила удобной для человека и компактной записи последовательности команд в программе. Правила эти стали называть языком программирования. Но если язык, то какой? Попробуем вникнуть У людей есть русский, английский, китайский языки. Что такое любой из этих языков общения людей? Грубо говоря, это набор букв, слов, знаков препинания и правил, по которым все эти элементы нужно выстроить в цепочку, чтобы получить правильное предложение. Язык программирования – примерно то же самое. Важнейшей частью языка программирования как раз и является набор правил, по которым различные элементы (например, команды) нужно выстроить в цепочку, чтобы получить правильную программу. Поскольку правила можно придумать разные, постольку и языки программирования могут быть разными.


Процесс создания процедур и языков продолжается и поныне. Очередной сборник процедур и использующий их язык записывают на компакт-диск и каждый желающий может взять диск, установить его содержимое в компьютер и пользоваться им. Процедуры и язык на таком диске записаны не разобщенно, а в комплексе, как составные части одной большой программы. Называют такую комплексную программу по-разному, например, «Среда и компилятор языка программирования Delphi» (она включает в себя один язык –  Pascal). Или «Visual Studio .NET», которая, как вы знаете, включает в себя несколько языков программирования. Если мы установим эту комплексную программу в компьютер и запустим ее на выполнение, то она позволит нам, во-первых, писать согласно содержащимся в ней языкам собственные программы с использованием упомянутых процедур, а во-вторых, сделает этот процесс удобным, то есть будет обнаруживать многие ошибки в ваших программах, позволит быстро запускать их на выполнение, исправлять, переписывать на диск и т.д.

Вот некоторые наиболее популярные языки программирования:

Visual Basic

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

Visual C++

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

Pascal (Delphi)

Паскаль в среде Дельфи

Универсальный красивый язык, по возможностям располагается между Basic и Си++.

Java

Ява (Джа ва)

Мощный модный язык, применяемый пока в основном в Интернете

Assembler

Ассемблер

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

Logo

Лого 

Язык, рассчитанный на детей, позволяющий просто и занимательно рисовать картинки и программировать простейшие игры

LISP, Prolog

Лисп, Про лог

Языки для создания искусственного интеллекта, роботов

<


Языков программирования, как и человеческих языков, придумано много. Зачем? Причина – в разнообразии потребностей программистов, в разных уровнях их квалификации и во многом другом. Так, начинающим вряд ли стоит предлагать Ассемблер, а профессионалу не нужен Лого. Часто разные языки ориентированы на разные предметные области. Например, язык Пролог позволяет удобно описывать логические взаимосвязи в окружающем нас мире, Лого позволяет удобно рисовать фигуры и снабжен для этого соответствующим набором процедур, а вот решать сложные математические задачи с его помощью лучше и не пытаться.

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

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

                                      покажи  3 + 2

А вот как она записывается на Паскале:

                                      Write (3+2)

А вот как на Visual Basic .NET:

                                      Debug.WriteLine (3+2)


Языки программирования и компиляция


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



Элемент управления ColorDialog


Создайте проект. Поместите в него кнопку и элемент управления ColorDialog. Введите такой код:

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        ColorDialog1.ShowDialog()

        Me.BackColor = ColorDialog1.Color

End Sub

Запустите проект. Щелкните по кнопке. Первым выполняется оператор ColorDialog1.ShowDialog() и перед вами раскроется диалоговое окно выбора цвета. Щелкните в нем по кнопке Define Custom Colors. Окно приобретет такой вид, как на Рис. 20.18.

Рис. 20.18

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

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

Выбрать цвет можно и проще, щелкнув по одному из основных цветов (Basic Colors) в левой верхней части окна. Щелкнув по кнопке окна Add to Custom Colors, вы добавляете выбранный цвет в ряд цветов Custom Colors в левой нижней части окна. Это избавит вас от необходимости снова выбирать понравившийся цвет.

Цвет можно не выбирать, а задавать, вводя числа в поля Red, Green, Blue в правой нижней части окна. Смысл полей вам ясен из 12.7.2. Можно вместо этого пользоваться тремя полями, что полевей: Hue (оттенок), Sat (Saturation – насыщенность), Lum (Luminosity – яркость). Оттенок означает определенное сочетание цветов и соответствует горизонтальному положению маркера на радужном квадрате. Насыщенность соответствует вертикальному положению маркера на радужном квадрате. Обратите внимание, что в верхней части квадрата цвета выраженней, насыщенней, в нижней же они более блеклые, сероватые. Яркость соответствует высоте положения черной треугольной стрелки в правой части окна. Чем выше, тем цвет светлее, чем ниже – тем темнее.



Элемент управления RichTextBox


На русский язык словосочетание RichTextBox переводится как «Богатое текстовое поле». Имеется в виду, что это обычное текстовое поле, которое вы можете при старании обогатить многими возможностями солидного текстового редактора. Запрограммировав его как следует, вы получите нечто среднее между привычным нам бедным текстовым полем и редактором Microsoft Word. Вы сможете создавать в нем вполне приличные документы. Так, отдельные фрагменты текста в поле RichTextBox вы сможете форматировать по-разному, то есть придавать им, например, разный размер шрифта и цвет шрифта. Вы сможете придавать разные стили абзацам и т.д. Созданные документы вы можете сохранять в формате RTF, который воспринимается редактором Word. И открывать, естественно, тоже.



Это мы не будем проходить


Кстати, список ListBox допускает настройку на множественный выбор. Это значит, что щелкая по элементам при нажатых клавишах Ctrl или Shift, мы можем выделить сразу несколько элементов. Этот режим сильно напоминает установку флажков в CheckedListBox. Указанной настройкой заведует свойство SelectionMode. Для работы в этом режиме тоже есть специальные коллекции. Но на этом мы останавливаться не будем.

До сих пор мы работали только со списками строк. Но элементами списков могут быть объекты любой сложности, например, структуры или кнопки. Вы скажете: Как это так? А что мы тогда увидим в списке? – То, что пожелаем, вернее, ту составную часть структуры или свойство объекта, которое имеет или может преобразовываться в строковый вид. Но, щелкнув по элементу, мы выбираем не только это свойство, а весь объект целиком. И на этом мы тоже останавливаться не будем.

Более богатые возможности нам предоставляет список ListView, о котором мы поговорим в 20.7.



К чему все эти сложности?


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

Итак, сделаем все переменные равноправными. И пусть безо всяких сложностей все переменные будут видны во всем п?оек?е, ?руг?ми ?лов?ми,?по ?аше? те?мин?лог?и, ?уст? вс? он? бу?ут ?лоб?льн?ми.?Тог?а б?льш?е п?оек?ы, ?озд?вае?ые ?ома?дой про?рам?ист?в, ?уде? кр?йне?тяж?ло ?тла?ива?ь, ?ак ?ак ?рид?тся?сле?ить?за ?ем,?что?ы н?где?не ?стр?тил?сь ?дин?ков?х и?ен ?что?и очень неудобно, кстати). Кроме этого, при отладке любой процедуры придется следить за тем, как бы случайно не испортить значение той или иной глобальной переменной, от которой зависит работа других процедур. Во многом потеряют смысл процедуры с параметрами, так как вся их прелесть в том как раз и состоит, чтобы «развязать» создателя процедуры и проект, чтобы сделать из процедуры «вещь в себе», надежно защищенную от «помех» со стороны проекта.

Итак, сделать все переменные глобальными нельзя. Тогда, может быть, сделать их все локальными? Тоже не получится, так как это крайне затруднит обмен информацией между процедурами и модулями. А без этого обмена проект рассыплется на отдельные процедуры, как карточный домик. Вот и получается, что переменные нужны разные – ? гл?бал?ные?и л?кал?ные? и ?ику?а от эт?го не д?неш?ся.?

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

Чем л?кал?нее? те? лу?ше. Вст?ет вопр?с: ?аку? об?аст? видимо?ти ?ам выби?ать?для?каж?ой конкретной переменной?  Объявлять ли ее глобальной, модульной или локальной? Здесь совет один – любую переменную делайте как можно более локальной, пусть ее область видимости будет как можно меньше. Если ее значение нужно только в одной процедуре и больше нигде, делайте ее локальной. Если ее значение нужно в нескольких процедурах одного модуля, а в других модулях нет, то делайте ее модульной. И только если значение переменной нужно в нескольких модулях, делайте ее глобальной. Такой подход обеспечит вашему проекту максимальную надежность и удобство в отладке.


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

Задача:  Переменная равна 3. Нужно увеличить ее на 2 и результат напечатать.

Дадим переменной имя A. Вот простейшая программа для решения задачи:

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        Dim A As Integer = 3

        A = A + 2

        Debug.WriteLine(A)

End Sub

Однако простейшая программа нас не устраивает. Нам нужно показать всю прелесть параметров. Для этого мы будем действовать так же, как действовал Том Сойер в книге «Приключения Гекльберри Финна». Ему нужно было освободить негра Джима из ветхого сарайчика, где того держали взаперти. Вместо того, чтобы просто отпереть дверь имевшимся у него ключом, Том придумал для освобождения массу ненужных вещей вроде веревочной лестницы, отпиленной ножки кровати и т.п. И все для того, чтобы побег из заточения был обставлен «как положено».

Вот и мы сейчас усложним проект, чтобы все было «как положено». Прежде всего, посчитав, что наша процедура слишком длинная, разобьем ее на две процедуры П1 и П2, первая из которых увеличивает переменную на 2, а вторая распечатывает результат.

Dim A As Integer = 3

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        Увеличиваем_на_2()

        Печатаем()

End Sub

Private Sub Увеличиваем_на_2()

        A = A + 2

End Sub

Private Sub Печатаем()

        Debug.WriteLine(A)

End Sub

Программа работает. Но нам этого мало. Будем усложнять дальше. Добавим к нашим процедурам параметры:

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        Dim A As Integer = 3

        Увеличиваем_на_2(A)

        Печатаем(A)

End Sub

Private Sub Увеличиваем_на_2(ByRef A1)      'А1 - переменная, которую увеличиваем

        A1 = A1 + 2



End Sub

Private Sub Печатаем(ByVal A2)                      'А2 - переменная, которую распечатываем

        Debug.WriteLine(A2)

End Sub

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

Зачем делить проект на мелкие процедуры, я уже раньше рассказывал. А вот зачем понадобились параметры?

Если ваша процедура сложная и делает что-нибудь полезное, то вы вполне можете захотеть, чтобы она использовалась и в других проектах. Но в другом проекте переменные скорее всего имеют другие имена, например, вместо A там используется B. В этом случае вам придется переписывать текст процедуры (в нашем конкретном случае везде заменить A на B). А переписывать не хочется. В общем, процедура теряет свою универсальность. Чтобы и не переписывать, и универсальность не потерять, надо применить параметры.

Параметру вы придумываете то имя, которое вам заблагорассудится (A1, A2), а не то, что вы видите в программе (A). И обратите внимание, что нашим процедурам с параметрами абсолютно все равно, что переменная имеет имя A. Нигде внутри процедуры это имя не встречается, поэтому процедура уверенно делает свое дело, каково бы оно ни было. Да и программисту как-то понятней, чем занимается процедура, если он видит в ее заголовке список параметров с удобными именами, да еще и с подробными комментариями. Это лучше, чем глядеть в тексте процедуры на чужую переменную A и вспоминать, что это, черт возьми, за переменная и в чем ее смысл.

В общем, совет такой – с глобальными и модульными переменными работайте по возможности через параметры. Что значит «по возможности»? А то, что некоторые такие переменные буквально пронизывают все процедуры проекта и организовывать для них в каждой процедуре параметры – та овчинка, которая не стоит выделки. Обычно таких переменных бывает немного и их легко запомнить.


Как улучшить и развить проект


Если проект вам понравился, вы можете его улучшить и развить, например, так:

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

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

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

Добавить «хищные» шары (см. ниже).



Как улучшить наш графический редактор


Давайте сравним наш простейший графический редактор с настоящим графическим редактором средней руки, а именно с Paint, который входит в состав Windows. Откройте его (Рис. 20.20).

Рис. 20.20

Давайте посмотрим, что нам нужно добавить к нашему графическому редактору, чтобы он в основном мог делать то, что делает Paint (пусть и не так элегантно), и определим, умеем ли мы это запрограммировать и трудно ли это программировать:

Что мы видим в Paint?

Умеем?

Трудно?

Лист с редактируемой картинкой. Это может быть PictureBox.

Да

Легко

Меню (внешний вид)

Да

Легко

Панель инструментов слева (внешний вид)

Да

Легко

Панель выбора формы кисти

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

Да

Легко

Панель выбора формы кисти

(работа). Мы уже рисовали круглой кистью, не правда ли? Ведь линия состояла из кружочков. А можно рисовать и квадратиками, и отрезками. Но здесь вам встретятся геометрические проблемы получения плавной линии из таких «угловатых» элементов.

Да

Трудно

Палитра цветов внизу. При небольшом числе цветов ее можно в простейшем случае запрограммировать, как коллекцию разноцветных кнопок на панели (Panel)

Да

Нетрудно

Полосы прокрутки. Придется применять событие Paint

Да

Нетрудно

Теперь поговорим об инструментах на панели инструментов Paint:

Карандаш, кисть. Это мы уже делали.

Да

Легко

Ластик. Это просто белая кисть

Да

Легко

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

Ломаная строится по тому же принципу.

Да

Нетрудно

Прямоугольник с закругленными углами. Это 4 отрезка и 4 дуги.

Да

Долго

Плавная кривая.

Нет

Нелегко

Пипетка. Она берет пробу краски на листе, чтобы покрасить в эту краску кисть и карандаш. Для этого подойдет функция GetPixel.

Да

Легко

Интересно сделать пульверизатор

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

Да

Средне

Текст. Здесь вам поможет метод DrawString.

Да

Средне

Заливка. Здесь вам помогут хорошее знание математики и метод SetPixel.

Да

Трудно

Увеличение (лупа). Здесь придется менять разрешение объектов Bitmap.

Да

Трудно

Выделение, копирование, перемещение фрагментов.

Да

Нелегко

<
Теперь поговорим о пункте Image меню Paint. В него входят подпункты:

Flip/Rotate (отразить/повернуть). Это мы уже делали.

Да

Легко

Stretch/Skew (растянуть/сдвинуть).

Да-нет

Нетрудно

Негатив

Нет

Нетрудно

Очистить рисунок

Да

Легко

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

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


Календари (MonthCalendar и DateTimePicker)


Оба они созданы для ручного выбора дат. Они похожи друг на друга, поэтому остановлюсь только на DateTimePicker (см. Рис. 18.13).

Рис. 18.13

Вы можете щелкать по любой дате мышкой, она появится в текстовом поле и будет считаться значением DateTimePicker.Value. Стрелками влево и вправо вы меняете месяцы. Вы можете вручную писать любую дату в текстовом поле. DateTimePicker воспринимает любые даты с 1753 по 9998 годы. Небрежным кружочком обведено текущее число.

При смене даты возникает событие ValueChanged. Запишем процедуру:

Private Sub DateTimePicker1_ValueChanged(ByVal sender As System.Object, ByVal e As System.EventArgs)  _

Handles DateTimePicker1.ValueChanged

        If DateTimePicker1.Value.Date = #11/24/2003# Then MsgBox("День рождения")

End Sub

На Рис. 18.13 вы можете видеть DateTimePicker после запуска проекта с этой процедурой и щелчка мышкой по 24 ноября 2003 года. Я вынужден был написать Value.Date, а не просто Value, так как свойство Value имеет тип DateTime, а значит включает в себя не только дату, но и время суток. В числе же #11/24/2003# время не указано.

Вы можете менять дату и в коде:

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        DateTimePicker1.Value = #11/20/2005 4:20:00 PM#

End Sub

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

Задание 6.    

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



Компилируем проект справочной системы


Проект справочной системы готов. Чтобы он смог заработать, его нужно откомпилировать. File ® Compile ® Compile. В папке проекта справки появляется откомпилированный файл Справка.chm. Теперь вы можете запускать этот файл из Windows и справка будет работать сама по себе, вне приложений Windows.



Компиляция


Итак, для компьютера родным языком является только машинный. Однако он понимает и языки программирования. Как реально происходит это понимание?

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

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

В языках программирования приказы, которые отдают на данном языке, называют командами (язык Лого и др.), операторами (языки Бейсик, Паскаль и др.), инструкциями. Не нужно путать команды и операторы языков программирования с командами машинного языка, так как они, во-первых, написаны на другом языке, а во-вторых, гораздо «крупнее». Так, команда языка Лого   покажи 3 + 2   после компиляции превращается в цепочку из нескольких команд машинного языка, которые сначала приказывают компьютеру вычислить сумму, а потом показать ее на экране.

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



Консольное приложение


Создадим так называемое консольное приложение (Console Application).

Для этого создайте новый проект. До сих пор мы с вами создавали только приложения Windows, поэтому в окне создания нового проекта выбирали Windows Application (Приложение Windows). Теперь же мы впервые создаем не приложение Windows, а консольное приложение, поэтому выбираем Console Application (Рис. 21.4).

Рис. 21.4

Нажимаем ОК. Перед нами появляется окно модуля с уже готовой заготовкой процедуры Main.

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

Рис. 21.5

Введем в окно кода такой код:

Module Module1

    Sub Main()

        Console.Write("Здравствуйте, ")

        Console.WriteLine("как Вас зовут?")

        Dim Имя As String = Console.ReadLine()

        Console.Write("Добро пожаловать, ")

        Console.WriteLine(Имя)

        Console.ReadLine()

    End Sub

End Module

Пояснения: Для вывода в консольное окно используются методы Write и WriteLine класса Console, подобно тому, как для вывода в окно Output используются одноименные методы класса Debug.

Для ввода информации с клавиатуры в консольное окно я использовал метод ReadLine класса Console. Наткнувшись в процессе выполнения программы на этот метод, VB делает паузу и ждет, когда человек наберет на клавиатуре информацию и нажмет клавишу Enter. Вводимые буквы по мере ввода появляются в окне.  В третьей строке нашей процедуры введенная строка присваивается переменной Имя.

Данная процедура осуществляет простейший диалог человека с компьютером, который выглядит так, как на Рис. 21.5.

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

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



Конструкторы


Смысл конструктора. Вы знаете, что если у вас в окне кода формы присутствует процедура Form1_Load, то она выполняется автоматически при загрузке формы. Аналогичная процедура может присутствовать и в коде класса. Ее имя New – и называется она конструктором. Она выполняется автоматически при создании объекта.

О конструкторах мы с вами говорили в 6.1.2 и 12.2.1, когда при помощи слова New создавали объекты из готовых классов, предоставленных нам библиотекой классов .NET Framework. Нынче мы создаем при помощи слова New объекты уже из собственных классов. Обратите внимание, что до сих пор нигде в коде классов мы не писали процедуры New, тем не менее объекты прекрасно создавались. Для чего же тогда нужен конструктор New? Для того же, для чего и процедура Form1_Load (ведь форма загружалась и при отсутствии этой процедуры) – а именно, чтобы произвести некоторые действия, которые программист считает нужным произвести в момент создания объекта из класса. Обычно это присвоение начальных значений переменным, открытие нужных файлов и т.п.

Пример конструктора. Давайте используем конструктор для присвоения в каждом объекте нужного значения полю Номер_участка. Сейчас оно у нас присваивается в модуле формы, что противоречит принципу инкапсуляции.

Уберем из кода формы строку

        Участки(k).Номер_участка = k

Вместо этого вот как дополним наш класс:

    Public Номер_участка As Integer

    Private Shared

Число_созданных_объектов As Integer = 0

    Public Sub New()

        Число_созданных_объектов = Число_созданных_объектов + 1

        Номер_участка = Число_созданных_объектов

    End Sub

Здесь при создании очередного участка мы увеличиваем на 1 статическую переменную класса Число_созданных_объектов. Поэтому к моменту создания очередного участка эта переменная автоматически приобретает нужное значение, которое оператором

        Номер_участка = Число_созданных_объектов

передается полю Номер_участка .

Конструктор с параметрами. Конструктор, как и всякая процедура, может иметь параметры. Обычно параметры используются для задания начальных значений переменным, полям и свойствам класса. Давайте зададим через параметры имя владельца, длину, ширину участка и высоту забора. Придумаем параметрам такие имена: Влад, Дл, Шир и Выс.


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

Public Class Участок

    Public Владелец As String

    Private Длина As Integer

    Public Property Длина_участка() As Integer

        Get

            Return Длина

        End Get

        Set(ByVal Value As Integer)

            If Value < 500 Then

                Длина = Value

            Else

                MsgBox("Слишком длинный участок")

                Длина = 0

            End If

        End Set

    End Property

    Private Ширина As Integer

    Public Высота_забора As Integer

    Public Shared Расход_краски_на_кв_м As Integer

    Private Периметр As Integer

    Public Номер_участка As Integer

    Private Shared Число_созданных_объектов As Integer = 0

    Public Sub New (ByVal Влад As String, ByVal Дл As Integer, ByVal Шир As Integer, ByVal Выс As Integer)

        Владелец = Влад

        Длина_участка = Дл

        Ширина = Шир

        Высота_забора = Выс

        Число_созданных_объектов = Число_созданных_объектов + 1

        Номер_участка = Число_созданных_объектов

    End Sub

    Private Sub Вычисляем_периметр()

        Периметр = 2 * (Длина + Ширина)

    End Sub

    Private Function Площадь_забора() As Integer

        Вычисляем_периметр()

        Return Периметр * Высота_забора

    End Function

    Public Function Расход_краски() As Integer

        Return Расход_краски_на_кв_м * Площадь_забора()

    End Function

End Class

Для проверки работы конструктора нажмем на кнопку:

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        Участок.Расход_краски_на_кв_м = 3

        Участки(1) = New Участок("Лиходеев", 5, 4, 2)

        Debug.WriteLine(Участки(1).Расход_краски)

        Участки(2) = New Участок("Берлага", 800, 5, 3)

        Debug.WriteLine(Участки(2).Расход_краски)

End Sub



Для краткости я здесь отказался от текстовых полей.

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

Обратите внимание, что при создании участка Берлаги свойство Длина_участка выдало сообщение, что участок слишком длинный. То есть свойство работает.

Проблема с именами. Мы придумали параметрам не очень-то хорошие имена. А зачем было вообще придумывать? Нельзя ли было обойтись старыми и написать так? –

    Public Sub New(ByVal Владелец As String, ByVal Длина_участка As ……………………

        Владелец = Владелец

        Длина_участка = Длина_участка

        ………………………

Нельзя, потому что непонятно, чем отличается левая часть оператора присваивания от правой. А вот так писать можно:

    Public Sub New(ByVal Владелец As String, ByVal Длина_участка

As Integer,  _

ByVal Ширина

As Integer, ByVal Высота_забора As Integer)

        Me.Владелец = Владелец

        Me.Длина_участка = Длина_участка

        Me.Ширина = Ширина

        Me.Высота_забора = Высота_забора

        Число_созданных_объектов = Число_созданных_объектов + 1

        Номер_участка = Число_созданных_объектов

    End Sub

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

        Me.Владелец = Владелец

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

Аналогичные рассуждения можно провести и для других параметров.


Конструкторы родителя и наследников


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

Класс clsПрямоугольник:

Public Class clsПрямоугольник

    Public Длина As Integer

    Public Ширина As Integer

    Public Sub New()

        Debug.WriteLine("Сработал конструктор прямоугольника без параметров.")

    End Sub

    Public Sub New(ByVal Длина As Integer, ByVal Ширина As Integer)

        Me.Длина = Длина

        Me.Ширина = Ширина

        Debug.WriteLine("Сработал конструктор прямоугольника с параметрами.")

    End Sub

    Public Function Площадь() As Integer

        Return Длина * Ширина

    End Function

    Public Function Периметр() As Integer

        Return 2 * Длина + 2 * Ширина

    End Function

End Class

Класс clsПараллелепипед не изменился (см. 22.7).

Класс clsКоробка:

Public Class clsКоробка

    Inherits clsПрямоугольник

    Public Высота As Integer

    Public Sub New()

        Debug.WriteLine("Сработал конструктор коробки без параметров.")

    End Sub

    Public Sub New(ByVal Высота As Integer)

        MyBase.New(3, 2)

        Me.Высота = Высота

        Debug.WriteLine("Сработал конструктор коробки с параметром.")

    End Sub

    Public Function Площадь_поверхности() As Integer

        Return 2 * Площадь() + Периметр() * Высота

    End Function

End Class

Процедура для проверки работы классов с конструкторами:

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        Dim Прямоугольник1 As New clsПрямоугольник

        Прямоугольник1.Длина = 4

        Прямоугольник1.Ширина = 3

        Debug.WriteLine(Прямоугольник1.Площадь)


        Debug.WriteLine(Прямоугольник1.Периметр)

        Dim Прямоугольник2 As New clsПрямоугольник(10, 5)

        Debug.WriteLine(Прямоугольник2.Площадь)

        Debug.WriteLine(Прямоугольник2.Периметр)

        Dim Параллелепипед As New clsПараллелепипед

        Параллелепипед.Длина = 40

        Параллелепипед.Ширина = 30

        Параллелепипед.Высота = 2

        Debug.WriteLine(Параллелепипед.Площадь)

        Debug.WriteLine(Параллелепипед.Периметр)

        Debug.WriteLine(Параллелепипед.Объем)

        Dim Коробка1 As New clsКоробка

        Коробка1.Длина = 5

        Коробка1.Ширина = 4

        Коробка1.Высота = 10

        Debug.WriteLine(Коробка1.Площадь)

        Debug.WriteLine(Коробка1.Периметр)

        Debug.WriteLine(Коробка1.Площадь_поверхности)

        Dim Коробка2 As New clsКоробка(100)

        Debug.WriteLine(Коробка2.Площадь)

        Debug.WriteLine(Коробка2.Периметр)

        Debug.WriteLine(Коробка2.Площадь_поверхности)

    End Sub

Вот что напечатает эта процедура:

Сработал конструктор прямоугольника без параметров.

12

14

Сработал конструктор прямоугольника с параметрами.

50

30

Сработал конструктор прямоугольника без параметров.

1200

140

2400

Сработал конструктор прямоугольника без параметров.

Сработал конструктор коробки без параметров.

20

18

220

Сработал конструктор прямоугольника с параметрами.

Сработал конструктор коробки с параметром.              

6

10

1012

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

Объяснять я буду в общих чертах, не вдаваясь в подробности. Начну с создания объекта Коробка1.

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



        Dim Коробка1 As New clsКоробка

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

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

В подтверждение этих слов вы видите две напечатанные строки:

Сработал конструктор прямоугольника без параметров.

Сработал конструктор коробки без параметров.

Когда вызывается конструктор родителя, то он первым делом вызывает конструктор своего родителя, тот – своего, и так далее до самого отдаленного предка.

Теперь обратите внимание на рождение объекта Параллелепипед. В коде класса clsПараллелепипед никакого конструктора нет – и тем не менее конструктор родителя был вызван, в подтверждение чего была напечатана строка

Сработал конструктор прямоугольника без параметров.

Не забывайте об этом неявном вызове.

Вы можете, если захотите, из кода конструктора наследника явно вызвать один из конструкторов родителя. Мы так сделали при создании объекта Коробка2. Здесь нам это понадобилось для того, чтобы длина и ширина могли получить исходные значения. Для этого из конструктора с параметром класса clsКоробка оператором

        MyBase.New(3, 2)

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

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

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

Обращаться к конструктору можно только из конструкторов.

Конструкторы не наследуются.


Контекстное меню


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

Создается контекстное меню аналогично Главному меню (перечитайте 3.8), но с одним отличием: Так как Главное меню одно на все приложение, а контекстное меню – свое для каждого объекта, то нам придется кроме всего прочего привязывать каждое контекстное меню к своему объекту. Приступим.

Поместим на форму кнопку и сделаем два контекстных меню: одно для формы, другое – для кнопки.

В контекстное меню для формы включим два пункта:

Покрасить форму в синий цвет

Покрасить форму в красный цвет

В контекстное меню для кнопки включим два пункта:

Расширить кнопку

Сузить кнопку

При щелчке правой клавишей мыши по поверхности формы должно появляться одно меню, по кнопке – другое (см. Рис. 18.16).

Рис. 18.16

Создаем контекстные меню. Поместим на форму элемент управления ContextMenu. Он расположится под формой под именем ContextMenu1. Это будет наше контекстное меню для формы. Аналогично разместим ContextMenu2. Это будет контекстное меню для кнопки.

Щелкнем по элементу ContextMenu1. На форме появится область, которую мы заполним пунктами про синий и красный цвета, как это делали с Главным меню. Щелкнем по элементу ContextMenu2  и аналогично заполним область про расширение и сужение.

Привязываем контекстные меню. Меню готовы. Теперь нужно как-то сказать компьютеру, что ContextMenu1 относится к форме, а ContextMenu2 – к кнопке. Зайдем в окно свойств формы и установим свойству ContextMenu значение ContextMenu1. Зайдем в окно свойств кнопки и установим свойству ContextMenu значение ContextMenu2.

Заставляем контекстные меню работать. Делаем это аналогично Главному меню. Двойной щелчок по пункту меню – и в окне кода появляется заготовка процедуры, куда мы пишем все, что нам нужно. Вот что у вас должно получиться:


Private Sub MenuItem1_Click( ByVal sender As Object, ByVal e As System.EventArgs) Handles MenuItem1.Click

        Me.BackColor = Color.Blue                           'Контекстное меню формы - Покрасить ее в синий цвет

End Sub

Private Sub MenuItem2_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles MenuItem2.Click

        Me.BackColor = Color.Red                      'Контекстное меню формы - Покрасить ее в красный цвет

End Sub

Private Sub MenuItem3_Click(ByVal sender As Object, ByVal e As System.EventArgs)  Handles MenuItem3.Click

        Button1.Width = Button1.Width + 20           'Контекстное меню кнопки - Расширить ее

End Sub

Private Sub MenuItem4_Click(ByVal sender As Object, ByVal e As System.EventArgs)  Handles MenuItem4.Click

        Button1.Width = Button1.Width - 20     'Контекстное меню кнопки - Сузить ее

End Sub

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


Копирование перемещение, удаление фрагментов текста


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

Копируемый фрагмент нужно сначала выделить.

Выделение фрагмента: Поставьте курсор мыши вплотную к началу копируемого фрагмента. Нужно, чтобы  в этот момент он имел форму вертикальной черточки (I), а не стрелки. Затем нажмите левую клавишу мыши и, не отпуская ее, ведите мышь на конец копируемого фрагмента. Фрагмент будет выделен темным цветом. Если в процессе движения ваша рука дрогнет, то на экране будут происходить «страшные вещи» в том смысле, что темным станет слишком большой фрагмент текста. Сохраняйте хладнокровие и ни за что не отпускайте клавишу мыши. Когда вы доведете ее до последней буквы фрагмента, все уляжется. Можете отпускать мышь. Выделенный же фрагмент вы сможете по желанию удалить, переместить или скопировать в другое место. Делается это следующим образом:

Копирование. Поставьте курсор мыши на темный выделенный фрагмент. В этот момент курсор должен иметь свою обычную форму наклоненной налево стрелки. Щелкните правой клавишей мыши. Выделение в этот момент не должно исчезнуть. В появившемся контекстном меню выберите пункт Copy. Компьютер запоминает фрагмент в специальном невидимом месте памяти, которое называется буфер обмена. Теперь щелкните правой клавишей мыши в том месте окна, куда вы хотите скопировать фрагмент. Убедитесь, что текстовый курсор мигает в нужном месте, и выберите в появившемся контекстном меню пункт Paste. То, что было в буфере обмена, переносится в нужное место. Если вам необходимо скопировать тот же фрагмент еще и в другое место, еще раз щелкните правой клавишей мыши в этом другом месте и выберите Paste.

Более быстрый способ копирования: просто перетащите выделенный фрагмент в нужное место, удерживая нажатой не левую, а правую клавишу мыши. За мышиным курсором будет перемещаться текстовый. Когда он переместится до нужного места, отпустите клавишу мыши. В появившемся контекстном меню выберите пункт Copy Here.

Перемещение: Все аналогично копированию, только вместо Copy выбирайте Cut.. Более быстрый способ: просто перетащите мышкой выделенный фрагмент в нужное место.

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

Удаление: Выделив фрагмент, выберите в контекстном меню Delete или щелкните клавишу Delete на компьютере.

Вместо щелчков по правой клавише мыши вы можете выбрать пункты главного меню Edit, а именно: Copy, Cut, Paste, Delete, или можете щелкать по соответствующим кнопкам на панели инструментов.



Кривая Безье


Кривая Безье – это плавная, красивая кривая, положение и кривизна которой определяются 4 точками. Рисуется она методом DrawBezier. Вот код:

Dim Гр As Graphics = Me.CreateGraphics

Гр.DrawBezier(Pens.Black, New Point(10, 50), New Point(100, 10), New Point(90, 80), New Point(150, 30))

Dim x As Integer

For x = 90 To 290 Step 20

    Гр.DrawBezier(Pens.Black, New Point(210, 50), New Point(300, 10), New Point(200 + x, x), New Point(350, 30))

Next

На Рис. 17.4 вы видите результат.

Рис. 17.4

Пояснения: Первый из двух операторов DrawBezier рисует кривую в левой части рисунка. Четыре точки для этой кривой я взял те же, что и для ломаной из 17.1.1, и тут же для лучшего понимания смысла кривой нарисовал саму ломаную. Мы видим, что кривая соединяет точки 1 и 4. Крайние отрезки исходной ломаной проходят по касательной к конечным участкам кривой. Точки 2 и 3 управляют прохождением и кривизной этой кривой.

Для иллюстрации роли точек 2 и 3 я написал цикл, в котором точка 3 меняет свое местоположение. Второй оператор DrawBezier, выполнившись 11 раз, рисует 11 кривых, которые вы видите в правой части рисунка. Можно представить себе, что точки 2 и 3, перемещаясь, «тянут» на себя кривую.

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



ListBox


Поместите на форму элемент управления список (ListBox. Его имя – ListBox1. Пока он пустой. Заполните его. Для этого зайдите в его свойство Items (элементы). Перед вами возникнет Редактор строковых коллекций (Рис. 18.7).

Рис. 18.7

Введите в поле редактора список из пары десятков, например, футбольных команд. Вводите по одной команде в строку. Текст вводите так, как вы это делаете в обычных текстовых редакторах или, например, в окне кода. Затем – ОК. Запустите проект. На форме ListBox1 приобретет следующий вид (Рис. 18.8).

Рис. 18.8

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

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

Вот программа:

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

    Debug.WriteLine("Следующим соперником нашей команды будет команда  "  &  ListBox1.SelectedItem)

End Sub

Свойство SelectedItem списка ListBox1 – это значение выбранного элемента списка. Пощелкайте по элементам списка, после каждого щелчка нажимая кнопку «Печать». Итак, мы нашли одно из применений списка: он облегчает ввод в компьютер часто встречающихся слов.

ListBox из нескольких столбцов. Установив свойство MultiColumn в True, мы превратим список в многостолбцовый (см. Рис. 18.9). Термин «многостолбцовый» неудачен, так как на самом деле это все тот же один столбец, только разделенный на несколько частей. Разница только во внешнем виде и удобстве обзора. Свойство ColumnWidth управляет шириной столбцов.

Рис. 18.9

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



ListView


ListView и TreeView – важные и удобные элементы управления. Если вы хотите увидеть одно из применений ListView и TreeView, то откройте Проводник Windows – Windows Explorer (о нем рассказано в Приложении 2). Левая панель проводника это TreeView, а правая – ListView. Элемент TreeView приспособлен для отображения любых древовидных структур, не обязательно структуры папок на диске. Например, вы можете отобразить собственное генеалогическое древо. Элемент ListView – это, попросту говоря, ListBox с расширенными возможностями. Он приспособлен для удобного отображения любых списков и может содержать пиктограммы (значки, иконки).



Логические диски Адрес файла (путь, дорожка к файлу)


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

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

C:\Переписка\Личная переписка\Переписка с Асей\Письмо Асе в Алушту в мае 2000 года.txt

Для этого сначала разберем, что такое логические диски.

Пусть на вашем компьютере есть жесткий диск, дисковод для дискет и дисковод для компакт-дисков. Компьютер именует все эти дисководы буквами латинского алфавита. Дисковод для дискет должен иметь имя А или В. Жесткий диск почти всегда имеет имя С.  Однако при первоначальной настройке компьютера вы можете делить винчестер на несколько независимых участков. Каждый участок называется логическим диском.  Эти логические диски получают имена С, D, Е и т. д.  Операционная система предлагает нам пользоваться этими логическими дисками, как независимыми винчестерами. Что ж, в принципе, пользователю все равно, он может даже и не знать, что у него на компьютере не несколько жестких дисков, а один, поделенный. Компакт-диск тоже получает одну из букв, следующую по алфавиту.

Итак, как же понимать вышеприведенную запись? Она означает, что файл с именем «Письмо Асе в Алушту в мае 2000 года.txt» находится в папке «Переписка с Асей», которая находится в папке «Личная переписка», которая находится в папке «Переписка», которая находится на логическом диске C. Эта запись называется путем или дорожкой к файлу. Я предпочитаю называть ее адресом. Обратите внимание, что после имени логического диска нужно ставить двоеточие.

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



Ломаная


Задается массивом точек. Метод DrawLines (не путать с DrawLine!) соединяет соседние точки массива отрезками прямых. Вот код:

Dim Гр As Graphics = Me.CreateGraphics

Dim T1 As New Point(10, 50)

Dim T2 As New Point(100, 10)

Dim T3 As New Point(90, 80)

Dim T4 As New Point(150, 30)

Dim Массив_точек() As Point = {T1, T2, T3, T4}

Гр.DrawLines(Pens.Black, Массив_точек)

Вот результат (Рис. 17.1).

Рис. 17.1

Вышеприведенный фрагмент можно записать короче:

Dim Гр As Graphics = Me.CreateGraphics

Dim Массив_точек() As Point = {New Point(10, 50), New Point(100, 10), New Point(90, 80), New Point(150, 30)}

Гр.DrawLines(Pens.Black, Массив_точек)

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

Отрезки ломаной могут, конечно, взаимно пересекаться.



Ловец и шар наследуют PictureBox


Возникает такая идея, касающаяся нашей игры «Ловец». На форме фигурки ловца и шаров представляют собой элементы управления PictureBox. Ими, как марионетками, управляют невидимые объекты Ловец и Шар(i). Получается некая раздвоенность: душа в одном месте, а тело в другом. А что если сделать классы clsЛовец и clsШар наследниками класса PictureBox? Тогда единство души и тела будет восстановлено и программа должна стать стройнее и логичнее. Попробуем!

Вот отличия в коде ловца:

Public Class clsЛовец

    Inherits PictureBox

…………………………….

    Public Sub New()

        Me.Width = Размер_ловца

        Me.Height = Размер_ловца

        Me.SizeMode = PictureBoxSizeMode.StretchImage

        Me.BackColor = Color.White

        Me.Image = Image.FromFile("FACE02.ICO")

        Форма.Controls.Add(Me)      'Добавляем новую рожицу в коллекцию формы

        Me.BringToFront()     'Чтобы Поле не заслоняло рожицу

    End Sub

……………………………….

    Private Sub Ставим_изображение_ловца_на_место()

        Me.Left = x

        Me.Top = y

    End Sub

…………………………………….

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

Вот отличия в коде шара:

Public Class clsШар

    Inherits PictureBox

…………………………………….

    Private Event Я_пойман()

    Public Sub New()

        Me.Width = Размер_шара

        Me.Height = Размер_шара

        Me.SizeMode = PictureBoxSizeMode.StretchImage

        Me.BackColor = Color.White

        Me.Image = Image.FromFile("MOON06.ICO")

        Форма.Controls.Add(Me)

        Me.BringToFront() 

        AddHandler Я_пойман, AddressOf Убираем_шар

        AddHandler Я_пойман, AddressOf Форма.Уменьшаем_счетчик_непойманных_шаров

    End Sub

Private Sub Ставим_изображение_шара_на_место()

        Me.Left = x

        Me.Top = y

    End Sub

    Public Sub Действие()  

        Поймали_или_нет()

        Отскок()  

        Шаг()

        Ставим_изображение_шара_на_место()


    End Sub

    Private Sub Поймали_или_нет()

        Const Дальность As Double = 10 

        If Math.Abs(x - Ловец.Xл - ((Размер_ловца - Размер_шара) / 2)) < Дальность _

        And Math.Abs(y - Ловец.Yл - ((Размер_ловца - Размер_шара) / 2)) < Дальность  _

Then RaiseEvent Я_пойман()

    End Sub

    Private Sub Убираем_шар()

        x = -10000 : y = -10000

        dx = 0 : dy = 0

    End Sub

……………………………………..

В классе шара нам теперь не понадобились переменные Номер_шара и Число_созданных_шаров. Это приятное сокращение произошло потому, что душа теперь не должна искать свое тело, а точнее: потому что объекту Шар(i) теперь не нужно искать по номеру свой элемент управления PictureBox. Соответственно, конструктор стал покороче и поприятнее.

В момент поимки шар генерирует событие Я_пойман. Как видно из кода конструктора, у этого события два обработчика: тот, что в коде шара, занимается делами шара, а тот, что в коде формы, занимается делами формы. Оба они заменяют бывшую одну процедуру Выход_шара_из_игры. Это лучше, чем было раньше, когда из кода шара мы обращались к счетчику непойманных шаров на форме. Функцию Поймали я переделал в процедуру Поймали_или_нет. Так мне показалось логичнее.

Вот отличия в коде формы:

    Public Sub Уменьшаем_счетчик_непойманных_шаров()

        Число_непойманных_шаров = Число_непойманных_шаров - 1

        Счетчик_непойманных_шаров.Text = Число_непойманных_шаров

    End Sub

Здесь нам также не понадобилось объявлять массив pictШар.

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


Механика работы с файлами


Файлы. Вы знаете, что в результате сохранения игра в следующий раз начинается с того места, на котором вы остановились. А как компьютер помнит, где вы остановились? В каком месте компьютера хранится эта информация? Как ее сохранить?

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

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

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

В VB есть 3 способа работы с файлами:

1. При помощи процедур и функций, доставшихся в наследство от Visual Basic 6.0. Вы можете воспользоваться ими, как методами модуля FileSystem пространства имен Microsoft.VisualBasic. При этом способе, работая с файлами, вы можете не думать ни о каких объектах.

Однако, мы с вами уже привыкли, что всю главную работу в VB делают объекты. Вот и при работе с файлами мы можем выбрать другие, более современные способы, когда и сам файл представляется объектом, и инструменты для работы над ним – тоже объекты. Таких способов два.


2.       Один из этих способов – так называемая модель File System Object (FSO). На ней мы останавливаться не будем, так как ее возможности чуть менее широки, чем у 1 и 3 способов.

3.       Другой способ – с более богатыми возможностями – модель .NET System.IO. Вот именно с ней мы и будем в основном знакомиться. Ее возможности в виде самых разных классов и нескольких перечислений сосредоточены в пространстве имен System.IO.

Текстовые файлы. В VB существует несколько типов файлов. Мы познакомимся для начала с самым простым из них – текстовыми файлами. Вам совершенно не обязательно знать, как физически располагается информация файла на диске. Обычно это длинная непрерывная цепочка байтов, лежащая вдоль одной из дорожек диска.  При работе с текстовым файлом удобно воображать, что носитель информации – не диск, состоящий из дорожек, а нечто  подобное листу бумаги или экрану монитора, файл же состоит из строк информации на этом листе или экране. Запись в файл и считывание из файла осуществляются магнитной (или лазерной) головкой, которая движется по строкам файла строго последовательно, не пропуская ни строки, подобно авторучке, когда вы пишете письмо, или глазу, когда его читаете. Но у головки в этом случае нет свободы глаза или авторучки, которые по вашему желанию могут «прыгать» по листу, как хотят. Головка движется строго последовательно, не пропуская ни символа, поэтому текстовые файлы еще называют файлами с последовательным доступом.

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

Сейчас на 7 задачах мы научимся работать с текстовыми файлами.


Механизм действия ссылочных типов


Рассмотрим три похожих фрагмента:

Фрагмент 1:

Фрагмент 2:

Фрагмент 3:

Dim A As Integer

Dim B As Integer

A = 5

B = A

Debug.WriteLine(B)

A = 100

Debug.WriteLine(B)

Dim A(2) As Integer

Dim B(2) As Integer

A(2) = 5

B = A

Debug.WriteLine(B(2))

A(2) = 100

Debug.WriteLine(B(2))

Dim A As New Класс

Dim B As New Класс

A.Поле= 5

B = A

Debug.WriteLine(B.Поле)

A.Поле= 100

Debug.WriteLine(B.Поле)

Что будет напечатано:

Что будет напечатано:

Что будет напечатано:

5

5

5

100

5

100

Вот код класса для третьего фрагмента:

Class Класс

    Public Поле As Integer

    Public Поле1 As Integer

End Class

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

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

При выполнении оператора   Dim A, B As Integer   переменным A и B отводится в памяти по ячейке (которые заполняются нулями). При выполнении оператора  A = 5  в ячейке A появляется пятерка:

Адреса

Значения

A

000101

5

 

B

000102

0

При выполнении оператора  B = A  пятерка из ячейки A копируется в ячейку B, а то, что впоследствии пятерка в ячейке A меняется на сотню, никак не влияет на содержимое ячейки B.

Все происходит привычно, потому что тип Integer, к которому принадлежат переменные A и B, относится к обычным типам.

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


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

2. Массивы. Рассмотрим второй фрагмент. Вот что находится в памяти после выполнения оператора A(2) = 5:

Адреса

Значения

Адреса

Значения

A

000101

002040



002040

0

002041

0

002042

5

B

000102

002300



002300

0

002301

0

002302

0

На схеме я для наглядности изобразил ячейки в два столбца.

Пояснения: При выполнении операторов

Dim A(2) As Integer

Dim B(2) As Integer

каждой из переменных A и B отводится в памяти одна

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

Когда выполняется оператор A(2) = 5,  компьютер, зная, что массив является ссылочным типом, рассматривает значение 002040 в ячейке для переменной A именно, как адрес, а не число типа Integer, и поэтому не пытается записать в эту ячейку число 5, а  отправляется по указанному адресу, где находит три ячейки. В какую из них записать число 5, ему указывает индекс 2.

Рассмотрим, как выполняется оператор B = A.  Вам могло показаться по аналогии с первым фрагментом, что при этом все три числа из области памяти для A копируются в область памяти для B.  Но нет, здесь по своему строгому, но привычному вам закону выполняется оператор присваивания. Закон этот говорит, что значение переменной A должно быть записано в ячейку для значения переменной B. Поскольку значением переменной A является адрес 002040, то он и копируется в ячейку для значения переменной B. Вот что получается:

Адреса

Значения

Адреса

Значения

A

000101

002040



002040

0

002041

0

002042

5

B

000102

002040

002300

0

002301

0

002302

0

<


Вы видите, что три числа в области памяти 002300 не изменились. Почему же тогда первый из двух операторов  Debug.WriteLine(B(2))  печатает пятерку, а не ноль? Вот почему. Механизм обращения к памяти здесь тот же, что и описанный парой абзацев ранее механизм обращения к памяти при выполнении оператора A(2) = 5. Компьютер, зная, что массив является ссылочным типом, рассматривает значение 002040 в ячейке для переменной B, как адрес, и отправляется по указанному адресу, где находит три ячейки. Индекс 2 указывает ему, что для печати нужно выбрать значение из последней ячейки, а там находится пятерка.

А кому же теперь нужна область 002300? В том-то и дело, что никому! Ссылок на нее теперь не существует и воспользоваться ей уже нельзя, даже если вы и захотите. Она превратилась в мусор (garbage). Уборкой мусора занимается VB и делает это без вашего ведома и участия.

Я думаю, что теперь мне не нужно объяснять, почему второй из двух операторов  Debug.WriteLine(B(2))  напечатает сотню.

3. Классы. Аналогично рассматривается третий фрагмент. При создании объекта из класса, как и при создании массива, в памяти под объект отводится одна ячейка и еще дополнительно область памяти для хранения  элементов объекта (в нашем случае две ячейки для полей, которые заполняются нулями). В этой одной ячейке находится ссылка на область памяти. Вот что будет в памяти при окончании работы третьего фрагмента:

Адреса

Значения

Адреса

Значения

A

000101

002040



Поле

002040

100

Поле1

002041

0

B

000102

002040

Поле

002300

0

Поле1

002301

0

Задание 21.         

Что напечатает фрагмент:

        Dim A, B, C As Класс

        A = New Класс

        A.Поле = 100

        B = A

        C = New Класс

        C.Поле = 50

        A = C

        Debug.WriteLine(A.Поле)

        Debug.WriteLine(B.Поле)


Метка с гиперссылкой (LinkLabel)


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

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

Поместим на форму два элемента управления LinkLabel. Придадим им текст, как на Рис. 18.17. При щелчке по тексту первой метки должен запускаться Internet Explorer и если вы подключены к Интернет, должна открываться страничка сайта http://www.Yandex.ru. При щелчке по тексту второй метки должно появляться окно с сообщением "Четыре".

Рис. 18.17

Сделайте двойной щелчок по каждой метке. В окне кода возникнут заготовки обработчика щелчка. Введите туда следующий код:

Private Sub LinkLabel1_LinkClicked(ByVal sender As System.Object,  _

ByVal e As System.Windows.Forms.LinkLabelLinkClickedEventArgs) Handles LinkLabel1.LinkClicked

        System.Diagnostics.Process.Start("http://www.Yandex.ru")

End Sub

Private Sub LinkLabel2_LinkClicked(ByVal sender As System.Object,  _

ByVal e As System.Windows.Forms.LinkLabelLinkClickedEventArgs) Handles LinkLabel2.LinkClicked

        MsgBox("Четыре")

End Sub

Запустите проект, щелкните по меткам. Все работает.

Пояснения: Internet Explorer запускается методом Start класса Process пространства имен System.Diagnostics (см. 25.5.2) В скобках мы указываем Интернет-адрес нужного нам сайта. Не пугайтесь, если VB подчеркнет в окне кода адрес в кавычках. Это привычное подчеркивание Интернет-адресов.

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

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



Метод Start класса Process


В пространстве имен System.Diagnostics имеется класс Process, метод Start которого запускает программы. Рассмотрим код:

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        System.Diagnostics.Process.Start("Notepad.exe")

        System.Diagnostics.Process.Start("Notepad.exe", "Text.txt")

        System.Diagnostics.Process.Start("Text.txt")

        System.Diagnostics.Process.Start("IExplore.exe")

        System.Diagnostics.Process.Start("IExplore.exe", "www.Google.com")

        System.Diagnostics.Process.Start("www.Google.com")

End Sub

В процедуре – 6 операторов. Одним щелчком по кнопке вы запускаете 6 окон:

Окно Блокнота с новым пустым текстовым документом

Окно Блокнота с открытым в нем текстовым документом из файла Text.txt

Еще одно окно Блокнота с открытым в нем текстовым документом из файла Text.txt

Окно Internet Explorer со стартовой страницей

Окно Internet Explorer с открытой в нем Интернет-страницей поисковой системы Google

Еще одно окно Internet Explorer с открытой в нем страницей Google

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

Откуда компьютер знает, что в 3-й строке процедуры файл Text.txt  нужно открывать Блокнотом, а не, скажем, приложением WordPad? Он берет эту информацию из ассоциаций файлов с приложениями, на которые настроена ваша Windows. Вы можете в Windows перенастроить эти ассоциации по своему усмотрению и тогда метод Start будет работать согласно новым настройкам.

Вообще метод Start работает аналогично пункту Run (Выполнить) стартового меню Windows.



Microsoft Chart Control


Добавьте в Toolbox элемент управления Microsoft Chart Control 6.0. Он имеет такой вид (Рис. 20.22).

Рис. 20.22

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

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



Миг между прошлым и будущим


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

Теперь же я рад сообщить Вам, что если Вы выполнили большинство из полутора сотен предложенных заданий (включая в обязательном порядке задания на большие проекты) и при изучении больших проектов, разобранных в книжке, не просто копировали код в компьютер, а старались по возможности опережать мои объяснения, то Вы вполне готовы к тому, чтобы выполнить задание на звание «Программист-любитель II ранга». На выбор – одно из трех:

Игра в морской бой

Игра в крестики-нолики на бесконечном поле

Игра «Танковый бой».

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

Правила первых двух игр:

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

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

Правила танкового боя приведу чуть ниже.

Требования к первым двум играм:

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

Компьютер должен вести счет партий и отображать его на экране


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

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

Неплохо сделать меню с такими, примерно, пунктами:

сохранить игру, загрузить игру, выход из игры, Help.

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

А вот правила танкового боя:

Посмотрите на Рис. 27.6.



Рис. 27.6

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

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

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

Необязательные правила: Вы можете выбрать любой из трех типов игры:

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

2 - Произвольная игра. Здесь нет уровней. Число снарядов и число рядов вы настраиваете сами.

3 – Игра с сохранением. Уровни те же, что и в простой игре, но гораздо более трудные. При этой игре ваши результаты сохраняются автоматически. При входе в игру вы должны ввести свое имя и пароль. Пароль хорош тем, что никто другой не сможет войти в игру под вашим именем и таким образом воспользоваться вашими достижениями. Плох пароль тем, что если вы его забудете, то вам придется начинать игру с 1 уровня и под другим именем.



Отчаявшись пройти уровень в игре с сохранением, вы можете утешиться тем же уровнем в простой.

Когда я программировал эту игру, я не заботился о красоте и занимательности. Это – хотя и большая, но всего лишь учебная задача на использование объектов. Улучшайте игру и ее правила, как вам заблагорассудится.

Советы по программированию:

Вот мое мнение о сложности стратегии: В морском бое

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

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

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

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

В танковой битве объектами будут:

Танк под управлением компьютера

Танк под управлением человека

Снаряд

Для простоты запретите танку стрелять, пока его снаряд от предыдущего выстрела все еще летит. Тогда размер массива снарядов не превысит размер массива танков.

До свидания!

Любая из этих задач достаточно сложна и потребует многих дней напряженной работы. В книге таких больших и сложных задач я не программировал. Если вам кажется, что вы не сможете запрограммировать все, что я перечислил в требованиях и советах, потому что «мы этого не проходили», то я вам заявляю – все, что нужно, мы проходили! Надо только как следует подумать над самым важным – над представлением информации об игре и над алгоритмами, по которым компьютер будет делать ходы. Пусть алгоритмы сначала родятся у вас в голове, а потом – на компьютере. Работая над задачей, значительную долю времени вы должны проводить вдали от компьютера!

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

 Желаю успеха!


Многодокументный интерфейс (MDI)


Во многих приложениях Windows вы можете одновременно работать с несколькими документами и одновременно видеть их на экране, каждый – в своем окне. Все эти окна не обладают полной свободой на экране, а находятся внутри главного окна приложения. Они не могут выходить за его пределы. Каждое окно на этапе проектирования было отдельной формой. Для создания таких приложений VB предлагает специальный инструмент – так называемый многодокументный интерфейс – MDI.

Задача: Давайте создадим приложение следующего вида (см. Рис. 27.5).

Рис. 27.5

Оно должно уметь делать следующие вещи:

При выборе пункта меню «Создать новый документ» внутри окна приложения должно создаваться очередное окно документа с заголовком «Документ № …» аналогично тому, как это делается в Microsoft Word. Окон может быть создано сколько угодно.

В окна можно вводить текст.

При выборе пункта меню «Крась» активное окно (то, в котором текстовый курсор) окрашивается в желтый цвет.

При выборе пункта меню «Выравнивай размеры окон» все окна приобретают ширину=200 и высоту=150.

Решение. Создайте проект из двух форм. Форма Form1 будет главным окном приложения, Form2 – образцом для создания окон документов.

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

Чтобы в окна документов можно было вводить текст, поместите на Form2 текстовое поле, сделайте его многострочным (свойство Multiline = True) и растяните на всю форму. Чтобы при растяжении окна поле растягивалось вместе с ним, придайте его свойству Anchor значение (Top, Bottom, Left, Right).

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

Dim Форма As New Collection

Private Sub MenuItem2_Click(ByVal sender As Object, ByVal e As EventArgs) Handles MenuItem2.Click

        Dim Ф As New Form2


        Static Счетчик As Integer

        Счетчик = Счетчик + 1

        Ф.Text = "Документ №" & Счетчик

        Форма.Add(Ф)

        Ф.MdiParent = Me

        Ф.Show()

End Sub

Private Sub MenuItem3_Click( ByVal sender As Object, ByVal e As EventArgs) Handles MenuItem3.Click

        Dim Объект As Form2 = Me.ActiveMdiChild

        If Not Объект Is Nothing Then Объект.TextBox1.BackColor = Color.Yellow

End Sub

Private Sub MenuItem4_Click(ByVal sender As Object, ByVal e As EventArgs) Handles MenuItem4.Click

        Dim i As Integer

        For i = 0 To Me.MdiChildren.GetUpperBound(0)

            Me.MdiChildren(i).Width = 200

            Me.MdiChildren(i).Height = 150

        Next

    End Sub

Запустите проект и проверьте, как он работает.

Пояснения:

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

Следующий оператор

        Ф.MdiParent

= Me

объясняет, какое окно для созданного окна является «родительским», то есть внутри какого окна созданное окно должно находиться. Указывает это свойство MdiParent созданного окна. Его значением является ссылка на «родительское» окно. Поскольку «родительским» окном является Form1, то мы можем смело написать Me.

Последний оператор показывает созданное окно на экране.

Красим. Поговорим о процедуре окраски активного окна (MenuItem3_Click). Не забывайте, что красить надо не форму, а текстовое поле. У главного окна многодокументного приложения есть свойство ActiveMdiChild, которое указывает на то из окон, которое в данный момент активно. Но было бы ошибкой решить дело оператором

ActiveMdiChild.TextBox1.BackColor = Color.Yellow



Дело в том, что свойство ActiveMdiChild имеет тип Form, а значит не обладает всем богатством свойств формы Form2. Попросту, оно «не знает», что на форме Form2 есть текстовое поле.

Результата можно добиться парой операторов:

        Dim Объект As Form2 = Me.ActiveMdiChild

        Объект.TextBox1.BackColor = Color.Yellow

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

У второго оператора есть недостаток. Когда в приложении нет активных окон (предположим, все закрыты), он, естественно, дает ошибку. Надо в этом случае предотвратить его выполнение. Здесь нам поможет тот очевидный факт, что при отсутствии активных окон значение свойства ActiveMdiChild равно Nothing. Это и дает нам окончательный код:

        Dim Объект As Form2 = Me.ActiveMdiChild

        If  Not

Объект Is Nothing  Then  Объект.TextBox1.BackColor = Color.Yellow

Выравниваем. Поговорим о процедуре выравнивания размеров (MenuItem4_Click). У главного окна многодокументного приложения есть свойство MdiChildren, которое представляет собой массив созданных окон. Массив этот заполняется автоматически, безо всяких усилий с нашей стороны. Получается, что коллекцию мы создавали зря? Я думаю, вы сами ответите на этот вопрос, когда дело дойдет до реальных проектов. Заметьте, кстати, что элементы этого массива тоже имеют тип Form. Остается напомнить, что GetUpperBound(0) – это свойство одномерного массива, равное верхней границе его индекса.


Многоугольник


Если соединить последнюю точку ломаной с ее первой точкой, получится замкнутая ломаная. Если это проделать для ломаной с Рис. 17.1, то получится замкнутая ломаная, нарисованная в левой части Рис. 17.3. Получившуюся ломаную можно было бы назвать многоугольником, но всем известно, что многоугольник – это замкнутая ломаная без самопересечения. Очевидно, что мы просто неудачно подобрали точки, иначе получили бы нормальный многоугольник.

Рисует замкнутую ломаную метод DrawPolygon. Ясно, что это копия метода DrawLines, но с одним добавлением – последняя точка массива автоматически соединяется с первой, то есть ломаная замыкается.

Зададим такой же массив точек, что в 17.1.1. Вот код:

Dim Гр As Graphics = Me.CreateGraphics

Dim Массив_точек() As Point = {New Point(10, 50), New Point(100, 10), New Point(90, 80), New Point(150, 30)}

Гр.DrawPolygon(Pens.Black, Массив_точек)

Результат вы можете видеть в левой части Рис. 17.3.

Рис. 17.3

Метод FillPolygon рисует залитый многоугольник.

        Гр.FillPolygon(Brushes.OrangeRed, Массив_точек)

Если ломаная получается с самопересечением, то здесь заливка не всегда сплошная, как вы и видите в правой части Рис. 17.3, где нарисована ломаная из 5 точек. Способ заливки зависит от третьего параметра метода. Вот какой параметр делает в данном случае заливку сплошной:

        Гр.FillPolygon(Brushes.OrangeRed, Массив_точек, Drawing2D.FillMode.Winding)



Начинаем проектирование


Хватит теории. Садимся за компьютер. Создайте новый проект. Разместите на форме большую белую метку – это будет поле, по которому и будут двигаться шары и ловец. Назовите ее Поле. Размер поля не имеет значения, потому что мы так запрограммируем шары и ловца, что они будут чувствовать бортики поля. Разместите на форме кнопку Начинай_сначала и текстовое поле Счетчик_времени.

Впоследствии украсьте как-нибудь проект. Только не надо помещать на метку фотографию – шары будут тормозить.

Создайте стандартный модуль. В нем нам будет удобно объявлять глобальные компоненты нашего проекта.

Создадим два класса с именами clsЛовец и clsШар. Впоследствии из класса clsЛовец создадим объект Ловец, а из класса clsШар создадим массив объектов Шар(10).

Разобьем создание проекта на три ступени:

На первой ступени мы создадим ловца и поставим его в начальную точку. Шаров пока не будет.

На второй ступени мы научим ловца двигаться.

На третьей ступени появятся шары и проект будет готов полностью.



Наследование


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

Аналогия. Вообразим, что мы создали простенький класс Автомобиль, наделив его всего лишь двигателем, рулем и 4 колесами. И никаких подробностей. Полюбовавшись работой получившейся самоходной тележки, мы решили развивать проект дальше и создать еще три класса, более богатых и подробных: Амфибия, Грузовик и Автобус. У каждого из новых классов, как и у Автомобиля, есть двигатель, руль и 4 колеса, но вдобавок к ним и много подробностей, например, у Амфибии гребной винт, у Грузовика – кузов, у Автобуса – пассажирский салон.

Как создавать код каждого из этих трех классов? В нашем случае можно было бы просто скопировать в каждый из них весь код Автомобиля, добавив затем для каждого класса свои переменные, процедуры и функции. Но есть более удобный и «правильный» способ – это наследование. Мы просто объявляем, что новый класс Грузовик является наследником класса Автомобиль. При этом Грузовик неявно (невидимо) приобретает весь код своего родителя – Автомобиля. Тем самым, ничего не записав в код Грузовика, мы уже можем пользоваться им как Автомобилем. А чтобы снабдить Грузовик дополнительными чертами, мы пишем ему новые процедуры и функции. Аналогично поступаем с Амфибией и Автобусом.

Самое интересное то, что если мы впоследствии изменяем что-то в коде родителя (Автомобиль), то это изменение тут же сказывается на наследниках. Например, если мы в Автомобиле заменили двигатель внутреннего сгорания на электрический, то эта замена немедленно произойдет и в Амфибии, и в Грузовике, и в Автобусе. Это очень ценное и удобное качество. При отсутствии наследования нам пришлось бы делать замену двигателя в каждом классе.

У детей могут быть свои дети. Так, у Грузовика могут быть наследники Самосвал, Пикап и др. Все они наследуют у папаши-Грузовика кузов, а у дедушки-Автомобиля – двигатель, руль и 4 колеса. В общем, гены передаются самым отдаленным потомкам.


Рассмотрим пример. Пусть имеется класс clsПрямоугольник. Его дело – предоставлять внешнему пользователю возможность вычислять по длине и ширине прямоугольника его площадь и периметр:

Public Class clsПрямоугольник

    Public Длина As Integer

    Public Ширина As Integer

    Public Function Площадь() As Integer

        Return    Длина * Ширина

    End Function

    Public Function Периметр() As Integer

        Return    2 * Длина + 2 * Ширина

    End Function

End Class

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

    Public Высота As Integer

    Public Function Объем() As Integer

        Return Площадь() * Высота

    End Function

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

Поэтому создаем новый класс – clsПараллелепипед:

Public Class clsПараллелепипед

    Inherits

clsПрямоугольник

    Public Высота As Integer

    Public Function Объем() As Integer

        Return Площадь() * Высота

    End Function

End Class

Как видите, класс получился очень короткий, но делает все что нужно. Это заслуга наследования. Строка

    Inherits

clsПрямоугольник

говорит о том, что он наследует все компоненты класса clsПрямоугольник. Слово Inherits переводится «наследует». В простейшем случае наследование выглядит так, как если бы мы скопировали код родителя в тело наследника.

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

Продолжаем пример. У родителя может быть много детей. Создадим еще один класс – clsКоробка, дело которого – вычислять полную площадь поверхности параллелепипеда. Вот он:

Public Class clsКоробка



    Inherits

clsПрямоугольник

    Public Высота As Integer

    Public Function Площадь_поверхности() As Integer

        Return 2 * Площадь() + Периметр() * Высота

    End Function

End Class

Вы видите, что каждый из наследников имеет 3 поля: длину, ширину и высоту – и умеет вычислять площадь и периметр. Проверим работу классов, нажав кнопку на форме:

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        Dim Прямоугольник As New clsПрямоугольник

        Прямоугольник.Длина = 10

        Прямоугольник.Ширина = 5

        Debug.WriteLine(Прямоугольник.Площадь)

        Debug.WriteLine(Прямоугольник.Периметр)

        Dim Параллелепипед As New clsПараллелепипед

        Параллелепипед.Длина = 4

        Параллелепипед.Ширина = 3

        Параллелепипед.Высота = 2

        Debug.WriteLine(Параллелепипед.Площадь)

        Debug.WriteLine(Параллелепипед.Периметр)

        Debug.WriteLine(Параллелепипед.Объем)

        Dim Коробка As New clsКоробка

        Коробка.Длина = 3

        Коробка.Ширина = 2

        Коробка.Высота = 100

        Debug.WriteLine(Коробка.Площадь)

        Debug.WriteLine(Коробка.Периметр)

        Debug.WriteLine(Коробка.Площадь_поверхности)

    End Sub

Вот что будет напечатано:

50

30

12

14

24

6

10

1012

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

Внуки. У детей, как я уже говорил, могут быть свои дети. Так, у класса clsКоробка могут быть наследники: Гараж, Чемодан и др. Все они наследуют у родителя – класса clsКоробка – высоту и площадь поверхности, а у дедушки – класса clsПрямоугольник – длину, ширину, площадь и периметр. Факт наследования объявляется аналогично:

Public Class clsГараж

    Inherits

clsКоробка

Область видимости Protected. Если какой-нибудь компонент родительского класса объявлен Private, то у наследника не будет доступа к этому компоненту. Когда такой доступ нужен, компонент рекомендую объявлять Protected. В этом случае он будет доступен наследникам, но только им. Пример проекта:

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        Dim Наследник As New clsНаследник

        Debug.WriteLine(Наследник.Фн)

End Sub

Public Class clsРодитель

    Protected А As Integer = 100

    Private B As Integer = 200

    Protected Function Ф() As Integer

        Return   B + 1   

    End Function

End Class

Public Class clsНаследник

    Inherits clsРодитель

    Public Function Фн() As Integer

        Return   Ф + А

    End Function

End Class

Проект напечатает

301

Если бы мы объявили так:

    Private А

As Integer = 100

    Private Function Ф() As Integer

то в строке

        Return   Ф + А

были бы подчеркнуты и Ф, и А, поскольку ни переменная, ни функция были бы недоступны.


Наследуем элементы управления


В 22.13.2 мы следующим образом создавали на форме кнопку:

Dim WithEvents  b As New Button          'Создаем кнопку

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

        Me.Controls.Add(b)                          'Добавляем созданную кнопку в коллекцию формы

End Sub

Что будет, если мы создадим и запустим проект такого содержания?:

Dim WithEvents Кнопка As New clsКнопка

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

        Me.Controls.Add(Кнопка)

End Sub

Public Class clsКнопка

    Inherits Button

End Class

Результат будет тот же самый.

Чем же новый способ отличается от старого? Почему вообще новый способ сработал? Поскольку класс clsКнопка является наследником класса Button, он обладает всеми свойствами, методами и событиями кнопки. А значит из него можно создавать объект – обычную кнопку, которым и пользоваться можно как обычной кнопкой. А отличается этот способ тем, что теперь в коде класса clsКнопка мы можем придавать нашей кнопке какие угодно новые свойства, методы и события. В результате мы можем сконструировать кнопку, внешний вид и поведение которой резко отличаются от обычной кнопки.

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



Наследуем шар


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

Очевидно, нам нужен еще один класс – clsХищный_шар. Чем отличается поведение хищного шара от поведения обычного? Только действиями при столкновении с ловцом. Если обычный шар при столкновении исчезает, то при столкновении хищного шара с ловцом игра должна останавливаться и должно выдаваться сообщение «Ловца поймали, вы проиграли!»

Кроме поведения есть еще и внешний вид. Фигурка у хищного шара другая.

Исходя из вышесказанного, спросим себя: Чем будет отличаться код класса clsХищный_шар от кода класса clsШар? Процедурой Выход_шара_из_игры, отвечающей за действия при столкновении, и процедурой New, отвечающей за создание изображения шара. Все! Все остальное (за небольшим исключением, о котором позже) будет одинаковым.

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

Для изображений хищных шаров нам нужен новый массив графических полей, назовем его pictХищный_шар. Если мы сделаем хищный шар наследником обычного, то при выполнении конструктора New хищного шара должен автоматически выполняться конструктор New обычного шара, который будет работать с массивом графических полей pictШар, что при создании хищного шара не только излишне, но и является ошибкой.

Вот как с учетом вышесказанного дополнится наш проект (многоточием обозначены фрагменты, оставшиеся без изменений):

Форма:

    Private Const Число_хищных_шаров As Integer = 2

    Private Хищный_шар(Число_хищных_шаров) As clsХищный_шар

    Public pictХищный_шар(Число_хищных_шаров) As PictureBox

    ………………

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load


       ………………

        For i = 1 To Число_хищных_шаров

            Хищный_шар(i) = New clsХищный_шар

        Next i

       ………………

    End Sub

    Private Sub Начальная_установка()

       ………………

        For i = 1 To Число_хищных_шаров

            Хищный_шар(i).Начальная_установка()

        Next i

    End Sub

    Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick

       ………………

        For i = 1 To Число_хищных_шаров

            Хищный_шар(i).Действие()

        Next i

       ………………

    End Sub

Абстрактный шар. Я думаю, что не нужно приводить тут полностью код этого класса, лучше сказать словами, как он получается. Возьмите бывший код класса clsШар из 22.12.6  и вычтите из него строки, которые вы видите пониже в коде нового обычного шара, после чего добавьте пять нижеследующих строк):

Public MustInherit Class clsШар_абстрактный

    Protected

Const Размер_шара As Double = 12

    Protected

x, y As Double

    Protected

dx, dy As Double 

      ………………

    Protected MustOverride Sub Ставим_изображение_шара_на_место()

      ………………

    Protected

MustOverride Sub Выход_шара_из_игры()

End Class

Обычный шар:

Public Class clsШар

    Inherits

clsШар_абстрактный

    Private Shared Число_созданных_шаров As Integer = 0

    Private Номер_шара As Integer

    Public Sub New()

        Число_созданных_шаров = Число_созданных_шаров + 1

        Номер_шара = Число_созданных_шаров

        Форма.pictШар(Номер_шара) = New PictureBox

        Форма.pictШар(Номер_шара).Width = Размер_шара

        Форма.pictШар(Номер_шара).Height = Размер_шара

        Форма.pictШар(Номер_шара).SizeMode = PictureBoxSizeMode.StretchImage

        Форма.pictШар(Номер_шара).BackColor = Color.White

        Форма.pictШар(Номер_шара).Image = Image.FromFile("MOON06.ICO")

        Форма.Controls.Add(Форма.pictШар(Номер_шара))    

        Форма.pictШар(Номер_шара).BringToFront()  

    End Sub



    Protected

Overrides Sub Ставим_изображение_шара_на_место()

        Форма.pictШар(Номер_шара).Left = x

        Форма.pictШар(Номер_шара).Top = y

    End Sub

    Protected

Overrides Sub Выход_шара_из_игры()

        x = -10000 : y = -10000

        dx = 0 : dy = 0

        Форма.Число_непойманных_шаров = Форма.Число_непойманных_шаров - 1

        Форма.Счетчик_непойманных_шаров.Text = Форма.Число_непойманных_шаров

    End Sub

End Class

Хищный шар:

Public Class clsХищный_шар

    Inherits clsШар_абстрактный

    Private Shared Число_созданных_шаров As Integer = 0

    Private Номер_шара As Integer

    Public Sub New()

        Число_созданных_шаров = Число_созданных_шаров + 1

        Номер_шара = Число_созданных_шаров

        Форма.pictХищный_шар(Номер_шара) = New PictureBox

        Форма.pictХищный_шар(Номер_шара).Width = Размер_шара

        Форма.pictХищный_шар(Номер_шара).Height = Размер_шара

        Форма.pictХищный_шар(Номер_шара).SizeMode = PictureBoxSizeMode.StretchImage

        Форма.pictХищный_шар(Номер_шара).BackColor = Color.White

        Форма.pictХищный_шар(Номер_шара).Image = Image.FromFile("MOON02.ICO")

        Форма.Controls.Add(Форма.pictХищный_шар(Номер_шара))

        Форма.pictХищный_шар(Номер_шара).BringToFront()

    End Sub

    Protected

Overrides Sub Ставим_изображение_шара_на_место()

        Форма.pictХищный_шар(Номер_шара).Left = x

        Форма.pictХищный_шар(Номер_шара).Top = y

    End Sub

    Protected Overrides Sub Выход_шара_из_игры()

        Форма.Timer1.Enabled = False                                  'Конец игры

        MsgBox("Ловца поймали, вы проиграли!")

    End Sub

End Class

Пояснения: Хищные и обычные шары равноправны, поэтому в коде формы мы для хищных шаров дописываем все те строки, что писали и для обычных. Переменные Число_созданных_шаров и Номер_шара я перенес из абстрактного шара в каждый из наследников, так как у каждого массива своя нумерация. Поскольку Номер_шара ушел из абстрактного шара, пришлось перенести из него в наследников и процедуру Ставим_изображение_шара_на_место, которая обращается к этой переменной. У обоих шаров процедуры Ставим_изображение_шара_на_место и Выход_шара_из_игры пришлось объявить, как Protected, так как именно таким образом объявлены эти процедуры у родителя. А у родителя они так объявлены потому, что иначе к ним не было бы доступа от наследников.

Недостатки нашего способа наследования:

Смысл процедуры Выход_шара_из_игры класса clsХищный_шар не соответствует ее названию.

Процедуры New и Ставим_изображение_шара_на_место обоих шаров практически одинаковы, а значит можно было постараться избежать излишней писанины. Для этого можно было вернуть их в абстрактный шар и снабдить параметрами – именем массива графических полей и именем файла картинки. В этом случае параметрами пришлось бы снабжать и те процедуры, которые к ним обращаются. В общем, «шило на мыло». Зато код обоих шаров получился бы маленький и аккуратный.


Настраиваем диалоговые окна Фильтр, шаблон


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

Фильтр. Сейчас мы видим в диалоговых окнах все типы файлов, а значит можем нечаянно попытаться сохранить наш текст, скажем, в графический файл или открыть графический файл, как текстовый, с неожиданными и возможно неприятными результатами. Поскольку текст имеет смысл сохранять только в текстовых файлах, то и не надо нам видеть никаких других. Все эти другие, как говорится, нужно отфильтровать. Для этого служит свойство Filter объектов SaveFileDialog и OpenFileDialog. Если мы напишем

OpenFileDialog1.Filter

= "Текстовые файлы|*.txt"

то нижняя часть диалогового окна открытия файла будет выглядеть так, как на Рис. 20.3.

Рис. 20.3

Пояснения: Свойство Filter имеет своим значением строку, содержимое которой объясняет компьютеру, что не надо отфильтровывать. В простейшем случае эта строка состоит из двух частей, разделенных вертикальной чертой. В левую часть мы пишем произвольный текст, предназначенный для человека и помогающий с нашей точки зрения понять, с каким типом файлов мы имеем дело. Этот текст мы видим в нижнем поле окна (в нашем случае это текст «Текстовые файлы»). Объясняю дальше.

Шаблон. В правой части строки фильтра мы пишем так называемую маску или шаблон. В нашем случае это «*.txt» – звездочка, точка и буквы txt. Точка означает точку, которая стоит в имени файла перед расширением. Буквы txt означают, что мы желаем видеть в окне только файлы с расширением txt. Звездочка слева от точки означает, что в имени файла слева от точки нам подойдут любые символы. Получается, что нам подходят файлы с любыми именами, лишь бы расширением файла служили буквы txt.

На Рис. 20.3 вы можете наблюдать, что в окне видны только указанные файлы, остальные не видны, то есть отфильтрованы.

Фильтр из нескольких вариантов. Если мы хотим дать возможность пользователю выбирать из нескольких типов файлов, мы пишем строку фильтра не из одной, а из нескольких пар. Пары тоже разделены вертикальными чертами. Например, вот строка фильтра из трех пар:


OpenFileDialog1.Filter

= "Текстовые файлы|*.txt|Интернет-страницы|*.htm|Все файлы|*.*"

Звездочка означает, что на ее месте может стоять любой набор символов, поэтому во фрагменте «*.*» звездочка справа от точки означает любое возможное расширение, а слева – любое имя.

Теперь нижняя часть диалогового окна открытия файла будет выглядеть так, как на Рис. 20.4.



Рис. 20.4

Пояснения: Здесь мы можем выбирать в списке одну из 3 возможностей. Соответственно в окне мы будем видеть или только текстовые файлы с расширением txt, или только Интернет-страницы с расширением htm, или все файлы (с любыми расширениями).

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

OpenFileDialog1.Filter = "Картинки|*.BMP;*.JPG;*.GIF|Интернет-страницы|*.htm"

Кстати, поскольку файлы с расширением htm тоже являются текстовыми файлами, их можно прекрасно открывать, редактировать и сохранять в нашем текстовом редакторе.

Примеры шаблонов. Кроме звездочки в шаблонах можно употреблять знак вопроса «?». Он не имеет никакого отношения к вопросам, а означает, что на его месте может стоять один произвольный символ. Пусть в папке содержатся файлы:

База.txt,   Банка.txt,   Баранка.txt,   Баржа.txt,   Барка.txt,  Заметки.txt

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

Шаблон

Какие файлы увидим в окне

Ба??а.*

Банка.txt,   Баржа.txt,   Барка.txt

Ба?а.*

База.txt

Ба*.*

База.txt,   Банка.txt,   Баранка.txt,   Баржа.txt,   Барка.txt


Настраиваем диалоговые окна Отказ от сохранения или открытия


Позаботимся о том, чтобы при нажатии на кнопку Cancel или на крестик в правом верхнем углу диалогового окна не возникала ошибка. А возникает она только по той причине, что значение FileName в этом случае является пустой строкой и объекты StreamReader и StreamWriter просто не знают, какой файл им открывать или сохранять.

Вникнем. Если пользователь нажал кнопку Cancel или на крестик, это наверняка значит, что он передумал открывать или сохранять файл. Значит, все, что нам нужно сделать, это при нажатии на кнопку Cancel или на крестик прекратить выполнение процедуры открытия или сохранения.

Но как компьютер почувствует эти нажатия? Обратите внимание, что метод ShowDialog является функцией, возвращающей значение типа DialogResult. А это перечисление, очень похожее на перечисление MsgBoxResult, которое выдает функция MsgBox (см. 7.8). При нажатии на кнопку Cancel или на крестик функция ShowDialog принимает значение DialogResult.Cancel. Поэтому нужного результата мы добьемся такой процедурой:

Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click

        OpenFileDialog1.Filter = "Текстовые файлы|*.txt"

        If  OpenFileDialog1.ShowDialog() = DialogResult.Cancel  Then  Exit Sub

        Dim Файл As String = OpenFileDialog1.FileName

        Dim Чтение As New System.IO.StreamReader(Файл)

        TextBox1.Text = Чтение.ReadToEnd

        Чтение.Close() 

End Sub

Аналогично пишется и процедура сохранения.

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

Если у вас или у вашего друга есть устройство записи на компакт-диски CD-R, то вы вполне можете создать свой собственный компакт-диск, который будет делать то же самое. Плеер мы с вами уже запрограммировали в 3.10. Усовершенствуйте его: пусть он создает список всех музыкальных файлов на диске, а запускать воспроизведение песни вы будете щелчком по выбранному элементу списка. При желании вы должны иметь возможность запустить воспроизведение песни, выбрав ее файл в диалоговом окне открытия файла. Сделайте инсталляционный пакет получившегося приложения и скопируйте на компакт-диск. На свободное место диска накопируйте файлов в формате MP3. Диск готов. Можно дарить друзьям и подругам!



Несколько модулей в проекте


Посмотрите, как выглядит проект из предыдущего раздела в окне Solution Explorer (Рис. 21.9).

Рис. 21.9

Solution Explorer предназначен для того, чтобы в удобной форме показывать нам составные части проекта, добавлять, переименовывать и удалять их. Информация в окне представлена в виде древовидной структуры, подобной Проводнику Windows. Она позволяет наглядно видеть вхождение одних компонентов проекта в другие. При этом не всегда те «папки», которые мы видим в окне, являются настоящими папками Windows.

Начнем просмотр снизу. Нижние строки отражают тот факт, что проект  WindowsApplication1 (он выделен на рисунке полужирным шрифтом) состоит из трех модулей: формы Form1, формы Form2 и стандартного модуля Module1.

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

Щелкая в окне Solution Explorer по нужному модулю, а затем (для форм) по одной из двух кнопок слева на панели окна (View Code и View Designer), мы можем удобно переключаться между различными окнами модулей.

Щелкая правой клавишей мыши в окне  Solution Explorer  по нужному модулю, а затем в открывшемся контекстном меню выбирая нужный пункт, вы можете этот модуль переименовывать (Rename), копировать (Copy), вырезать (Cut), удалять из проекта (Exclude From Project), удалять с диска (Delete), просматривать и изменять его свойства (Properties). Только не забывайте, что речь идет о файлах

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

Важное замечание: Переименовав форму (а не ее файл) в окне свойств формы, вы не сможете запустить проект, если он с этой формы стартует. Вам понадобится зайти в WindowsApplication1 Properties, как мы это делали в 21.1, и выбрать там имя формы.


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

Еще выше расположена «папка» References (Ссылки), внутри которой в нашем случае перечислены 5 пространств имен из библиотеки из библиотеки классов .NET Framework, которыми ваш проект имеет право пользоваться. Если вам скучно, сотрите оттуда какое-нибудь пространство имен, например System.Drawing, и ваш проект тут же разучится рисовать, зато будет меньше расходовать ресурсов компьютера. Если вы знаете, что в вашем компьютере где-то есть другие библиотеки, пусть даже не принадлежащие библиотеке классов .NET Framework, которые умеют делать что-нибудь интересное, щелкните правой клавишей мыши по «папке» References и добавьте нужную ссылку. После этого вы можете попытаться использовать их интересные возможности в своем проекте. Подробнее об этом написано в 25.2.2.

Второй справа на панели окна Solution Explorer на рисунке показана кнопка Show All Files. Она нужна для знатоков, которые хотят увидеть в окне Solution Explorer кроме всего прочего еще и многочисленные файлы, входящие в проект, и знать о которых начинающему программисту не обязательно.

Щелкнув правой клавишей мыши по строке проекта WindowsApplication1 (он выделен на рисунке полужирным шрифтом), вы можете его переименовать (Rename) и просматривать и изменять его свойства (Properties, см. Рис. 21.3).  В случае нашего проекта из двух форм и модуля, когда вы захотите установить стартовый объект, вам будет предложен выбор между формами Form1 и Form2, а если вы в модуле Module1 напишете процедуру Main, то в список для выбора добавится и Module1.


Нетипизированные файлы


Философия. Предположим, перед вами файл, о котором вы ничего не знаете. Вы даже не знаете, текст там записан или музыка, картинки иди видео, а может быть программа? Но вам хочется все-таки прочесть, что в нем есть. Вы пробуете открыть его в Блокноте, но видите только мешанину причудливых символов. Значит это не текстовый файл. Может быть это типизированный файл? Но чтобы его прочесть, нужно знать структуру записи, а вы не знаете. Может быть это картинка? Вы пробуете открыть его в графическом редакторе (20.9.2) – не получается. Может быть, это музыка или видео? Вы пробуете открыть его в нашем Плеере (3.10) – тоже не получается. Что же это? Вопрос не для новичка. Что-то может подсказать вам расширение. Если у вас есть опыт, то что-то может подсказать Блокнот или так называемый двоичный редактор.

Что мы знаем о файле, о котором ничего не знаем? Только то, что это длинная цепочка байтов, в которых создателем файла закодирована какая-то информация. Какая именно? – вот в чем проблема.

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

Что такое байт? Байт – это цепочка из 8 битов. Каждый бит – это элементарная компьютерная единичка информации, принимающая одно из двух значений. Принято условно обозначать эти значения, как 0 и 1. Таким образом, байт может иметь такой вид:

11000110

или, скажем, такой:

00000011

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

Попробуем поглядеть на код любого конкретного байта в файле. Пусть это будет

00100011

Можно ли, глядя на него, догадаться, что создатель файла закодировал им? Совершенно невозможно. Если он записывал целые числа типа Byte, то это число 35. Если он записывал строки или символы, то это половинка от какого-то символа в кодировке Unicode или целый символ  #  в кодировке ASCII. Если это картинки, музыка или видео, то для того, чтобы сказать, какой смысл имеет каждый отдельный байт файла, нужно знать применяемый в данном файле принцип кодировки картинок, музыки или видео. А это непростая задача – нужно хорошо разбираться в графике, музыке или видео. К тому же принципов кодировок много и они сложны.


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

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

        Dim а As Byte

и затем применить к файлу в цикле фрагмент

        FileGet(1, а)

        Debug.WriteLine(а)

в результате чего получим содержимое любого незнакомого файла, как цепочку чисел. Аналогично действуя, можно распечатать тот же самый файл, как цепочку символов. Спрашивается: что же хранится в этом файле – числа или символы? Вполне может быть, что ни то, ни другое, а что-то третье. Нужно только, чтобы кто-то нам сказал, что именно, и рассекретил принцип кодирования. После этого вся информация у нас в руках.

Если про файл известно, что он не является текстовым или типизированным, его называют нетипизированным.

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

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



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

Нетипизированные файлы, как и типизированные, являются файлами с произвольным доступом. Прыжки магнитной головки по файлу к нужному байту осуществляются при помощи метода Seek класса FileStream. Для непосредственной записи и чтения в нетипизированном файле нам понадобятся классы BinaryWriter и BinaryReader.

Записываем в нетипизированный файл массив байтов. Вот процедура для такой записи:

Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click

        Dim FS As New FileStream("E:\Папка\Файл.txt", FileMode.OpenOrCreate)

        Dim BW As New BinaryWriter(FS)

        Dim B() As Byte = {190, 47, 35, 205, 219}

        BW.Write(B)

        BW.Close()

        FS.Close()

End Sub

Пояснения: Сначала мы создаем объект FS класса FileStream. Он создается над файлом E:\Папка\Файл.txt. Второй параметр конструктора указывает, что этот файл можно открывать (Open), а в случае его отсутствия создавать (Create). Затем на основе объекта FS создается объект BW класса BinaryWriter для непосредственной записи в файл. Далее мы объявляем массив B типа Byte из 5 элементов. Затем при помощи метода Write класса BinaryWriter одним оператором записываем массив в файл. Там он располагается в цепочке из 5 байтов. После записи оба объекта закрываются.

Считываем из нетипизированного файла отдельные байты. Пусть байты в файле пронумерованы с 0. Тогда вот процедура считывания 4-го, а затем 2-го байта:

Private Sub Button4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button4.Click

        Dim FS As New FileStream("E:\Папка\Файл.txt", FileMode.Open)



        Dim BR As New BinaryReader(FS)

        Dim By As Byte

        FS.Seek(4, SeekOrigin.Begin)       : By = BR.ReadByte      : Debug.WriteLine(By)       : Debug.WriteLine(Chr(By))

        FS.Seek(2, SeekOrigin.Begin)       : By = BR.ReadByte      : Debug.WriteLine(By)       : Debug.WriteLine(Chr(By))

        BR.Close()

        FS.Close()

End Sub

Пусть файл создан и записан предыдущей процедурой (вот цепочка байтов: {190, 47, 35, 205, 219}). Тогда данная процедура распечатает вот что:

219

Ы

35

#

Пояснения: Второй параметр конструктора объекта FS указывает, что этот файл можно только открывать (Open). На основе объекта FS создается объект BR класса BinaryReader для чтения из файла. Далее мы объявляем переменную By типа Byte, в которую и будем считывать байты.

Затем работает метод Seek

класса FileStream. Он ставит считывающую головку на 4?й байт, если отсчитывать байты с начала файла, начиная с 0. То, что отсчитывать нужно именно с начала файла, указывает элемент Begin перечисления SeekOrigin.

Раз считывающая головка стоит на нужном байте, его можно прочесть. Делает это метод ReadByte класса BinaryReader. Считанный байт присваивается переменной By. Поскольку эта переменная имеет числовой тип Byte, следующий оператор распечатывает считанный байт как число. Следующий оператор распечатывает этот же байт, но уже в «маске» символа ASCII.

То же самое делает следующая четверка операторов, но уже не с 4-м, а со 2-м байтом файла.

После считывания оба объекта закрываются.

Записываем в нетипизированный файл отдельный байт. Пусть байты в файле пронумерованы с 0. Тогда вот процедура для записи во 2-й байт числа 39:

Private Sub Button5_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button5.Click

        Dim FS As New FileStream("E:\Папка\Файл.txt", FileMode.OpenOrCreate)

        Dim BW As New BinaryWriter(FS)

        Dim By As Byte = 39

        FS.Seek(2, SeekOrigin.Begin)

        BW.Write(By)

        BW.Close()

        FS.Close()

End Sub


Невидимый символ возврата каретки


Вы, наверное, удивитесь, если я скажу, что на самом деле в текстовом файле никаких строк нет. Вся информация в текстовом файле представляет собой одну непрерывную цепочку символов. А как же тогда работают методы ReadLine и WriteLine, которые имеют дело со строками файла? – спросите вы. Давайте разберемся.

Вернемся к 19.2.2Вот как на самом деле будет выглядеть информация в файле после выполнения четырех операторов Запись.WriteLine:

Азия5,2729.03.2005 21:30:0012

Каждое выполнение метода WriteLine записывает в файл не только положенную информацию, но последним символом в конец ее дописывает некий невидимый для нас управляющий символ, который я условно обозначил . Называется он «Перевод строки и возврат каретки» и название свое берет от тех древних времен, когда не было еще никаких мониторов и компьютер печатал информацию на автоматических пишущих машинках. Часто для краткости этот символ зовут просто «Возврат каретки» или «Перевод строки». В некотором смысле он является не одним, а парой символов: «Перевод строки» и «Возврат каретки», но мы не будем вдаваться в эти подробности. Для нас это – один символ.

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

Метод Write, в отличие от метода WriteLine, символ возврата каретки не дописывает.

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

Разместите на форме текстовое поле. Сделайте его многострочным (см. 3.4.2). При условии, что в файле Filimon.txt содержится описанная выше информация, запустите следующую учебную процедуру:

Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click


        Dim Чтение As New System.IO.StreamReader("E:\VB\Filimon.txt")

        Dim Строка, s As String

        Строка = Чтение.ReadLine

        s = Чтение.ReadToEnd

        Чтение.Close()

        TextBox1.Text = s

        Debug.WriteLine(s)

End Sub

Пояснения: Переменная Строка получает после выполнения оператора

        Строка = Чтение.ReadLine

значение "Азия”.

Переменная s  имеет строковый тип и поэтому получает после выполнения оператора

        s = Чтение.ReadToEnd

строковое значение оставшейся части файла:

5,27¶29.03.2005 21:30:00¶12¶

Как видите, в состав этой строки входят 3 символа возврата каретки.

Следующие два оператора

        TextBox1.Text = s

        Debug.WriteLine(s)

выводят эту строку в текстовое поле и в окно Output.

Вот как будет выглядеть окно Output:

5,27

29.03.2005 21:30:00

12

Точно так же будет выглядеть и текстовое поле.

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

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

А нам кажется, что строк было три.

Мы показали, что символ возврата каретки – это управляющий символ.

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

Символ возврата каретки является также значением константы vbNewLine из модуля Constants, входящего в пространство имен Microsoft.VisualBasic. Вы можете написать так:

        Dim s1 As String = "Азия"  &  vbNewLine  &  "Европа"

        TextBox1.Text = s1

        Dim s2 As String = "Пас"  &  vbNewLine  &  "Гол"  &  vbNewLine

 &  "Дисквалификация"

        Label1.Text = s2

Результат выполнения этого фрагмента вы видите на Рис. 19.1.



Рис. 19.1

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

Значение константы vbNewLine является комбинацией пары символов с номерами 13 и 10 в таблице ASCII. Поэтому можно было написать и так:

        TextBox1.Text = "Азия"  &  Chr(13) & Chr(10)  &  "Европа"


Nothing


Вы можете приказать переменной ссылочного типа, чтобы она ни на что не ссылалась. Делается это при помощи ключевого слова Nothing:

        B = Nothing

(Вы можете вообразить, что при этом в ячейке B вместо адреса 002040 возникает какой-нибудь невозможный адрес, например,  -99999.)

При выполнении следующего фрагмента

Dim B As New Класс

B.Поле= 5

B = Nothing

VB первым оператором отведет область памяти под объект, а третьим превратит ее в мусор (другими словами – освободит). Сборщик мусора VB в свой черед приберет мусор, чтобы в дальнейшем использовать освободившуюся область памяти для полезных дел. Естественно, переменной B вы больше не сможете пользоваться, пока снова не придадите ей нормальную ссылку, например, оператором B = A.

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

Обратите внимание, что для того, чтобы стать мусором, область памяти должна потерять все ссылки на себя. Рассмотрим пример:

        Dim A, B As Класс

        A = New Класс

        B = A

        A = Nothing

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

Когда дело касается массивов, то вы можете освободить память также оператором

        Erase  B



Области видимости


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

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

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

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

Public Class Class1

    Dim C As Integer

    Public

A As Integer

    Private

B As Integer

    Const M As Integer = 3

    Public Const

K As Integer = 1

    Private

Const L As Integer = 2

    Private Sub

Проц()

        Dim

C1 As Integer

    End Sub

    Sub Проц1()

        Const

L1 As Integer = 4

    End Sub

    Public Function Функц() As String

        Static

D As Integer

        Return "Привет"

    End Function

End Class

Мы различаем области видимости 5 уровней (в порядке увеличения охвата):

Блок внутри процедуры или функции

Процедура или функция

Модуль

Проект

Неограниченная область (элемент виден из своего и из других проектов)

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

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


Локальные переменные и константы. Тем, в какой области будет виден тот или иной элемент, мы можем управлять при помощи так называемых модификаторов доступа.  Модификаторов доступа всего 5: Public,

Private
, Protected,

Friend
,  Protected Friend. Кроме этого используется слово Dim.

Внутри процедур и функций переменные могут быть объявлены только при помощи знакомых нам слов Dim и Static, а константы только при помощи слова Const. Это знакомые нам блочная и локальная области видимости и знакомые нам локальные переменные и константы. Они видны только в той процедуре, функции или блоке, в которых объявлены.

Теперь поговорим об элементах, объявленных вне процедур и функций. Это и переменные, и константы, и процедуры, и функции, и модули. Их царство – три нижние области видимости. Они могут быть видны или только в том модуле, где они объявлены (независимо от того, насколько глубоко он угнездился внутри других модулей), или во всем проекте, или в неограниченной области в зависимости от того, при помощи каких слов это было сделано. Конкретно:

Область видимости – модуль. Слова Dim или Private для переменной и слова Const или Private Const для константы делают их видимыми только в своем модуле. Их область видимости – весь этот модуль, включая все процедуры, функции и другие модули (если они есть) внутри модуля. Это модульные переменные и константы.

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

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

Область видимости – проект, но не дальше. Если мы хотим, чтобы элемент VB был виден во всем проекте, но не дальше, мы объявляем его модификатором Friend. Часто программистам все равно – дальше или не дальше, и поэтому они употребляют вместо Friend более привычный модификатор Public, обеспечивающий неограниченную видимость



Неограниченная область видимости. Модификатор Public  делает элемент неограниченно видимым. Часто такие элементы называют глобальными.

Видимость по умолчанию. Если вы совсем уберете модификатор доступа к классам, структурам и модулям:

    Class Класс1

    End Class

они по умолчанию будут иметь доступ Friend.

Если вы совсем уберете модификатор доступа к процедуре или функции:

        Sub П4()

        End Sub

она по умолчанию будет иметь доступ Public.

Птичка в клетке. Имейте в виду, что элементу с неограниченной областью видимости не гарантирована эта неограниченная видимость, если он объявлен внутри элемента с ограниченной видимостью. Рассмотрим пример:

Public Class Form1

    Inherits System.Windows.Forms.Form

Windows Form Designer generated code

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        Класс.Структура.П4()

    End Sub

End Class

Public Class Класс

    Public Structure Структура

        Dim a As Integer

        Public Shared Sub П4()

            Debug.WriteLine("Сработала процедура П4 структуры Структура  класса Класс")

        End Sub

    End Structure

End Class

Здесь процедура П4 имеет неограниченную область видимости. Но стоит нам в строке

    Public Structure Структура

заменить Public на Private – и эта процедура вместе со всей структурой и ее компонентами станет недоступной из класса Form1. (На слово Shared пока не обращайте внимания.)

Область видимости – классы-наследники. Есть еще одна область видимости, которая не совпадает ни с одной из упомянутых и имеет отношение к наследованию классов. К ней относятся модификаторы Protected и Protected Friend. О Protected вы узнаете в 22.7.


Обработка ошибок Исключения Оператор Try


Здесь нас интересуют только исключения, то есть ошибки выполнения (см. 1.3.8).

Суть проблемы. Многие функции, процедуры и вообще операторы, которыми вы пользуетесь в VB, вовсе не обязаны при любых обстоятельствах успешно завершать свою работу. Например, пусть вы запустили процедуру, в которой встречается оператор копирования файла 13.txt  из папки c:\temp в папку c:\temp\222:

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        Dim a = 3

        File.Copy("c:\temp\13.txt", "c:\temp\222\13.txt")

        MsgBox("Ура! Файл скопирован!")

        Dim b = 8

End Sub

но забыли предварительно создать файл 13.txt в папке c:\temp. Метод File.Copy не может выполнить свою работу, VB выдает сообщение об ошибке и работа приложения прерывается. Говорят, что было выброшено исключение. Не в том смысле, что выброшено в мусорное ведро, а в том смысле, как выбрасывают лозунги, флаги, то есть выброшено из небытия в зону нашего внимания. Рядовой пользователь, работающий с вашим приложением, окажется в затруднительном положении. Он совсем не обязан разбираться в английском тексте сообщения, и если он даже догадается, в чем дело, и создаст нужный файл, все равно приложение надо будет запускать заново, так как оно прервано. Вы, как программист, должны учитывать, что с вашим приложением будут работать рядовые пользователи, причем даже не очень квалифицированные. Поэтому при программировании вы должны предугадать все возможные неправильные действия пользователя (за исключением выбрасывания компьютера в форточку), чтобы при таких действиях приложение, во-первых, не прерывалось, а во-вторых – выдавало вразумительное сообщение на русском языке и советы по выходу из затруднительной ситуации.

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


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

Вся обработка ошибки состоит в выполнении операторов, написанных нами между строками Catch и End Try. У меня обработка пока очень простая и не очень полезная. Сначала, как видите, выполняется оператор Beep(), чтобы привлечь внимание пользователя. Затем выдается сообщение об ошибке. Суть сообщения определяется свойством Message объекта ex. Если ошибка произошла из-за отсутствия файла 13.txt в папке c:\temp, то сообщение будет таким:



Рис. 19.2

Как видите, сообщение Message выводится на английском языке. Переводится оно так: «Не могла найти файл c:\temp\13.txt». Если вы знаете английский, то понимаете, в чем дело, нажимаете ОК, создаете файл, затем снова запускаете процедуру. Все в порядке.

Выводим собственные сообщения (по-русски). Ну а как насчет русского языка? Действительно, нас никто не заставляет выводить сообщение ex.Message. Давайте вместо

            MsgBox("При выполнении процедуры произошла следующая ошибка:   " & ex.Message)

напишем по-русски так:

            MsgBox("Ошибка: файл  c:\temp\13.txt  не существует. Создайте его и снова нажмите на кнопку.")

Вот это другое дело! Никакого английского и все понятно. И все бы хорошо, но тут нас ждет опасность. Дело в том, что при выполнении копирования файла причин у ошибки может быть несколько. Пусть, например, в папке 222 файл 13.txt уже есть, а вы снова запускаете процедуру копирования. Если вы заметили, наша File.Copy не допускает записи скопированного файла на место старого. Следовательно возникнет ошибка. Вот какое сообщение выдаст наш англоязычный MsgBox:



Рис. 19.3

что означает: «Файл  c:\temp\222\13.txt  уже существует». Новый же MsgBox обманет нас, на все случаи жизни заявляя, что он не может найти файл c:\temp\13.txt.

При моей простой обработке и не могло быть иначе. Чтобы справиться с ситуацией, усложним обработку ошибки. Вот что теперь мы поместим в процедуру между строками Catch и End Try:



Catch ex As Exception

    Beep()

    If Not File.Exists("c:\temp\13.txt") Then

        MsgBox("Ошибка: файл  c:\temp\13.txt  не существует. Создайте его и снова нажмите на кнопку.")

    ElseIf File.Exists("c:\temp\222\13.txt") Then

        MsgBox("Ошибка: файл  c:\temp\\222\13.txt  уже существует. Сотрите его и снова нажмите на кнопку.")

    Else

        MsgBox("Произошла непонятная ошибка при копировании")

    End If

End Try

Здесь мы использовали метод Exists класса File, который принимает значение True, если файл существует, и False – если нет. Поскольку кроме двух разобранных ошибок мыслимы и другие, неведомые для нас, я все же включил в оператор If  третью ветвь, в которой честно сознаюсь в своей некомпетентности.

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


Общие понятия


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

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

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

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

Вообразите, что вы собираете марки. Альбома у вас нет и вы храните марки в пакетиках. У вас есть огромный пакет, на котором написано «Марки».  Внутри него у вас находятся пакеты помельче с надписями «Российские марки», «Африканские марки» и т.д. В пакете «Российские марки» у вас находятся пакетики «Марки о спорте», «Марки о космосе» и т.д. Для чего вам все эти пакетики? Для того, чтобы легче было искать нужную марку.

На диске принята та же система. Только вместо слова «пакетик» мы говорим «папка» (раньше был в ходу другой термин – каталог). А вместо слова «марка» мы говорим «файл». Каждая папка тоже имеет имя. Типичная ситуация: На диске в ряду других папок есть папка «Программы», папка «Рисунки», папка «Переписка» и т.д. Внутри папки «Переписка» находятся две папки: «Деловая переписка» и «Личная переписка». Внутри папки «Личная переписка» находятся папки: «Переписка с Васей», «Переписка с Асей» и т.д. Внутри папки «Переписка с Асей» находятся файлы-документы, каждый из которых представляет собой письмо, например, файл с именем «Письмо Асе в Алушту в мае 2000 года».

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



Обычные и ссылочные типы


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



Окно Class View


Структуру решения или проекта удобно наблюдать в окне Class View. Откройте проект Проект и добавьте к решению проект Сборка. Затем View ® Class View.  Перед вами откроется окно Class View (Рис. 27.2), в котором вы можете щелчками по плюсикам развернуть всю структуру интересующих вас проектов. Здесь более наглядно, чем в Object Browser, видно, как входят друг в друга пространства имен и другие элементы.

Рис. 27.2



Основные понятия


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

алгоритмом. Пример программы я разобрал в 1.1 и если вы не читали тот подраздел, сейчас прочтите.

Для того, чтобы компьютер хоть что-нибудь умел, он должен иметь внутри себя программу этого умения. И наоборот, если компьютер что-нибудь умеет, это значит, что кто-то когда-то придумал программу этого умения и ввел ее в компьютер. Следовательно, если ваш компьютер умеет играть в игру «Quake», это значит, что внутри него находится программа этой игры, которую кто-то туда ввел. Разучится ваш компьютер играть в «Quake» только тогда, когда вы удалите программу этой игры из компьютера.

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

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

Как попадает программа в компьютер? Или с компакт-диска, или по сети, или ее туда вводит с клавиатуры программист.



Отлаживаем элемент управления пользователя


Если в работе кнопки что-то не заладилось, ее надо отлаживать. Для этого удобно создать решение из двух проектов: проекта вашей библиотеки и проекта приложения Windows, созданного для проверки. Как работать с решением из нескольких проектов, написано в 21.3.2. Сделайте приложение Windows стартовым проектом и расставьте точки прерывания в нужных местах кода обоих проектов. После этого приступайте к отладке, как это написано в Глава 9. .