Маргулев А.И.  Программирование на Visual Basic 5. – Информатика №№ 23, 28, 31, 1999

В начало сайта А.И.Маргулева

Программирование на Visual Basic 5
Опубликовано без приложений в "Информатике" №№ 23, 28, 31 за 1999 год

СОДЕРЖАНИЕ

ЯЗЫК БЕЗ ЛИШНИХ СЛОВ

Глава 1. ОБЪЕКТНЫЙ ПОДХОД Базовые элементы интерфейса Windows
Элементы управления как объекты программ Три составляющие Windows-приложения
Объект и его "паспорт"
Глава 2. ОСНОВЫ ПРОЕКТИРОВАНИЯ Интегрированная среда разработки
Важнейшие инструментальные окна
Проект "Калькулятор" Размещение элементов
Подготовка среды проектирования
Размещение элементов управления
Программирование набора операндов
Завершение проекта
А теперь работайте сами
Глава 3. РАБОТА С БАЗАМИ ДАННЫХ Проект "Словарной обучалки"
Немножко о базах данных
Элементы управления ListBox и ComboBox
Выбор и размещение элементов управления
Ну а если у нас "настоящая" база данных?
Отображение записей БД в списках
Создание файла БД и формы для его заполнения
Работа с базой данных "Словарной обучалки"
Работа с БД: о чем мы только упомянем
Глава 4. КОМПОНЕНТЫ ActiveX Пользовательский элемент управления ActiveX (OCX)
Компоненты программ ActiveX и технология OLE
Работа с серверными объектами в приложении-клиенте
Учимся создавать класс
Создаем компонент программ ActiveX EXE
Понятие об интерфейсах автоматизации
Полиморфизм и повторное использование кода
Создаем пользовательский элемент управления (ПЭУ) Формирование визуальной составляющей ПЭУ
Создание открытого интерфейса ПЭУ
Программирование функций ПЭУ Clock
Пример использования ПЭУ Clock-Alarm в приложении. Игра в “Крестики-Нолики” на время Подготовка к распространению
Глава 5. VISUAL BASIC 5 И INTERNET О чем пойдет речь
Создание простейшего приложения-броузера
Документы ActiveX Преобразование готового приложения в документ ActiveX
Создание программы установки для документа ActiveX
Создание документа ActiveX

ПРИЛОЖЕНИЯ

I. ТРАДИЦИОННОЕ ПРОГРАММИРОВАНИЕ Программа и данные
Структурное программирование
Типы данных
Массивы переменных
Преобразования типов данных
Области видимости, время жизни и именование переменных

II. ОПЕРАЦИИ
Арифметические операции
Операции сравнения
Операция конкатенации
Логические операции
Приоритет и ассоциативность операций
III. ИНСТРУКЦИИ Инструкции описаний и программных установок Const
Declare
Def
Dim
Enum
Function
Option Base
Option Compare
Option Explicit
Option Private Module
Private, Public
Static
Sub
Type
Инструкции управления ходом вычислений Call
Do ... Loop
End
Exit
For Each...In...Next
For...Next
GoSub...Return
GoTo
If...Then...Else
On Error
On...GoSub, On...GoTo
Select Case
Stop
While...Wend
Инструкции преобразований над данными в областях памяти Erase
Let (Присваивание)
Lset
Mid, MidB
Randomize
ReDim
Rset
Инструкции ввода-вывода данных Close
Get #
Input #
Line Input #
Open
Print #
Put
ReSet
SavePicture
Seek
Width #
Write #
ChDir
ChDrive
FileCopy
Kill
MkDir
Name
RmDir
SetAttr
Инструкции работы с процессами AppActivate
Lock и Unlock
Resume
Инструкции системных установок Date
DeleteSetting
SaveSetting
Time
Другие инструкции (методы-аналоги) Print
IV. КЛАВИШИ БЫСТРОГО ВЫЗОВА

ЯЗЫК БЕЗ ЛИШНИХ СЛОВ

Кто это говорил, что деревья - это всего-навсего недорезанные бревна? *
* Все эпиграфы в книге являются цитатами из произведений Венедикта Ерофеева

     Visual Basic 5 - это дружественный язык, позволяющий писать приложения "под Windows" (95(8) или NT) с профессионального уровня интерфейсом и без углубления в системные и инструментальные тонкости, как это требует использование языка С++. Именно с появлением этой версии языка Visual Basic скептицизм большинства профессиональных программистов свелся, по-существу, к "хорошей мине": существенно более высокая скорость создания приложений на Visual Basic 5 заставляет их все чаще прибегать именно к его услугам (прежде всего в задачах, связанных с базами данных).
     Особый интерес Visual Basic 5 представляет для тех, кто делает в программировании первые шаги, самостоятельно или в курсе обучения, позволяя уже с самого начала почувствовать вкус к созданию приложений с "профессиональным" интерфейсом, будь то калькулятор или редактор, броузер графических или звуковых файлов, мультимедийная игра или обучающая программа и т.д. Освоившись на уровне простейших приложений, начинающий программист может приступить к экспериментам по их переносу в документы ActiveX, обеспечивая возможность их инсталляции и запуска из броузера Internet. Потом ему захочется создавать собственные элементы управления и размещать их на своей Web-странице...
     Но не только эти особенности Visual Basic 5 делают его привлекательным для преподавания в школах, колледжах и вузах. Visual Basic 5 позволяет свести воедино "старый", математико-алгоритмический ("программирование - вторая грамотность") и "новый", информационно-технологический подходы к изучению информатики, которые до этого момента сосуществовали в едином курсе практически независимо друг от друга. Отказ от математико-алгоритмического подхода привел бы к редукции интеллектуально-логического аспекта обучения, в то время как отказ от изучения современных информационных технологий затруднил бы формирование основ общей информационной культуры. Visual Basic 5, во-первых, сам является таким же проводником объектно-ориентированной технологии Microsoft и идеологии совместно используемых ресурсов, как и MS Office 97 или Internet Explorer (вплоть до использования их компонентов), а с другой стороны - предоставляет структурированный (а также простой и удобный) язык записи и отладки алгоритмов, используемых как при создании приложений, так и для программирования в тех же офисных продуктах. То есть Visual Basic 5 дает учащемуся возможность взглянуть на информационные технологии "изнутри", в более интеллектуальном и творческом ключе.
     Именно с этой целью в проектах настоящего пособия рассматриваются такие вопросы, как COM ("Компонентно-Объектная Модель"), OLE-автоматизация, средства повторного использования кода, технология "клиент-сервер". Для облегчения самостоятельной работы обучаемого в приложениях книги впервые подробно изложено описание всех типов данных, операций и инструкций языка.
     Структура книги следующая.
     В первой главе излагаются принципы объектного и событийно-управляемого программирования в среде Visual Basic 5, описываются элементы сервиса, предлагаемого этой средой.
     Вторая глава на примере создания простого приложения рассматривает практическую реализацию этих принципов.
     В третьей главе на примере создания обучающей программы рассмотрена работа средствами Visual Basic 5 со списками и базами данных, включая запросы на SQL...
     Четвертая глава посвящена концепции COM, теории и практике повторного использования кода и OLE-автоматизации. Навыки обращения с объектами отрабатываются на примере создаваемого объекта "Стек". Здесь же создаются практически полезный пользовательский элемент управления "Часы-Будильник" и использующее его приложение.
     В пятой главе затронуты вопросы программирования для Internet.
     Наконец, в приложениях пособия изложены вопросы традиционного программирования, столь же актуальные и при событийно-управляемом подходе, однако остающиеся без должного освещения в существующей по Visual Basic литературе. К ним относятся представление и типы данных, принципы структурного программирования, описание операций и инструкций языка. Полнота изложения этих справочных сведений делает книгу полезной и для тех, кто уже имеет опыт программирования на Visual Basic 5.

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

Глава 1. ОБЪЕКТНЫЙ ПОДХОД

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

Базовые элементы интерфейса Windows

     В начале главы напомним некоторые базовые сведения, относящиеся к работе пользователя с любым приложением в операционной системе Windows(8). Иллюстраций приводить не будем, так как уже после двух десятков сеансов работы в этой графической системе можно без труда представлять, о каких именно объектах идет речь.
     Интерфейс - это набор средств взаимодействия программы с пользователем. Windows устанавливает единообразный графический интерфейс, в котором пользователь оперирует при помощи манипулятора “мышь”. Основные элементы интерфейса - меню и окна (приложений, документов и диалоговые). Предполагается, что пользователь имеет начальные навыки по работе с “мышью”: умеет указывать указателем мыши объекты экранного изображения и производить последующие операции "щелчок" или “клик”(однократное нажатие и отпу­скание клавиши), “двойной клик” (два щелчка с минимальным интервалом времени между ними), а также “дрэг энд дроп” или перетаскивание (“хватание” объекта на­жатием левой клавиши, перемещение схваченного объекта без отпускания клавиши и ее от­пускание при достижении требуемого места).
     Меню - это список объектов (как правило, тематически сгруппированных), из которых необходимо сделать выбор (помещая на объект указатель мыши и произведя однократный клик). Выбор пункта меню - это выполнение определенной команды программы. Все меню обладают общими свойствами:

     ¤   могут иметь несколько уровней, - когда при выборе указателем мыши пункта меню (имеющего после названия указатель “u”) открывается подменю этого пункта (меню следующего уровня, дочернее меню);

     ¤  могут иметь пункты недоступные для выбора в данный момент (выглядят “блеклыми” в данный момент);

     ¤  допускают возможность одновременного выбора нескольких объектов;

     ¤  могут иметь пункты (их названия оканчиваются на многоточие “...”), при выборе которых открываются диалоговые окна (см. ниже).
     Окно приложения (программы) - это элемент интерфейса, в котором выполняется любая запущенная на выполнение в операционной системе Windows прикладная программа (приложение). Открыть или закрыть окно приложения - то же, что и запустить программу на выполнение или завершить ее. Кнопки управления окном в его правом верхнем углу обеспечивают закрытие окна, а также режимы “полное окно”, “восстановить” (к некоему “исходному" размеру, который можно менять) и “свернуть” (в "пиктограмму" - кнопку со значком данного приложения на панели задач, рядом с кнопкой “Пуск” окна Windows). В незакрытом окне приложения (хотя бы и свернутом) всегда выполняется программа. Переход от одного выполняющегося приложения к другому (хотя бы и свернутому) осуществляется циклически по комбинации клавиш Alt+Tab; при этом свернутое окно восстанавливается. Заметим, что указанные кнопки управления окном дублируют соответствующие команды системного меню окна, открываемого щелчком мыши по иконке (синоним пиктограммы) окна, расположенной в его верхнем левом углу. Когда выполняется одновременно несколько приложений, только у одного из них окно (или пиктограмма!) активно, то есть воспринимает команды; оно располагается поверх остальных, и его заголовок выделен цветом.
     “Хватая” боковую или нижнюю рамки окна указателем мыши (он принимает при этом вид двунаправленной стрелки), мы можем менять его размер по горизонтали или вертикали (или оба размера одновременно, “ухватив” за правый нижний угол). Окно приложения всегда содержит зону заголовка (содержащую название приложения) и горизонтальное меню, являющееся главным меню приложения. “Схватив” окно за заголовок, мы можем передвигать его по экрану. При выборе почти каждого пункта главного меню появляется ниспадающее или падающее меню первого уровня. Окно документа - это элемент интерфейса, содержащий объект обработки приложения (данные). В полноэкранном варианте является частью окна соответствующего приложения. Всегда содержит зону заголовка (содержащую имя документа), часто - полосы прокрутки (появляющиеся, когда документ не помещается полностью в окне) и линейки. Открытое окно документа может находиться в активном, либо пассивном состояниях. Имеет те же кнопки управления, что и окно приложения, причем при свертывании сохраняет минимизированную зону заголовка и кнопки управления. Если окно находится в пассивном состоянии (зона заголовка не выделена цветом), то щелкнув по любой его части мышью, можно перевести его в активное состояние. Тот же переход позволяет, обычно, сделать команда, представляющая имя соответствующего окна из пункта главного меню Окно (в приложениях с многодокументальным интерфейсом). В приложениях других типов (с однодокументальным интерфейсом) нужное из открытых окон документов может быть активизировано в результате поиска циклическим переключением окон комбинацией клавиш Alt+Tab.
     Диалоговое окно (или просто диалог) выдается приложением и служит для ввода или установки параметров, представляющих данные для управления приложением. Оно содержит зону заголовка и различные элементы управления, поддерживающие диалог с пользователем. К ним, в основном, относятся:

     ¤  командные кнопки (надпись на которых поясняет их функции); например, кнопки с надписями OK позволяют закрыть диалог с сохранением настроек;

     ¤  переключатели или "флажки” (Check Box), представляющие квадратные окошечки, которые независимо друг от друга могут находиться в 2-х состояниях: пустом (режим, к которому относится флажок, выключен) и с “птичкой” (режим включен); переход из одного состояния в другое осуществляется щелчком мыши в окошечке;

     ¤  радиокнопки или поля выбора (Options), объединенные в группу альтернативных режимов; обозначаются кружочками, из которых один отмечен центральной точкой (выбранный режим), а остальные пусты; выбор нужного режима осуществляется щелчком мыши в соответствующем кружочке;

     ¤  списки (List), содержащие перечни каких-либо объектов и размещаемые в подокнах (имеющих полосу вертикальной прокрутки списка в окне, если оно не умещает весь список); для выбора объекта (пункта перечня) используется однократный щелчок мыши; иногда предусмотрен и двукратный щелчок (“двойной клик”) по объекту для вызова нового диалогового окна, в котором свойства данного объекта могут быть отредактированы (изменены).
     Частным случаем списка является раскрывающийся список, представляющий собой заголовочное окошко, содержащее выбранный в данный момент объект списка, и расположенную справа от окошка кнопку; щелчок по кнопке раскрывает список, повторный - закрывает; после выбора объекта щелчком мыши список закрывается и выбранный объект остается в его заголовке; - другим частным случаем списка является нераскрывающийся список значений с элементом управления “спин” (Spin) или "вороток", состоящим из пары кнопок с указателями направления прокрутки “вверх” и “вниз”; щелкая по кнопкам мышью, мы меняем текущие значения в окошке с заданными шагами;
     Еще одним вариантом списка является иерархический или дерево (Tree), примером которого являются списки с элементами файловой системы в Windows;

     ¤   поля ввода (Text Box), предназначенные для ввода символьных строк (чисел, текста); для начала редактирования указываем мышью позицию ввода и щелчком левой клавиши устанавливаем в нее курсор.
     С полем ввода может быть сопряжен список (какого-либо из приведенных вариантов), служащий для выбора требуемых для ввода значений (комбинированное поле ввода).
     Диалоговое окно может быть разбито на тематические подокна, снабженные вкладками (Tab); такие подокна располагаются каскадом (одно под другим), а их вкладки - мозаикой (рядом друг с другом), причем наверху находится подокно с выбранной мышью вкладкой. Такие трехмерные диалоговые окна напоминают картотеку.
     Существуют и другие элементы управления, причем их количество не предопределено. В какой-то момент подобные элементы управления мы начнем создавать и сами средствами Visual Basic 5.

Элементы управления как объекты программ

Три составляющие Windows-приложения

     Мы рассмотрели основные элементы интерфейса программ, работающих под управлением Windows, - "приложений Windows" (будем называть их просто "приложениями"). Их совокупность образует визуальную составляющую любого такого приложения. Система программирования VB, как и другие Visual-системы, позволяет формировать эту визуальную составляющую без написания кода, а просто средствами элементарного графического редактирования (компоновки). Не случайно такое программирование называют визуальным.
     Но сформированная визуальная составляющая должна еще определенным образом реагировать на те или иные события, происходящие вовне. К таковым можно отнести сигнал от мыши или клавиатуры, выполнение определенной команды центральным процессором, получение сообщения от другого приложения или другого окна данного приложения и т.д. Поэтому наряду с визуальной составляющей приложения имеется еще и другая его составляющая - системная.
     Дело в том, что элементы интерфейса - это просто видимые части тех "устройств", из которых, как из "кирпичиков", слагается любое приложение. Эти устройства принято называть объектами. Для изготовления таких объектов-"кирпичиков" в интегрированной среде VB используются "формочки" (шаблоны), каждая из которых определяет у изготавливаемых ею объектов "форму" (набор функциональных характеристик). В терминологии объектного программирования эти "формочки"-шаблоны называются классами. Забегая вперед отметим, что если считать объекты новой (наряду с числовой, символьной, логической и т.д.) категорией данных, то класс, по-существу, задает своим именем тип такого данного. Используя имя класса, можно, в дальнейшем, объявлять переменную, "значениями" которой будут объекты данного класса, - так же как имена целого типа Integer или строкового String используют для объявления обычных переменных, представляющих значения соответствующих категорий. (Разница лишь в том, что объектная переменная представляет не сам объект, а его адрес в оперативной памяти).
     Объект, таким образом, суть экземпляр объекта заданного класса. Но это слишком длинно. Сразу нужно привыкнуть к тому, что в одном контексте под "объектом" подразумевают именно экземпляр, а в другом - соответствующий класс. Держите в уме это обстоятельство!
     Приведем пример. Типичными объектами в VB являются элементы управления - такие, например, о которых мы упоминали описывая интерфейс Windows: поля ввода, списки, командные кнопки и т.д. В интегрированной среде VB эти объекты представлены своими классами, на которые ссылаются отображения этих элементов в специальном окне - палитре инструментов. В процессе визуального проектирования приложения мы будем в этой палитре выбирать мышью отображение того или иного элемента (выбирая, тем самым, ссылаемый этим отображением класс объекта) и размещать этот элемент в форме приложения уже как экземпляр объекта. Таких экземпляров мы можем разместить сколько угодно; все они будут различаться своими именами.
     В процессе функционирования приложения составляющие его объекты могут обмениваться сообщениями. Необходимость такого обмена возникает всякий раз, когда происходит то или иное значимое для объектов приложения событие. Эти события возникают как вовне приложения, так и в каком-либо его объекте. В последнем случае уместнее говорить не о возникновении события, а об его возбуждении объектом. Сообщение о произошедшем событии приходит в тот или иной объект от операционной системы. Именно системная составляющая приложения и определяет свойства каждого произошедшего события, формируя, далее, сообщение тому объекту, в котором предусмотрена реакция на это событие. Так, например, при нажатии на кнопку мыши именно системной составляющей приложения предстоит определить тот объект, которым данное нажатие должно управлять, и сформировать для него соответствующее сообщение. И то и другое будет зависеть, скорее всего, от взаимоположения указателя мыши и визуальных составляющих объектов (активного приложения) и их частей. Таким образом произойдет, скажем, полное раскрытие окна приложения. Или установка флажка в диалоге. Или выбор элемента списка. Или его прокрутка. Или что-нибудь еще, с чем хорошо знакомы все работающие в операционной системе Windows.
     Системная составляющая приложения скрыта от глаз прикладного программиста. Ее существование проявляется для него только в том, что активное приложение, чем бы оно не занималось, находится в любой момент времени еще и в состоянии ожидания какого-либо внешнего события. Для знакомых с программированием это выглядит так, как если бы приложение представляло собой обычную программу "под DOS", имеющую охватывающий цикл, в котором отслеживаются и обрабатываются внешние воздействия такие, например, как нажатия клавиш пользователем. Подобный цикл (обработки сообщений) существует и в самом деле, но не в той части приложения, которую создает пользователь, а в системной составляющей. И обрабатывает он не непосредственно сами события, а сообщения, сформированные на основе событий операционной системой и помещенные ею в очередь приложения, либо в единую системную очередь.
     Подведем некоторые итоги.
     С помощью объектов-элементов управления программист средствами визуальной компоновки создает визуальную составляющую приложения, и потому программирование на VB является визуальным. Кроме того, с помощью системной составляющей приложение через объекты управляется пользователем или другими приложениями посредством возбуждения и обработки событий, и потому программирование на VB является программированием с управлением по событиям. А это означает, что почти вся работа прикладного программиста на VB состоит просто в написании подпрограмм-обработчиков тех или иных событий. Обработчики событий являются, таким образом, третьей составляющей создаваемого на VB Windows-приложения. Их, в основном, мы и будем учиться писать.

Объект и его "паспорт"

     Мы уже сказали, что любое написанное на VB приложение - это просто набор устройств-объектов (Objects). Так же как, скажем, компьютер - набор съемных модулей - плат, приводов и т.д. Для каждого такого объекта, как и для модулей компьютера, можно составить описание-паспорт, в котором описать, во-первых, основные свойства объекта (Properties), во-вторых - события (Events), на которые объект может реагировать, в-третьих - способы реагирования (Perform Methods), представляющие собой отдельные действия, которые объект способен производить в качестве реакций на события (Respond Events). Сразу отметим, что традиционно способы реагирования, являющиеся внутренними процедурами VB, принято называть методами.
     Рассмотрим все три указанные понятия на на примере воздушного шарика из справочной подсистемы VB "Books Online" (если она была установлена при инсталляции системы VB).
     Открыв данную подсистему из пункта Help главного меню запущенной системы VB, либо из подменю группы "Microsoft Visual Basic 5.0" (Пуск=>Программы=>Microsoft Visual Basic 5.0=>Books Online), мы получаем слева - окно оглавления, справа - окно просмотра содержимого раздела. Откроем в оглавлении книгу "Forms, Controls and Menus" ("Формы, Элементы управления и Меню") и в ней - раздел "Understanding Properties, Methods and Events" ("Понятие о Свойствах, Методах и Событиях"). В разделе в качестве объекта рассматривается детский воздушный шарик (Balloon), наполненный гелием. Среди его свойств указываются высота (Height), диаметр поперечника (Diameter), цвет (Color), "надутость" (Inflated), возраст (Age). Событиями, вызывающими реакцию данного объекта, являются его протыкание (Puncture) и отпускание (Release). В качестве способов реагирования (методов) определяются характерный хлопок (MakeNoise "Bang"), выброс газа (Deflate) и подъем (Rise):

     Описанные характеристики объекта дают нам возможность программировать решение связанных с ним возможных задач. Вот как, например, будет выглядеть фрагмент установки (Set) указанных свойств объекта (кроме возраста):
     Balloon.Color = Red
     Balloon.Diameter = 10
     Balloon.Inflated = True

     Как мы видим, обращение к свойствам осуществляется по составным именам, в которых имени свойства предшествует имя объекта, отделяемое навигационной точкой (такая конструкция подобна обращению к элементу структуры по синтаксису языка C). А если бы наш объект Balloon входил в состав другого объекта - формы проекта, например, с именем Form1,
     Форма проекта служит заготовкой для окна проектируемого приложения; форма настраивается при проектировании интерфейса вашего приложения то из другой формы (Form2, например) обращение, допустим, к свойству Color нашего объекта производилось бы по составному имени Form1.Balloon.Color. В приведенных предложениях (Statements) гипотетической программы на Visual Basic справа от знака равенства указаны определенные значения из числа допустимых для соответствующих свойств. Значения имеют такие характеристики как тип и диапазон. Значения Red и 10 относятся, по-видимому, к целыму типу; разница между ними та, что первое представлено именованной, а второе - обычной константами. Значение True ("истина") является логическим; для свойства Inflated оно означает, что шарик действительно надут; противоположное значение False ("ложь") означало бы, что шарик сдут.
     Вызов методов нашего объекта может быть таким:
     Balloon.Inflate
     Balloon.Deflate
     Balloon.Rise 5

     Синтаксис вызова подобен обращению к свойству; разница лишь в том, что именем метода является глагол.
     А вот как может выглядеть подпрограмма-обработчик, вызываемая при возникновении определенного события - протыкания шарика, например:
     Sub Ballon_Puncture()
      Balloon.Deflate
      Balloon.MakeNoise "Bang"
      Balloon.Inflated = False
      Balloon.Diameter = 1
     End Sub

     В первом предложении подпрограммы вызывается метод Deflate, во втором - метод MakeNoise с аргументом "Bang", в третьем предложении свойство Inflated устанавливается в значение False, в четвертом - свойство Diameter устанавливается в 1.
     Имя подпрограммы-обработчика Balloon_Puncture является комбинацией соединенных через знак подчеркивания имен объекта (Balloon) и события (Puncture). Можно представить, что это экзотическое событие возникает в динамической компьютерной игре при соприкосновении экранного контура шарика с каким-либо "острым" контуром (т.е. с его сходящимся к точке элементом). Если же вместо этого для нашего шарика была бы предусмотрена возможность "хлопать" его кликом мыши, то имя приведенной подпрограммы должно было быть Ballooon_Click. Если были бы предусмотрены оба эти события, то должны были бы иметься две подпрограммы с разными именами и одинаковыми телами.
     Ну, а можно ли подпрограмму-обработчик вызывать из программного кода как обычную подпрограмму? Безусловно, причем синтаксис такого вызова может быть полностью идентичен синтаксису вызова метода (т.е. даже без ключевого слова Call и без круглых скобок).
     Действительно, и обработчик и метод представляют собой некоторые процедуры; разница лишь в том, что код метода мы не можем ни прочесть, ни поменять.
     В приведенном примере мы устанавливали свойства объекта в программном коде, т.е. в фазе выполнения программы (runtime mode). Большинство свойств можно также устанавливать и в фазе разработки (design mode), попутно с компоновкой объектов в экранной форме. Некоторые из свойств, однако, изменять в режиме выполнения нельзя (а можно только читать); другие, наоборот, доступны для установки только в режиме выполнения.
     Итак, паспортные данные объекта - свойства, события и методы - участвуют своими именами в создаваемом программистом коде. Неприятная неожиданность, если учесть, что эти имена сплошь и рядом включают в себя каждое по несколько слов! Поэтому спешим успокоить: никакой необходимости набирать эти имена или держать в голове нет. В окне редактора программного кода, под именем окна, располагаются два раскрывающихся списка: левый содержит имена всех объектов проекта (Project),

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

правый - имена всех событий, определенных для выбранного в левом списке объекта.
     В следующем примере для объекта с именем Dir1, являющегося списком директорий (папок), в правом раскрывающемся списке событий выбрано событие Change, наступающее при смене выделенной директории списка:

     Заметим, что всю информацию и о данном объекте, и о его событиях, а также свойствах и методах мы могли предварительно узнать выделив данный объект на форме приложения и войдя в режим помощи по F1.
     Выбрав из левого списка имя объекта, а из правого - имя события, мы получаем немедленно в поле окна заготовку соответствующей подпрограммы-обработчика, содержащую заголовок подпрограммы с правильно составленным ее именем.
     Пусть теперь при наборе, например, текста подпрограммы File1_Click мы хотим обратиться к свойству или методу некоторого определенного в данном проекте объекта Image1 (по его имени можно догадаться, что он представляет картинку в проектируемом приложении). Набрав "Image1." мы немедленно получаем окно списка, в котором перечислены все имена свойств (значок ) и методов ():

     Этот сервис называется Auto List Member (Автоматический список членов). Двойной клик на выбранном имени (или нажатие на клавишу после выделения) приводит к копированию его вслед за точкой в набираемое составное имя. Разумеется, можно осуществить и обычный набор нужного имени, игнорируя указанный список.
     Auto List Member - это лишь один из примеров дружественного сервиса, облегчающего жизнь программиста в системе VB. Существуют еще ряд подобных услуг системы с приставкой Auto: Complete Word (автодописывание служебного слова, когда набрано достаточное для его идентификации количество начинающих его символов; Data Tips (отображение значения переменной во всплывающем окошечке при помещении над ней указателя мыши); List Constant (открываемый список констант для свойств объекта). И, наконец, Auto Quick Info, представляющий собой вывод синтаксического шаблона обращения к методу, имя которого набрано (в ответ на пробел после этого имени), либо к встроенной функции VB (в ответ на открытие круглой скобки для записи аргументов вызова). Вот пример такой синтаксической подсказки для метода Move:

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

Глава 2. СНОВЫ ПРОЕКТИРОВАНИЯ

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

Интегрированная среда разработки

     Запустив Visual Basic 5.0 любым из известных вам способов, мы получим следующее окно New Project:

     Перед нами окно New Project с тремя вкладками ­- New, Existing и Recent. Верхняя вкладка New содержит пиктограммы различных типов проектов и сервисных модулей. Например Vb Application Wizard ("Мастер приложений") позволяет автоматизировать создание заготовки нового приложения. Вкладки Existing и Recent служат для открытия расположенных на накопителях существующих или только нескольких из последних модифицированных проектов соответственно. Возможности вкладок Existing и Recent сохраняются и после открытия выбранной пиктограммы Standart EXE на вкладке New (а именно этот вариант проекта ("стандартный") мы и будем использовать при создании наших приложений). Чтобы переходить сразу в режим создания стандартного проекта по умолчанию, после выбора пиктограммы Standart EXE и перед нажатием на кнопку "Открыть" (иногда ее имя Open) нужно установить флажок Don't show this dialog in the future ("Не показывать этот диалог в дальнейшем"). При этом, если в дальнейшем нам все же понадобится работать с окном New Project, то мы включим радиокнопку Prompt ("Запрос"), путь к которой из главного меню будет: Tools=>Options=>Environment=>Prompt. Ну а сейчас, после открытия стандартного проекта, мы увидим его главное окно следующего вида:

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

     Первая из них - строка заголовка. Мы уже знаем, что в ее левом конце располагается значок системного меню, позволяющего изменять геометрию окна и его местоположение, а также производить над ним те же действия, на которые способны и три известные нам кнопки изменения статуса окна (свертывания, максимизации/восстановления, закрытия) в правом конце строки. В центральной части строки располагается имя окна. Оно состоит из имени проекта (Project1), после которого через тире указана программная среда (Microsoft Visual Basic). Далее, выражением [design] указан текущий режим приложения - проектирование. В режиме выполнения проекта текст в квадратных скобках заменяется на [run]. Забегая вперед, отметим, что при максимизации активного окна документа с формой (заготовкой окна будущего приложения), область заголовка этого окна документа совмещается с областью заголовка главного окна и имя окна документа (в квадратных скобках) приписывается справа к имени главного окна через тире.
     Под строкой заголовка располагается строка главного меню. При максимизации активного окна документа с какой-либо формой в правый конец этой же строки переходят три кнопки изменения состояния указанного окна документа.
     Наконец, под строкой главного меню располагается стандартная панель инструментов. В ее левой части имеется двойной вертикальный рельеф, схватив за который мы можем "вырвать" эту панель из ее стандартного местоположения и разместить в любом другом месте экрана. Отсоединенная, эта панель получает собственную строку заголовка с именем Standard и кнопкой закрытия. Двойным кликом по этой строке можно вернуть панель на ее обычное место.
     Стандартная панель инструментов содержит пять групп пиктограмм команд, дублирующих наиболее "ходовые" команды, доступные через те или иные пункты главного меню. В правой же части этой панели располагается группа из двух координатных пар: положения (относительно левой и верхней внутренних границ главного окна в режиме выполнения) и размеров текущего (выделенного) объекта (Эта группа присутствует только при максимизированном статусе главного окна.) Когда проект только что открылся, таким объектом является расположенная в окне документа экранная форма будущего приложения с именем Form1.
     Координатные пары (у нас они 0, 0 и 4644, 3804) отображают значения следующих свойств выделенного объекта (Form1): Left (Левый край), Top (Верний край) Width (Ширина), Height (Высота) соответственно, выраженные в твипсах (twips) - условных единицах, равных 1/1440 дюйма на принтерной распечатке. Все эти параметры устанавливаются при проектировании в процессе компоновки формы будущего приложения и могут программно изменяться в процессе его работы. Положение на экране окна проектируемого приложения во время его выполнения устанавливается в специальном окне Form Layout (Внешний вид формы), о котором пойдет речь ниже.
     Перейдем к рассмотрению инструментальных окон, которые расположены на рабочей поверхности главного окна. Эти окна предоставляют нам те или иные инструментальные средства, используемые при проектировании приложения.

Важнейшие инструментальные окна


=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•

     ¤  Окно, на котором располагается множество пиктограмм различных элементов управления (рис. 2.1), называется Toolbox ("Палитра инструментов"). Выбрав щелчком мыши по соответствующей пиктограмме нужный элемент управления, мы устанавиваем его на форме проектируемого приложения. Установка заключается в том, что мы помещаем указатель мыши в предполагаемой точке расположения на форме левого верхнего угла элемента управления и, зафиксировав левую кнопку мыши в нажатом положении, тянем образующийся прямоугольный контур до предполагаемой точки расположения правого нижнего угла элемента, где и отпускаем. Другой способ состоит в выполнении двойного клика по пиктограмме элемента в палитре инструментов, после чего соответствующий элемент появляется в произвольном месте на активной форме, имея некоторые "стандартные" размеры. После этого, его положение на форме и размеры регулируются при помощи мыши обычным образом.



Рис. 2.1. Палитра инструментов Toolbox (слева) и пиктограмма управления ею на стандартной панели инструментов. О назначении пиктограммы при указании на нее мышью подсказывает надпись "Toolbox" в окне указателя (ToolTip)
     Сейчас самое время заметить, что в интегрированной среде VB5 активно используются контекстные меню, вызываемые кликом по объекту правой клавишей мыши (либо комбинацией Shift+F10). Сделав такой клик по палитре инструментов, мы получаем следующее меню:

     Горизонтальной чертой меню разделено на две группы. Верхняя имеет две команды. Components (Компоненты) отвечает за добавление в палитру инструментов новых элементов управления; она является дубликатом одноименной команды из пункта Project (Проект) главного меню и может выполняться по комбинации клавиш быстрого вызова (Shortcuts) Ctrl+T. По этой команде открывается окно компонентов, с которым нам еще предстоит работать при проектировании учебных приложений. Add Tab (Добавить вкладку) используется при нехватке места в палитре (и нежелании увеличивать ее размер) и заключается в создании именованных вкладок, на которых можно размещать дополнительные компоненты; первичная вкладка имеет, как мы видим, имя General (Главная). Нижняя группа предоставляет возможность убирать палитру инструментов с экрана, щелкая по состоянию Hide (Скрытая), причем того же достигают, щелкая по кнопке закрытия инструментального окна. Когда палитра на экране, галочкой отмечено ее состояния Dockable (Выставленная). Пункты Hide и Dockable имеются в контекстных меню и для ряда других типов окон.

     ¤  Окно документа, в котором располагается экранная форма проектируемого приложения, является окном формы. Оно имеет строку заголовка подобную по структуре строке заголовка главного окна. Имя окна состоит из имени проекта (у нас - Project1), к которому через тире приписано имя размещенной в нем формы (у нас - Form1); за этим именем в круглых скобках указан класс формы (Form).
     Каждой экранной форме соответствует входящий в проект одноименный файл с расширением .FRM. По умолчанию, при открытии нового проекта открывается одно окно экранной формы Form1; при надобности можно открывать дополнительные окна форм, имена которых, по умолчанию, Form2, Form3 и т.д. Все эти окна форм будут равноправны и независимы друг от друга. Интерфейс создаваемого приложения с такого типа независимыми окнами называется Single Document Interface - SDI ("однодокументальный интерфейс"). Бывают приложения и с другим типом интерфейса - Multiple Document Interface - MDI ("многодокументальный интерфейс"), в которых существует единственное главное ("родительское") окно, содержащее дочерние окна документов (как, например, в текстовом процессоре Microsoft Word или в самой интегрированной среде VB5). Созданием приложений с интерфейсом типа MDI мы заниматься не будем; научившись создавать однодокументальные приложения, мы, воспользовавшись услугами мастера приложений (VB Application Wizard), запускаемого из окна New Project (см. начало главы), без труда получим необходимую заготовку.
     А пока откроем еще одно окно формы в открытом нами SDI-проекте. Это можно сделать несколькими способами; мы воспользуемся самым очевидным - с помощью известной нам стандартной панели инструментов, как показано на рис. 2.2.



Рис. 2.2. Выбор пиктограммы Add Form (Добавить форму). Слева - диалог, открываемый при выполнении команды Add Form
Выполнив "Открыть" в появившемся диалоге с выделенной по умолчания пиктограммой Form, мы получаем в главном окне проекта новое окно формы Form2:

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

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


Рис. 2.3. Окно Проводника проекта (Project Explorer) (справа) и пиктограмма его вызова на стандартной панели инструментов
Собственно файл проекта, содержащий информацию о всех включенных в проект файлах, имеет расширение .VBP. Файлы экранных форм имеют расширение .FRM, модулей (отдельно хранимых текстов подпрограмм, не отвечающих за визуальную составляющую приложения) - .BAS. В нашем примере окно проводника проекта отображает файл проекта Project1.vbp и два файла форм - Form1.frm и Form2.frm. В проект для каждой формы может входить по одному файлу двоичных данных с расширением .FRX, хранящему свойства, представляющие картинки (Picture) и значки (Icon). Кроме того, проект может содержать файлы модулей классов (.CLS), управляющих элементов стандарта ActiveX (.OCX) и один файл ресурсов (.RES).
     Рассмотрим вторую сверху строку в изучаемом окне. В ней находятся 3 пиктограммы; подводя к ним указатель мыши, в окне указателя мы можем прочесть их названия. Левая пиктограмма называется View Code (Просмотр кода), средняя - View Object (Просмотр объекта), правая - Toggle Folders (Переключение папок). Первые две пиктограммы обеспечивают проектировщику приложения возможность перехода от его визуальной составляющей - к программной (т.е. к кодам обработчиков событий). Третья пиктограмма переключает режим отображения структуры файлов проекта в окне: в форме обычного списка, либо в виде дерева.
     Окно проводника проекта предназначено для быстрого доступа к той или иной экранной форме. Так, посредством двойного клика по имени содержащего экранную форму файла, мы активизируем ее и отображаем на экране поверх остальных. Щелкнув по пиктограмме просмотра кода (View Code), мы входим в окно текстового редактора кода VB5, обеспечивающее доступ к обработчикам событий для активной формы и содержащихся в ней управляющих элементов.
     Контекстное меню, вызываемое по щелчку правой клавишей внутри окна проводника проекта, обеспечивает быстрый доступ ко многим командам, дублирующим команды пунктов File и Project главного меню. Состав этого меню зависит от того, какой тип файла выделен в настоящий момент в списке окна: файл проекта или файл формы. Более полным является меню при выделенном файле проекта (для полноты картины показано также подчиненное меню команды Add):

     Пункт Set as Start Up (Установить как стартовый) имеет смысл в случае одновременной работы с несколькими проектами. Второй пункт (Project1 Properties...) вызывает диалоговое окно свойств проекта, дублируя соответствующую команду из пункта Project главного меню. Другие команды из пункта Project дублирует и команда Add с набором вариантов из подчиненного меню. Команда Print (Печать) позволяет распечатывать как код файла экранной формы, так и вид самой формы. Команды Print, Remove Project (Удалить проект) и Save Project (Сохранить проект) дублируют команды пункта File главного меню.

     ¤  Следующим важнейшим для работы окном является окно свойств (Properties Window). Оно предназначено для установки свойств объектов на стадии проектирования. Для его выставления можно использовать пиктограмму стандартной панели инструментов, соответствующую команду из пункта View главного меню, либо клавишу F4 (рис. 2.4).


Рис. 2.4. Выбор пиктограммы Properties Window (Окно свойств). Слева - Окно свойств. В верхнем, "выпадающем" списке выбираются объекты, а в нижнем - устанавливаются значения их свойств (открыто подчиненное меню со значениями одного из свойств)
Список свойств разделен на две колонки. В левой находятся имена свойств указанного в верхнем окошке объекта, а в правой - значения, установленные для этих свойств по умолчанию, либо программистом. Как видно из рисунка, для некоторых свойств предусмотрена возможность выбора из списка, раскрывающегося после клика по значку 6, появляющемуся в поле значения после выделения имени свойства.
     Список свойств присутствует на двух вкладках: Alphabetic (Алфавитный) и Categorized (По категориям), обозначающих принцип упорядочения списка свойств: по алфавиту, либо по категориям. Переключаться на вкладку Categorized может быть удобно тогда, когда имя свойства забыто, но известны его функциональные признаки. Отметим, наконец, что под списком свойств в окне Properties выводится краткое описание текущего выделенного свойства.

     ¤  Последним из инструментальных окон является окно Form Layout (Внешний вид формы), служащее для задания положения экранной формы на экране при его загрузке (Load) в фазе выполнения проекта (рис. 2.5).


Рис. 2.5. Выбор пиктограммы Form Layout Window (Окно внешнего вида). Слева - размещение двух форм с помощью этого Окна на экране монитора
Размеры формы устанавливаются в окне формы перетаскиванием размерных маркеров (темных квадратиков), расположенных на правой и нижней границах формы. А вот задавать положение формы с установленными размерами на экране во время выполнения можно, вызывая окно Form Layout и перетаскивая форму мышью внутри стилизованного изображения экрана монитора. Для точной установки формы в центре экрана можно вызвать контекстное меню окна и в подчиненном меню пункта Startup Position (Стартовая позиция) выбрать значение Center Screen (Центр экрана). Альтернативными значениями являются: Manual (Задавать вручную), Center Owner (По центру в области приложения) и Windows Default (По умолчанию - форма в верхнем левом углу экрана).
=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•
     С основными инструментальными окнами мы ознакомились. Займемся теперь проектированием нашего первого приложения.

Проект "Калькулятор"

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

Размещение элементов

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

На ней имеются два поля с именами Операнд 1 и Операнд 2, предназначенные для ввода операндов; имеются поля отображения выполняемой операции и результата (Оператор и Результат соответственно); имеются восемь командных кнопок со стандартным для калькуляторов назначением, а также кнопка "Выход" для окончания работы.
     Сценарий работы пользователя с калькулятором таков: в полях ввода вводится необходимое для операции число операндов, после чего кликом по командной кнопке выполняется требуемая команда, отображаемая в поле оператора; в поле результата читается результат операции. Если результат должен, далее, участвовать как операнд в следующей операции, то кликом мыши в поле результата мы копируем его в поле первого операнда.
     Перейдем непосредственно к проектированию интерфейса программы.

Подготовка среды проектирования

     Открыв новый проект, установим все нужные нам инструментальные окна (без окна Form Layout) так, чтобы форму можно было растянуть, не "налезая" на них, как можно больше, сохраняя, приблизительно, пропорции нашей схемы. Получается форма размером, примерно, 106Х77 мм. Обратим внимание на точечную сетку, которой покрыта форма. Ее назначение - помогать проектировщику выравнивать элементы управления и их части по горизонтали и вертикали. Настройка сетки производится на вкладке General (Общее), путь на которую Tools=>Options=>General:

     На этой вкладке в области Form Grid Settings (Параметры сетки формы) мы установим вдвое более мелкий, по сравнению со стандартным 120Х120, шаг сетки (Grid Units); включенный переключатель Align Controls to Grid означает, что границы элементов управления будут "притягиваться" к параллельным рядам точек сетки, оказавшись вблизи них.
     Изменим имя нашего проекта с "Project1" на "Калькулятор". Для этого щелкнем по файлу Project1.vbp в окне проекта и введем для его единственного свойства (Name) в окне свойств новое имя. Щелкнем по форме (или по файлу Form1.frm в окне проекта) и изменим имя формы (свойство Name) на frmCalc, а надпись (свойство Caption), которая указывается в заголовке окна формы - на "Калькулятор". Получим следующий исходный вид экрана перед началом проектирования:

Размещение элементов управления


=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•

     ¤  Разместим (двойным щелчком и последующим перемещением) поля ввода операндов (элемент панели инструментов TextBox: ). Сначала установим поле ввода первого операнда, отрегулируем его размеры. Элемент получает по умолчанию имя Text1. Чтобы не регулировать размеры равного ему поля ввода второго операнда, создадим последний посредством копирования первого в буфер обмена (Clipboard), что дает возможность вставки из него любого числа данных копий.
     Войдя на выделенном поле в контекстное меню (щелчок правой клавишей), выберем команду Copy. Затем, войдя повторно в контекстное меню, выберем Paste ("вставить") (вместо контекстного меню можно пользоваться привычными комбинациями Ctrl+C и Ctrl+V соответственно). Появляется диалог с запросом: "You already have a control "Text1". Do you want to create a control array?" ("У вас уже есть элемент "Text1". Вы хотите создать массив элементов?") - и вариантами ответов: "Да" и "Нет". Выбрав "Нет", мы получаем копию элемента Text1 с именем Text2 (убедитесь в этом с помощью окна свойств) и той же надписью (свойство Caption) внутри поля.

     ¤  Создание программы - это итеративный процесс, когда на некотором этапе приходится возвращаться назад и корректировать уже спроектированное. Почему, например, мы выбрали вариант копирования, а не создания массива элементов (т.е. элементов, к которым обращаются по единому имени, но со своим индексом, записываемым после имени в круглых скобках)? Нам показалось, что раз элементов будет всего два, никаких преимуществ в представлении их элементами массива нет. В дальнейшем, однако, выяснилось, что это не так. Оказалось, что вводя переменную i, принимающую два значения: 0 - если последним активизировалось ("получало фокус") поле ввода для первого операнда и 1 - если для второго, - мы можем написать единый обработчик на каждое событие, действительное для обоих полей, - определяя переменной i, используемой как индекс, именно на то из этих полей ввода, которое и должно обрабатываться в данный момент при возникновении события. С учетом этого, удалим скопированный элемент (выделяя и нажимая Delete) и повторим описанный процесс впоть до запроса на создание массива, где выберем уже ответом "Да". После этого на форме, точно так же как и в предыдущем варианте, появится второе поле ввода с надписью Text1, но заглянув в список элементов окна свойств, мы обнаружим, что вместо элементов Text1 и Text2 у нас появились элементы Text1(0) и Text1(1). По соглашению об именах объектов (введенному фирмой Microsoft), изменим, пользуясь свойством (Name) в окне свойств, общее имя Text1 этих элементов на txtOp; надписи (значения свойства Caption) "Text1" удалим.

     ¤  Разместим блок из 12 командных кнопок (элемент панели инструментов CommandButton: ), из которых 10 будут отвечать за ввод в активное поле ввода цифр (от 0 до 9), 11-ая - за ввод точки, отделяющей дробную часть от целой (если в системе для этой цели используется запятая, то нужно либо поменять данную установку - на вкладке "Числа" раздела "Язык и стандарты" Панели управления Windows 95, - либо самим использовать запятую), и 12-ая - за изменение знака введенного числа. Первые 11 из указанных кнопок создадим в форме массива cmdNum (индекс меняется от 0 до 10), 12-ую - отдельным элементом с именем cmdOpposit. В качестве надписей для первых 11-ти кнопок будут введены соответствующие им вводимые символы, для кнопки изменения знака - надпись "-\+".

     ¤  Разместим посредством копирования 12 командных кнопок для выполнения операций как бинарных (для двух операндов) - аддитивных ("+", "-"), мультипликативных ("*", "/"), возведения в степень (ab), вычисления остатка от деления (mod), так и операций вычисления функций от активного (получавшего фокус последним) операнда (функции sin, cos, Цa, a2, a3, 1/a). Для каждой из кнопок в окне свойств изменим имена объектов (на cmdPlus, cmdMinus, cmdMult, cmdDevide, cmdPower, cmdMod, cmd Sin, cmdCos, cmdRoot, cmdSquare, cmdCobe, cmdReverse соответственно) и установим для них соответствующие математическим обозначениям их операций надписи (вместо надписей вида ab мы вынуждены писать a^b).

     ¤  Каждую из описанных групп командных кнопок возьмем в рамку (элемент панели инструментов Frame: ).

     ¤  Установим 6 ярлыков (элемент панели инструментов Label: ). Ярлыки используются как для создания постоянных надписей на форме, так и для вывода данных в фазе работы программы. Так, два из наших ярлыков будут служить для вывода результата (lblRes) и знака выполненной бинарной операции (lblOper). Их надписи устанавливают в фазе проекта пустыми. Кроме того, для большей выразительности мы сделаем наши ярлыки "утопленными", установив свойство BorderStyle ("стиль границы") в "1 - Fixed Single" ("Фиксированная одиночная"). Три оставшихся ярлыка создадут постоянные надписи "Операнд 1" (lblOp1), "Операнд 2" (lblOp2) и "Результат" (lblResult) - над соответствующими полями ввода и под ярлыком lblRes. Изменим свойство Alignment ("выравнивание") для надписей у этих ярлыков на "2 - Center" ("центральное"). Последний ярлык, невидимый в фазе проекта (т.е. с пустой надписью и без "утопления"), установим над группой операционных кнопок. Назначение этого ярлыка lblMes - выводить предупреждающие сообщения при попытках некорректных вычислений (деления на 0, например).

     ¤  Для стилизации под вычисление "столбиком" отделим операнды от результата горизонтальной линией (элемент панели инструментов Line: ).

=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•

     Начальный вариант проекта Калькулятор готов. Для более удобного (чем щелчок указателем мыши по маленькой кнопке закрытия окна) выхода из программы создадим еще командную кнопку cmdExit с надписью "Выход". Уберем ненужную теперь сетку, выключив опцию Show Grid на рассмотренной выше вкладке. Спроектированная форма нашего приложения имеет следующий вид:

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

Программирование набора операндов

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

     Устройство этого окна мы уже разбирали в предыдущей главе. Так, под строкой заголовка мы видим два раскрывающихся списка: в левом - элементы управления (они представлены своими "экземплярными" именами), а также форма, которая представлена своим типовым именем Form), в правом ­- события, определенные для выбранного элемента или формы. Под списками расположено окно редактирования кода, причем курсор располагается внутри кода того обработчика, которым обрабатывается выбранное в правом списке событие.
     В левом списке первым пунктом всегда является General, обозначающий секцию общих объявлений (General Declaration section) нашего программного модуля (представляющего, в данном случае, файл с текстом программного модуля экранной формы). В этой секции объявляются переменные уровня модуля (Private), либо общедоступные (Public).
     Термин "переменная" нами еще не использовался, и его нужно определить (подробности - в Приложении I). Ведь до сих пор мы говорили только об объектах - элементах управления, имеющих свойства и методы. В подглаве "Объект и его "паспорт" мы видели, как свойствам объекта можно присваивать числовые, логические или текстовые значения с помощью констант - самоопределенных данных, представляющих в программе именно то значение, которое выражено их записью. А вот переменные - это такие элементы программы, которые записываются с помощью имен-идентификаторов, а представлять в программе могут разные значения, которые тем или иным способом им присваиваются (см., например, инструкцию Let в Приложении III). В какой-то степени свойства - это частный случай переменных, существующих только вместе с объектом и имеющих заданные создателями этих объектов имена. Но также как в VB5 есть необходимость в процедурах, не являющихся обработчиками, так в нем есть нужда и в именованных величинах, не связанных с какими-либо объектами. Их и принято называть "переменными" (сказанное не относится, разумеется, к уже упоминавшимся объектным переменным, хранящим адреса объектов).

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

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

     Dim Имя_переменной As Имя_типа


     Здесь на месте элементов, названных с помощью русского шрифта, в "настоящем" объявлении стоят конкретные имена переменных и требуемые имена типов, например:

     Dim i As Integer 'объявляется переменная i целого типа
     Dim s As String 'объявляется переменная s типа строки символов


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

     Dim a(500) As Integer


- обращаясь, далее, в программе к конкретному элементу массива ("индексированной переменной") с помощью точно такого же индекса:

     a(i) = a(i)+1 'Увеличили i-ый элемент массива на 1


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

     j = a(80*(m-1)+n) 'В скобках после а - индексное выражение


     При обращении к индексированной переменной стоящее на месте ее индекса арифметическое выражение вычисляется и приводится, если оно не целое, к целому числу путем округления дробной части; это и будет номером элемента в массиве, к которому осуществляется обращение (подробности - в Приложении I).
     Что же необходимо поместить нам в секцию General?
     Как уже было намечено при размещении на форме полей ввода операндов нашего калькулятора, представляющих элементы массива из двух элементов (индекс первого - 0, второго - 1), мы должны иметь переменную, которая все время работы нашей программы должна сохранять индекс того элемента - поля ввода, которое последним получало фокус. Кроме того, эта переменная должна быть "видна" в любом обработчике. Оба эти условия (время жизни и область видимости) обеспечиваются объявлением данной переменной не внутри какой-либо процедуры, а в секции общих объявлений в "самом верху" нашего программного модуля: Dim i As Integer      Перейдем теперь непосредственно к программированию набора операндов.
     Собственно, поля ввода именно и служат для того, чтобы этот ввод вообще не нужно было программировать. Так, если мы установим курсор в поле ввода (возьмем этот объект тем самым "в фокус") и начнем набор символов на клавиатуре, то набираемая строка на каждом шаге набора будет значением свойства Text ("текст") поля ввода, и в качестве этого значения будет в поле ввода отображаться. Но все это будет возможно при вводе данных с клавиатуры. Мы же хотим создать калькулятор, в котором все управление, а значит и набор операндов, осуществляется мышью - как в стандартном калькуляторе, входящем в состав Windows. Для этого-то мы и вводили кнопки с цифрами и десятичной точкой. Значит нам нужно самим запрограммировать обработчик "нажатий" на эти кнопки мышью так, чтобы при каждом нажатии изображенный на кнопке символ "подцеплялся" к строке, являющейся значением свойства Text.
     Вернемся через кнопку View Object (Просмотр объекта) окна проекта к форме и сделаем двойной щелчок по любой из перечисленных кнопок. Перед нами возникнет следующая заготовка обработчика: Private Sub cmdNum_Click(Index As Integer)
End Sub
представляющая собой "процедурные скобки": строку заголовка процедуры и строку ее завершения. Тело процедуры пока пусто; эту пустоту мы и должны заполнить нужным нам кодом.
     Подцепление символа производится с помощью одной из двух операций: "+" или "&" (перед значком "&" - "амперсандом" - нужно обязательно самим ставить пробел!). Таким образом, наш обработчик может быть таков: Private Sub cmdNum_Click(Index As Integer)
  If Index < 10 Then
    txtOp(i).Text = txtOp(i).Text & Index 'Приписываем цифру,
  Else
    txtOp(i).Text = txtOp(i).Text & "." 'или точку
  End If
End Sub
     Несмотря на свою простоту, текст приведенного обработчика нуждается в ряде замечаний.

=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•

     ¤  Переменная Index по своему смыслу является параметром, то есть такой переменной, которая получает значение в момент вызова процедуры. В данном случае вызов происходит в момент возникновения события Click при щелчке мышью по кнопке, являющейся элементом массива объектов - командных кнопок cmdNum. Эти элементы идентифицируются значениями индексов от 0 (кнопка с цифрой "0") до 10 (кнопка с точкой); индексы цифровых кнопок равны изображенным на них цифрам. Именно значения этих индексов и передаются параметру Index данного обработчика, единого для всех кнопок - элементов массива, указывая таким образом обработчику, к какому именно элементу относится произошедшее событие Click, а следовательно, какую именно цифру следует присоединять к значению свойства Text.
     Переменная Index объявлена внутри процедуры и потому является локальной; ее область видимости - только текст данного обработчика. Время ее жизни - от момента вызова обработчика до окончания его работы (автоматическая переменная). Любая локальная переменная является автоматической, если она не объявлена с ключевым словом Static, либо если с этим словом не объявлена процедура, содержащая объявление этой переменной. В обоих этих случаях локальная переменная будет статической, т.е. сохраняющей свои значения между вызовами процедуры. Объявление параметра, как мы видим, производится не так, как мы рассказали выше, а прямо в круглых скобках после имени обработчика. Тем самым одновременно указаывается и то, что эта переменная является параметром, и ее тип (Integer).

     ¤  В данном обработчике использована управляющая конструкция, называемая ветвление. Ведь нам надо присоединять к тексту в поле ввода либо символьное представление величины Index, если Index < 10, либо точку (символ "."). Мы так и записываем:
     Если Index < 10 То
      txtOp(i).Text = txtOp(i).Text & Index
     Иначе
      txtOp(i).Text = txtOp(i).Text & "."
На языке Visual Basic это предложение записывают тем же самым образом, но ключевые слова Если, То, Иначе пишутся по-английски: If, Then, Else. Кроме того, мы должны как-то указать окончание второй ветви (после Иначе). В естественном языке для этого мы используем точку. В тексте программы на VB используют словосочетание Конец Если (End If).

     ¤  Ключевое слово Private ("закрытая") означает, что данную процедуру можно вызвать по имени только из содержащего ее программного модуля (файла). Без этого слова, а также с ключевым словом Public ("общедоступная") процедура доступна и из других модулей проекта. Оба эти слова можно использовать и в объявлениях переменных вместо слова Dim; по умолчанию закрытой является локальная переменная, а общедоступной - объявленная на уровне модуля (секция General).

     ¤  Обратите внимание: "умный" компилятор понимает, что мы хотим к символьной величине Text присоединить именно символ, являющийся десятичным изображением числа Index. То есть происходит автоматическое преобразование величины целого типа к величине символьного. Если бы этого не происходило, то подцеплялся бы не символ "5" (имеющий код по кодовой таблице 53), а псевдосимвол, имеющий по кодовой таблице код 5).

     ¤  Мы неспроста решили отделять дробную часть вводимого числа точкой, а не запятой, что, в принципе, допускается настройкой Windows. Если, однако, у вас настройкой предусмотрена именно запятая, то поменять надо именно настройку (во избежании очень неприятных неожиданностей при работе с некоторыми функциями, например, Val)! Для этого из окна "Панели управления" надо войти в окно "Язык и стандарты" и на вкладке "Числа" поменять разделитель целой и дробной части с запятой на точку. Проконтролируйте это заранее!

=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•

Завершение проекта

     Набор операндов мышью в полях ввода txtOp(i), где i принимает значения 0 или 1, мы запрограммировали. Осталось, по-существу, запрограммировать вычисление результатов операций при нажатии на каждую из операционных кнопок (событие Click) и вывод результатов либо в активное поле ввода (если операция имеет один операнд: обратная величина, противоположная величина, квадрат, куб операнда и квадратный корень из него, другие функции одной переменной от операнда), либо в ярлыке результата lblRes для бинарных операций (сложение, вычитание, умножение, деление, возведение в степень, остаток от деления). Учтем, что для бинарных операций их знак должен отображаться ярлыком lblOper. Наконец, предусмотрим такую ситуацию: нас что-то отвлекло во время многоэтапных вычислениий, а когда мы вернулись к ним, то не помним, является ли число в ярлыке результата действительно результатом отображаемой ярлыком lblOper операции над теми операндами, которые находятся в данный момент в полях ввода, - или какой-то из этих операндов мы успели изменить для последующих вычислений. Поэтому запрограммируем изменение фона (свойство BackColor) у ярлыков lblOper и lblRes при выполнении бинарной операции и восстановление у них фона при изменении содержимого любого поля ввода (событие Change). Таким образом, действителен будет только тот результат, который расположен на измененном фоне ярлыка.
     Программный код для обработчиков нажатие следующий: Dim i As Integer ' - Секция общих объявлений (деклараций)
Private Sub cmdCos_Click() 'Косинус
  txtOp(i).Text = Cos(Val(txtOp(i).Text))
End Sub
Private Sub cmdCube_Click() 'Возведение в куб
  txtOp(i).Text = Val(txtOp(i).Text) ^ 3
End Sub
Private Sub cmdDevide_Click() 'Деление
  If Val(txtOp(1).Text) <> 0 Then
    lblRes.Caption = Val(txtOp(0).Text) / Val(txtOp(1).Text)
  Else
    lblMes.Caption = "Делить на 0 не умею!!!"
  End If
  lblRes.BackColor = &H80000009
  lblOper.Caption = "/"
  lblOper.BackColor = &H80000009
End Sub
Private Sub cmdMinus_Click() 'Вычитание
  lblRes.Caption = Val(txtOp(0).Text) - Val(txtOp(1).Text)
  lblRes.BackColor = &H80000009
  lblOper.Caption = "-"
  lblOper.BackColor = &H80000009
End Sub
Private Sub cmdMod_Click() 'Остаток от деления
  lblRes.Caption = Val(txtOp(0).Text) Mod Val(txtOp(1).Text)
  lblRes.BackColor = &H80000009
  lblOper.Caption = "mod"
  lblOper.BackColor = &H80000009
End Sub
Private Sub cmdMult_Click() 'Умножение
  lblRes.Caption = Val(txtOp(0).Text) * Val(txtOp(1).Text)
  lblRes.BackColor = &H80000009
  lblOper.Caption = "*"
  lblOper.BackColor = &H80000009
End Sub
Private Sub cmdOpposit_Click() 'Противоположное
  txtOp(i).Text = -Val(txtOp(i).Text)
End Sub
Private Sub cmdPlus_Click() 'Сложение
  lblRes.Caption = Val(txtOp(0).Text) + Val(txtOp(1).Text)
  lblRes.BackColor = &H80000009
  lblOper.Caption = "+"
  lblOper.BackColor = &H80000009
End Sub
Private Sub cmdPower_Click() 'Возведение в степень
  If Val(txtOp(0).Text) = 0 And Val(txtOp(1).Text) <= 0 Then
    lblMes.Caption = "Думай, что делаешь!"
  Else
    lblRes.Caption = Val(txtOp(0).Text) ^ Val(txtOp(1).Text)
    lblRes.BackColor = &H80000009
    lblOper.Caption = "^"
    lblOper.BackColor = &H80000009
  End If
End Sub
Private Sub cmdReverse_Click() 'Обратное
  If Val(txtOp(i).Text) <> 0 Then
    txtOp(i).Text = 1 / Val(txtOp(i).Text)
  Else
    lblMes.Caption = "Делить на 0 не умею!!!"
  End If
End Sub
Private Sub cmdRoot_Click() 'Квадратный корень
  If Val(txtOp(i).Text) < 0 Then
    lblMes.Caption = "Корень из отрицательного - это круто!"
  Else
    txtOp(i).Text = Sqr(Val(txtOp(i).Text))
  End If
End Sub
Private Sub cmdSin_Click() 'Синус
  txtOp(i).Text = Sin(Val(txtOp(i).Text))
End Sub
Private Sub cmdSquare_Click() 'Возведение в квадрат
  txtOp(i).Text = Val(txtOp(i).Text) ^ 2
End Sub
     В последнем фрагменте мы оформляем присвоение начального значения глобальной переменной i, хранящей индекс поля ввода, бывшего в фокусе последним. Начальные присвоения делаются обычно при загрузке формы (событие Load): Private Sub Form_Load() 'Загрузка формы
  i = 0 'Присвоение начального значения переменной i
End Sub
В процессе же работы калькулятора значения этой переменной будут присваиваться при каждом получении фокуса тем или иным полем ввода (а возможные предупреждающие сообщения от предыдущей операции гасятся): Private Sub txtOp_GotFocus(Index As Integer) 'Получение
  lblMes.Caption = "" 'фокуса полем ввода
  i = Index 'Запоминаем в i индекс поля ввода, получавшего фокус последним
End Sub
     При любых изменениях в полях ввода восстанавливается первоначальный цвет фона у ярлыков операции и результата: Private Sub txtOp_Change(Index As Integer) 'Изменение поля
  lblOper.BackColor = &H8000000A 'ввода
  lblRes.BackColor = &H8000000A
End Sub
     Для быстрой очистки полей ввода мы используем двойной клик: Private Sub txtOp_DblClick(Index As Integer) 'Очистка поля
  txtOp(i).Text = "" 'ввода End Sub
     И, наконец, запрограммируем кнопку выхода (хотя всегда есть возможность выйти путем закрытия окна программы стандартным средством управления окном): Private Sub cmdExit_Click()
  End
End Sub
     Первая версия калькулятора создана! В ней всего 60 строк кода, введенного нами с клавиатуры.
     Запустим нашу задачу в среде VB5. Для этого выполним команду Start из пункта Run главного меню, либо нажмем на F5, либо щелкнем по пиктограмме Start стандартной панели инструментов:

     Если для текущего варианта кода мы делаем это в первый раз, то системе VB5 потребуется определенное время, чтобы скомпилировать наш текст (перевести его в машинные коды) и осуществить компоновку исполнимого модуля программы (подключить необходимые библиотеки с используемыми вами и системой функциями). При последующих запусках в одном сеансе работы с VB5, программа будет запускаться мгновенно (если код ее не менялся).
     Итак, если вдруг вы ввели код программы без ошибок, то после запуска программы на экране, поверх других окон, появляется окно нашего калькулятора. Протестируем его и убедимся, что он вполне работоспособен. Вы спросите: о каких ошибках, собственно, идет речь? Ведь при вводе кода интеллектуальный редактор VB5 анализировал вводимый текст и немедленно выдавал предупреждения о сделанных ошибках... Но дело в том, что ошибки, которые делает в своей работе программист, бывают разнообразной природы. Редактор же позволяет отслеживать только синтаксические.
     Процесс выявления и устранения ошибок в программе называется отладкой. Англоязычный эквивалент - "debug" ("избавление от жучков"). В Приложении IV можно найти "горячие" клавиши, позволяющие выполнять те или иные отладочные действия: устанавливать точки останова в той или иной строке, использовать окна наблюдений и т.д. Мы не будем рассматривать эту сторону создания приложения, оправдывая себя лишь тем, что необходимость в знании средств отладки появляется при программировании существенно более серьезных задач, а значит этот пробел можно будет восполнить на последующих этапах (само)образования.
     Ну, а сейчас научимся прерывать выполнение запущенной программы. Конечно, если она работает нормально, мы и выйти из нее можем регламентированным образом: стандартным средством закрытия окна программы, либо специально запрограммированной кнопкой. А если программа прервалась по какой-либо ошибке фазы выполнения (run-time error)? Тогда мы должны закончить фазу выполнения с помощью щелчка по пиктограмме End стандартной панели:

     Если же в процессе отладки мы не хотим выходить из фазы выполнения, а лишь приостановить выполнение, мы должны щелкнуть по пиктограмме Break:

     Обе пиктограммы дублируют соответствующие команды пункта Run главного меню.

А теперь работайте сами!

     Наш замечательный (тем, что он наш) калькулятор имеет ряд недочетов. Перечислим их.

=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•

     ¤  Представим, что мы набрали "длинный" операнд и ошиблись в нескольких цифрах в середине числа. Мы можем выделить их мышью и либо удалить, нажав клавишу Delete (или Backspace), либо, при надобности замены, сразу набирать с клавиатуры новые значения. Но вот сделать эти действия мышью, без участия клавиатуры, мы не сможем. Более того, скоро выясняется, что даже простую вставку символов внутри набранного операнды мы можем осуществлять только с клавиатуры: "набор" с помощью мыши и командных кнопок добавляет символы только в конец операнда, независимо от текущего положения курсора в поле ввода! Ничего другого использованная нами операция конкатенации и не могла нам обеспечить. Что же делать?
     Самая естественная идея - отслеживать положение курсора внутри поля ввода - неожиданно завела в тупик: таких средств в Visual Basic попросту не оказалось (а зря!). Выход состоит в использовании инструкции SendKeys Строка, которая имитирует передачу активному окну одного или нескольких нажатий клавиш с символами, представленными в параметре Строка, как если бы они были действительно нажаты на клавиатуре.
     Каждое такое "нажатие" мы сопроводим удалением выделенного фрагмента операнда, если таковой существует. Такой фрагмент доступен для чтения и изменения в свойстве SelText поля ввода. Вот как будет выглядеть обработчик cmdNum_Click после модификации (замененный вариант оставлен как комментарий: Private Sub cmdNum_Click(Index As Integer)
  txtOp(i).SelText = "" 'Удаляем выделенное (если оно есть) перед
     'его заменой
  txtOp(i).SetFocus
  If Index < 10 Then
    txtOp(i).Text = txtOp(i).Text & Index
    SendKeys Index 'Приписываем цифру
  Else
    txtOp(i).Text = txtOp(i).Text & "."
    SendKeys "." 'или точку
  End If
End Sub


     ¤  Еще одно средство, которого мы пока не имеем - это имитация действий клавиш Backspace и Delete. Поэтому введем эти клавиши (с именами cmdBksp и cmdDel) и используем ту же инструкцию SendKeys, в которой специальным образом, с помощью встроенных констант (их имена приведены в справке по данной инструкции), можно задавать имитацию нажатий несимвольных клавиш и даже их комбинаций: Private Sub cmdBksp_Click()
  txtOp(i).SetFocus
  SendKeys "{BKSP}"
End Sub
Private Sub cmdDel_Click()
  txtOp(i).SetFocus
  SendKeys "{DEL}"
End Sub


     ¤  Последнее для чего мы еще вынуждены использовать клавиатуру - это запоминание результата в буфере обмена (Clipboard) и копирование из него значения в активный (находящийся в фокусе) операнд. Оказывается, у нас всегда имеется возможность обратиться с этими целями к объекту Clipboard, метод которого SetText запоминает в нем текстовую строку (в том числе, значение свойства какого-либо другого объекта): Private Sub cmdToClip_Click()
  Clipboard.SetText lblRes.Caption 'Запоминаем результат в буфере
End Sub
а метод GetText - копирует в строку из буфера обмена в строковую переменную (в том числе, свойство): Private Sub cmdFromClip_Click()
  txtOp(i).Text = Clipboard.GetText 'Копируем из буфера обмена
End Sub

=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•
     Ну, а теперь, в качестве упражнения, предлагаем дополнить нашу программу кодом, обеспечивающим работу вот такой "продвинутой" модели нашего калькулятора:

     Программирование работы с каждым из дополнительно размещенных элементов управления уже не должно вызывать затруднений, не считая, быть может, работы со списком, содержащим имена функций. Что ж, запрограммируйте все остальное, а к программированию работы со списком вернитесь после изучения следующей главы, в начале которой эта тема и будет разобрана (а заодно и инструкция Select Case множественного выбора). Заметим, что элемент "список" дает нам возможность без сложностей наращивать и изменять в нашем калькуляторе набор доступных функций.
     И последнее. Действия пользователя, даже неверные, не должны приводить к аварийному завершению работы приложения. То есть, если мы набрали не число, программа должна выявить и обработать эту ситуацию ранее системного обработчика. Универсальным средством является использование инструкции On Error GoTo. Но можно еще до возникновения ошибки проверять вводимое значение "на численность" с помощью функции IsNumeric(Выражение).

Глава 3. РАБОТА С БАЗАМИ ДАННЫХ

И если уж гнаться, то не меньше как за двумя зайцами.

Проект "Словарной обучалки"

     В данной главе мы будем создавать обучающую программу для пополнения словарного запаса у обучаемого по иностранному, в данном случае английскому, языку. Программа будет иметь две версии: первая проще и компактней, зато вторая - более профессиональная, так как работает с "настоящей" базой данных.
     Сценарий работы будущей программы предполагается следующим.
     В окне "обучалки" имеется список английских слов, расположенных по алфавиту. Программа последовательно и случайным образом выдает в специальном поле русские слова-переводы соответствующих английских слов из списка и, одновременно, выводит в другом поле изображения обозначаемых этими словами объектов. Обучаемый должен выбрать из списка соответствующее английское слово. Если он делает это правильно, то выбранное слово "произносится" компьютером, если же неверно, - то выдается звуковой сигнал, и поперек картинки - надпись: "Неверно!" После этого выдается следующее русское слово и картинка и т.д.
     Помимо данного "пассивного" режима в "обучалке" имеется и режим тестирования на оценку, который включается специальной кнопкой. В этом режиме обучаемый выполняет то же задание, но параллельно с отсчетом времени (60 секунд). По истечении времени выдается результат: количество данных ответов, из них число верных и оценка.
     Основная проблема, которая встает перед нами, - это организация работы с разнородными данными. Действительно, с одной стороны, это массивы текстовых данных - наименований предметов, с другой, - это графические файлы с изображениями этих предметов и звуковые файлы с произнесениями названий предметов. При этом надо учесть, что ни названия предметов, ни их количество, ни имена файлов в коде программы ни в каком случае присутствовать не должны; все они могут задаваться в виде свойств только на стадии проектирования. Ну, а в идеале сама работающая программа должна предусматривать настройку на те или иные данные. Обычно все подобные проблемы организации работы с данными решаются при помощи специальных систем управления базой данных (СУБД).

Немножко о базах данных

     СУБД работают с данными, которые организованы в базу данных (Database).
     База данных (БД) - это файл специального формата, содержащий структурированные данные.
     Структура БД - это совокупность таблиц, каждая из которых описывает, как правило, одну сущность. Каждая строка в таблице называется записью (Record) (записью о чем? - об экземпляре какой-либо сущности);

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

каждый столбец представляет поле (Field), обозначающее определенный элемент (свойство, характеристику, атрибут сущности) в записи таблицы. Некоторые поля служат для хранения ключей данных (Data keys), которые уникальным образом идентифицируют запись и обеспечивают связи между таблицами. Что это за связи и для чего они нужны? И для чего бывает нужно в одной базе данных иметь несколько таблиц, когда, например, в базе данных интегрированного пакета WORKS, изучение которого широко распространено в школьном курсе, данные расположены в только в одной?
     Недостатки однотабличного представления становятся видны в тех примерах баз данных, где мы вынуждены заведомо дублировать в записях одну и ту же информацию. В школьном курсе до таких примеров, обычно, не доходят руки. Скажем, в базе данных "Отдел кадров" единственная таблица, хранящая в виде записей личные данные сотрудников, никакого такого заведомого дублирования не предусматривает. Но представьте себе, что вам нужно вести сквозной учет всех товаров, хранящихся на нескольких складах единой организации. Опять-таки, никакого заведомого дублирования не возникло бы, если бы каждый товар хранился только на одном из имеющихся складов; в реальности, разумеется, один и тот же товар может храниться на разных складах. Рассмотрим, например, следующую сводную таблицу (очень условную, разумеется), отражающую текущее состояние "товара на складах" (так мы назовем эту сущность) организации:

№ п/п

Наименование товара

Единица измерения

Количество единиц

Учетная цена ед. (руб.)

Номер склада

 

1

Дискеты 3,5''

Коробка

5

400

3

2

Дискеты 3,5''

Коробка

28

400

5

3

Дискеты 5,25''

Коробка

1

200

1

4

Дискеты 5,25''

Коробка

2

200

2

5

Дискеты 5,25''

Коробка

1

200

3


     Видно, что в пяти записях таблицы указаны товары только двух видов; несколько записей, используемых для каждого вида, обусловлены различием места хранения (номера склада). Более половины объема таблицы составляет, таким образом, избыточная, дублированная информация.
     На данном примере видно, что база данных на основе единственной сущности (и, соответственно, одной таблицы) может быть крайне неэкономной. Поэтому в "настоящих" базах данных используется реляционная (от слова relate - относиться) модель хранения, при которой информация разбивается на сущности-таблицы, в которых имеются ключевые, используемые для межтабличных связей, поля.
     Обратимся к приведенному примеру базы данных, отражающей сущность "товар на складах". Как было известно заранее, один и тот же поступивший товар (но пришедший, скажем, в разное время), имея определенные свойства - наименование, учетную цену, количество и т.д., может храниться на разных складах. Поэтому атрибут "номер склада" не является присущим сущности "товар". Естественно разделить сущность "товар на складах" на две: "товар" и "товар на складе". Так как один товар может храниться на разных складах, то между этими двумя сущностями имеет место тип связи ("отношения") "один ко многим" или, по-другому, "родитель-потомок". При этом в родительской таблице "товар" каждый товар будет идентифицироваться уникальным значением, а содержащее его поле называться первичным ключом. Соответствующее поле в дочерней таблице "товар на складе" может содержать по нескольку одинаковых значений первичного ключа и называется внешним ключом. Именно такого типа связи наиболее часты в реальных базах данных. Нам осталось выяснить, по какому атрибуту (ключевому полю) будет осуществляться связь между этими сущностями. Поле наименования товара использовать нельзя: могут быть товары с одним наименованием, но разной учетной ценой. Кажется естественным использовать порядковый номер записи о товаре в таблице "Товар" (таблица, как мы уже говорили, является формой отображения соответствующей сущности). Порядковый номер записи - это номер очередности ввода записи в таблицу (поэтому его еще называют физическим номером). Для того, чтобы в любой момент иметь доступ к этому номеру, используют введение отдельного поля счетчика, значение которого для каждой новой записи автоматически увеличивается на 1 ("инкрементируется"). Забегая вперед, скажем, что достигается это путем установки соответствующего значения свойства Attributes (Атрибуты) объекта Field с помощью глобальной константы dbAutoIncrField. Недостатком такого ключа будет только отсутствие у него смыслового значения. В реальности же для идентификации экземпляра сущности стараются использовать какие-либо атрибутивные коды:

№ п/п

Наименование товара

Единица измерения

Учетная цена ед. (руб.)

Идентификационный номер товара

1

Дискеты 5,25''

Коробка

200

103

2

Дискеты 3,5''

Коробка

400

102


     После этого таблица "Товар на складе" примет вид:

№ п/п

Идентификационный номер товара

Единица измерения

Количество единиц

Номер склада

1

103

Коробка

1

1

2

103

Коробка

2

2

3

102

Коробка

5

3

4

103

Коробка

1

3

5

102

Коробка

28

5



     Отметим, что под номером склада мы также понимаем, в общем случае, идентификационный номер склада, вводимый в отдельной таблице "Склады". В данном случае номер склада, скорее всего, является значением поля-счетчика таблицы "Склады".
     Проделанная нами процедура реорганизации базы данных, направленная на устранение дублирования, называется нормализацией. Много ли мы от нее выгадали? Даже в нашем упрощенном примере выигрыш по объему таблицы составляет более 50%. В реальных задачах информация по товарам включает в себя гораздо большее количество полей-атрибутов: источник партии товара, приходная цена, цена продажи, покупатель партии, дата поступления и дата продажи, дата оформления и номер сопроводительного документа и т.п. При хранении в единой таблице масштаб паразитного расходования объема памяти возрос бы многократно. Кроме того, усложнилась бы организация работы с такой базой данных. Используя же реляционный подход и вводя необходимые, связанные по ключевым полям с соответствующими идентификационными номерами таблицы-сущности, - такие как "Товар отпущенный", "Организация" и другие, - мы находим возможность реализации гибкой и эффективной системы товарного учета.
     В пакете VB5 (папка VB) имеется модельная реляционная база данных Biblio.mdb, созданная в формате СУБД Access. Эта база данных обеспечивает работу с данными о книгах, их авторах и издательствах. Файл базы данных хранит несколько таблиц, связанных по ключевым полям. Система Access предоставляет нам возможность просмотра схемы связей этих таблиц. Вот как выглядит окно с этой схемой (имена ключевых полей с идентификационными номерами выделены жирным):

     Из схемы видно, что две связи (по полям PubID - идентификационный номер публикации и AuID - идентификационный номер автора) имеют тип "один ко многим" (когда идентифицирующие значения указанного поля в одной таблице уникальны (1), а в другой нет (?)). Имеется и одна связь (по полю ISBN - международному стандартному номеру книги) типа "один к одному" (когда в обоих таблицах идентификационные значения указанного поля уникальны и взаимно однозначны). Эти два типа связи являются преобладающими в правильно организованной базе данных (отношение "многие ко многим" используется только во вспомогательных целях). Подобную схематизацию нужно проводить при проектировании любой базы данных реляционной модели.
     Вернемся к нашей задаче.
     База данных, которая вполне удовлетворила бы ее условиям, двумерная, поскольку отображает только одну сущность - "Предмет" - совокупностью его атрибутов: наименованиями на английском и русском языках, изображением и произношением (на английском). Единая таблица такой базы данных имела бы следующее строение (фрагмент из трех записей):

EnglWord

RusWord

Image

Wave

DOG

Собака

<ссылка на файл>

<ссылка на файл>

DOLPHIN

Дельфин

<ссылка на файл>

<ссылка на файл>

DONKEY

Осел

<ссылка на файл>

<ссылка на файл>


     Записи должны отображаться в отсортированном по первому полю виде. Предусмотренное сценарием указание очередного предмета, отображаемого картинкой и наименованием на русском языке, производится путем генерации случайного целого числа, равномерно распределенного по всему диапазону значений поля счетчика (с коррекцией для избежания повторов). На форме располагается окно со списком английских слов-переводов, представляющий собой копию поля "Слово на английском" нашей базы данных (будем в дальнейшем использовать для краткости аббревиатуру БД), из которого обучаемый производит выбор соответствующего. При совпадении слова, выбранного обучаемым, со словом, стоящем в поле-оригинале выбранной программой записи БД, вызывается звуковой объект в данной записи; если слова не совпадают, регистрируется ошибка, после чего весь процесс повторяется.
     Осталось дело за малым: научиться создавать БД и работать с ней. Сразу скажем, что в языке VB5 есть все объекты, необходимые для работы с базой данных, например, в формате Access. Они образуют целое семейство DAO (Data Access Objects) - "объектов доступа к данным". Например, в этом семействе есть объект Database (База данных); через него можно работать с объектом Recordset (Набор записей), через него - с коллекцией объектов Fields (Поля). Но мы работу с этими объектами пока отложим: наша двумерная база данных настолько проста, что будет полезно смоделировать ее с помощью неких подручных средств, в качестве которых мы сейчас рассмотрим стандартные встроенные элементы управления, представляющие списки: ListBox и ComboBox.

Элементы управления ListBox и ComboBox

О простом и комбинированном списках мы упоминали как о стандартных элементах управления в среде Windows 95 еще в первой главе книги. Они призваны обеспечивать пользователю возможность выбора элемента-строки из их вертикально оформленного перечня. Комбинированный список наряду с этой возможностью обеспечивает также возможность задания выбираемого строкового элемента путем набора в поле ввода (кроме варианта значения 2 его свойства Style).
     Для организации работы со списком (элемент панели инструментов ListBox: ) или с комбинированным списком (элемент панели инструментов ComboBox: ) нужный элемент устанавливается на форме обычным образом. В момент создания элемента управления сам массив отображаемых им строковых элементов пуст. Этот строковый массив представляется свойством List; первый строковый элемент (первый пункт перечня) имеет индекс 0, последний - ListCount-1, где ListCount - свойство (доступное только в фазе выполнения), возвращающее (но не устанавливающее!) количество строковых элементов в массиве List (пунктов в перечне). Строковые элементы массива мы можем вводить в фазе проекта построчно, открывая массив List прямо в окне свойств, как это будет показано ниже.
     Очень важно, что при задании (в фазе проекта) значений массива List в элементах ListBox и ComboBox в них синхронно создается еще один массив - числовой (элементов типа Long), представляемый свойством ItemData. По умолчанию этот массив заполняется нулевыми значениями, однако так же как и строковые элементы массива List числовые элементы массива ItemData могут быть введены в фазе проектирования в окне свойств (в пределах количества элементов массива List!). Для чего же их используют?
     Как было сказано, массив ItemData создается только синхронно с созданием (заполнением строками) массива List (в фазе выполнения эта синхронность сохраняется). Это значит, что между элементами обоих массивов существует взаимно-однозначное соответствие. Приложив столбцы этих массивов друг к другу соответственными элементами, мы получим... таблицу, в одном столбце которой - значения некоторого атрибута (наименования определяемого предмета, в данном случае), а в другом - идентификационные номера этих предметов, что же еще! В качестве таких номеров введем просто порядковые номера строк, введенных в массив List (иначе - номера пунктов перечня, отображаемого элементом ListBox по мере ввода этих строк). И строки, и номера вводятся в фазе проекта построчно с помощью открывающегося комбинированного списка:

     Теперь становится ясно, что нашу однотабличную БД можно разбить на 4 таблицы (по числу полей) и связать эти таблицы, размещаемые в элементах-списках, с помощью идентификационного номера каждого определяемого предмета (отношение между таблицами - "один к одному"). При этом список с английскими наименованиями (единственный, который будет определен видимым в фазе выполнения) будет постоянно отсортирован по алфавиту (свойство Sorted установлено в True). Так как мы не можем хранить в элементе-списке OLE-объекты, то будем хранить соответствующие имена файлов с картинками и звукозаписью. Все эти файлы будем хранить в отдельной папке с предопределенным именем, например, "_PIC-WAV". Такая организация потребует от пользователя приложения, перед началом работы с ним, с помощью специального броузера (от слова Browse - пролистывать; на русском кнопку вызова броузера именуют обычно "Обзор") установить эту директорию как рабочую (обычно для броузера предусматривают собственное диалоговое окно, но в нашем случае мы пренебрежем этим обычаем).
     В броузерах также используются стандартные элементы-списки, но специализированные: для работы со списками логических дисков (элемент панели инструментов DriveListBox: ), с иерархическим списком каталогов текущего диска (элемент панели инструментов DirListBox: ) и списком файлов рабочего каталога (текущего каталога на текущем диске) (элемент панели инструментов FileListBox: ). Но для функций броузера эти элементы проще программировать (очень стандартным образом), чем рассказывать об их свойствах. Поэтому перейдем непосредственно к проектированию.

Выбор и размещение элементов управления

     Если организация хранения данных и последующей их выборки вроде бы прояснилась, то не совсем ясным остался вопрос о программном отображении графических объектов и проигрывании звуковых.
     Для отображения графических объектов используются 2 стандартных элемента панели инструментов - Image: и PictureBox: .
     Элемент Image значительно беднее по своим возможностим, но и ресурсов потребляет существенно меньше. Подробно останавливаться на особенностях свойств каждого из этих элементов нам нет смысла. Укажем только существенные для нынешней задачи моменты. Оба элемента имеют свойство Picture, которому в качестве значения устанавливается полное (с именем диска и путем) имя графического файла. При проектировании эта установка производится через вызов диалога Load Picture ("Загрузка картинки") путем нажатия на кнопку с многоточием в строке свойства Picture окна Properties: . В фазе выполнения установка файла производится с помощью функции LoadPicture().
     По разному эти элементы работают в случаях, когда картинка по размеру больше рабочей области элемента. Элемент Image изменяет свои размеры под размер картинки, если свойство Stretch ("Растягивание") установлено в False (по умолчанию); в противном случае, при установке его в True, картинка сжимается (или растягивается) под размер элемента. Элемент Picture обрезает не влезающую часть картинки, но может и подстроиться под ее размер при установке свойства AutoSize ("Авторазмер") в True (по умолчанию False).
     Помимо данных стандартных элементов панели ToolBox, нам потребуется - для проигрывания звукозаписи - и один нестандартный, созданный по технологии ActiveX ("компонент ActiveX"). Это элемент управления мультимедиа MMControl. Компоненты ActiveX перечислены в диалоге Components ("Компоненты"), который вызывается либо, как уже упоминалось, через одноименный пункт контекстного меню, вызванного в панели инструментов, либо командой Ctpl+T, дублирующей команду Components из пункта Project главного меню.
     Вызовем диалог Components и во вкладке Controls установим флажок напротив названия компонента Microsoft Multumedia Control 5.0, после чего щелкнем по кнопкам "Применить" и "ОК":

     После этого на панели инструментов появится пиктограмма элемента MMControl: .Наконец, остался не рассмотрен еще один очень важный стандартный элемент - таймер (Timer: ). Этот невидимый в фазе выполнения элемент предназначен для одной единственной цели - генерировать событие Timer через промежуток времени, заданный в миллисекундах значением свойства Interval (Интервал). Ну, а в обработчике этого события мы задаем с помощью программного кода те действия, которые нам необходимо выполнять в момент возникновения события. Так как тип значения Interval целый, то максимальный интервал, который можно задать для таймера, - чуть более минуты. Для задания больших интервалов между периодическими выполнениями требуемого программного кода мы можем ввести переменную-счетчик (статическую), накапливающую число обращений к обработчику, и при каждом обращении проверять на равенство нулю (в инструкции ветвления IF) остаток от деления значения счетчика на целое ычисло, задающее коэффициент удлинения интервала срабатывания таймера по сравнению со значением свойства Interval. Требуемый программный код должен содержаться в той ветви инструкции IF, которая выполняется при истинности условия.
     Наиболее часто таймер используется для целей анимации. Но он может использоваться и для задания времени работы каких-либо процедур, и для временных задержек каких-либо действий программы. Для этих целей у таймера имеется свойство, аналогичное пусковому устройству секундомера: таймер ведет отсчет времени только тогда, когда его свойство Enabled ("Возможный") установлено в True (по умолчанию), а значение Interval не равно 0. Таким образом, программно изменяя какое-либо из этих свойств, мы имеем возможность запускать таймер (например, устанавливая Enabled=True) и снова его останавливать через заданный для него интервал (помещая в обработчик установку Enabled=False).
     Займемся размещением на форме ТЕСТ нашего приложения элементов управления, как это показано на рис. 3.1. Наиболее важным из них дадим "правильные" (по рекомендуемому фирмой Microsoft соглашению) имена, другим, для лучшего различения "правильных" имен, оставим "естественные".

     Охарактеризуем устанавливаемые элементы.

=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•

     ¤  Элемент мультимедиа MMControl1 не должен быть виден в фазе выполнения (Visible=False). Его установки по умолчанию рассчитаны именно на работу с файлами звукозаписи с расширением .WAV (DeviceType="WaveAudio"). Так как элемент MMControl не встроенный, а создан по объектной технологии , являясь, таким образом, "пользовательским" элементом (UserControl), то команды управления им задаются (в фазе выполнения) как значения его свойства Command. Например, инструкция MMControl1.Command = "Open" открывает доступ к мультимедийному устройству, определенному свойством DeviceType ("устройство" - это совокупность аппаратного и программного обеспечения, например звуковая карта и те или иные драйверы). Помимо "WaveAudio" имеется еще 11 типов устройств, с которыми работает данный элемент - сканер, проигрыватель компакт дисков, видеодисков, MIDI-файлов, цифрового и обычного видео и т.д.

     ¤  Кнопка (элемент Command) с именем Command1 предназначена для выхода из приложения (Caption = "Выход").

     ¤  Список (элемент ListBox) с именем lsbRus предназначен для ввода (в фазе проектирования) и хранения русских слов (свойство List) с их идентификационными номерами (свойство ItemData). В фазе выполнения предполагается невидимым.

     ¤  Ярлык (элемент Label) с именем lblFault предназначен для вывода надписи (Caption = "Неверно!") при неверном выборе английского слова-перевода. Размер шрифта (свойство Font) установлен 18 пунктов, начертание - жирное; изменен цвет шрифта (ForeColor = &H8000000E&) и цвет фона (BackColor= &H80000017&).

     ¤  Кнопка с именем cmdStart служит для запуска минутного теста (Caption="Запуск теста 60 сек."). После запуска теста в значении свойства Caption отображается число секунд, прошедших от начала тестирования.

     ¤  Графическое окно (элемент Image) с именем Image1 предназначено для вывода сопутствующей русскому слову картинки.

     ¤  Ярлык с именем lblWord предназначен для отображения выбранного из списка lsbRus случайным образом русского слова. Размер шрифта установлен 12 пунктов.

     ¤  Таймер c именем tmrSec предназначен для отсчета секунд, отображаемых в надписи (свойство Caption) на кнопке cmdStart после запуска теста.

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

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

     ¤  Списки с именами Drive1, Dir1 и File1 используются в "броузере файлов" приложения для установки рабочей директории с графическими и звуковыми файлами, соответствующими наименованиям предметов. В элементе - списке файлов File1 предусмотрена фильтрация для отображения имен только графических файлов формата JPEG: Pattern="*.JPG" (так как именно в этом формате мы будем хранить наши графические файлы).

     ¤  Комбинированный список с именем cboEngl предназначен для ввода (в фазе проекта) и работы с английскими словами-переводами (свойство List) и их идентификационными номерами (свойство ItemData) - теми же, что и у соответствующих русских слов в элементе cboRus. Этот список дожен быть видим в фазе выполнения и отсортирован в лексикографическом ("по алфавиту") порядке (Sorted=True). Почему мы используем не обычный (ListBox), а комбинированный список? В будущем, особенно, в том случае, если количество слов будет достаточно велико, предполагается развить наше приложение так, что искомое слово-перевод можно будет вводить в поле ввода этого списка с клавиатуры, причем список будет программно "подтягиваться" к правильно набираемому слову. Приложение сможет служить, таким образом, и клавиатурным тренажером.

     ¤  Кнопка с именем cmdBrows служит для активизации работы с броузером и окончания работы с ним. При активизации работы первоначальная надпись на кнопке (Caption="Обзор...") сменяется надписью "OK", а затем, по окончании работы, восстанавливается.

     ¤  Ярлык с именем Label2 содержит надпись, подсказывающую пользователю броузера, какой именно путь (Caption = "Путь в каталог _PIC-WAV") должен быть выведен в надписи нижележащего ярлыка с именем lblPath.

=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•

     Наверное, у вас уже возник вопрос, почему мы не разместили, как планировали, списки с именами графических и звуковых файлов (и соответствующими идентификационными номерами)? Да просто нашелся способ еще более упростить наш проект. Чтобы не заниматься вводом в списки имен файлов, просто заранее дадим графическим и звуковым файлам имена, представляющие (без учета расширения) соответствующие идентификационные номера (точнее - их символьные десятичные записи) из списков русских и английских наименований соответствующих предметов. Тогда по идентификационному номеру выбранного слова мы сможем легко строить составные имена сооответствующих графического и звукового файлов (они будут различаться расширениями). Строковое представление номера (да и вообще числа) дает функция Str(Номер). В случае положительного числа, однако, эта функция в начале строки помещает пробел. Чтобы он не мешал, мы делаем "вырезку" остатка данной строки, начиная с ее второго символа. Такую вырезку возвращает функция Mid(2, Строка) (когда нужна вырезка не до конца строки, а определенной длины, эту длину задают третьим параметром данной функции, в нашем случае опущенным). Таким образом, имя файла возвращает композиция этих функций Mid(2, Str(Номер)).
     Займемся теперь написанием кода обработчиков событий (кроме очевидного для кнопки выхода), связанных с элементами управления нашей программы и отвечающих разработанному сценарию.

=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•

     ¤  Каждое нечетное нажатие на кнопку cmdBrows приводит к активизации броузера, четное - к его "закрытию". Соответствующие ветви инструкции If выбираются путем сравнения с 0 остатка от деления на 2 значения счетчика (переменная i) числа нажатий на эту кнопку.
Private Sub cmdBrows_Click()
 Static i
 i = i + 1
 If i Mod 2 = 1 Then
  Drive1.Visible = True 'Делаем видимыми элементы броузера,
  Dir1.Visible = True
  File1.Visible = True
  Image1.Visible = False 'а отображение графики отключаем
  cmdBrows.Caption = "OK"
 Else
  Drive1.Visible = False 'После настройки - наоборот
  Dir1.Visible = False
  File1.Visible = False
  Image1.Visible = True
  'Генерируем случайный индекс:
  li = li_o = Int(lsbRus.ListCount * Rnd)
  'Отображаем соответствующее ему русское слово:
  lblWord.Caption = lsbRus.List(li)
  'И картинку:
  Image1.Picture = LoadPicture(File1.Path + "\" + _
         Mid(Str(lsbRus.ItemData(li)), 2) + ".jpg")
  cmdBrows.Caption = "Обзор..."
 End If
End Sub

     ¤  Уже в первом фрагменте мы использовали две глобальных переменных: li и li_o. Первая хранит индекс текущего, случайным образом выбранного в массиве lsbRus.List русского слова, вторая - предшествующего. Хранить предшествующий индекс нужно для предотвращения ситуации, когда второй раз подряд выпадает одно и то же слово; в таком случае мы будем брать соседнее слово (это самый примитивный из способов избежать повторов).
     Приведем всю секцию (General), содержащую описание всех глобальных переменных программы:
Dim li As Integer, li_o As Integer, t As Integer, _
     n As Integer, nf As Integer
' li   - индекс очередного элемента в списке русских слов
' li_o - индекс предыдущего -//-
' t    - счетчик секунд теста
' n    - число ответов во время теста
' nf   - из них правильных

     ¤  В следующем фрагменте приведены коды обработчиков событий, отражающих работу элементов-списков броузера:
Private Sub Drive1_Change()
 Dir1.Path = Drive1.Drive 'Свойство Drive - текущий диск,
End Sub                   'Path - текущий каталог на текущем диске

Private Sub Dir1_Change()
 File1.Path = Dir1.Path
 lblPath.Caption = Dir1.Path 'Отображаем текущий путь
End Sub

Private Sub File1_Click()
  'Собственно, при работе с броузером файл выбирать нам не 
  'требуется, но если кликнем его, отобразится картинка.
 Image1.Visible = True
 Image1.Picture = LoadPicture(File1.Path + "\" + _
         File1.filename)
End Sub

     ¤  Обработка выбора (событие Click) из списка английского слова-перевода, в ответ на появление очередного случайно выбранного русского слова (после предшествующего выбора, либо после первоначальной установки рабочего каталога):
Private Sub cboEngl_Click()
 n = n + 1
 If cboEngl.ItemData(cboEngl.ListIndex) = _ 
                                               lsbRus.ItemData(li) Then
   MMControl1.filename = File1.Path + "\" + _
              Mid(Str(lsbRus.ItemData(li)), 2) + ".wav"
 Else
   lblFault.Visible = True
     'Ошибку сопровождает звуковой сигнал:
  MMControl1.filename = File1.Path + "\" + "ugly.wav"
  nf = nf + 1
 End If
 'Запускаем таймер tmrDelay:
 tmrDelay.Enabled = True
 'Команда "sound" определена для устройства WaveAudio и
 'не требует его предварительного открытия:
 MMControl1.Command = "sound"
End Sub

     ¤  Запущенный обработчиком выбора из списка cboEngl таймер tmrDelay через заданный промежуток времени (2 секунды) обеспечит очередной случайный выбор русского слова из массива lsbRus.List:
Private Sub tmrDelay_Timer()
 li = Int(lsbRus.ListCount * Rnd)
 'Если очередной индекс совпал с предыдущим:
 If li = li_o Then 
  If li = lsbRus.ListCount - 1 Then
   li = li - 1
  Else
   li = li + 1
  End If
 End If
 'Отображаем случайно выбранное слово:
 lblWord.Caption = lsbRus.List(li)
 'И соответствующую картинку:
 Image1.Picture = LoadPicture(File1.Path + "\" + _
        Mid(Str(lsbRus.ItemData(li)), 2) + ".jpg")
 'А сам таймер после этого останавливаем:
 tmrDelay.Enabled = False
 'И считаем, что очередная итерация стала уже предыдущей:
 lblFault.Visible = False
 li_o = li
End Sub

     ¤  Нажатие на кнопку запуска теста cmdStart запускает таймер tmrSec, в обработчике которого переменная-счетчик отсчитывает время теста, выдаваемое в надписи на кнопке cmdStart:
Private Sub tmrSec_Timer()
 t = t + 1
 cmdStart.Caption = Str(t) + " сек."
End Sub

     ¤  Обработчик нажатия на кнопку запуска теста cmdStart, кроме запуска таймеров tmrSec и tmrTest, делает первую (в рамках теста) генерацию случайно выбранного русского слова и соответствующим образом его отображает:
Private Sub cmdStart_Click()
 t = 0 'Обнуляем счетчик секундомера
 n = 0 'Количество ответов во время теста
 nf = 0 'Из них правильных
 Drive1.Visible = False
 Dir1.Visible = False
 File1.Visible = False
 li = Int(lsbRus.ListCount * Rnd)
 If li = li_o Then
  If li = lsbRus.ListCount - 1 Then
   li = li - 1
  Else
   li = li + 1
  End If
 End If
 lblWord.Caption = lsbRus.List(li)
 Image1.Visible = True
 Image1.Picture = LoadPicture(File1.Path + "\" + _
 Mid(Str(lsbRus.ItemData(li)), 2) + ".jpg")
 tmrSec.Enabled = True
 tmrTest.Enabled = True
 li_o = li
End Sub

     ¤  Таймер tmrTest генерирует через 60 секунд после своего запуска кнопкой cmdTest событие Timer. В его обработчике с помощью инструкции выбора Select Case организуется расчет оценки тестируемого исходя из количества данных им ответов и количества правильных из них. Инструкция Select Case имеет следующую структуру:
Select Case Выражение
  Case Диапазон_1
    Блок_1
  Case Диапазон_2
    Блок_2
........................
  Case Диапазон_n
    Блок_n
  Case Else
    Блок_default
End Select
     В приведенной структуре можно выделить граничные "скобки" инструкции, состоящие из строк заголовка Select Case и окончания инструкции End Select, и Case-ветви, каждая из которых состоит из строки заголовка, начинающегося словом Case, и следующего за ним блока инструкций. При этом ни одна Case-ветвь или ее блок не являются обязательными. После слова Case заголовка Case-ветви всегда имеется некоторый список (возможно, состоящий из одного элемента) выражений или специальных конструкций, который задает некоторое множество значений ("диапазон") числовой прямой (отдельные точки, сплошные отрезки и лучи). Мы будем рассматривать данную инструкцию (более подробно описанную в Приложении III) предполагая, что входящие в заголовки выражения имеют числовой тип. Это, однако, не является обязательным, поскольку операции сравнения определены и для строк (а некоторые - и для объектов). Подробности об операциях сравнения можно узнать в Приложении II.
     Исполнение инструкции происходит по следующему алгоритму.

     • Вычисляется Выражение в заголовке инструкции.

     • Последовательно сверху вниз проверяются заголовки Case-ветвей, содержащие Диапазоны, на вхождение вычисленного значения Выражения в эти Диапазоны. Как только первая такая Case-ветвь найдена, выполняется ее Блок (хотя бы и пустой).

     • Если значение Выражения не входит ни в один Диапазон и в инструкции имеется ветвь Case Else, то выполняется ее Блок.

     • После выполнения Блока какой-либо Case-ветви, либо при невыполнении ни одного Блока, инструкция завершается.
     Для тех, кому пока не хочется отвлекаться на изучение приложения, перечислим, чем может являться каждый элемент списка, задающего какой-либо Диапазон. Им может быть, во-первых, любое выражение (константа, переменная, обращение к функции, любые комбинации перечисленных объектов, соединенных, допустимым образом, знаками операций и взятием подобных соединений в качестве аргументов обращений к функциям).
     Во-вторых, таким элементом может быть конструкция
     Выражение_1 To Выражение_2
где Выражение_1 и Выражение_2 - соответственно начальное и конечное значения отрезка, задающего множество значений, входящих в итоговый Диапазон.
     В третьих, - конструкция
Is Оператор Выражение
где Оператор - один из 6 операторов сравнения:
	< 	(меньше)
	<= 	(меньше, либо равно)
	> 	(больше)
	>= 	(больше, либо равно)
	= 	(равно)
	<> 	(не равно)

     Выражение вычисляется, и его значение вместе с оператором задают множество значений, входящих в итоговый Диапазон (например, < 5 задает множество всех значений, меньших чем 5).
Private Sub tmrTest_Timer()
 Dim e As Integer
 cmdStart.Caption = "Запуск теста (60 сек.)"
 tmrDelay.Enabled = False
 tmrSec.Enabled = False
 lblFault.Visible = False
 Select Case n - nf
  Case Is > 9
   If nf = 0 Then
    e = 5
   Else
    e = 4
   End If
  Case 7 To 9
   If nf = 0 Then
    e = 4
   Else
    e = 3
   End If
  Case 4 To 6
   If nf = 0 Then
    e = 3
   Else
    e = 2
   End If
  Case Else
   e = 2
 End Select
 MsgBox "Дано " & n & " ответов; число Ваших ошибок:" & _
    nf & Chr(13) & "Отметка: " & e, vbInformation,"ТЕСТ"
 tmrTest.Enabled = False
End Sub
     В приведенном обработчике можно отметить также обращение к функции MsgBox, которая выводит на экран заданную программистом информацию в специальном окне сообщений (Message Box). Окно сообщений является диалогом, причем модальным: программа (либо даже операционная система) не будут продолжать работу до тех пор, пока пользователь не щелкнет по какой-либо кнопке, расположенной в окне диалога.
     Обращение к данной функции имеет, во-первых, стандартную синтаксическую форму, подразумевающую обязательное использование такого обращения в составе выражения: MsgBox(Текст_сооб., Признак, Текст_заг., Имя_файла_спр., Номер_разд.) а во-вторых, - форму обращения к методу (без скобок, в отдельной строке программы): MsgBox Текст_сооб., Признак, Текст_заг., Имя_файла_спр., Номер_разд. - в тех случаях, когда возвращаемое функцией значение заведомо не используется. На рис. 3.2 приведено строение окна сообщения, выдаваемое вызовом функции MsgBox в нашем обработчике.

     В приведенных формах обращения обязательным является наличие только первого аргумента (Текст_сообщения), представляющего строковое выражение, содержащее выводимое сообщение. Если других аргументов нет, то по умолчанию имя окна сообщения будет Project1, значок отсутствует и имеется единственная кнопка "OK" (при нажатии на которую функция возвращает значение 1, если обращение входит в состав выражения). Если в списке аргументов между парой присутствующих аргументов имеются опущенные, то следующие после каждого из опущенных аргументов запятые не опускаются (это общее правило для любого обращения к функции с переменным числом аргументов). Третий аргумент (Текст_заголовка) задает, строковым выражением, наименование окна сообщения; 4-й и 5-й - файл справки (о данном окне) и номер раздела в нем. Наконец, второй аргумент - Признак - определяет 4 независимые характеристики окна сообщений: наличие и тип значка, набор кнопок, какая кнопка имеет начальный фокус, тип модальности диалога. Рассмотрим этот аргумент подробнее.
     Для каждой из указанных характеристик существует таблица альтернативных значений. Выбранные по одному из каждой таблицы значения-слагаемые складываются, давая уникальное значение аргумента Признак. В таблице 3.1 приведены слагаемые, определяющие наличие и тип используемого в окне сообщения значка.

А сочетание имеющихся в окне кнопок задает следующая таблица:

Слагаемое
в аргумент
Признак
Отображаемый набор кнопок
0Отображается только кнопка "ОК"
1Отображаются кнопки "ОК" и "Отмена" (Cancel)
2Отображаются кнопки "Прервать" (Abort), "Повторить" (Retry) и "Пропустить" (Ignore)
3Отображаются кнопки "Да" (Yes), "Нет" (No) и "Отмена" (Cancel)
4Отображаются кнопки "Да" (Yes) и "Нет" (No)
5Отображаются кнопки "Повторить" (Retry) и "Отмена" (Cancel)

     Определение первоначального фокуса задает таблица:

Слагаемое
в аргумент
Признак
Кнопка,
на которую первоначально
направлен фокус
0Первая
256Вторая
512Третья
768Четвертая

     "Таблица" типа модальности содержит всего два значения: 0 - если окно модально для приложения и 4096 - если его модальность распространяется на всю систему.
     Заметим, что каждое из приведенных в таблицах значений слагаемых можно использовать в форме именованной константы (все они приведены в аналогичных таблицах справки по функции MsgBox), имя которой мнемонически указывает назначение данного слагаемого. Использование именованных констант в данном случае если и не облегчает программирование (их слишком много для запоминания), то облегчает последующее чтение кода.
     Наконец, приведем таблицу возвращаемых функцией значений, определяемых выбором нажатия той или иной кнопки:

КнопкаOKCancelAbortRetryIgnoreYesNo
Значение1234567

     ¤  Загрузка формы TECT сопровождается и начальной генерацией отображений случайно выбранного русского слова - в том случае, если рабочим каталогом сразу оказывается каталог _PIC-WAV. Проверку вхождения строки "_PIC-WAV" в текущий путь,заданный свойством Dir1.Path, осуществляет функция Instr(Строка_1, Строка_2), где первый аргумент - строка, в которой ведется проверка, второй - строка, проверяемая на вхождение (могут задаваться еще два необязательных аргумента, информацию о которых можно получить из справки):
Private Sub Form_Load()
 Dim s As String
 Randomize
 Drive1.Visible = False
 Dir1.Visible = False
 File1.Visible = False
 lblPath.Caption = Dir1.Path
 s = Dir1.Path
 If InStr(s, "_PIC_WAV") <> 0 Then
  Image1.Visible = True
  li = li_o = Int(lsbRus.ListCount * Rnd)
  lblWord.Caption = lsbRus.List(li)
  Image1.Picture = LoadPicture(File1.Path + "\" + _
           Mid(Str(lsbRus.ItemData(li)), 2) + ".jpg")
 End If
End Sub

     В остальном все элементы этого обработчика аналогичны уже рассмотренным.

=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•

Возможно, у вас давно уже возник вопрос, откуда можно брать файлы с картинками и как записать "сопровождающие" их звуковые файлы? Наиболее естественно начинать создание нашей базы данных именно с подбора картинок, отображающих предметы, соответствующие уровню сложности формируемого словаря. Огромные наборы подходящих картинок можно найти на компакт-дисках (CD). Лучше всего, если это будут растровые картинки (форматы BMP и PCX), картинки в виде метафайлов (WMF и EMF), либо картинки в форматах JPEG и GIF (иначе их придется переводить в один из этих форматов программой типа Corel PhotoPaint). (Забегая вперед, скажем, что в "настоящей" БД картинки должны быть только в форматах BMP и PCX, что, безусловно, является недоработкой разработчиком языка.) Звуковые файлы записываются (посредством микрофона, подключенного к звуковой карте) с помощью приложения Фонограф (Sound Recorder), находящегося в группе Мультимедиа, входящей в группу Стандартные из пункта Программы пускового меню Windows.
     Вот как выглядит окно созданного приложения в процессе тестирования обучаемого:

     Первый вариант "Словарной обучалки" создан!

Ну, а если у нас "настоящая" база данных?

     Давайте представим, что мы тем или иным способом сумели создать и заполнить информацией БД. Как нам с ней дальше работать?
     Мы уже упоминали о семействе объектов DAO, предназначенных именно для этой цели. Но оказывается, есть еще ряд в VB5 есть еще ряд средств сильно облегчающих решение наших проблем.
     Для организации работы с уже имеющейся базой данных VB5 содержит, во-первых, специальный компонент - элемент управления данными (Data Control, в подсказке - просто Data), а во-вторых, - ряд элементов управления (всего их 15), выполняющих по отношению к нему функцию "связанных элементов управления" (bound control). Связанные элементы управления - это в основном стандартные компоненты, имеющие ряд дополнительных свойств, обеспечивающих доступ к базе данных под управлением элемента Data. На рис. 3.3 приведена палитра инструментов с указанием как элемента Data, так и большинства связанных элементов.

     Когда с помощью элемента Data пользователь приложения перемещается от одной записи к другой, связанный элемент отображает, или даже позволяет редактировать (изменять) данные, содержащиеся в заданном для связанного элемента поле текущей записи. Кроме того, элемент Data позволяет создавать новые записи в базе данных, которые можно заполнять информацией с помощью связанных элементов.
     Рассмотрим работу элемента Data и связанных элементов на примере уже упомянутой базы данных Biblio.mdb. Создадим форму с заголовком "Каталог издания", на которой разместим элемент Data со стандартным именем и заголовком Data1 и 8 связанных элементов - полей ввода с именами Text1-Text8 (без надписей); для каждого поля ввода с помощью ярлыков Label1-Label8 зададим наименование отображаемой ими информации. На рис. 3.4 приведен вид созданной формы с указанием функций кнопок элемента управления Data.

     Займемся настройкой элемента Data (с именем Data1) и полей ввода на базу данных.

=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•

     ¤  Для взаимодействия элемента Data и БД нужно определить по меньшей мере два свойства: DatabaseName ("имя БД"), значением которого является имя файла (с полным путем), содержащего БД, и RecordSource ("источник записей"). Значением свойства RecordSource может быть имя таблицы, входящей в состав БД. Но им может быть также имя запроса (Query), сформированного и сохраненного в самой БД, а также непосредственно текст инструкции-запроса.
     Запрос - это инструкция, написанная на специальном языке SQL (Structured Query Language), результатом выполнения которой является набор записей (Set of records, или, как называется соответствующий объект в системе VB5, RecordSet).
     Вообще говоря, в VB5 существует 4 типа наборов записей: таблицы (tables) - физическая структура, содержащая реальные данные; динамические наборы (dynasets) - набор ссылок, обеспечивающих доступ к полям и записям в одной или нескольких таблицах; еще два типа - снимки (snapshorts) - доступные только для чтения копии данных из одной или нескольких таблиц, с которыми мы не будем иметь дела.
     Так как таблица - тоже набор записей, то в общем можно сказать, что элемент Data управляет набором записей, сопоставляемых ему через свойство RecordSource. Поля, из которых берутся данные для записей такого набора, могут находиться в разных таблицах БД. Условием формирования записей набора из записей разных таблиц является равенство значений ключевых полей в этих записях. Например, из приведенной схемы БД Biblio.mdb видно, что мы можем формировать набор данных, содержащий имя автора (поле Author таблицы Authors) и название книги (поле Title таблицы Titles), объединяя данные из таких записей таблиц Author и Titles, в которых значения поля Au_ID из Author и значения поля ISBN из Titles находятся в единых записях таблицы Title Author (в ее соответствующих полях Au_ID и ISBN).
     При создании базы данных формируемые запросы могут сохранятся в ней под заданными именами. Как мы сейчас увидим, есть такой запрос и в БД Biblio.mdb.
     В окне свойств выберем свойство DatabaseName, щелкнув по нему мышью, а затем, щелкнув по появившемуся многоточию (знак вызова диалога), получим стандартный диалог для открытия файла. Откроем файл Biblio.mdb, путь в папку к которому c:\Program Files\DevStudio\Vb. Затем, щелкнув по свойству RecordSource, получим значок для открывания списка (? ), щелкнув по которому откроем выпадающий список:

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

     ¤  Для настройки любого связанного элемента нужно определить минимум два его свойства: DataSource ("источник данных") и DataField ("поле данных"). При этом в качестве источника данных указывается имя какого-либо элемента Data, а в качестве поля данных - имя поля в наборе записей, определяемом свойством RecordSource этого элемента Data.
     Выделим на нашей форме поле ввода Text2 (против надписи "Наименование:") и в окне свойств установим (выбором из падающего списка) для свойства DataSource единственное в списке значение Data1 (имя установленного элемента Data), а для свойства DataField, таким же образом выбираем имя поля данных Title:

     При этом мы можем узнать имена и остальных полей в сопоставленном элементу Data наборе записей All Titles.

     ¤  Последовательно выделяя мышью поля ввода против надписей "Номер ISBN:", "Автор:", "Год издания:" и "Издательство:" установим таким же образом значения их свойств DataSource (Data1) и DataField (ISBN, Author, Year Published и Company Name соответственно). Элемент Data1 и связанные с ним поля ввода готовы к работе!

=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•

     Запустив приложение, мы посредством управляющих кнопок элемента Data "листаем" записи сопоставленного ему набора записей All Titles. Поля ввода допускают и редактирование данных в соответствующих полях базы данных, если только свойству ReadOnly элемента Data не установлено значение True. Кроме того, тип созданного набора записей должен допускать такую возможность; по умолчанию этот тип, задаваемый свойством RecordsetType, - Dynaset ("динамический"), - эту возможность допускает. Удалять же и добавлять записи в базу данных с помощью элемента Data мы не можем: для этого нужно установить соответствующие командные кнопки и написать программный код. При написании программного кода, если такое случится, мы будем иметь дело с объектом RecordSet (Набор записей), доступ к которому можно осуществить посредством обращения к свойству RecordSet элемента Data: Data1.RecordSet, например. Этот объект имеет ряд свойств и методов, обеспечивающих функции перемещения по записям, поиска и модификации записей. Мы же до сих пор не написали ни одной строки! И тем не менее можем спокойно листать и модифицировать данные в полях нашей БД:

     Рассмотрим, наконец, самую "продвинутую" возможность работы с элементом Data (без написания кода): возможность задания управляемого набора записей не выбором имеющихся таблиц или хранимых запросов БД, а с помощью инструкции SELECT языка SQL, размещаемой в поле ввода свойства RecordSource (окно Properties проекта) элемента Data. Общая структура этой инструкции для такой задачи упрощенно следующая: SELECT Cписок_полей FROM Cписок_таблиц [, Отношения_между_ними] (квадратные скобки указывают на необязательность названного внутри них элемента инструкции - здесь и далее).
     А теперь запишем конкретный вариант такой инструкции для отображения всех 8 полей, отображение которой предусмотрено нашей формой "Каталог изданий":
SELECT a.Author, a.Au_ID, b.Title, b.[Year Published],
          b.ISBN, b.PubId, d.[Company Name], d.City
FROM Authors As a, Titles As b, [Title Author] As c, Publishers As d,
a INNER JOIN c ON a.Au_ID = c.Au_ID,
b INNER JOIN c ON b.ISBN = c.ISBN,
b INNER JOIN d ON b.PubId = d.PubId

     Дадим некоторый комментарий.

     • Вся инструкция записывается в поле свойства RecordSource в одну строку (по другому и не получится).

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

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

     • Если одно и то же имя поля имеется в различных таблицах из списка полей, то для уточнения объекта используется синтаксис составного имени: Имя_таблицы.Имя_поля (в качестве имени таблицы возможен псевдоним). Для большей ясности в приведенной инструкции все имена полей составные.

     • Отношение между таблицами a и b задается в нашей инструкции фразой a INNER JOIN b ON a.Au_ID = b.Au_ID и означает следующее: "В формировании искомого набора записей участвуют только те записи таблиц a и b, у которых в одноименных полях Au_ID стоят одинаковые значения". Настроив, после ввода инструкции SELECT, связанные элементы на поля сформированного набора записей, запустим приложение и убедимся в его работоспособности (рис. 3.5).

Отображение записей БД в списках

     Помимо указанных стандартных элементов управления, используемых в функции связанных, существует 3 специализированных связанных элемента для списочно-табличного представления содержимого БД. Эти элементы, как и другие компоненты ActiveX, перечисляются в диалоге Components ("Компоненты"), который мы уже вызывали для использования элемента Мультимедиа в "Словарной обучалке".
     Вызовем диалог Components, во вкладке Controls установим флажки напротив названий интересующих нас компонентов Microsoft Data Bound Grid Control и Microsoft Data Bound List Controls 5.0 и щелкнем по кнопке "Применить" и "ОК". В палитре инструментов появляются 3 новых компонента (рис. 3.6).

     Элементы DBList и DBCombo позволяют автоматически отображать информацию из набора записей в виде списка, в то время как соответствующие стандартные элементы ListBox и ComboBox могут это делать только через применение метода AddItem (этот метод к тому же не действует, если эти списки связаны с элементом Data). Но помимо просто отображения данных полей БД, эти элементы дают возможность создания связей (путем копирования элементов одной таблицы, - например, идентификационных значений, - в другую таблицу) между какими-либо полями различных таблиц БД.
     Для использования элементов DBList и DBCombo нужно определить как минимум 5 их свойств:

     • RowSource - имя элемента Data - источника записей для списка

     • ListField - имя поля, данные которого отображает список

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

     • DataSource - имя элемента Data, связанного с целевым набором записей, формируемого в процессе выбора из списка; может совпадать с RowSource.

     • DataField - имя целевого поля в целевом наборе записей, куда копируется идентификационные значения поля BoundColumn для выбираемых из списка элементов (либо сами эти элементы, если поле BoundColumn совпадает с полем ListField); тип данного поля должен совпадать с типом поля BoundColumn.
     Если мы хотим просто отображать в виде списка какое-либо поле некоторого набора записей, то RowSource и DataSource имеют своими значениями один и тот же элемент Data. В этом случае, естественно, значения для свойств BoundColumn и DataField не указываются. Вот как выглядит окно приложения "Каталог изданий", в котором поле авторов из сформированного по инструкции Select указанного набора записей отображается с помощью списка DBList:

При этом мы можем перемещаться и производить выбор пунктов списка авторов с помощью мыши и полосы прокрутки совершенно независимо от состояния элемента Data; в свою очередь все свойства управления набором с помощью элемента Data сохраняются. Модифицировать пункты списков, используя стандартные клавиатурные возможности (как в связанных полях ввода), невозможно даже с помощью поля ввода элемента DBCombo.
     Информационная же таблица DBGrid позволяет просто просматривать одновременно в табличном виде весь набор записей, являющийся источником записей (RecordSource) какого-либо элемента Data. Для этого имя этого элемента надо указать значением свойства DataSource таблицы DBGrid. Используя мышь и линейки прокрутки мы можем перемещаться в любое место таблицы (независимо от состояния элемента Data) и выбирать в ней нужную запись. Вот как выглядит окно приложения, в котором таблица позволяет просматривать полностью все тот же набор записей:

     Теперь мы вполне достаточно знаем о том, как работать с БД при помощи элемента Data и связанных элементов управления (и при этом безо всякого программирования). Мы, правда, еще не научились добавлять и удалять записи в БД с помощью этих средств. Но главное, к чему нам давно уже пора приступить, - это создание новой БД средствами VB5.

Создание файла БД и формы для его заполнения

     Вернемся снова к нашему проекту "Словарной обучалки". В первоначальном проекте мы предполагали хранить данные со словарными, изобразительными и звуковыми отображениями предметов в двумерной БД, состоящей из единственной таблицы (назовем ее Subject - Предмет):

EnglWord

RusWord

Image

Wave

DOG

Собака

<ссылка на файл>

<ссылка на файл>

DOLPHIN

Дельфин

<ссылка на файл>

<ссылка на файл>

DONKEY

Осел

<ссылка на файл>

<ссылка на файл>


     Впоследствии мы нашли способ решения нашей задачи и без создания такой БД, на основе списков. Сейчас же мы вернемся к первоначальному замыслу и будем учиться создавать БД и заполнять ее средствами VB5.
     Откроем новый стандартный проект и выполним следующую последовательность действий сначала по созданию файла БД в формате Access, а затем по созданию формы для заполнения БД данными.

     • Выбираем команду Visual Data Manager из пункта Add-Ins (Надстройки) главного меню:

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

     • В пункте File выбираем формат будущей БД командой New...->Microsoft Access->Version 7.0 MDB с помощью последовательности выпадающих списков. Появляется диалог (используемый в VB5 для сохранения файлов) с именем Select Microsoft Access Database to Create, в котором мы выбираем или создаем каталог, в котором предполагаем сохранить файл с нашей БД, и задаем имя этого файла (obuchalka.mdb).

     • После сохранения в каталоге имени будущей БД это же имя появляется в заголовке окна VisData, а внутри него появляются 2 новых окна: SQL Statement (Инструкции SQL), которое нам не понадобится, и Database Window (Окно базы данных). Щелаем в нем правой клавишей и из появившегося контекстного меню выбираем New Table (Новая таблица):

после чего на экране появляется диалог Table Structure (Структура таблицы), используемый для задания структуры и свойств будущей таблицы.

     • В поле Table Name (Имя таблицы) диалога введем имя создаваемой таблицы Subject, после чего щелкнем по кнопке Add Field (Добавить поле), расположенной под окном списка Field List. Появляется диалог Add Field:


     • В диалоге Add Field мы будем последовательно вводить характеристики каждого из полей таблицы, заканчивая их ввод щелчком по кнопке OK. После ввода данных очередного поля его имя появляется в списке List диалога Table Structure. Для полей RusWord и EnglWord достаточно ввести только их имена в поле ввода Name, а значения по умолчанию в полях Type (Тип) и Size (Размер) оставить неизменными (Text и 50 соответственно). Это означает, что в данных полях должны располагаться значения строкового типа, причем длина строки не должна превышать (включена радиокнопка VariableField - Переменное поле) 50 символов. Для полей Image и Wave кроме их имен в поле Type вводится их тип Binary (Двоичный).
     По окончании ввода данных по всем полям диалог Add Field закрывается кнопкой Close (Закрыть).

     • Для поля, по которому предусмотрена сортировка (а также поиск), можно добавить индекс - специальную таблицу, которая будет содержать, во-первых, отсортированные значения индексируемого поля, а во-вторых, - для каждого такого значения - физический номер содержащей его записи данной таблицы БД (для одинаковых индексированных значений таблица индексов расположит их в очередности соответствующих физических номеров записей данной таблицы БД). Щелкаем по кнопке Add Index (Добавить индекс), расположенной под окном списка Index List формы Table Structure. В появившемся диалоге Add Index to мы задаем имя индекса (Ind) и выбираем в окне списка Available Fields (Доступные поля) поле EnglWord:

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


     • Выполняем сохранение созданной структуры таблицы: щелкаем по кнопке Build the Table (Построить таблицу). Окно Table Structure закрывается, а в окне Database Window на дереве структуры и полей создаваемой БД появляется узел таблицы Subject, развернув который получим следующий вид структуры БД:

     Сделав далее двойной клик по узлу с именем таблицы Subject, мы получим форму с именем Table:Subject для ввода данных в созданную таблицу:

     Однако эта форма непригодна для реальной работы (не поддерживает ввод русскоязычного текста) и, кроме того, не может быть использована независимо от надстройки. Поэтому мы закроем ее кнопкой Close и займемся созданием независимой формы для ввода данных в нашу БД.

     • Для создания независимой формы для ввода, удаления и модификации записей в таблицу Subject войдем в пункт Utility (Утилиты) окна надстройки VisData и выберем Data Form Designer (Конструктор формы БД). Появляется следующий диалог:


     • Вводим имя создаваемой формы (frmEnter_DB, например) и из списка RecordSource (Источник записи) выбираем таблицу, для ввода в которую создается форма (таблица Subject). В списке Available Fields в левой части диалога появляются имена полей таблицы. Щелкаем по кнопке >> для передачи всех имен в правый список Included Fields (Включенные поля) и получаем:

Щелкаем по кнопке Build the Form (Создать форму) и получаем требуемую экранную форму для ввода данных в нашу БД:

     Данную форму мы можем включать в любой проект, вызвав в нем диалог Add Form с закладкой Existing (Существующий), - щелчком мыши по пиктограмме Add Form, дублирующей соответствующую команду из пункта главного меню Project:

     В данный момент, однако, созданная форма уже включена в текущий, вновь открытый стандартный проект, в котором с самого начала уже имелась форма Form1. Если мы, захотев начать ввод данных в БД, запустим наш проект на выполнение, то загрузится именно форма Form1. Для загрузки же формы frmEnter_DB нам придется вызывать метод Show, помещая вызов frmEnter_DB.Show, например, в обработчик события Load формы Form1. Но в нашем случае можно поступить и иначе. Выполним команду Properties пункта главного меню Project и в окне появившегося диалога укажем форму frmEnter_DB как StartUp Object (Стартовый объект). Теперь именно эта форма будет загружена после запуска проекта на выполнение.
     Запустим проект и начнем вводить данные в поля данных формы frmEnter_DB. Ввод новых записей в таблицу БД начинается с того, что мы, при необходимости, перемещаемся на ее последнюю запись (для пустой таблицы этого делать не нужно) и щелкаем по кнопке Add. При этом имевшиеся в полях данные пропадают. Текстовые данные вводятся далее обычным образом. Для ввода же в поле OLE-данного необходимо произвести двойной клик в этом поле, после чего появляется следующий диалог:

Включена по умолчанию кнопка "Создать новый" и задан список разновидностей вставляемых OLE-объектов, которые можно создать с помощью соответствующих приложений. Для того чтобы создаваемые картинки открывались в OLE-объекте, необходимо создавать их в формате BMP или PCX, что можно сделать в приложении Corel PHOTO-PAINT. Для создания звуковых файлов выбирается тип объекта Звукозапись, которому соответствует приложение Фонограф. Создание графического объекта с помощью приложения не означает, разумеется, что оно обязано быть там "нарисовано"; нужная картинка может быть просто открыта в приложении.
     Если же у нас уже имеются заготовленные картинки в файлах указанных форматов или звуковые файлы, то нужно в диалоге "Вставка объекта" выбрать опцию "Создать из файла":

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

     • После того, как все данные будут введены в поля формы, необходимо щелкнуть по кнопке Update (Обновить). Данные запишутся в файл БД и отображаемый в поле элемента Data номер текущей записи увеличится на 1.
     Сходным образом можно произвести и модификацию любой записи таблицы. Удалить запись можно кнопкой Delete. По окончании корректировки БД форма закрывается кнопкой Close.

Работа с базой данных "Словарной обучалки"

     В нашем проекте уже создана форма (со свойством Name=frmEnter_DB), которая позволит самому пользователю "обучалки" дополнять и модифицировать предметный состав обучалки. Осталось дело за малым - использовать созданную по умолчанию пустую форму Form1 для задания основного окна приложения.
     В окне работающей новой версии нашей "обучалки" должны быть видны:

     • окно OLE-объекта (OLE1) с картинкой;

     • поле ввода для вывода русского слова (txtRus);

     • комбинированный список английских слов-переводов (dbcEngl);

     • кнопки запуска режима тестирования (cmdStart), прерывания тестирования (cmdBreak), запуска режима корректировки БД (cmdAdd) и выхода из программы (cmdEnd). Примерное расположение указанных элементов на форме (а также установленные во время проектирования зачения свойства Caption для тех из них, у кого это свойство есть) может быть таким:

     Кроме указанных видимых в фазе выполнения элементов, на форме произвольным образом располагаются следующие невидимые элементы (те, которые вообще не имеют свойства Visible, а также у которых оно при проектировании установлено в False):

     • элемент Data (Data1);

     • OLE-объекты OLE1 (для звукового отображения предметов) и OLE2 (для отображения звукового сигнала);

     • таймеры tmrSec, tmrDelay и tmrTest того же назначения и свойств, что и в предыдущей версии;

     • массив ярлыков (lblFault), посимвольно отображающих надпись "Неверно!" в вертикальном направлении в границах элемента OLE1;

     • ярлык (lblEngl), связанный с элементом Data1 по полю английских слов-переводов EnglWord.
     Принцип работы проектируемой программы подобен уже рассмотренному в предыдущем варианте. Единственной особенностью является то, что сравниваются не ключевые значения записей в разных списках, а сами значения поля EnglWord - для записи, случайно выбранной программой (отображается в связанном ярлыке lblEngl), и для пункта, выбранного обучаемым в комбинированном списке dbcEngl с помощью мыши, либо путем ввода с клавиатуры в поле ввода данного списка.
     Перед началом установки элементов и их свойств необходимо скопировать файл базы данных obuchalka.mdb в ту директорию, где расположены файлы программы, входящие в проект. Обратим внимание на некоторые свойства устанавливаемых элементов. Свойству DatabaseName элемента Data1 при проектировании задается текущий путь к файлу БД obuchalka.mdb. Однако мы обязаны предусмотреть, что фаза выполнения может происходить не обязательно на том же компьютере, что и проектирование, а потому путь (Path) по файловой системе к файлу БД может изменяться. К счастью, есть возможность программным образом переназначить этот путь так, что он будет определяться относительно каталога, в котором размещен либо файл проекта (при запуске интерпретатора кода программы из системы VB5), либо EXE-файл приложения (при независимом запуске загрузочного модуля).
     Для формирования набора записей RecordSet, в котором записи будут отсортированы в лексикографическом порядке по полю EnglWord, в качестве значения свойства RecordSource элемента Data1 запишем следующую инструкцию SELECT:
SELECT * FROM Subject ORDER BY EnglWord
     Здесь "*" означает "все записи", а ORDER BY - сортировку (по умолчанию - в порядке возрастания, т.е. "по алфавиту"). У связанных элементов - OLE1, OLE2, txtRus, lblEngl установим для свойства DataSource значение
     Data1, а для свойства DataField - имя отображаемого поля набора записей (у элемента dbcEngl эти значения устанавливаются у свойств RowSource и ListField соответственно).
     У элемента OLE3 в свойстве SourceDoc посредством открывающегося диалога "Вставка объекта" после установки опции "Создать из файла" находится средствами броузера файл с нужным нам звуковым объектом (звуковым сигналом), который и вставляется в OLE-объект. Установка значений других свойств элементов формы (и ее самой) уже нам знакома и не представляет никаких сложностей.
     Перейдем к рассмотрению кода, реализующего программный алгоритм.

=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•

     ¤  Возможно, вы уже обратили внимание на отдельные повторяющиеся фрагменты в разных обработчиках предыдущего варианта программы. Для уменьшения объема кода и ускорения компиляции такие фрагменты можно, как мы уже говорили, оформить в виде независимых, расположеннных на одном с обработчиками уровне, процедур-подпрограмм. Синтаксически они ничем не отличаются от обработчиков и могут просто вводиться с клавиатуры. Но можно воспользоваться и командой Add Procedure пункта Tools главного меню. В появившемся диалоге вводим имя создаваемой подпрограммы и переключаем опцию Public (по умолчанию) на Private (не нужно без надобности делать общедоступными имена из файла проекта). После выбора "OK" в тексте кода появляется заготовка создаваемой подпрограммы, аналогичная заготовкам обработчиков.
     Приводим секцию глобальных объявлений и тексты вспомогательных подпрограмм:
Dim li As Integer, ii_o As Integer, t As Integer, _
     n As Integer, nf As Integer, rc As Integer
' rc - количество записей в БД
Public Sub NewSelect() 'Случайный выбор очередного предмета
 li = Int(rc * Rnd)
 If li = li_o Then
  If li = rc - 1 Then
   li = li - 1
  Else
   li = li + 1
  End If
 End If
 Data1.Recordset.AbsolutePosition = li
 li_o = li
End Sub

Private Sub CheckChoice() 'Проверка ответа обучаемого
 n = n + 1
 If lblEngl.Caption = dbcEngl.Text Then
  OLE2.DoVerb
 Else
  Call OutFault(True)
  OLE3.DoVerb
  nf = nf + 1
 End If
 tmrDelay.Enabled = True
End Sub

Private Sub OutFault(boo As Boolean) 'Управление надписью
 Dim j As Integer                               ' "Неверно!"
 For j = 0 To 6
  lblFault(j).Visible = boo
 Next
End Sub
     Отметим использование следующих новых программных элементов. К ним относится упоминавшийся ранее объект RecordSet, к которому мы обращаемся через одноименное свойство элемента Data1. Свойство AbsolutePosition этого объекта возвращает или устанавливает номер текущей записи в нем (от 0 до rc-1, где rc - число записей в RecordSet). (Значение переменной rc, как мы увидим, устанавливается ранее в обработчике загрузки основной формы с помощью свойства RecordCount, возвращающего полное число записей в RecordSet. Причем для определения значения этого свойства, необходимо вначале "пролистать" все записи, перейдя к последней с помощью метода MoveLast).
     Другим новым элементом кода является метод DoVerb OLE-объектов, производящий открытие OLE-объекта (для редактирования или иных действий по умолчанию). Открытие "звукового" OLE-объекта приводит к проигрыванию его содержимого.

     ¤  В обработчике загрузки основной формы мы теперь должны инициализировать объект RecordSet и определять число записей в нем. Инициализация (то есть открытие доступа к нему в программе) объекта RecordSet, порожденного элементом Data с заданным при проектировании источником данных, может происходить автоматически - при первоначальном отображении значения поля связанным с элементом Data элементом. Но если источник данных устанавливается программно, то объет RecordSet должен инициализироваться методом Refresh. Как же устанавливается источник данных, если мы, как уже говорилось, хотим, чтобы его файл указывался относительно того каталога, в котором расположено приложение? Для этого используется объект App, который определяет или уточняет информацию о названии приложения, пути к его исполнимому файлу и файлам справки, о присутствии выполняющихся экземпляров этого приложения и т.д.:
Private Sub Form_Load() 'Инициализация набора записей
 Data1.DatabaseName = App.Path & "/obuchalka.mdb"
 Data1.Refresh
 Data1.Recordset.MoveLast
 rc = Data1.Recordset.RecordCount
 Randomize
 Call NewSelect
End Sub
     После инициализации объекта RecordSet число записей в наборе определяется, как мы видим, с помощью метода MoveLast и свойства RecordCount этого объекта.

     ¤  Проверка слова-перевода, выбранного обучаемым, осуществляется в двух нижеприведенных обработчиках: события Click и события KeyPress для списка dbcEngl. В последнем случае проверка производится, разумеется, не для каждого нажатия на клавишу (KeyPress) при вводе слова-перевода, а только при завершающем ввод нажатии клавиши ENTER. Это оказывается возможным потому, что при вызове обработчика этого события ему через аргумент передается ASCII-код нажатой клавиши (код клавиши ENTER - 13). Значение соответствующего параметра обработчика KeyAscii анализируется на равенство этому коду:
Private Sub dbcEngl_Click(Area As Integer) 'Клик слова-
 Call CheckChoice                     'перевода в списке
End Sub

Private Sub dbcEngl_KeyPress(KeyAscii As Integer) 'Ввод
 If KeyAscii = 13 Then      'слова-первода в поле списка
  Call CheckChoice
 End If
End Sub
     ¤  Для двух новых кнопок - прерывания теста и вызова формы для корректировки таблицы БД - появились два новых обработчика:
Private Sub cmdAdd_Click() 'Изменение состава БД
 Call cmdBreak_Click
 frmEnter_DB.Show
End Sub

Private Sub cmdBreak_Click() 'Прерывание теста
 cmdStart.Caption = "Запуск теста (60 сек)"
 tmrDelay.Enabled = False
 tmrSec.Enabled = False
 Call OutFault(False)
 tmrTest.Enabled = False
 Call NewSelect
 dbcEngl.SetFocus
End Sub
     Использованный для вызова формы метод Show (Показать), во-первых, загружает форму, если она не была загружена, а во-вторых, делает ее видимой. Снова скрывает форму (но не выгружает ее, т.е. программный доступ к ней сохраняется) метод Hide (Скрыть).
     В первом обработчике второй вызывается так, как будто это обычная, реализующая вспомогательный алгоритм подпрограмма.

     ¤  Остальные обработчики нашего приложения принципиально ничем не отличаются от своих аналогов в предыдущем варианте:
Private Sub cmdStart_Click() 'Запуск теста
 t = 0
 n = 0
 nf = 0
 frmEnter_DB.Hide
 Call NewSelect
 dbcEngl.SetFocus
 tmrSec.Enabled = True
 tmrTest.Enabled = True
End Sub
Private Sub tmrDelay_Timer()
 Call NewSelect
 tmrDelay.Enabled = False
 Call OutFault(False)
End Sub
Private Sub tmrSec_Timer()
 t = t + 1
 cmdStart.Caption = t & " сек"
End Sub
Private Sub tmrTest_Timer()
 Dim b As Integer
 cmdStart.Caption = "Запуск теста (60 сек)"
 tmrDelay.Enabled = False
 tmrSec.Enabled = False
 Call OutFault(False)
 Select Case n - nf
  Case Is > 8
   If nf = 0 Then
    b = 5
   Else
    b = 4
   End If
   If nf = 0 Then
    b = 4
   Else
    b = 3
   End If
  Case 3 To 5
   If nf = 0 Then
    b = 3
   Else
    b = 2
   End If
  Case Else
   b = 2
 End Select
 MsgBox "Дано " & n & " ответов; число Ваших ошибок: " _
        & nf & Chr(13) & "Отметка: " & b, vbInformation
 tmrTest.Enabled = False
 Call NewSelect
End Sub
     ¤  Последнее что мы сделаем, это заменим в обработчике события Click для кнопки Close формы frmEnter_DB вызов метода Unload Me (при его выполнении курсор "заклинивает" на форме песочных часов), выгружающего эту форму по окончании работы с ней, на следующий код:
Hide
frmObuchalka.dbcEngl.ReFill 'обновления содержимого связанного                     
'списка и его перерисовка после корректировки БД
frmObuchalka.dbcEngl.SetFocus
Кроме того, чтобы фокус гарантированно передавался после загрузки формы frmObuchalka сразу на поле ввода комбинированного списка, создадим обработчик:
Private Sub txtRus_GotFocus()
 dbcEngl.SetFocus
End Sub
=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•

Работа с БД: о чем мы только упомянем

     Средства работы с БД, некоторые из которых мы изучили в данной главе, представляют собой элементы программного интерфейса, позволяющего приложению запрашивать у базы данных тот или иной сервис. Для создания такого интерфейса в VB5 существуют, как уже упоминалось, "объекты доступа к данным" (DAO - Data Access Objects), одним из которых, в частности, является известный нам объект Recordset. Но база данных - это просто файл во внешней памяти; запросы же поступают к так назваемому ядру базы данных, которая вместе с программным интерфейсом составляет СУБД. Ядром БД в системе VB5 является процессор базы данных Microsoft Jet Egine версии 3.5, называемый также просто Jet-машиной. Подобный же процессор используется, в частности, и в СУБД MS Access 7.0. Поэтому особенно легко работается средствами VB5 именно с БД этого формата. На самом деле в VB5 имеется возможность работы с БД всевозможных форматов, в том числе и подключая их к Jet-машине.
     Мы, однако, до сих пор ни словом не обмолвились о том, что помимо программного интерфейса, рассчитанного на управление Jet-машиной, в VB5 существует реализация и программного интерфейса, рассчитанного на управление так называемыми "драйверами ODBC" (Open Database Connectivity - "открытые средства связи с базами данных"), представляющими динамические библиотеки функций подключения к БД разных типов. Если средства DAO и Jet-машины рассчитаны на БД, расположенные на персональном компьютере (Access, FoxPro, dBase, Paradox, Lotus), то средства драйверов ODBC предполагают работу в системе клиент-сервер с серверными БД (SQL Server, Oracle и др.), т.е. когда реальная обработка записей БД осуществляется СУБД, расположенной на файловом сервере, а клиентское приложение на удаленном компьютере только выдает запросы серверу (на языке SQL, например). Программный интерфейс с драйверами ODBC организуется уже не средствами DAO, а средствами "объектов удаленных данных" (RDO - Remote Data Objects). О RDO мы больше ничего не скажем, кроме того, что эти средства доступны только в Enterprise-редакции VB5. А вот про DAO дадим некоторую наиболее общую информацию, которая должна помочь в последующей самостоятельной ориентации.
     Прежде всего отметим, что элемент Data, который мы активно использовали, не является объектом DAO, хотя и может использоваться совместно с ними. Так мы и делали, работая с объектом DAO Recordset, обращаясь к нему через свойство Recordset элемента Data. Достоинством элемента Data при работе с БД является простота программирования (особенно с учетом возможностей связанных элементов), а также возможность определения БД и наборов записей на этапе проектирования простым выбором в диалогах и списках. Нередко используется создание набора записей как объекта DAO с последующей передачей его элементу Data посредством инструкции Set. Объекты DAO представляются либо как свойства (других объектов DAO, элемента Data), либо как объектные переменные (типа соответствующего объекта DAO). Вот пример создания объета Database путем открытия БД методом OpenDatabase и последующее создание набора данных:
Dim dbName As Database, recName As Recordset
Set dbName = OpenDatabase("Путь к файлу БД")
Set recName = dbName.OpenRecordset("Имя таблицы, " _
   "запроса, либо инструкция SELECT")
     Созданный таким образом набор можно, далее, передать для управления и отображения в связанных элементах элементу Data (с именем datName):
Set datName.Recordset = recName
     Преимущество такого подхода очевидны: мы можем формировать передаваемый элементу Data набор записей в фазе выполнения приложения по тому или иному запросу пользователя.
     Объект Recordset имеет множество методов, позволяющих работать с наборами данных: добавлять (AddNew), удалять (Delete), редактировать поля (Edit). Перед удалением или редактированием записи на нее необходимо предварительно позиционироваться с помощью методов группы Move, Find или Seek (контролируя свойство NoMatch, устанавливаемого в True при нерезультативном поиске), а также известного нам свойства AbsolutePosition. После обращения к методам AddNew или Edit поля вводимой или редактируемой записи заполняются новыми значениями, причем обращение к тому или иному полю производится указанием строки с его именем в круглых скобках после имени объекта Recordset. Окончательная физическая модификация БД производится последующим обращением к методу Update.
     Вот пример редактирования поля PhoneNumber набора запискей Person (находим запись со старым номером телефона и заменяем старый номер на новый):
Person.FindFirst "PhoneNumber = '" + gsOldNumber + "'"
'Параметром метода FindFirst является строковое выражение, 
'изображающее операцию сравнения. Обратите внимание на
'ее специфический синтаксис
If Person.NoMatch Then
  MsgBox "Запись отсутствует"
Else
  Person.Edit
  Person("PhoneNumber") = gsNewNumber
  Person.Update
End If
     Помимо модификации БД объекты DAO позволяют создавать новые БД в процессе работы приложения (а также менять структуру БД). Это бывает нужно при коммерческой поставке приложения, когда громоздкую БД,с которой работает приложение, проще создать "по месту", чем включать в поставку.
     Для создания новой БД используется метод объекта Workspace ("рабочая область") CreateDatabase ("создать БД). Объект Workspace определяет отдельный сеанс работы, отдельный поток обработки данных ("транзакцию") ядром БД (объект DBEngine). Работа с несколькими рабочими пространствами (они различаются с помощью механизма индексации) организуется в многопользовательских системах для различения пользователей. В обычных случаях, а также по умолчанию работа ведется в рабочей области Workspace(0). Метод CreateDatabase создает пустой файл БД; после этого необходимо задать структуру БД, используя для представления ее таблиц объект TableDef, для представления полей в таблицах - объект Field. Вот как выглядит процесс создания новой БД средствами DAO (для простоты в БД одна таблица, в которой одно поле):
Dim dbName As Database, tblName As TableDef, fldF1 As Field
Set dbName = WorkSpace(0).CreateDatabase("Путь_к_файлу_БД", _
   Порядок)
Set tblName1 = dbName.CreateTableDef("Таблица_1")
Set fldF1 = tblName1.CreateField()
fldF1.Name = "Поле_1"
fldF1.Type = Тип_поля 'Задается именованной константой
fldF1.Size = Размер_поля '(Существует не для всякого типа поля)
tblName1.Fields.Append fldF1 'Добавление описанного поля
                                    'в таблицу методом Append
     Если с типами и размерами полей мы уже познакомились при создании БД obuchalka.mdb, то параметр Порядок метода CreateDatabase нам еще не знаком. Он определяет (заданием именованной константы) язык (кодовую страницу), по алфавиту которого будут браться символы при сравнении строк. Значение dbLangGeneral задается для использования англо-американской кодовой страницы; для сравнения в соответствии с русским алфавитом используется константа dbLangCyrillic.
     Мы коснулись здесь только самых существенных моментов работы с БД через объекты DAO. Частности, как всегда, мы можем выяснять по справке VB5 в процессе решения практических задач.

Глава 4. КОМПОНЕТЫ ACTIVE X

"В мире компонентов нет эквивалентов", как говорили старые алхимики, а они-то знали что говорили. Покупайте советские часы — самые быстрые в мире.
     Если читатель этой книги сумел вслед за автором добраться до этой главы нашей книги и если в свое время он уже поучился программированию на обычном QBasic, PASCAL или C, то он уже "почувствовал разницу", о которой мы предупреждали во введении, между обычным и Visual-программированием. Говоря метафорически, "центр тяжести" приложения смещен в Visual-программировании в объекты; программный код, который пишет программист, является просто некоторым вспомогательным связующим звеном между объектами - компонентами приложения. При этом в Visual-языках естественным образом возникают целые библиотеки классов, участвующих в генерации объектов; парадигма программирования меняется настолько, что выражение "изучить язык Visual Basic (или Visual C++)" имеет смысл довольно отличный от смысла выражения "выучить язык QBasic (или C). Если в случае обычного алгоритмического языка его синтаксис и семантику (смысл синтаксических конструкций) довольно просто можно было описать с помощью естественного языка ("метаязыка"), и этого было бы уже достаточно, чтобы, проявляя то или иное "искусство программирования" (в том числе и технологию "структурного программирования"), создавать приложения любой сложности (если отвлечься от вопроса о трудозатратах), то в Visual-программировании помимо синтаксиса и семантики некоторого языка необходимо овладение технологиями построения приложения из компонентов, соответствующим инструментарием, предоставляемым для этого интегрированной средой, и уже существующими наработками объектов.
     Итак, в концепцию Visual-программирования заложено построение приложения из компонентов (хотя сама эта концепция возникла безотносительно Visual-программирования). В основе такого подхода лежит некий стандарт, называемый COM (Component Object Model) - "компонентно-объектная модель". Эта модель реализована в VB5 в форме технологии компонентов ActiveX.

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

     В VB5 возможно создание 3-х типов компонентов ActiveX: пользовательских элементов управления ActiveX (тип проекта - ActiveX Control), компонентов программ ActiveX (типы проектов - ActiveX DLL, ActiveX EXE) и документов ActiveX (ActiveX Document DLL, ActiveX Document EXE). Рассмотрим данные типы компонентов подробнее.

Пользовательский элемент управления ActiveX (OCX)

     Пользовательский элемент управления (ПЭУ) - это объект многократного использования. Также как и компонент программ (Code Component) он предоставляет приложению свои свойства, методы и события, с помощью которых обеспечиваются его функциональные возможности, однако в отличие от него ПЭУ размещается (средствами визуальной компоновки) в приложении-клиенте (или другом ПЭУ), в то время как компонент программ, являясь приложением-сервером, просто предоставляет часть своих функций приложению-клиенту. Во всех случаях приложение-клиент должно, разумеется, поддерживать технологию ActiveX, являясь, например, продуктом, созданным фирмой Microsift для работы под управлением Windows 9х (Office 97, Access 97, Internet Explorer, Visual FoxPro), либо просто любым приложением, созданным с помощью интегрированной среды VB5; можно использовать ПЭУ и в Web-страницах.
     Обычно ПЭУ включает в себя визуальную составляющую (которая, вообще говоря, необязательна) и программный код. Программный код и свойства ПЭУ хранятся в файле с расширением CTL (аналог файла формы FRM приложения VB5), который в фазе проектирования можно включать в любое приложение VB5. То же относится и к файлам графики (если ее элементы включены в ПЭУ), имеющим расширение CTX (аналоги файлов FRX). Но обычно готовый ПЭУ компилируется в файл OCX, причем в одном файле может содержаться несколько ПЭУ. Подготовка ПЭУ к распространению (также как и любого приложения) осуществляется с помощью программы Application Setup Wisard, входящей в состав VB5 .
     Мы уже познакомились с некоторыми ПЭУ, выбирая их из списка во вкладке Controls диалога Components, выводимого по соответствующей команде пункта главного меню Project, либо по Ctrl+T. Так, в первом варианте “Словарной обучалки” мы познакомились с элементом мультимедиа MMControl, во втором варианте - с элементами специализированных для БД списков DBList и DBCombo и сетки DBGrid. В таблице 4.1 приведены краткие описания еще 17-ти наиболее популярных ПЭУ из числа входящих в старшие редакции VB5 (Professional и Enterprise). В первом столбце таблицы приводится название компонента по списку во вкладке Controls диалога Components, во втором - имя класса элемента, под которым данный элемент появится в панели инструментов приложения, а также может быть найден в справочной системе для более подробного ознакомления.

     В данной главе, чуть позднее, мы и сами разработаем свой ПЭУ, а затем используем его в демонстрационном проекте.

Компоненты программ ActiveX и технология OLE

     ПЭУ ActiveX представляют собой один из 4-х типов (будем считать, что четвертый) реализации технологии OLE, разработанной фирмой Microsoft в соответствии с идеологией COM-архитектуры. Рассмотрим другие три типа.
     Технология OLE обеспечивала порядок использования в документе, созданном в одном приложении (например, в текстовом файле MS Word), объекта, созданного в другом приложении (например, картинки, созданной в графическом редакторе Paint). Первыми двумя типами технологии OLE были связывание (Linking) и внедрение (Embedding).
     При связывании объект из документа приложения-сервера (“источника”) подключался к документу приложения-клиента (“приемника”) с помощью ссылки (Link - “связь”) в приемнике на объект в источнике. Никаких копий связанного объекта при этом не создается и все изменения объекта в документе клиента сохраняются и в документе сервера. Связывание, таким образом, очень экономично по расходу внешней памяти, однако теряется свобода перемещения документа приложения-клиента на другой компьютер, т.к. объект из документа сервера не будет перемещен вместе с ним.
     При внедрении создается копия объекта из документа сервера, которая и вставляется (“внедряется”) в документ клиента. При этом расход ресурсов возрастает, зато появляется свобода перемещения документа клиента.
     С внедрением по OLE-технологии мы уже познакомились при заполнении БД “Словарной обучалки” визуальными и звуковыми отображениями предметов, причем соответствующие поля имели, как мы помним, тип OLE. Кроме того, там же мы использовали специальный элемент управления OLE (“контейнер OLE”, как еще его называют) для добавления в проект звукового сигнала. При этом в диалоге “Вставка объекта” в элемент OLE в случае выбора опции “Создание из файла”, в диалоге появлялся переключатель “Связь”, включением которого можно было выбрать вставку объекта связыванием вместо внедрения. Интегрированный таким образом объект должен допускать “активизацию по месту”: двойным щелчком мышью мы можем загрузить его в вызываемое приложение-сервер для возможности редактирования объекта.
     Связывание и внедрение были первыми двумя типами OLE-технологии, реализующей принципы COM. Третьим типом явилась автоматизация OLE (OLE Automation), с которой, собственно, и начинается технология компонентов ActiveX.
     Автоматизация OLE заключается в использовании приложением-клиентом объектов, их свойств и методов, предоставляемых приложением-сервером (то же: “сервером автоматизации OLE”, “компонентом программ ActiveX”). Эти предоставляемые объекты описываются в библиотеках OLE-автоматизации, на которые для использования их объектов нужно устанавливать ссылки.
     Необходимым (но не достаточным) условием такого использования объектов и методов приложения-сервера является его регистрация в операционной системе (осуществляемая обычно при инсталляции приложения). В интегрированной среде VB5 доступные библиотеки OLE-автоматизации могут просматриваться в диалоге References (Ссылки), вызываемом соответствующей командой из пункта Project главного меню:

     После установления ссылки мы можем ознакомиться с предоставляемыми нужной библиотекой объектами, методами и свойствами (вызов диалога Object Browser по F2).
     Существует два типа взаимодействия клиента и сервера (и, соответственно, два типа серверов автоматизации OLE): in-process (“внутрипроцессный”) и out-of-process (“внепроцессный”). Внепроцессный сервер - это приложение, которое представляет самостоятельно исполняемый файл (находящийся в собственном пространстве адресов, вне процесса исполнения приложения-клиента), который который активизируется клиентом, исполняемым в тот же момент в другом адресном пространстве. Если в VB5 создается такой внепроцессный OLE-сервер (компонент программ ActiveX), то выбирается проект ActiveX EXE, создающий исполняемый EXE-файл. Если же создается сервер, который будет исполняться в рамках процесса исполнения клиента, то выбирается проект ActiveX DLL (создание библиотеки динамической компоновки). Разновидностью внепроцессного сервера является удаленный сервер, расположенный на другом компьютере. Такая удаленная OLE-автоматизация использует развитие COM-архитектуры, называемое DCOM - Distributed COM (“распределенная COM”). Например, можно создать компоненты-серверы, обеспечивающие доступ к удаленной БД. Такие серверы (“серверы прикладных объектов” - Business Object Server) широко используются в архитектуре технологии клиент-сервер для обеспечения уровня бизнес-интерфейса между клиентом (приложением-клиентом пользователя, обращающимся к БД) и сервером (БД системы клиент-сервер: Microsoft SQL Server, Oracle, Sybase).
     Раз уж зашла об этом речь, попытаемся описать технологию клиент-сервер в двух словах.
     Базы данных создаются чаще всего не для автономного пользователя, а для целого коллектива, ну, например, фирмы. Это обуславливает объединение всех компьютеров в сеть, причем один (или не один) из компьютеров в этой сети является файл-сервером, содержащим БД. Управляет доступом пользователей к БД сетевая операционная система, а описанная модель совместной обработки данных называется сетевой информационной системой (ИС). Именно ей на смену и пришла модель многопользовательской системы “клиент-сервер”.
     Дело в том, что сетевая ИС страдает по меньшей мере двумя серьезными недостатками. Во-первых, каждый пользователь, обращаясь к таблице БД, делает ее на это время недоступной для других пользователей. Во-вторых, данные, с которыми работает пользователь, копируются при этом на его компьютер, что предопределяет высокую загруженность такой сети. В системах, работающих по технологии “клиент-сервер”, вместо запроса данных у сервера и последующей обработки на компьютере-клиенте, клиент посылает серверу запрос (мы уже знакомились с этим понятием; добавим только, что запрос клиента - это в большинстве своем обращение к “пользовательским хранимым процедурам”, которые, будучи созданы пользователем на языке SQL, хранятся на сервере БД в откомпилированном, готовом к выполнению виде) на получение определенного результата, а сервер, обрабатывая его, сам производит обработку данных БД и возвращает клиенту требуемый результат. Это - двухуровневая архитектура “клиент-сервер”: первый уровень - клиент, второй - клиент-серверная БД. При трехуровневой архитектуре между этими двумя уровнями включается уровень бизнес-интерфейса, обеспечиваемый как раз сервером удаленной OLE-автоматизации (сервером прикладных объектов). Его задача, во-первых, свести в одну точку (поэтому он располагается на стороне сервера БД) и стандартизировать процедуру обращения к серверу БД с клиентской стороны и во-вторых, - освободить клиента от учета тех или иных особенностей типа БД, с которой он работает. Общение клиента с сервером БД производится, таким образом, через программирование на сервере удаленной автоматизации свойств объектов уже упоминавшейся библиотеки RDO (Remote Data Objects). Утилиты VB5, обеспечивающие возможности DCOM, входят только в состав редакции Enterprise и здесь не рассматриваются.
     Библиотеки OLE-автоматизации (иначе - библиотеки типов) могут быть ресурсами и внутри- и внепроцессного OLE-серверов. Могут эти библиотеки и входить в файлы документов, и иметь форму независимых двоичных файлов (расширения OLB или TLB). Все эти варианты встречаются среди компонентов программ, представленных в приведенном списке диалога References. (К сожалению, библиотеки подобного типа имеются не у всех серверов OLE-автоматизации.) Объекты, предоставляемые сервером OLE-автоматизации, могут структурироваться в иерархическую объектную модель, отражающую, какие объекты сервера входят в состав других объектов (уточним еще раз: говоря в данном контексте об “объектах”, мы имеем в виду, разумеется, классы, порождающие соответствующие объекты; разница между объектом и его классом та же, что между детским куличиком и формочкой для его изготовления). Рассмотрим для примера объектную модель приложения Microsoft Word 97. Вызовем Object Browser (F2) и посмотрим в список Classes (Классы) при выбранном пункте в списке библиотек (слева сверху). В данном списке могут присутствовать (а могут и не присутствовать) несколько объектов (классов, конечно) с именем Application. Выбирая их мышью, мы можем видеть названия представляемых объектом Application приложений в нижней строке диалога. Так, про объект Application, представляющий “верхний уровень” приложения Word, будет сказано: Member of Word (член <объекта> Word):

     Если такого объекта Application в списке еще нет, то установим на него ссылку. Щелкнув в поле диалога правой клавишей мыши, выберем в контекстном меню известную нам команду References (дублирующую одноименную команду пункта Project главного меню). В появившемся диалоге с именами библиотек OLE-автоматизации включим переключатель в строке “Microsoft Word 8.0 Object Library” и выполним команды диалога “Применить” и “OK”. Нужный объект появится в списке окна Object Browser.
     Если при этом открыть верхний слева список библиотек, то можно увидеть в нем новый пункт - Word. Выбрав его, мы получим в списке Classes список классов именно этой библиотеки, причем для каждого выбраннного класса в правом списке будут отображаться его члены (свойства, методы и события). После выбора представляющего Word объекта Application в диалоге Object Browser, вызовем справку (F1), в которой будет графически представлена объектная модель данного сервера OLE-автоматизации:

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

=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•

     ¤  В некоторых узлах дерева представлены не простые объекты, а объекты-коллекции (Collection Object). Коллекция - это объект, объединяющий другие объекты (“входящие в него”). Коллекция имеет свои свойства и методы, независимые от свойств и методов входящих в нее объектов. Объекты, входящие в коллекцию могут иметь разный тип. Обращаются к ним, используя аппарат индексов (возможны и другие способы - по имени, например).
     В VB5 есть две встроенные коллекции: Forms, представляющая все формы проекта, и Controls - все его элементы управления.

     ¤  Для обращения к объектам нижележащего уровня, а также к их членам (свойствам и методам), используют навигационную точку точно таким же образом, как при обращении к элементам управления, содержащимся в других элементах управления, и их членам. При этом в качестве головного имени используется имя библиотеки (в нашем случае - Word - согласно списку библиотек в Object Browser), либо квалификатор Application, если этот объект единственный среди всех библиотек (согласно списку Classes в Object Browser), на которые были установлены ссылки в данном проекте. Использовать же для обращения к объектам, зависимым от объекта Application, навигационную цепочку вида: “Word.Application.” нет необходимости: квалификатор Application можно не использовать. Дело в том, что все свойства объекта Application, возвращающие зависимые объекты, являются, одновременно, глобальными членами библиотеки (пункт в списке Classes). Поэтому, например, для обращения к методу Add коллекции Documents, открывающему новый документ приложения Word, достаточно следующего предложения:
Word.Documents.Add
или, если коллекция Documents единственна в списке глобальных членов при выбранном пункте списка библиотек, то просто
Documents.Add
=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•

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

Работа с серверными объектами в приложении-клиенте

     После установки ссылки на сервер OLE-автоматизации (он же компонент программ ActiveX, он же сервер прикладных объектов), в приложении-клиенте нужно выполнить еще два шага, чтобы получить, наконец, возможность работать с серверными объектами. Первый из этих шагов - объявление объектной переменной, которая будет ссылаться на объект-сервер, второй - создание в клиенте объекта-сервера (в смысле экземпляра, а не класса!). Через свойства и методы головного объекта-сервера мы получаем, далее, доступ и к зависимым его объектам. При этом для обращения к многократно используемым зависимым объектам можно объявлять объектные переменные и устанавливать с их помощью ссылки на эти объекты.
     Мы опишем шаги по созданию в клиенте доступа к OLE-серверу на примере сервера Word 97, помня, однако, что все описанное, например, про объявление объектной переменной относится в той же мере вообще к любым, то есть и “внутренним” объектам любого приложения.

=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•

     ¤  Объявление объектных переменных имеет две основные формы.
     В первой из этих форм объявляется “общая” объектная переменная типа Object (как и во всех случаях, можно использовать и тип Variant), которая может ссылаться на объект любого типа (класса), например:
Dim objVar As Object
     Во второй форме объявляется объектная переменная определенного объектного класса, которая может ссылаться только на объекты того же типа (класса), например:
Dim objVar As Word.Selection
     Такой вариант объявления является предпочтительным, так как, во-первых, компилятор обнаружит связь переменной с классом еще во время написания кода приложения-клиента и обеспечит программисту сервис List Members (список членов объекта в ответ на ввод навигационной точки после имени переменной), а во-вторых, - компилятор использует в этом случае раннее связывание клиента с объектом (во время компиляции, а не выполнения, как при позднем), что предпочтительнее для отладки и по быстродействию.

     ¤  Создание экземпляра объекта-компонента программ (OLE-сервера) производится в клиенте одновременно с установкой на него ссылки для предварительно объявленной переменной объектного типа (общей, либо того же класса). Установление ссылки переменной на объект-сервер (существующий или создаваемый) производится инструкцией Set, поэтому все 3 варианта создания экземпляра сервера являются вариантами этой инструкции.
     Почему объект не может быть создан без установления на него ссылки? Дело в том, что по COM-спецификации объект должен вести счет устанавливаемым на него ссылкам с тем, чтобы при нулевом их количестве (когда в объекте генерируется событие Terminate - “уничтожение”) уничтожить себя. Поэтому созданный объект должен иметь по крайней мере одну ссылку. Принудительное зануление количества ссылок путем присвоения ссылочной переменной значения Nothing приводит к уничтожению объекта.
     Первый вариант использует для создания объекта (Selection, например) функцию CreateObject:
Set objVar = CreateObject(“Word.Selection”)
     Второй вариант использует ключевое слово New:
Set objVar = New Word.Selection
     Таким же образом можно создавать и несерверные объекты.
     Третий вариант использует вызов функции GetObject с пустой строкой в качестве значения первого аргумента, который должен специфицировать файл, содержащий объект определенного вторым аргументом класса (либо, при отсутствии второго аргумента, - объект, ассоциированный с расширением данного файла):
Set objVar = GetObject(“”, "Word.Selection")
(Если бы первый аргумент был опущен, данная инструкция устанавливала бы ссылку на уже существующий объект выполняемого приложения Word, либо, при отсутствии последнего, генерировала бы ошибку.) Отметим, что если тип сервера OLE-автоматизации внепроцессный, как в случае приложения Word, то для создания в клиенте его объектов он должен быть открыт (запущен на выполнение). Сделать это можно путем создании его объекта Application.
     Существует еще один способ создания объекта с использованием ключевого слова New в объявлении объектной переменной, однако ввиду его неэффективности он не рассматривается.

=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•

     В качестве примера, иллюстирующего процесс создания доступа в приложении-клиенте к серверу OLE-автоматизации, создадим стандартный проект, который по “нажатию” командной кнопки на форме будет загружать Word 97 и открывать в нем существующий документ, заданный своим полным именем (с путем). Весь код такого приложения будет содержаться в обработчике события Click для указанной кнопки:
Private Sub cmdWord_Click()
 Dim objWord As Object 'Объявляем объект
 On Error Resume Next '- Обратите внимание на эту инструкцию:
 '"В случае ошибки (On Error) продолжать (Resume) со
 ' следующей (Next) инструкции"
 'Открываем объект, установив ссылку на активный объект Word:
 Set objWord = GetObject(, "Word.Application")
 'Если такого объекта не оказалось,
 If objWord Is Nothing Then
  'то открываем его, запустив Word на выполнение:
  Set objWord = New Word.Application
  '(То же можно сделать, например, так:
  'Set objWord = CreateObject("Word.Application")  )
  'Если и после этого ссылка не удалась,
  If objWord Is Nothing Then
   'то распишемся в своем бессилии:
   MsgBox "Создать объект не удается!"
  End If
 End If
 objWord.Visible = True ' - Иначе окна приложения не увидим!
 'Откроем, пожалуй, наш документ:
 objWord.Documents.Open "C:\Мои документы\MyDoc.doc"
 'Уничтожаем объект (“Знак хорошего вкуса и традиций пример”):
 Set objWord = Nothing
End Sub
     Для завершения работы с Word 97 в клиенте необходимо использовать метод Quit объекта Application.
     Конечно, мы рассмотрели и проиллюстрировали здесь только принципиальную сторону OLE-автоматизации на компоненте Word 97.
     VB5 позволяет нам не только использовать в приложениях-клиентах чужие повторно используемые объекты (классы), но и создавать приложения-серверы. В любом из 7 типов проектов VB5, кроме стандартного EXE, можно создавать такие повторно используемые классы, объекты которых создает и использует клиент. Повторную используемость класса (объявленного обязательно как Public) определяет наличие у него свойства Instancing (“используемость”). Сейчас мы научимся создавать классы, а потом и превращать их, при надобности, в повторно используемые в приложениях-клиентах.

Учимся создавать класс

     Классы - это функционально обособленные конструкции, который описывают абстрактные “объекты”. Идея классов легла в основу объектно-ориентированного программирования (ООП), пришедшего на смену процедурному. Почему это произошло? Раньше, каждая процедура, назначением которой было выполнение определенной функции в рамках алгоритмической реализации решения задачи, работала с некоторым подмножеством множества всех данных программы. Эти подмножества пересекались между собой произвольным образом. Когда задачи и программы усложнились (вслед за стремительным ростом отношения производительность/стоимость микропроцессоров), начался резкий рост проблем поддержки программного продукта: изменения в составе данных программы вело к изменению заранее непредсказуемого количества зависимых от этого состава процедур. В ООП данные вместе с процедурами хранятся в классе и, как правило, недоступны извне. Этот качество класса называется инкапсуляцией. Когда объекты, построенные на основе классов, обмениваются между собой сообщениями (в VB5 - через генерацию событий, изменение значений свойств и вызов методов, как мы знаем), то, реагируя на них, они могут изменять только свои собственные данные (и посылать, в свою очередь, сообщения другим объектам).
     Идея класса оказалась прекрасно вписываемой в COM-архитектеуру. Дело в том, что компонентам COM также присуща инкапсуляция: клиент должен подключаться к компоненту через интерфейс, который не должен зависеть ни от реализации клиента, ни сервера.
     Мы создадим сейчас класс в форме модуля класса - одного из типов программных модулей, который добавляется к стандартному проекту (до сих пор мы работали только с модулями экранных форм).
     Итак, открыв стандартный проект, выполним команду Add Class Module, входящую в пункт Project главного меню. В появившемся окне на вкладке New запустим функцию создания пустого класса Class Module. Мы оказываемся в окне кода модуля. Как обычно, вверху окна, слева, располагается список Object, содержащий название секции объявлений (General) и названия объектов (у нас пока единственный “объект” с названием Class), а справа - список Procedure, содержащий названия процедур, относящихся к тому или иному “объекту”. Так как в окне кода содержится код, относящийся всегда к одному модулю класса, то вместо реального имени класса в списке Object указывается просто Class (так же, как в окне кода формы вместо реального имени формы - просто Form), что вряд ли можно считать удачной находкой разработчиков среды VB5. Для “объекта” Class в правом списке уже присутствуют названия (а в окне кода - вызываются заготовки соответствующих обработчиков) двух обязательных событий: Initilize (“инициализация”) и Terminate, которые возбуждаются объектом данного класса при его создании и уничтожении соответственно (в основном эти события используются в обработчиках тех программных модулей, где создаются объекты класса).
     Класс, который мы хотим сейчас создать, должен быть шаблоном для объекта, реализующего стек. Этот пример хотя и не будет простым, зато хорошо проиллюстрирует технику работы с объектами, в том числе и внутри порождающего их класса (“рекурсивные объекты”). Определим, для начала, понятие “стек” (а заодно и “очередь”).
     Пусть мы имеем множество каких-либо элементов, которое образовалось следующим образом. Сначала во множестве был один элемент. Потом к этому множеству добавили второй элемент следующим способом: в первый элемент поместили ссылку на второй (ссылка - это связь через адрес). И потом все время добавляли во множество i+1-ый элемент помещением ссылки на него в i-ый элемент (ссылка на последующий элемент). Множество, образуемое таким способом, называется однонаправленным списком, точнее, его разновидностью, называемой очередью. Ну, а если добавлять i+1-ый элемент путем помещения в него ссылки на i-ый (на предшествующий), то получится другая разновидность однонаправленного списка - стек (Stack - “кипа”). (Ну, а если добавлять сразу обе указанные ссылки, то получим двунаправленный список).
     Множество, являющееся списком, весьма сильно отличается от обычного множества с индексированными элементами. В множестве с индексом мы в любой момент можем обратиться по индексу к любому его элементу. В списке же мы можем обращаться к элементу только оттуда, откуда есть ссылка на этот элемент.
     В очереди нам доступна ссылка из первого элемента на второй, а первый элемент доступен непосредственно, т.к. он “крайний”, “в вершине” списка. В стеке, наоборот, в вершине всегда находится последний элемент, из которого доступен предпоследний, и т.д. Из сказанного следует, что стек заполняется с вершины, причем каждый новый элемент, становясь в вершину, “проталкивает” бывший в вершине элемент, а за ним и все остальные, “вглубь” стека. Разумеется, это проталкивание происходит на логическом уровне, физического перемещения данных при этом не происходит, все элементы остаются на прежних местах.
     В качестве элементов стека естественно использовать объекты, которые, с одной стороны, могут включать в себя в форме значений свойств любые данные, а с другой стороны, имеют доступ к себе посредством ссылок (через ссылочные переменные и свои свойства). Создаваемый класс (назовем его Stack) будет представлять объект (назовем его текущим), который всегда является вершиной стека. У этого объекта имеются свойство Value (хранимое в элементе стека данное) и Child (возвращающее объект того же типа (класса), а также два метода: Push (“протолкнуть”) и Pop (“выскочить”). Первый из них увеличивает глубину стека, создавая новый элемент в его вершине. Делается это так: 1) создается новый объект типа Stack; 2) его свойству Child устанавливается ссылка на объект, возвращаемый свойством Child текущего объекта, а в свойство Value копируется значение Value текущего объекта; 3) свойству Child текущего объекта устанавливается ссылка на новый объект. Таким образом, новый объект забирает у текущего хранимое в нем данное и как бы вставляется между текущим объектом и тем, на который тот ссылался до того свойством Child. Таким образом и происходит как бы “проталкивание” стека.
     Метод Pop реализует обратный к методу Push сдвиг стека в направлении его вершины. Происходит это путем уничтожения объекта, на который ссылается текущий в свойстве Child (после копирования в текущий данных из этого сыновнего объекта). Перед этим, ссылка на этот сыновний объект устанавливается для вспомогательной объектной переменной objElem, через которую текущему объекту передается ссылка в свойстве Child на “внучатый” объект, следующий за бывшим сыновним.
     В описанном здесь алгоритме вроде бы не предусмотрено создания никаких событий, возбуждение которых полезно было бы обрабатывать в содержащем объект приложении. Однако принято, чтобы по крайней мере события Initilize и Terminate, действующие внутри класса, могли возбуждаться классом и в приложении. Кроме того, мы создадим событие Void, возбуждаемое тогда, когда стек оказывается пуст, т.е. делается попытка “выталкивания” единственного оставшегося от него объекта.
     Таково устройство и принцип работы проектируемого нами класса. Теперь изложим последовательность работы по созданию его свойств, событий и методов.

=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•

     ¤  Первое что мы сделаем, - это объявим в создаваемом классе Stack объектную переменную objElem типа Stack, которая позволит объекту создавать объекты того же класса, порождая, таким образом, элементы стека:
Option Explicit 'Контроль за явной объявленностью 
                    'переменных
Private objElem As Stack
     ¤  Создать свойство можно двумя способами:
     - описать переменную с именем и типом свойства класса Public;
     - использовать процедуры свойств Property.
     В первом способе переменная-свойство доступна после создания объекта данного класса в приложении и для чтения, и для записи, причем контроль за корректностью устанавливаемых значений невозможен. Второй способ более гибок. Во-первых, процедуры свойств позволяют разделить функции чтения и записи значений. Так, процедура Property Get используется для считывания значений свойства, а процедура Property Let - для установки значения из приложения (если свойство представлено объектной переменной, то вместо процедуры Property Let используется Property Set). Во-вторых, использование процедур свойств позволяет кодировать проверку корректности устанавливаемых свойству значений.
     Свойство Child мы зададим первым из описанных способов, описав в разделе деклараций (General):
Public Child As Stack
     Для задания свойства Value вызовем командой Add Procedure пункта Tools главного меню соответствующий диалог и установим в нем имя свойства “Value”, а также опции Property и Public:

     После нажатия на OK мы увидим в окне кода две заготовки:
Public Property Get Value() As Variant
End Property
Public Property Let Value(ByVal vNewValue As Variant)
End Property
     Конечно, сами эти заготовки не способны хранить значение свойства. Для этого в декларациях объявляют специальную переменную уровня модуля:
Private mvar_Value As Variant
     После этого в процедуру Property Get Value помещается следующая инструкция, обеспечивающая считывание из приложения значения хранимого свойства:
Value = mvar_Value
     (Заметим, что если бы мы создавали свойство только для чтения, то заготовку Property Let Value надо было бы удалить, а вместо специальной переменной использовать для присвоения значения свойству константу, представляющую это значение).
     В процедуру Property Let свойство Value передает “по значению” (ключевое слово ByVal) через параметр vNewValue новое значение свойства. Его мы, естественно, запоминаем в нашей специальной переменной. Таким образом, процедуры свойств (вместе с добавкой объявления mvar_Value) приобретают следующий “стандартный” вид:
Public Property Get Value() As Variant
 Value = mvar_Value
End Property

Public Property Let Value(ByVal vNewValue As Variant)
 mvar_Value = vNewValue
End Property
     ¤  Говоря о создании событий класса, важно подчеркнуть различие между событиями возникающими (внутри класса) и событиями возбуждаемыми (в приложении, где создан объект класса). Когда говорят о создании событий класса имеют в виду именно возбуждаемые события.
     Для создания события класса нужно: - объявить событие (Public Event Имя_события); - возбудить событие (RaiseEvent Имя_события) в том месте кода модуля, где это требуется. В объявлении события после его имени могут располагаться круглые скобки с объявлениями параметров события. Тогда при возбуждении события стоящие на местах параметров аргументы будут переданы в приложение. В разделе деклараций поместим объявления создаваемых нами событий:
Public Event Initilize()
Public Event Terminate()
Public Event Void()
     После этого, в заготовки обработчиков событий Initilize и Terminate (событий, получаемых модулем класса от операционной системы) помещаем инструкции RaiseEvent Initilize и RaiseEvent Terminate соответственно. Что же касается инструкции RaiseEvent Void, то ее мы поместим в тело инструкции If, проверяющей внутри процедуры Pop наличие у текущего объекта ссылки на его сыновний (если таковой нет, то событие вызывается, а процедура Pop заканчивает работу по инструкции Exit - “выход”).

     ¤  Методы Push и Pop представляют собой обычные процедуры уровня доступа Public, реализующие описанный алгоритм. Приведем их в составе всего кода запрограммированного нами класса Stack:
Option Explicit
Private objElem As Stack
Public Child As Stack
Private mvar_Value As Variant
Public Event Initilize()
Public Event Terminate()
Public Event Void()
Public Property Get Value() As Variant
 Value = mvar_Value
End Property

Public Property Let Value(ByVal vNewValue As Variant)
 mvar_Value = vNewValue
End Property

Public Sub Push()
 Set objElem = New Stack 'Создаем новый объект со ссылкой objElem
 Set objElem.Child = Me.Child 'Ссылаемся сыном нового объекта
                                        'на сына текущего объекта 
 objElem.Value = Me.Value 'Копируем данное из текущего-в новый
 Set Me.Child = objElem 'Ссылаемся сыном текущего на новый
End Sub
Public Sub Pop()
 If Me.Child Is Nothing Then
  RaiseEvent Void
  Exit Sub
 End If
 Set objElem = Me.Child 'Сохраняем ссылку на сына текущего объ.
 Set Me.Child = Nothing 'Уничтожаем сына текущего объекта
 Set Me.Child = objElem.Child 'На место сына подставляем внука
 Me.Value = objElem.Value 'В текущий копируем данное, бывшее в
 Set objElem = Nothing 'уничтоженном сына и уничтожаем ссылку
End Sub                    'на этого сына

Private Sub Class_Initialize()
 RaiseEvent Initilize
End Sub

Private Sub Class_Terminate()
 RaiseEvent Terminate
End Sub
     В коде методов обратите внимание на два новые конструкции.
     Ключевое слово Me выполняет роль неявно описанной переменной, ссылающейся на текущий объект (уже встречалось в инструкции Unload кода формы, созданной для заполнения БД) .
     В инструкции If метода Pop в качестве условия используется операция ссылки на объект Is. Она возвращает True только тогда, когда левый и правый операнды ссылаются на один и тот же объект. В данном случае правым операндом выступает ключевое слово Nothing, означающее отсутствие объекта; следовательно для истинности условия необходимо, чтобы и левый операнд не ссылался ни на какой объект.

=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•

     Теперь протестируем созданный класс.
     Для этого используем форму нашего стандартного проекта. Разместим на ней 3 кнопки: “Создать объект”, “В стек” и “Из стека”. Естественно, первая из них будет создавать объект класса Stack, а две другие при каждом нажатии вызывать методы Push и Pop объекта. В обработчике события Void будет выдаваться соответствующее сообщение (“Стек пуст”). Правда, сделав все как надо, вы с удивлением обнаружите, что объект создается, методы его работают, но в списке объектов окна кода формы имя объекта (Elem) не появляется и, соответственно, в списке событий для этого объекта события Void нет. Дело в том, что объекты, имеющие события, должны объявляться с ключевым словом WithEvents. Вот код нашего теста:
Dim WithEvents Elem As Stack
Dim i As Integer, j As Integer 'i - натуральный ряд, заносимый в стек 
'j - флаг, устанавливаемый при чтении из пустого стека
Private Sub cmdClass_Click()
 Set Elem = New Stack
 Elem.Value = -1 'При пустом стеке объект хранит -1
 cmdClass.Enabled = False
End Sub
Private Sub cmdPop_Click()
 Elem.Pop
 If j = 1 Then
  MsgBox Str(Elem.Value)
  i = i - 1
 End If
End Sub
Private Sub cmdPush_Click()
 If j = 0 Then
  j = 1
 End If
 i = i + 1
 Elem.Push 'При каждом вызове в стек заносятся числа
 Elem.Value = i
End Sub
Private Sub Elem_Void()
 MsgBox "Стек пуст"
 j = 0
End Sub
     При первом “проталкивании” в стек заносится содержащаяся в объекте -1, при последующих - числа 1, 2, 3... и так далее. В любой момент эти числа можно извлекать из стека в обратном порядке, что контролируется выдачей их на печать.
     Сохраним наш проект в файле Pr_Class.vbp, модулю класса присвоим имя (при сохранении) Stack.cls.
     Помимо такого создания модуля класса “вручную” существует возможность обратиться к мастеру VB Class Builder (“Построитель классов”), запускаемому с помощью пиктограммы на уже известной нам вкладке New окна Add Class Module. С его помощью можно модифицировать и уже существующий класс.

Создаем компонент программ ActiveX EXE

     Итак, мы имеем модуль класса Stack.cls, который был добавлен к проекту с именем Pr_Class.vbp и который описывает класс Stack. Указанный проект имеет стандартный тип, поэтому наш класс известен пока только в рамках данного проекта; в списке диалога References строки с его описанием нет.
     Чтобы превратить проект со стандартным модулем класса в компонент программ ActiveX EXE, выполним следующие действия.

=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•

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

     ¤  Изменим тип проекта. Для этого откроем диалог Project Properties: Project_Pr_Class Properties. Во вкладке General диалога:

     • в списке Project Type выберем ActiveX EXE;

     • в списке Startup Object - None (“отсутствует”);

     • в поле Project Name введем новое имя проекта ClassSample;

     • в поле Project Description (Описание проекта) - Stack Object:

     Заметим, что именно установленное здесь описание проекта - Stack object - будет появляться в списке библиотек типов диалога References с момента регистрации компонента в системе. Регистрация же эта произойдет после компиляции. Имя, данное проекту - ClassSample - войдет при этом в список библиотек броузера объектов; в списке классов броузера будет имя класса Stack.
     Перейдя, далее, на вкладку Component диалога Project Properties, в группе радиокнопок Start Mode включим опцию ActiveX (если она еще не была включена).

     ¤  После описанного изменения типа проекта в окне его свойств, помимо имевшегося у класса Stack свойства Name, появляется уже упоминавшееся свойство класса Instancing (“используемость”). На рисунке 4.1 приведены все возможные (в проекте ActiveX EXE) значения этого свойства. Для нашего класса установим значение этого свойства равным 5 (MultiUse).


     ¤  Компилируем компонент: File_Make Pr_Class.exe. Создаваемому исполнимому файлу компонента даем перед компиляцией имя ClassStack.exe. При компиляции компонент регистрируется в системе и для него создается библиотека типов с именем ClassSample.

=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•

     Перейдем теперь к тестированию созданного компонента.
     Закроем проект с компонентом и откроем новый стандартный проект Project1. В нем вызовем диалог References и установим ссылку на описание созданного класса:

     Вызвав после этого Object Browser убедимся, что класс Stack доступен новому проекту вместе со всеми своими членами:

     Для дальнейшего тестирования компонента нам достаточно добавить к новому проекту ту же форму, которая была создана в проекте Pr_Class для тестирования класса. Для этого мы сначала удалим (с помощью контекстного меню в окне проводника проекта) текущую форму (Remove), а затем, через цепочку Add_Add Form_Existing выйдем в диалог, позволяющий выбрать файл добавляемой экранной формы. После этого, в свойствах проекта необходимо указать добавленную форму как Startup Object и - запускать проект на выполнение. При нажатии на кнопку “Создать объект” загруженной формы произойдет запуск экземпляра сервера - компонента программ ActiveX, предоставляющего объект Stack и его члены.
     Подобным образом можно создать и внутрипроцессный компонент программ (ActiveX DLL), для класса которого свойство Instancing по понятным причинам не может иметь значений 3 и 4.

Понятие об интерфейсах автоматизации

     Сейчас мы затронем те вопросы ActiveX-спецификации, которые, на первый взгляд, не слишком нам нужны для практического программирования, хотя на самом деле объясняется это только простотою рассматриваемых нами задач. В более сложных программах без этих знаний не будет понятен даже готовый код для понятного алгоритма.
     К сожалению, терминология, которую мы вынуждены будем использовать, предусматривает “перегрузку” весьма общего понятия “интерфейс” еще одним, очень частным смыслом, касающимся реализации COM-архитектуры. Все COM-объекты имеют встроенный интерфейс, состоящий из трех функций: AddRef, увеличивающей значение счетчика использования объекта; Release, уменьшающая значение этого счетчика при уничтожении ссылки переменной на него; и QueryInterface, проверяющий поддержку объектом того или иного дополнительного интерфейса. Итак, едва заговорив о встроенном интерфейсе, мы тут же перешли на какие-то дополнительные. Действительно, у компонента есть еще стандартный интерфейс автоматизации (4 функции, обеспечивающие предоставление членов компонента клиенту) и всевозможные дополнительные интерфейсы, представляемые посредством неких классов (“абстрактных”). Пора перейти от частного к общему: в компонентных технологиях

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

Полиморфизм и повторное использование кода

     Как уже упоминалось, Visual Basic не является языком объектно-ориентированного программирования (ООП), поскольку понятие ООП включает возможность наследования. Наследование - это возможность сопоставления с создаваемым классом одного или даже нескольких уже созданных классов в качестве родительских. Все члены родительских классов будут при этом являться и членами создаваемого класса, в котором они, обычно, переопределяются в соответствии с его характеристиками. Например, создавая классы Osyol и Solovey, можно определить их родительским классом класс Szivotnye, имеющий метод Sound (Звучать). Этот метод будет переопределен в классах Osyol и Solovey в соответствии с требуемой тональностью, продолжительностью и т.д.
     Помимо того, что наследование является, наряду с инкапсуляцией, основополагающим принципом ООП, оно обеспечивает один из способов реализации третьего принципа ООП - полиморфизма.
     Полиморфизм - это возможность использовать одноименные методы для однотипных действий у объектов разных классов. Понятно, что если эти разные классы являются наследниками общего класса, то переопределение родительских методов в этих классах как раз и создает нужную предпосылку для полиморфизма. Мы можем писать определения функций, в которых вызываются методы объектов- параметров типа родительского класса, а при вызове этих функций им будут передаваться через аргументы объекты-наследники, которые будут в реальности вызывать уже переопределенные в классе-наследнике одноименные методы. (Это возможно потому, что по принципу наследования, объект сыновнего класса всегда является и объектом родительского класса.) Такой механизм реализации полиморфизма называется поздним связыванием,

Связывание - это процесс открытия доступа к объекту при его создании.

поскольку выяснение того, какой именно вариант кода метода будет выполняться, происходит на этапе выполнения программы, а не ее компиляции. На этом механизме, в частности, построен полиморфизм языка ООП С++.
     По другому реализуется полиморфизм в VB5.
     В его основу положена концепция интерфейса, в частности, возможность определения дополнительных интерфейсов в так называемых абстрактных классах. Вот, приблизительно, как это выглядит.

     • Создается модуль класса, содержащий определение абстрактного класса, представляющее объявления процедурных заготовок для методов и свойств сервера. Делается это тем же образом, что и создание любого компонента: а) открывается проект ActiveX EXE или DLL; б) добавляются заготовки свойств и методов сервера, для которого создается данный интерфейс; в) задается имя данного класса, идентифицирующее созданный интерфейс; г) строится исполняемый файл данного проекта. После этого, при регистрации созданного компонента его автоматически создаваемая библиотека типов будет содержать информацию о имеющихся в классе заготовках, которая через инструкцию Implements (Комплектующие) передастся серверу.
     Заготовки, о которых мы сейчас говорим, это совсем не обязательно поцедурные скобки, наподобие тех, которые вставляет компилятор в программный код для обработчиков событий. Эти заготовки чаще всего уже содержат определенный код, называемый общей реализацией, зависящий, как правило, от параметров. При переопределении какой-либо процедуры абстрактного класса в классе сервера сначала создается объект абстрактного класса (он при этом называется объектом типа интерфейса), а затем, в переопределяющей процедуре вызывается переопределяемая процедура с аргументами, заданными классом сервера. Это и есть повторное использование кода (причем двоичного) при полиморфизме. Разумеется, в переопределяющей процедуре можно поместить и дополнительный код, а можно поместить только дополнительный код без вызова переопределяемой процедуры. Все это будут разновидности частной реализации.

     • При разработке сервера: а) создается ссылка на библиотеку типов для компонента с абстрактным классом; б) инструкциями Implements устанавливаются связи классов сервера (“вторичных классов”), использующих члены абстрактного класса, с этим абстрактным классом; в) реализуются описанным выше образом члены абстрактного класса в процедурах вторичных классов ; г) компилируется проект сервера.

     Для шагов б) и в) отметим следующее.
     Инструкция Implements, помещаемая в разделе деклараций серверного модуля, имеет форму: Implements ИмяАбстрактногоКласса
     Имена процедур, в которых реализуются (переопределяются) члены абстрактного класса, строятся с помощью символа подчеркивания: Private Sub ИмяАбстрактногоКласса_ИмяЧленаАбстрактногоКласса      • При разработке клиента нужно дополнительно, помимо ссылки на библиотеку типов сервера, установить ссылку и на библиотеку типов компонента с абстрактным классом.
     Приведем пример кода, иллюстрирующего сказанное.
'Код метода для абстрактного класса Szivotnye
Public Sub Sound(ByVal Tone As Double, _
                    ByVal Duration As Double, ByVal Sort As Integer)
 'Здесь какой-то код, представляющий общую реализацию метода
End Sub

'Один из классов сервера, использующий абстрактный класс Szivotnye:
Implements Szivotnye 'Объявляем класс Szivotnye интерфейсом
Private objSziv As Szivotnye 'Объявляем переменную класса интерфейса
'Обработчик инициализации объекта класса сервера:
Private Sub Class_Initilise() 
 Set objSziv = New Szivotnye 'Создание объекта интерфейса
End Sub

'Переопределение метода:
Private Sub Szivotnye_Sound(ByVal Tone As Double, _
                       ByVal Duration As Double, ByVal Sort As Integer)
 'Сначала - повторное использование кода общей реализации:
 Call objSziv.Sound(Tone, Duration, Sort)
 'А потом -  можно добавить и дополнительный код
End Sub
     Важно отметить, что представленное использование интерфейса для реализации полиморфизма основано не на позднем, а на раннем, то есть на этапе компиляции, связывании: обнаружив в классе инструкцию Implements, компилятор, с помощью библиотеки типов интерфейса и стандартного COM-интерфейса IDispatch (подробности мы опускаем), обеспечивает переход на используемые сервером члены интерфейса. Это дает существенный выигрыш по времени по сравнению с поздним связыванием.

Создаем пользовательский элемент управления (ПЭУ)

     Создание собственных ПЭУ (иначе - OCX-элементов управления, элементов управления ActiveX) - новое и очень удобное, основанное на COM-модели, визуальное средство повторного использования программного кода при разработке приложений. Оно применялось, например, в построении приложений MS Office и Internet Explorer. ПЭУ отличается от уже изученных нами компонентов программ тем, что помимо предоставления своих свойств и методов приложению-клиенту предоставляет также визуальный интерфейс пользователю. Поэтому ПЭУ широко используют для реализации тех или иных функциональных возможностей в формах приложений, а также в приложениях-документах ActiveX, работающих в сети Internet.
     Первое, что нужно сделать, - это уяснить себе функции и сценарий работы разрабатываемого ПЭУ.
     ПЭУ, который мы сейчас создадим, представляет собой часы-будильник, который имеет 2 альтернативных функции: функцию часов (отсчета секунд системного времени) и функцию будильника (отсчета секунд до границы установленного интервала либо до установленного граничного времени суток, при достижении которых ПЭУ возбуждает событие Timer). ПЭУ имеет 2 режима: установки той или другой функции и их параметров и отображения отсчета секунд. Размер циферблата можно при этом устанавливать на этапе проектирования. Управление ПЭУ средствами его визуального интерфейса (т.е. содержащимися в нем другими элементами управления - командными кнопками, полями ввода и т.д.) дублируется программным управлением с помощью свойств и методов. Для чего можно использовать такой ПЭУ?
     Представьте себе, что у вас в кабинете информатики проводится олимпиада, ну, допустим, по химии, когда в течение 3-х часов надо решить максимальное число из 10 данных задач. Как контролировать время? Часов у многих участников может и не быть, а значит они постоянно будут отвлекаться (и отвлекать соседей) вопросом: “Сколько осталось?”. Положение могут спасти лишь принесенные учителем из дома настенные часы, либо... Открывается новый проект, устанавливается ссылка на библиотеку типов компонента, ПЭУ Clock размещается на форме (во весь эран) и используется в функции будильника. Для события Timer пишется обработчик из двух строк, вызывающий диалог с сообщением и звуковой сигнал. Проблема решена!
     Другой пример. Вы разрабатываете игровую, либо обучающую программу, в которой лимитируется время ответов (индивида или группы), либо игры. И здесь незаменим наш ПЭУ. Как его используют при этом, будет показано, далее, на примере разработки игры “Крестики-нолики”.

Формирование визуальной составляющей ПЭУ


=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•

     ¤  Откроем проект типа ActiveX Control. В отличие от окна стандартного проекта, окно проекта ПЭУ содержит не экранную форму, а формоподобный объект-контейнер класса UserControl - базового класса всех ПЭУ. Имя этого объекта-контейнера по умолчанию - UserControl1, однако мы изменим его на Clock и именно под таким именем класс нашего ПЭУ будет зарегистрирован в системе; именно так будет обозначаться его значок в палитре инструментов того проекта, где объект ПЭУ класса Clock будет использоваться (имя по умолчанию при этом такому объекту при размещении его в форме будет дано Clock1). Всеми этими подробностями мы еще раз подчеркиваем: создавая ПЭУ, мы создаем не экземпляр объекта UserControl, а класс, базирующийся на классе-шаблоне UserControl. Как и экранные формы, объекты-контейнеры UserControl имеют могут содержать и экземпляры других элементов управления (они в этом случае называются составляющими), и модули кода. Процесс размещения составляющих элементов в объекте-контейнере UserControl ничем не отличается от процесса размещения элементов управления на экранной форме. Первоначально же окно проекта ПЭУ имеет следующий вид:


     ¤  Для тестирования создаваемого ПЭУ (а тестировать мы его будем во время разработки постоянно) необходимо открыть вспомогательный проект командой File_Add Project. Выберем стандартный тип проекта, после чего наряду с первым проектом (элемента ActiveX) Project1 будет открыт стандартный проект Project2 с формой Form1, в которую мы и будем помещать при тестировании наш ПЭУ. Теперь оба проекта образуют группу Group1.vbg, как это будет видно при сохранении проектов.
     Изменим имя и описание проекта ПЭУ Project1 - точно таким же образом, как мы делали это в проекте компонента Stack: в окне Project Properties дадим проекту имя User_Clock, которое, как мы помним, будет присутствовать в списке библиотек объектов окна Object Browser, и введем описание проекта как Clock-Alarm (Часы-Будильник), строка с которым появится в диалоге Components (Ctrl+T) проекта, а после включения соответствующего ей переключателя - и в диалоге References (для установления ссылки на соответствующую библиотеку типов):

     Изменим также имя тестового проекта на Test_Project (для этого можно просто изменить значение свойства Name в окне свойств). После этого в окне проводника проекта мы будем иметь следующую структуру группы проектов (хотя папки Forms в проекте User_Clock на этом этапе еще нет...):


     ¤  Протестируем работу нашего ПЭУ, который, признаться, состоит пока из одного пустого контейнера. Поэтому разместим в нем ярлык Label1 (чтобы отличать его от контейнера, установим свойство Border Style в Fixed Single). Имя ярлыка изменим на lblTime.
     Для начала заметим, что в правом нижнем углу панели инструментов проекта Test_Clock появился серый (неактивный) значок, которым обозначается любой создаваемый ПЭУ (потом этот значок можно изменить) и которым обозначена “веточка” ПЭУ (файл с расширением ctl) в приведенной выше структуре в окне проводника проекта группы. Чтобы активизировать ПЭУ в тестовом проекте, надо закрыть окно его проекта. Поэтому двойным кликом по файлу ПЭУ в проводнике проекта сделаем доступным окно его проекта и закроем его. После этого двойным щелчком по файлу формы (Form1.frm) тестового проекта в проводнике проекта снова вернемся в окно тестового проекта, где значок ПЭУ на панели инструментов “проявится”, перейдя в активное состояние.
     Сразу скажем, что и далее, всякий раз, когда мы будем переходить в окно тестового проекта после работы в окне проектаПЭУ, последнее надо предварительно закрывать; в противном случае ПЭУ на форме тестового проекта будет покрыт косой штриховкой:

     Итак, после активизации в панели инструментов тестового проекта значка нашего ПЭУ, мы обычным образом размещаем элемент управления класса Clock на форме Form1. Размеры размещенного объекта Clock1 можно было бы при этом устанавливать произвольными. Однако не при всех размерах ПЭУ содержащийся в нем ярлык будет виден: его положение относительно левой и верхней границ ПЭУ (значения свойств Left и Top соответственно) неизменны! Сразу ясно, что это не может нас устроить, т.к. отображение циферблата должно производиться при любом размере ПЭУ и размер цифр должен быть ему пропорционален. Эту проблему нужно решить программно, используя событие Resize (“изменение размера”).

     ¤  Перейдя в окно кода (либо с помощью броузера объектов), мы можем обнаружить, что у нашего ПЭУ имеется 4 события, среди которых нет события Resize. Но это лишь значит, что ПЭУ не возбуждает этого события вовне, что и не удивительно, так как менять размеры ПЭУ в фазе выполнения содержащего его приложения по умолчанию нельзя. Однако для контейнера класса UserControl такое событие возбуждается системной функцией при размещении ПЭУ в форме приложения на стадии проектирования. Открыв окно проектирования ПЭУ, мы обнаружим это событие в списке окна кода и вставим в процедурные скобки его обработчика следующий код:
Private Sub UserControl_Resize()
 With lblTime
  .Font.Size = UserControl.ScaleHeight * 41 / 79.8
  .Left = (UserControl.ScaleWidth - .Width) / 2
  .Top = (UserControl.ScaleHeight - .Height) / 2
 End With
End Sub 
     Подобный код (он написан, конечно, уже после того, как мы “поиграли” внешним видом нашего ПЭУ с расположенным на нем ярлыком-циферблатом) почти всегда необходим, так как он обеспечивает сохранение пропорций визуальной составляющей ПЭУ при его размещении в приложении-клиенте. Обратим внимание, что у объекта-контейнера UserControl (как и в том случае, если бы вместо него использовался его праобраз - экранная форма) используются размерные свойства с присоединением “Scale”, что указывает на действительность представляемых ими размеров именно в единицах внутренней области контейнера.
     А откуда взялись у нас константы 41 и 79.8?
     Содержащее их предложение нормирует (на высоту UserControl) с помощью этих констант размер (Size) шрифта (Font), которым отображается время надписью в ярлыке lblTime. Так как свойство ScaleMode у UserControl было предварительно установлено в 2, что означает измерение размеров в типографских пунктах (Point), то из указанного предложения следует, что при высоте нашего ПЭУ в 79.8 пункта высота надписи будет 41 пункт, и эта пропорция будет поддерживаться при изменениях размеров ПЭУ при его размещении в форме приложения-клиента. А что будет с размерами содержащего надпись ярлыка? Они буду автоматически изменяться вслед за изменениями размеров надписи. Это обеспечивается установкой у lblTime свойства AutoSize=True и WordWarp=False (последнее запрещает перенос строк в надписи). Установим и Caption= 00:00:00. Так будет устроена лицевая сторона нашего ПЭУ, где в ярлыке lblTime будет отображаться ход времени часов, либо будильника. Все же его элементы управления мы поместим на “изнанке” (там же будет и “дубликат” лицевого ярлыка с именем lblTime_1).

     ¤  Создание “изнанки” будет состоять в том, что мы накроем объект-контейнер UserControl элементом управления SSTab (см. таблицу 4.1) в форме одиночной вкладки, на которой и разместим элементы управления создаваемого ПЭУ. Переход с “изнанки” на “лицевую” сторону будет осуществляться двойным кликом, изменяющим свойство видимости (Visible) вкладки (а вместе с нею - и всех размещенных на ней элементов).
     После покрытия нашего ПЭУ элементом SSTab, оставим у него одну вместо 3-х по умолчанию вкладок (изменяя свойства Tabs и TabsPerRow) и придав свойству Caption значение “Установка режима”. Расположим на вкладке другие элементы управления, устанавливая их имена и значения “надписевых” свойств (Caption или Text) как показано рис. 4.2. (Кстати, именно после компоновки изнанки мы и получили “начальные” размеры UserControl, из которых высота 79.8 вошла в нормировочный коэффициент в обработчике события Resize.) Поле ввода txtInt предназначено для ввода граничного значения интервала установки будильника; после переключения ПЭУ в режим будильника на выполняющей эту функцию кнопке cmdCheck появляется надпись “В режим часов”. Назачение других элементов управления очевидно.

     ¤  Установим подходящий цвет букв надписи в ярлыке lblTime (свойство ForeColor) и цвет поверхности контейнера UserControl (его свойство BackColor). Фон надписи не будет иметь для нас значенния, так как позднее, при создании внешних свойств нашего ПЭУ, мы свяжем его со свойством фона BackColor самого ПЭУ. Напишем теперь начальную версию обработчика события Time таймера tmrSec - для тестирования работы проектируемого элемента Clock:
Private Sub tmrSec_Timer()
 Static strs As String, tmr As Date
 tmr = Time
 strs = Format(tmr, "hh:nn:ss")
 lblTime.Caption = strs
End Sub
     Здесь текущее время, возвращаемое стандартной функцией Time при каждом событии Timer таймера, преобразуется из “американского” 12-часового формата в строку 24-часового формата стандартной функцией Format (см. в Приложении I “Преобразование типов данных”). Сохраним изменения в проекте User_Clock и закроем его, после чего перейдем на форму тестового проекта. Наш ПЭУ предстанет на ней примерно в таком виде:

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

=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•

Создание открытого интерфейса ПЭУ

     В действительности обработчик события Timer должен быть гораздо сложнее, так как в нем (в режиме будильника) нужно проверять достижение границы временного интервала, причем при двух способах ее задания: длительностью интервала, либо граничным значением времени суток, и выполнять заданные управляющие действия по ее достижении. Однако прежде чем мы перейдем к написанию обработчиков, реализующих алгоритм ПЭУ, займемся созданием его открытого интерфейса, то есть определением предоставляемых членов класса элемента Clock, которые станут “видны” приложению-клиенту при размещении в нем этого элемента.
     Для определения открытого интерфейса ПЭУ и создания соответствующего кода удобно воспользоваться услугами мастера интерфейса элемента управления (ActiveX Control Interface Wizard). Для его включения в состав подменю настроек (Add-Ins) главного меню вызовем диалог менеджера надстроек (Add-In Manager): Add-Ins_Add-In Manager и установим там флажок в пункте VB ActiveX Control Interface:

После нажатия на OK в подменю Add-Ins появится команда вызова указанной надстройки.
     Запустим мастер. Работа с ним будет проходить в последовательно сменяющих друг друга по нажатию на кнопку “Next >” диалогах. В любой момент можно вернуться к предшествующим диалогам, используя кнопку “< Back”.
     Первый из диалогов является вводным и описывает возможности мастера.
     Следующий диалог - Select Interface Members - позволяет определить так называемые стандартные предоставляемые члены класса элемента Clock. Большинство из этих членов, указанных в списке Available names (Доступные), являются и членами базового класса UserControl. Будем выбирать необходимые нам и перебрасывать их в список Selected (Выбранные) нажатием на кнопку “>”:

     Таким образом мы выберем:
     - свойство Enabled, регулирующее доступность элемента Clock для управления из клиента;
     - свойства BackColor и ForeColor, определяющие цвета фона и символов элемента;
     - свойство Font, возвращающее объект Font, описывающий шрифт этих символов;
     - свойство Interval, задающее величину интервала срабатывания будильника (в форме длительности - при optType(0)= True - либо в форме граничного времени суток в противном случае);
     - событие Change, которое будет возбуждаться элементом Clock при изменении отображаемого им значения времени;
     - событие Timer, возбуждаемое по истечении заданного в режиме будильника интервала.
     В следующем диалоге Create Custom Interface Members (Создание пользовательских членов интерфейса) нам предлагается создавать члены нашего ПЭУ, имеющие нестандартные имена:

     По нажатию кнопки New в диалоге My Custom Member мы будем поочередно вводить имена этих членов и указывать их тип (свойства, методы или события). Таким образом мы зададим:
     - свойство TimeCaption, содержащее строку с отображаемым в данный момент значением времени;
     - свойство TypeInterval, содержащее признак формы задания величины интервала срабатывания будильника (подобно optType(0): True - задана длительность интервала, False - граничное время суток);
     - методы Start, Stopp (т.к. имя Stop зарезервировано системой и будет отвергаться) и Check, дублирующие программным образом нажатия на кнопки cmdStart, cmdStop и cmdCheck соответственно.
     На следующем шаге Set Mapping (Установить связи) мы осуществляем связывание (там, где это необходимо) отобранных и созданных нами членов класса нашего ПЭУ с членами классов составляющих элементов управления, размещенных в контейнере UserControl, и с членами класса самого контейнера UserControl:

     Мы свяжем:
     - члены BackColor, ForeColor, Font, Change - с одноименными членами ярлыка lblTime;
     - TimeCaption - с lblTime.Caption;
     - Change - c lblTime.Change;
     - Enabled - с UserControl.Enabled;
     - Interval - c txtInt.Text;
     - TypeInterval - c optType(0).Value.
     Члены Start, Stopp, Check и Timer останутся несвязанными. Пропустив диалог установки атрибутов для несвязанных членов (Set Attributes), мы оказываемся в финальном окне, где нам остается только нажать на кнопку Finish и, быть может, сохранить файл с итоговым отчетом о работе мастера.
     По окончании работы мастера к приведенному коду обработчиков UserControl_Resize и tmrSec_Timer добавится (не считая комментариев) следующий код:
Option Explicit
Event Change()
Event Timer()
Public Property Get Enabled() As Boolean
    Enabled = UserControl.Enabled
End Property

Public Property Let Enabled( _
           ByVal New_Enabled As Boolean)
    UserControl.Enabled() = New_Enabled
    PropertyChanged "Enabled"
End Property

Public Property Get TimeCaption() As String
    TimeCaption = lblTime.Caption
End Property

Public Property Let TimeCaption( _
           ByVal New_TimeCaption As String)
    lblTime.Caption() = New_TimeCaption
    PropertyChanged "TimeCaption"
End Property

Public Property Get Interval() As String
    Interval = txtInt.Text
End Property

Public Property Let Interval( _
           ByVal New_Interval As String)
    txtInt.Text() = New_Interval
    PropertyChanged "Interval"
End Property

Public Property Get Font() As Font
    Set Font = lblTime.Font
End Property

Public Property Set Font(ByVal New_Font As Font)
    Set lblTime.Font = New_Font
    PropertyChanged "Font"
End Property

Public Property Get BackColor() As OLE_COLOR
    BackColor = lblTime.BackColor
End Property

Public Property Let BackColor( _
           ByVal New_BackColor As OLE_COLOR)
    lblTime.BackColor() = New_BackColor
    PropertyChanged "BackColor"
End Property

Public Property Get ForeColor() As OLE_COLOR
    ForeColor = lblTime.ForeColor
End Property

Public Property Let ForeColor( _
           ByVal New_ForeColor As OLE_COLOR)
    lblTime.ForeColor() = New_ForeColor
    PropertyChanged "ForeColor"
End Property

Public Property Get TypeInterval() As Boolean
    TypeInterval = optType(0).Value
End Property

Public Property Let TypeInterval(ByVal New_TypeInterval As Boolean)
     optType(0).Value() = New_TypeInterval
    PropertyChanged "TypeInterval"
End Property

Public Function Start() As Variant
End Function

Public Function Stopp() as Variant
End Function

Public Function Check() As Variant
End Function

Private Sub UserControl_ReadProperties( _
               PropBag As PropertyBag)
    UserControl.Enabled = PropBag.ReadProperty( _
               "Enabled", True)
    lblTime.Caption = PropBag.ReadProperty( _
               "TimeCaption", "00:00:00")
    txtInt.Text = PropBag.ReadProperty( _
               "Interval", "00:00:00")
    optType(0).Value = PropBag.ReadProperty( _
               "TypeInterval", True)
    Set Font = PropBag.ReadProperty( _
               "Font", Ambient.Font)
    lblTime.BackColor = PropBag.ReadProperty( _
               "BackColor", &HC0C0C0)
    lblTime.ForeColor = PropBag.ReadProperty( _
               "ForeColor", &H8000&)
End Sub

Private Sub UserControl_WriteProperties( _
               PropBag As PropertyBag)
    Call PropBag.WriteProperty( _
               "Enabled", UserControl.Enabled, True)
    Call PropBag.WriteProperty("TimeCaption", _
         lblTime.Caption, "00:00:00")
    Call PropBag.WriteProperty( _
               "Interval", txtInt.Text, "00:00:00")
    Call PropBag.WriteProperty( _
               "TypeInterval", optType(0).Value, True)
    Call PropBag.WriteProperty( _
               "Font", Font, Ambient.Font)
    Call PropBag.WriteProperty( _
               "BackColor", lblTime.BackColor, &HC0C0C0)
    Call PropBag.WriteProperty( _
               "ForeColor", lblTime.ForeColor, &H8000&)
End Sub

     Обратим внимание на следующие особенности и незнакомые процедуры приведенного кода.

=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•

     ¤  Для объявлений свойств элементов управления не используются переменные класса Public, так как при этом способе невозможно обеспечить правильную работу со свойством через окно свойств в фазе проекта. Используются, таким образом, процедуры Property Get и Property Let (Set), как это было описано выше при изучении создания модуля класса. Однако вместо локальных переменных, в которых запоминаются значения свойств при их установке для последующего считывания, используются свойства составляющих элементов управления, связанных мастером с объявляемыми свойствами (делегирование объявляемого свойства составляющим элементам управления).

     ¤  С делегированием связана и другая особенность объявления свойств ПЭУ. Во всех процедурах свойств Property Let (Set) в конце вызывается метод PropertyChanged “ИмяСвойства” объекта UserControl, уведомляющий этот объект, что значение его свойства изменялось. Когда экземпляр нашего ПЭУ периода разработки разрушится (при закрытии содержащей его формы, либо при запуске содержащего его приложения в процессе отладки), произойдет событие WriteProperties. Обработчику этого события, - а код этого обработчика, как и обработчика события ReadProperties был сгенерирован мастером, - передастся объект класса PropertyBag (Портфель свойств), в котором будут сохранены текущие свойства объекта для их последующего восстановления (по событию ReadProperties, естественно).

     ¤  Обработчик события WritePropertis вызывает для каждого свойства нашего ПЭУ метод WriteProperty класса PropertyBag, передавая ему имя свойства и его сохраняемое значение, которое представлено соответствующим свойством составляющего элемента, так как именно в них, как мы видели, сохраняют эти значения процедуры свойств Property Let (Set). В качестве третьего аргумента вызова может передаваться значение свойства по умолчанию. Если таковое присутствует, то запись в файл контейнера сохраняемой информации будет производиться только при отличии текущего значения свойства от значения по умолчанию.
     Обработчик cобытия ReadProperties, возникающего при создании экземпляра ПЭУ в фазе выполнения приложения, вызывает для каждого его свойства метод ReadProperty класса PropertyBag, Данный метод возвращает сохраненное значение для свойства ПЭУ, имя которого передается методу через первый его аргумент, либо передаваемое во втором аргументе значение по умолчанию, если сохранения при разрушении предыдущего экземпляра ПЭУ не было. Возвращаемое методом значение присваивается, в нашем случае, соответствующему свойству составляющего элемента управления, в котором оно сохраняется во время жизни экземпляра.

     ¤  Обратим, наконец, внимание на то, что в обращениях к методам ReadProperty и WriteProperty, относящихся к свойству Font нашего ПЭУ, на месте аргумента со значением свойства по умолчанию стоит не константа, а составное имя Ambient.Font. Это означает, что по умолчанию в объект Font нашего ПЭУ будет копироваться объект Font содержащего наш ПЭУ контейнера, то есть формы приложения. Ибо свойство Ambient объекта UserControl возвращает объект AmbientProperties, содержащий свойства окружающей среды (Environment) контейнера.

=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•

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

     • Добавим форму About к проекту с нашим ПЭУ. Для этого щелкнем правой клавишей мыши по файлу проекта UserClock в окне проводника проекта и вызовем командой Add Form одноименный диалог. В нем выберем вид формы About Dialog и откроем ее.

     • Откроем окно кода ПЭУ Clock и добавим в нем процедуру:
Sub About1()
 frmAbout.Show
End Sub
     • В диалоге Procedure Attributes (Tools_Procedure Attributes..._Advanced>>) выберем в списке Procedure ID пункт AboutBox вместо None. После этого для размещенного в форме ПЭУ Clock в окне свойств появится строка свойства About. Форму About мы можем, далее, редактировать как обычную форму, изменяя компоновку составляющих элементов, в том числе положение и надпись кнопки cmdSysInfo, а также удалив ярлыки lblVersion и lblTitle. Посредством окна свойств введем требуемые строковые данные (надпись на форме, название ПЭУ и его описание), а также укажем файлы с картинками для свойств элементов PicIcon.Picture и frmAbout.Icon:

     После этого в окне кода удалим обработчик события Form_Load (с ненужными нам вызовами), закончив на этом создание интерфейса нашего ПЭУ.

Программирование функций ПЭУ Clock

     Наша задача теперь - запрограммировать установку тех или иных функций и режимов работы нашего ПЭУ (как осуществляемую пользователем через составляющие элементы управления, так и программную, осуществляемую через вызов методов ПЭУ), а также обеспечить возбуждения заданных событий при заданных условиях. Но перед этим добавим в раздел общих деклараций следующие объявления:
Dim i As Integer, f As Integer
Dim t_txtInt As Date
Dim sh As Single, sw As Single
'i - счетчик переходов между функциями Часы/Будильник
'f -флаг задания начала отсчета в функции Будильника
'sh, sw - хранители размеров ПЭУ на форме клиента для их
'восстановления после работы в режиме установки 
't_txtInt - величина заданного интервала для Будильника
Const C_height = 1596 'Проектная высота UserControl
Const C_width = 3324 'Проектная ширина UserControl
Const C_koef = 41 / 79.8 'Коэффициент отношения высот
'шрифта отображения времени в ПЭУ Clock и самого ПЭУ
Const С_interv = 100 'интервал таймера tmrSec
     Заметим, что в обработчик UserControl_Resize(), который мы уже создали, необходимо внести изменение, используя объявленную константу C_koef. “Магических чисел” (смысл которых понятен только разработчику в период разработки) в программе быть не должно!

     ¤  Для перехода с “лицевой” стороны ПЭУ (элемент UserControl)на “изнаночную” (элемент SSTab) используется двойной клик:
Private Sub UserControl_DblClick()
  'Запоминаем размер ПЭУ
  sh = UserControl.Height
  sw = UserControl.Width
  'Устанавливаем проектный размер “изнанки” ПЭУ
  UserControl.Height = C_height
  UserControl.Width = C_width
  SSTab1.Visible = True
  txtInt.Visible = True
  cmdStart.Visible = True
  cmdStop.Visible = True
  cmdCheck.Visible = True
  lblTime_1.Visible = True
  Label3.Visible = True
  Label4.Visible = True
  optType(0).Visible = True
  optType(1).Visible = True
End Sub

Private Sub SSTab1_DblClick()
  SSTab1.Visible = False
  'Возврат к исходному размеру ПЭУ
  UserControl.Height = sh
  UserControl.Width = sw
End Sub
     Обратим внимание, что установка значения False свойству Visible контейнера SSTab распространяется и на видимость составляющих элементов управления, а установка значения True - не распространяется.

     ¤  Обработчики нажатий на кнопки (и соответствующие методы):
Private Sub cmdCheck_Click()
 If i Mod 2 = 0 Then 'Функция будильника
   cmdCheck.Caption = "В режим часов"
   cmdStart.Enabled = True
   optType(0).Enabled = True
   optType(1).Enabled = True
   f = 0
   If optType(0).Value = True Then
    tmrSec.Interval = 0
    lblTime.Caption = "00:00:00"
    lblTime_1.Caption = "00:00:00"
   End If
 Else 'Функция часов
   cmdCheck.Caption = "В режим будильника"
   cmdStart.Enabled = False
   optType(0).Enabled = False
   optType(1).Enabled = False
   tmrSec.Interval = С_interv
 End If
 i = i + 1
End Sub

Private Sub cmdStart_Click()
 cmdStart.Enabled = False
 cmdCheck.Enabled = False
 cmdStop.Enabled = True
 t_txtInt = CDate(txtInt.Text)
 tmrSec.Interval = С_interv 'Включаем таймер
End Sub

Private Sub cmdStop_Click()
 cmdStart.Enabled = True
 cmdCheck.Enabled = True
 cmdStop.Enabled = False
End Sub

Public Function Start() As Variant
 Call cmdStart_Click
End Function

Public Function Check() As Variant
 Call cmdCheck_Click
End Function

Public Function Stopp() As Variant
 Call cmdStop_Click
End Function
     ¤  В функции будильника становятся доступны (для изменения формы задания интервала) радиокнопки optType(0)-(1). При задании интервала граничным значением (optType(1).Value= True) таймер работает, и ПЭУ отображает системное время.
     В обработчике события Timer таймера отслеживается, в функции будильника, исчерпание заданного интервала времени, в момент чего возбуждается событие Timer ПЭУ Clock:
Private Sub optType_Click(Index As Integer)
 optType(Index).Value = True
 optType(1 - Index).Value = False
 If optType(1).Value = True Then
  tmrSec.Interval = С_interv 'Включаем таймер
 Else
  tmrSec.Interval = 0
  lblTime.Caption = "00:00:00"
  lblTime_1.Caption = "00:00:00"
 End If
End Sub

Private Sub tmrSec_Timer()
  Static strs As String, tmr As Date, tmr0 As Date, tmrt As Date
  tmr = Time
  If cmdStop.Enabled = True Then
   If optType(0).Value = True Then
    If f = 0 Then
     tmr0 = tmr
     f = 1
    Else
     tmrt = tmr - tmr0
     lblTime.Caption = Format(tmrt, "hh:nn:ss")
     lblTime_1.Caption = lblTime.Caption
     If tmrt >= t_txtInt Then 'Событие Timer произошло
             'при интервале, заданном длительностью
      f = 0
      cmdStart.Enabled = True
      cmdCheck.Enabled = True
      cmdStop.Enabled = False
      tmrSec.Interval = 0
      RaiseEvent Timer
     End If
    End If
   Else
    lblTime.Caption = Format(tmr, "hh:nn:ss")
    lblTime_1.Caption = lblTime.Caption
    If tmr >= t_txtInt Then 'Событие Timer произошло
              'при интервале, заданном границей
     cmdStart.Enabled = True
     cmdCheck.Enabled = True
     cmdStop.Enabled = False
     RaiseEvent Timer
    End If
   End If
  Else
   If f = 1 Then 'Когда приостанавливается интервальный секундомер
    tmr0 = tmr - tmrt  'кнопкой "Стоп", системные часы продолжают
   End If                 'идти, и вслед за ними должна смещаться
                            'начальная точка отсчета времени
   If optType(1).Value = True Then
    lblTime.Caption = Format(tmr, "hh:nn:ss")
    lblTime_1.Caption = lblTime.Caption
   End If
  End If
  If optType(0).Enabled = False And optType(1).Enabled _
                                        = False Then
   lblTime.Caption = Format(tmr, "hh:nn:ss")
   lblTime_1.Caption = lblTime.Caption
  End If
End Sub
     Несколько десятков строк самостоятельно написанного кода - и мы реализовали довольно-таки разветвленную логику работы нашего ПЭУ. Он готов к использованию и распространению!

Пример использования ПЭУ Clock-Alarm в приложении.
Игра в “Крестики-Нолики” на время

     Игра в “Крестики-Нолики” на стоклеточном поле, реализованная в приведенном, написанном на VB5 приложении, моделирует игру на бесконечном поле:

     Обычно, условием окончания такой игры является заполнение одним знаком отрезка длиной 5 клеток (это состояние отслеживается программно). Предусмотрена игра двух игроков, поочередно щелкающих мышью по выбранной клетке (первым ходом ставится крестик). (Для алгоритмизации игры с игроком-компьютером потребовалось бы решать проблемы принципиально иной сложности; эту возможность автор предоставляет любезно всем желающим.)
     Введенным новшеством является возможность игры “на время”. Для игры “блицев” мы установили два описанных ПЭУ Clock, управляемых из приложения. При недостижении победы обычным образом, при игре блица можно победить быстротой ходов.
     В форме проекта установлены:
     - cmdField(0)-(99) - массив командных кнопок, образующих игровое поле 10Х10;
     - clcSec(0)-(1) - массив ПЭУ Clock (Visible=False);
     - cmdSet(0)-(1) - массив командных кнопок доступа к установке времени игры для каждого игрока;
     - txtSet(0)-(1) - поля ввода времени игры игроков (Visible=False);
     - cmdStart - командная кнопка начала игры (Caption=”Начать игру”, Enabled=False);
     - cmdFin - командная кнопка прекращения игры (Caption=”Закончить игру”, Enabled=False);
     - chkTime - переключатель режима игры без учета времени;
     - lblTablo - информационный ярлык-табло(BorderStyle=1).
     Подготовленному предыдущим материалом читателю не составит труда разобраться в приведенном коде приложения:
Option Explicit
Dim i As Integer, j As Integer
Dim a(1, 99) As Integer 'В этом массиве хранятся 2 "карты":
'простановки крестиков и ноликов; по этим картам проверяется
'условие победы тех или других
DefInt d-z  'Целый тип назначается всем переменным модуля,
               'начинающимся с букв из заданного диапазона
Private Sub chkTime_Click()
 Static c As Integer
 If c Mod 2 = 0 Then
  chkTime.Value = 1
 Else
  chkTime.Value = 0
 End If
 c = c + 1
End Sub
Private Sub clcSec_Timer(Index As Integer)
 If chkTime.Value = 0 Then
  If Index = 0 Then
   lblTablo.Caption = "Победа 'Ноликов'!"
  Else
   lblTablo.Caption = "Победа'Крестиков'!"
  End If
  Call cmdFin_Click
 End If
End Sub
Private Sub cmdField_Click(Index As Integer)
 i = i + 1
 j = i Mod 2
 If cmdStart.Enabled = False And cmdFin.Enabled = True Then
  If cmdField(Index).Caption = "" Then
   cmdField(Index).Caption = Mid("OX", j + 1, 1) 'Изображением
   'нуля служит (для красоты) буква О
   If Proverka(Index, j, a()) = 1 Then
    Exit Sub
   End If
   clcSec(1 - j).Stopp
   clcSec(j).Start
   If j = 0 Then
    lblTablo.Caption = "Ход 'Крестиков'"
   Else
    lblTablo.Caption = "Ход 'Ноликов'"
   End If
  End If
 End If
End Sub

Private Sub cmdField_GotFocus(Index As Integer)
 If cmdSet(0).Enabled = True Then
  cmdSet(0).SetFocus
 End If
End Sub

Private Sub cmdFin_Click()
 Dim x, y As Integer
 clcSec(j).Stopp
 clcSec(1 - j).Stopp
 cmdStart.Enabled = True
 cmdFin.Enabled = False
 For x = 0 To 1
  For y = 0 To 99
   a(x, y) = 0
  Next
 Next
End Sub

Private Sub cmdSet_Click(Index As Integer)
 lblTablo.Caption = ""
 clcSec(Index).Check
 clcSec(Index).Visible = True
 txtSet(Index).Visible = True
 cmdSet(Index).Enabled = False
 If cmdSet(0).Enabled = False And cmdSet(1).Enabled = False Then
  cmdStart.Enabled = True
 End If
End Sub

Private Sub cmdStart_Click()
 Dim x, y
  If (txtSet(0).Text = "00:00:00" Or txtSet(1).Text = "00:00:00") _
           And chkTime.Value = 0 Then
   Exit Sub
  End If
  i = 0
  clcSec(j).Check
  clcSec(1 - j).Check
  clcSec(j).Check
  clcSec(1 - j).Check
  clcSec(j).Visible = True
  clcSec(1 - j).Visible = True
  cmdSet(j).Enabled = False
  cmdSet(1 - j).Enabled = False
  For x = 0 To 99
   cmdField(x).Caption = ""
  Next
  For x = 0 To 1
   For y = 0 To 99
    a(x, y) = 0
   Next
  Next
  clcSec(0).Start
  cmdStart.Enabled = False
  cmdFin.Enabled = True
  lblTablo.Caption = "Ход 'Крестиков'"
End Sub

Private Sub Form_Load()
 lblTablo.Caption = "Добро пожаловать!"
End Sub
Private Sub txtSet_Change(Index As Integer)
 clcSec(Index).Interval = txtSet(Index).Text
End Sub

Private Function Proverka(ind As Integer, ind0 As Integer, _
                           a() As Integer) As Integer
 Dim x, y, z, zz, u, d, dx, mx, ind1, k, s
 'Здесь реализована проверка чьей-либо победы,
 'но понять КАК это сделано труднее, чем написать эту
 'функцию самому
 a(ind0, ind) = 1 'Отображается очередной знак на карте
 For z = 0 To 1 'Сначала проверяем горизонтали и вертикали
  For x = 0 To 9
   s = 0
   k = 0
   For y = 0 To 9
    If z = 0 Then
     ind1 = x + 10 * y
    Else
     ind1 = y + 10 * x
    End If
    s = s + a(ind0, ind1)
    k = k + 1
    If s = k Then
     If s = 5 Then
      If ind0 = 0 Then
       lblTablo.Caption = "Победа 'Ноликов'!"
      Else
       lblTablo.Caption = "Победа'Крестиков'!"
      End If
      Call cmdFin_Click
      Proverka = 1
      Exit Function
     End If
    Else
     s = 0
     k = 0
    End If
   Next
  Next
 Next
 For u = 0 To 1 'Потом проверяем диагонали
  For z = 0 To 5
   If u = 0 Then
    d = 55 - 11 * z
    zz = z + 4
   Else
    d = 45 - 9 * z
    zz = 5 - z
   End If
   If d = 0 Then
    mx = 0
   Else
    mx = 1
   End If
   For x = 0 To mx
   dx = d * x
    s = 0
    k = 0
    For y = 0 To z + 4
     If u = 0 Then
      ind1 = zz + 9 * y + dx
     Else
      ind1 = zz + 11 * y + dx
     End If
     s = s + a(ind0, ind1)
     k = k + 1
     If s = k Then
      If s = 5 Then
       If ind0 = 0 Then
        lblTablo.Caption = "Победа 'Ноликов'!"
       Else
        lblTablo.Caption = "Победа'Крестиков'!"
       End If
       Call cmdFin_Click
       Proverka = 1
       Exit Function
      End If
     Else
      s = 0
      k = 0
     End If
    Next
   Next
  Next
 Next
 Proverka = 0
End Function

Подготовка к распространению

     Для распространения созданного ПЭУ необходимо создать его инсталляционный пакет - так же, как это делается для распространения приложений. Эту работу нам поможет сделать мастер Application Setup Wizard, запускаемый из программной группы Microsoft Visual Basic 5.0 главного меню:

     Но прежде чем запустить этот мастер, вам потребуется а) решить, добавлять ли лицензирование ПЭУ (это делается установкой флажка Require License Key на вкладке General диалога Project Properties); б) скомпилировать проект в файл .ocx (Команда File_Make...ocx).
     Если было добавлено лицензирование, то при компиляции создается также файл .vbl, содержащий ключ лицензирования, который будет добавлен мастером в инсталляционный пакет.
     В процессе работы мастера нам последовательно представляются диалоги, в которых мы должны подтверждать или отмечать требуемые опции:
     - Select Projects and Options (в нем мы указываем имя проекта нашего ПЭУ и опцию “Create a Setup Program”);
     - Distribution Method (в нем мы указываем один из трех вариантов компоновки дистрибутива: Floppy Disk, Single Directory и Disk Directories (\Disk1, \Disk2, etc.); в первом и третьем вариантах нужно будет далее указать и Disk Size;
     - ActiveX Server Components (в нем мы, при надобности, добавляем локальные или удаленные компоненты);
     - Confirm Dependencies (здесь мы подтверждаем зависимости от файлов - лицензионных компонент; если зависимостей нет, то этот диалог пропускается; у нас это - TABCTL32.OCX).
     Если мы предусмотрели для нашего ПЭУ лицензирование, то перед выдачей следующего диалога File Summary (Сводка файлов) нам выдастся следующий запрос:

При положительном ответе мы переходим к диалогу File Summary, а при отрицательном выдается запрос:

при положительном ответе на который в дистрибутив будет включена и библиотека, отвечающая за доступность свойств ПЭУ в отличной от VB5 среде без файла с ключом лицензирования.
     Диалог File Summary имеет вид:

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

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

Глава 5. VISUAL BASIC 5 И INTERNET

Колхоз – дело добровольное: хошь не хошь, а вступать надо.

О чем пойдет речь

     Отношения между Visual Basic 5 и Интернетом не слишком просты. Поэтому мы с самого начала выделим в них две разные темы.
     Первая - это создание на VB5 приложений-клиентов, которые позволяют осуществлять доступ к Internet с теми или иными целями: сбора данных для последующей обработки, отбора и копирования файлов, навигации по Web-страницам. VB5 имеет для такого доступа ряд специализированных дополнительных элементов управления: Winsock (работа с удаленным компьютером), Internet Transfer (поиск и прием файлов на серверах, поддерживающих протоколы FTP и HTTP), WebBrowser (просмотр Web-документов в приложении).
     Вторая тема - это создание приложений, предназначенных для исполнения в броузере пользователя гиперсети. Как мы уже говорили, В VB5 возможно создание 3-х типов компонентов ActiveX: пользовательских элементов управления ActiveX (тип проекта - ActiveX Control), компонентов программ ActiveX (типы проектов - ActiveX DLL, ActiveX EXE) и документов ActiveX (ActiveX Document DLL, ActiveX Document EXE). Именно документы ActiveX и представляют собой приложения для гиперсети.
     Мы собираемся коснуться в этой главе обеих тем.
     Но сначала дадим некоторые вводные сведения и объяснения уже использованных терминов.
     У Интернета есть как бы два представления.
     С одной стороны, - это объединение компьютерных сетей, включающих миллионы компьютеров. Множество компьютеров, аппаратных платформ, операционных систем предполагает наличие определенных правил (общих для всех) для связи и обмена информацией. Набор этих правил принято называть протоколом. Функционирование Internet заключается в обмене данными под управлением протоколов TCP/IP: Transmission (вариант - Transfer) Control Protocol/Internet Protocol ("протокол управления передачей/межсетевой протокол"), реализуемых специальными программами на каждом из объединенных компьютеров.
     С другой тороны, Internet - это совокупность ряда служб или сервисов, независимо функционирующих в едином информационном пространстве. Самым популярным сервисом на сегодняшний день является World Wide Web (WWW, Web), что можно перевести как "всемирная паутина". Произнося "Internet", в 19 из 20 случаев имеют в виду именно WWW (но все же не будем забывать, что есть еще электронная почта, пересылка файлов, видеоконференции...). Web-сервис функционирует также на основе протокола (но уже другого, более высокого уровня) - протокола передачи гипертекста Hipertext Transfer Protocol (HTTP). Именно благодаря этому протоколу Web-сервис предстает перед нами в виде единой совокупности связанной между собой информации, по которой мы можем передвигаться (от документа к документу), используя механизм гипертекстовых ссылок и не задумываясь о том, на каком именно компьютере расположен тот или иной документ.
     Web-сервис использует в обмене данными архитектуру клиент/сервер, о которой мы уже рассказывали в предыдущей главе. То есть при таком обмене одна из сторон представляет собой программу-клиент, выдающую запрос программе-серверу, который в ответ предоставляет запрашиваемый документ. Запрос представляет собой адрес документа, называемый Uniform Resource Locator (URL) - "универсальный указатель ресурсов". Этот адрес-запрос программа-клиент, называемая броузером, получает от пользователя и передает серверу. Какому именно серверу - указано в URL, состоящему из адреса сервера и адреса файла (на данном сервере), содержащего документ. В ответ на запрос сервер предоставляет броузеру требуемый документ, который броузер интерпретирует в специальной форме, называемой Web-страницей (такие отображаемые броузером страницы часто по тем или иным поводам показывают по телевидению, а также в периодике). Самое интересное, что есть на такой странице - это гиперссылки: выделенные (цветом, подчеркиванием) элементы, используемые для переходов к другим документам.
     О каких, собственно, документах идет здесь речь? О вполне конкретных, называемых документами HTML, поскольку они представляют собой попросту тексты программ, написанных на языке гипертекстовой разметки: HyperText Murkup Language (HTML). Эти программы и указывают броузеру что и как он должен интерпретировать. В интерпретированном броузером виде HTML-документ предстает перед нами в виде WEB-страницы. Файлы с HTML-документами имеют расширения HTM или HTML.
     Заметим, что документ HTML может быть и динамическим, т.е. сформированным сервером специально для конкретного запроса. Более того, он может быть интерактивным, т.е. поддерживать диалог с пользователем, выполняя на сервере определенные действия, описанные, например, на языке VBScript - языке сценариев, являющемся подмножеством VB5. Это предусмотрено технологией Active Server, входящей в состав ActiveX. Так, щелкая по кнопке на Web-странице, мы можем запускать сценарий на сервере, который сформирует нам HTML-документ, содержащий, например, сведения о числе продаж товара. Но обо всех таких возможностях мы упоминаем просто для сведения; сценарии мы рассматривать не будем.
     Теперь самое для нас важное.
     Броузер (будем подразумевать под ним конкретный броузер - Internet Explorer версии 4.0 и старше фирмы Microsoft) позволяет не только интерпретировать HTML-документы, но и служит контейнером документов ActiveX, что, собственно, и обеспечивает возможность создания на Visual Basic приложений для работы в сети Internet (все выше- и нижесказанное относится и к сетям intranet: локальным сетям, управляемым на основе тех же протоколов и той же архитектуры, что и глобальные). Такие приложения, размещенные на сервере в специальном, подготовленном к распространению виде, загружаются в броузер тем же путем, что и любой HTML документ: заданием адреса файла с расширением HTM, входящего в состав подготовленного к работе в сети приложения. После загрузки документа ActiveX в броузер ему становятся доступны и свойства самого броузера, и методы объекта Hyperlink, позволяющие передавать броузеру запрос для перехода на другой URL-адрес (NavigateTo) или перемещаться по хронологически предшествующим адресам (GoForward, GoBack).
     План нашего изучения всех этих возможностей следующий:

     • Рассмотрим процесс создания приложения, которое использует готовый элемент управления ActiveX (WebBrowser).

     • Изучим возможности преобразования приложений в документы ActiveX.

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

     • Попробуем свои силы в создании документа ActiveX.

Создание простейшего приложения-броузера

     Как уже говорилось, в VB5 имеется элемент управления WebBrowser, предоставляющий клиенту доступ в Internet и инкапсулирующий все возможности броузера Internet Explorer. Этот элемент дает нам простейший способ интегрировать броузер в создаваемое приложение, просто разместив его в создаваемой экранной форме. Это мы сейчас и проделаем. Итак, загружаем VB5 и выбираем стандартный EXE-проект. Переходим в меню Project и выбираем пункт Components. В результате получаем хорошо уже знакомый нам диалог Components:

     На вкладке Controls помечаем компонент Microsoft Internet Controls. Элемент управления WebBrowser будет добавлен на панель инструментов после нажатия на кнопки Применить и ОК. После закрытия диалогового окна, в палитре инструментов видим элемент WebBrowser, представленный соответствующим значком:

     Что будет делать наш броузер? Входить в "детскую сеть" на Internet (Web-узел http://www.mkids.ru) и перемещаться по доступным из него Web-страницам, отображаемым элементом WebBrowser. Вход в узел будет осуществляться щелчком по кнопке "Детская сеть в Internet", но при дальнейшем развитии приложения уместнее предусмотреть вход на множество подобных, предназначенных для школьников, сайтов путем их выбора из элемента-списка (с адресами: www.globalfriends.com, www.rozmisel.irk.ru/children, www.infoline.ru/g23/7903/child.htm и другими, которые вы сочтете полезными). Поскольку в создаваемом приложении не будет предусмотрено ввода произвольного Web-адреса, броузер обеспечивает полную информационную безопасность пользователя и может использоваться при изучении темы Internet на уроке информатики.
     Итак, размещаем на форме:

     • элемент WebBrowser с именем brwWB;

     • кнопку с надписью "Детская сеть в Internet", имеющую имя cmdStart, со свойством ToolTipText = "http://www.mkids.ru" (всплывающая при указании на кнопку мышью подсказка);

     • две объединенные в массив кнопки cmdBF(0)-(1) с надписями "Назад" и "Вперед" - для навигации по стеку загруженных страниц;

     • ярлык lblURL, отображающий заголовок страницы (значение свойства LocationName элемента WebBrowser); вместо него можно отображать текущий адрес (свойство LocationURL).
     После размещения элементов мы можем, изменяя те или иные их свойства, снабдить интерфейс приложения теми или иными дополнительными свойствами (заголовки, рисунки на кнопках, значки, возникающие при нажатии на кнопки и т.д.). Наш вариант таков:

     Перейдем к написанию обработчиков.

=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•

     ¤  При нажатии на кнопку cmdStart вызывается метод элемента-броузера Navigate, устанавливающий соединение, а затем начинающий загрузку Web-страницы. При этом на кнопке появляется надпись "Стоп", и она становится недоступной до окончания установки соединения (т.е. до события DownLoadBegin элемента-броузера). Во врямя загрузки Web-страницы эта кнопка становится доступной для прекращения загрузки вызовом метода Stop элемента-броузера, после чего на кнопке появляется первоначальная надпись и ей возвращается прежняя функция.
     Для обеспечения такой двойной функции одной кнопки (мы уже с этим встречались в нашем ПЭУ) на уровне модуля объявлена переменная i. Вот соответствующий код:
Option Explicit
Dim i As Integer
Private Sub cmdStart_Click()
 On Error Resume Next 'При установке связи часты внепрограммные сбои
 If i Mod 2 = 0 Then
  cmdStart.Caption = "CTOП"
  cmdStart.Enabled = False
  brwWB.Navigate "http://www.mkids.ru/"
 Else
  cmdStart.Caption = "Вход в детскую сеть"
  cmdStart.Enabled = True
  brwWB.Stop
 End If
 i = i + 1
End Sub
     ¤  Нажатия на кнопки "Назад" и "Вперед" вызывают (при доступности соответствующей кнопки) методы GoBack и GoForward соответственно элемента-броузера:
Private Sub cmdBF_Click(Index As Integer)
 If Index = 0 Then
  brwWB.GoBack
 Else
  brwWB.GoForward
 End If
End Sub
     ¤  Каждый вызов методов GoBack или GoForward вызывает событие CommandStateChange. Соответствующий обработчик отслеживает, какая команда вызывалась, и управляет доступностью кнопок "Назад" и "Вперед":
Private Sub brwWB_CommandStateChange(ByVal Command As Long,   _
                                                         ByVal Enable As Boolean)
 Select Case Command
  Case CSC_NAVIGATEBACK     'Внутренняя константа WebBrowser
   cmdBF(0).Enabled = Enable
  Case CSC_NAVIGATEFORWARD 'Внутренняя константа WebBrowser
   cmdBF(1).Enabled = Enable
 End Select
End Sub
     ¤  Обработка начала и окончания загрузки Web-страницы:
Private Sub brwWB_DownloadBegin()
 cmdStart.Enabled = True
End Sub

Private Sub brwWB_DownloadComplete()
 cmdStart.Enabled = False
 lblURL.Caption = brwWB.LocationName
End Sub
     В начале загрузки Web-страницы кнопка "Стоп" становится доступной для прекращения загрузки; в конце загрузки доступность с нее снимается и выводится заголовок страницы в ярлык lblURL.

     ¤  Наконец, логично было бы предусмотреть код, "подстраивающий" размер элемента-броузера и размещение в окне приложения кнопок (по горизонтали) при изменении размеров окна в фазе выполнения:
Private Sub Form_Resize()
 If Me.WindowState <> vbMinimized Then
  brwWB.Height = ScaleHeight - 2 * lblURL.Height - lblURL.Top
  brwWB.Width = ScaleWidth - 1.5 * cmdStart.Width - lblURL.Left
  lblURL.Width = brwWB.Width
  cmdStart.Left = lblURL.Left + lblURL.Width + 0.25 * cmdStart.Width
  cmdBF(0).Left = cmdStart.Left
  cmdBF(1).Left = cmdStart.Left
 End If
End Sub
=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•

     Испытаем наш проект. Запустив его на выполнение, щелкнем по кнопке "Детская сеть в Internet". Далее все произойдет так же как в программе Internet Explorer, и мы можем приступить к просмотру:

     Заметим, в заключение, следующее.
     С точки зрения COM-технологии элемент управления WebBrowser - просто средство доступа к членам броузера Internet Explorer. Другой способ управления им - сделать это приложение сервером OLE-автоматизации (компонентом программ ActiveX). По предыдущей главе мы помним, как это делается: в диалоге References из пункта главного меню Project устанавливается флажок в строке Microsoft Internet Control, после чего в приложении-клиенте объявляется, на уровне модуля, объектная переменная типа InternetExplorer (с ключевым словом WithEvents!). Далее, в нужный момент мы создаем объект InternetExplorer, устанавливая для объявленной переменной ссылку на него инструкцией Set. Мы получаем, таким образом, возможность "дистанционного управления" броузером - ну, например, для контроля за действиями пользователя или автоматической регистрации программного продукта у поставщика.

Документы ActiveX

     Документы ActiveX являются Web-документами, которые созданы без использования кода HTML, но также как и HTML-документы интерпретируются броузером клиента. Это дает возможность распространения приложений, написанных на VB5, по гиперсети, поскольку средствами того же VB5 приложения легко преобразуются к документам ActiveX. Да и процесс создания документа ActiveX практически такой же, как и приложения в стандартном проекте.
     Для документов ActiveX доступны практически все элементы стандартной панели инструментов Visual Basic 5 (исключение составляют контейнеры OLE), что дает возможность преобразовать большинство созданных приложений на Visual Basic 5 в документы ActiveX. Ниже будет рассмотрена возможность преобразования готового проекта, созданного на Visual Basic 5, в документ ActiveX при помощи ActiveX Document Migration Wizard (Мастера преобразования в документы ActiveX). После этого мы научимся создавать (посредством уже известного нам Мастера установки) программу установки документа ActiveX на Web-сервере для работы с ней из броузера пользователя. И уже после этого мы создадим простой документ ActiveX непосредственно.

Преобразование готового приложения в документ ActiveX

     Мы написали в прошлой главе программу "Крестики-Нолики" (проект Game.vbp), использующую разработанный нами же ПЭУ "Часы-Будильник". Дадим возможность теперь всем желающим насладиться этой игрой (если есть напарник) прямо из своего броузера. Для этого мы сначала должны преобразовать это приложение в документ ActiveX. Для этой цели VB5 имеет специальный Мастер преобразования в документы ActiveX (ActiveX Document Migration Wizard).
     Воспользуемся им.

=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•

     ¤  Войдем в меню Visual Basic 5 и выберем пункт Add-Ins. Если Мастер преобразования еще не использовался, добавим его в список меню. Это выполняется с помощью команды Add-In Manager:

     Далее в диалоге Add-In Manager установим флажок напротив VB ActiveX Document Migration Wizard и щелкнем по кнопке OК. После выполнения указанных операций в списке меню Add-Ins появится команда вызова Мастера ActiveX Document Migration Wizard:

     Запустим его.

     ¤  Первым появляется диалоговое окно Introduction. Следующим идет диалоговое окно Form Selection (Выбор формы). Данное окно предлагает пользователю выбрать форму (возможно, не одну) проекта, которая будет преобразована в документ ActiveX. В нашем тестовом проекте всего одна форма frmGame. Она и представлена в этом диалоговом окне. Установим флажок напротив нее:


     ¤  В следующем диалоге Options нам будет предложено установить два флажка. Первый - Comment out invalid code? (“Закомментировать ошибочный код?”) - позволяет автоматически закомментировать код, зависящий от формы, в котором встречаются недопустимые для документов ActiveX инструкции или методы Load, Unload, Show, Hide, End:

     При установке второго флажка (Remove original form after conversion? - “Удалять формы-оригиналы после преобразования?”) форма-оригинал будет удалена из текущего проекта, в котором создается документ ActiveX.
     Группа радиокнопок Your Project Type is Invalid намекает нам на то, что стандартный проект, из которого мы создаем проект документа ActiveX, не годится для этой цели без предварительного преобразования в проект ActiveX EXE или ActiveX DLL.

     ¤  Диалоговое окно Finished! предлагает нам получить краткий отчет о работе и сохранить все параметры и установки, которые вы выбирали во время работы этого Мастера:

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

     ¤  Последнее диалоговое окно сообщает нам о том, что документ (или документы) ActiveX созданы:

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

     • запустить проект на выполнение;

     • переключиться на Internet Explorer;

     • в пункте меню File выполнить команду Open, указав в появившемся диалоге имя временного .vbd-файла, относящегося к созданному ActiveX-документу (его ищут в каталоге VB, установив в окне списка Типы файлов пункт "все файлы").
     После этого можно вести отладку и тестирование тем же образом, что в случае обычного ActiveX DLL (EXE) проекта.
     Сразу заметим, что файл .vbd будет удален сразу после прекращения работы нашего документа в среде VB5. Чтобы создать постоянный документ, нужно воспользоваться командой Make из пункта File меню VB5. При этом кроме .vbd-файла (он будет создан уже в каталоге проекта) будет создана библиотека DLL, позволяющая исполнять документ в броузере и без запуска VB5.

     ¤  Протестируем созданный документ ActiveX. Это можно осуществить двумя способами. О первом уже было рассказано (в приведенном отчете Мастера). Для того чтобы протестировать документ вторым способом, необходимо в пункте File меню главного меню VB5 выбрать команду Make Game.exe, после чего загрузить файл полученный постоянный файл DocGame.vbd в окно Internet Explorer:

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

Создание программы установки для документа ActiveX

     Завершение работы над проектом сопровождается, как мы знаем, созданием дистрибутива - комплекта дискет или компакт-диска, с которых пользователь может установить на локальный компьютер разработанное приложение. В случае проекта документа ActiveX пользователь обычно получает файлы с расширением .CAB, которые содержат в упакованном виде все необходимые для автономной работы компоненты приложения.
     Internet-технологии обеспечивают распространение и установку программ не только посредством привычных носителей информации, но и непосредственно с Web-узла сети. Броузер Internet Explorer является клиентом, который может автоматически загружать и распаковывать CAB-файлы из сети и инсталлировать элементы управления.
     Рассмотрим процесс создания дистрибутива для установки приложения через Internet с помощью Application Setup Wizard на примере преобразованного проекта игры в "Крестики-Нолики". Для начала выберем Application Setup Wizard в группе программ Visual Basic.

=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•

     ¤  При запуске Setup Wizard появляется диалоговое окно, которое сообщает о назначении данной программы. В этом окне, как и в последующих, присутствует ценное замечание о том, что вы можете в любой момент времени отказаться от сделанных вами установок, нажав на кнопку Back и вернувшись на шаг назад. Вы можете запретить появление этого окна в будущем, установив флажок Skip this screen in the future. Нажатие на кнопку Next позволяет перейти к следующему окну.

     ¤  В следующем окне нам будет предложено выбрать файл проекта, перекомпилировать его (если это нужно), выбрать тип установки:

     Войдем, щелкнув по кнопке Browsе, в соответствующий диалог и выберем проект Game.vbp. Включим в группе Options радиокнопку Create Internet Download Setup (Создать программу установки для Internet) и щелчком по кнопке Next перейдем к следующему шагу. В ответ может быть выдано предупреждение, подобное такому:

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

     ¤  Следующее диалоговое окно информирует нас о том, что Setup Wizard не нашел файлов с информацией о зависимостях (*.dep) для перечисленных файлов. Нам будет предложено либо включить их в состав разрабатываемого дистрибутива, либо отказаться от такой возможности. Мы решаем включить:


     ¤  Далее Setup Wizard просит нас указать расположение файлов дистрибутива на диске. При этом по умолчанию предлагается каталог \WINDOWS\TEMP\SWSETUP. Мы же выбрали каталог расположения файлов дистрибутива на локальном Web-сервере:


     ¤  Некоторые компоненты, которые требуются для установки и выполнения нашего документа, могут загружаться отдельно. Например, библиотека времени выполнения Visual Basic. Возможно, на компьютере пользователя уже установлена эта библиотека и поэтому не имеет смысла держать ее в одном CAB-файле с компонентами нашего документа. В следующем диалоговом окне нам предложено указать, откуда надо будет загружать компоненты поддержки. Мы можем выбрать Web-узел Microsoft или какой-либо другой адрес. Если мы хотим, чтобы пользователи могли получить самые последние версии компонентов поддержки, то разумнее всего указать сервер:

     Обратим внимание на кнопку Safety (Безопасность). Если мы уверены в безопасности нашего продукта, отметим этот факт установкой флажков в диалоге, вызываемом этой кнопкой:

     После щелчка по OK вернемся в диалог Internet Package и по нажатию на Next перейдем в следующее окно.

     ¤  В окне ActiveX Server Components нам предлагается проверить состав найденных автоматически компонентов ActiveX, входящих в документ, и, при необходимости, добавить вручную другие требуемые компоненты:

     В числе подобных компонентов у нам был назван (в сообщении перед диалогом Missing Dependecies) компонент VB5EXT.OLB. Чтобы добавить его вручную, щелкнем по кнопке Add Local и в файловом броузере появившегося диалога находим данный файл в указанном сообщением месте (для этого сначала в поле имени введем *.olb, а затем найдем и сам требуемый файл). После щелчка по кнопке Открыть может быть выдано предупреждение, что указанный компонент не способен к авторегистрации.

     ¤  В следующем окне нам предлагается выдается список всех дополнительных элементов управления, от которых, по мнению мастера, зависит наше приложение. Нам также следует проверить условия, на которых допускается использование того или иного элемента в распространяемом приложении. Узнать об этом можно из диалога, вызываемого по нажатию на кнопку File Details:


     ¤  В следующем после вызова команды Next окне File Summary нам выводится список всех файлов, требующихся для работы документа:

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

     ¤  Наконец, последнее диалоговое окно Finished! сообщает нам, что Мастер установки собрал все необходимые сведения для создания дистрибутива:

     Если в дальнейшем мы намериваемся модифицировать наш проект, то мы можем сохранить данные установки нажав на кнопку Save Template. После нажатия на кнопку Finish, Мастер создаст дистрибутивный комплект файлов в каталоге, который был указан в окне Internet Distribution Location, и закончит свою работу.

=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•

     В результате работы Мастера установки мы получили установленный на Web-узле комплект файлов, среди которых файл Game.htm (файл Web-страницы), Game.cab (все файлы, от которых зависит наш документ) и DocGame.vbd (код нашего документа ActiveX). Подключившись к Internet, загрузим в броузер созданную страницу - и начнем работу с программой:

Создание документа ActiveX

     После экспериментов с преобразованием форм проекта на Visual Basic в документы ActiveX пришло время для создания проекта типа ActiveX Document. Как и при создании пользовательского элемента управления ActiveX, мы можем использовать два типа документов ActiveX: ActiveX Document DLL, внутрипроцессный компонент, и ActiveX Document EXE - внепроцессный. Для создания документа ActiveX будем использовать соответствующие шаблоны из диалогового окна New Project среды Visual Basic. Выберем из диалогового окна шаблон Document ActiveX DLL:

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

В соответствии с рисунком зададим надписи ярлыков, а также значения свойств Style, Text и массива List() комбинированного списка cboOthers. Введем также значения (0, 1, 2...) свойства-массива ItemData(), которые являются индексами элементов массива List(). С помощью этих индексов будет поддерживаться соответствие наименований Web-страниц, хранимых в свойстве List(), и их адресов, хранимых в объявленном на уровне модуля массиве strURL(50). Чтобы в поле ввода комбинированного списка нельзя было редактировать введенные в список данные, его свойство Locked устанавливается в True.
     Центральным пунктом приведенного ниже кода нашего документа является обращение к объекту HyperLink броузера клиента посредством одноименного свойства контейнера UserDocument. При этом вызовом метода этого объекта NavigateTo мы находим и отображаем выбранную в списке Web-страницу. Так как работа со списком подробно разбиралась в главе 3, нижеприведенный код созданного документа не нуждается в подробных комментариях.
Option Explicit
Dim strURL(50) As String
Private Sub cboOthers_Click()
 On Error GoTo Er 'Если возникнет ошибка в процессе соединения, то
                     'то процедура заканчивает работу без последствий
  UserDocument.Hyperlink.NavigateTostrURL(cboOthers.ItemData(cboOthers.ListIndex))
 On Error GoTo 0 'Отключаем описанную обработку ошибки
Er:
End Sub

Private Sub UserDocument_Initialize()
 strURL(0) = "http://www.uic.nnov.ru/~edu/school_13.htm"
 strURL(1) = "http://www.cacedu.unibel.by/partner/sch54"
 strURL(2) = "http://into.nit.spb.su/eng/school/sc56/main.htm"
 strURL(3) = "http://www.pmg17vstu.vinnica.ua"
 'strURL(3) = "http://127.0.0.1/Game.htm" 'Используем для отладки
End Sub
     Произведем отладку созданного документа ActiveX таким же образом, как и предыдущего, преобразованного нами из проекта Game.vbp. Затем, описанным ранее образом создадим на локальном сервере программу его установки для распространения по гиперсети. Теперь мы будем иметь, по-существу, Web-страницу с гиперссылками, созданную средствами VB5:

Приложения

I. ТРАДИЦИОННОЕ ПРОГРАММИРОВАНИЕ

     Итак, Visual Basic 5 - средство визуального и событийно-управляемого программирования. Так обстоит дело на уровне глобального подхода, когда вырабатывается информационная модель задачи с определенным сценарием диалога компьютера и пользователя для ее решения. Однако в процессе реализации этого подхода мы, на локальном уровне кодирования алгоритмов, переходим к "традиционному" программированию. Таковым мы будем считать такую запись алгоритмов на алгоритмическом языке, при которой управление на любой ее фрагмент (кроме выделенного "начального") передается не извне (посредством системной составляющей), а от одного или нескольких других ее фрагментов. Такое "традиционное" программирование имеет место, например, в рамках кода одного обработчика или явно вызываемой общей процедуры; единственной и несущественной особенностью при этом явится то, что среди данных программы будут встречаться не только переменные и константы, но и члены классов (в дальнейшем изложении главы этой несущественной особенностью мы пренебрежем).
     С точки зрения "традиционного" программирования Visual Basic 5 можно рассматривать просто как один один из диалектов языка Basic (Beginner's All-purpose Symbolic Instruction Code - "Многоцелевой язык символических инструкций для начинающих"). Рассмотрим, что же характеризует этот язык в его "традиционном" измерении, т.е. вне элементов управления, классов и событийной управляемости.

Программа и данные

     "Традиционное" программирование является программированием процедурным. Что это значит?
     Программа при процедурном программировании состоит из последовательности строк, содержащих те или иные инструкции (процессору) по обработке данных (информации, хранящейся в памяти компьютера в кодированном виде). Можно сказать, что программа работает с данными, преобразуя их: выполнение программы состоит в последовательном выполнении инструкций с целью преобразования исходного состояния памяти в конечное.
     В программе данные предстают в виде констант и переменных. Константы - это самоопределенные данные: значение константы и ее тип определены ее записью (например, 107 - константа целого типа, "ёё" - строкового, #September 1, 2000# - типа даты); константа без знака соответствует положительной величине представляемого ею данного. Некоторые константы, однако, имеют имена-идентификаторы, по которым и "читают" их значения. VB5 имеет множество внутренних именованных констант, с которыми можно ознакомиться через вызов диалога Object Browser и выделение пункта Constants в списке классов. Собственные именованные константы объявляются посредством инструкции Const.
     Через имена-идентификаторы осуществляется доступ и к таким данным, значения которых можно менять, а не только "читать". По математической аналогии такие данные называют переменными. Обращаясь в программе к переменной, мы обращаемся к ее значению, содержащемуся в сопоставленной данной переменной области памяти. Переменные характеризуются типом, временем жизни и областью действия. Каждому типу переменной соответствует формат представления ее значения в сопоставленной области памяти (такую область называют иногда "ячейкой"). Например, целому типу Integer соответствует двухбайтовая область, хранящая целые числа от -32768 до 32767 в дополнительном коде.

Структурное программирование

     В развитии процедурного программирования большую роль сыграли принципы структурного программирования, которым мы должны следовать при написании процедур VB5. Будем рассматривать программу как некоего исполнителя, решающего определенную задачу. Когда программа уже написана, наша исходная задача оказывается разбитой на множество подзадач, каждая из которых решается элементарным (базовым) исполнителем-инструкцией. Отсюда возникает следующий подход, называемый методикой программирования сверху вниз: мы разбиваем задачу на подзадачи со своими промежуточными исполнителями (фрагментами кода с одним входом и одним выходом, обобщенно называемыми "процедурами"); после этого с каждой подзадачей делаем то же самое и так до тех пор, пока не достигнем уровня базовых исполнителей (инструкций). Теоретической основой такой методики является возможность представления любого алгоритма структурной блок-схемой. Что это такое?
     Представьте себе сеть с тремя типами узлов:

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

•   Композиция (а):     <Процедура 1>
                        <Процедура 2>


•   Ветвление (б):      если <условие> 
                           <Процедура 1>
                        иначе 
                           <Процедура 2>


•   Цикл I (в):		пока <условие> делать 
                            <Процедура>


•   Цикл II (г):        делать 
                            <Процедура>
                        пока <условие>


     Эта совокупность структур управления является достаточной для построения любого алгоритма, но не всегда удобной. Поэтому, на практике используются и другие элементарные блок-схемы с обязательным свойством "один вход и один выход". Например, вариант блок-схемы ветвления, где <Процедура 2> пуста: если <условие> <Процедура>      Но для нашего рассмотрения это не важно. Важно то, что любая <Процедура> любой элементарной структуры управления является либо базовым исполнителем, описание которого изначально определено, либо представима опять-таки элементарной структурой управления из их фиксированного набора, включающего 4 перечисленных типа. При этом структуру композиции не требуется "организовывать": она образуется при естественном (сверху вниз и слева направо) следовании инструкций программы. Для организации структуры ветвления служит инструкция IF...Then...Else, структур циклов - циклические инструкции For...Next, Do...Loop и другие.
     Таким образом, обоснован следующий процесс структурного программирования (проектирования исполнителей) сверху вниз.

=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•

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

     ¤  Для каждого промежуточного исполнителя также производится шаг декомпозиции.

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

=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•

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

Типы данных

     Как уже говорилось, данные, представляемые в программе константами и переменными, могут быть разного типа. Например, константа 5 представляет данное целого типа (этот тип имеет имя Integer), 50000 - целого типа двойной длины (Long), 5. - вещественного типа одинарной точности (Single), "5" - строкового (String). По имени переменной тоже можно в ряде случаев определить тип представляемого данного. Для этой цели используется добавление к имени переменной специального окончания (неявное объявление типа). Но этот способ считается устаревшим и, следуя принципам структурного программирования, каждую переменную следует явно объявлять в инструкции Dim...As, либо в инструкциях Static, Private или Public. Например:
Dim strFamily As String, mintLength As Integer
'Объявлены переменные строкового и целого типов
     Те же самые типы имели бы эти переменные и без явного объявления при добавлении к их именам соответствующих окончаний: strFamily$ и mintLength% (эти окончания можно добавлять и к числовым константам - для явного указания формата их хранения). Не используйте в одной процедуре имена переменных, различающиеся только окончанием, в Visual Basic это запрещено!
     Вещественные десятичные константы отличаются по виду от целых наличием в их записи определенных нецифровых символов. Таковым символом может быть точка, отделяющая в записи целую часть значения от дробной, причем какой-то из этих частей может и не быть:
     5. -2.09 .08
     Если других нецифровых символов в такой записи нет, то она называется записью вещественной константы "с фиксированной точкой". Помимо нее существует экспоненциальная форма записи вещественной константы. Она основана на том, что любое вещественное число r может быть представлено в виде a·10b, где коэффициент "a" называется мантиссой, а показатель степени b - порядком или характеристикой. Например, -5. = -5·100 = -0.05·102 = -50·10-1. Таким образом, вместо записи вещественного числа r в форме константы с фиксированной точкой можно было бы использовать форму записи, состоящую из двух констант: мантиссы и порядка, причем константа мантиссы может быть с фиксированной точкой, либо целой, а порядка - только целой. Так мы приходим к следующей экспоненциальной (или плавающей, учитывая ее неоднозначность) форме записи вещественной константы: мантиссаEпорядок или мантиссаDпорядок. Наличие двух вариантов обусловлено тем, что кроме вещественного типа одинарной точности (Single) существует вещественный тип двойной точности (Double); буква D в экспоненциальной форме указывает, что формат размещения данного в памяти будет соответствовать типу Double.
     Для чего нужна бывает экспоненциальная форма? Естественно, для компактной записи очень больших или очень маленьких (по модулю) чисел. Причем такая запись используется не только для обращения к константам в коде программы, но и в фазе выполнения программы при вводе данных в нее пользователем или выводе данных программой.
     Надо сказать, что представление числа в плавающей форме используется не только как одна из форм записи вещественных констант, но и лежит в основе формата размещения данных вещественных типов (Single, Double) в памяти, почему эти типы называют также плавающими.
     Целая величина 3 и вещественная 3. математически равны. С точки же зрения машинной арифметики это, вообще говоря, неверно. Ибо эти величины не только занимают области памяти различного размера, но и кодируются в этих областях совершенно по-разному. Кодирование данного целого типа основано на двоичной записи целого неотрицательного числа, когда каждый бит разрядной сетки интерпретируется как коэффициент разложения числа по степеням двойки, причем показателем степени выступает номер разряда. Схематически двухбайтовая ячейка (байты с адресами n и n+1), выделенная под величину целого типа, может быть представлена так:

     Двоичные разряды ячейки пронумерованы от 0 до 15 в направлении от младшего байта (с меньшим адресом) к старшему. Каждый из этих двоичных разрядов может находиться в одном из двух состояний (типа "включен"-"выключен"), которые интерпретируются как 0, либо 1. В таком случае можно интерпретировать состояние всей ячейки как двоичную запись (запись в системе счисления по основанию 2) целого числа . Например, состояние

интерпретируется как 0·215+0·214+...+0·29+1·28+1·27+1·26+0·25+1·24+
+0·23+0·22+1·21+1·20 = 256+128+64+16+2+1 = 467.
     Легко понять, что в двухбайтовой ячейке можно хранить таким образом неотрицательные целые числа от 0 до 65535. Понятно, что если мы хотим хранить еще и отрицательные целые, нам нужно сдвинуть этот диапазон в отрицательную область. Как это сделать? Мы, например, могли бы использовать для этого самый старший бит ячейки, где 1 указывала бы на отрицательность величины, хранимой в последующих разрядах описанным способом, а 0 - на неотрицательность. Диапазон представимых величин был бы при этом от ­-32767 до 32767. На практике однако используется представление отрицательной целой величины в так называемом дополнительном коде. При этом 0 в старшем разряде ячейки также взаимно-однозначно определяет неотрицательность хранимой в последующих разрядах величины из диапазона 0 ÷ 32767. Противоположная отрицательная величина для каждой из положительных величин этого диапазона образуется так:

     • все биты ячейки инвертируются, т.е. все 1 заменяются на 0, и наоборот;

     • к полученному значению добавляется 1.
     Нетрудно проверить, что при сложении исходного положительного целого с полученным числом в дополнительном коде даст 0 - т.е. то, что и должно быть при сложении противоположных чисел.
Проверим:   47 = 	00000000 00101111
Инвертируем:	        11111111 11010000
Добавляем 1:	        11111111 11010001 = -47
Складываем с 47:	00000000 00101111
			00000000 00000000

     Число -32767 в описанном дополнительном коде имеет 1 только в самом старшем и самом младшем разрядах. Отняв от этого числа 1, мы получаем еще одно представимое отрицательное целое: -32768 = 10000000 000000002 (нижний индекс указывает основание системы счисления). Таким образом, диапазон целых величин, представимых в дополнительном коде от -32768 до 32767. Ну, а самое главное чем хорош дополнительный код - операция вычитания сводится при, его использовании, к операции сложения с противоположной величиной.
     Целый тип Long отличается от типа Integer только своей вдвое большей разрядностью.
     Перейдем теперь к плавающим типам данных Single и Double.
     Название "плавающие", как уже отмечалось, характеризует то обстоятельство, что разрядная сетка ячейки представляет не саму числовую величину, а пару параметров, характеризующих запись числа в экспоненциальной форме. Это позволяет охватить гораздо больший диапазон представимых величин, чем в случае целого типа.
     В формате представления данных плавающих типов вещественная величина рассматривается в экспоненциальной форме, но не по основанию 10, как при записях констант, а по основанию 2:
     a·2b
     Для удобства используется нормализованное экспоненциальное представление, когда значение модуля мантиссы a всегда лежит в диапазоне 1 <= |a| < 2. При этом каждое вещественное число становится однозначно определяемым единственной парой мантиссы и порядка.
     Рассмотрим формат их размещения в 4-байтовой ячейке для типа Single:

     Самый старший бит ячейки - знаковый; если а<0 то этот бит установлен в 1, в противном случае - в 0. Следующие 8 бит содержат преобразованный порядок: целое значение b+127 в форме обычного двоичного представления целой величины. Наконец, последние 23 бита содержат преобразованную мантиссу: величину |а|-1 в форме коэффициентов ее разложения по степеням 2 с отрицательными показателями (в порядке убывания от 2-1 до 2-23). Средства плавающей арифметики обеспечивают правильную работу с хранимой таким образом величиной типа Single.
     Рассмотрим, для примера, десятичное число 0.4 (в программировании принято отделять целую часть от дробной точкой). Его двоичное представление в ячейке типа Single таково:

     0 0 1 1 1 1 1 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 1 ,

где слева направо отдельно подчеркнуты бит знака мантиссы, 8 бит представления увеличенного на 127 порядка и 23 бита представления уменьшенного на 1 модуля мантиссы.
     Вычислим представленную в ячейке величину:
     (1 + 1·2-1 + 0·2-2 + 0·2-3 + 1·
     2-4 + 1·2-5 + 0·2-6 + 0·2-7 +
     1·2-8 + 1·2-9 + 0·2-10 + 0·2-11 + 1·2-12 + 1·2-13 + 0·2-14 +
     0·2-15 +1·2-16 + 1·2-17 + 0·2-18 + 0·2-19 + 1·2-20 + 1·2-21 +
     0·2-22 + 1·2-23)·20·2^7;+1·26+1·25+1·24+1·23+1·22+0·21+1·20­-127 =
     (1+0.5+0.0625+0.03125+0.00390625+0.001953125+0.000244140625+
     0.0001220703125+ 0.0000152587890625+0.00000762939453125+
     0.0000009536743164062+0.0000004768371582031+
     0.0000001192092895508)·264+32+16+8+4+1-127 = 1.6000000·2-2 = 0.4
     В результате получено 7 верных знаков в дробной части.
     Можно заметить, что при описанном формате хранения числа невозможно представить значение 0. Нулевое значение всех битов ячейки типа Single представляет, как нетрудно определить, величину 2-127. Плавающая арифметика, тем не менее, рассматривает это представление как 0 ("машинный ноль"). Вообще, 0 считается любое значение, которое по модулю меньше 2-126; наименьший представимый модуль соответствует 1.17549435·10-38 (или, в форме константы языка Visual Basic, 1.17549435E-38). Машинной бесконечностью считается число 2128; наибольший представимый модуль (2-2-23)·2127 ~ 3.4028235·1038 (3.4028235Е38).
     Типу данных Double отводится 8-байтовая ячейка, в которой хранимая величина в плавающей форме a·2b представлена таким же образом, как и в ячейке типа Single. Разница лишь в большей точности и диапазоне представления: на хранение увеличенного на 1023 порядка, изменяющегося в диапазоне от 0 до 2046, отводится 11 бит, а на хранение уменьшенного на 1 модуля мантиссы - 52 бита. Диапазон изменения модуля мантиссы -
     от 2.2250738585072014·10-308 до 1.7976931348623157·10308; машинный ноль - 2-1023, машинная бесконечность - 21024.
     Какие еще бывают константы, кроме строковых, десятичных целых и вещественных?
     Мы задаем значения цвета (в фазе проектирования или выполнения) с помощью шестнадцатиричных (Hexadecimal) целых констант, начинающихся с приставки &H (или &h): &H00FFFF. Как следует из названия, запись такой константы (после приставки) содержит коэффициенты разложения числа по степеням числа 16. Первые 10 цифр этой системы счисления совпадают с десятичными цифрами (0­-9), а последующие цифры обозначаются начальными буквами латинского алфавита: A (1010), B (1110), C (1210), D (1310), E (1410), F (1510). После F идет 1016=1610, 1116=1710 и так далее до FF=15·161+15·160=25510, после чего идет 10016=25610 и так далее. Почему удобно задавать цвета шестнадцатиричными константами? Дело в том, что для задания цвета по системе RGB (Red, Green, Blue) используется смешение красного, зеленого и синего цветов различной интенсивности. Так вот каждая из трех пар шестнадцатиричных цифр в такой константе задает интенсивность каждого из этих основных цветов в формируемой смеси. Самая младшая (левая) пара задает интенсивность красного, средняя - зеленого и левая - синего цветов. Например, если взять каждого цвета по половине интенсивности (&H7F7F7F), то получится серый цвет. Всего таким образом можно задать 256·256·256=224=16777216 оттенков (такой стандарт цветности называют True Color; число 24, определяющее количество двоичных разрядов, требуемых для задания всех оттенков, называется глубиной цветности). Рекомендуется к задающей цвет шестнадцатиричной константе приписывать справа окончание "&", указывающее на тип Long формата размещения такой константы в памяти.
     Возможно задание целых констант и в восьмеричном (Octal) представлении, на что указывается приставкой &O (&o): &O127=8710.
     Константы даты и времени (тип Date) допускают очень большое разнообразие в указаниях даты и/или времени суток, которые, однако, всегда помещены внутри пары символов "#" ( hash - "лапша"):
#17:04:23# 'Время суток
#January 27, 1993# ' Дата
#11/30/1998#
#3-6-93 13:20#
#March 27, 1993 1:20am#
#Apr-2-93#
#4 April 1993#
     Данное типа Date (константа или переменная) хранится в памяти так, как хранилось бы числовое значение типа Double, у которого при записи его "с фиксированной точкой" целая часть давала бы число дней, на которое дата в данном типа Date отличается от 1 января 1900 года (более ранняя - отрицательна), а дробная - долю от полных суток (24 часа), содержащегося в данном типа Date времени суток (0 - полночь; 0.5 - полдень).
     Наконец, существуют булевы (тип Boolean) константы True (истина) и False (ложь). Они занимают двухбайтовые области, во всех разрядах которых либо 0 (для константы False), либо 1 (для True).
     А вот констант типа Decimal пока нет, как и нет такого самостоятельного типа данных. Есть только такой подтип свободно избираемого типа Variant. А раз нет - мы не будем указывать этот тип в числе допустимых при описаниях тех или иных инструкций, но укажем каков этот будущий тип в таблице.
     В следующих таблицах перечислены поддерживаемые типы данных, размеры, требуемые для сохранения значений, и диапазоны допустимых значений.

Числовые типы

     Целые

     Плавающие

СТРОКОВЫЕ И ПРОЧИЕ ТИПЫ




     Отметим некоторые детали приведенных описаний.
     Variant является особым типом данных. Переменные этого типа могут содержать любые данные, за исключением строк фиксированной длины и определяемых пользователем типов. Переменная типа Variant может также содержать специальные значения Empty (переменная не была инициализирована), Nothing (отсутствие у объектного подтипа ссылки на объект) и Null (пустое значение как результат определенных операций). Значения типа Error создаются путем преобразования действительных чисел в значения ошибки с помощью функции CVErr.
     Указать характер подтипов типа Variant позволяют функции VarType или TypeName.
     В общем случае, числовые данные типа Variant сохраняют свой исходный тип данных внутри типа Variant. Например, если присвоить переменной типа Variant значение типа Integer, то в последующих операциях Variant трактуется как Integer. Однако если арифметическая операция, выполненная над переменной типа Variant, содержащей значение типа Byte, Integer, Long или Single, приводит к тому, что результат выходит за границы диапазона допустимых значений исходного типа, то результат преобразуется к следующему более широкому типу внутри типа Variant. Значение типа Byte преобразуется к типу Integer, Integer преобразуется к типу Long, а значения типа Long и Single преобразуются к типу Double. Если же за пределы допустимого диапазона значений выходят переменные типа Variant, содержащие значения типа Currency, Decimal или Double, то регистрируется ошибка.

Массивы переменных

     Массивы - это специальная форма организации данных. В определенных ситуациях массив может выступать и как тип данных, и как переменная типа массив. Массивы могут быть одномерные и многомерные; фиксированного размера и динамические.
     Одномерный массив - это упорядоченный набор переменных, расположенных в смежных ячейках памяти, имеющих одно имя и разные целочисленные индексы, указываемые при обращении к таким переменным арифметическими выражениями в круглых скобках после имени: A(10); u2(x); Аrr(s+2*t) и т.д. (вычисленное нецелочисленное выражение округляется до ближайшего целого: дробная часть 0.5 и меньше - до 0; больше 0.5 - до 1). Такие переменные ("переменные с индексом") одного массива называются его элементами. Индексы смежных элементов массива различаются на 1; наименьший для данного массива индекс называется его нижней границей, наибольший - верхней. Количество элементов в одномерном массиве, таким образом, на 1 больше разности его верхней и нижней границ.
     Одномерный массив переменных может быть объявлен с помощью инструкций Dim, Public, Private и Static (в зависимости от того, какую область видимости и время жизни мы хотим ему обеспечить) так же, как и обычная переменная, например:
Dim Arr(20) As String 'Массив размером в 21 строковую величину
Dim Koef(3 To 43) As Double 'Массив в 41 величину типа Double
Dim Point() As Long 'Динамический массив длинных целых
Dim Obj(20) As String, Obj1(20) As String 'Два массива типа String
Dim Obj(20), Obj1(20) As String 'Первый массив типа Variant (по 
                                'умолчанию), второй - String
 
     Если при объявлении одномерного массива на месте индекса (в круглых скобках после имени массива) стоит константа (возможно константное выражение), то ее значение определяет верхнюю границу массива; нижняя граница при этом по умолчанию равна 0. Изменить значение нижней границы массивов по умолчанию в пределах текущего модуля можно предварительным включением в код модуля инструкции Option Base 1. Можно, однако, задать обе границы измерения явно, как это было сделано во втором примере, с использованием ключевого слова To между заданными константами нижней и верхней границами. Таким образом, при объявлении массива на месте индекса задается диапазон его значений.
     Наряду с одномерным массивом могут быть объявлены и двумерные массивы, то есть такие, у которых каждый элемент определяется парой индексов: Имя_массива(Индекс_1, Индекс_2): a(4, 9); strNames(a+j, (k+i)*m). То есть элементы массива можно представить как ячейки таблицы. При этом первый индекс дает нам номер строки, а второй - номер столбца. А каков порядок расположения этих элементов в памяти? Сначала располагаются один за другим элементы первой строки, затем второй и т.д. То есть при последовательном переходе от элемента к элементу быстрее меняется второй индекс (при переходе к соседнему элементу), а затем уже первый (при переходе к соседней строке).
     По аналогии с переходом от одномерного массива к двумерному мы можем представить себе переход к трехмерному массиву и далее к n-мерному (n?60): Имя_массива(Список_индексов). Многомерные массивы располагаются в памяти так, что при переходе к соседнему элементу быстрее всего меняется самый правый индекс, медленнее всего самый левый. Каждый из индексов многомерного массива образует его измерение: первый индекс первое измерение, второй второе и так далее. Действительно, если зафиксировать все индексы многомерного массива кроме одного, то мы получает одномерное сечение исходного массива, измеряющее "глубину" многомерного массива в направлении определенного данным индексом сечения.
     Количество измерений дает, таким образом, размерность массива.
     Каждый индекс имеет свою нижнюю и верхнюю границы (границы измерения), определяемые так же, как в одномерном случае. Размер каждого измерения также на 1 превышает разность верхней и нижней его границ.
     А как посчитать размер (т.е. количество элементов) многомерного массива? Очевидно, перемножив размеры его измерений.
     Объявление многомерного массива осуществляется по тем же принципам, что и одномерного:
Dim Tab(20, 30) As Integer 'Массив размером в 21*31 целую величину
Dim K_01(14, ­-3 To 43, 2) As Byte 'Массив размером в 15*47*3 байт
     Заметим следующее.
     Когда мы говорим о размере массива, мы говорим только об объеме хранимых в нем данных. На самом же деле массивы любых типов данных требуют, помимо этого, еще 20 байт памяти плюс 4 байт на каждое измерение массива. Поэтому, например, в последнем примере реальный занимаемый массивом объем будет 15*47*3+32 байта.
     Массив (любого типа) может быть подтипом переменной (простой или с индексом) типа Variant. Таковым он становится, например, при присваивании массива переменной типа Variant:
Dim var As Variant, arrDate(1000) As Date
var=arrDate()
     После этого переменная var, "содержащая" массив arrDate, будет занимать весь объем, выделенный массиву (8*1001+24), и еще 16 байт, из которых 4 байта содержат ссылку на массив: адрес байта, с которого начинается область памяти, занимаемая массивом. То есть никакой пересылке элементов массива при присваивании его переменной типа Variant не производится. Ссылочный механизм используется и при присваивании переменной типа Variant строковых и объектных значений (присваивание значения типа Object делается с использованием ключевого слова Set). Это позволяет эффективно сортировать размещаемые в строках, объектах или массивах списки данных больших объемов, так как при этом реально переприсваиваются только адресующие эти списки 4-байтовые значения.
     Существуют стандартные функции Visual Basic, обеспечивающие сервис при работе с массивами. Функции, возвращающие значения типа Long
 
Lbound(Имя_Массива  [, Номер_измерения])
Ubound(Имя_Массива  [, Номер_измерения])
возвращают соответственно нижнюю и верхнюю границы массива по заданному измерению (если их несколько). (Так как в общем случае задание номера измерения не обязательно, то в соответствие с общепринятыми правилами нотации необязательная часть конструкции представлена в квадратных скобках.)
     Функция, возвращающая значение типа Boolean IsArray(Имя_переменной) возвращает значение True, если передаваемое функции в качестве аргумента имя переменной (без индексов) является именем массива; в противном случае функция возвращает False. Ее используют обычно для определения, не является ли массивом переменная типа Variant.
     Функция, возвращающая значение типа Variant подтипа массив
Array([Список_аргументов])
возвращает массив внутри значения типа Variant, элементами которого являются значения аргументов в порядке их взаимного следования:
Dim arrWeek, newDay
arrWeek = Array("Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс")
newDay = arrWeek(2) 'присвоение значения "Ср" переменной newDay	
     Если список аргументов пуст, создается массив нулевой длины. Список_аргументов представляет разделенный запятыми список значений, присваиваемых элементам массива, содержащегося внутри значения типа Variant. Если аргументы не указываются, создается массив нулевой длины.
     Значение типа Variant, содержащее массив, требует 12 байт в дополнение к объему, требуемому массивом.
     Помимо массивов фиксированного размера инструкцией Dim может быть объявлен динамический массив, в объявлении которого не указываются границы. Реальное размещение элементов массива производится, затем, инструкцией ReDim, причем неоднократно (но число измерений при переразмещениях меняться не должно). Для избежания потери данных при переразмещении массива в инструкции ReDim может использоваться ключевое слово Preserve. В случае его использования допустимо изменение инструкцией ReDim только верхней границы последнего измерения массива.

Преобразования типов данных

     Во многих случаях Visual Basic 5 "подстраивает" используемые данные к тем типам, которые должны быть использованы "по смыслу". Наиболее распространена взаимная подстройка числовых и строкового типов: при присваивании, в арифметических и строковых выражениях, при использовании в качестве параметров инструкций, при передаче "по значению" (ByVal) аргументов в функцию. Так что везде, где возможно применение "числового или строкового выражения", возможно применение и смешанного, "строково-числового" выражения. Подстройка строковых типов к числовым, разумеется, возможна только при числовом содержимом строки (с учетом соответствия использованного символа десятичного разделителя указанному в настройке операционной системы).
     Проиллюстрируем "интеллект" среды разработчика по интерпретации данных смешанного типа в операциях "+" и "&":
Dim Y As String
Y = "7"
Print "6" + Y, "6" & Y 'Будет напечатано: 67    67
Print 6 + Y, 6 & Y     'Будет напечатано: 13    67
     Когда другие числовые данные конвертируются в тип Date, целая часть значения представляет информацию о дате, в то время как дробная часть время. Полночь интерпретируется как 0, полдень - как 0.5. Отрицательные целые представляют даты ранее 30 декабря 1899 года. Чтобы определить, может ли аргумент дата быть преобразована к типу даты или времени, следует использовать функцию IsDate. При преобразовании числа в дату переводится целая часть числа. Любая дробная часть числа преобразуется во время суток, отсчитываемое от полуночи. Для явного преобразования строки цифр в число используется функция Val; явное обратное преобразование (в тип Variant) осуществляет функция Str (вместо которой более гибкое использование допускает функция Format, приведенная ниже). Обе эти функции не допускают иных десятичных разделителей, кроме точки.
     Наряду с ними имеется еще 11 функций, каждая из которых преобразует допустимое числовое или строковое выражение к соответствующему типу данных:

     Если переданное в функцию значение аргумента выражение находится вне допустимого диапазона для соответствующего типа данных, возникает ошибка. Функции преобразования типов данных позволяют показать, что результат некоторой операции должен быть представлен определенным типом данных, а не типом, используемым по умолчанию. Например, функция CCur обеспечивает арифметические действия над числами в денежном формате в тех случаях, когда по умолчанию используются действия над числами с плавающей точкой обычной или двойной точности или над целыми числами. Кроме того, эти функции используют вместо функции Val для совместимости с различными национальными настройками при преобразовании из любого типа данных в другой. Например, при использовании функции CCur разделители целой и дробной частей, а также разделители разрядов и параметры денежных единиц правильно распознаются в зависимости от национальной настройки компьютера.
     Если дробная часть числа в точности равна 0.5, то функции CInt и CLng всегда округляют число до ближайшего четного числа. Например, 0.5 округляется до 0, а 1.5 до 2. Необходимо отличать функции CInt и CLng от функций Fix и Int, которые выполняют усечение (отбрасывание), а не округление дробной части числа. Кроме того, функции Fix и Int всегда возвращают значение с тем же типом данных, который был передан через аргумент.
     Функция CDate распознает форматы дат в соответствии с национальной настройкой системы (не считая того, что длинный формат даты не распознается, если он содержит строку для дня недели).
     Для обеспечения совместимости с предыдущими версиями Visual Basic поддерживается также функция CVDate. Синтаксис функции CVDate совпадает с синтаксисом функции CDate, но CVDate возвращает значение с подтипом Date типа Variant, а не значение типа Date. Тот же результат получается при преобразовании выражения к типу Date, а затем присвоении его переменной типа Variant. Это соответствует преобразованию всех других внутренних типов данных к их эквивалентным подтипам Variant.
     Функция CDec не возвращает конкретный тип данных; вместо этого всегда возвращается значение типа Variant, преобразованное к подтипу Decimal.
     Для определения подтипа переменной типа Variant используется функция VarType. Ниже приведена таблица возвращаемых ею значений в зависимости от типов ее аргументов, а также имена соответствующих им глобальных констант:

     Функция VarType никогда не возвращает значение vbArray само по себе. Эта константа всегда складывается с каким-либо другим значением, указывающим тип массива. Константа vbVariant возвращается только в сочетании с константой vbArray, чтобы показать, что в аргументе функции VarType указан массив типа Variant. Например, значение, возвращаемое массивом с целочисленными элементами, равно vbInteger + vbArray, т.е. 8194. Если объект имеет свойство, используемое по умолчанию, то VarType(Объект) возвращает тип этого свойства.
     Как уже говорилось, функция Format имеет большие чем функция Str возможности преобразования в строковый тип. Приведем примеры различных применений функции Format для форматирования значений с использованием стандартных и специальных форматов.
     Фактические символы, используемые в качестве разделителя даты (/), разделителя времени (:) и индикатора "AM/PM", определяются текущими национальными настройками. В среде разработчика для изображения дат и времени используются установленные в этой среде краткий формат времени и даты, соответственно. Во время выполнения программы для изображения дат и времени используются краткие системные форматы даты и времени, которые могут отличаться от установленных в среде разработчика. В данном примере используются национальные настройки, принятые в России.
     В среде разработчика MyTime и MyDate изображаются в текущем системном кратком формате времени и даты соответственно.
Dim MyTime As Date, MyDate As Date, MyStr As String
MyTime = #5:04:23 PM# 'Краткий формат даты
MyDate = #1/27/98# 'Краткий формат времени
MyStr = Format(MyTime, "h:m:s") 'Возвращает "17:4:23"
MyStr = Format(MyTime, "hh:mm:ss AMPM") 'Возвращает "05:04:23"
MyStr = Format(MyDate, "dddd, mmmm d yyyy") ' Возвращает 
                                                      '"Среда, Январь 27 1993"
'Если формат не указан, возвращается строка
MyStr = Format(23) 'Возвращает "23"

'Специальные форматы
MyStr = Format(5459.4, "##,##0.00") 'Возвращает "5 459.40"
MyStr = Format(334.9, "###0.00") 'Возвращает"334.90"
MyStr = Format(5, "0.00%") 'Возвращает "500.00%"
MyStr = Format("ПРИВЕТ", "<") 'Возвращает "привет"
MyStr = Format("Вот он какой", ">") 'Возвращает "ВОТ ОН КАКОЙ"

Области видимости, время жизни и именование переменных

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

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

     ¤  Переменные уровня формы или модуля (объявленные в их разделе General Declarations) доступны внутри всех процедур той же формы или модуля и не доступны в каких-либо других процедурах, если только переменная не объявлена с ключевым словом Public. В случае объявления переменной как Public она доступна из всех процедур данного проекта и даже (при отсутствии инструкции Option Private Module) - везде вне данного проекта. При этом обращение к ней извне формы осуществляется с помощью конструкции Имя_формы.Имя_переменной. Время жизни переменной уровня формы - до события Terminate данной формы, уровня модуля - в течение времени выполнения приложения. Имена переменных (да и именованных констант) состоят из последовательностей заглавных и строчных букв латинского алфавита, цифр и знаков подчеркивания, начинаются с буквы, имеют длину не более 255 символов и не совпадают с ключевыми словами. Имена должны быть осмысленными. Если имя составное, т.е. включает два смысловых слова, то каждое из них рекомендуется начинать с заглавной буква и продолжать строчными: BranchNumber.
     При именовании переменных рекомендуется учитывать их тип и область видимости. Единого соглашения пока не существует. Однако в большинстве таких соглашений переменные уровня формы или модуля начинают с буквы g, если она имеет область доступа Public и m - в противном случае. По рекомендации фирмы Microsoft после области [g/m] в имени переменной идет область символа [a], используемого для указания переменных, являющихся массивами. Третья область приставки имени состоит из буквы, указывающей тип переменной:
n	- Integer
c	- Currency
d	- Double
b	- Boolean
y	- Byte
l	- Long
g	- Single
t	- Date
o	- Object
s	- String
v	- Variant
e	- Enum
u	- тип, определяемый пользователем
     После приставки описанного формата, состоящей из 1-3 букв, следует собственно содержательное имя переменной. Например, имя matDateBirth указывает собою на то, что оно представляет массив переменных типа Date c областью доступа в форме или модуле ее объявления; gOldValue - локальную переменную типа Single.

II. ОПЕРАЦИИ

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

     • арифметические (используемые для выполнения математических вычислений);

     • сравнения (используемые для задания отношений порядка между значениями);

     • конкатенации (используемые для объединения строковых значений);

     • логические (используемые для выполнения логических операций).

Арифметические операции

     Категория арифметических содержит 8 операций:
Оператор    Синтаксис			Результат

   +	  операнд1+операнд2	   Сумма операндов
   -	  операнд1-операнд2	   Разность между операндами
   -	      -операнд		   Противоположный операнд
   *	  операнд1*операнд2	   Произведение операндов
   /	  операнд1/операнд2	   Частное от деления операндов
   \	  операнд1\операнд2	   Целая часть частного от деления
  Mod	 операнд1 Mod операнд2	   Остаток от целочисленного деления
   ^	  операнд1^операнд2	   <Операнд1> в степени <операнд2>
     Не вдаваясь в тонкости, которые можно получить из справочной системы, укажем лишь самое важное.

=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•

     ¤  Если операндами операций сложения, вычитания или умножения являются данные различных типов точности (типы упорядочиваются по точности так: Byte, Integer, Long, Single, Currency, Double и Decimal), то результат имеет тип операнда с большей точностью. Из этого правила имеются такие исключения: умножение чисел типа Single и Long дает тип Double; если операнды имеют тип Variant и результат выходит за границы диапазонов значений их подтипов, то подтип типа Variant результата берется более старший, чем у обоих операндов.

     ¤  Несмотря на то, что допускается использование оператора "+" для слияния строковых значений, рекомендуется во избежание возможной путаницы и необходимости дополнительных описаний в программе использовать для слияния строк оператор конкатенации "&".

     ¤  Результат обычного деления имеет тип данных Double или подтип Double типа Variant. Из этого правила имеются следующие исключения. Если оба операнда имеют тип Byte, Integer или Single, то результат имеет тип Single, если при этом результат не выходит за рамки допустимого для этого типа диапазона значений; в последнем случае возникает ошибка. Если же оба операнда имеют такие же, но не типы, а подтипы типа Variant, то результат имеет подтип Single типа Variant, если при этом он не выходит за рамки допустимого диапазона значений; в последнем случае результат получает значение подтипа Double.

     ¤  Перед выполнением целочисленного деления значения операндов округляются (дробная часть равная 0.5 округляется в сторону получения четного значения) и преобразуются до целых значений типа Byte, Integer или Long. Типом результата после этого будет более старший из типов операндов. Деление полученных целых значений производится для получения только целой части; дробная отбрасывается. Например, в выражении 25.3 \ 6.6 вырабатывается результат 3.

     ¤  В операции нахождения остатка от деления ("деления по модулю") над операндами производятся те же преобразования, что и при целочисленном делении. После этого абсолютная величина первого операнда делится нацело на абсолютную величину второго. Полученный от такого целочисленного деления остаток со знаком первого операнда буде являться результатом. Например, в выражении 6.5 Mod -4.7 вырабатывается результат 1.

     ¤  В операции возведения в степень (^) отрицательные значения первого операнда (основания степени) допускаются только для целых значений второго операнда (показателя). Стоящие рядом в выражении несколько операторов возведения в степень "^" выполняются слева направо!

=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•

Операции сравнения

     Имеется три синтаксические разновидности операций сравнения:
<операнд1> <оператор cравнения> <операнд2>
<операнд1> Is <операнд2>
<операнд1> Like <операнд2>
     Рассмотрим их.

     ¤  В первой разновидности сравниваются операнды, представляющие собой числовые, либо строковые выражения (в порядке, определяемом значениями кодов, либо в лексикографическом порядке на основе системной национальной настройки без учета регистра - см. инструкцию Option Compare). Если операнды имеют разный (числовой и строковый) тип, то ошибка возникает только в том случае, если сравниваются числовое выражение и выражение типа Variant со строковым подтипом, представляющее нечисловую строку; в остальных случаях сравниваются преобразованные к одному (строковому, либо числовому) типу (по тем же правилам, что и при операции сложения) операнды. При этом результат операции имеет логический тип, но кроме значений True и False может принимать значение Null - если хотя бы один из операндов имеет значение Null. Значение True вырабатывается в том случае, если при вычисленных значениях операндов оператор правильно указывает отношение порядка (в математическом, либо лексикографическом смысле) между этими значениями; в противном случае вырабатывается False.
     Если сравниваются числовые значения разной точности, то правила их преобразования к одному типу отличаются от правил преобразования при арифметических операциях. Так, при сравнении значения типа Single со значением типа Double значение типа Double округляется до точности типа Single. Если значение типа Currency сравнивается со значением типа Single или Double, то последние преобразуются к типу Currency. Аналогично, при сравнении значения типа Decimal (пока как подтипа типа Variant) со значением типа Single или Double последние преобразуются к типу Decimal. Для типа Currency любая дробная часть со значением меньшим 0,0001 может оказаться потеряной. Для типа Decimal могут быть потеряны дробные части меньшие 1E-28 или возникнуть ошибка переполнения. Потеря дробных частей может привести к тому, что числа, не являющиеся целыми, будут сравниваться как целые.

     ¤  Оператор Is используется для сравнения двух переменных, содержащих ссылки на объекты. Если переменные содержат ссылки на один объект, результат имеет значение True; в противном случае результат имеет значение False. Ссылки на один объект в двух переменных задаются различными способами с использованием инструкции присваивания Set.

     ¤  Оператор Like используется для сравнения на совпадение двух строковых выражений. Если значение левого операнда соответствует значению правого (имеющего смысл "образец"), результат имеет значение True; при их несоответствии результат имеет значение False. Если любой из операндов имеет значение Null, результат также имеет значение Null. В операнде-образце могут также использоваться спецсимволы, имеющие смысл шаблонов, совпадающих (для обеспечения совпадения операндов) со строковыми значения из заданных для каждого спецсимвола совокупностей.

СпециальныеСовпадающие
символысимволы
?Любой одиночный символ
*Любое (с нулевого) количество символов
#Любая одиночная цифра (0-9)
[список]Любой одиночный символ из списка
[!список]Любой одиночный символ вне списка

     Спецсимвол, представленный списком (можно без разделителей) в квадратных скобках, предоставляет для возможного совпадения любой содержащийся в нем символ, включая символы: левая квадратная скобка ([), вопросительный знак (?), символ (#) и звездочка (*). Правая квадратная скобка (]) в группе всегда рассматривается как специальный символ, но может сравниваться и как обычный символ вне группы. В дополнение к простому списку символов в квадратных скобках, допускается указание в образце диапазона символов с помощью дефиса (-), отделяющего нижнюю и верхнюю границы диапазона. Например, [A-Z] указывает, что совпадающими будут считаться все символы верхнего регистра латинского алфавита, находящиеся в нужной позиции значения левого операнда. Допускается указание внутри прямых скобок нескольких диапазонов без символов разделителей:
"BAU123khg" Like "B?[N-TV-X]*" 'Вырабатывается значение False.

Операция конкатенации

     Операция имеет следующий формат: <операнд1> & <операнд2>      Результатом операции (типа String ли Variant) является строка, образованная из значения первого операнда путем записи непосредственно вслед за ним значения второго операнда.
     Если операнд не содержит строковое значение, то оно преобразуется к подтипу String типа Variant. Если оба операнда являются строковыми выражениями, то результат имеет тип данных String; в противном случае результат принадлежит к подтипу String типа Variant. Если оба операнда является выражением со значением Null, результат имеет значение Null. Однако если значение Null имеет только одно выражение, то при слиянии с другим операндом этот операнд. рассматривается как пустая строка (""). Любой операнд со значением Empty также рассматривается как пустая строка.

Логические операции

     В Visual Basic 5 имеется 6 логических операций: 5 бинарных, представленных операторами And (конъюнкция или логическое "И"), Eqv (логическая эквиваленция), Imp (логическая импликация от первого операнда ко второму), Or (дизъюнкция или логическое "ИЛИ"), Xor (исключающее "ИЛИ" или сумма по модулю 2) и имеющими формат <операнд1> <оператор> <операнд2> и одну унарную (оператор логического отрицания Not) формата Not <операнд>      Действие данных операторов зависит от типов операндов.

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

     Если операнд унарной операции Not имеет логический тип и значение (True либо False), то результат операции будет иметь значение логически противоположное значению операнда (False либо True соответственно). При значении операнда Null значение результата будет тоже Null.

     ¤  Если операнд операции Not, либо оба операнда бинарных логических операций имеют числовые типы, то соответствующая логическая операция производится независимо над каждым разрядом (над парами соответствующих разрядов) операндов, предварительно преобразованных к целому типу необходимого размера. При этом в качестве логического значения True выступает битовое значение 1, а в качестве логического False битовое значение 0. Набор получивших таким образом значение разрядов интерпретируется, далее, как целое значение.

     ¤  Если один из операндов бинарной логической операции имеет логический тип и отличен от Null, а другой - числовой, то значение логического типа преобразуется в целое число (False в 0, True в -1), после чего логические операции выполняются по правилам для операндов числовых типов. Если же операнд логического типа имеет значение Null, то в операциях Or и Imp он интерпретируется как целое число 0, а в остальных операциях недопустим.

Приоритет и ассоциативность операций

     Если выражение содержит несколько операторов, то его вычисление производится в определенном порядке. Этот порядок определяется исходя из относительных приоритетов операций и ассоциативности (порядка выполнения: слева направо, либо наоборот) расположенных один за другим операторов одного приоритета.
     Сразу следует сказать, что операторы внутри операндообразующих круглых скобок всегда выполняются раньше, чем операторы вне скобок, так что имеет смысл порядок вычисления исходя из приоритетов и ассоциативности только для несодержащих операндообразующих скобок выражений.
     По синтаксису, выражение - это конструкция, в состав которой входит по крайней мере одна из лексем идентификатор, константа или стринг, а также, возможно, операторы, являющиеся знаками операций. Кроме того, для изменения естественного порядка формирования операндов используются круглые скобки (заключенное в них выражение является операндом).
     При наличии в выражении операторов, компилятор для каждой представляемой ими операции определяет ее операнды-подвыражения, значения которых участвуют в операции. Можно считать, что выполняющий грамматический разбор компилятор берет в невидимые "скобки" операнды для каждой операции выражения.
     Сначала формируются операнды для операций с высшим приоритетом. Если операций с одинаковым приоритетом несколько и расположены они так, что одно и то же подвыражение может быть операндом любой из них, то формирование идет согласно ассоциативности данной группы приоритета.
     После расстановки обозначающих операнды "скобок", компилятор начинает формировать последовательность кодов машинных команд. Для этого он берет самую "внешнюю" (не стоящую в "скобках") операцию и пробует строить код для вычисления ее операндов (очередность взятия того или иного операнда операции либо регламентируется самой операцией, либо, при отсутствии регламентации, определяется компилятором нерегламентированным образом). Если взятый компилятором операнд не содержит "скобок" (тех, что ранее "расставил" компилятор), то он включается в код. Если же содержит, - то компилятор переходит к рассмотрению самой внешней операции взятого операнда, анализируя ее в изложенном порядке. Эта процедура производится до тех пор, пока не будет построен код всего выражения.
     Подчеркнем: приоритет не определяет, что должно вычисляться в выражении раньше, а что позже. Он определяет только расстановку "скобок", определяющих, какое подвыражение чьим операндом является. Заметим также, что ассоциативность всех операций в Visual Basic 5 - слева направо.
     Если выражение содержит операторы разных типов, то первыми формируются операнды арифметические операций, следом за ними - операций конкатенации, далее - операций сравнения, а последними - логических операций. (Еще ранее, однако, формируются операнды, представляющие вызовы функций).
     Арифметические операции разбиты на следующие уровни приоритета:

     • Возведение в степень (^)

     • Изменение знака (-)

     • Умножение и деление (*, /)

     • Целочисленное деление (\)

     • Остаток от деления (Mod)

     • Сложение и вычитание (+, -)
     Все операции сравнения имеют равный приоритет.
     Уровни приоритета логических операций:

     • Not

     • And

     • Or

     • Xor

     • Eqv

     • Imp

III. ИНСТРУКЦИИ

     Инструкции языка разбиты на 8 тематических групп:

     • описаний и программных установок;

     • управления (ходом выполнения программы);

     • вычислений и преобразований данных;

     • ввода-вывода;

     • системных установок;

     • работы с файловой системой;

     • работы с процессами;

     • общего назначения.

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

Инструкции описаний и программных установок

Const


     [Public | Private] Const Имя [As Тип] = Выражение
     Public - ключевое слово, используемое на уровне модуля для описания констант, доступных всем процедурам во всех модулях. Не допускается в процедурах.
     Private - ключевое слово, используемое на уровне модуля для описания констант, доступных только внутри модуля, в котором выполняется описание. Не допускается в процедурах. Действует по умолчанию.
     Имя - имя константы, удовлетворяющее стандартным правилам именования переменных.
     Тип - один из поддерживаемых типов данных: Byte, Boolean, Integer, Long, Currency, Single, Double, Date, String или Variant. Для каждой описываемой константы следует использовать отдельное предложение As Тип.
     Выражение - константа или любое сочетание, которое включает все арифметические или логические операторы за исключением Is. Не допускается использование переменных, определяемых пользователем, а также любых функций в выражениях, присваиваемых константам.
     Примечания
     Константы, описанные в процедурах Sub, Function или Property, являются локальными для этих процедур. Областью определения константы, описанной вне процедуры, является модуль, в котором она описана. В любом месте, в котором возможно использование выражений, допускается использование и констант.
     Примеры
Const MyNumber = 1001
Public Const MyString = "Вторая школа"
Private Const MyInt As Integer = 5
Const MyStr = "1001 ночь", MySalary As Double = 1E-50

Declare

     Применяется на уровне модуля ссылок на внешние процедуры в библиотеке динамической компоновки (DLL).
     [Public | Private] Declare Sub Имя Lib "Имя_библиотеки" [Alias "Алиас"] [([Список])]
     Либо:
     [Public | Private] Declare Function Имя Lib "Имя_библиотеки"[Alias "Алиас"] [([Список_аргументов])][As Тип]
     Public - используется для описания процедур, которые доступны для всех других процедур во всех модулях.
     Private - используется для описания процедур, которые доступны только в модуле, в котором выполняется описание.
     Sub - указывает, что процедура не возвращает значение.
     Function - указывает, что процедура возвращает значение, которое может быть использовано в выражении.
     Имя - любое допустимое имя процедуры с учетом регистра.
     Lib - указывает, что описываемая процедура содержится в библиотеке динамической компоновки или в программном ресурсе. Обязательно для каждого описания. Имя_библиотеки - имя библиотеки динамической компоновки или программного ресурса, которые содержат описываемую процедуру.
     Alias - указывает, что вызываемая процедура имеет другое имя в библиотеке динамической компоновки. Это полезно, когда имя внешней процедуры совпадает с ключевым словом. Имеет смысл использовать Alias и в том случае, когда имя процедуры библиотеки динамической компоновки совпадает с именем общей переменной, константы или любой другой процедуры в той же области определения, а также если некоторые символы в имени процедуры библиотеки динамической компоновки не соответствуют правилам именования для библиотек динамической компоновки.
     Алиас - имя процедуры в библиотеке динамической компоновки или в программном ресурсе. Если первым символом не является символ (#), Алиас является именем точки входа процедуры в библиотеке динамической компоновки. Если символ (#) является первым символом, все последующие символы должны указывать порядковый номер точки входа в процедуру.
     Список_аргументов - список описаний переменных-аргументов, которые передаются в процедуру при ее вызове. Каждое описание имеет следующий синтаксис: [Optional] [ByVal | ByRef] [ParamArray] Имя[()] [As Тип]
     Optional - указывает, что этот аргумент необязателен, причем при этом также должны быть необязательными и быть описаны с помощью ключевого слова Optional и другие аргументы; альтернативным является использование ParamArray.
     ByVal - указывает, что этот аргумент передается по значению.
     ByRef - указывает, что этот аргумент передается по ссылке (по умолчанию).
     ParamArray - Используется только для последнего аргумента, указывая, что он является массивом необязательных элементов типа Variant. Позволяет передавать произвольное число аргументов. Это ключевое слово не может использоваться со словами ByVal, ByRef или Optional.
     Имя - имя переменной, представляющее передаваемый в процедуру аргумент; должно соответствовать стандартным правилам именования переменных. ( ) - требуются, если Имя является массивом.
     Тип - тип данных аргумента, передаваемого в процедуру; допускаются типы Byte, Boolean, Integer, Long, Currency, Single, Double, Date, String (только строки переменной длины), Object, Variant, определяемый пользователем тип.
     Тип - тип данных значения, возвращаемого процедурой Function; допускаются типы Byte, Boolean, Integer, Long, Currency, Single, Double, Date, String (только строки переменной длины), Variant, определяемый пользователем тип или объектный тип.
     Примечание. В языке Visual Basic имеется константа vbNullString для использования при вызове внешних процедур, когда внешней процедуре требуется строка без значения. Это не то же самое, что пустая строка ("").

Def

     Используются (только на уровне модуля) для задания типа данных, используемого по умолчанию для переменных, аргументов, передаваемых в процедуры, и значений, возвращаемых процедурами Function и Property Get, имена которых начинаются с соответствующих символов.
     DefОбозначение_типа Диапазон[, Диапазон] ...
     Обозначение_типа - определяет следующие объявляемые типы данных:
DefBool	-	Boolean
DefByte	-	Byte
DefInt	-	Integer
DefLng	-	Long
DefCur	-	Currency
DefSng	-	Single
DefDbl	-	Double
DefDate	-	Date
DefStr	-	String
DefObj	-	Object
DefVar	-	Variant
     Диапазон - указывает диапазон имен (по их начальным буквам), для которых задается по умолчанию указанный тип данных:
     Буква1[-Буква2]
     Буква1 и Буква2 представляют первую букву имени переменной, аргумента, процедуры Function или Property Get и могут быть любой буквой алфавита (регистр символов не существенен).
     Возможно явное указание типа данных любой определенной или не определенной ранее переменной с помощью инструкции Dim. Например, имеется возможность использовать следующую программу на уровне модуля для определения переменной с типом Double, хотя был задан тип данных по умолчанию Integer:
DefInt A-Z
Dim MyVar As Double

Dim

     Описывает переменные и выделяет для них память. Dim [WithEvents] Имя[([Список_диапазонов])] [As [New] Тип] [, [WithEvents] Имя[([Список_диапазонов])] [As [New] Тип]]...      WithEvents - указывает, что переменная Имя является объектной переменной, которая используется при отклике на события, генерируемые объектом ActiveX. Применяется только в модулях класса. Не допускается применение к массиву, а также использование совместно с ключевым словом New.
     Имя - имя переменной, удовлетворяющее стандартным правилам именования переменных.
     Список_диапазонов - указывает границы каждого измерения массива; допускается описание до 60 размерностей: [Нижняя To] Верхняя [, [[Нижняя To] Верхняя]...      Если нижняя граница массива не задана явно, она определяется инструкцией Option Base, а при отсутствии этой инструкции - равняется нулю.
     New - ключевое слово, включающее возможность неявного создания объекта. Если оно указано при описании объектной переменной, новый экземпляр объекта создается при первой ссылке на объект, поэтому нет необходимости присваивать ссылку на объект с помощью инструкции Set. Несовместимо с ключевым словом WithEvents.
     Тип - один из поддерживаемых типов данных: Byte, Boolean, Integer, Long, Currency, Single, Double, Date, String, Object, Variant, а также определяемый пользователем тип. Для каждой описываемой переменной следует использовать отдельную конструкцию As Тип; если она опущена и тип соответствующей переменной не задан инструкцией DefТип, то ее тип - Variant.
     Переменные, описанные с помощью ключевого слова Dim на уровне модуля, доступны для всех процедур в данном модуле. Переменные, описанные на уровне процедуры, доступны только в данной процедуре.
     Возможно применение инструкции Dim с пустыми скобками после имени переменной для описания динамического массива.
     При объявлении переменных производится выделение области памяти для их размещения и их инициализация, то есть присвоение им начальных значений. При этом числовая переменная получает значение 0, строка переменной длины получает значение пустой строки (""), а строка фиксированной длины заполняется нулями. Переменные типа Variant получают при инициализации значение Empty. Каждый элемент переменной определяемого пользователем типа при инициализации получает значение, которые он получил бы, если бы являлся отдельной переменной.
     Примеры
Dim AnotherVar, Choice As Boolean, BirthDate As Date
Dim MyMatrix(1 To 5, 4 To 9, 3 To 5) As Double
Dim MyArray() 'Динамический массив типа Variant

Enum

     Объявляет (только на уровне модуля) перечислимый тип (перечень, тип Enum) с заданным именем.
[Public | Private] Enum Имя_типа
    Имя_элемента [= Выражение]
    Имя_элемента [= Выражение]
    ...
End Enum

     Public - определяет, что Имя_типа Enum видимо во всем проекте (действует по умолчанию).
     Private - определяет, что Имя_типа Enum видимо только в модуле, в котором оно объявлено.
     Имя_типа - имя перечня, удовлетворяющее правилам именования переменных. Является спецификатором типа при последующих объявлениях инструкцией Dim переменных типа данного перечня.
     Имя_элемента - идентификатор, определяющий имя константы, принадлежащей данному перечню.
     Выражение - константное выражение, значение которого приводится к типу Long.
     Список элементов перечня задает каждому элементу целое значение по следующему алгоритму:
     1) всем элементам, которым приравнены константные выражения, присваиваются вычисленные значения этих выражений;
     2) если первому элементу списка не приравнено выражение, ему присваивается 0;
     3) после этого, просматривая все элементы от второго до последнего, присваивают каждому элементу, еще не имеющему значения, - значение предыдущего элемента, увеличенное на 1.
     Примечание. Чаще всего перечни объявляют для компактного задания набора именованных констант, которыми выступают элементы перечня. Однако можно объявлять и переменные типа перечня, что гарантирует появление списка имен элементов данного перечня (List Members) при при вводе инструкции присваивания значения такой переменной.

Function

     Описывает имя процедуры-функции, возвращаемое ею значение, аргументы, а также локализованную часть программного кода, называемую ее телом.
[Public | Private] [Static] Function Имя[(Список_аргументов)] [As Тип]
    [Инструкции]
End Function

     Public - указывает, что процедура доступна для всех других процедур во всех модулях. При использовании в личном модуле (содержащем инструкцию Option Private) такая процедура недоступна вне проекта.
     Private - указывает, что процедура доступна для других процедур только того модуля, в котором она описана.
     Friend - используется только в модуле класса. Показывает, что процедура видима в проекте (компонента программ), но невидима для приложения-клиента.
     Static - указывает, что локальные переменные процедуры сохраняются в промежутках времени между вызовами этой процедуры. Не действует на переменные, описанные вне процедуры.
     Имя - имя процедуры, удовлетворяющее стандартным правилам именования переменных.
     Список_аргументов - список переменных, разделенных запятой, представляющий аргументы, которые передаются в процедуру при ее вызове. [Optional] [ByVal | ByRef] [ParamArray] Имя[()] [As Тип] [= Значение_по_умолчанию]
     Optional - указывает, что этот аргумент необязателен, причем при этом также должны быть необязательными и быть описаны с помощью ключевого слова Optional и другие аргументы; альтернативным является использование ParamArray.
     ByVal - указывает, что этот аргумент передается по значению.
     ByRef - указывает, что этот аргумент передается по ссылке (по умолчанию).
     ParamArray - Используется только для последнего аргумента, указывая, что он является массивом необязательных элементов типа Variant. Позволяет передавать произвольное число аргументов. Это ключевое слово не может использоваться со словами ByVal, ByRef или Optional.
     Имя - имя переменной, представляющее передаваемый в процедуру аргумент; должно соответствовать стандартным правилам именования переменных.
     ( ) - требуются, если Имя является массивом.
     Тип - тип данных аргумента, передаваемого в процедуру; допускаются типы Byte, Boolean, Integer, Long, Currency, Single, Double, Date, String (только строки переменной длины), Object, Variant, определяемый пользователем тип.
     Значение_по_умолчанию - любая константа или константное выражение. Используется только вместе с параметром Optional. Если указан тип возвращаемого значения Object, единственным значением по умолчанию может быть Nothing.
     Тип - тип значения, возвращаемого процедурой-функцией. Поддерживаются типы Byte, Boolean, Integer, Long, Currency, Single, Double, Date, String (за исключением строк фиксированной длины), Object, Variant или любой определяемый пользователем тип. Невозможен возврат массивов любого типа, однако допускается возврат значения типа Variant, содержащего массив.

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

=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•

     ¤  Подобно процедуре-подпрограмме Sub процедура-функция Function является самостоятельной процедурой, которая может получать аргументы, выполнять последовательность инструкций и изменять значения своих аргументов. Однако в отличие от процедуры Sub вызов функции может (но не обязан!) применяться в качестве операнда выражения (как и вызов любой встроенной функции).
     Процедура Function вызывается в выражении по своему имени, за которым следует список аргументов в скобках (см. также описание инструкции Call).

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

     ¤  Для возврата значения из функции следует внутри ее тела присвоить это значение (какой-либо инструкцией присваивания) имени функции. Любое число таких инструкций присваивания может находиться в любом месте процедуры. Если имени процедуры-функции не присваивается никакого значения, процедура возвращает значение по умолчанию: числовая функция возвращает нулевое значение, строковая функция возвращает значение пустой строки (""), функция типа Variant возвращает значение Empty, а возвращающая ссылку на объект - значение Nothing.

     ¤  Процедуры, не описанные явно с помощью ключевых слов Public или Private, по умолчанию являются Public. Если не используется ключевое слово Static, значения локальных переменных не сохраняются между вызовами.

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

     ¤  Инструкция Exit Function приводит к немедленному завершению процедуры-функции. В точку вызова возвращается значение, последним присвоенное в процедуре имени функции, либо, если присвоений не было, возвращаемое значение объявленного типа функции по умолчанию. В теле процедуры-функции допустимо наличие любого числа инструкций Exit Function.

     ¤  В языке Visual Basic возможна реорганизация арифметических выражений для увеличения скорости их выполнения. Избегайте применения процедуры Function в арифметическом выражении, если эта процедура изменяет значения переменных, используемых в этом же выражении.

=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•

Option Base

     Применяется на уровне модуля для задания значения по умолчанию нижней границы индексов массива. Option Base {0 | 1}
     0 | 1 - значение по умолчанию нижней границы индексов.
     Может задаваться в модуле только один раз и должна предшествовать описаниям массивов.

Option Compare

     Применяется на уровне модуля для задания используемого по умолчанию способа сравнения строковых данных. Option Compare {Binary | Text | Database}
     Должна находиться в модуле перед любой процедурой.
     Указывает способ сравнения строк (Binary, Text или Database) в соответствующих инструкциях модуля (по умолчанию используется способ сравнения Binary).
     Способ Binary задает сравнение строк на основе порядка сортировки, определяемого внутренним двоичным представлением символов Microsoft Windows в соответствии с кодовой таблицей. В следующем примере представлен типичный результат двоичного порядка сортировки:
     A < B < E < Z < a < b < e < z < Б < Л < Ш < б < л < ш
     Способ Text задает сравнение строк без учета регистра символов на основе системной национальной настройки. Тем же символам, что и выше, при сортировке с инструкцией Option Compare Text соответствует следующий порядок:
     (A=a) < (B=b) < (E=e) < (Z=z) < (Б=б) < (Л=л) < (Ш=ш)
     Способ Database может использоваться только в Microsoft Access. Сравнение строк производится на основе порядка сортировки, определяемого национальной настройкой базы данных, в которой производится сравнение строк.

Option Explicit

     Налагает на уровне модуля требование на явное описание всех переменных этого модуля. Option Explicit
     Должна находиться в модуле до любой процедуры.
     При ее использовании необходимо явно описывать все переменные с помощью инструкций Dim, Private, Public, ReDim или Static. При попытке использовать неописанное имя переменной возникает ошибка времени компиляции.

Option Private Module

     Запрещает ссылки на содержимое модуля извне проекта. Option Private Module
     Инструкция должна находиться на уровне модуля перед любой процедурой. В этом случае общие элементы, например, переменные, объекты и определяемые пользователем типы, описанные на уровне модуля, остаются доступными внутри проекта, содержащего этот модуль, но недоступными для других приложений или проектов.
     Примечание. Инструкция Option Private Module полезна только для главных приложений, которые поддерживают одновременную загрузку нескольких проектов и допускают ссылки между загруженными проектами. Например, Microsoft Excel поддерживает загрузку нескольких проектов. В этом случае инструкция Option Private Module позволяет ограничить взаимную видимость проектов. Хотя Visual Basic допускает загрузку нескольких проектов, взаимные ссылки между проектами Visual Basic не разрешены.

Private, Public

     Описывают частные (Private) или общедоступные (Public) переменные и выделяют для них память.
     Синтаксис данных инструкций полностью совпадает с синтаксисом инструкции Dim (кроме наименования инструкции, разумеется): {Private | Public} [WithEvents] Имя[([Список_диапазонов])] [As [New] Тип] [, [WithEvents] Имя[([Список_диапазонов])] [As [New] Тип]]...
     Однако в отличие от инструкции Dim они применяются только на уровне модуля. При этом инструкция Private, описывающая частные, то есть доступные только для всех процедур модуля переменные, эквивалентна инструкции Dim, а инструкция Public распространяет область действия описываемых ею общедоступных переменных на все модули всех приложений, если только не действует инструкция Option Private Module. В последнем случае эти переменные являются общими только внутри проекта, в котором они содержатся.
     Не допускается использование инструкции Public в модуле класса для описания строковых переменных фиксированной длины.

Static

     Применяется на уровне процедуры для описания переменных и выделения памяти. Переменные, описанные с помощью данной инструкции, сохраняют свои значения все время, пока выполняется программа. Static Имя[([Список_диапазонов])] [As [New] Тип] [, Имя[([Список_диапазонов])] [As [New] Тип]]...
     Имя - имя переменной, удовлетворяющее стандартным правилам именования переменных.
     Список_диапазонов - указывает границы каждого измерения массива; допускается описание до 60 размерностей: [Нижняя To] Верхняя [, [[Нижняя To] Верхняя]...
     Если нижняя граница массива не задана явно, она определяется инструкцией Option Base, а при отсутствии этой инструкции - равняется нулю.
     New - ключевое слово, включающее возможность неявного создания объекта. Если оно указано при описании объектной переменной, новый экземпляр объекта создается при первой ссылке на объект, поэтому нет необходимости присваивать ссылку на объект с помощью инструкции Set.
     Тип - один из поддерживаемых типов данных: Byte, Boolean, Integer, Long, Currency, Single, Double, Date, String, Object, Variant, а также определяемый пользователем тип. Для каждой описываемой переменной следует использовать отдельную конструкцию As Тип; если она опущена и тип соответствующей переменной не задан инструкцией DefТип, то ее тип - Variant.
     Примечание. Инструкция Static и ключевое слово Static (в объявлениях процедур) похожи, но используются для получения разных результатов. При описании процедуры с ключевым словом Static память для всех ее локальных переменных выделяется один раз, и значения этих переменных сохраняются на все время выполнения программы. Для нестатических процедур память для переменных выделяется при каждом вызове процедуры и освобождается при завершении процедуры. Инструкция же Static применяется для описания переменных в нестатических процедурах, чтобы сохранить их значения на время выполнения программы.
     Инструкцию Static обычно помещают в начало процедуры вместе с другими инструкциями описаний, такими как Dim.

Sub

     Описывает имя процедуры-функции, возвращаемое ею значение, аргументы, а также локализованную часть программного кода, называемую ее телом. [Public | Private] [Static] Sub Имя[(Список_аргументов)] [Инструкции] End Sub
     Public - указывает, что процедура доступна для всех других процедур во всех модулях. При использовании в личном модуле (содержащем инструкцию Option Private) такая процедура недоступна вне проекта.
     Private - указывает, что процедура доступна для других процедур только того модуля, в котором она описана.
     Friend - используется только в модуле класса. Показывает, что процедура видима в проекте (компонента программ), но невидима для приложения-клиента.
     Static - указывает, что локальные переменные процедуры сохраняются в промежутках времени между вызовами этой процедуры. Не действует на переменные, описанные вне процедуры.
     Имя - имя процедуры, удовлетворяющее стандартным правилам именования переменных.
     Список_аргументов - список переменных, разделенных запятой, представляющий аргументы, которые передаются в процедуру при ее вызове. [Optional] [ByVal | ByRef] [ParamArray] Имя[()] [As Тип] Optional - указывает, что этот аргумент необязателен, причем при этом также должны быть необязательными и быть описаны с помощью ключевого слова Optional и другие аргументы; альтернативным является использование ParamArray.
ByVal - указывает, что этот аргумент передается по значению.
ByRef - указывает, что этот аргумент передается по ссылке (по умолчанию).
ParamArray - Используется только для последнего аргумента, указывая, что он является массивом необязательных элементов типа Variant. Позволяет передавать произвольное число аргументов. Это ключевое слово не может использоваться со словами ByVal, ByRef или Optional.
Имя - имя переменной, представляющее передаваемый в процедуру аргумент; должно соответствовать стандартным правилам именования переменных.
( ) - требуются, если Имя является массивом.
Тип - тип данных аргумента, передаваемого в процедуру; допускаются типы Byte, Boolean, Integer, Long, Currency, Single, Double, Date, String (только строки переменной длины), Object, Variant, определяемый пользователем тип.

     Инструкции - любая группа инструкций, выполняемых внутри процедуры.
     Дополнительные сведения
=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•

     ¤  Подобно процедуре-функции Function процедура-подпрограмма Sub является самостоятельной процедурой, которая может получать аргументы, выполнять последовательность инструкций и изменять значения своих аргументов. Однако в отличие от процедуры Function вызов процедуры-подпрограммы не может применяться в качестве операнда выражения.
     Процедура Sub вызывается в выражении по своему имени, за которым следует список аргументов в скобках (см. также описание инструкции Call).

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

     ¤  Процедуры, не описанные явно с помощью ключевых слов Public или Private, по умолчанию являются Public. Если не используется ключевое слово Static, значения локальных переменных не сохраняются между вызовами.

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

     ¤  Инструкция Exit Sub приводит к немедленному выходу из процедуры-подпрограммы. Выполнение программы продолжается с инструкции, следующей за инструкцией, в которой была вызвана процедура-подпрограмма. В теле процедуры-подпрограммы допустимо наличие любого числа инструкций Exit Sub.

     ¤  Для входа или выхода из процедуры Sub нельзя использовать инструкции GoSub, GoTo или Return.
=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•

Type

     Применяется на уровне модуля для описания определяемого пользователем типа данных, который содержит один или несколько элементов. После определения типа данных с помощью инструкции Type имеется возможность в области доступности объявленного типа данных описать переменную этого типа. Для описания переменной определяемого пользователем типа используются инструкции Dim, Private, Public, ReDim или Static. [Private | Public] Type Имя_типа Имя_составляющей [([Список_диапазонов])] As Тип [Имя_составляющей [([Список_диапазонов])] As Тип] ,,, End Type
     Public - обеспечивает доступность определяемого пользователем типа для всех процедур во всех модулях всех проектов. Действует по умолчанию при объявлении пользовательского типа в стандартном модуле и не влияет при объявлении пользовательского типа в модуле класса (когда доступность ограничена всегда самим этим модулем).
     Private - ограничивает доступность определяемого пользователем типа только тем стандартным модулем, в котором он описан.
     Имя_типа - имя типа, определяемого пользователем, удовлетворяющее правилам именования переменных.
     Имя_составляющей - имя структурной составляющей определяемого пользователем типа. Должно соответствовать правилам именования переменных, за исключением того, что для их задания разрешается использовать ключевые слова.
     Список_диапазонов - указывает границы каждого измерения массива; допускается описание до 60 размерностей: [Нижняя To] Верхняя [, [[Нижняя To] Верхняя]...
     Если нижняя граница массива не задана явно, она определяется инструкцией Option Base, а при отсутствии этой инструкции - равняется нулю.
     Тип - один из поддерживаемых типов данных: Byte, Boolean, Integer, Long, Currency, Single, Double, Date, String, Object, Variant, другой определяемый пользователем тип или объектный тип.
     Примечания
     Не допускается появление номеров строк и меток строк в блоках Type...End Type.
     Пользовательские типы данных используются для описания структуры записей файлов произвольного доступа (см. инструкции Put и Get)
     Пример
Type GroupData
    GroupMember String * 50 
    Marks(15) As Integer
End Type
'Объявляем массив с элементами пользовательского типа:
Dim Group(1 To 25) As GroupData
Group(3).Marks(10) = 4 'Присвоение значения его структурной составляющей.

Инструкции управления ходом вычислений

Call

     Передает управление программной процедуре (подпрограмме или функции), либо процедуре библиотеки динамической компоновки (DLL). Имеет две формы синтаксиса: Call Имя[(Список_аргументов)]
     Имя[Список_аргументов]
     Call - если указано это необязательное ключевое слово, необходимо заключить Список_аргументов в круглые скобки.
     Имя - имя вызываемой процедуры.
     Список_аргументов - разделяемый запятыми список выражений, передаваемых в процедуру. При вызове процедуры из библиотеки DLL перед выражениями из списка могут стоять ключевые слова ByVal или ByRef для описания того, каким образом аргументы будут рассматриваться вызываемой процедурой ("по значению" или "по ссылке" соответственно).
     Примечание. Для передачи в процедуру массива используют его имя с последующими пустыми скобками.

Do ... Loop

     Повторяет выполнение набора инструкций, пока условие имеет значение True, либо пока оно не примет значение True.
     Имеет два варианта синтаксиса:
Do [{While | Until} Условие]
    [Инструкции]
Loop

Do
    [Инструкции]
Loop [{While | Until} Условие]

     Условие - числовое или строковое выражение, которое (будучи преобразовано к типу Boolean) имеет значение True или False. Если условие имеет значение Null,то аргумент условие рассматривается как значение False.
     Инструкции - одна или несколько инструкций, составляющих тело цикла, выполнение которых повторяется, пока (при использовании ключевого слова While) условие имеет значение True или до тех пор (при использовании ключевого слова Until)оно не приобретет значение True.
     Примечание. В любом месте тела цикла Do…Loop может быть размещено любое число инструкций Exit Do, обеспечивающих альтернативные возможности выхода из цикла. Часто используемая вместе с определением некоторого условия (например, If…Then) инструкция Exit Do передает управление инструкции, непосредственно следующей за инструкцией Do…Loop.

End


     Завершает выполнение программы. End
     Может быть помещена в любое место процедуры для закрытия файлов, открытых с помощью инструкции Open, и для очистки переменных.
     Инструкция End мгновенно останавливает выполнение программы, не вызывая события Unload, QueryUnload или Terminate, а также другую программу Visual Basic. Программы, связанные с событиями Unload, QueryUnload и Terminate форм и модулей классов, не выполняются. Созданные из модулей классов объекты уничтожаются, открытые инструкцией Open файлы закрываются, а использованная программой память освобождается. Ссылки на объекты, содержащиеся в других программах, нарушаются.
     При выполнении инструкции End производится сброс всех переменных на уровне модуля и всех статических локальных переменных во всех модулях. Если требуется сохранить значения указанных переменных, следует использовать инструкцию Stop. Затем можно возобновить выполнение с использованием сохраненных значений этих переменных.
     Примечание.
     Инструкция End предоставляет способ вызвать непредусмотренную остановку программы. Для нормального завершения программы Visual Basic необходимо выгрузить все формы. Программа закрывается, как только не станет других программ, содержащих ссылки на объекты, созданные из общих модулей классов, и отсутствует выполнение кода программы.

Exit

     Завершает (принудительно) выполнение инструкций Do…Loop, For...Next, а также процедур.
Exit Do
Exit For
Exit Function
Exit Property
Exit Sub

     Инструкция имеет следующие формы:
     Exit Do - используется только внутри инструкции Do...Loop, передавая управление следующей за ней инструкции.
     Exit For - используется только внутри инструкции For...Next или For Each...Next, передавая управление следующей за ними инструкции.
     Exit Function - немедленно завершает выполнение содержащей ее процедуры-функции. В точку вызова возвращается значение, последним присвоенное в процедуре имени функции, либо, если присвоений не было, возвращаемое значение объявленного типа функции по умолчанию. Если вызов функции не являлся операндом выражения, управление передается на инструкцию, следующую за инструкцией вызова процедуры-функции (Call).
     Exit Property - немедленно завершает выполнение содержащей ее процедуры свойств. Выполнение продолжается с инструкции, следующей за инструкцией вызова процедуры свойств (Call).
     Exit Sub - немедленно завершает выполнение содержащей ее процедуры-подпрограммы. Выполнение продолжается с инструкции, следующей за инструкцией вызова процедуры-подпрограмм (Call).

For Each...In...Next

     Циклическая инструкция. Повторяет выполнение группы инструкций (тела цикла) для каждого элемента массива или семейства (или, иначе говоря, для каждого значения, принимаемого параметром).
For Each Имя_параметра In Имя_группы
    [Инструкции]
Next [Имя_параметра]

     Имя_параметра - переменная, используемая для выполнения итераций по элементам семейства или массива (последовательном принятии значений этих элементов). Для семейств элемент может быть только переменной типа Variant, Object или переменной, представляющей любой конкретный объект. Для массивов элемент может быть только переменной типа Variant.
     Имя_группы - имя семейства объектов или массива (за исключением массивов с определяемыми пользователем типами).
     Инструкции - одна или несколько последовательных инструкций, составляющих тело цикла, которые выполняются на каждой итерации.
     Примечания.
     Инструкция For Each...In...Next выполняется только в том случае, если группа (семейство объектов, либо массив) содержит хотя бы один элемент.
     В любом месте тела цикла For Each...In...Next может быть размещено любое число инструкций Exit For, обеспечивающих альтернативные возможности выхода из цикла. Часто используемая вместе с определением некоторого условия (например, If…Then), инструкция Exit For передает управление инструкции, непосредственно следующей за инструкцией For Each...In...Next.
     Допускается организация вложенных циклов For Each...In...Next (один цикл For Each...In...Next располагается внутри другого), однако параметр каждого цикла должен быть уникальным.

For...Next

     Циклическая "пошаговая" инструкция. Повторяет выполнение группы инструкций (тела цикла).
For Параметр = Начало To Конец [Step Шаг]   
    [Инструкции]
Next [Параметр]

     Параметр - числовая переменная-счетчик, используемая для выполнения итераций (последовательного принятия значений от Начало до Конец с шагом Шаг). Не может принадлежать к типу Boolean или быть элементом массива.
     Начало - выражение, значение которого представляет начальное значение параметра.
     Конец - выражение, значение которого представляет конечное значение параметра.
     Шаг - выражение, значение которого добавляется к параметру при каждом достижении предложения Next после очередного выполнения тела цикла (по умолчанию шаг равен единице).
     Инструкции - одна или несколько последовательных инструкций, составляющих тело цикла, которые выполняются на каждой такой итерации, что значение Параметр не выходит за границы значений Начало и Конец.
     Примечание. Изменять значение переменной Параметр внутри цикла возможно, но крайне нежелательно, т.к. такой цикл уже не будет пошаговым.
     В любом месте тела цикла For…Next может быть размещено любое число инструкций Exit For, обеспечивающих альтернативные возможности выхода из цикла. Часто используемая вместе с определением некоторого условия (например, If…Then), инструкция Exit For передает управление инструкции, непосредственно следующей за инструкцией For…Next.

GoSub...Return

     Задает вызов подпрограммы, представленной расположенным ниже инструкции GoSub и в той же процедуре фрагментом программы, содержащим связанную с GoSub инструкцию Return. GoSub Строка
     Строка - уникальный номер строки, помещаемый в ее начало, либо метка (алфавитно-цифровая последовательность), отмечающие начальную строку представляющего подпрограмму фрагмента (помечающую строку метку завершает двоеточие).
     Первая обнаруженная в подпрограмме инструкция Return приводит к передаче управления назад к инструкции, непосредственно следующей за последней выполненной инструкцией GoSub.
     Примечание. Данная инструкция оставлена в Visual Basic в основном для совместимости с допроцедурными версиями языка.

GoTo

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

If...Then...Else

     Задает выполнение определенных групп инструкций в зависимости от значений выражений.
     Имеет две формы синтаксиса: однострочную:
If Условие Then [Инструкции] [Else Инструкции_для_ложности_"Условия"]
и блоковую:
If Условие Then
    [Инструкции]
[ElseIf Условие_N Then
    [Инструкции_для_"Условия_N"]]...
[Else
    [Инструкции_для_ложности_всех_"Условий"]]
End If
     Условие, Условие_N - имеют 2 варианта:
     1) - логическое выражение, а также числовое или строковое выражения, приводимые к логическому значению. Если Условие имеет значение Null, то оно рассматривается как False.
     2) - TypeOf Имя_Объекта Is ТипОбъекта
     Имя_Объекта - ссылка на объект, а ТипОбъекта - любой допустимый тип объекта. Выражение имеет значение True, если объект Имя_Объекта имеет тип объекта, указанный в аргументе ТипОбъекта; в противном случае выражение имеет значение False.
     Инструкции - в строчной форме: одна или несколько инструкций, разделяемых двоеточиями, обязательны в конструкции без предложения Else; - в блочной форме - одна или несколько инструкций.
     Инструкции выполняются, если Условие имеет значение True.
     Инструкции_для_"Условия_N" - одна или несколько инструкций, выполняемых, если Условия_N ближайшего из предшествующих предложений ElseIf имеет значение True.
     Инструкции_для_ложности_всех_"Условий" - одна или несколько инструкций, которые выполняются, если ни одно из предыдущих выражений Условие или Условие-N не имеет значение True.
     Инструкция If...Then...Else может иметь множество управляющих предложений ElseIf и не более одного Else. Без предложений ElseIf инструкция If...Then...Else реализует обычную структуру ветвления. Каждое предложение ElseIf со своим "Условием_N" эквивалентно помещению в Else-ветвь охватывающей его обычной инструкции If...Then...Else другой обычной инструкции If...Then...Else с тем же "Условием_N".
     Пример
'Проверяем, какая кнопка ("Да" или "Нет") нажата в информационной панели:
If MsgBox("Хочешь съесть пирожок?", vbYesNo) = vbYes Then
    MsgBox "Ешь на здоровье!"
Else
    MsgBox "Ну, как хочешь"
End If

On Error

     Открывает доступ к подпрограмме обработки ошибок, специфицированной как фрагмент программы внутри содержащей инструкцию процедуры.
     On Error GoTo Строка
     On Error Resume Next
     On Error GoTo 0
     On Error GoTo Строка
     Открывает доступ к подпрограмме обработки ошибок при возникновении программной ошибки времени выполнения в нижележащем коде текущей процедуры. Строка - уникальный номер строки, помещаемый в ее начало, либо метка (алфавитно-цифровая последовательность), отмечающие начальную строку представляющего подпрограмму фрагмента (помечающую строку метку завершает двоеточие).
     On Error Resume Next
      - специфицирует передачу управления на инструкцию, следующую за той, при исполнении которой возникла ошибка времени выполнения. Эта форма предпочтительнее предыдущей при работе с объектами.
     On Error GoTo 0
      - запрещает доступ ко всем подпрограммам обработки ошибок для нижележащего кода в пределах текущей процедуры.
     Примечания. Если не использовать данную инструкцию, любая ошибка времени выполнения является фатальной, т.е. прекращает дальнейшее выполнение программы.
     Чтобы предотвратить выполнение фрагмента программы, являющегося подпрограммой обработки ошибок, в том случае, когда при отсутствии ошибок управление на него передается в процессе выполнения инструкций в естественном порядке, используют инструкцию Exit так, как показано в следующем фрагменте:
 
Sub RatherErroneous()
    On Error GoTo ErrorHandler
    ...
    Exit Sub
    ErrorHandler:
        ...
    Resume Next 'Передает управление на инструкцию, следующую за той,
                   'где возникла ошибка
End Sub

On...GoSub, On...GoTo

     Задают переход к одной из нескольких указанных строк (On...GoTo) или вызов подпрограммы, начинающейся в одной из указанных строк текущей процедуры (On...GoSub), в зависимости от значения выражения (о вызове подпрограммы см. инструкцию GoSub...Return). On Выражение GoSub Список_Строк
On Выражение GoTo Список_Строк
     Выражение - числовое выражение, которое имеет целое (либо округленное до целого) значение от 0 до 255 включительно. Его значение используется как порядковый номер элемента в Списке_Строк, определяющего строку передачи управления данной инструкцией.
     Список_Строк - список разделенных запятыми номеров строк или меток (алфавитно-цифровых последовательностей), помечающих строки, куда осуществляются переходы (помечающую строку метку завершает двоеточие).
     Примечания
     Если значение Выражения меньше 1 или превышает число элементов списка, происходит одно из следующих событий:
     - равняется 0 - управление передается инструкции, следующей за On...GoSub или On...GoTo;
     - превышает число элементов списка - управление передается инструкции, следующей за On...GoSub или On...GoTo;
     - меньше 0 или больше 255 - возникает ошибка.
     Данные инструкции обеспечивают совместимость с предыдущими версиями языка Basic. Инструкция Select Case обеспечивает более структурированный и гибкий способ выполнения множественных ветвлений.

Select Case

     Выполняет одну из нескольких групп инструкций (в Case-ветвях) в зависимости от значения выражения.
Select Case Выражение
[Case Список_диапазонов-N-ой_ветви
	[Инструкции_N-ой_ветви]] ...
[Case Else
	[Инструкции_по_умолчанию]]
End Select
     Выражение - числовое или строковое выражение.
     Список_диапазонов-N-ой_ветви - список, состоящий из одной или нескольких, задающих числовые или строковые (в соответствии с установленным лексикографическим порядком) диапазоны элементов (разделенных запятыми) в следующих формах:
     Выражение - диапазон из отдельного значения.
     Выражение1 To Выражение2 - непрерывный диапазон значений от значения Выражение1 (меньшего) до значения Выражение2 (большего).
     Is Оператор_Сравнения Выражение - диапазон значений задается как множество таких значений недостающего левого операнда оператора сравнения (правым выступает Выражение), которые обеспечивают истинность результата операции сравнения. В качестве операторов сравнения не допустимы Is и Like. Если ключевое слово Is не указано, оно вставляется по умолчанию. Инструкции_N-ой_ветви - одна или несколько инструкций, выполняемых в том случае, если значения Выражения совпадает со значением любого элемента Списка_диапазонов-N-ой_ветви.
     Инструкции_по_умолчанию - одна или несколько инструкций, выполняемых в том случае, если значение Выражения не попадает ни в один из диапазонов, заданных в предложениях Case.
     Примечания
     Как только значение Выражения попадает в диапазон из списка в предложении Case, выполняются все инструкции, следующие за данным предложением Case до следующего предложения Case, либо, для последнего предложения Case, - до предложения End Select. Затем управление передается инструкции, следующей за End Select, вне зависимости от наличия подходящих диапазонов в других предложениях Case.
     Если ни в одном предложении Case не содержится включающего значения Выражения диапазона, и отсутствует инструкция Case Else, выполнение продолжается с инструкции, следующей за инструкцией End Select.
     Пример:
For i = 1 To 50
    Select Case i
    Case 1 To 3, 7 To 11, 15, 17, Is > 20
        Beep
    End Select
Next
     Следует отличать оператор сравнения Is от ключевого слова Is, используемого в инструкции Select Case.

Stop


     Приостанавливает выполнение, но, в отличие от инструкции End, не закрывает все файлы и не очищает все переменные. Stop      Использование инструкции Stop в любом месте программы эквивалентно установке в программе точки останова.

While...Wend

     Циклическая инструкция, выполняющая последовательность инструкций, составляющих тело цикла, пока (While) заданное условие имеет значение True.
While Условие
    [Инструкции]
Wend
     Условие - логическое, а также числовое или строковое выражения, которые преобразуются к типу Boolean (если это возможно) и имеют значение True или False. Если условие имеет значение Null, условие рассматривается как имеющее значение False.
     Инструкции - составляющие тело цикла одна или несколько инструкций, выполняемых, пока Условие имеет значение True.
     Если Условие имеет значение True, выполняются все Инструкции до предложения Wend. Затем управление возвращается предложению While и вновь проверяется Условие.
     Если Условие по-прежнему имеет значение True, процесс повторяется. Если оно не имеет значение True, выполнение возобновляется с инструкции, следующей за предложением Wend.

Инструкции преобразований над данными в областях памяти

Erase

     Повторно инициализирует элементы массивов фиксированного размера и освобождает память, отведенную для динамического массива. Erase Список      Список - одно или несколько разделенных запятыми имен очищаемых массивов переменных.
     При инициализации массивов фиксированного размера, их элементы получают следующие значения, в зависимости от их типа:
     Числовой тип - значение 0.
     Строковый, переменной длины - значение пустой строки ("").
     Строковый, фиксированной длины - значение 0.
     Тип Variant - значение Empty.
     Определяемый пользователем тип - каждому элементу переменной присваивается такое значение, которое присвоилось бы при инициализации переменной того же что и элемент типа.
     Тип Object - значение Nothing.

Let (Присваивание)

     Присваивает значение выражения переменной или свойству. [Let] Имя = Выражение      Let - ключевое слово, которое обычно опускают (обеспечивает совместимость со старыми версиями языка Basic).
     Имя - имя переменной или свойства, удовлетворяющее стандартным правилам именования переменных или свойств.
     Выражение - выражение любого типа, значение которого вычисляется, преобразуется (при возможности) к типу переменной (свойства) Имя и копируется в эту переменную (свойство).
     Примечания
     Вопреки утверждению справочной системы, Visual Basic 5.0 не запрещает присваивание строковым переменным числовых значений (происходит преобразование к строке, содержащей десятичную запись числа) и, в допустимых случаях, присваивание числовой переменной строкового значения (оно должно представлять десятичную запись числа с разделителем, указанным в настройке операционной системы). Если же присваиваимое строковое значение содержит нечисловые символы (не используемые в числовых константах), то возникнет ошибка времени выполнения.
     Инструкция Let может быть использована для присвоения значения пользовательского типа данных переменной, имеющей тот же тип; для присвоения же такого значения переменной другого пользовательского типа используется инструкция LSet.
     Для присвоения переменным ссылок на объекты применяется инструкция Set.

Lset

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

Mid, MidB

     Заменяет указанное число символов в переменной типа Variant (String) символами (Mid) или байтами (MidB) из другой строки. Mid(Имя, Выражение_1 [, Выражение_2]) = Выражение_3      Имя - имя строковой переменной, подлежащей изменению.
     Выражение_1 - выражение со значением типа Variant (Long), определяющее позицию символа (байта) в значении переменной Имя, с которой начинается замена.
     Выражение_2 - выражение со значением типа Variant (Long), определяющее число символов в значении переменной Имя, подлежащих замене. Если этот аргумент опущен, используется длина строкового значения Выражения_3. После замены последнего (самого правого) символа (байта) в значении переменной Имя, замена прекращается в любом случае.
     Выражение_3 - выражение, имеющее строковое (или преобразуемое к строковому) значение, используемое для замены части строкового значения переменной Имя.
     Примечание. "Вырезку" части строки осуществляет одноименная функция Mid(Выражение_3, Выражение_1 [, Выражение_2]), которая возвращает значение типа Variant (String), содержащее указанное Выражением _2 число символов строки, заданной Выражением_3, начиная с позиции, заданной Выражением_1.

Randomize

     Randomize [Выражение]
     Инструкция Randomize использует значение числового Выражения, либо, если оно опущено, значение, выдаваемое системным таймером, как опорные значения для инициализации датчика случайных чисел функции Rnd (без аргументов) перед первым ее вызовом.
     Примечание. Если инструкция Randomize не используется, функция Rnd (без аргументов) использует в качестве опорного числа случайное число, возвращенное при предыдущем вызове.

ReDim

     Применяется на уровне процедуры для перераспределения памяти динамическому массиву переменных.
ReDim [Preserve] Имя[([Список_диапазонов])] [As Тип] 
                 [, Имя[([Список_диапазонов])] [As Тип]]...
     Preserve - ключевое слово, используемое для сохранения данных в существующем массиве при изменении размеров (только за счет изменения верхней границы) последнего его измерения (единственного, в случае одномерного массива).
     Имя - имя переменной, удовлетворяющее стандартным правилам именования переменных.
     Список_диапазонов - указывает границы каждого измерения массива; допускается описание до 60 размерностей:
     [Нижняя To] Верхняя [, [[Нижняя To] Верхняя]...
     Если нижняя граница массива не задана явно, она определяется инструкцией Option Base, а при отсутствии этой инструкции - равняется нулю.
     Тип - один из поддерживаемых типов данных: Byte, Boolean, Integer, Long, Currency, Single, Double, Date, String, Object, Variant, а также определяемый пользователем тип. Для каждой описываемой переменной следует использовать отдельную конструкцию As Тип; если она опущена и тип соответствующей переменной не задан инструкцией DefТип, то ее тип - Variant.
     Примечания
     Инструкция ReDim используется для задания или изменения (в том числе и многократного) размера динамического массива, который уже был формально описан с помощью инструкций Private, Public или Dim с пустыми скобками (без границ измерений). При этом не допускается использование инструкции ReDim для последующего изменения типа данных ранее описанного массива, если только массив не содержится в переменной типа Variant и не было использовано ключевое слово Preserve, запрещающее изменение типа данных. Инициализация размещаемых элементов массива производится так же, как для фиксированного массива.
     Если уменьшить размер массива, данные из удаленных элементов будут потеряны.
     При передаче массива в процедуру по ссылке нельзя изменять размеры массива в процедуре.
     Не следует (хотя и возможно) использовать инструкцию ReDim как инструкцию описания, а только для изменения размеров массивов.
     Пример
Dim A(100, 100)
...
ReDim Preserve A(100, 1000) 'Увеличение размера массива без потери данных

Rset

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

Инструкции ввода-вывода данных

Close

     Завершает операции ввода/вывода с файлом, открытым с помощью инструкции Open, разрывая связь между файлом и его программным номером. Close [Cписок_номеров_файлов]      Cписок_номеров_файлов - любой допустимый номер файла:
     [[#]Номер_файла] [, [#]Номер_файла]...
     Примечания
     Если Cписок_номеров_файлов опущен, закрываются все активные файлы, открытые с помощью инструкции Open.
     При закрытии файла, открытого в режиме Output или Append, в него добавляется содержимое последнего буфера вывода. Все буферы, связанные с закрытым файлом, освобождаются.

Get #

     Читает данные из открытого файла произвольного доступа (random-access file) на диске в переменную. Get [#]Номер_файла, [Номер_записи], Имя
     Номер_файла - любой допустимый номер файла.
     Номер_ записи - номер записи (для файлов с произвольным доступом Random) или номер байта (для файлов с организацией доступа Binary), с которого следует начать чтение.
     Имя - допустимое имя переменной, в которую следует поместить считанные данные.
     Данные, считываемые с помощью инструкции Get #, обычно записываются в файл с помощью инструкции Put #.
     Первой записи (или байту) файла соответствует номер 1, второй (или второму) 2 и т.д. Если Номер_записи опущен, читается запись (или байт), на которую был установлен указатель после выполнения последней инструкции Get # или Put # (или переведен при последнем вызове функции Seek). Наличие запятых-разделителей является обязательным, например: Get #4,,FileBuffer
     Файлы с произвольным доступом Random, подчиняются следующим правилам.

=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•

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

     ¤  Если данные считываются в строку переменной длины, инструкция Get сначала считывает 2-байтовый дескриптор, содержащий длину строки, а затем данные, которые следует поместить в эту переменную. Таким образом длина записи, указанная в предложении Len инструкции Open, должна быть по крайней мере на 2 байта больше, чем фактическая длина этой строки.

     ¤  Если данные считываются в переменную типа Variant числового подтипа, инструкция Get сначала считывает 2 байта, указывающие подтип этой переменной, а затем данные, которые следует поместить в эту переменную. Длина записи, указанная в предложении Len инструкции Open, должна по крайней мере на 2 байта превышать фактический размер, необходимый для размещения этой переменной. С помощью инструкции Get можно считать массив типа Variant с диска, однако нельзя прочитать скаляр типа Variant, содержащий массив. Кроме того, инструкцию Get нельзя использовать для чтения объектов с диска.

     ¤  Если данные считываются в переменную типа Variant подтипа String, инструкция Get сначала считывает 2 байта, указывающие подтип, потом 2 байта, указывающие длину строки, а затем содержимое строки. Длина записи, указанная в предложении Len инструкции Open, должна быть по крайней мере на 4 байта больше, чем фактическая длина этой строки.

     ¤  Если данные считываются в динамический массив, инструкция Get сначала считывает дескриптор, длина которого равняется (2 + 8 * Число_измерений) байт. Длина записи, указанная в предложении Len инструкции Open, должна быть больше либо равна сумме всех байтов, необходимых для размещения массива данных и дескриптора массива. Например, для размещения массива
Dim MyArray(1 To 5,1 To 10) As Integer
требуется 118 байт: 18 для дескриптора (2 + 8 * 2) и 100 байт для данных (5 * 10 * 2).

     ¤  Если данные считываются в массив фиксированного размера, инструкция Get считывает только данные. Дескриптор не считывается.

     ¤  Если данные считываются в переменную любого другого типа (кроме строки переменной длины и переменной типа Variant), инструкция Get считывает только данные. Длина записи, указанная в предложении Len инструкции Open, должна быть больше либо равна длине данных, подлежащих считыванию.

     ¤  В элементы определяемых пользователем типов считывание производится так, как будто это отдельные переменные, причем пространство между элементами не заполняется текущим содержимым буфера файла. На диске динамический массив типа, определенного пользователем, записанный с помощью инструкции Put, предваряется дескриптором, длина которого равняется (2 + 8 * Число_измерений) байт. Длина записи, указанная в предложении Len инструкции Open, должна быть больше либо равна сумме всех байтов, необходимых для размещения отдельных элементов, в том числе, массивов и их дескрипторов.

     ¤  Особенностью файлов, открытых в режиме Binary, является то, что предложение Len инструкции Open игнорируется.
     Инструкция Get считывает все переменные с диска непрерывно, т.е. без заполнения пространства между записями текущим содержимым буфера файла, причем при чтении любых массивов, кроме являющихся элементами типов, определяемых пользователем, считываются только данные (без дескриптора).
     При считывании строк переменной длины, не являющихся элементами типов, определяемых пользователем, 2-байтовый дескриптор не считывается. Число считываемых байтов равняется числу символов, уже содержащихся в строке. Например, следующие инструкции считают 10 байт из файла, которому соответствует номер 1:
VarString = String(10," ")
Get #1,,VarString
     Пример
     В данном примере инструкция Get используется для чтения данных из файла в переменную. Предполагается, что файл TESTFILE содержит пять записей определенного пользователем типа Record.
Type Record ' Тип, определенный пользователем.
    ID As Integer
    Name As String * 20
End Type

Dim MyRecord As Record, Position 'Объявляет переменную
' Открывает файл произвольного доступа:
Open "TESTFILE" For Random As #1 Len = Len(MyRecord)
' Читает из файла с помощью инструкции Get:
Position = 3 'Определяет номер записи
Get #1, Position, MyRecord 'Читает третью запись
Close #1 'Закрывает файл

Input #

     Читает данные из открытого файла последовательного доступа и присваивает их переменным. Input #Номер_файла, Список_переменных      Номер_файла - любой допустимый номер файла.
     Список_переменных - разделяемый запятыми список переменных, которым следует присвоить значения, считанные из файла. Нельзя использовать массивы или объектные переменные. Однако допускается использование переменных, описывающих элементы массива или определяемого пользователем типа.
     Данные, считываемые с помощью инструкции Input #, обычно записываются в файл с помощью инструкции Write #. Эта инструкция применима только к файлам, открытым в режиме Input или Binary.
     После считывания стандартные строки и числовые данные присваиваются переменным без изменения. В следующей таблице показано, как обрабатываются остальные данные: запятая Empty пустая строка Empty #NULL# Null #TRUE# или #FALSE# True или False #yyyy-mm-dd hh:mm:ss# Дата и/или время #ERROR Код_ошибки# Код_ошибки типа Variant (Error)      Прямые кавычки (" ") внутри считываемых данных игнорируются.
     Элементы данных в файле должны располагаться в том же порядке, что и переменные в Список_переменных, и иметь соответствующие переменным типы данных. Если переменная является числовой, а элемент данных нечисловым, переменной присваивается нулевое значение.
     При достижении конца файла во время считывания элемента данных ввод прекращается и возникает ошибка.
     Примечание. Чтобы иметь возможность корректно считывать данные из файла в переменные с помощью инструкции Input #, следует всегда использовать инструкцию Write # (а не Print #) для записи данных в файлы. Использование инструкции Write # гарантирует правильность размещения разделителей между отдельными элементами данных.
     Пример
     В данном примере инструкция Input # используется для чтения данных из файла в две переменные. Предположим, что текстовый файл TESTFILE существует и содержит несколько строк текста, записанных с помощью инструкции Write #, т.е. каждая строка содержит заключенную в кавычки строку и отделенное от нее запятой число, например ("Привет", 234).
Dim MyString, MyNumber
Open "TESTFILE" For Input As #1 'Открывает файл для чтения
Do While Not EOF(1) 'Цикл до конца файла
    Input #1, MyString, MyNumber 'Читает данные в две переменные
    Debug.Print MyString, MyNumber 'Выводит данные в окно отладки
Loop
Close #1	'Закрывает файл

Line Input #

     Читает строку из открытого последовательного файла и присваивает ее переменной типа String. Line Input #Номер_файла, Имя_переменной      Номер_файла - любой допустимый номер файла.
     Имя_переменной - опустимое имя переменной типа Variant или String.
     Инструкция Line Input # последовательно считывает из файла по одному символу до тех пор, пока не встретит символ возврата каретки (Chr(13)) или комбинацию символов возврата каретки и перевода строки (Chr(13) + Chr(10)). Когда считанная строка присваивается переменной, символы возврата каретки и конца строки отбрасываются.
     Данные, считываемые с помощью инструкции Line Input #, обычно записываются в файл с помощью инструкции Print #.

Open

     Открывает доступ к файлу для операций ввода/вывода. Open Имя_файла For Организация_доступа [Access Разрешенные_операции] [Уровень_разделяемости] As [#]Номер_Файла [Len = Размер]      Имя_файла - строковое выражение, указывающее имя файла, возможно с путем к нему.
     Организация_доступа - ключевое слово, указывающее вид организации доступа к файлу: Append (последовательная запись вслед за существующим содержимым), Binary (байтовая интерпретация при чтении), Input (последовательное чтение), Output (последовательная запись) или Random (чтение и запись в произвольном порядке). По умолчанию, файл открывается для доступа в режиме Random.
     Разрешенные_операции - ключевое слово, указывающее операции, разрешенные с открытым файлом: Read, Write или Read-Write (по умолчанию).
     Уровень_разделяемости - ключевое слово, указывающее операции, разрешенные с открытым файлом другим процессам: Shared (совместное использование несколькими процессами), Lock Read (разрешена совместная запись, запрещено чтение), Lock Write (разрешено совместное чтение, запрещена запись) и Lock Read Write (запрещено совместное использование).
     Номер_Файла - допустимый номер файла в интервале от 1 до 511 включительно. Для определения следующего свободного номера файла следует использовать функцию FreeFile.
     Размер - число, меньшее либо равное 32 767 (байт). Для файлов, открытых в режиме Random, является длиной записи. Для файлов с последовательным доступом является числом буферизуемых символов.
     Чтобы получить возможность выполнить любую операцию ввода/вывода, файл необходимо открыть. Инструкция Open резервирует буфер ввода/вывода для файла и определяет режим использования этого буфера.
     Примечания
     Если Имя_файла описывает несуществующий файл, такой файл будет создан при открытии в режиме Append, Binary, Output или Random.
     Если файл уже открыт другим процессом и указанные Разрешенные_операции не разрешены, инструкция Open не будет выполнена и возникнет ошибка.
     Если Организация_доступа имеет значение Binary, предложение Len игнорируется.
     В режимах Binary, Input и Random можно еще раз открыть уже открытый файл под другим номером, не закрывая его. В режиме Append и Output необходимо закрыть файл, чтобы получить возможность открыть его еще раз под другим номером.

Print #

     Записывает отформатированные данные в файл с последовательным доступом. Print #Номер_файла, [Список_выражений]      Номер_файла - любой допустимый номер файла.
     Список_выражений - выражение или список выражений, которые следует напечатать. Разделителями в списке могут быть пробел или точка с запятой. Если после последнего элемента списка стоит точка с запятой, следующее выполнение инструкции Print # начинает вывод не в новую строку, а со следующей позиции текущей.
     Кроме числовых и строковых выражений, элементами списка могут быть вызовы функций Spc(n) и Tab(n):
     Spc(n) - используется для вставки n пробелов в файл;
     Tab(n) - устанавливает курсор в столбец с указанным номером n; Tab без аргумента устанавливает курсор в начало следующей зоны печати (а в промежуток пишутся пробелы).
     Примечания
     Данные, записанные с помощью инструкции Print #, могут считываться из файла с помощью инструкций Line Input #, либо Input # (не рекомендуется).
     Если Список_выражений опущен, в файл печатается пустая строка.
     При выводе логических данных (тип Boolean) в файл записываются слова True или False.
     При выводе данных типа Date используется текущий краткий системный формат даты. Если компонент, описывающий дату либо время, отсутствует или равен нулю, в файл записывается только имеющийся в наличии компонент.
     Если выводимое данное имеет значение Null, в файл записывается слово Null.
     Данные типа Error записываются в файл как Error Код_ошибки.
     Разделитель целой и дробной части числа выбирается с учетом национальной настройки системы.
     Для записи в файл данных, который в будущем планируется читать с помощью инструкции Input #, следует вместо инструкции Print # использовать инструкцию Write #, гарантирующую корректное разделение данных при любых национальных настройках.

Put

     Записывает содержимое переменной в файл на диске. Put [#]Номер_файла, [Номер_записи], Имя      Номер_файла - любой допустимый номер файла.
     Номер_ записи - номер записи (для файлов с произвольным доступом Random) или номер байта (для файлов с организацией доступа Binary), с которого следует начать запись.
     Имя - допустимое имя переменной, содержащей данные, которые записываются в файл.
     Данные, записываемые в файл с помощью инструкции Put #, обычно считываются из файла с помощью инструкции Get #.
     Первой записи (или байту) файла соответствует номер 1, второй (или второму) 2 и т.д. Если Номер_записи опущен, читается запись (или байт), на которую был установлен указатель после выполнения последней инструкции Get # или Put # (или переведен при последнем вызове функции Seek). Наличие запятых-разделителей является обязательным, например:
Put  #4,,FileBuffer
     Файлы с произвольным доступом Random, подчиняются следующим правилам.
=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•=•

     ¤  Даже если длина данных, подлежащих записи, меньше длины записи, указанной в предложении Len инструкции Open, инструкция Put записывает запись длины, указанной в Len. Пространство между концом одной записи и началом следующей записи заполняется текущим содержимым буфера файла. Поскольку объем данных, используемых для заполнения, не может быть определен с достаточной степенью уверенности, рекомендуется обеспечивать совпадение длины записываемых данных с длиной записи в предложении Len.

     ¤  При записи строки переменной длины инструкция Put сначала записывает 2-байтовый дескриптор, содержащий длину строки, а затем данные, которые следует поместить в эту переменную. Таким образом длина записи, указанная в предложении Len инструкции Open, должна быть по крайней мере на 2 байта больше, чем фактическая длина этой строки.

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

     ¤  Если данные записываются из переменной типа Variant подтипа String, инструкция Put сначала записывает 2 байта, указывающие подтип, потом 2 байта, указывающие длину строки, а затем содержимое строки. Длина записи, указанная в предложении Len инструкции Open, должна быть по крайней мере на 4 байта больше, чем фактическая длина этой строки.

     ¤  При записи динамического массива инструкция Put сначала записывает дескриптор, длина которого равняется (2 + 8 * Число_измерений) байт. Длина записи, указанная в предложении Len инструкции Open, должна быть больше либо равна сумме всех байтов, необходимых для размещения массива данных и дескриптора массива. Например, для размещения массива
Dim MyArray(1 To 5,1 To 10) As Integer
требуется 118 байт: 18 для дескриптора (2 + 8 * 2) и 100 байт для данных (5 * 10 * 2).

     ¤  При записи массива фиксированного размера инструкция Put записывает только данные. Дескриптор не считывается.

     ¤  При записи переменной любого другого типа (кроме строки переменной длины и переменной типа Variant), инструкция Put записывает только данные. Длина записи, указанная в предложении Len инструкции Open, должна быть больше либо равна длине данных, подлежащих записи.

     ¤  Запись данных определяемых пользователем типов производится так, как будто это отдельные переменные, причем пространство между элементами не заполняется текущим содержимым буфера файла. На диске динамический массив типа, определенного пользователем, записанный с помощью инструкции Put, предваряется дескриптором, длина которого равняется (2 + 8 * Число_измерений) байт. Длина записи, указанная в предложении Len инструкции Open, должна быть больше либо равна сумме всех байтов, необходимых для размещения отдельных элементов, в том числе, массивов и их дескрипторов.

     ¤  Особенностью файлов, открытых в режиме Binary, является то, что предложение Len инструкции Open игнорируется. Инструкция Put записывает все переменные на диск непрерывно, т.е. без заполнения пространства между записями текущим содержимым буфера файла, причем при записи любых массивов, кроме являющихся элементами типов, определяемых пользователем, записываются только данные (без дескриптора). При записи строк переменной длины, не являющихся элементами типов, определяемых пользователем, 2-байтовый дескриптор не записывается. Число записываемых байтов равняется числу символов, уже содержащихся в строке. Например, следующие инструкции считают 10 байт из файла, которому соответствует номер 1:
VarString = String(10," ")
Get #1,,VarString
     Пример
     В данном примере инструкция Put используется для записи данных в файл. В файл записываются пять записей определенного пользователем типа Record.
Type Record 'Тип, определенный пользователем
    ID As Integer
    Name As String * 20
End Type

Dim MyRecord As Record, RecordNumber 'Объявляет переменную
'Открывает файл произвольного доступа:
Open "TESTFILE" For Random As #1 Len = Len(MyRecord)
For RecordNumber = 1 To 5 '5 итераций:
    MyRecord.ID = RecordNumber 'Определяет номер
    MyRecord.Name = "My Name" & RecordNumber 'Создает строку
    Put #1, RecordNumber, MyRecord 'Записывает запись в файл
Next RecordNumber
Close #1 'Закрывает файл

ReSet

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

SavePicture

     Оператор сохраняет в файл графическое изображение из свойства Picture или Image объекта или элемента управления. SavePicture Имя_элемента, Имя_файла      Имя_элемента - имя элемента управления Picture или Image, из которого импортируется графическое изображение.
     Имя_файла - имя сохраняемого графичекого файла.
     Примечание. Если графическое изображение, являвшееся значением свойства Picture объекта во время разработки или во время выполнения, имеет формат растра, значка, метафайла или расширенного метафайла, то при записи в файл его формат не изменится, а если оно имело формат GIF или JPEG, - то будет сохранено в виде растрового файла. Если же графическое изображение являлось значением свойства Image, то оно независимо от исходного формата записывается в виде растрового файла (.bmp).

Seek

     Задает положение указателя для следующей операции чтения/записи внутри файла, открытого с помощью инструкции Open. Seek [#]Номер_файла, Позиция
     Номер_файла - любой допустимый номер файла.
     Позиция - число в диапазоне 1 ? 2,147,483,647 указывающее позицию, начиная с которой будет выполнена следующая операция чтения или записи.
     Примечания.
     При выполнении инструкций Get и Put, содержащих конкретные номера записей, позиция указателя, установленная с помощью инструкции Seek, игнорируется. Выполнение операции записи в файл после установки указателя с помощью инструкции Seek за конец файла приводит к увеличению размера файла. Установка указателя с помощью инструкции Seek в отрицательное или нулевое положение приведет к возникновению ошибки.

Width #

     Задает ширину строки для файла, открытого с помощью инструкции Open. Width #Номер_файла, Ширина
     Номер_файла - любой допустимый номер файла.
     Ширина - числовое выражение в интервале 0 ? 255 включительно, которое указывает, сколько символов следует поместить на одной строке прежде чем перейти к следующей. Если Ширина равняется 0 (это значение используется по умолчанию), то ширина строки не ограничивается.

Write #

     Записывает неформатированные данные в файл с последовательным доступом. Write #Номер_файла, [Список_выражений]      Номер_файла - любой допустимый номер файла.
     Список_выражений - одно или несколько разделяемых запятыми, точками с запятой или пробелами числовых или строковых выражений, которые следует записать в файл. Если Список_выражений опущен, то в файл записывается пустая строка.
     Примечания
     В отличие от инструкции Print #, инструкция Write # вставляет запятые между элементами и заключает строки в кавычки по мере записи их в файл. Разработчику не требуется включать разделители в список явным образом. Write # вставляет символ новой строки, т.е. комбинацию символов возврата каретки и перевода строки (Chr(13) + Chr(10)), после записи в файл последнего символа, включенного в Список_выражений.
     Данные, записанные с помощью инструкции Write #, обычно считываются из файла с помощью инструкции Input #. Они могут быть корректно прочитаны с помощью инструкции Input # при любой национальной настройк, если:
     - в качестве десятичного разделителя при записи числовых данных всегда используется точка;
     - при выводе логических данных (тип Boolean) в файл записываются слова #TRUE# или #FALSE#;
     - при выводе в файл данных типа Date используется универсальный формат даты. Если компонент, соответствующий дате или времени, отсутствует или равен нулю, в файл записывается только имеющийся в наличии компонент;
     - если элемент Списка_выражений имеет значение Empty, в файл ничего не записывается;
     - если элемент Списка_выражений имеет значение Null, в файл записывается #NULL#;
     - данные типа Error записываются в файл как #ERROR Код_ошибки#.

ChDir

     Изменяет текущий каталог или папку. ChDir Каталог      Каталог - строковое выражение, специфицирующее каталог (папку), который станет текущим (на текущем либо ином диске). Может содержать имя диска. Если диск не указан, инструкция ChDir изменяет текущий каталог на текущем диске.

ChDrive

     Изменяет текущий диск. ChDrive Диск      Диск - строковое выражение, указывающее существующий диск. Если задается пустой строкой (""), текущий диск не изменится. Если диск является строкой, состоящей из нескольких символов, инструкция ChDrive использует только первый символ.

FileCopy

     Копирует файл (не открытый инструкцией Open). FileCopy Источник, Цель      Источник - строковое выражение, специфицирующее копируемый файл.
     Цель - строковое выражение, специфицирующее результирующий файл.

Kill

     Удаляет файлы с диска (кроме открытых). Kill Имя      Имя - строковое выражение, специфицирующее один или несколько файлов, подлежащих удалению. Для указания нескольких файлов поддерживает групповые имена файлов (с подстановочными знаками для нескольких символов (*) и для одиночного символа (?)).
     Примечание. Для удаления каталогов следует использовать инструкцию RmDir.

MkDir

     Создает новый каталог или папку. MkDir Каталог      Каталог - строковое выражение, специфицирующее создаваемый каталог (папку). Если в спецификации не указан диск, создается новый каталог на текущем диске.

Name

     Изменяет имя файла или каталога (папки), а также местоположение файла. Name Старое_имя As Новое_имя      Старое_имя - строковое выражение, указывающее спецификацию существующего файла (каталога).
     Новое_имя - строковое выражение с новой спецификацией файла (каталога); в родительском (относительно местоположения вновь специфицированного файла (каталога)) каталоге не должно перед выполнением инструкции существовать файла (каталога) с тем же именем.
     Примечания
     Изменение местоположения файла возможно только в пределах одного диска.
     Местоположение каталога менять данной инструкцией нельзя.
     При попытке переименовать открытый файл с помощью инструкции ошибка.
     В спецификациях файлов (каталогов) не допускается использование подстановочных знаков (*) и (?).

RmDir

     Удаляет существующий пустой каталог (папку). RmDir Каталог      Каталог - строковое выражение специфицирующее каталог, который следует удалить. Если в спецификации диск не указан, инструкция удаляет каталог с текущего диска.
     Примечание. При попытке удалить с помощью инструкции RmDir каталог, содержащий файлы, возникает ошибка. Для предварительного удаления всех файлов из каталога следует использовать инструкцию Kill.

SetAttr

     Задает атрибуты файла (в том числе каталога). SetAttr Имя,Выражение      Имя - строковое выражение, специфицирующее файл.
     Выражение - преобразуемое к числовому значению выражение, задающее атрибуты файла.
     При вводе инструкции SetAttr в программу, после ввода разделительной запятой выполняется функция ListConstants, выдающая список именованных констант, по именам которых можно понять, какой из атрибутов файла они "включают": vbAlias, vbArchive, vbHidden, vbNormal, vbReadOnly, vbSystem, vbVolume. Для "включения" нескольких атрибутов используют операцию суммирования, например:
SetAttr "c:\sys", vbArchive + vbHidden + vbReadOnly
     Примечание. При попытке изменения атрибутов открытого файла возникает ошибка выполнения. Не нужно также пытаться сделать меткой тома файл или каталог.

Инструкции работы с процессами

AppActivate

     Активизирует окно приложения. AppActivate Заголовок[, Условие]      Заголовок - строковое выражение со значением строки заголовка окна приложения, которое следует активизировать. Допускается использование также кода задачи, возвращаемого функцией Shell.
     Условие - выражение типа Boolean, указывающее, должно ли вызывающее приложение иметь фокус при активизации другого приложения. При значении False (по умолчанию), активизируемое приложение активизируется независимо от наличия фокуса у вызывающего приложения; при значении True активизируемое приложение активизируется только после получения фокуса вызывающим приложением.
     Инструкция AppActivate передает фокус активизируемому приложению (окну), не изменяя его размеры. Активизированное окно приложения может потерять фокус в результате соответствующих действий пользователя или закрытия этого окна. Для запуска приложения и указания типа окна следует использовать функцию Shell.
     При определении приложения, подлежащего активизации, Заголовок последовательно сравнивается со строкой заголовка каждого приложения, находящегося в памяти компьютера. При отсутствии точного совпадения активизируется приложение, строка заголовка которого начинается со строки Заголовок. Если в памяти компьютера находятся несколько экземпляров приложения, строка заголовка которого совпадает со строкой Заголовок, активизируется произвольно выбранный экземпляр.
     Примеры
Dim MyAppID, ReturnValue
AppActivate "Microsoft Word" 'Активизирует Microsoft Word

'Кроме того, можно использовать значение, возвращаемое функцией Shell
MyAppID = Shell("C:\WORD\WINWORD.EXE", 1) 'Запускает Microsoft Word
AppActivate MyAppID 'Активизирует Microsoft Word

'Кроме того, можно использовать значение, возвращаемое функцией Shell
ReturnValue = Shell("c:\EXCEL\EXCEL.EXE", 1) 'Запускает Microsoft Excel
AppActivate ReturnValue 'Активизирует Microsoft Excel

Lock и Unlock

     Управляют доступом других процессов ко всему файлу, открытому с помощью инструкции Open, или к его части. Lock [#]Номер_файла[, Диапазон]
...
Unlock [#]Номер_файла[, Диапазон]
     Номер_файла - любой допустимый номер файла.
     Диапазон - диапазон (в записях, либо в байтах - для режима Binary) структурных единиц файла, которые следует заблокировать или разблокировать (при последовательном доступе не используется). Номер_записи | [Начало] To Конец      Номер_записи - номер записи или номер байта (режим Binary), с которого следует начать установку или снятие блокировки.
     Начало - номер первой записи (или байта), которую следует заблокировать или разблокировать.
     Конец - номер последней записи (или байта), которую следует заблокировать или разблокировать.
     Примечания
     Инструкции Lock и Unlock используются в средах, в которых нескольким процессам может понадобиться доступ к одному и тому же файлу.
     Инструкции Lock и Unlock всегда используются парами, применяемыми к одной и той же области файла. Несоблюдение этого правила может привести к непредсказуемым последствиям.
     Если файл открыт для последовательного ввода или вывода, инструкции Lock и Unlock применяются ко всему файлу независимо от указания диапазона.
     Если файл открыт для произвольного доступа, то первой записи (или байту) файла соответствует номер 1, второй - 2 и т.д. Если указана только одна запись, то блокируется или разблокируется только одна эта запись. Если диапазон записей указан без Начала, блокируются или разблокируются все записи от первой записи до конца диапазона. Если Диапазон не указан, инструкции применяются ко всему файлу.

Resume

     Возобновляет выполнение процедуры после окончания работы подпрограммы-обработчика ошибок (оформленной как фрагмент данной процедуры). Resum [0]
Resume Next
Resume Строка
     Resume - возобновляет выполнение с инструкции, вызвавшей ошибку, если ошибка возникла в той же процедуре, где располагается подпрограмма-обработчик. Если ошибка возникла в вызванной процедуре, выполнение возобновляется с инструкции, которой был выполнен последний вызов в процедуре, содержащей подпрограмму-обработчик ошибок.
     Resume Next - передает управления на инструкцию, следующую за той, при исполнении которой возникла ошибка времени выполнения. Если ошибка возникла в вызванной процедуре, выполнение возобновляется с инструкции, следующей за той, которой был выполнен последний вызов в процедуре, содержащей подпрограмму-обработчик ошибок (или инструкцию On Error Resume Next ).
     Строка - уникальный номер строки, помещаемый в ее начало, либо метка (алфавитно-цифровая последовательность), отмечающие начальную строку представляющего подпрограмму-обработчик фрагмента (помечающую строку метку завершает двоеточие). Выполнение возобновляется со строки, специфицированной меткой или номером и находящейся в той же самой процедуре, что и подпрограмма-обработчик.
     Примечание. Если вы попытаетесь использовать предложение Resume где-нибудь еще, кроме подпрограммы-обработчика, возникнет ошибка.

Инструкции системных установок

Date

     Устанавливает текущую системную дату. Date = Дата      Дата - выражение типа Date либо строковое, приводимое к Date. Для компьютеров, работающих под управлением Microsoft Windows 95, аргумент дата должен задавать дату в интервале от 1 января 1980 года до 31 декабря 2099 года. Для компьютеров, работающих под управлением Microsoft Windows NT, дата в интервале от 1 января 1980 года до 31 декабря 2079 года.
     Замечание. Обратите внимание на учет национальных особенностей при преобразовании строковой величины в тип Date:
Dim t As String
Date = #12/2/98# 'Устанавливает дату 2 декабря 1998 года
 a = "12/2/98"
Date = a 'Устанавливает дату 12 февраля 1998 года

DeleteSetting

     Удаляет раздел или запись из раздела приложения в реестрe Windows. Если указанный раздел или запись не существует, инструкция не имеет последствий. DeleteSetting Имя_приложения, Имя_раздела[, Имя_записи]      Имя_приложения - строковое выражение, представляющее имя приложения или проекта, к которому относится раздел или запись.
     Имя_раздела - строковое выражение, содержащее имя раздела, в котором удаляется запись. Если определены только аргументы Имя_приложения и Имя_раздела, указанный раздел удаляется полностью со всеми записями.
     Имя_записи - строковое выражение, содержащее имя удаляемой записи.
     Пример
     В данном примере сначала с помощью инструкции SaveSetting заполняются элементы реестра Windows для приложения MyApp, а затем инструкция DeleteSetting вызывается для их удаления. Поскольку аргумент Имя_записи не определен, удаляется весь раздел, в том числе имя раздела и все содержащиеся в нем записи.
'Заполнение реестра:
SaveSetting "MyApp", "Startup", "Left", 50 
'Удаление из реестра раздела со всем содержимым
DeleteSetting "MyApp", "Startup" 

SaveSetting

     Сохраняет или создает раздел или запись в разделе приложения в реестре Windows. SaveSetting Имя_приложения, Имя_раздела, Имя_записи, Значение      Имя_приложения - строковое выражение, представляющее имя приложения или проекта, к которому относится сохраняемый раздел или запись.
     Имя_раздела - строковое выражение, содержащее имя раздела, в котором сохраняется запись.
     Имя_записи - строковое выражение, содержащее имя сохраняемой записи.
     Значение - выражение, содержащее значение записи, указанной в аргументе Имя_записи. Если значение по какой-либо причине сохранить не удается, регистрируется ошибка.

Time

     Устанавливает системное время. Time = Время      Время - числовое или строковое выражение, которые могут быть интерпретированы как временная компонента в величине типа Date.
     Пример
Time = #9:28:17 PM# 'Изменяет системное время

Другие инструкции (методы-аналоги)

     В использованную классификацию не вошла инструкция Beep, вызывающая однократный звуковой сигнал. Не вошли также и некоторые "обязательные" инструкции вывода данных или графических объектов, входящие в традиционные версии языка (Print, Pset, Line и т.д.), но уже по другой причине: таких инструкций в Visual Basic нет, но есть соответствующие методы тех или иных объектов. Чаще всего обращаются к этим методам без указания содержащего их объекта, так как подразумевается вывод в находящуюся фокусе стандартную область отображения (экранную форму, например). Вот их краткое описание:

     Цвет выдачи этими методами определяется свойствами отображающего контейнера; программно мы можем указывать цвета не шестнадцатиричными константами, а той же что и в обычных версиях языка цветовой схемой с номерами от 0 (черный) до 15 (насыщенный белый), передавая их в качестве аргумента преобразующей функции QBColor. Единственное важное различие: мы можем регулировать толщину линий и точек, задавая значение свойства DrawWidth контейнера.
     Организацию работы с графикой иллюстрирует следующий пример программы "Новогоднее конфетти".
Sub Form_Load()
    Dim CX, CY, Msg, XPos, YPos ' Объявим переменные
    ScaleMode = 3   ' Установим свойство ScaleMode на пикселы
    DrawWidth = 5   ' Задаем "толщину" точки в пикселях
    ForeColor = QBColor(4)  ' Зададим красный фон
    FontSize = 24   ' Установим размер шрифта
    CX = ScaleWidth / 2 ' Определим горизонтальное положение центра
    CY = ScaleHeight / 2    ' Определим вертикальное положение центра
    Cls ' Очистим форму
    Msg = "Happy New Year!"
    CurrentX = CX - TextWidth(Msg) / 2
    'Горизонтальное положение
    CurrentY = CY - TextHeight(Msg) ' Вертикальное положение
    Print Msg   ' Напечатаем сообщение
    Do
        XPos = Rnd * ScaleWidth ' Определим горизонтальное положение
        YPos = Rnd * ScaleHeight    ' Определим вертикальное положение
        PSet (XPos, YPos), QBColor(Rnd * 15)
        'DoEvents передает управление операционной системе
        'Если часть программы занимает основное процессорное время,
        'следует периодически использовать функцию DoEvents для отказа
        'от управления в пользу операционной системы, чтобы такие
        'события как ввод с клавиатуры и нажатия кнопок мыши могли быть
        'обработаны без существенной задержки
        DoEvents 'Без этого вызова мы никогда не закроем приложение
    Loop
End Sub
     Из всех указанных в таблице методов мы рассмотрим достаточно подробно только метод Print, относящийся к объекту Debug и выводящий элементы списка печати в стандартную область отображения (контейнеры Form, UserDocument, UserControl, PropertyPage). (Для графических методов стандартной областью отображения является также элемент PictureBox.)

Print

Объект.Print [Список_Печати]      Объект - объектное выражение, значением которого объект Debug.
     Список_Печати - выражение или список печатающихся выражений. Если этот аргумент опущен, выводится пустая строка.
     Для каждого аргумента, входящего в Список_Печати используется следующий синтаксис и элементы:
     Spc(n) | Tab(n) Выражение Позиция
     Spc(n) - указывает количество пробелов n, вставляемых в выводящийся текст.
     Tab(n) - помещает курсор в экранный столбец с номером n. Элемент Tab без аргумента помещает курсор в первый столбец следующей зоны печати.
     Выражение - выводимое выражение.
     Позиция - указывает позицию, в которой выводится следующий символ. Двоеточие (;) помещает курсор непосредственно за последним выведенным символом. Tab(n) помещает курсор в экранный столбец с номером n. Элемент Tab без аргумента помещает курсор в первый столбец следующей зоны печати. Если аргумент позиция опущен, следующий символ выводится в начале новой строки.
     Примечания
     Для печати нескольких выражений следует разделять их точкой с запятой или пробелом.
     Все данные отображаются в окне отладки с учетом национальной настройки. Используется соответствующее форматирование (с указанным символом десятичного разделителя), а ключевые слова выводятся на языке, используемом в главном приложении. Логические (тип Boolean) значения печатаются как True или False. Ключевые слова True и False отображаются в соответствии с языком головного приложения.
     Значения типа Date выводятся в стандартном кратком формате дат, установленном в системе. Если в значениях даты/времени опущен или равен нулю один из компонентов, выводится оставшийся компонент.
     Ничего не выводится, если аргумент Список_Печати имеет значение Empty; если же он имеет значение Null, печатается слово Null (в переводе, определяемом настройками). Значение Error выдается в виде: ERROR Код_ошибки (слово ERROR также в переводе, определяемом настройками).

IV. КЛАВИШИ БЫСТРОГО ВЫЗОВА