This content was uploaded by our users and we assume good faith they have the permission to share this book. If you own the copyright to this book and it is wrongfully on our website, we offer a simple DMCA procedure to remove your content from our site. Start by pressing the button below!
Моим родителям, живущим далеко-далеко на Юге: папе — другу и собеседнику — и маме, чья нелепая душа и во времена тяжелых испытаний по-прежнему полна любви. —Джон Кларк Крейг
Моей законной супруге. Триш, подарившей мне двоих чудесных ребятишек, канделябр, вонючую медвежью шкуру и много радостных минут. — Джефф Уэбб
John Clark Craig and Jeff Webb
Microsoft®
Visual Basic n
6 •w Workshop
Fifth Edition
Microsoft Press
Джон Кларк Крейг и Джефф Уэбб
Microsoft®
Visual Basic АЛ Мастерская W •W разработчика
Издание пятое
Москва 2001 г.
N. Р У
УДК 004-43 ББК 32.973.26-018 К85
Крейг Дж. К. и Уэбб Дж.
К85
Microsoft Visual Basic 6.0. Мастерская разработчика/Пер, с англ. — 5-е изд. — М.: Издательско-торговый дом «Русская Редакция», 2001. — 720 с.: ил. ISBN 5-7502-0127-9 Книга состоит из 3 частей (34 главы) и предметного указателя. Написанная живо и доходчиво, она позволит освоить множество полезных приемов программирования, в том числе объектно-ориентированного, и научит, как создавать 32-разрядные приложения для Windows 95/98 и Windows NT — от экранных заставок до программ, ориентированных на Интернет. Кроме того, Вы узнаете, как расширить возможности языка за счет функций Win32 API и воспользоваться преимуществами технологии ActiveX. Книга предназначена всем программистам, которые имеют базовые знания по элементам языка Visual Basic, желают изучить Visual Basic версии 6.0 и стремятся повысить квалификацию. Она в полной мере оправдывает свое название — это действительно «мастерская разработчика». УДК 004.43 ББК 32.973.26-018
Подготовлено к печати издательско-торговым домом «Русская Редакция» по лицензионному договору с Microsoft Corporation, Редмонд, Вашингтон, США. ActiveX, JScript ; MSDM, Visual Basic, Visual C++, Visual FoxPro, Visual J++, Visual SourceSafe, Visual Studio, Windows, Windows NT и Word являются либо товарными знаками, либо охраняемыми товарными знаками Microsoft Corporation. Все другие товарные знаки являются собственностью соответствующих фирм.
Выпуски Visual Basic 6 Интегрированная среда разработки Настоящий компилятор ActiveX Новинки, связанные с Интернетом Новые и модифицированные элементы управления Средства объектно-ориентированного программирования Изменения в языке Доступ к данным Интернет
Г Л А В А 2 Стиль программирования Присвоение описательных имен Префиксы для элементов управления Имена переменных Объявление переменных Меню Имена классов Контроль типов данных Ограничение диапазона действия элементов программы Комментарии Дополнительные источники информации
Ч А С Т Ь
3 4 7 8 8 9 11 11 12 13
14 14 15 17 19 20 20 20 21 22 22
II
ДОРОГОЙ ДЖОН, КАК?
23
Г Л А В А 3 Переменные
25
Эмулировать беззнаковые целые? Использование переменных типа Long Упаковка беззнаковых байтовых значений с применением структур Работать с Булевыми значениями? Использовать байтовые массивы? Передача байтовых массивов вместо строк Обмен данными между строками и байтовыми массивами Работать с датами и временем? Применение элементов управления DTPicker и Calendar Запись в переменные типа Date
25 26
26 28 29 30 33 33 33 35
VI
ОГЛАВЛЕНИЕ
Отображение даты и времени Выборка компонентов Вычисления с использованием дат и времени Проверка допустимости даты и времени Работать с переменными типа Variant? Циклы For Each Гибкий тип для передачи параметров Функции, связанные с типом Variant Empty и Null -. Приведение типов данных Работать со строками? Замена символов в строке Разбиение и объединение строк Применение фильтров Поиск строк Работать с объектами? Новые объекты Существующие объекты Операции над объектами Уничтожение объектов Работать с предопределенными константами? Константы компилятора Константы Visual Basic Пользовательские константы Перечислимые типы Флаги и битовые маски Создавать UDT-структуры? Выравнивание Создавать новые типы данных с помощью классов? Создание нового типа данных Использование нового типа данных
Использовать именованные аргументы? Использовать необязательные параметры? Передавать параметры-массивы? Передавать в параметре произвольный тип данных? Использовать в параметрах перечислимые типы?
54 55 56 56 57
Г Л А В А 5 Объектно-ориентированное программирование
60
Выбрать между ЕХЕ- и DLL-сервером ActiveX? Разместить все свои объекты во внешних ActiveX-компонентах? Создать новый объект? Пример модуля класса: Loan Использовать мой новый объект? Назначить объекту свойство, используемое по умолчанию? Создать и использовать ЕХЕ-сервер ActiveX? Пример ЕХЕ-сервера ActiveX: компонент Chance DICE.CLS Тестирование ActiveX-компонента, реализованного как ЕХЕ-сервер Создать объект, способный отображать формы? Event, WithEvents и RaiseEvent .
60 61 61 62 68 71 71 72 72
74 75 .. 78
ОГЛАВЛЕНИЕ
VII
Работать с наборами объектов? Пример набора: SolarSys Класс Star Класс Planets Класс Planet Класс Moons Класс Moon Как работают вложенные наборы Использовать полиморфизм? Применять дружественные методы?
Г Л А В А
6
ActiveX-элементы
78 79 81 81 84 84 85 85 88 89
,
90
Создать ActiveX-элемент? 90 Этапы разработки ActiveX-элемента 90 Создание проекта ActiveX-элемента 91 Конструирование интерфейса 91 Изменение размеров элемента управления 92 Добавление свойств, методов и событий 93 Программирование функциональности элемента управления 95 Отладить элемент управления? 97 Откомпилировать и зарегистрировать элемент управления? 99 Создать свойство, доступное на этапе разработки? 101 Вывести диалоговое окно Property Pages? 104 Загрузить свойство асинхронно? 108 Создать элемент управления для использования с базой данных? .. 110 Использовать элемент управления DataRepeater? 114 Создать контейнерный элемент управления? 116
Г Л А В А
7
Использование компонентов для Интернета Выбрать подходящий ActiveX-компонент? Представить уровни интернет-протоколов? Настроить доступ к сети? Обеспечить связь через Winsock? Вещание по протоколу UDP Беседа «один-на-один» по протоколу TCP Создать FTP-браузер? Перехват ошибок Управлять браузером Internet Explorer?
Г Л А В А
8
Создание компонентов для Интернета Создавать ActiveX-элементы, рассчитанные на применение в Интернете? Использовать ActiveX-элементы с VBScript? Создавать DHTML-документы? Создавать ActiveX-документы? Создавать Web-классы?
Г Л А В А
9
Создание приложений для Интернета
Выбрать тип приложения? Создать DHTML-приложение? Получение ввода в DHTML-приложениях Отображение результатов в DHTML-приложениях
119
119 120 121 123 124 127 131 132 135
138 139 140 142 145 147
149
149 151 152 153
VIII
ОГЛАВЛЕНИЕ Создать US-приложение? Отображение HTML-шаблонов Получение ввода в US-приложениях Отображение результатов в US-приложениях Создать приложение на базе ActiveX-документа? Получение ввода в ActiveX-документах Отображение результатов в ActiveX-документах Устанавливать ActiveX-документы по Интернету? Устанавливать DHTML-приложения по Интернету? Распространять US-приложения по Интернету?
Г Л А В А API-функции
156 158 161 162 164 165 167 168 172 172
10
Вызывать API-функции? Объявления Объявление 32-разрядных функций Строки : Передать адрес процедуры в API-функцию? Интерпретировать ByVal, ByRef и As Any в объявлении API-функции? Упростить вставку объявлений API-функций? Получить информацию о системе через API-функции? Определение версии операционной системы с применением элемента управления Syslnfo Определение системных цветов Определение типа процессора Определение времени работы системы Определение типов дисков Вызывать API-функции из кода ActiveX-элемента? Добавление объявлений API-функций в ActiveX-элемент Расширение существующего элемента управления
Г Л А В А 11 Мультимедиа Воспроизвести WAV-файл? Функция mciExecute Элемент управления Multimedia Воспроизвести AVI-файл? Функция mciExecute Элемент управления Multimedia Проигрывать звуковые компакт-диски?
Г Л А В А 12 Окна, диалоговые окна и прочие формы Добавить стандартное диалоговое окно About? Шаблон формы для диалогового окна About Автоматически позиционировать форму на экране? Создать «плавающее» окно? Модальный режим Динамический режим Режим «поверх остальных» Создать экран-заставку? Шаблон формы для экрана-заставки Использовать элементы TabStrip и SSTab? Элемент управления SSTab
Заставить форму мигать, чтобы привлечь внимание пользователя? Переместить элемент управления в новый контейнер? Г Л А В А 13
Визуальный интерфейс
Использовать «облегченные» элементы управления? Добавить горизонтальную полосу прокрутки в окно списка? Создать панель инструментов? Динамически модифицировать форму? Динамически настраивать меню? Удалить строку заголовка с формы? Создать индикатор прогресса? Разработка собственного индикатора прогресса Использовать элемент управления Slider? Использовать элемент управления UpDown? Использовать элемент управления FlatScrollBar? Использовать элемент управления CoolBar? Г Л А В А
14
Графические методы
Вычислить «цветовую" константу по RGB-, HSVили HSL-значениям? Преобразовывать единицы измерения? Создать фон с плавным переходом из синего в черный? Выделять прямоугольные области «резиновым» контуром? Создавать «горячие» участки на графических изображениях? Быстро нарисовать многоугольник? Нарисовать эллипс? Закрасить произвольный замкнутый участок? Поворачивать растровое изображение? Прокручивать графические изображения? Использовать BitBIt для создания анимации? Использовать для анимации объекты Picture? Работать с элементом управления Animation? Точно позиционировать текст в окне рисунка? Произвольно масштабировать шрифты? Повернуть текст на произвольный угол? Вывести текст несколькими шрифтами в одном окне рисунка? Г Л А В А 15
Файловый ввод/вывод
Эффективно переименовать, скопировать или удалить файл? Работать с каталогами и путями? MkDir, ChDir и RmDir CurDir и App.Path Dir Ускорить файловый ввод/вывод? Работать с двоичными файлами? UDT-структуры Строки Байтовые массивы Взаимосвязь строк и байтовых массивов Использовать o6beKTTileSystemObject? Работа с дисками
Считывать и записывать данные в реестре? Сохранить состояние приложения? Сопоставить с приложением определенный тип файлов? Извлечение аргументов из командной строки
Г Л А В А 17 Справочные системы, подсказки и мастера Добавить подсказки по интерфейсным объектам? Добавить строку состояния в программу? Выводить при запуске «Совет дня»? Использовать мастера? Создать справочный файл в формате WinHelp? Базовые принципы подготовки исходного текста Создание файла проекта Компиляция и тестирование справочного файла Связывание идентификаторов Полнотекстовый поиск и оглавления Создание окон справочной системы Использование макросов Подключить справочный файл к проекту через API-функцию WinHelp? Подключить к проекту контекстно-зависимую справку? Подключить справочный файл к проекту через элемент управления CommonDialog? Включить режим WhatsThisHelp? Метод WhatsThisMode Создать справочную систему в формате HTML Help? Преобразование файлов проектов из формата WinHelp В формат HTML Help Использование элемента управления HTML Help
Г Л А В А Защита
18
Г Л А В А Мышь
19
Включить в программу скрытое окно с именами авторов? Создать диалоговое окно Password? Зашифровать пароль или другой текст? Класс Cipher Объект Cipher в действии Защита данных в реестре Работать со средствами защиты в Интернете?
Изменить курсор мыши? Создать нестандартный курсор мыши? Вывести на экран курсор с анимацией? Определить координаты курсора мыши?
Определить состояние клавиш-модификаторов? Создать "Горячие» клавиши?
XI
345 346
Г Л А В А 21 Программирование элементов TextBox и RichTextBox . . . 348 Отобразить содержимое файла? 348 Создать простой текстовый редактор? 350 Обнаружить изменение текста? 352 Уместить в элементе управления TextBox более 64 Кб текста? 353 Выбрать шрифт для TextBox или RichTextBox в период выполнения? .. 356
Г Л А В А 22 Многодокументный интерфейс
Создать MDI-приложение? MDI-форма Дочерние формы Свойства ActiveForm и ActiveControl Ключевое слово Me Свойство Tag Фундаментальные особенности MDI-формы Добавить эмблему на MDI-форму?
Г Л А В А 23 Доступ к базам данных
Использовать мастера при разработке баз данных? Подключить приложение к базе данных через элемент управления Data? Создание базы данных с помощью Visual Data Manager Создание пользовательского интерфейса Запуск приложения Подключить приложение к базе данных через DAO? ОАО Создание базы данных через ОАО Доступ к базе данных Создать отчет?
358
.'.. ,358 358 358 359 359 359 360 360
363 363
365 365 367 368 36S 368 370 371 374
Г Л А В А 24 ActiveX-объекты из других приложений
375
Г Л А В А 25 Экранные заставки
385
Использовать ActiveX, чтобы проверить правописание? Проверка правописания средствами Microsoft Word Проверка правописания средствами Microsoft Excel Раннее и позднее связывание Применить ActiveX для подсчета слов? Получить доступ к математическим функциям Microsoft Excel?
375 375 379 381 382 383
Создать экранную заставку? 385 Избежать одновременного запуска двух экземпляров заставки? . . . . 389 Погасить курсор мыши при запуске заставки? 390 Обнаружить перемещение мыши, чтобы завершить экранную заставку? 392 Обнаружить нажатие клавиши? 393 Использовать в заставке изображение с дисплея? 393 Дополнить заставку поддержкой паролей и настройки параметров? . . 398
XII
ОГЛАВЛЕНИЕ
Г Л А В А 26 Разработка проектов Захватить изображение выполняемой формы и сохранить его в файле? Вставка графики в редактор Paint Команды Save As и Сору То Использовать файлы ресурсов? Создание файла ресурсов Использование файла ресурсов в приложении В каких случаях применяются файлы ресурсов Использовать строковую базу данных для «интернационализации» приложения?
Г Л А В А 27 Изощренные приемы программирования Создать ActiveX DLL на Visual Basic? Объект Fraction Тестирование в среде разработки Создание и использование конечного DLL-модуля Создать DLL наС? Два С-файла Тестирование DLL Создать приложение, выполняемое удаленно? Создание удаленного приложения Регистрация удаленного приложения Выполнение удаленного приложения Доступ к удаленному приложению Возврат ошибок из удаленных приложений Отладка удаленных приложений Выявление и устранение проблем при использовании удаленной автоматизации Создать надстройку для среды разработки Visual Basic? Базовые концепции Разработка надстройки Первый запуск надстройки Использование надстройки Ввести в приложение поддержку сценариев Элемент управления Script Добавление объектов и макропроцедур Обработка ошибок периода компиляции Выполнение макросов Обработка ошибок периода выполнения Передача строк в методы Создание макросов Передать объекту UDT-структуру?
Г Л А В А 28 Другие приемы
Создать связанный список? Определить "разрядность» операционной системы? Перезапустить Windows? Набрать телефонный номер из программы? Использовать строчный перехват ошибок? Строчный перехват ошибок
Г Л А В А 31 Дата и время Приложение VBCal Это — мастер! Элемент управления DTPicker Элемент управления MonthView Элемент управления Calendar VBCALWIZ.BAS Приложение VBCIock VBCLOCK.FRM Использование реестра VBCLOCK2.FRM ABOUT2,FRM Приложение NISTTime Как работает NISTTime NISTTIME.FRM .
Приложение AreaCode AREACODE.FRM Приложение DataDump DATADUMP.FRM Тестирование приложения DataDump Приложение Jot «Тиражирование- дочерней формы «Горячие» клавиши в MDI-форме Создание базы данных Центрирование изображения на MDI-форме JOT.FRM Панели инструментов, кнопки и подсказки SPLASH.FRM NOTE.FRM Тестирование приложения Jot
Г Л А В А 34 Сложные приложения Приложение Messages Синтаксис файла сообщений Для чего нужны файлы сообщений? MESSAGES.FRM MSG.CLS MSG.FRM Приложение Secret Как работает приложение Secret? SECRET.FRM VIEW.FRM CIPHER.CLS Приложение BitPack Генерация таблицы простых чисел (решето Эратосфена) Создание файлов проекта BitPack.DLL BITPACK.FRM Приложение Dialogs Особенности приложения Dialogs Состав приложения Dialogs DIALOGS.FRM DLGEGG.FRM
БЛАГОДАРНОСТИ .icrosoft Visual Basic 6.0 — очередной шаг вперед в традиционном для Microsoft развитии Бейсика. Всего несколько лет назад многие (и кто знает его досконально, и кто лишь думает, что знает его) сходились во мнении, что Бейсик — «игрушечный» язык для написания коротких программ на скорую руку, мало что значащий в разработке коммерческих или промышленных приложений. Так вот, пусть теперь кто-нибудь только посмеет сказать, что современный Visual Basic подпадает под эту категорию! Мне очень приятно сообщить, что Visual Basic 6.0 отвечает целям и запросам самых взыскательных и требовательных разработчиков современных приложений. Это отличный инструмент для создания ActiveX-компонентов и полноценных автономных 32-разрядных Windows-приложений. Не требуя длительного обучения, Visual Basic 6.0 — с его проверенной временем высокопродуктивной средой программирования и способностью создавать быстрые, эффективные приложения и компоненты — может все! Я признателен всем сотрудникам Microsoft Press, помогавшим мне в работе над книгой. Некоторые из них сыграли исключительно важную роль и заслуживают особой благодарности. Научный редактор Марк Янг (Marc Young) был, как всегда, на высоте. Отлично поработала и Памела Хейфи (Pamela Hafey), выпускающий редактор. А Эрик Стру (Eric Stroo) — с его чутьем и умением руководить — вновь не дал нам сбиться с курса. Это второе издание в соавторстве с Джеффом Уэббом (Jeff Webb). Как и в прошлый раз, мне было по-настоящему приятно работать с ним — содержание этой книги значительно улучшилось благодаря его таланту, мастерству и знаниям. Вряд ли бы у меня получилась такая книга без Джеффа, быстро схватывающего и доходчиво объясняющего многие новые и сложные вещи. Двоих читателей следует упомянуть особо, их вклад в улучшение нескольких программ просто неоценим. Дэннис Борг (Dennis Borg) предоставил исходный код для преобразований, связанных с цветовой моделью HSL (Hue, Saturation, Luminosity — оттенок, насыщенность, яркость), и помог в отладке моих программ. Спасибо, Дэннис! Дилан Уоллес (Dylan Wallace) долго общался со мной по электронной почте, поясняя, как усовершенствовать экранные заставки и, в частности, режим их предварительного просмотра. Спасибо, Дилан! Оба они — настоящие эксперты в своем деле, и мне было интересно поучиться у них.
ВВЕДЕНИЕ О это издание мы добавили много новой информации по Интернету, программированию баз данных и разработке ActiveX-элементов. Кроме того, учтя пожелания читателей, мы переработали большую часть материалов из предыдущего издания. Как и в прошлый раз, купив эту книгу, Вы получаете двух авторов по цене одного. (Джефф Уэбб уже написал несколько прекрасных книг по тематике, связанной с Visual Basic). Вдвоем нам удается не отставать от постоянных изменений в мире программирования на Visual Basic. Теперь это такой многоцелевой программный продукт, что только коллективный труд позволяет должным образом освещать его перспективы и охватывать важнейшую тематику. За время совместных исследований мы многому научились и надеемся, что Вы найдете на этих страницах массу интересной информации о программировании на Visual Basic. В самом начале мы постарались четко, без всякой рекламной шумихи описать новые возможности Visual Basic 6. Вы сами оцените, насколько эффективнее сможете реализовать свои проекты его средствами. В этой версии действительно много изменений, и, чтобы ознакомиться с ними, прочтите (или хотя бы пролистайте) главу 1 «Что нового в Visual Basic 6->. Ссылки на Microsoft Windows 95 и Microsoft Windows NT, встречающиеся в этой книге, относятся и к Microsoft Windows 98. Любые программы, написанные на Visual Basic для Windows 95, как правило, работают и в Windows 98-
Стиль программирования В книгу включена глава по стандартному стилю программирования — эти сведения пришлось добывать по крохам у экспертов Microsoft и из других источников. Мы не предлагаем слишком уж жестко придерживаться изложенных в ней правил — там, где пунктуально выполняют все правила, обычно пропадает творческая атмосфера и вообще скучно, — просто собранная нами информация поможет группам разработчиков лучше понимать друг друга и обмениваться фрагментами кода. Исходные тексты всех программ, написанных нами, близки к этому стилю.
Что не вошло в это издание В него не вошли вводные материалы по истории Бейсика и часть информации об использовании Visual Basic, рассчитанная исключительно на начинающих. Так мы расчистили место для материа-
ВВЕДЕНИЕ
XVII
лов поинтереснее! Сведений вводного характера хватает в других книгах по программированию на Бейсике. Мы же обнаружили, что наши читатели достаточно знакомы с Visual Basic и стремятся найти новую информацию, чтобы повысить свою квалификацию.
«Как сделать то-то и то-то» Выступая на конференциях и семинарах, мы заметили, что большинство вопросов сводится по сути к одному: «Как сделать то-то и то-то». Программисты на Visual Basic могут теперь использовать массу полезных, но не всегда хорошо документированных трюков и приемов; кроме того, Visual Basic 6 зачастую позволяет решать те же задачи гораздо эффективнее предыдущих версий. Поэтому мы подобрали для части II «Дорогой Джон, как?..» новые материалы. Кстати, после долгих споров мы решили — простоты ради оставить вопросы в той же форме, что и в предыдущих изданиях, т. е. в виде «Дорогой Джон...», но заметьте; во многих случаях «Дорогой Джефф...» было бы точнее. От всяких «Дорогой Дж. и Дж...», «Уважаемые Джефф и Джон...» и «Эй, вы, парни!,.» мы отказались. И по той же причине употребляем в основном тексте книги местоимение «я» вместо «мы» — даже несмотря на то, что нас двое.
Программы-примеры Содержание компакт-диска, прилагаемого к книге, изменилось и расширилось. В него включен не только программный код из всех глав, но и сама книга в электронном виде. Visual Basic — в высшей степени многообразная среда разработки, и мы старались приводить примеры, которые не только бы иллюстрировали основные области ее применения, но и были бы полезны. В итоге мы ограничили круг рассматриваемых тем и глубину их изложения. Скажем, вполне работоспособный пример программы, управляющей базой данных, относительно несложен, потому что весь набор встроенных в Visual Basic средств программирования баз данных охватить в одной книге попросту немыслимо. Здесь Вы изучите фундаментальные основы программирования баз данных на Visual Basic, но, если Ваши интересы сосредоточены именно в этой области, Вы, вероятно, захотите прочитать и какую-нибудь другую книгу, целиком посвященную данному предмету.
Программирование для 32-разрядных версий Windows Visual Basic 4 поставляли в 16- и 32-разрядных версиях, что позволяло использовать его в операционных системах Windows 3.1, Windows 95 и Windows NT. В Visual Basic 6 нет 16-разрядной версии. Поэтому основное внимание мы уделяем программированию на Visual Basic 6 для 32-разрядных версий Windows.
XVIII
ВВЕДЕНИЕ
Книга небесполезна и в том случае, если Вы создаете программы для Windows NT. Конечно, отдельные вызовы API-функций системного уровня потребуют определенной модификации, но большинство программ-примеров будет работать в Windows NT без всякой переделки.
Компакт-диск, прилагаемый к книге Этот диск содержит все файлы с исходным кодом, проекты, формы и прочие файлы программ, представленных в части П и Ш. Этот код можно откомпилировать и скомпоновать в исполняемые файлы, включить в собственные приложения или просто посмотреть, как он работает. Вы можете копировать файлы иди загружать проекты прямо с компакт-диска. ЕСЛИ Вы предпочтете сначала скопировать файлы проектов на жесткий диск, то легко найдете нужное — мы создали структуру каталогов, имена которых соответствуют номерам глав. Кроме того, на компакт-диске находится полная электронная версия нашей книги, которую можно читать на экране, упражняясь с примерами. Она подготовлена в формате HTML, снабжена средствами поиска и позволяет быстро находить нужную информацию (процедуры, определения и т. д.) едва ли не одним щелчком мыши. Подробнее об использовании компакт-диска, прилагаемого к -книге, см, файл КЕАГШЕ.ТХТ.
Ч А С Т Ь
I
ПРИСТУПАЯ К РАБОТЕ С VISUAL BASIC
Изданный в 1963 году, Бейсик был первым языком, который позволил программисту сосредоточиться на методах и алгоритмах решения своих задач, не отвлекаясь на алгоритмы управления аппаратными средствами компьютеров. Microsoft Visual Basic далеко ушел в своем развитии от предка, но положенная в его основу философия осталась прежней. Программирование для Microsoft Windows на С или C++ известно своей сложностью, но тех же результатов программисты могут добиться с помощью Visual Basic, затратив на обучение гораздо меньше времени. Visual Basic -- передовая и высокоэффективная система разработки приложений для Windows, требующая минимум средств и усилий. В прошлом одним из самых крупных недостатков Бейсика было низкое быстродействие написанных на нем программ. Но сегодня созданные на Visual Basic приложения и компоненты можно компилировать с помощью оптимизирующего компилятора, ядро которого идентично применяемому в языке программирования Microsoft С. И пусть больше никто не говорит, что Visual Basic — «игрушечный» язык!
Г Л А В А
Что нового в Visual Basic 6 11осле запуска Visual Basic 6 на экране появляется диалоговое окно New Project (создать проект), показанное на рис. 1-1. Как видите, кроме «Standard EXE», Visual Basic позволяет создавать множество типов компонентов, включая ActiveX DLL, элементы управления на базе ActiveX (будем называть их для краткости ActiveX-элементами) и ActiveX-документы, применяемые в интрасетях и Интернете. 'Microsoft•":
visual Basic
ActiveX EXE
Data Piciec! 115 Application
Arties XL
ActiveX Control
Adcin
ActiveX DocurrentDl
VE Application Wizard
Рис. 1-1 Диалоговое окно New Project в Visual Basic 6 В этой главе я перечислю все новинки и изменения, превратившие Visual Basic в очень мощный и простой в использовании инструмент. Ко многим из них мы с Вами еще не раз вернемся. Хотя в этой и последующих главах я постараюсь охватить максимально широкую тематику, по каким-то темам может понадобиться более подробная информация. В таком случае советую обратиться к документации Visual Basic, заглянуть в Visual Basic Books Online и на Web-узел корпорации Microsoft (http://microsoft.com').
Выпуски Visual Basic 6 Сейчас Visual Basic поставляется в трех 32-разрядных версиях, или выпусках, — каждый последующий является расширением предыду-
ЧАСТЬ I ПРИСТУПАЯ К РАБОТЕ С VISUAL BASIC
щего. Во все выпуски входит комплект Visual Basic Books Online — полная электронная документация по Visual Basic в мультимедийном формате, основанном на HTML. Visual Basic Learning Edition позволяет легко создавать мощные 32-разрядные приложения для Microsoft Windows 95, Microsoft Windows 98 и Microsoft Windows NT. В него включены все встроенные элементы управления, а также «сетка» (grid control), «набор вкладок» (tab control) и элементы управления, связанные с доступом к данным (data-bound controls) . Visual Basic Professional Edition содержит все средства Learning Edition плюс ActiveX-элементы, в том числе предназначенные для работы в Интернете. Поставляется дополнительная документация: Component Tools Guide и Data Access Guide. Visual Basic Enterprise Edition включает те же средства, что и Visual Basic Professional Edition, плюс Automation Manager, Component Manager, средства управления базами данных, проектно-ориентированная версия Microsoft Visual SourceSafe и др.
Интегрированная среда разработки Интегрированная среда разработки Visual Basic с каждой версией становится все лучше и лучше. Мне очень нравится возможность выбора между режимом SDI (Single Document Interface — однодокументный интерфейс) и MDI (Multiple Document Interface —• многодокументный интерфейс), а также загрузки сразу нескольких проектов как единой группы. Режим SDI знаком тем, кто работал с Visual Basic еще до версии 5. В нем окна открываются независимо друг от друга (рис. 1-2); при этом между окнами Visual Basic виден и рабочий стол, и окна ранее открытых приложений. Режим MDI, устанавливаемый теперь по умолчанию (рис. 1-3), делает среду разработки очень похожей на Microsoft Word, Microsoft Excel или Microsoft Access. В этом режиме появляется одно большое окно для всего приложения, а в нем — несколько дочерних, ограниченных рамками основного окна. Поначалу режим MDI кажется непривычным, но поработайте в нем, и он наверняка понравится Вам больше, чем SD1. Object Browser (рис. 1-4) — мощный инструмент среды программирования Visual Basic. Вы можете быстро переходить из Object Browser в модули и процедуры своего проекта. Благодаря встроенным средствам поиска, секции описания и ряду других особенностей Object Browser находить и изучать компоненты, которые включены (или могут быть включены) в проект, намного легче.
Прим. перев.: Для краткости мы будем использовать в дальнейшем термин «элементы управления, связанные с данными».
SST. planEtOtoject - . JuJd["Eacth" Kith planetObJcct .Diameter - 1275Й .Haas - 5.9736E+21 With .Itoons Sat nraonCtjjec; - .Add|"L Hich raoonObiecc .Eisunecer - 3476 .Иазз - T.SSE+ii End With End W i t h End ¥ich c«,
Set planetcfcjcct
Рис. 1-3
Интегрированная среда разработки в MDI-режиме
Code Editor (редактор исходного текста) поддерживает несколько по-настоящему интересных возможностей. При наборе текста на экране возникают списки, которые ускоряют ввод ключевых слов, помогают выбирать свойства или методы, доступные для данного элемента управления или объекта, и т. д. Примеры таких средств — Auto List Members (список компонентов) (рис. 1-5) и Auto Quick Info (краткие сведения) (рис. 1-6).
ЧАСТЬ I
ПРИСТУПАЯ К РАБОТЕ С VISUAL BASIC
* Add «Count «Delete
'•л Пет
& Planet
1
Й mcolPlanets NewEnum
о.*: SolarSys Л! Star *rjwale£la££ Planets MT,!;T ПГ -
Рис. 1-4
Усовершенствованный Object Browser
.Diameter = 12756 .Haas - S.9^Э6E^•21 ' Aila вмял to с h i s ]!!ал-я Tilth .Hoons Set noonOtajEct - . Add("Li. 1 1 1 ' Sc- к- -n-i' at :.".af ru ! ;-.»?•(; i Uitii ra
Рис. 1-5
-Sow так работает средство Auto List Members
Print , .Mane, .Diameter, .Яазз End Mich Next moonObject Nest planetObject PclM Feme "The E a r t h ' s moon has a diameter of "; Pcint atacCteject.Planets ("Earth") . Hoons (' Print "lars has"; Print starCtoJect.Planets("Hars"|.noons.Count;
Рис. 1-6 А так — средство Auto Quick Info К числу других изменений можно отнести более совершенные отладочные окна и панели инструментов, стыкуемые окна, улучшенную цветовую палитру, индикаторы текстовых полей, помогающие при отладке кода (с их помощью можно, например, вставлять точки прерывания), вызов из меню справочной информации с интер-
ГЛАВА 1
Что нового в Visual Basic 6
нет-сервера Microsoft и возможность закомментировать или раскомментировать все строки в выделенном блоке. Последнее, помоему, очень полезно при разработке программы — обратите внимание на кнопки Comment Block (закомментировать блок) и Uncomment Block (раскомментировать блок) на панели инструментов Edit (правка). Кроме того, предлагаются утилиты, помогающие разрабатывать приложения. Например, в Visual Basic Enterprise Edition включена новая утилита Application Performance Explorer (APE), написанная на Visual Basic. Она удобна при разработке, планировании развертывания и настройке распределенных клиент-серверных приложений. АРЕ позволяет прогонять автоматизированные тесты типа «а что, если?» и исследовать производительность многозвенного приложения в сетях с разной топологией; при этом учитываются такие факторы, как пропускная способность сети, частота запросов, требования к передаче данных, дисковая емкость сервера и т. д. Исходный текст этой программы можно использовать как учебное пособие и модифицировать под свои задачи.
Настоящий компилятор Компилятор, генерирующий машинный («родной») код, — одно из важнейших средств Visual Basic. Почему оно так важно? Ну, отчасти потому, что придает Visual Basic респектабельность в глазах тех, кто не хотел работать с ним, считая его игрушкой, но в большей мере потому, что позволяет делать на Visual Basic вещи, прежде немыслимые. Например, до недавнего времени Visual Basic не годился для программирования игр с высокоскоростной анимацией, преобразования трехмерных изображений, обработки освещенности объектов и т. д. Р|«ИО» - Project Properties
Параметры компилятора настраиваются на вкладке Compile диалогового окна Project Properties (свойства проекта), открываемого одноименной командой из меню Project (рис. 1-7). Все приложения, написанные на Visual Basic, после компиляции работают быстрее, хотя наибольший выигрыш от нового компилятора получают программы, требующие интенсивных числовых расчетов, обработки трехмерных изображений и др. Компилятор в Visual Basic построен по той же технологии, что и в Visual C++, и при желании откомпилированный код можно отлаживать в среде Visual C++.
ActiveX ActiveX — ученое словечко, относящееся к технологиям, которые раньше связывали с термином OLE. Microsoft назвала так технологии, основанные на COM (Component Object Model — модель компонентных объектов). Теперь, работая в Visual Basic, можно создавать на базе ActiveX элементы управления, документы и DLL, а также программы, предоставляющие ActiveX-объекты другим приложениям, и т. д. Даже сам Visual Basic и его среда разработки состоят из компонентов, построенных на базе ActiveX. С технологией ActiveX Вы еще не раз встретитесь и в этой книге, и практически везде, где пишут о Visual Basic. Она лежит в основе всех компонентов Visual Basic — как встроенных, так и создаваемых Вами.
Новинки, связанные с Интернетом Очевидно, технологии Интернета и интрасетей занимают сейчас особое место в политике и долгосрочных целях Microsoft. Толчком к использованию Visual Basic для Интернета и интрасетей послужило то, что еще до выпуска окончательной версии Visual Basic на Web-узле Microsoft появился бесплатно распространяемый специальный выпуск Visual Basic — Control Creation Edition (ССЕ). Эта урезанная версия Visual Basic позволяла создавать элементы управления ActiveX, предназначенные для тесного взаимодействия с Microsoft Internet Explorer. В Visual Basic 6 вошли все возможности ССЕ плюс многое другое. С помощью Visual Basic — на основе существующих браузеров — можно создавать мощные приложения для Интернета, не ограниченные рамками стандартных HTML-документов. Visual Basic позволяет Вам формировать собственные ActiveX-документы. Они создаются почти так же, как и другие формы Visual Basic, но базируются на таких контейнерах ActiveX-документов, как Internet Explorer. Эти документы поддерживают гиперссылки, подстановку меню (menu negotiation) в браузер, расширение справочного меню и другие, весьма мощные средства. Но, пожалуй, самое главное — это возможность создания полноценных приложений, работающих в Интернете или интрасети.
ГЛАВД1
Что нового в Visual Basic 6
Новые и модифицированные элементы управления Элементы управления — неотъемлемая часть Visual Basic. Как и в прежних версиях Visual Basic, совершенствование языка идет по пути разработки новых элементов управления и улучшения уже имеющихся. ActiveX-элементы — одна из самых «горячих» концепций в Visual Basic. Вы можете скомбинировать несколько существующих элементов или разработать свои и использовать их локально или в Интернете. В ближайшие годы Вы еще не раз услышите об ActiveX-элементах и их применении в Интернете. В Visual Basic 6 появилось несколько новых ActiveX-элементов. Но сначала я расскажу о тех, которые поставляются со всеми его выпусками. и ADO (ActiveX Data Objects) Data обеспечивает более гибкое и эффективное подключение к любому источнику данных и требует минимального объема дополнительного кода. Кроме того, он позволяет «на лету» модифицировать источники данных. и DataGrid, функционально аналогичный старому элементу управления DBGrid, поддерживает Unicode, а также упрощает просмотр и модификацию наборов данных. 1 DataList и DataCombo — усовершенствованные версии элементов управления DBList и DBCombo — обеспечивают динамическое переключение источников данных. ш Hierarchical FlexGrid создан на базе прежнего элемента FlexGrid и дает возможность отображать иерархические наборы записей, сформированные из нескольких (разных) таблиц. * ImageCombo — аналог элемента ComboBox с тем отличием, что позволяет добавлять в список изображения. * ImageList теперь поддерживает графические файлы в формате GIF. А сейчас я перечислю новые и усовершенствованные элементы управления, поставляемые только с профессиональным выпуском Visual Basic 6 и выпуском для предприятий. я CoolBar позволяет создавать новомодные панели инструментов, настраиваемые пользователем (Вы увидите их в среде Visual Basic). ш DTPicker — раскрывающийся календарь для выбора и ввода дат и времени. и DataRepeater — совершенно новый способ структуризации информации, отображаемой из.источников данных. Он «несет на себе* ряд элементов управления, связанных с данными, — в том числе TextBox и CheckBox — и позволяет представлять базы данных по аналогии с формами Access. я FlatScrollBar предоставляет новый тип экранных объектов плоскую полосу прокрутки.
10
ЧАСТЬ I
ПРИСТУПАЯ К РАБОТЕ С VISUAL BASIC
» ListView теперь позволяет добавлять подчиненные элементы в свой новый набор ListSubltems. • MonthView формирует лист календаря на один месяц и дает возможность пользователю выбирать как отдельные даты, так и их диапазоны. • MSChart теперь поддерживает прямую связь с источником данных. » ProgressBar усовершенствован для поддержки плавной прокрутки, а также размещения как в горизонтальном, так и в вертикальном положении. № Script — новый мощный элемент управления, дополняющий Ваши приложения средствами написания сценариев, доступными конечным пользователям. Поддерживаются VBScript, Microsoft JScript или определенные Вами функции. и Slider теперь поддерживает всплывающие подсказки (ToolTips) и связанные окна. s TabStrip дополнен рядом новых возможносгей, в том числе вставки элементов управления, подсветки, поддержки плоских кнопок и др. я В Tree View введена поддержка флажков, выделения строк и др. У многих существующих элементов управления в Visual Basic 6 появились новые возможности. Рис. 1-8 иллюстрирует пример одного из таких новшеств — теперь почти все видимые элементы управления поддерживают всплывающие подсказки. Кроме того, все большее число элементов управления дополняется поддержкой операций drag-and-drop, доступа к данным и средств для работы в Интернете.
Рис. 1-8 Свойство ToolTipText теперь стандартно для большинства элементов управления Одно из моих любимых свойств всех элементов управления, поддерживающих вывод изображений, — их способность показывать картинки в стандартных форматах <JPf.G, GIF и др.), принятых в Интернете (рис. 1-9). Решив использовать какие-то элементы управления, обязательно изучите все их свойства, методы и события — наверняка найдете массу новых и заманчивых возможностей!
ГЛАВА 1
Что нового в Visual Basic 6
11
Рис. 1-9 Все элементы управления, способные выводить изображения, теперь поддерживают форматы JPEG' и GIF
Средства объектно-ориентированного программирования Модули классов, впервые появившиеся в Visual Basic 4, позволили создавать и использовать объекты. В Visual Basic 5 модули классов усовершенствовали, а в язык ввели много новых средств объектноориентированного программирования (ООП). Как ни странно, в Visual Basic 6 в этом плане почти ничего не изменилось. Но ясно, что твердый фундамент ООП, заложенный в предыдущих версиях Visual Basic, будет играть все более важную роль в разработке серьезных приложений — и сегодня, и завтра. Если Вам пока не знакомы эти средства ООП, сейчас самое время примкнуть к победившей партии! На протяжении всей книги я буду стараться приводить практические примеры объектно-ориентированного кода и модулей, но делать это так, чтобы Вы не утонули в новых концепциях структурной организации программ. ООП привело к появлению целого пласта новой терминологии. И если Вы еще плохо представляете, что стоит за терминами полиморфизм, дружественные функции, инкапсуляция, интерфейсы, наборы и т. п., не паникуйте! В свое время я все подробно объясню и проиллюстрирую на примерах, что поможет твердо усвоить эту новую и очень важную часть языка.
Изменения в языке В языке Visual Basic усовершенствован ряд существующих функций и появилось несколько новых, значительно упрощающих некоторые виды распространенных задач программирования. Весьма кстати, например, пять новых строковых функций. Они еще не раз встретятся Вам в этой книге, но я перечислю их здесь просто для справки: Filter, InStrRev, Join, Replace и Split. Ближе к концу книги смотрите в оба, и Вы поймете, на какие чудеса они способны и как этого добиться!
12
ЧАСТЬ I
ПРИСТУПАЯ К РАБОТЕ С VISUAL BASIC
Кроме строковых функций, хочу прямо сейчас упомянуть и об одной новой, очень важной, математической функции: Round, которая позволяет округлять любое число до заданного количества разрядов. Такая операция была возможна и раньше — за счет строковых преобразований или заумной математики, но функция Round упрощает ее и выполняет гораздо быстрее. С помощью этой функции легко реализуется такая часто встречающаяся задача, как округление денежных сумм с точностью до цента, что я и продемонстрирую на примере объекта Loan в главе 5 «Объектно-ориентированное программирование*. Целый букет новых возможностей открывает функция CallByName, которая обеспечивает доступ к свойству или методу объекта по ссылке на его имя, хранящееся в строковой переменной. Новые объекты FileSystem предоставляют Вам принципиально новый способ взаимодействия написанных на Visual Basic приложений с файловой системой. Объектно-ориентированный подход к перечислению файлов и работе с дисками, папками, файлами и всеми их свойствами прямо позаимствован из мира C++. Этот подход имеет свои преимущества и упрощает некоторые весьма сложные, но распространенные задачи программирования, относящиеся к файловой системе. Несколько усовершенствований, внесенных в язык Visual Basic, освобождают от отдельных ограничений прежних версий. Пользовательские типы данных (UDT) теперь допустимы в качестве аргументов и типов значений, возвращаемых открытыми свойствами и методами. Функции и процедуры-свойства могут возвращать массивы целиком. А массивы переменного размера (также называемые SAFEARRAY) MOiyr присутствовать в левой части оператора присвоения. В Visual Basic 6 есть и другие новинки, например, в Visual Basic for Applications добавлен целый ряд функций и свойств. Везде, где только можно, я буду стараться использовать новейшие функции, средства и способы реализации тех или иных задач.
Доступ к данным Новая модель доступа к объектам данных ADO проще и надежнее, чем предыдущие версии, лучше интегрирована с другими средствами и приложениями Microsoft. Интерфейс теперь одинаков независимо от того, где находится информация — в локальной или удаленной базе данных. Кроме того, поддерживаются иерархические наборы записей. Для поддержки новой модели ADO с Visual Basic теперь поставляется новый элемент управления ADO Data. Он позволяет быстро создавать приложения, работающие с базами данных, и требует минимального объема дополнительного кода.
ГЛАВА 1
Что нового в Visual Basic 6
13
Провайдеры данных (data providers) и их потребители (data consumers) можно связывать друг с другом большим числом способов, чем раньше. С базой данных теперь разрешается связывать едва ли не любой объект. А такое средство, как Binding Manager, заметно облегчает связывание визуальных и не-визуальных объектов. Элементы управления Data Report Designer — многоцелевой набор средств для эффективного формирования и вывода отчетов. Б набор входят шесть элементов управления, разработанных специально для вычисления и форматирования данных в отчетах, выводимых на печать. Нестандартные элементы управления (user controls) можно создавать как источники данных, что позволяет связывать с ними другие элементы управления. Новое окно Data View (в среде Visual Basic) обеспечивает просмотр всех связей с базой данных, сформированных в Вашем проекте на данный момент. Новый мастер Data Form Wizard автоматически генерирует формы Visual Basic с соответствующими элементами управления и процедурами для управления информацией, получаемой из таблиц и запросов базы данных. Несколько новых и усовершенствованных элементов управления предлагают более удобные способы для просмотра и редактирования наборов записей из баз данных. К таким элементам относятся DataGrid, DataRepeater и Hierarchical FlexGrid.
Интернет Интернет играет все более важную роль в разработке современных приложений, и Visual Basic 6 предоставляет много новых и улучшенных средств, которые помогают создавать программы, способные работать с Интернетом. Вы можете формировать содержимое Web-страниц, используя новый объект DHTMLPageDesigner, который дает возможность размещать элементы управления на странице методом drag-and-drop и писать код для их обработки. Visual Basic Enterprise Edition позволяет создавать серверные приложения с поддержкой Dynamic HTML (DHTML). Мастер Package and Deployment Wizard (ранее известный под названием Setup Wizard) упаковывает файлы Вашего приложения для распространения на дискетах, компакт-дисках и через Web либо создает дистрибутив на локальном или сетевом диске. Многие усовершенствованные средства Visual Basic 6 поддерживают загрузку и развертывание по сети, а также все возможности ActiveX-документов с применением технологии Internet Explorer 4.
Г Л А В А
Стиль программирования 1 Ipocxoxa работы с Visual Basic часто маскирует необходимость в едином стиле программирования, но эта необходимость растет пропорционально размерам создаваемых программ. Совсем несложно отладить программу из 100 строк, труднее — из 1000 и почти невозможно — из 10 000, если только Вы не придерживаетесь определенной системы. Когда же над программой работает не один человек или ее предполагается поддерживать длительное время, тем более не обойтись без соблюдения каких-то правил. Главное в стиле программирования — логичность и четкость. Хороший стиль снижает вероятность ошибок и упрощает восприятие исходного текста. Он включает в себя схему именования, контроль типов, область действия переменных и комментарии. Связанные с ним правила таковы: * присваивайте элементам описательные имена; ш всегда используйте оператор Option Explicit; ш присваивайте каждой переменной и каждому параметру наиболее подходящие типы данных; в ограничивайте диапазон действия элементов программы (применяйте процедуры-свойства вместо глобальных данных); и пишите комментарии в тексте программы. Пусть эти правила станут Вашей второй натурой: Вы должны соблюдать их на подсознательном уровне. А теперь рассмотрим каждое правило подробнее.
Присвоение описательных имен Легко ли «говорить» на Бейсике? Все зависит от того, с кем Вы разговариваете, какова Ваша квалификация и насколько Вы аккуратны. Когда-то давным-давно номера строк и операторы GOTO легко превращали текст программы в клубок спагетти. К счастью, современные версии Бейсика структурированы гораздо лучше и даже объектно-ориентированны, хотя при желании и сегодня можно устроить полную кашу! За последние несколько лет высказывалось немало предложений по стандартному стилю программирования и некоторые из них были не очень-то хороши. Но ломать копья из-за этого, на мой
ГЛАВА 2
Стиль программирования
15
взгляд, не стоит. Достаточно привыкнуть к нескольким сравнительно простым и стандартным приемам, и Вы, вполне вероятно, заслужите репутацию пишущего легкочитаемый код. Если Вы работаете не один, а в команде, Ваши усилия, затраченные на упрощение восприятия кода, окупятся сторицей.
Префиксы для элементов управления Один из простейших способов прослыть знатоком — начинать имя каждого элемента управления в тексте программы со стандартного трехбуквенного префикса. Это действительно улучшает читаемость кода. Вдумайтесь, к чему относится процедура Buffalo_Click? К кнопке? К рисунку? К переключателю? Не зная, какому объекту на форме присвоено имя Buffalo, Вы будете сильно озадачены . Но стоит написать cmdBuffalojClick, и тут же станет понятно, что процедура относится к кнопке, picBuffalo_Click — к элементу управления PictureBox («окно рисунка*) и т. д. Список общепринятых префиксов публиковали уже несколько раз, я повторю его здесь в слегка расширенном виде.
Стандартные префиксы компонентов Visual Basic Префикс ado ant cal cbo ch cb3 cbk dp cm3 cmd com con ctr fiat db dbc dbd dbe dbgrd dbl
Компонент ADO Data Animation Calendar ComboBox Chart 3D Checkbox CheckBox Картинка (picture clip) 3D ComtnandButton CommandButton Comm Container (DAO) Control Data Database (DAO) DataCombo DataGrid Ядро базы данных (DAO) Сетка, связанная с данными (data-bound grid) DataList см. след. стр.
Прим. псрсв.: Здесь Б оригинале обыгрывается слово buffalo, глагольная форма которого переводится как «озадачивать», «мистифицировать». 2—1204
Имена переменных Некоторые — особенно из числа программистов на С — предлагают именовать все переменные по так называемой венгерской системе, аналогичной той, что применяется для префиксов компонентов (см. предыдущую таблицу). Эти предтожения вызывают у меня смешанные чувства. С одной стороны, вроде бы удобно видеть тип переменной, читая текст программы, а с другой, такая система зачастую слишком громоздка. С самого начала у программистов на Microsoft Basic была возможность именовать переменные с использованием суффикса, обозначающего тип данных. Например, Х% — переменная целого типа,
18
ЧАСТЬ I
ПРИСТУПАЯ К РАБОТЕ С VISUAL BASIC
X! — переменная вещественного типа одинарной точности, a XS — строка. Какая система лучше — венгерская или эта, по-моему, дело вкуса. Мне попадались схемы именования, в которых «венгерские* префиксы переменных уточнялись даже диапазоном действия, но подобная детализация избыточна. В следующей таблице перечислены суффиксы, обозначающие типы данных. Там же указаны и более новые типы, для которых суффиксов нет, что, кстати, подтверждает мое мнение насчет постепенного отказа Microsoft от суффиксов переменных.
Стандартные суффиксы для различных типов данных Суффикс % & @ / S нет нет нет нет нет нет нет
Тип данных Integer (2-байтовый целый со знаком) Long (4-байтовый целый со знаком) Currency (8-байтовый «денежный») Single (4-байтовый вещественный одинарной точности) Double (8-байтовый вещественный двойной точности) String (строчный) Boolean (Булев) Byte (байтовый) Colleccion (набор) . Date (дата) Decimal (28-разрядный тип Variant) Object (объектный) Variant (произвольный)
Если Вы считаете, что венгерская система именования переменных — истина в последней инстанции, то специально для Вас я перечислю здесь и префиксы, принятые в этой нотации. Кстати, для некоторых типов данных предусмотрено более одного префикса, что позволяет обозначать сферу применения конкретной переменной. Например, стандартный 16-битный целый тип со знаком можно назвать Boolean, Handle, Index, Integer или Word в зависимости от того, как Вы используете данную переменную.
Тип данных Object — универсальный, он не относится к конкретному объекту, чье имя Вам известно; например, процедура может принимать объект как параметр, не «зная» заранее, какого типа этот объект. Если же Вам известен тип объекта, сформируйте префикс по имени класса данного объекта (см. раздел «Имена классов»).
Префиксы, обозначающие тип данных в венгерской системе Префикс
Типданных Boolean Byte Currency
ГЛАВА 2
Стиль программирования
19
Префиксы, обозначающие тип данных в венгерской системе (продолжение) Префикс dbl dec dtm sng h г Ing int obj str и ulng vnt wrd
Тип данных Double Decimal Date (Time) Single Описатель (handle) Индекс (index) Long Integer Object (универсальный) String Беззнаковый Беззнаковый Long Variant Слово (word)
Объявление переменных Следить за типами переменных помогает оператор Option Explicit. Может быть, Вы даже предпочтете, чтобы он автоматически появлялся в каждом создаваемом модуле, — лично я советую поступать именно так. Для этого выберите из меню Tools (сервис) команду Options (параметры), щелкните ярлычок Editor (редактор) и пометьте флажок Require Variable Declaration (требовать объявления переменных). Оператор Option Explicit запрещает ссылки на переменные, не объявленные в явном виде. Переменные лучше всего объявлять оператором Dim, указывая при этом их тип. Тогда переменные легко читать и отличать от элементов управления и других объектов, в именах которых есть свои префиксы, а чтобы узнать тип нужной переменной, достаточно бросить взгляд на блок операторов Dim в начале модуля или процедуры. ВНИМАНИЕ Весьма распространенная и очень опасная ошибка — чаще всего ее совершают те, кто знает язык программирования С, — некорректное объявление оператором Dim сразу нескольких переменных одного типа. Не выйдет! Вы должны явно определять тип каждой переменной в операторе Dim. Читайте дальше! Эти объявления переменных корректны: Dim intA As Integer, sngB As Single, dblC As Double Dim DX, E!, F#
20
ЧАСТЬ I
ПРИСТУПАЯ К РАБОТЕ С VISUAL BASIC
Но вот следующее объявление приведет к созданию двух переменных типа Variant (intl и intj) и одной переменной типа Integer (intK) вместо предполагавшихся трех переменных типа Integer: Dim intl, IntJ, intK As Integer В итоге Ваша программа может работать не так, как Вы ожидали, и при этом будет очень непросто выявить причину ее некорректного поведения.
Меню Для элементов меню тоже есть несколько схем именования. Я предпочитаю начинать их со стандартных на сегодняшний день префиксов тпи. Может быть, Вы привыкли добавлять название элемента и меню, к которому он относится, — например, mnuHelpAbout соответствует команде About из меню Help. Я же пользуюсь сокращениями и вместо mnuHelpAbout пишу что-то вроде mnuAbout. В большинстве случаев понять, какую позицию в структуре меню занимает тот или иной элемент, достаточно просто, и, на мой взгляд, незачем усложнять их имена.
Имена классов Создавая новые классы, присваивайте им описательные имена, а объявляя объектные переменные, указывайте префиксы, обозначающие их классы. Обычно ставят уникальный трехбуквенный префикс, но при необходимости можно использовать и более длинный. Иначе говоря, составьте свою схему именования объектов. Microsoft предлагает именовать классы целыми словами, которые начинаются с заглавных букв:
Примеры именования классов и объектных переменных Имя класса Loan Planet EmployeeRecord DailySpecial
Префикс объектной переменной Ion pint ere dspec
Пример имени объектной переменной lonBoat plntEarth ercSales dspecTuesday
Контроль типов данных Старайтесь присваивать конкретной переменной наиболее подходящий ей тип. Это предложение несколько отличается от принципа использования «наименьшего из возможных* типов. Например, тип Boolean должен занимать всего 1 бит — True или False, правильно? Но на деле у переменной типа Boolean такой же размер, как и у переменной типа Integer, т. е. 2 байта.
ГЛАВА 2
Стиль программирования
21
Так зачем же таскать лишний багаж? А затем, что в наше время надежность важнее компактности. Даже если Вы разрабатываете приложения для Интернета (где размер файлов очень важен), все равно лучше использовать оптимальные типы данных и тем самым повысить надежность и читаемость кода. Переменная типа Boolean всегда содержит одно из двух значений — True или False. Наименьший из возможных типов, Byte, тоже позволяет хранить одно из этих значений, но где гарантия, что он не хранит нечто совсем иное? Указав тип Boolean, Вы сразу же расставите все по своим местам, и программа станет чуточку яснее, а значит, и надежнее. Применение наиболее подходящих типов особенно важно для объектных переменных. Объявив такую переменную с именем конкретного класса, а не как универсальный тип Object, Вы дадите компилятору возможность оптимизировать доступ к соответствующему объекту. (Специфика этого процесса не настолько проста, чтобы обсуждать ее здесь, — см. главу 24 «ActiveX-объекты из других приложений».) Кроме того, указывайте типы всех параметров в процедуре. Это поможет обнаружить ее некорректное использование. Помните: необязательные параметры теперь могут быть не только типа Variant. Этого не было в Visual Basic 4, так что неплохо бы пройтись по всем процедурам, написанным Вами на той версии языка, и проверить, нельзя ли каким-то параметрам присвоить другие типы.
Ограничение диапазона действия элементов программы Диапазон действия (scope) подразумевает видимость переменной, процедуры или объекта другим процедурам в программе. В старину все данные были глобальными — Вы получали или изменяли значение любой переменной в любом месте программы. Потом, когда в Бейсик ввели подпрограммы и процедуры типа Function, появились 2 диапазона действия — данные могли быть глобальными или локальными в пределах процедуры. А теперь возможны 4 диапазона: » универсальный — через Microsoft ActiveX элемент виден всем выполняемым приложениям; и глобальный — элемент виден всем процедурам во всех модулях проекта; s модульный — элемент виден всем процедурам в пределах данного МОДУЛЯ; ш локальный — переменная видна лишь содержащей ее процедуре. Ограничивая диапазон элемента, Вы получаете возможность контролировать его изменение. Чем шире этот диапазон, тем осторожнее следует действовать. Потому-то и нужно максимально сужать его. И, кстати, процедуры-свойства позволяют, по сути, отка-
22
ЧАСТЬ I
ПРИСТУПАЯ К РАБОТЕ С VISUAL BASIC
заться от глобальных переменных. Эти процедуры вводят дополнительный уровень контроля за глобальными данными, проверяя допустимость значения и права вызывающей процедуры на изменение данных.
Комментарии Хорошие комментарии — приятный сюрприз любому, кто хоть раз пробовал разобраться в чужой программе. Не так давно отхватил я на свою голову контракт на подготовку' руководства по новому языку программирования, разработанному одной компанией для сугубо внутренних целей. Думаете, была какая-нибудь спецификация? Нет. Образцы программ? Тоже нет. Комментарии в исходном коде? Нет, нет и еще раз нет. Ведущий разработчик и я провели времени вместе куда больше, чем со своими семьями, — вот уж пришлось поработать! Но нужно ли это? Лучше сразу писать комментарии. Чтобы структурировать их Е рамках процедуры, используйте несколько приемов: п пишите для процедуры заголовок, в котором указывайте, что делает данная процедура, и перечисляйте используемые ею другие процедуры и глобальные данные; я перед операторами Case и прочими управляющими конструкциями кратко описывайте возможные варианты и соответствующие им действия; я перед циклами комментируйте их предназначение и условия выхода; я придумайте систему <'ключевых слов» в комментариях и помечайте ими связанные блоки кода, чтобы при необходимости их можно было быстро найти и что-то изменить.
Дополнительные источники информации Более подробную информацию о стандартных схемах именования и стиле программирования ищите в справочной системе Visual Basic в разделе Visual Basic Coding Conventions. Приведенных там рекомендаций придерживаются многие корпорации и программисты, и, если Вы намерены работать в большом коллективе, нет смысла цепляться за собственные «стандарты».
Ч А С Т Ь
II
ДОРОГОЙ ДЖОН, КАК?..
о
дна из сильных сторон Visual Basic — его гибкость. Если Вам нужно сделать нечто такое, на что в Visual Basic нет готовых рецептов, Вы непременно добьетесь своего, «извернувшись» за счет функций API (интерфейса прикладного программирования) или нетривиального применения элементов управления. Для этой части книги я отобрал самые интересные вопросы типа «Дорогой Джон, как?..». Отвечая на них, я, естественно, использовал лишь новейшие приемы программирования. Например, можно написать процедуру, заменяющую строкой все экземпляры какого-либо символа, но в Visual Basic теперь есть встроенная функция Replace, которая делает это сама. Не держитесь за устаревшие приемы — осваивайте возможности Visual Basic 6!
Г Л А В А
Переменные ГТовые строковые функции Visual Basic уменьшают потребность в том багаже, который большинство из нас таскает за собой еще со времен BASICA. Распространенные строковые операции вроде удаления пробелов, замены табуляторов или разбиения строк на лексемы теперь поддерживает сам Visual Basic. Visual Basic 6 предоставляет также элементы управления для выбора и отображения дат в форматах календаря и списка, Элементы Calendar и DTPicker упрощают прием дат от пользователей и освобождают программиста от большей части кропотливой работы, необходимой для отображения дат в привлекательном виде. Я расскажу, как использовать такие средства, и продемонстрирую некоторые приемы работы с переменными, отнюдь не новые для этой версии Visual Basic. Будут рассмотрены операции с Variantпеременными и предопределенными константами, эмуляция беззнаковых целых в вызовах функций Windows API, создание структур пользовательского типа (UDT-структур) и многое другое.
Эмулировать беззнаковые целые? Увы, Visual Basic не поддерживает беззнаковые 16-битные целые — тип данных, часто используемый в API-вызовах. Но не беда — есть способы обойти этот недостаток. Целочисленные типы данных в Visual Basic представлены тремя разновидностями: Long, Integer и Byte. Переменные типа Long принимают 32-битные числа со знаком в диапазоне от -2 147 483 648 до +2 147 483 647. Чаще всего используется тип Integer, позволяющий хранить 16-битные числа со знаком в диапазоне от -32 768 до +32 767. Переменные типа Byte хранят 8-битные беззнаковые значения в диапазоне от О до 255. Заметьте, что только Byte является беззнаковым целочисленным типом. Беззнаковые 16-битные целые необходимы для вызова многих API-функций. Можно, конечно, пойти напролом и передавать этим функциям «знаковые» переменные, но тогда Вам придется создать обработчик отрицательных значений. Существует несколько подходов к эмуляции беззнаковых 16-битных целых, и я расскажу о двух лучших.
26
ЧАСТЬ II
ДОРОГОЙ ДЖОН, КАК?..
ПРИМЕЧАНИЕ Компилируя программу в машинный код, Вы можете отключить контроль переполнения целочисленных типов, В некоторых случаях это позволит выполнять некорректные вычисления, так что внимятельно просмотрите свою программу, прежде чем отключать данный параметр. Но положительный результат его отключения — ускорение целочисленных операций и возможность более эффективного представления беззнаковых значений в определенных видах вычислений.
Использование переменных типа Long В ряде случаев можно манипулировать беззнаковыми целыми в диапазоне от 0 до 65 535, храня их в переменных типа Long. Когда наступит пора присвоить это значение переменной типа Integer — заменителю беззнаковой 16-битной переменной, воспользуйтесь следующей формулой: intShort = UngLong And &H7FFF&) - (IngLong And &H8000&) где intShort — переменная типа Integer, передаваемая API-функции, a IngLong — переменная типа Long, хранящая нужное (беззнаковое) значение. Преобразование 32-битного значения в 16-битное беззнаковое реализуется побитовыми операциями с помощью оператора And и шестнадцатеричных битовых масок. Чтобы записать в переменную типа Long значение целой переменной Integer, использованной для эмуляции беззнаковой 16-битной переменной (эта операция обратна только что рассмотренной), действуйте так: IngLong = intShort And &HFFFF& Учтите, что 16-битное беззнаковое значение (mtSbort), хранимое в переменной типа Integer, может быть интерпретировано как отрицательное. Поэтому описанные выше преобразования следует проводить непосредственно до и после вызова API-функции, ожидающей беззнакового 16-битного целого. Таким образом, в своей программе Вы работаете с беззнаковым целым, хранящимся в переменной типа Long (IngLong), а передаете его API-функциям в переменной типа Integer (intSborf).
Упаковка беззнаковых байтовых значений с применением структур В Visual Basic нет конструкции Union, как в С, но ее функциональность легко имитировать, копируя байты между UDT-структурами оператором LSet. Это позволяет записывать беззнаковые байтовые значения в целые со знаком и извлекать их оттуда. Вот фрагмент кода, демонстрирующий этот прием:
ГЛАВА 3_ Переменные
27
Option Explicit Private Type UnsignedlntType lo As Byte hi as Byte End Type Private Type SlgneblntType n As Integer End Type Каждая из показанных UDT-структур хранит по 2 байта. Хоть области памяти и не перекрываются, как в случае Union в языке С, их двоичное содержимое несложно перемещать между соответствующими им переменными: Private Sub Form_Click() ' создаем UDT-переменные Dim intu As UnsignedlntType Dim intS As SignedlntType ' инициализируем старший и младший байты, создавая тем самым целое intU.hi = 231 intU.lo = 123 1 копируем двоичные данные в другую структуру LSet intS = intU Print intS.n, intU.hi; intU.lo ' присваиваем целое и извлекаем старший и младший байты intS.n = intS.n - 1 ' уменьшаем целое, создавая новое значение
' копируем обратно в другую структуру LSet intU = intS Print intS.n, intU.hi; intU.lo End Sub
Если Вы, поместив эти два фрагмента кода в модуль формы, щелкнете ее, на ней появятся целое число со знаком (-6277) и два составляющих его байтовых значения (231 и 123). После какой-нибудь операции над целым (в данном случае — вычитания единицы) будут выведены новые результаты: целое со знаком (-6278) и его старший и младший байты (231 и 122). Все операции реализуются объявлением двух переменных — intUn intS — с применением определенных ранее UDT-структур. Значения тШЫ и intU.lo присваиваются старшему и младшему байтам переменной intU. Затем двоичное содержимое переменной intU копируется в intS оператором LSet и полученное в результате целое со знаком выводится на форму. Наконец, целое значение в intS уменьшается на единицу (просто чтобы получить значение, отличное от исходного) и intS копируется обратно в intU — вот так можно разбить целое со знаком на 2 беззнаковых байтовых значения.
28
ЧДСТЬ И ДОРОГОЙ ДЖОН, КАК?.
Добавив на форму текстовые поля и надписи, Вы получите калькулятор, преобразующий целое в составные байты и наоборот (рис. 3-1). Заметьте, что LSet способен копировать любое двоичное содержимое одной UDT-структуры в другую. А это позволяет обрабатывать двоичное содержимое некой области памяти как данные разных типов.
Рис. 3-1 Целое число со знаком, состоящее из двух беззнаковых байтов ВНИМАНИЕ В Microsoft Windows 95 и Microsoft Windows NT элементы Ваших UDT-структур могут быть выровнены в памяти не совсем так, как Вы ожидали. Это происходит из-за того, что каждый элемент выравнивается по границе, кратной 4 байтам, с добавлением недостающих байтов. Перемещая данные из одной структуры в другую оператором LSet, поэкспериментируйте сначала и убедитесь, что байты располагаются именно так, как Вы хотели. Будьте осторожны! 1гО
См. также...
• раздел «Дорогой Джон, как... использовать С, чтобы создать DLL?» в главе 27 «Изощренные приемы программирования» — о создании DLL на Microsoft Visual C++. Язык С идеален для перекомпоновки байтов в целых значениях.
Работать с Булевыми значениями? Значения Булева типа хранят одну из констант — True (истина) или False (ложь) — и больше ничего. Булевы переменные обычно используют для хранения результатов сравнений и других логических проверок. Например, следующая процедура присваивает результат логического сравнения двух величин Булевой переменной blriTestResult и выводит это значение на экран: Private Sub Form_Click() Dim blnTestResult as Boolean blnTestResult = 123 < 246 Print blnTestResult End Sub
ГЛАВА 3 Переменные
29
В данном случае ня экране появится значение True. Когда Вы выводите значение Булевой переменной на экран или принтер, всегда появляется либо True, либо False. ВНИМАНИЕ Бойтесь неявных приведений типов! Visual Basic поддерживает многие виды автоматического приведения типов, а это значит, что Вы можете присвоить значение любого типа практически любой переменной. Скажем, хотя Булевы переменные принимают в качестве значений только True или False, есть возможность присвоить такой переменной число или даже строку. Но результаты могут оказаться весьма неожиданными будьте осторожны! Небольшой пример. Фрагмент кода, приведенный ниже, выведет на экран True, True и False, т. е. bytA равно True, intB равно True, но bytA не равно intB\ Причина такой странности не столь очевидна и связана с внутренним приведением разнотипных переменных bytA и intB к одному типу при проверке их на равенство. Private Sub Form_Click() Dim bytA As Byte Dim intB As Integer
bytA = True intB = True Print bytA = True Print intB = True Print bytA = intB End Sub
Сложные логические выражения можно составить с помощью операторов And, Or и Not. Логические выражения объединяют в себе несколько операций сравнения, давая в результате True или False. Например: If blnExit And Not blnChanged Then End Эта строка кода завершает программу, если blnExit равно True и в то же время blnChanged — False. Для тех же целей пригодны и арифметические операции, но логические делают код короче и яснее. Кроме того, Булевы переменные в таких выражениях позволяют избежать нежелательных приведений типов.
Использовать байтовые массивы? Едва ли не главной причиной введения в язык байтовых массивов было стремление обеспечить обмен буферами двоичных данных с 32-разрядными API-функциями. Одно из различий между 16- и 32разрядными программами, написанными на Visual Basic, в том, что строки, используемые 32-разрядными версиями, содержат символы Unicode, а это требует двух байтов на символ. Система сама преобра-
30
ЧАСТЬ II
ДОРОГОЙ ДЖОН, КАК?..
зует двухбайтовые символы Unicode в однобайтовые символы ANSI, но если строка содержит двоичные данные, ее содержимое после такого преобразования становится бессмысленным. Чтобы избежать проблем, возьмите за правило передавать в строках только символьные данные, а двоичные данные — в байтовых массивах.
Передача байтовых массивов вместо строк Байтовые массивы содержат беззнаковые байтовые значения в диапазоне от 0 до 255. В отличие от строк содержимое таких массивов никогда не преобразуется операционной системой. Байтовые массивы можно передавать вместо строк многим API-функциям. Следующий код, вызывающий API-функцию GetWindowsDirectory и таким образом определяющий путь к каталогу Windows, демонстрирует, какие изменения нужно внести в объявление функции и ее параметров. Код представлен в двух версиях: первая передает строку, в которой функция возвращает путь к каталогу Windows, a вторая с той же целью передает байтовый массив. Разместите любую версию этого кода в модуле формы, запустите программу, щелкните форму, и Вы увидите путь к каталогу Windows на своем компьютере по аналогии с тем, что показано на рис. 3-2.
Рис. 3-2 API-функция, возвращающая путь к каталогу Windows Подметив различия двух версий, Вы поймете суть отличий параметров-строк и параметров — байтовых массивов. Вот пример с использованием строк: Option Explicit Private Declare Function GetWindowsDirectory Lib "kerne!32" _ Alias "GetWindowsDirectoryA" ( Byval IpBuffer As String, ByVal nSize As Long ) As Long Private Sub Form_Click() Dim intN As Integer Dim strA As String устанавливаем размер строковой переменной strA = Space$(256) intN = GetWindowsDirectory(strA, 256} ' отсекаем лишние символы
ГЛАВА 3_Переменные
31_
strA = Left$(strA, intN) Print strA End Sub
В этой версии функция возвращает путь к каталогу Windows в строковом параметре IpBuffer. Перед ее вызовом я установил длину строки равной 256 символам, и получив результат, отсек лишние символы в строке. ВНИМАНИЕ Прежде чем вызывать API-функцию, всегда устанавливайте размер строки или байтового массива, которые она должна заполнить данными. Ваша программа, скорее всего, «рухнет», если Вы забудете про это! А вот пример с использованием байтового массива: Option Explicit Private Declare Function GetWlndowsDirectory Lib 'kerne!32' _ Alias "GetWindowsDirectoryA" ( ByRef IpBuffer As Byte, _ ByVal nSize As Long _ ) As Long Private Sub Form_Click() Dim intN As Integer Dim bytBuffer() As Byte Dim strA As String ' устанавливаем размер байтового массива bytBuffer = Space$(256) intN = GetWindowsDirectory(bytBuffer(0), 256) strA = StrConv(bytBuffer, vbUnicode) отсекаем лишние символы strA = Left$(strA, intN) Print strA End Sub Присмотритесь к объявлению API-функции GetWindowsDirectory во втором примере. Первый параметр вместо строки, передаваемой по значению (ByVal), теперь принимает байтовый массив, передаваемый по ссылке (ByRef). Первоначально его объявление было таким: ByVal IpBuffer As String А в новой версии оно выглядит иначе: ByRef bytBuffer As Byte При передаче строковых параметров API-функциям модификатор ByVai необходим потому, что строковая переменная в действительности определяет место в памяти, где хранится адрес содержимого строки (в терминологии С — указатель на указатель). Моди-
32
ЧДСТЬ II
ДОРОГОЙ ДЖОН, КАК?..
фикатор ByVal означает, что передается содержимое области памяти, расположенной по адресу, который идентифицируется именем строки; иначе говоря, передается адрес, по которому находится фактическое содержимое строки. Во втором варианте вызова функции я передаю bytBuffer(0), что при использовании модификатора ByRef влечет передачу адреса первого байта массива. Результат в обоих случаях одинаков: функция GetWindowsDirectory слепо записывает в указанный буфер путь к каталсиу Windows. В коде присутствует важная строка, требующая пояснений: strA = StrConvfbytBuffer, vbUnicode) Эта команда преобразует двоичное содержимое байтового массива Б допустимую строку Visual Basic. Возможно и прямое присвоение динамических байтовых массивов строкам или, наоборот, строк динамическим байтовым массивам, как показано ниже: bytBuffer = strA strB = bytBuffer Если Вы присвоите строку динамическому байтовому массиву, то число байтов в массиве будет вдвое больше количества символов в строке. Это происходит из-за того, что строки Visual Basic содержат символы Unicode, а каждый такой символ занимает 2 байта. Когда в элементы байтового массива преобразуются символы ASCII, каждый второй байт массива оказывается нулевым. (Второй байт используется для поддержки других наборов символов, скажем, алфавитов европейских и азиатских языков, что важно при написании приложений с многоязыковой поддержкой.) Однако API-функция GetWindowsDirectory возвращает буфер не в формате Unicode, и мы должны преобразовать его содержимое в формат Unicode вызовом функции StrConv, как и показано выше. В первой версии эту рутинную операцию выполняет сам Visual Basic — в момент заполнения строкового параметра. Преобразование в Unicode «расширяет» каждый символ в буфере до двух байтов и тем самым удваивает число байтов в конечной строке. Это не столь очевидно, особенно если учесть, что значения выражений Len.(strA) и l3Bound(bytBiiffer) после преобразования массива в строку совпадают. Однако LenB(strA) показывает значение, вдвое большее, чем функция llBound(bytBuJfer). Вся штука в том, что Len возвращает число символов в строке, a LenB — количество байтов в строке. Длина Unicode-строки в символах (вспомним, что таковыми являются все строки в 32-разрядной версии Visual Basic) составляет только половину от количества байтов в строке, поскольку каждый Unicode-символ состоит из двух байтов. Итак, преобразуя параметр API-функции из строкового типа в тип байтового массива, замените ключевое слово ByVal на ByRef, передайте первый байт массива вместо имени строки и, если двоич-
ГЛАВА Э_Переменные
33
ные данные следует преобразовать в строку, используйте функцию StrConv с константой vbUnicode.
Обмен данными между строками и байтовыми массивами Чтобы упростить обмен данными между строками и байтовыми массивами, разработчики Visual Basic предусмотрели особый случай присвоения динамических байтовых массивов строкам, и наоборот. ПРИМЕЧАНИЕ Присвоить строку байтовому массиву можно только в том случае, если этот массив — динамический, а не фиксированный. Простейший способ создать динамический массив байтов — указать при его объявлении пустые скобки, например так: Dim bytBuffer() As Byte В то же время следующий оператор Dim объявляет фиксированный массив байтов, пригодный для самых разных задач, но только не для присвоения строк. Попытка присвоить ему строку дает ошибку. Dim b y t B u f f e r ( 8 0 ) As Byte
Работать с датами и временем? Переменная типа Date занимает 8 байтов, в которых размещается информация не только о дате, но и о точном времени. Выводя на экран содержимое переменной типа Date, Вы, как по волшебству, видите строковое представление года, месяца, дня, часа, минут и секунд, и все эти данные хранятся в 8 байтах! Конкретный формат вывода даты и времени зависит от стандартов, выбранных в Вашей системе. В последующих примерах я полагаю, что значения даты и времени всегда хранятся в переменных типа Date.
Применение элементов управления DTPicker и Calendar Эти элементы управления очень удобны в получении и представлении информации, связанной с датами. DTPicker входит в Microsoft Windows Common Controls-2 (xMSCOMCTZ.OCX), a Microsoft Calendar содержится в отдельном файле MSCAL.OCX. Если Вы хотите выводить или получать даты в окне списка, применяйте DTPicker. Пользователь, щелкнув этот элемент управления, откроет небольшую форму календарика и сможет выбрать из нее нужную дату (рис. 3-3). А если Вы предпочитаете выводить или получать даты на отдельной странице календаря, используйте элемент управления Calendar. Обычно он занимает больше места, чем DTPicker, зато его возможности гораздо шире (рис. 3-4).
ЧАСТЬ II ДОРОГОЙ ДЖОН, КАК?..
?!
12
13
14
1§
IS
Ч?
ta 'Э a a s , ,3i гэ 24 25. зет г? -га гэ эо si.
Рис. 3-3 DTPicker позволяет не только выбирать даты, но и вводить их в свое текстовое поле
'January 1938 fj*™^
]*j П»*""^] LffyJ
Рис. 3-4 Элемент управления Calendar позволяет использовать разные шрифты, изменять вид подсветки выбранной даты и предоставляет другие возможности По умолчанию оба этих элемента управления показывают текущую дату. Чтобы программно изменить отображаемую дату, присвойте нужное значение свойству Value элемента управления. Но прежде чем присваивать дату элементу Calendar, обязательно удалите информацию о времени — иначе возникнет ошибка. Вот пример правильного и неправильного присвоения текущей даты элементу Calendar: calDate.Value = Date calDate.Value = Now
' правильный способ ' неправильный способ; даст ошибку
DTPicker таких ограничений не имеет, что, кстати, может вызвать проблемы при совместном использовании обоих элементов управления. Следующий фрагмент кода иллюстрирует, как информация о времени, записанная в DTPicker (dtpDate), приводит к ошибке после ее передачи в Calendar (calDate):
Запись в переменные типа Date Чтобы ввести значения даты и времени в такую переменную, заключите нужную информацию в символы *. Если Вы укажете в программе строку в этом формате, Visual Basic проверит ее синтаксис. При некорректных дате или времени Вы тут же получите сообщение об ошибке. Вот пример, в котором Date-переменной dtmD присваивается значение даты и времени: Dim dtmD As Date dtmD = #11/17/96 6:19:20 РКП
Это значение переменной dtmD используется и в других примерах. Преобразовать числовые значения даты и времени в тип Date позволяют несколько функций. Например, DateSerial сводит числовые значения года, месяца и дня в одно значение типа Date. Функция TimeSerial аналогичным образом объединяет значения часов, минут и секунд. dtmD = DateSerial(1996, 11, 17) dtmD = TimeSerial(18, 19. 20) Чтобы скомбинировать в одной переменной типа Date и дату, и время, просто сложите результаты, возвращаемые соответствующими ФУНКЦИЯМИ: dtmD = DateSerial(1996, 11, 17) + TimeSerlal(18, 19, 20) Для преобразования строкового представления даты или времени в значение типа Date предназначены функции DateValue и TimeValue: dtmD = DateValue("11/17/96") dtmD = TimeValue("18:19:20") И вновь, чтобы скомбинировать дату и время в одной переменной, просто сложите результаты этих функций: dtmD = DateValue("Nov-17-96") + TimeValue("6:19:20 РН") Для строкового представления даты и времени существует множество форматов, но при оценке допустимости формата надо учитывать национальные стандарты, установленные в операционной системе. Так, порядок номеров месяца и дня в разных странах может быть прямо противоположным.
36
ЧДСТЫ1
ДОРОГОЙ ДЖОН, КАК?..
Отображение даты и времени Функция Format обеспечивает высокую гибкость в преобразовании переменной типа Date в строку, пригодную для вывода на экран или принтер. Вот пример с использованием предопределенных именованных форматов: Print Format(dtmD, "General Date") Print Format (dtinD, "Long Date") Print Format(dtmD, "Medium Date")
Помните, что результаты зависят от региональных стандартов, выбранных в Вашей системе. Запустите этот код и посмотрите, насколько отличаются Ваши результаты от приведенных здесь. При отображении даты и времени можно использовать не только именованные, но и собственные форматы. Следующая строка кода форматирует компоненты даты и времени и сохраняет результат в строковой переменной. strA = Format
' 11/17/1996 06:19 PM
Пользовательские форматы очень гибки. Так, чтобы сформировать название месяца из какой-либо даты, напишите: strMonthName = Format(dtmD, "mmmm") ' November Подробнее о многочисленных вариантах пользовательских форматов см. раздел справочной системы, посвященный функции Format.
Выборка компонентов Извлечь отдельные компоненты из переменной типа Date позволяют несколько функций. Саедующий пример послужит кратким «справочником* по этой группе функций: Print Month(dtmD) ' 11 Print Day(dtmD) ' 17 Print Print Print Print Print
Для значений, возвращаемых функцией WeekDay, в Visual Basic предусмотрен набор встроенных констант: vbSunday равна 1, vbMonday — 2 и так до vbSaturday, равной 7.
Вычисления с использованием дат и времени Переменную типа Date Вы можете напрямую использовать в математических вычислениях, но не забывайте, что единица в ней -
ГЛАВА 3
Переменные
37
день. Например, очень легко написать программу, вычисляющую возраст человека в днях (рис. 3-5). Для этого просто нужно вычесть дату его рождения, сохраненную в переменной типа Date, из результата, возвращаемого Date — системной функцией, которая сообщает текущую дату.
Рис. 3-5 Переменные типа Date позволяют вычислять количество дней между двумя датами Для преобразования времени в дату удобна функция TimeSerial. Допустим, Вы хотите узнать точную дату и время через 10 000 минут от данного момента. Вот как это сделать, не углубляясь в дебри математики: dtmD = Now + TimeSerial(0, 10000, 0} Здесь TimeSerial возвращает значение, представляющее комбинацию 0 часов, 10 000 минут и О секунд. Это значение суммируется с результатом функции Now, и Вы получаете переменную типа Date с нужными датой и временем.
Проверка допустимости даты и времени Присваивая переменной типа Date значения даты и времени, введенные пользователем, можно перехватывать ошибку «Type mismatch» (несоответствие типов), генерируемую Visual Basic при недопустимости одного из этих значений. Другой подход состоит в применении элемента управления Calendar, позволяющего выбирать лишь допустимую дату (со страницы на один месяц). *О См. также... • Программу-пример VBCal из главы 31 «Дата и время», в которой представлено диалоговое окно для выбора даты; Вы можете встроить его в свою программу.
Работать с переменными типа Variant? Тип Variant чрезвычайно гибок (некоторые считают, что даже слишком) и тем не менее открывает ряд возможностей по более четкой структуризации данных. Любые переменные, тип которых не объявлен в явном виде, относятся к типу Variant. В переменных типа Variant (будем называть их Variant-переменными) можно хранить практически все, что угод-
38
ЧАСТЫ1
ДОРОГОЙ ДЖОН, КАК?..
но, в том числе массивы, объекты, UDT-структуры и другие Variantпеременные. Массивы всегда содержат элементы одного типа, а это значит, что в одном массиве нельзя смешивать, скажем, строки и числа. Но массив Variant-переменных позволяет обойти это огра.ничение. Обратите внимание на фрагмент кода, приведенный ниже. В нем создается массив типа Variant, в который заносится целое число, строка и другая Variant-переменная. Чтобы подчеркнуть его гибкость, я записываю в третий элемент первого Variant-массива другой Variant-массив. Так что в Variant-переменной можно хранить практически все! Option Explicit Private Sub Form_Click() Dim i
' по умолчанию это Variant
Dim vntMatn(1 To 3} As Variant Dim intX As Integer
Dim strA As String Dim vntArray(1 To 20) As Variant 1 заполняем первичные переменные
strA = "This is a test," For i = 1 To 20 vntArray(i) = i
В последнем операторе Print я обращаюсь к элементу Variantмассива, который в свою очередь является элементом другого массива. Элемент vntMain(3)(l 7) выглядит, да и работает, как элемент двухмерного массива, но смысл индексации во многом отличен. Этот прием — эффективный способ создания многомерных массивов с разным числом размерностей и даже с разнотипными элементами.
Циклы For Each Тип Variant играет важную роль в циклах For Each...Next, позволяющих перебирать элементы набора (collection) или обычного массива. Допустимые типы переменной, служащей для доступа к каждому элементу, — Object или Variant. Перебирая элементы массива. Вы должны использовать переменную типа Variant, а перебирая элементы набора, можете использовать переменную типа Variant или Object.
ГЛАВА 3
Переменные
39
Гибкий тип для передачи параметров Тип Variant часто назначается параметрам — особенно для свойств объектов, которые позволяют устанавливать один из нескольких типов данных. Вы можете передать через Variant-переменную практически любые данные.
Функции, связанные с типом Variant Вам не помешает знать о нескольких функциях, полезных при работе с Variant-переменными. Функция TypeName возвращает строку, которая описывает текущее содержимое Variant-переменной. Семейство Is-функций — таких как IsNumeric и IsObject — позволяет быстро проверять содержимое Variant-переменной на соответствие определенному типу. Подробнее об этих и других функциях, связанных с Variant-переменными, см. справочную систему.
Empty и Null Вы должны хорошо понимать разницу между значениями Empty и Null переменной типа Variant. Она равна Empty, пока ей не присвоено значение какого-нибудь типа, но Null — специальный индикатор, сообщающий, что Variant-переменная не содержит значимых данных. Null можно присвоить переменной явно, и тогда результатом любой математической операции с ее участием тоже будет Null. Чаще всего это значение используется при работе с базами данных, где оно указывает на отсутствие какой-либо информации.
Приведение типов данных Тип Variant очень гибок, но требует крайне осторожного подхода к автоматическому преобразованию данных. Например, кое-что в следующей программе может оказаться для Вас неожиданным: Option Explicit Private Sub Form_Click{) Dim vntA, vntB vntA = "123" vntB = True Print vntA + vntB Print vntA & vntB Print vntA And vntB = 0 Print vntB And vntA = 0 End Sub
' ' ' '
122 123True 0 False
Первый оператор Print обрабатывает содержимое двух Variantпеременных как числовые значения, а второй — как строки. Последние два оператора Print выдают разные результаты из-за иерархии приоритетов операций, которая вовсе не очевидна. Максимум, что я могу посоветовать, — быть осторожным и тщательно прове-
40
ЧАСТЬ II
ДОРОГОЙ ДЖОН, КАК?..
рять написанный код; если в чем-то сомневаетесь, лучше напишите по-другому.
Работать со строками? В язык Visual Basic встроены пять новых строковых функций. ^ Replace — заменяет одну группу символов другой. Это резко упрощает замену всех вхождений строки в тексте (текстовом поле или других источниках текста). • Split — разбивает строку- на массив строк меньшей длины, или лексем (tokens), исходя из указанного разделителя (пробела, табулятора или другого символа). и Join — выполняет операцию, противоположную тому, что делает Split. Преобразует массив лексем в единую строку, где каждая лексема отделяется от соседней заданным разделителем. я Filter — используется совместно с функциями Split и Join. Ищет в массиве строк заданную подстроку и возвращает массив строк, содержащих эту подстроку. и InStrRev — в противоположность функции InStr — позволяет просматривать строку от конца к началу. Это полезно в программах, где нужно организовать операции поиска и замены в обоих направлениях: вперед (InStr) и назад (InStrRev). На рис. 3-6 показано окно программы StringFun.VBP, которая демонстрирует все новые строковые функции. Операции, выполняемые этой программой, описываются в следующих разделах. are cijle as they gambol and серег but thej> cunlusB their tunaues with tdlel pap
Sewnse
I
Rs*iee
Pot
<Ц*М|
Рис. 3-6 Поочередно щелкая кнопки в нижней части окна StringFun.VBP, Вы увидите демонстрацию работы каждой из пяти новых строковых функций
Замена символов в строке Одна из самых распространенных задач программирования — преобразование текста из одного формата в другой. В большинстве преобразований нужно заменять одни разделители (табуляторы,
ГЛАВА 3_ Переменные
41_
пробелы, скобки и т. д.) другими. Функция Replace обрабатывает операции такого типа «одним махом». Следующая процедура иллюстрирует, как с помощью Replace выполнить простейшее преобразование — заменить пробелы символами возврата каретки и наоборот. ' cmdReplace_Click использует Replace для глобального поиска и замены Private Sub cmdReplace_Click{) Static blnReplaced As Boolean ' заменяем пробелы символами возврата каретки If Not blnReplaced Then txtString = Replace(txtString, " ", vbCrLf) ' заменяем символы возврата каретки пробелами Else txtString = Replace(txtString, vbCrLf, " ") End If
blnReplaced = Not blnReplaced End Sub
Разбиение и объединение строк Такие операции — не редкость при извлечении информации из текстового поля и ее переупорядочивании. Используя функцию Split совместно с функцией Join, можно разбивать текст на отдельные слова, изменять их порядок и выводить на экран, как показано на следующем примере. ' cmdReverse, используя Split и Join, меняет ' порядок слов в текстовом поле на обратный Private Sub cmdHeverse_CHck() Dim strForward() As String Dim strReverse() As String Dim intCount As Integer Dim intUpper As Integer ' разбиваем текст на лексемы strForward = Split(txtString, " ") ' инициализируем другой массив intUpper = UBound(strForward) ReDim strReverse(intUpper) ' меняем порядок слов в strReverse на обратный For intCount = 0 То intUpper strReverse{intUpper - intCount) = strForward(intCount) Next ' соединяем лексемы в единый текст txtString = Join(strReverse, " ") End Sub
Главное назначение Split и Join заключается в преобразовании строк. Первая преобразует строку в массив лексем, а вторая — массив лексем в единую строку. Превратив строку в массив неких элементов, Вы можете переупорядочить его с помощью одной из стандартных процедур сортировки массивов либо извлечь определен-
42
ЧАСТЫ1
ДОРОГОЙ ДЖОН, КАК?..
ные элементы, используя фильтр, как описывается в следующем разделе.
Применение фильтров Функция Filter принимает массив строк и возвращает массив элементов, содержащих указанную подстроку. Обычно она используется совместно с функциями Split и Join при обработке списков элементов в текстовом поле или другом источнике текста. Показанная ниже процедура иллюстрирует, как «фильтровать» слова, вводимые в текстовом поле. ' cmdFilter_Click, вызывая Filter, отображает только те слова, ' в которых присутствует заданная группа символов; это позволяет 1 сужать список элементов, вводимых пользователем Private Sub cmdFilter_Click() Dim strFilter As String Dim strWords() As String ' получаем подстроку (группу символов) для поиска strFilter = InputBoxf'Show only words containing:") ' если ввод отменен, выходим If strFilter = "" Then Exit Sub 1 избавляемся от символов возврата каретки txtString = Replac0(txtString, vbCrLf, " ") ' 'преобразуем строку в массив отдельных слов strWords = Split(txtString, " ") ' получаем список слов, содержащих подстроку из strFilter strWords = Filter(strWords, strFilter) ' отображаем список в текстовом поле txtString = Join{strWords) End Sub
Поиск строк Поиск определенного слова или предложения в блоке текста стандартная операция, необходимая в текстовых процессорах, справочных системах и т. д. Функция InStr позволяет вести поиск заданной строки в блоке текста от его начала к концу, а с появлением функции InStrRev стал возможен и поиск в обратном направлении (от конца к началу). Следующие две процедуры демонстрируют эти виды поиска в текстовом поле. ' cmdBack_Click и cmdForward_Cllck ведут поиск заданной строки ' в текстовом поле с помощью InStr ("вперед") и InStrRev ("назад") ' и иллюстрируют приемы избирательной замены текста Private Sub cmdBack_Click{) Dim strFlnd As String On Error GoTo errNotFoundRev ' получаем строку для поиска strFind = InputBox("String to find:", "Search Backward") txtString.SelStart = InStrRev(txtString, strFind,
ГЛАВА 3
Переменные
43
txtString.SelStart) - 1 txtString.SelLength = Len(strFind) Exit Sub errNotFoundRev: MsgBox strFind & " not found.", vblnformat!on End Sub Private Sub cmdForward_Click() Dim strFind As String On Error GoTo errNotFoundFor ' получаем строку для поиска strFind = InputBox("String to find:", "Search Forward") txtString.SelStart = InStr(txtString.SelStart + 1, _ txtString, strFind) - 1 txtString.SelLength = Len(strFind) Exit Sub errNotFoundFor: MsgBox strFind & " not found.", vblnformation End Sub
Весьма странно, что у функций InStr и InStrRev разный порядок аргументов. В InStr позиция, с которой начинается поиск, указывается в первом аргументе, а в InStrRev — в третьем. Из-за такого разнобоя очень легко перепутать аргументы при совместном использовании этих функций.
Дорогой Джон; как.., Работать с объектами? Объектные переменные позволяют не только хранить информацию, но и выполнять операции. Поэтому работа с этими переменными требует учитывать ряд особенностей.
Новые объекты Объекты объявляются несколько иначе, чем остальные переменные. Visual Basic инициализирует большинство переменных при первом их использовании. Скажем, Вы объявляете оператором Dim переменную тина Integer и, как только впервые используете эту переменную, Visual Basic автоматически инициализирует ее нулевым значением. Объекты так не инициализируются. Если Вы объявите объектную переменную и проверите ее тип, то получите «Nothing*; Dim frmX As Forml Debug.Print TypeName(frmX)
' отображается "Nothing"
Объектная переменная ни на что не годна, пока Вы не создадите экземпляр соответствующего объекта. Для создания нового объекта предназначено ключевое слово New: Dim frmX As Forml Debug.Print TypeName(frmX)
' отображается "Nothing"
44 Set frmX = New Forml Debug.Print TypeName(frmX)
ЧДСТЫ1
ДОРОГОЙ ДЖОН, КАК?..
' отображается "Forml"
Простое объявление объектной переменной лишь создает переменную указанного типа, и, пока Вы не присвоите ей какой-либо объект, она содержит значение Nothing. Ключевое слово New можно использовать и в объявлении объектной переменной: Dim frmX As New Forml Debug.Print TypeName(frmX)
' отображается "Forml"
При таком объявлении ключевое слово New сообщает Visual Basic создать новый объект, как только он встретит ссылку на объектную переменную в исполняемом коде, а до тех пор установить ее как Nothing. В приведенном выше примере, поскольку оператор Dim не является исполняемым, Visual Basic откладывает создание объекта до выполнения оператора Debug.Princ. Эта тонкость может показаться не слишком значимой, но иногда дает неожиданные результаты. Так, в следующем фрагменте кода Вы не получите «Nothing», хотя, наверное, предполагали обратное: Dim frmX As New Forml Set frmX = Nothing Debug.Print TypeName(frmX)
' отображается "Forml"
На самом деле этот код создает экземпляр Forml в операторе Set и тут же уничтожает его. Потом, выполняя оператор Debug.Print, coздяет новый экземпляр объекта. Чтобы избежать подобных фокусов, не объявляйте объектные переменные с ключевым словом New. Если хотите получать ожидаемые результаты, делайте так: Dim frmX As Forml Set frmX = New Forml Debug.Print TypeNameffrmX) Set frmX = Nothing Debug.Print TypeName(frmX)
' ' ' '
создается экземпляр формы отображается "Forml" уничтожается экземпляр формы отображается "Nothing"
Как правило, синтаксис; Dim . . . As New следует применять лишь для создания объектов, которые будут существовать вплоть до выхода из области видимости того элемента программы (модуля, процедуры и т. д.), где они объявлены, а в остальных случаях используйте синтаксис: Dim ... As ... Set ... New . . .
Существующие объекты Объектная переменная ссылается на объект, но не является объектом. Оператор Set позволяет переназначить объект, на который ссылается объектная переменная;
ГЛАВА 3
Переменные
45
Dim frmX As New Forml Dim frmY As New Forml Set frmX = frmY
' создаем новый объект ' создаем еще один экземпляр ' frmX и frmY ссыпаются теперь на один 1 и тот же объект
Присваивая переменной ссылку на объект, следует применять именно Set, а не простое присвоение. Дело в том, что у большинства объектов есть свойство, используемое по умолчанию (default property), и Set указывает Visual Basic присвоить ссылку на объект, а не установить значение такого свойства.
Операции над объектами Объекты сравниваются оператором Is. Он сообщает, ссылаются ли две объектные переменные на один и тот же объект. Вот пример, иллюстрирующий этот оператор: Dim frmX As New Forml Dim frmY As New Forml Debug.Print frmX Is frmY Set frmX = frmY Debug.Print frmX Is frmY
' ' ' ' ' '
создаем новый объект создаем еще один экземпляр отображается False frmX и frmY ссылаются теперь на один и тот же объект отображается True
Уничтожение объектов Сколько живет объект? Пока не станет Nothing! Объекты не уничтожаются., пока существует хоть одна переменная, которая ссылается на них, или пока — как в случае с объектами-формами — они видимы, Обычно Вы можете управлять видимостью объекта через его свойство Visible. Чтобы избавиться от объекта, присвойте его переменной другой объект или Nothing: Dim frmX As New Forml Dim frmY As Object Set frmY = frmX Set frmX = Nothing Set frmY = Nothing
' ' ' ' '
создаем новый объект объявляем переменную типа Object frmX и frmY ссылаются теперь на один и тот же объект frmY все еще содержит ссылку, 1 и объект пока существует ' объект уничтожается
По крайней мере, так должно быть. Но некоторые объекты не столь корректны. Ранние версии отдельных объектов (в том числе и от Microsoft) самоуничтожались отнюдь не всегда. Сейчас объекты «стали получше*, но все равно следует быть очень внимательным, работая с объектами, не имеющими визуального представления. В общем, я бы сказал таю объекты, создаваемые в Visual Basic, ведут себя корректно, а объекты в других продуктах — обычно корректно. «О Си. также... • Главу 5 «Объектно-ориентированное программирование* — подробнее об объектах.
ЧАСТЬ II ДОРОГОЙ ДЖОН, КАК?.
46
Работать с предопределенными константами? В Visual Basic фактически несколько видов констант, включая определенные в операционной системе.
Константы компилятора Если Вам нужно приложение как в 16-, так и в 32-разрядной версии, используйте Visual Basic 4 — только эта версия позволяет создавать программы для Windows 3-1 (16-разрядные), Windows 95 и Windows NT 3.51 (32-разрядные). Чтобы создать приложение, выполняемое и в 16-, и в 32-разрядной операционной системе, необходимы константы компилятора Win 16 и Win32. Они дают возможность определять, в какой операционной системе Вы разрабатываете свое приложение, и выбирать соответствующие блоки кода (удовлетворяющие заданным условиям). Эти константы указываются в директивах *If...*Then...*Else и позволяют компилировать лишь избранные блоки кода. Visual Basic версий 5 и 6 по-прежнему поддерживает эти константы, но Win32 всегда True, a Winl6 всегда False, так как эти версии работают исключительно в 32-разрядных операционных системах Windows.
Константы Visual Basic В Visual Basic громадный список предопределенных констант практически на все случаи жизни. Чтобы увидеть доступные константы, щелкните кнопку Object Browser, в верхнем списке выберите VBA или VB, а в списке классов — нужную группу констант. Теперь в списке компонентов (справа) будут перечислены константы с соответствующим значком и префиксом vb, как показано на рис. 3-7.
Рис. 3-7 Встроенные константы, показываемые в окне Object Browser
[JIABflj Переменные
47
Заметьте, что у многих компонентов тоже есть свои константы, и это окно — именно то место, где с ними можно ознакомиться. Вы обнаружите, что почти для каждого элемента управления и параметра функции предопределены свои константы, например: vbModal и vbModeless для метода Show формы, vbRed, vbBlue и vbGreе.п для обозначения цветов, применяемых в большинстве графических методов, и даже vbCr, vbLf, vbCrL/тл vbTab для часто используемых неотображаемых символов. Следующий фрагмент кода иллюстрирует, как включить в текстовую строку символы возврата каретки (Сг) и перевода строки (Lf): strA = "Line one" & vbCrLf & "Line two" Начиная писать числовое значение в качестве аргумента свойства или метода, остановитесь и проверьте, не определена ли в системе константа, которую Вы могли бы использовать вместо числового значения. Это поможет сделать программу «читабельнее» и удобнее в сопровождении.
Пользовательские константы Как и в предыдущих версиях Visual Basic, Вы можете определять в программе собственные константы с помощью ключевого слова Const, а также создавать константы условной компиляции, используя директиву *Const (она, хоть и похожа на ключевое слово Const, предназначена совсем для других целей). Константы, созданные директивой *Const, применяются только в конструкциях *If...*Then... *Else. И наоборот, константы, определенные ключевым словом Const, не могут быть константами условной компиляции. Некоторые из предопределенных констант очень полезны при формировании сложных строковых констант. Так, в следующем примере константа выводится на форму как колонка цифр. В предыдущих версиях Visual Basic такой формат был бы невозможен без написания дополнительного кода, а сейчас достаточно единственного оператора. Option Explicit Private Sub Form_CHck() Const DIGITS = _ "1" & vbCrLf & _ "2" & vbCrLf & _ "3" 8, vbCrLf & _ "4" & vbCrLf & _ "5" & vbCrLf & _ "6" & vbCrLf & _ "7" & vbCrLf & _ "8" i VbCrLf & _ "9" & vbCrLf Print DIGITS End Sub 3—1204
48
ЧАСТЬ II
ДОРОГОЙ ДЖОН, КАК?..
Определить диапазон действия констант, объявленных на уровне модуля, позволяют ключевые слова Private и Public. Константы, объявленные как Private, видимы лишь внутри модуля, а Public-константы доступны во всем проекте. Эти ключевые слова не годятся для определения констант внутри процедур. Константа, объявленная в процедуре, всегда видима только в этой процедуре.
Перечислимые типы Перечислимые типы (Enum — для краткости) дают возможность связывать целые значения с именами. Это полезно в процедурахсвойствах. Например, у свойства SpellOption могут быть такие допустимые значения: 1 объявления на уровне модуля класса MailDialog Public Enufn CheckedState Unchecked ' перечисление по умолчанию начинается с О Checked '1 Grayed ' 2 и т. д. CantDeselect = 255 ' (можно указывать и специфические значения) End Enum Используя этот Enum в объявлении параметров, можно определить свойства, которые устанавливают или возвращают состояние флажков в диалоговых окнах: Public Property Let SpellOption(Setting As CheckedState) ' устанавливаем состояние флажка Check Spelling 1 (проверять правописание) frmSendMail.chkSpelling.Value = Setting End Property Public Property Get SpellOptionO As CheckedState 1 возвращаем состояние флажка Check Spelling SpellOption = frmSendMail.chkSpelling.Value End Property Enum можно использовать в любых модулях. В Object Browser они появляются в списке классов, а набор их допустимых значений - в списке компонентов. Но в других проектах видимы только Enum, объявленные как Public в открытых модулях классов.
Флаги и битовые маски Логические операции And, Or и Not чаще всего применяют для комбинирования логических сравнений в управляющих конструкциях. Например, оператор If с помощью операции And позволяет проверить несколько условий: If blnExit And Not blnChanged Then End Логика — это прекрасно, но по-настоящему хороший оператор должен допускать и побитовые операции, т. е. сравнение каждого
ГЛАВА 3
Переменные
49
бита двух чисел. Побитовые операции очень удобны, если Вы хотите включить несколько смысловых значений в одно числовое — флаг. Хороший пример флага — свойство Flags диалогового окна Print (печать): DlgPrint.Flags = cdlPDCollate Or cdlPDNоSelection В большинстве случаев комбинирование флагов операцией Or эквивалентно их сложению (+). Однако, если значения флагов конфликтуют. Вы получите при сложении другой результат, например, 1 + 1 = 2, a l Or 1 = 1. Этого не произойдет, если значения флагов хорошо продуманы, но операция Or все же безопаснее. Обнулить флаги или проверить значение отдельного флага позволяет операция And. Этот код проверяет, выбрана ли в диалоговом окне Print печать в файл: 1 проверяем, печатать ли в файл If dlgPrint.Flags And cdlPDPrintToFile Then
Здесь с помощью операции And проверяется, установлен ли 5-й бит (значение константы cdlPDPrintToFile равно &Н20); если да, выражение дает True. При такой проверке флагов используется битовая маска — набор проверяемых битов, например 01101101 в двоичном виде или &H6D в шестнадцатеричном. Если в проверяемом значении биты установлены так же, как в маске, то говорят, что маска «подходит» значению. Именно маскированием мы и преобразовывали целые со знаком в беззнаковые значения. Целые со знаком принимают значения от &Н8000 до &H7FFF (или от -32 768 до +32 767 в десятичном виде). Маскируя определенные биты, можно преобразовать целое со знаком в беззнаковое: intShort = (IngLonQ And &H7FFF&) - (IngLong And &H8000&)
Создавать UDT-структуры? Для объявления UDT-структуры данных предназначено ключевое слово Туре. Заметьте, что оператор Туре не создает структуру, а только описывает ее. Создание переменных — экземпляров структуры осуществляется оператором Dim. Определить отарытую UDT-структуру можно в стандартном модуле или в открытом модуле класса. Внутри модуля формы или класса Вы должны определять ее только как закрытую (Private) для данного модуля. Комбинация с типом Variant дает удивительно гибкие структуры. Некоторые возможности демонстрирует этот фрагмент кода: Option Explicit Private Type typX
50
ЧАСТЬ И
ДОРОГОЙ ДЖОН, КАК?..
а<) As Integer b As String с As Variant End Type
Private Sub Form_Click() ' создаем переменную типа typX Dim X As typX 1 изменяем размер динамического массива внутри структуры ReDim X.a(22 То 33) ' присваиваем значения полям структуры Х.а(27) = 29 X.b = "abc" ' вставляем целый массив в структуру Dim y(100) As Double у(ЭЭ) = 4 * Atn(1) Х.с = у() 1 проверяем отдельные элементы структуры Print X.a(27) ' 29 Print X . b ' abc Print Х.с(ЗЗ) ' 3.14159265358979 End Sub
Обратите внимание на третье поле структуры typX, объявленное как Variant, и вспомните, что этот тип позволяет хранить практически любые значения, а следовательно, в период выполнения можно создать массив и вставить его в UDT-структуру, присвоив полю типа Variant.
Выравнивание Стремясь во всем соответствовать стандартам 32-разрядной операционной системы, Visual Basic 6 выравнивает элементы UDT-структур по границам, кратным 4 байтам, — этот процесс называют также выравниванием по двойным словам (DWORD alignment). Таким образом, объем памяти, в действительности занимаемый структурой, может оказаться больше, чем ожидалось. Но еще важнее то, что старый трюк с передачей оператором LSet двоичных данных из одной UDT-структуры в другую может сработать некорректно. Вы должны понимать, что здесь кроется потенциальный источник проблем, и, прежде чем прибегнуть к каким-либо трюкам, тщательно тестируйте код. Иначе могут появиться такие ошибки, которые потом очень трудно отловить.
Создавать новые типы данных с помощью классов? Для начала немного терминологии. Классы, или модули классов, определяют объекты — точно так же, как UDT-структуры определяют
ГЛАВА 3
Переменные
51
пользовательские типы данных. Реальный объект — это экземгшяр класса. Свойства класса определяют, что будет хранить объект, а методы — какие операции он сможет выполнять. Классы могут быть и простыми, и очень сложными. Начинать, конечно, лучше с простого. Здесь Вы узнаете, как создать беззнаковый целый тип данных, используя модуль класса. Более сложные аспекты ООП см. в главе 5 «Объектно-ориентированное программирование».
Создание нового типа данных В первом разделе этой главы я показал, как эмулировать в Visual Basic беззнаковые целые. Включите тот код в класс UInt, и Вы создадите новый ^фундаментальный» тип данных для работы с беззнаковыми целыми. Объявления на уровне модуля класса UInt, показанные ниже, определяют UDT-структуры и закрытые переменные для хранения старшего и младшего байтов целого значения. Считается хорошим тоном описывать назначение класса, а также включать комментарии в текст его модуля. Это делает класс более понятным и пригодным для повторного использования. ' уровень модуля класса UInt ' реализует беззнаковый целый тип ' методы: Signed (возвращает целое значение со знаком) 1 свойства: Value (по умолчанию) HiByte LoByte ' UDT-структуры для возврата старшего и младшего байтов Private Type UnsignedlntType lo As Byte hi As Byte End Type Private Type SignedlntType n As Integer End Type ' внутренние переменные для младшего и старшего байтов Private mnValue As SignedlntType Private muValue As UnsignedlntType
Класс использует закрытую переменную mnValue уровня модуля для хранения фактического значения свойства, но доступ к ней контролируется процедурами-свойствами Let Value и Get Value, показан-
52
ЧАСТЬ II
ДОРОГОЙ ДЖОН. КАК?..
ными ниже. Эти процедуры выполняют и преобразования, необходимые для представления беззнакового целого. Property Let Value(lngln As Long) mnValue.n = (Ingln And &H7FFFi) - (Ingln And ШООО&) LSet muValue = mnValue End Property Property Get ValueO As Long Value = mnValue.n And &HFFFF& End Property
У каждого свойства класса UInt есть Let-процедур а, где свойству присваивается значение, и Get-процедура, которая возвращает значение свойства. Именно так и реализованы свойства HiByte и LoByte: Property Let HiByte(bytIn As Byte) muValue.hi = bytln LSet mnValue = muValue End Property Property Get HiByteO As Byte HiByte = muValue.hi End Property Property Let LoByte(bytIn As Byte) muValue.lo = bytln LSet mnValue = muValue End Property Property Get LoByteQ As Byte LoByte = muValue.lo End Property
И, наконец, у класса UInt имеется открытая функция, которая возвращает целое значение со знаком: Function SignedQ As Integer Signed = mnValue.n End Function
Использование нового типа данных Перед работой с классом UInt не забудьте объявить соответствующую объектную переменную с ключевым словом New — это создаст новый экземпляр класса. Dim uNewVar As New UInt Свойство Value класса UInt — используемое по умолчанию. (Об определении свойства, используемого по умолчанию, см. главу 5 «Объектно-ориентированное программирование».) Поэтому имя свойства Value можно опустить, как здесь:
ГЛАВА 3
Переменные
53
устанавливаем значение свойства, используемого по умолчанию uNewVar = 64552
А к свойствам HiByte и LoByte Вы обращаетесь так же, как и к любому другому свойству в Visual Basic: 1
устанавливаем старший байт uNewVar.HiByte = 4HFF 1 получаем значение младшего байта Print Hex(uNewVar.LoByte)
Г Л А В А
Параметры 11араметры в Visual Basic бывают обязательные и необязательные. Они принимают данные или одного типа или любого, включая пользовательские типы, массивы и перечислимые константы (Enum). Такая гибкость поневоле требует мудрого подхода к определению параметров для того, чтобы не передавать в процедуры недопустимых значений. В этой главе я расскажу, как и когда использовать именованные и необязательные параметры, а также как объявлять типы данных в заголовках процедур. Большая часть этих языковых средств была добавлена и усовершенствована еще в прошлых версиях Visual Basic. Хоть они и не новы для нас, все же большинство программистов, вероятнее всего, не настолько широко применяют их в повседневной работе и могут просто не знать о некоторых их особенностях и возможностях. ПРИМЕЧАНИЕ В этой главе аргументом считается константа, переменная или выражение, передаваемые процедуре, а параметром — переменная, которая объявлена в заголовке процедуры и которой присваивается аргумент при передаче его в процедуру.
Использовать именованные аргументы? В Visual Basic (как, впрочем, и почти в любом языке программирования) принято передавать аргументы по позициям соответствующих параметров в заголовке процедуры, разделяя их запятыми. Так, если Вы пишете процедуру, принимающую параметры, допустим, sngRed, sngGreen и sngBlue, то передаете ей эти три значения и подразумеваете, что первый аргумент всегда представляет sngRed, второй — sngGreen, а последний — sngBlue. Теперь посмотрим на показанную ниже процедуру и вызывающий ее код. Я использовал так называемые именованные аргументы, и это позволило передать их в обратном порядке! Option Explicit Private Sub FormColor(sngRed As Single, sngGreen As Single,
ГЛАВА 4
Параметры
55
sngBlue As Single) BackColor = RGB(sngRed * 256, sngGreen * 256, sngBlue * 256) End Sub
Private Sub Form_Click<) FormColor sngBlue:=0, sngGreen:=0.5, sngRed:=1 End Sub
' коричневый
Истинная ценность именованных аргументов не только в возможности их передачи в произвольном порядке. Они разрешают четко указывать и передавать подмножество параметров в процедуру с большим числом параметров. Но последнее преимущество именованных аргументов проявляется, только когда отдельные параметры процедуры необязательны, а это подводит нас к следующему вопросу.
Использовать необязательные параметры? Необязательные параметры особенно полезны, когда у процедуры длинный список параметров. Слегка модифицируем предыдущий пример и посмотрим, как это все работает: Option Explicit Private Sub FormColor( _ Optional sngRed = 0, _ Optional sngGreen = 0, _ Optional sngBlue = 0 _ ) BackColor = RGB{sngRed * 256, sngGreen * 256, sngBlue * 256} End Sub Private Sub Form_Click{) FormColor sngGreen:=0.5 End Sub
' зеленый цвет средней насыщенности
Добавив ключевое слово Optional к объявлениям параметров в процедуре FormColor, мы получили возможность передавать все или отдельные параметры как именованные аргументы, четко указывая, какие, собственно, параметры передаются. В этом примере я передал лишь параметр sngGreen, зная, что по умолчанию остальные параметры получат нулевое значение. Заметьте, что значения по умолчанию определяются при объявлении параметров. В Visual Basic 4 каждый необязательный параметр приходилось проверять вызовом функции IsMissing и лишь потом можно было присвоить ему значение по умолчанию. Теперь необязательные параметры могут быть не только типа Variant. Но в том случае, когда какой-то параметр объявлен с ключевым словом
56
_
ЧАСТЬ
II
ДОРОГОЙ
ДЖОН,
КАК?.
Optional, все последующие в списке параметры тоже должны быть необязательными. ьО См. также... • Приложение Metric в главе 30 «Средства разработки*, в котором демонстрируется применение необязательных параметров.
Передавать параметры-массивы? Если Вы объявите последний параметр процедуры как массив Variant-переменных с ключевым словом ParamArray, то сможете передавать переменное число аргументов. Следующая программа демонстрирует этот прием — она преобразует любые аргументы, передаваемые функции MakeVerticalList, в строку, содержащую их список в формате с вертикальным расположением: Option Explicit Private Function MakeVerticalList(ParamArray vntNQ) As String Dim strA As String, i As Integer For i = LBound(vntN) To UBound(vntN) strA = strA + vbCrLf + CStr(vntN(i)) Next i HakeVerticalList = strA End Function Private Sub Form_Click() Dim intA As Integer Dim sngB As Single Dim strC As String intA = 123 sngB = 3.1416 strC = "This is a test." Print MakeVerticalListCA, В, С) Print Print MakeVerticalList("This", "time", "we'll", "pass five", "string arguments.") End Sub
Заметьте, что я вызывал MakeVerticalList дважды: первый раз с 3 аргументами разных типов, а второй — с 5 строковыми аргументами. Результаты, выводимые программой (Вы должны щелкнуть форму), показаны на рис. 4-1.
Передавать в параметре произвольный тип данных? Вспомним, что переменные типа Variant могут содержать данные практически любого типа, какой только можно себе представить.
ГЛАВА 4
57
Параметры
Это позволяет передавать данные произвольного типа, если параметр объявлен как Variant. Здесь я передаю процедуре VariantTest целое, строку и массив: Option Explicit Private Sub VariantTest(vntV) Print TypeNanie(vntV) End Sub
Private Sub Form_Click() Dim dblA(3, 4, 5, 6, 7) As Double VariantTest 123 VariantTest "This is a test string" VariantTest dblA End Sub
Variant-параметр vntV — единственный параметр процедуры VariantTest — принимает все, что ни передай. А функция TypeName сообщает тип переданного значения. Результаты выполнения этой программы показаны на рис. 4-2.
Рис. 4-1 Результаты передачи в функцию MakeVerticalList сначала трех, а затем пяти аргументов
Рис. 4-2 Результат работы процедуры, использующей параметр типа Variant U""lt ™" вг~ *•
-
tK~"ffi
f
Использовать в параметрах перечислимые типы? Перечислимый тип позволяет ограничить диапазон допустимых значений параметра. Он определяет набор символических значе-
58
ЧАСТЬ II
ДОРОГОЙ ДЖОН, КАК?..
ний — почти так же, как и Const, — но название перечислимого типа можно включить в объявление параметра процедуры. При этом, когда Вы будете писать оператор, который вызывает такую процедуру, средство Auto List Members покажет список допустимых значений для аргумента. Чтобы увидеть, как это происходит, наберите КОД: Enurn Number Zero One
Two Three End Enurn Sub ShowNumberCValue As Number) HsgBox Value End Sub Sub Form_Load() ShowNumber One End Sub
Стоит лишь в процедуре обработки события Form_Load ввести ShowNumber, как Visual Basic откроет список значений перечислимого типа, из которого можно выбрать нужное. Нечто похожее происходит, когда перечислимый тип используется в списке параметров какого-либо свойства нестандартного элемента управления. Следующий код создает для такого элемента управления свойство Value и показывает его в окне свойств; Enum Number Zero
One Two End Enum
Dim mValue As Number Property Get ValueO As Number Value = mValue End Property Property Let Value(Setting As Number) mValue = Setting End Property Private Sub UserControl_ReadProperties(PropBag As PropertyBag) mValue = PropBag.ReadPropertyC'Value", One) End Sub Private Sub UserControl_WriteProperties(PropBag As PropertyBag)
ГЛАВА 4
Параметры
PropBag.WriteProperty "Value", mValue, One End Sub
Процедуры обработки событий ReadProperties и WriteProperties обеспечивают работу со свойством Value в окне Properties (свойства) данного элемента управления. Выбрав в нем свойство Value, Вы увидите список допустимых значений перечислимого типа Number: Zero, One и Two (рис. 4-3).
Рис. 4-3 Список допустимых значений свойства Value, открываемый в окне Properties Еще один бит полезной информации о перечислимых типах — они автоматически приводят значения с плавающей точкой к длинным целым. Например, если процедуре ShowNumber передать значение 0.9, она покажет 1. Перечислимые типы не предусматривают какого-то особого контроля типов, чудно принимая и недопустимые значения, поэтому Вы должны сами заботиться о проверке входных значений.
Г Л А В А
Объектно-ориентированное программирование JL.1з всех возможностей Visual Basic наиболее важна, пожалуй, поддержка настоящего объектно-ориентированного программирования (ООП). Это обширная тема, которой уделяется немалое внимание во многих руководствах Microsoft. Я не стану их пересказывать, а рассмотрю основные концепции ООП, иллюстрируя их несложными примерами. Так у Вас появится база для самостоятельного изучения этой темы. ООП в Visual Basic — вещь просто очаровательная. Модули классов позволяют структурировать программы ранее недоступными способами, наборы — формировать гибкие структуры данных для хранения создаваемых объектов и операций над ними, а технология ActiveX обеспечивает разделение объектов между приложениями и их (объектов) повторное использование. Вы увидите, насколько ООП упрощает структуру больших и сложных проектов. Если Вы только-только приступаете к изучению средств Visual Basic, связанных с ООП, Вас ждет море новой информации. Немного настойчивости, экспериментов с примерами из этой или другой книги, и Вы освоитесь. Со мной это случилось, когда я вдруг почувствовал, что создавать объекты чертовски увлекательно! В этой главе мы с Вами создадим образцы объекта и ЕХЕ-сервера на базе ActiveX, рассмотрим один из способов работы с наборами объектов и познакомимся с полиморфизмом и дружественными методами.
Выбрать между ЕХЕ- и DLL-сервером ActiveX? EXE- и DLL-серверы ActiveX позволяют включать объекты в компоненты, способные предоставлять эти объекты другим приложениям через Автоматизацию (Automation). Клиентское приложение, которое создает и использует экземпляры объектов, предоставляемые ЕХЕ-сервером ActiveX, оперирует с ними как с внешними (oul-of-process), т. е. клиент и сервер исполняются в разных процессах.
ГЛАВА 5
Объектно-ориентированное программирование
61
Что касается DLL-сервера ActiveX, то он не может работать как автономное приложение и предоставляет клиентскому приложению динамически подключаемую библиотеку объектов, с которыми клиент взаимодействует как с внутренними (in-process). Таким образом, код DLL-сервера ActiveX выполняется в том же процессе, что и клиентское приложение, и программа работает быстрее и эффективнее. Оба типа ActiveX-компонентов одинаково полезны, и Visual Basic позволяет создавать как те, так и другие, не прибегая к С или каким-то иным языкам.
Разместить все свои объекты во внешних ActiveX-компонентах? А это и не нужно! Вокруг объектов в ActiveX-компонентах много шумихи — не без причин, надо сказать, — но создание собственных объектов лучше начать с простого добавления модулей классов к проектам обычных исполняемых программ. Объекты, определенные в модулях классов проекта, доступны только в нем. (Если Вы хотите, чтобы экземпляры Ваших объектов можно было создавать и из других приложений, не спешите — мы еще вернемся к ActiveX-компонентам.) Ваша программа может создать один или несколько экземпляров любого объекта, определенного в модуле класса, при этом каждый экземпляр получает свой набор данных. Одно из преимуществ этих объектов в том, что память, занятая каждым экземпляром, освобождается сразу после разрушения объекта. Но самое важное, что принесло нам ООП, — улучшение структуры программ, организации и «читабельности» их кода. Избавление Бейсика от номеров строк около десяти лет назад было первым крупным шагом в верном направлении, и введение в язык объектов — событие не меньшего масштаба. В следующих двух разделах мы рассмотрим сравнительно несложный пример объекта, создаваемого в стандартной исполняемой программе. Это простейший способ начать работу с объектами.
Создать новый объект? Если кратко — создать модуль класса. Модули классов открывают новые горизонты в структурировании программ. Все тонкости работы с модулями классов и объектами, создаваемыми с их помощью, подробно описаны в руководствах и справочной системе. Я же приведу простой пример — лишь для того, чтобы пробудить в Вас интерес и сориентировать в дальнейшем обучении. Кстати, в книге довольно много рабочих примеров модулей классов; они
62
ЧАСТЫ1
ДОРОГОЙ ДЖОН, КАК?..
позволяют создавать структурированный код, и мне нравится использовать их.
Пример модуля класса: Loan Чтобы продемонстрировать многие важные особенности объектов, я написал сравнительно простой модуль класса, позволяющий создавать экземпляры объекта Loan. Он предназначен для составления графика платежей в течение всего срока займа (loan). Вы можете добавить к этому классу новые методы и свойства или изменить его реализацию, я же стремился максимально упростить его и подготовить нечто вроде наглядного пособия. Чтобы создать модуль класса, откройте новый проект стандартной ЕХЕ-программы, выберите из меню Project команду Add Class Module (добавить модуль класса) и в появившемся диалоговом окне дважды щелкните значок Class Module. Наберите код, приведенный ниже, измените значение свойства Name на Loan и сохраните модуль как файл LOAN.CLS. 1
LOAN.CLS - модуль класса, матрица объектов Loan
Option Explicit ' эта переменная известна лишь в самом модуле класса Private mintMonths As Integer ' срок займа в месяцах свойство для чтения и записи: Principal Public Principal As Currency свойство для чтения и записи: AnnuallnterestRate Public AnnuallnterestRate As Single свойство для чтения и записи: Months эта процедура устанавливает значение свойства Months Property Let Months(intMonths As Integer) mintMonths = intMonths End Property эта процедура возвращает текущее значение свойства Months Property Get MonthsO As Integer Months = mintMonths End Property свойство для чтения и записи: Years эта процедура устанавливает значение свойства Years Property Let Years(intYears As Integer) mintMonths = intYears * 12 End Property эта процедура возвращает текущее значение свойства Y e a r s Property Get Years() As Integer Years = RounddnintHonths / 12#, 0)
ГЛАВА 5 Объектно-ориентированное программирование
63
End Property " свойство только для чтения: Payment эта процедура возвращает текущее значение свойства Payment Property Get PaymentO As Currency Dim sngMonthlylnterestRate As Single ' проверяем, заданы ли значения всех свойств If PropertiesAreLoadedO Then sngMonthlylnterestRate = Annuallnterestfiate / 1200 Payment = (-sngMonthlylnterestRate * Principal) / _ ({sngMonthlylnterestRate + 1) A (-mintHonths) - 1) Else Payment = 0 End If End Property 1
свойство только для чтения: BalanceC) ' эта процедура возвращает массив ежемесячных остатков по займу Property Get Balance() As CurrencyO Dim intCount As Integer Dim curPayment As Currency ReDim curBalance(O) As Currency If PropertiesAreLoadedO Then ReDim curBalance(mintMonths) curBalance(O) = Principal curPayment = Round(Payment, 2) ' округляем до ближайшего пенни For intCount = 1 To mintHonths curBalance(intCount) = curBalance(intCount - 1} * _ (1 + AnnuallnterestRate / 1200) curBalance(intCount) = curBalance(tntCount) - curPayment curBalance(intCount) = Round(curBalance(intCount), 2) Next intCount End If Balance = curBalance End Property метод: Reset ' заново инициализирует все свойства Public Sub ResetC) Principal = 0 AnnuallnterestRate = 0 mintHonths = 0 End Sub ' закрытая функция, которая проверяет, присвоены ли значения всем ' свойствам Private Function PropertiesAreLoadedO As Boolean If Principal > 0 And AnnuallnterestRate > 0 And mintMonths > 0 Then PropertiesAreLoaded = True
64
ЧАСТЫ1
ДОРОГОЙ ДЖОН, КАК?..
End If End Function
Объекты Loan, как и другие объекты, обладают свойствами и методами. Простейший способ создать свойство для чтения и записи — объявить открытую переменную. Principal и AnnuallnterestRate, объявленные в верхней части листинга LOAN.CLS, как раз и являются такими свойствами: Public Principal As Currency Public AnnuallnterestRate As Single
Одна переменная — mintMonths — объявлена закрытой (как Private), так что извне о ней никто не подозревает, а Вы можете быть уверены, что ее значение полностью контролируется внутренним кодом объекта Loan. Private mintMonths As Integer
' срок займа в месяцах
ПРИМЕЧАНИЕ Вас, наверное, заинтересовал префикс mint в имени этой переменной. Частица т указывает, что переменная объявлена на уровне модуля, а частица int — что у нее тип Integer. В то же время Вы, вероятно, хотите спросить, почему открытым переменным не присвоено префиксов по венгерской нотации. Дело в том, что открытые переменные ведут себя как свойства, а по принятым стандартам в именах свойств, открываемых внешнему миру, следует избегать префиксов. Все встанет на свои места, когда Вы вспомните, что весь смысл префиксов — улучшить читаемость внутреннего кода в модуле класса или каком-то другом модуле. С другой стороны, свойства предоставляются внешним пользователям, и здесь принципиально важнее другое: имя должно отражать назначение свойства. Переменная mintMonths содержит значение — срок займа в месяцах. Я мог бы объявить эту переменную открытой, но предусмотрел у объекта Loan два взаимосвязанных свойства (Years и Months), каждое из которых позволяет определить срок займа. А они устанавливают в коде значение mintMonths. Рассмотрим, как это делается. Оператор Property Let дает возможность определить свойство, реагирующее на присвоение ему значения. Простые свойства вроде Principal просто принимают указанные значения, но свойство Months определено так, что можно добавить код, выполняемый всякий раз, когда этому свойству присваивается значение. В нашем случае это простой оператор присвоения числа месяцев локальной переменной mintMonths: Property Let Months(intMonths As Integer) mintHonths = intMonths End Property
ГЛАВА 5
Объектно-ориентированное программирование
65
Операторы Property Let и Property Get образуют в паре одно свойство объекта, доступное и для чтения, и для записи. Так, оператор Property Get Months обеспечивает доступ к текущему значению свойства Months: Property Get HonthsO As Integer Months = mintMonths End Property
Остановитесь и поразмыслите над тем, что я сейчас сделал. Извне свойство Months представляется простой переменной, которая запоминает присвоенное ей значение и возвращает его при обращении к данному свойству. Но посмотрев на реализацию свойства Months в модуле Loan, Вы увидите, что Months — не просто переменная. Запись или чтение этого свойства активизирует код (его пишете Вы), позволяющий выполнить практически все, что только можно представить. Если оставить оператор Property Let без парного ему оператора Property Get, соответствующее свойство будет доступно лишь для записи. И наоборот, Property Get без парного ему Property Let определяет свойство, доступное только для чтения. Это полезно для некоторых свойств и относится, например, к свойству Payment, о котором я расскажу чуть позже. Свойство Years, показанное ниже, — альтернативный способ определить срок займа. Значение, присвоенное свойству Years, заметьте, умножается на 12 и тем самым преобразуется в месяцы. Эта операция скрыта от пользователя. Property Let Years(intYears As Integer) mintMontns = intYears * 12 End Property Property Get YearsO As Integer Years = Round{mintMonths / 12», 0) End Property
ПРИМЕЧАНИЕ Функция Round — новинка Visual Basic 6. Я применил ее в процедуре Property Get Years для округления вычисляемой величины до целого значения, т. е. до ближайшего числа полных лет. Сравните процедуры-свойства Years и Months, и Вы поймете, зачем я ввел локальную переменную mintMonths для хранения срока займа независимо от свойств, которые устанавливают это значение. Еще раз повторю: реализация свойств Years и Months инкапсулирована в объекте Loan и подробности того, как эти свойства справля-. ются со своими функциями, скрыты от пользователей объекта. Обращение к свойству Payment объекта Loan запускает достаточно сложные вычисления, и сумма платежа рассчитывается, исходя
66
ЧАСТЬ II
ДОРОГОЙ ДЖОН, КАК?..
из текущих значений других свойств. Этот пример, кстати, четко иллюстрирует, зачем нужны операторы Property Let/Get, когда свойство можно создать и простым объявлением открытой переменной в модуле класса. Property Get Payment() As Currency Dim sngMonthlylnterestRate As Single ' проверяем, заданы ли значения всех свойств If PropertiesAreLoadedO Then sngMonthlylnterestRate = AnnuallnterestRate / 1200 Payment = (-sngMonthlylnterestRate - Principal) / A ((sngMonthlylnterestRate + 1} C-mintMonths) - 1) Else Payment = 0 End If End Property
Свойство Balance доступно только для чтения, так как я не написал для Property Get парный ему Property Let. Property Get BalanceO As CurrencyO Dim intCount As Integer Dim curPayment As Currency ReDifn curBalance(O) As Currency If PropertiesAreLoadedO Then ReDim curBalance(mintHonths) curBalance(O) = Principal curPayment = Round(Payment, 2} ' округляем до ближайшего пенни For intCount = 1 To mintMonths curBalance(intCount) = curBalance(intCount - 1) * _ (1 + AnnuallnterestRate / 1200) curBalance(intCount) = curBalance(intCount) - curPayment curBalance(intCount) = Round(curBalance(intCount), 2) Next intCount End If Balance = curBalance End Property
Свойство Balance демонстрирует две новинки Visual Basic 6. Кроме новой функции Round, используемой и в других местах класса Loan, обратите внимание на свойство Balance, которое возвращает числовой массив, а не единственное значение. Присмотритесь к объявлению Property Get для этого свойства. Тип возвращаемого значения определен как As Currency(). Круглые скобки означают, что возвращаемое значение является массивом. В коде свойства Balance я объявляю локальный динамический массив типа Currency — curBalance — и после его переопределения и заполнения присваиваю переменной Balance, имя которой совпадает с именем свойства. Вот и все! Массив ежемесячных остатков по займу возвращается вызывающей программе.
ГЛАВА 5
Объектно-ориентированное программирование
67
Если что-то пойдет не так (например, пользователь забудет инициализировать какое-нибудь свойство), Balance вернет массив с нулевой размерностью и единственным нулевым значением. Это простейший способ обработки возможных ошибок. Я ведь хотел показать простой и ясный пример объекта, а не то, как лучше обрабатывать ошибки. Методы, как и свойства, — важная часть интерфейса объекта, предоставляемого внешним пользователям. Чтобы Вы прочувствовали, как они работают, я добавил в объект Loan один простой метод, Reset. Это обыкновенная Sub-процедура, обнуляющая значения всех свойств объекта Loan. Вам незачем вызывать этот метод, если только Вы не используете один и тот же экземпляр объекта в нескольких циклах вычислений. Public Sub ResetQ Principal = О AnnuallnterestRate = О mintMonths = О End Sub
ПРИМЕЧАНИЕ Обработчики событий Class^Initialize и Class_ Terminate позволяют автоматически инициализировать переменные и выполнять любые другие операции в момент создания и разрушения объекта. И, наконец, чтобы показать, как пишутся процедуры, скрытые от внешних пользователей, я включил в свой объект закрытую функцию Pro per tiesAre Loaded. Такие процедуры можно вызывать только из кода внутри класса. В данном случае мне нужно было проверять состояние свойств объекта в нескольких местах и я выделил соответствующий код в одну вспомогательную процедуру. ' закрытая функция, которая проверяет, присвоены ли значения ' всем свойствам Private Function PropertiesAreLoaded() As Boolean If Principal > 0 And AnnuallnterestRate > 0 And mintMonths > 0 Then PropertiesAreLoaded = True End If End Function
ПРИМЕЧАНИЕ Если Вы явно не присвоите возвращаемое значение процедуре типа Function, она вернет значение по умолчанию, принятое для данного типа. Например, Variantфункции возвращают в таких случаях Empty, целочисленные — 0, а Булевы — False. Вот почему, когда проверка условия дает False, я не присваиваю функции PropertiesAreLoaded возвращаемого значения.
ЧАСТЬ II ДОРОГОЙ ДЖОН, КАК?..
*Лкь
См. также...
Следующий раздел «Дорогой Джон, как... использовать мой новый объект?», где дается пример работы с объектами.
Использовать мой новый объект? Модуль класса Loan сам по себе объекта не создает, а только определяет его или, иначе говоря, предоставляет матрицу, с которой программа по мере необходимости штампует нужное количество реальных объектов Loan. Завершим предыдущий пример, включив модуль LOAN.CLS в какой-нибудь простой проект. Откройте новый проект стандартной ЕХЕ-программы, содержащий всего одну форму. Она будет элементарной, так как ее единственное назначение — создать объект Loan и продемонстрировать его работу. Измените свойство формы Caption на Please Click on This Form, добавьте в проект модуль класса LOAN.CLS, созданный в предыдущем разделе, а в модуль формы введите следующий код. Private Sub Fonti_Click() Dim loanTest As New Loan Dim intCount As Integer Dim curBalanceO As Currency ' очищаем форму Cls ' устанавливаем параметры - условия займа loanTest.Reset loanTest.Principal = 1000 loanTest.Months = 12 loanTest.AnnuallnterestRate = 8.5 выводим используемые параметры Print "Principal: ", , Format(loanTest.Principal, "Currency") Print "No. Months: ", , loanTest.Months Print "(Years:) ", , loanTest.Years Print "Interest Rate:", , Format(loanTest.AnnuallnterestRate _ / 100, "Percent")
Print "Monthly Payment: ", Format(loanTest.Payment, "Currency") Print ' получаем массив ежемесячных остатков по займу curBalanceO = loanTest. BalanceO выводим график платежей For intCount = LBound(curBalance) To UBound(curBalance) Print "Month: "; intCount, Print "Balance: "; Format(curBalance(intCount), "Currency") Next intCount End Sub Рис. 5-1 иллюстрирует форму на этапе разработки.
ГЛАВА 51
Объектно-ориентированное программирование
09
Рис. 5-1 Форма, предназначенная для демонстрации объекта Loan Новый экземпляр объекта Loan — loanTest — создается при щелчке формы и уничтожается автоматически, как и любая другая локальная переменная, при выходе за пределы видимости. Dim loanTest As New Loan
Следующие три строки кода присваивают значения трем свойствам, определенным нами для объекта Loan. Вернитесь к тексту модуля класса Loan, если Вы подзабыли, чем отличается свойство Months от свойств Principal и AnnuallnterestRate. loanTest.Principal = 1000 loanTest.Months = 12 loanTest.AnnuallnterestRate = 8 . 5 Выводя результаты, Вы обращаетесь к свойствам объекта Loan. В большинстве случаев объект loanTest просто возвращает значения, уже хранящиеся в объекте, но обращение к свойству loanTest.Payment запускает сложные вычисления. Впрочем, вызывающий код пребывает в счастливом неведении насчет того, когда, где и как выполняются эти вычисления. Print "Principal: ", , Format(loanTest.Principal, "Currency") Print "No. Months: ", , loanTest.Months Print "(Years:) ", , loanTest.Years Print "Interest Rate: ", , Format(loanTest.AnnuallnterestRate _ / 100, "Percent") Print "Monthly Payment: ", Format(loanTest.Payment, "Currency") Print Вызывая свойство Balance. Вы заставляете объект loanTest создать и вернуть массив ежемесячных остатков по займу: Прим, псрсв.: точнее, экземпляр loanTest объекта Loan.
НАСТЬ М
ДОРОГОЙ ДЖОН, КАК?.
1
получаем массив ежемесячных остатков по займу СигВа1апсе() = loanTest.BalanceO
Последние несколько строк нашего примера выводят таблицу долговых сумм, остающихся к концу каждого месяца: ' выводим график платежей For intCount = LBound(curBalance) To UBound(curBalance) Print "Month: "; intCount, Print "Balance: "; Format(curBalance(intCount), "Currency") Next intCount
При выходе из процедуры Form_Click все переменные, объявленные в ней, автоматически уничтожаются системой. Это касается и экземпляра объекта Loan со всем его кодом и данными. На рис. 5-2 показаны результаты выполнения программы. Ее, кстати, можно значительно усовершенствовать, не трогая модуль класса Loan.
Рис. 5-2 Результаты программы, использующей объект Loan Правильно сконструированный объект, описанный в модуле класса, позволит Вам расширить Visual Basic, добавляя в свою копилку все новые и новые программируемые объекты. Как только Вы добились от объекта требуемой функциональности, задокументируйте его интерфейс — набор открытых свойств и методов. А реализация объекта остается для пользователя «черным ящиком» и, вообще говоря, его не интересует. Представьте, что объект заключен в непроницаемую оболочку и только его интерфейс доступен для обозрения. В ООП это называется инкапсуляцией. Объект отвечает за корректность реализации своей функциональности, а вызывающая программа — за корректность использования его интерфейса. Инкапсуляция значительно упрощает разработку и отладку больших приложений. В этом и заключается красота и сила ООП! Класс Loan мы просто добавили к проекту как еще один модуль. Это прекрасный способ использования модулей классов, но один или несколько таких модулей можно скомпоновать в ActiveX-компоненты.
ГЛАВА 5
IfO
Объектно-ориентированное программирование
71_
См, также...
• Раздел «Дорогой Джон, как... создать и использовать ЕХЕ-сервер ActiveX?» и главу 27 «Изощренные приемы программирования» — подробнее об ActiveX-компонентах. • Программу-пример Lottery в главе 29 «Графика», где демонстрируется использование объектов.
Назначить объекту свойство, используемое по умолчанию? Лично я всегда устанавливаю свойства явно, но некоторые предпочитают сокращать тексты программ, опуская имена свойств, используемых по умолчанию. Например, у элемента управления TextBox по умолчанию используется свойство Text, а значит, его можно установить как явно, так и неявно. Скажем, эти две строки кода равнозначны: Text!.Text = "This string is assigned to the Text property." Textl = "This string is assigned to the Text property." Хоть это и не очень хорошо задокументировано, но Visual Basic теперь позволяет Вам назначать своим объектам свойство, используемое по умолчанию, — если Вам нравится такой стиль. Посмотрим, как это делается, на примере свойства Principal объекта Loan. Активизируйте в проекте окно кода для класса Loan и выберите из меню Tools (сервис) команду Procedure Attributes (атрибуты процедуры) — появится одноименное диалоговое окно. В списке Name (имя) выберите свойство Principal и щелкните кнопку Advanced (дополнительно). В списке ProcedurelD (идентификатор процедуры) выберите строку (Default) [(по умолчанию)] и щелкните кнопку ОК. С этого момента свойство Principal объекта Loan становится используемым по умолчанию. Чтобы проверить корректность работы свойства, получившего статус используемого по умолчанию, активизируйте окно кода для формы, созданной для тестирования объекта Loan, и найдите в нем строку, где свойству Principal присваивается значение 1000. Удалите в этой строке Principal, оставив: LoanTest = 1000 Запустите тестовое приложение и убедитесь, что оно по-прежнему корректно работает.
Создать и использовать ЕХЕ-сервер ActiveX? EXE- и DLL-серверы ActiveX — вещи довольно близкие. В главе 27 (-Изощрённые приемы программирования* я покажу, как создавать ActiveX DLL. а здесь мы рассмотрим создание ЕХЕ-сервера ActiveX.
72
ЧАСТЫ1
ДОРОГОЙ ДЖОН, КАК?..
Такой сервер можно запустить как обычную программу, но, помимо этого, он предоставляет объекты для других приложений. Я уже говорил, что эти объекты работают как внешние, т. е. выполняются в своем адресном пространстве и в потоке, отличном от потока клиентского приложения.
Пример ЕХЕ-сервера ActiveX: компонент Chance Попробуем построить простейший ЕХЕ-сервер ActiveX, содержащий определение одного объекта или, точнее, одного класса. Класс служит шаблоном, по которому создаются экземпляры объекта. Подготовив ЕХЕ-сервер ActiveX, мы запустим его, и он автоматически зарегистрируется в системе. Потом мы напишем на Visual Basic второе, более заурядное приложение, которое создает несколько объектов нашего класса и манипулирует их свойствами и методами. Назовем объект Dice (игральная кость), а ЕХЕ-сервер ActiveX — Chance (шанс). Хоть я и дал объект}' имя Dice, но предусмотрел в нем свойства, позволяющие клиентской программе устанавливать число сторон, так что игральная кость может превратиться и в подобие монеты (2 стороны), и в двенадцатигранник.
DICE.CLS Итак, приступим. Откройте новый проект и дважды щелкните значок ActiveX EXE в диалоговом окне New Project. Введите в модуль класса код, приведенный ниже, установите свойство Name класса кзкОгсе и сохраните этот модуль под именем DICE.CLS, а проект — под именем CHANCE.VBP: Option Explicit ' объявляем свойства, определяющие диапазон допустимых значений Public Smallest As Integer Public Largest As Integer 1 объявляем свойство, доступное только для чтения; его значение ' результат броска игральной кости Public Property Get Value() As Integer Value = Int(Rnd * (Largest - Smallest + 1)) + Smallest End Property
1 метод для перемешивания последовательности случайных чисел Public Sub ShuffleO
Randomize End Sub Этот модуль класса определяет 3 свойства и 1 метод объекта Dice. Свойства Smallest и Largest реализованы как открытые переменные, с помощью которых клиентское приложение (мы создадим его чуть позже) задает диапазон возвращаемых значений, в нашем
ГЛАВА_5
Объектно^ориентированное программирование
73
случае — число сторон игральной кости. Именно так зачастую и реализуются свойства объектов — как открытые переменные. Value — еще одно свойство объекта Dice, но здесь перед возвратом значения клиентскому приложению желательно провести коекакие вычисления. Оператор Property Get как раз и предоставляет механизм для запуска любых операций при запросе значения свойства. В данном случае для имитации броска игральной кости генерируется случайное число, возвращаемое как значение свойства. (Для математиков это число не является подлинно случайным, но достаточно «случайно» для наших целей.) Свойство Value доступно клиентскому приложению только для чтения, так как у соответствующего оператора Property Get нет парного оператора Property Let. Наличие последнего позволило бы клиентскому приложению присвоить новое значение свойству Value. Но в этом примере мы лишь имитируем случайный результат броска игральной кости, и нам не нужно, чтобы клиентское приложение устанавливало значение этого свойства. Shuffle — простой метод, добавленный мной для того, чтобы у объекта был хоть один метод. Его вызов вносит элемент случайности в генератор псевдослучайных чисел, и это делает непредсказуемыми последующие броски игральной кости. Теперь добавим код, выполняемый при запуске ЕХЕ-сервера ActiveX (компонента Chance). Выберите из меню Project команду Add Module и дважды щелкните значок Module в диалоговом окне Add Module. Вставьте в новый модуль следующий код и сохраните его как CHANCE.BAS; Option Explicit Sub NainC) Dim diceTest As New Dice diceTest.Smallest = 1 diceTest.Largest = 6 diceTest.Shuffle MsgBox "A roll of two dice: " 1 _ diceTest.Value 4 "," & diceTest.Value,' — 0, "Message from the Chance ActiveX EXE" End Sub
Выберите из меню Project команду Project 1 Properties, чтобы открыть одноименное диалоговое окно. В списке Startup Object (стартовый объект) щелкните строку Sub Main, в поле Project Name (имя проекта) введите Chance, а в поле Project Description (описание проекта) — Chance ~ ActiveX EXE Example. Щелкните кнопку ОК. Наконец, выберите из меню File команду Make Chance.exe (собрать Chance.exe). В диалоговом окне Make Project (собрать проект) укажите, куда поместить ЕХЕ-файл, и щелкните кнопку ОК. Visual Basic
74
ЧАСТЬ II
ДОРОГОЙ ДЖОН, КАК?.
скомпилирует и автоматически зарегистрирует ЕХЕ-сервер ActiveX с именем Chance.
Тестирование ActiveX-компонента, реализованного как ЕХЕ-сервер Теперь, когда ЕХЕ-сервер ActiveX скомпилирован и зарегистрирован, к нему можно обращаться и использовать его объекты из любого приложения, которое поддерживает технологию ActiveX. Продемонстрируем это, создав приложение на Visual Basic, способное задействовать данный ЕХЕ-сервер ActiveX. Откройте новый проект стандартной ЕХЕ-программы и сохраните ее форму как DICEDEMO.FRM, а сам проект — как DICEDEMO.VBP. Измените свойство Caption формы на Taking a Chance witb Our ActiveX EXE и добавьте на форму кнопку. Я назвал ее Roll 'em (брось их) и сдвинул в правый верхний угол формы. Добавьте к модулю формы следующий код и сохраните проект: Option Explicit Dim diceTest As Mew Dice Private Sub Command1_Click() diceTest.Shuffle diceTest.Smallest = 1 diceTest.Largest = 2 Print "Coin:" & diceTest.Value, diceTest.Largest = 6 Print "Dice:" 4 diceTest.Value, diceTest.Largest = 12 Print "Dodecahedron:" 4 diceTest.Value End uuu Sub Рис. 5-3 иллюстрирует форму на этапе разработки.
Рис. 5-3 Форма DiceDemo Форма создает экземпляр объекта Dice, поэтому нужно установить ссылку на компонент Chance, чтобы в текущем проекте было известно, где определен объект Dice. Выберите из меню Project команду References (ссылки) и найдите в одноименном диалоговом окне строку Chance - ActiveX EXE Example. (Это то самое описание,
ГЛАВА 5
Объектно-ориентированное программирование
75
которое мы закрепили за проектом Chance.) Пометьте флажок в этой строке и щелкните кнопку ОК. Теперь все должно работать! Запустите программу и понаблюдайте, что происходит. Сначала появляется тестовая форма с кнопкой Roll 'em. Щелчок этой кнопки приводит к созданию объекта Dice, и на форме показываются результаты бросков двух-, шести- и двенадцатигранной кости. Рис. 5-4 иллюстрирует, что получится, если щелкнуть кнопку несколько раз.
Рис. 5-4 Результат работы программы DiceDemo после нескольких щелчков кнопки Roll 'em После первого щелчка кнопки появляется одно важное сообщение. Как только ЕХЕ-сервер Chance загружается в память, его стартовая процедура Sub Main открывает окно и с помощью своего объекта Dice тоже «бросает» пару игральных костей. Выглядит это окно так, как показано на рис. 5-5. age; iron th<- ГЬапсв ArtweX EXfc
Рис. 5-5 Сообщение, выводимое EXE-сервером Chance при загрузке в память Данное окно появляется лишь раз, когда компонент Chance впервые загружается в память. Если Вас это заинтересовало, проверьте: Chance выгружается из памяти, как только уничтожается последний из его объектов (что происходит при завершении приложения DiceDemo).
Создать объект, способный отображать формы? У объектов Loan и Dice, рассмотренных выше, нет визуального интерфейса вроде формы. Многие объекты тоже не имеют визуального интерфейса, но бывают случаи, когда нужен объект, который через форму получал бы данные от пользователя. Добавить форму в проект ЕХЕ- или DLL-сервера ActiveX неоюжно, но следует учитывать, что объект, выводящий формы, должен
76
ЧАСТЫ!
ДОРОГОЙ ДЖОН. КАК?..
тщательно контролировать их создание и уничтожение. Клиентское приложение никогда напрямую не взаимодействует с формой внутри ActiveX-компонента (она доступна лишь самому компоненту), поэтому очень важно, чтобы объект корректно управлял своей формой. Иначе экземпляры формы скрытно «засядут» в памяти, попусту транжиря ресурсы и временами приводя к непредсказуемым результатам. Ниже приведено определение модуля класса для объекта User. Этот объект, предназначенный для сбора данных об имени и пароле пользователя, выводит модальную форму с двумя текстовыми полями txtName и txtPassword и кнопкой ОК (ее кодовое имя cmdOK). • USER.CLS ' """.Name Property Get Name() Name = frmPass.txtName End Property ' .Password Property Get Password() Password = frmPass.txtPassword End Property ' """".Show Sub Show() frmPass.Show vbModal End Sub Private Sub Class_Terminate() 1 выгружаем форму при уничтожении объекта Unload frmPass End Sub
Форма frmPass содержит лишь один обработчик события, который скрывает форму при щелчке кнопки ОК. Это позволит, проверив имя и пароль пользователя, быстро отобразить форму повторно, если окажется, что введенные данные неправильны. При этом данные в полях формы останутся нетронутыми, и пользователь легко исправит их. ' FRMPASS.FRM Private Sub cmdOK_Click() Hide End Sub
Чтобы использовать объект, клиентское приложение просто создает его экземпляр и вызывает метод Show. В следующем коде это происходит при щелчке кнопки cmdSignOn. Обратите внимание на синтаксис ссылки на класс User, реализованный в проекте PasswordSample.
ГЛАВА 5 Обьектно-ориентированное программирование
11
Private Sub cmdSignOn.ClickO 1 создаем экземпляр объекта Dim usrSignOn As New PasswordSample.User ' цикл ожидания ввода пароля Guest Do While usrSignOn.Password <> "Guest" ' отображаем диалоговое окно usrSignOn.Show Loop End Sub
Пример формы User Object показан на рис. 5-6,
Рис. 5-6
Пример объекта, отображающего форму
Чтобы убедиться в важности контроля за созданием и уничтожением формы, удалите у объекта User процедуру Class_Terminate, а потом создайте этот объект из клиентского приложения, как показано выше. Когда процедура обработки события cmdSignOn_Click завершится, переменная usrSignOn выйдет из области видимости и будет уничтожена, но форма fivnPass останется, а значит, продолжится выполнение и скрытого экземпляра сервера данного объекта. Если сервер объекта — ЕХЕ-файл, этот скрытый экземпляр Вы увидите в окне диспетчера задач Windows, нажав клавиши Ctrl+Alt+Del. Закройте скрытый экземпляр, устраните ошибку и перекомпилируйте исправленную версию. Сделать это нужно обязательно и как можно скорее, чтобы не допустить повторения проблемы, поскольку обнаружить скрытые экземпляры серверов объектов достаточно трудно. Б общем случае создание и уничтожение форм лучше всего контролировать через обработчики Class_Initialize и Class_Terminate. Мой объект был совсем крошечным, и я сделал так, что форма неявно создавалась в первой же вызванной процедуре (свойстве или методе). Для явного создания формы следовало бы ввести процедуру Class_Initialize: ' добавьте к USER.CL8, чтобы явно создааать форму Private Sub Class_Initialize() 1 создаем форму сразу после создания объекта, вызывая метод Snow Show End Sub
78
ЧАСТЬ II
ДОРОГОЙ ДЖОН, КАК?..
ПРИМЕЧАНИЕ Возьмите за правило всегда явно создавать и уничтожать формы в объектах. Это сэкономит массу времени на отладке.
Event, WithEvents и RaiseEvent В Visual Basic есть несколько операторов для определения в создаваемом объекте пользовательских событий. Я упоминаю о них именно здесь потому, что эти средства позволяют реализовать механизм взаимодействия закрытых форм, принадлежащих объектам в ActiveX-компонентах, с клиентскими приложениями. Генерация события в закрытой форме порождает немедленный отклик в ActiveX-объекте, владеющем данной формой, а этот объект в свою очередь возбуждает событие в клиентском приложении. Хороший пример такого сценария приведен в разделе «Adding a Form to the ThingDemo Project» электронной документации Books Online.
Работать с наборами объектов? Встроенный в язык объект Collection позволяет включать объекты (или элементы данных любого типа) в набор, с которым можно обращаться как с единой сущностью. Объект Collection допустимо рассматривать как очень гибкий массив, способный содержать практически все, в том числе объекты и другие наборы. Но самое распространенное применение объектов класса Collection — поддержка наборов с произвольным количеством экземпляров объекта определенного типа. Как раз это и продемонстрирует следующий пример. В созданных Вами объектах Visual Basic наборы можно использовать самыми разными способами. Некоторые из них «опаснее» других, и поэтому конкретные способы выбирают в зависимости от того, насколько глубоко хотят застраховаться от ошибок. Я советую внимательно прочесть разделы Books Online по этой теме и получить представление о подходе, предлагаемом Microsoft. Я применил лишь слегка модифицированную версию методики Microsoft и мне кажется, что Вам тоже будет полезно придерживаться их рекомендаций. (Чтобы найти нужные разделы, проведите поиск по одному из словосочетаний: House of Straw, House of Stricks или House of Bricks.) В следующем примере я представлю простой способ реализации наборов пользовательских объектов внутри других объектов. Этот способ достаточно надежен и легко адаптируется под конкретную задачу. Мы создадим модель Солнечной системы, которая включает объект верхнего уровня Star (звезда), тот — набор Planets (планеты), а каждый объект Planet — набор Moons (спутники). Объекты Moon крайне просты и содержат всего несколько свойств.
ГЛАВА 5
Объектно-ориентированное программирование
79
Пример набора: SolarSys Запустите Visual Basic и откройте новый проект стандартной ЕХЕпрограммы. Установите свойство формы Name как SolarSys и сохраните ее под именем SOLARSYS.FRM, а сам проект — под именем SOLARSYS.VBP. Измените свойство формы Caption на Collections Examle, добавьте на форму кнопку с именем cmdBuildSolarSystem и установите ее свойство Caption КАК Build Solar System. Затем вставьте показанный ниже код и сохраните проект. Option Explicit Public starQbject As New Star Private Sub cfndBuildSolarSystem_Click() ' готовим ссылки на рабочие объекты Dim planetObject As Planet Dim moonObject As Moon ' создаем в Солнечной системе планеты и их спутники With starObject.Planets ' создаем первую планету Set planetObject = .Add("Mercury") ' устанавливаем некоторые из ее свойств With planetObject .Diameter = 4880 .Mass = З.ЗЕ+23 End With создаем вторую планету Set planetObject = .Add("Venus") 1 устанавливаем некоторые из ее свойств With planetObject .Diameter = 12104 .Mass = 4.869E+24 End With создаем третью планету Set planetObject = .Add("Earth") ' устанавливаем некоторые из ее свойств With planetObject .Diameter = 12756 .Mass = 5.9736E+24 ' создаем спутники этой планеты With .Moons ' создаем Луну для Земли Set moonObject = .Add("Luna") ' устанавливаем некоторые из ее свойств With moonObject .Diameter = 3476 .Mass = 7.35E+22 End With End With 4—1204
80
ЧАСТЬ II
End With
ДОРОГОЙ ДЖОН, КАК1?..
' создаем четвертую планету Set planetObject = .Add("Mars") 1 устанавливаем некоторые из ее свойств With planetObject .Diameter = 6794 .Mass = 6.4219E+23 1 создаем спутники этой планеты With .Moons ' создаем первый спутник Марса Set moonObject = .AddC'Phobos") ' устанавливаем некоторые из его свойств With moonObject .Diameter = 22 .Mass = 1.08E+16 End With ' создаем второй спутник Марса Set moonObject = .Add("Deimos") ' устанавливаем некоторые из его свойств With moonObject .Diameter = 13 .Mass = 1.8E+15 End With End With End With End With 1 отключаем кнопку cmdBuildSolarSystem.Enabled = False отображаем результаты Print "Planet", "Moon", "Diameter (km)", "Mass (kg)" Print StringdOO, "-") For Each planetObject In starObject.Planets With planetObject Print .Name, , .Diameter, .Mass End With For Each moonObject In planetObject.Moons With moonObject Print , .Name, .Diameter, .Mass End With Next moonObject Next planetObject ' напрямую обращаемся к некоторым свойствам Print Print "The Earth's moon has a diameter of "; Print starObject.Planets("Earth").Moons("Luna").Diameter; Print " kilometers." Print "Mars has"; Print starObject.Planets("Mars").Moons.Count; Print " moons." End Sub
ГЛАВА 5 Объектно-ориентированное программирование
81
Рис. 5-7 иллюстрирует форму SolarSys на этапе разработки. РпяссП BnTaiSy! (Гот)
Рис. 5-7 Форма SolarSys, демонстрирующая вложенные наборы объектов Мы еще вернемся к этому коду, а пока определим требуемые объекты и их наборы.
Класс Star Выберите из меню Project команду Add Class Module и в появившемся диалоговом окне дважды щелкните значок Class Module. Установите свойство Name класса как Star, вставьте приведенный ниже код и сохраните модуль как файл STAR.CLS. ' STAR.CLS
Option Explicit Public Name As String Private mPlanets As New Planets Public Property Get PlanetsO As Planet Set Planets = mPlanets End Property
В объекте Star объявляется и содержится набор объектов-планет. Класс Planets мы рассмотрим чуть позже, а пока имейте в виду, что этот класс определяет набор объектов Planet, в свою очередь определяемых классом Planet. Заметьте, что набор Planets объявлен закрытым (как mPlanets} и тем самым защищен от прямого доступа извне. Внешние процедуры обращаются к этому набору через свойство Planets, у которого есть процедура Property Get, но нет парной процедуры Property Let. Для обращения к отдельным объектам Planet в наборе Planets нам нужен доступ только для чтения.
Класс Planets Все планеты в Солнечной системе представлены как объекты Planet в наборе Planets. Обратите внимание, что по принятому стандарту
82
,
ЧАСТЫ1
ДОРОГОЙ ДЖОН, КАК?..
наборам присваиваются те же имена, что и содержащимся в них объектам, — только во множественном числе. Добавьте в проект новый модуль класса, присвойте ему имя Planets, введите код, показанный ниже, и сохраните его под именем PLANETS.CLS. Option Explicit Private mcolPlanets As New Collection Public Function Add(strName As String) As Planet Dim PlanetNew As New Planet PlanetNew.Name = strNaroe mcolPlanets.Add PlanetNew, strName Set Add = PlanetNew End Function Public Sub Delete(strName As String) mcolPlanets.Remove strName End Sub Public Function Count() As Long Count = mcolPlanets.Count End Function Public Function Item(strName As String) As Planet Set Item = mcolPlanets.Item(strName) End Function Public Function NewEnun{) As lUnknown Set NewEnum = mcolPlanets.[_NewEnum] End Function
Впервые начав создавать наборы объектов, я поддался соблазну скомбинировать каждый объект и его набор в одном модуле класса. Все работало, но, отделив набор от содержащихся в нем объектов, Вы избавитесь от уймы скрытых проблем и странных «глюков». Вот почему в этой программе присутствуют два класса — Planets и Planet. Вместе они создают довольно надежный набор объектовпланет. Аналогичным образом, как Вы сами скоро убедитесь, устроен и набор объектов -с путников (классы Moons и Moon). Класс набора Planets инкапсулирует все, что связано с созданием и управлением отдельными объектами Planet. Настоящий объект Collection, mcolPlanets, объявлен в этом модуле класса закрытым, чтобы инкапсулировать и защитить его от внешнего воздействия. Методы, стандартные для любого набора, — Add, Delete, Count, Item и NewEnum — образуют интерфейс для взаимодействия с набором объектов-плане т. Реализуя методы, я применил пару специфических приемов, о которых Вам не помешает знать, так что дзвайте-ка пройдемся по этим процедурам.
ГЛАВА 5
Объектно-ориентированное программирование
83
Метод Add предназначен для создания новой «планеты* в наборе. В данном примере имя каждого объекта-планеты, передаваемое методу Add, используется двояко: для установки свойства Name объекта-планеты и как ключ, по которому индексируется объект. Ключ может отличаться от имени, как в примерах наборов, описанных в Visual Basic Books Online. У каждого элемента, хранящегося в наборе, должен быть уникальный строковый ключ, и один из способов обеспечить уникальность каждого ключа — возложить эту задачу на класс набора, автоматически поддерживающий свой набор ключей. Я предпочел более простой и естественный способ, при котором объект индексируется по имени. Правда, попытка добавить в такой набор два объекта-планеты с одинаковым именем даст ошибку. Вероятно, Вы дополните код метода Add обработкой ошибок, вызванных дублированием имен планет. Я же предпочел не усложнять код. Процедура Count возвращает количество планет в наборе Planets, а процедура Delete удаляет из него указанный объект. Наверное, и здесь стоит добавить код для обработки ошибок — на случай попытки удалить несуще ствующи и объект. Процедура Item возвращает ссылку на объект-планету по заданному имени. В ней применяется первый из специфических приемов, о которых я хотел бы упомянуть. Если Вы сделаете процедуру Item свойством по умолчанию, то синтаксис доступа к конкретной планете будет естественнее. Например, Вы можете обратиться к объекту-планете Earth (Земля) через свойство Item так: starQbject.Planetsltem('>Eartb*>); но, придав свойству Item статус используемого по умолчанию, Вы сократите синтаксис до starObjectPlanets.(«Earth'>). Такой синтаксис гораздо лучше сочетается со стандартным синтаксисом объектов многих других Windows-приложений. ПРИМЕЧАНИЕ Чтобы сделать какую-то процедуру-свойство используемой по умолчанию, выберите из меню Tools команду Procedure Attributes. В списке Name укажите требуемую процедуру и щелкните кнопку Advanced для доступа к дополнительным параметрам. Теперь в списке ProcedurelD выберите строку (Default) и щелкните кнопку ОК. У класса может быть лишь одна процедура, используемая по умолчанию. Второй прием, достойный упоминания, — перечисление объектов набора в цикле For Each. Здесь не все так просто, поэтому соблюдайте мои инструкции. Прежде всего в класс набора надо ввести специальную процедуру NcwEnum интерфейса lUnknown, как показано в исходном коде класса Planets. Имя этой процедуры предваряется знаком подчеркивания и заключается в квадратные скобки. Затем Вы скрываете функцию NewEnum и разрешаете ее поддержку в своем классе, действуя так, как рассказывается в следующем примечании.
84
ЧАСТЬ II
ДОРОГОЙ ДЖОН, КАК?..
ПРИМЕЧАНИЕ Чтобы скрыть процедуру NewEnum и разрешить ее поддержку в своем классе, выберите из меню Tools команду Procedure Attributes. В списке Name укажите строку NewEnum, щелкните кнопку Advanced, установите флажок Hide This Member (скрыть этот элемент) и введите в поле ProcedurelD специальный код -4. С этого момента Ваш набор можно будет перебирать в цикле For Each.
Класс Planet Вставьте в проект SolarSys второй модуль класса, установите его свойство Name как Planet и сохраните модуль под именем PLANET.CLS. Этот класс определяет объекты Planet, находящиеся внутри набора Planets. Добавьте в модуль следующий код: Option Explicit Public Name As String Public Diameter As Long Public Mass As Single Private mMoons As New Moons Public Property Get MoonsQ As Moons Set Moons = mKoons End Property
У каждого объекта Planet имеется несколько свойств (Name, Diameter и Mass) и набор Moons, содержащий произвольное число объектов-спутников. И вновь настоящий набор объявляется закрытым, чтобы предотвратить запись в него недопустимых ссылок. Вы ссылаетесь на объекты- с путники данной планеты через свойство Moons только для чтения.
Класс Moons Этот класс относится к классам наборов; его структура очень похожа на структуру класса Planets. Добавьте новый модуль класса в проект SolarSys, установите его свойство Name как Moons, вставьте показанный ниже код и сохраните модуль под именем MOONS.CLS. Option Explicit Private mcolMoons As New Collection Public Function Add(strName As String) As Moon Dim MoonNew As New Moon MoonNew.Name = strName mcolMoons.Add MoonNew, strName Set Add = MoonNew End Function
ГЛАВА 5 Объектно-ориентированное программирование
85
Public Sub Delete(strName As String) mcolMoons.Remove strName
End Sub
Public Function Count() As Long Count = mcolMoons.Count End Function Public Function Item(strName As String) As Moon Set Item = mcolMoons.Item(strName) End Function Public Function NewEnum() As IDnknown Set NewEnum = mcolMoons.[_NewEnum] End Function
Класс Moon Этот класс крайне прост. У каждого объекта Moon есть свойства Name, Diameter и Mass — вот и все. Вставьте в проект очередной модуль класса, установите его свойство Name как Moon, сохраните модуль под именем MOON.CLS и добавьте код: Option Explicit Public Name As String Public Diameter As Long Public Mass As Single
Как работают вложенные наборы Теперь внимательно рассмотрим код в модуле формы SolarSys. При щелчке кнопки Build Solar System создается новый экземпляр объекта Star с именем starObject. Поскольку мы не собираемся создавать более одного экземпляра объекта Star, то и не станем устанавливать его свойство Name. (Если б мы писали программу для регистрации всех звездных систем, упоминаемых в каком-нибудь сборнике научно-фантастических рассказов, то создали бы множество объектов Star, и вот тогда-то уникальное имя для каждого из них было бы весьма полезно.) Чтобы не нагромождать слишком много кода, я добавил лишь первые четыре планеты нашей Солнечной системы и их спутники. Хотите — создайте полную модель сами! Следующий код добавляет первую планету с именем Mercury (Меркурий), у которой нет спутников: With starObject.Planets ' создаем первую планету Set planetObject = .Add("Mercury") ' устанавливаем некоторые из ее свойств
86
ЧАСТЬ II
ДОРОГОЙ ДЖОН, КАК?..
With planetObject .Diameter = 4880 .Mass = З.ЗЕ+23 End With
Я применил оператор With, стремясь упростить ссылки на вложенные объекты. Но можно было бы получить прямой доступ к свойствам объекта Mercury, явно указав цепочку ссылок через оператор-точку. При полной нотации строка, в которой устанавливается значение свойства Diameter объекта Mercury выглядела бы так: star-Object. PlanetsC'Mercury"). Diameter = 4880 Иными словами, здесь значение 4880 присваивается свойству Diameter объекта Mercury набора Planets объекта Star с именем starObject. Уф! Теперь Вы видите, как удобен оператор With? Более того, блоки With, уменьшая число операций разыменования (de-referencing), требуемых для доступа к каждому объекту и его свойствам, не только упрощают восприятие программы, но также и ускоряют ее выполнение. Следующий блок кода повторяет описанный процесс, добавляя второй объект Planet — Venus (Венера), у которого тоже нет объектов-спутников. Так что пропустим его и перейдем сразу к третьей планете, Earth (Земля), и ее спутнику, Luna (Луна). Вот как выглядит код, добавляющий эту планету и ее спутник во вложенные наборы: ' создаем третью планету Set planetObject = .Add("Earth") ' устанавливаем некоторые из ее свойств With planetObject .Diameter = 12756 .Mass = 5.9736E+24 1 добавляем спутник этой планеты With .Moons
' создаем Луну
Set moonQbject = .AddC'Luna") 1 устанавливаем некоторые из ее свойств With moonObject .Diameter = 3476 .Mass = 7.35E+22 End With End With End With
Еще раз обратите внимание, как удобны вложенные конструкции With при ссылках на элементы вложенных наборов. Построив упрощенную модель Солнечной системы (как наборы объектов, содержащиеся в других наборах), Вы можете обратиться к этой структуре и вывести имена всех вложенных объектов:
ГЛАВА 5
Обьектно-ориентированное программирование
.
87
' отображаем информацию Print "Planet", "Moon", "Diameter (km)", "Mass (kg)" Print StringOOO, "-") For Each planetObject In starObject.Planets With planetObject Print .Name, , .Diameter, .Mass End With For Each moonObject In planetObject.Moons With moonObject Print , .Name, .Diameter, .Mass End With Next moonObject Next planetObject
Активизировав поддержку синтаксиса For Each (об этом я уже рассказывал), мы облегчили ссылку на свойства планет и спутников. Благодаря этому код, приведенный выше, совершенно понятен и легко читается. Для сравнения я обращаюсь к некоторым свойствам напрямую, используя полную нотацию с точками: ' напрямую обращаемся к некоторым свойствам
Print Print "The Earth's moon has a diameter of "; Print starObject.Planets("Earth")•Moons("Luna").Diameter; Print " kilometers." Print "Mars has"; Prtnt starObject.Planets("Mars").Moons.Count; Print " moons." Имена объектов отображаются на форме SolarSys после щелчка кнопки, как показано на рис. 5-8.
Рис. 5-8 Наборы Planets и Moons упрощенной Солнечной системы Предложенный здесь прием легко адаптируется под многие другие задачи. Как я уже говорил, в класс набора можно включить код, который предотвращает случайные попытки добавить несколько одноименных объектов, и более серьезно обрабатывать ошибки, связанные с отсутствием объектов. Но я представил лишь концептуальную базу, на которой Вы построите что-то свое, и поэтому старался не отвлекаться на второстепенные детали. Во многих ситуациях этот метод дает практически все, что нужно.
ЧАСТЬ II
ДОРОГОЙ ДЖОН. КАК?..
Использовать полиморфизм? В новом мире ООП целое море новой терминологии и концепций! Взять хотя бы полиморфизм. Что он значит и чем полезен? В самых общих чертах, полиморфизм означает, что несколько объектов поддерживают одни и те же интерфейсные элементы. Например, Вы могли бы создать множество объектов, каждый из которых поддерживает «стандартный* набор файловых функций. Все эти объекты предоставляли бы методы и свойства с идентичными именами, скажем, FileName, Read и Write, что позволило бы упростить и стандартизировать эти объекты, а также сделать их более предсказуемыми и согласованными. Полиморфизм в Visual Basic трактуется несколько иначе, чем в C++. Исторически сложилось так, что в большинстве языков, поддерживающих ООП, методы и свойства одного объекта наследуются новым объектом. Последний может потом модифицировать элементы унаследованного интерфейса или предоставить их клиентскому приложению в неизменном виде («как есть*). Но Visual Basic не использует наследование для поддержки полиморфизма. Вместо этого он позволяет создать интерфейс, который есть не что иное, как набор определений взаимосвязанных свойств и методов, включенный в абстрактный класс. Последний — всего-навсего модуль класса, содержащий пустые оболочки свойств и методов. Этот особый класс потом объявляется в модулях классов, готовых реализовать его элементы интерфейса с применением ключевого слова Implements. Я предпочитаю рассматривать абстрактный класс как своего рода соглашение. Несколько модулей классов «принимают» условия данного соглашения, заявляя об этом ключевым словом Implements. Клиентское приложение объявляет экземпляры этих объектов, в том числе объектную переменную с типом абстрактного класса. Обращаясь к элементам интерфейса через объект абстрактного класса, можно быть уверенным, что вызванные методы или свойства будут выполняться корректно — каждым объектом по-своему. Лучший способ освоить эти концепции — поработать с ними. Пример, приведенный в Books Online, дает отличные возможности для изучения и экспериментов. Проведите поиск в Books Online no слову Tyrannosaur (тиранозавр) либо — если Вам некогда или лень набирать такое длинное слово — по Flea (блоха). Пояснения насчет того, как объекты «блоха» и «тиранозавр», так сказать, договорились через полиморфизм Visual Basic реализовать метод Bite (укусить), помогут понять и запомнить все, что нужно.
ГЛАВА 5 Объектно- ориентирован мое программирование
89
Применять дружественные методы? Visual Basic 4 впервые познакомил нас с объектами, определяемыми в модулях классов, их свойствами и методами. Но программисты вскоре заметили небольшой изъян в том, как компонент предоставляет свойства и методы собственных модулей классов клиентским приложениям: класс не мог предоставить такие методы другим объектам своего компонента, не открыв их внешним программам. Эта проблема была решена за счет ключевого слова Friend, которое применительно к методу означает, что данный метод доступен всем классам внутри компонента, но не клиентским приложениям. Компоненты часто предоставляют набор взаимосвязанных объектов и именно дружественные методы позволяют им четко взаимодействовать друг с другом, не открывая детхчей этого механизма клиентским приложениям.
Г Л А В А
ActiveX-элементы 1У1ногие аналитики приписывают быстрый успех Visual Basic возможности использовать нестандартные элементы управления (custom controls). И легко понять почему: Вы не привязаны к одному поставщику инструментальных средств, а выбираете лучшее, что есть на рынке. Более того, Visual Basic позволяет Вам самому создавать нестандартные элементы управления (называемые .элементами управления на базе ActiveX, или — в краткой форме — ActiveX-элементами). ActiveX-элементы, написанные на Visual Basic, выглядят и действуют точно так же, как и написанные на С. Поэтому Вы можете распространять их среди своих коллег, программирующих на С, чтобы они пользовались ими из Microsoft Visual C++. ActiveX-элементы можно применять практически везде — на Web-страницах, просматриваемых с помощью Internet Explorer, в документах Microsoft Excel и Microsoft Word, в СУБД Microsoft Access и Visual FoxPro и, конечно же, в Visual Basic, Microsoft Visual C++ и Microsoft Visual J++. В этой главе я расскажу, как создавать ActiveX-элементы, и опишу специфику их программирования. Все примеры кода из этой главы Вы найдете на компакт-диске, прилагаемом к книге.
Создать ActiveX-элемент? ActiveX Control Interface Wizard (мастер интерфейса ActiveX-элементов) позволяет создать новый элемент управления на базе существующего. Но код, генерируемый мастером, труден для понимания, и поэтому мы создадим ActiveX-элемент вручную. А как только Вы разберетесь в составных частях ActiveX-элемента, работать с мастером Вам станет значительно легче.
Этапы разработки ActiveX-элемента Чтобы создать ActiveX-элемент: 1. Создайте новый проект ActiveX-элемента. 2. В окне UserControl нарисуйте визуальный интерфейс элемента управления, используя элементы из Toolbox так же, как и при разработке формы.
ГЛАВАJL ActiveX-элементы
91_
3. В окне кода напишите обработчик события Resize, чтобы при изменении размеров элемента управления соответственно модифицировать его визуальное представление. 4. Добавьте свойства, методы и события, которые элемент управления предоставит клиентской программе. Они определяют программный интерфейс элемента. 5. Напишите код, реализующий функциональность разрабатываемого элемента. 6. Добавьте новый проект стандартной ЕХЕ-программы для отладки созданного элемента. 7. Выберите из меню File команду Make, чтобы скомпилировать элемент в OCX-файл. На примере элемента управления Blinker (BLINKER.VBP) я подробно рассмотрю первые 6 операций. Blinker («мигалка») заставляет мигать окна или элементы управления, что привлекает к ним внимание. Компиляция и отладка рассматриваются в разделах: «Дорогой Джон, как... отладить элемент управления?» и «Дорогой Джон, как... откомпилировать и зарегистрировать элемент управления?*
Создание проекта ActiveX-элемента Чтобы создать проект ActiveX-элемента: 1. Выберите из меню File команду New Project, и появится диалоговое окно New Project. 2. Дважды щелкните значок ActiveX Control. Visual Basic создаст новый проект и откроет окно UserControl. 3. Выберите из меню Project команду Project Properties, чтобы открыть одноименное диалоговое окно. 4. В поле Project Name введите имя проекта: VB6WkSamp, а в поле Project Description — описание проекта: VB6 Workshop Blinker Sample Control. Затем щелкните кнопку ОК. 5. В окне свойств UserControl установите свойство Name как Blinker. Это имя класса элемента управления; оно используется при именовании экземпляров данного элемента, добавляемых на форму: Blinker I, Blink,er2 и т. д. 6. Выберите из меню File команду Save Project и сохраните элемент управления Blinker как BLINKER.CTL, а проект — как BLINKER.VBP.
Конструирование интерфейса Элемент Blinker заставит мигать другие элементы управления и окна в Вашем приложении, привлекая к ним внимание пользователя. Реализовать это не так трудно, как кажется, что для примера хорошо. Blinker использует ActiveX-элемент UpDown, а также встроенные элементы TexlBox и Timer (рис. 6-1).
92
ЧАСТЫ1 ДОРОГОЙ ДЖОН: КАК?..
Чтобы создать визуальный интерфейс элемента Blinker: 1. Выберите из меню Project команду Components, и на экране появится одноименное диалоговое окно. 2. На вкладке Controls пометьте флажок Microsoft Windows Common Controls-2 6.0 (MSCOMCT2.OCX) и щелкните кнопку ОК. Visual Basic добавит на панель Toolbox элемент управления UpDown и ряд других стандартных элементов управления. 3- В окне UserControl нарисуйте текстовое поле. В окне Properties установите его свойство Name как txtRate, а значение свойства Text приравняйте нулю. 4. Разместите элемент управления UpDown рядом с текстовым полем и установите его свойство Name как updnRate. 5. Добавьте элемент управления Timer и установите его свойство Name как tmrBlink. 3
^*WffJI*Ba шшшша
Рис. 6-1 Визуальный интерфейс элемента управления Blinker, состоящего из одного ActiveX- и двух встроенных элементов
Изменение размеров элемента управления Можете особенно не стараться, рисуя интерфейс элемента управления Blinker, — все равно размеры и расположение визуальных элементов задаются в коде. Вставив элемент Blinker на форму, пользователь сможет сжимать и растягивать его, пока не добьется нужных размеров. Изменение размера генерирует событие, обрабатываемое процедурой UserControl_Resize, которая и устанавливает
ГЛАВД_6
АсйуеХ-элеме_нты
93
соответствующим образом размеры и расположение элементов визуального интерфейса. Ниже приведен код, обрабатывающий изменение размеров элемента Blinker: 1 код для визуального интерфейса элемента управления Private Sub UserControl_Resize() 1 корректно размещаем визуальные элементы 1 в окне UserControl updnRate.Top = 0 txtRate.Top = 0 txtRate.Left = 0 ' изменяем размеры визуальных элементов при изменении размеров самого элемента управления updnRate.Height = Height txtRate.Height = Height ' регулируем ширину элемента управления UpDown ' максимум до 240 твипов If Width > 480 Then updnRate.Width = 240 Else updnRate.Width = Width \ 2 End If 1 устанавливаем ширину текстового поля txtRate.Width = Width - updnRate.Width ' смещаем элемент управления UpDown < правому краю ' текстового поля updnRate.Left = txtRate.Width End Sub Как видите, я чуть-чуть пофантазировал в процедуре, изменяющей размеры элемента управления UpDown. Вместо того чтобы зафиксировать его ширину на 240 твипах, я предусмотрел возможность ее уменьшения, пока ширина элемента Blinker не превышает 480 твипов. Ваш код, обрабатывающий изменение размеров элемента, может быть проще или сложнее — дело Ваше.
Добавление свойств, методов и событий Помимо стандартных свойств, определяющих размеры, позицию, видимость и присущих всем элементам управления, у Blinker есть свойство TargetObject, которое указывает мигающий объект, и Interval, которое задает частоту мигания объекта. Кроме того, у Blinker имеются событие Blinked, генерируемое после очередного мигания, и метод Blink, устанавливающий свойства TargetObject и Interval. Свойства, методы и события ActiveX-элемента создаются точно так же, как и у любого другого объекта. Следующий код определяет TargetObject, Interval, Blinked и Blink для элемента управления Blinker:
94
ЧАСТЬ II
ДОРОГОЙ ДЖОН, КАК?.
Option Explicit ' объявление API-функции Windows, заставляющей мигнуть окно Private Declare Function FlashWindow _ Lib "user32" ( _ ByVal nwnd As Long, _ ByVal blnvert As Long } As Long 1
определение события Blinked Public Event Blinked О
'
' внутренние переменные Private mobJTarget As Object Private mlngForeground As Long Private mlngBackground As Long Private mblnlnitialized As Boolean 1 открытые константы кодов ошибок Public Enum BlinkerErrors blkCantBlink = 4001 blkObjNotFound = 4002 End Enum
' исходный код свойств и методов элемента управления ' "~.TargetObject Public Property Set TargetObjectfSetting As Object) If TypeName(Setting) = "Nothing" Then Exit Property ' записываем во внутреннюю переменную ссылку на целевой объект Set mobJTarget = Setting End Property Public Property Get TargetObJectO As Object Set TargetObject = mobJTarget End Property 1
""".Interval Public Property Let Interval(Setting As Integer) ' настраиваем элемент управления UpDown; это приводит ' к автоматическому обновлению элементов TextBox и Timer updnRate.Value = Setting End Property Public Property Get IntervalO As Integer Interval = updnRate.Value End Property 1
'".Blink
Sub Blink(TargetObject As Object, Interval As Integer)
ГЛАВА 6 ActiveX-злементы
95
' делегируем свойствам TargetObject и Interval Set Me.TargetObject = TargetObject Me.Interval = Interval End Sub
Процедура Property Set TargetObject, определяя объект, который должен мигать, присваивает ссылку на него внутренней объектной переменной. Свойство Interval просто устанавливает и считывает значение UpDown. Это в свою очередь изменяет значение текстового поля и тем самым модифицирует значение свойства Interval таймера. Событие Blinked здесь только объявляется, а генерируется из обработчика события Timer таймера. Далее я покажу обработчики событий таймера и текстового поля — там-то все и происходит.
Программирование функциональности элемента управления Пока что Blinker, хоть и смотрится отлично, ничего не делает. Его функция — обеспечить мигание окна или элемента управления — определяется кодом обработчиков события Timer таймера и Change текстового поля: ' код, определяющий функциональность элемента управления Private Sub updnRate_Change() 1 обновляем содержимое текстового поля txtRate.Text = updnRate.Value End Sub Private Sub tmrBlink_Timer() On Error GoTo errTimer ' счетчик чередования миганий Static blnOdd As Boolean blnOdd = Not blnOdd 1 если объект - форма, используем API-функцию FlashWindow If TypeOf mobjTarget Is Form Then FlashWindow mobjTarget.hwnd, CLng(blnOdd) 1 если объект - элемент управления, обмениваем значения цветов Elself TypeOf mobjTarget Is Control Then If Not mblnlnitialized Then mlngForeground = mobjTarget.ForeColor mlngBackground = mobjTarget.BackColor mblnlnitialized = True End If If blnOdd Then mobjTarget.ForeColor = mlngBackground mobjTarget.BackColor = mlngForeground Else mobjTarget.ForeColor = mlngForeground mobjTarget.BackColor = mlngBackground End If Else Set mobjTarget = Nothing
96
—~
~
п
"
^в^—^™—
ЧАСТЫ1
—чв_^^^^в_
ДОРОГОЙ ДЖОН, КАК?..
т^т~—
I
^^^__
GoTo errTiraer End If ' генерируем событие Blinked RaiseEvent Blinked Exit Sub errTimer: If TypeName(mobjTarget) * "Nothing" Then Err.Raise blkObjNotFound, "Blinker control", _ "Target object is not valid for use with this control." Else Err.Raise blkCantBlink, "Blinker control", "Object can't blink," End If End Sub Private Sub txtRate_Change() ' устанавливаем свойство Interval элемента управления Tinier ' в соответствии со значениек в текстовом поле If txtRate = 0 Then tmrBlink.Interval = О tmrBlink.Enabled = False mblnlnitialized = False ' если мигание отключено, возвращаем объект ' в исходное состояние If TypeOf mobjTarget Is Form Then FlashWindow mobjTarget.hwnd, CLng(False) Elself TypeOf mobjTarget Is Control Then mobjTarget.ForeColor = mlngForeground mobjTarget.BackColor = mlngBackground End If Else tmrBlink.Enabled = True tmrBlink.Interval = 1000 \ txtRate End If End Sub Обработчик события Change элемента UpDown просто копирует значение последнего в текстовое поле. Процедура, обрабатывающая событие Timer таймера, обеспечивает мигание так: если целевой объект — форма, вызывает API-функцию FlashWindow, а если это видимый элемент управления, обменивает значения свойств ForeColor и BackColor формы и элемента. (Кстати, использование API-функции FlashWindow эффективнее обмена значений свойств ForeColor и BackColor.) Обработчик события Change текстового ноля определяет частоту мигания. кО См. также... • Главу 12 «Окна, диалоговые окна и прочие формы» — подробнее об API-функции FlashWindow.
ГЛАВА 6
97
ActiveX-элементы
Отладить элемент управления? Создав ActiveX-эле мент, Вы, наверное, захотите его отладить. По умолчанию Visual Basic 6 предлагает отладку ActiveX-элементов в Internet Explorer, отображая их в окне браузера после выбора из меню Run команды Start (рис. 6-2). Хотя это позволяет выяснить, как будет выглядеть элемент управления в период выполнения, отладка в таком режиме — идея не самая удачная, потому что проверить свойства и методы элемента здесь не так-то просто. ЧашЛ SlurtK>WIl№Utmk«-l.llnl
о
ечиЬ
Но™
";$ . Sea'ch
isual Sli id o\VEI 33\tlnker Hr.i
Ka
7] -
Рис. 6-2 £сл« бы создадите проект ActiveX-элемента и запустите его, Visual Basic 6 покажет элемент управления в окне Internet Explorer Чтобы по-настоящему протестировать ActiveX-эле мент, создайте группу проектов, в которую входят тестовый проект и проект ActiveX-элемента: 1. Загрузив проект элемента управления, сохраните его, выбрав из меню File команду Save Project. 2, Выберите из меню File команду New Project. 3- Дважды щелкните значок Standard EXE. Visual Basic создаст новый проект с единственной формой. В нем Вы и протестируете свой ActiveX-элемент. 4. Выберите из меню File команду Add Project. 5. В диалоговом окне Add Project на вкладке Existing (существующий) выберите проект своего ActiveX-элемента и щелкните кнопку Open. 6. Закройте окно UserControl, если оно открыто. Сразу после его закрытия Visual Basic активизирует на панели Toolbox значок созданного Вами ActiveX-элемента.
98
ЧДСТЫ1
ДОРОГОЙ ДЖОК КАК?..
7. Щелкните этот значок в Toolbox и разместите элемент управления на форме тестового проекта. 8. Напишите код тестового проекта, манипулирующий свойствами и методами этого элемента. 9. Запустите тестовый проект. Следующий код позволяет протестировать элемент Blinker (blnkTest), размещенный на форме с текстовым полем txtStuffOption Explicit Private Sub Form_Load() ' определяем мигающий объект blnkTest.Blink txtStuff, 1 End Sub Private Sub FormJJlickO 1 прекращаем мигание blnkTest.Interval = 0 End Sub Private Sub blnkTest_Blinked() Static intCount As Integer intCount = intCount + 1 Caption = "Blinked " & intCount & " times" End Sub
Элемент Blinker в процессе тестирования показан на рис. 6-5-
Рис. 6-3 Тестирование элемента управления Blinker При такой отладке элемента управления можно отслеживать выполнение кода ActiveX-элемента из тестового проекта. При этом Вы можете устанавливать точки прерывания, выполнять код в пошаговом режиме и использовать контрольные выражения и переменные, как показано на рис. 6-4. Часть кода элемента управления исполняется перед выполнением тестового проекта. Чтобы убедиться в этом, поставьте точку прерывания в процедуре UserControl_Resize до размещения элемента управления на форме. Visual Basic перейдет в эту точку, как только Вы отпустите кнопку мыши, добавив ActiveX-элемент на форму. Вы можете изменить код своего ActiveX-элемента в любой момент, но помните, что при открытии окна UserControl элемент ста-
ГЛАВА Б
99
ActiveX-элементы
нет недоступным как на тестовой форме, так и в Toolbox. Чтобы вновь сделать его доступным, закройте окно UserControl,
Elseli TypeOf гаоЬлТшдес Is Control "Tbfr It Mot ittblnlnitial On Етгсос С-оТо errT mlngForegtound = rn
.'I
Рис. 6-4 Элемент управления Blinker при отладке из тестового проекта Если Вы уже поместили свой элемент на тестовую форму, а затем добавили к нему какое-нибудь свойство, действующее на этапе разработки, то элемент управления на форме блокируется. Тот же эффект Вы подучите, прервав выполнение кода ActiveX-элемента. Элемент снова активизируется при запуске тестового проекта. ПРИМЕЧАНИЕ При инициализации ActiveX-элемента в период выполнения свойства других элементов управления на той же форме могут быть доступны, а могут быть и недоступны коду ActiveX-элемента. Ссылка на элемент управления, помещенный на форму до тестируемого ActiveX-элемента, иногда приводит к зависанию приложения. Похоже, это ошибка Visual Basic.
Откомпилировать и зарегистрировать элемент управления? Компиляция ActiveX-элемента — операция очень простая, но сначала надо решить, компилировать его в машинный код или псевдокод (р-код). Машинный код исполняется быстрее, но р-код дает OCX-файл меньшего размера. В случае элемента управления Blinker эти различия минимальны. Поскольку его работа зависит от событий таймера, скорость вы-
ЧАСТЬ II
100
ДОРОГОЙ ДЖОН, КАК?.
полнения не имеет особого значения, а разница между OCX-файлами с машинным и р-кодом составляет всего 5 килобайт (Кб). Конечно, если Вы загружаете элемент управления по Интернету, разница даже в 5 Кб может оказаться решающей. Поэтому все элементы управления, которые Вы собираетесь использовать в приложениях для Интернета, лучше компилировать в р-код. Чтобы установить параметры компилятора и скомпилировать созданный Вами элемент управления сделайте следующее. 1. Выберите проект элемента управления в окне Project Explorer. Затем выберите из меню File команду Make, дополненную именем проекта (например, Make Blinker.OCX), и появится диалоговое окно Make Project. 2. Щелкните кнопку Options (параметры), чтобы открыть диалоговое окно Project Properties. 3. Активизируйте вкладку Compile и выберите метод компиляции и параметры оптимизации кода, после чего щелкните кнопку ОК. 4. В диалоговом окне Make Project укажите каталог и имя файла, в который следует поместить скомпилированный элемент управления, и щелкните кнопку ОК, чтобы начать компиляцию. По окончании компиляции Visual Basic автоматически регистрирует элемент управления на Вашем компьютере. Зарегистрированный элемент управления появляется в диалоговом окне Components (рис. 6-5).
ActiveX Control. LlShockwaw Flash jTLBMSMusCd ! TreeCH CiE Control module ,VB6 Application Wizard 1KB 6 Data Form Wizard Wizard
: Wang Image Ad™ Co "Wang Image Ed* Control .'. Wang Image 5г_яп Control
3 Jj
.„,;,,,;,yf f~ gi|iMt<
Рис. 6-5 Диалоговое окно Components, в котором перечисляются все зарегистрированные элементы управления Если Ваш элемент управления — часть приложения, которое Вы планируете распространять, Package and Deployment Wizard (мастер упаковки и распространения) обеспечит регистрацию этого
ГЛАВА 6
ActiveX-элементы
101
элемента при установке приложения на другом компьютере. А если Вы распространяете свой элемент управления без установочной программы, нужно будет установить DLL исполняющей системы (runtime DLL) Visual Basic, MSCOMCT2.OCX и собственно BLINKER.OCX, а затем зарегистрировать MSCOMCT2.OCX и BLINKER.OCX с помощью утилиты REGOCX32.EXE, которая находится в каталоге \COMMON\TOOLS\VB \REGUTILS на компакт-диске Visual Studio. Следующая команда регистрирует ActiveX-элемент Blinker: REGOCX32.EXE BLINKER.OCX
Создать свойство, доступное на этапе разработки? Свойства TargetObject и Interval элемента управления Blinker могут быть установлены только в период выполнения. Чтобы создать свойство, которое можно было бы установить на этапе разработки из окна свойств, в процедурах обработки событий ReadProperties и WriteProperties следует воспользоваться объектом PropertyBag: ' определяем Interval как свойство, доступное на этапе разработки Private Sub UserControl_ReadProperties(PropBag As PropertyBag) updnRate.Value = PropBag.fleadPropertyC'Interval", 0) End Sub Private Sub UserControl_WriteProperties(PropBag As PropertyBag) PropBag.WriteProperty "Interval", updnRate.Value, 0 End Sub
Рис. 6-6 Объект PropertyBag позволяет добавлять свойства в окно Properties
102
ЧАСТЫ!
ДОРОГОЙ ДЖОН, КАК?..
Этот код добавляет свойство Interval в список свойств элемента управления Blinker, доступных в окне Properties, как показано на рис. 6-6. Вам также следует добавить оператор PropertyChanged в процедуру Property Let Interval, чтобы Visual Basic сохранил значение свойства, измененного на этапе разработки. Вот как выглядит код модифицированной процедуры: .Interval Public Property Let Interval(Setting As Integer) ' настраиваем элемент управления UpDown; это приводит ' к автоматическому обновлению элементов TextBox и Timer updnRate.Value = Setting ' обновляем значение свойства, установленного на этапе разработки PropertyChanged "Interval" End Property
И, наконец, нужно позаботиться о том, чтобы изменение значения свойства Interval на этапе разработки не привело к генерации события таймера. Узнать, находится элемент управления в режиме разработки или он выполняется клиентским приложением, можно, проверив свойство Ambient.UserMode объекта UserControl. Это свойство равно False на этапе разработки и True в период выполнения. Чтобы избежать появления ошибок при записи в свойство Interval ненулевого значения на этапе разработки, внесем изменения в процедуру обработки события Change текстового поля: Private Sub txtRate_Change() ' выход, если мы находимся в режиме разработки If Not UserControl.Ambient.UserMode Then Exit Sub 1 устанавливаем свойство Interval элемента управления Timer ' в соответствии со значением в текстовом поле If txtRate = 0 Then tmrBlink.Interval = О tmrBlink.Enabled = False fnblnlnitialized = False если мигание отключено, возвращаем объект ' в исходное состояние If TypeOf mobjTarget Is Form Then FlashWindow mobjTarget.hwnd, CLng(False) Elself TypeOf mobjTarget Is Control Then mobjTarget.ForeColor = mlngForeground mobjTarget.BackColor = mlngBackground End If Else tmrBlink.Enabled = True tmrBlink.Interval = 1000 \ txtRate End If End Sub
ГЛАВА 6
ActiveX-элементы
103
ПРИМЕЧАНИЕ Свойство UserMode не доступно в обработчике события Initialize элемента управления. Чтобы свойство TargetObject стало доступно на этапе разработки, прибегнем к одному трюку. Так как окно Properties не показывает объекты, создадим новое свойство, которое принимает строку, используемую в свою очередь для установки TargetObject. Это свойство — TargetString — уже может появиться в окне Properties. ".TargetString Public Property Let TargetString(Setting As String) If UserControl.Parent.Name = Setting Then Set TargetObject = UserControl.Parent Elself Setting <> "" Then Set TargetObject = UserControl.Parent.Controls(Setting) End If
End Property Public Property Get TargetStringO As String If TypeName(mobjTarget) <> "Nothing" Then TargetString = mobjTarget.Name Else TargetString = "" End If
End Property Кроме того, добавим в процедуру Property Set TargetObject оператор PropertyChanged: 1 """.TargetObject Public Property Set TargetObject(Setting As Object) If TypeName(Setting) = "Nothing" Then Exit Property ' записываем во внутреннюю переменную ссылку на целевой объект Set mobjTarget = Setting ' значение свойства изменилось PropertyChanged "TargetObject" End Property
А теперь, чтобы добавить свойство TargetString в окно Properties, отредактируйте процедуры обработки событий ReadProperties и WriteProperties: ' получаем значения, установленные на этапе разработки Private Sub UserControljieadProperties(PropBag AS propertyBag) updnRate.Value = PropBag,ReadPropertyC'Interval", 0) TargetString = PropBag.ReadPropertyC'TargetString", "") End Sub 1 сохраняем значения, установленные на этапе разработки Private Sub UserControl_WriteProperties(PropBag As PropertyBag) PropBag.WriteProperty "Interval", updnRate.Value, 0 PropBag.WriteProperty "TargetString", TargetString, End Sub
104
ЧАСТЫ! ДОРОГОЙ ДЖОН, КАК?.
Вывести диалоговое окно Property Pages? Страницы свойств (property pages) позволяют определять свойства, доступные на этапе разработки ActiveX-элемента, в диалоговом окне с несколькими вкладками, а не из окна Properties среды Visual Basic. Такие страницы полезны для элементов управления, имеющих группы взаимосвязанных свойств; они упрощают доступ к свойствам, и пользователю не приходится подолгу прокручивать содержимое окна Properties в поисках нужного. На страницах свойств можно отображать и списки допустимых значений, определяемых при разработке. Например, страница свойств элемента управления Blinker показывает список объектов на форме — допустимых значений свойства TargetString (рис. 6-7).
Рис. 6-7 Страница свойств элемента управления Blinker
Рис. 6-8 Диалоговое окно Connect Property Pages связывает страницу свойств с элементом управления Чтобы добавить страницу свойств к элементу управления, в окне Project Explorer выберите проект данного элемента, из меню Project — команду Add Property Page (добавить страницу свойств) и дважды щелкните значок Property Page. Присвойте странице свойств имя и задайте текст, показываемый на ярлычке страницы свойств,
ГЛАВА 6
ActiveX-элементы
105
установив соответствующим образом свойства Name и Caption в окне Properties. Чтобы связать страницу свойств с элементом управления, откройте его окно UserControl и выберите этот элемент управления. В окне Properties дважды щелкните свойство PropertyPages, чтобы открыть диалоговое окно Connect Property Pages (присоединить страницы свойств), показанное на рис. 6-8. Выберите нужную страницу свойств в списке Available Property Pages (доступные страницы свойств) и щелкните кнопку ОК. Как только Вы свяжете страницу свойств с ActiveX-элементом, Visual Basic добавит строку (Custom) в список свойств, доступных на этапе разработки данного ActiveX-элемента. Двойной щелчок этой строки откроет страницу свойств. Как и в случае окна UserControl, открытие окна страницы свойств означает, что Вы переключаетесь в режим конструирования (design mode) и можете размещать в этом окне элементы управления и писать код, реагирующий на события, — точно так же, как Вы делали это для формы. У страницы свойств есть встроенный набор SelectedControls; из него можно получить экземпляр элемента управления, к которому относится эта страница свойств. Данные, показываемые в элементах управления на странице свойств, инициализируйте в процедуре обработки события SelectionChanged. Приведенный ниже код устанавливает начальные значения свойств Interval и TargetObject, которые показываются на странице свойств Blinker в текстовом поле txtlnterval и поле со списком cmb TargetString. Private Sub PropertyPage_SelectionChanged() ' устанавливаем значение в поле Interval на странице свойств ' соответственно значению одноименного свойства элемента управления txtlnterval = SelectedControls(O).Interval ' формируем список объектов для свойства TargetString Dim frmParent As Form Dim ctrlndex As Control Dim strTarget As String 1 получаем ссылку на форму, на которой размещен элемент управления Set frmParent = SelectedControls(O).Parent strTarget = SelectedControls(0).TargetString If strTarget <> "" Then 1 добавляем текущее значение свойства в поле со списком cmbTargetString.List(0) = strTarget End If If frmParent.Name <> strTarget Then ' добавляем имя формы в поле со списком cmbTargetStririg.Addltem frmParent.Name End If 1
добавляем название каждого элемента управления, расположенного на форме, в поле со списком For Each ctrlndex In frmParent.Controls 1
106
ЧАСТЬ II
ДОРОГОЙ ДЖОН, КАК?..
1
исключаем элемент управления Blinker If TypeName(ctrlndex) <> "Blinker" Or ctrlndex.Name = strTarget Then cmbTargetString.Addltem ctrlndex.Name End If Next ctrlndex ' показываем текущее значение TargetString cmbTargetString.Listlndex = 0 End Sub
ПРИМЕЧАНИЕ Набор SelectedControls не доступен при обработке события Initialize страницы свойств. Заметьте, что SetectedControls(O) возвращает объект, выбранный пользователем в данный момент, в нашем случае — элемент управления Blinker. Мне пришлось добавить в Blinker свойство Parent, чтобы страница свойств могла получить информацию о контейнере элемента Blinker. Исходный код свойства Parent элемента управления Blinker выглядит так: ' """.Parent Public Property Get ParentO As Object Sat Parent = UserControl.Parent End Property
Процедура обработки события ApplyChanges страницы свойств служит для записи значений, установленных на странице, в свойства ActiveX-элемента. Следующий код считывает такие значения и помещает их в свойства ActiveX-элемента: Private Sub PropertyPage_ApplyChanges() 1 сохраняем значения со страницы свойств ' в свойствах элемента управления SelectedControls(O).Interval = txtlnterval.Text SelectedControls(O).TargetString = _ cmbTargetString.List _ (cmbTargetString.Listlndex) End Sub
Вы должны уведомлять страницу свойств о том, что пользователь изменил на ней значения. Встроенное свойство Changed указывает Visual Basic обработать изменения свойств, когда пользователь щелкнет кнопку ОК или Apply. Процедуры, показанные далее, устанавливают свойство Changed, если какое-либо значение на странице свойств элемента Blinker изменяется. Private Sub txt!nterval_Change() . Changed = True End Sub Private Sub cmbTargetString_Change() Changed = True End Sub
ГЛАВА 6
ActiveX-элементы
107
Страницу свойств можно открыть несколькими способами: щелкнуть элемент управления правой кнопкой мыши и выбрать из контекстного меню команду Properties; дважды щелкнуть свойство (Custom) в окне Properties; щелкнуть кнопку с многоточием в поле значений свойства (Custom). Чтобы добавить кнопку с многоточием к какому-либо свойству в окне Properties: 1. переключитесь в окно исходного кода ActiveX-элемента; 2. выберите из меню Tools команду Procedure Attributes, чтобы открыть одноименное диалоговое окно; 3. щелкните кнопку Advanced; 4- выберите нужное свойство в списке Name, а в списке Use This Page In Property Browser (использовать эту страницу при просмотре свойств) — страницу, которую Вы хотите связать с данным свойством (рис. 6-9); 5. щелкните кнопку ОК.
Рис. 6-9 Диалоговое окно Procedure Attributes позволяет связать страницу свойств с определенным свойством Так, чтобы открыть страницу свойств элемента управления Blinker, показанную на рис. 6-10, щелкните кнопку с многоточием в поле значений свойства TargetString. При открытии страницы свойств щелчком кнопки с многоточием фокус ввода должен устанавливаться на соответствующее свойство. Это действие реализуется в обработчике события EditProperty страницы свойств. Вот пример такой процедуры, устанавливающей фокус на нужное поле после щелчка кнопки с многоточием в поле значений свойства TargetString или Interval элемента управления Blinker:
108
ЧАСТЫ! ДОРОГОЙ ДЖОН, КАК?.
Private Sub PropertyPage_EditProperty(PropertyName As String) 1 устанавливаем фокус на соответствующий элемент управления Select Case PropertyName Case "TargetString" cmbTargetString.SetFocus Case "Interval" txtlnterval.SetFocus Case Else End Select End Sub
Рис. 6-10 Диалоговое окно Property Pages элемента управления Blinker, открытое щелчком кнопки с многоточием
Загрузить свойство асинхронно? ActiveX-элементы, используемые на Web-страницах, часто загружают значения своих свойств асинхронно. Это позволяет просматривать содержимое Web-страницы в Web-браузере, пока идет фоновая пересылка графики и других данных. На рис. 6-11 показан элемент управления Asynchronous Animation, созданный на базе элемента управления Animation из библиотеки стандартных элементов управления Microsoft Windows Common Controls-2 6.0 (MSCOMCT2.OCX). Свойство AVIFile элемента управления Asynchronous Animation принимает строку, которая может содержать как спецификацию локального файла, так и универсальный указатель на ресурсы (Uniform
ГЛАВА 6
ActiveX-элементы
109
Resource Locator, URL). Метод AsyncRead начинает передачу файла на локальный компьютер, записывая его в каталог Windows\Temp под именем, формируемым самим Visual Basic. Исходный код свойства AVIFile таков: Option Explicit Dim fnstrAVISourceFile As String Dim mstrTempAVIFile As String 1
—.AVIFile Property Let AVIFile(Setting As String) If UserControl.Ambient.UserMode And Len(Setting) Then AsyncRead Setting, vbAsyncTypeFile, "AVIFile" mstrAVISourceFile = Setting End If End Property Property Get AVIFile() As String AVIFile = mstrAVISourceFile End Property ...
Рис. 6-11 Элемент управления Asynchronous Animation, который воспроизводит AVI-файл, загруженный в фоновом режиме По завершении пересылки метод AsyncRead генерирует событие AsyncReadComplete. Процедура обработки AsyncReadComplete обрабатывает все асинхронные события элемента управления. Переход к обработке события от нужного асинхронного свойства обеспечивает оператор Select Case, в котором анализируется значение свойства AsyncProp.PropertyName. В данном примере обработчик события AsyncReadComplete открывает и воспроизводит AVI-файл, используя элемент управления Animation с именем aniControl: ' базовый обработчик всех событий AsyncReadComplete Private Sub UserControl_AsyncReadComplete _ (AsyncProp As AsyncProperty) Select Case AsyncProp.PropertyName ' для свойства AVIFile Case "AVIFile11 ' получаем имя временного файла mstrTempAVIFile = AsyncProp.Value 1 открываем файл aniControl.Open mstrTempAVIFile
ПО
ЧАСТЫ1
ДОРОГОЙ ДЖОН, КАК?..
' воспроизводим анимационный ролик aniControl.Play Case Else End Select End Sub
По завершении работы элемента управления обязательно удалите все созданные Вами временные файлы. Хорошо продуманные приложения для Интернета не должны забивать диски на компьютерах пользователей бесполезными временными файлами. Этот код закрывает AVI-файл и удаляет временный файл с данными: Private Sub UserControl_Termlnate() ' удаляем временный файл If Len(ntstrTempAVTFile) Then aniControl.Close Kill mstrTempAVIFile End If End Sub
Использовать элемент управления Asynchronous Animation очень легко: поместите его на форму и установите свойство AVIFile в обработчике какого-нибудь события. Например, код, приведенный ниже, запускает анимационный ролик Find File с компакт-диска Visual Studio в момент загрузки формы (aaniFindFile — имя элемента управления Asynchronous Animation): Private Sub Form_Load(} aaniFindFile.AVIFile = "d:\common\graphics\avis\findflle.avi" End Sub IrO
См. также...
• Главу 8 «Создание компонентов для Интернета» — о том, как внедрить элемент управления Asynchronous Animation на Webстраницу.
Создать элемент управления для использования с базой данных? ActiveX-элементы способны отображать информацию из записей базы данных, позволяя связывать с данными одно или несколько свойств. Связывание с данными (data binding) — формирование зависимости между свойством элемента управления и каким-либо полем записи или запроса базы данных. Например, свойство Interval элемента Blinker, созданного в предыдущих разделах этой главы, могло бы быть связано с числовым полем базы данных. Чтобы установить связь между свойством элемента управления и какимилибо данными:
ГЛАВА Б
ActiveX-элементы
111
1. загрузите в Visual Basic проект элемента управления;. 2. выберите из меню Tools команду Procedure Attributes. Появится одноименное диалоговое окно; 3. щелкните кнопку Advanced. Visual Basic покажет полный вариант диалогового окна Procedure Attributes (рис. 6-9); 4. в списке Name укажите имя свойства, связываемого с данными; 5. установите флажок Property Is Data Bound (свойство связано с данными). Затем пометьте другие флажки в группе Data Binding (связывание с данными), описанные ниже, и щелкните кнопку ОК. Q Установите флажок This Property Binds To DataField (это свойство связывается с полем данных), если это свойство Вашего элемента управления — главное или единственное, связанное с данными. Если же у элемента управления несколько свойств, связываемых с данными, пометьте этот флажок для свойства, которое будет содержать важнейшее поле из записи базы данных. Так или иначе, но этот флажок можно активизировать только для одного свойства элемента управления. Q Установите флажок Show In DataBindings Collection At Design Time (показывать в наборе DataBindings на этапе разработки), чтобы данное свойство появлялось в диалоговом окне Data Bindings (связи с данными) на этапе разработки. А если свойство должно настраиваться только в период выполнения, оставьте этот флажок неактивным. Q Установите флажок Property Will Call CanPropertyChange Before Changing (свойство будет вызывать CanPropertyChange перед изменением), если пользователь или программа может изменять данные, отображаемые через это свойство. Тогда будет проверяться допустимость данных, записываемых в это свойство. Q Установите флажок Update Immediate (обновлять немедленно), чтобы любые изменения данных в свойстве сразу отражались на поле, связанном с этим свойством.
Education indL.de; ч Е:~ г. рмипЫо» лап, 2э\аваа Slale Univasilji in 1970 Ste alu compleled "The Art" of the Cold Cil:" Nancy is a n*ni»r of Toastmastsi»
Рис. 6-12 Элемент управления Employee предназначен для отображения полей из набора записей Employees ЦЦ NWIND
5—1204
112
ЧАСТЬ И
ДОРОГОЙ ДЖОН, КАК?..
Я создал простой элемент управления Employee (рис. 6-12) специально для использования с базой данных NWIND, поставляемой с Visual Basic. Элемент Employee (DatCtl.VBP) состоит из нескольких меток и текстового поля, которое показывает данные из таблицы Employees базы данных NWIND. Свойства FirstName, LastName, HireDate и Notes этого элемента определены так: ' раздел определений свойств Public Property Get FirstNameO FirstName = IblFirstName.Caption End Property Public Property Let FirstName(Setting) IblFirstName.Caption = Setting PropertyChanged FirstName End Property Public Property Get LastNameC) As String LastName = IblLastName.Caption End Property Public Property Let LastName{Setting As String) IblLastName.Caption = Setting PropertyChanged LastName End Property Public Property Get HireDateO As String HireDate = IblHireDate.Caption End Property Public Property Let HireDatefSetting As String) IDlHireDate.Caption = Setting PropertyChanged HireDate End Property Public Property Get Notes() As String Notes = txtNotes.Text End Property Public Property Let Notes(Setting As String) txtNotes.Text = Setting PropertyChanged Notes End Property Заметьте: все процедуры Property Let содержат оператор PropertyChanged.. Согласно документации Visual Basic Вы должны включать этот оператор в любые свойства, связываемые с данными, — даже если они доступны лишь в период выполнения.
ГЛАВА 6
ActiveX-элементы
113
Чтобы использовать элемент управления Employee сделайте следующее. 1. Откройте новый проект стандартной ЕХЕ-программы. 2. Добавьте в него проект DatCtl.VBP, чтобы создать новую группу проектов. 3. Поместите на форму элемент управления Data и настройте его свойство DataBaseName на базу данных NWIND.MDB (она находится в каталоге программ Visual Basic). В списке значений свойства RecordSource выберите Employees. 4. Поместите на форму элемент управления Employee и в поле значений его свойства DataBindings щелкните кнопку с многоточием. Visual Basic откроет диалоговое окно Data Bindings (рис. 6-13).
Рис. 6-13 Для связывания свойств с конкретными полями в наборе записей используйте диалоговое окно Data Bindings 5. В списке Property Name выберите свойство FirstName, в списке Data Source (источник данных) — Datal и в списке Data Field (поле данных) — поле FirstName.
?"ie Ни: в ЗА depee in Engish Пят SI Ls-j'eic Irllecp =l-B,!llijenl n Frencli ard йетг.ап
4ll
J
JKjjIJV.'I'Ji: Empbyee;
JuMl
Рис. 6-14 Совместное использование элементов управления Employee и Data для отображения записей из EfiNWINDMDB
114
ЧДСТЫ1 ДОРОГОЙ ДЖОН, КАК?..
6. Повторите операцию из п. 5 для установки всех свойств элемента Employee и щелкните кнопку ОК. 7. Запустите проект. При «пролистывании» записей в элементе Data элемент Employee будет показывать информацию из базы данных NWIND (рис. 6-14).
Использовать элемент управления DataRepeater? Элемент DataRepeater — своего рода контейнер для создаваемых Вами ActiveX-элементов, связанных с данными. Чтобы задействовать DataRepeater, Вы должны сначала создать и откомпилировать соответствующий элемент управления (вроде Employee, о котором я рассказывал в предыдущем разделе). DataRepeater позволяет заменять страничные представления (page views) базы данных прокручиваемым списком. В этом плане он похож на FlexGrid, но дает возможность вставлять в сетку дополнительные элементы управления. Рис. 6-15 иллюстрирует, как выглядит одна и та же таблица базы данных на форме и в элементах управления FlexGrid и DataRepeater.
Рис. 6-15 Нестандартная форма и элементы управления FlexGrid и DataRepeater — три способа просмотра таблицы Employees базы данных NWIND ПРИМЕЧАНИЕ DataRepealer требует совместимого источника данных типа элемента управления ADO Data. Co встроенным элементом управления Data он не работает.
ГЛАВА о _АсйуеХлЭлементы
11_5
Чтобы использовать элемент управления DataRepeater сделайте следующее. 1. Выберите из меню Project команду Components. Visual Basic откроет диалоговое окно Components. 2. Установите флажки в строках Microsoft DataRepeater Control 6.0 и Microsoft ADO Data Control 6.0, потом щелкните кнопку ОК. Visual Basic добавит на панель Toolbox элементы управления ADO Data и DataRepeater. 3. Поместите на форму элемент управления DataRepeater. 4. Поместите на ту же форму элемент управления ADO Data. 5. В поле значений свойства Connectionstring элемента ADO Data щелкните кнопку с многоточием, чтобы открыть диалоговое окно Property Pages. 6. Выберите параметр Use ODBC Data Source Name (использовать имя источника данных ODBC) и либо укажите в списке источник данных (и тогда пропустите этапы 7-11), либо создайте новый источник данных, щелкнув кнопку New. (В пп. 7-11 я поясняю, как создать новый источник данных ODBC.) 7. В окне Create New Data Source (создание нового источника данных) выберите переключатель System Data Source (Applies To This Machine Only) [системный источник данных (применим только к этой машине)] и щелкните кнопку Next (далее). 8. В списке Name выберите Microsoft Access Driver (*.MDB), щелкните кнопку Next, затем — кнопку Finish (готово). 9. В поле Data Source Name окна ODBC Microsoft Access Setup (установка Microsoft Access ODBC) введите имя источника данных. 10. Щелкните кнопку Select (выбрать) и укажите базу данных Access для этого источника данных. Щелкните кнопку ОК, чтобы закрыть окно ODBC Microsoft Access Setup. 11. Активизируйте параметр Use ODBC Data Source Name, выберите в списке источник данных, который Вы создали на этапе 9, и щелкните кнопку ОК. 12. В поле значений свойства RecordSource элемента ADO Data щелкните кнопку с многоточием, чтобы открыть диалоговое окно Property Pages. 13. В списке Command Type (тип команды) выберите строку 2 - adCrndTable, а в списке Table Or Stored Procedure Name (имя таблицы или хранимой процедуры) — нужную таблицу; потом щелкните кнопку ОК. 14. Выберите элемент управления DataRepeater и установите его свойство DataSource на имя элемента управления ADO Data, который Вы разместили на форме в п. 4.
116
ЧАСТЫ1 ДОРОГОЙ ДЖОН, КАК?..
15. Настройте свойство RepeatedControlName элемента DataRepeater на имя ActiveX-элемента, связанного с данными. 16. В поле значений свойства (Custom) элемента DataRepeater щелкните кнопку с многоточием, чтобы открыть диалоговое окно Property Pages, и выберите вкладку RepcaterBindings (рис. б-1б). 17. В списке PropertyName укажите имя нужного свойства, а в списке DataField — поле записи, которое будет отображаться данным свойством; затем щелкните кнопку Add.
Рис. 6-16 Свяжите повторяемые свойства (repeated properties) с полями записи в диалоговом окне Property Pages для элемента управления DataRepeater 18. Повторите операцию из п. 8 для всех свойств элемента управления, связанных с данными. Закончив, щелкните кнопку ОК. В период выполнения Вы сможете прокручивать записи, используя кнопки со стрелками в элементе ADO Data или полосу прокрутки в элементе Data Repeater. He исключено, что именно по этой причине Вы предпочтете скрывать элемент ADO Data в период выполнения.
Создать контейнерный элемент управления? Если свойство ControlContainer нестандартного элемента управления установить как True, объекты, размещенные поверх этого элемента, можно перемещать и масштабировать как единую группу. К контейнерным элементам управления, поставляемым с Visual Basic, относятся, например, Microsoft Tabbed Dialog и DataRepeater. На рис. 6-17 показан простой контейнерный элемент управления, созданный в Visual Basic с применением элементов Shape и Label.
ГЛАВА 6 ActiveX-элементы
117
Код элемента Containr (Containr.VBP) обрабатывает изменение размеров рамки так: Private Sub UserControl_Resize() ' подгоняем рамку под размеры элемента управления shpFrame.Width = UserControl.ScaleWidth - shpFrame.Left shpFrame.Height = UserControl.ScaleHeight - shpFrame.Top End Sub Заголовок элемента Containr инициализируется через объект Exlender (он обеспечивает доступ ко встроенным свойствам, которые Visual Basic и другие контейнерные элементы управления открывают всем объектам). Объект Extender доступен в процедуре InitProperties, но, если Вы попытаетесь обратиться к нему на более ранних стадиях жизненного цикла элемента управления (например, из обработчика события Initialize), возникнет ошибка. Следующий код отображает имя элемента управления, присваиваемое самим Visual Basic при создании экземпляра этого элемента: Private Sub UserControl_InitProperties() 1 отображаем соответствующий заголовок IblTitle.Caption = Extender.Name End Sub
Рис. 6-17 Свойство ControlContainer элемента управления Containr установлено как, True, поэтому он имитирует поведение элемента управления Frame Свойство Caption устанавливает и возвращает заголовок, показываемый меткой IblTitle. Это свойство, предназначенное для чтения и записи, доступно на этапе разработки, поэтому для него предусмотрены процедуры Property Let и Get, а также обработчики событий ReadProperties и WritcProperties: Private Sub UserControl_ReadProperties(PropBag As PropertyBag) IblTitle.Caption = PropBag.ReadPropertyC'Caption") End Sub
Private Sub UserControl_WriteProperties(PropBag As PropertyBag)
118
ЧАСТЫ1
ДОРОГОЙ ДЖОН, КАК?..
PropBag.WriteProperty "Caption", IblTitle.Caption
End Sub
' раздел определений свойств Public Property Get CaptionC) As String Caption = IblTitle.Caption End Property Public Property Let Caption(Setting As String) IblTitle.Caption = Setting End Property Свойство Controls возвращает набор элементов управления, содержащихся в элементе Containr. Это единственное, чего не предусматривает стандартный элемент Frame, и я включил этот пример специально для того, чтобы продемонстрировать, как использовать набор ContainedControls. Последний создается Visual Basic, когда Вы устанавливаете свойство ControlContainer как True; этого набора нет у элементов управления других типов. Определение свойства Controls (только для чтения) выглядит так: ' свойство только для чтения Public Property Get ControlsO As Collection Set Controls = UserControl.ContainedControls End Property
Г Л А В А
Использование компонентов для Интернета JL оворя о программировании для Интернета, разные люди имеют в виду разные вещи. Применительно к коммерческому программному обеспечению это может означать разработку новых клиентсерверных средств, использующих Web. Отдел продаж и маркетинга может понимать под этим термином создание Web-страницы, способной принимать заказы на продукцию, продаваемую через Интернет, а отдел кадров — публикацию некоего справочника для сотрудников, работающего как автономное приложение в интрасети. Из-за таких расхождений я разбил тематику по программированию для Интернета на три главы. В этой главе я покажу, как ActiveX-элементы, поставляемые с Visual Basic, позволяют решать самые разные задачи, начиная с обращения к низкоуровневым коммуникационным протоколам и заканчивая созданием собственного Web-браузера на основе Internet Explorer (IE). В главе 8 «Создание компонентов для Интернета» я объясню, как создаются контейнеры для «содержания», распространяемого по Интернету. В этой же главе будут рассмотрены вопросы, связанные с созданием ActiveX-элементов и Web-классов, которые можно использовать в Интернете. А в главе 9 «Создание приложений для Интернета» мы, опираясь на информацию из глав 7 и 8, реализуем на практике все три класса приложений для Интернета.
Выбрать подходящий ActiveX-компонент? Visual Basic (Professional и Enterprise Editions) включает три ActiveXэлемента, которые обеспечивают доступ ко всем уровням связи через Интернет. & Элемент управления Winsock (MSWINSCK.OCX) предоставляет низкоуровневый доступ к TCP и UDP — сетевым протоколам, применяемым в Интернете. С помощью этого элемента управления создаются программы для «бесед» и прямой передачи данных между двумя и более компьютерами, объединенными сетью.
120
ЧАСТЬ II
ДОРОГОЙ ДЖОН, КАК?..
я
Элемент управления Internet Transfer (MSINET.OCX) позволяет копировать файлы с HTTP- и FTP-серверов, просматривать на них каталоги и получать данные как синхронно, так и асинхронно. Этот элемент управления предназначен для создания средств просмотра FTP-серверов и обмена файлами. в. Элемент управления WebBrowser (SHDOCVW.DIX) заключает в себе все возможности IE. Используйте его для расширения существующих приложений средствами Интернет-браузера или для управления IE из другого приложения посредством Автоматизации. ПРИМЕЧАНИЕ Элемент WebBrowser в диалоговом окне Components представляется строкой Microsoft Internet Controls и устанавливается вместе с IE. Версия 4.0 последнего находится на компакт-диске Visual Studio.
В следующих разделах мы поговорим о разных уровнях доступа к Интернету и рассмотрим примеры использования каждого из этих элементов управления. I,
Представить уровни интернет-протоколов? Интернет — система, связывающая постоянно растущее число различных компьютерных сетей. Чтобы охватить все это многообразие, в Интернете принят целый набор протоколов. Подобно дипломатическим протоколам, они определяют характеристики каждого уровня Интернета. Иерархия некоторых из важнейших протоколов показана на рис. 7-1. Транспортный уровень обозначает границу, где кончается Ваше приложение и начинается собственно Интернет. Он состоит из протоколов UDP (User Datagram Protocol) и TCP (Transmission Control Protocol). Эти два протокола определяют, как приложения обмениваются данными по Интернету, в том числе как устанавливается соединение и как данные разбиваются на отдельные пакеты. Вы можете использовать протокол UDP или TCP для прямой связи по Интернету. Например, передавать данные в некоем специфическом для Вашего приложения формате или рассылать простое сообщение на множество машин. Поскольку эти протоколы работают на более низком уровне, чем Ваше приложение, они не приводят к тем издержкам, которые свойственны протоколам прикладного уровня (скажем, HTTP или FTP). Как я уже говорил, элемент управления Winsock обеспечивает прямой доступ к протоколам UDP и TCP, а элемент управления Internet Transfer — к протоколам FTP и HTTP. Элемент управления WebBrowser интерпретирует данные, передаваемые по FTP или HTTP, и форматирует их так же, как и IE. Выбор конкретного элемента уп-
ГЛАВА 7
Использование компонентов для Интернета
121
равления зависит от типа создаваемого Вами приложения, и на эту тему мы поговорим в следующих разделах.
Физический уровень
fiber, RS232, V.42
Рис. 7-1 Протоколы разных уровней помогают справиться со сложностью связи по Интернету
Настроить доступ к сети? Операции с большинством компонентов для Интернета, рассматриваемых в этой и двух последующих главах, потребуют доступа минимум к двум компьютерам, подключенным к сети и выполняющим протокол TCP/IP (Transmission Control Protocol/Internet Protocol). Этот протокол входит в состав Windows 95 и Windows NT, но Бы должны убедиться, что он установлен. Для этого дважды щелкните значок Network (Сеть) в окне Control Panel (Панель управления), выберите вкладку Protocols (Протоколы) в Windows NT или Configuration (Конфигурация) в Windows 95 и проверьте список протоколов, установленных на Вашем компьютере (рис. 7-2). Если полноценной сети у Вас нет, создайте в Windows NT Server с помощью службы RAS (Remote Access Server) сервер удаленного доступа и подключитесь к этой машине по телефонной линии, ис-
122
ЧАСТЬ II
ДОРОГОЙ ДЖОН, КАК?..
пользуя утилиту Dial-Up Networking (Удаленный доступ к сети). Служба RAS устанавливается как компонент Windows NT Server, a утилита Dial-Up Networking поставляется с Windows NT и Windows 95 как стандартная программа.
Рис, 7-2 Д/тл использования элемента управления Winsock надо установить протокол TCP/IP
Рис. 7-3 Диа/юговое окно TCP/IP Properties позволяет установить фиксированный IP-адрес компьютера, к которому Вы будете подключаться через RAS и Dial-Up Networking
ГЛАВЛ7
Использование компонентов для Интернета
123
Работая с RAS и утилитой удаленного доступа к сети, Вы скорее всего не сможете использовать «дружественные имена» при подключении к другим компьютерам в сети. Вместо этого Вам придется указывать фиксированный IP-адрес, который можно определить через диалоговое окно TCP/IP Properties (Свойства: TCP/IP) (рис. 7-3). IP-адрес применим везде, где ожидается имя компьютера. Например, в свойстве RemoteHost элемента управления Winsock, в поле Address (Адрес) браузера IE или в методе OpenURL элемента управления Internet Transfer. Для публикации файлов на одной из машин и просмотра их с другой посредством браузера IE Вы, вероятно, установите Microsoft Personal Web Server (PWS) или Microsoft Internet Information Server (IIS). Это весьма удобно для переноса файлов с одной машины на другую, и, кроме того, позволит отлаживать активные серверные страницы (Active Server Pages, ASP) и тестировать пакет интернеткомпонентов перед их распространением. PWS и IIS входят в набор серверных компонентов, поставляемых с Microsoft Visual Studio.
Обеспечить связь через Winsock? Приложения соединяются с Интернетом по IP-адресу и номеру порта. Комбинация IP-адреса и номера порта называется сокетом (socket). Для установления связи между двумя компьютерами Вы должны создать соответствующую пару сокетов. А чтобы создать такие сокеты для работы с протоколом UDP или TCP, можно воспользоваться элементом управления Winsock (MSWINSCK.OCX). Выбор конкретного протокола зависит от типа нужной Вам связи. Щ UDP обеспечивает безадресную связь (не требуя соединения с определенным компьютером). Вы посылаете данные, не зная, кто и когда примет их. Этот протокол подходит для сетевых файловых сервисов, широковещательных сообщений по сети и приложений, позволяющих вести «беседы». ш TCP требует устанавливать соединение с другим компьютером перед обменом данными и закрывать его по завершении сеанса. Этот протокол предусматривает передачу данных отдельными пакетами и проверку корректности их приема. Он удобен для передачи файлов и других больших потоков данных, посылки и приема электронной почты, а также для установления прямой связи между двумя компьютерами. В следующем разделе я покажу, как на основе элемента управления Winsock создать интернет-приложение, рассчитанное на протокол UDP или TCP Однако, прежде чем браться за разработку собственной системы электронной почты, Вы должны осознать, что Winsock — средство, предназначенное для низкоуровневого про-
124
ЧАСТЬ II
ДОРОГОЙ ДЖОН, КАК?..
граммирования, и что для Visual Basic уже реализованы средства поддержки протоколов прикладного уровня. К последним относятся Internet Transfer (MSINET.OCX) для FTP и HTTP, а также ряд коммерческих элементов управления для POP (Post Office Protocol) и передачи звука и трехмерных изображений. Информацию об элементах управления, созданных сторонними разработчиками, Вы найдете в Интернете. Вот несколько адресов, которые послужат Вам отправной точкой: » Mabry Software, Inc. — Internet Pack for POP, Finger, Whols и другие элементы управления (bnp://wwiv.mabry.coni)\ ш devSoft Inc. — IP'Works! for POP, SMTP, DNS и другие элементы управления (f}ttp://tvww.dev-sofl.com)\ и Template Graphics Software, Inc. — продукты из семейства TGS 3D Developer Toolkit (http://unvw.tgs.com).
Вещание по протоколу UDP Использование элемента управления Wmsock с UDP требует двух подготовительных процедур: одной — для передачи данных, другой — для приема. Чтобы передать данные, добавьте элемент Winsock на форму и действуйте по следующей схеме: 1. запишите в свойство RemotePort номер порта, используемого другими машинами для приема данных, посылаемых Вами; 2. установите свойство RemoteHost. Его значением должен быть IPадрес или имя хост-компьютера, который будет принимать Ваши данные; 3. откройте методом Bind локальный порт, через который будут отправлены данные; 4. передайте данные методом SendData. Чтобы принять данные по протоколу UDP: 1. запишите в свойство RemotePort номер порта, указанный при вызове метода Bind в п. 3 предыдущей процедуры; 2. откройте методом Bind локальный порт, через который будут приняты данные. Номер порта должен совпадать со значением, записанным в свойство RemotePort в п. 1 предыдущей процедуры; 3. вызовите метод GetData из обработчика события DataArrival, чтобы получить поступившие данные. Пример Поскольку идентифицировать удаленный хост при приеме данных не требуется, UDP позволяет получать их и на других компьютерах. Связь типа «многие-к-одному» дает возможность создавать административные средства для отслеживания состояния рабочих станций
ГЛАВА 7
Использование компонентов для Интернета
125
в сети. Рассмотрим пример такого средства — программу Systems Status Monitor (SYSSTAT.VBP), окно которой показано на рис. 7-4Командная строка, введенная на локальной машине SysStat.exe wornbatl
Рис. 7-4 Systems Status Monitor передает на машину администратора сведения о свободном дисковом пространстве на рабочих станциях Systems Status Monitor выполняет две функции. При запуске из командной строки вычисляет размер свободного дискового пространства (в процентах) на локальном компьютере и передает его значение на заданный удаленный компьютер. А при обычном запуске выступает в роли пассивного получателя информации, поступающей от рабочих станций. Процедура FormJLoad в Systems Status Monitor демонстрирует настройку элемента Winsock для приема или передачи данных в зависимости от способа запуска программы. Option Explicit 1 создаем объект для файла журнала Dim fsysLog As New Scripting.FileSystemObject
1
s командной строке указывается имя или IP-адрес машины, ' которой посылаются данные Private Sub Form_Load() ' запускаем программу в режиме передачи, 1 вели в командной строке что-то задано If Command <> "" Then ' указываем порт на удаленной машине sckUDP.RemotePort = 1002 ' идентифицируем хост, которому посылаются данные sckUDP.RemoteHost = Command ' выполняем привязку к локальному порту,
126
ЧДСТЬ II
ДОРОГОЙ ДЖОН. КАК?.
1
через который передаются данные sckUDP. Bind 1001 ' посылаем информацию sckUDP. SendData ShowStats • завершаем программу Unload Me 1 запускаем программу в режиме приема, ' если в командной строке ничего не задано Else ' указываем порт на удаленной машине, который ' мы будем "слушать" sckUDP. RemotePort = 1001 ' указываем локальный порт, через который принимается данные sckUDP.Bind 1002 ' отображаем полученную информацию и имя компьютера Me. Caption = Me. Caption & ": " & sckUDP. LocalHostName End If End Sub
Обработчик события Error элемента Winsock вызывается, если нет соединения с Интернетом или если имя удаленного хоста задано неправильно. Поскольку Systems Status Monitor использует протокол UDP, контроль ошибок минимален. Когда возникает какая-нибудь ошибка, просто выводится соответствующее сообщение. 1 обрабатываем ошибки передачи Private Sub sckUDP_Error(ByVal Number As Integer, Description As String, ByVal Scode As Long, ByVal Source As String, ByVal HelpFile As String, ByVal HelpContext As Long, CancelDisplay As Boolean ) ' выводим сообщения об ошибках MsgBox Number & " " 4 Description, vbCritical, App. Title End Sub Обработчик события Data Arrival элемента Winsock вызывается в момент поступления данных в порт, к которому была осуществлена привязка. Метод GetData извлекает поступившие данные и очищает приемный буфер, освобождая место для следующей порции данных в очереди. ' обрабатываем данные по мере поступления Private Sub sckUDP_DataArrival(ByVal bytesTotal As Long) Dim strData As String ' извлекаем данные sckUDP. GetData strData ' отображаем их ShowText strData ' если флажок помечен, сохраняем данные в файле журнала If chkLog. Value Then fsysLog. CreateTextFile(CurDir & "\stats.log", _
ГЛАВА 7 Испальзование_компднентов_для Интернета
127
True).Write txtReceive.Text
End If End Sub
Данные передаются по протоколу UDP единой порцией, размер которой зависит от параметров сети — поэкспериментируйте, если хотите подобрать его верхний предел. Однако обмениваться большими объемами данных при использовании UDP неразумно. В этом случае переходите на протокол TCP.
Беседа «один-на-один» по протоколу TCP Применяя элемент Winsock с протоколом TCP, Вы должны устанавливать соединение до передачи или приема данных. При этом одна машина должна выдать запрос на соединение, а другая принять его. Чтобы выдать запрос, добавьте элемент Winsock на форму и действуйте по следующей схеме: 1. установите свойство RemoteHost. Его значением должен быть IPадрес или имя хост-компьютера, который будет принимать Ваши данные; 2. запишите в свойство RemotePort номер порта, используемого другими машинами для приема данных, посылаемых Вами; 3. вызовите метод Connect, чтобы выдать запрос на соединение с другой машиной; 4. передайте данные методом SendData. Чтобы принять запрос на соединение: 1. запишите в свойство LocalPort номер порта, указанный в свойстве RemotePort в п. 2 предыдущей процедуры-, 2. откройте методом Listen локальный порт для приема запросов на соединение; 3. вызовите метод Accept из обработчика события ConnectionRequest, чтобы установить соединение. Установив соединение, можно обмениваться данными, используя методы SendData и GetData — так же, как и по протоколу UDP. Но в отличие от последнего протокол TCP требует перед созданием нового соединения закрывать существующее (для чего применяется метод Close). Пример Программа-пример Chat (CHAT.VBP) демонстрирует, как использовать Winsock с протоколом TCP для создания соединения между двумя компьютерами. После того как соединение установлено, пользователи этих компьютеров могут обмениваться сообщениями, набирая текст в поле (рис. 7-5).
ЧАСТЬ II ДОРОГОЙ ДЖОН, КАК?.
128
Первый компьютер hal's happening?
Второй компьютер
Рис. 7-5 Программа Chat позволяет двум пользователям обмениваться сообщениями по протоколу TCP Программа Chat в обработчике Form_Load указывает локальный порт, который она будет «слушать», и переходит в режим ожидания запроса на соединение. Option Explicit ' ждем запросов на соединение Private Sub Form_Load() ' задаем порт, который мы будем "слушать" sckTCP.LocalPort = 1002 ' переходим в режим ожидания sckTCP.Listen ' обновляем строку состояния ShowText "Listening" End Sub
Чтобы инициировать соединение, вызовите метод Connect. Программа Chat позволяет указать имя или IP-адрес вызываемого компьютера (ввод данных осуществляется через InputBox), закрыть любое существующее соединение, а потом установить новое: Private Sub mnuConnect_Click() Dim strRemoteHost As String ' получаем имя или IP-адрес вызываемого компьютера strRemoteHost = InputBox("Enter name or IP address of computer " 4 "to connect to.", vbOKCancel)
ГЛАВА 7 Использование компонентов для Интернета
129
' выходим,если была отмена If strRemoteHost = "" Then Exit Sub ' закрываем любое открытое соединение sckTCP.Close ' устанавливаем имя вызываемого компьютера sckTCP.RemoteHost = strRemoteHost ' задаем номер порта на удаленном хосте sckTCP,RemotePort = 1002 1 так вроде бы удается избежать некоторых ошибок TCP DoEvents 1 посылаем запрос на соединение sckTCP.Connect End Sub Когда «слушающая» машина получает запрос на соединение от другой машины, возникает событие ConnectionRequest. Программа Chat принимает все запросы: Private Sub sckTCP_ConnectionRequest(ByVal requestID As Long) sckTCP.Close sckTCP.Accept requestID ShowText "Accepting request from " & sckTCP.RemoteHostlP End Sub Как только «слушающая» машина принимает запрос на соединение, любой из двух компьютеров может передавать данные методом SendData. Для ввода посылаемых и отображения получаемых сообщений программа Chat использует поле txtChat. Обработчик события KeyPress, показанный ниже, отслеживает все, что вводит пользователь, и, обнаружив нажатие клавиши Enter, отправляет сообщение. Private Sub txtChat_KeyPress(KeyAscii As Integer) Static strSend As String ' проверяем, установлено ли соединение If sckTCP.State <> sckConnected Then Exit Sub ' посылаем данные при нажатии клавиши Enter If KeyAscii = Asc(vbCr) Then ' посылаем строку sckTCP.SendData strSend ' очищаем переменную strSend = "" Else ' отслеживаем все, что вводит пользователь strSend = strSend & Chr(KeyAscil) End If End Sub Метод SendData генерирует событие DataArrival на принимающей стороне соединения. Программа Chat извлекает информацию из очереди сообщений методом GetData и отображает ее в поле txtChat-.
130
ЧАСТЬ II
ДОРОГОЙ ДЖОН, КАК?..
Private Sub sckTCPJ}ataArrival(ByVal bytesTotal As Long) Dim strText As String ' получаем данные sckTCP.GetData strText ' отображаем полученные данные txtChat = txtChat i "»" 4 strText & vbCrLf ' перемещаем курсор в конец текста txtChat.SelStart = Len(txtChat) ShowText "Bytes received: " & bytesTotal End Sub
Для завершения сеанса связи вызовите метод Close. В моей программе отключение и переход в режим ожидания осуществляется через меню: Private Sub mnuDisconnect_Click() sckTCP.Close DoEvents sckTCP.Listen ShowText "Listen" End Sub
Вызов метода Close генерирует событие Close на другой машине. В его обработчике программа Chat возвращается в режим ожидания запросов на новое соединение: Private Sub sckTCP_Close() ShowText "Close" ' когда удаленная машина закрывает соединение, ' возвращаемся в режим ожидания sckTCP.Close sckTCP.Listen ShowText "Listen" End Sub
TCP-соединения предоставляют гораздо больше информации об ошибках, чем UDP-соединения, поэтому ее очень важно выводить на экран. Любая ошибка при установлении или использовании соединения вызывает событие Error. В программе Chat ошибки выводятся на экран так: ' отображаем информацию об ошибках Private Sub sckTCP_Error(ByVal Number As Integer, Description As String, ByVal Scode As Long, ByVal Source As String, ByVal HelpFile As String, _ ByVal HelpContext As Long, CancelDisplay As Boolean ) ShowText "Error " & Number & " " & Description End Sub Подробнее о событиях, возникающих в процессе работы с TCPсоединением, см. исходный текст программы-примера Chat (CHAT.VBP) на компакт-диске, прилагаемом к книге. Дополнитель-
ГЛДВД 7 ^пользование компонентов для Интернета
131
ную информацию на этот счет можно найти и в разделе «Using the Winsock Control» справочной системы Visual Basic.
Создать FTP-браузер? В разделе справочной системы Visual Basic, посвященном элементу управления Internet Transfer, предлагаются фрагменты кода, подходящие для создания FTP-браузера, но, увы, они слишком разрозненные. Это серьезный недостаток, поскольку элемент Internet Transfer является асинхронным, а именно взаимодействие событий и кода обработки ошибок — самый сложный аспект работы с этим элементом. На рис. 7-6 показан простой FTP-браузер, созданный мной из двух текстовых полей и элемента управления Internet Transfer. Вы вводите LIRL сервера FTP в первое поле (Address), после чего выбираете файл или каталог из второго поля. При выборе каталога программа выводит список его файлов и подкаталогов, а при выборе файла — сохраняет его в каталоге Windows\Temp.
Рис. 7-6 Простой FTP-браузер, созданный на основе элемента управления Internet Transfer Текстовое поле Address выполняет запрос сразу после нажатия клавиши Enter. При этом устанавливается свойство URL элемента Internet Transfer и вызывается его метод Execute. При запросе конкретного файла метод OpenURL действует так же, как и метод Execute, но запрос содержимого каталога приводит к тому, что он возвращает исходный HTML-код. Поэтому в данном примере я избегал метода OpenURL. А теперь рассмотрим обработчик события KeyPress для поля txtAddress. Private Sub txtAddress_KeyPress(KeyAscii As Integer) If KeyAscii = Asc(vbCr) Then "глотаем" нажатие клавиши KeyAscii = О ' выделяем текст txtAddress.SelStart = 0 txtAddress.SelLength = Len(txtAddress)
132
ЧАСТЬ II
ДОРОГОЙ ДЖОН, КАК?..
On Error GoTo errOpenURL 1 устанавливаем FTP-адрес inetBrowse.URL = txtAddress ' получаем каталог inetBrowse.Execute , "Dir " txtAddress = inetBrowse.URL End If Exit Sub arrOpenURL: Select Case Err.Number Case icBadUrl MsgBox "Bad address. Please reenter." Case icConnectFalled, icConnectionAborted, icCannotConnect MsgBox "Unable to connect to network." Case iclnetlimeout MsgBox "Connection timed out." Case icExecuting ' отменяем предыдущий запрос inetBrowse.Cancel ' проверяем, сработала ли отмена If inetBrowse.StillExecuting Then Caption = "Couldn't cancel request." ' вновь посылаем запрос Else Resume End If Case Else Debug.Print Err.Number, Err.Description End Select End Sub
Перехват ошибок Крайне важно перехватывать ошибки, которые могут произойти после выдачи запроса. Особого внимания требует ошибка icExecuting. Элемент Internet Transfer, обрабатывая все запросы асинхронно, не поддерживает одновременной обработки двух и более запросов. Если Вы отменяете незавершенный запрос, обязательно проверьте свойство StillExecuting перед выходом из обработчика ошибок, как показано выше. Некоторые запросы нельзя отменить, поэтому простое выполнение оператора Resume приведет к бесконечному циклу! Следующий пример демонстрирует процедуру обработки события DblClick текстового поля txfConfents, в котором отображаются списки каталогов и файлов. Процедура формирует строку URL и выполняет команду Dir, если выбран подкаталог, или Get, если выбран файл.
ГЛАВА? Использование компонентов для Интернета Private Sub txtContents_DblClick() 1 просматриваем выбранный каталог If txtContents.SelLength Then 1 если выделен каталог,.. If Right(txtContents.SelText, 1} = "/" Then ' добавляем выделенный элемент к адресу txtAddress = txtAddress & "/" & Left(txtContents.SelText, txtContents.SelLength - 1) 1 перехват ошибок (важен!) On Error GoTo errBrowse ' показываем каталог mstrDir = Right(txtAddress, Len(txtAddress) - Len(inetBrowse.URL)) InetBrowse.Execute , "Dir " & mstrDir & "/*" 1 иначе - это файл; считываем его Else Dim strFilename ' формируем полный путь к файлу mstrDir = Right(txtAddress, Len(txtAddress) _ - Len(inetBrowse.URL)) & "/" & _ txtContents.SelText mstrDir = Right(mstrDir, Len(mstrDir) - 1) strFilename = mstrDir Do
strFilename = Right(strFilename, Len(strFilename) - InStr(strFilenaine, "/"» Loop Until InStr(strFilename, "/") - 0 ' считываем файл inetBrowse.Execute , "Get " & mstrDir & " " & mstrTempDir & strFilename End If End If Exit Sub errBrowse: If Err = icExecuting Then ' отменяем предыдущий запрос inetBrowse.Cancel ' проверяем, сработала ли отмена If inetBrowse.StillExecuting Then Caption = "Couldn't cancel request." Else Resume End If Else ' выводим информацию об ошибке Debug.Print Err & " " & Err.Description End If End Sub
133
134
ЧАСТЬ II
ДОРОГОЙ ДЖОН, КАК?..
Методы Execute и OpenURL генерируют событие StateChanged — хотя OpenURL вроде бы синхронный метод, событие StateChanged все же возникает, а это порождает проблему реентерабельности. Следующий код периодически обновляет заголовок формы, держа нас в курсе выполнения запроса, и считывает содержимое каталога методом GetChunk, если текущая команда — Dir: Private Sub inetBrowse_StateChanged(ByVal State As Integer) Select Case State Case icError Debug.Print inetBrowse.ResponseCode & " " & inetBrowse.Responselnfo Case icHesolvingHost, icRequesting, icReauestSent Caption = "Searching..." Case icHostResolved Caption = "Found." Case icReceivingResponse, icResponseReceived Caption = "Receiving data." Case icResponseCompleted Dim strBuffer As String ' получаем данные strBuffer = inetBrowse,GetChunk(1024) 1 если данные - каталог, выводим его If strBuffer <> "" Then Caption = "Completed." txtContents = strBuffer Else Caption = "File saved in " 4 mstrTempDir & "." End If Case icConnecting, icConnected Caption = "Connecting." Case icDisconnecting Case icDisconnected Case Else Debug.Print State End Select End Sub
ПРИМЕЧАНИЕ В документации утверждается, что метод GetChunk работает с командой Get метода Execute, но в команде Get определяется как файл-источник, так и файл-приемник, поэтому для копирования файла с сервера метод GetChunk просто не нужен. Полный код FTP-браузера, включая объявление API-функции GetTempPath и процедуру Form_Load, см. на компакт-диске, прилагаемом к книге.
ГЛАВА 7
Использование компонентов для Интернета
135
Управлять браузером Internet Explorer? Интересная особенность элемента управления WebBrowser в том, что его исходный файл, SHDOCVW.DLL, является в то же время библиотекой типов для IE. Библиотека типов содержит всю информацию, нужную для создания и управления ActiveX-объектами посредством Автоматизации (ранее известной как OLE-автоматизация). Установив в своем проекте ссылку на SHDOCVW.DLL, Вы сможете запускать и контролировать экземпляры IE. Этот прием весьма полезен при отладке интернет-приложения, написанного на Visual Basic. По умолчанию IE кэширует Web-страницы, но при отладке желательно получать свежие версии Webстраниц. Надстройка-пример WebTool (рис. 7-7) запускает экземпляр Ш с отключенным кэшированием страниц и тем самым облегчает отладку интернет-приложений.
Рис. 7-7 Программа-надстройка WebTool всегда загружает свежие Web-страницы, не кэшируя их Помимо стандартного класса подключения (CLS) и стартового модуля регистрации (BAS), которые есть у любой программы-надстройки, WebTool (WEBTOOL.VBP) включает форму с текстовым полем для ввода URL и кнопками Forward (вперед) и Back (назад). Код, приведенный ниже, запускает экземпляр IE и помещает в поле txtAddress путь к каталогу, где Visual Basic хранит временные VBD-файлы при отладке интернет-приложений (о VBD-файлах — см, главу 9 «Создание приложений для Интернета»).
136
ЧАСТЬ II ДОРОГОЙ ДЖОН. КАК?..
Option Explicit 1
удаляет кэшированную страницу и устанавливает для метода Navigate ' флаг navNoReadFromCache Private Declare Function DeleteUrlCacheEntry Lib "WinInet.DLL" (strURL As String) As Boolean ' указываем путь к каталогу, где установлен VB.EXE Const VBPath = "file://C:\Program Files\DevStudio\VB\" ' создаем объектную переменную, которая ссылается на IE; 1 заметьте: чтобы осуществить привязку к событию ieView_Quit, ' нужно использовать объект "_V1" Private WlthEvents ieView As SHDocVw.WebBrowser_V1 ' для 1ЕЗ использовалось следующее объявление: ' Private WithEvents ieView As IntarnetExplorer Private Sub Form_Load() ' устанавливаем ссылку на объект приложения
Set ieView = GetObject("", "InternetExplorer.Application") 1 Internet Explorer должен быть видим ieView.Visible = True ' начинаем с каталога, где установлен VB.EXE, поскольку
' при отладке VBD-файлы хранятся именно там txtAddress = VBPath End Sub
К сожалению, нельзя вызвать функцию GetObject с пустым первым аргументом, чтобы получить ссылку на уже выполняемый экземпляр IE, — приходится запускать новый. Кроме того, этот экземпляр нужно сделать видимым, установив его свойство Visible как True. Следующий код реализует навигацию. API-функция DeleteUrlCacheEntrv гарантирует, что метод Navigate загрузит новую версию Web-страиицы, не пытаясь использовать содержимое кэша браузера на Вашем компьютере. Private Sub txtAddress_KeyPress(KeyAscii As Integer) Dim blnResult As Boolean If KeyAscii = Asc(vbCr) Then ' "глотаем" нажатие клавиши (чтобы не пропустить ' данный символ в элемент управления) KeyAscii = О ' выделяем текст txtAddress.SelLength = Len(txtAddress) ' удаляем URL, если он имеется в кэше blnResult = DeleteUrlCacheEntryCByval txtAddress) ieView.Navigate txtAddress End If End Sub
ГЛАВА 7
Использование компонентов для Интернета
.
137
Объектная переменная ieView объявлена с ключевым словом WithEvents, поэтому наша форма может перехватывать события от IE. Так, событие CommandStateChange используется для включения или отключения кнопок Forward и Back: Private Sub ieView_CommancJStateChange ( _
ByVal Command As Long, ByVal Enable As Boolean _
) ' включаем или отключаем кнопки Back и Forward в зависимости ' от того, есть ли адрес для перехода в данном направлении Select Case Command Case CSC_NAVIGATEBACK cmdBack.Enabled = Enable Case CSC.NAVIGATEFQRWARD cmdForward.Enabled = Enable Case CSC_UPDATECOMMANDS End Select End Sub Private Sub cmdBack_Click() ieView.GoBack End Sub Private Sub cmdForward_Click() ieView.GoForward End Sub
Когда Web-страница полностью выведена на экран, IE генерирует событие NavigateComplete, а когда пользователь закрывает приложение, — событие Quit. Код программы-надстройки WebTool обрабатывает оба эти события: Private Sub ieView_NavigateComplete(ByVal URL As String) ' записываем в поле Address конечный адрес txtAddress.Text = URL txtAddress.SelLength = Len(txtAddress) 1 выводим название Web-страницы в заголовке формы Caption = ieView.LocationName End Sub Private Sub ieView_Quit(Cancel As Boolean) ' завершаем это приложение, если пользователь ' закрывает Internet Explorer End End Sub
Полный код программы-надстройки WebTool см. на компактдиске, прилагаемом к книге. i См. также... • Главу 27 «Изощренные приемы программирования» — подробнее о создании программ-надстроек,
Г Л А В А
8
Создание компонентов для Интернета -D предыдущей главе мы рассмотрели, как использовать интернеткомпоненты, поставляемые с Visual Basic. Они предоставляют клиентские и одноранговые сервисы для написания файловых серверов, браузеров и приложений, позволяющих вести «беседы* по Интернету. В этой главе я расскажу о том, как создавать собственные компоненты, способные работать в Интернете. Эти компоненты могут быть нескольких видов. ш ActiveX-элементы. Расширяют возможности любого интернетприложения независимо от того, что именно они используют: HTML (HyperText Markup Language), DHTML (Dynamic HTML) или ActiveX-документы. Основные сведения о создании этих компонентов изложены в главе б «ActiveX-элементы», а здесь обсуждается специфика их использования в Интернете. я HTML-страницы. Формат большей части «содержания* Интернета. В Visual Basic нет средств, напрямую поддерживающих написание HTML-страниц, зато есть компоненты, которые можно использовать на таких страницах (например, ActiveX-элементы и сценарии на VBScript). Я поясню, как задействовать эти средства в HTML. ш DHTML-страницы. Новый тип компонентов, которые позволяет создавать Visual Basic. Я расскажу, как работать с DHTML Page Designer и о чем следует помнить, используя DHTML-страницы в своем приложении. & ActiveX-документы. Появились в результате первой попытки — еще до того, как был предложен стандарт DHTML, — ввести в Visual Basic поддержку динамических Web-страниц. Эти компоненты отличаются как от форм Visual Basic, так и от DHTML, a значит, и у них есть своя специфика. S Web-классы. Управляют серверными объектами в приложениях Internet Information Server (IIS). Эти классы работают под управлением IIS и требуют использования его модели объектов.
ГЛАВА 8
Создание компонентов для Интернета
139
В этой главе поясняется, как создать или улучшить перечисленные компоненты в Visual Basic. Ее тематика тесно связана с тем, что рассматривается в главе 9 «Создание приложений для Интернета*, и, по-видимому, стоит прочесть обе главы, если Вы хотите получить представление о всем спектре задач программирования, связанных с Интернетом.
Создавать ActiveX-элементы, рассчитанные на применение в Интернете? ActiveX-эле менты можно использовать в любом приложении Visual Basic, в том числе ориентированном на Интернет. Однако в создании элементов управления, рассчитанных на применение в Интернете, есть своя специфика. Цифровая подпись. В любом исполняемом компоненте должна быть цифровая подпись, которая идентифицирует автора и подтверждает безопасность запуска этого компонента и его использования в сценариях. Цифровую подпись можно приобрести, например, у VeriSign (http://www.verisign.com}. Модель потоков. ActiveX-элемент должен базироваться на модели потоков (threading model), совместимой со структурой хост-приложения. Однопоточные приложения могут использовать либо однопоточные элементы (single threading controls), либо элементы с разделенными потоками (apartment threading controls), но приложения, построенные на модели разделенных потоков, — только элементы с разделенными потоками. DHTML-приложения относятся ко вторым. Асинхронная загрузка. По умолчанию все объекты загружаются по Интернету асинхронно. Прежде чем ссылаться на объекты в своем коде, убедитесь, что они инициализированы. Проверить, существует ли нужный объект, позволяет функция IsNull. Упаковка перед распространением. У ActiveX-элементов, устанавливаемых через Интернет, должна быть соответствующая программа установки. Для создания нужных САБ- и НТМфайлов проще всего воспользоваться мастером Package and Deployment Wizard (см. главу 9 «Создание приложений для Интернета*). Элемент управления Asynchronous Animation, созданный нами в главе 6, рассчитан на применение в Интернете. Вы не раз встретите его в следующих разделах — в примерах компонентов, построенных на HTML, DHTML и ActiveX-документах.
140 ^
ЧАСТЫ1 ДОРОГОЙ ДЖОН.. КАК?..
Использовать ActiveX-элементы с VBScript? HTML-документы — самый распространенный вид данных в Интернете. Они могут включать ActiveX-элементы, а также апплеты, написанные на VBScript, JScript или Java и реагирующие на определенные события. Пример HTML-документа (htAni.HTM), содержащего элемент управления Asynchronous Animation, показан на рис. 8-1. HTML-код для этой Web-страницы приведен ниже, Заметьте, что Asynchronous Animation и стандартные элементы включаются в HTML-код тэгом
<SCRIPT LANQUASE="VBScript"> 1
обработчик событий onClick для встроенной кнопки Sub butView_onCllck ' проверяем, существуют ли объекты If IsNull(dlgFile) Or IsNull(asyncAnimation) Then Exit Sub ' получаем файл dlgFile.ShowOpen strFile = dlgFile.FileName ' если было введено имя файла... If strFile <> "" Then ' ...отображаем его в элементе asyncAnimation asyncAnimation.AVIFile = strFile сбрасываем имя файла dlgFile.FileName = "" End If End Sub обработчик ошибок для asyncAnimation Sub asyncAnimation_FileErгоr(Number, Description) ' выводим сообщение об ошибке MsgBox "File is not a valid animation.", vbOKOnly And vbCritical, "Error " & Number End Sub
ГЛАВЛ 8
141
Создание компонентов для Интернета
ви
Рис. 8-1 Элемент управления Asynchronous Animation, встроенный в Web-страницу Самый простой способ получить идентификатор класса для любого элемента — запустить мастер Package and Deployment Wizard и обработать файл проекта этого элемента, как описано в разделе «Дорогой Джон, как... устанавливать ActiveX-документы по Интернету?* главы 9 «Создание приложений для Интернета*. Мастер генерирует НТМ-файл с тэгом , который встраивает элемент управления в Web-страницу. Вы можете модифицировать полученный НТМ-файл, добавляя текст, графику, встроенные в Visual Basic элементы управления, другие ActiveX-компоненты и исполняемые сценарии. Работая с VBScript, учитывайте следующее. * Все переменные имеют тип Variant. Их нельзя объявлять оператором Dim. Они создаются динамически — при первом использовании. и Все параметры имеют тип Variant. Поэтому, если Вы берете какие-то куски кода на Visual Basic, не забудьте удалить объявления типов из заголовков процедур. То же самое относится и к объявлениям обработчиков событий для ActiveX-объектов. ш В VBScript нет операторов файлового ввода/вывода. Для работы с дисками, каталогами и файлами предназначен объект FileSystemObject. Подробнее о VBScript см. разделы «VBScript. Language Reference* и «VBScript Tutorial» справочной системы на компакт-диске Visual Studio MSDN Library. Советую также прочесть Inside Microsoft Visual Basic Scripting Edition.
142
ЧАСТЫ! ДОРОГОЙ ДЖОН, КАК?..
Создавать DHTML-документы? DHTML включен в новую версию HTML — 4.0. Предоставляя программируемую модель объектов документов (Document Object Model, DOM) и каскадные таблицы стилей (Cascading Style Sheets, CSS), DHTML позволяет добиться большей интерактивности Webстраниц. ПРИМЕЧАНИЕ Сейчас существуют две реализации DHTML — от Microsoft и Netscape. World Wide Web Consortium (W3C) изучает эти стандарты и публикует рекомендации на своем Web-узле: http://www.w3c.org. Новые версии браузеров Microsoft и Netscape, возможно, будут поддерживать единый стандарт DHTML. В Visual Basic появился новый конструктор для разработки DHTMLстраниц — DHTML Page Designer (рис. 8-2). Он отображает иерархическую схему страницы и предоставляет область редактирования, где можно вводить текст и добавлять объекты.
•ч P(f
- и* PL) P
I? D
Ъ Я P[
scrolllon
Рис. 8-2
Создание новой Web-страницы в DHTML Page Designer
В работе с DHTML Page Designer тоже есть своя специфика. Здесь многое делается не так, как Вы привыкли, создавая в среде Visual Basic формы, нестандартные элементы управления, ActiveXдокументы или страницы свойств. Область редактирования больше напоминает клиентскую среду, связанную с браузером. Вот основные отличия, которые Вы заметите, работая с DHTML Page Designer.
ГЛАВА 8
Создание компонентов для Интернета
143
» Любой элемент управления на DHTML-странице идентифицируется свойством ID, а не Name. Конструктор DHTML автоматически назначает идентификатор каждому элементу, добавляемому на страницу. Если Вы вдруг измените идентификатор элемента, Вам придется использовать некое уникальное имя и заботиться об этом самостоятельно, так как конструктор не проверяет уникальность имен. И учтите, что Visual Basic не выдаст сообщения об ошибке, если Вы случайно присвоите одинаковый идентификатор двум разным элементам. и Встроенные элементы управления Visual Basic на DHTML-странице не доступны. Используйте вместо них HTML- или ActiveXэлементы. № При отладке, когда элемент Visual Basic открывает какое-нибудь окно, Internet Explorer иногда перестает отвечать. Тогда, чтобы увидеть это окно, Вы должны переключиться в Visual Basic, и пока Вы его не закроете, Internet Explorer отвечать не будет. В скомпилированном приложении такой проблемы не возникает. На рис. 8-3 показана DHTML-страница, созданная в Visual Basic с применением элемента Asynchronous Animation, Common Dialog и HTML Button. Исходный код для этого примера Вы найдете в проекте dhAni.VBP на компакт-диске, прилагаемом к книге.
Рис. 8-3 Animation Viewer воспроизводит Windows-анимацию на Web-странице, использующей DHTML Я специально взял пример, аналогичный предыдущему, чтобы продемонстрировать разницу в использовании элементов управления и написании HTML- и DHTML-кода. Хотя готовые страницы выглядят в браузере совершенно одинаково, код Visual Basic в dhAni.VBP несколько отличается от кода VBScript в htAni.VBP. DHTML-страница допускает объявления типов переменных и пара6—1204
144
ЧАСТЬ И
ДОРОГОЙ ДЖОН, КАК?..
метров; кроме того, ссылки на элементы надо уточнять ссылкой на объект Document: ' воспроизводит Windows-анимацию на Web-странице Private Function butView_onClick{) As Boolean ' проверяем, существуют ли объекты If IsNull(dlgFile) Or IsNull(asyncAnimation) Then Exit Function Dim strFilename As String 1 открываем диалоговое окно Open File Document.dlgFile.SnowOpen ' получаем имя файла strFilename = Document.dlgFile.FileName ' если выбран файл... If strFilename <> "" Then ' ...отображаем его в элементе asyncAnimation Document.asyncAnimation.AVIFile = strFilename End If End Function Private Sub asyncAnimation_FileError(Nu)nber As Long, Description As String) ' выводим сообщение об ошибке MsgBox "File is not a valid animation.", vbOKOnly And vbCritical, "Error " & Number End Sub Наверное, Вы заметили, что обработчик события onCIick для кнопки butView является процедурой типа Function, а не Sub. В DHTML принято, что обработчики событий возвращают значения, активизирующие операции по умолчанию. Например, если Вы пишете обработчик onCIick для гиперссылки, сделайте так, чтобы он возвращал True, — тогда браузер, обработав Ваш код, перейдет по этой гиперссылке. А иначе она будет проигнорирована. Сказанное поясняет следующий код: Private Function lnkHome_onClick() As Boolean Dim intGo As Integer 1 предлагаем выбор intGo = MsgBoxf'Do you want to go home?", vbYesNo) ' если пользователь выбирает "да"... If intGo = vbYes Then '1 ...разрешаем операцию по умолчанию (переходим по гиперссылке) lnkHome_OnClick = True Else ' ...отменяем операцию по умолчанию ' (не переходим по гиперссылке) lnkHome_OnClick = False End If End Function
ГЛАВА 6
Создание компонентов^я Интернета
1_45
Подробнее о DHTML, включая и то, как добавить в DHTML-приложение несколько страниц, см. главу 9 «Создание приложений для Интернета».
Создавать ActiveX-документы? ActiveX-документ — это комбинация пользовательских документов, форм, модулей кода и любых других компонентов, которые Вы решите добавить. Пользовательский документ (user document) — уникальный контейнер Visual Basic, рассчитанный на отображение в браузере. ПРИМЕЧАНИЕ ActiveX-документы не подпадают под действие стандартов World Wide Web Consortium (W3C), но являются частью стратегии Microsoft в области ActiveX-компонентов, каковая уже стала стандартом «де факто» в индустрии программного обеспечения. (Толчком к развитию этой стратегии послужила концепция OLE.) ActiveX-документы поддерживаются браузером Internet Explorer, но не Netscape Navigator. Пользовательские документы очень похожи на формы. В них можно помещать любые встроенные в Visual Basic элементы управления — причем точно так же, как и на формы. Отличия проявляются, когда дело доходит до написания кода. Поскольку эти документы загружаются и выгружаются по велению браузера, учитывайте, ЧТО: я прежде чем ссылаться из одного объекта на другой, следует дождаться загрузки всех объектов. Например, пользователь может щелкнуть кнопку, которая ссылается на некий элемент управления, а он еще только загружается. Поэтому перед ссылкой на любой объект вызывайте функцию IsNull и проверяйте, существует ЛИ ОН; и данные в других пользовательских документах не становятся доступными сами по себе. Если какие-то элементы данных совместно используются несколькими такими документами, храните их в глобальных переменных или в разделяемом объекте. На рис. 8-4 показан давно знакомый нам элемент управления Asynchronous Animation, выполняемый браузером в ActiveX-документе. Последний выглядит также, как и ранее созданные HTML- и DHTML-страницы. Исходный код для этого примера Вы найдете в проекте axAni.VBP на компакт-диске, прилагаемом к книге. Код для пользовательского документа, показанный ниже, был бы идентичен примеру с формой. ' воспроизводит Windows-анимацию на Web-странице Sub cmdView_Click()
146
ЧАСТЫ!
ДОРОГОЙ ДЖОН, КАК?.
Dim strFilename As String ' проверяем, существуют ли объекты If IsNull(dlgFile) Or IsNull(asyncAnimation) Then Exit Sub 1 открываем диалоговое окно Open File dlgFile.ShowOpen ' получаем имя файла strFilename = dlgFile.FileName ' если выбран файл... If strFilename <> "" Then ' ...отображаем его в элементе asyncAnlmation asyncAnimation.AVIFile = strFilename End If End Sub Private Sub asyncAniniation_FileError(Number As Long, Description As String) ' выводим сообщение об ошибке HsgBox "File is not a valid animation.", vbOKOnly And vbCritical, "Error " & Number
End Sub
Рис, 8-4 Animation Viewer воспроизводит Windows-анимацию на Web-странице, использующей ActiveX-документ (где-то мы уже видели это!) Программистам на Visual Basic создавать ActiveX-документы определенно легче, чем приложения, использующие HTML или DHTML, но поддерживать актуальность информации в ActiveX-документах труднее. В публикации материалов HTML и DHTML эффективнее, чем ActiveX-документы, так как эти форматы поддерживаются целым рядом текстовых редакторов и не требуют перекомпиляции страниц после изменения их содержимого. Сравнительные преимущества приложений каждого типа обсуждаются в главе 9 «Создание приложений для Интернета*.
ГЛАВА 8
Создание компонентов для Интернета
14?
Создавать Web-классы? Web-класс — просто класс, который управляет созданием и разрушением объектов, используемых IIS-приложением. Visual Basic автоматически создает такой класс, когда Вы запускаете новое IISприложение. ПРИМЕЧАНИЕ Д-тя поддержки Web-классов установите Internet Information Server (IIS) или Personal Web Server (PWS). Все, что в этом разделе говорится об IIS, полностью относится и к PWS. Вы можете легко модифицировать исходный Web-класс и выводить HTML-строки через браузер. Например, эта процедура обработки события WebClass_Start создает объект Dice и отображает результаты двух «бросков» игральных костей: Private Sub WebClass_Start() Dim sQuote As String 1 создаем объект Dice Dim diceObject As New Dice ' определяем символ кавычек для формирования HTML-строк sQuote = Chr$(34) ' устанавливаем счетчик для этой страницы Sflssion("Tries") = Session("Tries") + 1 1 пишем ответ пользователю With Response .Write "" .Write ""
.Write "
Chance -- Try " & SessionC'Tries") & "" .Write "
You rolled a " & diceObject.Roll & _ " and a " & diceObject.Roll & "." .Write "
"
.Write "
Refresh or reload to roll again.
" .Write "" .Write "" End With End Sub
Когда Вы запустите программу Chance (CHANCE.VBP), Visual Basic сформирует виртуальный каталог для Вашего проекта в IIS, создаст активную серверную страницу (ASP), которая ссылается на Web-класс, и выполнит эту ASP Потом будет запущен Internet Explorer, где Вы и увидите результаты (рис. 8-5). Кроме того, Chance иллюстрирует, как с помощью объекта Session сохраняются значения свойств. Следующая строка создает в объекте Session свойство Tries и увеличивает его значение на 1: SessionC'Tries") = SessionC'Tries") + 1
148
ЧАСТЬ II
ДОРОГОЙ ДЖОН, КАК?.
ЛЗГ Microitilr inleiiwl f xpfurer
Chance -- Try 1 You rolled a 5 and a 4 Refresh or reload to rell agam
Рис. 8-5 Internet Explorer «бросает» игральные кости при каждом щелчке кнопки Refresh Чтобы сохранить это значение между обновлениями, свойству StateManagement Web-класса присваивается значение wcRetainlnstance. Если поменять это значение на wcNoState, Web-класс перестанет сохранять информацию об объекте Session, и число бросков игральных костей, отображаемое на странице, будет всегда равно 1. Чтобы передать браузеру HTML-строку, программа Chance вызывает метод Write объекта Response. В данном примере, где я хотел лишь продемонстрировать понятие Web-класса, этот способ удобен, но для отображения больших объемов информации или для обновления «содержания» не годится. Последнюю проблему можно решить за счет хранения информации в отдельных файлах HTMLшаблонов. Об этом приеме я расскажу в следующей главе.
Г Л А В А
Создание приложений для Интернета V isual Basic позволяет создавать три типа приложений, выполняемых браузером. ш DHTML-приложения. Загружаются по Интернету и выполняются на компьютере пользователя. В таких приложениях применяются элементы управления HTML 4,0, гиперссылки и средства форматирования отображаемой информации. и HS-приложения. Выполняются на интернет-сервере и обмениваются данными с компьютером пользователя. Работают с моделью объектов IIS (Internet Information Server) и ActiveX-объектами, установленными на интернет-сервере. в Приложения на основе ActiveX-документов. Загружаются по Интернету и выполняются на компьютере пользователя. Б ActiveX-документах применяются стандартные элементы управления Visual Basic и другие ActiveX-объекты. Приложения этого типа обеспечивают полное управление браузером, в том числе контроль внешнего вида его окна и подстановку своих меню. Я расскажу о создании многостраничных приложений (multipage applications) с применением каждой из перечисленных технологий. При этом я использую один и тот же базовый пример College Cost Calculator (калькулятор для расчета стоимости обучения в колледже). Все версии этого калькулятора находятся на компакт-диске, прилагаемом к книге.
Выбрать тип приложения? Такое разнообразие способов создания интернет-приложений обусловлено самим характером развития Интернета. Microsoft, Netscape, Sun Microsystems и другие компании выпускали множество конкурирующих инструментальных средств и все претендовали на то, чтобы именно их продукт стал стандартом в области разработки для Интернета. Но единого четкого стандарта нет до сих пор. Вместо этого Вы должны исходить из конкретной задачи. Visual Basic предлагает 4
150
ЧАСТЬ II
ДОРОГОЙ ДЖОН, КАК?..
подхода к созданию интернет-приложений: HTML с применением ActiveX-элементов и VBScript, DHTML, ActiveX-документы и IIS-приложения. Выбирайте оптимальный подход, учитывая следующие соображения. Тип браузера. Если Ваше приложение должно работать и с Netscape Navigator, и с Internet Explorer (IE), создавайте его как USприложение. Netscape не поддерживает ActiveX-объекты в HTML, VBScript, реализацию Microsoft DHTML и ActiveX-документы. Место обработки. HS-приложения рассчитаны на обработку сервером. Идеальны для доступа к данным, хранящимся на сервере, Но возможны проблемы с быстродействием на очень занятых узлах. ActiveX-документы, DHTML и HTML с ActiveX-объектами выполняются на клиентской машине (предварительно их надо загрузить). Простота в сопровождении. Приложения, которые выводят много текста, должны использовать IIS, DHTML или HTML. Приложения этих типов — в отличие от ActiveX-документов — позволяют помещать информацию в отдельные файлы без перекомпиляции и повторного распространения исполняемых компонентов. Контроль со стороны клиента. ActiveX-документы дают возможность управлять браузером и открывают неограниченный доступ к файловой системе на компьютере пользователя. IIS-, DHTML- и HTML-приложения не могут модифицировать браузер и имеют весьма ограниченный доступ к файловой системе. Безопасность. В любой исполняемый компонент вроде ActiveX-документа или элемента можно включить цифровую подпись, удостоверяющую, что данный компонент не устроит хаоса на клиентской машине. Но пользователи крайне неохотно принимают компоненты от незнакомых им авторов. Ш-ириложения такой проблемы не создают, поскольку их исполняемые части работают на сервере, а не на клиентской машине. Будущие стандарты. Microsoft и Netscape сейчас поддерживают несовместимые реализации DHTML. Проблема, вероятно, будет снята, когда Netscape выпустит Navigator 5.O. Как обещает Netscape, Navigator 5.0 будет поддерживать и ActiveX-компоненты. Если Вы создаете интернет-приложение, предназначенное для World Wide Web, то реального выбора у Вас по сути нет и Вы должны использовать IIS, потому что Netscape Navigator ничего другого не поддерживает. А если Вы создаете приложение, выполняемое в интрасети, можете выбирать любую технологию, так как в этом случае намного проще диктовать пользователям, с каким браузером они должны работать.
ГЛАВА_9 _Создание приложений для Интернета
151
Будущие версии IE и Netscape Navigator должны стать более совместимыми друг с другом, и поэтому какая-то часть изложенных мной соображений, вероятно, утратит свою значимость. Впрочем, рассуждать на эту тему пока преждевременно.
Создать DHTML-приложение? Проект DHTML-приложения содержит по одному DHTML-конструктору на каждую HTML-страницу и может включать модули классов, кода, форм и других исполняемых компонентов. После компиляции исполняемые компоненты помещаются в отдельный DLL-файл, на который ссылаются все HTML-файлы приложения. Это позволяет отделить информацию от исполняемых компонентов. Рис. 9-1 иллюстрирует приложение Future Shock (DHFSHOCK.VBP) на этапе разработки. Возможности DHTML-конструктор а в редактировании крайне ограниченны. Чтобы задействовать другие средства редактирования, Вам придется связать конструктор с внешним HTML-файлом. Для этого выберите IDHTMLPageDesigner в окне Properties и укажите в свойстве SourceFile существующий HTML-файл. После того как Visual Basic загрузит HTML-файл, Вы сможете отредактировать его, щелкнув на панели инструментов кнопку Launch Editor (запустить редактор).
Document g BODY [Colege С H1 HI [Colics Cost Calculator] HRil -: i* P|Clild':name:] lUNflinel л if P [Child's age') • SB- tuiAqe INPUT (| -=; 14 F Пив of college, In-sla. iBlColleoe SELECT [In « Sf P [ т ле ol tiansporta BIT lanipoit SELECT [I -: If P (Type of housing HameD) .*5f »IHoi»ing SELECT HI
-= m P I ] _1 L.uil ill. INPUT |)
Рис. 9-1 Каждый DHTML-конструктор обрабатывает одну HTML-страницу
152
ЧДСТЫ1
ДОРОГОЙ ДЖОН, КАК?..
Получение ввода в DHTML-приложениях Чтобы сделать какой-то элемент DHTML-страницы программируемым, запишите идентификатор в его свойство ID. Элементы, у которых есть события, требуют установки этого свойства — только тогда их события доступны в Visual Basic. У каждого элемента управления на форме конструктора dhCalc (рис. 9-1) имеется свой идентификатор, как показано в этом HTML-коде: