А.Я. Архангельский
УДК 004.43 ББК 32.973.26-018.1 А87
А.Я. Архангельский Программирование в Delphi 7. — М.: ООО «Бином-Пресс», 2003 г. — 1152 с.: ил.
Книга содержит методические и справочные материалы по новой версии системы визуального объектно-ориентированного программирования Delphi 7 и предшествующим версиям Delphi 6 - 4 . Дается методика построения прикладных программ, реализующих текстовые и графические редакторы, мультипликацию и мультимедиа, работу с базами данных, разработку отчетов, приложений для Интернет, распределенных приложений, клиентов и серверов. Рассмотрены такие технологии доступа к данным, как BDE. ADO, InterBase Express, dbExpress, компоненты-серверы COM, технологии распределенных приложений: COM, CORBA, MIDAS, технологии Web Broker, WebSnap, IntraWeb, Web Services, Indy. Рассмотрен новый инструментарий Delphi: ModelMaker и Rave. Справочная часть книги содержит материалы по языку Object Pascal, функциям Delphi и API Windows, компонентам и классам Delphi, их свойствам, методам и событиям. Книга рассчитана как на начинающих, владеющих только основами какого-нибудь языка программирования, так и на опытных разработчиков.
ISBN 5-9518-0042-0
© Архангельский А.Я., 2003 © Издательство БИНОМ, 2003
Научно-техническое издание Архангельский Алексей Яковлевич Программирование в Delphi 7 Оформление обложки И. /О. Буровой Компьютерная верстка .Си. Свиридова Подписано в печать 17.06.2003. Формат 70x100/16. Усл. печ. л. 93,6 Гарнитура Школьная. Бумага газетная. Печать офсетная Тираж 4 000 экз. Заказ 2342 Издательство «Бином-Пресс», 2003 г. 170026, Тверь, Комсомольский просп., 12 При участии ООО «Сашко» Отпечатано с готовых диапозитивов во ФГУП ИПК «Ульяновский Дом печати» 432980, г. Ульяновск, ул. Гончарова, 14
Содержание От автора Почти обо всем в Delphi? Для кого эта книга Чем отличается эта книга от предшествующих .•
17 17 18 19
ЧАСТЬ I. Delphi 7 Studio . . . , , . 21 Глава 1. Delphi и современные информационные технологии . . . 23 1.1 Объектно-ориентированное программирование 23 1.2 Визуальное программирование интерфейса 25 1.3 Взаимодействие приложений в информационных системах 28 1.4 Распределенные многозвенные приложения 29 1.5 Переносимость данных и программ 31 1.6 Сетевые службы 32 1.7 Delphi 7 Studio и ее место в семействе программных продуктов Borland . . 33 1.8 Для опытных пользователей: что нового в Delphi 7 Studio? 35 1.9 Язык объектно-ориентированного проектирования Object Pascal . . . . 40 1.9.1 Введение 40 1.9.2 Структура файла головной программы приложения Delphi 40 1.9.3 Структура модуля 42 1.9.4 Области видимости и доступ к объектам, переменным и функциям модуля 43 1.9.4.1 Структура модуля, содержащего объекты и процедуры 43 1.9.4.2 Доступ к свойствам и методам объектов 45 1.9.4.3 Различие переменных, функций и процедур, включенных и не включенных в описание класса 46 1.9.4.4 Области видимости переменных и функций 47 1.9.4.5 Передача параметров в функции 48
Глава 2. Работа в Интегрированной Среде Разработки Delphi 7 . . 49 2.1 Интегрированная Среда Разработки (ИСР) Delphi 2.1.1 Общий вид окна ИСР 2.1.2 Полоса главного меню и всплывающие меню 2.1.3 Быстрые кнопки 2.1.4 Палитра компонентов 2.1.5 Окно формы 2.1.6 Окно Редактора Кода 2.1.7 Инспектор Объектов . . 2.1.8 Перетаскивание и встраивание окон в ИСР 2.1.9 Управление конфигурациями окон ИСР 2.2 Первые шаги — первые собственные приложения 2.2.1 Очень простое приложение 2.2.2 Немного более сложное приложение 2.3 Проекты Delphi 2.3.1 Организация проекта в Delphi, основные файлы проектов 2.3.2 Создание и сохранение нового проекта 2.3.2.1 Организация каталогов проекта 2.3.2.2 Создание нового проекта 2.3.2.3 Сохранение проекта, его повторное открывание 2.3.3 Менеджер проектов 2.3.4 Планирование работ — список To-Do List 2.3.5 Завершение проекта, задание учетной информации 2.4 Включение в проект форм * 2.4.1 Зачем надо включать в проект новые формы. . 2.4.2 Включение в проект новой формы
49 49 50 51 53 55 56 58 61 62 64 64 66 68 68 72 72 72 74 . 75 78 81 83 83 83
Программирование в Delphi 7 2.4.3 Создание отдельной копии формы 2.4.4 Заимствование форм из Депозитария . . ' 2.4.5 Просмотр форм и модулей без включения их в проект 2.5 Размещение компонентов на форме 2.5.1 Перенос компонентов со страниц библиотеки на форму. 2.5.2 Родители и владельцы компонентов — Parent и Owner 2.5.3 «Многослойное» размещение компонентов на форме 2.5.4 Поиск «пропавших» компонентов 2.5.5 Окно Object Tree View и страница диаграмм Редактора Кода 2.5.6 Работа с группой компонентов, выравнивание компонентов по размеру и положению 2.5.7 Фиксация компонентов 2.6 Депозитарий — хранилище форм и проектов 2.7 Инструментальные средства поддержки разработки кода 2.7.1 Применение Code Insight — Знатока Кода 2.7.2 Исследователь Кода Code Explorer 2.7.3 Object Browser — просмотр классов, модулей, типов 2.7.4 Получение информации о классах, свойствах, методах из окна Редактора Кода 2.7.5 Навигация в коде, закладки и дополнительные окна редактирования 2.7.6 Справочная система Delphi и программа ее конфигурирования OpenHelp 2.8 Отладка приложений 2.8.1 Варианты компиляции и сообщения компилятора 2.8.2 Что делать, если произошла ошибка выполнения 2.8.3 Окно наблюдения Watches 2.8.4 Окно оценки и модификации Evaluate/Modify 2.8.5 Выполнение приложения по шагам . . . . 2.8.6 Точки прерывания 2.8.7 Использование окна Инспектора Отладки Debug Inspector 2.8.8 Протокол событий, функция OutputDebugString 2.8.9 Некоторые приемы программирования, встраивающие отладку в код 2.9 Основные настройки Интегрированной Среды Проектирования Delphi и ее компонентов 2.9.1 Настройка инструментальных панелей 2.9.2 Настройка палитры компонентов 2.9.3 Настройка Редактора Кода 2.9.4 Настройка Инспектора Объектов 2.9.5 Настройка Code Insight — Знатока Кода 2.9.6 Настройка Исследователя Кода Code Explorer и Object Browser . . 2.9.7 Общие настройки среды 2.9.8 Настройка отладчика
. . 84 84 85 86 86 87 88 . . 89 89 91 95 95 99 99 101 103 104 . . 105 . . 106 108 108 112 114 116 117 119 123 124 . . 125 128 128 130 130 133 134 . . 135 136 137
Глава 3. Обзор компонентов библиотеки YCL Delphi 3.1 Страницы палитры компонентов 3.2 Компоненты ввода и отображения текстовой информации 3.2.1 Перечень компонентов ввода и отображения текстовой информации . 3.2.2 Отображение текста в надписях компонентов Label, StaticText и Panel. 3.2.3 Окна редактирования Edit, LabeledEdit и MaskEdit 3.2.4 Многострочные окна редактирования Memo и Richedit . . . . . . . 3.2.5 Компоненты выбора из списков — ListBox, CheckListBox, ValueListEditor, ComboBox, ComboBoxEx 3.2.6 Таблица строк — компонент StringGrid 3.3 Ввод и отображение чисел, дат и времени 3.3.1 Перечень компонентов ввода и отображения чисел, дат и времени . . 3.3.2 Ввод и отображение целых чисел — компоненты UpDown и SpinEdit. 3.3.3 Ввод и отображение дат и времени — компоненты DateTimePicker, MonthCalendar, Calendar 3.3.4 Страницы Excel — компонент FIBook
141 141 142 . 142 . 144 146 . 150 155 163 165 . 165 . 165 167 168
Содержание 3.4 Обеспечение синтаксически правильного ввода текстовых и цифровых данных 3.5 Компоненты отображения иерархических данных 3.5.1 Перечень компонентов 3.5.2 Компоненты отображения дерева данных — Tree View и Outline . . . . 3.5.3 Компонент ListView 3.6 Отображение графической и мультимедиа информации 3.6.1 Перечень компонентов отображения графической информации . . . . 3.6.2 Таблицы изображений — компоненты DrawGrid и StringGrid 3.6.3 Компонент Shape 3.6.4 Компонент Chart 3.6.5 Компоненты страницы ActiveX — FIBook, Chartfx, VtChart 3.6.6 Отображение мультимедиа и иной информации — компоненты Animate, MediaPlayer, ProgressBar, Gauge 3.7 Кнопки, индикаторы, управляющие элементы 3.7.1 Общая характеристика 3.7.2 Управляющие кнопки Button и BitBtn 3.7.3 Кнопка с фиксацией SpeedButton 3.7.4 Группы радиокнопок — компоненты RadioGroup, RadioButton и GroupBox 3.7.5 Индикаторы Checkbox и CheckListBox 3.7.6 Ползунки и полосы прокрутки — компоненты TrackBar и ScrollBar . . 3.7.7 Компоненты заголовков HeaderControl и Header 3.7.8 Таймер — компонент Timer 3.8 Компоненты — меню 3.8.1 Главное меню — компонент MainMenu 3.8.2 Контекстное всплывающее меню — компонент PopupMenu 3.8.3 Горячие клавиши — компонент HotKey 3.9 Панели и компоненты внешнего оформления 3.9.1 Общая характеристика 3.9.2 Панели общего назначения — компоненты Panel, GroupBox, Bevel, ScrollBox, Splitter 3.9.3 Многостраничные панели — компоненты TabControl, PageControl, TabSet, TabbedNoteBook, NoteBook 3.9.4 Инструментальные панели — компоненты ToolBar и PageScroller 3.9.5 Перестраиваемые панели — компоненты CoolBar и ControlBar 3.9.6 Полоса состояния StatusBar 3.9.7 Фреймы 3.10 Системные диалоги 3.10.1 Общая характеристика компонентов — диалогов . . 3.10.2 Диалоги открытия и сохранения файлов — компоненты OpenDialog, SaveDialog, OpenPictureDialog, SavePictureDialog . . . . 3.10.3 Компоненты работы с файлами и папками ShellListView, ShellTreeView и ShellComboBox 3.10.4 Фрагменты диалогов — компоненты DriveComboBox, DirectoryListBox, FilterComboBox, FileListBox и DirectoryOutline . . . 3.10.5 Диалог выбора шрифта — компонент FontDialog 3.10.6 Диалоги выбора цвета — компоненты ColorDialog и ColorBox 3.10.7 Диалоги печати, установки принтера и параметров страницы — компоненты PrintDialog, PrinterSetupDialog и PageSetupDialog. . . . 3.10.8 Диалоги поиска и замены текста —г компоненты FindDialog и ReplaceDialog
ЧАСТЬ II. Методика проектирования приложений Глава 4. Организация управления приложением 4.1 Технология разработки приложений 4.2 Список изображений — компонент ImageList
170 171 171 171 177 182 182 183 184 185 190 193 195 195 197 199 200 201 203 204 209 210 210 215 215 218 218 220 222 225 228 230 231 237 237 239 244 247 249 251 254 257
261 263 • 263 266
8
Программирование в Delphi 7 4.3 Диспетчеризация действий на основе компонента ActionList 269 4.4 Работа со стандартными действиями 272 4.5 Диспетчеризация действий на основе компонентов ActionManager, ActionMainMenuBar, ActionToolBar, CustomizeDlg . 275 4.5.1 Диспетчер действий ActionManager 275 4.5.2 Тестовое приложение 279 4.6 Приложение — объект Application и компонент ApplicationEvents . . 283 4.7 Объект Screen и приложения, работающие с несколькими мониторами. . 289
Глава 5. Разработка графического интерфейса пользователя . . . 293 5.1 Требования к интерфейсу пользователя приложений для Windows . . . 293 5.1.1 Общие рекомендации по разработке графического интерфейса 293 5.1.2 Многооконные приложения 294 5.1.3 Стиль окон приложения 295 5.1.4 Цветовое решение приложения 301 5.1.5 Шрифты текстов 302 5.1.6 Меню 303 5.1.7 Компоновка форм 306 5.1.8 Последовательность фокусировки элементов 307 5.1.9 Подсказки и контекстно-зависимые справки 309 5.2 Проектирование окон с изменяемыми размерами 315 5.2.1 Выравнивание компонентов — свойство Align 315 5.2.2 Изменение местоположения и размеров компонентов 317 5.2.3 Панели с перестраиваемыми границами 319 5.2.4 Ограничение пределов изменения размеров окон и компонентов . . . . 321 5.2.5 Масштабирование компонентов 321 5.3 Обработка событий клавиатуры и мыши 323 5.3.1 События мыши 323 5.3.1.1 Последовательность событий 323 5.3.1.2 Распознавание источника события, нажатых кнопок и клавиш, координат курсора 326 5.3.2 События клавиатуры 330 5.3.2.1 Последовательность событий 330 5.3.2.2 Распознавание нажатых клавиш 332 5.4 Перетаскивание объектов 335 , 5.4.1 Перетаскивание информации об объектах — технология Drag&Drop . . 335 5.4.2 Перетаскивание и встраивание объектов — Drag&Doc. Плавающие окна . . 338 5.4.3 Буксировка компонентов в окне приложения 345 5.5 Формы . . 349 5.5.1 Управление формами 349 5.5.2 Модальные формы 353 5.5.3 Пример приложения с модальными формами заставки и запроса пароля 354 5.5.4 Управление формами в приложениях с интерфейсом множества документов (приложениях MDI) 358 5.5.5 Пример приложения с интерфейсом множества документов — простой многооконный редактор 360 5.6 Печать и разработка отчетов 364 5.6.1 Печать с помощью различных функций 364 5.6.1.1 Печать с помощью функций файлового ввода/вывода 364 5.6.1.2 Печать форм методом Print 365 5.6.1.3 Методы компонентов, обеспечивающие печать 365 5.6.1.4 Печать файлов средствами стандартных приложений Windows с помощью функции ShellExecute и обращения к серверам СОМ . . . . 366 5.6.2 Печать с помощью объекта Printer 366 5.6.3 Подготовка и печать отчетов с помощью Rave 367 5.6.3.1 Общие сведения 367 5.6.3.2 Редактор Rave 368 5.6.3.3 Компоненты Delphi для связи с Rave 371
Содержание 5.6.3.4 Примеры работы с отчетами Rave 375 5.7 Развертывание приложений 380 5.7.1 Локализация и интернационализация приложений 380 5.7.2 Установка и настройка приложения: работа с системным реестром. . . 387 5.7.3 Установка и настройка приложения: работа с файлами .INI 391
Глава 6. Графика и мультимедиа
395
6.1 Построение графических изображений . . 395 6.1.1 Использование готовых графических файлов 395 6.1.1.1 Компонент Image и некоторые его свойства 395 6.1.1.2 Простое приложение для просмотра графических файлов . 397 6.1.1.3 Форматы графических файлов 397 6.1.1.4 Классы для хранения графических объектов TPicture, TBitMap, TIcon и TMetaf ile .398 6.1.2 Редактор Изображений Image Editor 400 6.1.2.1 Создание файла изображения 400 6.1.2.2 Создание пиктограммы для шаблона компонента в библиотеке . . . . . 402 6.1.2.3 Создание пиктограммы для кнопки , . 403 6.1.2.4 Работа с файлами ресурсов 404 6.1.3 Канва — холст для рисования 406 6.1.3.1 Канва и пикселы 406 6.1.3.2 Рисование по пикселам . 406 6.1.3.3 Рисование с помощью пера Реп 408 6.1.3.4 Brush — кисть . . 411 6.1.4 Пример построения собственного простого графического редактора . . 413 6.1.5 Режимы рисования 416 6.1.5.1 Режимы пера 416 6.1.5.2 Режимы копирования и рисования канвы. . . . . . . . . . . . . . . 417 6.1.6 Продолжение создания собственного графического редактора . . . . . 419 6.1.7 События OnPaint 425 6.2 Мультимедиа и анимация , ч. 427 6.2.1 Звук 427 6.2.1.1 Типы звуковых и мультимедиа файлов . . . i 427 6.2.1.2 Процедуры воспроизведения звуков Веер, MessageBeep и PlaySound . . 428 6.2.2 Начала анимации — создание собственной мультипликации 431 6.2.3 Воспроизведение немых видео клипов — компонент Animate 437 6.2.4 Универсальный проигрыватель MediaPlayer 440
Глава 7. Процессы, потоки, распределенные приложения 7.1 Общие сведения 7.2 Порождение дочерних процессов " 7.2.1 Запуск внешней программы функцией WinExec 7.2.2 Порождение процесса функцией CreateProcess 7.2.3 Запуск внешней программы и открытие документа функцией ShellExecute 7.3 Управление окнами внешних программ 7.3.1 Определение дескриптора окна приложения 7.3.2 Некоторые функции API Windows для управления окнами 7.4 Сообщения Windows и их обработка 7.4.1 Обработка сообщений в приложениях Delphi . 7.4.2 Посылка сообщений 7.4.2.1 Функция SendMessage 7.4.2.2 Функция PostMessage 7.4.2.3 Пример посылки сообщений 7.4.3 Обработка сообщений 7.4.4 Определение собственных сообщений 7.5 Динамический обмен данными — DDE 7.5.1 Общие сведения 7.5.2 Установление контакта с сервером 7.5.3 Обмен данными между клиентом и сервером
445 445 445 446 447 450 452 452 453 454 454 456 456 456 457 458 460 461 461 462 465
10
Программирование в Delphi 7 7.5.3.1 Построение приложения-сервера 7.5.3.2 Построение приложения-клиента 7.5.3.3 Запрос данных сервера 7.5.3.4 Постоянное отслеживание информации на сервере . 7.5.3.5 Передача информации от клиента к серверу
7.6 Организация приложений с несколькими потоками 7.6.1 Класс TThread 7.6.2 Пример . . 7.6.3 Более сложный пример 7.7 Внедрение и связывание объектов — OLE 7.8 Технология СОМ 7.8.1 Основные понятия СОМ 7.8.2 Реализация СОМ в Delphi 7.9 Компоненты — серверы СОМ 7.9.1 Компоненты — серверы в Delphi 7.9.2 Свойства и методы сервера Word 7.9.3 Тестовый пример работы с сервером Word 7.10 Технология CORBA 7.10.1 Общее описание 7.10.2 Реализация CORBA в Delphi Глава 8. Различные виды проектов Delphi 8.1 Кросс-платформенные проекты CLX 8.2 Создание и хранение шаблонов компонентов 8.3 Создание новых компонентов и включение их в библиотеку 8.3.1 Начало создания и установка компонента 8.3.2 Анализ класса нового компонента 8.3.2.1 8.3.2.2 8.3.2.3 8.3.2.4
Структура класса компонента Свойства Методы События
465 466 467 468 469
471 471 476 480 483 489 . 489 491 497 497 501 507 511 511 512 513 513 519 521 521 524
524 525 528 530
8.4 Разработка классов с помощью ModelMaker 533 8.5 Динамически присоединяемые библиотеки DLL 542 8.6 Пакеты 545 8.6.1 Общее описание концепции пакетов 545 8.6.2 Поддержка пакетов 545 8.6.3 Разработка собственных пакетов 548 ЧАСТЬ 3. Базы данных и сети 551 Глава 9. Приложения для работы с локальными базами данных. . 553 9.1 Базы данных 553 9.1.1 Принципы построения баз данных 553 9.1.2 Типы баз данных 556 9.1.2.1 Автономные базы данных 9.1.2.2 Файл-серверные базы данных 9.1.2.3 Базы данных клиент/сервер 9.1.2.4 Многоуровневые распределенные базы данных 9.1.3 Организация связи с базами данных в Delphi
557 557 558 558 558
9.2.2 Задание полей 9.2.3 Задание свойств таблицы 9.2.3.1 Validity Checks — проверка правильности значений 9.2.3.2 Table Lookup — таблица просмотра 9.2.3.3 Secondary Indexes — вторичные индексы 9.2.3.4 Referential Integrity — целостность на уровне ссылок 9.2.3.5 Password Security — пароли доступа 9.2.3.6 Table Language — язык таблицы 9.2.3.7 Dependent Tables — зависимые таблицы
562 564 564 565 565 567 568 569 569
9.2 Создание баз данных с помощью Database Desktop 9.2.1 Создание новой таблицы
560 560
Содержание 9.2.4 Завершение создания таблицы 9.2.5 Изменение структуры и заполнение таблицы с помощью Database Desktop . . 9.3 Создание и редактирование псевдонимов баз данных и каталогов . . . 9.3.1 Автоматически создаваемые псевдонимы рабочего и частного каталогов. . 9.3.2 Создание и просмотр псевдонимов баз данных в Database Desktop . . . 9.3.3 Создание и просмотр псевдонимов драйверов и баз данных в BDE Administrator 9.3.4 Создание и просмотр псевдонимов в SQL Explorer 9.4 Обзор компонентов, используемых в BDE для связи с базами данных . . 9.5 Основные свойства компонента Table и простейшие приложения на его основе 9.5.1 Установка связей между компонентами и базой данных, навигация по таблице 9.5.2 Свойства полей 9.5.3 Перенос полей на форму из Редактора Полей 9.5.4 Ограничения вводимых значений 9.5.5 Вычисляемые поля 9.5.6 Фильтрация данных •. 9.6 Использование словарей атрибутов полей . 9.7 Некоторые компоненты визуализации и управления данными . . . . 9.8 Компонент Session 9.9 Компонент BatchMove 9.10 Приложения с несколькими связанными таблицами 9.10.1 Связь головной и вспомогательной таблиц 9.10.2 Поля просмотра (lookup fields) 9.11 Программирование работы с базами данных 9.11.1 Состояние набора данных 9.11.2 Пересылка записи в базу данных 9.11.3 Кэширование изменений 9.11.4 Доступ к полям 9.11.5 Методы навигации 9.11.6 Поиск записей . 9.11.7 Методы установки диапазона допустимых значений 9.11.8 Методы создания и модификации таблиц 9.12 Пример программирования работы с базой данных 9.13 Модули данных 9.14 Клиентские наборы данных 9.14.1 Общие сведения 9.14.2 Наборы данных, основанные на файлах 9.14.3 Совокупные характеристики 9.14.4 Портфельные наборы данных 9.14.5 Тестовый пример
11 569 570 571 571 572 574 575 578 579 579 582 583 586 587 589 592 595 599 601 603 603 604 606 606 607 608 610 611 612 614 616 617 622 624 624 625 626 628 629
Глава 10. Создание приложений для работы с базами данных в сети . . 635 10.1 Основы языка SQL и его использование в приложениях 10.1.1 Общие сведения 10.1.2 Оператор выбора Select 10.1.2.1 Отбор записей из таблицы 10.1.2.2 Совокупные характеристики 10.1.2.3 Вложенные запросы 10.1.2.4 Объединения таблиц 10.1.3 Операции с записями 10.1.4 Операции с таблицами 10.1.5 Операции с индексами 10.1.6 Компонент Query 10.1.6.1 Table или Query — что лучше? 10.1.6.2 Ручная настройка Query 10.1.6.3 Визуальный построитель запросов SQL Builder
635 635 635 635 639 641 642 644 645 648 648 648 649 650
12
Программирование в Delphi 7 10.1.6.4 Динамические запросы и параметры Query 651 10.1.6.5 Основные свойства Query, связывание таблиц 653 10.1.6.6 Основные методы компонента Query 654 10.1.6.7 Кэширование изменений, совместное применение Query и UpdateSQL . . 655 10.1.7 Примеры приложений с использованием Query 658 10.1.7.1 Пример замены Table на Query 658 10.1.7.2 Пример формирования произвольных запросов SQL 663
10.2 Работа с базами данных в сети 666 10.2.1 Транзакции и проблемы многопользовательского режима работы . . . 666 10.2.2 Управление транзакциями, компонент Database 667 10.2.3 Работа с SQL Monitor 669 10.2.4 Управление доступом 670 10.3 InterBase — работа на платформе клиент/сервер ,. 672 10.3.1 Общие сведения 672 10.3.2 Программа IBConsole 672 10.3.3 Interactive SQL 676 10.3.4 Просмотры — Views 679 10.3.5 Хранимые на сервере процедуры 680 10.3.5.1 Создание выполняемых процедур 10.3.5.2 Вызов выполняемых хранимых процедур из приложения 10.3.5.3 Хранимые процедуры выбора
680 . 682 684
10.4 Доступ к базам данных через Microsoft ActiveX Data Objects (ADO) . . 10.4.1 Соотношение между компонентами BDE и ADO . . 10.4.2 Задание соединения компонентов ADO с базой данных . 10.4.3 Соединение с помощью компонента ADOConnection, управление транзакциями 10.4.4 Компоненты наборов данных ADOTable, ADOQuery, ADOStoredProc, ADODataSet 10.4.5 Выполнение команд компонентами ADOCommand и ADOConnection. . 10.5 Доступ к InterBase через InterBase Express (IBX) 10.5.1 Технология InterBase Express (IBX) 10.5.2 Компоненты IBDatabase и IBTransaction 10.5.3 Компоненты наборов данных IBTable, IBQuery, IBStoredProc 10.6 Доступ к базам данных с помощью компонентов dbExpress 10.6.1 Общие сведения 10.6.2 Соединение с базой данных — компонент SQLConnection 10.7 Коротко о MIDAS Глава 11. Обработка и документирование данных 11.1 Многомерный анализ данных — компоненты Decision Cube 11.1.1 Настройка компонентов приложения 11.1.2 Управление выполняющимся приложением 11.1.3 Компонент DecisionPivot 11.1.4 Компонент DecisionGraph 11.2 Создание отчетов 11.3 Использование серверов СОМ для документирования данных . . . . Глава 12. Разработка приложений, использующих сети Интернет и интранет 12.1 Интернет и WWW 12.2 Программное обеспечение, необходимое для работы с WWW 12.3 Описание документов на HTML 12.4 Создание собственного браузера 12.5 Динамические страницы Web-приложения CGI 12.6 Сервер Web Delphi 12.6.1 Создание модуля Web 12.6.2 Пример серверного приложения 12.7 Отладка серверов и преобразование их типов 12.8 Использование форм и таблиц в HTML . . . . . . ;
684 684 686
689 692 698 698 698 700 703 704 704 705 707 709 709 709 713 715 716 717 722 731 731 732 733 736 741 743 743 745 747 750
Содержание
13
12.8.1 Обработка ответа пользователя 750 12.8.2 Обмен данными меду клиентом и сервером 752 12.9 Использование шаблонов HTML 754 12.10 Работа с базами данных в Интернет 758 12.10.1 Просмотр таблицы данных .; 758 12.10.2 Работа с отдельными записями 761 12.10.3 Редактирование наборов данных 765 12.11 Технология WebSnap. 768 12.11.1 Web Broker или WebSnap? 768 12.11.2 Сервер WebSnap 769 12.12 Технология IntraWeb 779 12.13 Использование активных форм . . 784 12.13.1 Создание активной формы 784 12.13.2 Развертывание и использование активной формы. . . . . . . . . . 788 12.14 Технология Web Services . . . . .791 12.14.1 Общее описание 791 12.14.2 Invokable интерфейсы 791 12.14.3 Разработка сервера 793 12.14.4 Файлы описания интерфейса 796 12.14.5 Создание клиента 798 12.15 Обзор компонентов Internet Direct (Indy) 800 ЧАСТЬ IV. Справочные сведения 805 Глава 13. Справочные данные по языку Object Pascal в Delphi 7 . . 807 13.1 Синтаксис языка 807 13.2 Компилятор 808 13.2.1 Общее описание, директивы компилятора 808 13.2.2 Настройка компилятора 809 13.2.3 Директивы условной компиляции 812 13.2.4 Некоторые ключевые директивы ^ . . . . 813 13.2.5 Процедура Assert 815 13.3 Файлы проекта Delphi 816 13.3.1 Основные файлы проекта 816 13.3.2 Структура головного файла программы 817 13.3.3 Структура модуля 820 13.3.4 Предложение uses — подключение модулей 821 13.3.5 Файлы ресурсов 822 13.4 Области видимости и время жизни 824 13.5 Константы, переменные, типизированные константы . . 825 13.5.1 Константы и константные выражения 825 13.5.2 Переменные 826 13.5.3 Типизированные константы 827 13.6 Процедуры и функции 828 13.6.1 Объявление и описание функций и процедур 828 13.6.2 Различные варианты передачи параметров в функции и процедуры. . 830 13.6.3 Параметры со значениями по умолчанию 832 13.6.4 Перегрузка функций 833 13.7 Операции 833 13.7.1 Общее описание 833 13.7.2 Арифметические операции 834 13.7.3 Особенности выполнения арифметических операций с целыми и действительными числами 835 13.7.4 Операции отношения . 836 13.7.4 Булевы операции 837 13.7.5 Логические поразрядные операции . . . 837 13.7.6 Операции со строками . 838 13.7.7 Операции с указателями . 838 13.7.8 Операции с множествами 839
14
Программирование в Delphi 7
13.7.9 Операции с классами 13.7.10 Операция @ 13.7.11 Приоритет и ассоциативность операций 13.8 Операторы 13.8.1 Оператор присваивания и его соотношение с методом Assign 13.8.2 Оператор передачи управления goto 13.8.3 Оператор with 13.8.4 Условные операторы выбора if 13.8.5 Условный оператор множественного выбора case 13.8.6 Оператор цикла for 13.8.7 Оператор цикла repeat 13.8.8 Оператор цикла while 13.8.9 Прерывание цикла: оператор break, процедуры Continue, Exit и Abort. . 13.9 Динамическое распределение памяти 13.10 Исключения 13.10.1 Исключения и их стандартная обработка 13.10.2 Иерархия классов исключений в Delphi 13.10.3 Базовый класс исключений Exception . . •. 13.10.3.1 Свойства исключений 13.10.3.2 Конструкторы исключений 13.10.4 Обработка исключений в блоках try ... except . 13.10.4.1 Синтаксис блоков try ... except и операторов on...do 13.10.4.2 Последовательность обработки исключений 13.10.5 Исключение EAbort и функция Abort 13.11 Сообщения Windows и их обработка Глава 14. Типы данных в языке Object Pascal 14.1 Классификация типов данных, объявление типов 14.2 Приведение типов 14.3 Порядковые типы данных . 14.4 Целые типы данных 14.5 Действительные типы данных 14.6 Булевы типы данных 14.7 Символьные типы данных 14.8 Типы строк 14.9 Перечислимые типы 14.10 Ограниченные типы 14.11 Множества 14.12 Тип variant 14.13 Указатели 14.14 Файлы 14.14.1 Способы организации файлового ввода/вывода 14.14.2 Типы файлов 14.14.2.1 Текстовые файлы 14.14.2.2 Файловый ввод/вывод с помощью компонентов 14.14.2.3 Типизированные файлы 14.14.2.4 Нетипизированные файлы 14.14.3 Использование дескрипторов файлов 14.15 Массивы 14.15.1 Одномерные массивы 14.15.2 Многомерные массивы 14.15.3 Операции с массивами, передача массивов как параметров 14.16 Записи 14.17 Классы , 14.17.1 Объявление класса 14.17.2 Свойства 14.17.3 Методы и их наследование, полиморфизм
839 840 840 841 841 842 843 843 844 845 846 847 848 849 851 851 852 854 855 855 857 857 859 860 860 863 863 864 865 866 867 868 868 869 872 873 874 875 877 878 878 880 880 880 881 882 882 883 883 885 886 888 889 889 890 891
Содержание
15
14.17.4 Конструкторы и деструкторы 14.17.5 События
893 894
Глава 15. Справочные данные по интегрированной среде разработки Delphi
895
15.1 Структура меню Delphi 15.1.1 Меню файлов Pile 15.1.2 Меню редактирования EDIT 15.1.3 Меню поиска Search 15.1.4 Меню просмотра View 15.1.5 Меню проекта Project 15.1.6 Меню выполнения Run 15.1.7 Меню компонентов Component 15.1.8 Меню баз данных Database 15.1.9 Меню инструментария Tools 15.1.10 Меню ModelMaker 15.1.11 Меню Windows 15.1.12 Меню справки Help 15.2 Некоторые настройки Интегрированной Среды Разработки Delphi . . 15.2.1 Настройка Редактора Кода 15.2.2 Общие настройки среды
15.3 Палитра компонентов в Delphi 7
15.3.1 Общее описание 15.3.2 Страница Standard 15.3.3 Страница Additional 15.3.4 Страница Win32 15.3.5 Страница System 15.3.6 Страница Data Access 15.3.7 Страница Data Controls 15.3.8 Страница dbExpress 15.3.9 Страница DataSnap 15.3.10 Страница BDE 15.3.11 Страница ADO 15.3.12 Страница InterBase 15.3.13 Страница WebServices 15.3.14 Страница InternetExpress 15.3.15 Страница Internet 15.3.16 Страница WebSnap 15.3.17 Страница Decision Cube 15.3.18 Страница Dialogs 15.3.19 Страница WinS.l 15.3.20 Страница Samples 15.3.21 Страница ActiveX 16.3.22 Страница Rave 15.3.23 Страницы IndyClients, IndyServers, Indy I/O Handlers, Indy Intercepts, IndyMisk, FastNet 15.3.24 Страница СОМ+ 15.3.25 Страница InterBase Admin 15.3.25 Страница IW Standard 15.3.26 Страница IW Data 15.3.27 Страница IW Client Side 15.3.28 Страница IW Control 15.3.26 Страница Servers
Глава 16. Процедуры и функции Object Pascal, Delphi и API Windows 16.1 Справочные данные общего характера 16.1.1 Коды клавиш 16.1.2 Коды ошибок файлового ввода-вывода
895 895 900 903 904 906 908 910 911 911 915 915 915 915 915 921
926
926 929 930 933 935 935 936 937 938 939 940 . 941 942 943 943 944 946 946 947 948 949 950 951 951 952 952 954 955 956 956
957 957 957 960
16
Программирование в Delphi 7
16.1.3 Строка описания формата и функция Format 960 16.2 Математические функции 963 16.3 Тригонометрические и гиперболические функции 965 16.4 Процедуры и функции преобразования дат и времени 966 16.5 Функции обработки строк с нулевым символом в конце . . . . . . . 968 16.6 Процедуры и функции обработки строк в стиле Pascal (без нулевого символа в конце) 971 16.7 Процедуры и функции файлового ввода/вывода 973 16.8 Процедуры и функции управления файлами 976 16.9 Процедуры динамического распределения памяти. . 979 16.10 Прочие процедуры и функции Object Pascal 979 16.11 Процедуры и функции вызова диалоговых окон 981 16.11.1 Процедуры ShowMessage и ShowMessageFmt 981 16.11.2 Функции MessageDlg, MessageDlgPos и CreateMessageDialog . . . . 982 16.11.3 Функция TApplication.MessageBox 985 16.11.4 Другие процедуры вызова диалоговых окон 987 16.12 Процедуры и функции воспроизведения звуков 989 16.12.1 Веер — процедура Object Pascal 989 16.12.2 Веер — функция API Windows 989 16.12.3 MessageBeep — функция API Windows 989 16.12.4 PlaySound - функция API Windows 990 16.13 Функции API Windows для запуска из приложения внешних программ . 992 16.13.1 Сообщения об ошибках при запуске внешних программ 992 16.13.2 Функция ShellExecute 994 16.13.3 Функция FindExecutable 996 16.13.4 Функция WinExec 996 16.14 Функции API Windows для работы с сообщениями 997 16.14.1 PostMessage — функция 997 16.14.2 SendMessage — функция 998 16.14.3 RegisterWindpwMessage — функция 999 16.14.4 WM_USER — константа 999 16.14.5 TMessage — тип 1000 16.15 Некоторые вспомогательные функции API Windows и Delphi . . . 1000 16.15.1 CloseWindow 1000 16.15.2 EnableWindow 1001 16.15.3 FindWindow 1001 16.15.4 GetLastErrore 1002 ' 16.15.5 GetLongHint 1002 16.15.6 GetNextWindow 1003 16.15.7 GetShortHint 1003 16.15.8 GetSystemDirectory 1003 16.15.9 GetWindow 1004 16.15.10 GetWindowsDirectory 1005 16.15.11 GetWindowText 1005 16.15.12 Point 1006 16.15.13 Rect 1006 16.15.14 Shortcut 1007 16.15.15 ShortCutToText 1008 16.15.16 TextToShortCut 1000 Глава 17. Свойства, методы, события, типы, классы 1009 17.1 Свойства 1009 17.2 Методы 1057 17.3 События 1089 17.4 Некоторые типы и классы . 1112 17.5 Предметный указатель разделов книги, содержащих описания компонентов 1148 Дополнительные источники информации о Delphi 7 1151
'
Посвящается моей жене, без поддержки которой эта книга не появилась бы на свет
От автора Почти обо всем в Delphi? Данная книга является методическим пособием по Delphi 7 и предшествующим версиям Delphi. Я собирался вынести на обложку книги несколько нахальную фразу: «Почти обо всем в Delphi», но воздержался. Фраза нахальная, потому что обо всем в Delphi рассказать невозможно даже в такой толстой книге. Delphi — это прекрасная система визуального объектно-ориентированного программирования, одинаково радующая и новичков, и ассов. Новичку она позволяет с небольшими затратами сил и времени создавать прикладные программы, которые внешне неотличимы от программ, созданных профессионалами, и удовлетворяют всем требованиям Windows. Впрочем, теперь в Delphi имеется возможность создавать кросс-платформенные программы, которые могут работать и в среде Windows, и в среде Linux. Ну а для опытного профессионала Delphi открывает неограниченные возможности создавать сколь угодно сложные, распределенные приложения, работающие с любыми базами данных. Delphi настолько многогранна, что рассказать о всех ее аспектах в одной книге невозможно. Так что, возвращаясь к фразе «Почти обо всем в Delphi», я хочу подчеркнуть слово «почти», и кроме того, должен отметить, что «обо всем» — не значит «все». Я действительно пытался в данной книге охватить большинство возможностей Delphi. Но, конечно, степень детализации различных вопросов не одинакова. Основные аспекты применения Delphi я излагал подробно, с многочисленными примерами. Другие же вопросы я только обозначал, чтобы читатель знал, что подобные возможности в Delphi имеются, и целенаправленно искал дополнительную информацию по ним во встроенной справке Delphi или в других источниках. Итак, что же вы найдете в этой книге. Подробно изложена методика работы со средой Delphi. Причем я не ограничился только Delphi 7, но рассмотрел и три предыдущие версии 6-4, так как не все работают с самой последней версией (и правильно делают — для многих задач вполне достаточно Delphi 5 или 6). Рассмотрено то новое, что появилось в Delphi 7 — новые технологии и новый инструментарий среды разработки. Изложена методика грамотного проектирования, использующего возможности Delphi 7. Рассмотрены различные виды приложений: текстовые и графические редакторы, мультимедиа, работа с базами данных. Уделено внимание прикладным программам для Интернет. Рассмотрены технологии COM, CORBA, MIDAS, Web Broker, WebSnap, IntraWeb, Web Services, Indy. В общем, я действительно постарался рассказать или, по крайней мере, поговорить обо всем в Delphi. В результате изложение местам, возможно, слишком сжатое, шрифт книги мелковат, верстка предельно компактная. И я, и издательство делали все возможное, чтобы вместить столь обширный материал в приемлемый объем книги. Но, поскольку нельзя объять необъятное, в разделе «Дополнительные источники информации о Delphi 7» в конце книги приводится информация о других моих книгах и программных продуктах (ссылки на них в тексте книги даются в квадратных скобках, например, [4]), которые можно считать дополнением к данному изданию. Это, прежде всего, справочное пособие,, содержащее полное описание языка Object Pascal, подробные сведения по функциям Object Pascal и API Windows, а также развернутые сведе-
18
Программирование в Delphi 7
ния по компонентам и классам Delphi, их свойствам, методам, событиям. А для тех, кто освоил материал данной или другой аналогичной книги и хочет совершенствовать свое мастерство, предназначена книга «Приемы программирования в Delphi. Версии 7-5». Так что данная книга — первая в этой трилогии. Прилагаемый к книге диск содержит многие из примеров и баз данных, рассмотренных в книге. Они отличаются от демонстрационных примеров, распространяемых с Delphi, большей прозрачностью кодов (в них намеренно пропущены некоторые усложнения, затрудняющие чтение законченных реальных приложений) и подробным описанием в виде комментариев. На диске имеются также файлы демонстрационной версии справок по Delphi на русском языке [4], распространяемой автором через Интернет.
Для кого эта книга Настоящая книга рассчитана как на начинающих пользователей (но, все-таки, хотя бы поверхностно знакомых с каким-нибудь языком программирования), так и на опытных специалистов. Причем прежде всего — на начинающих, так как опытным разработчикам предназначена моя другая книга — «Приемы программирования'в Delphi». Отдельные главы данной книги можно рассматривать как курс начального обучения. Но книга намного шире простого учебника для начинающих. Как следует из многочисленных отзывов читателей моих предыдущих аналогичных книг, она позволяет, начав, практически, с нуля, стать полноценным специалистом и разрабатывать в Delphi профессиональные программы для Windows. По моим сведениям, эти книги реально используются в процессе обучения во многих ведущих ВУЗах и Университетах страны. При написании книги я отошел от стандартной структуры учебников с их жесткой регламентацией последовательности изучения материала. Я с уважением отношусь к своим потенциальным читателям и считаю, что они сами смогут регулировать последовательность и темп своего изучения Delphi или своей работы с этой системой. К тому же, я уверен, что овладеть какой-то системой можно только работая с ней, решая какую-то свою (не учебную) конкретную задачу. И последовательность изучения тех или иных вопросов определяется требованиями этой конкретной задачи. Поэтому я считаю основной задачей данной книги - дать побольше конкретной информации по всем затронутым в ней вопросам и расположить эту информацию в такой последовательности, чтобы ее легко было отыскать. Тогда и начинающий, и опытный пользователь сможет в любой момент найти то, что ему нужно сейчас для работы. Надо отметить, что опытному пользователю тоже нужна подобная книга, поскольку никто не в состоянии держать в голове все сведение по сотням компонентов Delphi, по синтаксису и параметрам тех или иных функций и процедур языка программирования. Выше я уже написал, что доверяю способности читателя самому выбрать наиболее удобную последовательность изучения материала данной книги. Благо она построена так, что отдельные темы можно изучать независимо друг от друга и в любой последовательности. Но я все же возьму на себя смелость дать некоторые советы по изучению материала разными категориями читателей. Тем, кто до чтения этой книги никогда не работал с Delphi, вероятно, будет полезной следующая последовательность работы. Сначала надо прочитать тот материал главы 1, который вводит читателя в мир объектно-ориентированного программирования: разделы 1.1, 1.2, 1.9. Затем следует научиться работать в интегрированной среде разработки Delphi с помощью последовательного изучения материалов главы 2 (ряд тонкостей, связанных с настройкой среды проектирования и с отладкой приложений, можно при этом пропустить, вернувшись к этому материалу позднее). Указанные разделы первых двух глав книги построены как
От автора
_________
1Э
начальный курс обучения работе с Delphi и не должны вызвать затруднений у тех, кто впервые установил эту систему на своем компьютере. Тем из начинающих, кто не знаком с языком программирования Object Pascal, можно рекомендовать по началу не отвлекаться на его изучение (некоторый минимум сведений о нем содержится в разделе 1.9 главы 1), а смело создавать по рекомендациям главы 2 и последующих глав свои первые приложения. Мой опыт преподавания Delphi говорит, что это вполне возможно. Если что-то в кодах этих приложений вам будет непонятно, тогда обращайтесь за разъяснениями к соответствующим разделам глав 13 и 14. Более внимательное изучение этих глав целесообразно отнести к тому времени, когда вы уже хорошо освоитесь с Delphi и вам захочется перейти к каким-то сложным приложениям, в которых потребуется использовать более глубокие аспекты языка Object Pascal. После начального обучения по материалам глав 1 и 2 желательно последовательно изучить разделы 4.1-4.3 главы 4 и материал разделов 5.1, 5.2, 5.3, 5.5 главы 5. По мере изучения этих разделов полезно обращаться к соответствующим разделам главы 3, чтобы получить дополнительную информацию об используемых в примерах компонентах. Изучив все это, вы можете перестать считать себя начинающим пользователем и планировать дальнейшее знакомство с материалом других глав и с пропущенными разделами указанных глав исходя из того, что вам более всего нужно в вашей работе. Теперь некоторые рекомендации более или менее опытным пользователям Delphi. Можно, вероятно, не читать указанные выше разделы главы 1, но раздел 1.8 написан специально для вас — в нем коротко перечислено то новое, что появилось в Delphi 7, и указано, в каких разделах книги вы найдете описание этих новых возможностей. Полезно также посмотреть ряд разделов главы 2,.связанных с различными инструментами среды Delphi и с их настройкой. Жизнь показывает, что даже достаточно опытные пользователи используют далеко не все возможности, встроенные в Delphi. К тому же, если вы работали ранее с другими версиями Delphi, то в этой главе вы найдете немало нового, что появилось только в Delphi 7. Полезно также просмотреть материал главы 3, посвященной компонентам Delphi общего назначения. Не говоря уже о новых компонентах, появившихся в Delphi 7, вы, вероятнее всего, и из прежних компонентов используете далеко не все, отдавая предпочтение наиболее любимым вами. Так что посмотреть сравнительный анализ различных компонентов будет в любом случае полезно. Ну, а в остальном т- изучайте то, что вам надо, и в любой последовательности. Опыт подскажет вам наиболее эффективный способ работы с материалом.
Чем отличается эта книга от предшествующих У читателей, знакомых с моими предыдущими книгами «Программирование в Delphi ...» по версиям Delphi 4-6, может возникнуть законный вопрос: «Чем отличается эта книга от прежних и имеет ли смысл ее приобретать». Начет приобретения — решать вам. А отличий очень много. Я даже не говорю о новых возможностях, появившихся только в Delphi 7, новых технологиях доступа к данным, новых компонентах, новом инструментарии среды разработки. Но и в изложении традиционных для Delphi вопросов я многое изменил и добавил, по сравнению с прошлыми книгами. Уже в книге по Delphi 6 появилось три новые главы: 4 «Организация управления приложением», 7 — «Процессы, потоки, распределенные приложения» и 13 — «Разработка приложений для Интернет». Появился материал по технологиям создания распределенных приложений, по COM.CORBA и MIDAS, по клиентским приложениям, существенно переработан материал подавляющего большинства глав, добавлены новые примеры, новые сведения о компонентах. А в данной книге введено рассмотрение таких технологий, как WebSnap,
20
Программирование в Delphi 7
IntraWeb, Web Services, Indy. Некоторые из них появились только в Delphi 7, а некоторые были ранее, но в своих книгах я о них не говорил. Добавились материалы по новому инструментарию Delphi: ModelMaker и Rave, по новым компонентам. Это то, что добавилось по сравнению с книгой по Delphi 6. А вспомнить все, что добавилось по сравнению с более ранними книгами, я затрудняюсь — от тех книг осталось не так уж много непереработанного материала. За счет чего в книгу вместилось много нового материала? Прежде всего, из книги удален некоторый устаревший или не очень интересный материал. Удалена глава, посвященная разработке справочных систем. Несколько сокращен размер справочной части, тем более, что имеется отдельное справочное пособие [1] и имеется источник справочной информации [4]. Ну, а остальное получено за счет более компактного изложения. Я надеюсь, что читатели с интересом и пользой ознакомятся с материалом данной книги, овладеют методикой разработки прикладных программ для Windows, и станут такими же приверженцами Delphi, каким являюсь я сам. А для тех, кто овладеет материалом этой книги и захочет продолжить свое знакомство с Delphi, я написал книгу [2] «Приемы программирования в Delphi», которую осмеливаюсь предложить вниманию читателей.
Часть 1 Delphi 7 Studio
Глава 1
Delphi и современные информационные технологии Delphi — одна из самых мощных систем, позволяющих на самом современном уровне создавать как отдельные прикладные программы Windows, так и разветвленные комплексы, предназначенные для работы в корпоративных сетях и в Интернет. Но прежде, чем рассказывать о Delphi, надо сделать краткий обзор тех современных информационных технологий, которые она поддерживает, чтобы была ясна терминология, используемая на протяжении всей книги. Вводимые термины выделяются в тексте курсивом. Заодно, обзор будет и подобием путеводителя по книге, так как по ходу изложения будут даваться ссылки на главы и разделы, содержащие сведения об использовании тех или иных технологий в Delphi. Те, кто знаком с приводимой терминологией и с сущностью рассматриваемых информационных технологий, могут пропустить первые разделы этой главы и сразу перейти к разделам, посвященным конкретно Delphi.
1.1 Объектно-ориентированное программирование Объектно-ориентированное программирование (сокращенно ООП) • • это в наше время совершенно естественный подход к построению сложных (и не очень сложных) программ и систем. Когда вы открываете любую программу Windows, вы видите окно с множеством кнопок, разделов меню, окон редактирования, списков и т.п. Все это объекты. Причем сами по себе они ничего не делают. Они ждут каких-то событий — нажатия пользователем клавиш или кнопок мыши, перемещения курсора и т.д. Когда происходит подобное событие, объект получает сообщение об этом и как-то на него реагирует: выполняет некоторые вычисления, разворачивает список, заносит символ в окно редактирования. Вот такая программа Windows и есть объектно-ориентированная программа (для краткости в дальнейшем на протяжении этой книги мы будем называть прикладные программы приложениями). Приложение, построенное по принципам объектной ориентации — это не последовательность каких-то операторов, не некий жесткий алгоритм. Объектно-ориентрованная программа — это совокупность объектов и способов их взаимодействия. Отдельным (и главным) объектом при таком подходе во многих случаях можно считать пользователя программы. Он же служит и основным, но не единственным, источником событий, управляющих приложением. Попробуем разобраться с основным понятием ООП — объектом. Для начала можно определить объект как некую совокупность данных и способов работы с ними. Данные можно рассматривать как поля записи. Это характеристики объекта. Пользователь и объекты программы должны, конечно, иметь возможность читать эти данные объекта, как-то их обрабатывать и записывать в объект новые значения. Здесь важнейшее значение имеют приниипы инкапсуляиии и скрытия данных. Принцип скрытия данных заключается в том, что внешним объектам и поль-
24
Глава 1
зователю прямой доступ к данным, как правило, запрещен. Делается это из двух соображений. Во-первых, для надежного функционирования объекта надо поддерживать целостность и непротиворечивость его данных. Если не позаботиться об этом, то внешний объект или пользователь могут занести в объект такие неверные данные, что он начнет функционировать с ошибками. Во-вторых, необходимо изолировать внешние объекты от особенностей внутренней реализации данных. Для внешних потребителей данных должен быть доступен только пользовательский интерфейс — описание того, какие имеются данные и функции и как их использовать. А внутренняя реализация — это дело разработчика объекта. При таком подходе разработчик может в любой момент модернизировать объект, изменить структуру хранения и форму представления данных, но, если при этом не затронут интерфейс, внешний потребитель этого даже не заметит. И, значит, во внешней программе и в поведении пользователя ничего не придется менять. Чтобы выдержать принцип скрытия данных, в объекте обычно определяются процедуры и функции, обеспечивающие все необходимые операции с данными: их чтение, преобразование, запись. Эти функции и процедуры называются методами и через них происходит общение с данными объекта. Совокупность данных и методов их чтения и записи называется свойством. Со свойствами вы будете иметь дело на протяжении всей этой книги. Свойства можно устанавливать в процессе проектирования, их можно изменять программно во время выполнения вашей прикладной программы. Причем внешне это все выглядит так, как будто объект имеет какие-то данные, например, целые числа, которые можно прочитать, использовать в каких-то вычислениях, заложить в объект новые значения данных. В процессе проектирования вашего приложения с помощью Delphi вы можете видеть значения некоторых из этих данных в окне Инспектора Объектов, можете изменять эти значения. В действительности все обстоит иначе. Все общение с данными происходит через методы их чтения и записи. Это происходит и в процессе проектирования, когда среда Delphi запускает в нужный момент эти методы, и в процессе выполнения приложения, поскольку компилятор Delphi незримо для разработчика вставляет в нужных местах программы вызовы этих методов. Помимо методов, работающих с отдельными данными, в объекте имеются методы, работающие со всей их совокупностью, меняющие их структуру. Таким образом, объект является совокупностью свойств и методов. Но это пока нельзя считать законченным определением объекта, поскольку прежде, чем дать полное определение, надо еще рассмотреть взаимодействие объектов друг с другом. Средой взаимодействия объектов (как бы силовым полем, в котором существуют объекты) являются сообщения, генерируемые в результате различных событий. События наступают, прежде всего, вследствие действий пользователя — перемещения курсора мыши, нажатия кнопок мыши или клавиш клавиатуры. Но события могут наступать и в результате работы самих объектов. В каждом объекте определено множество событий, на которые он может реагировать. В конкретных экземплярах объекта могут быть определены обработчики каких-то из этих событий, которые и определяют реакцию данного экземпляра объекта. К написанию этих обработчиков, часто весьма простых, и сводится, как будет видно далее, основное программирование при разработке графического интерфейса пользователя с помощью Delphi. Теперь можно окончательно определить объект как совокупность свойств и методов, а также событий, на которые он может реагировать. Условно это изображено на рис. 1.1. Внешнее управление объектом осуществляется через обработчики событий. Эти обработчики обращаются к методам и свойствам объекта. Начальные значения данных объекта могут задаваться также в процессе проектиро-
Delphi и современные информационные технологии
25
вания установкой различных свойств. В результате выполнения методов объекта могут генерироваться новые события, воспринимаемые другими объектами программы или пользователем. .-
гИС. !•!
Установки при проектировании
Схема организации объекта
1 Свойства События
Методы Обработчики событий
3
ТУТ События
Представление о программе как о некоторой фиксированной совокупности объектов не является полным. Чаще всего сложная программа — это не просто какая-то предопределенная совокупность объектов. В процессе работы объекты могут создаваться и уничтожаться. Таким образом, структура программы является динамическим образованием, меняющимся в процессе выполнения. Основная цель создания и уничтожения объектов — экономия ресурсов компьютера и, прежде всего, памяти. Несмотря на бурное развитие вычислительной техники, память, наверное, всегда будет лимитировать возможности сложных приложений. Это связано с тем, что сложность программных проектов растет теми же, если не более быстрыми, темпами, что и техническое обеспечение. Поэтому от объектов, которые не нужны на данной стадии выполнения программы, нужно освобождаться. При этом освобождаются и выделенные им области памяти, которые могут использоваться вновь создаваемыми объектами. Простой пример этого — окно-заставка с логотипом, появляющееся при запуске многих приложений. После начала реального выполнения приложения эта заставка исчезает с экрана и никогда больше не появится в данном сеансе работы. Было бы варварством не уничтожить этот объект и не освободить занимаемую им память для более продуктивного использования. С целью организации динамического распределения памяти во все объекты заложены методы их создания — конструкторы и уничтожения — деструкторы. Конструкторы тех объектов, которые изначально должны присутствовать в приложении (прикладной программе), срабатывают при запуске программы. Деструкторы всех объектов, имеющихся в данный момент в приложении, срабатывают при завершении его работы. Но нередко и в процессе выполнения различные новые объекты (например, новые окна документов) динамически создаются и уничтожаются с помощью их конструкторов и деструкторов. Включать объекты в свою программу можно двумя способами: вручную включать в нее соответствующие операторы (это приходится делать не очень часто) или путем визуального программирования, используя заготовки — компоненты.
1.2 Визуальное программирование интерфейса Сколько существует программирование, столько существуют в нем и тупики, в которые оно постоянно попадает и из которых, в конце концов, доблестно выходит. Один из таких тупиков или кризисов был не так уж давно связан с разработкой графического интерфейса пользователя. Программирование вручную всяких привычных пользователю окон, кнопок, меню, обработка событий мыши и клавиатуры, включение в программы изображений и звука требовало все больше и
26
Глава 1
больше времени программиста. В ряде случаев весь этот сервис начинал занимать до 80-90% объема программных кодов. Причем весь этот труд нередко пропадал почти впустую, поскольку через год — другой менялся общепринятый стиль графического интерфейса, и все приходилось начинать заново. Выход из этой ситуации обозначился благодаря двум подходам. Первый из них — стандартизация многих функций интерфейса, благодаря чему появилась возможность использовать библиотеки, имеющиеся, например, в Windows. В частности, появился API Windows — интерфейс, в котором описано множество функций, причем от версии к версии набор функций расширяется, внутреннее описание функций совершенствуется, но формы вызова функций не изменяются. В итоге, при смене стиля графического интерфейса (например, при переходе от Windows 3.x к Windows 95, а затем к Windows 2000) приложения смогли автоматически приспосабливаться к новой системе без какого-либо перепрограммирования. На этом пути создались прекрасные условия для решения Одной из важнейших задач совершенствования техники программирования — повторного использования кодов. Однажды разработанные вами формы, компоненты, функции могли быть впоследствии неоднократно использованы вами или другими программистами для решения их задач. Каждый программист получил доступ к наработкам других программистов и к огромным библиотекам, созданным различными фирмами. Причем была обеспечена совместимость программного обеспечения, разработанного на разных алгоритмических языках. Вторым революционным шагом, кардинально облегчившим жизнь программистов, явилось появление визуального программирования, возникшего в Visual Basic и нашедшего блестящее воплощение в Delphi и С+-l-Builder фирмы Borland. Визуальное программирование позволило свести проектирование пользовательского интерфейса к простым и наглядным процедурам, которые дают возможность за минуты или часы сделать то, на что ранее уходили месяцы работы. В современном виде в Delphi это выглядит так. Вы работаете в Интегрированной Среде Разработки (ИСР или Integrated development environment — IDE) Delphi. Среда предоставляет вам формы (в приложении их может быть несколько), на которых размещаются компоненты. Обычно это оконная форма, хотя могут быть и невидимые формы. На форму с помощью мыши переносятся и размещаются пиктограммы компонентов, имеющихся в библиотеках Delphi. С помощью простых манипуляций вы можете изменять размеры и расположение этих компонентов. При этом вы все время в процессе проектирования видите результат — изображение формы и расположенных на ней компонентов. Вам не надо мучиться, многократно запуская приложение и выбирая наиболее удачные размеры окна и компонентов. Результаты проектирования вы видите, даже не компилируя программу, немедленно после выполнения какой-то операции с помощью мыши. Но достоинства визуального программирования не сводятся к этому. Самое главное заключается в том, что во время проектирования формы и размещения на ней компонентов Delphi автоматически формирует коды программы, включая в нее соответствующие фрагменты, описывающие данный компонент. А затем в соответствующих диалоговых окнах пользователь может изменить заданные по умолчанию значения каких-то свойств этих компонентов и, при необходимости, написать обработчики каких-то событий. То есть проектирование сводится, фактически, к размещению компонентов на форме, заданию некоторых их свойств и написанию, при необходимости, обработчиков событий. Компоненты могут быть визуальные, видимые при работе приложения, и невизу алъные, выполняющие те или иные служебные функции. Визуальные компоненты сразу видны на экране в процессе проектирования в таком же виде, в каком их увидит пользователь во время выполнения приложения. Это позволяет очень легко выбрать место их расположения и их дизайн — форму, размер, оформление,
Delphi и современные информационные технологии
27
текст, цвет и т.д. Невизуальные компоненты видны на форме в процессе проектирования в виде пиктограмм, но пользователю во время выполнения они не видны, хотя и выполняют для него за кадром весьма полезную работу. В библиотеки визуальных компонентов Delphi включено множество типов компонентов и их номенклатура очень быстро расширяется от версии к версии. Имеющегося уже сейчас вполне достаточно, чтобы построить практически любое самое замысловатое приложение, не прибегая к созданию новых компонентов. При этом даже неопытный программист, делающий свои первые шаги на этом поприще, может создавать приложения, которые выглядят совершенно профессионально. Компоненты библиотек Delphi и типы других объектов оформляются в виде классов. Классы — это типы, определяемые пользователем. В классах описываются свойства объекта, его методы и события, на которые он может реагировать. Язык программирования Object Pascal, который используется в Delphi, предусматривает только инструментарий создания классов. А сами классы создаются разработчиками программного обеспечения. Создатели Delphi уже разработали для вас множество очень полезных классов и включили их в библиотеки системы. Этими классами вы пользуетесь при работе в Интегрированной Среде Проектирования. Впрочем, это нисколько не мешает создавать вам свои новые классы. Если бы при создании нового класса вам пришлось все начинать с нуля, то эффективность этого занятия была бы под большим сомнением. Да и разработчики Delphi вряд ли создали бы в этом случае такое множество классов. Действительно, представьте себе, что при разработке нового компонента, например, какой-нибудь новой кнопки, вам пришлась бы создавать все: рисовать ее изображение, описывать все свойства, определяющие ее место расположения, размеры, надписи и картинки на ее поверхности, цвет, шрифты, описывать методы, реализующие ее поведение — изменение размеров, видимость, реакции на сообщения, поступающие от клавиатуры и мыши. Вероятно, представив себе все это, вы отказались бы от разработки новой кнопки. К счастью, в действительности все обстоит гораздо проще, благодаря одному важному свойству классов — наследованию. Новый класс может наследовать свойства, методы, события своего родительского класса, т.е. того класса, на основе которого он создается. Например, при создании новой кнопки можно взять за основу один из уже разработанных классов кнопок и только добавить к нему какие-то новые свойства или отменить какие-то свойства и методы родительского класса. Благодаря визуальному объектно-ориентированному программированию была создана технология, получившая название быстрая разработка приложений, по-английски RAD — Rapid Application Development. Эта технология характерна для нового поколения систем программирования, к которому относится и Delphi. Delphi базируется на языке Object Pascal (впрочем, начиная с версии Delphi 7 разработчики назвали его языком Delphi). Компиляторы с языков семейства Паскаль фирмы Borland (начиная с Turbo Pascal 1.0) были одними из самых быстрых компиляторов. В настоящее время Object Pascal — это объектно-ориентированный язык с твердой опорой в виде хорошего компилятора. Надо иметь в виду, что ориентация приложений Delphi на Object Pascal нисколько не сужает возможностей разработчика. Приложения Delphi прекрасно могут использовать разработки на других языках, включая C++ и даже ассемблер. Можно использовать библиотеки, созданные другими фирмами, в частности, Microsoft или независимыми разработчиками. Можно реализовывать свои разработки в виде самостоятельных выполняемых файлов или в виде пакетов, поддерживающих выполнение ряда приложений. Отдельно надо сказать об одной из главных задач Delphi — разработке приложений для работы с базами данных. В этой области Delphi занимает самые передовые позиции, работая с любыми системами управления базами данных.
28
Глава 1
В целом Delphi — великолепный инструмент, как для начинающих программистов, так и для ассов программирования. Так что не зря вы заинтересовались Delphi и, надеюсь, с пользой ознакомитесь с данной книгой.
1.3 Взаимодействие приложений в информационных системах ООП и визуальное проектирование позволяют создавать прекрасные прикладные программы самого разного назначения. Но в настоящее время приложения, разрабатываемые для различных предприятий и их подразделений, как правило, должны функционировать не сами по себе, а являться частью некоторой информационной системы. В этом случае один из основных вопросов — организация взаимного общения приложений друг с другом и с хранилищами информации — базами данных (БД). Как правило, приложения, работающие в составе информационной системы, черпают информацию из баз данных, к которым имеют доступ и другие приложения. При этом естественным образом создается возможность общения приложений через данные. Например, одно приложение может записать результаты своей работы в БД, а другое — прочитать эти данные и использовать их в своей работе. Такое простейшее общение требует только одного — унификации данных, форматов их хранения и языка запросов к БД. Последнее решается, чаще всего, с помощью языка SQL, с которым вы познакомитесь в разд. 10.1 гл. 10. Во многих случаях подобного общения через данные недостаточно для эффективной работы системы. Приложение-потребитель данных не может ждать, пока кто-то запустит приложение, поставляющее эти данные. Значит нужна возможность запускать из одного приложения другие, передавая в них какую-то информацию. Запуск внешнего приложения называется порождением процесса. Дочерний процесс может выполняться в адресном пространстве родительского процесса, т.е. как бы встраиваться в породивший его процесс, а может выполняться в другом, собственном адресном пространстве и в другом параллельном потоке. Вопросы, связанные с порождением процессов и организацией потоков, будут рассмотрены в гл. 7. Запуск из одного приложения других приложений позволяет использовать результат работы дочернего процесса, но и только. Часто этого мало. Требуется обмен информацией между приложениями, выполняющимися параллельно. Причем, надо, чтобы этот обмен не зависел от того, на каком языке написано то или иное приложение. А при работе в сети компьютеров, использующих разные платформы (Windows, Unix, Solaris и др.), желательно обеспечить и независимость общения от платформы. Простейшими средствами общения, появившимися еще на заре Windows, являются разделяемые файлы (файлы, к которым одновременно могут получать доступ разные приложения), буфер обмена Clipboard, доступный практически всем приложениям Windows, и технология DDE — динамического обмена данными. Использование разделяемых файлов, баз данных и буфера обмена достаточно актуальны и сейчас как простейшие средства связи приложений друг с другом. А технология DDE (она будет рассмотрена в разд. 7.5 гл. 7), пожалуй, уступила место более современным способам общения. Позднее появилась технология связывания и внедрения объектов (Object Linking and Embedding) — OLE 1. Она позволяла создавать составные документы, включая, например, в документ Word таблицу Excel. На смену этой технологии пришла OLE 2, позволявшая разным программам предоставлять друг другу свои функции. Пользуясь этой технологией, одно приложение может не просто вызвать
Delphi и современные информационные технологии
29
другое, но и обратиться к отдельным его функциям, т.е. управлять им. Вопросы реализации OLE в Delphi рассмотрены в разд. 7.7 гл. 7. Следующим шагом на пути совершенствования способов взаимодействия приложений друг с другом явилась разработка COM (Component Object Model) — компонентной модели объектов. Это стандартизованное описание функций (служб) программы, к которым она дает доступ другим программам. При этом неважно, на каких языках написаны программы и где они выполняются: в одном потоке, в разных потоках, на разных компьютерах. Особенно расширяет эти возможности распределенная модификация СОМ — DCOM. Основой технологии СОМ является понятие интерфейса. Каждый объект СОМ имеет несколько интерфейсов, дающих доступ к его функциям. Клиенту передается указатель на требуемый ему интерфейс, после чего клиентское приложение может вызывать описанные в интерфейсе функции. Основные понятия технологий СОМ и DCOM и примеры их использования будут рассмотрены в разд. 7.8 и 7.9 гл. 7. На основе спецификаций СОМ и DCOM разработан ряд современных технологий, к которым имеют доступ приложения Delphi и о которых подробнее сказано в следующем разделе. А завершая данный раздел, необходимо сказать еще об одном бурно развивающемся способе общения приложений — использовании глобальной сети Интернет. В Интернет могут размещаться и базы данных, и серверы, с которыми общается приложение пользователя. Вопросам работы приложений Delphi с протоколами Интернет посвящена гл. 12. Так что здесь на взаимодействии приложений в Интернет мы останавливаться не будем. Отмечу только, что рассмотренное в гл. 12 относится не столько к Интернет, сколько к любым сетям, использующим протоколы, первоначально разработанные для Интернет. Сейчас эти протоколы используются в очень многих локальных и корпоративных сетях.
1.4 Распределенные многозвенные приложения Спецификация DCOM и созданные на ее основе технологии позволили перейти к новому этапу построения систем — распределенным многозвенным приложениям. Предшественником их явилась платформа клиент/сервер. Она сводится к тому, что на верхнем уровне имеется удаленный сервер данных, который хранит базу данных и осуществляет управление ею. А на нижнем уровне имеются клиентские приложения, работающие с этими данными. Это так называемые толстые клиенты, которые реализуют бизнес-логику — правила манипулирования с данными, проверки их непротиворечивости и достоверности. Впрочем, в системах клиент/сервер имеется возможность частично перенести бизнес-логику на сервер с помощью хранимых на сервере процедур. Создание подобных процедур, позволяющих разгрузить компьютеры клиентов, рассмотрены в разд. 10.3.5 гл. 10. Клиенты могут вызывать эти процедуры со своих компьютеров и просматривать полученные ответы. Технология клиент/сервер удовлетворительно обслуживает системы уровняотдельных подразделений некоторого предприятия. Но при создании более крупных систем уровня предприятия организация клиент/сервер сталкивается с рядом сложностей. Размещение бизнес-логики в основном на компьютерах клиентов мешает созданию единой системы с едиными правилами обработки и представления данных. Много сил уходит на согласование работы различных клиентских приложений, на построение мостов между ними, на устранение дублирования одних и тех же функций различными приложениями. Выходом из такого положения явилась концепция многозвенных распределенных приложений. Чаще всего используется трехзвенная архитектура. На верхнем уровне расположен удаленный сервер баз данных. Он обеспечивает хранение и управление данными. На среднем уровне (middleware) располагается сервер прило-
30
Глава 1
жений. Он обеспечивает соединение клиентов с сервером БД и реализует бизнес-логику. На нижнем уровне находятся клиентские приложения. В ряде случаев это могут быть тонкие клиенты, обеспечивающие только пользовательский интерфейс, поскольку вся бизнес-логика может располагаться на сервере приложений. Многозвенная система функционирует следующим образом. Пользователь запускает клиентское приложение. Оно соединяется с доступным ему сервером приложений. Затем клиент запрашивает какие-то данные. Этот запрос упаковывается в пакет установленного формата и передается серверу приложений. Там пакет расшифровывается и передается серверу БД, который возвращает затребованные данные. Сервер приложений обрабатывает эти данные согласно заложенной в него бизнес-логике, упаковывает и передает этот пакет клиенту. Клиент распаковывает данные и показывает их пользователю. Если пользователь изменил данные, то цепочка их передачи на сервер БД выглядит следующим образом. Клиент упаковывает измененные данные и отправляет пакет на сервер приложений. Тот распаковывает их и отправляет на сервер БД. Если все исправления могут быть без осложнений занесены в БД, то на этом все завершается. Но могут возникнуть осложнения: сделанные изменения могут противоречить бизнес-правилам.или изменения одних и тех же данных разными пользователями могут противоречить друг другу. Тогда те записи, которые не могут быть занесены в БД, возвращаются клиенту. Пользователь может исправить их или отказаться от сделанных изменений. Для обмена информацией сервера приложений с клиентами и серверами БД разработаны различные протоколы и средства: DCOM, CORBA, MIDAS, MTS, OLEnterprise, J2EE, TCP/IP, HTTP, XML. Большинство этих средств обмена будут далее рассмотрены в книге более или менее подробно. А пока ограничимся краткой характеристикой основных из них. О СОМ и DCOM уже говорилось в разд. 1.3 и подробнее эти основополагающие спецификации рассмотрены в разд. 7.8. CORBA является одним из самых мощных инструментов создания распределенных приложений. Для использования CORBA на каждом компьютере, содержащем приложения клиентов, должен быть установлен брокер VisiBroker, обеспечивающий связь приложения со средой CORBA. А на одном из компьютеров в сети должны быть установлены элементы этой среды. Если все это сделано, открываются очень широкие возможности для создания сложных распределенных информационных систем. Описание основ CORBA и примеры работы с CORBA из Delphi вы найдете в разд. 7.10. Система MIDAS позволяет создавать распределенные приложения, использующие самые разные протоколы и технологии: DCOM, CORBA, OLEnterprise, HTTP, сокеты TCP/IP, MTS и другие. О технике работы с MIDAS см. разд. 10.7 и 9.14. Система Microsoft Transaction Server (MTS ) — это еще одна система создания многозвенных приложений. Правда, она работает в полную силу далеко не во всех версиях Windows. Поэтому в данной книге она не рассматривается. О TCP/IP и HTTP, применяемых в Интернет и корпоративных сетях, основанных на тех же протоколах, что Интернет, будет немало сказано в гл. 12. Там же вы найдете сведения о языке HTML, используемдм для описания документов Web. Дальнейшим развитием языка HTML стал XML, о котором подробнее сказано в следующем разделе. Язык программирования Java — это объектно-ориентированный язык, используемый, прежде всего, для приложений Интернет. Он характеризуется простотой, надежностью, способностью создавать простыми средствами интерактивные сетевые программы. Отличительной особенностью Java является возможность исполнять созданный код на любой из поддерживаемых платформ. Достигается это тем, что программы транслируются в некоторое промежуточное представление, называемое байт-кодом. Этот байт-код может затем интерпретироваться
Delphi и современные информационные технологии
31
в. любой системе, в которой есть среда выполнения Java. Обычно программы на языках, использующих интерпретацию, работают медленно. Но к Java это не относится. Байт-код легко переводится непосредственно в машинные коды, что позволяет достичь очень высокой производительности. Дальнейшим развитием возможностей, заложенных в Java, явилась платформа J2EE (Java 2 Enterprise Edition). Она вобрала в себя все технологии, наработанные к этому времени, и стала основой для большинства серверов приложений. Эта платформа обеспечивает выполнение приложений, написанных в рамках определенных стандартов, и снимает с разработчика заботу об управлении очередями, транзакциями, сообщениями и многом другом. Перечисленные технологии могут использоваться в различных видах распределенных приложений, работающих с базами данных. Но, конечно, в настоящий момент основной упор при разработке распределенных приложений делается на использование Интернет, корпоративных сетей, основанных на тех же протоколах, что и Интернет, а также технологии Web. Глобальность Интернет делает эту сеть незаменимой при создании многопользовательских распределенных приложений.
1.5 Переносимость данных и программ Одна из важнейших проблем, решаемых в настоящее время, — переносимость программ и данных между платформами. Переносимость приложений между разными аппаратными платформами на уровне исходных кодов давно решена во многих алгоритмических языках, и, прежде всего — в С. Но сейчас требуется большее — переносимость на уровне исполняемых кодов. Т.е. надо, чтобы одна и та же программа без дополнительной перекомпиляции могла выполняться под управлением Windows, Sun Solaris, IBM AIX и т.п. Эта задача решается средствами Java — языка программирования, коротко описанного в предыдущем разделе. Реализация байт-кода и виртуальных машин для его выполнения на современных аппаратных платформах обеспечивает для многих приложений достаточную эффективность выполнения. Наряду с потребностями переносимости программ имеется, даже, пожалуй, более насущная потребность переносимости данных. Ведь до сих пор во многих случаях приходится поддерживать и по возможности модернизировать старые приложения DOS только потому, что написанные с их помощью документы невозможно прочитать иными способами. Решающим шагом на пути решения этой проблемы стал в свое время язык HTML. Он по праву завоевал весь мир и стал основой построения документов Web. Но со временем проявилась недостаточность возможностей этого языка. Он стал развиваться, в него вносилось множество дополнений, а в итоге он потерял свою стройность, целостность и главное — переносимость. Дальнейшее развитие направления, начатого в HTML, вылилось в создание языка XML (Extensible Markup Language) — расширяемого языка разметки гипертекстов. Гипертекст — это то, с чем все знакомы по справкам Windows, в которых, щелкая на ссылках в тексте, вы вызываете ту или иную тему. Те, кто использует Интернет и W W W , знакомы с аналогичной особенностью любых страниц Web. Язык XML, наряду с HTML, может использоваться для описания подобных гипертекстовых документов. Но в действительности этот язык — нечто большее. Это средство разработки пользователем своих собственных языков описания гипертекстовых документов. Созданный с помощью XML язык разметки может отражать специфические потребности конкретной фирмы или пользователя. После своего описания, такой специализированный язык может использоваться, наряду с HTML, для описания самых различных документов.
34
Глава 1
• Создавать многозвенные распределенные приложения, основанные на различных технологиях. ш Создавать приложения, которые управляют другими приложениями, в частности, такими программами Microsoft Office, как Word, Excel и др. • Создавать кросс-платформенные приложения, которые можно компилировать и эксплуатировать как в Windows, так и в системе Linux. • Создавать приложения различных классов для работы в Интернет. • Создавать профессиональные программы установки для приложений Windows, учитывающие всю специфику и все требования Windows. • и многое, многое другое, включая создание отчетов, справочных систем, библиотек DLL, компонентов ActiveX и т.п. Delphi — чрезвычайно быстро развивающаяся система, так как ее создатели постоянно отслеживают все новое в информационных технологиях. Первая версия — Delphi 1.0 была выпущена в феврале 1995 г. А затем новые версии выпускались практически ежегодно: 1996 г. — Delphi 2.0, 1997 г. — Delphi 3.0, 1998 г. Delphi 4.0, 1999 г. — Delphi 5.0, 2001 г. — Delphi 6.0, 2002 г. — Delphi 7. О Delphi мы будем говорить на протяжении всей книги. А в данном разделе мне хотелось бы коротко остановится на других программных продуктах фирмы Borland, чтобы можно было оценить общую стратегию развития и место, которое занимает в ней Delphi. Прежде всего, надо отметить родного брата Delphi C++Builder. Это тоже система визуального объектно-ориентированного программирования, внешне являющаяся копией Delphi, но использующая не язык Object Pascal, а язык C++. Новые версии C++Builder выходят параллельно с версиями Delphi, но сдвинутые во времени примерно на полгода. Поэтому, например, версия C++Builder 6 мощнее версии Delphi 6, но слабее, чем Delphi 7. Для большинства применений возможности эквивалентных версий Delphi и C++Builder примерно одинаковы. Это не удивительно, поскольку язык Object Pascal (или язык Delphi, как его начали называть его создатели, начиная с Delphi 7) в настоящее время очень близок (конечно, если не учитывать синтаксис) к C++. Так что для большинства задач выбор C++ или Object Pascal, и, соответственно, C++Builder или Delphi — дело вкуса и привычки программиста. Сознаюсь, что я лично в большинстве случаев предпочитаю Delphi. На мой взгляд, создавать в ней не слишком сложные приложения проще и приятнее. Но все-таки язык C++ пока несколько более мощный. К тому же в C++ наработаны более обширные библиотеки функций. Так что некоторые, сравнительно сложные задачи проще решать с помощью C++Builder. Впрочем, как я уже сказал, все это дело вкуса. Например, студенты, которые изучали Pascal (не Object) в школе, а в вузе изучали язык С, предпочитают во всех случаях C++Builder. Он им более понятен. Среди традиционных продуктов Borland надо также упомянуть JBuilder систему визуального проектирования на языке Java. Об этом языке коротко было сказано в разд. 1.4. JBuilder — система, аналогичная Delphi, но использующая Java, а не Object Pascal. В последние поколения программ Borland, наряду с Delphi, входит и новая разработка фирмы — система визуального проектирования Kylix. Она предназначена для разработки приложений Интернет, настольных приложений и приложений баз данных в операционной системе Linux. Эта ОС в последнее время становится основной платформой для создания приложений Интернет и начинает конкурировать с Windows в настольных операционных системах. Внешне среда разработки Kylix выглядит так же, как среда Delphi или C++Builder. И методика работы с ней та же, что и в Delphi. Набор компонентов аналогичен библиотеке CLX в Delphi, о которой будет сказано позднее. Так что создание Kylix — подарок всем, работающим в Linux. i
Delphi и современные информационные технологии
35
Если говорить о соотношении Delphi и Kylix, то надо отметить, что Delphi тоже сейчас позволяет строить кросс-платформенные приложения, которые могут компилироваться как для Windows, так и для Linux. Для этого, начиная с Delphi 6, наряду с традиционной библиотекой компонентов VCL включена библиотека CLX (cross-platform component library). Эта библиотека эквивалентна той, которая имеется в Kylix. Так что приложения для ОС Linux теперь можно создавать и с помощью Delphi. Особое внимание в последних генерациях своих программных продуктов фирма Borland уделяет созданию средств проектирования распределенных систем уровня системы управления предприятием. Здесь, прежде всего, надо отметить сервер приложений Borland AppServer. Поскольку AppServer использует Java, он естественным образом интегрируется с JBuilder. Но нет никаких препятствий для создания клиентских и серверных приложений на Delphi, которые предназначены для последующей работы с AppServer. Таким образом, Delphi 7 органически вписывается в семейство программных продуктов Borland, отслеживающее, а во многом и формирующее новейшие тенденции информационных технологий. И из прекрасного средства создания приложений для Windows Delphi уже превратилась в инструмент создания приложений для многозвенных распределенных кросс-платформенных корпоративных информационных систем. Delphi 7 Studio надо рассматривать как этап в реализации программы, анонсированной Borland. Эта программа предусматривает полноценную поддержку .NET (см. разд. 1.6). То, что сделано в Delphi 7, — только первый шаг на этом пути. Будущий проект под названием Galileo объединит в себе языковые средства Delphi, С# и Visual Basic. Он даст возможность создания трех видов приложений — классических для Windows, кросс-платформенных для Windows и Linux, а также работающих под управлением Microsoft .NET. Так что любителям Delphi предстоит в ближайшем будущем изучать много нового.
1.8 Для опытных пользователей: что нового в Delphi 7 Studio? Итак, что нового в Delphi 7 по сравнению с предыдущими версиями? Честно говоря, создается впечатление, что Delphi 7 — это некий переходной этап к будущей Delphi .NET. Впрочем, все-таки новшеств, хотя и не очень кардинальных, в данной версии Delphi немало, и перечисление всех кх займет слишком много места. Так что о многих новациях я буду рассказывать по ходу дела на протяжении всей книги. Но основные пункты хотелось бы отметить сейчас. Правда, этот раздел (кроме, пожалуй, его заключительной части) представляет интерес для тех, кто уже работал с более ранними версиями Delphi. Для них этот раздел может служить путеводителем по новшествам с указанием, в каких разделах книги они рассмотрены подробнее. А новичкам в Delphi спокойно можно пропустить материал этого раздела, так как для них все ново и то, к чему прежним пользователям Delphi надо привыкать или переучиваться, для новичков будет естественно и просто. Технологии Web Пожалуй, наибольшее продвижение наблюдается в различных технологиях приложений Web, доступных в Delphi. Получила дальнейшее развитие технология Web Services (см. разд. 1.6 и 12.14). Эта действительно революционная технология изменяет прежнее представление о клиентах и серверах Web. Ранее клиентом служил браузер, а сервер поставлял ему документы HTML и принимал запросы. В тех-
36
Глава 1
нологии Web Services сервер экспонирует доступные функции, а клиентом служит обычное приложение, вызывающее по мере необходимости эти функции. Поддержка Web Services началась еще в Delphi 6, но в новой версии Delphi возможности Web Services значительно расширились. Нововведением явилось включение в Delphi технологии IntraWeb (см. разд. 12.12), разработанной фирмой AtoZed Software. С помощью этой технологии можно создавать как самостоятельные приложения, так и страницы, предназначенные для технологий Web Broker и WebSnap. Особенность IntraWeb заключается в том, что она позволяет проектировать приложения серверов Web так же, как проектируется обычное приложение Delphi. Это, конечно, существенно облегчает разработку, так как позволяет использовать все прелести визуального проектирования и быстро создавать различные сложные формы, используя богатую библиотеку компонентов. А затем сервер IntraWeb автоматически переводит во время выполнения соответствующие программные элементы в скрипты и HTML. Существенно расширилось семейство компонентов Indy, обеспечивающее работу с различными сетевыми протоколами. Теперь компоненты этого семейства совершенно вытеснили компоненты аналогичного назначения, размещавшиеся ранее на страницах Internet и FastNet. Библиотеки компонентов Как всегда, с появлением новой версии Delphi существенно изменилась палитра компонентов. В библиотеке CLX появилась страница System, содержащая ряд компонентов, обеспечивающих работу с файлами и каталогами. Расширены возможности компонентов TOpenDialog и TSaveDialog этой библиотеки. Семейство компонентов Indy расширилось еще на две страницы: Indy Intercepts. и Indy I/O Handlers. В то же время исчезла страница FastNet, содержащая ранее иные компоненты аналогичного назначения. Borland счел также устаревшими компоненты TServerSocket и TClientSocket. Они теперь отсутствуют в палитре компонентов. Если пользователи все-таки хотят использовать эти компоненты в прежних проектов, они могут сделать это, установив пакет времени проектирования dclsockets70.bpl. В связи с появлением в Delphi технологии IntraWeb появились страницы соответствующих компонентов: IW Standard, IW Data, IW Client Side, IW Control. Появилась в палитре страница Rave, содержащая компоненты новой системы подготовки отчетов Rave. Соответственно, исчезла существовавшая ранее страница QReport, содержавшая компоненты использовавшейся ранее системы подготовки отчетов. Впрочем, модули компонентов этой исчезнувшей страницы пока еще доступны программно. На странице Additional появились компоненты TXPColorMap, TStandardColorМар и TTwilightColorMap, управляющие цветами меню и инструментальных панелей. На странице Dialogs добавлен компонент TPageSetupDialog, вызывающий диалог задания свойств страницы. На странице Win32 появился компонент TXPManifest, обеспечивающий построение приложений в стиле Windows XP, т.е. с поддержкой настроек тем. Из палитры компонентов удалены TSQLClientDataSet и TBDEClientDataSet, которые признаны устаревшими. Вместо них для простых приложений с двумя потоками Borland рекомендует использовать новый компонент TSimpleDataSet со страницы dbExpress. Впрочем, TSQLClientDataSet и TBDEClientDataSet пока еще доступны программно: их модули находятся в каталоге ...\Delphi7\Demos. Появился ряд новых стандартных действий, доступных из редактора компонента ActionList: TClientData Set Apply, TCHentDataSetUndo, TClientDataSetRevert, ряд действий, связанных с компонентом RichEdit. Из изменений, сделанных в ранее существовавших компонентах и классах, обратите внимание на следующее. В формах (класс TCustomForm) введены свойства
Delphi и современные информационные технологии
37
ScreenSnap и SnapBuffer, управляющие захватом окна вблизи края экрана. В компоненте ComboBoxEx (класс TCustomComboBoxEx) появилось свойство AutoCompleteOptions, обеспечивающее пользователю ряд очень удобных возможностей при работе со списком. Есть и еще ряд более мелких дополнений в некоторых компонентах и классах. Доступ к данным В этом отношении ничего принципиально нового в Delphi 7 не появилось. Продолжается начатый в Delphi 6 процесс оттеснения родной для Borland BDE на задний план. А на первый план выдвигаются клиентские наборы данных, удаленные модули данных, dbExpress и ADO. На страницу библиотеки dbExpress добавлен компонент SimpleDataSet, вытеснивший компонент SQLClientDataSet. Добавлены новые драйверы dbExpress для Informix SE, Oracle 9i, DB2 7.2, InterBase 6.5, MySQL 3.23.49, MSSQL 2000. Добавлены новые стандартные действия TClientDataSetApply, TClientDataSetUndo, TClientDataSetRevert. В DataSnap внесены некоторые изменения, связанные с соединениями SOAP, и устранена поддержка соединений CORBA. Вот, пожалуй, и все, что изменилось в доступе к данным. ModelMaker и Bold for Delphi Включение в название новой версии Delphi слова Studio требует обеспечения всего цикла разработки крупных проектов. Очевидно, поэтому в Delphi 7 включены новые инструменты моделирования. Прежде всего, речь идет о ModelMaker (см. разд. 8.4), использующем последние стандарты языка проектирования UML (Unified Modelling Language). Этот инструмент полностью написан на Delphi и совместим с популярными средствами проектирования от компании Rational. ModelMaker позволяет создавать модель и обеспечивает двунаправленную связь между этой моделью и исходным кодом проекта. Таким образом, создав с помощью ModelMaker модели различных классов и их взаимодействия, можно легко получить исходный текст Delphi, реализующий данную модель. И наоборот, существующие коды Delphi легко преобразуются в модели ModelMaker. ModelMaker позволяет также проводить удобное и наглядное документирование проекта. Другой вошедший в состав Delphi 7 Studio (версии Architect) инструмент моделирования — Bold for Delphi — работает уже на более высоком уровне и позволяет реализовать архитектуру MDA (Model Driven Architecture), созданную разработчиками CORBA. Данная архитектура позволяет разработчикам не просто моделировать и генерировать те или иные классы и интерфейсы, а оперировать целыми бизнес-объектами и их поведениями. При этом интеграция модели и приложения настолько тесна, что при появлении в модели новых связей или объектов изменение в поведении системы достигается всего лишь перекомпиляцией, без изменений в исходном коде. В разд. 8.4 рассмотрено использование ModelMaker для создания и редактирования классов. Иные возможности ModelMaker и инструментарий Bold for Delphi представляют интерес, как мне кажется, только для небольшой части читателей, связанных с разработкой крупных проектов. Исходя из этого, в данной книге эти вопросы не рассматриваются. К тому же, Bold for Delphi доступен только в версии Architect, которая мало у кого имеется. Rave Reports В Delphi 7 появился новый генератор отчетов Rave Reports, разработанный компанией Nevrona. Ранее использовавшийся Quick Report по прежнему доступен в Delphi 7 на уровне модулей, но в палитре компонентов установлен именно Rave Reports. Rave Reports поддерживает как VCL, так и CLX. Создавать формы отчетов можно в достаточно удобном визуальном редакторе. Rave Reports может рабо-
38
Глава 1
тать с БД через ADO, BDE и dbExpress. Это делает его совместимым практически со всеми популярными серверами БД. В целом Rave Reports, работа с которым рассмотрена в разд. 5.6.3 и 11.2, более удобен и предоставляет больше возможностей при разработке отчетов, чем его предшественник Quick Report. Поддержка Windows XP Существенной чертой Delphi 7 является поддержка Windows XP. Приложение, использующее VCL, может теперь работать или с прежними компонентами Windows, или с новыми, характерными для Windows XP. Это осуществляется поддержкой файлов manifest, обеспечивающих учет тем Windows XP. Имеются также новые компоненты, управляющие цветами меню и инструментальных панелей в стиле Windows XP. Язык Object Pascal (язык Delphi) Никаких особых нововведений в языке Object Pascal, который теперь рекомендуется называть языком Delphi, не состоялось. Связано это, очевидно, с тем, что Delphi 7 является как бы промежуточным шагом на пути к Delphi .NET, в которой будут, конечно, кардинальные изменения в языке. Так что вводить что-то существенно новое в уходящую в прошлое редакцию языка разработчики не стали. В модуле StrUtils изменены функции LeftBStr, RightBStr, MidBStr и добавлены функции AnsiLeftStr, AnsiRightStr, AnsiMidStr. Цель этих изменений — поддержка многобайтных символов. Введена также новая функция поиска PosEx. В модуль SysUtils введен ряд новых функций, поддерживающих локализацию преставления чисел, дат, времени, монетарных значений. Эти функции черпают информацию из записи типа TFormatSettings, инициализируемой функцией GetLocaleFormatSettings. Ряд новых функций добавлен также в модули VarCmplx и Variants. Вот, собственно, и все новости в Object Pascal. Интегрированная Среда Разработки Интегрированная Среда Разработки (ИСР) Delphi изменилась не очень существенно. Но изменения достаточно приятные (большинство из них рассмотрено в гл. 2). Например, Редактор Кода обеспечивает теперь выделение синтаксических конструкций не только языка Object Pascal, но и HTML, XML, C++, и даже нового языка программирования С#. Если вы загружаете в Редактор Кода соответствующий файл, все выделение осуществляется автоматически. Введена также функция подсказок по HTML и функция автоматического завершения тегов. Когда вы напишете какой-то тег, Редактор Кода немедленно добавит соответствующий завершающий тег. Вообще, такой инструмент, как Знаток Кода (Code Insight) заметно ускорился и приобрел ряд новых функций. При работе с файлами HTML в Редакторе Кода появляются закладки, обеспечивающие предварительный просмотр редактируемой страницы Web и отображающие код, сгенерированный скриптами. Определенные усовершенствования появились также в окне наблюдения Watches (разд. 2.8.3). Немало нового добавлено в окна опций настройки среды (разд. 2.9). Появилась возможность дополнять и изменять саму Среду Разработки Delphi, добавляя новые разделы меню, новые кнопки инструментальных панелей, разрабатывая новые варианты Мастеров, создающих формы и проекты, новые редакторы. Все это позволяет делать Open Tools API — собрание около 100 интерфейсов, взаимодействующих с ИСР. Конечно, использование Open Tools API требует соответствующего программирования и оформления в виде пакетов времени проектирования или в виде DLL. В рамках данной книги подобными изысками мы зани-
Delphi и современные информационные технологии
39
маться не будем. Но желающие могут изучить эти возможности по встроенной справке Delphi. Изменился состав замечаний, выдаваемых компилятором. Связано это в значительной степени с подготовкой к связи с .NET и с разработкой кросс-платформенных приложений. Управление отображением сообщений осуществляется появлением новой страницы Compiler Messages в окне опций проекта. Появилась также новая команда View | Additional Message Info, обеспечивающая возможность загрузки из Интернет новых сообщений компилятора. Ограничимся пока приведенным кратким перечнем нового в Delphi 7. В заключение этого раздела остановимся на краткой характеристике предшествующих версий Delphi. Дело в том, что при выборе версии, с которой вы хотите работать, не последнее место занимают и требования к ресурсам компьютера, которые, конечно, нарастают из года в год. Установка новых версий Delphi на компьютере требует все большего дискового пространства. Существенно расширенные возможности помощи пользователю при написании кодов заметно замедляют работу программы даже на достаточно мощных компьютерах (впрочем, эти возможности можно отключить при настройке, как будет показано в дальнейшем). Размеры результирующих выполняемых модулей создаваемых вами программ тоже увеличиваются от версии к версии, хотя и не очень быстрыми темпами. Таким образом, при ограниченных вычислительных ресурсах имеет смысл подумать, нужна ли вам именно самая последняя версия Delphi. Если вам не требуются перечисленные выше возможности Delphi 7, то можно использовать более ранние версии 6, 5 или даже 4. Хотя, конечно, с Delphi 7 работать приятнее и разработка идет намного эффективнее. Исходя из сказанного, в данной книге рассматривается не только Delphi 7, но и версии 6 и 5, хотя, конечно, основной упор делается на новую версию. Следует еще упомянуть, что все версии, начиная с Delphi 2, предназначены для 32-разрядных версий Windows, т.е. они не годятся для приложений, которые должны работать на любых Windows, включая Windows 3.x. Так что, если вам требуется приложение, предназначенное для работы на любых платформах Windows, надо использовать Delphi 1. Хотя, конечно, мне жаль тех, кому это придется делать. Вроде, не так давно была создана Delphi 1, но фирма Borland идет вперед такими быстрыми темпами, что эта первая версия, произведшая в свое время фурор, выглядит нынче как нечто совершенно допотопное. В заключение несколько слов о вариантах Delphi. Большинство версий до Delphi 6 выпускалось в вариантах: Standard — стандартном, Professional — профессиональном, Client/Server — клиент/сервер, Enterprise — разработка баз данных предметных областей. В Delphi 6 были 4 варианта: Standard, Professional, Enterprise и Personal. А в Delphi 7 имеются варианты Architect, Enterprise, Professional, Personal. Различаются варианты в основном разным уровнем доступа к системам управления базами данных и разным набором инструментария. Вариант Architect наиболее мощный, включающий, помимо всего прочего, Bold for Delphi — моделирование на базе Model Driven Architecture. Вариант Enterprise включает в себя МоdelMaker — более скромный, но тоже мощный инструмент технологии CASE. Он обеспечивает построение .визуальной модели классов и их взаимодействия. В варианте Professional отсутствует ModelMaker, но остальные основные возможности более мощных вариантов в них присутствуют. Наконец, вариант Personal лишен основных возможностей, связанных с базами данных. Так что этот вариант имеет смысл, только если не планируется работа с данными, что в наше время редкость. Впрочем, библиотеки компонентов общего назначения в различных вариантах практически одинаковы. Так что если ваши задачи не связаны с базами данных, вы вполне можете обойтись не самыми верхними (и соответственно не самыми дорогими) вариантами.
40
Глава 1
1.9 Язык объектно-ориентированного проектирования Object Pascal 1.9.1 Введение Возможности объектно-ориентированного проектирования в Delphi базируются на свойствах языка Object Pascal. Детальное изучение Object Pascal выходит за рамки данной книги. Впрочем, все необходимые вам для чтения этой книги сведения об Object Pascal вы можете почерпнуть в справочной части книги в гл. 13. Кроме того, отдельные аспекты языка не раз будут обсуждаться в различных главах книги. В целом вы получите достаточно сведений для написания большинства приложений, с которыми вас столкнет жизнь. Ну а желающим глубоко изучить Object Pascal и научиться профессионально использовать все его возможности придется изучить его по специальной литературе. Впрочем, можно рекомендовать для этих целей книгу [1], содержащую полное изложение языка и большинства функций его библиотек, книгу [3], содержащую несколько сокращенное изложение, но зато намного меньшую и дешевую, а также источник [4]. Если вы совсем не знакомы ни с одной версией семейства языков Паскаль, то можно рекомендовать вам прямо сейчас, прежде чем читать эту книгу далее, посмотреть разд. 13.1, в котором описан синтаксис этого языка, и бегло просмотреть разделы, в которых описано объявление констант, переменных, функций и процедур. Впредь обращайтесь к гл. 13, когда что-то в изложении того или иного вопроса покажется вам неясным. Я надеюсь, что это будет происходить редко и гл. 13 пригодится вам, в основном, только для углубленного изучения каких-то тем. В данной главе мы ограничим знакомство с Object Pascal только вопросами общей организации программы. Программа, которую строит Delphi в процессе проектирования вами приложения, основана на модульном принципе. Сама головная программа получается предельно простой и короткой. Она состоит из объявления списка используемых модулей и нескольких операторов, которые создают объекты тех форм, которые вы задумали, и запускают выполнение приложения. Принцип модульности очень важен для создания надежных и относительно легко модифицируемых и сопровождаемых приложений. Четкое соблюдение принципов модульности в сочетании с принципом скрытия информации позволяет внутри любого модуля проводить какие-то модификации, не затрагивая при этом остальных модулей и головную программу. Все объекты компонентов размещаются в объектах — формах. Для каждой формы, которую вы проектируете в своем приложении, Delphi создает отдельный модуль. Именно в модулях и осуществляется программирование задачи. В обработчиках событий объектов — форм и компонентов, вы помещаете все свои алгоритмы. В основном они сводятся к обработке информации, содержащейся в свойствах одних объектов, и задании по результатам обработки свойств других объектов. При этом вы постоянно обращаетесь к методам различных объектов. Вопросами доступа к свойствам и методам объектов мы и займемся в дальнейших разделах данной главы.
1.9.2 Структура файла головной программы приложения Delphi В процессе проектирования вами приложения Delphi автоматически создает код головной программы и отдельных модулей. В модули вы вводите свой код, создавая обработчики различных событий. Но головную программу, как правило, вы не трогаете и даже не видите ее текст. Только в исключительных случаях вам надо что-то изменять в тексте головной программы, сгенерированном Delphi. Тем не менее, хотя бы ради этих исключительных случаев, надо все-таки представлять вид головной программы и понимать, что означают ее операторы.
Delphi и современные информационные технологии
41
Типичная головная программа приложения имеет вид: program P r o j e c t l ; uses Forms, 1 Unitl in 'Unitl.pas IForml), Unit2 in 'Unit2.pas' {Form2}; {$R *.RES) {Здесь вы можете поместить описания каких-то констант, переменных, функций, процедур. Все это будет доступно только в пределах данного файла} begin Application.Initialize; Application.CreateForm(TForml, Forml); Application.CreateForm(TForm2, Form2); Application.Run; end.
Начинается программа с ключевого слова program, после которого пишется имя программы. Оно совпадает с именем файла, в которым вы сохранили свой проект. Это же имя присваивается выполняемому файлу приложения. По умолчанию это имя Projectl. Хороший стиль программирования Всегда сохраняйте проект под каким-то осмысленным именем, изменяя тем самым имя проекта, заданное Delphi по умолчанию. Иначе очень скоро вы запутаетесь в бесконечных программах Projectl, лежащих в различных ваших каталогах.
После заголовка в тексте программы располагается предложение uses. В этом предложении перечисляются модули, загружаемые программой. Первый модуль Forms — системный. А следующие — модули разработанных вами форм. Данный пример подразумевает, что вы создали в проекте две формы с именами Forml и Form2 в модулях с именами Unitl и Unit2. Помещенные в фигурные скобки названия форм — это комментарии. Следующая строка текста — {$R *.RES} представляет собой директиву компилятора, смысл которой вы можете посмотреть в справочной части книги. Затем после ключевого слова begin и до последнего завершающего программу оператора end с точкой (end.) записано тело программы. Первый оператор в теле программы инициализирует приложение, два следующих — создают объекты форм Forml и Form2, последний — начинает выполнение приложения. Если вам надо ввести какой-то свой текст в головную программу, вы можете сделать это, введя описания необходимых констант, переменных, функций и процедур в место программы, отмеченное соответствующим комментарием в приведенном выше тексте. Кроме того, вы можете добавить или изменить операторы в теле программы. Например, вам может потребоваться при запуске приложения на выполнение провести какие-то настройки (например, настроить формы на тот или иной язык — русский или английский). Или сделать какой-то запрос пользователю, и в зависимости от ответа создавать или не создавать те или иные формы. Пусть, например, вы хотите, чтобы вторая форма вашего приложения Form2 создавалась только в случае, если при запуске приложения через командную строку в него передана опция Y. В этом случае вы можете заменить приведенный выше оператор Application.CreateForm(TForm2, Form2);
т 42
Глава 1
на оператор if(ParamStr(1) = ' Y ' ) then Application.CreateForm(TForm2, Form2); Этот оператор анализирует функцией ParamStr первый параметр командной строки. Если ваше приложение Projectl будет запускаться командой "Projectl Y", то форма Form2 будет создаваться. В остальных случаях этой формы не будет. Вы можете ввести в головной файл и другие операторы, функции, процедуры. Все это можно сделать, но это будет плохой стиль программирования, поскольку он противоречит принципу модульности. Выше уже говорилось о важности соблюдения этого принципа. Все необходимые вам в начале выполнения процедуры и функции настройки помещайте в отдельный модуль без формы. Хороший стиль программирования -~—«—«—.«, Не перегружайте головную программу приложения никаким дополнительным кодом. Все необходимые настройки, которые должны осуществляться в начале выполнения приложения, помещайте в отдельный модуль. Такой модуль, не связанный с какой-то формой, можно включить в приложение, выполнив команду File | New и щелкнув на пиктограмме Unit.
Предупреждение Учтите, что все определенные вами в головном файле приложения константы, переменные, процедуры, функции, типы будут доступны только в пределах этого головного файла и не доступны в модулях.
1.9.3 Структура модуля Рассмотрим теперь, как выглядят тексты модулей. Ниже приведен текст модуля с пустой формой. Подробные комментарии в этом тексте поясняют, куда и что в этот код вы можете добавлять. unit Unitl; interface // Открытый интерфейс модуля (Список подключаемых модулей) uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; {Объявление класса формы} type TForml = class(TForm) private // Закрытый раздел класса f Private declarations } {Сюда могут помещаться объявления переменных, функций и процедур, включаемых в класс формы, но не доступных для других модулей) public // Открытый раздел класса { Public declarations } {Сюда могут помещаться объявления переменных, функций и процедур, включаемых в класс формы и доступных для других модулей)
end; var Forml: TForml; {Сюда могут помещаться объявления типов, констант, переменных, функций и процедур, к которым будет доступ из других модулей, но которые не включаются в класс формы)
~~
~~~
Delphi и современные информационные технологии implementation // Реализация модуля
($R * .DFM} (Сюда могут помещаться предложения uses, объявления типов, констант, переменных, к которым не будет доступа из других модулей. Тут же должны быть реализации всех объявленных в разделе interface функций и процедур, а также могут быть реализации любых дополнительных, не объявленных ранее функций и процедур. )
end.
Модуль начинается с ключевого слова unit, после которого пишется имя модуля. Оно совпадает с именем файла, в которым вы сохранили свой модуль. По умолчанию для первого модуля имя равно Unitl, для второго Unit2 — и т.д. Хороший стиль программирования Если в вашем приложении несколько модулей, сохраняйте их файлы под какими-то осмысленными именами, изменяя тем самым имена модулей, заданные Delphi по умолчанию. Вам проще будет работать с осмысленными именами, а не с именами Unitl, Unit2, Unit3, которые ни о чем не говорят.
Текст модуля состоит из двух основных разделов: interface — открытый интерфейс модуля, и implementation —реализация модуля. Все, что помещается непосредственно в раздел interface (типы, переменные, константы, функции, процедуры), может быть использовано другими модулями программы. Все, что помещается в раздел implementation — внутреннее дело модуля. Внешние модули не могут видеть типы, переменные, константы, функции и процедуры, размещенные в разделе реализации. В разделе interface после предложения uses, содержащего список подключаемых модулей, вы можете видеть заготовку объявления класса вашей формы, подготовленную Delphi. Имя класса вашей формы -- TForml. Класс содержит два раздела: private — закрытый раздел класса, и public — открытый раздел класса. То, что вы или Delphi объявите в разделе public, будет доступно для других классов и модулей. То, что объявлено в разделе private, доступно'только в пределах данного модуля. После завершения объявления класса формы вы можете видеть строки var Forml: TForml;
Это объявляется переменная Forml класса TForml, т.е. объявляется ваша форма как объекта класса TForml. Затем следует пока пустой раздел реализации implementation, в котором содержится только директива компилятора, смысл которой вы можете найти в справочной части книги. Вообще говоря, для работы вам этот смысл знать не обязательно. Следите только за тем, чтобы случайно не стереть эту директиву. В модуль вы можете ввести еще два раздела, кроме interface и implementation: разделы initialization и finalization. О них рассказано в разд. 13.3.3.
1.9.4 Области видимости и доступ к объектам, переменным и функциям модуля 1.9.4.1 Структура модуля, содержащего объекты и процедуры Теперь посмотрим, как можно вводить в модуль переменные, функции и осуществлять к ним доступ. Ниже приведен текст кода модуля, в котором на форме размещены два компонента: кнопка Buttonl типа TButton и метка Labell типа TLabel. Кроме того, в модуле введен обработчик события, связанного со щелчком
44
Глава 1
пользователя на кнопке, и в разных местах модуля введены переменные и функции, чтобы можно было видеть, как получить к ним доступ. unit Unitl; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForml = class(TForm) Labell: TLabel; Buttonl: TButton; procedure ButtonlClick(Sender: TObject); private {Процедура Fl доступна только в данном модуле) procedure Fl (Ch:char) ; public {Переменная Chi и процедура F2 доступны для объектов любых классов и для других модулей, но со ссылкой на объект} Chi:char; procedure F2(Ch:char); end; var Forml: TForml; {Переменная Ch2 и процедура F3 доступны для объектов любых классов и для других модулей} Ch2:char; procedure F3(Ch:char);
implementation {$R *.DFM) uses unit2; (Переменная Ch3 и процедура F4 доступны только внутри данного модуля} var Ch3:char; procedure F4(Ch:char); begin Forml .Labell.Caption:=Forml .Labell .Caption+Ch+Forml .Chi; end/procedure TForml.Fl(Ch:char); begin Labell.Caption:=Labell.Caption+Ch+Chl; end; procedure TForml.F2(Ch:char); begin Labell.Caption:=Labell.Caption+Ch+Chl; end; procedure F3(Ch:char); begin Forml.Labell.Caption:=Forml.Labell.Caption+Ch+Forml.Chi; end; procedure TForml.ButtonlClick(Sender: TObject); • (Переменная Ch4 и процедура F5 доступны только внутри данной процедуры) var Ch4:char; procedure F5(Ch:char);
Delphi и современные информационные технологии
45
begin Labell.Caption:=Labell.Caption+Ch+Chl; end; begin Chi:»'-1; Ch2:='A'; Ch3:='B'; Ch4:='C'; Labell.Caption:=''; Fl(Chi); F2 (Ch2) F3 (Ch3) F4(Ch4) F5('D') Labell.Font.Color:=clRed; end; end.
Структура кода та же, что и в приведенном ранее примере с пустой формой. Но кое-что в текст добавлено. В описании класса вы можете видеть строки Labell: TLabel; Buttonl: TButton; procedure ButtonlClick(Sender: TObject);
Первые две из них Delphi включила автоматически, как только разработчик приложения разместил на форме метку и кнопку. Эти строки говорят, что в форме имеются объекты соответствующих типов. Третью из приведенных строк Delphi добавила в код в тот момент, когда разработчик приложения начал писать обработку события OnClick — щелчка на кнопке. В этот момент Delphi не только занесла в описание класса это объявление процедуры, но и занесла в раздел implementation заголовок соответствующей процедуры: procedure TForml.ButtonlClick(Sender: TObject);
после которого разработчик может писать свой текст. Помимо этой процедуры в код занесено в разные места модуля еще несколько одинаковых процедур: F1 - - F5, и несколько переменных символьного типа: СМ — Ch4. Сейчас мы не будем разбираться подробно в самих процедурах. Нам важно понять, как из процедур обращаться к различным объектам и переменным. Но краткое описание того, что делают эти процедуры, надо дать. У компонента — метки типа TLabel имеется свойство Caption — надпись на метке. Каждая из процедур Fl — F5 берет значение этой надписи, прибавляет к ней символ, переданный в нее как параметр Ch, прибавляет далее символ, хранящийся в переменной СЫ, и возвращает надпись с этими добавлениями обратно в метку. Обработчик щелчка на кнопке -- процедура TForml.ButtonlClick, задает символьным переменным Chi — Ch4 значения символов "•-", "А", "В" и "С", затем очищает свойство Caption метки Labell, занося в него пустую строку, а затем поочередно обращается к процедурам Fl — F5, передавая в них в качестве параметров различные символы. В заключение надпись метки окрашивается в красный цвет. Для этого используется свойство Font — шрифт объекта Labell. Это свойство само является объектом, имеющим свойство Color — цвет. Значение этого свойства изменяет последний оператор процедуры TForml.ButtonlClick. 1.9.4.2 Доступ к свойствам и методам объектов Рассмотрим теперь, как получить из программы доступ к свойствам и методам объектов. Если вас интересует какое-то свойство объекта, то ссылка на него осуществляется в следующем формате: .
46
_
Глава
1
После имени объекта пишется без пробела символ точки, а затем так же без пробела пишется имя свойства. Например, ссылка на свойство Caption метки Labell осуществляется записью Labell. Caption (см. в тексте примера процедуры Fl, F2, TForml.ButtonlClick). Иногда свойство объекта является в свою очередь объектом. Тогда в обращении к этому свойству указывается через точки вся цепочка предшествующих объектов. Например, метки имеют свойство Font — шрифт, которое в свою очередь является объектом. У этого объекта имеется множество свойств, в частности, свойство Color — цвет шрифта. Чтобы сослаться на цвет шрифта метки Labell, надо написать Labell.Font.Color (см. в тексте примера процедуру TForml.ButtonlClick). Это означает: свойство Color объекта Font, принадлежащего объекту Labell. Аналогичная нотация с точкой используется и для доступа к методам объекта. Например, для метки, как и для большинства других объектов, определен метод Free, который уничтожает метку и освобождает занимаемую ею память. Если вы в какой-то момент решили уничтожить метку Labell в своем приложении, то вы можете написать оператор Labell .Free;
Только имейте в виду, что если по ошибке Далее в программе будет выполняться оператор, обращающийся к свойствам или методам уничтоженного объекта, то это вызовет прерывание программы, поскольку данного объекта уже не будет в памяти. Обращение к объекту, удаленному из памяти методами Destroy или Free вызывает ошибку и аварийное завершение выполнения программы.
1.9.4.3 Различие переменных, функций и процедур, включенных и не включенных в описание класса Теперь посмотрим, чем различаются константы, переменные, функции и процедуры, включенные и не включенные в описание класса. В приведенном в разд. 1.9.4.1 примере переменная Chi и процедуры F1 и F2 включены в описание класса, а переменные Ch2, Ch3 и процедуры F3 и F4 объявлены вне класса. В чем будет проявляться различие в их использовании? Если в приложении создается только один объект данного класса (в нашем примере — только один объект формы класса TForml), то различие в основном чисто внешнее. Для процедур, объявленных в классе, в их описании в разделе implementation к имени процедуры должна добавляться ссылка на класс. В нашем примере имена процедур Fl, F2 и ButtonlClick в описании этих процедур в разделе implementation заменяются на TForml.Fl, TForml.F2 и TForml.ButtonlClick. Для процедур F3 и F4, объявленных вне класса, такой замены не требуется. Необходимость добавления к именам процедур, описанных в классе, ссылок на класс объясняется просто. Вы можете вне класса описать другую свою процедуру с тем же именем (например, F1), что и у процедуры класса. И тогда из процедур, не описанных в классе, вы сможете ссылаться на обе эти процедуры F1, только на одну из них непосредственно — по имени F1, а на другую через объект класса — Forml.Fl. Благодаря этому, при описании своих процедур вне класса вы можете даже не знать имен всех процедур описанных в классе (может быть этот класс описан в другом модуле, текст которого вы не видели). Никакой путаницы при этом не возникнет. Обращение к переменным и процедурам, описанным внутри и вне класса, из процедур, описанных вне класса, различается. К переменным и процедурам, описанным вне класса, обращение происходит просто по их именам, а к переменным и процедурам, описанным в классе, через имя объекта класса. Поэтому в нашем примере в процедурах F3 и F4 обращение к переменной СЫ имеет вид Forml.Chl. По той же при-
X Delphi и современные информационные технологии
47
чине и обращение к свойству Caption объекта Labell в этих процедурах имеет вид Forml.Labell.Caption. Только через ссылку на объект Forml внешние по отношению к классу процедуры могут получить доступ ко всему, объявленному в классе, Все эти ссылки на объект не требуются в процедурах, объявленных в классе. Поэтому в процедурах TForml.Fl, TForml.F2 и TForml.ButtonlClick ссылки на переменную СЫ и на объект Labell не содержат дополнительных ссылок на объект формы. Если в приложении создается несколько объектов одного класса, например, несколько форм класса TForml (как вы увидите впоследствии, это часто делается в приложениях с интерфейсом множества документов MDI), то проявляются более принципиальные различия между переменными, описанными внутри и вне класса. Переменные вне класса (в нашем примере Ch2 и СЬЗ) так и остаются в одном экземпляре. А переменные, описанные в классе (в нашем примере СЫ), тиражируются столько раз, сколько объектов данного класса создано. Т.е. в каждом объекте класса TForml будет своя переменная СЫ, и все они друг с другом никак не будут связаны. Таким образом, в переменную, описанную внутри класса, можно заносить какую-то информацию, индивидуальную для каждого объекта данного класса. А переменная, описанная в модуле вне описания класса, может хранить только одно значение, 1.9.4.4 Области видимости переменных и функций Теперь остановимся на вопросе об областях видимости элементов программы — констант, переменных, функций и процедур, т.е. о связи места их объявления в программе и места их использования. Частично этот вопрос мы уже затрагивали в предыдущем разделе, не упоминая о самом понятии область видимости. Видимость отдельных элементов модуля, описанного в разд. 1.9.4.1, поясняется подробными комментариями в тексте этого модуля. Поэтому ограничимся только некоторым подведением итогов. • Элементы, объявленные в разделе interface модуля вне описания типа, видимы и доступны внутри данного модуля и из внешних модулей. В рассмотренном примере это относится к переменной Ch2 и процедуре F3. • Элементы, объявленные в разделе implementation модуля, видимы и доступны внутри данного модуля, но не доступны из внешних модулей. В рассмотренном примере это относится к переменной СЬЗ и процедуре F4. • Элементы, объявленные в классе в разделе private, видимы и доступны только внутри данного модуля. При этом из процедур, объявленных внутри класса, к ним можно обращаться непосредственно по имени, а из других процедур только со ссылкой на объект данного класса. В рассмотренном примере это относится к процедуре F1. Если в модуле описано несколько классов, то объекты этих классов взаимно видят элементы, описанные в их разделах private. • Элементы, объявленные в классе в разделе public, видимы и доступны для объектов любых классов и для других модулей. При этом из объектов того же класса к ним можно обращаться непосредственно по имени, а из других объектов и процедур — только со ссылкой на объект данного класса. В рассмотренном примере это относится к переменной СЫ и процедуре F2. • В классах, помимо обсуждавшихся ранее, могут быть еще разделы protected — защищенные. Элементы, объявленные в классе в разделе protected, видимы и доступны для любых объектов внутри данного модуля, а также для объектов классов — наследников данного класса в других модулях. Объекты из других модулей, классы которых не являются наследниками данного класса, защищенных элементов не видят. • Элементы, объявленные внутри другой процедуры (в рассмотренном примере это переменная Ch4 и процедура F5, описанные внутри процедуры TForml.ButtonlClick), являются локальными, т.е. они видимы и доступны только внутри
48
Глава 1
данной процедуры или внутри процедур, вложенных в данную. При этом время жизни переменных, объявленных внутри процедуры, определяется временем выполнения данной процедуры. Так переменная Ch4 в нашем примере создается в момент вызова процедуры TForml.ButtonlClick и уничтожается при завершении работы этой процедуры. Она видима в самой процедуре TForml.ButtonlClick и ее может видеть процедура F5. Вложенная процедура F5 доступна только из процедуры TForml.ButtonlClick, в которой она описана. 1.9.4.5 Передача параметров в функции Параметры в функции и процедуры могут передаваться в основном двумя способами: по значению и по ссылке. То, что вы могли видеть в рассмотренном ранее примере — это передача параметра по значению. В этом случае в заголовке процедуры указывается имя параметра и его тип. Например: procedure T F o r m l . F 2 ( C h r c h a r ) ;
В этом случае Ch — это локальное имя формального параметра, используемое только внутри данной процедуры. При вызове этой процедуры, который может иметь вид: F2(Ch2); в памяти создается временная переменная с именем Ch и в нее копируется значение аргумента Ch2. На этом связь между Ch и Ch2 разрывается. Вы можете изменять внутри процедуры значение Ch, но это никак не отразится на значении внешней переменной Ch2, указанной в вызове функции в качестве аргумента. Такая передача параметров по значению имеет свои достоинства и недостатки. Достоинство заключается в том, что процедура не может испортить переданный в нее аргумент. Это важно для надежной работы приложения и позволяет разрабатывать процедуру, не задумываясь о том, не использованы ли где-то в программе те же имена параметров. Но у передачи параметра по значению имеется и ряд недостатков. Во-первых, процедура не может изменить значение аргумента, а иногда это очень желательно. Во-вторых, копирование значения аргумента требует дополнительных затрат времени и памяти для хранения копии. Если речь идет о какой-то переменной простого типа, это, конечно, не существенно. Но если, например, аргумент — массив из тысячи элементов, то соображения затрат времени и памяти могут стать существенными. И в-третьих, после окончания работы процедуры временная переменная, хранившая значение параметра, уничтожается. Поэтому ее нельзя использовать, например, для накопления какой-то информации. Для реализации второго способа передачи информации — по ссылке, перед именем параметра в заголовке функции должно быть указано ключевое слово var. Например: procedure TForml.F2(var Ch:char);
В этом случае не происходит копирования значения аргумента в локальную, временную переменную в процедуре. Процедура реально работает не с параметром, а со ссылкой — указателем на место хранения аргумента в памяти. И любые изменения параметра Ch, произведенные в процедуре, в действительности относятся не к этому параметру, а к тому аргументу, который передан при вызове процедуры. Таким образом, ключевое слово var позволяет возвращать информацию из процедуры в вызвавшую его внешнюю процедуру. Более подробную информацию о передаче параметров в функции и процедуры вы найдете в разд. 13.6.2. На этом мы закончим наше первое знакомство с Object Pascal. Это знакомство будет продолжаться на всем протяжении книги, а для более глубокого изучения вы всегда можете воспользоваться гл. 13 справочной части книги. Но и сейчас вы уже готовы начать работать непосредственно с Delphi и создавать свои первые приложения. Этим вы и займетесь в следующей главе.
Глава 2
Работа в Интегрированной Среде Разработки Delphi 7 2.1 Интегрированная Среда Разработки (ИСР) Delphi 2.1.1 Общий вид окна ИСР Интегрированная Среда Разработки (Integrated Development Environment — IDE, в дальнейшем мы будем использовать для нее аббревиатуру ИСР) — это среда, в которой есть все необходимое для проектирования, запуска и тестирования приложений и где все нацелено на облегчение процесса создания программ. ИСР интегрирует в себе редактор кодов, отладчик, инструментальные панели, редактор изображений, инструментарий баз данных — все, с чем приходится работать. Эта интеграция предоставляет разработчику гармоничный набор инструментов, дополняющих друг друга. Более того, как вы увидите в дальнейшем, вам предоставлена возможность расширять меню ИСР, включая в него необходимые вам дополнительные программы, в том числе и собственные. Результатом является удобная для вас среда быстрой разработки сложных прикладных программ. Запустите Delphi, выбрав пиктограмму Delphi в разделе меню Windows Пуск | Программы. Когда вы щелкнете на пиктограмме Delphi, перед вами откроется основное окно Интегрированной Среды Разработки. В Delphi 7 вид окна представлен на рис. 2.1. В общих чертах окна всех версий достаточно сходны. Рис. 2.1
Основное окно Интегрированной Среды Разработки в Delphi 7
В верхней части окна ИСР вы видите полосу главного меню. Ее состав несколько различается от версии к версии и, кроме того, зависит от варианта Delphi, с которым вы работаете. На рис. 2.1 приведен вид окна для варианта Enterprise.
50
Глава 2
Ниже полосы главного меню расположены две инструментальные панели. Левая панель (состоящая в свою очередь из нескольких панелей) содержит два ряда быстрых кнопок, дублирующих некоторые наиболее часто используемые команды меню. Правая панель содержит палитру компонентов библиотеки визуальных компонентов. Палитра компонентов содержит ряд страниц, закладки которых видны в ее верхней части. Состав палитры зависит не только от варианта Delphi, но и от того, создаете ли вы приложение Windows, или кросс-платформенное приложение. В зависимости от этого палитра компонентов отображает или состав Visual Component Library - VCL, или состав cross-platform component library — CLX. В версиях, младше Delphi 6, имеется только VCL. Правее полосы главного меню в Delphi 7 и 6 размещена еще одна небольшая инструментальная панель, содержащая выпадающий список и две быстрые кнопки. Это панель сохранения и выбора различных конфигураций окна ИСР, которые вы сами можете создавать и запоминать. В основном поле окна вы можете видеть слева два окна: сверху — Дерево Объектов (Object Tree View), под ним — Инспектор Объектов (Object Inspector). Окно Дерево Объектов будет отображать иерархическую связь визуальных и невизуальных компонентов и объектов вашего приложения. А Инспектор Объектов — это основной инструмент, с помощью которого вы в дальнейшем будете задавать свойства компонентов и обработчики событий. Правее этих окон вы можете видеть окно пустой формы, готовой для переноса на нее компонентов. -Под ним расположено окно Редактора Кодов. Обычно оно при первом взгляде на экран невидимо, так как его размер равен размеру формы и окно Редактора Кодов практически полностью перекрывается окном формы. На рис. 2.1 это окно немного сдвинуто и выглядывает из под окна формы. Рассмотрим теперь основные элементы окна ИСР.
2.1.2 Полоса главного меню и всплывающие меню Подробные сведения о всех разделах меню приведены в справочной части книги (см. разд. 15.1). В дальнейшем при обсуждении различных проектных операций мы еще будем подробно рассматривать функции многих разделов меню. А пока просто дадим краткий обзор основных разделов. Разделы меню File (файл) позволяют создать новый проект, новую форму, фрейм, модуль данных, открыть ранее созданный проект или форму, сохранить проекты или формы в файлах с заданными именами. Разделы меню Edit (правка, редактирование) позволяют выполнять обычные для приложений Windows операции обмена с буфером Clipboard, а также дают возможность выравнивать группы размещенных на форме компонентов по размерам и местоположению. Разделы меню Search (поиск) позволяют осуществлять в коде вашего приложения поиск и контекстные замены, которые свойственны большинству известных текстовых редакторов. Разделы меню View (просмотр) позволяют вызывать на экран различные окна, необходимые для проектирования. Разделы меню Project позволяют добавлять и убирать из проекта формы, задавать опции проекта, компилировать проект без его выполнения и делать много других полезных операций. Меню R u n (выполнение) дает возможность выполнять проект в нормальном или отладочном режимах, продвигаясь по шагам, останавливаясь в указанных точках кода, просматривая значения переменных и т.д. Меню Component (компонент) позволяет создавать и устанавливать новые компоненты, конфигурировать палитру компонентов, работать с пакетами packages.
Т" 1
X
Работа в Интегрированной Среде Разработки Delphi 7
51
Разделы меню Database (база данных) позволяют использовать инструментарий для работы с базами данных. Меню Tools (инструментарий) включает ряд разделов, позволяющих выполнять настройки ИСР и вызывать различные вспомогательные программы, например, вызывать Редактор Изображений (Image Editor), работать с программами, конфигурирующими базы данных и сети и т.д. Кроме того, в это меню вы можете сами включить любые разделы, вызывающие те или иные приложения, и таким образом расширить возможности главного меню Delphi, приспособив его для своих задач. Меню ModelMaker введено только начиная с Delphi 7 и имеется в вариантах Enterprise и старше. Оно обеспечивает связь с программой ModelMaker, помогающей в разработке классов (см. разд. 8.4) и сложных проектов. Меню Window (окно) имеется только начиная с Delphi 6. Разделы этого меню позволяют ориентироваться среди массы окон, обычно одновременно открытых в процессе проектирования и переключаться в нужное окно. Меню Help (справка) содержит разделы, помогающие работать со встроенной в Delphi справочной системой, в частности, настраивать ее. Мы рассмотрели меню, отображаемые в полосе главного меню. Но помимо этого в Delphi имеется система контекстных всплывающих меню, которые появляются, если пользователь поместил курсор мыши в том или ином окне или на том или ином компоненте и щелкнул правой кнопкой мыши. Большинство разделов этих контекстных меню дублируют основные разделы главного меню. Однако во всплывающих меню в ряде случаев имеются разделы, отсутствующие в главном меню. И до многих инструментов, используемых для работы с некоторыми компо•нентами, можно добраться только через всплывающие меню. Так что почаще пробуйте в процессе работы щелкать правой кнопкой мыши. Это ускорит выполнение многих проектных операций.
2.1.3 Быстрые кнопки Инструментальные панели быстрых кнопок для Delphi 7 представлены на рис. 2.2. Панель Интернет по умолчанию невидима. В Delphi 6 ее состав несколько отличается от приведенного, а в Delphi 5 она вообще отсутствует. В Delphi 4 нет и панели настройки конфигурации (рис. 2.2 б). Назначение размещенных на панелях быстрых кнопок можно узнать из ярлычков, появляющихся, если вы поместите курсор мыши над соответствующей кнопкой и на некоторое время задержите его. В табл. 2.1 приведены пиктограммы этих кнопок, соответствующие им команды меню и «горячие» клавиши, а также краткие пояснения.
IP
1 |
СЭ'
а)
б)
в)
Рис. 2.2. Инструментальные панели в Delphi 7: основные (а), панель настройки конфигурации (б) и панель Интернет (в)
52
Глава 2
Таблица 2.1. Быстрые кнопки Пиктограмма
т
ш-
Команда меню / «горячие» клавиши File New | Other File Open File Reopen
File Save As File Save (Ctrl - S)
•
File Save All
* т ш р • ЦУ
Пояснение команды Открыть проект или модуль из Депозитария Открыть файл проекта, модуля, пакета. Кнопочка со стрелкой справа от основного изображения соответствует команде Reopen, позволяющей открыть файл из списка недавно использовавшихся Сохранить файл модуля, с которым в данный момент идет работа Сохранить все (все файлы модулей и файл проекта) Открыть файл проекта
File Open Project (Ctrl-Fll) Project | Add to Project Добавить файл в проект (Shift- F l l ) Project | Remove from Project Удалить файл из проекта Help | Contents
•г^.:--
И
с а
ш И
i 1
и н
View | Units (Ctrl- F12) View | Forms (Shift - F T 2) View | Toggle Form/Unit (F12) File New | Form Run | Run (F9)
Run | Program Pause
Run | Trace Into (F7) Run | Step Over (F8) View | Desktops | Save Desktop View | Desktops | Set Debug Desktop
Вызов страницы Содержание встроенной справки Переключиться на просмотр текста файла модуля, выбираемого из списка Переключиться на просмотр формы, выбираемой из списка Переключение между формой и соответствующим ей файлом модуля Включить в проект новую форму Выполнить приложение. Кнопочка со стрелкой справа от основного изображения позволяет выбрать выполняемый файл, если вы работаете с группой приложений Пауза выполнения приложения и просмотр информации CPU. Кнопка и соответствующий раздел меню доступны только во время выполнения приложения Пошаговое выполнение программы с заходом в функции Пошаговое выполнение программы без захода в функции Сохранение текущей конфигурации окна (начиная с Delphi 5) Установка конфигурации окна при отладке (начиная с Delphi 5)
Работа в Интегрированной Среде Разработки Delphi 7
Пиктограмма
Создание приложения WebSnup для Web (начиная с Delphi 6) Вызов внешнего редактора страниц Web, который устанавливается кнопкой Edit на странице Internet окна опций, вызываемого командой Tools | Environment Options (в Delphi 6)
•»«—»»—»^»^»^»™™«__»™»т»« При разработке простых прототипов проектов удобно задавать имена файлов проектов и модулей одинаковыми, различая их только префиксом «Р» или «U» соответственно. Например, файл проекта — PEditorl, файл модуля — UEditorl. Это упрощает понимание того, какие файлы к какому варианту проекта относятся, поскольку на начальных стадиях проектирования у вас может появиться несколько альтернативных вариантов одного и того же проекта.
Итак, рекомендации по созданию нового проекта сводятся к следующему. • Создайте новый каталог для своего нового проекта. • Создайте новый проект командой File | New | Application. • Сразу сохраните проект и файл модуля командой File | Save All. В последующих сеансах работы вы можете открыть сохраненный проект командой File | Open Project. Но если вы работали с проектом недавно, то много удобнее открыть его командой File | Reopen. А еще удобнее воспользоваться стрелочкой рядом с соответствующей этой команде быстрой кнопкой (см. разд. 2.3.3). В обоих случаях открывается окно, в котором вы легко найдете несколько проектов, с которыми работали в последнее время, а также ряд файлов, с которыми вы работали. Имеется еще более удобный способ автоматически открывать при загрузке Delphi тот проект, с которым вы работали в предыдущем сеансе. Для этого надо выполнить команду Tools | Environment Options. В открывшемся многостраничном диалоговом окне надо перейти на страницу Preferences и включить индикатор Desktop группы опций автосохранения Autosave options (подробности см. в разд. 2.9.7). Тогда при каждом очередном запуске Delphi будет загружаться ваше последнее приложение предыдущего сеанса и будут открываться все окна, которые были открыты в момент предыдущего выхода из Delphi. Это очень удобно, если вы намерены продолжать работу над тем же проектом. Вы можете сохранить свой проект в Депозитарии. Впоследствии при появлении схожей задачи вы можете заимствовать его оттуда, что-то в нем изменить и таким образом сэкономить себе много времени. Все операции, связанные с сохранением проектов и форм в Депозитарии подробно рассмотрены в разд. 2.6.
2.3.3 Менеджер проектов В управлении проектом существенную помощь может оказать Менеджер Проектов — Project Manager. Он является не только и не столько инструментом управления проектом, сколько инструментом работы с группами проектов. Группа проектов — это группа родственных проектов, с которыми удобно работать параллельно. Например, это могут быть проекты клиента и сервера, обменивающихся какой-то информацией, или проект, создающий библиотеку DLL, и проект, использующий ее. Открывается Менеджер Проектов командой View | Project Manager. Если в данный момент нет открытого проекта, то в окне Менеджера Проектов будет написано
76
Глава 2
. Если же какой-то проект открыт, то вид окна Менеджера Проектов показан на рис. 2.17 а. В этом примере формируется группа с именем по умолчанию ProjectGroupl, содержащая только один открытый проект. В окне виден состав проекта в виде иерархического дерева. Двойной щелчок на вершине, связанной с текстом модуля или формой, открывает соответственно текст модуля в окне Редактора Кода или форму. Так что из окна Менеджера Проектов очень удобно осуществлять навигацию по сложным проектам с множеством модулей и форм. Рис. 2.17
а)
Окно Менеджера Проектов в Delphi 7 с одним (а) и двумя (б) проектами в группе
fPmess1.exe
New
Remove
Files
Palh
Э i|§ ProjeclGroupl В flP Pme«i1.e«e Ы 3 Ulmessl ! j B] Ulrnessl.pas S3 П8Д В g] U2messl |j) U2mess1.pas Э F"m2
D:\Progran) FilesSBorlancftDelphi?^»! F:\Delphi7\Messages F:\Delphi7\Messages FADelphi7\Messages F:\Delphi7\Messages F:\Delphi7\Messages F:\Delphi7\Messages F:\Delphi7\Messages
6) jPmessZe
"3
Files | f PG.mess и £3 Pmess1.exe 8 Щ] Ulmessl Щ] Ulmessl.pas Э ШЯ \ Й @] U2mess1 i Щ U2messl.pas nSI Foim2 И Ш Pmest2.exe R g] Ulmess2 Д] U1mess2.pas : SI F°im1
New
Remove Activate
Palh F:\Delphi7\Messages F:\Delphi7\Messages F:\Delphi7\Messages F:\Delphi7\Messages F:\Delphi7\Messages F:\Delphi7\Messages F:\Delphi7\Messages F:\Delphi7\Messages FADelphi7\Messages F:\Delphi7\Messages FADelphi7\Messases F:\Delphi7\Messages
Кнопка New позволяет включить в группу новый проект. Действие кнопки Remove (удалить) зависит от того, какая вершина выделена в окне Менеджера Проекта. Если выделена вершина проекта, то из группы удаляется проект. Если выделена вершина модуля или его текста, то удаляется данный модуль. Впрочем, модуль из проекта вы можете также удалить, если выделите интересующий вас модуль и щелкнете правой кнопкой мыши. Во всплывшем меню будут разделы Open (открыть), Remove From Project (удалить из проекта), Save (сохранить), Save As (сохранить как) и ряд других. Удаление выделенного модуля происходит также просто при нажатии клавиши Delete. Если вы выделите в окне не модуль, а проект — имя файла .ехе, и щелкнете правой кнопкой мыши, то всплывет уже другое меню, в котором будут, в частности, разделы Add (добавить новую форму или модуль), Remove File (удалить файл — появится диалоговое окно, в котором вы можете выбрать удаляемый модуль), Save (сохранить проект), Options (вызов окна опций выделенного проекта) и ряд других. Добавить в группу еще один проект можно двумя способами. Если вы хотите добавить новый проект, вы можете щелкнуть на кнопке New, или выделить в окне имя группы проектов, щелкнуть правой кнопкой мыши и выбрать из всплывшего меню раздел Add New Project (добавить новый проект). Вы по-
Работа в Интегрированной Среде Разработки Delphi 7
77
падете в уже рассмотренное нами окно Депозитария и можете выбрать в нем вид открываемого проекта, в частности, Application — новый проект с пустой формой. Если же вы хотите добавить в группу один из разработанных вами ранее проектов, то, выделив в окне имя группы проектов и щелкнув правой кнопкой мыши, надо выбрать из всплывшего меню раздел Add Existing Project (добавить существующий проект). Далее в обычном окне открытия файла вы можете указать добавляемый файл проекта .dpr. При добавлении в группу нового проекта в окне Менеджера Проектов будет отображаться уже информация о всех проектах группы (рис. 2.17 б). Вы можете сохранить файл вашей группы, если выполните команду File | Save As или если выделите в окне Менеджера Проектов имя группы, щелкнете правой кнопкой мыши и из всплывшего меню выберете команду Save Project Group As. Группа проектов сохраняется в файле с расширением .bpg. В дальнейших сеансах работы вы можете открыть этот файл той же командой File | Open Project или File | Reopen, которой вы открываете проект. Окно Менеджера Проектов — встраиваемое, так что вы можете встроить его, например, в окно Инспектора Объектов, и оно не будет занимать на экране лишнего места. Теперь посмотрим, что дает нам объединение нескольких проектов в группу. Прежде всего, вы можете параллельно работать над всеми проектами группы, открыв их модули в окне Редактора Кода. Для того чтобы открыть нужный модуль, достаточно сделать двойной щелчок на его имени в окне Менеджера Проектов. Вы можете также выполнить любой проект группы. В каждый данный момент активным является один из проектов. Имя активного проекта выделено в окне Менеджера Проектов жирным шрифтом. Например, в окне рис. 2.17 б активным является проект Pmess2. Если вы выполните команду Run | Run или нажмете F9, то именно этот проект будет компилироваться и выполняться. Вы можете сделать активным другой проект. Это можно сделать, выделив нужный проект в окне Менеджера Проектов и щелкнув на кнопке Activate. Второй способ — щелкнуть на имени проекта, который надо активизировать, правой кнопкой мыши и выбрать во всплывшем меню команду Activate. Третий способ добиться того же — выбрать имя выполняемого файла проекта в выпадающем списке вверху окна Менеджера Проектов. Наконец, четвертый способ — нажать кнопочку справа от быстрой кнопки выполнения Run в панели быстрых кнопок ИСР (см. разд. 2.3.3) и из выпавшего списка выбрать тот исполняемый файл, который вы хотите запустить. Описанные приемы позволяют, если вы работаете в Windows NT\2000\XP, одновременно выполнять и отлаживать несколько проектов одной группы. Для этого сначала надо выполнить компиляцию всех проектов командой Project | Compile All Project. После этого вы можете активизировать один файл и запустить его на выполнение. Затем, не закрывая его, вернуться в ИСР Delphi, активизировать другой проект группы и тоже запустить его на выполнение. При этом у вас окажется два выполняемых файла проекта, причем вы можете отлаживать их совместную работу (о способах отладки программ будет рассказано позднее). ' В Windows 95\98 такая параллельная отладка нескольких проектов невозможна. При необходимости выполнять одновременно несколько проектов вы можете один из них запустить на выполнение в режиме отладки из Delphi, а остальные должны запустить вне Delphi средствами Windows, например, программой «Проводник». Начиная с Delphi 5, в Менеджер Проектов введена еще одна очень удобная возможность — перетаскивать файлы мышью. Вы можете внутри группы перетащить модуль из проекта в проект. Средствами Windows (папками или программой «Проводник») вы можете перетащить аналогичным образом модуль из любого каталога в окно Менеджера Проектов. Он включится в тот проект, который в данный момент является активным. Эта особенность Менеджера Проектов — еще одна воз-
Глава 2
78
можность включать в проект модули (формы), разработанные ранее для других проектов. Вопросы включения форм в проект подробно рассмотрены в разд. 2.4. Там же описаны и возникающие при этом проблемы: перекрытие имен модулей и форм, опасность испортить прежний проект и т.п. Все это относится и к перетаскиванию модуля в окно Менеджера Проектов. Копирование модулей может осуществляться не только перетаскиванием, но и с использованием буфера обмена Clipboard. Вы можете выделить в окне Менеджера Проектов в каком-то проекте вершину модуля и выполнить команду Edit | Сору или просто нажать клавиши Ctrl-C — копирование в Clipboard. Затем вы можете выделить вершину другого проекта в той же группе или открыть другую группу проектов и выделить в ней нужный проект, после чего выполнить команду Edit | Paste или нажать клавиши Ctrl-V. Модуль скопируется в новый проект. При этом, как и при перетаскивании модулей, надо помнить о достоинствах и недостатках совместного владения формой несколькими проектами (см. разд. 2.4.2). Последовательность размещения вершин в Менеджере Проектов определяет последовательность их компиляции при выполнении команд Project | Build All Projects и Project | Compile A l l Projects. Переместить проект в начало или конец списка можно соответственно командами контекстного меню Build Sooner и Build Later. Команды Compile All from Here и Build All from Here соответственно компилируют и создают все проекты, начиная с выделенного в Менеджере Проектов и до конца списка.
2.3.4 Планирование работ — список To-Do List Список To-Do List, появившийся начиная с Delphi 5, представляет собой запись тех текущих задач, которые надо решить при разработке проекта. Эти задачи могут заноситься в список непосредственно или передаваться в него из кода ваших модулей. Ведение списка облегчает планирование работ, позволяет ранжировать задачи, отслеживать очередность и своевременность их решения. Список может быть создан для каждого проекта и хранится в файле с расширением .todo. Посмотреть список To-Do List, связанный с текущим проектом, можно командой View | To-Do List. Если вы выполните эту команду, перед вами откроется окно, вид которого представлен на рис. 2.18. Рис. 2.18
Окно списка To-Do List
ЯШШК,
1 To-Do List - Pmessl Action Item
j ! Module
П ip Главная Форма
5
ВЭ Глэонап *орма
1
М r?Ll Фоома документа
1
Иванов И.И. у
1J
полный вариант
..
полный еари !!?Я
2 F..AU1roest1.pa* Сидоров С. С. код
Q Н Главное мен»
5 items (0 hidden)
Category
Петров П.П
П^рВ
ли
jSl | Ownei
2.5.7 Фиксация компонентов После того, как вы тщательно разместили и выровняли компоненты, их местоположение полезно зафиксировать. Иначе в процессе последующей работы над проектом вы можете случайно сдвинуть тот или иной компонент, когда будете его выделять курсором, и всю работу по выравниванию придется начинать заново. Чтобы этого не произошло, выполните команду Edit | Lock Controls. Она зафиксирует расположение всех компонентов на форме и не позволит их перемещать. Если в дальнейшем у вас все-таки возникнет потребность изменить расположение компонентов, то выполните повторно команду Edit | Lock Controls и компоненты будут разблокированы.
2.6 Депозитарий — хранилище форм и проектов В Депозитарий (хранилище — Repository) вы попадете, когда выполняете команду File | New | Other. При этом открывается диалоговое окно New Items (см. рис. 2.14, 2.15, 2.21), в котором вы можете выбрать включенные в Delphi готовые формы или воспользоваться разработанными фирмой Borland мастерами. Но вы можете использовать Депозитарий и для хранения собственных разработок. Нередко создание сложной формы с множеством размещенных на ней компонентов требует немалого времени. Причем однажды разработанная удачная форма может пригодиться вам в последующих приложениях. Конечно, можно сохранить ее в каком-либо каталоге и, когда возникнет необходимость, использовать в очередном проекте. Но если разработка этого нового проекта будет не скоро, вы, возможно, потратите много времени на поиск каталога с необходимой вам формой, если вообще найдете ее. Хотелось бы иметь возможность как-то зарегистрировать свои удачные разработки в Delphi, чтобы в дальнейшем без труда повторно их использовать. Такую возможность и предоставляет вам Депозитарий. Депозитарий позволяет не просто хранить формы, но и наследовать их (см. разд. 2.4.4), т.е. создавать иерархию форм. Это важно, поскольку в сложном приложении, содержащем много форм, все эти формы должны быть спроектированы в едином стиле, с единообразным расположением органов управления, ввода и редактирования данных, в единой цветовой гамме и т.п. Это легко делается созданием иерархии форм. Сохранение формы в Депозитарии вы можете опробовать на любой созданной вами ранее форме, например, на форме "риложения, созданного в разд. 2.2.2 и перемножающего два числа. Перед занесением формы в Депозитарий ее модуль должен быть обязательно сохранен в файле. Щелкните на вашей форме правой кнопкой мыши и выберите во всплывшем контекстном меню раздел Add To Repository. Откроется диалоговое окно, вид которого приведен на рис. 2.31. В верхнем окне Title вы должны написать название вашей формы — подпись под ее пиктограммой при входе в Депозитарий. В следующем окне — Description можете написать более развернутое пояснение. Его может увидеть пользователь, войдя в Депозитарий, щелкнув правой кнопкой мыши и выбрав во всплывшем меню форму, отображения View Delails. В выпадающем списке Page вы можете выбрать страницу Депозитария, на которой хотите разместить пиктограмму своей формы. Впрочем, вы можете указать и новую страницу с новым заголовком. В результате она появится в Депозитарии. В окне Author вы можете указать сведения о себе как об авторе. Наконец, если стандартная пиктограмма вас не устраивает, вы можете выбрать другую, щелкнув на кнопке Browse. После выполнения всех этих процедур щелкните на кнопке ОК и ваша форма окажется включенной в Депозитарий.
96
Глава 2
Рис. 2.31
Окно добавления формы в Депозитарий
.*] 1 forms;
1>1е: _ (Форма умножения Prescription:
:
|форма перемножает два числа Cage: ]Мои Формы
^] ]Я
—и^— Select an icon to represent this object: ' '" ' *
Jjrowse...
| Cancel
j
Help
Теперь вы можете использовать ее в последующих ваших приложениях. Для этого вам надо будет выполнить команду File | New | Other и в открывшемся диалоговом окне New Items отыскать вашу форму (рис. 2.32). Рис. 2.32
Окно New Items с включенной новой формой
New | ActiveX) Mufti» j I >x2 } Forms } Dialogs) Projects Business j WebSnap j Data Modules I IntraWeb
Copy.
'"Inherit
Web Documents j Corba Мои Формы I WebServices
Г JJse Cancel
Help
В нижней части окна расположены три радиокнопки, которые определяют, как именно вы хотите заимствовать форму из Депозитария: Сору — копировать, Inherit — наследовать, Use — использовать. Если включена кнопка Сору, то файлы формы просто будут скопированы в ваше приложение. При этом никакой дальнейшей связи между исходной формой и копией не будет. Вы можете спокойно изменять свойства вашей копии и это никак не отразится на форме, хранящейся в Депозитарии. А если вы в дальнейшем что-то измените в форме, хранящейся в Депозитарии, то эти изменения никак не затронут вашего приложения, куда вы до этого скопировали форму. При включенной кнопке Inherit вы получите в своем проекте форму, наследующую размещенной в Депозитарии. Это значит, что если вы что-то измените в форме, хранящейся в Депозитарии, то это отразится при перекомпиляции во всех проектах, которые наследуют эту форму. Однако изменения в наследуемых формах никак не скажутся на свойствах формы, хранящейся в Депозитарии. При включенной кнопке Use вы получите режим использования. В этом случае в ваш проект включится сама форма, хранящаяся в Депозитарии. Значит любое изменение свойств формы, сделанное в вашем проекте, отразится и на хранящейся в Депозитарии форме, и на всех проектах, наследующих или использующих эту форму (при их перекомпиляции). Таким образом, режим Inherit целесообразно использовать для всех модулей вашего проекта, а режим Use — для изменения базовой формы. Тогда усовершенствование вами базовой формы будет синхронно сказываться на всех модулях проекта, при их перекомпиляции.
Работа в Интегрированной Среде Разработки Delphi 7
97
Все эти режимы заимствования форм из Депозитария вы можете легко опробовать на практике. Начните новый проект, удалите из него пустую форму (команда Project | Remove from Project или соответствующая быстрая кнопка) и введите в него вашу форму из Депозитария при включенной в окне рис. 2.32 кнопке Сору. Назовите форму (свойство Name), например, FCopy и сохраните ее модуль (команда File Save As) под именем UFCopy. Сохранять под новым именем необходимо, так как мы хотим ввести в наш проект несколько экземпляров нашей формы, а модули с одинаковыми именами в проекте быть не могут. Введите в проект второй экземпляр вашей формы из Депозитария при включенной кнопке Inherit, назовите ее FInherit и сохраните модуль этой формы под именем UFInherit. Введите третий экземпляр формы из Депозитария при включенной кнопке Use. Имя этой формы и имя модуля при сохранении изменять не надо. Установите во всех формах свойство Visible (видимость) в true и сохраните проект. Если хотите, выполните его и убедитесь, что все три формы одинаковы. Теперь измените какие-то надписи в форме FCopy, сохраните проект и выполните его. Вы убедитесь, что изменение затронуло только форму FCopy. Так что эта форма никак не связана с другими формами. А теперь измените что-то в форме, введенной с помощью кнопки Use. Даже не выполняя проект, вы увидите, что введенные изменения тут же отражаются не только в этой форме, но и в форме FInherit. Это происходит потому, что форма, полученная с помощью кнопки Use — это сама форма, хранящаяся в Депозитарии. А форма FInherit наследует ей. Поэтому в ней отражаются все изменения, сделанные в основной форме. Обратной связи от формы FInherit к форме, хранящейся в Депозитарии, нет. Вы можете в этом убедиться, изменяя что-то в форме FInherit. Более того, как только вы изменили в ней какое-то свойство, оно перестает наследоваться. Последующие изменения этого свойства в основной форме никак не скажется на FInherit. Таким образом, в наследуемых формах те свойства, которые были изменены в процессе проектирования, так и остаются неизменными. А остальные наследуются из базовой формы. В этом и заключаются положительные стороны наследования форм. Если вы построили различные формы вашего приложения (настоящего, а не этого тестового) наследованием, то изменив родительскую форму (например, изменив в ней размеры панелей или добавив какие-нибудь кнопки), вы автоматически внесете эти изменения и во все производные формы. Правда, эти изменения проявятся только при перекомпиляции вашего приложения. Но у наследования есть и отрицательные свойства. Попробуйте в вашем приложении удалить в форме FInherit какой-то компонент. Сделать это невозможно, так как Delphi выдаст сообщение об ошибке. Однако этот недостаток можно обойти. В производной форме можно сделать ненужные компоненты невидимыми (установить свойство Visible равным false) и недоступными (установить свойство Enabled равным false). Тогда во время выполнения они как бы исчезнут с формы. Обратим внимание еще на одну особенность наследования. Посмотрите коды форм приложения. В модулях базовой формы и в модуле UFCopy вы увидите обычное объявление класса формы, наследующего классу TForm, и в нем объявления компонентов, расположенных на форме, и объявление процедуры обработчика щелчка на кнопке ButtonlClick. А в модуле UFInherit все объявление класса сводится к строкам: Tl'Innerit = class (TForml) private (' Private declarations ) public { Public declarations I end; 4 Программирование в Delphi 7
98
Глава 2
Класс формы TFInherit наследует классу базовой формы TForml, а не классу Delphi TForm. Наследуются все компоненты класса TForml и обработчик события ButtonlClick. Таким образом, в модуле UFInherit ничего дополнительно не описывается. А теперь попробуйте в этом модуле ввести обработчик щелчка на кнопке. Вы увидите, что заготовка обработчика щелчка будет иметь вид: procedure T F I n h e r i t . B u t t o n l C l i c k (Sender: T O b j e c t ) ; begin inherited; end;
В отличие от обычных пустых заготовок, в данной содержится оператор inherited. Этот оператор означает вызов соответствующего наследуемого обработчика родительского класса. Если такой наследуемый обработчик имеется, то вам надо подумать, помещать ли оператор inherited перед вашим кодом, или после него. Это зависит от того, какой из обработчиков должен выполняться первым. Так в нашем примере вы можете перед или после вычислений, предусмотренных в родительской форме, предусмотреть какое-то сообщение или запрос пользователю. А если вам вообще не надо вызывать соответствующий обработчик родительской формы, то оператор inherited надо удалить. Например, это надо сделать, если данную форму вы решите использовать не для перемножения, а для сложения двух чисел. Мы рассмотрели включение в Депозитарий форм и их использование. Точно так же, начиная с Delphi 5, можно включать в Депозитарий и аналогичным образом использовать фреймы (см. гл. 3 разд. 3.9.7). Это дает дополнительные возможности организовывать иерархию не только форм, но и типовых фрагментов форм — панелей. В Депозитарий можно включать не только формы и фреймы, но и целые проекты. Если вы хотите включить в него ваш проект, откройте его и выполните команду Project | Add To Repository. Дальнейшие действия аналогичны тем, которые вы выполняли при включении в Депозитарий формы. Отличие проекта от формы при их заимствовании из Депозитария состоит в том, что проект можно взять оттуда только в режиме Сору, т.е. скопировать его и далее сохранить под другим именем. Если вы хотите взять проект, хранящийся в Депозитарии, то начать работу надо не с привычной команды File | New | Application, а с команды File | New | Other. Вы выбираете проект из Депозитария и вам сразу же предлагается диалоговое окно выбора каталога, в котором вы хотите сохранить копию проекта. После этого вы можете обычным образом работать с этой копией и менять в ней все, что вам захочется. Если есть механизм включения в Депозитарий форм и проектов, то должен быть и механизм их удаления. Удаление объектов, хранящихся в Депозитарии, выполняется командой Tools | Repository. При этом открывается окно, представленное на рис. 2.33. То же окно открывается, если в окне Депозитария New Items щелкнуть правой кнопкой мыши и выбрать из контекстного меню раздел Properties. В левой панели окна на рис. 2.33 вы можете выбрать одну из страниц и в правой панели увидеть состав этой страницы. Вы можете добавить (Add Page), удалить (Delete Page), переименовать (Rename Page) страницы Депозитария, поменять их последовательность с помощью кнопок со стрелками, выделить один из хранящихся объектов и удалить его (Delete Object), отредактировать (Edit Object) информацию об объекте. Вы можете также выделить в правой панели одну из форм и включить флажок Main Form (главная форма). Тогда при открытии нового проекта будет появляться не обычная пустая форма, а именно эта помеченная в Депозитарии как главная. Если для одной из форм включить флажок New Form (новая форма); то именно эта форма, а не пустая, будет включаться в проект при выполнении команды File | New | Form.
99
Работа в Интегрированной Среде Разработки Delphi 7
Если вы выделите в правой панели не форму, а проект (включенный вами или один из расположенных на странице Projects), то вместо индикаторов Main Form и New Form появится индикатор New Project. Если его включить, то именно этот проект будет в дальнейшем открываться при создании вами нового проекта: при выполнении команды File | New | Application и даже при щелчке на пиктограмме Application в окне New Items. В окне рис. 2.33 вы можете и реорганизовывать страницы Депозитария. Для этого достаточно перетащить мышью объект их правого окна на нужную страницу в левом окне. Если же перетащить объект в строку [Object: Repository], то он не будет виден ни на одной странице Депозитария, но хранится будет. И когда потребуется, его можно будет взять из объектов этой строки и перенести на любую страницу. Рис. 2.33
Окно реорганизации Депозитария
Pages: Forms Dialogs Projects Dala Modules IntraWeb Мои формы WebSemces
яввт
WebSnap Web Documents Coiba [Object Repository]
Add Page.. :
EelelePage
!;
flename Page...
:
'at abase Foirn Wizard IQTeeChartWizaid SJ30B Web Application Wiz...
Edit Obiecl... Delete Obiecl
Т Hew Form
Of.
Cancel
Г M» F°™
I
He*>
2.7 Инструментальные средства поддержки разработки кода 2.7.1 Применение Code Insight — Знатока Кода Этот инструмент встроен в окно Редактора Кода и может оказать большую помощь при написании кода и его отладке. Он во многих случаях подскажет вам имена свойств, методов, событий, типы аргументов, типовые синтаксические конструкции и многое другое. Code Insight может работать в двух режимах: автоматическом и не автоматическом. В разд. 2.9.5 вы увидите, как настроить Code Insight на автоматическую работу. Однако автоматически возникающие подсказки очень полезны для начинающих, но могут раздражать более опытных пользователей. Поэтому имеется возможность отключить автоматический режим и вызывать Code Insight по мере надобности, нажимая клавиши Orl-Shift-пробел или Ctrl-пробел в зависимости от того, к каким возможностям Code Insight вы хотите обратиться. Code Insight может выполнять следующие функции. Завершение кода Если вы написали в своем приложении имя компонента, поставили после него точку и немного задержались с вводом последующего текста, то появится окно, содержащее список всех свойств, методов и событий класса, к которому принадлежит данный компонент. Вы можете, выбрать из него требуемое или начать писать первые символы свойства или метода, а затем нажать Enter, и в ваш код вставится соответствующее имя. Так будет при автоматической работе Code Insight. Если ав-
100
Глава 2
тематический режим отключен, то вы можете вызвать ту же подсказку, если, поставив точку после имени компонента, нажмете Ctrl-пробел. Начиная с Delphi 6, в списке подсказок выделяются различными цветами свойства, функции, процедуры, что делает список более обозримым. По умолчанию строки всплывающего списка упорядочены по категориям. Можно упорядочить их по алфавиту, что обычно намного удобнее. Для этого надо щелкнуть в окне списка правой кнопкой мыши и выбрать в контекстном меню раздел Sort by Name. Если вы написали идентификатор переменной или идентификатор с последующими символами присваивания «:=» и нажали Ctrl-пробел, то вам будет показан список возможных аргументов: свойств, функций, переменных, типов. Аналогичным образом можно получить подсказку по аргументам функций или процедур. Правда, возникающие списки подсказок в обоих этих случаях настолько длинные, что выбрать из него требуемое не так-то просто. Начиная с Delphi 6, если вы нажмете Ctrl-пробел в пустой строке внутри тела процедуры или функции, в окне всплывет список различных функций, из которого вы можете выбрать нужную. Вы можете начать писать имя требуемой функции и, когда Code Insight найдет в списке нужную, нажать Enter. Если вы нажмете Ctrl-пробел в процессе записи параметров какой-то функции, в которой параметром является сообщение Windows (см. разд. 7.4 гл. 7), Code Insight покажет вам список всех сообщений, что очень полезно, так как помнить их все вряд ли возможно. Параметры функций, процедур, методов Если Code Insight работает в автоматическом режиме, то после того, как вы напишете имя функции, процедуры или метода и поставите открывающуюся скобку, вы увидите список параметров и их типов. Причем по мере того, как вы будете вводить значения аргументов, вам будет высвечиваться тип следующего параметра. Это, может быть, наиболее мощная возможность Code Insight, поскольку вряд ли кто-нибудь способен помнить параметры всех функций и методов Delphi. Если автоматическое высвечивание подсказок вы отключили, то можете вызвать подсказку, нажав клавиши Shift-Ctrl-пробел. Шаблоны кода В Code Insight занесено множество шаблонов стандартных структур языка Object Pascal: операторов, объявлений и др. Причем вы сами можете добавлять или удалять эти шаблоны (см. разд. 2.9.5). Вызов шаблона производится нажатием клавиш Ctrl-J. Из выпадающего списка вы можете выбрать нужный шаблон. Например, если вы выбрали шаблон управляющей структуры for, то в ваш код занесется текст: for := begin
to
do
end;
Вам остается только заполнить этот шаблон, занеся в него имя переменной цикла, начальное и конечное значения переменной и написать тело цикла. Оценка выражений Эта способность Code Insight очень полезна в процессе отладки и подробнее будет рассмотрена позднее в разд. 2.9.5 при обсуждении способов отладки. Code Insight позволяет при останове или пошаговом выполнении приложения подвести курсор в окне Редактора Кода к имени любой переменной или к выражению и увидеть текущее значение оцениваемой величины.
Работа в Интегрированной Среде Разработки Delphi 7
101
Информация об идентификаторах — Code Browser Если задано автоматическое выполнение этого режима Code Insight, то при перемещении курсора мыши в тексте приложения над любой переменной или идентификатором функции, метода и т.п. автоматически высвечивается информация об объявлении этого элемента, его типе и о модуле и номере строки, содержащей это объявление. Это помогает при разработке больших приложений, но не очень удобно в простых задачах, так как на поиск этой информации Code Insight тратит заметное время. Так что можно рекомендовать обычно отключать эту возможность и включать ее только в случае необходимости. Поиск информации об идентификаторах Code Browser проводит в каталогах, устанавливаемых при настройке Delphi, в следующей последовательности: 1. Каталог проекта Search, устанавливаемый командой Project Options на странице Directories/Conditionals. 2. Каталог проекта Source, устанавливаемый командой Project | Options на странице Directories/Conditionals. 3. Каталог Browsing, устанавливаемый командой Tools | Environment Options на странице Library. 4. Каталог Library, устанавливаемый командой Tools Environment Options на странице Library. Code Browser не может находить информацию об идентификаторах, объявленных в новых, еще не сохраненных модулях.
2.7.2 Исследователь Кода Code Explorer Исследователь Кода Code Explorer (см. рис. 2.34) показывает дерево всех типов, классов, свойств, методов, глобальных переменных и глобальных процедур, содержащихся в модуле, открытом в Редакторе Кода. По умолчанию окно Исследователя Кода появляется автоматически встроенным в окно Редактора Кода. Правда это поведение по умолчанию может быть изменено отключением опции Automatically show на странице Explorer, что выполняется командой Tools | Environment Options. В этом случае при необходимости вы можете вызвать Исследователя Кода командой View | Code Explorer. Рис. 2.34
Окно Исследователя Кода
Й-С] P'ivale (t FP: Weget : -f) P: integer Й-iO Pubfc si..i WP(Value: inlegef) Ej Ц] Published Ю BUlonliTBulton A.i Button1Oick(Sende[:TObiecl) i Q Button2: TBullon L t> Labell: Т Label 3 Cl Variables/Constants Ф A: double i.^ Foiml:TFotm1 О i: word Uses S| Classes
В окне Исследователя Кода вы можете видеть структуру своего модуля и места объявлений переменных, констант, функций. На рис. 2.34 приведен пример, в котором встречаются многие из используемых при этом значков. Если вы сделаете
102
Глава 2
двойной щелчок в окне Исследователя Кода на имени переменной или функции, то курсор в окне Редактора Кода перейдет на строку, в которой эта переменная или функция объявлена. Если вы объявили в классе какой-то метод или свойство, но еще не написали реализацию метода или функций записи и чтения свойства, то имена соответствующих методов или свойств будут выделены в окне Исследователя Кода жирным шрифтом (см. WP на рис. 2.34). Это является напоминанием вам, что надо реализовать эти объявления. Но Исследователь Кода способен на большее. С его помощью можно вводить в класс новые переменные, свойства, методы и автоматически создавать заготовки их реализации. Рассмотрим это на простом примере. Впрочем, этот пример требует некоторых элементарных знаний о свойствах и событиях формы (неизвестные вам основные свойства и события вы можете найти в.справочной части книги или, что еще удобнее, в [4]), а также об объявлении свойств в классах (см. описание классов в гл. 14). Те, кто этих знаний пока не имеет, могут сейчас пропустить этот пример и вернуться к нему позднее. Пусть вы хотите ввести в класс вашей формы целое свойство Area, в которое будете заносить площадь окна формы. В этом случае в обработчик события OnResize формы (это событие наступает при изменении пользователем размеров окна приложения) надо вставить оператор Area := Height * Width; определяющий Size как произведение высоты и ширины формы. Кроме того, давайте поместим на форму кнопку Button, которая при щелчке на ней показывала бы пользователю сообщение о площади окна. Для этого в обработчик щелчка на этой кнопке надо вставить оператор ShowMessage('Площадь окна ' + I n t T o S t r ( A r e a ) ) ;
Создайте такое приложение, записав в нем указанные обработчики событий. Но пока это приложение не будет компилироваться, поскольку в нем используется неизвестный идентификатор Area. Теперь давайте попробуем ввести свойство Area в класс нашей формы с помощью Исследователя Кода. Выделите в окне Code Explorer вершину формы, щелкните на ней правой кнопкой мыши и выберите из всплывшего меню раздел New. В открывшейся строке новой вершины напишите «property Area: integer» и нажмите Enter. В окне появится соответствующая вершина Area (см. рис. 2.35 а), выделенная жирным шрифтом — это показывает, что свойство объявлено, но еще не реализовано. Рис. 2.35
Ввод свойства с помощью Исследователя Кода: объявление свойства (а) и завершение его оформления (б)
а) JJfflfBIBIIWIIJ'JJimilif" Н »5> TForml(TFom) • В £j Public ••••• ь$ Area: integer \ В CJ Published Ф Butlon1:TBullon Butlon1:TBuilon !••••>., Button! ClicklSender. TObjecl) L »J FormResize(Sender: TObject) J Q Variables/Constants iQ Uses i
Сделайте двойной щелчок на этой вершине. В окне Редактора Кода курсор переместится на строку property A r e a : integer;
Работа в Интегрированной Среде Разработки Delphi 7
103
которую Исследователь Кода ввел в объявление вашего класса. А теперь, находясь в этой строке, нажмите клавиши Ctrl-Shift-C. В окне Исследователя Кода вы увидите (рис. 2.35 б), что в дерево класса в раздел private добавились две вершины: FArea — целое значение, соответствующее свойству Area, и процедура SetArea — метод записи этого свойства. Метод записи необходим, чтобы выдерживался принцип инкапсуляции (см. разд. 1.1). Посмотрев в окне Редактора Кода объявление класса формы, вы увидите строки: type TForml = class(TForm) private FArea: integer; procedure S e t A r e a ( c o n s t V a l u e : i n t e g e r ) ; public property Area: integer read FArea w r i t e SetArea; end;
В раздел private Исследователь Кода занес объявления переменной FArea и функции ее чтения SetArea, а строка объявления свойства Area изменилась: в нее добавились элемент read FArea, говорящий о том, что чтение свойства осуществляется непосредственно по значению FArea, и элемент write SetArea, говорящий о том, что запись свойства производится с помощью функции SetArea. Посмотрев код далее, вы увидите, что в раздел implementation модуля добавился код: procedure T F o r m l . S e t A r e a ( c o n s t V a l u e : i n t e g e r ) ; begin FArea := Value; end;
Это заготовка реализации функции чтения. В нашем случае в эту заготовку ничего добавлять не надо. Но при желании можно было бы добавить какие-то операторы. Теперь можете выполнить приложение и убедиться, что оно работает правильно. Как видите, Исследователь Кода провел за вас немалую работу по оформлению введенного вами свойства. Аналогично он может помочь и при введении в класс методов.
2.7.3 Object Browser — просмотр классов, модулей, типов Инструментом, способным оказать вам помощь в написании и отладке приложения, является Object Browser. Этот инструмент существенно изменен, начиная с Delphi 5. Он вызывается командой View | Browser. Вид окна Object Browser в Delphi 7 представлен на рис. 2.36. В его левой панели имеется три страницы: Globals (общая), Classes (классы) и Units (модули). Правая панель присутствует, если установлен параметр окна Details. Этот параметр можно установить в контекстном меню, всплывающем при щелчке правой кнопкой мыши в окне Project browser. На странице Globals представлена развернутая информация по объявленным в проекте классам, их свойствам, методам, как объявленным непосредственно в классе, так и наследуемым (вершина Inherited на рис. 2.36). Правая панель дает дополнительную информацию по вершине, выделенной в левой панели. В общем случае эта дополнительная информация располагается на трех страницах: Scope — информация о свойствах и методах сгруппирована по категориям видимости (Private, Protected и т.п.), Inheritance — информация о наследовании, References — ссылки. Ссылки представляют собой строки с путем к файлу, в котором содержится объявление вершины, выделенной в левой панели, с указа-
Глава 2
104
нием строки объявления в этом файле. Двойной щелчок на ссылке приводит к загрузке соответствующего файла в окно Редактора Кода и перемещению курсора в нем на строку, в которой объявлен соответствующий элемент левой панели. Рис. 2.36 Окно Object Browser при отображении общей информации о проекте (а) и информации о классах (б)
а) Stobals !'-: Classes *$ TForml » dass{TFormJ Н-Г_3 Private Buttonl: TButton j ' *j ButtonlClickpender T0b|. *.i FormResize(Sendei: TObje S-CJ Inherited Ш £| Private S LJ Protected Sl-UJ Public B-Q Published 4 Action
r-,.;;j •.
Scope
:
Inheritance I References!
.TForro *.class(TCuslornFamJ »$> TFotml . class{TForm)
I
6) (Interface * Jnleiface 'i llntetface * interface 1 ILJnknown - llnterface Scope inheritance [References! Ei fcj Т Object «class llnterface • intefface ^»] 81^ Exception-class(TObiectl Q(J lActiveDesigner • rterfacZj * EAboit - class(Exception) 0^ lActiveDesktop •> intetfacc EAbstractE noi * class(Excc 9$ lAdviseSink « interfacefllr EAssertionFaited * class(E>•&У (Authenticate »intetface{l EBitsEnor = class(Exceptioi i-«J lAutcComplele • interface ECommonCalendarEffW = с, BjJ IBindDx - interlacedlnterf EDateTimeError = clasi »»ij IBindHost - rterlace(llnte ,.w EMonthCaError-class -(^ IBindHost «hterfacedlnle ;--tA EComponenlEtrof = class(E ss
OS Exceptions
i" Exception Types to Ignore V VCL EAoorl Exception! V IndyEIDConnClosedGracelulry Exception Vj Microsoft DAO Enceptrans •/ Vr:i6roker Internal Exceptions У', CQRBA System Exceptions C." CORBA User Exceptions
£top on Delphi ExceptKins
Integrated debugging
Рис. 2.69
Страница OS Exceptions окна настройки отладчика
*l General \ Evtnt Los I £лпэ,мэе Exenptem OS E « Aece» Violation !К1ШЕ05| !пР» после символа «\» воспримется как знак >, а не как символ, указывающий на верхний регистр. Символ «L» означает, что в данной позиции должна быть буква. Символ «1» означает, что в данной позиции может быть только буква или ничего. Символ «А» означает, что в данной позиции должна быть буква или цифра. Символ «а» означает, что в данной позиции может быть буква, или цифра, или ничего. Символ «С» означает, что в данной позиции должен быть любой символ. Символ «с» означает, что в данной позиции может быть любой символ или ничего. О
Символ «О» означает, что в данной позиции должна быть цифра. Символ «9» означает, что в данной позиции может быть цифра или ничего.
#
Символ «#» означает, что в данной позиции может быть цифра, знак « + », знак «-» или ничего. Символ «:» используется для разделения часов, минут и секунд. Символ «/» используется для разделения месяцев, дней и годов в датах. Символ «_» означает автоматическую вставку в текст пробела.
Вводить маску можно непосредственно в свойство EditMask. Но удобнее пользоваться специальным редактором масок, вызываемым при нажатии кнопки с многоточием в строке свойства EditMask в Инспекторе Объектов. Окно редактора масок имеет вид, представленный на рис. 3.3. Рис. 3.3
а)
Окно редактора масок с загруженными файлами стандартных масок: американским (а) и российским (6)
input Mask: |!\(999MOOO-0000;1;_ Character for Blanks: I? Save literal Character
] lest Input
'
Extension Social Security Short Zip Code Long Zip Code Dale Long Time Short Time
OK
б)
15450 555-55-5555 90504 90504-0000 06.27.94 09:0515PM
13:45
Cancel
Input Mask Editor InpulMask: |!4999\) 000-00-OOA.
Sample Masks: Телефон ИйМВЁяКмЕ
Character lor gtanks: Г
Save Literal Characters
[jest Input:
Почтовый индекс Паспорт Дата с указанием дня Дата без указания дня Время с секундами Время без секунд
555-1202 ЙШ1дШ 123456 V-II123456 01.03.02 03.02 21:05:15
13:45
Help
150
_
Глава
3
В редакторе масок окно Sample Masks содержит наименования стандартных масок и примеры ввода с их помощью. В окно Input Mask надо ввести маску. Если вы выбираете одну из стандартных масок, то окно Input Mask автоматически заполняется и вы можете, если хотите, отредактировать эту маску. Окно Character for Blanks определяет символ, используемый для обозначения позиций, в которых еще не осуществлен ввод (третий раздел маски). Индикатор Save Literal Characters определяет второй раздел маски: установлен, если второй раздел равен 1, и не установлен, если второй раздел равен 0. Кнопка Masks позволяет выбрать и загрузить какой-либо другой файл стандартных масок. К сожалению, среди файлов стандартных масок, поставляемых с Delphi, отсутствует маска, соответствующая российским стандартам. Но вы легко можете сами сделать себе такой файл стандартных масок. Он делается в обычном текстовом редакторе и должен сохраняться как «только текст» с расширением Лет. Чтобы редактор масок Delphi видел этот файл, его надо сохранить в каталоге Delphi BIN. Каждая строка файла состоит из трех частей, разделяемых символом вертикальной черты. Первая часть состоит из пояснительного текста, появляющегося в левой панели окна Sample Masks редактора масок. Вторая часть — пример, который появляется в правой панели окна Sample Masks редактора масок. А третья часть — сама маска. Например, я сделал себе файл с текстом, приведенным ниже, и сохранил его с именем ru.dem. Телефон | 5551212 | ! 000-00-00; 0;_ Телефон с кодом страны I 0955551212 I ! \ ( 9 9 9 \ ) 000-00-00;0;_ Почтовый индекс | 123456 | ! 0 0 0 0 0 0 0 ; 1 ;_ Паспорт! VII123456 | ! L-LL 9 9 9 9 9 9 ; 0 ; _ Дата с указанием дня | 2 7 0 6 9 4 | ! 9 9 / 9 9 / 0 0 ; 1;_
Дата без указания дня | 0694 I ! 99/00; 1;_ Время с секундами | 210515 | ! 90: 00:00; 1;_ Время без секунд | 1345 | ! 9 0 : 0 0 ; 1 ; _
На рис. 3.3 б вы можете видеть его в работе, а на рис. 3.2 вы можете видеть ввод в окна с масками телефона и даты. Рассмотрим примеры масок. В приведенном выше файле маска для ввода номера телефона имеет вид: ! \ ( 9 9 9 \ ) 000-00-00;0;_
В этой маске символ «9» означает, что в соответствующей позиции может быть только цифра. Символ «О» означает, что в данной позиции должна быть цифра. Символ подчеркивания в конце маски будет заполнять пустые позиции. Таким образом, пользователю для ввода в окне будет отображен шаблон (см. рис. 3.2): Поскольку второй раздел маски равен 0, то при чтении введенных пользователем значений свойства EditText и Text будут различаться. Свойство EditText для примера рис. 3.5 будет равно «(095) 123-45-67», а свойство Text будет равно «0951234567». Если второй раздел маски сделать равным 1, то значения обоих свойств будут равны «(095) 123-45-67». Рассмотрим еще пример. Если с помощью EditMask надо ввести, например, целое число без знака, состоящее не более чем из двух цифр, можно задать маску «99;0; ». Если число обязательно должно быть двузначным, то маска должна иметь вид «00;0; ».
3.2.4 Многострочные окна редактирования Memo и Richedit Компоненты Memo и RichEdit (см. пример на рис. 3.4) являются окнами редактирования многострочного текста. Они так же, как и окна, рассмотренные в разд. 3.2.3, снабжены многими функциями, свойственными большинству редак-
Обзор компонентов библиотеки VCL Delphi
151
торов. В них предусмотрены типичные комбинации горячих клавиш: Ctrl-C — копирование выделенного текста в буфер обмена Clipboard (команда Copy), Ctrl-X вырезание выделенного текста в буфер Clipboard (команда Cut), Ctrl-V — вставка текста из буфера Clipboard в позицию курсора (команда Paste), Ctrl-Z — отмена последней команды редактирования. Рис. 3.4 Примеры компонентов Memo и RichEdit
Ш Компоненты Memo и RichEd.t
Memo Это текст. занесенный в окно.
ШИ-Ш!
RichEdit Это текст, занесенный
в окно.
В компоненте Memo формат (шрифт, его атрибуты, выравнивание) одинаков для всего текста и определяется свойством Font. Если вы сохраните в файле текст, введенный или отредактированный пользователем, то будет создан текстовый файл, содержащий только символы и не содержащий элементов форматирования. При последующем чтении этого файла в Memo формат будет определяться текущим состоянием свойства Font компонента Memo, а не тем, в каком формате ранее вводился текст. Компонент RichEdit работает с текстом в обогащенном формате RTF. При желании изменить атрибуты вновь вводимого фрагмента текста вы можете задать свойство SelAttributes. Это свойство типа TTextAttributes, которое в свою очередь имеет подсвойства: Color (цвет), Name (имя шрифта), Size (размер), Style (стиль) и ряд других. Например, введите на форму компонент RichEdit, диалог выбора шрифта FontDialog (см. разд. 3.10.5) и кнопку Button, которая позволит пользователю менять атрибуты текста. В обработчик щелчка кнопки можно было бы ввести текст: if FontDialogl.Execute then with R i c h E d i t l . S e l A t t r i b u t e s do begin Color:=FontDialogl.Font.Color; Name:=FontDialogl.Font.Name; Size:=FontDialogl.Font.Size; Style:=FontDialogl.Font.Style; end; RichEditl.SetFocus;
В приведенном коде присваивается поочередно значение каждого свойства. Но этот текст можно кардинально сократить, воспользовавшись тем, что объекты SelAttributes и Font совместимы по типу. Поэтому можно присвоить сразу все свойства одного объекта другому: if F o n t D i a l o g l . E x e c u t e then RichEditl.SelAttributes.Assign(FontDialogl.Font); RichEditl.SetFocus;
Запустите приложение и увидите, что вы можете менять атрибуты текста, выполняя отдельные фрагменты различными шрифтами, размерами, цветами, стилями. Устанавливаемые атрибуты влияют на выделенный текст или, если ничего не выделено, то на атрибуты нового текста, вводимого начиная с текущей позиции курсора (позиция курсора определяется свойством SelStart). В компоненте имеется также свойство Def Attributes, содержащее атрибуты по умолчанию. Эти атрибуты действуют до того момента, когда изменяются атрибуты
Глава 3
152
в свойстве SelAttributes. Но значения атрибутов в DefAttributes сохраняются и в любой момент эти значения могут быть методом Assign присвоены атрибутам свойства SelAttributes, чтобы вернуться к прежнему стилю. Свойство Def Attributes доступно только во время выполнения. Поэтому его атрибуты при необходимости можно задавать, например, в обработчике события OnCreate. За выравнивание, отступы и т.д. в пределах текущего абзаца отвечает свойство Paragraph типа ТРагаAttributes. Этот тип в свою очередь имеет ряд свойств: Alignment
Определяет выравнивание текста. Может принимать значения taLeftJustify (влево), taCenter (по центру) или taRightJustify (вправо).
Firstlndent
Число пикселов отступа красной строки.
Numbering
Управляет вставкой маркеров, как в списках. Может принимать значения nsNone — отсутствие маркеров, nsBullet — маркеры ставятся.
Leftlndent
Отступ в пикселах от левого поля.
Rightlndent Отступ в пикселах от правого поля. TabCount
Количество позиций табуляции.
Tab
Значения позиций табуляции в пикселах.
Значения подсвойств свойства Paragraph можно задавать только в процессе выполнения приложения, например, в событии создания формы или при нажатии какой-нибудь кнопки. Значения подсвойств свойства Paragraph относятся к тому абзацу, в котором находится курсор. Например, каждый из следующих операторов осуществит соответствующее выравнивание текущего абзаца: RichEditl.Paragraph.Alignment:=taLeftJustify; // Влево RichEditl.Paragraph.Alignment:=taCenter; // По центру RichEditl.Paragraph.Alignment:=taRightJustify; // Вправо Следующий оператор приведет к тому, что текущий абзац будет отображаться как список, т.е. с маркерами: RichEditl.Paragraph.Numbering:=nsBullet; Уничтожение списка в текущем абзаце осуществляется оператором RichEditl.Paragraph.Numbering:=nsNone; В целом, если с помощью компонента ActionList (см. разд. 4.3) определено некоторое действие ввода и уничтожения списка, названное ABullet, то операторы обработки соответствующего действия могут иметь вид: i f ( A B u l l e t . C h e c k e d ) then RichEditl.Paragraph.Numbering:=nsNone else RichEditl.Paragraph.Numbering:=nsBullet; ABullet.Checked:=not ABullet.Checked; Они обеспечивают переключение соответствующей быстрой кнопки и раздела меню из нажатого состояния (отмеченного) в ненажатое с соответствующим изменением вида текущего абзаца. Свойства TabCount и Tab имеют смысл при вводе текста только при значении свойства компонента WantTabs = true. Это свойство разрешает пользователю вводить в текст символ табуляции. Если WantTabs = false, то нажатие пользователем клавиши табуляции просто переключит фокус на очередной компонент и символ табуляции в текст не введется.
Обзор компонентов библиотеки VCL Delphi
153
Мы рассмотрели основные отличия Memo и RichEdit. Теперь остановимся на общих свойствах этих окон редактирования. Свойства Alignment и Wordwrap имеют тот же смысл, что, например, в метках, и определяют выравнивание текста и допустимость переноса длинных строк. Установка свойства Readonly в true задает текст только для чтения. Свойство MaxLength определяет максимальную длину вводимого текста. Если MaxLength = О, то длина текста не ограничена. Свойства WantReturns и WantTab определяют допустимость ввода пользователем в текст символов перевода строки и табуляции. Свойство ScrollBars определяет наличие полос прокрутки текста в окне. По умолчанию ScrollBars = ssNone, что означает их отсутствие. Пользователь может в этом случае перемещаться по тексту только с помощью курсора. Можно задать свойству ScrollBars значения ssHorizontal, ssVertical или ssBoth, что будет соответственно означать наличие горизонтальной, вертикальной или обеих полос прокрутки. В качестве примера на рис. 3.5 приведен пример текстового редактора, использующего описанные выше свойства компонента RichEdit. Текст в окне редактора частично поясняет атрибуты шрифта, использованные при его написании. Рис. 3.5 Пример редактора на основе компонента RichEdit
А? ТЕКСТОВЫЙ редактор №№ Файл
[Травка
Формат
Это текст, введенный в компонент RichEdilj 8 первом абзаце:
* taCenter В слове RichEdit SMtlribijtes. Stjilf = [fiBold) В строках 4 и 5 РзгядгярЬ. N umbering • ntBullet
:модиф,
редактирование текста
Основное свойство окон Memo и RichEdit — Lines, содержащее текст окна в виде списка строк и имеющее тип TStrings. Начальное значение текста можно установить в процессе проектирования, нажав кнопку с многоточием около свойства Lines в окне Инспектора Объектов. Перед вами откроется окно редактирования списков строк, представленное на рис. 3.6. Вы можете редактировать или вводить текст непосредственно в этом окне, или нажать кнопку CodeEditor и работать в обычном окне Редактора Кода. В этом случае, завершив работу с текстом, выберите из контекстного меню, всплывающего при щелчке правой кнопкой мыши, команду Close Page и ответьте утвердительно на вопрос, хотите ли вы сохранить текст в соответствующем свойстве окна редактирования. Рис. 3.6 Окно редактирования списков строк
Л String List editor : 2 lines Это текст записанный в RichEdiM
Cancel
154
Глава 3
Во время выполнения приложения вы можете заносить текст в окно редактирования с помощью методов свойства Lines типа TStrings. Этот тип широко используется в свойствах многих компонентов и его подробное описание вы можете найти в справочной части книги в разд. 17.4. Здесь коротко укажем только на его основные свойства и методы, используемые в свойстве Lines. Весь текст, представленный одной строкой типа String, внутри которой используются разделители типа символов возврата каретки и перевода строки, содержится в свойстве Text. Доступ к отдельной строке текста вы можете получить с помощью свойства Strings[Index: Integer]. Индексы, как и везде в Delphi, начинаются с 0. Так что Memol.Lines.Strings[0] — это текст первой строки. Учтите, что если окно редактирования изменяется в размерах при работе с приложением и свойство WordWrap = true, то индексы строк будут изменяться при переносах строк, так что в этих случаях индекс мало о чем говорит. Свойство только для чтения Count указывает число строк в тексте. Для очистки текста в окне надо выполнить процедуру Clear. Этот метод относится к самому окну, а не к его свойству Lines. Для занесения новой строки в конец текста окна редактирования можно воспользоваться методами Add или Append свойства Lines. Для загрузки текста из файла применяется метод LoadFromFile. Сохранение текста в фале осуществляется методом SaveToFile. Пусть, например, в вашем приложении имеется окно редактирования Editl, в котором пользователь вводит имя сотрудника, и есть кнопка, при щелчке на которой в окно Memol должна занестись шапка характеристики этого сотрудника, после чего пользователь может заполнить текст характеристики. Обработчик щелчка на кнопке может иметь вид: Memol.Clear; Memol.Lines.Add('X А Р А К Т Е Р И С Т И К А ' ) ; Memol.Lines.Add('Сотрудник '+Editl.Text); Memol.SetFocus;
Загрузка в окно Memol текста из файла (например, хранящейся в файле характеристики сотрудника) может осуществляться командой Memol.Lines.LoadFromFile('text.txt');
Сохранение текста в файле может осуществляться командой Memol.Lines.SaveToFile('text.txt');
Свойство SelStart компонентов Memo и RichEdit указывает позицию курсора в тексте или начало выделенного пользователем текста. Свойство CaretPos указывает на запись, поле X которой содержит индекс символа в строке, перед которым расположен курсор, а поле Y — индекс строки, в которой находится курсор (встроенная справка Delphi утверждает другое — что свойство CaretPos содержит координаты курсора в пикселах; но, к счастью, это не так). Таким образом, учитывая, что индексы начинаются с 0, значения Memol.CaretPos.Y+1 и Memol.CaretPos.X+1 определяют соответственно номер строки и символа в ней, перед которым расположен курсор. В редакторе на рис. 3.5 именно эти значения (только не для Memo, а для RichEdit) использованы, чтобы отображать в полосе состояния (см. разд. 3.9.6) позицию курсора. Конечно, в этом разделе рассмотрены далеко не все возможности такого интересного компонента, как RichEdit. Развернутую методику работы с этим компонентом, позволяющим оперировать с документами разных форматов, вы можете посмотреть в [2] и, частично, в справках [4].
Обзор компонентов библиотеки VCL Delphi
155
3.2.5 Компоненты выбора из списков — ListBox, CheckListBox, ValueListEditor, ComboBox, ComboBoxEx Пример компонентов, рассмотренных в данном разделе и обеспечивающих выбор из списка, приведен на рис. 3.7. Вверху расположены два компонента ListBox, справа — компонент CheckListBox, а нижнюю левую часть занимает ValueListEditor. Рис. 3.7
Пример компонентов выбора из списков
7 Примеры списков ListBox,
.JDJ.X1
ListBox Строка 2 Строка 3
ListBox
Строка 5 Строка Б
"трока 5
Раздел Фамилия Имя
Отчество Телефон Отдел Должность
Щ
Строка 6 Строка 7
вял
i Строка 9
ка 3
{Заполните Иванов Иван Иванович 1234567 Управление Начальник
Строка 10
I; CheckListBox Заголовок 1 L; Строка 1_1 [1 Строка 1_2 р Строка 1_3 I ! Строка 1_4 U Строка 1_5 ! \ Строка 1_Б L..I Строка 1_7 Заголовок 2 П Строка 2_1 П Строка 2_2 О Строка 2_3 Q Строка 2_4 i.J Строка 2_5 Г! Строка 2_6 Q Строка 2_7
Компоненты ListBox и CheckListBox (вверху и справа на рис. 3.7) отображают списки строк и позволяют пользователю выбрать в них нужную строку. Основное свойство обоих компонентов, содержащее список строк, — Items, имеющее рассмотренный ранее тип TStrings. Заполнить его во время проектирования можно, нажав кнопку с многоточием около этого свойства в окне Инспектора Объектов. Во время выполнения работать с этим свойством можно, пользуясь свойствами и методами класса TStrings (см. разд. 3.2.4 и гл. 17) — Clear, Add и другими. Этот же класс позволяет поставить в соответствие каждой строке некоторый объект. Тогда выбор пользователем строки в списке можно программно соотносить с этим объектом. В компоненте ListBox имеется свойство MultiSelect, разрешающее пользователю множественный выбор в списке (на рис. 3.7 это свойство установлено в true в среднем верхнем списке). Если MultiSelect = false (значение по умолчанию), то пользователь может выбрать только один элемент списка. В этом случае можно узнать индекс выбранной строки из свойства Itemlndex, доступного только во время выполнения. Если ни одна строка не выбрана, то Itemlndex = -1. Например, следующий код проверяет выбор пользователя. Если выбор не сделан, появляется сообщение «Вы не сделали свой выбор». Если один из элементов списка выбран, то появляется сообщение вида «Ваш выбор ...: ...», где вместо первого многоточия отображается номер выбранной строки, а вместо второго многоточия — текст выбранной строки. with (Sender as TCustomListBox) do begin if Itemlndex < 0 then ShowMessage('Вы не сделали свой выбор') else ShowMessage('Ваш выбор ' + IntToStr(Itemlndex+l) + ' : ' + Items.Strings[Itemlndex]); end; Начальное значение Itemlndex невозможно задать во время проектирования. По умолчанию Itemlndex = — 1. Это означает, что ни один элемент списка не выбран. Если вы хотите задать этому свойству какое-то другое значение, т.е. устано-
156
Глава 3
вить выбор по умолчанию, который будет показан в момент начала работы приложения, то сделать это можно, например, в обработчике события OnCreate формы, введя в него оператор вида ListBoxl.Itemlndex:=0;
Если допускается множественный выбор (MultiSelect = true), то значение Itemlndex соответствует тому элементу списка, который находится в фокусе. При множественном выборе проверить, выбран ли данный элемент, можно проверив свойство Selected[Index: Integer] типа Boolean. Например, следующий код отображает сообщения вида «Выбрана строка ...: ...» (похожие на предыдущий пример) обо всех выбранных строках. f o r i:=0 to L i s t B o x l . I t e m s . C o u n t — 1 do if ( L i s t B o x l . S e l e c t e d ! ! ] ) then ShowMessage('Выбрана строка ' + IntToStr(i+1) + ': ' + ListBoxl.Items.Strings[i]);
На способ множественного выбора при MultiSelect = true влияет еще свойство ExtendedSelect. Если ExtendedSelect = true, то пользователь может выделить интервал элементов, выделив один из них, затем нажав клавишу Shift и переведя курсор к другому элементу. Выделить не прилегающие друг к другу элементы пользователь может, если будет удерживать во время выбора нажатой клавишу Ctrl. Если же ExtendedSelect = false, то клавиши Shift и Ctrl при выборе не работают. Свойство Columns определяет число столбцов, в которых будет отображаться список, если он не помещается целиком в окне компонента ListBox (в среднем верхнем списке на рис. 3.7 свойство Columns равно 2). Свойство Sorted позволяет упорядочить список по алфавиту. При Sorted = true новые строки в список добавляются не в конец, а по алфавиту. Свойство AutoComplete, если установить его в true, позволяет пользователю быстро находить строку списка, нажимая клавишу, соответствующую ее первому символу. Свойство Style, установленное в IbStandard (значение по умолчанию) соответствует списку строк. Другие значения Style позволяют отображать в списке не только текст, но и изображения, а в Delphi 6 введена возможность создавать виртуальные списки. Значения IbOwnerDrawFixed и IbOwnerDrawVariable используются для занесения в список изображений. При этом IbOwnerDrawFixed означает, что высота всех элементов списка одинакова, а значение IbOwnerDrawVariable означает, что высота элементов может быть различной. Значения lbVirtual и IbVirtualOwnerDraw соответствуют виртуальным спискам соответственно со строками текстов и изображений, означает, что высота всех элементов списка одинакова, а значение Свойство Style, установленное в IbStandard (значение по умолчанию) соответствует списку строк. Другие значения Style позволяют отображать в списке не только текст, но и изображения. При значении Style, равном IbOwnerDrawFixed или IbOwnerDrawVariable, в момент, когда должна рисоваться какая-то строка списка, наступает событие OnDrawItem. Заголовок обработчика этого события имеет вид: procedure TForml.ListBoxlDrawItern(Control: TWinControl; Index: Integer; Rect: TRect; State: TOwnerDrawState);
Параметр Control является указателем на список, в котором происходит событие. Параметр Index указывает индекс элемента, который'должен быть перерисован. Параметр Rect типа TRect (см. в разд. 17.4) указывает область канвы списка, соответствующую рисуемому элементу списка. Параметр TOwnerDrawState является множеством, элементами которого могут быть значения odSelected — строка выделена, odFocused строка находится в фокусе и ряд других.
157
Обзор компонентов библиотеки VCL Delphi
В обработчике события OnDrawItem надо методами работы на канве нарисовать изображение элемента. При значении Style равном IbOwnerDrawFixed перед прорисовкой наступает только событие OnDrawItem. При Style = IbOwnerDrawVariable перед этим событием наступает другое — OnMeasureltem, в котором надо указать высоту элемента. Заголовок обработчика этого события имеет вид: procedure TForml.ListBoxlMeasureltem(Control: TWinControl; Index: Integer; var H e i g h t : Integer);
Параметры Control и Index имеют тот же смысл, что и в обработчике OnDrawItem, а значение параметра Height надо задать равным высоте данного элемента списка. Рассмотрим пример. Правда, в нем мы будем использовать некоторые компоненты, которые будут рассмотрены в последующих разделах. Так что, если вам трудно будет понять и воспроизвести этот пример, вернитесь к нему позднее, когда овладеете соответствующими компонентами. Пусть в вашем приложении имеется инструментальная панель ТооШаг (см. разд. 3.9.4) и вы хотите отобразить в списке пиктограммы этих кнопок и тексты их подсказок так, как показано на рис. 3.8. Это может требоваться, например, чтобы дать возможность пользователю настроить панель. Рис. 3.8
7: ListBox с иэображеии
Пример списка с занесенными в него изображениями
Вырезать Фрагмент в буфер Копировать Фрагмент в буфер Вставить из буфера Отменить последнюю операцию Удалить выделенный Фрагмент Поужирный шрифт Курсив Подчеркнутый Зачеркнутый Вставить список Выровнять влево Выровнять по центру Выровнять вправо
Для решения данной задачи, прежде всего, надо согласовать список ListBox с отображаемым списком: задать высоту элемента списка равной высоте пиктограммы и занести в список столько строк (можно пустых), сколько кнопок на панели. Это можно сделать, например, следующим обработчиком события OnCreate, наступающего в момент создания формы: procedure TForml.FormCreate(Sender: T O b j e c t ) ; var i:integer; begin ListBoxl.ItemHeight := ToolBarl.Images.Height; for i:=0 to ToolBarl.ButtonCount — 1 do ListBoxl.Items.Add(''); end;
Первый оператор этого обработчика согласовывает высоту строки списка (ListBoxl.ItemHeight) с высотой Height пиктограмм, содержащихся в списке изображений ImageList (см. разд. 4.2), на который панель ToolBarl ссылается своим свойством Images. А цикл for заносит в список столько пустых строк, сколько кнопок в панели (ToolBarl.ButtonCount). Занести эти строки необходимо, чтобы в дальнейшем для них возникали события OnDrawItem.
158
Глава 3
Обработчик события OnDrawItem списка ListBoxl может иметь вид: procedure T F o r m l . L i s t B o x l D r a w I t e m ( C o n t r o l : T W i n C o n t r o l ; Index: Integer; Rect: TRect; State: TOwnerDrawState); begin T o o l B a r l . I m a g e s . D r a w ( L i s t B o x l . C a n v a s , R e c t . L e f t + 5, Rect.Top, T o o l B a r l . B u t t o n s [ I n d e x ] . I m a g e l n d e x , t r u e ) ; L i s t B o x l . C a n v a s . T e x t O u t ( R e c t . L e f t + 30, Rect.Top, GetLongHint(ToolBarl.Buttons[Index].Hint)); end;
Первый оператор рисует пиктограмму методом Draw компонента ImageList (см. гл. 4, разд. 4.2). В этом методе в качестве канвы указана канва списка ListBoxl (о канве см. в разд. 6.1.3). Позиция рисования определяется на 5 пикселов левее области данной строки (Rect.Left + 5). Переданный в обработчик параметр Index позволяет получить через свойство ToolBarl.Buttons доступ к кнопке с аналогичным индексом. Индекс рисуемой пиктограммы в ImageList определяется свойством кнопки Imagelndex. Второй оператор приведенного кода выводит на канву методом TextOut вторую часть подсказки кнопки Hint (об этом свойстве см. в разд. 5.1.9 и 17.1). Эта вторая часть подсказки извлекается функцией GetLongHint. Теперь рассмотрим, что подразумевается под виртуальными списками, которые формируются, если значение Style списка равно IbVirtual или IbVirtualOwnerDraw. Виртуальный список формируется программно во время выполнения. Для этого используются события компонента OnData, OnDataFind, OnDataObject. Но для того, чтобы эти события наступили, надо прежде всего задать свойство списка Count — число строк. Это свойство всегда присутствует в списке ListBox, но только в виртуальных списках допускается задание его значения. В остальных случаях это свойство только для чтения. Событие OnData наступает в тот момент, когда приложению надо отобразить очередную строку списка. Заголовок обработчика этого события имеет вид: procedure T F o r m l . L i s t B o x l D a t a ( C o n t r o l : T W i n C o n t r o l ; Index: Integer; var Data: S t r i n g ) ;
Параметр Control — список, в котором происходит событие. Параметр Index — это индекс строки, которая должна отображаться. А в параметр Data надо задать отображаемый текст. Например, оператор Data := 'Строка ' + I n t T o S t r ( I n d e x + 1);
занесет в строку текст «Строка ...», в котором вместо многоточия будет номер строки. Если со строками виртуального списка надо связать какие-то объекты, это делается в обработчике события OnDataObject, заголовок которого имеет вид: procedure T F o r m l . L i s t B o x l D a t a O b j e c t ( C o n t r o l : T W i n C o n t r o l ; Index: Integer; var D a t a O b j e c t : T O b j e c t ) ;
Параметр Index указывает индекс строки, а в параметр DataObject заносится связываемый со строкой объект. Для возможности управления виртуальным списком, например, для поиска строки по первым символам или для упорядочивания строк, надо написать обработчик события OnDataFind. Его заголовок имеет вид: function TForml.ListBoxlDataFind(Control: TWinControl; FindString: S t r i n g ) : Integer;
Параметр FindString — это искомая строка, индекс которой надо вернуть как Result — результат, возвращаемый функцией.
Обзор компонентов библиотеки VCL Delphi
159
Мы подробно рассмотрели список ListBox. Имеется еще один компонент, очень похожий на ListBox — это список с индикаторами CheckListBox. Выглядит он так же, как ListBox (справа на рис. 3.7), но около каждой строки имеется индикатор, который пользователь может переключать. Индикаторы можно переключать и программно, если список используется для вывода данных и необходимо в нем отметить какую-то характеристику каждого объекта, например, наличие товара данного наименования на складе. Свойства компонента CheckListBox, связанные с индикаторами, будут рассмотрены в разд. 3.7.5. Остальные свойства, характеризующие компонент CheckListBox как список, аналогичны ListBox, за исключением свойств, определяющих множественный выбор. Эти свойства компоненту CheckListBox не нужны, поскольку в нем множественный выбор можно осуществлять установкой индикаторов. Начиная с Delphi 6, в CheckListBox появились новые свойства, позволяющие зрительно разбить список на несколько разделов с помощью заголовков (см. рис. 3.7). Свойство Header представляет собой индексированный массив булевы значений, определяющих, является ли соответствуют ' строка заголовком (значение true), или это обычная строка с индикатором значение false). Свойство Header — только времени выполнения и должно заполняться программно (по умолчанию все значения равны false). Например, операторы CheckListBoxl.Header[1] := true; C h e c k L i s t B o x l . H e a d e r [ 9 ] := true;
задают в качестве заголовков вторую и десятую строки, как показано на рис. 3.7. Заголовки отображаются в строках с цветом фона, определяемым свойством HeaderBackgroundColor, и цветом надписи, задаваемым свойством HeaderColor. Рассмотрим теперь компонент ValueListEditor. Это окно редактирования списка строк, вида «имя = значение». Окно имеет две колонки с заголовками «Key» для имен и «Value» для значений. Заголовки можно изменить во время проектирования или программно, используя свойство TitleCaptions типа TStrings. Первая строка этого свойства соответствует первой колонке (именам), вторая — второй колонке. Например, строки Раздел Заполните
дадут результат, показанный на рис. 3.7. Свойство Strings типа TStrings содержит список всех строк. Во время проектирования он может быть заполнен редактором строк, вызываемым из Инспектора Объектов. Во время выполнения пользователь может заполнять его, если в компоненте разрешено редактирование (см. далее). Можно также программно присвоить свойству Strings содержание некоторого другого списка типа TStrings. Все строки присваиваемого списка должны иметь вид «имя = значение». Тогда содержимое списка отобразится в колонках компонента. Методы класса TStrings позволяют манипулировать списком, добавлять строки, удалять и т.п. Свойство Keys является индексированным списком имен. Индексы начинаются с 1. Свойство Values[const Key: string]: string позволяет задать или прочитать значение из строки с именем Key. Если свойство используется для задания значения, а имени Key в списке нет, то в список добавляется новая строка с указанным именем и значением. Если свойство используется для чтения, а имени Key в списке нет, то возвращается пустая строка. Доступ к именам и значениям дают также свойства Strings.Names и Strings.Values (как в обычных списках типа TStrings). Еще один способ доступа к элементам строк дает свойство Cells[ACol, ARow: Integer]. В этом свойстве ACol = 0 соответствует колонке имен, a ACol = 1 —• колонке значений. ARow = 0 соответствует строке заголовков, a ARow > 0 — строкам списка.
Глава 3
160
Свойство только для чтения RowCount показывает число строк, включая строку заголовка. Свойство KeyOptions позволяет определить операции, доступные пользователю при редактировании колонки имен. Это свойство является множеством, пустым или содержащим элементы keyEdit, key Add, keyDelete, keyUnique. Элемент keyEdit позволяет пользователю редактировать имена в первой колонке. Элемент key Add позволяет пользователю добавлять в список новые строки, используя клавишу Insert или перемещая курсор клавишей со стрелкой ниже позиции последней строки. Элемент keyAdd можно включать только вместе с keyEdit. Элемент keyDelete позволяет пользователю удалять выделенную строку клавишей Delete. Элемент keyUnique не позволяет пользователю добавить новую строку с именем, уже имеющимся в списке. Такая попытка приводит к генерации исключения. Свойство только времени выполнения ItemProps позволяет управлять способом редактирования пользователем значения каждого из элементов списка. Элемент задается или его именем, или индексом строки (начинаются с 0). Массив ItemProps содержит объекты класса TItemProp, описывающие доступ к соответствующему элементу. Свойства этого класса задают способы редактирования значений: EditMask
Маскированный ввод (см. формат масок в разд. 3.2.3) Стиль редактирования: EditStyle esSimple Обычное окно редактирования. esEllipsis Кнопка с многоточием, при нажатии на которую возникает событие OnEditButtonClick. В обработчике этого события можно предусмотреть вызов какого-то окна специализированного редактора свойства. esPickList Выпадающий список, строки которого задаются свойством PickList или в обработчике события OnGetPickList (можно сочетать эти две возможности, задавая список в PickList и модифицируя его в обработчике OnGetPickList). KeyDesc Имя, появляющееся в строке данного элемента списка в колонке «Key». MaxLength Максимальное число вводимых символов. PickList Список типа TStrings, появляющийся в выпадающем списке при значении EditStyle = esPickList. Readonly . Только для чтения, редактирование запрещено. Например, сочетание Readonly = true и стиля EditStyle, равного esPickList или esEllipsis, не даст пользователю задать значение иначе, как выбрав его из списка или введя с помощью редактора свойства. Поясним некоторые возможности свойства ItemProps. Оператор ValueListEdi.tr1.ItemProps['Телефон'].EditMask := '!000-00-00;0,• ';
задает маску ввода семизначного номера телефона (см. рис. 3.7). Стиль EditStyle = esPickList приводит к тому, что при попытке пользователя редактировать значение он видит кнопку выпадающего списка (рис. 3.9 а), из которого он может выбрать соответствующее значение. Если при этом задать ReadOnly = true, то пользователь не сможет изменить значение иначе, чем выбором из списка. В качестве примера приведем операторы, обеспечивающие выбор, показанный на рис. 3.9 а:
Обзор компонентов библиотеки VCL Delphi
161
with ValueListEditrl.ItemProps['Отдел'] do begin EditStyle := esPickList; PickList.Add('Управление'); PickList.Add('Цех 1'); PickList.Add('Цех 2'); Readonly := true; end;
Рис. 3.9 a) Редактирование строки при EditStyle = esPickList (a) и EditStyle = esEilipsis (6)
(7/ Примеры списков UstBox, СИесШаВок, ¥а1(ЖЙ:1Швг ListBox Строка 2 Строка 3 Строка 4 Строка 5 Строка Б Раздел Фамилия Имя Отчество Телефон Отдел
б)
~Ц 1
d
ListBox Строка 2 Строка 3 Строка 4 Строка 5
Строка G Строка 7 Строка 8 Строка 9 Строка 1 0 :
Заполните Иванов Иван Иванович 1234567 Цех1 Упоавленме Цех! Цех 2
& ]
J
ЯЯЕЩ!
Q CheckListBox Заголовок. 1 LJ Строка 1_1 D Строка 1_2 П Строка 1_3 D Строка 1_4 D Строка 1_5 О Строка 1_6 [ ] Строка 1 7 Заголовок. 2 D Строка 2_1 D Строка 2_2 D Строка 2_3 Г] Строка 2_4 D Строка 2_5 D Строка 2_6 L ] Строка 2 7
7'.' Примеры списков ListBoK, CheckUstBox, ValoeUsttrfitojr LittBox Строка 2 Строка 3 Строка 4 Строка 5 Строка 6 Раздел Фамилия Имя Отчество Телефон Отдел Должность
ж| • ListBox Строка 2 1 Строка 3 Строка 4 , Строка 5
Строка 6 Строка 7 Строка 8 Строка 9 Строка 10
Заполните Иванов Иван Иванович 1234567 Управление
...I
!.. j CheckLislBox Заголовок 1 П Стро 1 ПСтро 2 б Стро 3 П Стро 4 D Стро _5 I 1 Строк 6 П Строк 7 Заголовок 2 П Строка 2_1 П Строка 2 2 О Строка 2 3 П Строка 2 4 П Строка 2_5 П Строка 2_Б Г 3 Строка 2_7
Стиль EditStyle = esEilipsis приводит к тому, что при попытке пользователя редактировать значение он видит кнопку с многоточием (рис. 3.9 б). При нажатии на нее возникает событие OnEditButtonClick. В его обработчике можно, например, вызвать какое-то диалоговое окно, в котором пользователь должен ответить на какие-то вопросы. В результате будет выработано значение, которое занесется в строку списка. Если к тому же задать для этой строки Readonly = true, то пользователь не сможет задать значение иначе, чем в результате работы этого диалога. Свойство DisplayOptions является множеством, которое может содержать элементы doColumnTitles - - строка заголовка фиксирована (не прокручивается), doKeyColFixed — ширина колонки имен неизменна, doAutoColResize — при изменении ширины компонента ширина колонок автоматически изменяется. Свойство Options содержит множество опций, управляющих отображением линий таблицы, возможность для пользователя изменять размеры колонок и т.п. Рассмотрим теперь компоненты выпадающих списков: ComboBox и ComboВохЕх. Их вид представлен на рис. 3.10. Программирование в Delphi 7
162 Рис. 3.10 Выпадающие списки
Глава 3 Ь/ Выпадающие списки £ Вырезать
•Н-ШЦ ComboBox. Style « csSimple
ComboBoxEx .*]
ComboGoxl
Вырезать ComboBox. Style - ctDiopDown Копировать Вставить Вырезать _^J : Отменить Удалить
Стиль изображения компонента ComboBox определяется его свойством Style, которое может принимать следующие основные значения: csDropDown
Выпадающий список со строками одинаковой высоты и с окном редактирования, позволяющим пользователю вводить или редактировать текст (левый нижний список на рис. 3.10).
csSimple
Развернутый список со строками одинаковой высоты и с окном редактирования, позволяющим пользователю вводить или редактировать текст (правый список на рис. 3.7).
csDropDownList
Выпадающий список со строками одинаковой высоты, не содержащий окна редактирования.
csOwnerDrawFixed
Выпадающий список со строками одинаковой высоты, в которых могут отображаться изображения и текст.
csOwnerDrawVariable
Выпадающий список со строками разной высоты, в которых могут отображаться изображения и текст.
При стилях csOwnerDrawFixed и csOwnerDrawVariable изображение рисуются на канве в обработчике события OnDrawItem так же, как это рассказывалось ранее для ListBox. При стиле csOwnerDrawVariable перед рисованием возникает событие OnMeasureltem, в обработчике которого надо задать высоту элемента. Выбор пользователя или введенный им текст можно определить по значению свойства Text. Если же надо определить индекс выбранного пользователем элемента списка, то можно воспользоваться обсуждавшимся в компоненте ListBox свойством Itemlndex. Все сказанное выше об Itemlndex и о задании его значения по умолчанию справедливо и для компонента ComboBox. Причем для ComboBox задание начального значения Itemlndex еще актуальнее, чем для ListBox. Если начальное значение не задано, то в момент запуска приложения пользователь не увидит в окне компонента одно из возможных значений списка и, вероятнее всего, не очень поймет, что с этим окном надо делать. Если в окне проводилось редактирование данных, то Itemlndex = —1. По этому признаку можно определить, что редактирование проводилось. Свойство MaxLength определяет максимальное число символов, которые пользователь может ввести в окно редактирования. Если MaxLength = 0, то число вводимых символов не ограничено. Свойство DropDownCount указывает число строк, появляющихся в выпадающем списке без возникновения полосы прокрутки. Как и в компоненте ListBox, свойство Sorted позволяет упорядочить список по алфавиту. При Sorted = true новые строки в список добавляются не в конец, а по алфавиту. В заключение остановимся на компоненте ComboBoxEx. Он во многом подобен ComboBox. Различие, прежде всего, заключается в том, что в ComboBoxEx легче, чем
163
Обзор компонентов библиотеки VCL Delphi
в ComboBox, вводить изображения в элементы списка. С другой стороны, некоторые возможности ComboBox, например, возможность сортировки, ComboBoxEx в не поддерживаются. Невозможно также в этом списке заказное рисование на канве. Изображения, отображаемые в элементах, должны содержаться в компоненте ImageList (см. разд. 4.2). Ссылка на этот компонент задается в свойстве Images. Ввод элементов списка во время проектирования осуществляется редактором коллекций, вызываемым щелчком на кнопке с многоточием в окне Инспектора Объектов около свойства ItemsEx. Перед вами откроется окно, показанное на рис. 3.11. Кнопка Add New (левая) позволяет ввести новый элемент. Если вы ввели элемент и выделили его, то в окне Инспектора Объектов увидите его свойства. Основные из них: Caption — текстовая строка, Imagelndex — индекс соответствующей пиктограммы в списке ImageList, Indent — отступ от левого поля элемента списка. Рис. 3.11
21)
7 Editing ConiboBoKEKl
Окно редактора коллекций при задании элементов списка
El 0 - Вырезать 1 • Копировать 2-Вставить 3 • Отменить 4 - Удалить
Свойство Style определяет стиль элемента и может быть равным csExDropDown, csExSimple или csExDropDownList. Смысл этих значений подобен рассмотренному выше для компонента ComboBox.
3.2.6 Таблица строк — компонент StringGrid Компонент StringGrid (см. пример на рис. 3.12) представляет собой таблицу, содержащую строки. Данные таблицы могут быть только для чтения или редактируемыми. Таблица может иметь полосы прокрутки, причем заданное число первых строк и столбцов может быть фиксированным и не прокручиваться. Таким образом, можно задать заголовки столбцов и строк, постоянно присутствующие в окне компонента. Каждой ячейке таблицы может быть поставлен в соответствие некоторый объект. Рис. 3.12 Пример компонента StringGrid
I?/ Компонент StringGrid
ИИЭВ! StringGrid
столбец 1 столбец 2 столбец 3 столбец JM строка1
1:1
J1:2
1:3
1:4
строка 2
2:1
2:2
2:3
:2:4
строка 3
3:1
3:2
3:3
:
LJJ
I,
3:4
—^
, !±Г
Компонент StringGrid предназначен в первую очередь для отображения таблиц текстовой информации. Однако в разд. 3.6.2 поясняется, как этот компонент может отображать и графическую информацию. Основные свойства компонента, определяющие отображаемый текст:
164
Глава 3
CeIls[ACol, ARow: Integer]: string
Строка, содержащаяся в ячейке с индексами столбца и строки ACol и ARow. Cols[Index: Integer]: TStrings Список строк, содержащихся в столбце с индексом Index. Rows[Index: Integer]: TStrings Список строк, содержащихся в строке с индексом Index. Objects [ACol, ARow: Integer]:TObject; Объект, связанный со строкой, содержащейся в ячейке с индексами столбца и строки ACol и ARow. Все эти свойства доступны во время выполнения. Задавать тексты можно программно или по отдельным ячейкам, или сразу по столбцам и строкам с помощью методов класса TStrings, описанных ранее в разд. 3.2.4 и более подробно изложенных в справочной части книги в гл. 17. Свойства ColCount и RowCount определяют соответственно число столбцов и строк, свойства FixedCols и FixedRows — число фиксированных, не прокручиваемых столбцов и строк. Цвет фона фиксированных ячеек определяется свойством FixedColor. Свойства LeftCol и TopRow определяют соответственно индексы первого видимого на экране в данный момент прокручиваемого столбца и первой видимой прокручиваемой строки. Свойство ScrollBars определяет наличие в таблице полос прокрутки. Причем полосы прокрутки появляются и исчезают автоматически в зависимости от того, помещается таблица в соответствующий размер, или нет. Свойство Options является множеством, определяющим многие свойства таблицы: наличие разделительных вертикальных и горизонтальных линий в фиксированных (goFixedVertLine и goFixedHorzLine) и не фиксированных (goVertLine и goHorzLine) ячейках, возможность для пользователя изменять с помощью мыши размеры столбцов и строк (goColSizing и goRowSizing), перемещать столбцы и строки (goColMoving и goRowMoving) и многое другое. Важным элементом в свойстве Options является goEditing — возможность редактировать содержимое таблицы. В основном компонент StringGrid используется для выбора пользователем каких-то значений, отображенных в ячейках. Свойства Col и Row показывают индексы столбца и колонки выделенной ячейки. Возможно также выделение пользователем множества ячеек, строк и столбцов. Среди множества событий компонента StringGrid следует отметить событие OnSelectCell, возникающее в момент выбора пользователем ячейки. В обработчик этого события передаются целые параметры ACol и ARow — столбец и строка выделенной ячейки, и булев параметр CanSelect — допустимость выбора. Параметр CanSelect можно использовать для запрета выделения ячейки, задав его значение false. А параметры ACol и ARow могут использоваться для какой-то реакции программы на выделение пользователя. Например, оператор Labell.Caption := 'Выбрана ячейка ' 1 +IntToStr (ARow)-t- : '+IntToStr (ACol) ;
выдаст в метку Labell номер выбранной ячейки. А оператор Labell.Caption := S t r i n g G r i d l . C e l l s [ A C o l , A R o w ] ;
выведет в ту же метку текст выделенной ячейки. Конечно, в реальном приложении задача заключается не в том, чтобы вывести подобные тексты при выборе пользователем той или иной ячейки, а в том, чтобы сделать нечто более полезное.
Обзор компонентов библиотеки VCL Delphi
165
3.3 Ввод и отображение чисел, дат и времени 3.3.1 Перечень компонентов ввода и отображения чисел, дат и времени В библиотеке визуальных компонентов Delphi существует ряд компонентов, позволяющих вводить, отображать и редактировать числа, даты и время. Конечно, с подобной информацией можно обращаться и просто как с текстовой, используя компоненты, описанные в разд. 3.2. Но это не удобно, так как не гарантирует от ошибок при вводе. В табл. 3.4 приведен перечень специализированных компонентов ввода и отображения чисел, дат и времени с краткими характеристиками и указанием основных параметров, содержащих отображаемый или вводимый текст. Таблица 3.4. Компоненты ввода и отображения чисел, дат и времени Пикто- Компонент грамма
Страница Описание
И
UpDown (кнопкасчетчик)
rai
SpinEdit Samples (кнопкасчетчик с окном редактирования)
Окно редактирования в комбинации с кнопкой-счетчиком. Почти то же, что комбинация Edit и UpDown. Основное свойство — Value.
DateTime- Win32 Picker (окно ввода дат и времени)
Ввод даты (с выпадающим календарем) и времени. Основные свойства — Date и Time.
Win32 MonthCalendar (окно ввода дат)
Ввод дат с выбором из календаря.
Calendar Samples (календарь на указанный месяц)
Отображение календаря на указанный месяц. Компонент DateTimePicker имеет больше возможностей по вводу дат, чем этот компонент. Основные свойства — Month и Day.
FIBook (страницы Excel)
Компонент ввода и обработки числовой информации, аналогичный страницам Excel.
т
ш т
ш
Win32
ActiveX
Кнопка-счетчик, в сочетании с компонентами Edit и другими позволяющая вводить цифровую информацию. Основное свойство — Position.
V
3.3.2 Ввод и отображение целых чисел UpDown и SpinEdit
компоненты
В Delphi имеются специализированные компоненты, обеспечивающие ввод целых чисел — UpDown и SpinEdit (см. пример на рис. 3.13).
166 Рис. 3.13 Пример компонентов UpDown и SpinEdit
Глава 3 7 Компоненты ввода UpDown * Edit
UpDown * Edit
2003
{2003
•fj:
Ш
SpinEdit
12003±j
Компонент UpDown превращает окно редактирования Edit в компонент, в котором пользователь может выбирать целое число, изменяя его кнопками со стрелками. Если к тому же установить в true свойство окна Readonly, то пользователь просто не сможет ввести в окно какой-либо свой текст и вынужден будет ограничиться выбором числа. Компонент SpinEdit представляет собой сочетание Edit и UpDown, оформленное как отдельный тип компонента. Основное свойство компонента UpDown — Associate, связывающее кнопки со стрелками с одним из оконных компонентов, обычно с Edit. Чтобы опробовать компонент UpDown, перенесите на форму его и окно редактирования Edit, расположив Edit там, где это требуется, a UpDown — в любом месте формы. Далее в выпадающем списке свойства Associate компонента UpDown выберите Editl. Компонент UpDown немедленно переместится к Edit и как бы сольется с ним. Свойство AlignButton компонента UpDown, которое может принимать значения udLeft или udRight, определяет, слева или справа от окна будут размещаться кнопки. Свойство Orientation, которое может принимать значения udHorizontal или udVertical, определяет, расположатся ли кнопки по вертикали (одна под другой — см. левый компонент на рис. 3.13) или по горизонтали (одна рядом с другой — см. правый компонент на рис. 3.13). Свойство ArrowKeys определяет, будут ли управлять компонентом клавиши клавиатуры со стрелками. Свойство Thousands определяет наличие или отсутствие разделительного пробела между каждыми тремя цифрами разрядов вводимого число. Свойства Min и Мах компонента UpDown задают соответственно минимальное и максимальное значения чисел, свойство Increment задает приращение числа при каждом нажатии на кнопку. Свойство Position определяет текущее значение числа. Это свойство можно читать, чтобы узнать, какое число задал пользователь. Его можно задать во время проектирования в диапазоне Min — Мах. Тогда это будет значение числа по умолчанию, отображаемое в окне в начале выполнения приложения. Свойство Wrap определяет, как ведет себя компонент при достижении максимального или минимального значений. Если Wrap = false, то при увеличении или уменьшении числа до максимального или минимального значения это число фиксируется на предельном значении и нажатие кнопки, пытающейся увеличить максимальное число или уменьшить минимальное, ни к чему не приводит. Если же Wrap = true, то попытка превысить максимальное число приводит к его сбросу на минимальное значение. Аналогично, попытка уменьшить минимальное число приводит к его сбросу на максимальное значение. Т.е. изменение чисел «закольцовывается». Если в компоненте Edit, связанном с UpDown, не задать ReadOnly равным true, то пользователь сможет редактировать число, не пользуясь кнопками со стрелками. Это удобно, если требуемое число далеко от указанного по умолчанию, а шаг приращения Increment в UpDown мал. Но тут проявляется серьезный недостаток компонента UpDown: ничто не мешает пользователю ввести по ошибке не цифры, а какие-то другие символы. Чтобы избавиться от этого недостатка, можно использовать прием, описанный в гл. 5 в разд. 5.3.2, который не дает возможность
Обзор компонентов библиотеки VCL Delphi
167
пользователю ввести в окно редактирования какие-то символы, кроме цифр. Но лучше для этих целей использовать компонент SpinEdit. Свойства компонента SpinEdit похожи на рассмотренные, только имеют другие имена: свойства Min, Max, Position называются соответственно MinValue, MaxValue, Value. В целом компонент SpinEdit во многих отношениях удобнее простого сочетания UpDown и Edit. Так что, если не требуются какие-то из описанных выше дополнительных возможностей UpDown (нестандартное расположение кнопок, «закольцовывание» изменений и т.п.), то можно рекомендовать пользоваться компонентом SpinEdit.
3.3.3 Ввод и отображение дат и времени — компоненты DateTimePicker, MonthCalendar, Calendar Примеры компонентов ввода и отображение дат и времени приведены на рис. 3.14. Рис. 3.14
Примеры компонентов отображения дат и времени
|7 Календари DateTimePicker. Kind = dlkTime [10:19:32
MonthCalendar
JLJ
DaleTimePickei. Kind - dlkDate, DateMode - dmUpDown. DaleFoimal - dfShort .
ham 2003
_H
Апрель 2003 r. Пн
Вт Ср Чт Пт Сб 1 2 3 4 5 7 8 9 10 11 12 14 15 16 17 18 Щ 21 22 23 24 25 26 28 29 30
DateTimePicker. Kind - dtkDate, DateMode - dmComboBox. DateFoimat * dfLong 30 апреля 2003г.
1 2 7 8 9 14 15 16 21 22 23 28 29 «Я»
3 4 5 6 10 11 12 13 17 18 20 24 25 26 27
Сегодня: 19.04.2003 Calendar Пн [.Вт
15 • 22 23
Ср 2 9 16 23 30
Пт 3 10 17
Сб | Be | 5 Б 12 13 20 26 27
£3 Сегодня: 19.04.2003
Из этих компонентов наиболее удобным является DateTimePicker (на рис. 3.14, слева вверху показан этот компонент в режиме ввода времени, а ниже — в двух вариантах режима ввода даты). Компонент очень эффектен за счет появления выпадающего календаря (иногда даже слишком эффектен для строго оформленного приложения) и обеспечивает безошибочный с точки зрения синтаксиса ввод дат и времени. Его свойство Kind определяет режим работы компонента: dtkDate — ввод даты, dtkTime — ввод времени. При вводе дат можно задать свойство DateMode равным dmComboBox — наличие выпадающего календаря, или равным dmUpDown — наличие кнопок увеличения и уменьшения (см. средний компонент DateTimePicker на рис. 3.14), напоминающих те, которые используются в описанных ранее компонентах UpDown и SpinEdit. Только в данном случае пользователь может независимо устанавливать с помощью кнопок число, месяц и год. Формат представления дат определяется свойством DateFormat, которое может принимать значения dfShort — краткий формат (например, «19.04.2003» - средний компонент на рис. 3.14), или dfLong — полный формат (например, «19 апреля 2003 г.» — нижний компонент на рис. 3.14).
168
Глава 3
Значение даты по умолчанию можно задать в Инспекторе Объектов через свойство Date. Это же свойство читается для определения заданной пользователем даты. При чтении Date надо учитывать тип этого свойства — TDateTime, представляющий собой число с плавающей запятой, целая часть которого содержит число дней, отсчитанное от некоторого начала календаря, а дробная часть равна части 24-часового дня, т.е. характеризует время и не относится к дате. За начало календаря принята дата 12/30/1899 00 часов. Для преобразования значения свойства Date в строку можно воспользоваться функцией DateToStr. Например, оператор Memol.Lines.Add('Дата: ' + DateToStr(DateTimePickerl.Date)); добавит в окно Memol строку вида «Дата: 19.04.2003». При вводе дат можно задать значения свойств MaxDate и MinDate, определяющих соответственно максимальную и минимальную дату, которую может задать пользователь. В режиме ввода времени dtkTime введенное пользователем значение можно найти в свойстве Time, тип которого — тот же рассмотренный выше TDateTime. Преобразовать время в строку можно функцией TimeToStr. Компонент MonthCalendar похож на компонент DateTimePicker, работающий в режиме ввода дат. Правда, в компоненте MonthCalendar предусмотрены некоторые дополнительные возможности: можно допустить множественный выбор дат в некотором диапазоне (свойство MultiSelect), можно указывать в календаре номера недель с начала года (свойство WeekNumbers), перестраивать календарь, задавая первый день каждой недели (свойство FirstDayOfWeek) и т.п. Для некоторых офисных приложений все это достаточно удобно. Компонент Calendar представляет собой менее красочный- и более обыденно оформленный календарь на один месяц. Вместо свойства Date в нем предусмотрены отдельные свойства Year — год, Month — месяц, Day — день. Все это целые числа, с которыми иногда удобнее иметь дело, чем с типом TDateTime. Перед отображением на экране или в процессе проектирования надо задать значения Month и Year, чтобы компонент отобразил календарь на указанный месяц указанного года. Впрочем, если вам надо иметь календарь на текущий месяц, надо установить в true значение свойства UseCurrentDate (установлено по умолчанию). В этом случае по умолчанию будет показан календарь на текущий месяц с выделенным в нем текущим днем. Свойство StartOfWeek задает день, с которого начинается неделя. По умолчанию задано 0 — воскресенье, как это принято в западных календарях. Но для нас все-таки как-то привычнее начинать неделю с рабочего дня — понедельника. Так что желательно задать StartOfWeek = 1.
3.3.4 Страницы Excel — компонент FIBook Очень интересным компонентом является FIBook на странице ActiveX. Этот компонент позволяет встроить в ваше приложение таблицы типа Excel (рис. 3.15), которые пользователь может заполнять соответствующими числами, а компонент будет производить по заданным формулам вычисления и тут же отображать их результаты в указанных ячейках. В таблицу можно встроить диаграммы и графики различных типов. И все изменения, вносимые пользователем в данные таблицы, немедленно будут отображаться в диаграммах. Таким образом вы можете включать в свое приложение различные бланки смет, счетов, ведомостей, с которыми будет работать пользователь, различные таблицы, производящие статистические или технические расчеты и т.п.
169
Обзор компонентов библиотеки VCL Delphi
Рис. 3.15 Приложение с компонентом FIBook
| / Учет шарив на складе
НАЛИЧИЕ НА СКЛАДЕ № Наименование Количество (шт.) Цена (за шт.) Стоимость Грабли 100 50 SOOO Лопаты 50 2000 Мониторы LG 4500 22500 Тяпки ?0 ВО Сигареты LM 3600 ИТОГО 33160 Количество
Стоимость
100 т
l\ Sheetl Г'
Перенесите на форму компонент FIBook и щелкните на нем правой кнопкой мыши. Выберите из всплывшего меню команду Workbook Designer. Перед вами появится диалоговое окно проектирования, представленное на рис. 3.16. Те, кто знаком с программой Excel, могут увидеть, что это окно является несколько упрощенным вариантом Excel. Проектирование таблицы производится фактически по тем же правилам, что и в Excel. Вы можете писать в ячейках необходимые надписи, задавая шрифт, его стиль, обрамление. Мбжете записывать формулы. Так на рис. 3.15 и 3.16 последний столбец представляет собой стоимость соответствующего товара, являющуюся произведением его количества на его цену. А ячейка внизу таблицы суммирует стоимость всех товаров. Рис. 3.16 Диалоговое окно проектирования компонента FIBook
1 1'B Formula One Workbook resign»''''"'"" Ete Е* S5«w Qata £h«t Format Qbjwt
ЦН№Щ1 Help
QjiSjH] *]|&]в|.М1Ш1* JsJ \|ajoj[ь|&1 ajia aJMl ":A1 ' •"'!'*;•• :
=2 №
Наименование 1 Грабли 2 Лопаты 3 Мониторы LG 4 Тяпки 5 Сигареты LM
\ Sheet! / For Help, pies? F1
НАЛИЧИЕ НА СКЛАДЕ Количество (шт.) Цена (за шт.) 100 50
5
3 40
Стоимость 5000 2000 22500 4500 60 20 90 3600 331 БО
50 40
ИТОГО
•i
?3? • |fr.,:.
i
l
l
^
Правая быстрая кнопка на рис. 3.16 позволяет ввести на страницу диаграммы и графики. Чтобы задать диаграмму, надо сначала выделить курсором в таблице данные, которые должны отображаться в диаграмме, затем нажать кнопку ввода диаграммы, после этого указать курсором рамку, в которой должна отображаться диаграмма. В результате вы попадете в диалоговое окно, в котором сможете выбрать тип диаграммы и необходимые ее атрибуты.
Глава 3
170
Рассказывать подробно о работе с окном проектирования компонента FIBook невозможно из-за ограничения на объем данной книги. Те, кто .знаком с Excel, без труда смогут в этом окне ориентироваться. К тому же, в нем имеется встроенная справка, вызываемая командой меню Help или клавишей F l . Щелкнув правой кнопкой мыши на компоненте FIBook, вы можете выбрать еще одну команду - - Properties. В появившемся при этом диалоговом окне (рис. 3.17) вы можете, в частности, задать опции, определяющие, что будет видно или не видно в таблице при работе приложения: заголовки строк и столбцов (Row Heading и Column Heading), сетка (Gridlines), формулы вычислений (Formulas) и т.п. Рис. 3.17
Диалоговое окно задания свойств объекта FIBook
VCI Formula One Workbook Properties General j Allow [show | FixedCol:
"
П
FrxedCols: |5
MaxRow:|l6384
FixedRow: .p
Г
FixedRows: J0~ "™~"
OK
1
:
— '
^ _». .••• :!':. ••"•:. 'Hotizonta! Sctol Bat Ttormulas
M«Coi:p:-fF~
Л/ейЫ Scroll Bar
.:';I5?? — — ~( Automatic MaxCol: J256 . ,>... . ,:, : .
~
—
Cancel
,:jQff
^J
firidKries
gelections
Г" BOW Heading
" j Automatic
Г
£4imnH»fl!ling
'
Apply
• •
jjj
,l*f . „ , „ „ „ ' , ' » , . (Bottom _J
Help '».,.,..,„.,„ ...J
3.4 Обеспечение синтаксически правильного ввода текстовых и цифровых данных Мы рассмотрели различные компоненты ввода информации. Теперь остановимся на проблеме сокращения числа возможных ошибок пользователя при вводе текстовых и числовых данных. Это очень серьезная проблема, особенно при построении приложений, рассчитанных на массового и не очень квалифицированного пользователя. Частично безошибочного ввода можно добиться за счет маскирования, описанного при рассмотрении компонента MaskEdit. Однако и при маскированном вводе пользователь может ошибиться в синтаксисе, в результате чего будет генерироваться исключение. Еще лучше использовать, если возможно, выбор с помощью компонентов типа ListBox, ComboBox, StringGrid, DateTimePicker, SpinEdit и т.п. Это гарантирует, если не правильный в смысловом плане, то, по крайней мере, синтаксически правильный ввод. В качестве примера на рис. 3.18 приведены два варианта ввода информации о сотрудниках некоей организации. Слева ввод осуществляется с помощью компонентов Edit и пользователь может сделать любые ошибки. Справа ввод осуществляется с помощью компонентов ComboBox, SpinEdit, MaskEdit и DateTimePicker. ComboBox со значением свойства Style равным csDropDownList не допускает редактирования и, значит, пользователь просто не может указать неправильный отдел. SpinEdit гарантирует синтаксически правильное указание стажа работы, а его свойства MinValue и Max Value определяют реальные пределы вводимого числа (например, число лет от 1 до 50). Аналогично компонент MaskEdit гарантирует синтаксически правильный ввод номера телефона, а компонент DateTimePicker гарантирует (даже без использования выпадающего календаря) синтаксически правильный ввод даты рождения и ее допустимые пределы (например, от 1930 г. до 1985 г.). В Delphi 6 широкие возможности для организации синтаксически безошибочного ввода предоставляет компонент ValueListEditor (см. разд. 3.2.5), в частности
Обзор компонентов библиотеки VCL Delphi
171
стили элементов esPickList, esEllipsis и маскированный ввод EditMask. На рис. 3.7 и 3.9 вы могли видеть организацию ввода с помощью ValueListEditor примерно той же информации, которая вводится в примере на рис. 3.18. Рис. 3.18
/ Примеры организации ввода
Пример правильной и неправильной организации ввода информации
Плохая организация ввода
Правильная организация ввода
Отдел
Отдел
Шугаятеря Данные о сотруднике Иванове Стаж
Цех1 Цех 2
Стаж
Дата рождения [7.5:970
Дата рождения 7
мая
1970 г jj
3.5 Компоненты отображения иерархических данных 3.5.1 Перечень компонентов Ниже будут рассмотрены компоненты отображения иерархических данных произвольного вида. Это может быть структура некоторого учреждения, структура сложного проекта, состав документации и т.п. Перечень компонентов, способных отображать подобную информацию, приведен в табл. 3.5. Помимо этих компонентов, имеются специфические, отображающие иерархическую информацию о каталогах и файлах. Подобные специализированные компоненты будут рассмотрены в разд. 3.10.3 и 3.10.4. Таблица 3.5. Компоненты отображения произвольных иерархических данных Страница
Описание
TreeView)
Win32
Просмотр структуры иерархических данных в стиле Windows 95/98/2000/XP.
Outline
WinS.l
Просмотр структуры иерархических данных в стиле Windows 3.x.
List View
Win32
Отображение списков в колонках или в виде пиктограмм.
Пикто- Компонент грамма
В
ш т
3.5.2 Компоненты отображения дерева данных — TreeView и Outline Компоненты TreeView и Outline служат для отображения иерархических данных в виде дерева (см. пример на рис. 3.19), в котором пользователь может вы-
Глава 3
172
брать нужный ему узел или узлы. Иерархическая информация может быть самой разной: структура некоторого предприятия, структура документации учреждения, структура документации и т.п. С каждым узлом дерева могут быть связаны некоторые данные. -iDl'xf
Рис. 3.19
Примеры компонентов отображения дерева иерархических данных
TieeView
Outline -ЙПроизводство
ВЁ> Производство
hi Цех 1
i- 1 Цех1 : i Цех 2
[§ЦехЗ -^ifffifl?fjfi5|J|?|5 |- Ш Администрация '-В Бухгалтерия
1 ЦехЗ 3-U Управление ; ЦР] Администрация -•[В] Бухгалтерия
Возможности компонента TreeView несколько шире, чем компонента Outline. К тому же TreeView — 32-разрядный компонент, a Outline — 16-разрядный. Поэтому Outline целесообразно использовать только в приложениях, создаваемых с помощью Delphi 1. Основным свойством TreeView, содержащим информацию об узлах дерева, является Items. Доступ к информации об отдельных узлах осуществляется через этот индексный список узлов. Например, TreeViewl.Items[l] — это узел дерева с индексом 1 (второй узел дерева). Каждый узел является объектом типа TTreeNodes, обладающим своими свойствами и методами. Во время проектирования формирование дерева осуществляется в окне редактора узлов дерева, представленном на рис. 3.20. Это окно вызывается двойным щелчком на компоненте TreeView или нажатием кнопки с многоточием около свойства Items в окне Инспектора Объектов. Рис. 3.20 Окно редактора узлов дерева компонента TreeView
[Ъ TreeView Items Editor I i Item Properties
г Items
Hew Item | \ \ lext ;
Цек2 \. \ ЦехЗ В Управление ; Администрация ; Бухгалтерия
New Subltem I
(Ж
Cancel
]Производство <
| Image Index: JO
Delete
i Selected Index: |l
Load
I State Index:
UHV
[-1
Help
Кнопка New Item (новый узел) позволяет добавить в дерево новый узел. Он будет расположен на том же уровне, на котором расположен узел, выделенный курсором в момент щелчка на кнопке New Item. Кнопка New Subltem (новый дочерний узел) позволяет добавить в дерево дочерний узел. Он будет расположен на уровень ниже уровня того узла, который выделен курсором в момент щелчка на кнопке New Subltem. Кнопка Delete (удалить) удаляет выделенный узел дерева. Кнопка Load позволяет загрузить структуру дерева из файла. Файл, хранящий структуру дерева это обычный текстовый файл, содержащий тексты узлов. Уровни узлов обозначаются отступами. Например, файл дерева, изображенного на рис. 3.19 и 3.20, может иметь вид:
173
Обзор компонентов библиотеки VCL Delphi Производство Цех 1 Цех 2 Цех 3
Управление Администрация Бухгалтерия
Для каждого нового узла дерева можно указать ряд свойств в панели Item Properties окна на рис. 3.20. Это прежде всего свойство Text — надпись, появляющаяся в дереве около данного узла. Свойства Image Index и Selected Index определяют индекс пиктограммы, отображаемой для узла, который соответственно не выделен и выделен пользователем в данный момент. Эти индексы соответствуют списку изображений, хранящихся в отдельном компоненте ImageList (см. разд. 4.2). Указание на этот компонент вы можете задать в свойстве Images компонента TreeView. Индексы начинаются с 0. Если вы укажете индекс -1 (значение по умолчанию), пиктограммы изображаться не будут. Последнее свойство — State Index в панели Item Properties позволяет добавить вторую пиктограмму в данный узел, не зависящую от состояния узла. Подобная пиктограмма может просто служить дополнительной характеристикой узла. Индекс, указываемый как State Index, соответствует списку изображений, хранящихся в отдельном компоненте ImageList, указанном в свойстве Statelmages компонента Tree View. Мы рассмотрели формирование дерева в процессе проектирования. Однако дерево можно формировать или перестраивать и во время выполнения приложения. Для этого служит ряд методов объектов типа TTreeNodes. Следующие методы позволяют вставлять в дерево новые узлы: function Add(Node: TTreeNode; Добавляет новый узел с текстом S как const S: string): TTreeNode; последний узел уровня, на котором расположен Node. function AddFirst(Node: TTreeNode; Вставляет новый узел с текстом S как const S: string): TTreeNode; первый из узлов уровня, на котором находится Node. Индексы последующих узлов увеличиваются на 1. function Insert(Node: TTreeNode; Вставляет новый узел с текстом S сразу const S: string): TTreeNode; после узла Node на то же уровень. Индексы последующих узлов увеличиваются на 1. function AddChild(Node: TTreeNode; Добавляет узел с текстом S как последconst S: string): TTreeNode; ний дочерний узла Node. function AddChildFirst(Node: Вставляет новый узел с текстом S как TTreeNode; const S: string): первый из дочерних узлов узла Node. Индексы последующих узлов увеличиTTreeNode; ваются на 1. Каждый из этих методов возвращает вставленный узел. Ниже в качестве примера приведен код, формирующий то же дерево, которое вы можете видеть на рис. 3.19 и 3.20. TreeViewl.Items.Clear; // очистка списка // добавление корневого узла 'производство' T r e e V i e w l . I t e m s . A d d ( n i l , 'Производство 1 );
(индекс 0)
// добавление дочених узлов 'цех 1' — 'цех 3' (индексы 1—3) T r e e V i e w l . I t e m s . A d d C h i l d ( T r e e V i e w l . I t e m s . I t e m [ 0 ] , 'Цех 1 ' ) ;
Глава 3
174
T r e e V i e w l . I t e m s . A d d C h i l d ( T r e e V i e w l . I t e m s . I t e m [ 0 ] , 'Цех 2' ) ; T r e e V i e w l . I t e m s . A d d C h i l d ( T r e e V i e w l . I t e m s . I t e m [ 0 ] , 'Цех 3' ) ; / добавление корневого узла 'управление' после узла 'производство ' (индекс 4) } TreeViewl.Itemsi.Add(TreeViewl.Items.Item[0], 'Управление'); { добавление дочених узлов 'администрация' и 'бухгалтерия' узла 'управление' } TreeViewl.Items.AddChild( T r e e V i e w l . I t e m s . I t e r n [ 4 ] , 'Администрация'); TreeViewl.Items.AddChild( T r e e V i e w l . I t e m s . I t e m [ 4 ] , 'Бухгалтерия'); Дерево может быть сколь угодно разветвленным. Например, следующие операторы добавляют дочерние узлы «бригада 1» и «бригада 2» в сформированный ранее узел «Цех 1»: T r e e V i e w l . I t e m s . A d d C h i l d ( T r e e V i e w l . I t e m s . I t e m [ l ] , 'бригада 1 T r e e V i e w l . I t e m s . A d d C h i l d ( T r e e V i e w l . I t e m s . I t e m [ l ] , 'бригада 2 Текст, связанный с некоторым узлом, можно найти с помощью его свойства Text. Например, TreeViewl.Items.Item[l].Text — это надпись «Цех 1». С каждым узлом может быть связан некоторый объект. Добавление таких узлов осуществляется методами AddObject, AddObjectFirst, InsertObject, AddChildObject, AddChildObjectFirst, аналогичными приведенным выше, но содержащими в качестве параметра еще указатель на объект: function AddObject(Node: TTreeNode; const S: string; Ptr: Pointer): TreeNode;
Добавляет новый узел с текстом S и объектом Ptr как последний узел уровня, на котором расположен Node.
function AddObjectFirst(Node: TTreeNode; const S: string; Ptr: Pointer): TTreeNode;
Вставляет новый узел с текстом S и объектом Ptr как первый из узлов уровня, на котором находится Node. Индексы последующих узлов увеличиваются на 1.
function InsertObject(Node: TTreeNode; Вставляет новый узел с текстом S и const S: string; Ptr: Pointer): TTreeNode; объектом Ptr сразу после узла Node на то же уровень. Индексы последующих узлов увеличиваются на 1. function AddChildObject(Node: TTreeNode; Добавляет узел с текстом S и объconst S: string; Ptr: Pointer): TTreeNode; ектом Ptr как последний дочерний узла Node. function AddChildObjectFirst(Node: Вставляет новый узел с текстом S и TTreeNode; объектом Ptr как первый из дочерconst S: string; Ptr: Pointer): TTreeNode; них узлов узла Node. Индексы последующих узлов увеличиваются на 1. Объект, связанный с некоторым узлом, можно найти с помощью его свойства Data. Например, TreeViewl.Items.Item[l].Data. Для удаления узлов имеется два метода: Clear, очищающий все дерево, и Delete(Node: TTreeNode), удаляющий указанный узел Node и все его узлы — потомки. Например, оператор TreeViewl.Items.Clear; удалит в нашем примере все узлы, а оператор
Обзор компонентов библиотеки VCL Delphi
175
TreeViewl.Iterns.Delete(TreeViewl.Items.Item[I])•
удалит узел «Цех 1» и его дочерние узлы (если они имеются). При удалении узлов, связанных с объектами, сами эти объекты не удаляются. Реорганизация дерева, связанная с созданием или удалением многих узлов, может вызывать неприятное мерцание изображения. Избежать этого можно с помощью методов BeginUpdate и EndUpdate. Первый из них запрещает перерисовку дерева, а второй — разрешает. Таким образом, изменение структуры дерева может осуществляться по следующей схеме: TreeViewl.Items.BeginUpdate; TreeViewl.Items.EndUpdate;
Если метод BeginUpdate применен подряд несколько раз, то перерисовка дерева произойдет только после того, как столько же раз будет применен метод EndUpdate. Среди свойств узлов следует отметить Count — число узлов, управляемых данным, т.е. дочерних узлов, их дочерних узлов и т.п. Если значение Count узла равно нулю, значит у узла нет дочерних узлов, т.е. он является листом дерева. Вернемся к свойствам компонента TreeView. Важным свойством компонента Tree View является Selected. Это свойство указывает узел, который выделен пользователем. Пользуясь этим свойством можно запрограммировать операции, которые надо выполнить для выбранного пользователем узла. Если ни один узел не выбран, значение Selected равно nil. При выделении пользователем нового узла происходят события OnChanging (перед изменением выделения). В обработчик события передаются параметры Node: TTreeNode — узел, который выделен в данный момент, и var AllowChange: Boolean — разрешение на перенос выделения. Если в обработчике задать AllowChange = false, то переключение выделения не произойдет. У компонента TreeView имеется свойство RightClickSelect, разрешающее (при значении равном true) выделение узла щелчком как левой, так и правой кнопкой мыши. Ряд событий компонента TreeView связан с развертыванием и свертыванием узлов. При развертывании узла происходят события OnExpanding (перед развертыванием) и OnExpanded (после развертывания). В обработчики обоих событий передается параметр Node: TTreeNode •— развертываемый узел. Кроме того в обработчик OnExpanding передается параметр var AllowExpansion: Boolean, который можно задать равным false, если желательно запретить развертывание. При свертывании узла происходят события OnCollapsing (перед свертыванием) и OnCollapsed (после свертывания). Так же, как и в событиях, связанных с развертыванием, в обработчики передается параметр Node: TTreeNode — свертываемый узел, а в обработчик OnCollapsing дополнительно передается параметр var AllowCollapse: Boolean, разрешающий или запрещающий свертывание. Свойство ReadOnly компонента TreeView позволяет запретить пользователю редактировать отображаемые данные. Если редактирование разрешено, то при редактировании возникают события OnEditing и OnEdited, аналогичные рассмотренным ранее (в обработчике OnEditing параметр var AllowEdit: Boolean позволяет запретить редактирование). Несколько свойств компонента TreeView: ShowButtons, ShowLines, ShowRoot отвечают за изображение дерева и позволяют отображать или убирать из него кнопки, показывающие раскрытия узла, линии, связывающие узлы, и корневой узел. Поэкспериментируйте с этими свойствами и увидите, как они влияют на изображение. Свойство SortType позволяет автоматически сортировать ветви и узлы дерева. По умолчанию это свойство равно stNone, что означает, что дерево не сортируется. Если установить SortType равным stText, то узлы будут автоматически сортиро-
176
Глава 3
ваться по алфавиту. Возможно также проводить сортировку по связанным с узлами объектам Data (значение SortType равно stData), одновременно по тексту и объектам Data (значение SortType равно stBoth) или любым иным способом. Для использования этих возможностей сортировки надо написать обработчик события OnCompare, в который передаются, в частности, параметры Nodel и Node2 сравниваемые узлы, и по ссылке передается целый параметр Compare, в который надо заносить результат сравнения: отрицательное число, если узел Nodel должен располагаться ранее Node2, 0, если эти узлы считаются эквивалентными, и положительное число, если узел Nodel должен располагаться в дереве после Node2. Например, следующий оператор в обработчике события OnCompare обеспечивает обратный алфавитный порядок расположения узлов: Compare:= - AnsiCompareText(Nodel.Text, N o d e 2 . T e x t ) ;
События OnCompare наступают после задания любого значения SortType, отличного от stNone, и при изменении пользователем свойств узла (например, при редактировании им надписи узла), если значение SortType не равно stNone. После сортировки первоначальная последовательность узлов в дереве теряется. Поэтому последующее задание SortType = stNone не восстанавливает начальное расположение узлов, но исключает дальнейшую генерацию событий OnCompare, т.е. автоматическую перестановку узлов, например, при редактировании их надписей. Если же требуется изменить характер сортировки или провести сортировку с учетом новых созданных узлов, то надо сначала задать значение SortType = stNone, a затем задать любое значение SortType, отличное от stNone. При этом будут сгенерированы новые обращения к обработчику событий OnCompare. Имеются и другие возможности сортировки. Например, метод AlphaSort обеспечивает алфавитную последовательность узлов независимо от значения SortType, но при отсутствии обработчика событий OnCompare (если обработчик есть, то при выполнении метода AlphaSort происходит обращение к этому обработчику). Отличие метода AlphaSort от задания значения SortType = stText заключается в том, что изменение надписей узлов приводит к автоматической пересортировке дерева только при SortType = stText. Мы рассмотрели основные возможности компонента TreeView. Компонент Outline похож на него. Структура дерева тоже содержится в свойстве Items и доступ к отдельным узлам также осуществляется через этот индексный список узлов. Но индексы начинаются с 1. Например, Outlinel.Items[l] — это узел дерева с индексом 1 (первый узел дерева). Свойство Items имеет тип TOutlineNode. Его свойства и методы отличаются от свойств и методов типа узлов в TreeView. И заполняется структура дерева иначе: через свойство Lines типа TStrings. Редактор этого свойства можно вызвать, щелкнув на кнопке с многоточием около свойства Lines в окне Инспектора Объектов. Вы попадете в окно, в котором можете записать тексты всех узлов, делая отступы, чтобы выделить уровни узлов. Текст должен выглядеть так, как выше описывалось представление структуры в текстовом файле. Пиктограммы, сопровождающие изображения узлов, задаются параметрами PictureOpen (пиктограмма развернутого узла), PictureClosed (пиктограмма свернутого узла), Picture Minus (пиктограмма символа «-» около развернутого узла, имеющего наследников), PicturePlus (пиктограмма символа «+» узла, имеющего наследников, но не развернутого), PictureLeaf (пиктограмма узла, не имеющего наследников — листа дерева). Основное отличие пиктограмм компонента Outline заключается в том, что они одинаковы для всех узлов одного типа, тогда как в TreeView можно задавать пиктограммы индивидуально для каждого узла. Программно изменять структуру дерева можно с помощью методов Add, AddObject (добавление узла в дерево), Insert, InsertObject (вставка узла в заданную позицию), AddChild, AddChildObject (вставка дочернего узла), Delete (удаление узла).
177
Обзор компонентов библиотеки VCL Delphi
Индекс выделенного пользователем узла можно определить через свойство Selectedltem. Если Selectedltem = 0, значит ни один узел не выделен. Текст выделенного узла определяется свойством Text: например, OutLinel.Items[Outlinel.Selectedltem].Text Впрочем, тот же самый текст даст и выражение OutLinel.Lines[Outlinel.Selectedltem-1]
3.5.3 Компонент ListView Компонент ListView позволяет отображать данные в виде списков, таблиц, крупных и мелких пиктограмм. С подобным отображением все вы сталкиваетесь, раскрывая папки Windows. Стиль отображения информации определяется свойством ViewStyle, которое может устанавливаться в процессе проектирования или программно во время выполнения. Свойство может принимать значения: vslcon — крупные значки (см. рис. 3.21 a), vsSmalllcon — мелкие значки, vsList — список (см. рис. 3.21 б), vsReport — таблица (см. рис. 3.21 в). Что означает каждое из этих значений, вы можете посмотреть не только на рис. 5.3, но и в любой папке Windows на рабочем столе. Рис. 3.21
Пример компонента ListView в режимах vslcon (a), vsList (б) и vsReport (в)
а)
II/ListView
НЯШШ
б)
Вид Выравнивание
По
по
УЩЦИК
_Ю1х|
Вид Выравнивание
По
ПО Объект! ЕЮ Объект 2 ЕЮ Объект 3 ПО Объект 4
Объект 1 Объект 2 Объект 3 00
Объект 4
в) Вид Выравнивание Объект
Подр. 1
Подр. 2 1
ПО Объект! . 0О Объект 2 ИаЮбьектЗ! ПО Объект 4
Цех! Цех! Цех 5 Цех 4
Цех 2 Цех 2
Рассмотрим основные свойства и методы компонента ListView. Начните новое приложение и перенесите на него ListView. Основное свойство компонента ListView, описывающее состав отображаемой информации — Items. Во время проектирования оно может быть установлено специальным редактором (рис. 3.22), вызываемым щелчком на кнопке с многоточием рядом с этим свойством в окне Инспектора Объектов. Окно редактора похоже на окно, описанное для компонента TreeView (рис. 3.20). Точно так же в нем задаются новые узлы кнопкой New Item и дочерние узлы — кнопкой New Subltem. Только смысл дочерних узлов другой: это информация, которая появляется только в режиме vsReport — в виде таблицы. Задайте в редакторе некоторое множество элементов и дочерних узлов. Смысл их зависит от конкретной задачи. Это могут быть некоторые изделия и цеха, в которых они должны проходить обработку, или это могут быть товары и пункты,
Глава 3
178
куда их надо отправить, и т.п. На рис. 3.22 указаны некоторые абстрактные объекты и цеха. Рис. 3.22 Окно редактора списка объектов компонента ListView
\Ъ ListView Hems Editor i • Item Ptopetties
iems . . . . ;
; !••• Цех 1 ! -•• Цех 2 ffl Объект 2 В Объект 3
] .
New Item
i I Caption:
New Subl tern | ,.J
OK
Delete Cancel
(Объект 1
•[•.'
Irnage Index: |0
I State Index: j-1 J
S№iy
|
Help
j
Для каждого узла задается свойство Caption — надпись, появляющаяся около пиктограммы. Для дочерних узлов это свойство соответствует надписи, появляющейся в ячейках таблицы в режиме vsReport (см. надписи вида «Цех ...» на рис. 3.21 в). Свойство Image Index определяет индекс пиктограммы. Индекс соответствует спискам изображений, хранящимся в отдельных компонентах ImageList (см. разд. 4.2). Указания на эти компоненты вы можете задать в свойствах Largelmages для режима vslcon и Smalllmages для режимов vsSmalllcon, vsList и vsReport. Индексы начинаются с 0. Если вы укажете индекс -1 (значение по умолчанию), пиктограммы изображаться не будут. Свойство State Index в панели Item Properties позволяет добавить вторую пиктограмму в данный объект. Подобная пиктограмма может просто служить дополнительной характеристикой объекта. Индекс, указываемый как State Index, соответствует списку изображений, хранящихся в отдельном компоненте ImageList, указанном в свойстве Statelmages компонента ListView. Остановимся пока на этих свойствах и построим приложение, предоставляющее пользователю возможность изменять вид списка в окне ListView и, кроме того, позволяющее при стилях vslcon и vsSmalllcon перетаскивать пиктограммы мышью в любое место окна. Для реализации такого приложения нам потребуется компонент меню MainMenu (см. разд. 3.8.1). Кроме того для понимания приведенного ниже текста надо представлять себе технологию перетаскивания Drag&Drop. Эта технология подробно рассмотрена в гл. 5 разд. 5.4.1. Так что, если вы с ней незнакомы, или пока не акцентируйте на ней внимание, или вернитесь к этому примеру после знакомства с гл. 5. Для реализации примера надо сделать следующее: • Введите в приложение разделы меню Крупные значки (пусть его имя будет Micon), Мелкие значки (имя MSmalllcon), Список (имя MList) и Таблица (имя MReport) • Установите во всех этих разделах одинаковый отличный от нуля индекс Grouplndex и свойства Radioltem в true Напишите следующие обработчики щелчков для этих разделов: procedure TForml.MIconClick(Sender: T O b j e c t ) ; begin L i s t V i e w l . V i e w S t y l e := vslcon; MI con.Checked := true; ListViewl.DragMode := dmAutomatic; end; procedure TForml.MSmalllconClick(Sender: begin L i s t V i e w l . V i e w S t y l e := vsSmalllcon;
TObject);
Обзор компонентов библиотеки VCL Delphi
,
179
MSmalllcon.Checked := true; ListViewl.DragMode := dmAutomatic; end; procedure TForml.MListClick(Sender: TObject); begin ListViewl.ViewStyle := vsList; MList.Checked := true; ListViewl.DragMode := dmManual; end; procedure TForml.MReportClick(Sender: TObject); begin ListViewl.ViewStyle, := vsReport; MReport.Checked := true; ListViewl.DragMode := dmManual; end;
В обработчике события формы OnCreate (наступает в момент создания формы в начале выполнения приложения) сошлитесь на тот из этих обработчиков (например, на MIconClick), который соответствует способу отображения, желательному в первый момент выполнения. Напишите следующие обработчики событий OnDragOver и OnDragDrop компонента ListView: procedure TForml.ListViewlDragOver(Sender, Source: TObject; X, Y:, Integer; State: TDragState; var Accept: Boolean); begin Accept := (Source = ListViewl); end;
|
procedure TForml.ListView.lDragDrop(Sender, Source: TObject; X, Y: Integer); begin ListViewl.Selected.Position := Point(X,Y); end;
Эти обработчики, так же, как задание в предыдущих обработчиках значения свойства DragMode, связано с технологией Drag&Drop. Реализуйте этот пример. Вы увидите, что создали окно, имеющее много общего с обычными папками Windows, включая возможность перемещать объекты мышью (это и есть Drag&Drop). Но после перемещений желательно иметь возможность упорядочить пиктограммы. Для упорядочивания служит метод Arrange: procedure A r r a n g e ( C o d e :
TListArrangement);
Он упорядочивает пиктограммы в режимах vslcon и vsSmalllcon. Параметр Code определяет способ упорядочивания: arAlignBottom arAlignLeft arAlignRight arAlignTop arDefault arSnapToGrid
выравнивание вдоль нижнего края области выравнивание вдоль левого края области выравнивание вдоль правого края области выравнивание вдоль верхнего края области выравнивание по умолчанию (вдоль верхнего края области) размещение каждой пиктограммы в ближайшем узле сетки
Глава 3
180
Вы можете ввести в свое тестовое приложение раздел Выравнивание и в обработчик щелчка на нем записать оператор L i s t V i e w l .Arrange (arAlignTop) ;
Тогда после перетаскивания элементов вы всегда сможете опять упорядочить их расположение, выбрав этот раздел меню. Способ упорядочивания определяется соответствующим заданием свойства SortType, которое уже рассматривалось нами для компонента Tree View. Свойство Checkboxes, установленное в true, определяет отображение индикатора с флажком около каждого элемента списка (см. рис. 3.21). Но учтите, что свойство срабатывает, только если вы не установили описанное ранее свойство Statelmages. Иначе говоря, около пиктограммы может появляться или индикатор, или дополнительная пиктограмма. Индикаторы элементов можно устанавливать программно или их может изменять пользователь во время выполнения. Тогда узнать программно, установлен ли индикатор в некотором элементе Items[i], можно проверкой его свойства Checked. Например: for i := 0 to L i s t V i e w l . Items .Count - 1 do if ( L i s t V i e w l . Items. Item [i] .Checked) then ShowMessage( 'Выбран элемент ' + ListViewl. Items. Item[i] .Caption) ;
Свойства HotTrack и HotTrack Styles определяют появление выделения при перемещении курсора над элементом списка и стиль этого выделения. Свойство HoverTime задает в миллисекундах -задержку появления такого выделения. Свойство списка Selected определяет выделенный пользователем элемент списка. Этим можно воспользоваться для выполнения каких-то действий. Например, если требуются некие действия при двойном щелчке на каком-то элементе, то в обработчике события OnDblClick компоненты ListView можно написать оператор: if
( L i s t V i e w l . Selected о n i l ) then ShowMessage (ListViewl . Selected. Caption) ;
Оператор if проверяет, выделен ли какой-то элемент, т.е. произведен ли двойной щелчок на элементе, а не просто на пустом поле списка. Если щелчок на элементе, то с ним можно провести какие-то действия (в данном примере вместо этого просто отображается сообщение). Свойство Columns определяет список заголовков таблицы в режиме vsReport (см. заголовки «Объект», «подр. 1», «подр. 2» на рис. 3.21 в) при свойстве ShowColumnHeaders (показать заголовки), установленном в true. Свойство Columns можно задать в процессе проектирования специальным редактором заголовков, вызываемом двойным щелчком на компоненте ListView или щелчком на кнопке с многоточием рядом со свойством Columns в окне Инспектора Объектов. В обоих случаях перед вами откроется окно редактора заголовков, представленное на рис. 3.23. Кнопка Add New (крайняя левая) позволяет добавить новую секцию в заголовок, кнопка Delete Selected (вторая слева) — удалить секцию, кнопки Move Selected Up и Move Selected Down (кнопки со стрелками) позволяют изменять последовательность секций. Рис. 3.23 Окно редактора заголовков
?Ed,tmgU*tVieW|.C
1 -Подр. 1 2 • Подр. 2
181
Обзор компонентов библиотеки VCL Delphi
После того, как вы добавили секцию и установили на ней курсор, в окне Инспектора Объектов появится множество свойств этого объекта. В свойстве Caption вы можете задать текст заголовка. В свойстве Imagelndex можете указать индекс пиктограммы, которая появится перед заголовком. Свойства MinWidth и MaxWidth определяют соответственно минимальную и максимальную ширину заголовка в пикселах. Только в этих пределах пользователь может изменять ширину заголовка курсором мыши. Значение ширины по умолчанию задается значением свойства Width. При изменении ширины секции во время выполнения генерируется событие OnSectionResize. Начиная с Delphi 5, введено еще одно свойство ListView — WorkAreas. Это свойство определяет рабочую область (прямоугольную рамку), в которой осуществ.ляется выравнивание пиктограмм в режимах vslcon и vsSmalllcon. СВОЙСТРО WorkAreas представляет собой индексированный список, аналогичный Items, но ; совершенно независимый от него. Если WorkAreas — пустой список (ни одна об ласть в него не добавлена), то упорядочивание пиктограмм производится в пределах всей клиентской области ListView. Добавление новой рабочей области осуществляется методом Add. Свойство рабочей области Rect типа TRect (см. разд. 17.4) определяет границы области. Свойство Color определяет цвет рамки, обрамляющей область. Свойство DisplayName определяет подпись под рамкой. И рамка, и подпись отображаются только в случае, если свойство списка ShowWorkAreas установлено в true. Ниже приведен пример операторов, создающих в площади списка две рабочие области: ListViewl WorkAreas .Add(); ListViewl WorkAreas .Items[0].Rect := Rect (20,0,ListViewl.ClientWidth div 4 + 20, ListViewl.ClientHeight - 20); ListViewl WorkAreas .Items[0] .DisplayName := 'Область О' ; ListViewl WorkAreas .Items[0].Color := clRed; ListViewl WorkAreas .Add(); ListViewl WorkAreas . I t e m s [ 1 ] . R e c t := Rect { L i s t V i e w l . C l i e n t W i d t h div 2 + 20, 0 , ( L i s t V i e w l . C l i e n t W i d t h div 4 ) * 3 + 2 0 , ListViewl .ClientHeight - 20).; ListViewl.WorkAreas . I t e m s [ 1 ] . D i s p l a y N a m e := 'Область 1';
Результат применения этих операторов в тестовом примере показан на рис. 3.24. При заданных рабочих областях упорядочивание пиктограмм происходит в пределах той области, в которой они находятся. Если вы разрешили пользователю перетаскивать пиктограммы, как было описано ранее, то вид выравнивания будет зависеть от расположения пиктограмм. Если одна или несколько из них расположены в пределах одной области, а другие размещаются вне рабочих областей, то при вызове метода Arrange все они расположатся в пределах той области, в которой была хоть одна пиктограмма. Если же пиктограммы были расположены в нескольких рабочих областях, то они будет упорядочиваться в пределах областей их размещения. Рис. 3.24 Упорядочивание в нескольких рабочих областях
Вид Выравнивание
: Объект 1
Объект 3
: Объект 2
[Объект*
б&МТкО
Эбласть 1
Па )
Глава 3
182
Большинство остальных свойств, методов и событий в компоненте List View те же, что в описанном в разд. 3.5.2 компоненте Tree View. Компонент ListView можно использовать для отображения самой различной информации, в частности, для отображения каталогов и файлов. Вы можете посмотреть подобный пример VirtualListView, поставляемый вместе с Delphi. Однако начиная с Delphi 6, в библиотеке на странице Scruples появились компоненты, с помощью которых гораздо проще реализовать работу с папками и файлами. Эти компоненты будут рассмотрены в разд. 3.10.3.
3.6 Отображение графической и мультимедиа информации 3.6.1 Перечень компонентов отображения графической информации Для отображения графической информации в библиотеке Delphi предусмотрены компоненты, список которых дан в табл. 3.6. Таблица 3.6. Компоненты отображения графической информации Пикто- Компонент грамма
н if
Image (изображение)
Страница
Описание
Additional
Используется для отображения графики: пиктограмм, битовых матриц и метафайлов.
PaintBox System (окно для рисования)
Используется для создания на форме некоторой области, в которой можно рисовать.
_k щ.
DrawGrid (таблица рисунков)
*
Additional Chart (диаграммы и графики)
Компонент принадлежит к семейству компонентов TeeChart, которые используются для создания диаграмм и графиков.
ActiveX Chartfx (диаграммы и графики)
Редактор диаграмм и графиков.
3®
FIBook (страницы Excel)
ActiveX
Компонент ввода и обработки числовой информации (в том числе в графическом виде), аналогичный страницам Excel.
Цг
VtChart (диаграммы)
ActiveX
Окно построения диаграмм.
ц
Additional
Используется для отображения в строках и столбцах нетекстовых данных.
Кроме того, отображать и вводить графическую информацию можно на поверхности любого оконного компонента, имеющего свойство Canvas — канва. Компоненты Image и PaintBox подробно рассмотрены в гл. 6. Там же рассматриваются вопросы рисования на канве Canvas.
Обзор компонентов библиотеки VCL Delphi
183
3.6.2 Таблицы изображений — компоненты DrawGrid и StringGrid Компонент DrawGrid используется для создания в приложении таблицы, которая может содержать графические изображения (см. пример на рис. 3.25). Этот компонент подобен компоненту StringGrid (см. разд. 3.2.6), поскольку последний является производным от DrawGrid. Поэтому в DrawGrid присутствуют все свойства, методы, события компонента StringGrid, кроме относящихся к тексту, т.е. кроме свойств Cells, Cols, Rows, Objects. С этой точки зрения компонент StringGrid обладает существенно большими возможностями, чем DrawGrid, поскольку он может хранить в ячейках и изображения, и тексты. А если вы захотите внести текст в какие-то ячейки DrawGrid, то вам надо будет использовать для этого методы вывода текста на канву, что не очень удобно. Рис. 3.25
"f/ Таблица Orawl.nd
Пример таблицы DrawGrid
Ц ! «с
:Е
а. w % л. !** • «
Н
Ш
И : & ', -
-.
У м
:
н'
: "
V • X
Рассмотрим свойства компонентов DrawGrid и StringGrid, относящиеся к изображениям, поскольку свойства StringGrid, относящиеся к тексту, уже рассматривались в разд. 3.2.6. Компоненты DrawGrid и StringGrid имеют канву Canvas, на которой можно размещать изображения методами, изложенными в гл. 6. Имеется метод CellRect, который возвращает область канвы, отведенную под заданную ячейку. Этот метод определен как function CellRect(ACol, ARow: L o n g i n t ) : TRect;
где ACol и ARow — индексы столбца и строки, начинающиеся с 0, на пересечении которых расположена ячейка. Возвращаемая этой функцией область является областью канвы, в которой можно рисовать необходимое изображение. Например, оператор DrawGridl.Canvas.CopyRect(DrawGridl.CellRect(1,1), BitMap.Canvas,Rect(0,0,BitMap.Height,BitMap.Width)); копирует методом CopyRect (см. гл. 6 разд. 6.1.5.2) в ячейку (1,1) таблицы DrawGridl изображение из компонента BitMap. Эта ячейка является второй слева и второй сверху в таблице, поскольку индексы начинаются с 0. Учтите, что если размеры ячейки меньше, чем размер копируемого изображения, то в ячейке появится только левая верхняя часть картинки. Изображение на канве компонентов DrawGrid и StringGrid, как и на канве любого компонента, подвержено стиранию при перекрытии окна приложения другими окнами или, например, при сворачивании приложения. Поэтому необходимо принимать меры, описанные в гл. 6 в разд. 6.1.7, чтобы с помощью обработчика событий OnPaint восстанавливать испорченное изображение. Это делает компонент DrawGrid не слишком удобным для использования.
184
Глава 3
Удобным способом занесения изображений в ячейки DrawGrid является использование обработчика событий OnDrawCell. Эти события наступают для каждой ячейки таблицы в момент ее перерисовки. Заголовок обработчика имеет вид: procedure TForml.DrawGridlDrawCell(Sender: TObject; . ACol, ARow: Integer; Rect: TRect; State: TGridDrawState)
Параметры ACol и ARow — это номер столбца и строки, на пересечении которых расположена перерисовываемая ячейка. Параметр Rect типа TRect (см. разд. 17.4).определяет область данной ячейки на канве. В эту область надо заносить изображение. Параметр State указывает состояние ячейки. Он является множеством, которое может содержать следующие элементы: gdSelected — выделенная ячейка, gdFocused — ячейка, находящаяся в фокусе, gdFjxed — ячейка в фиксированной области таблицы. Параметр State можно использовать для различного характера отображения ячеек в различных состояниях. Например, if (gdFocused in State) then ... else . . -
Приведем пример использования обработчика событий OnDrawCell. Следующий обработчик обеспечивает заполнение ячеек таблицы изображениями, хранящимися в компоненте ImageListl (см. разд. 4.2): procedure TForml.DrawGridlDrawCell(Sender: TObject; ACol, ARow: Integer; Rect: TRect; State: TGridDrawState); var ind: word; begin ind := ARow * DrawGridl.ColCount + ACol; if (ind О ImageListl.Count — 1) then ImageListl.Draw(DrawGridl.Canvas, Rect.Left+10, Rect.Top+10, ind, t r u e ) ; end;
Первый оператор этого кода определяет по значениям ARow и ACol с учетом числа столбцов таблицы ColCount индекс того изображения в ImageListl, которое должно размещаться в данной ячейке. Если этот индекс не превышает длины списка, то соответствующее изображение рисуется в ячейке методом Draw компонента ImageListl, описанным в разд. 4.2. При этом делаются отступы по 10 пикселов от левой и верхней границ области рисования. Именно таким кодом получено изображение, показанное выше на рис. 3.25. Все свойства и события, позволяющие определить выбранную пользователем ячейку таблицы DrawGrid, были рассмотрены в разд. 3.2.6. Там же вы найдете описание свойств, отвечающих за внешний вид и допустимость перестройки пользователем таблицы во время выполнения приложения. i
3.6.3 Компонент Shape Компонент Shape только условно может быть отнесен к средствам отображения графической информации, поскольку просто представляет собой различные геометрические фигуры, соответствующим образом заштрихованные. Основное свойство этого компонента — Shape (форма), которое может принимать значения: stRectangle
прямоугольник
stSquare
квадрат
stRoundRect прямоугольник со stRoundSquare скругленными углами
квадрат со скругленными углами
stEllipse
круг
эллипс
stCircle
Обзор компонентов библиотеки VCL Delphi
185
Примеры этих форм показаны на рис. 3.26. Рис. 3.26 Примеры компонента Shape
Shape = stEllipse liush.Style - biBDiagonal
Shape » stSquate Biu«h Style - biCleai
Shape - liRoundSquaie Biush.Styte • bsCross
Shape - «(Rectangle Biuth Style - btDiagCiots
Shape - itRoundReci Bruth Style * btFDiagonal
Другое существенное свойство компонента — Brush (кисть). Это свойство является объектом типа TBrush, имеющим ряд подсвойств, в частности: цвет (Brush.Color) и стиль (Brush.Style) заливки фигуры. Заливку при некоторых значениях Style вы можете видеть на рис. 3.26. Третье из специфических свойство компонента Shape — Pen (перо), определяющее стиль линий. Свойства Brush и Реп подробно рассмотрены в гл. 6. Справочные данные об этих свойствах вы можете найти в гл. 17.
3.6.4 Компонент Chart Теперь рассмотрим компонент Chart. Этот компонент позволяет строить различные диаграммы и графики, которые выглядят очень эффектно (рис. 3.27). Компонент Chart имеет множество свойств, методов, событий, так что если все их рассматривать, то этому пришлось бы посвятить целую главу. Поэтому ограничимся рассмотрением только основных характеристик Chart. А с остальными вы можете ознакомиться во встроенной справке Delphi или просто опробовать их, экспериментируя с диаграммами. Полное изложение объектов, методов, свойств, событий, связанных с Chart, вы найдете также в одной из справок, входящих в систему [4]. Компонент Chart является контейнером объектов Series — наследников класса TChartSeries. Каждый такой объект представляет серию данных, характеризующихся определенным стилем отображения: тем или иным графиком или диаграммой. Каждый компонент Chart может включать несколько серий. Если вы хотите отображать график, то каждая серия будет соответствовать одной кривой на графике. Если вы хотите отображать диаграммы, то для некоторых видов диаграмм можно наложить друг на друга несколько различных серий, для других (например, для круговых диаграмм) это, вероятно, будет выглядеть некрасиво. Однако и в этом случае вы можете задать для одного компонента Chart несколько серий одинаковых данных с разным типом диаграммы. Тогда, делая в каждый момент времени активной одну из них, вы можете предоставить пользователю выбор типа диаграммы, отображающей интересующие его данные. Разместите один или два (если захотите воспроизвести рис. 3.27) компонента Chart на форме и посмотрите открывшиеся в Инспекторе Объектов свойства. Приведем пояснения некоторых из них.
Глава 3
186
б)
Печать ДИАГРАММА ПРОДУКЦИИ ПОДРАЗДЕЛЕНИЙ
:
{к Демонстрация компонент а Ш«4 Печать
ДИАГРАММА ПРОДУКЦИИ ПОДРАЗДЕЛЕНИЙ
ПОЗ
Вклад Е зыпуск продукции в ' ГРАФИКИ SIN И COS
Оклад s выпуск продукции Б % ГРАФИКИ SJN И <
Рис. 3.27. Пример приложения с диаграммами: начальное состояние (а) и состояние при изменении типа диаграммы и увеличении фрагмента графика (б)
AllowPanning
Определяет возможность пользователя прокручивать наблюдаемую часть графика во время выполнения, нажимая правую кнопку мыши. Возможные значения: pmNone — прокрутка запрещена, pmHorizontal, pm Vertical или pmBoth — разрешена соответственно прокрутка только в горизонтальном направлении, только в вертикальном или в обоих направлениях.
AllowZoom
Позволяет пользователю изменять во время выполнения масштаб Изображения, вырезая фрагменты диаграммы или графика курсором мыши (на рис. 3.27 б внизу показан момент просмотра фрагмента графика, целиком представленного на рис. 3.27 а). Если рамка фрагмента рисуется вправо и вниз, то этот фрагмент растягивается на все поле графика. А если рамка рисуется вверх и влево, то восстанавливается исходный масштаб. Определяет заголовок диаграммы.
Title Foot Frame Legend Margin Left, MarginRight, MarginTop, MarginBottom BottomAxis, LeftAxis, RightAxis
Определяет подпись под диаграммой. По умолчанию отсутствует. Текст подписи определяется подсвойством Text. Определяет рамку вокруг диаграммы. Легенда диаграммы — список обозначений. Значения левого, правого, верхнего и нижнего полей.
Эти свойства определяют характеристики соответственно нижней, левой и правой осей. Задание этих свойств имеет смысл для графиков и некоторых типов диаграмм.
187
Обзор компонентов библиотеки VCL Delphi
LeftWall, BottomWall, BackWall
Эти свойства определяют характеристики соответственно левой, нижней и задней граней области трехмерного отображения графика (см. рис. 3.27 а, нижний график).
SeriesList
Список серий данных, отображаемых в компоненте.
ViewSd
Разрешает или запрещает трехмерное отображение диаграммы.
ViewSDOptions
Характеристики трехмерного отображения.
ChartSDPercent Масштаб трехмерности (для рис. 3.27 это толщина диаграммы и ширина лент графика). Рядом со многими из перечисленных свойств в Инспекторе Объектов расположены кнопки с многоточием, которые позволяют вызвать ту или иную страницу Редактора Диаграмм - - многостраничного окна, позволяющего установить все свойства диаграмм. Вызов Редактора Диаграмм возможен также двойным щелчком на компоненте Chart или щелчком на нем правой кнопкой мыши и выбором команды Edit Chart во всплывшем меню. Если вы хотите попробовать воспроизвести приложение, показанное на рис. 3.27, сделайте двойной щелчок на верхнем компоненте Chart. Вы попадете в окно Редактора Диаграмм (рис. 3.28) на страницу Chart, которая имеет несколько закладок. Прежде всего вас будет интересовать на ней закладка Series. Щелкните на кнопке Add — добавить серию. Вы попадете в окно (рис. 3.29), в котором вы можете выбрать тип диаграммы или графика. В данном случае выберите Pie — круговую диаграмму. Воспользовавшись закладкой Titles вы можете задать заголовок диаграммы, закладка Legend позволяет задать параметры отображения легенды диаграммы (списка обозначений) или вообще убрать ее с экрана, закладка Panel определяет вид панели, на которой отображается диаграмма, закладка 3D дает вам возможность изменить внешний вид вашей диаграммы: наклон, сдвиг, толщину и т.д. Рис. 3.28 Редактор Диаграмм, страница Chart, закладка Series
: :
IliLjl Series I Series | General | Axis | Title: | Legend | Panel j Paging j Walls j 3D
(^l* У^ГНзенезД
----J--.-I
J
Add.:...
Belete Titfe... Clone Change...
Help.
Когда вы работаете с Редактором Диаграмм и выбрали тип диаграммы, в компонентах Chart на вашей форме отображается ее вид с занесенными в нее условными данными (см. рис. 3.30). Поэтому вы сразу можете наблюдать результат применения различных опций к вашему приложению, что очень удобно. Страница Series, также имеющая ряд закладок, дает вам возможность выбрать дополнительные характеристики отображения серии. В частности, для круговой диаграммы на закладке Format полезно включить опцию Circled Pie, которая обеспечит при любом размере компонента Chart отображение диаграммы в виде круга. На
Глава 3
188
.=1SJ*J
Рис. 3.29 Выбор типа диаграммы в Редакторе Диаграмм
OK
Рис. 3.30 Форма приложения рис. 3.27 с занесенными в нее условными данными
Cancel
f/ 30
jfe Демонстраций компонентаCtwirt
ДИАГРАММА ПРОДУКЦИИ ПОДРАЗДЕЛЕНИЙ
:
ГГ*Т У -
Вклад в выпуск продукции е * ГРАФИКИ SIN И COS
700 BOO
FSOO 400 300 JOB 10 12 14 16 18 20 22 24 X
закладке Marks кнопки группы Style определяют, что будет написано на ярлычках, относящихся к отдельным сегментам диаграммы: V a l u e — значение, Percent — проценты, Label — названия данных и т.д. В примере рис. 3.27 включена кнопка Percent, а на закладке General установлен шаблон процентов, обеспечивающий отображение только целых значений. Вы можете, если хотите, добавить на этот компонент Chart еще одну тождественную серию, нажав на закладке Series страницы Chart кнопку Clone, а затем для этой новой серии нажать кнопку Change (изменить) и выбрать другой тип диаграммы, например, Bar. Конечно, два разных типа диаграммы на одном рисунке будут выглядеть плохо. Но вы можете выключить индикатор этой новой серии на закладке Series, а потом предоставить пользователю выбрать тот или иной вид отображения диаграммы (ниже будет показано, как это делается).
Обзор компонентов библиотеки VCl Delphi
189
Выйдите из Редактора Диаграмм, выделите в вашем приложении нижний компонент Chart и повторите для него задание свойств с помощью Редактора Диаграмм. В данном случае вам надо будет задать две серии, если хотите отображать на графике две кривые, и выбрать тип диаграммы Line. Поскольку речь идет о графиках, вы можете воспользоваться закладками Axis и Walls для задания координатных характеристик осей и трехмерных граней графика. На этом проектирование внешнего вида приложения завершается. Осталось написать код, задающий данные, которые вы хотите отображать. Для тестового приложения давайте зададим в круговой диаграмме просто некоторые константные данные, а в графиках — функции синус и косинус. Для задания отображаемых значений надо использовать методы серий Series. Остановимся только на трех основных методах. Метод Clear очищает серию от занесенных ранее данных. Метод Add: Add(Const AValue:Double; Const ALabel:String; AColor:TColor)
позволяет добавить в диаграмму новую точку. Параметр AValue соответствует добавляемому значению, параметр ALabel — метка, которая будет отображаться на диаграмме и в легенде, AColor — цвет. Параметр ALabel — не обязательный, его можно задать пустым: ". Метод AddXY: AddXY(Const AXValue, AYValue: Double; Const ALabel: String; AColor: TColor)
позволяет добавить новую точку в график функции. Параметры AXValue и AYValue соответствуют аргументу и функции. Параметры ALabel и AColor те же, что и в методе Add. Таким образом, процедура, обеспечивающая загрузку данных в нашем примере, может иметь вид: const А1=155;
var i:word; begin
With Seriesl do begin Clear; Add (Al , 'Цех I', clYellowl Add (A3, 'Цех 2 ' ,с 1 Blue) ; Add (A3, 'Цех З', clRed) ; Add(A4, 'Ilex 4', clPurple)
end; Series2 .Clear; Series3 .Clear; for i:^0 to 100 do begin Series2.AddXY(0.02*Pi*i,sin^J Г" ПоДЧв(Ж1«гыв
Глава 3
192
Свойство Series на странице Doio Values (рис. 3.32 а) диалога (в Инспекторе Объектов это свойство названо NScries) обозначает число серий данных (на рис. 3.31 равно 2). Свойство Points на той же странице диалога (в Инспекторе Объектов оно названо NValu.es) обозначает число значений по оси аргументов (на рис. 3.31 равно 4). Страница диалога Elements позволяет задать какие-то характерные уровни (опции V a l u e — Text, на рис. 3.31 отмечен уровень 40 с текстом «Минимально допустимый запаса), выделить цветом какие-то полосы уровней (опции From — То — Color, на рис. 3.31 выделены полосы 0 — 20 и 20 — 40), задать текст в строке состояния (опции ID — Width — Text). Прочие свойства позволяют задать тексты вверху диаграммы, внизу, слева, справа, задать координатные сетки и многое другое. Следует обратить внимание на выбор шрифтов на странице Шрифты (рис. 3.32 б). Шрифты, естественно, надо выбрать такие, которые содержат символы кириллицы. При выборе шрифтов надо сначала в выпадающем списке Свойство выбрать надпись, для которой указывается шрифт (например, TopFont — шрифт надписи над диаграммой), а затем в окнах Шрифт, Начертание, Размер установить атрибуты шрифта. Подобную процедуру надо повторить для всех надписей. Теперь давайте коротко рассмотрим некоторые кнопки инструментальной панели компонента во время выполнения приложения (см. рис. 3.31). Первая и вторая слева быстрые кнопки обеспечивают соответственно чтение и сохранение диаграммы. Диаграмма сохраняется в файле с расширением .chf и может быть прочитана в последующих сеансах работы. Третья кнопка слева заносит диаграмму в буфер обмена Clipboard, откуда ее можно взять в каком-то другом приложении (например, в Word) и вставить в документ. Кнопки в центральной части панели позволяют изменять тип диаграммы или графика. Поэкспериментируйте с ними и вы легко поймете, что они означают. Вторая справа группа кнопок позволяет вводить на диаграмме или графике координатную сетку. Правая группа кнопок обеспечивает задание надписей на изображении, выбор шрифта надписей и т.п. Главной из этих кнопок является крайняя правая. Она вызывает выпадающее меню, содержащее, в частности, раздел Data Editor. Если вы выберите этот раздел, вместо диаграммы вы увидите окно редактора данных, отображаемых на графике или в диаграмме (см. рис. 3.33). Сделав двойной щелчок на том или ином числе, вы можете изменить его. После того, как вы отредактировали данные, опять щелкните на правой кнопке инструментальной панели и снимите выделение с раздела Data Editor. Вы опять увидите диаграмму, отображающую введенные вами данные. Рис. 3.33 Редактор отображаемых данных
7 Товар склада 1
Обзор компонентов библиотеки VCL Delphi
193
Компонент YtChart предоставляет пользователю врзможности, близкие к тому, что описаны для Chartfx. Но если в окне Chartfx пользователь работает с инструментальной панелью, что для него привычно, то в VtChart он получает доступ к диалоговым окнам настройки, щелкнув на том или ином элементе диаграммы. Таким образом, компонент VtCbart скорее предназначен для квалифицированного пользователя, желающего построить какую-то сложную диаграмму, чем для рядового пользователя, основная задача которого — работать с данными, представленными в графическом виде. К тому же, в компоненте VtChart возникают проблемы с кириллицей. Исходя из этих соображений, мы не будем подробнее останавливаться на VtChart.
3.6.6 Отображение мультимедиа и иной информации — компоненты Animate, MediaPlayer, ProgressBar, Gauge Краткая характеристика этих компонентов приведена в таблице 3.7. Первые два из этих компонентов, отображают мультимедиа информацию. Компонент Animate воспроизводит системные клипы Windows, такие, как копирование фала или файлов, удаление файлов и т.п. Компонент MediaPlayer является универсальным плеером, воспроизводящим аудио и видео файлы. Оба эти компонента подробно рассмотрены в гл. 6 в разд. 6.2,3 и 6.2.4. Таблица 3.7. Компоненты отображения мультимедиа и иной информации Пикто- Компонент грамма
В
т i"«~
и
Страница Описание
Animate (воспроизведение немых клипов)
Win32
Используется для воспроизведения немых клипов AVI, подобных используемым в Windows клипам копирования файлов и т.п.
MediaPlayer
Syslern
ProgressBar (отображение хода процесса)
Win32
Gauge (отображение хода процесса)
Samples
Используется для создания панели управления воспроизведением звуковых и видео файлов, а также устройств мультимедиа. Используется для отображения хода процессов, занимающих заметное время. Пример компонента, используемого для создания индикатора хода процесса в виде линейки, текста или секторной диаграммы.
(аудио и видео плеер)
Рассмотрим компоненты ProgressBar со страницы библиотеки Win32 и Gauge со страницы Samples, предназначенные для отображения хода процессов, занимающих заметное время, например, копирования больших файлов, настройку приложения, установку приложения на компьютере и т.п. Пример возможных вариантов отображения хода процесса компонентами ProgressBar и Gauge приведен на рис. 3.34. Основные свойства этих компонентов очень схожи, различаясь только именами:
7 Програчмнрование a Delphi 7
Глава 3
194
Свойство Свойство ProgressBar Gauge MaxValue Max
Min
MinValue
Position
Progress
—
Позиция, которую можно задавать по мере протекания процесса, начиная со значения Min или MinValue в начале процесса, и кончая значением Мах или MaxValue в конце. Если минимальное и максимальное значения выражены в процентах, то позиция — это процент завершенной части процесса.
—
Шаг приращения позиции, используемый в методе Steplt. Значение по умолчанию — 10. Ориентация шкалы компонента: pbHorizontat — горизонтальная, pbVertical — вертикальная. Если задана ориентация pbVertical, то компонент надо вытянуть по вертикали (см. на рис. 3.34 компонент слева).
ForeColor ShowText
Цвет заполнения.
Kind
Тип диаграммы: gk Horizontal Bar — горизонтальная полоса, gk Vertical Bar — вертикальная полоса, gkPie — круговая диаграмма, gkNeedle — секторная диаграмма, gkText — отображение текстом.
Orientation
—
Максимальное значение позиции (Position, Progress), которое соответствует завершению отображаемого процесса. По умолчанию задается в процентах — 100. Начальное значение позиции (Position, Progress), которое соответствует началу отображаемого процесса.
Непрерывное (при значении true) или дискретное отображение процесса. На рис. 3.34 в горизонтальном компоненте ProgressBar задано Smooth = true, а в вертикальном — false.
Smooth
Step
Описание
Текстовое отображение процента выполнения на фоне диаграммы.
Рис. 3.34 Пример отображения хода процесса компонентами ProgressBar и Gauge
7 Отображение кода
Обзор компонентов библиотеки VCL Delphi
195
Отображение хода процесса можно осуществлять, задавая значение позиции — Position в ProgressBar или Progress в Gauge. Например, если полная длительность процесса характеризуется значением целой переменной Count (объем всех копируемых файлов, число настроек, количество циклов какого-то процесса), а выполненная часть — целой переменной Current, то задавать позицию диаграммы в случае, если используются значения минимальной и максимальной позиции по умолчанию (т.е. О и 100), можно операторами P r o g r e s s B a r l . P o s i t i o n := 100 * Current div C o u n t ; ИЛИ
Gaugel.Progress := 100 * Current div Count;
соответственно для ProgressBar и Gauge. Можно поступать иначе: задать сначала значение максимальной величины равным Count, а затем в ходе процесса задавать позицию равной Current. Например: Gaugel.MaxValue:=Count; Gaugel.Progress:= Current;
Компонент ProgressBar имеет два метода, которыми тоже можно воспользоваться для отображения процесса: StepBy(Delta: Integer) — увеличение позиции на заданную величину Delta, и Steplt — увеличение позиции на один шаг, величина которого задается свойством Step.
3.7 Кнопки, индикаторы, управляющие элементы 3.7.1 Общая характеристика В данной главе будут рассмотрены такие управляющие элементы, как кнопки, индикаторы и некоторые другие. В табл. 3.8 приведен перечень этих элементов с краткими характеристиками. В этой таблице не указаны аналогичные элементы, связанные с базами данных, так как они будут рассмотрены в гл. 9. Кнопка UpDown уже была рассмотрена в разд. 3.3.2. На рис. 3.35 показаны примеры приведенных в табл. 3.8 компонентов. Рис. 3.35 Пример кнопок и индикаторов
[^ЩВ^^^^НННШНННИ Button I , . Подразделение Е^ыполннгь I
апаш
j
-inixi
fladofifoup
.
p
**""""—
•
Г,*"3
Projectl dp IKE DethiProiKl ' i"*wXP(0] гО.МЗМЗ jPrajeetLdtk EK6 4>aCUi"DSK" S'iePROGIE] yptufctt etc 413 КБ Прилокепие 1 3 01 2003 : a--w> TESTS |F:| "ОН Proiectl les 1 КБ CarpJedRes. . 13.042003 iji UButton dcu 5 КБ Фейя-ОШ" 1904.2003 ! i i|J DATABASE ^ UButton ddo 1 KE Файл "OOP1 19.042003 S s^t DefcW7 21.091938 >UBi*tondfm 5KE DefctvForrr, IfUBdtonpu . 2КБ DeteKSouce... 1904.2003 ^ '^ Aign i E*l CD Arrargs i Й-£) Buttons |
-i
1
1
J
ЦО -J» Д^^цц
'.?'
-Г
«1 »
1
И
должна ли надпись под пиктограммой элемента автоматически переноситься на новую строку, если длина надписи превышает ширину пиктограммы. Свойство Arrangement определяет, как именно располагаются упорядоченные элементы: iaTop
выстраиваются рядами слева направо в верхней части окна
iaLeft
выстраиваются колонками сверху вниз в левой части окна
Свойство ObjectTypes является множеством, определяющим, что должно отображаться в компоненте: ofFolders — папки, ofNonFolders — не папки (ярлыки и файлы), ofHidden — невидимые элементы. Свойство Root определяет корневую папку отображения. При щелчке в окне Инспектора Объектов на кнопке с многоточием около этого свойства, а также просто при двойном щелчке на компоненте Shell List View открывается диалоговое окно, показанное на рис. 3.61. В нем вы можете, включив индикатор Use Standard folder, выбрать одну из стандартных папок в качестве корня: rfDesktop — Рабочий стоп, rfMyComputer — Мой компьютер и т.п. Включение индикатора Use Path позволяет записать или выбрать кнопкой с многоточием путь к любой папке, которая будет восприниматься как корневая.
246
Рис. 3.61 Окно выбора корневой папки
Глава 3 KSdect Root Path
г
Use Standard Folder
rfDesktop
l"f UsePetfi
Cancel
Свойство ColumnClick определяет поведение заголовков столбцов в режиме vsReport как кнопок. При значении true щелчок пользователя на заголовке генерирует событие OnColumnClick, в обработчике которого можно предусмотреть какую-то реакцию. Мы рассмотрели все специфические свойства компонента ShellListView, кроме двух: ShellTreeView и ShellComboBox. Первое из них связывает ShellListView с компонентом ShellTreeView, а второе — с ShellComboBox. Займемся теперь этими компонентами. Компонент ShellTreeView отображает дерево папок и файлов. Впрочем, что именно отображается в дереве, определяется свойством ObjectTypes, аналогичным описанному выше для ShellListView. На рис. 3.60 вы можете видеть компонент ShellTreeView на левой панели, Свойства и события компонента ShellTreeView являются комбинацией рассмотренных выше свойств ShellListView и свойств компонента Tree View (см. разд. 3.5.2). Так что повторять их описание не стоит. Можно отметить только свойство UseShelllmages, определяющее наличие в дереве стандартных пиктограмм Windows для дисков, папок, файлов. Между компонентами ShellListView и ShellTreeView устанавливается с помощью взаимных ссылок двухсторонняя связь. Выделение той или иной папки в ShellTreeView приводит к отображению ее содержимого в ShellListView. И наоборот: раскрытие папки в ShellListView приводит к раскрытию той же папки в ShellTreeView. Словом, все происходит так же, как в программе Проводник Windows, с которой, конечно, вы хорошо знакомы. Фактически, пример на рис. 3.60 является прообразом программы Проводник. Если вы хотите реализовать подобный пример, достаточно выполнить следующие шаги: Перенесите на форму компонент ShellTreeView и выровняйте его по левому краю овна (Align = alLeft). Перенесите на форму компонент Splitter (см. разд. 3.9.2) и тоже выровняйте его влево. Перенесите на форму компонент ShellListView и пусть он займет остальную часть клиентской области (Align = alClient). В результате вы получите две панели, показанные на рис 3.60, границу между которыми пользователь сможет перемещать. В свойстве ShellTreeView компонента ShellListView сошлитесь на ваш компонент SheIJTreeViewl. Впрочем, можно сделать и наоборот: в свойстве ShellListView компонента ShellTreeView сослаться на компонент ShellListViewl. Результат будет одинаковый — компоненты окажутся связанными друг с другом. Настройте свойства Аи to Con text Men us, AutoNavigate, IconOptions, ObjectTypes и Root компонента ShellListViewl так, как вам хочется. Настройте также по своему желанию свойство ObjectTypes компонента SheHTreeViewl.
Обзор компонентов библиотеки VCL Delphi
247
Добавьте на форму инструментальную панель ТооШаг (см. разд. 3.9.4) и создайте на ней четыре кнопки, которые будут управлять стилем отображения в компоненте ShellListViewl. Во всех этих кнопках задайте свойство Grouped = true, свойство AllowAHUp = false и свойство Style = tbsCheck. Все это, как рассказано в разд. 3.9.4, обеспечит согласованную работу кнопок, из которых в каждый момент может быть выбрана одна и только одна. Напишите обработчики щелчков этих кнопок вида: ShellListViewl.ViewStyle
:= ...
;
где вместо многоточия напишите vslcon, vsSmalllcon, vsList или vsReport в зависимости от того, для какой из кнопок пишется обработчик. Это все! Можете выполнить ваше приложение и убедиться, что оно в основных чертах работает так же, как Проводник Windows. Конечно, приложение надо бы дополнить еще некоторыми возможностями. Но я думаю, что, познакомившись в последующих главах с возможностями Delphi, вы сами сможете это сделать. К тому же, вряд ли имеет смысл повторять сделанное создателями Windows. Рассматриваемые компоненты тем и хороши, что позволяют создавать специализированные приложения, приспособленные к особенностям ваших задач. Нам осталось рассмотреть компонент ShellComboBox. Это выпадающий список дисков и папок, размещенных на рабочем столе компьютера. Он может связываться с компонентами ShellListView и ShellTreeView своими соответствующими свойствами. На рис. 3.60 вы видите ShellComboBox на инструментальной панели. Конечно, в данном примере он избыточен. Вряд ли имеет смысл вводить в окно приложения и ShellComboBox, и ShellTreeView. Для управления папкой ShellListView достаточно одного из этих компонентов.
3.10.4 Фрагменты диалогов — компоненты DriveComboBox, DirectoryListBox, FilterComboBox, FileListBox и DirectoryOutline Помимо компонентов, рассмотренных в разд. 3.10.3, в Delphi имеется еще ряд компонентов, представляющих собой фрагменты диалогов: выпадающие списки дисков (драйверов) — DriveComboBox и фильтров (масок) файлов — FilterComhoВох, списки каталогов — DirectoryListBox и файлов — FileListBox, дерево каталогов — DirectoryOutline. Внешний вид этих компонентов вы можете увидеть на рис. 3.62. Компоненты работы с файловой системой облегчают вам создание собственных диалоговых окон, что нередко требуется. Например, вы можете захотеть включить в диалоговое окно отображение каких-то характеристик файлов (размера, даты создания и т.п.) или оперативный просмотр содержания текстовых файлов. Тогда вам очень пригодятся готовые компоненты работы с файлами. Правда, все они, кроме DirectoryOutline, предназначены в основном для Delphi 1, т.е. для 16-разрядных приложений, и в последующих версиях Delphi расположены на Рис. 3.62 Компоненты фрагменты диалогов
JalxJ
|7''Forml FileLittBoH
.. Q Q Q Q . Q
BORLAHD.GIF dent.sSp depbl'itf deptoy.l>it iratalnf
FilleiComboBoi все файлы ['.")
DiiecloiyLiilBoH • 0:VABc»land\Dekni7
j
В рл (^ Piosam Fie
DiiBctotyOulIine
±J ^J, ,u,,u
u^ ,.=UJ
•SProgiamFies P О Adobe
-f-J "•*
A
[ [-QCBuldeb D ri v eConboB ox
1 • D..W
^J
^J"
248
Глава 3
странице Win 3.1 палитры компонентов. Это значит, что они не рекомендуются для 32-разрядных приложений. Но, во-первых, у вас остается компонент DirectoryOutline. Кроме того, никто не запрещает вам все-таки использовать и остальные компоненты в любых приложениях Delphi. И, наконец, если уж вы хотите четко следовать рекомендации не использовать первые четыре фрагмента диалогов в 32-разрядных приложениях, вы можете разработать свои аналогичные компоненты, используя обычный компонент ComboBox. Начнем рассмотрение компонентов работы с файловой системой с компонента DriveComboBox — выпадающего списка дисков (драйверов). При размещении на форме этот компонент автоматически отображает список имеющихся на компьютере дисков. Во время выполнения приложения вы можете прочитать имя выбранного пользователем диска в свойстве Drive, а строку, содержащуюся в окне списка — в свойстве Text. Свойство TextCase задает регистр отображения; tcUpperCase — в верхнем регистре, tcLowerCase — в нижнем. Связать компонент DriveComboBox со списком каталогов, отображаемых компонентом Directory List В ох, можно во время проектирования через свойство DirList компонента DriveComboBox. Это свойство может указывать на компонент типа Direct о ryListBox. Можно обеспечить связь этих двух типов компонентов и программно, включив в обработчик события OnChange компонента DriveComboBox оператор DirectoryListBoxl.Drive := DriveComboBox1.Drive;
Этот оператор задает имя диска, выбранное пользователем в компоненте DriveComboBoxl, свойству Drive списка каталогов DircctoryListBoxl. Аналогичным оператором можно обеспечить связь компонента DriveComboBox с деревом каталогов и файлов в компоненте DirectoryOutline: D i r e e t o r y O u t l i n e l . D r i v e := DriveCombcBoxl.Drive;
Рассмотрим теперь выпадающий список фильтров — компонент FilterComboВох. Его основное свойство — Filter, которое задается так же, как в описанных ранее диалогах. К отдельным частям фильтра — тексту и маске, можно получить доступ через свойства Text и Mash соответственно. Связь компонента со списком файлов типа TFilcListBox можно установить, задав свойство FilcList. Компонент DirectoryListBох отображает список каталогов диска, заданного свойством Drive. Значение этого свойства можно установить программно во время выполнения. Как уже говорилось выше, связь этого свойства с выбранным пользователем диском в компоненте DriveComboBox устанавливается или программно, или с помощью свойства DirectoryListBox компонента DriveComboBox. Связь списка каталогов с компонентом типа TFileListBox, отображающим список файлов, осуществляется с помощью свойства FileList. Можно также использовать результаты выбора пользователем каталога, читая свойство Directory в обработчике события OnChange. С компонентом Directory List Box можно также связать метку типа Label. В этой метке будет отображаться путь к текущему каталогу. Если путь не умещается в метке, он автоматически отображается в сокращенном виде (см. рис. 3.62) с помощью функции MmimizcName. Метка, отображающая каталог, указывается в свойстве DirLabel. Список файлов содержится в компоненте FileListBox. Его свойства Drive, Directory и Mask определяют соответственно диск, каталог и маску файлов. Эти свойства можно устанавливать программно или связывая описанным ранее способом компонент FileListBox с компонентами DriveComboBox, DircctoryListBox и Filter ComboBox. Свойство FileType позволяет включать в список не все файлы, а только те, которые имеют соответствующие атрибуты. Свойство FileType представ-
Обзор компонентов библиотеки VCL Delphi
249
ляет собой множество, указывающее типы включаемых файлов. Элементы этого множества могут иметь значения: ftReadOnly — только для чтения, ftHiddcn невидимые, ftSystem — системные, ftVolumelD — обозначения дисков, ftDirectoгу — каталоги, ftArchive — архивные, ftNormal — не имеющие особых атрибутов. Свойство ShowGlyphs разрешает или исключает показ пиктограмм файлов (в примере рис. 3.62 это свойство = true). Свойство MultiSelect разрешает выбор нескольких файлов. Основное свойство, в котором можно прочитать имя выбранного пользователем файла — FileName. Со списком файлов может быть связано окно редактирования Edit, в котором отображается выбранный файл (см. окно над списком файлов на рис. 3.62). На этот список указывает устанавливаемое во время проектирования свойство FileEdit. Теперь рассмотрим компонент DirectoryOutline, содержащий дерево каталогов. В этом компоненте значение диска устанавливается свойством Drive. Текущий каталог, выбранный пользователем, можно прочитать в свойстве Directory. Свойство TextCase определяет стиль отображения имен каталогов: tcLowerCase — преобразование к нижнему регистру, tcUpperCase — к верхнему, tcAsIs — без преобразования (этот режим использован в примере рис. 3.62). Остальные свойства идентичны компоненту OutLine, на основе которого построен данный пример. Вы можете найти исходный текст этого примера в каталоге ...\source\sajnpies.
3.10.5 Диалог выбора шрифта — компонент FontDialog Компонент FontDialog вызывает диалоговое окно выбора атрибутов шрифта, представленное на рис. 3.63. В нем пользователь может выбрать имя шрифта, его стиль (начертание), размер и другие атрибуты. Рис, 3.63 Диалоговое окно выбора атрибутов шрифта
О MVBt* fl Palalina Lbotyoe PROMT HelvCjn Ч? PromUrreerial Виаонэмечвте Г" Зачесхгутый Г~ Подчеркнутый
JJ Образец
АаВЬБбФ»
Основное свойство компонента — Font типа TFont (см. разд. 5.1.5), в котором вы можете задать при желании начальные установки атрибутов шрифта и в котором вы можете прочитать значения атрибутов, выбранные пользователем в процессе диалога. Свойства MaxFontSize и MinFontSize устанавливают ограничения на максимальный и минимальный размеры шрифта. Если значения этих свойств равны О (по умолчанию), то никакие ограничения на размер не накладываются. Если же значения свойств заданы (обычно это целесообразно делать исходя из размеров
Глава 3
250
компонента приложения, для которого выбирается шрифт), то в списке Размер диалогового окна (см. рис. 3.63) появляются только размеры, укладывающиеся в заданный диапазон. При попытке пользователя задать недопустимый размер ему будет выдано предупреждение вида «Размер должен лежать в интервале ...» и выбор пользователя отменится. Свойства MaxFontSize и MinFontSize действуют только при включенной опции fdLimitSize (см. ниже). Свойство Device определяет, из какого списка возможных шрифтов будет предложен выбор в диалоговом окне: fdScreen — из списка экрана (по умолчанию), fdPrinter — из списка принтера, fdBoth — из обоих. Свойство Options содержит множество опций: fdAnsiOnly
Отображать только множество шрифтов символов Windows, не отображать шрифтов со специальными символами.
fdAppIyBatton
Отображать в диалоге кнопку Применить независимо от того, предусмотрен ли обработчик события OnApply.
fdEffects
Отображать в диалоге индикаторы специальных эффектов (подчеркивание и др.) и список Цвет.
fdFixedPitchOnly Отображать только шрифты с постоянной шириной символов. fdForceFontExist Позволять пользователю выбирать шрифты только из списка, запрещать ему вводить другие имена. fdLimitSize
Разрешить использовать свойства MaxFontSize и MinFontSize, ограничивающие размеры шрифта.
fdNoFaceSel
Открывать диалоговое окно без предварительно установленного имени шрифта.
fdNoOEMFonts
Удалять из списка шрифтов шрифты OEM.
Отображать только масштабируемые шрифты, удалять из списка не масштабируемые (шрифты bitmap). fii N о S i m u 1 a t i о n s Отображать только шрифты и их начертания, напрямую поддерживаемые файлами, не отображая шрифты, в которых жирный стиль и курсив синтезируется. fdScalableOnly
fdNoSizeSel
Открывать диалоговое окно без предварительно установленного размера шрифта.
fdNoStyleSel
Открывать диалоговое окно без предварительно установленного начертания шрифта.
fdNoVectorFonts Удалять из списка векторные шрифты (типа Roman или Script для Windows 1.0). fdShowHclp Отображать в диалоговом окне кнопку Справка. fdTnieTypeOnly fdWysiwyg •
Предлагать в списке только шрифты TrueType. Предлагать в списке только шрифты, доступные и для экрана, и для принтера, удаляя из него аппаратно зависимые Шрифты.
По умолчанию все эти опции, кроме fdEffects, отключены. Если установить опцию fdApplyEutton, то при нажатии пользователем кнопки Применить возникает событие OnApply, в обработчике которого вы можете написать код, который применит выбранные пользователем атрибуты, не закрывая диалогового окна.
Обзор компонентов библиотеки VCL Delphi
251
Приведем примеры применения компонента FontDialog. Пусть ваше приложение включает окно редактирования Memol, шрифт в котором пользователь может выбирать командой меню Шрифт. Вы ввели в приложение компонент FontDialog, имя которого по умолчанию FontDialogl. Тогда обработчик команды Шрифт может иметь вид: if F o n t D i a l o g l . E x e c u t e then M e m o l . F o n t - A s s i g n ( F o n t D i a l o g l . F o n t ) ;
Приведенный оператор вызывает диалог выбора атрибутов шрифта и, если пользователь произвел выбор, то значения всех выбранных атрибутов, содержащиеся в свойстве FontDialogl .Font, присваиваются атрибутам окна редактирования, содержащимся в свойстве Memol.Font. Шрифт в окне редактирования немедленно изменится. Если вы установите в компоненте FontDialogl опцию fdApplyButton, то можете написать обработчик события OnApply: Memol .Font'.Assign ( F o n t D i a l o g l . Font) ;
Тогда пользователь может наблюдать изменения в окне Memol, нажимая в диалоговом окне кнопку Применить и не прерывая диалога. Это очень удобно, так как позволяет пользователю правильно подобрать атрибуты шрифта. Если в качестве окна редактирования в вашем приложении вы используете RichEdit, то можете предоставить пользователю возможность выбирать атрибуты шрифта для выделенного фрагмента текста или для вновь вводимого текста. Тогда выполнение команды меню Шрифт может осуществляться операторами: if F o n t D i a l o g l - E x e c u t e then RichEdit1.SelAttributes.Assign(FontDialogl.Font);
Вы можете разрешить пользователю изменять шрифт не только отдельных компонентов, но и всех компонентов и надписей на форме. Это осуществляется оператором: if FdntDialogl-Execute then F o n t . A s s i g n ( F o n t D i a l o g l . F o n t ) ;
В этом операторе свойство Font без ссылки на компонент подразумевает шрифт формы.
3,10.6 Диалоги выбора цвета — компоненты ColorDialog и ColorBox Компонент ColorDialog вызывает стандартное диалоговое окно выбора цвета, представленное на рис. 3.64. В нем пользователь может выбрать цвет из базовой палитры или, нажав кнопку Определить цвет, раскрыть дополнительную панель (на рис. 3.64 она раскрыта), позволяющую синтезировать цвет, отличный от базовых. Синтезированный цвет можно добавить кнопкой Добавить в набор в палитру дополнительных цветов на левой панели и использовать его в дальнейшем. Основное свойство компонента ColorDialog — Color. Это свойство соответствует тому цвету, который выбрал в диалоге пользователь. Если при вызове диалога желательно установить некоторое начальное приближение цвета, это можно сделать, установив Color предварительно во время проектирования или программно. Свойство CustomColors типа TStrings позволяет задать заказные цвета дополнительной палитры. Каждый цвет определяется строкой вида =;
Имена цветов задаются от ColorA (первый цвет) до ColorP (шестнадцатый, последний). Например, строка ColorA=B08022
Глава 3
252 Рис. 3.64 Диалоговое окно выбора цвета
Основные цвета:
• rrrr', г • rrrr яггяя
ЯГ-тШЯ
гмг Догюпшчтельпые цвета:
JJK_ ^
Отмена
^правка
задает первый заказной цвет. Подробнее о задании цветов см. в справочной части книги в гл. 17. Свойство Options содержит множество следующих опций: cdFullOpen
Отображать сразу при открытии диалогового окна панели определения заказных цветов.
cdPre ven tFull Open
Запретить появление в диалоговом окне кнопки Определито цвет, так что пользователь не сможет определять новые цвета.
cdShowHelp
Добавить в диалоговое окно кнопку Справке.
cdSoIidColor
Указать Windows использовать сплошной цвет, ближайший к выбранному (это обедняет палитру).
cdAnyColor
Разрешать пользователю выбирать любые не сплошные цвета (такие цвета могут быть неровными).
По умолчанию все опции выключены. Приведем пример применения компонента Col or Dialog. Если вы хотите, чтобы пользователь мог задать цвет какого-то объекта, например, цвет фона компонента Memol, то это можно реализовать оператором if C o l o r D i a l o g l - E x e c u t e then M e m o l . C o l o r : = C o l o r D i a l o g l . C o l o r ;
Рассмотренный компонент ColorDialog вызывает стандартный диалог Windows. Однако нередко его возможности избыточны и пользователю удобнее выбирать цвет с помощью выпадающего списка, как, например, вы делаете это в окне Инспектора Объектов. Такую возможность предоставляет введенный, начиная с Delphi 6, компонент ColorBox. Свойство Style является множеством, элементы которого определяют, какие именно категории цвета представлены в списке. Элементы множества означают следующее: cbS tandardColors 16 стандартных цветов типа clRed, clBlack и т.п. cbExtendedColors Набор дополнительных цветов cl Money Green, cl Sky Blue, clCream, elMedGray.
Обзор компонентов библиотеки VCL Delphi
253
cbSystemColors
Системные цвета, установленные в Windows.
cblncludeNone
Список включает в себя строку *clNone*; какой именно цвет будет отображаться в квадратике этой строки, определяется свойством NoneColorColor компонента ColorBox, a истинный цвет определяется компонентом, воспринимающим этот цвет; эта опция влияет только при наличии опции cbSystemColors.
cblncludeDefauIt
Список включает в себя строку «clDefault» — цвет по умолчанию; какой именно цвет будет отображаться в квадратике этой строки, определяется свойством DefaultColorColor компонента ColorBox, а истинный цвет определяется компонентом, воспринимающим этот цвет; эта опция влияет только при наличии опции cbSystemColors.
cbCustoraColor
Первой строкой в списке появляется «Custom...»; при выборе пользователем этой строки открывается диалог, показанный на рис. 3.64, в котором пользователь может определить заказной (нестандартный) цвет.
cbPrettyNames
Строки в списке обозначают цвета, а не их имена: например, «Black» а не «clBlack».
Как видно из приведенной таблицы, в список, помимо различных цветов, могут включаться строки cclDefault» — цвет компонента по умолчанию, и eclNone» — цвет, зависящий от версии Windows. Это белый цвет для Windows 98 и черный для Windows 2000\XP\NT. Если присвоить цвет clDefault какому-то компоненту, то компонент будет рисоваться цветом, который заложен в него по умолчанию. Аналогично, присваивание clNone тоже приведет к тому, что истинный цвет будет определяться самим компонентом. Свойство DefaultColorColor определяет, квадратиком какого цвета будет помечена в списке строка «clDefault». Свойство NoneColorColor определяет, квадратиком какого цвета будет помечена в списке строка «clNone». При этом, как сказано выше, в действительности присваиваемые цвета будут определяться теми компонентами, в которые они передаются. Узнать цвет, выбранный пользователем в списке, позволяет свойство Selected. Для этого можно воспользоваться событием компонента OnSelect, наступающим в момент выбора пользователем цвета. Например, оператор Memol.Color := ColorBoxl.Selected;
помещенный в обработчик этого события, задаст фону окна Memol цвет, выбранный пользователем. Свойство только времени выполнения Colors является индексированным массивом цветов в списке (индексы начинаются с 0). Свойство ColorNames — аналогичный массив строк с именами цветов. Большинство остальных свойств, методов, событий компонента Со1огВок подобны компоненту ComboBox (см. разд. 3.2.5). В частности, список всех строк содержится в свойстве Items типа TStrings. Индекс строки цвета, которая будет показана пользователю в момент начала выполнения приложения, определяется свойством только времени выполнения Itemlndcx. Если вам желательно в первый момент показать пользователю определенный цвет, это можно сделать в обработчике события формы OnCreate, определив в нем Itemlndex с помощью метода IndexOf. Например, следующий оператор в первый момент показывает пользователю строку «clDefault»: ColorBoxl.Itemlndex
:= C o l o r B o x l . I t e m s . I n d e x O f ( ' c l D e f a u l t ' | ;
Глава 3
254
3.10.7 Диалоги печати, установки принтера и параметров страницы — компоненты PrintDialog, PrinterSetupDialog и PageSetupDialog Компонент PrintDialog вызывает диалоговое окно печати, представленное на рис. 3.65. В нем пользователь моясет выбрать принтер и установить его свойства, указать число копий и последовательность их печати, печатать в файл или непосредственно на принтер, выбрать печатаемые страницы или печатать только выделенный фрагмент. Рис. 3.65 Диалоговое окно настройки печати
- Принтер
j И*« : Состояние. I Тип
Готов . EpionLXOOO
Миги Коммепгарнй.
LPT1: Г" Печать в файл '
Компонент PrintDialog не осуществляет печать. Он только позволяет пользователю задать атрибуты печати. А сама печать должна осуществляться программно с помощью объекта Printer или иным путем (о способах печати см. разд. 5.6.1). Рассмотренные ранее диалоговые компоненты возвращали одно свойство — имя файла, цвет, или один объект — Font, содержащий множество свойств. В отличяе от них компонент PrintDialog возвращает ряд свойств, характеризующих выбранные пользователем установки. Это следующие свойства: PrintRange Показывает выбранную пользователем радиокнопку из группы Печатать: prAHPages — выбрана кнопка Все страницы, prSelectiоп — выбрана кнопка Страницы с .. по ..., prPageNums — выбрана кнопка Страницы. FromPage
Показывает установленную пользователем начальную страницу в окне Страницы с ... го ...
ToPage
Показывает установленную пользователем конечную страницу в окне Страницы с ... по ...
PrintToFile Показывает, выбран ли пользователем индикатор Печать в файл. Copies Показывает установленное пользователем число копий. Collate Показывает, выбран ли пользователем индикатор Разобрать. Перед вызовом диалога желательно определить, сколько страниц в печатаемом тексте, и задать параметры MaxPage и MinPage — максимальный и минимальный номера страниц. В противном случае пользователю в диалоговом окне не
255
Обзор компонентов библиотеки VCL Delphi
будет доступна кнопка Страницы с ... по .... Кроме того, следует определить множество опций в свойстве Options: poDisablePrintToFile Запретить доступ к индикатору Печать в файл. Эта опция
работает только при включенной опции poPrintToFile.
poHelp
Отображать в диалоговом окне кнопку Справка. Опция может не работать для некоторых версий Windows 95/98.
poPageNums
Сделать доступной радиокнопку Страницы, позволяющую пользователю задавать диапазон печатаемых страниц.
poPrintToPile
Отображать в диалоговом окне кнопку Печать в файл. Сделать доступной кнопку Выделение, позволяющую пользователю печатать только выделенный текст. Выдавать замечания, если пользователь пытается послать задачу на неустановленный принтер.
po Select ion po Warning
Теперь остановимся на компоненте Printer SetupDialog, вызывающем диалоговое окно установки принтера, представленное на рис. 3.66. Это единственный диалоговый компонент, не имеющий никаких специфических свойств, которые надо было бы устанавливать или читать. Диалог выполняет операции по установке принтера, на котором будет производиться ечать, и задании его свойств. Этот диалог не возвращает никаких параметров. Рис. 3.66 Диалоговое окно установки принтера
Настройка печати Принтер • Имя:
Состояние: Тип.
Готов
Место.
LPTV.
EpsonLX-Ш
• — - г Ориентация
' Бумага "Размер Подана:
СЕТЬ...
А4 [Двтоеыбор
Ьнижная Г Альбомная
Отмена
Начиная с Delphi 7, в библиотеке появился компонент диалога задания параметров страницы PageSetupDialog. Вызываемый им стандартный диалог показан на рис. 3.67. Перед вызовом диалога можно программно установить начальные значения свойств страницы — ориентацию, поля, размер бумаги и т.п. А после закрытия диалога можно прочитать заданные пользователем значения. Эти значения можно использовать при программной печати с помощью объекта Printer (см. разд. 5.6.2). Но более интересно то, что установки пользователя в этом диалоге автоматически передаются в методы печати различных компонентов. Например, в метод Print компонента RiehEdit. Такие параметры компонента PageSetupDialog, как MarginBottom, MarginLeft, MarginRight, MarginTop, позволяют задать начальные значения соответственно нижнего, левого, правого и верхнего поля. По окончании работы полъзовате-
Глава 3
256
ля с диалогом в этих свойствах можно прочитать размеры полей, выбранных пользователем. Аналогичным образом свойства PageHeight и Page Width определяют высоту и ширину страницы. Значения свойств MinMarginBottom, MinMarginLeft, MinMarginRight и MinMarginTop задают минимальные размеры полей, которые может задать пользователь. Если в этих свойствах заданы значения 0, ограничения на размер полей не накладываются. Все перечисленные выше размеры определяются в единицах измерения, задаваемых свойством Units. Это свойство может принимать следующие значения: pmDc fault
Единицы измерения определяются установками Windows.
pmMiHimeters
Измерение в миллиметрах.
рш Inches
Измерение в дюймах.
При измерениях в дюймах размеры полей и страницы задаются в тысячных долях дюйма. При измерениях в миллиметрах размеры задаются в сотых долях миллиметра. Параметры 1П[гзиицы
Рис. 3.67
Диалоговое окно задания параметров страницы
Справка]
DK
Отмена
I
Принтер... j
Свойство Options представляет собой множество опций: psoDefaultMinMargins
Определят, что минимальные размеры полей, которые может задать пользователь, устанавливаются теми, которые допускает принтер.
psoDisab leMargin s
Делает недоступным задание пользователем размеров полей.
psoDisab 1 eOrien ta tion
Делает недоступным задание пользователем ориентации страницы: книжной или альбомной.
ps oDig abl e P agePa in ting Отменяет прорисовку вверху окна диалога (см. рис. 3.67) примера страницы -
257
Обзор компонентов библиотеки VCL Delphi
psoDisablePaper
Делает недоступным задание пользователем размера страницы и способа подачи бумаги.
psoDigablcPr in ter
Делает недоступной кнопку Принтер, так что пользователь не может изменить информацию о принтере.
ps о Margins
Задает начальные значения, исходя из свойств МагginLeft, Margin Right, Margin Bottom, MarginTop. Если опция не установлена, начальные значения всех полей равны одному дюйму или 25 миллиметрам.
psoMinMargins
Задает минимальные значения, исходя из свойств MinMarginLeft, MinMarginRight, MinMarginBottom, MinMarginTop. Если опция не установлена, минимальные значения полей определяются драйвером принтера.
psoShowHelp
Отображает кнопку Справка и системную кнопку ? в заголовке диалогового окна.
ps о Warning
Запрещает появление замечания при отсутствии принтера, установленного по умолчанию.
psoNoNet work Button
Убирает из диалога кнопку Сеть.
|
По умолчанию все опции выключены, кроме psoDefauItMinMargins. Множество событий, связанных с компонентом, позволяют обеспечить нестандартное изображение примера страницы в верхней части диалогового окна. Но на подобных изысках мы останавливаться не будем.
3.10.8 Диалоги поиска и замены текста — компоненты FindDialog и ReplaceDialog Компоненты FindDialog и ReplaceDialog, вызывающие диалоги поиска и замены фрагментов текста (рис. 3.68 и 3.69), очень похожи и имеют одинаковые свойства, кроме одного, задающего заменяющий текст в компоненте ReplaceDialog. Такое сходство не удивительно, поскольку ReplaceDialog — производный класс от FindDialog. Рис. 3.68
Диалоговое окно поиска фрагмента текста
Рис. 3.69 Диалоговое окно замены фрагмента текста
Что:
[М e
[С* ^рлько сло&о целиком Отмена Г" С учетом регистра Справка
9 Программирование -- Delphi 7
Глава Э
258
Компоненты имеют следующие основные свойства:
FindText
Текст, заданный пользователем для поиска или замены. Программно может быть установлен как начальное значение, предлагаемое пользователю.
ReplaceText Только в компоненте ReplaceDialog — текст, который должен заменять FindText, Position
Позиция левого верхнего угла диалогового окна, заданная типом TPoint — записью, содержащей поля X (экранная координата по горизонтали) и Y (экранная координата по вертикали).
Left
Координата левого края диалогового окна, то же, что Position.X.
Top
Координата верхнего края диалогового окна, то же, что Position.Y.
Options
Множество опций,
Последний параметр Options — может содержать следующие свойства: f r DisableM atchC ase
Делает недоступным индикатор С учетом регистра в диалоговом окне.
frDisableUpDown
Делает недоступными в диалоговом окне кнопки Вверх и Вниз группы Направление, определяющие направление поиска.
frDisableWholeWord Делает недоступным индикатор Только слово целиком в диалоговом окне.
frDown
Выбирает кнопку Вниз группы Направление при открытии диалогового окна. Если эта опция не установлена, то выбирается кнопка Вверх.
frFindNext
Эта опция включается автоматически, когда пользователь в диалоговом окне щелкает на кнопке Найти далее, и выключается при закрытии диалога.
frffideMatchCase fi-HideWholeWord
"Удаляет индикатор С учетом регистра из диалогового окна.
f r Hi deU pDo wn
Удаляет кнопки Вверх и Вниз из диалогового окна.
frMatcbCase
Этот флаг включается и выключается, если пользователь включает и выключает опцию С учетом регистра в диалоговом окне. Можно установить эту опцию по умолчанию во время проектирования, чтобы при открытии диалога она была включена.
fr Replace
Применяется только для ReplaceDialog. Этот флаг устанавливается системой, чтобы показать, что текущее (и только текущее) найденное значение FindText должно быть заменено значением ReplaceText.
frReplaceAll
Применяется только для ReplaceDialog. Этот флаг устанавливается системой, чтобы показать, что все найденные значения FindText должны быть заменены значениями ReplaceText.
frShowHelp
Задает отображение кнопки Справка в диалоговом окне.
Удаляет индикатор Только слово целиком из диалогового окна.
|
Обзор компонентов библиотеки VCL Delphi
frWholeAVord
259
Этот флаг включается и выключается, если пользователь включает и выключает опцию Только слово целиком в диалоговом окне. Можно установить эту опцию по умолчанию во время проектирования, чтобы при открытии диалога она была включена.
Сами по себе компоненты FindDialog и EeplaceDialog не осуществляют ни поиска, ни замены. Они только обеспечивают интерфейс с пользователем. Л поиск и замену надо осуществлять программно. Для этого можно пользоваться событием OnFind, происходящим, когда пользователь нажал в диалоге кнопку Найти далее, и событием OnReplace, возникающим, если пользователь нажал кнопку Заменить или Заменить see. В событии OnReplace узнать, какую именно кнопку нажал пользователь, можно по значениям флагов frReplace и frRepIaceAll. Поиск заданного фрагмента легко проводить, пользуясь функцией Object Pascal Ров, которая определена в модуле System следующим образом: function Pos(Substr; string; S: string): Byte;
где S — строка, в которой ищется фрагмент текста, a Substr — искомый фрагмент. Функция возвращает позицию первого символа первого вхождения искомого фрагмента в строку. Если Substr в S не найден, возвращается 0. Для организации поиска нам потребуется еще две функции: Сору и AnsiLowerCase. Первая из них определена как: function C o p y ( S : s t r i n g ; Index, Count: I n t e g e r ) : string;
Она возвращает фрагмент строки S, начинающийся с позиции Index и содержащий число символов, не превышающее Count. Функция AnsiLowerCase, определенная как function AnsiLowerCase(const S: s t r i n g ) : string; возвращает строку символов S, переведенную в нижний регистр. Теперь мы можем рассмотреть пример организации поиска. Пусть в вашем приложении имеется компонент Memol, и при выборе раздела меню MFind вы хотите организовать поиск в тексте, содержащемся в Memol. Для упрощения задачи исключим опцию поиска только целых слов и опцию поиска вверх от положения курсора. Программа, реализующая поиск, может иметь следующий вид: var S P o s i i n t e g e r ; procedure TForral.MFindCliclclSender: T O b j e c t ) ; begin // запоминание позиции курсора SPosi=Memol.SelStart; with FindDialogl do begin (начальное значение текста поиска — текст, выделенный в MemolI FindText;=Memol. SelText; // позиционирование окна диалога внизу Memol Position:-Point(Forml.Left,Forml.Top+Memol.Top+Memol.Height); (удаление из диалога кнопок "Вверх", "Вниз", "Только
слово целиком"}
Options:"Options + [ f r H i d e U p D o w n , f r H i d e W h o l e W o r d ] ; // выполнение Execute; end; end; , procedure TForml.FindDialoglFind(Sender: TObject); begin
260
Глава 3 with FindDialogl do begin if frMatchCase in Options // поиск с учетом регистра then Memol.SelStart:=Pos(FindText, Copy(Memo 1.Lines.Text, SPos + 1, Length(Memol.Lines.Text)))+Spos-l // поиск без учета регистра else M e m o l . S e l S t a r t : = P o s ( A n s i L o w e r C a s e ( F i n d T e x t ) , A n s i L o w e r C a s e ( C o p y ( M e m o l . L i n e s . T e x t , SPos + 1, Length(Memol.Lines.Text))))+Spos-l; if Memol.SelStart>=Spos then begin // выделение найденного текста Memol.SelLength;-Length(FindText); // изменение начальной позиции поиска SPos;=Memol.5elStart+Memol.SelLength+1; end else if MessageDlg( 'Текст " ' + F i n d T e x t + ' " не найден. Продолжать диалог?', mtConfirmation,mbYesNoCancel, 0] mrYes then CloseDialog; end; Memol.SetFocus; end;
В программе вводится переменная SPos, сохраняющая позицию, начиная с которой надо проводить поиск. Процедура MFindClick вызывает диалог, процедура FindDialoglFind обеспечивает поиск с учетом или без учета регистра в зависимости от флага frMatchCase. После нахождения очередного вхождения искомого текста этот текст выделяется в окне Memol и управление передается этому окну редактирования. Затем при нажатии пользователем в диалоговом окне кнопки Найти далее, поиск продолжается в оставшейся части текста. Если искомый текст не найден, делается запрос пользователю о продолжении диалога. Если пользователь не ответил на этот запрос положительно, то диалог закрывается методом CloseDialog. В дополнение к приведенному тексту полезно в обработчики событий OnClick и OnKeyUp компонента Memol ввести операторы SPos:=Memol.SelStart;
Это позволяет пользователю во время диалога изменить положение курсора в окне Memol. Это новое положение сохранится в переменной SPos и будет использовано при продолжении поиска. При реализации команды Заменить приведенные выше процедуры можно оставить теми же самыми, заменив в них FindDialogl на ReplaceDialogl. Дополнительно можно написать процедуру обработки события OnReplace компонента Re ptaceDia 1 о gl: procedure T F o r m l . R e p l a c e D i a l o g l R e p l a c e ( S e n d e r : T O b j e c t ) ; begin if Memol. SelTexto' ' then Memol.SelText:=ReplaceDialogl.ReplaceText; if f r R e p l a c e A l l in R e p l a c e D i a l o g l - O p t i o n s then R e p l a c e D i a l o g l F i n d ( S e l f ) ; end;
Этот код производит замену выделенного текста и, если пользователь нажал кнопку Заменить все, то продолжается поиск вызовом уже имеющейся процедуры поиска RcplaceDialoglFind. Если лее пользователь нажал кнопку Заменить, то производится только одна замена и для продолжения поиска пользователь должен нажать кнопку Найти далее.
:'.':.
"
ость 2 Методика проектирования приложений
.
Глава 4
Организация управления приложением 4.1 Технология разработки приложений Приложения Delphi можно, конечно, разрабатывать в любой последовательности — как бог на душу положит. Компонентов в Delphi множество, функции многих из них понятны (см. гл. 3). Так что стоит ли мудрствовать: переноси компоненты на форму, пиши обработчики их событий и получай награду за прекрасно сделанное приложение. Честно говоря, приведенные в предыдущих главах примеры так и строились. Это были тестовые приложения, единственной задачей которых было показать возможности различных компонентов. Но настоящее приложение так проектировать нельзя. Бессистемно созданное приложение, даже очень хорошее, через некоторое время становится непонятным и самому разработчику, не говоря уж о проблемах сопровождения такого приложения кем-то из коллег. Подобное приложение, если возникает необходимость его доработки, проще создать заново, чем разбираться в его хитросплетениях. Поэтому, чтобы избежать в дальнейшем лишней работы и нареканий в ваш адрес со стороны коллег, которым выпало несчастье модернизировать ваше приложение, лучше сразу приучить себя придерживаться определенной технологии разработки. Благо, B.Delphi имеются для этого все возможности. Речь идет, прежде всего, о проектировании на основе списков действий, управляемых специальными компонентами. В гл. 1 было рассказано об объектно-ориентирован ном программировании. В свое время объектная ориентация произвела революцию в программировании и сейчас уже трудно представить иное построение программы. Но увлечение объектами как-то отодвинуло временно на второй план функции, т.е. те действия, которые являлись основой предшествующего этапа в программировании. Сейчас интерес к действиям вновь возвращается. Ведь действия — это то, ради чего программа создается. И объекты интересуют только постольку, поскольку они могут облегчить действия пользователя. Поэтому грамотная разработка серьезного приложения должна начинаться не с создания красивого и удобного пользовательского интерфейса, а с составления списка действий, которые пользователь сможет выполнять с помощью данного приложения. Конечно, по мере проектирования этот первоначальный список будет расширяться и уточняться. Но хотя бы начальный вариант такого списка должен являться отправной точкой проектирования. Без этого вряд ли можно создать что-нибудь стоящее. Когда список действий составлен, можно начать размышлять над исполнительными элементами интерфейса, с помощью которых пользователь сможет инициировать действия. И здесь могут возникнуть определенные сложности. Обычно для одного и того же действия предусматривается несколько инициаторов. Например, как правило, некоторые разделы главного меню дублируются быстрыми кнопками инструментальных панелей, разделами контекстных меню, горячими клавишами, иногда обычными кнопками. Такое дублирование удобно пользователю. Но независимая разработка всех этих управляющих элементов часто приводит
264
Глава 4
к неоправданному дублированию кодов, а главное — снижает возможности модернизации и сопровождения приложения. Действительно, если вы надумаете что-то изменить в одном из действий, вы должны будете вносить изменения сразу в нескольких местах программы. И не дай бог что-то пропустить: согласованная работа управляющих элементов нарушится. Чтобы избежать подобных сложностей, в Delphi предусмотрены компоненты, осуществляющие централизованное управление действиями, их диспетчеризацию. Составленный вами список действий вы заносите в специальный компонент — диспетчер. Для каждого действия при этом вы можете задать множество свойств: надписи на соответствующих элементах управления, пиктограммы, тексты ярлычков подсказок, горячие клавиши и многое другое. Вы можете также написать обработчик, обеспечивающий выполнение задуманного действия. Впрочем, как вы увидите далее, для стандартных действий это даже обычно не нужно делать. Разработчики Delphi создали множество классов таких стандартных действий, в которых необходимые функции реализуются автоматически. Так что, не ознакомившись в деталях с этими возможностями, вы рискуете «изобретать велосипед». Вы потратите время и силы на их реализацию, а потом окажется, что то же самое можно было сделать несколькими движениями мыши, причем много лучше, чем вы это придумали сами. Каюсь, и в моей работе бывали подобные случаи. Если вы сформировали подобный список действий, то последующее проектирование управляющих элементов существенно упрощается. В большинстве элементов имеется свойство Action- Достаточно сослаться в этом свойстве на одно из действий, как все свойства этого действия и обработчик, реализующий его, перенесутся в данный управляющий элемент. И вам не придется для каждого элемента задавать все это заново. Представляете, сколько времени вы сэкономите? А если вы в дальнейшем что-то измените в реализации действия или в его свойствах (например, смените горячие клавиши или пиктограмму), то вам даже не надо будет вспоминать, какие элементы в различных формах вашего приложения инициируют это действие. Все эти элементы автоматически воспримут внесенные изменения. В Delphi предусмотрена подобная централизация управления приложением на нескольких уровнях. Для диспетчеризации действий, начиная с Delphi 4, имеется компонент ActionList, Он упростил создание меню и инструментальных панелей, позволил делать программы более понятные и структурированные. Уже в Delphi 5 число стандартных действий, к которым можно обращаться из ActionList, было свыше двадцати. А в Delphi 7 их число достигло 66. Представляете, сколько стандартных операций реализовали за вас создатели Delphi? Кроме того, начиная с Delphi 6, в библиотеке появилась группа гораздо более мощных компонентов, предназначенных для управления событиями: ActionManager, ActionMaiiiMenuBar, ActionToolBar, GustomizeDlg. Они не только обеспечивают новые возможности визуального проектирования, но и решают, например, такую задачу, как настройка меню и инструментальных панелей пользователем. Пока мы рассматривали только диспетчеризацию действий. Но централизация управления приложением имеет еще несколько уровней. Во-первых, надо упомянуть список изображений ImageList, который может централизованно снабжать изображениями и пиктограммами различные элементы приложения. Во-вторых, в Delphi имеется такой объект, как Application — приложение. Этот объект управляет наиболее общими свойствами вашего проекта. А для перехвата множества событий компонента Application имеется компонент ApplicationEvents, который позволяет осуществить диспетчеризацию сообщений о событиях. Наконец, в каждом приложении имеется объект Screen — экран, который позволяет управлять отображением различных форм на мониторе.
Организация управления приложением
265
Все эти возможности и компоненты будут рассмотрены в данной главе. Но прежде, чем переходить к конкретным вопросам, надо договориться о том, что мы будем понимать под действиями. Действие (action) — реализация некоторого поведения, являющегося реакцией на поступок пользователя, такой, как щелчок на кнопке или на разделе меню — инициаторе действия или интерфейсном компоненте действия. Примерами действий являются открытие файла и его загрузка в какой-то приемник, сохранение информации в файле на диске, установка атрибутов шрифта и выравнивание текстов в окнах редактирования, поиск и замена фрагментов текста, навигация по набору данных, выполнение какого-то внешнего приложения, вызов справки и многое другое. Перечисленные примеры относились к стандартным действиям. В действительности таких действий много больше, чем перечислено выше. Все стандартные действия реализованы в Delphi классами, наследующими базовому классу действий TAction. Обработчики подобных действий в ряде случаев вам писать вообще не надо, так как они реализованы в соответствующих классах. Помимо стандартных действий в реальных приложениях имеются, конечно, и нестандартные, связанные с какими-то расчетами, обработкой данных и т.п. Объекты таких действий вам надо создать самим (делается это очень просто методами визуального программирования) и для них надо написать обработчики, реализующие данные действия. Таким образом, как уже говорилось, прежде чем начинать программирование приложения, вам надо составить для себя список действий, которые должны быть доступны будущему пользователю через разделы меню, инструментальные панели, кнопки и другие элементы управления. При программировании этот список реализуется специальными компонентами, обеспечивающими диспетчеризацию действий, В Delphi 4 и 5 был только один такой диспетчер действий — компонент ActionList. Редактор этого компонента позволял сформировать список действий, написать обработчики, выполняющие задуманные действия, задать основные свойства будущих интерфейсных компонентов — пиктограммы, надписи, быстрые кнопки, тексты подсказок и т.п. Начиная с Delphi 6, появился еще один диспетчер действий — компонент Action Manager, который намного мощнее ActionList. После того как список действий создан, надо сформировать полосы действий, Это компоненты, на которых располагаются интерфейсные компоненты действий. Такими полосами действий являются полоса главного меню и инструментальные панели. При использовании ActionList эти полосы действий надо добавлять на форму в виде отдельных компонентов, создавать на них инициаторы действий (разделы меню, быстрые кнопки), а затем связывать инициаторы с соответствующими действиями из списка ActionList. При таком связывании свойства, заложенные в действия, автоматически передаются интерфейсным компонентам. В АсtionManager создание полос действий упрощается. Они создаются и формируются непосредственно из редактора ActionManager простым перетаскиванием мышью. Интерфейсные компоненты действий обычно должны содержать поясняющие их изображения. Эти изображения собираются в списке изображений — компоненте ImageList. Для нестандартных действий эти изображения вы должны загрузить в ImageList сами. А изображения для стандартных действий загружаются в него автоматически по мере формирования списка в диспетчере действий. Таким образом, последовательность формирования списка действий и проектирования меню и инструментальных панелей сводится к следующим шагам: 1. Продумывается и составляется список действий, которые должны быть доступны будущему пользователю через разделы меню, инструментальные панели, кнопки и другие элементы управления.
266
Главэ 4
2.
Для тех нестандартных действий, которые должны быть доступны из быстрых кнопок инструментальной панели, готовится список пиктограмм на кнопках в компоненте ImageList. 3. На главную форму приложения переносится компонент диспетчеризации действий: ActionList или ActionManager. Компонент связывается с ImageList. Формируется список стандартных и нестандартных действий. 4. Каждому действию задается набор характеристик: Name (имя) Caption (надпись, в которой выделяется символ быстрого доступа), Shortcut (горячие клавиши), Imagelndex (номер изображения в ImageList), Hint (тексты подсказок), HelpContext или HelpKeyword (ссылка на тему справки) и др. Для нестандартных действий все эти характеристики вы записываете сами. Для стандартных действий они заносятся автоматически. Вам надо только перевести надписи и подсказки на русский язык и, может быть, исправить ссылки на не устраивающие вас стандартные изображения и комбинации горячих клавиш. А если у вас предусмотрена в приложении контекстная справка, то надо задать ссылки на соответствующие темы. Впрочем, в начале проектирования справки, конечно, еще нет. Так что свойства HelpContext и HelpKeyword вы можете задать позднее. 5. Записываются обработчики событий выполнения для всех нестандартных действий. Стандартные действия обрабатываются автоматически и для многих из них достаточно задать некоторые свойства обработки. Впрочем, как будет видно позднее, иногда надо писать обработчики и для стандартных действий. Дальнейшие шаги зависят от того, используете ли вы компонент ActionList, или ActionManager. Для ActionList далее надо сделать следующее: 6. На форму переносится компонент MainMenu — главное меню, связывается с ImageList, в компоненте формируется меню и в его разделах даются ссылки на действия, описанные в ActionList. 7. На форме создается инструментальная панель (обычно, компонент ТооШаг). Панель связывается с ImageList, а в ее кнопках даются ссылки на действия, описанные в Action List. Если вы используете компонент ActionManager, то эти шаги выглядят иначе: в. На форму переносится компонент ActionMainMenuBar — полоса главного меню. Она связывается с диспетчером ActionManager. Затем из редактора ActionManager перетаскиваются мышью на полосу меню категории разделов, которые должны входить в меню как головные разделы, или отдельные действия. 7. В редакторе ActionManager создается новая инструментальная панель, или несколько панелей. На них перетаскиваются мышью необходимые действия. Вот в общих чертах последовательность операций при проектировании списка действий, меню и инструментальных панелей. В последующих разделах данной главы мы рассмотрим все эти операции подробнее.
4.2 Список изображений — компонент ImageList Компонент ImageList представляет собой набор изображений одинаковых размеров, на которые можно ссылаться по индексам, начинающимся с 0. Во многих компонентах, рассмотренных в гл. 3 — в меню, списках и др. встречались свойства, представляющие собой ссылки на компонент ImageList. Этот компонент позволяет организовать эффективное и экономное управления множеством пиктограмм
Организация управления приложением
267
и битовых матриц. Он может включать в себя монохромные битовые матрицы, содержащие маски для отображения прозрачности рисуемых изображений. Изображения в компонент ImageList могут быть загружены в процессе проектирования с помощью редактора списков изображений. Окно редактора, представленное на рис. 4.1, вызывается двойным щелчком на компоненте ImageList или щелчком правой кнопки мыши и выбором команды контекстного меню ImageList Editor. Рис. 4.1
Окно редактора списков изображений
xl
I 7' * umil.lrM.wiPlisN lividgeLtet
Г
detected Image
Л 11 1
_jv liiij.Ti '*
D0
;;,. "~"
Г ^^
*
I2' I :::; ———~~-ъ
.- - _ -
roagn
a
]
ffij
1
I %^..
.—_—~—
2ewe —
|
H*
1
•-. '
4
t
•1
4dd.
W
X 3
2
OK |
Cancel
o« -
^J „-- —
В окне редактора списков изображений вы можете добавить в списки изображения, пользуясь кнопкой Add, удалить изображение из списка кнопкой Delete, очистить весь список кнопкой Clear. При добавлении изображения в список открывается обычное окно открытия файлов изображений, в котором вы можете выбрать интересующий вас файл. Только учтите, что размер всех изображений в списке должен быть одинаковым. Как правило, это размер, используемый для пиктограмм в меню, списках, кнопках. При добавлении в список изображений для кнопок надо иметь в виду, что они часто содержат не одно, а два и более изображений (см. разд. 3.7.2). В этих случаях при попытке добавить изображение задается вопрос: «Bitmap dimensions for ... are greater then imagelist dimensions. Separate into ... separate bitmaps?» (Размерность изображения ... больше размерности списка. Разделить на... отдельных битовых матрицы?). Если вы ответите отрицательно, то все изображения уменьшатся в горизонтальном размере и лягут как одно изображение. Использовать его в дальнейшем будет невозможно. Поэтому на заданный вопрос надо отвечать положительно. Тогда загружаемая битовая матрица автоматически разделится на отдельные изображения и потом вы можете удалить те из них, которые вам не нужны, кнопкой Delete. Как видно из рис. 4.1, каждое загруженное в список изображение получает индекс. Именно на эти индексы впоследствии вы можете ссылаться в соответствующих свойствах разделов меню, списков, кнопок и т.д., когда вам надо загрузить в них то или иное изображение. Изменить последовательность изображений в списке вы можете, просто перетащив изображение мышью на новое место. В редакторе списков изображений вы можете, выделив то или иное изображение, установить его свойства: Transparent Color и Fill Color. Но это можно делать только в том сеансе работы с редактором списков изображений, в котором загружено данное изображение. Для изображений, загруженных в предыдущих сеансах, изменение этих свойств невозможно. Свойство Transparent Color определяет цвет, который используется в маске для прозрачного рисования изображения. По умолчанию это цвет левого нижнего пиксела изображения. Для пиктограмм данное, свойств о устанавливается в clNone, поскольку пиктограммы у нее маскированы.
Глава 4
268
Свойство Fill Color определяет цвет, используемый для заполнения пустого пространства при перемещении и центрировании изображения. Для пиктограмм данное свойство устанавливается в clNone. Группа радиокнопок Oplions определяет способ размещения изображения битовой матрицы с размерами, не соответствующими размерам, принятым в списке: Crop
Отображается часть изображения, помещающаяся в размер списка, начиная с левого верхнего угла.
Stretch Размеры изображения изменяются, становясь равными размерам списка. При этом возможны искажения. Center
Изображение центрируется, а если его размер больше размера списка, то не помещающиеся области отсекаются.
Теперь рассмотрим основные свойства TImageList: Свойство Тип
Описание
AlIocBy
Integer Высота изображений в списке. Integer Ширина изображений в списке. Integer Определяет количество изображений , на которое увеличи-
Count
Integer Определяет число изображений в списке. Свойство только
Height Width
вается список для добавления новых изображений. для чтения.
Остальные свойства определяют цвета и способы рисования изображения. Теперь рассмотрим некоторые методы ImageList. Если в приведенных ниже кратких описаниях вам будет что-то непонятно, обратитесь к гл. 6, посвященной графике. Прежде всего, остановимся на методе Draw: procedure D r a w ( C a n v a s : 'TCanvas; X, Y, Index: Integer; Enabled: Boolean=True];
Этот метод рисует на канве (см. разд. 6.1.3) Canvas изображение из списка, имеющее индекс Index. Параметры X и У указывают координаты левого верхнего угла рисуемого изображения. Параметр Enabled указывает, должно ли изображение рисоваться нормально, или серым (при значении false), как рисуются недоступные компоненты. Поскольку по умолчанию значение Enabled равно true, то для обычного режима рисования этот параметр может не указываться. Пример применения метода Draw см. в разд. 3.2.5 гл. 3. Методы procedure GetBitmap{Index: Integer; Image: TBitmap); procedure G e t l c o n ( I n d e x ; Integer; Image: T I c o n ) ;
переносят изображение с индексом Index из списка в графический объект Image. Далее это изображение можно использовать любым образом. Ряд методов позволяют добавлять в список новые изображения: function Addllraage, Mask: T B i t m a p ] : integer; function Addlcon(Image: T I c o n ) : Integer; function AddMasked(Image: TBitmap; MaskColor: TColorl :- Integer;
Эти методы добавляют в список изображение Image и возвращают индекс, присваиваемый этому изображению. Параметр Mask позволяет вместе с изображе-
Организация управления приложением
-
269
нием добавить его маску. Если свойство Masked списка равно false, то этот параметр игнорируется. Метод AddMashed эквивалентен Add, но вместо маски в нем указывается цвет MaskColor для автоматической генерации маски. Каждый пиксел этого цвета в изображении заменяется черным цветом и, следовательно, становится прозрачным. Методы procedure I n s e r t ( I n d e x : Integer; Image, M a s k : TBitraap); procedure I n s e r t I c o n ( I n d e x : Integer; Image: IIcon| ; procedure InsertMasked(Index: Integer; Image: TBitmap; MaskColor: TColor);
аналогичны предыдущим, но вставляют изображение в список, задавая ему индекс Index. Индексы последующих изображений соответствующим образом сдвигаются. Методы procedure Replace(Index: Integer; Image, Mask: TBitmap); procedure Replacelcon(Index: Integer; Image: TIcon); procedure ReplaceMasked(Index: Integer; Newlraage: TBitmap; MaskColor: TColor];
отличаются тем, что замещают изображение с индексом Index новым изображением Newlmage. Метод procedure -Delete(Index:
Integer);
удаляет из списка изображение с индексом Index. Метод procedure Move(Curlnden, Newlndex: Integer!; перемещает изображение с индексом Curlndex на новое место, указанное параметром Newlndex.
4.3 Диспетчеризация действий на основе компонента ActionList Смысл диспетчеризации действий подробно рассматривался в разд. 4.1. В данном разделе мы обсудим, как это можно делать с помощью компонента ActionList. Этот компонент менее мощный, чем введенный в Delphi 6 компонент Act ion Manager. Но и ActionList не потерял своего значения. Во-первых, только его можно использовать в версиях, младше Delphi 6. Во-вторых, компонент ActionList можно использовать в кросс-платформенных приложениях, тогда как ActionManager — только в приложениях Windows. Наконец, мощь ActionManager проявляется в основном при взаимодействии со специальными компонентами ActionMainMcnuBar и ActionToolBar, а в некоторых случаях эти компоненты менее удобны, чем описанные в гл. 3 Main Menu и ToolBar. В компонент ActionList, расположенный в библиотеке на странице Standard, заносится тот список действий, который, согласно рекомендациям разд. 4.1, вы составили в начале процесса проектирования. Перенесите на форму этот компонент. Перенесите также описанный в разд. 4.2 компонент ImageList, и сошлитесь на него в свойстве Images компонента ActionList. Это полезно сделать в самом начале, так как описанные далее стандартные действия автоматически занесут в ImageList соответствующие им изображения. Теперь сделайте на компоненте ActionList двойной щелчок. Вы попадаете в редактор действий (рис. 4.2), позволяющий вводить и упорядочивать действия.
Глава 4
270
Рис. 4.2 Окно редактора действий
7=: Frtiting Form 1 JktiimU1
Q-
ы в S RchEitttafcl Н RehE*JndBina!
—J
Щелчок правой кнопкой мыши или щелчок на маленькой кнопочке со стрелкой вниз правее первой быстрой кнопки окна редактирования позволит вам выбрать одну из команд: New Action (новое действие) или New Standard Action (новое стандартное действие). Первая из них относится к вводу нового действия любого типа. По умолчанию эти действия будут получать имена Actionl, Acticm2 и т.д. Вторая команда открывает окно, в котором вы можете выбрать необходимое вам стандартное действие. Работу со стандартными действиями мы рассмотрим в разд. 4.4. Каждое действие, которое вы внесли в список — это объект типа TAction. 'Выбрав в левом окне редактора ту или иную категорию или [AllActions] (все категории), а в правом — конкретное действие, вы можете увидеть в Инспекторе Объектов его свойства и события. Вы можете установить свойство Name (имя) — оно появится в правом окне редактора свойств и будет в дальнейшем фигурировать в заголовках обработчиков событий. При задании имени следует заботиться, чтобы оно было осмысленным, так как это облегчит вашу последующую работу. С другой стороны, желательно, чтобы имена не совпадали с какими-то именами функций, компонентов и т.п. Подобное совпадение в некоторых случаях может внести двусмысленность в код вашего приложения. Для каждого действия надо задать надпись (Caption), которая далее будет появляться в инициаторах действия — кнопках, разделах меню и т.д. В надписи можно (и нужно) выделить символ, который будет подчеркнут и обеспечит быстрый доступ к данному действию (см. в разд. 3.7.2). Можете задать горячие клавиши (Shortcut), надписи на ярлычках подсказок и в строке состояний (Hint), идентификатор темы контекстной справки (HelpContext) и другие обычные для многих компонентов свойства. Можно для каждого или некоторых действий указать свойство Image Index, которое является индексом (начиная с 0) изображения, соответствующего данному действию в компоненте списка изображений IraageList. Этот индекс передастся в дальнейшем компонентам, связанным с данным действием — разделам меню, кнопкам. Эти же изображения появляются также в окне редактора действий (рис. 4.2). Свойство Category (категория) не имеет отношения к выполнению приложения. Задание категории просто позволяет в процессе проектирования сгруппировать действия по их назначению. Вы можете для каждого действия выбрать категорию из выпадающего списка, или написать имя новой категории и отнести к ней какие-то действия. Например, на рис. 4.2 видны введенные пользователем категории Файл, Формат. Обычно целесообразно называть категории по заголовкам будущих меню или инструментальных панелей.
Организация управления приложением
271
На странице событий Инспектора Объектов для каждого действия определено три события: OnExecute. OnUpdate я OnHint. Событие OnExecute возникает в момент, когда пользователь инициализировал действие, например, щелкнув на компоненте (разделе меню, кнопке), связанном с данным действием. Обработчик этого события должен содержать процедуру, реализующую данное действие. Например, обработчик события OnExecute действия AExit может в простейшем случае иметь вид procedure T F o r m l . A E x i t E x e c u t e ( S e n d e r : T O b j e c t ) ; begin Close; end;
а в более сложных случаях может содержать проверку возможности закрыть приложение, запросы пользователю и т.д. Одним из преимуществ использования действий является то, что заголовки обработчиков приобретают осмысленный характер и код становится более прозрачным. Действительно, гораздо понятнее заголовок AExitExecute, чем, например, ButtonVClick или N14CHck (попробуйте найти в вашем большом приложении, где эта кнопка Button?-или раздел меню N14). В результате вы избавляетесь от необходимости давать осмысленные имена кнопкам и разделам меню, т.е. облегчаете свою работу с компонентами. Событие OnUpdate периодически возникает в промежутках между действиями. Возникновение этих событий прекращается только во время реализации события или во время, когда пользователь ничего не делает и компьютер находится в состоянии ожидания действий. Обработчик события OnUpdate может содержать какке-то настройки, подготовку ожидаемых дальнейших действий или выполнение каких-то фоновых операций. Событие OnHint возникает в момент, когда на экране отображается ярлычок подсказки в результате того, что пользователь задержал курсор мыши над компонентом, инициализирующим событие. Наличие в объекте действия событий OnUpdate и OnHint обогащает ваши возможности по проектированию приложения. Без этого объекта подобные события отсутствуют, и их при необходимости приходится моделировать более сложными приемами. Связь объектов действий с конкретными инициаторами действий — управляющими элементами типа кнопок, разделов меню и т.д., осуществляется через свойство Action, имеющееся у всех управляющих элементов. Поместите на вашу форму кнопку, и вы увидите в Инспекторе Объектов это свойство. Раскройте его выпадающий список и выберите из него действие, которое вами было предварительно описано. Вы сможете заметить, что после этого в кнопку перенесутся такие свойства объекта действия, как Caption, Hint и др., и что в ее событие OnClick подставится обработчик, предусмотренный вами для данного действия. Если далее вы введете в приложение меню с разделом, соответствующим тому же действию, или на панели ToolBar введете кнопку и укажете в этом объекте то же значение свойства Action, то свойства и обработчики событий объекта действия будут перенесены и на этот раздел меню, и на эту кнопку. Вам не придется повторно задавать значение Hint, Caption и др. Подобная связь между свойствами объекта действия и свойствами управляющих элементов выполняется классом TActionLink и его наследниками. Передаются в компоненты такие свойства действия, как Caption, Checked, Enabled, HelpContext, Hint, Imagelndex, Shortcut, Visible. Впрочем, в любом компоненте разработчик может изменить переданное в него свойство. Обратной связи TActionLink с компонентами нет, так что эти изменения будут локальными и не отразятся на других компонентах. Если же требуется изменить свойства всех связанных с одним действием компонентов, надо изменять свойство объекта действия. Это облегчает программное управление компонентами, связанными с одним и тем же деист-
Глава 4
272
вием. Например, если в какой-то момент вам надо сделать недоступными (или, наоборот, доступными) два компонента — кнопку ButtonS и раздел меню N5, связанные с одним событием (назовем его объект Do), то при отсутствии централизованной диспетчеризации событий через ActionList вам пришлось бы писать два оператора: Button3.Enabled := f a l s e ; N 5 . E n a b l e d ;= f a l s e ;
a при наличии объекта Do — всего один: Do.Enabled := f a l s e ;
Дело не только в экономии кода, но и в его прозрачности, понятности его как для вас самих, так и для тех, кому придется, может быть, в дальнейшем сопровождать ваше приложение.
4.4 Работа со стандартными действиями Теперь рассмотрим другую команду окна редактора действий — команду New Standard Action (новое стандартное действие). Она открывает окно (рис. 4.3), в котором вы можете выбрать из списка необходимое вам стандартное действие (или сразу несколько действий). Действия в списке сгруппированы по категориям, В Delphi 5 список включает 26 действий. А в Delphi 7 этих стандартных действий уже 66. И, вероятно, такое лавинообразное нарастание числа стандартных действий продолжится и далее. Стандартные действия охватывают операции редактирования текстов (категория Edit), форматирования текстов (категория Formal), поиска в текстах (категория Search), работу со справками (категория Help), с файлами (категория File), с окнами в приложениях MDI (категория Window — см. о приложениях MDI в разд. 5.5.4), с многостраничными панелями (категория Tab), списками (категория List), стандартными диалогами (категория Dialog), с Интернет (категория Internet), наборами данных (категория DataSet) и некоторые другие. Вы видите, сколько места заняло простое перечисление категорий. А ведь в каждой из них много различных действий. Конечно, в рамках данной книги невозможно рассмотреть особенности всех этих стандартных действий. Поэтому ограничимся простым экспериментом, который позволит понять отличие стандартных действий от нестандартных. Перенесите на форму компоненты ActionList и ImageList. Сошлитесь в свойстве Images компонента ActionList на список ImageList. Теперь перейдите в окно Рис. 4.3 Окно выбора стандартных действий
ЕШЕгДСШБиШ /*tt»c дает causs -; (Nn С
ГЁЛЫ TEdlPeste
tH«J£,ilSli**3J THichE*eules :- TRichEiMAhgnLelt THuhEdWtonRi^il
273
Организация управления приложением
редактора действий, вызовите окно рис. 4.3, выделите в нем действия категории Edit и действие TFilcExlt категории File, нажмите кнопку ОК. Вы вернетесь в окно редактора действий (рис. 4.4), и увидите, что в список занеслись выбранные вами действия, причем большинство из них имеют пиктограммы, хотя вы никаких пиктограмм в компонент ImageList не вводили. Вы можете выделить ImageList и убедиться, что в него автоматически занеслись пиктограммы выбранных действий. Правда, не все пиктограммы могут вас удовлетворить. Но это уже ваше дело заменить их другими и добавить пиктограммы для действий, автоматически с пиктограммами не связанных. Автоматическое занесение пиктограмм в список действий произойдет только в том случае, если до создания соответствующих объектов событий компонент ActionList был подключен к ImageList. В противном случае объекты появятся в ActionList без пиктограмм. Именно поэтому я рекомендовал с самого начала связать с компонентом ActionList пусть даже пустой список ImageList. Рис. 4.4 Окно редактора действий с занесенными в него объектами стандартных действий (No Categoty] Edit Fife
EdilCull
EittSalecA.11 "~> EdilUndol •
X
Теперь выделите в окне рис. 4.4 какое-то действие и посмотрите в Инспекторе Объектов его свойства. Во-первых, вы увидите, что класс объектов стандартных действий не TAction. Каждый вид стандартного действия имеет класс, производный от TAction. Названия этих классов вы можете видеть на рис, 4.3. В Инспекторе Объектов вы можете увидеть, что в стандартные действия заложены общепринятые значения множества свойств: надписи (Caption), подсказки (Hint), горячие клавиши (ShortCnt). Впрочем, если вы делаете приложение для отечественного пользователя, то от этих значений мало проку: все равно надписи и подсказки надо переводить на русский язык, а горячие клавиши не всегда те, которые вы х, гите, и их тоже может потребоваться изменять. Но самое главное отличие стандартных объектов действий от нестандартных заключается в том, что для стандартных действий не надо писать обработчики событий OnExecute. Все операции, необходимые для выполнения стандартных действий уже заложены в их объекты. Они не только не требуют обработчиков событий OnExecute, но могут реализоваться через горячие клавиши далее без инициаторов действий — разделов меню, кнопок и т.п. Чтобы убедиться во всем этом, давайте продолжим наш эксперимент. Добавьте на форму инструментальную панель ToolBar (см. разд. 3.9.4) и свяжите ее свойством Images со списком изображений ImageListl. Установите в ToolBar в true свойство ShowHint. А теперь создайте на инструментальной панели быстрые клавиши для всех действий, имеющих пиктограммы. В каждой новой кнопке ссылайтесь в свойстве Action на соответствующее действие. Добавьте на форму нашего тестового приложения какие-нибудь окна редактирования, например, так, как показано на рис. 4.5. Теперь выполните приложение. В начале вы увидите, что все кнопки, кроме кнопки выхода, недоступны. Это оп-
Глава 4
274
равдано, поскольку никакой текст не выделен, так что нечего копировать или вырезать, и никакого редактирования не было, так что нечего отменять. Может оказаться доступной только кнопка Вставить (Paste, если вы не переводили подсказки на русский язык). Доступной она будет, если в буфере обмена в данный момент находится текст. Но нажмите, например, клавишу компьютера Print Screen, занеся в буфер обмена изображение экрана, и кнопка Вставить станет недоступной. Чувствуете, какое умное поведение. Если вы выделите текст в одном из окон редактирования, то, пока это окно в фокусе, будут доступны кнопки Копировать (Сору), Вырезать (Cut), Удалить (Delete). Если вы провели какую-то операцию редактирования, станет доступна кнопка Отменить (Undo). Но стоит вам переместить курсор в другое окно редактирования, где нет выделения и не было редактирования, как эти кнопки станут недоступны. Рис. 4.5 Окно тестового Приложения
MlfeMdil
JSi2=i
JEdill
Таким образом, в стандартные действия заложено достаточно интеллектуальное поведение. Они относятся всегда к тому элементу, который находится в фокусе (в данном примере — к находящемуся в фокусе окну редактирования), и доступность их в каждый момент автоматически определяется возможностью выполнения того или иного действия. Представляете, сколько кода вам надо было бы написать, чтобы реализовать подобное поведение, включая тестирование формата, занесенного в буфер обмена, определение окна, находящегося в фокусе, и определение выделения в нем? Вы не написали ни одного оператора, а получили такой эффект благодаря возможностям, заложенным в стандартных действиях. Но учтите, что почти весь «интеллект» стандартного действия может исчезнуть, если вы введете для такого действия свой обработчик события OnExecute. Тогда вам придется все реализовывать самому. Так что для многих стандартных действий не следует писать обработчиков событий OnExecute. Интересно, что доступ к стандартным действиям через горячие клавиши сохраняется, даже если в приложении отсутствуют их инициаторы или они недоступны. Например, задайте для действия FileExitl, обеспечивающего выход из приложения, горячие клавиши Ctrl-E. Сделайте невидимой инструментальную панель ТооШаг! (Visible = false) и снова запустите приложение. Ни один управляющий элемент не будет доступен. Но все горячие клавиши будут действовать. В частности, если вы нажмете Ctrl-E, приложение закроется. Так что стандартные действия могут работать и «за кадром». Ограничимся этим примером, так как подробнее о стандартных действиях не позволяет говорить ограничение на объем книги. Вы можете узнать больше из встроенной справки Delphi или из источника [4]. Отметим только одно свойство действий, связанных с вызовом стандартных диалогов Windows, Эти действия (классы TFileOpen, TFileSaveAs, TFilePrintSetup, TSearchFind, TSearchReplace, TSearchFindFirst, TOpenPicture, TSavePicture, TColorSelect, TFontEdit, TPrintDlg) имеют свойство Dialog — объект соответствующего диалога, подобный рассмотренным в гл. 3 в разд. 3.10. Таким образом, эти стандартные действия не требуют явного ввода в приложение компонентов-диалогов и явного их вызова. Но
Организация управления приложением
275
зато они имеют события BeforeExecute (наступает перед вызовом диалога), ОпАсcept (наступает, если пользователь в диалоге произвел выбор и нажал ОК) и OnCancel (наступает, если пользователь в диалоге не произвел выбор и нажал кнопку Отказ или клавишу Esc). Из этих событий важнейшим является OnAccept, в обработчике которого можно прочитать выбор пользователя. Например, если в приложении имеется компонент Memol и действие FileOpc'nl, загружающее в Memol текст из выбранного пользователем файла, то обработчик события OnAccept этого действия должен иметь вид: Memol.Lines.LoadFromFile(FileOpenl.Dialog.Filename);
4.5 Диспетчеризация действий на основе компонентов ActionManager, ActionMainMenuBar, ActionToolBar, CustomizeDIg 4.5.1 Диспетчер действий ActionManager Диспетчер действий ActionManager, появившийся в Delphi 6, создает список стандартных и нестандартных действий и в этом отношении подобен ActionList. Но возможности ActionManager несравненно шире. Он не только хранит набор действий. Он управляет также полосами действий — визуальными компонентами, на которых располагаются элементы пользовательского интерфейса. К таким компонентам относятся ActionMainMenuBar — полоса главного меню, и ActionToolBar — инструментальная панель. Эти компоненты могут вводиться в приложение непосредственно из палитры компонентов, а могут создаваться Редактором Действий ActionManager. Из окна Редактора Действий можно формировать полосы действий простым перетаскиванием на них необходимых действий. Компонент ActionManager запоминает информацию о составе набора действий и конфигурации полос действий в текстовом или двоичном файле на диске. При этом можно предоставить пользователю возможность настройки меню и инструментальных полос во время выполнения. Эта настройка сохранится в файле и в следующем сеансе работы автоматически загрузится в приложение. Настройка во время выполнения может осуществляться или вызовом соответствующего стандартного действия, или обращением к специальному компоненту CustomizeDlg, перенесенному на форму. В обоих случаях во время выполнения открывается то же диалоговое окно Редактора Действий, которое используется во время проектирования, но в несколько упрощенном варианте. Это окно позволяет пользователю в процессе выполнения приложения настраивать меню и инструментальные панели, перенося на них новые действия, убирая прежние, делая те или иные инструментальные панели видимыми или невидимыми. Компонент ActionManager обеспечивает сохранение в файле на диске этих пользовательских настроек и загрузку их в следующем сеансе работы с приложением. К сожалению, диалог Редактора Действий, позволяющий пользователю проводить настройки, реализован, естественно, на английском языке. Можно, конечно, создать собственный аналогичный диалог на русском языке. Но эта задача выходит за рамки данной книги. Частично вопросы оперативной настройки меню и инструментальных панелей с русскими диалогами рассмотрена в [2]. Остановимся на основном компоненте всей этой системы — ActionManager. Свойство State определяет реакцию на действия пользователя. Значение asNormal соответствует нормальной рабочей реакции: при щелчке пользователя на доступных интерфейсных компонентах действий выполняются соответствующие действия. Два других возможных значения — asSuspended и asSuspendedEnabled от-
276
Глава 4
ключаюг возможность выполнения действий. Щелчок пользователя не приводит ни к каким результатам. Эти значения State используются при настройке пользователем меню и инструментальных панелей. Различие этих двух значений в том, что первое не изменяет свойства Enabled действий, а второе переводит во всех действиях Enabled в true. Таким образом, если вы в некоторый момент хотите, чтобы полосы действий, управляемые компонентом ActionManagerl, перешли в состояние настройки, надо выполнить оператор A c t i o n M a n a g e r l . S t a t e := asSuspended,•
Тогда нажатие пользователем кнопки инструментальной панели или раздела меню не будет вызывать выполнение соответствующего действия и может интерпретироваться программой как-то иначе. Свойство FileName задает имя файла, в котором ActionManager хранит информацию о составе связанных с ним полос действий. В начале выполнения приложения ActionManager читает информацию из этого файла и в соответствии с ней формирует полосы действий. А при любых изменениях настройки в процессе выполнения компонент записывает в этот файл проведенные изменения. Так что при следующем сеансе работы состав полос действий будет таким, каким сделал его пользователь в предыдущем сеансе. Предупреждение Пока вы ведете проектирование и что-то изменяете в полосах действий, не устанавливайте значение свойства FileName. В противном случае в файле запомнится состояние полос предыдущего сеанса выполнения приложения, и введенные после этого изменения не будут видны в полосах действий при очередном тестировании приложения. Задавать значение FileName имеет смысл в конце проектирования. Если же после этого вам еее-таки потребуется изменить полосы действий, удалите с диска файл, в котором запоминается состояние. В этом случае при очередном выполнении приложения этот файл создастся заново.
Если вы в процессе проектирования впервые задаете значение FileName, вам надо просто записать в этом свойстве имя файла с путем к нему. При отсутствии пути файл будет создан в том каталоге, в котором расположен ваш проект. В случае, если вы хотите задать в качестве значения FileName имя уже существующего файла, можете воспользоваться для его выбора кнопкой с многоточием около свойства FileName в окне Инспектора Объектов. Свойство Images компонента Act ion Manager указывает на компонент ImageList, содержащий пиктограммы, используемые для обозначения действий. Об остальных свойствах ActionManager мы поговорим позднее. А пока рассмотрим основной инструмент проектирования —• Редактор Действий компонента ActionManager. Перенесите на форму ActionManager и сделайте на нем двойной щелчок. Вы попадете в окно Редактора Действий на страницу Actions, показанную на рис. 4.6. Только сначала панели Category (категории) и Aciions (действия) будут пустыми. Щелкнув правой кнопкой мыши, вы можете ввести новое нестандартное или стандартное действий, выбрав из контекстного меню соответственно команду New Action или New Standard Action. 0 том, что представляют собой эти действия подробно рассказано в разд. 4.3 и 4.4. После того как вы выбрали некоторые действия, в панели Aciions появятся имена объектов этих действий, а в панели Category — их категории. Если вы выделите какое-то действие, в Инспекторе Объектов вы можете увидеть и изменить его свойства: Caption, Hint, Shortcut и другие. В частности, вы можете изменить его категорию (свойство Category). В отличие от рассмотренного в разд. 4.3 компонента ActionList, в ActionManager понятие категории имеет вполне определенный смысл. При создании меню названия категорий станут надписями головных разде-
Организация управления приложением
277
Рис. 4.6 Страница Actions Редзюора Действий компонента ActionManager
Description Выделить весь о
. То add actionr 1о уош jppbcation simply dfeg and drop from eillier Categoiies en Achoia ото an existing Acliorfia
лов меню. Так что имеет смысл оформить их сразу так, как положено в меню, переведя на русский язык и введя символ амперсанда (см. рис. 4.6). Страница Toolbars (инструментальные панели) окна Редактора Действий компонента ActionManager показана на рис. 4.7. В окне Toolbars содержится список управляемых диспетчером ActionManager инструментальных панелей (компонентов ActionToolBar) и меню (компонентов ActionMainMenuBar). Компонент ActionMainMenuBar (полосу главного меню) надо добавлять в приложение обычным способом, перенося его из палитры компонентов. А добавить на форму компонент ActionToolBar можно, просто нажав кнопку New. Нажатие кнопки Delete удаляет выделенную панель действий из списка и вообще из приложения. Рис. 4.7 Страница Toolbars Редактора Действий компонента ActionManager
7,-EditingForml.flrtlonManagerl " Toolbais [ Toolbars
[Checkmark toggles virility)
Caption Options
E~ Apply caption oplbns t
0lag to Cleats Separate"?
После того как вы добавили кнопкой New новую инструментальную панель или перенесли на форму меню ActionMainMenuBar, можно вернуться на страницу Actions (рис. 4.6) и перетащить с нее мышью на панель или в полосу меню требуемые действия или целиком категории. Если в меню перетаскивается категория, ее надпись становится головным разделом меню. Индикаторы на странице Toolbars около названий полос действий управляют их видимостью. Если выключить такой индикатор, то во время выполнения соответствующая панель будет невидимой.
Глава 4
278
Если вы выделили на странице Toolbars одну из полое действий, вы можете увидеть в Инспекторе Объектов ее свойства. Большинство из них обычны для любых панелей. В частности, имеет смысл изменить задаваемую по умолчанию надпись (Caption) на что-то более понятное пользователю,, так как далее будет показано, что при настройках во время выполнения пользователь будет видеть эти надписи. На рис. 4.7 надписи изменены на «Главное меню*, «Инструментальная панель 1», «Инструментальная панель 2». Стоит также обратить внимание на свойство — AIlowHiding. Оно позволяет (при значении true) или запрещает делать полосу невидимой в процессе выполнения. Если задать AllowHiding = false, то в Редакторе Действий такая полоса отображается серой (см. полосу Главное меню на рис. 4.7). Для такой полосы ни во время проектирования, ни во время выполнения невозможно переключить индикатор видимости. Полоса всегда будет видна. Выпадающий список Capiion Options управляет способом отображения надписей (Caption) действий в выделенной в окне Toolbars инструментальной панели. Значение None соответствует отсутствию надписей. Отображаются только пиктограммы. Впрочем, если действие не имеет пиктограммы, то надпись все равно отображается. Значение Selective позволяет для каждого действия задать видимость надписи индивидуально. Значение АИ обеспечивает отображение надписей для всех действий. Индикатор Apply caption options to all toolbars распространяет выбранную опцию на все инструментальные панели. Страница Options Редактора Действий позволяет задать некоторые опции отображения. Индикатор Menus show recently used items first делает меню перестраиваемым, причем первыми располагаются разделы, которые недавно использовались. Если этот индикатор включен, то диспетчер действий при каждом очередном выполнении приложение проверяет, давно ли пользователь обращался к тому или иному разделу меню. Если на протяжении нескольких сеансов работы какие-то разделы не использовались, они делаются невидимыми. Точнее, их можно увидеть, только развернув меню полностью. Таким образом, меню перестраивается от сеанса к сеансу, делая видимыми и легко доступными те разделы, к которым пользователь обращается чаще всего. Кнопка Reset Usage Dato восстанавливает первоначальные установки полос действий. Индикатор Large icons приводит к отображению в полосах действий больших пиктограмм. Индикатор Show tips on toolbars управляет появлением всплывающих ярлычков у элементов инструментальных панелей. А индикатор Show shortcut keys in tips определяет включение в тексты этих ярлычков обозначений горячих клавиш. Выпадающий список Menu animation определяет форму отображения меню. Рис. 4.8 Страница Options Редактора Действий компонента ActionManager
7-- Editing Fo Toolbars | AcJions Options
Г~ ^c^ustow r«enfy used items fir^ Reset Usflfle Data
I
ОгЬа
р Show Jips on I J7 Show shortcut keys *i lips jOeJault
Dreg Го cieale SffD*afors
Организация управления приложением
279
4.5.2 Тестовое приложение Теперь давайте построим несложное тестовое приложение. Пусть мы хотим, чтобы наше приложение содержало окно редактирования Memo, полосу главного меню с разделами Файл, Правка и Сервис, а также две инструментальные панели: одна из которых должна дублировать основные разделы меню Правка, а другая — основные разделы меню Файл. Меню Файл должно содержать разделы Открыть, Сохранить, Сохранить как. Выход. Меню Правка должно содержать разделы Вырезать, Копировать, Вставить, Выделить все, Отменить, Удалить. Меню Сервис должно иметь один раздел — Настройка, позволяющий пользователю настроить полосу .главного меню и инструментальные панели. На рис. 4.9 показано это приложение во время выполнения. Показан момент, когда нажата кнопка настройки видимости одной из инструментальных панелей. Об этой кнопке будет сказано позднее. Рис. 4.9 Тестовое приложение с нажатой кнопкой настройки панели
$*' Тестовое привоженрц ArEionf^an^af i Файл Правка Сервис Вьреэеть 1^=1 £ипидмагь
сгевигь О Дтменить
£°'Partrrb.. fldd я Remove ButUn! » RtiK ТооЬаг
Начните новое приложение, перенесите на форму компоненты Memo (см. разд. 3.2.4), ImageList (см. разд. 4.2), ActionMainMenuBar и ActionManager. В компоненте ActionManager установите свойство Images равным ImageListl, связав тем самым диспетчер действий со списком изображений. Сделайте двойной щелчок на ActionManager. В открывшемся окне Редактора Действий перейдите на страницу Toolbars (рис. 4.7) и дважды нажмите на кнопку New, чтобы добавить в приложение две инструментальные полосы. Перейдите в окне Редактора Действий на страницу Actions (рис. 4.6) и щелкните правой кнопкой мыши. Выберите в контекстном меню команду New Standard Action, поскольку в основном мы будем использовать стандартные действия. В открывшемся списке стандартных действий (рис. 4.3) выделите все действия категории Edit (для выделения группы действий, расположенных подряд, надо держать нажатой клавишу Shift), действия TFileOpen, TFileSaveAs и TPileExit категории File (для выделения группы действий, не расположенных подряд, надо держать нажатой клавишу Ctrl) и действие TCustomizeActionBars категории Tools. Нажмите кнопку ОК. Вы вернетесь в окно рис. 4.6, в котором появятся введенные вами действия. Только, в отличие от рис. 4.6, все надписи в списках его панелей будут английскими. Нам потребуется еще одно нестандартное действие — Сохранить. Чтобы ввести его перейдите в левой панели окна в категорию File и нажмите кнопку New Action или выберите аналогичный раздел из контекстного меню. В правой панели окна на рис. 4.6 появится новое действие с именем Actionl. Вы ввели все необходимые действия. Теперь желательно перевести все их надписи на русский язык и сделать русские заголовки категорий. Выделите в правой палели рис. 4.6 все действия какой-то категории, например, File. Перейдите в Инспектор Объектов и задайте для них русское наименование в свойстве Category — «&Файл». Аналогичным образом переведите на русский язык названия остальных категорий. Далее надо, выделяя поочередно действия в правой панели рис. 4.6 за-
280
Глава 4
дать для них русские тексты в свойствах Caption и Hint (впрочем, поскольку приложение сугубо тестовое- можно этого и не делать, если не хочется). Для действия Actionl желательно задать более осмысленное имя (Name), например, FileSavel. Поскольку это нестандартное действие, для него надо задать надпись Caption (например, *&Сохранить*), тексты подсказок Hint (например, «Сохранить(Сохранить активный документ в файле») и, если хотите — горячие клавиши Shortcut (например, F2). Кроме того, неплохо несколько упорядочить значки действий Открыть и Сохранить. Почему-то в стандартном действии Открыть используется значок, чаще применяемый для раздела Сохранить. Если вы хотите исправить это, то задайте значение свойства Imagelndex действия Сохранить равным значению этого свойства в действии Открыть. А в действии Открыть уберите значок, задав значение равным Imagelndex - 1. С помощью кнопок со стрелками установите для каждой категории ту последовательность действий, которая желательна вам в соответствующих меню. После того как оформление отображения всех действий закончено, можно перенести их на полосы действий. Это делается обычной буксировкой их с помощью мыши. Выделите в левой панели рис. 4.6 категорию Файл и перетащите ее на полосу главного меню ActionMainMenuBarl. В меню появится выпадающее меню Файл со всеми разделами данной категории. Аналогичным образом можете формировать и инструментальные панели ActionToolBar. Если вы перетащите на панель категорию, на нее перенесутся все ее действия. Но можете перетаскивать и действия по одному. Если вы перетащили на панель или в меню лишнее действие, его можно удалить, просто перетащив мышью. Причем, это.можно сделать в любой момент, даже при закрытом Редакторе Действий. Завершив формирование меню И панелей, можете попробовать выполнить приложение, хотя оно еще не готово. Но сначала задайте в свойстве FileName компонента ActionManagerl имя файла, в котором будет сохраняться информация о конфигурации меню и панелей. После этого выполните приложение. Вы увидите, что команды редактирования в меню и инструментальных панелях работают так, как было описано в разд. 4.4. Они доступны, только если в фокусе находится окно редактирования (правда, в вашем приложении нет других элементов, но вы легко можете это проверять, добавив на форму, например, индикатор). При этом команды Вырезать, Копировать, Удалить доступны, только если в окне выделен какой-то текст. Команда Вставить доступна, только если в буфере обмена в данный момент находится текст. Команда Отменить доступна, только если была проведена какая-то операция редактирования. В правых концах инструментальных панелей вы можете увидеть кнопочки со стрелкой. Нажав такую кнопку, пользователь откроет меню с двумя разделами: Add or Remove Buttons (добавление или удаление кнопок) и Rese! Toolbar (восстановить начальное состояние панели). Первый раздел открывает меню (см. рис. 4.9), содержащее перечень действий и индикаторы рядом с ними. Выключение индикатора делает соответствующее действие невидимым, т.е. удаляет его с панели. Если вы таким образом удалили действие и затем завершили приложение, то при новом выполнении приложения это действие будет отсутствовать на панели. Проведенное изменение панели запомнилось в указанном вами файле и эта информация считалась в очередном сеансе работы. Восстановить начальное состояние панели можно, или опять включив индикатор действия, или выполнив команду Reset Toolbar в меню, показанном на ряс. 4.9. Но этими настройками панелей в нашем приложении дело не ограничивается. Если вы выполните команду Настройка (действие TCustomizeActionBars), перед вами откроется диалоговое окно, уже знакомое по Редактору Действий. В нем будут те же страницы Aclions, Toolbars, Oplions (см. рис. 4.6 — 4.8). Только на странице Actions (рис. 4.6) будут отсутствовать кнопки создания и удаления действий и не будет контекстного меню, содержащего эти команды. На странице Toolbars
Организация управления приложением
281
(рис. 4.7) будут отсутствовать кнопки New и Delete, а вместо них появится кнопка Reset, восстанавливающая начальное состояние панелей и меню. Страница Options будет такой же, как в Редакторе Действий (рис. 4.8). Таким образом, пользователь имеет возможность во время выполнения произвести серьезную настройку меню и панелей, сделать какие-то инструментальные панели невидимыми и т.п. Поэкспериментируйте с индикатором Menus show recently used items first на странице Options, чтобы почувствовать смысл автоматической перестройки меню в зависимости от частоты вызова тех или иных его разделов. В нашем тестовом приложении вызов диалогового окна настройки осуществляется стандартным действием TCustomizeActionBars. Но то же самое можно сделать с помощью компонента CustomizeDlg, Если хотите, попробуйте этот вариант. Добавьте на форму компонент CustomizeDlg и свяжите его свойством ActionManager с диспетчером действий ActionManagerl. А в обработчик щелчка на какой-нибудь кнопке или, например, в обработчик двойного щелчка на форме (событие OnDblClick) вставьте оператор CustomizeDlgl.Show;
Этот оператор вызовет диалог настройки и позволит реализовать все его возможности. Мы рассмотрели основные вопросы, связанные с разработкой меню и инструментальных панелей и их настройкой. Теперь давайте завершим наше тестовое приложение. Ведь в нем пока не реализованы команды работы с файлами. Команда Сохранить вообще недоступна, так как не написан обработчик соответствующего события. А команды Открыть и Сохранить как вызывают соответствующие стандартные диалоговые окна, но результат выбора файла в этих окнах нигде не используется. Да и сами диалоги выглядят неполноценными, так как в них отсутствуют шаблоны типов файлов. Начнем с приведения в порядок этих диалогов. Откройте двойным щелчком на ActionManagerl Редактор Действий и на странице Actions выделите действие Открыть. В Инспекторе объектов вы увидите свойства этого действия, и среди них — свойство Dialog. Это объект того диалога, который вызывается данным действием. Он подобен описанному в разд. 3.10.2 диалогу открытия файла OpenDialog. Раскройте щелчком на Dialog его свойства и задайте так, как описано в разд. 3.10.2, фильтры Filter и расширение по умолчанию Default Ext. Проведите аналогичную операцию с диалогом, вызываемым действием Сохранить как. Теперь надо записать операторы, использующие результаты вызова зтих диалогов. Вспомните — вы реализовали достаточно сложное приложение, не написав пока ни одного оператора. Но теперь надо, все-таки, немного заняться программированием. Давайте введем в приложение глобальную переменйую FileNanie и будем в ней хранить имя файла, в котором надо сохранять текст окна редактирования Memo: var Filename:
string;
Как уже говорилось в разд. 4.4, при выполнении действий, связанных с вызовом диалога, наступают события BeforeExecute (перед вызовом диалога), ОпАсcept (если пользователь в диалоге произвел выбор и нажал ОК) и OnCancel (если пользователь в диалоге не произвел выбор и нажал кнопку Отказ или клавишу Esc). Для команды Открыть нам надо написать только обработчик события ОпАсcept: FileName := FileOpenl.Dialog.FileName; Memol.Lines.LoadFronFile(FileName);
Первый оператор этого обработчика запоминает имя выбранного пользователем файла в переменной FileName. Это имя потребуется, если в дальнейшем поль-
282
Глава_4
зователь захочет сохранить отредактированный текст в файле. Второй оператор загружает файл в компонент Memol. Чтобы написать этот обработчик, надо в окне Редактора Действий выбрать действие Открыть и затем обычным образом, щелкнув в Инспекторе Объектов рядом с соответствующим событием, занести в текст указанные операторы. Для команды Сохронить кок надо написать обработчики событий BeforeExecute и OnAccept. В первом из них надо занести в диалог имя файла FileName как предлагаемое по умолчанию: FileSaveAsl.Dialog.FileNarae := FileName;
В обработчике события OnAccept надо запомнить имя выбранного пользователем файла и сохранить в этом файле текст из окна Memol: FileName ;= FileSaveAsl.Dialog.FileName; Memol.Lines.SaveToFile(FileName);
Осталось написать обработчик выполнения действия Сохранить. Это нестандартное действие и, как для всех нестандартных действий, его реализация записывается в обработчике события OnExecute. В нашем случае прежде всего надо проверить, запомнено ли в переменной FileName имя файла. Если запомнено, то текст окна Memol над о .с охранить в этом файле. А если переменная FileName пуста, значит еще не известно, в каком файле надо сохранять текст. В этом случае надо вызвать действие Сохронить кок, чтобы пользователь мог указать имя файла. Для вызова любого действия используется метод Execute. Так что требуемую логику можно осуществить оператором: if FileName о '' then Memol.Lines.SaveToFile(FileName) else FileSaveAsl.Execute;
Мы рассмотрели методику проектирования меню и инструментальных панелей с помощью диспетчера действий Action Manage г. Теперь коротко обсудим некоторые свойства рассмотренных компонентов, которые пока не затрагивались. Компонент ActionManager имеет свойство ActionBars — коллекцию объектов, описывающих используемые полосы действий. Класс этого свойства — TActionBars. Из свойств этого класса можно отметить Count — число полос действий, и ActionBars — индексированный массив объектов, описывающих полосы. Это свойство по умолчанию, так что выражения ActionBars.ActionBarsfi] и ActionBars[i] идентичны. Каждый объект, описывающий полосу, имеет класс Т ActionBarltem. Его основное свойство ActionBar —- сама полоса. С помощью указанных свойств можно в цикле просматривать или задавать какие-то свойства полос. Например, цикл for i:"О to ActionManagerl.ActionBars-Count — 1 do ActionManagerl.ActionBars[i].ActionBar.AllowHiding := false;
запрещает делать невидимыми все полосы, управляемые диспетчером ActionManagerl. Компонент ActionManager имеет свойство Linked Ac tionlasts — коллекцию связанных с ним списков действий. Свойство Linked Act ion Lists позволяет присоединить к ActionManager другие списки действий, например, список, сформированный в компоненте ActionList (см. разд. 4.3). Действиями этого присоединенного списка можно управлять так же, как действиями, введенными в самом ActionManager. Для этого служит пока не рассмотренный нами выпадающий список в левом верхнем углу окна на рис. 4.6. В нем можно выбрать раздел All Aclions (все действия) или действия одного из списков. Таким образом, можно, например, размещать (в частности, и во время выполнения) разделы обычного меню MainMenu на полосе Act io л То о Шаг.
Организация управления приложением
283
Во время проектирования добавление списков действий в L inked Ac tionLists производится щелчком на кнопке с многоточием около этого свойства в окне Инспектора Объектов. Открывается обычное окно формирования коллекций. В нем щелчок на кнопке Add New добавляет новый список. Затем надо выделить его и в инспекторе объектов в свойстве ActionList выбрать из выпадающего списка нужный элемент. В заключение рассмотрения диспетчера Act ion Manager надо отметить, что любые управляющие элементы Delphi, имеющие свойство Action, могут использовать действия из списка Action Manager. Их можно использовать в компонентах меню Main Menu и PopupMenu (см. разд. 3.8), в инструментальных панелях ТооШаг (см. разд. 3.9.4), в кнопках и т.д. Но, конечно, никакая настройка таких меню и панелей посредством диспетчера ActionManager невозможна. Точнее, она требует серьезного программирования, выходящего за рамки данной книги. В заключение остановимся но новом свойстве CoIorMap, появившемся в Delphi 7 в компонентах ActionToolBar и ActionMainMenuBar. Это свойство ссылается на карту цветов, используемую при отображении компонента. Введены также в библиотеку компоненты карт цветов StandardColorMap, Twilight CoIorMap и XPColorМар. В этих компонентах можно задать нестандартные цвета элементов инструментальных панелей и полос меню. А в свойстве CoIorMap панели можно сослаться на такой компонент карты цветов, и ваши установки будут восприняты. '
4.6 Приложение — объект Application и компонент ApplicationEvents В каждом приложении автоматически создается объект Application типа TApplication — приложение. Этот компонент отсутствует в палитре библиотеки, вероятно, только потому, что он всегда один в приложении. Application имеет ряд свойств, методов, событий, характеризующих приложение в целом. Рассмотрим сначала некоторые свойства Application. Булево свойство Active (только для чтения) характеризует активность приложения. Оно равно true, если форма приложения находится в фокусе. Если же пользователь переключился на работу с другим приложением, свойство Active равно false. Свойство ExeName является строкой, содержащей имя выполняемого файла ' с полным путем к нему. Это свойство удобно использовать, чтобы определить каталог, из которого запущено приложение и который может содержать другие файлы (настройки, документы, базы данных и т.п.), связанные с приложением. Выражение Ex tract PilePath(Application.ExeNanie) дает этот каталог. Обычно свойство ExeName тождественно функции ParamStr{0), возвращающей нулевой параметр командной строки — имя файла с путем. Свойство Title определяет строку, которая появляется около пиктограммы свернутого приложения. Если это свойство не изменяется во время выполнения, то оно равно опции Title, задаваемой во время проектирования на странице Application окна опций проекта (команда Project Options). Свойство может изменяться программно, например, изменяя надпись в зависимости от режима работы приложения. Свойство MainForm типа TForm определяет главную форму приложения. Булево свойство ShowMainForm определяет, должна ли главная форма быть видимой в момент запуска приложения на выполнение. По умолчанию оно равно true, что обеспечивает видимость главной формы в момент начала работы приложения. Если же установить в головном файле проекта Application.ShowMainForm равным false до вызова метода Application.Run и если при этом свойство Visible главной формы тоже равно false, то главная форма в первый момент будет невидимой.
284
Глава 4
Свойство HelpFile указывает файл справки, который используется в приложении в данный момент как файл по умолчанию. Если это свойство не изменяется во Бремя выполнения, то оно равно опции Help File, задаваемой во время проектирования на странице Application окна опций проекта (команда Project Options). Свойство можно изменять программно, назначая в зависимости от режима работы приложения тот или иной файл справки. Ряд свойств объекта Application определяет ярлычки подсказок компонентов приложения. Свойство Hint содержит текст подсказки Hint того визуального компонента или раздела меню, над которым в данный момент перемещается курсор мыши. Смена этого свойства происходит в момент события OnHint, которое будет рассмотрено позднее. Во время этого события текст подсказки переносится из свойства Hint компонента, на который переместился курсор мыши, в свойство Hint объекта Application. Свойство Application.Hint можно использовать для отображения этой подсказки или для установки и отображения в полосе состояния текста, характеризующего текущий режим приложения. Свойство HintColor типа TColor определяет цвет фона окна ярлычка. По умолчанию это цвет clInfoBk, но его значение можно изменять программно. Свойство HintPause определяет задержку появления ярлычка в миллисекундах после переноса курсора мыши на очередной компонент (по умолчанию 500 миллисекунд или половина секунды). Свойство HintHidePause аналогичным образом определяет интервал времени, после которого ярлычок становится невидимым (по умолчанию 2500 миллисекунд или две с половиной секунды). Свойство HintShortPause определяет аналогичным образом задержку перед появлением нового ярлычка, если •в данный момент отображается другой ярлычок (по умолчанию 50 миллисекунд). Это свойство позволяет предотвратить неприятное мерцание, если пользователь быстро перемещает курсор мыши над разными компонентами. Теперь остановимся на некоторых методах объекта Application. Методы Initialize — инициализация проекта, и Run — запуск выполнения приложения, включаются в каждый проект автоматически — вы можете это увидеть в головном файле проекта, если выполните команду Project | View Source. Там же вы можете увидеть применение метода создания форм CreateForm для всех автоматически создаваемых форм проекта. Если же в вашим проекте есть форма, которая исключена из списка автоматически создаваемых (команда Project | Options и соответствующая установка на странице Forms), то когда эта форма вам потребуется, вы должны будете вызвать этот метод: procedure CreateForm(ForrnClass:
T F o r m C l a s s ; var R e f e r e n c e ! ;
где FormClass — класс создаваемой формы. Reference — ссылка на создаваемый объект (его имя). Например: Application.CreateForm(TForm2, Form2);
Метод Terminate завершает выполнение приложения. Если вам надо завершить приложение из главной формы, то вместо метода Application .Terminate вы можете использовать метод Close главной формы. Но если вам надо закрыть приложение из какой-то вторичной формы, например, из диалога, то надо применять метод Application.Terminate. Метод Minimize сворачивает приложение, помещая его пиктограмму в полосу задач Windows. Ряд методов связан с работой со справочными файлами. Выше уже говорилось о свойстве HelpFile, указывающем текущий файл справки. Метод HelpContext: f u n c t i o n HelpContext(Context: THelpContext) : Boolean;
вызывает переход в файл справки на тему с идентификатором Context. Это идентификатор, который при проектировании справки поставлен в соответствие некоторой теме. Метод HelpJump:
Организация управления приложением
285
function HelpJump(const JurapID: s t r i n g ) : Boolean;
выполняет аналогичные действия, но его параметр JumpID — не идентификатор темы, а имя соответствующей темы в файле справки, задаваемое в нем сноской *. Метод HelpCommand: function HelpCominand(Command: Word; Data: L o n g i n t ) : Boolean;
позволяет выполнить указанную параметром Command команду API WinHelp с параметром Data. Метод генерирует событие Onllelp активной формы или приложения, а затем выполняет указанную команду WinHelp. Полный список команд WinHelp вы можете найти в теме WinHelp справочного файла Win32.Ыр, расположенного в каталоге ...\Program Files\Common Files\Borlond Shored\MSHelp. Приведем только некоторые из них. Команда HELP_CONTBNTS с параметром 0 отображает окно Содержание справки. Команда HELP_INDEX с параметром 0 отображает окно Указатель справки. Команда HELP_CONTEXT с параметром, равным идентификатору темы, отображает тему с заданным идентификатором (это тождественно рассмотренному ранее методу HelpContext). Команда HELP_CONTEXTPOPUP с параметром, равным идентификатору темы, делает то же самое, но отображает тему во всплывающем окне. В классе TApplication имеется еще немало методов, но часть из них используется в явном виде очень редко (вы можете посмотреть их во встроенной справке Delphi), а часть будет рассмотрена ниже при обсуждении событий объекта Application. Хотелось бы только обратить внимание читателя на очень полезный метод MessageBox, позволяющий вызывать диалоговое окно с указанным текстом, указанным заголовком и русскими надписями на кнопках (в русифицированных версиях Windows). Это наиболее удачный полностью русифицируемый стандартный диалог. См. о нем подробнее в гл. 16 разд. 16.11.3. В классе TApplication определено множество событий, которые очень полезны для организации приложения. Ранее для использования этих событий было необходимо вводить соответствующие обработчики и указывать на них объекту Application специальными операторами. В Delphi 5 введен компонент ApplicationEvents, существенно облегчивший эту задачу. Этот компонент перехватывает события объекта Application и, следовательно, обработчики этих событий теперь можно писать как обработчики событий невизуального компонента ApplicationEvents. На каждой форме приложения можно разместить свой компонент ApplicationEvents. События объекта Application будут передаваться всем этим компонентам. Если вы хотите, чтобы событие передавалось прежде всего какому-то одному из них, примените к нему метод Activate, который поставит его в начало очереди компонентов Application Events. Если же вы при этом не хотите, чтобы другие компоненты ApplicationEvents получали события, примените к привилегированному компоненту метод Can eel Dispatch. Тогда после обработки события в данном компоненте другие компоненты ApplicationEvents вообще не будут реагировать на эти события. Во многие обработчики событий компонента ApplicationEvents передается по ссылке параметр Handled. По умолчанию его значение равно false. Если вы обработали соответствующее событие и не хотите, чтобы оно далее обрабатывалось другими компонентами ApplicationEvents, надо в обработчике установить Handled = true. Если же вы оставите Handled = false, то событие будут пытаться обрабатывать другие компоненты ApplicationEvents (если они есть). Если событие так и останется необработанным, то его будет пытаться обработать активный компонент, а если не обработает — то активная форма. Предотвратить обработку события другими компонентами можно, используя описанный ранее метод CancelDispatch. Ниже приведена таблица событий компонента ApplicationEvents с их краткими описаниями.
Глава 4
286
Событие
Описание
On ActionExecute
Возникает при выполнении (Execute) некоторого действия, объявленного в компоненте ActionList, но не обработанного им (не написан соответствующий обработчик). Инициализация этого события может быть, например, выполнена методом Аррlication.Execute Act 1оп(). Если событие не обработано в ActionList, то оно может быть обработано на уровне приложения. В обработчик OnActionExecute передается параметр Action — действие и по ссылке передается параметр Handled (см. выше).
On Action Update
Возникает'при обновлении (Update) некоторого действия, объявленного в компоненте AetionList, но не обработанного им (не написан соответствующий обработчик). Если событие не обработано в ActionList, то оно может быть обработано на уровне приложения. В обработчик On Action Update передается параметр Action — действие и по ссылке передается параметр Handled (см. выше).
OnActivate
Возникает, когда приложение становится активным. Это происходит при начале выполнения и в случаях, когда пользователь, перейдя к другим приложениям, вернулся в данное. Если это событие обработано в ApplicationEvents, то предотвратить дальнейшую его обработку можно методом CancelDispatch.
OnDeaetivate
Возникает перед тем моментом, когда ириложение перестает быть активным (пользователь переключается на другое приложение). Если событие обработано в ApplicationEvents, то предотвратить дальнейшую его обработку можно методом CancelDispatch.
ОпЕхсерtion
Возникает, когда в приложении сгенерировано исключение, которое нигде не перехвачено. В обработчик передается параметр Sender — источник исключения, и параметр Е типа Exception — объект исключения. Параметр Е помогает определить тип исключения. Например, if (E is EDivByZero) then ... . В обработчике события OnException вы можете предусмотреть нестандартную обработку исключений на уровне приложения, например, русифицировать стандартные сообщения об исключениях и дать пользователю какие-то рекомендации. Учтите, что введение вами обработчика OnException отключит стандартную реакцию приложения на исключительные ситуации.
OnHelp
Возникает при запросе приложением справки. Это событие возникает, в частности, при выполнении рассмотренных ранее методов приложения HelpContext, HelpJump и Help Command. Обработчик может использоваться для каких-то подготовительных операций, например, для задания файла справки (параметр Арр Heat ion. HelpFile). В обработчик передаются параметры — Command команда API WinHelp (см. выше описание HelpCommand), Data — параметр этой команды и по ссылке передается булев параметр CallHelp. Если его установить в true, то после завершения обработчика будет вызван WinHelp, в противном случае вызова WinHelp не будет.
OnHint
Возникает в момент, когда курсор мыши начинает перемещаться над компонентом или разделом меню, в котором определено свойство Hint. При этом свойство Hint компонента переносится в свойство Hint приложения (Application.Hint) и в обработчике данного события может отображаться, например, в строке состояния.
Организация управления приложением
287
Событие
Описание
Onldle
Возникает, когда приложение начинает простаивать, ожидая, например, действий пользователя. В обработчик передается по ссылке булев параметр Done, который по умолчанию равен true. Если оставить его без изменения, то по окончании работы обработчика данного события будет вызвана функция WaitMessage API Windows, которая займется в период ожидания другими приложениями. Если задать Done = false, то эта функция вызываться не будет. Это может снизить производительность Windows.
On Mess age Возникает, когда приложение получает сообщение Windows (но не переданное функцией SendMessage API Windows). В обработчике события OnMeasage можно предусмотреть нестандартную (отличную от определенной в TAppIication) обработку сообщения. В обработчик передается параметр Msg — полученное сообщение и по ссылке передается параметр Handled (см. выше). Учтите, что в Windows передаются тысячи сообщений в секунду, так что обработчик OnMessage может серьезно снизить производительность приложения. On Minimize
Возникает при сворачивании приложения.
OnRestore
Возникает при восстановлении ранее свернутого приложения.
OnShortCut
Возникает при нажатии пользователем клавиши. Событие возникает до того, как возникло стандартное событие OnKeyDown компонента или формы. Обработчик позволяет предусмотреть нестандартную реакцию на нажатие какой-то клавиши. В него передается параметр сообщения Windows Msg, поле CharCode которого (Msg. Char Code) содержит виртуальный код нажатой клавиши. Передается также по ссылке параметр Handled. Если задать ему значение true, то стандартные события OnKeyDown, OnKeyPress, OnKeyUp не наступят.
OnShowIlint
Возникает, когда приложение собирается отобразить ярлычок с текстом подсказки Hint. В обработчик передается по ссылке параметр HintStr — первая часть свойства Hint компонента, ярлычок которого должен отображаться. В обработчике этот текст можно изменить. Так же по ссылке передается параметр CanShow. Если в обработчике установить его равным false, то ярлычок отображаться не будет. Третий параметр, передаваемый по ссылке — Hintlnfo. Это структура, поля которой (см. встроенную справку Delphi) содержат информацию о ярлычке: его координаты, цвет, задержки появления и т.п. В частности, имеется поле HintControl — компонент, сообщение которого должно отображаться в ярлычке, и поле HintStr — отображаемое сообщение. По умолчанию Hintlnfo.HintStr — первая часть свойства Hint компонента. Но в обработчике это значение можно изменить.
Приведем примеры использования событий компонента Applications vents. Обработчик события On Hint: procedure' Т F o r m l . A p p l i c a t i o n E v e n t s l H i n t ( S e n d e r : T O b j e c t ) ; begin StatusBarl.SimpleText:=Application.Hint; end;
отображает в полосе состояния StatusBarl (см. разд. 3.9.6) вторую часть свойства Hint любого компонента, в котором определено это свойство и над которым пере-
Глава 4
283
мещается курсор мыши. Отображение происходит независимо от значения свойства ShowHint компонента. Обработчик события OnShowHint: procedure ТForml.ApplicationEventslShowHint{ var H i n t S t r : String; var CanShow: Boolean; var H i n t l n f o : T H i n t l n f o ) ; begin 1 if ( H i n t l n f o . H i n t C o n t r o l . C l a s s N a m e = ' T E d i t ) then begin H i n t S t r : = ( H i n t I n f o . H i n t C o n t r o l as T E d i t ) . T e x t ; ApplicationEventsl.CancelDispatch; end;
подменяет для окон редактирования типа TEdit текст ярлычков на текст, содержащийся в этих окнах редактирования. Такой прием позволяет пользователю, подведя курсор мыши к окну редактирования, увидеть во всплывающем окне полный текст, который может не быть виден обычным образом, если он длинный и не помещается целиком в окне редактирования. Обработчик события OnHelp: function TForml.ApplicaticnEventslHelp(Command: Word; D a t a : Integer; var CallHelp: B o o l e a n ! : Boolean; begin if(Comraand=HELP_CONTEXT) and (Data Height then F o r m s [ I ] - H e i g h t := Height; if Forms[I].Width > Width then F o r m s ( I ] . W i d t h := W i d t h ; end;
Размеры форм, превышающие размер экрана, урезаются этим кодом. 10 Программ ирокшие в Delphi 7
290
Глава 4
В приведенных примерах надо, конечно, предусмотреть, чтобы при изменении размеров формы адекватно изменялось и расположение компонентов на ее поверхности. Этот вопрос подробно рассмотрен в разд. 5.2. Так же, как к формам, можно получить доступ и к модулям данных (см. разд. 9.13). Свойство DataModuIes[ I ] содержит список всех существующих на текущий момент модулей данных приложения, а параметр DataModuleCount указывает число таких модулей. Эти параметры можно использовать в совокупности для поиска по всем модулям данных приложения. Еще одно полезное свойство объекта Screen — Fonts (шрифты). Это свойство типа TStrings содержит список шрифтов, доступных на данном компьютере (свойство только для чтения). Его можно использовать в приложении, чтобы проверять, имеется ли на компьютере тот или иной шрифт, используемый в приложении. Если нет — то можно или дать пользователю соответствующее предупреждение, или сменить шрифт в приложении на один из доступных, или дать пользователю возможность самому выбрать соответствующий шрифт. Например, вы можете поместить в вашем приложении компонент списка TComboBox и при событии формы OnCreate загрузить его доступными шрифтами с помощью операторов: ComboBoxl.Items:=Screen.Fonts; ComboBoxl. Itemlndex:=0;
Тогда в нужный момент пользователь может выбрать подходящий шрифт из списка, а для того, чтобы этот шрифт использовался, например, для текста в компоненте RichEditl, в обработчик события OnClick или OnChange списка вставьте оператор: RichEditl.Font.Name := C o m b o B o x l . I t e m s [ C o m b o B o x l . I t e m l n d e x ] ;
Если хотите использовать выбранный шрифт для всех компонентов формы, в которых свойство ParentFont установлено в true, то приведенный выше оператор должен иметь вид: Font.Name := ComboBoxl.Items[ComboBoxl.Itemlndex];
Свойство Cursor объекта Screen определяет вид курсора. Если это свойство равно crDefau.lt, то вид курсора при перемещении над компонентами определяется установленными в них свойствами Cursor. Но если свойство Cursor объекта Screen отлично от crDefault, то соответствующие свойства компонентов отменяются и курсор имеет глобальный вид, заданный в Screen. Этим можно воспользоваться для такой частой задачи, как изменение курсора на форму «песочные часы? во время выполнения каких-то длинных операций. Это можно сделать следующим образом: • Screen.Cursor := c r H o u r g l a s s ; try finally Screen.Cursor:=crDefault; end;
При успешном или аварийном окончании длинных операций курсор в любом случае возвращается в значение по умолчанию. Если в приложении в какие-то отрезки времени используется отличный от crDefault глобальный вид курсора, то приведенный код можно изменить, чтобы по окончании длинных операций восстановить прежнее глобальное значение: var 0 1 d C u r s o r ; T C u r s o r ; begin
OldCursor :- Screen.Cursor; try finally
Организация управления приложением
291
Screen.Cursor:= OldCursor; end;
Имеется также свойство Cursors[ I ], которое представляет собой список доступных приложению курсоров. Вы можете создать и использовать также свой собственный курсор. Создается он и включается в ресурс приложения встроенным в Delphi Редактором Изображений (Image Editor). Как работать с этим редактором поясняется в гл. 6 разд. 6.1.2.4. А регистрируется созданный вами курсор с помощью функции LoadCursor. Сделать это можно следующим образом. Пусть, например, вы создали свой курсор и включили его в ресурс приложения под именем MyCursorl. Тогда в своем приложении вы можете ввести глобальную константу, обозначающую ваш курсор. Например: implementation const crMyCursor = 1;
Значение этой константы может лежать в пределах от -32768 до 32767. Но важно, чтобы она не совпадала с предопределенными значениями стандартных курсоров, лежащими в диапазоне от 0 до -21. В обработчике события OnCreate формы вы можете ввести оператор, регистрирующий ваш курсор в свойстве Cursors: Screen.Cursors [crMyCursor] :=LoadCursor (HInstance, ' M y C u r s o r l ' ) ,-
Тогда в нужный момент вы можете установить этот курсор в качестве глобального оператором Screen.Cursor:=crMyCursor ; а затем восстановить значение глобального курсора оператором Screen.Cursor:=crDefault;
Вы можете использовать ваш зарегистрированный курсор и как локальный, например, для панели Panell оператором Рапе11.Cursor:=crMyCursor;
С помощью Screen можно получить доступ к активной в текущий момент форме вашего приложения через свойство ActiveForm. Если в данный момент пользователь переключился с вашего приложения на какое-то другое и, следовательно, ни одна форма вашего приложения не активна, то ActiveForm указывает на форму, которая станет активной, когда пользователь вернется к вашему приложению. В момент переключения фокуса с одной вашей формы на другую, генерируется событие OnActiveFormChange. Аналогично с помощью свойства ActiveControl можно получить доступ к активному в данный момент оконному компоненту на активной форме. При смене фокуса генерируется событие OnActiveControlChange. Начиная с Delphi 4, предусмотрена возможность разработки мультиэкранных приложений, работающих одновременно с множеством мониторов. При этом приложение может решать, какие формы и диалоги надо отображать на том или ином мониторе. Свойства различных мониторов, используемых в таком приложении, можно найти с помощью свойства Screen.Monitors[ I ], где I — индекс монитора. Индекс О относится к первичному монитору. Свойство Screen.Monitors[ I ] является списком объектов типа TMonitor, содержащих информацию о конкретных мониторах. Среди свойств объектов типа TMonitor имеются Height — высота и Width — ширина экрана монитора. Кроме того, имеются свойства Left и Тор. Эти свойства означают следующее. Все доступное экранное пространство можно представить себе разбитым на экраны отдельных мониторов, размещающихся слева направо и сверху вниз. Соответственно свойства Left и Тор определяют координаты левого 10-
292
Глава 4
верхнего угла экрана монитора в этом логическом экранном пространстве. Объекты типа TMonitor имеют также свойство MonitorNum — номер монитора, представляющий собой его индекс е свойстве Screen.Monitors[ I ]. Для управления тем, на каком мониторе должна появляться та или иная форма, служит свойство формы Default Monitor. Это свойство может принимать значения: dmDesktop
не предпринимается попыток разместить форму на конкретном мониторе
dm Primary
форма размещается на первом мониторе в списке Screen. Monitors
dmMamForm
форма появляется на том мониторе, на котором размещена главная форма
dmActiveForm форма появляется на том мониторе, на котором размещена текущая активная форма
Глава
Разработка графического интерфейса пользователя 5.1 Требования к интерфейсу пользователя приложений для Windows 5.1.1 Общие рекомендации по разработке графического интерфейса Под графическим интерфейсом пользователя (Graphical User Interface — GUI) подразумевается тип экранного представления, при котором пользователь может выбирать команды, запускать задачи и просматривать списки файлов, указывая на пиктограммы или пункты в списках меню, показанных на экране. Действия могут, как правило, выполняться с помощью мыши, либо нажатием клавиш на клавиатуре. Типичным примером графического интерфейса пользователя является сам Windows. Delphi предоставляет разработчику приложения широкие возможности быстрого и качественного проектирования графического интерфейса пользователя — различных окон, кнопок, меню и т.д. Так что разработчик может в полной мере проявить свою фантазию. Но полеты фантазии очень полезно ограничивать. Есть определенные принципы построения графического интерфейса пользователя, и пренебрегающий ими обречен на то, что его приложение будет выглядеть чужеродным объектом в среде Windows. Для пользователя одним из принципиальных преимуществ работы с Windows является то, что большинство имеющихся приложений выглядят и ведут себя сходным образом. После того как вы поработаете с несколькими приложениями, вы обнаружите, что можете заранее почти наверняка сказать, где можно найти ту или иную функцию в программе, которую только что приобрели, или какие быстрые клавиши надо использовать для выполнения тех или иных операций. Фирма Microsoft предложила спецификации для разработки программного обеспечения Windows, направленные на то, чтобы пользователь не тратил время на освоение нюансов пользовательского интерфейса новой программы, чтобы он смог как можно скорее продуктивно применять ваше приложение. Эти спецификации образуют основу программы логотипа Windows, проводившейся Microsoft. Чтобы вы могли поставить на свой программный продукт штамп «Разработано для Windows ...9, ваша программа должна удовлетворять определенным критериям. Когда вы видите этот логотип на каком-то изделии, аппаратном или программном, вы можете быть уверены, что оно будет работать нормально в среде Windows. Конечно, вряд ли вы будете очень озабочены приобретением официального права на логотип Windows, если только не разработали сногсшибательную программу широкого применения, которую надеетесь успешно продавать на международном рынке. Но прислушаться к рекомендациям по разработке графического интерфейса пользователя в любом случае полезно. Они основаны на психофизиологических особенностях человека и существенно облегчат жизнь будущим пользователям вашей программы, увеличат производительность их работы.
294
Как вы сами можете видеть, работая с различными программами Windows, графический интерфейс пользователя любой серьезной программы должен включать в себя: • Главное меню. Реализуется компонентом MainMenu или с помощью компонентов ActionM a nage г и ActionMainMenuBar(cM. разд. 5.1.6, 3.8 и разд. 4.5) • Инструментальную панель быстрых кнопок, дублирующих основные разделы меню. Чаще всего это компонент ToolBar (см. разд. 3.9.4). Если панель большая, то целесообразно использовать ее совместно с компонентом PageScroller (см. разд. 3.9.4), обеспечивающим ее автоматическую прокрутку. Если у вас несколько инструментальных панелей и желательно дать пользователю возможность их перестроения, то панели ТооШаг целесообразно размещать в компонентах СооШаг или ControlBar (см. разд. 3.9.5). Панель, настраиваемую пользователем, проще всего делать на основе компонентов ActiouManager и ActionTooIBar (см. разд. 4.5) • Контекстные меню (реализуется компонентом PopupMenu — см. разд. 3.8), всплывающие при щелчке пользователя правой кнопкой мыши на том или ином компоненте • Продуманную последовательность переключения фокуса управляющих элементов (см. разд. 5.1.8) • Клавиши быстрого доступа ко всем разделам меню и всем управляющим элементам (см. разд. 3.7.2), горячие клавиши для: доступа к основным командам (см. разд. 3.8.1) • Ярлычки подсказок, всплывающие при перемещении курсора мыши над быстрыми кнопками и иными компонентами (см. разд. 5.1.9) • Полосу состояний (реализуется компонентом StatusBar — см. разд. 3.9.6), используемую для развернутых подсказок и выдачи различной информации пользователю • Файл справки, темы которого отображаются при нажатии клавиши F1 или при выборе пользователем соответствующего раздела меню (см. разд. 5.1.9) • Информацию о версии, доступную пользователю при щелчке на пиктограмме приложения правой кнопкой мыши (см. разд. 2.3.5) • Возможность настройки приложения и запоминания настроек, чтобы при очередном сеансе работы восстанавливались настройки, установленные в прошлом сеансе (см. разд. 5.7.2, 5.7.3 и 4.5) • Средства установки приложения, регистрации его в Windows и удаления из Windows (это нужно для приложений, которые содержат не один, а несколько файлов; для простых программ установка, регистрация и удаление не требуют специальных средств) Помимо общих рекомендаций по разработке графического интерфейса пользователя полезно также поддерживать с первых шагов разработки приложения контакт со своими заказчиками или будущими пользователями. С этой точки зрения не стоит сразу браться за разработку всего приложения. Полезно сначала построить его прототип, отражающий предлагаемый вами графический интерфейс, за которым на первых порах не скрывается реальных программ. Этот прототип можно обсудить с заказчиками и пользователями и только после их одобрения приступать к действительной реализации приложения. Одним из достоинств Delphi как раз и является возможность очнь быстрого построения подобных прототипов.
5.1.2 Многооконные приложения Чаще всего сколько-нибудь сложное приложение не может ограничиться одним окном. Поэтому, прежде всего, вам нужно решить вопрос управления окнами.
Разработка графического интерфейса пользователя
295
Есть две различные модели приложений: с интерфейсом одного документа (SDI) и с интерфейсом множества документов (MDI). В большинстве случаев следует отдавать предпочтение интерфейсу SDI. Этот интерфейс не обязательно предполагает наличие действительно только одного окна, как в приложениях Windows, типа *Калькулятор*. Такое приложение, как • Проводник» Windows, также является SDI приложением, но в нужные моменты оно создает вторичные окна для поиска файлов или папок, задания параметров, просмотра свойств файлов и других целей. С другой стороны, у приложений MDI тоже есть свои преимущества. Хороший пример такого приложения — Microsoft Word. В приложении MDI имеется родительское (первичное) окно и ряд дочерних окон (называемых также окнами документов). Бывают ситуации, когда выгодно отображать информацию в нескольких окнах, которые совместно используют элементы интерфейса (например, меню или инструментальные линейки). Окна документов управляются и ограничиваются родительским окном. Если вы уменьшаете размер родительского окна, то дочерние окна могут исчезать из поля зрения. Случаи, когда нужно использовать модель MDI, довольно редки. Прежде всего, это следует делать только тогда, когда все дочерние окна будут содержать идентичные объекты — например, текстовые документы или электронные таблицы. Не применяйте MDI, если вы собираетесь работать в приложении с дочерними окнами разного типа (например, текстовыми документами и электронными таблицами одновременно). Не применяйте MDI, если вы хотите управлять тем, какое из дочерних окон должно находиться поверх других, используя свойство «всегда наверху*, или если вы хотите управлять размерами окон, делать их невидимыми и т.п. Интерфейс MDI предназначен для очень узкого диапазона приложений, в которых все дочерние окна однородны (как это имеет место в Word или Excel). Приспособить его к чему-то другому не получится. Наконец, следует заметить, что Microsoft не поощряет разработку новых приложений MDI (в основном потому, что было написано слишком много плохих программ этого типа). Подробнее о технологии построения приложений MDI будет рассказано в разд. 5.5.4.
5.1.3 Стиль окон приложения Основным элементом любого приложения является форма — контейнер, в котором размещаются другие визуальные и невизуальные компоненты. С точки зрения пользователя форма — это окно, в котором он работает с приложением. Каждой новой форме, вводимой в приложение, соответствует свой модуль (unit), описывающий эту форму как класс и включающий, если необходимо, какие-то дополнительные константы, переменные, функции и процедуры. К внешнему виду окон в Windows предъявляются определенные требования. К счастью, Delphi автоматически обеспечивает стандартный для Windows вид окон вашего приложения. Но вам надо продумать и указать, какие кнопки в полосе системного меню должны быть доступны в том или ином окне, должно ли окно допускать изменение пользователем его размеров, каким должен быть заголовок окна. Все эти характеристики окон обеспечиваются установкой и управлением свойствами формы. Свойство BorderStyle определяет общий вид окна и операции с ним, которые разрешается выполнять пользователю. Это свойство может принимать следующие значения:
296
Глава 5
bsSizeable
Обычный вид окна Windows с полосой заголовка, с возможностью для пользователя изменять размеры окна с помощью кнопок в полосе заголовка или с помощью мыши, потянув за какой-либо край окна. Это значение BorderStyle задается по . умолчанию.
bsDialog
Неизменяемое по размерам окно. Типичное окно диалогов.
bsSingle
Окно, размер которого пользователь не может изменить, потянув курсором мыши край окна, но может менять кнопками в полосе заголовка.
bsToolWindow
То же, что bsSingle, но с полосой заголовка меньшего размера.
bsSizeToolWin То же, что bsSizeable, но с полосой заголовка меньшего размера и с отсутствием в ней кнопок изменения размера. bsNone
Без полосы заголовка. Окно не только не допускает изменения размера, но и не позволяет переместить его по экрану.
Свойство Borderlcons определяет набор кнопок, которые имеются в полосе заголовка. Множество кнопок задается элементами: bySistemMenu
кнопка системного меню -- это кнопка с крестиком. закрыва> ющая окно
byMinimizG by Maximize
кнопка Свернуть, сворачивает окно До пиктограммы кнопка Развернуть, разворачивает окно на весь экран
byHelp
кнопка справки
Следует отметить, что не все кнопки могут появляться при любых значениях BorderStyle. На рис. 5.1 представлен вид окон форм во время выполнения при некоторых сочетаниях свойств BorderStyle и Borderlcons. Для создания диалоговых окон обычно используется стиль заголовка bsDialog (формы FormS и Fonn4 на рис. 5.1), причем в этих окнах можно исключить кнопку системного меню (форма Form4), и в этом случае пользователь не может закрыть окно никакими способами, кроме как выполнить какие-то предписанные ему действия на этой форме. При стиле bsNone пользователь не может изменить ни размер, ни положение окна на экране. Формы FormS, FormG и FormS внешне различаются только размером полосы заголовка, уменьшенным в двух последних формах. Но между ними есть Рис. 5.1
Формы при разных сочетаниях свойств BorderStyle и Borderlcons
0*uM»i
Разработка графического интерфейса пользователя
297
и принципиальное различие: размер формы Рогтб (стиль заголовка bsSizeToolWin) пользователь может изменить, потянув курсором мыши за край окна. Для форм РоппЗ и FormS это невозможно. Обратите также внимание, что в формах Рогтб и Гогт8 задание кнопок свертывания и развертывания окна никак не влияет на его вид: эти кнопки просто не могут появляться в этих стилях полос заголовков окон. ХОрОШИЙ СТИЛЬ ПрОГрОММИРОВОНИЯ
''•••
'
'
• и •
|
_ _ ;
:::::
Без особой необходимости не делайте окна приложения с изменяемыми пользователем размерами. При изменении размеров, если не применены специальные приемы, описанные в разд. 5.2, нарушается компоновка окно, и пользователь ничего не выигрывает от своих операиий с окном. Окно имеет смысл делать с изменяемыми размерами, только если это позволяет пользователю изменять полезную площадь каких-то расположенных в нем компонентов отображения и редактирования информации: текстов, изображений, списков и т.п.
Хороший стиль программирования Для основного окна приложения с неизменяемыми размерами наиболее подходящий стиль — BorderSlyle = bsSingle с исключением из числа доступных кнопок кнопки Развернуть, (Border-icons.byMaximize = false). Это позволит пользователю сворачивать окно, восстанавливать, но не даст возможности развернуть окно на весь экран или изменить размер окна.
Хороший стиль программирования Для вторичных диалоговых окон наиболее подходящий стиль — BorderSlyle = bsDialog. Можно также использовать BorderStyle = bsSingle, одновременно исключая из числа доступных кнопок кнопку Развернуть (задавая BorderIcons.byMaxirnize = false). Это позволит пользователю сворачивать диалоговое окно, если оно заслоняет на экране что-то нужное ему, восстанавливать окно, но не даст возможности развернуть окно на весь экран или изменить размер окна. •AWbWVBBBBavBvtB^kiBa
Предупреждение Избегайте, как правило, стиля BorderSlyle - bsNone. Невозможность переместить окно может создать пользователю трудности, если окно заслонит на экране что-то интересующее пользователя.
Свойство формы WindowState определяет вид, в котором окно первоначально предъявляется пользователю при выполнении приложения. Оно может принимать значения: ws Normal
нормальный вид окна (это значение WindowState используется по умолчанию)
wsMinimized окно свернуто ws Maximized окно развернуто на весь экран Если свойство WindowState имеет значение wsNormal или пользователь, манипулируя кнопками в полосе заголовка окна, привел окно в это состояние, то положение окна при запуске приложения определяется свойством Position, которое может принимать значения;
Глава 5
298
poUusigned
Первоначальные размеры и положение окна во время выполнения те же, что во время проектирования. Это значение принимается по умолчанию, но обычно его следует изменить.
poScreenCeuter
Окно располагается в центре экрана. Размер окна тот, который был спроектирован. В мультиэкранных приложениях (см. разд. 4.7), работающих одновременно с множеством мониторов, эта центральная позиция может быть несколько изменена, чтобы изображение попало точно на один монитор, определяемый свойством Default Monitor.
poDesktopCenter
Окно располагается в центре экрана. Размер окна тот, который был спроектирован. Этот режим не приспосабливается к приложениям с множеством мониторов (см. разд. 4.7).
poDe fault
Местоположение и размер окна определяет Windows, учитывая размер и разрешение экрана. При последовательных показах окна его положение сдвигается немного вниз и вправо.
poDefaultPosOnly
Местоположение окна определяет Windows. При последовательных показах окна его положение сдвигается немного вниз и вправо. Размер окна — спроектированный.
poDefault-S izeOnly poMainFormCenter
Размер окна определяет Windows, учитывая размер и разрешение экрана. Положение окна - спроектированное.
poOwnerForm Center
Это значение предусмотрено, начиная с Delphi 6. Окно располагается в центре формы, указанной как владелец данной в свойстве Owner. Размер окна тот, который был спроектирован. Если свойство Owner указывает не форму, действует как poMainFormCenter.
Это значение предусмотрено, начиная с Delphi 5. Окно располагается в центре главной формы. Размер окна тот, который был спроектирован. Этот режим не приспосабливается к приложениям с множеством мониторов (см. разд. 4.7). Используется только для вторичных форм. Для главной формы действует так же, как poScreenCenter.
Хороший стиль программирования Обычно целесообразно для главной формы приложения задавать значение Position равным poScreenCenler или poDefault. И только в сравнительно редких случаях, когда на экране при выполнении приложения должно определенным образом располагаться несколько окон, имеет смысл оставлять значение poDesigned, принимаемое по умолчанию.
Если выбранное значение свойства Position предусматривает выбор размера формы самим Windows по умолчанию, то на этот выбор влияют свойства PixelsPerlnch и Scaled. По умолчанию первое из них задается равным количеству пикселов на дюйм в системе, второе установлено в false. Если задать другое число пикселов на дюйм, то свойство Scaled автоматически становится равным true. В этом случае при запуске приложения размер формы будет изменяться в соответствии с пересчетом заданного числа пикселов на дюйм к реальному числу пикселов на дюйм в системе (но только при разрешающем это значении свойства Position). В Delphi 7 в форму добавлены свойства ScreenSnap и SnapBuffer. Если установить ScreenSnap в true, то при перемещении пользователем окна приложения оно будет захватываться краем экрана, если пользователь приблизил его к краю на расстояние меньшее, чем число пикселов, заданное свойством SnapBuffer.
Разработка графического интерфейса пользователя
299
Свойство AutoScrol! определяет, будут ли на форме в процессе выполнения появляться автоматически полосы прокрутки в случае, если при выбранном пользователем размере окна не все компоненты помещаются в нем. Если значение AutoScroll равно true, то будут. В противном случае при уменьшении размера окна пользователь теряет доступ к компонентам, не поместившимся в его поле. Свойство AutoSize установленное в true обеспечивает автоматическое изменение размеров формы таким образом, чтобы были видны все размещенные на ней компоненты. Свойство Icon задает пиктограмму формы. По умолчанию используется стандартная пиктограмма Delphi. Нажав в Инспекторе Объектов кнопку с тремя точками в строке свойства Icon, вы попадаете в окно Редактора Изображений, которое уже не раз встречалось нам (см. например рис. 3.36 в разд. 3.7.2). Щелкнув в нем на кнопке Load (загрузить), вы можете выбрать любой файл с изображением пиктограммы (файл с расширением .ico). С Delphi поставляется некоторое число пиктограмм, расположенных в каталоге Images\Icons. Свойство Icon задает только пиктограмму формы, которая отображается в левом верхнем углу окна приложения в его нормальном состоянии. Но если пользователь свернет окно, то в полосе задач будет видна другая пиктограмма — пиктограмма приложения. Ту же пиктограмму увидит пользователь, если будет просматривать средствами Windows содержимое каталога. По умолчанию для нее используется стандартная пиктограмма Delphi. При свертывании приложения рядом с пиктограммой в полосе задач пользователь будет видеть надпись — по умолчанию это имя приложения. Если вы хотите, то можете изменить эту пиктограмму и эту надпись. Для этого вы должны выполнить команду Project j Options и в открывшемся окне опций проекта перейти на страницу Application (рис. 5.2). В этом окне вы можете задать заголовок (Title), который увидит пользователь в полосе задач при сворачивании приложения. А кнопка Load Icon позволяет вам выбрать пиктограмму, которая будет видна в полосе задач при сворачивании приложения или при просмотре пользователем каталога, в котором расположен выполняемый файл приложения. Рис. 5.2 Страница Application окна опций проекта
iPmiert n^ions for Protectl.exe Vasicnlnfo Fan*
\
Packages age* | U*ei
- Appicati»! atfrings
Qulpul stlir-gi
Г Defa*
OK
Caned
Глава 5
300
Одно из основных свойств формы — FormStyle, которое может принимать значения: fs Normal fsMDIForm
Окно обычного приложения. Это значение FormStyle принято по умолчанию. Родительская форма приложения MDI, т.е приложения с дочерними окнами, используемого при работе с несколькими документами одновременно.
fsMDIChild
Дочерняя форма приложения MDI.
fsStayOnTop
Окно, остающееся всегда поверх остальных окон Windows.
О приложениях MDI подробно будет рассказано в разд. 5.5.4. Так что значения fsMDIForm и fsMDIChild мы пока подробнее обсуждать не будем. А значение FormStyle = fsStayOnTop делает окно всегда остающимся на экране поверх остальных окон не только данного приложения, но и всех других приложений, в которые может перейти пользователь, Хороший стиль программирования —
-
Используйте стиль FormSlyle = fsStayOnTop для отображения окон сообщений пользователю о каких-то аварийных ситуациях.
В ряде случаев полезно предоставить пользователю самому решать, сделать ли данное окно располагающимся всегда поверх остальных, или нет. Например, ему может временно потребоваться перейти в какое-то другое приложение, чтобы получить необходимую информацию, и при этом будет хотеться видеть поверх этого приложения ваше окно, чтобы сравнивать в этих двух окнах какие-то данные. Такую возможность легко предоставить пользователю. Введите в меню вашего окна (рис. 5.3) раздел Поверх остальных, задайте свойство этого раздела AutoCheck = true, чтобы его состояние при каждом выполнении переключалось, и в обработчик щелчка на этом разделе вставьте оператор if
MStayOnTop.Checked Chen Forml.FormStyle := fsStayOnTop else Forml.FormStyle := fsMorraal;
В этом коде подразумевается, что объект раздела меню, о котором идет речь, назван MStayOnTop. Тогда при выборе пользователем этого раздела меню в нем появится индикатор (см. рис. 5.3), а окно приобретет статус расположенного всегда поверх остальных. При повторном выборе этого раздела индикатор исчезнет, и окно приобретет обычный статус. Свойство AutoCheck введено только начиная с Delphi 6. В более ранних версиях перед приведенным выше оператором надо вставить оператор MStayOnTop.Checked := not MStayOnTop.Checked; который программно переключает состояние раздела. Рис 5.3 Окно с переключающимся статусом
' Параметры •f Поверх осгальгых
Разработка графического интерфейса пользователя
301
5.1.4 Цветовое решение приложения Цвет является мощным средством воздействия на психику человека. Именно поэтому обращаться с ним надо очень осторожно. Неудачное цветовое решение может приводить к быстрому утомлению пользователя, работающего с вашим приложением, к рассеиванию его внимания, к частым ошибкам. Слишком яркий или неподходящий цвет может отвлекать внимание пользователя или вводить его в заблуждение, создавать трудности в работе. А удачно подобранная гамма цветов, осмысленные цветовые акценты снижают утомляемость, сосредоточивают внимание пользователя на выполняемых в данный момент операциях, повышают эффективность работы. С помощью цвета вы можете на что-то намекнуть или привлечь внимание к определенным областям экрана. Цвет может также связываться с различными состояниями объектов. Надо стремиться использовать ограниченный набор цветов и уделять внимание их правильному сочетанию. Расположение ярких цветов, таких, как красный, на зеленом или черном фоне затрудняет возможность сфокусироваться на них. Не рекомендуется использовать дополнительные цвета. Обычно наиболее приемлемым цветом для фона будет нейтральный цвет, например, светло-серый (используется в большинстве продуктов Microsoft). Помните также, что яркие цвета кажутся выступающими из плоскости экрана, в то время как темные как бы отступают вглубь. Цвет не должен использоваться в качестве основного средства передачи информации. Можно использовать различные панели, формы, штриховку и другие методики выделения областей экрана. Microsoft даже рекомендует разрабатывать приложение сначала в черно-белом варианте, а уже потом добавлять к нему цвет. Нельзя также забывать, что восприятие цвета очень индивидуально. А по оценке Microsoft девять процентов взрослого населения вообще страдают нарушениями цветов ос приятия. Поэтому не стоит навязывать пользователю свое видение цвета, даже если оно безукоризненно. Надо предоставить пользователю возможность самостоятельной настройки на наиболее приемлемую для него гамму. К тому же, не стоит забывать, что может быть кто-то захочет использовать вашу программу на машине с монохромным монитором. Посмотрим теперь, как задаются цвета приложения, разрабатываемого в Delphi. Большинство компонентов имеют свойство Color (цвет), который вы можете изменять в Инспекторе Объектов при проектировании или программно во время выполнения (если хотите, чтобы цвета в различных режимах работы приложения были разные). Щелкнув на этом свойстве в Инспекторе Объектов, вы можете увидеть в выпадающем списке большой набор предопределенных констант, обозначающих цвета. Все их можно разбить на две группы: статические цвета типа clBlack — черный, cl Green — зеленый и т.д., и системные цвета типа clWindow — текущий цвет фона окон, clMenuText — текущий цвет текста меню и т.д. Полный список всех констант и их описание, а также способ конструирования любого другого статического цвета вы можете найти в справочной части книги в гл. 17 в разд. «Color». Статические цвета вы выбираете сами, и они будут оставаться неизменными при работе приложения на любом компьютере. Это не очень хорошо, поскольку пользователь не сможет адаптировать вид вашего приложения к своим потребностям. При выборе желательной ему цветовой схемы пользователь может руководствоваться самыми разными соображениями: начиная с практических (например, он может хотеть установить черный фон, чтобы экономить энергию батареи), и кончая эстетическими (он может предпочитать, например, шкалу оттенков серого, потому что не различает цвета). Все это он не может делать, если вы задали в приложении статические цвета. Но уж если по каким-то соображениям вам надо их задать, старайтесь использовать базовый набор из 16 цветов. Если вы попытае-
302
Глава 5
тесь использовать 256 (или, что еще хуже, 16 миллионов) цветов, это может замедлить работу вашего приложения, или оно будет выглядеть плохо на машине пользователя с 16 цветами. К тому же подумайте (а, как правило, это надо проверить и экспериментально), как будет выглядеть ваше приложение на монохромном дисплее. Исходя из изложенных соображений, везде, где это имеет смысл, следует использовать для своего приложения палитру системных цветов. Это те цвета, которые устанавливает пользователь при настройке Windows. Когда вы создаете новую форму или размещаете на ней компоненты, Delphi автоматически присваивает им цвета в соответствии со схемой цветов, установленной a Windows. Конечно, вы будете менять эти установки по умолчанию. Но если при этом вы используете соответствующие константы системных цветов, то, когда пользователь изменит цветовую схему оформления экрана Windows, ваше приложение также будет соответственно меняться и не будет выпадать из общего стиля других приложений. Хороший стиль программирования Не злоупотребляйте в приложении яркими цветами. Пестрое приложение — обычно признак дилетантизма разработчика, утомляет пользователя, рассеивает его внимание. Как правило, используйте системные цвета, которые пользователь может перестраивать по своему усмотрению. Из статических цветов обычно имеет смысл использовать только clBlock — черный, clWhite — белый и cIRed — красный цвет предупреждения об опасности.
Единству цветового решения отдельных частей экрана способствует также использование свойства ParentColor. Если это свойство установлено в true, то цвет компонента соответствует цвету содержащего его контейнера или формы. Это обеспечивает единство цветового решения окна и, кроме того, позволяет программно изменять цвет сразу группы компонентов, если вы, например, хотите, чтобы их цвет зависел от текущего режима работы приложения. Для такого группового изменения достаточно изменить только цвет контейнера.
5.1.5 Шрифты текстов Шрифт надписей и текстов компонентов Delphi задается свойством Font, имеющим множество подсвойств. Кроме того, в компонентах имеется свойство ParentFont. Если это свойство установлено в true, то шрифт данного компонента берется из свойство Font его родительского компонента — панели или формы, на которой расположен компонент. Использование свойств ParentFont и ParentColor помогает обеспечить единообразие отображения компонентов в окне приложения. По умолчанию для всех компонентов Delphi задается имя шрифта MS Sans Serif и размер — 8. Константа множества символов Charset задается равной DEFAULT_CHARSET. Последнее означает, что шрифт выбирается только по его имени и размеру. Если описанный шрифт недоступен в системе, то Windows заменит его другим шрифтом. Чаще всего эти установки по умолчанию можно не изменять. Конечно, никто не мешает задать для каких-то компонентов другой размер шрифта или атрибуты типа полужирный, курсив и т.д. Но изменять имя шрифта для вашего приложения надо с определенной осторожностью. Дело в том, что шрифт, установленный на вашем компьютере, не обязательно должен иметься и на компьютере пользователя. Поэтому использование какого-то экзотического шрифта может привести к тому, что пользователь, запустив ваше приложение на своем компьютере, увидит вместо русского текста абракадабру на никому не понятном языке. Чтобы избежать таких казусов, вам придется прикладывать к своему приложению еще и файлы использованных шрифтов и пояснять пользователю, как он должен установить их на своем компьютере, если они там отсутствуют. Или вводить автоматическую
Разработк^графического^интерфейса^оль^ователя
303
проверку и установку нужных вам шрифтов в установочную программу вашего приложения. Использование шрифтов по умолчанию: System или MS Sans Serif, чаще всего позволяет избежать подобных неприятностей. Впрочем, увы, не всегда. Если вы используете для надписей русские тексты, то при запуске приложения на компьютере с нерусифицированным Windows иногда возможны неприятности. Для подобных случаев все-таки полезно приложить файлы использованных шрифтов к вашей программе. Вы можете при установке вашего приложения узнать, имеется ли на компьютере пользователя нужный шрифт, например, с помощью следующего кода: if
(Screen.Fonts.IndexOf('Arial') then . . .
= -1)
В этом коде многоточием обозначены действия, которые надо выполнить, если нужного шрифта (в примере — Arial) на компьютере нет. Эти действия могут заключаться в копировании файлов шрифта с установочной дискеты или CD ROM на компьютер пользователя. Другой выход из положения — ввести в приложение команду выбора шрифта пользователем. Это позволит ему выбрать подходящий шрифт из имеющихся в его системе. Осуществляется подобный выбор с помощью стандартного диалога, оформленного в виде компонента FontDialog (см. гл. 3 разд. 3.10.5). Проведенную пользователем установку можно запоминать в файле .ini, в реестре или в другом файле конфигурации и читать автоматически информацию из этого файла при каждом запуске приложения (см. разд. 5.7.2 и 5.7.3). Подробное рассмотрение всех свойств компонентов, связанных со шрифтами, примеры их использования и исследования вы найдете в гл. 17 в разд. «Pont».
5.1.6 Меню Практически любое приложение должно иметь меню, поскольку именно меню дает наиболее удобный доступ к функциям программы. Существует несколько различных типов меню: главное меню с выпадающими списками разделов, каскадные меню, в которых разделу первичного меню ставится в соответствие список подразделов, и всплывающие или контекстные меню, появляющиеся, если пользователь щелкает правой кнопкой мыши на каком-то компоненте. В Delphi меню обычно создаются компонентами MainMenu — главное меню, и PopupMenu — всплывающее меню. Оба компонента расположены на странице Standard. Кроме того, начиная с Delphi 6, имеется возможность создания меню, настраиваемого пользователем во время выполнения, с помощью компонентов ActionManager и ActionMainMenuBar. Все операции по проектированию меню с помощью этих компонентов подробно рассмотрены в разд. 3.В и 4.5. А в данном разделе мы обсудим требования, предъявляемые к меню приложений Windows. Основное требование к меню — их стандартизад ия. Это требование относится ко многим аспектам меню: месту размещения заголовков меню и их разделов, форме самих заголовков, клавишам быстрого доступа, организации каскадных меню. Цель стандартизации — облегчит пользователю работу с приложением. Надо, чтобы пользователю не приходилось думать, в каком меню и как ему надо открыть или сохранить файл, как ему получить справку, как работать с буфером обмена Clipboard и т.д. Для осуществления всех этих операций у пользователя, поработавшего хотя бы с несколькими приложениями Windows, вырабатывается стойкий автоматизм действий и недопустимо этот автоматизм ломать. Начнем рассмотрение требований с размещения заголовков меню. Типичная полоса главного меню приведена на рис. 5.4. Конечно, состав меню зависит от конкретного приложения. Но размещение общепринятыхразделов должно быть стан-
Глава 5
304
дартизировзнным. Все пользователи уже привыкли, что меню Файл размещается слева в полосе главного меню, раздел справки — справа, перед ним в приложениях MDI размещается меню Окно и т.д. Главное меню должно также снабжаться инструментальной панелью (см. рис. 5.4), быстрые кнопки которой дублируют наиболее часто используемые команды меню. На этих кнопках надо использовать, по возможности, привычные картинки. В Delphi имеется множество изображений кнопок на все случаи жизни, но, к сожалению, они не всегда имеют привычный для пользователя рисунок. Выходом из этого положения является использование картинок, присущих стандартным действиям. Технология работы со стандартными действиями рассмотрена в разд. 4.4. Рис. 5.4
17 !екстиЕЫй редактор RichEdit
Типичная полоса главного меню
вуйл [Травка Форраг
и инструментальная панель Это текст.
|волиф. Редактирование теиста
По возможности стандартным должно быть и р_ас под ожен и е_разяе л о в в выпадающих меню. На рис. 5.5 приведены распространенные варианты меню Файл — работа с файлами, Правка — работа с текстами. Окно — управление окнами в приложении MDI и меню Справка или 2 — просмотр справочных данных. Обратите внимание, что, например, раздел Выход всегда размещается последним в меню Файл, а раздел информации о версии программы О программе — последним в справочном меню. Рис. 5.5
а)
• Правка D Создать
Типовые меню
ctri+z
*"> Отменить
&• Открыть
Jt вырезать
Закрытр Q Сохранить,.,
К
Согранитькак... Предваритегьиый гросиотр
Ctrl+X
% Копировать
CtrN-C
Щ Вставить
QrH-V
И Найти. . ,
Ctrl+F
А? =
уд Запенить,,.
Уггановиа принтера.,. выход
;
Ctrl+Q
В)
Новое
Быт справки FI Опрогранме
Каскад Упирядочить Улорядоштть значки
•f 2 Окно 2 ЗОкноЗ
По вертикали
J
Разработка графического^нтерфейса пользователя
305
Группы функционально связанных разделов отделяются в выпадающих меню разделителями. Названия разделов меню должны быть привычными пользователю. Если вы не знаете, как назвать какой-то раздел, не изобретайте свое имя, а попытайтесь найти аналогичный раздел в какой-нибудь русифицированной программе Microsoft для Windows. Названия должны быть краткими и понятными. Не используйте фраз, да и вообще больше двух слов, поскольку это перегружает экран и замедляет выбор пользователя. Названия разделов должны начинаться с заглавной буквы. Применительно к английским названиям разделов существует требование, чтобы каждое слово тоже начиналось с заглавной буквы. Но применительно к русским названиям это правило не применяется. Названия разделов меню, связанных с вызовом диалоговых окон, должны заканчиваться мно готоч и ем. показывающим пользователю, что при выборе этого раздела ему предстоит установить в диалоге еще какие-то параметры. Разделы, к которым относятся каскадные меню (например, раздел Упорядочить на рис. 5.5 в) должны заканчиваться стрелкой, указывающей на наличие дочернего меню данного раздела. Delphi ставит эту стрелку автоматически, так что о ней вам думать не приходится. Вообще злоупотреблять каскадными меню не следует, так как пользователю не так просто до них добираться. Если в дочернем меню должно быть много разделов, например, связанных с какими-то опциями и настройками, то подумайте, не лучше ли вместо этого дочернего меню предусмотреть диалоговое окно, в котором эти опции будут более обозримыми и доступными. В каждом названии раздела должен быть выделен подчеркиванием символ, соответствующий клавише быстрого доступа к разделу (клавиша A l l плюс подчеркнутый символ). Хотя вряд ли такими клавишами часто пользуются, но традиция указания таких клавиш незыблема. Многим разделам могут быть поставлены в соответствие горячие клавиши, позволяющие обратиться к команде данного раздела, даже не заходя в меню. Комбинации таких горячих клавиш должны быть традиционными. Например, команды вырезания, копирования и вставки фрагментов текста практически всегда имеют горячие клавиши Grl-X, Qrl-C и Orl-V соответственно. Заданные сочетания клавиш отображаются в заголовках соответствующих разделов меню (см. рис. 5.5 а и 5.5 б). Многие разделы меню желательно снабжать пиктограммами (см. рис. 5.5 а и 5.5 б), причем пиктограммы для стандартных разделов должны быть общепринятыми, знакомыми пользователю. Некоторые разделы меню, соответствующие выбору каких-то настроек, могут содержать индикаторы, показывающие, выбран или нет данный раздел, и могут содержать радиокнопки. Способы создания подобных разделов меню см. в разд. 3.8.1. Не все разделы меню имеют смысл в любой момент работы пользователя с приложением. Например, если в приложении не открыт ни один документ, то бессмысленно выполнять команды редактирования в меню Правка. Если в тексте документа ничего не изменялось, то бессмысленным является раздел этого меню Отменить, отменяющий последнюю команду редактирования. Такие меню и отдельные разделы должны делаться временно недоступными или невидимыми. Это осуществляется заданием значения false свойствам раздела Enabled или Visible соответственно. Различие между недоступными и невидимыми разделами в том, что недоступный раздел виден в меню, но отображается серым цветом, а невидимый раздел просто исчезает из меню, причем нижележащие разделы смыкаются, занимая его место. Выбор того или иного варианта — дело вкуса и удобства работы пользователя. Вероятно, целиком меню лучше делать невидимыми, а отдельные разделы — недоступными. Например, пока ни один документ не открыт, меню Правка можно сделать невидимым, чтобы он не отвлекал внимания пользователя. А раздел Отменить
306
Глава 5
этого меню в соответствующих ситуациях лучше делать недоступным, чтобы лоль', зователь видел, что такой раздел в меню есть и им можно будет воспользоваться в случае ошибки редактирования. Вот и все основные требования к главному меню приложения. Проектирование меню — не очень быстрый процесс и обидно повторять его снова и снова для каждого нового приложения, тем более, что требование стандартизации приводит к тому, что одни и те же разделы с одинаковыми свойствами кочуют из приложения в приложение. Поэтому можно рекомендовать один раз потратить время, создать меню, содержащее большинство разделов, которые могут вам понадобиться в различных приложениях, и сохранить это меню как шаблон (в разд. 3.8.1 рассказано, как это делается с помощью команды Save As Template в Конструкторе Меню). В дальнейшем вы сможете использовать этот шаблон в любом своем приложении, загружая его командой Insert From Template Конструктора Меню в компонент MainMenu. А удалить после этого разделы, не используемые В данном приложении, не представляет никакого труда — вы просто выделяете соответствующий раздел в Конструкторе Меню и нажимаете клавишу Del. Технология проектирования меню подробно рассмотрена в разд. 3.8.1, 4.3 и 4.5. Поскольку меню обычно проектируется в комплексе с диспетчером действий, списком изображений, инструментальными панелями, то указанная выше возможность сохранения шаблона самого меню не слишком помогает избежать повторения множества проектных операций в каждом новом приложении. Поэтому можно рекомендовать сохранять как шаблон всю совокупность компонентов: диспетчер действий, список изображений, меню, инструментальные панели. Как это делается — показано в разд. 8.2 гл. 8. Создание подобного шаблона заметно сэкономит ваше время при разработке последующих проектов и будет способствовать единообразию интерфейса в серии ваших проектов, что также немаловажно. Помимо главного меню в приложении обычно должны быть контекстные всплывающие меню отдельных компонентов, например, окон редактирования. Эти меню обычно дублируют команды главного меню, используемые при работе с данным компонентом. В разд. 3.8.2 рассказано, как проектируются всплывающие меню с помощью компонента PopupMenu. Всплывающие меню желательно не перегружать разделами, вводя в них только действительно часто используемые команды.
5.1.7 Компоновка форм Каждое окно, которое вы вводите в свое приложение, должно быть тщательно продумано и скомпоновано. Удачная компоновка может стимулировать эффективную работу пользователя, а неудачная — рассеивать внимание, отвлекать, заставлять тратить лишнее время на поиск нужной кнопки или индикатора. Управляющие элементы и функционально связанные с ними компоненты экрана должны быть зрительно объединены в группы, заголовки которых коротко и четко поясняют их назначение. Такое объединение позволяют осуществлять различные панели, рассмотренные в гл. 3 разд. 3.9. Можно рекомендовать, как правило, размещать компоненты не непосредственно на форме, а на таких панелях. Но и внутри панелей надо продумывать размещение компонентов, как с точки зрения эстетики, так и с точки зрения визуального отражения взаимоотношений элементов. Например, если имеется кнопка, которая разворачивает окно списка, то эти два компонента должны быть визуально связаны между собой: размещены на одной панели и в непосредственной близости друг от друга. Если же ваш экран представляет собой случайные скопления кнопок, то именно так он и будет восприниматься. И в следующий раз пользователь не захочет пользоваться вашей программой. .
Разработка графического интерфейса пользователя
307
Каждое окно должно иметь некоторую центральную тему, которой подчиняется его композиция. Пользователь должен понимать, для чего предназначено данное окно и что в нем наиболее важно. При этом недопустимо перегружать окно большим числом органов управления, ввода и отображения информации. В окне должно отображаться главное, а все детали и дополнительную информацию можно отнести на вспомогательные окна. Для этого полезно вводить в окно кнопки с надписью «Больше...», многоточие в которой показывает, что при нажатии этой кнопки откроется вспомогательное окно с дополнительной информацией. Помогают также разгрузить окно многостраничные компоненты с закладками, рассмотренные в разд. 3.9.3. Они дают возможность пользователю легко переключаться между разными по тематике страницами, на каждой из которых имеется необходимый минимум информации. Примеры удачной организации окон вы можете посмотреть в Delphi, выполнив команду Tools Environment Options и полистав страницы .окна опций. Еще один принцип, которого надо придерживаться при проектировании окон — стилистическое единство всех окон в приложении. Недопустимо, чтобы сходные по функциям органы управления в разных окнах назывались по-разному или размещались в разных местах окон. Все это мешает работе с приложением, отвлекает пользователя, заставляет его думать не о сущности работы, а о том, как приспособиться к тому или иному окну. Появившийся начиная с Delphi 5 компонент Frame — фрейм (см. разд. 3.9.7) позволяет один раз разработать некий повторяющийся фрагмент окна, поместить его в Депозитарий (см. разд. 2.6), а затем использовать его в разных формах и приложениях. Стилистическому единству окон приложения способствует имеющаяся в Delphi возможность создания иерархии форм. Эта возможность рассмотрена в разд. 2.6. Приступая к большому проекту полезно сначала создать иерархическую последовательность форм, а затем уже, используя эту иерархию, проектировать конкретные окна. Кроме всего прочего, это позволяет сэкономить немало времени при разработке, потому что внесение каких-то усовершенствований в одну форму автоматически ведет к тиражированию этих изменений в других формах. Единство стилистических решений важно не только внутри приложения, но и в рамках серии разрабатываемых вами приложений. Это нетрудно обеспечить с помощью имеющихся в Delphi многочисленных способов повторного использования кодов. Вам достаточно один раз разработать какие-то часто применяемые формы — ввода пароля, запроса или предупреждения пользователя и т.п., включить их в Депозитарий, а затем вы можете использовать их многократно во всех своих проектах. И последнее — вам надо заботиться не только о единстве стиля внутри своих приложений, но и о том, как ваше приложение впишется в общую организацию рабочего пространства системы и как оно будет взаимодействовать с другими приложениями. Если приложение выпадает из общего ансамбля, оно напрашивается на неприятности.
5.1.8 Последовательность фокусировки элементов При проектировании приложения важно правильно определить последовательность табуляции оконных компонентов. Под этим понимается последовательность, в которой переключается фокус с компонента на компонент, когда пользователь нажимает клавишу табуляции Tab. Это важно, поскольку в ряде случаев пользователю удобнее работать не с мышью, а с клавиатурой. Пусть, например, вводя данные о каком-то сотруднике пользователь должен в отдельных окнах редактирования указать фамилию, имя и отчество. Конечно, набрав фамилию ему удобнее нажать клавишу Tab и набирать имя, а потом опять, нажав Tab, набирать
308
Глава 5
отчество, чем каждый раз отрываться от клавиатуры, хватать мышь и переключаться в новое окно редактирования. Свойство формы ActiveControl, установленное в процессе проектирования, определяет, какой из размещенных на форме компонентов будет в фокусе в первый момент при выполнении приложения. В процессе выполнения это свойство изменяется и показывает тот компонент, который в данный момент находится в фокусе. Последовательность табуляции задается свойствами TabOrder компонентов. Первоначальная последовательность табуляции определяется просто той последовательностью, в которой размещались управляющие элементы на форме. Первому элементу присваивается значение TabOrder, равное 0, второму 1 и т.д. Значение TabOrder, равное нулю, означает, что при первом появлении формы на экране в фокусе будет именно этот компонент (если не задано свойство формы ActiveControl). Если на форме имеются панели, фреймы и иные контейнеры, включающие в себя другие компоненты, то последовательность табуляции составляется независимо для каждого компонент а-контейнера. Например, для формы будет последовательность, включающая некоторую панель как один компонент. А уже внутри этой панели будет своя последовательность табуляции, определяющая последовательность получения фокуса ее дочерними оконными компонентами. Каждый управляющий элемент имеет уникальный номер TabOrder внутри своего родительского компонента. Поэтому изменение значения TabOrder какого-то элемента на значение, уже существующее у другого элемента, приведет к тому, что значения TabOrder всех последующих элементов автоматически изменятся, чтобы не допустить дублирования. Если задать элементу значение TabOrder, большее, чем число элементов в родительском компоненте, он просто станет последним в последовательности табуляций. Из-за того, что при изменении TabOrder одного компонента могут меняться TabOrder других компонентов, устанавливать эти свойства поочередно в отдельных компонентах трудно. Поэтому в среде проектирования Delphi имеется специальная команда Edit | Tab Order, позволяющая в режиме диалога задать последовательность табуляции всех элементов. Значение свойства TabOrder играет роль только, если другое свойство компонента — TabStop установлено в true и если компонент имеет родителя. Например, для формы свойство TabOrder имеет смысл только в случае, если для формы задан родитель в виде другой формы. Установка TabStop в false приводит к тому, что компонент выпадает из последовательности табуляции и ему невозможно передать фокус клавишей ТоЬ (однако предать фокус мышью, конечно, можно). Имеется и программная возможность переключения фокуса — это метод SetFocus. Например, если вы хотите переключить в какой-то момент фокус на окно Edit2, вы можете сделать это оператором: Edit2.SetFocus;
Выше говорилось, что в приложении с несколькими окнами редактирования, в которые пользователь должен поочередно вводить какие-то данные, ему удобно переключаться между окнами клавишей табуляции. Но еще удобнее пользователю, закончившему ввод в одном окне, нажать клавишу Enler и автоматически перейти к другому окяу. Это можно сделать, обрабатывая событие нажатия клавиши OnKeyDown (см. о событиях при нажатии клавиш в разд. 5.3.2). Например, если после ввода данных в окно Editl пользователю надо переключаться в окно Edit2, то обработчик события OnKeyDown окна Editl можно сделать следующим: procedure T F o r m l . E d i t l K e y D o w n [ S e n d e r : TObject; var Key: Word; Shift: TShiftState); begin if (Key = VK_RETURN) then Edit2. SetFocus,end,-
Разработка графического интерфейса пользователя
309
Единственный оператор этого обработчика проверяет, не является ли клавиша, нажатая пользователем, клавишей Enter. Если это клавиша Enler, то фокус передается окну Edit2 методом SetFocus. Можно подобные операторы ввести во все окна, обеспечивая требуемую последовательность действий пользователя (впрочем, свобода действий пользователя от этого не ограничивается, поскольку он всегда может вернуться вручную к нужному окну). Однако можно сделать все это более компактно, используя один обработчик для различных окон редактирования и кнопок. Для этого может использоваться метод FindNextControl, возвращающий дочерний компонент, следующий в последовательности табуляции. Если компонент, имеющий в данный момент фокус, является последним в последовательности табуляции, то возвращается первый компонент этой последовательности. Метод определен следующим образом: f u n c t i o n FindNextControl(CurControl: TWinControl; GoForward, CheckTabStop, Checkparent: Boolean]: TWinControl;
Он находит и возвращает следующий за указанным в параметре CurControl дочерний оконный компонент в соответствии с последовательностью табуляция. Параметр GoForward определяет направление поиска. Если он равен true, то поиск проводится вперед и возвращается компонент, следующий за CurControl. Если же параметр GoForward равен false, то возвращается предшествующий ком* понент. Параметры CheckTabStop и CheckParcnt определяют условия поиска. Если, CheckTabStop равен true, то просматриваются только компоненты, в которых свойство TabStop установлено в true. При CheckTabStop равном false значение TabStop не принимается во внимание. Если параметр CheckParent равен true, то просматриваются только компоненты, в свойстве Parent которых указан данный оконный элемент, т.е. просматриваются только прямые потомки. Если CheckParent равен false, то просматриваются все, даже косвенные потомки данного элемента. Таким образом, для решения поставленной нами задачи — автоматической передачи фокуса при нажатии клавиши Enter, вы можете написать единый обработчик событий OnKeyDown всех интересующих вас оконных компонентов, содержащий оператор: if
(Key = VK_RETURN| then FincfflextContiol(Sender as TWinControl, false).SetFocus;
true,
true,
Этот оператор обеспечивает передачу фокуса очередному компоненту. Для кнопок аналогичный оператор можно вставить в обработчик события OnClick: FindNextControl(Sender as T W i n C o n t r o l , false).SetFocus;
true, true,
5.1.9 Подсказки и контекстно-зависимые справки Приложение должно предельно облегчать работу пользователя, снабжая его системой подсказок, помогающих сориентироваться в приложении. Эта система включает в себя: • Ярлычки, которые всплывают, когда пользователь задержит курсор мыши над каким-то элементом окна приложения. В частности, такими ярлычками обязательно должны снабжаться быстрые кнопки инструментальных панелей, поскольку нанесенные на них пиктограммы часто не настолько выразительны, чтобы пользователь без дополнительной подсказки мог понять их назначение.
310
Глава 5
• Более развернутые подсказки в панели состояния или в другом отведенном под это месте экрана, которые появляются при перемещении курсора мыши в ту или иную область окна приложения. • Встроенную систему контекстно-зависимой оперативной справки, вызываемую по клавише F 1 . • Раздел меню Справка, позволяющий пользователю открыть стандартный файл справки Windows Mp, содержащий в виде гипертекста развернутую информацию по интересующим пользователя вопросам. Тексты ярлычков и подсказок панели состояния устанавливаются для любых визуальных компонентов в свойстве Hint в виде строки текста, состоящей из двух частей, разделенных символом вертикальной черты ''. Первая часть, обычно очень краткая, предназначена для отображения в ярлычке; вторая более развернутая подсказка предназначена для отображения в панели состояния или ином заданном месте экрана. Например, в кнопке, соответствующей команде открытия файла, в свойстве Hint может быть задан текст: Открыть|Открытие текстового файла
Как частный случай, в свойстве Hint может быть задана только первая часть подсказки без символа '|'. Для того чтобы первая часть подсказки появлялась во всплывающем ярлычке, когда пользователь задержит курсор мыши над данным компонентом, надо сделать следующее: 1. Указать тексты свойства Hint для всех компонентов, для которых вы хотите обеспечить ярлычок подсказки. 2. Установить свойства ShowHint (показать подсказку) этих компонентов в true или установить в true свойство Pa rent ShowHint (отобразить свойство ShowHint родителя) и установить в true свойство ShowHint контейнера, содержащего данные компоненты. Конечно, вы можете устанавливать свойства в true или false программно, включая и отключая подсказки в различных режимах работы приложения. При ShowHint, установленном в true, окно подсказки будет всплывать, даже если компонент в данный момент недоступен (свойство Enabled = false). Если вы не задали значение свойства компонента Hint, но установили в true свойство ShowHint или установили в true свойство Pa rent ShowHint, а в родительском компоненте ShowHint = true, то в окне подсказки будет отображаться текст Hint из родительского компонента. Правда, все описанное выше справедливо при значении свойства ShowHint приложения Application равном true (это значение задано по умолчанию). Если установить Application.ShowHint в false, то окна подсказки не будут появляться независимо от значений ShowHint в любых компонентах. Свойства Hint компонентов можно также использовать для отображения текстов заключенных в них сообщений в какой-то метке или панели с помощью функций GetShortHint и GetLongHint, первая из которых возвращает первую часть сообщения, а вторая — вторую (если второй части нет, то возвращается первая часть). Наприм'ер, эти функции можно использовать в обработчиках событий OnMouseMove, соответствующих прохождению курсора мыши над данным компонентом. Так обработчик procedure TForml.LabelIMouseMove(Sender: Tobject; S h i f t : TShiftState,- У., Y : I n t e g e r ) ; begin Panell.Caption := GetShortHint((Sender as TControl).Hint); Panel2.Caption := GetLongHint ( (Sender as TControl) . H i n t ) ,end;
Разработка графического интерфейса пользователя
311
отобразит в панели Panell первую, а в панели Рапе12 — вторую часть свойства Hint всех компонентов, над которыми будет перемещаться курсор, если в этих компонентах в событии OnMouseMove указан этот обработчик LabelIMouseMove. Причем это не зависит от значения их свойства ShowIIint. Еще один пример. Пусть вы хотите, чтобы при нажатии некоторой кнопки Buttonl вашего приложения в панели Panell высвечивалась подсказка пользователю, например, «Укажите имя файла», а сама кнопка имела всплывающий ярлычок подсказки с текстом «Ввод». Тогда вы можете задать свойству Hint этой кнопки значение «Ввод|Укажите имя файла», задать значение true свойству ShowHint, а в обработчик события нажатия этой кнопки вставить оператор P a n e l l . C a p t i o n := G e t L o n g H i n t ( B u t t o n l . H i n t ) ;
Если же вы не хотите отображать ярлычок подсказки для кнопки, то можете ограничиться значением Hint, равным «Укажите имя файла», а приведенный выше оператор оставить неизменным или заменить на эквивалентный ему в данном случае оператор Panell.Caption := GetShortHint(Buttonl.Hint); или даже просто на оператор P a n e l l . C a p t i o n := B u t t o n l . H i n t ;
Перед тем моментом, когда должен отобразиться ярлычок какого-нибудь компонента, возникает событие приложения OnShowHint. В обработчике этого события можно организовать какие-то дополнительные действия, например, изменить отображаемый текст. Особенно легко работать с событиями приложения, начиная с Delphi 5, в которой появился компонент Applications vents (см. разд. 4.6), перехватывающий все эти события. В обработчик его события OnShowHint можно поместить те операторы, которые надо выполнить перед отображением ярлычка. Заголовок этого обработчика имеет вид: procedure ApplicationEventslShowHint(var H i n t S t r : String; var CanShow: Boolean; var Hint Info: T H i n t l n f o ) ;
Здесь передаваемый по ссылке параметр HintStr — отображаемый в ярлычке текст. В обработчике этот текст можно изменить. Так же по ссылке передается параметр CanShow. Если в обработчике установить его равным false, то ярлычок отображаться не будет. Третий параметр, передаваемый по ссылке — Hintlnfo. Это структура, поля которой содержат информацию о ярлычке: его координаты, цвет, задержки появления и т.п. В частности, имеется поле HintControl — компонент, сообщение которого должно отображаться в ярлычке, и поле HintStr — отображаемое сообщение. По умолчанию Hintlnfo.HintStr — первая часть свойства Hint компонента. Но в обработчике это значение можно изменить. Рассмотрим пример. Часто текст, записанный в окнах редактирования, не виден целиком, так как длина текста превышает размер окна. Полезно предусмотреть, чтобы при перемещении пользователем курсора мыши в окно редактирования во всплывающем окне ярлычка отображался полный текст, содержащийся в окне. Аналогичные процедуры неплохо предусмотреть в списках, которые также могут содержать текст, не помещающийся целиком в окне. Давайте посмотрим, как это можно сделать. Начните новое приложение и поместите на форму несколько окон редактирования Edit. Установите в них свойство ShowHint в true. Поместите на форму также компонент Application^ vents со страницы Additional и в обработчике его события OnShowHint напишите текст: if
(Hintlnfo.HintControl.ClassName = 'TEdit') then begin HintStr := ( H i n t l n f o . H i n t C o n t t o l as T E d i t ) . T e x t ; ApplicationEventsl.CancelDispatch; end;
312
Глава5
Структура if проверяет, является ли компонент (Hintlnfo.HintControl), ярлычок которого должен отображаться, окном редактирования типа TEdit. Если является, то текст ярлычка (HintStr) подменяется текстом окна редактирования. Для этого с помощью операции as компонент Hintlnfo.HintControl рассматривается как объект типа TEdit. Последний оператор Cancel Dispatch введен для того, чтобы, если в приложении имеются еще компоненты Applications vents, то они не обрабатывали бы это событие. Аналогичный пример уже рассматривался ранее в разд. 3.9.7 и 4.6. Недостатком приведенного выше кода является то, что текст отображается в ярлычке независимо от того, виден ли он полностью в окне редактирования, или он длинный и целиком в окне не помещается. Проверять длину текста проще всего методом канвы TextWidth (см. соответствующий раздел гл. 17). К сожалению, окна редактирования не имеют своей канвы. Но если, например, на вашей форме есть метка Label, использующая тот же шрифт, что и окна редактирования, то для отображения ярлычка только в случаях длинного текста приведенный выше код можно изменить следующим образом {предполагается, что имя метки Labell): If ( H i n t l n f o . H i n t C o n t r o l . C l a s s N a m e = ' T E d i t 1 ) then with H i n t l n f o . H i n t C o n t r o l as TEdit do if ( L a b e l l . C a n v a s . T e x t W i d t h ( T e x t ) > Clientwidth) then begin HintStr := Text; ApplicationEventsl.СaneelDispatch; end;
Появление начиная с Delphi 6 окна редактирования LabeledEdit с присоединенной к нему меткой облегчает решение задачи, поскольку для проверки длины текста можно использовать не какую-то постороннюю метку, а метку (свойство EditLabel) самого компонента: if ( H i n t l n f o . H i n t C o n t r o l . C l a s s N a m e = "TLabeledEdit') then with H i n t l n f o . H i n t C o n t r o l as TLabeledEdit do if ( E d i t L a b e l . c a n v a s . T e x t W i d t h ( T e x t ) > C l i e n t w i d t h ) then begin HintStr := Text; ApplicationEventsl.Caneel Dispatch; end;
Конечно, это сработает правильно только в том случае, если шрифты текста окна (Font) и метки (EditLabel.Font) одинаковы. Имеется еще один способ отображения второй части сообщения, записанного в Hint, в строке состояния или какой-то области экрана в моменты, когда курсор мыши проходит над компонентом — это использование обработки события приложения OnHint. Это событие не того компонента, над которым проходит курсор мыши, а именно приложения — объекта Application. Начиная с Delphi 5, это событие также перехватывается компонентом Application Events. Если обработчик этого события определен, то в момент прохождения курсора над компонентом, в котором задано свойство Hint, вторая часть сообщения компонента заносится в свойство Hint объекта Application. Если свойство Hint компонента содержит только одну часть, то в свойство Hint объекта Application заносится эта первая часть. Если ваше приложение содержит инструментальную панель с быстрыми кнопками, то, как правило, эти кнопки должны снабжаться не только всплывающими ярлычками, но и развернутыми подсказками в панели состояния (см. пример панели на рис. 5.4). Если у вас есть подобный пример, вы можете опробовать на нем методику отображения подсказок в панели состояния. Но проще просто продолжить предыдущий пример. Поместите на форму панель состояния — компонент StatusBar со страницы Win32 и установите его свойство Simple Panel в true. Тем самым вы превратили панель в о дно секционную. Если вы хотите использовать
Разработка графического интерфейса пользователя
313
многосекционную панель, то в приведенном ниже операторе надо заменить свойство SimpleText ссылкой на панель, в которой вы хотите отображать подсказку. Напишите какие-нибудь тексты в свойствах Hint ваших окон редактирования. А в обработчик события OnHint компонента AppIicationEvents вставьте оператор StatusBarl.SimpleText := A p p l i c a t i o n . H i n t ;
Запустите приложение на выполнение. Вы увидите, что тексты подсказок отображаются в панели состояния, когда курсор мыши перемещается над тем или иным окном редактирования. Причем это не мешает появляться ярлычкам, отображающим тексты окон. Более подробные пояснения пользователю может дать контекстно-зависимая справка, встроенная в приложение. Она позволяет пользователю нажать в любой момент клавишу F] и получить развернутую информацию о том компоненте, который в данный момент находится в фокусе. Для того чтобы это осуществить, надо разработать для своего приложения стандартными средствами Windows 'файл справки .hip. Затем надо в каждом компоненте, для которого вы хотите обеспечить контекстно-зависимую справку, задать свойства, обеспечивающие ссылку на соответствующую тему. В версиях, младше Delphi 6, такое свойство одно — HelpContext. Это номер темы, который задается в проекте справки специальной таблицей [MAP], содержащей эти условные номера и соответствующие им идентификаторы тем. Начиная с Delphi 6, появилось еще два свойства: HelpKeyword и HelpType. Первое из них является идентификатором темы, содержащимся в сноске К (это обозначения тем, которые видны в окне справки на странице Указатель). А второе определяет, каким свойством — HelpContext или HelpKeyword задается ссылка на тему. Если HelpType = htContext — используется свойство HelpContext; если HelpType = htKeyword — используется свойство HelpKeyword. Если HelpContext компонента равен нулю, то данный компонент наследует это свойство от своего родительского компонента. Например, для всех компонентов, размещенных на некоторой панели можно задать HelpContext = 0, а для самой панели задать отличное от нуля значение HelpContext, соответствующее теме, описывающей назначение всех компонентов панели. Для того чтобы все это работало, надо выполнить команду Pro[ec! Options и в окне Project Options (опции проекта) на странице Application (приложение) установить значение опции Help file, равное имени подготовленного файла .hip. Это приведет к тому, что в головном файле проекта появится оператор вида: ч A p p l i c a t i o n . H e l p F i l e := ' ' ;
В этом операторе используется метод HelpFile, определяющий файл справки, к которому обращается проект. Этот метод и подобный оператор можно использовать в приложении в любом обработчике события, если в какие-то моменты требуется сменить используемый файл справки. Если предполагается, что файл справки будет расположен в том же каталоге, где находится само приложение, то имя файла и в окне Опции проекта и в приведенном выше операторе надо задавать без указания пути. Иначе приложение, работающее на вашем компьютере, перестанет работать на компьютере пользователя, у которого каталоги не совпадают с вашими. Для того чтобы приложение в свойствах HelpContext могло ссылаться на какой-то номер контекстной справки, в файле проекта справки .hpj в разделе [MAP] надо поместить таблицу соответствия использованных значений HelpContext и тем файла Мр. К сожалению, из данной книги по соображениям экономии места мне пришлось удалить главу, которая в предыдущих книгах «Программирование в Delphi ...» подробно рассматривала методику разработки справок. Так что несколько слов о создании справок стоит сказать.
314
Глава 5
Разработка справочной системы осуществляется не Delphi, а средствами Windows. Просмотр ее также осуществляется программой Windows WinHelp. Delphi только позволяет встроить справочную систему в приложение. Разработка справки состоит из двух основных этапов: • Создание файла или нескольких файлов, содержащих темы справок. Это делается с помощью любого текстового редактора, например, с помощью Microsoft Word. Созданные файлы сохраняются в формате RTF. • Компиляция справки в один или в несколько файлов ,Мр и отладка всей справочной системы. Это осуществляется с помощью специальных программ: в частности, HCRTF — Microsoft Help Workshop. При этом создаются вспомогательные файлы проекта и содержания. Подробнее останавливаться на создании справок мы не будем. При необходимости вам придется изучить эти вопросы по иной литературе или по справке, встроенной в Microsoft Help Workshop. А теперь вернемся к Delphi и поговорим о традиционном разделе меню Справка, позволяющем пользователю открыть файл справки Мр и получить развернутую информацию по всем вопросам, связанным с данным приложением. В обработчик события при выборе данного раздела меню или при нажатии соответствующих кнопок помощи вставляются операторы вида A p p l i c a t i o n . H e l p C o n t e x t ( < н о м е р тены>);
Задаваемые в этих операторах номера тем аналогичны используемым при задании свойств HelpContext. Это номер той темы, которая первой отобразится при открытии окна справки. А в дальнейшем пользователь, как обычно, может перейти, работая с программой справки, к любой интересующей его теме. Имеется еще несколько методов объекта Application, обеспечивающих работу со справочными файлами. Метод HelpJump: function HelpJump(const JumpID: s t r i n g ) : Boolean;
выполняет действия, аналогичные HelpContext, но его параметр JumpID — не идентификатор темы, а имя соответствующей темы в файле справки, задаваемое в нем сноской *. Метод HelpCommand: function HelpCommand(Command: Word; Data: L o n g i n t ) : Boolean;
позволяет выполнить указанную параметром Command команду API WinHelp с параметром Data. Метод генерирует событие OnHelp активной формы или приложения, а затем выполняет указанную команду WinHelp. Полный список команд WinHelp вы можете найти в теме WinHelp справочного файла win32.hlp, расположенного в каталоге ...\Program Files\Common File$\Borland Shared\MSHelp. Приведем только некоторые из них. Команда HELP__CONTENTS с параметром 0 отображает окно Содержание справки. Команда HELP_INDEX с параметром 0 отображает окно Указатель справки. Команда HELP_CONTEXT с параметром, равным идентификатору темы, отображает тему с заданным идентификатором (это тождественно рассмотренному ранее методу HelpContext). Команда HELP_CONTEXTPOPUP с параметром, равным идентификатору темы, делает то же самое, но отображает тему во всплывающем окне. Перед вызовом справки любым из рассмотренных методов возникает событие приложения OnHelp, которое может быть перехвачено обработчиком аналогичного события компонента Application Events. Заголовок обработчика этого события имеет вид: function TForml.ApplicationEventslHelp(Command: Word; Data: Integer; var CsllHelp: Boolean! : Boolean,-
Параметр Command — это выполняемая команда, параметр Data — параметр этой команды, параметр CallHelp (по умолчанию его значение равно true) опреде-
Разработка графического интерфейса пользователя
315
ляет, будет ли после завершения обработчика вызвана программа справки WinHelp. Если задать CallHelp = false, то WinHelp вызываться не будет. Обработчик этого события можно использовать, чтобы что-то изменить в команде вызова справки. Приведем пример. Обработчик: f u n c t i o n TForml. ApplicationEventslHelp (Command: Word,- Data ; Integer; var CallHelp: Boolean) : Boolean; begin if(Coramand=HELP_CONTEXT) and (Data then ...
Этот оператор можно записать короче, воспользовавшись операцией in: i f (Key i n [ ' Д ' ,
' я ' ] ) then
Приведенные операторы реагируют только на положительный ответ пользователя, не реагируя на отрицательный или ошибочный ответ. Реакцию на все возможные ответы обеспечивает структура case: case Key of 'Д','П': ...; 1 Н' , ' и ' : else
beep; end; Здесь предусмотрена реакция на положительный и отрицательный ответ, а также звуковой сигнал при ошибочном ответе. Посмотрев на приведенный ранее заголовок обработчика, вы можете увидеть, что параметр Key передается как var. Это позволяет в обработчике изменять этот параметр, изменяя соответственно его стандартную обработку в компоненте, поскольку ваш обработчик события срабатывает раньше стандартного обработчика компонента. Пусть, например, вы хотите иметь на форме окно редактирования Editl, в котором пользователь должен вводить только целые числа без знака, разделенные запятыми или пробелами. Вы можете обеспечить безошибочный ввод, подменяя все недопустимые символы кулевым с помощью, например, такого оператора: if not (Key in [ ' О 1 . , ' 9 ' , ' ', ' , ' ] ) then Key :- 10;
При нажатии пользователем любой клавиши, кроме клавиш с цифрой, запятой или пробелом, символы подменяются нулевым символом и просто не появляются в окне редактирования, как вы можете убедиться, сделав приложение с этим простым примером. Можно добавить в обработчик звуковой сигнал при нажатии пользователем ошибочной клавиши: if not (Key in [ ' 0 ' . . ' 9 ' , ' ', ' , ' ] ] then begin Key := 10;
Beep; end;
Разработка графического интерфейса пользователя
335
5.4 Перетаскивание объектов 5.4.1 Перетаскивание информации об объектах — технология Drag&Drop Процесс перетаскивания с помощью мыши информации из одного объекта в другой (Drag&Drop), коротко называемый перетаскиванием, очень широко используется в Windows. Например, вы можете перемещать файлы между папками, перемещать сами папки, включая их в другие папки, и т.д. Посмотрим, как осуществляется подобное перетаскивание информации объектов в Delphi. Все свойства, методы и события, связанные с процессом перетаскивания, определены в классе TControl, являющемся прародителем всех визуальных компонентов Delphi. Поэтому они являются общими для всех компонентов. Начало процесса перетаскивания определяется свойством DragModc, которое может устанавливаться в процессе проектирования или программно равным dm Manual или dm Automatic. Значение dmAutomatic (автоматическое) определяет автоматическое начало процесса перетаскивания при нажатии пользователем кнопки мыши над компонентом. Имейте в виду, что в этом случае событие OnMouseDown, связанное с нажатием пользователем кнопки мыши, для этого компонента вообще не наступает. Значение же dmManual (ручное) говорит о том, что начало процесса перетаскивания должен определять программист. Для этого он должен в соответствующий момент вызвать метод BeginDrag. Например, он может поместить вызов этой функции в обработчик события OnMouseDown, наступающего в момент нажатия кнопки мыши. В этом обработчике он может проверить предварительно какие-то условия (режим работы приложения, нажатие тех или иных кнопок мыши к вспомогательных клавиш) и при выполнении этих условий вызвать BeginDrag. Пусть, например, процесс перетаскивания должен начаться, если пользователь нажал левую кнопку мыши и клавишу A l t над списком ListBoxl. Тогда свойство DragModc этого компонента надо установить в dmManual, а его обработчик события OnMouseDown может иметь вид: procedure TForml.ListBoxlMouseDown(Sender: TObject; Button; TMouaeButton? S h i f t : TShiftState? X , Y : Integer); begin if (Button - mbLeft) and (ssAlt In Shift) then L i s t B o x l . B e g i n D r a g ( f a l s e ) ; end; Параметр Button обработчика события OnMouseDown показывает, какая кнопка мыщи была нажата, а параметр Shift является множеством, содержащим обозначения нажатых в этот момент кнопок мыши и вспомогательных клавиш клавиатуры (см. разд. 5.3.1.2). Приведенный выше оператор проверяет, нажаты ли левая кнопка мыщи и клавиша Alt. Бели нажаты, то вызывается метод BeginDrag данного компонента. В предыдущем примере в функцию BeginDrag передано значение false. Это означает, что процесс перетаскивания начнется не сразу, а только после того, как пользователь сдвинет мышь с нажатой при этом кнопкой. Это позволяет отличить простой щелчок мыши от начала перетаскивания. Если же передать в BeginDrag значение true, то перетаскивание начнется немедленно. Когда начался процесс перетаскивания, обычный вид курсора изменяется. Пока он перемещается над формой или компонентами, которые не могут принять информацию, он обычно имеет тип crNoDrop; @. Если же он перемещается над компонентом, готовым принять информацию из перетаскиваемого объекта, то приобретает вид, определяемый свойством перетаскиваемого объекта DragCorsor.
336
Главаj
По умолчанию это свойство равно crDrag, что соответствует изображению Ig . Надо подчеркнуть, что вид курсора определяется свойством DragCorsor перетаскиваемого объекта, а не того объекта, над которым перемещается курсор. В процессе перетаскивания компоненты, над которыми перемещается курсор, могут информировать о готовности принять информацию от перетаскиваемого объекта. Для этого в компоненте должен быть предусмотрен обработчик события OnDragOver, наступающего при перемещении над данным компонентом курсора, перетаскивающего некоторый объект. В этом обработчике надо проверить, может ли данный компонент принять информацию перетаскиваемого объекта, и, если не может, задать значение false передаваемому в обработчик параметру Accept. По умолчанию этот параметр равен true, что означает возможность принять перетаскиваемый объект. Обработчик для списка ListBoxl может иметь, например, следующий вид: procedure TForral.ListBoxlDragGver(Sender, Source: TObject; X , i : Integer; State: TDragState; var Accept: Boolean); begin i f ( S o u r c e as TControl Sender) then Accept := Source is TListBox else Accept := false; end;
В нем сначала проверяется, не являются ли данный компонент (Sender) и перетаскиваемый объект (Source) одним и тем же объектом. Это сделано, чтобы избежать перетаскивания информации внутри одного И того же списка. Если источник и приемник являются одним и тем же объектом, то срабатывает else и параметр Accept становится равным false, запрещая прием информации. Если же это разные объекты, то Accept делается равным true, если источником является какой-то другой список (компонент класса TListBox), и равным false, если источник является объектом любого другого типа. Таким образом компонент ListBoxl сообщает, что готов принять информацию из любого другого списка. Значение параметра Accept, задаваемое в обработчике события OnDragOver, определяет вид курсора, перемещающегося при перетаскивании над данным компонентом. Этот вид показывает пользователю, может ли данный компонент принять перетаскиваемую информацию. Если в компоненте не описан обработчик события OnDragOver, то считается, что данный компонент не может принять информацию перетаскиваемого объекта. Процедура приема информации от перетаскиваемого объекта записывается в обработчике события OnDragDrop принимающего компонента. Это событие наступает, если после перетаскивания пользователь отпустил кнопку мыши над данным компонентом. В обработчик этого события передаются параметры Source (объект-источник) и X и Y координаты курсора. Бели продолжить начатый выше пример перетаскивания информации из одного списка в другой, то обработчик события OnDragDrop может иметь вид: procedure TForrol.ListBoxlDragDrop(Sender, Source: TObject; X, Y: Integer); begin (Sender as T L i s t B o x ) . I t e m s . A d d ( ( S o u r c e as T L i s t B o x ] . I t e m s [ ( S o u r c e as T L i s t B o x ] . I t e m l n d e x ] | ; end;
В этом обработчике строка, выделенная в списке-источнике (Source as TListBox). Items[( Source as TListBox). I tern Index], добавляется в список-приемник методом (Sender as TListBox). I terns. Add, Используется операция as, позволяющая рассматривать параметры Sender и Source как указатели на объекты класса TListBox. Это делается потому, что эти параметры объявлены в заголовке процедуры
Разработка графического интерфейса пользователя
337
как указатели на объекты класса TObject. Но в классе TObject нет свойств Items и Itemlndex, которые нам требуются. Эти свойства определены в классе TListBox, являющемся наследником TObject. Поэтому с параметрами Sender и Source в данном случае надо оперировать как с указателями на объекты TListBox, что и выполняет операция as. Конечно, в данном случае можно было бы не использовать параметр Sender, заменив (Sender as TListBox) просто на ListBoxl. Но запись оператора в общем виде с помощью параметра Sender позволяет воспользоваться таким обработчиком и для других компонентов ListBox, если они имеются в приложении. После завершения или прерывания перетаскивания наступает событие ОпEndDrag. в обработчике которого можно предусмотреть какие-то дополнительные действия. Имеется также связанное с перетаскиванием событие OnStartDrag, которое позволяет произвести какие-то операции в начала перетаскивания. Это событие полезно при автоматическом начале перетаскивания, когда иным способом этот момент нельзя зафиксировать. Теперь, объединив приведенные выше фрагменты обработки перетаскивания, просуммируем, что надо сделать, если вы имеете в приложении несколько списков ListBox и хотите обеспечить возможность копирования строк каждого из этих списков в любой другой. Это потребует двух операций: • Напишите для одного списка приведенный выше обработчик события OnDragOver. Для всех остальных списков с помощью Инспектора Объектов сошлитесь в событиях OnDragOver на этот же обработчик. Все это можно даже сделать проще: выделите на форме все списки, объединив их таким образом в одну группу и с помощью Инспектора Объектов напишите обработчик события OnDragOvcr сразу для всей группы. • Напишите аналогичным образом для всех списков приведенный выше обработчик события OnDragDrop. И это все! Для обеспечения возможности перетаскивания вам потребовалось написать всего два оператора. Если вы хотите начинать перетаскивание только при выполнении какого-то дополнительного условия, например, при нажатии клавиши Alt, то вам потребуется сделать еще один шаг: • Задайте для всех списков значение свойства DragMode, равное dmManual. Напишите описанным выше способом для всех списков приведенный выше обработчик события OnMouseDown. Рис. 5.13
Пример приложения, использующего Drag&Drop
d*Maruolr neper
rpcoal ии>аЗ IPO*a 2 спица 3
стро-а 2 списки 4 строка t спис'о Э
338
Главаj
Теперь вы можете создать форму с несколькими списками, занести в них информацию, написать эти обработчики и проверить все это на практике. На рис. 5.13 приведен пример, демонстрирующий различные аспекты перетаскивания. Этот и другие примеры имеются на диске, прилагаемом к книге. Можете попробовать для тренировки сами создать аналогичный пример, позволяющий перетаскивать строки из одного списка в другой и из списков в окно редактирования Memo.
5.4.2 Перетаскивание и встраивание объектов — Drag&Doc. Плавающие окна Технологию перетаскивания и встраивания оконных объектов Drag&Doc вы можете видеть, работая с Интегрированной Средой Разработки Delphi. Она предоставляет пользователю полную свободу в перестройке интерфейса. Отдельные окна могут объединяться вместе, создавать многостраничное окно с закладками, затем опять разъединяться и т.д. Посмотрим, как подобный подход можно реализовать в своем приложении. У оконных компонентов введено свойство DockSite, которое по умолчанию равно false, но если его установить в true, то компонент становится контейнером: приемником, способным принимать переносимые в него компоненты •— клиенты. Еще одно свойство приемника — UseDockManager. Если оно равно true (это принято по умолчанию), то процессом встраивания клиента автоматически управляет диспетчер встраивания, соответствующий тому компоненту, который является приемником. Если же задать UseDockManager равным false, то встраивание клиентов ложится на плечи программиста. В компонентах — потенциальных клиентах надо установить свойство DragKind в dkDock. Кроме того, если вы хотите, чтобы процесс перетаскивания начинался автоматически, то, так же, как и в технологии Drag&Drop, надо установить свойство DragMode в dmAutomatic. Бели оставить значение DragMode равным dmManual по умолчанию, то управление процессом Drag&Doc осуществляется так же, как описывалось ранее для процесса Drag&Drop. Посмотрим, к чему все это может привести. Давайте построим тестовое приложение, в котором осуществим Drag&Doc, не написав ни одной строчки кода. Начните новое приложение. Перенесите на форму 6 панелей, расположив их примерно так, как показано на рис. 5.14 а. В панели Panell установите свойство DockSite в true. Эта панель сможет служить у вас приемником. В панелях Рйпе12 — Рапе1в установите DragKind в dkDock и DragMode в dmAutomatic. Запустите приложение. Попробуйте перетаскивать мышью панели Рапе12 — РпеШ. Вы увидите (рис. 5.14 б), что панель Panell может принимать другие панели, размещая их внутри себя, причем способ встраивания зависит от того, в какой части приемника вы завершаете буксировку клиента. Между размещенными клиентами имеются разделители, которые позволяют вам перемещать буксировкой границы между клиентами, изменяя их относительные размеры. Вы можете также видеть, что даже независимо от наличия или отсутствия в приложении приемника, при щелчке на компонентах-клиентах они превращаются в самостоятельные плавающие окна (панели Panela и Рапе1б на рис. 5.14 б), которые можно перемещать, выходя даже за пределы родительской формы, можно изменять их размеры, можно закрывать их, после чего соответствующие компоненты исчезают. Причем все это достигнуто только заданием соответствующих свойств, без единого написанного вами оператора. Приемником может быть и сама форма. Укажите для формы вашего приложения DockSite равным true и выполните приложение опять. Поведение перетаскиваемых панелей несколько изменится. При щелчке на компонентах-клиентах они по-прежнему превращаются в плавающие окна, которые можно перемещать, изме-
Разработка графического интерфейса пользователя
Рис. 5.14 Демонстрация техники Огад&Оос: форма (а) и окно приложения во время выполнения (б)
339
а)
б:
нять их размеры и т.п. Но при повторном щелчке они опять становятся обычными панелями, сохраняя при этом измененное местоположение и размеры. Объясняется это тем, что повторный щелчок воспринимается в этом случае как встраивание клиента в приемник — форму. Теперь посмотрим, как можно управлять из компонента-приемника всеми этими процессами. У приемника имеется свойство DockClients, объявленное как D o c k C l i e n t s l I n d e x : Integer] : TControl;
которое позволяет получить доступ к каждому клиенту, перенесенному в приемник, Свойство DockClientCount содержит число клиентов. Оба свойства — DockClients и DockClientCount только для чтения. Определены соответствующие события, которые позволяют приемнику управлять процессом встраивания. Они генерируются только в том случае, если в компоненте установлено DockSitc - true. Первое из этих событий — OnGetSitelnfo, возникающее в момент начала перетаскивания и повторяющееся непрерывно в процессе перетаскивания. Заголовок соответствующего обработчика имеет вид: procedure TForml.PanelIGetSiteInfo(Sender: TObject; DockClient: TControl; var InfluenceRect: TRect; MousePos: TPoint; var CanDock: Boolean); В качестве параметров в обработчик передается DockClient — перетаскиваемый объект, InfluenceRect — прямоугольник рамки перетаскиваемого объекта, который можно изменять, MousePos — положение курсора мыши и CanDock — разрешение перетаскивания. Если в обработчике указать CanDock = false, это будет означать, что приемник отказывается принимать компонент. Например, вы можете написать в обработчике события OnGetSitelnfo панели Panell оператор: CanDock :- DockClient о Рапе16; Тогда Panell будет принимать любой компонент, кроме PanelG. Еще одно событие — OnDockOver, возникающее и повторяющееся, когда компонент-клиент перемещается над компонентом-приемником, точнее, когда в поле
340
Глава 5
приемника войдет курсор мыши, буксирующий клиента. Это событие аналогично событию OnDragOver в технологии Drag&Drop. Заголовок соответствующего обработчика имеет вид: procedure TForml. PanellDockOver(Sender: TObject; Source: TDragDockObject; X , Y : Integer,State: TDragState; var Accept: Boolean); В этом обработчике вы можете, как и в описанном выше, проверить, хотите ли вы, чтобы данный оконный компонент принял перетаскиваемый компонент, определяемый параметром Source. Если принимать не надо, то задается значение Accept — false. Например, прием контейнером Panell любой панели, кроме Рапе16, обеспечивается оператором: Accept := Source.Control о Рапе1б; Параметр Accept будет равен true — значению по умолчанию, только если перетаскивается не Рапе16. Впрочем, того же самого результата вы достигали ранее в обработчике события OnGet Site Info. Объект перетаскивания Source типа TDragDockOhject имеет свойство DockRect типа TRect, описывающее перемещаемую рамку. Вы можете управлять ею. Например, если вы хотите, чтобы ваша Panels вела себя наподобие панели Microsoft Office, которая может прижиматься к одной из сторон окна или перемещаться в середине окна в виде квадратной панели, вы можете, установив предварительно в приемнике (в форме вашего приложения или в панели Panell) DockSite равным true, написать обработчик события OnDockOver в виде: if Source.Control = Panels then w i t h (Sender as T W i n C o n t r o l ) . C l i e n t O r i g i n do with (Sender as T W i n C o n t r o l ] do if Source.DockRect.Left= X+ClientWidth then Source-DockRect := Rect(X+ClientWidth-25,У, X+ClientWidth,Y+ClientHeight) else if Source.DockRect.Top= Y+ClientBeight then Source.DockRect :- Rect(X,Y+ClientHeight-25, X+ClientWidth,Y+ClientHeight) else Source.DockRect := Rect(Source.DockRect.Left, Source.DockPect.Top, Source.DockRect.Left+100, Source.DockRect.Top+100) ; Текст написан в общем виде, пригодном и для формы, и для любого приемника. Поэтому вместо указания конкретного приемника используется (Sender as TwinControl). Для компактности использованы две конструкции with...do. Соответственно X и V — это не параметры, передаваемые в обработчик через заголовок процедуры, а свойства (Sender as TWinControl).ClientOrigin, т.е. координаты левого верхнего угла клиентской области формы. A ClientWidth и ClientHeight — это (Sender as TWinControl). Client Width и (Sender as TWinControl). Client Height. Если в процессе буксировки панели PanelS ее координаты выходят за пределы клиентской области приемника, то панель вытягивается вдоль соответствующего края приемника, а ее ширина равна 25. При перемещении панели внутри окна она представляется квадратом со стороной 100. Это будет нормально срабатывать, если для приемника, например, для панели Panell, установить UseDockManager равным false. В противном случае сначала
Разработка графического интерфейса пользователя
341
сработает диспетчер встраивания, растянув рамку клиента, а затем наступит событие OnDockOver, так что его обработчик будет иметь дело с уже измененной рамкой, что приведет к ошибке встраивания. Поэтому надо отключить диспетчер встраивания для клиента PanclS, не отключая его для остальных клиентов. Это легко сделать, включив в обработчик события OnGetSitelnfo оператор Panell.UseDockManager := DockClientPanel5;
Этот оператор задаст значение UseDockManager равным true для всех потенциальных клиентов, кроме Рапе15. Еще одно событие, возникающее в компоненте-приемнике — OnDockDrop. Это событие возникает в момент окончания перетаскивания клиента над приемником. Оно аналогично событию OnDragDrop в технологии Drag&Drop. Заголовок соответствующего обработчика имеет вид: procedure T F o r m l . P a n e l 5 D o c k D r o p [ S e n d e r : T O b j e c t ; Source: TDragDockObject; X, Y: Integer);
Этот обработчик позволяет разместить клиента Source тем или иным образом в зависимости от его имени, типа, местоположения — координат X и Y. Например, в этот момент можно что-то изменить в надписях приемника, сигнализируя пользователя о приеме клиента и числе клиентов. Событие, возникающее в компоненте-приемнике в момент, когда пользователь перетаскивает клиента из приемника — OnUnDock. Заголовок соответствующего обработчика имеет вид: procedure
T F o r m l . P a n e l l U n D o c k ( S e n d e r ; TObject; Client: T C o n t r o l ; NewTarget: TWinControl,var A l l o w : Boolean);
В обработчик передаются как параметры Client — компонент, который был клиентом, NewTarget — новый приемник, в который пользователь перетаскивает клиента, и Allow — параметр, определяющий, можно ли перетащить клиента. Например, если в вашем тестовом приложении вы поместите в обработчик события OnUnDock панели Panell оператор: Allow
:= C l i e n t
о Рапе12;
то, выполняя приложение, обнаружите, что Рапс12, попав клиентом в Panell, уже не может оттуда выбраться, так как ей это запрещено. Теперь, когда мы познакомились с основными возможностями технологии Drag&Doc, давайте построим что-нибудь более полезное, чем просто игра панелями. Но сначала сохраните ваше тестовое приложение, поскольку оно еще нам потребуется . Давайте построим многооконный редактор текстов, имеющий хранилище, в которое можно помещать документы на хранение и затем извлекать из него нужные документы. Начните новый проект и выполните следующие пункты: • Назовите форму {свойство Name) Fmain. • Разместите на форме компонент MainMenu и задайте в нем всего один пункт — Новый, подразумевая под этим создание нового документа. • Поместите на форму панель, задайте ее свойство Caption равным Хранилище документов и задайте Align = alTop, • Поместите на форму компонент PageControl и задайте Align = alClient, чтобы он занимал вся площадь окна, кроме полосы, занятой панелью Хранилище документов (рис. 5.15 а). Задайте в нем свойство Dock Site равным true. Этот компонент будет служить приемником документов.
342
Глава 5
Рис. 5.15 Формы для приложения, использующего технику Drag&Doc
б)
а)
I - -JPJ.-J
ХРАНИЛИЩЕ ДОКУМЕНТОВ
• Добавьте в проект еще одну форму, выполнив команду File New Form. Назовите ее FDoc (свойство Name). Установите ее свойство DragKind равным ilkDock, а свойство DragMode равным dmAutomatic. Эта форма будет служить клиентом компонента TPageControl на форме Fmain. • Поместите на форму FDoc компонент Memo, задав для него Align = alClient, чтобы он занял всю форму (рис. 5.15 б). Сотрите в нем текст (свойство Lines). • Выполните команду Project Options и перенесите форму FDoc из окна Auto-create forms в окно Available forms, поскольку она должна создаваться не автоматически, а при выборе пользователем раздела меню Новый (если эта операция непонятна, см. разд. 5.5.1 рис. 5.18). • Сохраните проект, назвав модуль с первой формой Umain, а со второй — UDoc. Мы создали необходимые нам формы. Осталось написать несколько операторов, чтобы это все работало. Введите в раздел Implementation модуля Umain операторы uses UDoc; var LDoc: TList;
Первый из них дает доступ к модулю с формой FDoc, а второй объявляет переменную LDoc типа TList. Она представляет собой список, в котором будут храниться указатели на создаваемые пользователем формы документов. В обработчик события OnCreate формы Fmain запишите оператор Ldoc :- T L i s t . C r e a t e ;
Этот оператор создает список LDoc. В обработчик события OnDestroy формы Fmain запишите оператор Ldoc.Free;
Этот оператор освобождает память при закрытии приложения. Осталось написать обработчик щелчка на разделе меню Новый. Он может иметь следующий вид: procedure T F o r r n l . M N e w C l i c k ( S e n d e r : var New:TFDoc; begin New := TFDoc.Create(Forml] ,-
TObject);
LDOC.Sdd(New);
New.Caption := 'Документ ' + I n t T o S t r ( L D o c . C o u n t ) ; end;
,
Разработка графического интерфеиса пользователя
343
В этом обработчике вводится переменная New типа TFDoc, соответствующего типу формы FDoc. Первый оператор обработчика создает методом Create новую форму FDoc и присваивает указатель на нее переменной New. Следующий оператор добавляет указатель на эту форму в список LDoc. А последний оператор задает заголовок окна формы FDoc, равным «Документ ...», где многоточие заменяется на номер, соответствующий числу строк в списке LDoc. Теперь ваше приложение полностью готово. Сохраните его и попробуйте в работе (рис. 5.16). При щелчке на Новый создается новая форма документа, в котором вы можете писать текст. Документы можно помещать в хранилище, в котором каждому документу автоматически отводится новая страница. Вы можете работать с документом непосредственно в хранилище, а можете изъять его оттуда, превратив опять в отдельное окно. Рис. 5.16
Приложение, использующее технику DragSDoc, во время выполнения
ХРАНИЛИЩЕ ДПКНМЕНЮВ w*«™ г | Д Это текст Д«¥4анга 2
Э т а течет Документа 4
Для того чтобы сделать из вашего приложения законченный продукт, нужна еще, конечно, некоторая техническая работа. Надо бы добавить разделы меню, позволяющие открыть заданный пользователем файл и прочитать его в новый документ, позволяющие сохранить документ в файле, надо бы ввести при закрытии приложения или документа запрос пользователю о необходимости сохранить измененный документ и т.п. Для всех этих операций вам пригодится созданный вами список LDoc, который в приведенном упрощенном примере практически не использовался. Но мы не будем этим заниматься, так как все эти манипуляции подробно рассмотрены в разд, 3.10.2. А для того, чтобы почувствовать мощь технологии Drag&Drop, достаточно и сделанного вами примера. Причем, заметьте, что для его реализации вам пришлось написать всего несколько операторов. В заключение рассмотрим еще некоторые свойства и методы, управляющие встраиванием и работой с плавающими окнами. У оконных компонентов есть свойство Floating (только для чтения), которое показывает, является компонент свободно плавающим окном, или размещен, в другом оконном компоненте-приемнике. При переводе ранее размещенного компонента в состояние плавающего окна и при встраивании плавающего окна можно использовать свойства, в которых запоминаются размеры компонента в предыдущем состоянии: UndockH eight
Высота компонента, которая была в последний раз, когда он отображался плавающим окном.
UndockWidth
Ширина компонента, которая была в последний раз , когда он отображался плавающим окном.
344
Глава 5
TBDockHeight
Высота компонента, когда он в последний раз размещался в контейнере вертикально.
LRDockWidth
Ширина компонента, когда он в последний раз размещался в контейнере горизонтально.
Пользуясь этими свойствами можно восстанавливать при необходимости предшествующие размеры компонента. В частности, по умолчанию после перевода компонента из размещенного состояния в состояние плавающего окна его размеры восстанавливаются исходя из свойств UndockHeight и UndockWidth. Имеется метод ManualFloat, который переводит размещенный компонент в состояние плавающего окна. Он объявлен следующим образом: function M a n u a l F l o a t ( S c r e e n P o s : T R e c t l : Boolean;
Параметр функции ScreenPos определяет положение и размер компонента после его перевода в состояние плавающего окна. Например, вы можете открыть свое тестовое приложение (рис. 5.14) и ввести в него кнопку, которая будет переводить в состояние плавающего окна, скажем, панель Рапе!2. Текст обработчика щелчка на такой кнопке может быть следующим: procedure T F o r m l . B u t t o n l C l i c k ( S e n d e r : T O b j e c t ) ; var TempRect: TRect; begin TempRect.TopLeft := Panel2.ClientToScreen(Point(0, 0] | ; TempRect.BottomRight := Panel2.ClientToScreen( Point(PanelZ.UndockWidth,Panel2.UndockHeight)); Panel2.ManualFloat(TempRect);
end;
В этом коде вводится переменная TempRect, в которой формируется прямоугольник, определяющий размер и местоположение генерируемого плавающего окна. Первые два выполняемые операторы кода формируют этот прямоугольник, восстанавливая размер панели Рапе12, бывший у нее в последний раз, когда она отображалась плавающим окном. Левый верхний угол генерируемого плавающего окна совпадает с текущим положением панели. Для правильного встраивания окна координаты с помощью метода ClientToScreen переводятся в координаты экрана. Последний оператор методом ManualFloat генерирует плавающее окно. И последний метод, который мы рассмотрим — Manual Dock, размещающий компонент в указанном приемнике. Его описание: f u n c t i o n ManualDock(NewDockSite: T W i n C o n t r o l ; DropControl: TControl = nil; ControlSide: TAlign = a l N o n e ) : Boolean;
Параметр NewDockSite определяет приемник, в котором должен размещаться клиент. Второй параметр DropControl определяет другой компонент в приемнике NewDockSite, который должен разместить данного клиента. Как правило, этот параметр задается равным nil. Параметр ControlSide определяет выравнивание клиента внутри приемника. Если это единственный клиент в приемнике, то значение этого параметра безразлично. Но если там уже есть клиенты, то задание, например, ControlSide = alLeft приведет к перестановке, если только данный клиент не самый левый в приемнике. Можете ввести в свое тестовое приложение кнопку, снабдив ее обработчиком: Panel2.ManualDock(PanelI,nil,alLeft);
Этот обработчик будет размещать Pnalel2 в Panel 1, выравнивая ее по левому краю приемника, если в нем расположены еще какие-нибудь клиенты.
I
Разработка графического интерфейса пользователя
345
Имеется, однако, особенность, не оговоренная в документации: метод ManualDock срабатывает только в случае, если компонент-клиент был ранее в состоянии плавающего окна. Так что вам придется предварительно один раз перевести Рапе!2 в это состояние или щелчком на панели, или с помощью кнопки, соответствующей ранее рассмотренному методу ManualFloat. Другой способ обойти эту трудность автоматически — ввести соответствующий метод ManualFloat в обработчик события формы OnCreate. И еще одно нестандартное использование методов ManualFloat и ManualDock. Пусть, например, вы хотите иметь форму, содержащую несколько панелей, которые пользователь мог бы перемещать, не переводя их в состояние плавающего окна и не изменяя размеров. Тогда вы можете в форме установить DockSite = true, сделать в панелях соответствующие установки свойств DragKind в dkDock и DragMode в dmAutomatic, а в событии формы OnCreate выполнить для каждой панели методы ManualFloat и ManualDotk, назначив приемником в ManualDock форму. Вы получите приложение, в котором пользователь сможет перемещать панели мышью. Это может быть полезно в каких-то приложениях, связанных с проектированием топологии или конструированием. Подобные задачи будут рассмотрены в следующем разделе.
5.4.3 Буксировка компонентов в окне приложения В ряде случаев в приложениях Windows используется перемещение отдельных компонентов в поле окна или какой-то панели. Это перемещение может быть перемещением в какие-то заранее определенные позиции (это легко осуществляется заданием соответствующих значений свойствам Left и Тор) или осуществляться непрерывной буксировкой компонента с помощью мыши. Простой пример этого — буксировка компонентов по площади формы в среде разработки Delphi. Подобные задачи возникают достаточно часто в технических приложениях, связанных с компоновкой какого-то устройства, с формированием каких-то схем (например, электрических) и т.п. Опробовать различные способы буксировки можно в тестовом приложении, подобном приведенному на рис. 5.17. Этот пример имеется на прилагаемом к книге диске. В нем на форме размещено четыре компонента Image, в которые загружены какие-то изображения или части изображения (о загрузке в Image изображений и о работе с этими компонентами см. разд. 6.1.1). Если хотите воспроизвести пример, подобный рис. 5.17, в котором на отдельных компонентах, как на детских кубиках, воспроизведены фрагменты единого изображения, то можете в обработчик события формы OnCreate вставить следующий код: var Pict:TImage; begin Pict :=- TImage.Create (Self) ; Pict.AutoSize := true; // В следующем операторе вместо ... надо указать имя файла Pict.Picture.LoadFromFile('...'); Imagel.Canvas.CopyRect(Imagel.ClientRect, Pict.Canvas, Rect(0,0,Pict.Width div 2,Pict.Height div 2)}; Iraage2.Canvas.CopyRect(Image2.ClientRect, Pict.Canvas, Rect(Pict.Width div 2,0,Pict-Width,Pict.Height div 2} ) ; Image3.Canvas.CopyRect(Image3.ClientRect, Pict.Canvas, Rect[0,Pict.Height div 2,Pict.Width div 2,Pict.Height)); Image4.Canvas.CopyRect(Imaged.ClientRect, Pict.Canvas, Rect(Pict.Width div 2,Pict.Height div 2, Pict.width,Pict.Height)]; Pict.Free; end;
Глава 5
346
Рис. 5.17 Приложение, демонстрирующее буксировку компонентов
а)
б)
Сейчас мы не будем анализировать этот код. Он станет вам понятен после изучения гл. 6. В компонентах Image свойство AutoSize должно быть установлено в true. Начнем рассмотрение на этом примере приемов буксировки. Все приведенные далее обработчики событий записаны в общем виде. Так что вы можете применять их одновременно ко всем вашим компонентам Image, а можете к разным компонентам применить разные методы, чтобы легче их было сравнивать друг с другом. Все методы используют несколько глобальных переменных, объявление которых надо поместить в разделе implementation модуля вне каких-либо процедур: var move: boolean Х О , Y O : Integer;
ГГеременная move определяет режим буксировки. Она будет устанавливаться в true в начале буксировки и сбрасываться в false в конце. Поэтому по значению move можно будет различать перемещения мыши с буксировкой и без нее. Переменные ХО и YO потребуются нам для запоминания координат курсора мыши. Один из возможных вариантов решения нашей задачи — буксировка самого компонента. Буксировка начинается при нажатии левой кнопки мыши на соответствующем компоненте Image. Поэтому начало определяется событием OnMouseDown, обработчик которого имеет вид: • procedure TForrnl.IraagelMouseDown(Sender: TObject; Button: TMouseButton; S h i f t : TShiftState; X, 1: I n t e g e r ) ; begin if Button mbLeft then exit; XO := X; YO := Y;
move := true; [Sender as T C o n t r o l ) . B r i n g T o F r o n t ; end;
Сначала в этой процедуре проверяется, нажата ли именно левая кнопка мыши (равен ли параметр Button значению mbLeft, обозначающему левую кнопку). Затем в переменных ХО и YO запоминаются координаты мыши X и Y в этот момент времени. Задается режим буксировки — переменная move устанавливается в true. Последний оператор выдвигает методом BringToFront компонент, в котором произошло событие -- (Sender as TControl), на передний план. Это позволит ему в дальнейшем перемещаться поверх других аналогичных компонентов. Данный оператор не обязателен, но если его не записать, то в процессе буксировки перемещаемый компонент может оказаться под другими компонентами. Во время буксировки компонента работает его обработчик события OnMouseMove, имеющий вид: procedure TForrnl.ImagelMouseMove(Sender: T O b j e c t ; S h i f t : TShiftState; X , Y : I n t e g e r ) ; begin if move then with (Sender as TControll do SetBounds(Left + X — X O , Top + Y - Y O , W i d t h , Height) end;
Разработка графического интерфейса пользователя
347
Он изменяет с помощью метода SetBounds координаты левого верхнего угла на величину сдвига курсора мыши (X — ХО для координаты X и Y — YO для координаты Y). Тем самым поддерживается постоянное расположение точки курсора в системе координат компонента, т.е. компонент смещается вслед за курсором. Ширина Width и высота Height компонента остаются неизменными. Изменение координат можно было бы задавать непосредственным изменением свойств Left и Тор: Left := Left + X -ХО; Тор := Тор + Y - YO;
Но поскольку эти операторы выполняются поочередно, то компонент будет делать каждый раз два перемещения: сначала по горизонтали, а потом по вертикали. Это приведет к заметному на глаз дрожанию изображения. По окончании буксировки, когда пользователь отпустит кнопку мыши, наступит событие OnMouseUp, Обработчик этого события должен содержать всего один оператор: move := false; указывающий приложению на окончание буксировки. Тогда при последующих событиях OnMouseMove их обработчик, приведенный ранее, перестанет изменять координаты компонента. Рассмотренный вариант буксировки не свободен от недостатков. Основной из них — некоторое дрожание изображения при перемещении, что выглядит не очень приятно. Устранить этот недостаток позволяет другой вариант, заключающийся в букировке не самого компонента, а его контура (этот вариант вы можете видеть на рис. 5.17 а). При этом отмеченных выше неприятных явлений не наблюдается, поскольку сам компонент перемещается только один раз -— в момент окончания буксировки, когда требуемое положение уже выбрано. В этом варианте используются методы рисования на канве, которые подробно рассматриваются в гл. 6 разд. 6.1.3. Для их применения нам потребуется еще одна глобальная переменная: var rec:Trect; Это объявление следует добавить к приведенным ранее объявлениям move, ХО и YO. Переменная гее будет использоваться для запоминания положения перемещаемого контура компонента. Начинается процесс буксировки, как и ранее, с события OnMouseDown, которое обрабатывается процедурой вида: procedure TForral.IraagelMouseDown(Sender: TObject; B u t t o n : TMouseButton; S h i f t : T S h i f t S t a t e ; X , Y : I n t e g e r ) ; begin
if Button mbLeft then exit;
XO
:= X;
YO := Y;
rec := (Sender as TControl) .BoundsRect; move := true; end;
Эта процедура отличается от рассмотренной ранее только оператором rec :=
(Sender зз T C o n t r o l ] . B o u n d s R e c t ;
который запоминает в переменной гее исходное положение компонента. В процедуре отсутствует также оператор BringToFront, поскольку сам компонент не будет перемещаться и не приходится заботиться о том, чтобы он оказался поверх аналогичных компонентов. При дальнейшем перемещении курсора мыши срабатывает обработчик события OnMouseMove, имеющий вид:
348
Глава j procedure TForml.ImageIMouseMove(Sender: TObject; Shift: TShiftState; X , Y : Integer); begin if not move then exit; Canvas.DrawFocusRect(rec); w i t h rec do begin l e f t := l e f t •+ X - X Q ; r i g h t : = right + X - X O ; top := top + Y - ¥0; bottom := bottom + Y - Y O ; XO := X; YO := Y; end; Canvas . DrawFocusRect (rec) ,end;
В этой процедуре перерисовывается и сдвигается только прямоугольник контура компонента с помощью метода DrawFocusRect. Первое обращение к этому методу стирает прежнее изображение контура, поскольку повторная прорисовка того же изображения по операции ИЛИ (ог) стирает нанесенное ранее изображение (см. гл. 6 разд. 6.1.5). Затем изменяются значения, хранимые в переменной гее, и той же функцией DrawFocusRect осуществляется прорисовка сдвинутого прямоугольника. При этом сам компонент остается на месте. Когда пользователь отпускает кнопку мыши, наступает событие OnMouscUp и срабатывает следующая процедура: procedure TForml.ImageIMouseUp(sender: TObject; B u t t o n : TMouseButton; S h i f t : TShiftState; X , Y : I n t e g e r ) ; begin Canvas.DrawFocusRect(rec) ; w i t h (Sender as TControl) do begin SetBounds(rec.Left •+• X - X O , rec.Top + Y - Y O , Width, H e i g h t ) ; BringToFront; end,move := false; end;
Первый ее оператор стирает последнее изображение контура, а второй оператор перемещает компонент в новую позицию. Оператор BringToFront не является обязательным. Он проявит себя только в случае, если перемещенный компонент ляжет поверх другого аналогичного компонента. Приведенный алгоритм исключает мерцание изображения, так как само перемещение компонента осуществляется только в момент отпускания кнопки мыши. К тому же, при этом в обработчике события OnMouseUp легко предусмотреть какие-то условия отказа от перемещения (например, нажатую клавишу Alt). Для этого достаточно в приведенный выше обработчик добавить перед оператором with оператор: if not
( s s A l t in S h i f t )
then
В этом случае, если пользователь перед завершением буксировки нажмет клавишу АИ, компонент не переместится. Это полезно в некоторых приложениях, связанных с каким-то проектированием топологии или размещения предметов: прикинув новое положение компонента пользователь может тут же вернуться к предыдущему, если новое расположение неудачно. С другой стороны, для некоторых задач буксировка только контура мало информативна. Например, при проектировании какой-то мозаики перемещение контура не даст представления о том, подойдет ли компонент по своему виду к данному месту. В этих случаях предпочтительнее буксировка всего изображения.
Разработка графического интерфейса пользователя
349
Описанный выше способ перемещения изображения контура компонента можно осуществить иначе с использованием техники Drag&Dock и методов ManualFloat и ManualDock, описанных в разд. 5.4.2. Чтобы опробовать этот подход, установите в форме свойство DockSite равным true и сделайте в перемещаемых компонентах (в вашем примере - - в Image) установку свойств DragKind в dkDock и DragMode в draAutomatic. Это надо сделать по крайней мере а двух компонентах. Если перемещаемым будет только один компонент, описываемый метод не сработает. В начале работы приложения надо выполнить для каждого компонента, который в дальнейшем может перемещаться, методы ManualFtoat и ManualDock, назначив приемником в ManualDock форму. Для этого можно вставить в обработчик события формы OnCreate, например, следующие операторы: Imagel.ManualFloat(Rect(Forml.Left-Umagel.Left, Forrol.Top+Imagel.Top, Forml.Left-Hmagel.Left+Imagel.Width, Forml.Top+Imagel.Top+Imagel.Height)); Imagel .ManualDock(Forml, nil, alLeft) ;
Аналогичные операторы с заменой имени компонента Imagel надо вставить и для других перемещаемых компонентов. И это Бее. Вам не надо писать никаких обработчиков событий компонентов. Вы получили приложение, в котором пользователь может перемещать мышью ваши изображения. При этом в процессе буксировки перемещается только контур изображения в виде утолщенной рамки (рис. 5.17 б). Сам компонент перемещается на новое место только в конце буксировки. Если хотите, то можете в обработчик события OnEndDock внести оператор (Sender as TControl).BringToFront; который обеспечит в момент переноса размещение компонента поверх других.
5.5 Формы 5.5.1 Управление формами Основным элементом любого приложения является форма — контейнер, в котором размещаются другие визуальные и невизуальные компоненты. С точки зрения пользователя форма — это окно, в котором он работает с приложением. Каждой новой форме, вводимой в приложение, соответствует свой модуль (unit), описывающий эту форму как класс и включающий, если необходимо, какие-то дополнительные константы, переменные, функции и процедуры. Рассмотрим некоторые свойства, методы и события, присущие формам. Обычно сколько-нибудь сложное приложение содержит несколько форм. Включение в проект новой формы осуществляется командой File New | Form или другими способами, подробно описанными в разд. 2.4. По умолчанию все формы создаются автоматически при запуске приложения и первая из введенных в приложение форм считается главной. Главная форма отличается от прочих рядом свойств. Во-первых, именно этой форме передается управление в начале выполнения приложения. Во-вторых, закрытие пользователем главной формы означает завершение выполнения приложения. В-третьих, главная форма так же, как и любая другая, может быть спроектирована невидимой, но если все остальные формы зарыты, то главная форма становится в любом случае видимой (иначе пользователь не смог бы продолжать работать с приложением и даже не смог бы его завершить). Указанные выше условия, принятые по умолчанию (первая форма — главная, все формы создаются автоматически), могут быть изменены. Главной в вашем приложении может быть вовсе не та форма, которая была спроектирована первой. Не
Глава 5
350
стоит также в общем случае все формы делать создаваемыми автоматически. В приложении могут быть предусмотрены формы (например, формы для установки различных опций), которые требуются далеко не в каждом сеансе работы с приложением. Было бы варварским расточительством создавать на всякий случай такие формы автоматически при каждом запуске приложения и занимать под них память. А в приложениях MDI дочерние формы в принципе не могут быть автоматически создаваемыми, так как число таких форм определяет пользователь во время работы приложения, создавая каждую новую форму командой типа Новое окно. Изменить принятые по умолчанию условия относительно форм можно в окне опций проекта, которое вызывается командой Project Options. В открывшемся окне опций проекта (Project Options) надо выбрать страницу Forms, представленную на рис. 5.18. Рис. 5.18 Страница Forms окна опций проекта
|projectO«Mnt(tirPro|H±i.Hie
(31
D«ec!Ofi«/Ctx*Mionals
Version Info Packages F "f"b | Apc*cabon | С чтрйй J Compiler Messages Linker
r~ Иач1огт
JFoml
L____ t ц, .
1
In i
i
liktk
iwadable битF«rfi1 AboutBoK
]• . \
rv+t
OK
Cancel
Ц*
В верхнем выпадающем списке Main forms можно выбрать главную форму среди имеющихся в проекте. Пользуясь двумя нижними окнами можно установить, какие формы должны создаваться автоматически, а какие не должны. Например, если надо исключить форму Form2 из списка автоматически создаваемых, то надо выделить ее в левом окне (Auto-create forms) и с помощью кнопки со стрелкой, направленной вправо, переместить в правое окно доступных форм (Available forms). Для каждой автоматически создаваемой формы Delphi добавляет в файл программы соответствующий оператор ее создания методом CreateForm. Это можно увидеть, если выполнить команду Project | View Source и просмотреть появившийся файл проекта .dpr. Он может, например, содержать следующие выполняемые операторы: Application.CreateForm(TForml, F o r m l ) ; Application.CreateFarmlTAboutBox, AboutBox); Application.Run;
Первый и второй из них создают соответствующие формы, а третий начинает выполнение приложения. Для форм, которые были исключены из списка автоматически создаваемых, аналогичный метод CreateForm надо выполнить в тот момент, когда форма должна быть создана. В момент создания формы возникает событие OnCreate. Обработка этого события широко используется для настройки каких-то компонентов формы, создания списков и т.д.
Разработка графического интерфейса пользователя
351
В нужный момент форму можно сделать видимой методами Show или ShowModal. Последний метод открывает форму как модальную. Это означает, что управление передается этой форме и пользователь не может передать фокус другой форме данного приложения до тех пор, пока он не закроет модальную форму. Более подробное описание модальных форм и примеры работы с ними будут рассмотрены позднее в разд. 5.5.2. Методы Show и ShowModal можно применять только к невидимой в данный момент форме. Если нет уверенности, что форма в данный момент видима, то прежде, чем применять эти методы, следует проверить свойство Visible формы. Например: if
(not Forra2.Visible)
then Form2.ShowModal;
При выполнении методов Show или ShowModal возникает событие формы onShow. Это событие возникает до того момента, как форма действительно станет видимой. Поэтому обработку события onShow можно использовать для настройки каких-то компонентов открываемой формы. Отличи^ ^т упомянутой ранее настройки компонентов в момент события onCreate заклю .ается в том, что событие onCreate наступает для каждой формы только один раз в момент ее создания, а события onShow наступают каждый раз, когда форма делается видимой. Так что при этом в настройке можно использовать какую-то оперативную информацию, возникающую в процессе выполнения приложения. Методом Hide форму в любой момент можно сделать невидимой. В этот момент в ней возникает событие onHide. Необходимо помнить, что для выполнения методов CreateForm, Show, ShowModal, Hide и вообще для обмена любой информацией между формами модули соответствующих форм должны использовать друг друга. Например, если форма Б модуле "Unitl должна управлять формой в модуле Unit2, то я оператор uses модуля Unitl должно быть включено имя второго модуля Unit2. А если к тому же форма в модуле Unit2 должна пользоваться какой-то информацией, содержащейся в модуле Unitl, то в оператор uses модуля Unit2 должно быть включено имя первого модуля Unitl. В этом случае, если операторы uses в обоих модулях расположены в разделах Interface, возникнут проблемы с недопустимыми круговыми ссылками (circuior unit reference) и компилятор выдаст соответствующую ошибку. От недопустимых круговых ссылок можно избавиться; если разомкнуть их, поместив один или оба оператора uses в раздел implementation. Впрочем, проще не включать имена модулей приложения в операторы uses вручную, а использовать команду File Use Unit, которая автоматизирует этот процесс и гарантирует отсутствие круговых ссылок. Закрыть форму можно методом Close. При этом в закрывающейся форме возникает последовательность событий, которые можно обрабатывать. Их назначение — проверить возможность закрытия формы и указать, что именно подразумевается под закрытием формы. Проверка возможности закрытия формы необходима, например, для того, чтобы проанализировать, сохранил ли пользователь документ, с которым он работал в данной форме и который изменял. Если не сохранил, приложение должно спросить его о необходимости сохранения и, в зависимости от ответа пользователя, сохранить документ, закрыть приложение без сохранения или вообще отменить закрытие. Рассмотрим последовательность событий, возникающих при выполнении метода Close. Первым возникает событие onCloseQuery. В его обработчик передается как var (по ссылке) булева переменная CanClose, определяющая, должно ли продолжаться закрытие формы. По умолчанию CanClose равно true, что означает продолжение закрытия. Но если из анализа текущего состояния приложения или из ответа пользователя на запрос о закрытии формы следует, что закрывать ее не надо,
Глава 5
352
параметру CanClose должно быть присвоено значение false. Тогда последующих событий, связанных с закрытием формы не будет. Например, пусть в приложении имеется окно редактирования RichEditl, в котором свойство Modified указывает на то, был ли изменен пользователем текст в этом окне с момента его последнего сохранения. Тогда обработчик события onCloseQuery может иметь вид: procedure TForml . FormClo'seQuery (Sender: T O b j e c t ; var CanClose: B o o l e a n ) ; begin i f RichEditl.Modified then if ( A p p l i c a t i o n . M e s s a g e B c x ( ' Т е к с т документа не с о х р а н е н . ' + ' Действительно хотите з а к о н ч и т ь работу ? ' , 1 'Подтвердите завершение р а б о т ы , MB_ICONQUESTION+MB_YESNOCANCEL] О I D Y E S ] then CanClose := f a l s e ; end,-
В приведенном операторе вызывается методом Application.MessageBox диалоговое окно. Первый параметр метода содержит текст сообщения пользователю. Второй параметр — текст заголовка окна. Третий параметр — набор флагов, определяющих вид окна. Флаг MB_ICONQUESTION отображает в окне пиктограмму вопросительного знака. Флаг MB_YESNOCANCEL заносит в окно кнопки До, Нет и Отмена. В результате пользователю будет показано окно, приведенное на рис. 5.19. Рис. 5.19 Диалоговое окно, вызываемое методом Application.MessageBox
Подтвердите аавершемие набаты i }
Телег дгжуиентане сохранен. Дейстйнтйпьно котите iafioMMHTb работу >
Функция MessageBox возвращает результат, который указывает на реакцию пользователя в диалоговом окне. Значение IDYES возвращается, если пользователь нажал кнопку Да, значение IDNO возвращается при нажатии кнопки Нет, значение IDCANCEL — при нажатии кнопки Отмена или клавиши Esc. Подробнее о методе Application.MessageBox вы можете узнать в справочной части книги в гл. 16 разд. 16.11.3. В нашем примере, если пользователь в диалоговом окне с запросом о сохранении не ответит До, то CanClose будет равно false и окно не закроется. Причем этот обработчик сработает при любой попытке пользователя закрыть приложение: нажатии в нем кнопки или раздела меню Выход, нажатии кнопки системного меню в полосе заголовка окна и т.п. Если обработчик события onCloseQuery отсутствует или если в его обработчике сохранено значение true параметра CanClose, то следом наступает событие OnClose. В обработчик этого события передается как var переменная Action, которой можно задавать значения: с a None
Не закрывать форму. Это позволяет и в обработчике данного события еще отказаться от закрытия формы.
caHide
При этом значении (оно принято по умолчанию для форм, не являющихся главными и дочерними в приложениях MDI) закрыть форму будет означать сделать ее невидимой. Для пользователя она исчезнет с экрана, однако вся хранящаяся в форме информация сохранится. Она может использоваться другими формами или той же самой формой, если она снова будет сделана видимой.
Разработка графического интерфейса пользователя
353
caMiniinize
При этом значении закрыть форму будет означать свернуть ее до пиктограммы. Как и р предыдущем случае, вся информация в форме будет сохранена.
с a Free
При этом значении закрыть форму будет означать уничтожение формы и освобождение занимаемой ею памяти. Вся информация, содержащаяся в форме, будет уничтожена. Если эта форма в дальнейшем потребуется еще раз, ее надо будет создавать методом Create For m.
Не все значения Action допустимы для любых форм. Например, для дочерних форм в приложении MDI возможны значения только caNone и caMinimize. Если в обработчике события OnClose задано значение Action, равное caFrce, то при освобождении памяти возникает еще одно последнее событие — OnDestroy. Оно обычно используется для очистки памяти от тех объектов, которые автоматически не уничтожаются при закрытии приложения. Свойство форм OldCreateOrder определяет моменты событий OnCrealc и OnDestroy. Если это свойство установлено в false (значение по умолчанию), то событие OnCrcate наступает после того, как закончили работу все конструкторы компонентов, содержащихся на форме, а событие OnDestroy наступает прежде, чем вызывается какой-либо деструктор. При OldCreateOrder = true, что соответствует поведению компонентов в Delphi 3 и более ранних версиях, событие OnCreate наступает при выполнении конструктора TCustomForm, а событие OnDestroy наступает при выполнении деструктора TCustomForm.
5.5.2 Модальные формы Открытие форм как модальных используется в большинстве диалоговых окон. Модальная форма приостанавливает выполнение вызвавшей ее процедуры до тех пор, пока пользователь не закроет эту форму. Модальная форма не позволяет также пользователю переключить фокус курсором мыши на другие формы данного приложения, пока форма не будет закрыта. Т.е. пользователь должен выполнить предложенные ему действия прежде, чем продолжить работу. Модальной может быть сделана любая форма, если она делается видимой методом ShowModal. Если та же самая форма делается видимой методом Show, то она не будет модальной. Поведение модальной формы определяется ее основным свойством ModalResult. Это свойство доступно только во время выполнения приложения. При открытии формы методом ShowModal сначала свойство ModalResult равно нулю. Как только при обработке каких-то событий на форме свойству ModalResult будет присвоено положительное значение, модальная форма закроется. А значение ее свойства ModalResult можно будет прочитать как результат, возвращаемый методом ShowModal. Таким образом программа, вызвавшая модальную форму, может узнать, что сделал пользователь, работая с этой формой, например, на какой кнопке он щелкнул. В Delphi предопределены некоторые константы, облегчающие трактовку результатов, полученных при закрытии модальной формы:
12 Профаммнроваине в DelphP?
Глава 5
354
Численное Константа значение ModalRcsult
Пояснение
0
mrNone
1
mrOk или idOK
закрытие модальной формы нажатием кнопки ОК
2
m r Cancel или idCaucel
закрытие модальной формы нажатием кнопки Coneel, или методом Close, или нажатием кнопки системного меню в полосе заголовка окна
3
mrAbort или idAbort
закрытие модальной формы нажатием кнопки Abort
4
mrRetry или idRetry
закрытие модальной формы нажатием Кнопки Retry
5
mr Ignore или idlgnore
закрытие модальной формы нажатием кнопки Ignore
6
mrYes или idYes
закрытие модальной формы нажатием кнопки Yes
7
nu-No или idNo
закрытие модальной формы нажатием кнопки No
8
mrAll
закрытие модальной формы нажатием кнопки АИ
9
mrNoToAH
закрытие модальной формы нажатием кнопки No То АИ
10
mrYesToAll
закрытие модальной формы нажатием кнопки Yes То АИ
Все приведенные выше пояснения значений ModalResu.lt (кроме значений О и 2) носят чисто условный характер. В своем приложении вы вольны трактовать ту или иную величину ModalResult и соответствующие константы как вам угодно. Требуемые значения ModalResult можно задавать в обработчиках соответствующих событий в компонентах модальной формы. Однако при использовании кнопок можно обойтись и без подобных обработчиков. Дело в том, что кнопки типа TButton и TBitBtn имеют свойство ModalResult, по умолчанию равное mrNone. Для кнопок, расположенных на модальной форме, значение этого свойства можно изменить и тогда не потребуется вводить каких-либо обработчиков событий при щелчке на них. В кнопках BitBtn при свойстве Kind, не равном bkCustom, заложены по умолчанию значения ModalResult, соответствующие назначению той или иной кнопки.
5.5.3 Пример приложения с модальными формами заставки и запроса пароля Во многих больших приложениях при их запуске сначала на экране появляется форма-заставка, содержащая логотип приложения и сведения о программе и ее
Разработка графического интерфейса пользователя
355
разработчике. Назначение этой формы чаще всего заключается в том, чтобы обеспечить начальную загрузку и настройку программы. Тогда эта форма должна закрываться не раньше, чем закончатся эти операции. Но иногда эта форма носит чисто информационный характер. В этих случаях желательно, чтобы она немедленно закрывалась при любых действиях пользователя и даже закрывалась через какое-то время без каких-либо действий со стороны пользователя. Именно такую форму-заставку мы и попробуем создать. Помимо формы-застав к и нередко в приложениях, особенно в тех, которые работают с базами данных, в начале работы приложения появляется форма с запросом пароля. При неверном пароле приложение закрывается, не позволяя пользователю работать с ним. Формы-заставки и формы запроса пароля могут быть реализованы множеством различных способов. Рассмотрим один из них. 1. Откройте в Delphi новое приложение (File New Application). Пусть открывшаяся форма будет главной в нашем приложении (вместо такой пустой формы вы можете взять любое разработанное вами ранее приложение и добавлять форму-заставку и форму запроса пароля в него). Назовите для определенности главную форму приложения FMain. 2. Добавьте в приложение новую форму (File j New | Form). Пусть это будет ваша форма-заставка. Назовите ее FLog. Ее свойство BorderStyle надо сделать равным hsNone (см. разд. 5.1.3), чтобы в окне этой формы отсутствовала полоса заголовка. Вы можете поместить на форме какой-то рисунок (разместить компонент Image и вставить в его свойство Picture желаемый рисунок), надписи и т.п. В простейшем случае поместите в центре формы метку Label и напишите в ней какой-то текст. Размер формы-заставки задайте небольшим, меньшим, чем обычные окна приложения. Свойство Position следует сделать равным poScreenCenter, чтобы форма появлялась в центре экрана. 3. Теперь напишите обработчики событий, которые при любом действии пользователя закрывали бы форму. Щелкните на форме, чтобы в Инспекторе Объектов открылись относящиеся к ней страницы (если у вас форма накрыта панелями или рисунками, то, щелкнув на них, нажимайте клавишу Esc до тех пор, пока в Инспекторе Объектов не откроются страницы, относящиеся к форме). Перейдите в Инспекторе Объектов на страницы событий, выберите событие onKeyDown и напишите для него обработчик, состоящий из одного оператора — Close. Аналогичный обработчик напишите для события onMouseDown. Если на форме у вас имеются метки, компоненты Image и др., то выделите их все, задайте в событии onMouseDown ссылку на тот же обработчик, что вы сделали для формы, а в форме поставьте свойство KeyPreview в true, чтобы форма перехватывала все связанные с нажатием клавиш события компонентов. Теперь форма будет закрываться при нажатии пользователем любой клавиши или кнопки мыши. Давайте сделаем так, чтобы и при отсутствии каких-то действий со стороны пользователя форма закрывалась сама, например, через 5 секунд. 4. Добавьте на форму компонент Timer со страницы System. Это невизуальный компонент, который может отсчитывать интервалы времени (см. разд. 3.7.8). Интервал задается в свойстве компонента Interval в миллисекундах. Задайте его равным 5000. Единственное событие таймера — onTimer, наступающее по истечении заданного интервала времени. Напишите в обработчике этого события все тот же единственный оператор Close. Теперь при любом действии и даже бездействии пользователя форм а-заставка будет закрываться. Но что это означает? По умолчанию закрыть форму значит сделать ее невидимой. Однако форма-заставка не нужна после того, как она будет предъявлена пользователю в первый момент выполнения приложения. Хранить 12- -
356
Глава 5
все время в памяти эту уже ненужную форму не имеет смысла. Поэтому в форме надо предусмотреть, чтобы закрытие формы означало ее удаление из памяти и освобождение памяти для чего-нибудь более полезного. Для этого надо сделать следующее. 5. В событие формы OnClose вставьте оператор: Action := caFree; Как указывалось в предыдущих разделах, этот оператор приводит к уничтожению объекта формы и освобождению занимаемой формой памяти. Форма-заставка готова к использованию. Проверьте только, имеет ли ее свойство Visible значение false. Это важно, поскольку только невидимую форму можно открыть методом ShowModal. В главной форме свойство Visible тоже должно иметь значение false. Сохраните проект, дав файлу модуля главной формы имя Umain, а файлу модуля формы-заставки имя ULog. Теперь можно записать в главной форме оператор ShowModal. Но чтобы это можно было сделать, необходимо сослаться в модуле Umain на модуль ULog. 6. Добавьте в оператор uses модуля Umain имя модуля ULog, или напишите оператор uses ULog в разделе Implementation модуля Umain, или сделайте то же самое путем выполнения команды File Use Unit. Впрочем, если вы забудете это сделать, Delphi сама поправит вас и добавит этот оператор при попытке компиляции приложения, задав вопрос: "Form 'Fmain' references form 'FLog' declared in unit 'ULog' which is not in your USES list. Do you wish to add it?», что означает «Форма 'Fmain' ссылается на форму 'FLog', объявленную в модуле 'ULog', который отсутствует в вашем списке USES. Хотите ли вы добавить его в список?». При положительном ответе на этот вопрос соответствующий оператор будет добавлен в ваш модуль Umain. После этого осталось написать в модуле Umain обработчик события формы OnShow. 7. Напишите в модуле Umain обработчик события формы OnShow, состоящий из одного оператора: FLog . ShowModal ,Событие OnShow наступает перед тем, как форма становится видимой. Поэтому в момент выполнения указанного оператора она еще не видна. Оператор открывает форму FLog как модальную, передает ей управление и дальнейшее выполнение программы в модуле Umain останавливается до тех пор, пока модальная форма не будет закрыта. После закрытия модальной формы выполнение программы продолжится, и главная форма станет видимой. Можете сохранить проект, запустить приложение и убедиться, что все работает правильно. Теперь добавим в приложение форму запроса пароля. Реальная форма такого типа должна предлагать пользователю ввести свое имя и пароль, сравнивать введенные значения с образцами, хранящимися где-то в системе, при неправильном пароле давать возможность пользователю поправиться. Если пользователь так и не может ввести правильный пароль, форма должна закрыть приложение, не допустив к нему пользователя. При правильном пароле после закрытия формы запроса должна открыться главная форма приложения. Все это не трудно сделать, но мы упростим задачу, чтобы не отвлекаться от главного — взаимодействия форм в приложении. Будем использовать всего один пароль, который непосредственно укажем в соответствующем операторе программы. И не будем давать пользователю возможности исправить введенный пароль.
Разработка графического интерфейса пользователя
8.
9.
357
Добавьте к приложению новую форму. Назовите ее FPSW и сохраните ее модуль в файле с именем UPSW, Уменьшите размер формы до разумных пределов, поскольку она будет содержать всего одно окно редактирования. Установите свойство формы BorderStyle равным bsDialog, свойство Position равным ро Screen С enter. В свойстве Caption напишите «Введите пароль и нажмите Enter». Эта надпись будет служить приглашением пользователю. Поместите в центре формы окно редактирования Edit, в котором пользователь будет вводить пароль. Очистите его свойство Text. Задайте в свойстве PasswordChar символ «*». В обработчике события OnKeyDown этого компонента запишите оператор: if (key = VK_RETURNt then begin if EPSW.Text = ' a '
then ModalResult := 6 else Close;
end;
Этот оператор выполняет следующее. Прежде всего, он анализирует нажатую клавишу (подробнее об этом смотрите в разд. 5.3.2.2). Если нажата клавиша Enler, то введенный текст сличается с паролем. В данном операторе для упрощения непосредственно указан правильный пароль — символ 'а'. Если введен правильный пароль, то свойству ModalResuJt присваивается некоторое условное число — 6 (можно было бы выбрать и любое другое допустимое число, кроме 0 и 2). Если пароль неправильный, то выполняется метод Close. В обоих случаях форма закрывается, так как задание отличного от нуля положительного значения ModalResult равносильно закрытию формы. Но при правильном пароле значение ModalResult будет равно 6, а при неправильном — 2. Это значение получается при выполнении мето. да Close или если пользователь нажмет кнопку системного меню (кнопку с крестиком) в полосе заголовка окна. На этом проектирование формы запроса пароля закончено. Теперь запишем в модуле главной формы Umain оператор, показывающий пользователю эту форму и анализирующий ответ пользователя. Для этого надо выполнить следующие операции. 10. В модуле Umain, надо, как и ранее, добавить в оператор uses ссылку на модуль UPSW, а в обработчике события OnShow после ранее введенного оператора FLog. Show Modal добавить оператор: if
(FPSW.ShowModal 6) then Close else begin S h o w M e s s a g e ( ' В а ш пароль
FPSW.Free; end;
'''+PPSW.KPSW.Text+''''>;
Этот оператор анализирует значение свойства ModalResult формы запроса пароля. Значение этого свойства возвращает функция FPSW.ShowModal. Если результат не равен 6, то был введен неправильный пароль. Тогда главная форма, а с ней вместе и приложение, закрываются методом Close. При правильном пароле можно продоллсать работу приложения. Оператор ShowMessage введен просто для того, чтобы показать, как можно использовать свойство другой формы — в данном случае текст, введенный пользователем в качестве пароля. В реальном приложении по этому паролю можно было бы определить уровень доступа пользователя к конфиденциальной информации. Затем следует уничтожение формы запроса пароля методом Free. Это необходимо сделать, чтобы освободить память. Сама по себе эта форма в момент ее закрытия не уничтожается, поскольку но умолчанию закрыть форму — значит сделать ее невидимой. Уничтожать форму до этого мо-
358
Глава 5
мента было нельзя, так как при этом уничтожилась бы содержащаяся в ней информация — введенный пароль. На этом разработка нашего приложения закончена. Можете сохранить проект, запустить приложение и посмотреть, как оно работает. Описанный выше способ управления формой запроса пароля не является оптимальным. Он просто призван был показать, как можно обрабатывать величину ModalResult, возвращаемую методом Show-Modal. Но то же самое можно было бы сделать и проще. В обработчике события OnKeyDown окна редактирования на форме FPSW можно было бы написать более простой оператор: if (key = VK_RETURN) and (EPSW.Text о 'а'] then Application.Terminate;
При неверном пароле этот оператор завершает работу всего приложения методом Application .Terminate. Тогда в главной форме не надо анализировать результат работы пользователя с формой FPSW, так как если приложение не закрылось при выполнении оператора ShowModal, то значит пароль введен правильный. Поэтому операторы в главной форме тоже упрощаются: FPSW.ShowModal;
ShowMessage('Ваш пароль ' ' ' + F P S W . E P S W . T e x t + ' ' ' ' ) ; FPSW.Free;
Проведите в вашем приложении соответствующие замены операторов и убедитесь, что приложение и в этом случае работает правильно.
5.5.4 Управление формами в приложениях с интерфейсом множества документов (приложениях MDI) Типичным приложением MDI является привычный всем Word. В приложении MDI имеется родительская (первичная) форма и ряд дочерних форм (называемых также формами документов). Окна документов могут создаваться самим пользователем в процессе выполнения приложения с помощью команд типа Окно | Новое. Число дочерних окон заранее неизвестно — пользователь может создать их столько, сколько ему потребуется. Окна документов располагаются в клиентской области родительской формы. Поэтому чаще всего целесообразно в родительской форме ограничиваться только главным меню, инструментальными панелями и, если необходимо, панелью состояния, оставляя все остальное место в окне для окон дочерних форм. При этом обычно окно родительской формы в исходном состоянии разворачивают на весь экран. Требования, которые надо учитывать при разработке приложений MDI, рассмотрены в разд. 5.1.2. Для создания приложения MDI необходимо спроектировать родительскую и дочернюю формы. В родительской форме свойство FormStyle устанавливается в fsMDIForm, а в дочерней — в fsMDIChild. Поскольку дочерние окна будет создавать сам пользователь в процессе выполнения приложения, дочернюю форму необходимо исключить из числа создаваемых автоматически (в разд. 5.5.1 рассказывалось, как это сделать с помощью окна опций проекта). Рассмотрим теперь, как можно сделать обработчик команды, по которой пользователь задает в родительском окне создание нового окна документов — нового экземпляра дочерней формы. Этот обработчик может иметь вид: var : ; begin :=.Create(Application); , Show; end;
Разработка графического интерфейса пользователя
359
Переменная, объявленная в этой процедуре, используется для создания произвольного временного имени (указателя) вновь создаваемого объекта — формы. Первый из выполняемых операторов процедуры создает этот объект. Далее могут следовать какие-то операторы настройки нового дочернего окна. Например, новому окну надо присвоить какой-то уникальный заголовок (свойство Caption дочерней формы), чтобы пользователь мог отличать друг от друга окна документов — это безусловное требование к приложениям MDI Windows. Последний оператор процедуры делает видимым вновь созданное окно. Пусть, например, вы создали в модуле UMain родительскую форму, содержащую раздел меню Окно Новое, и создали в модуле UDoc дочернюю форму с именем FDoc, имеющую тип TFDoc (посмотреть для контроля имя и тип дочерней формы вы можете в верхнем выпадающем списке Инспектора Объектов, выделив интересующую вас форму, или в модуле, посмотрев автоматически создаваемый Delphi оператор, объявляющий переменную формы и расположенный сразу после определения класса данной формы). Тогда в операторе uses модуля родительской формы вы должны сослаться на модуль дочерней формы UDoc. А в обработчике события, связанного с выбором пользователем раздела меню Окно Новое, можно написать обработчик вида: var NewF : TFDoc; begin NewF := TFDoc.Create(Application); NewF.Show; end;
В родительской форме имеется ряд свойств, позволяющих управлять дочерними окнами. Все они доступны только для чтения и только во время выполнения. Свойство MDIChildCoimt определяет количество открытых дочерних окон. Свойство MDI Chi Idrenfi: integer] дает доступ к i-му окну (окна индексируются в порядке их создания). Приведем оператор, который можно вставить в предыдущий пример для задания уникального имени вновь созданного окна NewF: NewF.Caption :- 'Окно • + IntToStr(MDIChildCount);
Следующий пример показывает процедуру, с помощью которой из родительской формы Forml можно закрыть (свернуть) все дочерние окна, начиная с последнего: var I: Integer; begin
with Forml do
for I := MDIChildCount-1 downto 0 do MDIChildren[I].Close;
end;
А следующий код восстанавливает все свернутые дочерние окна: var I: Integer; begin for I :- 0 to MDIChildCount-1 do MDIChildren[I].WindowState := wsNormal; end;
В момент создания окон документов они автоматически располагаются каскадом в клиентской области родительской формы. При этом, если размера клиентской области не хватает для размещения дочерних окон, размеры последних автоматически уменьшаются. Имеется ряд методов родительской формы, упорядочивающих размещение дочерних окон. Метод Cascade располагает все открытые (не свернутые) окна каскадом. Метод Tile располагает окна мозаикой. При этом учи-
360
Глава 5
тывается свойство родительской формы TileMode. Если оно равно tbVertical, то упорядочивание производится по вертикали, а если TileMode равно tbHorizotital, то упорядочивание производится по горизонтали. Метод Arrangelcons упорядочивает расположение пиктограмм свернутых окон. Отдельно надо упомянуть об объединении главных меню родительской и дочерних форм. Обычно обе эти формы имеют главные меню, но они различны. Например, родительская форма может иметь меню Окно, а дочерняя форма — меню Файл и Правка. Меню дочерних форм не должно появляться в окнах документов, а должно всегда встраиваться в главное меню родительской формы. Поэтому свойство AutoMerge компонента типа TMamMenu на приложения MDI не влияет: встраивание меню .происходит независимо от значения этого свойства. А.места, на которые встраиваются разделы меню дочерней формы, определяются значениями свойства Grouplndex каждого раздела меню так же, как это имеет место в обычных многооконных приложениях при задании свойства AutoMerge равным true (см. разд. 3.8.1). '
5.5.5 Пример приложения с интерфейсом множества документов — простой многооконный редактор Рассмотрим проектирование простого приложения MDI. Пусть мы хотим создать многооконный редактор, в каждое окно которого можно загрузить содержимое заданного входного текстового файла, что-то в нем изменить и сохранить текст в заданном выходном файле (рис. 5.20). Построим дочерние окна с использованием компонентов RichEdit. Рис. 5.20 Пример приложения MOI: многооконный редактор в режимах упорядочивания окон каскадом (а) и по вертикали (6)
7' Чпапнканчм" редактор Пк}£&1
Сначала для простоты не будем тратить время на разработку сервиса, чтобы просто показать взаимодействие родительской формы с формами документов.
Разработка графического интерфейса пользователя
361
Начнем с построения формы окна документа. Откройте в Delphi новое приложение. Назовите открывшуюся форму FDoc. Поместите на форме компонент RichEditl типа TRichEdit. Его свойство Align задайте равным alCIient, чтобы окно редактирования заняло всю площадь окна. Сотрите текст, появившийся в RichEditl (свойство Lines). 4. Измените свойство формы FormStyle на fsMDIChild. 5. Сохраните проект, дав спроектированному модулю имя UDoc. На этом закончим пока создание формы документа. Потом мы к нему вернемся и наполним необходимым сервисом. А теперь давайте спроектируем родительскую форму. 6. Откройте в вашем проекте новую форму (File | New Form). Назовите ее FMDI. Сохраните ее модуль с именем VMDI (File Save As). 7. Измените свойство FormStyle новой формы на fsMDIForni. Может, если хотите, задать свойство WindowState равным wsMaximized, чтобы окно этой формы предъявлялось пользователю в первый момент развернутым на весь экран. В оператор uses модуля UMDI введите ссылку на модуль дочерней формы UDoc (иначе вы не сможете открывать дочерние формы и управлять ими). 8. Выполните команду Project \ Oplions и в открывшемся окне на странице Forms переведите дочернюю форму FDoc из списка автоматически создаваемых в список доступных. При этом, как вы сможете убедиться по выпадающему списку вверху окна, главной станет родительская форма FMDI — единственная, оставшаяся в списке автоматически создаваемых форм. 9. Поместите на форме список изображений ImageList и диспетчер действий АсtionList. Сошлитесь в свойстве Images компонента ActionListl на ImageListl. 10. В диспетчере действий ActionList введите действие NewWindow, которое будет соответствовать созданию нового окна документа. Обработчик его события OnExecute может иметь вид, уже рассмотренный в предыдущем разделе: 1. 2. 3.
procedure T F o r m l . N e w W i n d o w E x e c u t e ( S e n d e r : TObjsct]; vac NewF : TFDoc; begin NewF
:= 'IFDoc. Create (Application) ,-
NewF.Caption := 'Документ ' +• IntToStr (MDIChildCcurit) ; NewF.Show; end;
11. Сошлитесь на этот же обработчик в событии формы OnShow, чтобы в первый момент открывалось одно окно документа. 12. В диспетчере действий ActionList введите стандартные действия раздела Window: WindowCascade, WindowTileHorizonial, WindowTiieVeriical, WindowArrange. Никаких обработчиков событий для них писать не надо. А если вы работаете со старыми версиями Delphi, в которых нет указанных стандартных действий, вам придется ввести аналогичные нестандартные действия. В этом случае вам надо написать обработчики их событий OnExecute. Для действия Каскад: Cascade;
Для действия Упорядочить по горизонтали: TileMode := tbHorizontal; Tile;
362
Глава 5 Для действия Упорядочить по вертикали: TileMode := tbVertical; Tile;
Для действия Упорядочить значки: ArrangeIcons; 13. Введите на форму компонент MainMenu. Сформируйте в нем раздел меню Окно и сошлитесь в его подразделах на введенные вами действия. На этом первая стадия проектирования многооконного редактора завершена. Можете сохранить проект и опробовать приложение в работе. Посмотрите, как ведет себя приложение при создании нового окна документа, если размеры родительского окна не достаточно велики. Обратите внимание на то,-что ваше приложение автоматически удовлетворяет всем требованиям Windows к приложениям MDI. Например, при развертывании окна документа его заголовок автоматически перемещается в заголовок окна приложения. А если для заголовков не хватает места, они автоматически сворачиваются (см, рис. 5.20 б). Теперь можно заняться совершенствованием нашего приложения. Мы хотим, чтобы меню содержало разделы Файл, Правка, Формат, Окно, Справка. Очевидно, что разделы Файл, Окно, Справка целесообразно отнести к главной форме. А разделы Правка и Формат лучше отнести к форме документа. Для такого решения есть два аргумента. Во-первых, если не открыт ни один документ, то править и форматировать просто нечего, так что соответствующие разделы меню должны отсутствовать. А во-вторых, для большинства разделов редактирования и форматирования целесообразно использовать стандартные действия. Они действуют только для той формы, в которой они созданы. Так что создавать объекты этих действий надо на форме документов. И представляется логичным ссылаться на эти действия из меню, расположенного на той же форме. Если мы приняли такое решение, надо позаботиться, чтобы разделы меню наших двух форм не стирали друг друга. Как указывалось в разд. 5.5.4, это обеспечивается заданием соответствующих значений свойств Grouplndex разделов меню. Для разделов Файл, Окно и Справка меяю главной формы надо задать соответственно значения Grouplndex, равные О, 3, 4. Для разделов Правка и Формат меню формы документов надо задать соответственно значения Grouplndex, равные 1 и 2. Это обеспечит ту последовательность разделов, которую вы видите на рис. 5.20. Дальнейшие действия по совершенствованию нашего редактора я изложу кратко, так как они знакомы вам по предыдущим проектам. К тому же, более детально вы можете посмотреть соответствующие коды в примере, имеющемся на приложенном к книге диске. Перейдите в модуль UDoc. Сошлитесь в нем оператором uses на модуль UMDI, так как нам надо будет иметь доступ к его списку изображений. Перенесите на форму компонент ActionList. Сошлитесь в его свойстве Images на ImageListl, расположенный на главной форме (если вы не забыли сделать указанную выше ссылку на главный модуль, его компонент ImageListl должен появиться в выпадающем списке свойства Images). Введите в диспетчер ActionList стандартные действия, связанные с редактированием текстов и форматированием. Перенесите на форму компонент MainMenu и сформируйте в нем разделы меню Правка и Формат, ссылающиеся на введенные действия. Не забудьте установить в разделах указанные выше значения свойства Grouplndex. Введите также в раздел public класса формы объявление переменной FName, в которой будет храниться имя файла, загруженного в окно: FName:string;
Это имя, которое нам потребуется для сохранения текста, будет в каждом окне разным. Так что его надо независимо помнить в каждом объекте формы.
Разработка графического интерфейса пользователя
363
Теперь перейдите в модуль главной формы. Перенесите на форму диалоги открытия и сохранения файла. Введите в диспетчере ActionList нестандартные действия Open (открыть файл), CloseDoc (закрыть окно документа). Save (сохранить в файле), SaveAs (сохранить как ...), Можете также ввести действия печати, установки принтера, задания параметров страницы и т.д. Но на этих вспомогательных действиях мы останавливаться не будем. Напишите обработчики событий OnRxecute введенных действий. Обработчик для действия Open может иметь вид: procedure T F o r r n l . Q p e n E x e c u t e ( S e n d e r : T O b j e c t ) ; begin if OpenDialogl.Execute then begin (ftctiveMDIChild.ActiveControl as T R i c h E d i t ) . Lines.LoadFromFile(OpenDialogl.FileName); (ActiveMDIChild as T F D o c ] . F N a m e := OpenDialogl.FileName; ActiveMDIChild.Caption := E x t r a c t F i l e N a m e ( O p e n D i a l o g l . F i l e N a m e ) ; end.end;
E приведенном обработчике сначала вызывается диалог открытия файла. Выбранный пользователем файл нам надо загрузить в окно RithEdit формы активного документа. Доступ к этой форме обеспечивается свойством ActiveMDIChild. Поскольку активным на этой форме является окно RichEd.it (больше там, фактически, ничего нет), то доступ к нему можно получить через свойство ActiveControl. Но так как это свойство имеет тип TWinControl, а нам надо рассматривать его как объект типа TRichEdit, то окончательно доступ k окну редактирования обеспечивается конструкцией (ActiveMDIChild.ActiveControl as TRichEdit). А далее обычным образом в окно загружается выбранный пользователем файл. Рассматриваемый оператор можно было бы изменить следующим образом: (ActiveMDIChild as TFDoc).RichEditl. Lines.LoadFromFile(OpenDialogl.FileName);
Здесь форма активного окна документа рассматривается как форма типа TFDoc, и обращение следует непосредственно к расположенному на ней компоненту RichEditl. Подобное обращение позволяет в общем случае получить доступ к любому компоненту дочерней формы. Но зато предыдущий вариант обеспечивает доступ именно к активному компоненту, что имело бы смысл, если бы на дочерней форме располагалось несколько окон RichEdit. После загрузки файла в переменную FName активного окна документа заносится имя файла, чтобы в дальнейшем отредактированный текст можно было сохранить в том же файле. Чтобы компилятор понял введенную нами переменную FName, активная форма документа рассматривается как форма типа TFDoc: (ActiveMDIChild as TFDoe). Последний оператор обработчика заносит в заголовок активного окна документа имя файла, извлеченное из его полного имени функцией ExtractFileName. Обработчик события OnExecute для действия SaveAs может иметь вид: procedure TForml. SaveAsExecute(Sender: TQbject]; begin SaveDialogl.FileName := ActiveMDIChild.Caption; if(SaveDialogl.Execute 1 then begin (ActiveMDIChild.ActiveControl as TRichEdit]. Lines.SaveToFile(SaveDialogl.FileName); (ActiveMDIChild as TFDoc).FName := SaveDialogl.FileName; ActiveMDIChild.Caption := ExtractFileName(SaveDialogl.FileName); end; end;
364
Глава_5
Первый оператор предлагает пользователю имя файла, хранящееся в заголовке окна активного документа, как имя но умолчанию. Далее вызывается диалог сохранения файла. Оператор сохранения текста в файле почти не отличается от рассмотренного ранее оператора загрузки из файла. Последние два оператора запоминают имя файла. Обработчик события OnExecute для действия Save может иметь вид: procedure T F o r m l . S a v e E x e c u t e ( S e n d e r : T O b j e c t ) ; begin if((ActiveMDIChild as TFDoc).FName = T l ) then S a v e A s E x e c u t e ( S e n d e r ) e l s e (ActiveMDIChild.ActiveControl as TRichEdit) .Lines . SaveToFilel (ActiveMDIChild as TFDoc) . FName) ,end;
Оператор if проверяет, является ли переменная FName активного окна документа пустой строкой. Поскольку при загрузке документа из файла значение переменной FName делается равным имени файла, то пустая строка означает, что пока неизвестно, в каком файле надо сохранять текст документа. В этом случае производится вызов описанной выше процедуры SaveAsExecute, чтобы пользователь мог указать имя файла. А если имя файла уже известно, то документ сразу сохраняется в этом файле без вызова диалога. Чтобы приведенная процедура надежно работала, надо обеспечить при создании окна документа задание в качестве FName пустой строки. Для этого в описанную ранее процедуру NewWindowExccute надо добавить оператор KewF.FKame := '';
Осталось рассмотреть обработчик события OnExecute для действия CloseDoc. Он может состоять всего из одного оператора: ActiveMDIChild.Free;
Этот оператор удаляет из памяти объект активной форы документа. Здесь нельзя было бы применить метод Close, так как для дочернего окна приложения MDI закрыть форму означает свернуть окно. На этом мы закончим рассмотрение примера приложения MDI. Все особенности приложений такого вида мы рассмотрели. Л несущественные детали вы можете посмотреть в примере, приведенном на приложенном к книге диске.
5.6 Печать и разработка отчетов 5.6.1 Печать с помощью различных функций 5.6.1.1 Печать с помощью функций файлового ввода/вывода Печать в Delphi может осуществляться различными способами. Простейший способ — использование обычных функций ввода/вывода в текстовый файл, но связывание выходного потока не с файлом, а с принтером. Этот простейший способ можно использовать для получения оперативных распечаток протоколов какого-то процесса. Предположим, например, что компьютер на заводе подключен к строчному принтеру, чтобы сохранять данные по мере их накопления. Если в компьютере происходит аппаратный отказ, то все данные, собранные к этому моменту, имеются в распечатках. Аналогично, вы, возможно, захотите напечатать простой список, которому не требуется графика, шрифты и другое форматирование, доступное в Windows. Код, используюший этот метод для печати текста, хранящегося в компоненте Editl, выглядит следующим образом:
Разработка графического интерфейса пользователя
365
var Р : T e x t F i l e ; begin AssignPrn ( Р ) ; rewrite (Р|_; wciteln (P,Editl.Text); CloseFile ( P ) ; end;
Здесь объявляется переменная Р типа TextFile. Далее используется разновидность команды Assign — AssignPrn. Она настраивает переменную Р на порт принтера, обращаясь с ним, как с файлом. Затем порт принтера должен быть открыт, что делается командой rewrite. Текст передается в принтер процедурой writeln, а закрывается порт принтера командой CloseFile. Важно закрыть порт принтера для завершения операции печати. По этой команде, как и в случае файла, любой текст, остающийся в памяти, пересылается в принтер, и порт закрывается. 5.6.1.2 Печать форм методом Print Формы в Delphi имеют метод Print, который печатает клиентскую область формы. При этом полоса заголовка формы и полоса главного меню не печатается. Таким образом, можно включить в приложение форму, в которой пользователь во время выполнения размещает необходимые для печати результаты. Если имя этой формы Form2, то ее печать может выполняться оператором Form2.Print;
Свойство формы PrintScale определяет опции масштабирования изображения при печати. Возможные значения PrintScale: poNone
Масштабирование не используется. Размер изображения может изменяться в зависимости от используемого принтера.
Делается попытка напечатать изображение формы того же размера, который виден на экране. ро Proportional Увеличивает или уменьшает размер изображения, подгоняя его под размер страницы, заданный при установке принтера. Это значение принято по умолчанию.
poPrintToFit
5.6.1.3 Методы компонентов, обеспечивающие печать Ряд компонентов в Delphi имеют методы, обеспечивающие печать хранящихся в них данных. Например, компонент KichEdit имеет метод Print, позволяющий печатать в обогащенном формате текст, хранящийся в компоненте. В этот метод передается единственный параметр типа строки, назначение которого заключается только в том, что при просмотре в Windows очереди печатаемых заданий принтера эта строка появляется как имя задания. Например, оператор R i c h E d i t l . P r i n t ( ' P r i n t i n g of RichEditl');
обеспечивает печать текста компонента RichEditl, причем задание на печать получает имя «Printing of RichEdill», Печать воспроизводит все заданные особенности форматировании. Перенос строк и разбиение текста на страницы производится автоматически. Длина строк никак не связана с размерами компонента RichEdit, содержащего этот текст. Компонент Chart, используемый для отображения графиков и диаграмм (см. разд. 3.6.4), также имеет метод Print, обеспечивающий печать. Предварительно может быть выполнен метод PrintPortrait, задающий книжную (вертикальную) ориентацию бумаги, или метод Print Landscape, задающий альбомную (горизонтальную) ориентацию. Масштабировать размер печатаемого графика можно, вызвав предварительно метод PrintRect:
366
Глава 5 procedure PrintRect < Const R : TRect ) ,-
в котором параметр R определяет размер области принтера, в которой осуществляется печать. Компонент Chartfx (см. разд. 3.6.5) имеет быструю кнопку печати (пятая слева в инструментальной панели рис. 3.31), с помощью которой пользователь в любой момент может напечатать текущий график или диаграмму. 5.6.1.4 Печать файлов средствами стандартных приложений Windows с помощью функции ShellExecute и обращения к серверам СОМ Для печати файлов средствами стандартных приложений Windows можно использовать функцию ShellExecute. Подробно об этой функции см. разд. 7.2.2. А здесь мы коротко рассмотрим технологию такой печати без каких-либо дополнительных пояснений. Чтобы воспользоваться функцией ShellExecute, надо в оператор uses вашего приложения добавить ссылку на модуль ShelLApi. Функция ShellExecute при соответствующем задании ее параметров ищет по расширению заданного для печати файла соответствующую ему системную программу Windows, и, если находит, то осуществляет печать. Например, обычно Windows настроен так, что файлам с расширением .txt соответствует программа Notepad, а файлам с расширением .doc — Word. В этом случае выполнение оператора ShellExecute(Handle, ' p r i n t 1 , ' t e s t . t x t ' , n i l , n i l , S W _ H i d e ) ; вызовет печать файла с именем test.txt средствами программы Notepad, а оператор ShellExecute(Handle,
'print',
'test.doc',nil,nil,SW_Hicfe);
вызовет печать файла с именем test.doc средствами программы Word. Этот способ печати можно использовать как для распечатки заранее созданных файлов, так и для распечатки файлов, созданных во время выполнения приложения методами SaveToFile, имеющимися у многих компонентов. Имеется также возможность печатать тексты и графику средствами стандартного редактора Windows Word или средствами Excel. Для этого, начиная с Delphi 5, в библиотеке имеются компоненты — серверы СОМ, позволяющие сначала создать документ соответствующего приложения Windows, а затем его напечатать. Эти возможности рассмотрены в гл. 7 разд. 7.9.
5.6.2 Печать с помощью объекта Printer В Delphi имеется класс печатающих объектов TPrinter, который обеспечивает печать текстов, изображений и других объектов, расположенных на его канве — Canvas. Свойства канвы подробно рассмотрены в разд. 6.1.3 и здесь мы не будем на них останавливаться. Достаточно знать, что на канве могут размещаться различные изображения и текст. Модуль Delphi, именуемый Printers, содержит переменную Printer, являющуюся объектом типа TPrinter. Чтобы ее использовать, надо добавить модуль Printers в оператор uses вашей программы. Автоматически он не добавляется. Рассмотрим некоторые свойства и методы объекта типа TPrinter. Свойство, метод Описание Canvas Канаа Canvas — место в памяти, в котором формируется страница или документ перед печатью. Canvas обладает рядом свойств, включая Реп (перо) и Brush (кисть), которые позволяют вам делать рисунки и помещать на них текст. Подробное описание канвы и методов работы с ней вы найдете в разд. 6.1.3.
Разработка графического интерфейса пользователя
367
Свойство, метод Описание TextOut
Метод канвы, который позволяет посылать в нее текст.
BeginDoc
Используется для начала задания печати.
EndDoc
Используется для окончания задания печати. Фактическая печать происходит только при вызове EndDoc.
PageHeight
Возвращает высоту страницы в пикселах.
NewPage
Принудительно начинает новую страницу на принтере.
PageNumber
Возвращает текущий номер печатаемой страницы.
Предположим, вы хотите напечатать текст, используя печатающий объект. Вы можете написать код вида: Printer.BeginDoc; P r i n t e r . C a n v a s . T e x t O u t ( 1 0 , 1 0 , ' Я печатаю через объект P r i n t e r ' ) ; Printer.EndDoc;
Этот код вызывает печать на канве принтера текста *Я печатаю через объект Printer», начиная с десятого пиксела слева и десятого сверху. BeginDoc запускает задание на печать. Текст посылается на канву с помощью метода TextOut объекта Canvas. Метод EndDoc вызывает печать текста и останавливает задание на печать. Если вы хотите напечатать изображение, хранящееся в компоненте Imagel, это можно сделать операторами: Printer.BeginDoc; with Imagel.Picture.BitMap do Printer.Canvas.CopyRect{Rect(0,0,Height,Width) , Canvas,Rect(0,0,Height,Width!]; Printer.EndDoc;
Печатающий объект Printer не производит автоматического переноса строк и разбиения текста на страницы. Поэтому печать длинных текстов с помощью объекта Printer требует достаточно сложного программирования. Проще это делать описанными ранее способами или с помощью изложенной в следующем разделе системы Rave.
5.6.3 Подготовка и печать отчетов с помощью Rave 5.6.3.1 Общие сведения Для подготовки отчетов в версиях, предшествующих Delphi 7, имелась страница QReport палитры компонентов. На ней было расположено множество элементов системы QuickReport. Однако в Delphi 7 эта страница ликвидирована. Ей на смену пришла страница Rave с компонентами системы Rave Reports фирмы Nevrona. Прежняя система QuickReport еще доступна на уровне кодов, но это, очевидно, не надолго и делается только из соображений обратной совместимости. Так что в данной книге система QuickReport не рассматривается (она рассматривалась в предыдущих книгах «Программирование в Delphi ...»). Система Rave Reports Borland Edition (ранее известная как Nevrona ReportPrinter Pro) дает большую гибкость при построении отчетов. Система состоит как бы из двух частей: редактора Rave, позволяющего визуально проектировать форму отчета, и страницы компонентов Rave, интегрирующих сохраненную в файле форму отчета в приложение Delphi. Полное рассмотрение Rave в рамках данной книги невозможно. Интересующиеся могут ознакомиться с этой системой и документацией по ней, например, по адресу http://'www.nevrona.com/rave/index.html или по специальной литературе. Ниже рассмотрены очень коротко только основы работы
Глава 5
368
с Rave, причем пока мы не будем затрагивать главное в этой системе работу с данными. О работе Rave с данными см. в разд. 11.2. Ряд возможностей Rave можно использовать в приложениях Delphi, только если у вас имеется специальная лицензия Rave EUDL. Эти возможности далее мы рассматривать не будем. 5.6.3.2 Редактор Rave Для создания отчетов используется редактор Rave. Открыть редактор можно из среды Delphi 7 командой Tools Rave Designer, или выполнить средствами Windows файл Rave.exe, находящийся в каталоге ...\Delphi7\Rave5. Перед вами откроется окно редактора, показанное на рис. 5.21. Рис. 5.21 Окно редактора Rave
Отчет от [Reoort.DaleShort]
Как я осваиваю Delphi Я только учусь работать с Дельфи. Но уже кой-что уывч!![ Это мой первый отчет Дельфи,
Б центральной частя окна расположено поле страницы будущего отчета. На него, примерно так же, как в Delphi, можно переносить компоненты, например, со страницы Standard, которая открыта на рис. 5.21. В правой части экрана редактора Rave вы можете видеть дерево отчетов, включенных в текущий проект, включая страницы и размещенные на них компоненты. В левой части окна расположен Инспектор Объектов, подобный тому, с которым вы уже освоились в Delphi. В нем отображаются свойства объекта, выделенного на поле страницы или выделенного в дереве проекта. Ниже окна Инспектора Объектов вы можете видеть пояснение по тому свойству, которое выделено в окне. Состав свойств, отображаемых в окне Инспектора Объектов, зависит от настройки редактора Rave. Чтобы проверить установленные опции, выполните команду Edil Preferences. В открывшемся окне в разделе Environment вы увидите группу радиокнопок User Level — уровень пользователя. В группе три кнопки: Beginner — начинающий, Intermediate — средний, Advanced — продвинутый. Поскольку вы уже овладели средой Delphi, смело можете считать себя продвинутым и включать кнопку Advanced. Новый проект Rave открывается быстрой кнопкой New Project — крайней левой в верхнем ряду на рис. 5.21. Если вы щелкнете на этой кнопке, то в дереве проекта сможете увидеть, что создался новый проект, в нем создался отчет, и в отчете создалась пустая страница. Каждый проект Rave может включать несколько отчетов. Если вам надо добавить в проект новый отчет, это делается командой File New Report или соответствующей быстрой кнопкой — четвертой слева в верхнем ряду на
Разработка графического интерфейса пользователя
369
рис. 5.21. Команда File | New Report Роде или соответствующая быстрая кнопка (седьмая слева в верхнем ряду на рис. 5.21) создаст новую страницу в том проекте, вершина которого выделена в дереве проекта. Давайте рассмотрим работу с редактором Rave на примере создания простого отчета, содержащего какое-то изображение, тексты и имеющего две страницы. Пример первой страницы такого отчета вы видите на рис, 5.21. А нечто более сложное и главное - разработку отчетов, связанных с базой данных, мы отложим до гл. 11, Откройте новый проект. Перенесите на первую страницу отчета компонент Bitmap со страницы Standard (четвертая справа пиктограмма на этой странице см. рис. 5.21). Компонент Bitmap имеет свойство FileLink — файл, содержимое которого отображается в компоненте. Щелкните на кнопке с многоточием около этого свойства в окне Инспектора Объектов, и выберите в диалоге какой-то файл изображения (как вы увидите в гл. 6, файлы изображений, поставляемые с Delphi 7, хранятся в каталоге ...\Program Files\Common Files'\Borland Shared\Images). На рис. 5.21 в компонент загружен из этого каталога файл \Splash\16Color\ATHENA.BMP. Свойство Anchor компонента позволяет выбрать привязку поля компонента на странице. Если вы щелкнете на кнопке с многоточием около этого свойства в окне Инспектора Объектов, то сможете в довольно наглядном диалоге выбрать размещение компонента. Очевидно, в нашем примере по горизонтали надо поместить его в центре страницы. Теперь перенесите на страницу компонент Text (левая пиктограмма на странице Standard). В этом компоненте мы поместим заголовок отчета («Как я осваиваю Delphi» на рис. 5.21). Напишите этот или другой текст в свойстве Text. У этого и других текстовых компонентов имеется свойство Font, позволяющее выбрать шрифт. Нам надо воспользоваться им, чтобы выбрать шрифт большого размера и, по возможности, красивый. Но тут вас могут поджидать неприятности. В диалоге, вызываемом кнопкой в свойстве Font, имеется ограниченный набор шрифт-.в, да и те при русских надписях могут давать сбои. Если вы выберете системный шрифт MS Sans Serif, то, вероятно, все будет нормально. Но для других шрифтов вы можете увидеть в текстовом окне абракадабру. Эти сбои можно устранить, если jaflaвать шрифты не свойством Font, а с помощью страницы Fonts — вторая справа закладка на рис. 5.21. Там вы можете задать желательный шрифт с символами кириллицы. Ниже введенного заголовка давайте разместим многострочиое текстовое окно Memo (вторая слева пиктограмма на странице Standard). Текст этого окна за,:) 1втся свойством Text. Шрифт, как и в предыдущем случае, лучше задавать с пом. щью страницы Fonls. Выравнивание текста внутри окна задается свойством Font Justify. А привязка самого окна на странице задается, как и во всех других компонентах, свойством Anchor. Теперь добавим перед заголовком отчета строку с текстом «Отчет от ...», в котором вместо многоточия пусть отображается текущая дата. Для текста «Отчет от » поместите на страницу уже рассмотренный ранее компонент Text. А правее него поместите компонент DataText со страницы Report (закладка правее закладки Standard). Щелкните в окне Инспектора Объектов на кнопке с многоточием около свойства DataField этого компонента. Перед вами откроется окно, показанное на рис. 5.22. В выпадающем списке Report Variobles вы сможете найти различные переменные, которые позволяет отобразить компонент DataText. Среди них номера страниц, общее число страниц, дата, время и т.п. Выберите в списке DaleShort краткое отображение даты. После этого щелкните на кнопке Insert Report Var. В панель Dale Text занесется текст «Reprt.DateShort», который обеспечит отображение даты.
Глава 5
370
Окно задания данных
Добавьте в отчет еще одну страницу, просто для того, чтобы посмотреть, как работать с несколькими страницами отчета. Можете поместить на странице какой-то текст. Можете нечто нарисовать с помощью компонентов, размещенных на странице Drawing. Словом, заполните страницу, чем хотите. Теперь вам надо указать, в какой последовательности эти страницы должны печататься и показываться пользователю при просмотре. Для этого выделите в дереве проекта вершину вашего отчета. В окне Инспектора Объектов вы увидите свойство PageList — список страниц. Щелкните на кнопке с многоточием около этого свойства. Откроется окно, показанное на рис. 5.23. Из выпадающего списка Report Pages вы можете выбрать поочередно страницы отчета, и кнопкой Add Роде добавить выбранные страницы в список Page List. Кнопками со стрелками вы можете поменять последовательность страниц. Так что первая созданная вами страница не обязательно должна быть первой в отчете. Рис. 5.23 Задание последовательности страниц отчета
if Aveilab e Pages Report Pages ]Pas«2 Окв Pages
1
:•
j Прежде, чем расставаться с редактором Rave, обратите внимание еще на некоторые свойства страниц и отчета в целом. Все они имеют привычное для вас свой-
Разработка графического интерфейса пользователя
371
ство Name — имя, которое вы, конечно, можете изменить на что-то более осмысленное, чем имя, даваемое по умолчанию. Но у страниц и проекта имеется еще одно свойство — FullName. В этом свойстве вы можете написать строку - пояснение, содержащее несколько слов русского текста. Есть также свойство Description. Оно может содержать развернутое многострочное пояснение. Заполните для вашего отчета свойства FullName и Description. Например: «Мой первый отчет» и «Простейший отчет по Delphi без связи с базами данных». Эти свойства пригодятся нам, когда мы будем строить приложение Delphi, работающее с отчетами. Можно также заполнить еще одно свойство Category — категория. Например, можете написать в этом свойстве: «Простые отчеты». Категории, как вы увидите позднее, дают дополнительные возможности классификации и упорядочивания отчетов, входящих в проект. Мы завершили наш первый отчет Rave. Сохраните его командой File [ Save as или соответствующей быстрой кнопкой (третья слева на рис, 5.21). Можете посмотреть результат ваших трудов, выполнив команду File Execute Report (правая быстрая кнопка с изображением принтера, или горячая клавиша F9). Но сейчас я не буду описывать диалоговые окна, которые вы при этом увидите, так как они идентичны тем, которые мы рассмотрим в следующем разделе. 5.6.3.3 Компоненты Delphi для связи с Rave Теперь, создав отчет в редакторе Rave, вернемся в среду Delphi и рассмотрим создание приложения, работающего с этим отчетом. Компоненты Delphi, обеспечивающие работу с отчетами Rave, расположены на странице Rave. Основным из них является RvProject. Рассмотрим некоторые методы и свойства этого компонента. Процедура Open: procedure Open;
открывает проект Rave, заданный свойством ProjectFile этого компонента. Метод LoadFromFile: procedure LoadFromFile(FileName: s t r i n g ) ;
загружает проект из файла, указанного параметром FileName. Так что методы Open и LoadFromFile — два альтернативных варианта открытия проекта. Процедура Close: procedure Close;
закрывает открытый проект и освобождает занятую им память. Эту процедуру обязательно надо вызывать перед окончанием приложения, если в нем открывался какой-то проект. Каждый проект Rave может содержать несколько отчетов. Список этих отчетов можно получить методом GetReportList: procedure GetReportList (ReportList: TStrings; FullName; boolean);
Метод загружает список имен отчетов, содержащихся в проекте, г список ReportList. Если параметр FullName равен true, то загружаются имена, заданные свойствами FullName отчетов. Если параметр FullName равен false, то загружаются имена, заданные свойствами Name. Например, оператор RvProjectl.GetReportList(ListBoxl.Items,true);
загрузит в список ListBoxl полные имена всех отчетов, содержащихся в проекте. При разработке проекта отчеты могут быть отнесены к той или иной категории своими свойствами Category. Имеется метод GetReportCategoryList, который отличается от GetReportList тем, что загружает список отчетов только перечисленных в нем категорий. Метод объявлен следующим образом:
Глава 5
372
procedure G e t R e p o r t C a t e g o r y L i s t ( R e p o r t L i s t : TStrings; C a t e g o r i e s : s t r i n g ; FullName: b o o l e a n ) ;
Параметры ReportList и FullName имеют тот же смысл, что и в методе GetReportList. А параметр Categories является строкой, в которой через точки с запятой перечисляются категории (свойства Category), отчеты которых должны войти в список ReportList. Например, оператор RvProjectl.GetReportCategoryList(ListBoxl.Items, 'Простые отчеты;Отчеты по базам д а н н ы х ' , t r u e ] ;
загрузит в список ListBoxl полные имена отчетов, в которых указаны категории «Простые отчеты* и «Отчеты по базам данных». В момент открытия проекта текущим становится его первый отчет. Функция Select Report: f u n c t i o n SelectReport(ReportNarae: s t r i n g ; F u l l N a m e : b o o l e a n ) : boolean;
делает текущим отчет, указанный именем ReportName. Если параметр FullName равен true, то ReportNarae — это имя отчета, заданное его свойством FullName. Если параметр FullName равен false, то ReportName — это свойство Name отчета. Например, оператор R v P r o j e c t l . S e l e c t R e p o r t ( ' М о й первый о т ч е т ' , t r u e ) ;
сделает текущим отчет с указанным полным именем. Если отчета с указанным именем нет в проекте, функция SelectReport возвращает false. Свойства ReportName, ReportFullName и ReportDesc компонента RvProject содержат соответственно свойства Name f FullName и Description текущего отчета. Процедура ReportDescToMemo: procedure ReportDescToMemo(Memo: TCustomMemo];
заносит в окно редактирования, указанное параметром Memo, описание текущего отчета, заданное его свойством Description. Свойства DLLFile, LoadDesigner, метод Design и некоторые другие можно использовать только при наличии лицензии Rave EUDL. Так что не будем на них останавливаться. Метод Execute: procedure Execute;
вызывает выполнение отчета текущего отчета. При вызове этого метода открывается диалоговое окно, показанное на рис. 5.24 (аналогичное окно открывается в редакторе Rave при выполнении отчета). Выбор кнопки Preview в этом окне обеспечивает предварительный просмотр отчета. Кнопка Printer соответствует печати. При выборе этой кнопки становятся доступными элементы панели Options, позволяющие задать параметры печати: число копий (Copies), необходимость разобрать копии по экземплярам (Collate), двустороннюю печать (Duplex). Выбор кнопки File Рис. 5.24 Диалоговое окно выполнения отчета
Разработка графического интерфейса пользователя
373
обеспечивает запись отчета в файл. При выборе File делается доступной кнопка, щелчок на которой позволяет выбрать имя файла. Список Formal дает возможность выбрать формат файла: формат NDR Rave или формат установленного принтера. Если вы поместите в свое приложение компоненты RvRenderPDF, RvRenderHTML, RvRenderRTF и RvRenderText со страницы Rave, то в выпадающем списке Format появятся строки, соответствующие другим форматам файлов. Кнопка Setup открывает диалог, позволяющий выбрать принтер и установить параметры печати. Описанный метод Execute вызывает выполнение текущего отчета. Аналогичный метод ExecuteReport: procedure ExecuteReport(ReportName: s t r i n g ) ;
вызывает выполнение отчета, имя которого (Name) задано строкой ReportName. Возможности компонента RvProject могут быть существенно расширены, если в приложение перенести компонент RvSystem и в свойстве Engine компонента RvProject сослаться на введенный компонент RvSysteml. Тогда при выполнении методов Execute и ExecuteReport компонента RvProject будут учитываться установки, заданные в компоненте RvSystem. А в этом компоненте имеется множество свойств, позволяющих раздельно выполнять операции, связанные с предварительным просмотром и печатью, задавать настроечные параметры операций и т.п. Свойство DefaultDest задает операцию, выполняемую по умолчанию: rdFile — печать в файл, rdPreview — предварительный просмотр, rdPrinter — печать. Если при этом установить подсвойство soShowStatus свойства SystemOptions в false, то выполнение соответствующей операции будет выполняться без предварительного показа диалогового окна рис. 5.24. Так что от пользователя не будет требоваться никаких дополнительных действий. Множество подсвойств таких свойств, как System Preview, SystemPrinter и System Setups позволяют программно или во время проектирования задать соответственно опции режима просмотра, печати и окна на рис. 5.24. Свойства TitlePreview, TitleSetup и TitleStatus позволяют задать нестандартные заголовки соответствующих диалоговых окон. Некоторые из упомянутых свойств мы подробнее рассмотрим в следующем разделе при построении тестовых приложений. Надо сказать, что компонент RvSystem'объединяет в себе возможности двух других компонентов — RvRenderPreview и RvRenderPrinter, которые аналогичным образом позволяют настроить операции просмотра и печати и выполнить их методом Execute. Так что мы не будем отдельно рассматривать эти компоненты. А теперь остановимся на событиях OnPrintHeader, О nPrint Footer и On Print компонента RvSystem. События OnPrintHeader и OnPrintFooter, соответствующие печати верхнего и нижнего колонтитулов, происходят при выполнении метода Execute или ExecuteReport компонента RvProject. Они наступают при печати или просмотре каждой новой страницы. Обработчики этих событий позволяют ввести в отчет соответствующие колонтитулы. Эти события наступают также при выполнении метода Execute компонента RvSystem. Но в этом случае к ним добавляется еще события OnPrint и OnPrintPage. Первое из них наступает при печати тела отчета, а второе — при печати каждой страницы отчета. Обработчики этих событий позволяют программно сформировать тело отчета. Метод OnPrintPage удобен, если страницы отчета имеют одинаковую форму и отличаются только печатаемыми данными. В обработчики событий OnPrintHeader, OnPrintFooter, OnPrint и OnPrintPage в качестве параметра Sender передается объект типа TRvNDRWriter. В па.? литре библиотеки имеется компонент такого типа. Он может быть добавлен в проект и на него может быть сделана ссылка в свойстве Engine компонента RvProject. Но если такой компонент не вводится в приложение явно, он создается автоматически, и именно он генерирует указанные события. Тогда доступ к его многочис-
374
Глава 5
ленным свойствам и методам можно получить с помощью конструкций Sender as TRvNDRWriter или Sender as TBaseReport (TBaseReport — это родительский класс для TRvNDRWriter). Среди свойств объекта TRvNDRWriter, прежде всего, отметим канву Canvas. Канва, подробно рассмотренная в гл. 6, позволяет размещать на ней изображения и тексты с помощью самых различных методов. Такие свойства объекта TRvNDRWriter, как FontName, PontSize, FontHeight, FontWidth, FontCharset, FontColor позволяют определять и задавать имя шрифта, его размер, высоту, среднюю ширину символов, множество символов, цвет. Свойство Fonts возвращает строку с перечнем шрифтов, доступных на заданном принтере. Свойство FontRotation задает в градусах угол поворота надписи. Булевы свойства Bold, Italic, Underline определяют полужирный шрифт, курсив и подчеркивание. Свойство LineHeightMethod определяет способ расчета высоты строк текста. При значении IhmFont (расчет по шрифту) высота строки определяется высотой шрифта — свойством FontHeight. Если LineHeightMethod равно IhmLinesPerlnch (расчет по числу строк на дюйм), то высота строки определяется параметром LinesPerlnch. Если же LineHeightMethod равно hmUser, то высота строки задается свойством LineHeight. Свойство LineNum указывает текущую строку. Задавая это свойство, можно перемещать текстовый курсор в нужную строку. Свойства YPos и ХРоз определяют и устанавливают вертикальную и горизонтальную координаты текстового курсора. Свойства LineBottom и LineTop определяют вертикальную координату нижней и верхней границ текущей строки. Печать текста может задаваться методами Print (печать в текущую позицию тела отчета), Println (печать и переход на новую строку), PrintFooter (печать нижнего колонтитула), PrintHeader (печать верхнего колонтитула) и рядом других. Метод NewLine обеспечивает переход к следующей строке. Метод CR переводит курсор к началу текущей строки. Метод PrintJustify: procedure P r i n t J u s t i f y ( T e x t : s t r i n g ; Pos: double; J u s t i f y : T P r i n t J u s t i f y ; Margin: double; W i d t h : double] ,-
обеспечивает печать текста Text, начиная с позиции Pos, с выравниванием Justify (pjLeft, pjRight, pjCenter — влево, вправо, по центру). Параметр Margin указывает отступ от границ области, параметр Width — ширина области печати. Возможна печать в несколько колонок, т.е. в виде таблицы. В этом случае число и расположение колонок задается методом SetColumns: procedure SetColumns(NewColumns: integer; Between: d o u b l e ) ;
Параметр NewColumns задает число колонок, а параметр Between определяет расстояние между ними. Перечисление и обсуждение всех свойств и методов класса TBaseReport в рамках данного раздела нереально. Тем более что многие методы, связанные с изображением различных линий, фигур и т.п. тождественны методам, рассмотренным в гл. 6. А некоторые методы не работают, если у вас нет лицензии Rave. Поэтому ограничусь несколькими примерами. Ниже приведен обработчик события OnPrint Header: with Sender as TBaseRepart do begin SetFontl 'Arial C 5 t R ' , 1 4 ) ; PrintHeader('Мой первый отчет Rave 1 ,pjLeft) ; PrintHeader('Дата ' + D a t e T o S t r ( D a t e ) , p j R i g h t ) ; SetPen(clBlack, psSolid , 1, pmCopy); MoveTo(0, LineBottom); LineTo(PageWidth, LineBottom); end;
Разработка графического интерфейса пользователя
375
Первый оператор методом SetFont устанавливает шрифт Arial CYR размера 14. Далее печатается текст «Мой первый отчет Rave», выровненный влево. Следующий оператор печатает текущую дату, выравнивая текст вправо. Далее следует оператор, который методом SetPen устанавливает свойства пера (см. о свойствах пера в гл. 6). Два следующих оператора проводят этим пером линию, подчеркивающую снизу верхний колонтитул. Эти операторы станут вам понятны после изучения гл. 6. Ниже приведен аналогичный пример, добавляющий в отчет нижний колонтитул, содержащий номер страницы: with Sender as TBaseReport do begin SetFontСArial CYR',14); PrintFooter('Стр. ' + IntToStr(CurrentPage),pjLeft); SetPen{clBlack, psSolid , 1, pmCopy); LineHeightMethod := IhmFont; MoveTo(0, LineTop); LineTo (PageWictth, LineTop) ; end;
Вряд ли этот код требует пояснений, кроме, может быть, использованного в нем свойства CurrentPage — номера текущей страницы. Еще ряд примеров вы найдете в следующем разделе. 5.6.3.4 Примеры работы с отчетами Rave Рассмотрим примеры построения приложений Delphi, работающих с отчетами Rave. Начните новое приложение. Перенесите на форму компонент RvProject, список ListBox, окно Memo, диалог OpenDialog и две кнопки Button. Список ListBoxl будет содержать перечень отчетов в открытом проекте. Окно Memol будет показывать описание отчета, выделенного в окне ListBoxl. Кнопка Buttonl с надписью «Просмотр/Печать* будет выполнять указанную пользователем операцию с выбранным отчетом. А кнопка Button2 с надписью «Открыть проект» позволит пользователю отрыть проект Rave, сохраненный на диске. Расположить эти компоненты можно примерно так, как показано в верхней части рис. 5.25. Правда, на рисунке вы видите еще ряд компонентов, которые мы введем позднее. Рис. 5.25
Тестовое приложение во время выполнения
№'pa6oid с «четами Rave
376
Глава 5
В свойстве ProjectFile компонента RvProjectl задайте ссылку на файл проекта, который вы создали в разд. 5.6.3.1. Впрочем, это надо сделать, если вы хотите, чтобы в первый момент открылся именно этот проект. Обработчик события формы OnCreatc может иметь вид: procedure TForml.ForraCreate(Sender: TObject); begin RvProjectl-Open; RvProjectl-GetReportList(ListBoxl.Items,true); ListBoxl.Itemlndex := 0;
ListBoxlClick(Sender); end; Первый оператор этого обработчика открывает проект, файл которого указан • в свойстве ProjectFile компонента RvProjectl. Второй оператор загружает в список ListBoxl методом GetReportList перечень отчетов, содержащихся в проекте. Эти строки, которые вы можете видеть на рис. 5.25, являются свойствами FullName соответствующих отчетов. Третий оператор задает индекс списка ListBoxl, соответствующий первой строке. А последний оператор вызывает процедуру ListBoxlClick, являющуюся обработчиком щелчка на списке ListBoxl. Эта процедура может иметь вид: procedure T F o r m l . L i s t B o x l C l i c k ( S e n d e r : T O b j e c t ) ; begin RvProjectl.SelectReport(ListBoxl.Items[ListBoxl.Itemlntiex], true); RvProjectl.ReportDescToMemo(Memol); . end;
Первый оператор этого кода с помощью метода SelectReport делает активным отчет, выбранный пользователем в списке ListBoxl. В второй оператор методом ReportDescToMemo заносит в окно Memol развернутое описание {свойство Description) этого отчета. Обработчик события OnClose приложения должен содержать оператор, выгружающий проект из памяти: RvProjectl.Close;
Диалог OpenDialOffl должен открывать только проекты Rave. Так что в свойстве DefauItExt этого компонента напишите «rav», а в свойстве Filter задайте фильтр: файлы проектов RAVE ( * . r a v ) I * . r a v
Обработчик щелчка на кнопке Открыть проект может иметь вид; if (OpenDialogl.Execute) then begin R v P r o j e c t l . LoadFrornFile(OpenDialogl.Filename); FormCreate(Sender); end;
Сначала методом LoadFromFile загружается выбранный пользователем файл проекта, а затем следует вызов описанной ранее процедуры FormCreate. Обработчик щелчка на кнопке Просмотр/Печать может содержать всего один оператор: RvProjectl.Execute; Он открывает окно выбора операции с отчетом (см. рис. 5.24), рассмотренное в предыдущем разделе, Вы создали -приложение, позволяющее пользователю открыть любой проект Rave, выбрать в нем требуемый отчет, просмотреть его и напечатать. Можете выполнить это приложение и убедиться в его работоспособности. Если в окне рис. 5.24 вы выберете резким просмотра, то увидит окно предварительного про-
377
Разработка графического интерфейса пользователя
смотра (рис. 5.26 а) с загруженным в него отчетом, который подготовили в разд. 5.6.3.1. Можете вместо своего проекта открыть проект RaueDetno.rav (каталог ...Delphi7\Raue5\Demos), поставляемый с Delphi 7, и изучить имеющиеся в нем примеры отчетов. Теперь попробуем усовершенствовать это приложение. Во-первых, хотелось бы обеспечить пользователю возможность работать, не вызывая диалоговое окно, приведенное на рис. 5.24. Тем более что это окно содержит английские тексты, способные запутать неопытного пользователя. Во-вторых, хотелось бы добавить в печатаемые отчеты верхние и нижние колонтитулы. Для решения этих задач добавьте в приложение компонент RvSystera. и группу радиокнопок RadioGroup (на рис. 5.25 она расположена в середине окна). Введите кнопки, соответствующие различным операциям (см. рис. 5.25). В свойстве Engine компонента RvProjectl сошлитесь на компонент RvSystenil. Теперь можно задать некоторые характеристики RvSysteml. В свойстве DefaultDest выберите операцию, которая должна выполняться по умолчанию. Например, rdPreview — просмотр. Кнопка, включенная в группе RadioGrou.pl в первый момент, должна соответствовать этой операции. В свойстве TitlePrevicw можете задать заголовок окна предварительного просмотра: «Предварительный просмотр* в примере на рис. 5.26. В свойстве SystemPrinter можете задать подсвойство Unit, определяющее единицу измерения. Например, unMM — измерение в миллиметрах. В свойстве SystemOptions проследите, чтобы была установлена в true опция soPreviewModal. Она обеспечивает показ окна предварительного просмотра как модального. Если эта опция выключена, то пользователь не сможет воспользоваться кнопками в этом окне. В свойстве SystcmSetups надо выключить опцию ssAllowSctup (задать значение false), чтобы запретить отображение окна рис. 5.24. В свойстве SystemOptions надо выключить опцию soShowStatus (задать значение false), так как иначе окно рис. 5.24 все-таки будет мелькать на экране. Можете также задать множество опций, определяющих характеристики печати, просмотра и диалоговых окон.
Рис. 5.26 Предварительный просмотр отчетов: подготое ленного заранее (а) и программно сформированного (б)
а)
б)
Fit Pa?! jaw
bit £ч«
Jj Q ^ j И « > W Paga |-f~*=-rf j
J Н 4*
|Й£
ВШ«
N •« > И Page |1
of 2
Эю текспестовага отчата Rave с форы и&ав энного программы о
/^jjf o™t, ,,„.».,
JTD печать Е 4 колонки
Как л осваиваю Delphi «0 vwtk *ou«» t Hfl».*iL Чо/«е Кн* ЧЮ menН|ОМОЛ П^р^ын 01ЧГ1 Д"1Ь**1. ПО 041 I»*4I UHDTHt *>ЙР1Ы, tBOIKIHCI-*!* ^0|»|МЧЧ>»У ЙТЧЯОН.
.
.0 hi, I- ••
Ir ч i
i ', i
i i D
11 IJ 13
21 12 23
15 1G 17 IB 19 20
3S 26 27 28 29 30
11
it
31 32 33
за
35 36 37 38 39 40
'•
'1
Теперь напишите обработчик щелчка на группе радиокнопок: procedure TForml.RadioGrouplClick(Sender: begin
TObjectl;
378
Глава 5 if (RadioGroupl.Itemlndex < 2] then RvSysteral.SysteraSetups := RvSysteml.SysteraSetups — IssAllowSetup] else RvSysteml.SystemSetups := RvSysteral.SystemSetups + [ssAllowSetup]; RvSysteml.TitleSetup := 'Выберите требуемую операцию'; case RadioGroupl.Itemlndex of 0:. RvSysteral.DefaultDest := rdPreview; 1: RvSysteral.DefaultDest := rdPrinter; 2: begin RvSysteral.DefaultDest := rdFile; RvSysteral.SysteraSetups : = RvSysteml.SystemSetups [ssAllowCopies, ssAllowCcllate, ssAllowDuplex, ssAllowDestPreview, ssAllowDestPrinter); RvSysteml.TitleSetup ;= 'Укажите имя файла'; end; 3 r RvSysteml.SystemSetups := [ssAllowSetup, ssAllowCopies, ssAllowCollate, ssAllowDuplex, ssAllowDestPreview, ssAllowDestPrinter, ssAllowDestFile, ssAllowPrinterSetup]; end; end;
Рассмотрим приведенный код, Первый оператор проверяет индекс группы радиокнопок RadioGroupl. Если индекс меньше 2, значит задан режим просмотра или печати на принтер. В этих случаях можно избежать появления диалога рис. 5.24. Поэтому из множества SystemSetups удаляется опция ssAllowSetup. Если индекс равен 3 (кнопка Диалог), то появление диалога необходимо. Нужен диалог, правда, в ограниченном виде, и при индексе 2 (кнопка В файл), гак как пользователю надо указать имя и тип файла. Поэтому при индексе больше или равном 2 опция ssAllowSetup заносится в множество SystemSetups. Следующий оператор заносит в заголовок окна рис. 5.24 текст «Выберите требуемую операцию». Далее следует структура case. При индексе группы радиокнопок 0 и 1 просто изменяется операция по умолчанию — свойство DefaultDest. При индексе 2, кроме задания операции по умолчанию, из свойства SystemSetups удаляются опции ssAllowCopies, ssAllowCollate, ssAllowDuplex, ssAllowDestPreview, ssAllowDestPrinter. Таким образом, в окне рис. 5.24 остаются доступными только элементы, относящиеся к записи в файл. Соответственно изменяется заголовок окна: «Укажите имя файла ». А при индексе 3 восстанавливаются: все опции свойства SystemSetups, включая опцию ssAllowSetup, обеспечивающую появление диалогового окна рис. 5.24. Для добавления в отчеты колонтитулов, задаются обработчики событий ОпPrintHeader и OnPriniFooter компонента RvSysteml. Их коды уже анализировались в разд. 5.6,3.2. Так что ниже они приводятся без дополнительных пояснений: procedure TFotml.RvSystemlPrintFooter(Sender: T O b j e c t ) ; begin w i t h Sender as TBaseReport do begin SetFont('Arial C Y R ' , 1 4 ) ; PrintFooter('CTp. ' +• IntToStr(CurrentPage) ,pjLeft] ; SetPen (clBlack, psSolid , 1, pmCopy) ,LineHeiglltMethod := IhmFont; MoveTo(0, LirieTop) ; L i n e T o ( P a g e W i d t h , LineTopI; end; end; procedure T F o r m l . R v S y s t e m l P r i n t H e a d e r ( S e n d e r : begin
TObject);
Разработка графического интерфейса пользователя
379
with Sender as TBaseReport do begin SetFontCSrial C Y R ' , 1 4 ) ; PrintHeader('Мой первый отчет R a v e ' , p j L e f t ) ; PrintHeader('Дата Ч-DateToStr(Date),pjRight); S e t P e n ( c l B l a c k , psSolid , 1, pmCopy); MoveTo (0, LineBottom); LineTo (PageWidth, LineBottora]; end; end;
Сохраыите усовершенствованное приложение и проверьте его в работе. Поэкспериментируйте с различными опциями, что почувствовать их влияние на печать и предварительный просмотр отчетов. Последний пример, который мы рассмотрим, связан с программным формированием тела отчета. Добавьте на форму компоненты ButtonS с надписью «Программируемый», и Мешо2, в котором пользователь сможет написать включаемый в отчет текст (см. в нижней части рис. 5.25). В обработчик щелчка на кнопке Программируемый надо вставить единственный оператор: RvSysteral.Execute;
А для формирования отчета надо написать обработчик события OnPrint компонента RvSysteml. Этот обработчик может выглядеть, например, так: procedure T F o r m l . R v S y s t e m l P r i n t ( S e n d e r : T O b j e c t ) ; var i: integer; Y B e g i n : double; begin w i t h Sender as TBaseReport do begin SetFont('Arial C Y R ' , 2 6 ) ; LineNum := 5; // Печать текста окна Меп\о2 for i := 0 to M e m o 2 . L i n e s . C o u n t do begin P r i n t J u s t i f y ( M e m o 2 . L i n e s [ i J , 0 . 5 , pj-Left, 0 , O i ; NewLine; // Если страница кончилась, переход на новую if YPos > SectionBottom then NewPage; end; NewLine; P r i n t l n ( ' 3 T o печать в 1 к о л о н к и : ' ) ; NewLine; // Печать четырех колонок SetColumns. Create; // задание свойств кисти основного я вспомогательного цветов Imagel.Canvas.Brush.Color := clBlack; Image2.Canvas.Brush.Color := clWhite; // заполнение окон основного и вспомогательного цветов with Imagel.Canvas do FillRect(Rect(0,0,Width,Height)]; with Image2.Canvas do FillRect(Rect(0,0,Width,Height)]; // задание ширины элемента палитры цветов HW:-Image4.Width div 10; // закраска элементов палитры цветов with 1таде4.Canvas do for i:=l to 10 do begin case i of 1: Brush.Color clBlack; 2: Brush.Color clAqua; 3: Brush.Color clBlue; clFuchsia; 4: Brush.Color clGreen; 5: Brush.Color 6: Brush.Color clLime; 7i Brush.Color clMaroon; 8: Brush.Color clRed; 9: Brush.Color clYellow; 10:Brush.Color clWhite; endi Rectangle ( (i-D*HW, 0, i'HW, Height! ; end; // рисование креста на холсте — только для тестирования with Image3 do begin Canvas.MoveTo{0,0); Canvas.LineTofWidth,Height); Canvas.MoveTo(OrHeight); Canvas.LineTolWidth,0|; end; BitMap.Assign(Image3.Picture); end;
Обработчик длинный, но смысл его достаточно прост.
Графика и мультимедиа
415
Сначала создается объект BitMap, в котором будет храниться первоначальное изображение. Затем задаются свойства кисти окон основного (Imagel) и вспомогательного (Image2) цветов: черный и белый. Окна заливаются соответствующим цветом с помощью функции FillRect. После этого формируется палитра цветов: для каждого элемента палитры задается свой цвет и элемент заполняется этим цветом с помощью функции Rectangle. Затем на холсте ImageS рисуется крест (рис. 6.11 а). Он имеет чисто демонстрационный характер, чтобы можно было протестировать программу на простом изображении. В реальной программе этот рисунок не нужен и, если хотите, можно эти операторы не писать. В конце нарисованное на канве сохраняется в объекте BitMap методом Assign. 11. В обработчик события формы OnDestroy запишите оператор BitMap.Free; который освобождает память при закрытии приложения. 12. Для подраздела меню Открыть в обработчик включите операторы: if OpenPictureDialogl.Execute then begin ImageS.Picture.LoadFromFile(OpenPictureDialogl.FileName) ; BitMap. As sign (Irnage3. Picture) ; end;
Эти операторы загружают в компонент IraagcS файл изображения, который пользователь выбирает в диалоге, и запоминают изображение в BitMap. 13. Для подраздела меню Отменить в обработчик включите оператор: Image3.Picture.Assign[BitMap);
Этот оператор восстанавливает на холсте изображение, сохраненное в BitMap. 14. В обработчик события OnClick кнопок SBBrush и SBColor запишите оператор
if (Sender as TSpeedButton).Down
then B i t M a p . A s s i g n ( l m a g e 3 . P i c t u r e ! ; Этот оператор запоминает текущий вид изображения перед началом работы с очередным инструментом. 15. В обработчик события OnMouseDown компонентов ImageS и Image4 вставьте код: if(Sender - Image4) or SBColor.Down then // режим установки основного и вспомогательного цветов begin if(Button-mbLeft) then w i t h I m a g e l . C a n v a s do begin // установка основного цвета Brush.Color: = (Sender as T l m a g e ) . C a n v a s . P i x e l s [ X , Y ] ; FillRect[Rect10,0,Width,Height)) ; end else with Image2.Canvas do begin //установка вспомогательного цвета Brush.Color := (Sender as T I m a g e ) . C a n v a s . P i x e l s [ X , 1 ] ; FillRect (Rect , 0) ; Image3.Canvas.CopyRect(Rect(0,0,BMCopy.Width,BMCopy.Height), BMCopy.Canvas,Rect(0,0,BMCopy.Width,BMCopy.Height)]; finally BMCopy.Free; end; except On EInvalidGraphic do ShowMessage('Ошибочный формат графики'); end; end;
Чтение из Clipboard осуществляется методом LoadFromClipBoardFormat. Предусмотрен перехват исключения EInvalidGraphic, если в Clipboard содержится не битовая матрица. Попробуйте реализовать описанный графический редактор или разберитесь подробнее в его работе, посмотрев его код на прилагаемом к книге диске. Попробуйте также усовершенствовать редактор, добавив, в него, например, выбор ширины линий, рисование эллипсов и т.д.
6.1.7 События OnPaint До сих пор мы рисовали в основном на канве компонента Image. Но канву имеет не только Image. Ее имеют и многие другие компоненты, например, формы. Все, что ранее вы рисовали на канве компонентов типа TImage, вы могли бы рисовать и на форме. Кроме того, есть специальный компонент PaintBox, имеющий канву и позволяющий рисовать на ней. Рисование на PaintBox вместо формы не имеет никаких преимуществ, кроме, может быть, некоторого облегчения в расположении одного или нескольких рисунков в площади окна. При рисовании на канве формы или PaintBox надо учитывать некоторые особенности. Давайте сначала выясним на собственном опыте, о чем идет речь. Откройте новое приложение, перенесите на него диалог OpenPictureDialog (или OpenDialog), кнопку и в обработчик щелчка на ней вставьте операторы: procedure TForml-ButtonlClick(Sender: TObject); var BitMap:TBitMap; begin if OpenPictureDialogl.Execute then begin BitMap := TBitMap.Create; try BitMap.LoadFromFile(OpenPictureDialogl.E'ileNarae); Canvas.Draw(0,0,BitMap); finally BitMap-Free; end; end; end;
Эти операторы обеспечивают загрузку выбранного пользователем графического файла и отображение изображения непосредственно на канве формы (поскольку оператор Canvas.Draw относится к канве формы, можно было бы это уточнить, написав Forml.Canvas.Draw). Запустите приложение, выберите файл и вы увидите что-нибудь вроде представленного на рис. 6.14 а. А теперь, не закрывая своего
426
Глава б
приложения, вернитесь в Delphi и, ничего там не делая, опять перейдите в свое выполняющееся приложение. Если окно Редактора Кода, выступившее на первый план при вашем переходе в Delphi, целиком перекрыло окно вашего приложения, то вернувшись в него вы увидите, что картинка в окне исчезла. Если же вы опять загрузите в него картинку и сдвинете окно приложения так, чтобы окно Редактора Кода не могло целиком его закрыть, то, повторив эксперимент с переходом в Delphi и обратно вы, возможно, увидите результат, подобный представленному на рис. 6.14 б. Рис. 6.14 Демонстрация исходного изображения (а) и его стирания (б) при перекрытии его другим окном
Вы видите, что если окно какого-то другого приложения перекрывает на время окно вашего приложения, то изображение, нарисованное на канве формы, портится. В компоненте Image этого не происходило, поскольку в классе TImage уже предусмотрены все необходимые действия, осуществляющие перерисовку испорченного изображения. А при рисовании на канве формы или других оконных компонентов эти меры должен принимать сам разработчик приложения. Если окно было перекрыто и изображение испортилось, операционная система сообщает приложению, что в окружении что-то изменилось и что приложение должно предпринять соответствующие действия. Как только требуется обновление окна, для него генерируется событие OnPaint. В обработчике этого события (в нашем случае события формы) нужно перерисовать изображение. Перерисовка может производиться разными способами в зависимости от приложения. В нашем примере можно было бы вынести объявление переменной BitMap за пределы приведенной выше процедуры, т.е. сделать эту переменную глобальной, и перенести оператор BitMap.Free из этой процедуры в обработчик события формы OnDestroy. Тогда в течение всего времени выполнения вашего приложения вы будете иметь копию картинки в компоненте BitMap и вам достаточно ввести в обработчик события OnPaint формы всего один оператор: Canvas.Draw(О,О,BitMap);
Сделайте это, и увидите, что изображение на форме не портится при любых перекрытиях окон. Сделанный вами обработчик перерисовывает все изображение, хотя, может быть, испорчена только часть его. При больших изображениях это может существенно замедлять перерисовку и вызывать неприятные зрительные эффекты. Перерисовку можно существенно ускорить, если перерисовывать только испорченную область канвы. У канвы есть свойство ClipRect типа TRect, которое в момент обработки события OnPaint указывает область, которая подлежит перерисовке. Поэтому более экономным будет обработчик: if BitMap nil then Canvas.CopyRect(Canvas.ClipRect,BitMap.Canvas,Canvas.ClipRect];
Оператор if используется в нем, чтобы избежать ошибочного обращения к BitMap, пока графический файл еще не загружался и объект BitMap не создан. А перерисовывается только область ClipRect, которая испорчена.
Графика и мультимедиа
427
6.2 Мультимедиа и анимация *4 Л *Ч 6 .2.1 Звук
6.2.1.1 Типы звуковых и мультимедиа файлов Так же, как существует множество рассмотренных нами форматов графических файлов, существует немало файлов звуковых и мультимедиа. Мы коротко охарактеризуем обе эти группы файлов в рамках данного раздела, так как файлы мультимедиа часто содержат звуковую дорожку, и было бы не очень правомерно, говоря о звуке, не упомянуть звук в мультимедиа. Наиболее простым звуковым файлом является волновой файл .шаи. В нем записано цифровое представление информации о волновой форме электрического сигнала, соответствующего каждому звуку. Волновой файл *не знает» вообще ничего о том, что такое звук и что он означает; поэтому для хранения звукового клипа приходится запоминать массу информации. Другим часто применяемым типом файлов-носителей являются файлы цифрового интерфейса музыкальных инструментов (MIDI). Файлы .mldi используются для хранения музыкальных фрагментов. В этих файлах звук хранится в виде данных о том, на каких инструментах исполняются определенные ноты и как долго они звучат. Одним из главных преимуществ MIDI является то, что файлы получаются сравнительно небольшими. Файлы MIDI относятся к волновым файлам примерно так же, как метафайлы — к файлам .Ьтр. В обоих случаях файлы первого типа «понимают», какие данные они представляют, а файлы второго типа хранят сырые данные, просто посылаемые на выходное устройство. Волновые и MIDI файлы могут хранить только звук или музыку. Для хранения видео информации разработан ряд форматов. Отметим среди них файлы AVI и MPEG. Большинство видеофайлов поддерживают также хранение звуковой дорожки, так что звук воспроизводится синхронно с картинкой. Что собой представляет видеофайл, и как он работает? Человеческий мозг интерпретирует быструю последовательность изображений, незначительно отличающихся друг от друга, как движение. Каждое из этих изображений называется кадром. Каждый следующий кадр несколько отличен от предыдущего. Чтобы мозг воспринимал смену кадров как плавное движение, желательно воспроизводить около 30 кадров в секунду. Более высокая частота не приводит к заметному росту качества, а более низкая производит впечатление мерцания экрана. Если бы каждый кадр хранился в файле в виде битовой матрицы экрана (а это несколько сотен килобайт), то потребовался бы огромный объем дисковой памяти. При такой простой схеме хранения на компакт-диск, например, можно было бы записать всего 72 секунды видеофильма. Реально для хранения видеофильмов используется техника сжатия видеоданных. Если не углубляться в сложную математику методов сжатия, то суть сводится к следующему. Когда захватывается очередной кадр, аппаратура или программа сжатия задается вопросом: «Можно ли сохранить этот кадр более компактно, если записать только то, что в нем отличается от предыдущего, или нужно сохранить картинку целиком?» Чаще всего выгодно сохранять только изменившиеся части сцены. Но в определенных обстоятельствах, например, при переключении на другую камеру, накладные расходы описания изменений заняли бы больше места, чем непосредственное сохранение кадра. В методах хранения мультимедиа достигнуты большие успехи. Сейчас можно записать целый полнометражный кинофильм на стандартном CD-ROM.
Глава 6
428
6.2.1.2 Процедуры воспроизведения звуков Веер, MessageBeep и PlaySound Наиболее простой процедурой, управляющей звуком, является процедура Веер. Она не имеет параметров и воспроизводит стандартный звуковой сигнал, установленный в Windows, если компьютер имеет звуковую карту и стандартный сигнал задан (он устанавливается в программе Windows «Панель управления» после щелчка на пиктограмме Звук). Если звуковой карты нет или стандартный сигнал не установлен, звук воспроизводится через динамик компьютера просто в виде короткого щелчка. Откройте новое приложение, введите в него кнопку, в обработчике щелчка которой напишите одно слово: Веер; Можете запустить приложение, щелкнуть на кнопке и прослушать стандартный звук Windows или просто щелчок, если стандартный звук не установлен. Более серьезной процедурой является MessageBeep. Она определена как function MessageBeep(uType:word): boolean; Параметр uType указывает воспроизводимый звук как идентификатор раздела [sounds] реестра, в котором записаны звуки, сопровождающие те или иные события в Windows. С помощью приложения Звук в «Контрольной панели» пользователь может удалить или установит соответствующие звуки. Параметр uType может иметь следующие значения: Значение MB ICONASTERISK
Звук
MB ICONEXCLAMATION
SystemExclamation — восклицание
MB ICONHAND
SystemHand — • критическая ошибка
MB_ICONQUEST1ON
SystemQuestion — вопрос
MB OK
SystemDefault — стандартный звук
System Asterisk — звездочка
После запроса звука функция MessageBeep возвращает управление вызвавшей функции и воспроизводит звук асинхронно. Во время воспроизведения приложение может продолжать выполняться. Если невозможно воспроизвести указанный в функции звук, делается попытка воспроизвести стандартный системный звук, установленный по умолчанию. Если и это невозможно, то воспроизводится стандартный сигнал через динамик. При успешном выполнении возвращается ненулевое значение. При аварийном завершении возвращается нуль. Можете в своем тестовом приложении ввести еще одну кнопку и написать для нее обработчик; MessageBeep (МВ_ОК) ,-
Вы услышите тот же стандартный звук Windows, что и при выполнении процедуры Веер. Или услышите тот же тихий щелчок, если стандартный звук не установлен. Попробуйте установить различные звуки с помощью «Панели управления» и проверить MessageBeep при различных значениях ее параметра. А теперь давайте займемся более серьезной функцией PlaySound, которая позволяет воспроизводить не только звуки событий Windows, но и любые волновые файлы. Это функция API Windows, параметры которой описаны в модуле mmsystem. Поэтому для использования этой функции в вашем приложении необходимо
Графика и мультимедиа
429
включить в его оператор uses ссылку на mmsystem, поскольку автоматически эту ссылку Delphi не включает. Функция PlaySound определена следующим образом: function
PlaySound
484
Глава 7
разными компаниями и на разных языках. Ключом к успеху является модульность этих компонентов. Они могут покупаться, модернизироваться или заменяться поодиночке или группами, причем это никак не влияет на работу целого. СОМ определяет двоичный интерфейс, полностью независимый от языка программирования, использованного при реализации компонента. Компонент, написанный в соответствии со спецификациями двоичного интерфейса СОМ, может вступать во взаимодействие с другим компонентом, не зная в действительности ничего о реализации последнего. Новая особенность, появившаяся в OLE 2.0, — это автоматизация OLE, которая обеспечивает доступ к объектам приложения и манипуляцию с ними извне. Такие объекты, предоставленные (экспонированные) для внешнего пользования, называются автоматными объектами OLE. Типы объектов, которые могут быть экспонированы, так же разнообразны, как и сами приложения Windows. Текстовый процессор мог бы экспонировать в качестве автоматного объекта документ, абзац или предложение. Электронная таблица могла бы экспонировать таблицу, диаграмму, ячейку или группу ячеек. Главное отличие автоматных объектов от обычных объектов OLE состоит в том, что автоматные объекты доступны только программно, они создаются и используются при помощи программного кода и, следовательно, в принципе временны. Они не могут быть внедрены или связаны. Они могут существовать только в течение времени выполнения ваших программ и не видны непосредственно конечному пользователю. Существуют два типа автоматных серверов: внутри-процесс а и вне-процесса (их еще называют локальными). Delphi поддерживает оба типа серверов. Сервер внутри процесса является DLL (динамически связываемой библиотекой), которая экспортирует автоматные объекты. Поскольку автоматные объекты поставляются из DLL, а не из других приложений, они являются частью приложения клиента. Это избавляет от больших накладных расходов, сопутствующих каждому вызову автоматного сервера. Сервер внЪ-процесса — это автономный исполняемый файл, экспортирующий автоматные объекты. Примером этого мог бы быть Microsoft Word. Word имеет некоторое количество объектов, которые он экспонирует в качестве автоматных. Позже будет приведен пример использования Word как автоматного сервера. Автоматизации OLE, чтобы функционировать правильно, опирается на три различных источника информации: классы, документы и элементы. Класс OLE определяет приложение сервера, которое создает автоматный объект. Например, если класс объекта — документ .doc, то он был создан с помощью Word. Документы OLE ссылаются на исходный файл, содержащий данные объекта OLE. Любой документ OLE должен быть связанным документом (т.е. не внедренным), потому что данные связанных документов должны храниться в файле. Элемент OLE определяет, какая часть документа содержит данные, подлежащие связыванию или внедрению в другой документ. Это способ, с помощью которого вы можете вместо целого документа связывать с другими документами лишь небольшую часть его данных. Элементы OLE позволяют сократить размеры объектов. Теперь рассмотрим основные приемы использования OLE в Delphi. На странице System библиотеки визуальных компонентов имеется контейнер OLE — основной компонент, обеспечивающий внедрение и связывание. Начнем его изучения с примера. Разместите на форме контейнер OleContainer, компонент главного меню Main Menu, диалоги OpenDialog и SaveDialog. Можно также разместить диспетчер действий, инструментальную панель и на ней быстрые кнопки, дублирующие основные команды меню. Впрочем, можете этими изысками не заниматься, но меню, как мы увидим ниже, должно быть обязательно. -
Процессы, потоки, распределенные приложения
485
Панель, если вы ее ввели, выравнивается по верху формы (AIign= alTop), а контейнер должен занимать всю оставшуюся площадь формы (Aligii= alClient). Пример такого приложения, содержащийся на прилагаемом к книге диске, приведен на рис. 7.6, которые мы рассмотрим позднее. В MainMenu введите меню Фойл и в нем разделы Открыть файл, Сохранить фойл, Выход. В дальнейшем тексте предполагается, что первые два из этих разделов связаны с действиями AOpenFile и ASaveFile. Введите также меню Объект и в нем разделы Новый, Открыть, Сохранить, Закрыть. Установите в диалогах в свойстве Filter: «объекты ОЬЕ|*.о1е|файлы Excel (*.х1в)|*.х!н|файлы документов (*.doc,*.txt,*.rtf)l*.doc;*.txt;*.rtf|Bce файлы|*.*». Можете добавить еще разделы, если хотите, например, работать не только с документами Excel и Word, но еще, например, с графическими файлами. Теперь перейдем собственно к программированию. В разделе implementation введите переменную fname, в которой будет храниться имя файла: var fname: s t r i n g = ' ' ;
Процедура, соответствующая разделу меню Открыть файл, может иметь вид: with OpenDialogl do begin FilterIndex := 2; if Execute then begin if OleCoritainerl .Modified then case MessageDlg('Документ был изменен. Сохранить его?', mtConfirmation, [mbYes, rabNo, rabCancel], 0) of m r Y e s : ASaveFileExecute(Self); mrCancel: exit end; OleContainerl.CreateObjectFromFile(FileName, f a l s e ) ; fname := FileName; O l e C o n t a i n e r l . M o d i f i e d ;= false; end; end;
Первый оператор этого кода просто подставляет в диалог OpenDialogl к качестве текущего второй фильтр, который, как видно из приведенного ранее его текста, является фильтром документов Excel. Далее вызывается метод диалога Execute. Если пользователь произвел в диалоге выбор файла, то, прежде чем загрузить новый файл, проверяется, не был ли ранее загружен и изменен объект в контейнере OleContainerl. Это проверяется по свойству контейнера Modified. Если был изменен, то пользователю задается вопрос, надо ли этот объект сохранять. При положительном ответе вызывается процедура ASaveFileExecute, которую мы рассмотрим далее я которая обеспечивает сохранение файла. После всех этих проверок выбранный пользователем файл загружается в контейнер OleContainerl методом CreateObjectFromFile. Этот метод создает объект OLE из файла, указанного в его первом параметре. Второй параметр определяет, должно ли в контейнере отображаться содержимое файла (при значении false), или только его пиктограмма (при значении true). Последние операторы кода запоминают имя открытого файла в переменной fname и сбрасывают в false признак модификации объекта Modified. Процедура, соответствующая разделу меню Сохранить файл, может иметь вид: SaveDialogl.Filterlndex := 2; SaveDialogl.DefaultExt :- 'doc'; if SaveDialogl.Execute then fname := SaveDialogl.FileName else exit; OleContainerl.SaveAsDocurnent(fname);
486
Глава 7
В приведенном коде, кроме очевидных операций с диалогом SaveDialogl и переменной fname, имеется только один последний оператор, который сохраняет методом SaveAsDocument объект OLE, содержащийся в контейнере, как документ. Единственный параметр этого метода определяет файл, в котором сохраняется документ. Хотя наш пример еще не завершен, можете сохранить его и выполнить. Нажмите кнопку Открыть файл или выберите соответствующий раздел меню. В открывшемся диалоге выберите какой-то файл, например, файл документа Excel. Вы увидите (рис. 7.6), что в контейнере вашего приложений откроется этот файл. Таким образом, вы создали в своем приложении внедренный (но не связанный) объект OLE. Это может быть объект любого документа — текстового, графического, видео и т.п. Рис. 7.6
7 ' Вне др eiv -f и евязывлнкр
Приложение с загруженным а него файлом документа Excel Это тзблчцэ Товар цснд зд шт. иолич. 120 4ЕЕ 240 160
3600 36000 -W71S
Сделайте двойной щелчок, чтобы редактировать объект
Если вы сделаете двойной щелчок на контейнере, вызовется программа, обслуживающая данный файл. В этой программе вы сможете просматривать и редактировать объект. Так, если загружен документ Excel, то при двойном щелчке ваше приложение чудесным образом преобразится (рис. 7.7). В него встроится Excel вместе со своими инструментальными панелями и меню. Вы можете что-то изменять в вашем объекте. Например, на рисунках 7.6 и 7.7 приведен пример, когда объект создается из некоторой таблицы Excel. Создав этот объект, вы можете с помощью Excel что-то в нем изменить, ввести в таблицу новую информацию. И сохранить его. Рис. 7.7 Приложение с открытой в нем программой Excel
ПЕмЪкшкииецсшэыияе *.JBUJ Объект Грэека ЁИД Вставка Фордат СЕРВИС Дате* £праек4
D15
_4_ А_! _....В_ ._. j С 1 , Это таблица Excel 2_ Гонлр цен.ч зл шт. колич. суыцд 123 120 30 456 240 150 36000 JIVI'J
"и\1Ыст1 /rWr'z'/листа /
ГЕ:
Процессы, потоки, распределенные приложения
487
Но когда вы захотите сохранить документ, вы увидите, что Excel встроился в ваше приложение все-таки не полностью. Не все быстрые кнопки и не все разделы меню вам доступна. А главное — меню Файл программы Excel заменено соответствующими меню вашего приложения Файл и Обьект. Именно поэтому и надо было обязательно вводить меню Файл в приложение. Иначе вы не смогли бы сохранить документ. Описанные разделы меню Файл оперируют с объектом OLE как с документом типа, определяемого типом файла, из которого он создается. Но можно оперировать с ним и как с объектом OLE. Введем в созданное нами меню Объект разделы Новый, Открыть, Сохранить, Сохранить как. Закрыть. Раздел Новый может обрабатываться следующим кодом: if O L E C o n t a i n e r l . I n s e r t O b j e c t D i a l o g than begin fname := ' ' ; OleContainerl.Modified := f a l s e ; OleContainerl.DoVerb(ovPrimary); end;
В этом коде вызывается метод InsertObjectDialog, который в свою очередь вызывает стандартное окно Windows Insert Object (Вставка объекта). В этом окне, которое мы рассмотрим несколько позднее при описании работы с приложением, пользователь может указать тип вставляемого объекта. Если пользователь выбрал тип объекта, функция InsertObjectDialog загружает объект в контейнер и возвращает true. В этом случае приведенный код очищает имя файла в fname и признак модификации объекта в контейнере Modified. В заключение вызывается метод DoVerb, который выполняет одну из предусмотренных операций. Обычно это ovShow — показать, или ovPrimary — активизировать. Метод DoVerb применен для того, чтобы сразу активизировать загруженный документ. Процедура, соответствующая разделам меню Сохранить и Сохранить как может иметь вид: if ( f n a m e = ' ' ) o r ( S e n d e r = ASaveAsI then begin SaveDialogl.Filterlndex := 1,if SaveDialogl.Execute then fname := S a v e D i a l o g l . F i l e N a m e else exit; end; OleContainerl.SaveToFile(Copy(fname,0,Pos('.',fname))+'ole'];
В начале кода производится проверка, отсутствует ли в переменной fname имя файла и не является ли источником события действие ASaveAs (Сохранить как). В обоих случаях вызывается диалог сохранения файла SaveDialogl, в котором пользователь может задать имя файла. Последний оператор методом SaveToFile сохраняет объект в файле. Имя сохраняемого файла — единственный параметр метода, который формируется из имени файла, к которому добавляется расширение .ole. Файл, сохраненный методом SaveToFile, — это двоичный файл, содержащий сам объект OLE, а не документ, на основе которого создан объект. Его нельзя открыть программой, связанной с документом. Этот файл можно только в дальнейшем загрузить в контейнер OLE. Соответствующий этой загрузке код раздела меню Открыть может иметь вид: with OpenDialoql tio begin Filterlnd&x :- 1; if Execute then begin if O l e C o n t a i n e r l . M o d i f i e d then
Глава 7
488
case MessageDlg('Документ был изменен. Сохранить его?', mtConfirrnation, [mbYes, mbKo, mbCancel] , 0) of mtYes: ASaveExecute(Selff; mrCancel: exit end; OleContainerl.LoadFromFile(FileNarae); fname := FileNarae; O l e C o n t a i n e r l . M o d i f i e d := false; OleContainerl.DoVerb(ovPriroaty); end; end;
Данный код отличается от приведенного ранее кода раздела Открыть файл прежде всего методом создания объекта LoadFromFile. Этот метод открывает файл объекта OLE и загружает его в контейнер. Кроме того, здесь применен описанный выше метод DoVerb, который активизирует загруженный объект. Код последнего еще не реализованного раздела меню Закрыть может иметь вид: if OleContainerl.Modified then case MessageDlg('Документ был изменен. Сохранить его?', mtConfirmation, [mbYes, mbNo, mbCancel], 0) of m r Y e s : ASaveExecute (Self) ,mrCancel: exit end; OleContainerl.DestroyObject; OleContainerl.Modified :- f a l s e ;
Здесь используется метод DestroyObject, который уничтожает объект OLE в контейнере (предварительно делается запрос о его сохранении). Теперь посмотрим, как работает наше усовершенствованное приложение. Запустите его на выполнение и выполните команду Новый. Перед вами откроется окно Вставка объекта (Insert Objed), представленное на рис. 7.8, в котором вы можете указать тип вставляемого объекта, например, Лист Microsoft Excel. После этого в вашем приложении возникнет панель программы Excel вместе с ее меню (см. приведенный ранее рис. 7.7), и вы сможете создать новый документ Excel. Если бы мы не применили в обработчике раздела Новый метод DoVerb, то в первый момент панель программы Excel в программе не возникла бы. Но двойной щелчок на контейнере активизировал бы объект, и результат был бы тот же самый.
-Uai
Рис. 7.8 Окно вставки объекта OLE вставка нового объекта Набор копана MIDI Презентация Microsofl PowftPomi
Добавление в "Лит Miaosoft ЕмяГ.
нового объекта типа
Если вы в окне рис. 7.8 выберете радиокнопку Создать из файла (Create from File), вы сможете создать объект OLE на основе имеющегося файла. При этом диалоговое окно изменит свой вид (см. рис 7.9). Выберите какой-нибудь из имеющихся у вас файлов типа ,xls, подготовленных ранее программой Excel. Обратите внимание на очень важный индикатор Связь (Link). Пока не устанавливайте этот индикатор, а просто нажмите ОК. Результат будет тот же, что и в предыдущем случае. Только объект будет не пустым, он будет содержать файл, на основе которого создан.
489
Процессы, потоки, распределенные приложения Рис. 7.9
1спнш овъвпга
Окно вставки объекта OLE вставка объекта из файла
Лкгг Mcrosoil Е-с?1 47
Соодль из Файла
Отмена
|F\DeWi7\OLEMe« then key := #0; if (not FEnableLet) and not (key in ['0'..'9']> then key :- #0; if fkey 10) then FModified := true; inherited KeyPress(Key); end; procedure TEditLetNum.Clear; begin // Вызов обработчика пользователя if assigned(FClear) then OnClear(Self); // Вызов родительского метода inherited clear; FModified := false; er.c; end.
Можете запускать тестовое приложение и проверять работу компонента во всех мыслимых ситуациях. Мы рассмотрели простейший вариант задания события типа TN о t if у Event. Попробуем несколько усложнить это событие. Пусть мы будем передавать в это событие параметр CanClear, который по умолчанию равен true, что будет означать разрешение очистить текст окна редактирования. Но предоставим пользователю возможность в своем обработчике события задать этому параметру значение false, что будет означать отказ от очистки. В данном случае нам надо объявить новый тип события: TClearFunc = procedure (Sender:TObject; var CanClear: boolean) of object; Это объявление типа должно помещаться в разделе type, например, перед предложением TEditLetNum = class(TEdit) Тогда в объявлениях свойства в его поля надо ссылаться на этот тип, следующим образом, изменив соответствующие операторы: FClear: TClearFunc; // Поле события property OnClear:TClearFunc read FClear write FClear;// событие Осталось изменить метод Clear так, чтобы в нем учитывался новый параметр CanClear: procedure TEditLetNum.Clear; var CanClearrboolean; begin CanClear := true; // Вызов обработчика пользователя if assigned(FClear) then OnClear(Self.CanClear); if not CanClear then exit; // Выход, если CanClear=false // Вызов родительского метода inherited clear; FModified := false; end;
Откомпилируйте новый вариант компонента. Теперь надо изменить тестирующее приложение. Возможный вид нового тестирующего приложения представлен на рис. 8.8. В нем добавлено три индикатора типа TCheckBox, которые указывают допустимость ввода цифр, букв и допустимость очистки (назовем их CBNum, CBLet и СВ Clear соответственно). .
Различные виды проектов Delphi
533
Рис. 8.8 Тестовое приложение вашего нового компонента
.
Mllia^-ilUCT*;
,,_,~; х.
Г» р йиары
Qea
Р БdBin*igk4»»Edta| ч> "юм! *E_il±
SI, H #!»«
эТ-7"-
Ке?7
w 1
"*1
eed
if
*
»«Bi" «i (ь« t f f L B i i .
-! Э Ss I 3 SsCnabteHuWalue B"*H
' 33 a?al*IADr-*r ITcrtponert] 1
'• H M«SeJ . Botleoi f W Eiffel*
Bmtii
ПГТ
if ' ll
-I--*.-,
Aty
*
[Pot
FEbftbll
l(
i"*y 1
t h c j i • = -/
,- SO; then m o d i f i e d :
Ch.f 4* *Qh
••d;
Uf-.З
end
:i n i : ;.: - sleLtt) and n*t Ifc
u (hey о Ь I|
ZJ
(not ГЕщ» DleNum) then k t y - во;
-1
Ля:Я
SS«
I
^J
542
Глава 8
удалены, если удалится сам элемент. Но именно поэтому вы не можете отредактировать оператор inherited, пока не станете его владельцем. Стать владельцем очень просто: надо щелкнуть правой кнопкой мыши на вершине inherited и выбрать в контекстном меню раздел Reduce to user owned. После этого вы сможете изменить оператор в правом окне. Теперь нам надо перед оператором inherited добавить в тело процедуры код (см. разд. 8.3.2.3): if if if
(not FEnableNuml and (key i n [ ' O ' - . ' S ' ] ] then key := #0; (not F E n a b l e L e t ) and not (key in [ ' 0 ' . . ' 9 ' ] > then key := S O ; (key о К О ) then FModified ;= true,-
Мозкно, конечно, просто вставить его в правом окне перед оператором inherited. Но более общий подход заключается в следующем. Щелкните на кнопке Add section (третья слева в верхнем ряду инструментальной панели). Появится вершина, соответствующая новому разделу кода. Выделив ее, вы можете в правом окне написать соответствующий код (этот момент показан на рис. 8.14). Далее вы можете с помощью мыши перетащить вершину этого раздела кода выше вершины inherited, что требуется в нашем примере. Аналогичным образом вы можете ввести несколько разделов кода. Размещение кода в нескольких разделах имеет только один смысл: эти разделы как единое целое можно перемещать в коде, копировать, удалять и т.п. Таким образом, в ModelMaker вы можете полностью создавать и редактировать как объявление, так и реализацию класса. В какой-то степени ModelMaker — альтернатива ИСР Delphi при разработке классов. Так что вам решать, какую часть работы делать в ModelMaker, а какую — в ИСР Delphi. Мне, все-таки, кажется, что разработку объявления класса удобно проводить в ModelMaker. а написание и отладку кода лучше делать в ИСР Delphi. Мы рассмотрели один из механизмов разработки класса в ModelMaker. Имеется еще и другой механизм — разработка с помощью диаграмм. Но оставим это для самостоятельного изучения тем, кому больше нравится визуальная разработка. Невозможно останавливаться на всем инструментарии, заложенном в ModelMaker. Это заняло бы целую главу, а объем книги, увы, ограничен.
8.5 Динамически присоединяемые библиотеки DLL Динамически присоединяемая библиотека DLL -- это прекрасная возможность повторного использования разработанных вами кодов. DLL — это специального вида исполняемый файл, используемый для хранения функций и ресурсов отдельно от исполняемого файла. Обычно, когда вы пишете программу и создаете функции, ресурсы и т.п., все они компонуются в ваш исполняемый файл. Это называется статической сборкой. Когда же вы используете стандартные программы из DLL, то это называется динамическим присоединением, поскольку программы соединяются с библиотекой в процессе выполнения. DLL делает полезные, часто используемые функции доступными сразу для многих приложений одновременно, хотя работа ведется только с одной ее копией на диске и в памяти. Обычно DLL не загружается в память, пока это не станет необходимым, но, будучи однажды загружена, она делает свои функции и ресурсы доступными для любой программы. Предположим, вы разработали различные полезные процедуры и функции и используете их в ряде своих приложений. Конечно, вы можете просто скопировать эти процедуры и функции в каждый ваш проект. Но тогда, если несколько ваших приложений работает на компьютере одновременно, то копии ваших процедур загружены в память в выполняемом модуле каждого приложения и память, таким образом, расходуется неэффективно. Да и затраты пространства на дисках также избыточны, поскольку эти процедуры в составе выполняемых модулей так-
Различные ^иды проектов Delphi
543
же тиражируются. Если же вы поместили свои процедуры в DLL, то все эти проблемы снимаются. Впрочем, в Delphi имеется и другое решение этих проблем — пакеты, которые будут рассмотрены в разд. 7.8. Таким образом, значение разработки в Delphi DLL несколько упало. Но, безусловно, осталось одно очень существенное преимущество DLL — они могут использоваться приложениями, написанными на других языках, таких, как С, Visual Basic, Access Basic, а также на языках любых других систем, которые умеют вызывать DLL. Так что создавать в Delphi свои DLL имеет смысл прежде всего в случаях, если вы планируете использовать их в вызовах из других языков программирования. В этих целях DLL уже использовались в некоторых разделах предыдущих глав. Рассмотрим коротко процедуру создания DLL в Delphi. Создание своей DLL начинается с выполнения команды File New Other и выбора в окне New Items на странице New пиктограммы DLL Wizard. Вы попадете в окно Редактора Кода, в котором появится следующий текст: library Projectl; /Important note about DLL memory management: ShareMem must be the f i r s t unit in your library's USES clause AND your p r o j e c t ' s (select ProjectView Source; USES clause if your DLL exports any procedures or f u n c t i ons t h a t pass strings as parameters or friction r e s u l t s . This applies to aJJ strings passed to and from your DLL - even those that are nested in records and classes. ShareMem is the interface unit to the BORLNDMM.DLL shared memory manager, which must be deployed along with your DLL. To avoid using BORLNDMM.DLL, pass string information using PChar or ShortString parameters. } (Комментарий гласит: Важное замечание об организации памяти DLL: модуль SfiareMem должен бь/ть включен первым в предложения uses в библиотеке И в вашем проекте (выполните Project | View Source), если ваша DLL экспортирует какие-то процедуры или функции, которым передаются или от которыл получаются строки в виде параметров или значений функции. Это относится ко всем строкам, пересылаемым или получаемым из DLL, даже к тем, которые вложены в записи и классы. ShareMem является интерфейсным модулем для диспетчера разделяемой памяти BORLNDMM.DLL. Чтобы избежать использования BORLNDMM.DLL, передавайте строковую информацию посредством параметров типа PChar или ShortString.J
uses SysUtils, Classes; begin end.
Эта заготовка библиотеки очень похожа на заготовку модуля. Основное отличие — ключевое слово library вместо unit в первой строке. Сохраните ваш проект в каком-то отведенном вами для этого каталоге, назвав сохраняемый файл MyDLL. При этом в приведенном выше тексте автоматически имя библиотеки тоже заменится на MyDLL. Пусть мы хотим занести в эту1 библиотеку функцию, которая, например, получала бы три действительных числа и возвращала их среднее значение. Назовем ее Average. Включение кода этой функции в текст модуля библиотеки осуществляется следующим образом (текст дан без многословного комментария, который вы видели выше): library MyDLL;
uses -
SysUtils, Classes;
S44
Глава 8 Function A v e r a g e ( А , В , С : r e a l ) : r e a l ; export; stdcall; begin Average:=(Д+В+С)/3; end;
exports Average; end.
Обратите внимание, что после заголовка функции записаны два ключевых слова — export и stdcall. Первое из них означает, что функция предназначена для внешнего использования, т.е. что пользователь может вызвать ее из вашей библиотеки. Компилятор будет использовать для этой функции модель вызова far. Наряду с такими функциями вы можете включить в библиотеку и какие-то вспомогательные функции — утилиты, которые используются другим вашими библиотечными функциями, но не предназначены для внешнего обращения к ним. Около таких функций вы не будете указывать export. Ключевое слово stdcall означает, что функция использует стандартные соглашения по передаче параметров при вызове. В конце текста вы видите операторы exports Average;
После ключевого слова exports перечисляются функции и процедуры, которые должны экспортироваться. Список exports можно рассматривать как указатель, упрощающий поиск в библиотеке. Занеся в текст модуля библиотеки свою функцию, выполните команду Project | Build. В результате будет создана библиотека — файл MyDLL.dll. Теперь вы можете использовать ее в программах. Создайте приложение, тестирующее эту функцию. Откройте новое приложение, перенесите на форму три компонента Edit, в которых пользователь сможет вводить числа, метку Label для отображения результатов и кнопку, при нажатии на которую в надписи метки должно отображаться среднее значение введенных чисел. Раздел implementation вашего теста должен иметь вид: implementation ($R *.DFM] Function A v e r a g e ( А , В , С r r e a l ) : r e a l ; f a r ; external
'MYDLL1;
procedure T F o r m l . B u t t o n l C l i c K t S e n d e r ; T O b j e c t l ; begin Label1.Caption:=FloatToStr(Average(StrToFloat(Editl.Textl , StrToFloat(Edit2.Text),StrToFloat(Edit3.Text)]); end;
Обратите внимание на то, как обеспечивается связь с DLL. Для этого в раздел implementation вводится объявление функции (имена ее параметров не обязательно должны совпадать с именами в библиотеке), которое позволяет компилятору узнать типы параметров и функции. После объявления указывается модель вызова far и после слова external (внешняя) — имя библиотеки DLL. Чтобы эта библиотека была найдена, сохраните ваше тестовое приложение в том же каталоге, в который вы поместили свою библиотеку. Запустите приложение на выполнение и убедитесь, что общение с DLL осуществляется нормально. На этом завершим краткое введение в DLL. Много подробнее вопросы построения и использования различных типов DLL, их статического и динамического связывания, получения информации о DLL, созданных другими разработчиками, рассмотрены в [2].
Различные виды проектов Delphi
545
8.6 Пакеты 8.6.1 Общее описание концепции пакетов Вы уже использовали пакеты в предыдущих разделах, создавая новые компоненты. Но теперь давайте рассмотрим этот инструмент более подробно. Одним из основных достоинств Delphi, начиная с Delphi 1, являлось то, что в результате проектирования создавались автономные исполняемые модули, т.е. приложение и все его ресурсы размещались в одном исполняемом файле. Причем по сравнению с рядом других систем, позволяющих создавать автономные модули, размеры файлов в Delphi были достаточно небольшими. Однако если у вас создано много выполняемых файлов или если вам надо передавать их по сети множеству пользователей, то размеры файлов становятся существенным критерием разработки. Стремление уменьшить затраты на хранение и распространение выполняемых файлов привело фирму Borland к концепции пакетов. Пакеты (packages) — это специальные динамически присоединяемые библиотеки, содержащие библиотеки визуальных компонентов и другие объекты, функции, процедуры и т.д. Эти DLL позволяют вам создавать очень небольшие выполняемые модули, обращающиеся за поддержкой к пакетам. Вы можете также скомпилировать в пакеты свои собственные компоненты и библиотеки. Файлы пакетов имеют расширение .dpi. Пакеты разделяются на два типа: пакеты времени проектирования и пакеты времени выполнения. Пакеты времени проектирования Delphi вызывает в процессе проектирования. Эти пакеты используются для установки компонентов в палитре компонентов среды разработки Delphi или для создания специализированного редактора свойств для заказных компонентов. Пакеты времени проектирования не используются приложениями; они используются только самой Delphi. Так что на этих пакетах далее мы останавливаться не будем. Пакеты времени выполнения содержат библиотеки визуальных компонентов Delphi. Вы можете добавить к Delphi заказные пакеты, разработанные вами или где-то приобретенные. При использовании пакетов времени выполнения вы должны передавать пользователю не только ваш выполняемый модуль, но и все пакеты временя выполнения, которые используются им. За счет того, что большая часть, кодов помещается в этих пакетах, размеры ваших выполняемых модулей существенно сокращаются. Например, модуль в 300 Кб может быть сокращен примерно до 15 Кб. Вы передаете пользователям один раз все пакеты и устанавливаете их на компьютерах клиентов. А затем, когда вы делаете новые приложения, вам достаточно передавать только небольшие исполняемые модули. Таким образом, у вас есть две альтернативы: • создавать законченные, автономные выполняемые модули • использовать поддержку пакетов времени выполнения
8.6.2 Поддержка пакетов Чтобы использовать пакеты в вашем приложении, вы должны сначала обратиться к поддержке пакетов, а затем компилировать и строить свое приложение. Давайте рассмотрим простой пример построения приложения с использованием пакетов и без них, чтобы сравнить получающиеся результаты. Построим простейшее приложение Delphi — с пустой формой. 1. Откройте новый проект. Сохраните его под именами по умолчанию ргоjectl.dpr и unitl.pas. 2. Выполните команду Project Build. В результате будет создан выполняемый модуль projecll.exe без поддержки пакетов. 1R Программирование в DelpM 7
546
Глава 8
Рис. 8.15
Окно информации о приложении без поддержки пакетов времени выполнения (а) и с поддержкой (б)
6) Picgraro Source compietf
• Package* Used 4! ties
«pata
vt'J:,.:
.
IfXityltt
Slalur ' - - • - ••• Ptqeal SucnS!h*Ctilnpil«i
3.
Посмотрите размер получившегося файла, воспользовавшись для этого, например, программой Windows «Проводник», или выполните команду Project Informolion for project. Команда открывает окно информации о проекте, которое представлено на рис. 8.15 а. В окне вы можете видеть число строк откомпилированного кода (Source Compiled), размер, занимаемый в памяти выполняемым кодом без учета отладочной информации (Code Size), объем оперативной памяти, необходимой для хранения глобальных переменных (Dalo Size), объем оперативной памяти, необходимой для хранения локальных переменных (Inilial Slock Size), размер результирующего файла (File size). Строка Status Information сообщает об успешности (или неудаче) при компиляции приложения. Панель Package Used показывает список пакетов времени выполнения, если приложение использует их поддержку. На рис. 8.15 а вы можете видеть, что размер вашего файла составляет 368128 байта или 360 Кб (от версии к версии Delphi этот размер нарастает: в Delphi 6 он составляет 352, в Delphi 5 — 286 Кб, в Delphi 4 — 275 Кб, в Delphi 3 — менее 200 Кб). Продолжим наш эксперимент. 4. Выполните команду Project Options. 5. В раскрывшемся окне опций проекта Project Options выберите страницу Packages — пакеты. Ее вид в Delphi 7 показан на рис. 8.16. 6. Отметьте на странице индикатор Built with runtime packages (строить с поддержкой пакетов времени выполнения) и щелкните на ОК. 7. Перестройте ваш выполняемый модуль, выполнив команду Project | Build (как в пункте 2). 8. Посмотрите теперь размер вашего файла projectl.exe с поддержкой пакетов (рис. 8.15 б). Размер сократился до 16896 или до 16,5 Кб (этот размер, как и размер без поддержки пакетов, тоже нарастает с каждой новой версией Delphi). Как видите, выигрыш весьма значительный: без поддержки пакетов размер файла составляет 368128 байта (360 Кб), а с поддержкой — 16896 байтов (16,5 Кб), т.е. примерно в 24 раз меньше. Но, как видно из рис. 8.15 б, для поддержки вашего приложения требуются пакеты VCL70.bpl и rtlTO.bpl. Так что если вы надумали использовать поддержку пакетов времени выполнения, то вместе со своим при-
Различные виды проектов Delphi
547
Рис. 8.16 Страница Packages окна опций проекта
Eoitpte \ Corrode" Mtea&e* • Unkn | , Vftwrilrio Psckeaea
i rlj^Vin* package* •*tlf|*iPeck«)eb
ложением вы должны поставлять пользователю откомпилированные файлы этих пакетов —• файлы ucl.dcp. и rtl.dcp. Размеры этих файлов составляют соответственно 1,90 Мб (1 997 435 байт) и 2,91 Мб (3 055 511 байт). Итого 4,81 Мб (4935 Кб или 5 052 946 байт). Это, конечно, очень внушительный размер. Но дело в том, что эти файлы вам надо поставить пользователю один раз и они обеспечат поддержку подавляющего большинства ваших приложений, файлы которых будут небольшими. Давайте прикинем, когда это становится выгодным. Предположим, что средний размер файла приложения с поддержкой пакетов времени выполнения — 20 Кб, а без поддержки — 450 Кб. Тогда, если пользователь использует в своей работе N приложений, то без поддержки пакетов ему потребуется для хранения файлов N*450 Кб дискового пространства, а при поддержке пакетов (4935 + N*20) Кб. 5135 Нетрудно посчитать, что поддержка пакетов становится выгодной при N большем 12. Таким образом, в каждом конкретном случае надо решать, создавать ли вам автономный выполняемый модуль, или использовать поддержку пакетов времени выполнения. Очевидно, что при создании отдельной прикладной программы лучше делать автономный модуль, А при создании большой серии приложений, которые будут использоваться одними и теми же пользователями, возможно вариант с поддержкой пакетов может быть предпочтительнее. Использовать, или не использовать поддержку пакетов времени выполнения — вопрос, возникающий только при заключительной компиляции уже готового приложения. Сам процесс проектирования никак не изменяется от того, будете, или не будете вы использовать поддержку пакетов. А поскольку поддержка пакетов позволяет существенно уменьшить затраты дискового пространства под хранение выполняемых модулей, то можно дать следующий совет. Совет
•
Для экономии место но диске при параллельной разработке нескольких проектов или нескольких вариантов одного проекта имеет смысл в процессе разработки включить поддержку пакетов времени выполнения. Это позволит вам более чем но порядок сократить размеры генерируемых выполняемых модулей. Для использования этой возможности выполните команду Project | Options, в раскрывшемся окне опций проекта выберите страницу Packages и на странице Packages включите индикатор Built with runtime packages. Одновременно полезна включить в том же окне (рис 8.16] индикатор Default, что обеспечит статус этой установки как установки по умолчанию для всех ваших будущих проектов. Только не забудьте при заключительной компиляции законченного проекта выключить опцию поддержки пакетов, если вы намерены передавать пользователям автономный выполняемый модуль.
Глава 8
548
Давайте теперь вернемся к странице Packages окна опций проекта {рис. 8.16) и рассмотрим ее более подробно. В верхнем окне Design packages вы можете увидеть список используемых пакетов времени проектирования. Вы можете добавить в него новые пакеты (кнопка Add) или удалить пакет (кнопка Remove или щелчок на окне индикатора рядом с выделенным пакетом). Впрочем, удалять системные пакеты вряд ли стоит. Лишние пакеты никак не повлияют на ваше приложение. Но зато, когда вы захотите создать новое приложение с компонентами, хранившимися в удаленном пакете, вам это не удастся сделать. Впрочем, в этом случае вы можете вновь установить требуемый пакет кнопкой Add. Кнопка Components позволяет вам просмотреть список компонентов, хранящихся в выделенном вами пакете. Индикатор Built with runtime packages вы уже знаете — он устанавливает режим поддержки пакетов. Индикатор Default позволяет принять установленную вами конфигурацию пакетов по умолчанию для всех новых проектов. При изменениях списка в окне Design packages одновременно будет меняться и перечень пакетов времени выполнения Runtime packages в окне редактирования внизу. Для своего приложения с пустой формой вы можете удалить из списка Design packages практически все пакеты времени проектирования. Тогда список пакетов времени выполнения Runfime packages предельно сократится. Но никакого выигрыша в размере выполняемого модуля вы за счет этого не получите, поскольку Delphi в любом случае не включит в модуль ничего лишнего. При разработке приложений с поддержкой пакетов надо иметь в виду, что пакеты используют API Windows, содержащийся в различных DLL. Если какая-то из этих DLL у потребителя вашего программного продукта ошибочна или не соответствует по дате (версии), у вас могут возникнуть проблемы. Их можно избежать, если проверять свое приложение на той системе, для которой оно предназначено, или на чистых установках Windows; тогда сможете быть уверенными, что оно будет выполняться без ошибок, и будете знать, что требуется вашему приложению для нормальной работы. В результате вы сможете убедиться, что включаете в поставку все необходимые файлы, или можете потребовать от пользователя работать на определенной версии операционной системы с определенными установками путей и т.д.
8.6.3 Разработка собственных пакетов В разделе 8.6.2 обсуждалась возможность использования поддержки пакетов времени выполнения. Вы можете создавать и свои собственные пакеты. Обычно они создаются, если вы разрабатываете свои новые компоненты. Вы это уже видели в разделе 8.3.1, когда начинали создание своего компонента. Как вы уже знаете, новые компоненты обязательно должны включаться в пакеты. А далее дело пользователя этого компонента (в частности, вас, если вы создавали пакет только для своего удобства): включать ли используемый пакет в приложение или делать Рис. 8.17 Окно Диспетчера Пакетов
Cunpte
В
Различные виды проектов Delphi
549
приложение с поддержкой пакетов времени выполнения. В последнем случае, передавая приложение пользователю, вы должны будете приложить к нему наряду с другими и свой пакет. Процедура создания нового пакета уже была рассмотрена в разделе 8.3.1- Напомню, что она начинается выполнением команды File | New Other и выбором пиктограммы Package в диалоговом окне New Hems на странице New. Вы попадете в окно Диспетчера Пакетов, которое представлено на рис. 8.17. Это окно вы уже видели в разделе 8.3.1, но тогда наша задача была не разработка пакета, а создание компонента. Поэтому, чтобы не отвлекать вас от этой основной задачи, я пропустил тогда некоторые подробности, связанные с пакетами. В окне Диспетчера Пакетов вершина Contains — содержимое пакета (пока этот раздел пустой), а вершина Requires — список других пакетов, требующихся для поддержки вашего. Прежде всего сохраните ваш создаваемый пакет на диске, выполнив команду File Save As и дав ему какое-то имя. Затем нажмите в окне Диспетчера Пакетов кнопку Options, чтобы задать характеристики пакета. Перед вами откроется окно опций проекта со страницей Description {см. рис. 8.18), обычно отсутствующей в этом окне. На этой странице Description — краткое описание пакета. На рисунке 8.18 это описание — «Пакет моих компонентов». Введенное вами описание впоследствии, после установки пакета в систему можно будет видеть на странице Packages (см. рис. 8.16, на котором есть строка этого пакета). Группа радиокнопок Usage options определяет назначение пакета: Design Only
Пакет времени проектирования. Компоненты, включенные в пакет, могут быть установлены в палитре библиотеки.
Runtime Only Пакет времени выполнения. Если вы используете в своих приложениях поддержку пакетов времени выполнения, то этот пакет должен передаваться пользователям вместе с приложениями. Design and runtime
Пакет и времени проектирования, и времени выполнения. Компоненты пакета могут быть установлены в палитре библиотеки, а сам пакет может распространяться вместе с приложениями, откомпилированными с учетом поддержки пакетов времени выполнения.
Чаще всего пакеты предназначены и для времени проектирования, и для времени выполнения. Но если, например, в пакете предусмотрен специальный редакРис. 8.18
Окно опций проекта со страницей Description
550
Глава 8
тор свойств, используемый во время проектирования, то такой пакет целесообразно отдельно компилировать как пакет времени проектирования (с редактором), и как пакет времени выполнения (без редактора). При этом пакет времени выполнения будет меньше. Группа радиокнопок Build control определяет режим компиляции пакета. При включенной опции Explicit rebuild (явное задание команды построения) для перекомпиляции пакета вам придется каждый раз нажимать в окне Диспетчера Пакетов кнопку Compile. Если же вы включите опцию Rebuild os needed, то перекомпиляция пакета будет осуществляться автоматически по мере надобности. Группа Package name содержит опции, определяющие имя проекта: LIB Prefix
Добавляет префикс в имя выходного файла.
LIB Version
Добавляет второе расширение в имя файла после стандартного расширения .bpl. Например, если вы введете в это окно текст "2.1.3", то файл будет иметь вид "..Ьр1.2,1.3".
LIB Suffix
Добавляет суффикс, вставляемый перед стандартным расширением .bpl. Например, если вы введете в это окно текст "-2.1.3", то файл будет иметь вид "-2.1.3.Ьр1".
После того как вы завершили работу со страницей Description, нажмите ОК и вы вернетесь в окно Диспетчера Пакетов, Нажмите кнопку Install — установка. Ваш пакет будет установлен в систему. Если после этого вы откроете окно опций проекта и выберете страницу Packages, то вы увидите свой пакет в списке Design packages (см. рис. 8.16). Впрочем, из окна Диспетчера Пакетов (рис. 8.17), как вы уже знаете, можно попасть в окно опций проекта и проще — нажав кнопку Options. Если вы впоследствии надумаете удалить свой пакет, то выделите его в списке Design packages страницы Packages окна опций проекта (рис. 8.16) и нажмите кнопку Remove. При этом пакет удалится только из списка, но физически его файл на диске останется. В дальнейшем вы опять можете добавить пакет в список используемых, воспользовавшись кнопкой Add на той же странице Packages. Если вы хотите удалить пакет не вообще из списка зарегистрированных в системе, а просто из числа используемых в конкретном проекте, может не нажимать кнопку Remove, а снять установку с индикатора, расположенного в строке вашего пакета (см. рис. 8.16). Теперь вернемся к окну Диспетчера Пакетов (рис. 8.17). Кнопка Add позволяет включить в пакет новый элемент. При нажатии этой кнопки открывается многостраничное окно, позволяющее вам включить в пакет модуль (страница Add Unit), компонент (страница New Component) или ActiveX (страница Import ActiveX). Для добавления в пакет уже разработанного вами компонента достаточно на странице Add Unit добавить файл его модуля .pas. Остальные файлы, относящиеся к компоненту, добавятся автоматически. В дальнейшем вы можете удалить включенный в пакет элемент кнопкой Remove в окне Диспетчера Пакетов. Кнопка Compile в окне Диспетчера Пакетов позволяет скомпилировать пакет с новыми включенными или измененными элементами. О кнопке Opfions уже говорилось — с ее помощью можно открыть окно опций проекта. С помощью описанной методики вы можете наполнять свой пакет процедурами и компонентами, а затем, если хотите, можете передавать пользователям или прикладывать к своим программам, использующим поддержку пакетов времени выполнения.
JO.
Часть 3 Базы данных и сети
-
•
Глава 9 •
Приложения для работы с локальными базами данных 9.1 Базы данных 9.1.1 Принципы построения баз данных Всегда, когда возникает потребность манипулировать большими массивами данных, используются базы данных. Работа с базами данных в Delphi — это столь обширная тема, что ей надо было бы посвящать отдельную книгу объемом не меньшую, чем та, которую вы сейчас читаете. Впрочем, таких книг выпущено немало. Поэтому в рамках данной книги мы вынуждены будем рассмотреть только основы создания приложений для работы с базами данных. Впрочем, этого будет достаточно для того, чтобы строить мощные и полезные приложения. Допуская, что читатели неплохо знакомы с принципами построения баз данных, я, тем не менее, очень коротко рассмотрю в данном разделе эти принципы, чтобы в дальнейшем использовать единую и понятную всем терминологию. База данных (мы будем говорить о так называемых реляционных базах данных) — это прежде всего набор таблиц, хотя, как мы увидим позднее, в базу данных могут входить также процедуры и ряд других объектов. Таблицу можно представлять себе как обычную двумерную таблицу с характеристиками (атрибутами) какого-то множества объектов. Таблица имеет имя — идентификатор, по которому на нее можно сослаться. В табл. 9.1 приведен пример фрагмента подобной таблицы с именем Pers, содержащей сведения о сотрудниках некоторой организации. Эта таблица будет в дальнейшем использоваться в примерах по работе с базами данных. Вы можете найти ее на диске, приложенном к книге, или построить сами при изучении материала разд. 9.2. Таблица 9.1, Пример таблицы данных о сотрудниках Pers Номер Отдел
Харак- Фототериграфия стика
Par
Пол Год рождения Year b Sex
...
Фамилия
Имя
Отчество
Fam
Nam
Num
Вер
1
Бухгал- Иванов терия
Иван
Иванович
1950
м
2
Петров
Петр
Петрович
1960
м
3
Цех! Цех 2
4
Цех!
Иванова Ирина Ивановна
...
...
...
Сидоров Сидор ...
Сидорович 1955 ...
Char act Photo
...
м
...
1961
ж
...
• *•
...
...
...
...
Столбцы таблицы соответствуют тем или иным характеристикам объектов — полям. Каждое поле характеризуется именем и типом хранящихся данных. Имя поля — это идентификатор, который используется в различных программах для
Глава 9
554
манипуляции данными. Он строится по тем же правилам, как любой идентификатор, т.е. пишется латинскими буквами, состоит из одного слова и т.д. Таким образом, имя — это не то, что отображается на экране или в отчете в заголовке столбца (это отображение естественно писать по-русски), а идентификатор, соответствующий этому заголовку. Например, для табл. 9.1 введем для последующих ссылок имена полей Num, Dep, Fam, Nam, Par, Year_b, Sex, Charact, Photo, соответствующие указанным в ней заголовкам полей. Тип поля характеризует тип хранящихся а поле данных. Это могут быть строки, числа, булевы значения, большие тексты (например, характеристики сотрудников), изображения (фотографии сотрудников) и Т.п. Каждая строка таблицы соответствует одному из объектов. Она называется записью и содержит значения всех полей, характеризующие данный объект. При построении таблиц баз данных важно обеспечивать непротиворечивость информации. Обычно это делается введением ключевых полей — обеспечивающих уникальность каждой записи. Ключевым может быть одно или несколько полей. В приведенном выше примере можно было бы сделать ключевыми совокупность полей Fam, Nam и Par. Но в этом случае нельзя было бы заносить в таблицу сведения о полных однофамильцах, у которых совпадают фамилия, имя и отчество. Поэтому в таблицу введено первое поле Num -— номер, которое можно сделать ключевым, обеспечивающим уникальность каждой записи. При работе с таблицей пользователь или программа как бы скользит курсором по записям. В каждый момент времени есть некоторая текущая запись, с которой и ведется работа. Записи в таблице базы данных физически могут располагаться без какого-либо порядка, просто в последовательности их ввода (появления новых сотрудников). Но когда данные таблицы предъявляются пользователю, они должны быть упорядочены. Пользователь может хотеть просматривать их в алфавитном порядке, или рассортированными по отделам, или по мере нарастания года рождения и т.п. Для упорядочивания данных используется понятие индекса. Индекс показывает, в какой последовательности желательно просматривать таблицу, Он является как бы посредником между пользователем и таблицей (см. рис. 9.1). Курсор скользит по индексу, а индекс указывает на ту или иную запись таблицы. Для пользователя таблица выглядит упорядоченной, причем он может сменить индекс и последовательность просматриваемых записей изменится. Но в действительности это не связано с какой-то перестройкой самой таблицы и с физическим перемещением в ней записей. Меняется только индекс, т.е. последовательность ссылок на записи. Индексы могут быть первичными и вторичными. Например, первичным индексом могут служить поля, отмеченные при создании базы данных как ключевые. А вторичные индексы могут создаваться из других полей как в процессе создания самой базы данных, так и позднее в процессе работы с ней. Вторичным индексам присваиваются имена — идентификаторы, по которым их можно использовать. Если индекс включает в себя несколько полей, то упорядочивание базы данных сначала осуществляется по первому полю, а для записей, имеющих одинаковые значения первого поля — по второму и т.д. Например, базу данных персонала можно индексировать по отделам, а внутри каждого отдела — по алфавиту. рис g -J Схема перемещения курсора по индексу
индекс
таблицо
Приложения для работы с локальными базами данных
555
База данных обычно содержит не одну, а множество таблиц. Например, база данных о некоторой организации может содержать таблицу имеющихся в ней подразделений с характеристикой каждого из них. Пример такой таблицы с именем Dep, которая будет использоваться нами в дальнейшем, приведен в табл. 9.2. Имена полей этой таблицы, которые в дальнейшем мы будем использовать: Dep и Proisv. Таблица 9.2. Пример таблицы данных о подразделениях Dep
Отдел Dep
Бухгалтерия Цех! Цех 2
Тип Proisv
управление производство производство
Отдельные таблицы, конечно, полезны, но гораздо больше информации можно извлечь именно из совокупности таблиц. Например, пользователю может требоваться узнать общее количество сотрудников, работающих в производственных цехах. Но ни одна из приведенных выше таблиц не поможет ответить на этот вопрос, поскольку в таблице Pers отсутствуют сведения о типах отделов, а в таблице Dep — о сотрудниках. Для получения ответов на подобные запросы необходимо рассмотрение совокупности связных таблиц. В связных таблицах обычно одна выступает как главная, а другая или несколько других — как вспомогательные, управляемые главной. В этом случае взаимодействие таблиц иллюстрируется рисунком 9.2. Главная и вспомогательная таблицы связываются друг с другом ключом. В качестве ключа могут выступать какие-то поля, присутствующие в обеих таблицах. Например, в приведенных ранее таблицах головной может быть таблица Dep, вспомогательной Pers, а связываться они могут по полю Dep, присутствующему в обеих таблицах. Курсор скользит по индексу главной таблицы. Каждой записи в главной таблице ключ ставит в соответствие в общем случае множество записей вспомогательной таблицы. Так в нашем примере каждой записи главной таблицы Dep соответствуют те записи вспомогательной таблицы Pers, в которых ключевое поле Dep с названием отдела совпадает с названием отдела в текущей записи главной таблицы. Иначе говоря, если в текущей записи главной таблицы в поле Dep написано "Бухгалтерия", то во вспомогательной таблице Pers выделяются все записи сотрудников бухгалтерии. Рис. 9.2 Схема взаимодействия главной и вспомогательной таблицы
Г л «не* таблица
Вспомогательна! таблица таблица
556
Глава 9
Создают базы данных и обрабатывают запросы к ним системы управления базами данных — СУБД. Известно множество СУБД, различающихся своими возможностями или обладающих примерно равными возможностями и конкурирующих друг с другом: Paradox, dBase, Microsoft Access, FoxPro, Oracle, InterBase, Sybase и много других. Разные СУБД по-разному организуют и хранят базы данных. Например, Paradox и dBase используют для каждой таблицы отдельный файл. В этом случае база данных — это каталог, в котором хранятся файлы таблиц. В Microsoft Access и в InterBase несколько таблиц хранится как один файл. В этом случае база данных — это имя файла с путем доступа к нему. Системы типа клиент/сервер, такие, как серверы Sybase или Microsoft SQL, хранят все данные на отдельном компьютере и общаются с клиентом посредством специального языка, называемого SQL. Поскольку конкретные свойства баз данных очень разнообразны, пользователю было бы весьма затруднительно работать, если бы он должен был указывать в своем приложении все эти каталоги, файлы, серверы и т.п. Да и приложение часто пришлось бы переделывать при смене, например, структуры каталогов и при переходе с одного компьютера на другой. Чтобы решить эту проблему, используют псевдонимы баз данных. Псевдоним (alias) содержит всю информацию, необходимую для обеспечения доступа к базе данных. Эта информация сообщается только один раз при создании псевдонима. А приложение для связи с базой данных использует псевдоним. В этом случае приложению безразлично, где физически расположена та или иная база данных, а часто безразлична и СУБД, создавшая и обслуживающая эту базу данных. При смене системы каталогов, сервера и т.п. ничего в приложении переделывать не надо. Достаточно, чтобы администратор базы данных ввел соответствующую информацию в псевдоним. При работе с базами данных часто используется кэширование всех изменений. Это означает, что все изменения данных, вставка новых записей, удаление существующих записей, т.е. все манипуляции с данными, проводимые пользователем, сначала делаются не в самой базе данных, а запоминаются в памяти во временной, виртуальной таблице. И только по особой команде после всех проверок правильности вносимых s таблицу данных пользователю предоставляется возможность или зафиксировать все эти изменения в базе данных, или отказаться от этого и вернуться к тому состоянию, которое было до начала редактирования. Фиксация изменений в базе данных осуществляется с помощью транзакций. Это совокупность команд, изменяющих базу данных. На протяжении транзакции пользователь может что-то изменять в данных, но это только видимость. В действительности все изменения сохраняются в памяти. И пользователю предоставляется возможность завершить транзакцию или внесением всех изменения в реальную базу данных, или отказом от этого с возвратом к тому состоянию, которое было до начала транзакции,
9.1.2 Типы баз данных Для разных задач целесообразно использовать различные модели баз данных, поскольку, конечно, базу данных сведений о сотрудниках какого-то небольшого коллектива и базу данных о каком-нибудь банке, имеющем филиалы во всех концах страны, надо строить по-разному. Процесс определения того, какая база данных более подходит для конкретного приложения, называется масштабированием. Это сложная задача, которую мы не будем затрагивать. Однако прежде чем двигаться дальше, необходимо иметь представление о возможных моделях баз данных, поскольку это влияет на построение приложений в Delphi.
Припд_жения_длд работы с локальными базами данных
557
В следующих разделах коротко рассмотрены четыре модели баз данных: Автономные Фай л-сер верные Клиент/сервер Многоуровневые распределенные Прежде, чем переходить к рассмотрению различных моделей баз данных, отметим, что если работа с данными в Delphi осуществляется через Borland Database Engine (BDE) — процессор баз данных фирмы Borland, то BDE должна быть поставлена на компьютере пользователя во всех моделях баз данных, кроме многоярусных. Подробные сведения об этой программе будут даны позднее. • • • •
9.1.2.1 Автономные базы данных Автономные базы данных являются наиболее простыми. Они хранят свои данные в локальной файловой системе на том компьютере, на котором установлены; система управления и машина базы данных, осуществляющая к ним доступ, находится на том же самом компьютере. Сеть не используется. Поэтому разработчику автономной базы данных не приходится иметь дело с проблемой параллельного доступа, когда два человека пытаются одновременно изменить одну и ту же запись, потому что такого никогда не может быть. Вообще, автономные базы данных не используются для приложений, требующих значительной вычислительной мощности, потому что процессорное время будет потрачено на выполнение манипуляций с данными и в целом будет потеряно для приложения. Автономные базы данных полезны для развития тех приложений, которые распространены среди многих пользователей, каждый из которых поддерживает отдельную базу данных. Это, например, приложения, обрабатывающие документацию небольшого офиса, кадровый состав небольшого предприятия, бухгалтерские документы небольшой бухгалтерии. Каждый пользователь такого приложения манипулирует своими собственными данными на своем компьютере. Пользователю нет необходимости иметь доступ к данным любого другого пользователя, так что отдельная база данных здесь вполне приемлема. 9.1.2.2 Файл-серверные базы данных Фай л-серверные базы данных отличаются от автономных тем, что они могут быть доступны многим клиентам через сеть. Это очень удобно, так как изменения в таких базах данных видят все пользователи. Например, базу данных сотрудников крупного учреждения целесообразно делать именно такой, чтобы администраторы отдельных подразделений обращались к ней, а не заводили у себя локальные базы данных (при этом можно сделать так, чтобы каждый администратор видел только ту информацию, которая относится к его подразделению). Сама база данных хранится на сетевом файл-сервере в единственном экземпляре. Для каждого клиента во время работы создается локальная копия данных, с которой он манипулирует. При этом возникают (и решаются) проблемы, связанные с возможным одновременным доступом нескольких пользователей к одной и той же информации. Например, при проектировании приложений, работающих с подобными базами данных, должны быть решены такие проблемы: что делать, если пользователь прочел некоторую запись и, пока он ее просматривает и собирается изменить, другой пользователь меняет или удаляет эту запись. Одним из недостатков баз данных файл-сервер является непроизводительная загрузка сети. При каждом запросе клиента данные в его локальной копии полностью обновляются из базы данных на сервере. Даже если запрос относится всего к одной записи, обновляются все записи данных. Если записей в базе данных много, то даже при небольшом числе клиентов сеть будет загружена очень основательно, что серьезно скажется на скорости выполнения запросов.
558
Глава g
Другой недостаток связан с тем, что забота о целостности данных при такой организации работы возлагается на программы клиентов. Если они недостаточно тщательно продуманы, в базу данных легко занести ошибки, которые могут отразиться на всех пользователях. 9.1.2.3 Базы данных клиент/сервер Для больших баз данных с множеством пользователей часто используются базы данных на платформе клиент/сервер. В этом случае доступ к базе данных для группы клиентов выполняется специальным компьютером — сервером. Клиент дает задание серверу выполнить те или иные операции поиска или обновления базы данных. И мощный сервер, ориентированный яа операции с запросами самым оптимальным способом, выполняет их и сообщает клиенту результаты своей работы. Подобная организация работы повышает эффективность выполнения приложений за счет использования мощности сервера, разгружает сеть, обеспечивает хороший контроль целостности данных. В базах данных клиент/сервер возникает дополнительная проблема — спроектировать приложение так, чтобы оно максимально использовало возможности сервера и минимально нагружало сеть, передавая через нее только минимум информации. Это достигается созданием хранимых на сервере процедур (см. в гл. 10), которые реализуют бизнес-логику: обрабатывают данные и пересылают пользователю только те данные, которые он запросил. После долгого господства архитектуры клиент/сервер стали выявляться недостатки такой организации работы с данными (см. в раз. 1.4). Поэтому на первый план все решительнее выдвигаются многоуровневые распределенные системы обработки данных. •• 9.1.2.4 Многоуровневые распределенные базы данных Это сравнительно новый, но уже прочно завоевавший позиции путь обработки данных в сети с помощью распределенных приложений (см. раз. 1.4 и гл. 7). Наиболее распространен трехуровневый вариант: • На нижнем уровне на компьютерах пользователя расположены приложения клиентов, обеспечивающие пользовательский интерфейс, • На втором уровне расположен сервер приложений, обеспечивающий обмен данными между пользователями и распределенными базами данных. Сервер приложений размещается в узле сети, доступном всем клиентам • На третьем уровне расположен удаленный сервер баз данных, принимающий информацию от серверов приложений и управляющий ими. Это наиболее сложная к гибкая организация баз данных. Delphi обеспечивает в основном создание приложений для первых двух уровней этой системы. При этом надо отметить, что яа нижнем уровне — на компьютерах пользователя не требуется установки Borland Database Engine (BDE). В этом заключается одно из преимуществ многоярусных распределенных баз данных. -
9.1.3 Организация связи с базами данных в Delphi В первых версиях Delphi основой работы с базами данных являлся Borland Database Engine (BDE) — процессор баз данных фирмы Borland. He потерял он своего значения и сейчас. Но. начиная a Delphi 5, в библиотеке компонентов стали появляться альтернативные механизмы связи с данными. Особенно много их появилось, начиная с Delphi В, что связано, в частности, с ориентацией на работу с разными платформл м и . Тем не менее, начнем рассмотрение организации связи приложений TV i t i i • Лшами данных с рассмотрения BDE.
Приложения для работы с локальными базами данных
559
BDE служит посредником между приложением и базами данных. Он предоставляет пользователю единый интерфейс для работы, развязывающий пользователя от конкретной реализации базы данных. Благодаря этому не надо менять приложение при смене реализации базы данных. Приложение Delphi обращается к базе данных через BDE. В этом случае общение с базами данных соответствует схеме, приведенной на рис. 9.3. Рис. Э.З Схема связи приложения Delphi с базами данных
Приложение Delphi
База данных
SQL
сервер
jj Боза данныи
Приложение Delphi, когда ему нужно связаться с базой данных, обращается к BDE и сообщает обычно псевдоним базы данных и необходимую таблицу в ней. BDE реализован в виде динамически присоединяемых библиотек DLL (файлы IDAPI01.DLL, IDAPI32.DLL). Они, как к любые библиотеки, снабжены API (Application Program Interface — интерфейсом прикладных программ), названным IDAPI (Integrated Database Application Program Interface). Это список процедур и функций для работы с базами данных, которым и пользуются приложения. BDE no псевдониму находит драйвер, подходящий для указанной базы данных. Драйвер ~ это вспомогательная программа, которая понимает, как общаться с базами данных определенного типа. Если в BDE имеется собственный драйвер соответствующей СУБД, то ВОЕ связывается через него с базой данных и с нужной таблицей в ней, обрабатывает запрос пользователя и возвращает в приложение результаты обработки. BDE поддерживает естественный доступ к таким базам данных, как Microsoft Access, FoxPro, Paradox, dBase и ряд других. Если собственного драйвера нужной СУБД в BDE нет, то используется драйвер ODBC. ODBC (Open Database Connectivity) — DLL, аналогичная по функциям BDE, но разработанная фирмой Microsoft. Она хранится в файле ODBC.DLL. Поскольку Microsoft включила поддержку ODBC в свои офисные продукты и для ODBC созданы драйверы практически к любым СУБД, фирма Borland включила в BDE драйвер, позволяющий использовать ODBC. Правда, работа через ODBC осуществляется несколько медленнее, чем через собственные драйверы СУБД, включенные в BDE, но благодаря связи с ODBC масштабируемость Delphi существенно увеличилась и сейчас из Delphi можно работать с любой сколько-нибудь значительной СУБД. BDE поддерживает SQL — стандартизованный язык запросов, позволяющий обмениваться данными с SQL-сераерами, такими, как Sybase, Microsoft SQL,
560
Глава-_9
Oracle, Interbase. Эта возможность используется особенно широко при работе на платформе клиент/сервер и в распределенных приложениях. В Delphi 5 была введена альтернативная возможность работы с базами данных, минуя BDE. Это разработанная в Microsoft технология ActiveX Data Objects (ADO). ADO — это пользовательский интерфейс к любым типам данных, включая реляционные и не реляционные базы данных, электронную почту, системные, текстовые и графические файлы. Связь с данными осуществляется посредством так называемой технологии OLE DB. Использование ADO (см разд. 10.4) обеспечивает более эффективную работу с данными. К тому же, снимаются некоторые проблемы с кириллицей, которые в последнее время стали проявляться при работе с BDE. Для применения ADO на вашем компьютере должна быть установлена система ADO 2.1 или более старшая версия. Кроме того, должна быть установлена клиентская система доступа к данным, например, Microsoft SQL Server, а в ODBC должен иметься драйвер OLE DB для того типа баз данных, с которым вы работаете. Впрочем, все это входит в поставки современных версий Windows. Еще один альтернативный доступ к базам данных Interbase был введен в Delphi 5 на основе технологии InterBase Express (IBX). В библиотеке компонентов Delphi 5 появилась страница InterBase, содержащая компоненты для работы с InterBase, минуя BDE. Эти компоненты обеспечивают повышенную производительность я позволяют использовать новые возможности сервера InterBase, недоступные обычным компонентам BDE. Этот вариант связи с базами данных будет рассмотрен в гл. 10 в разд. 10.5. В Delphi б и 7 альтернативные возможности доступа к базам данных расширены за счет технологии dbExpress. Это набор драйверов, обеспечивающих доступ к серверам SQL на основе единого интерфейса. При использовании dbExpress вы можете поставлять свое приложение совместно с DLL соответствующего драйвера и, если надо, с дополнительными файлами, содержащими информацию о соединениях. Однако возможна поставка и в виде автономного исполняемого файла .ехе. Особенности dbExpress будут рассмотрены в гл. 10 в разд. 10.6. Надо сказать, что в Delphi 7 разработчики из Borland уже начали понемногу отказываться от своего родного детища — BDE. Например, рекомендуется в распределенных приложениях использовать не BDE, a dbExpress. Постепенный отход BDE на второй план проявился, начиная с Delphi 6, в том, что компоненты, использующие BDE, были вытеснены с главной страницы библиотеки на отдельную страницу. Тем не менее, в данной главе мы сосредоточимся, прежде всего, на работе с BDE и на основном компоненте набора данных BDE Table. Этот компонент позволяет наиболее просто рассмотреть основы работы с данными. Причем все, что будет рассмотрено, применимо и к другим технологиям. В конце главы в разд. 9.14 будет рассмотрена работа с клиентскими наборами данных, которые применяются независимо от того, используется BDE, или нет. А особенности использования ADO, dbExpress и некоторых других технологий будут рассмотрены в гл. 10 и, частично, в гл. 12.
9.2 Создание баз данных с помощью Database Desktop 9.2.1 Создание новой таблицы Прежде, чем начать строить приложения, работающие с базами данных, надо иметь сами базы данных. Delphi поставляется с примерами, имеющими немало баз данных, которыми можно воспользоваться для обучения. Базы данных Pers и Dep, фрагменты которых были приведены выше в табл. 9.1 и 9.3 и которые будут ис-
Приложения для работы с локальными базами данных
561
пользоваться в примерах данной и последующих глав книги, вы можете найти на диске, приложенном к этой книге. Но можно и самим создать необходимые базы данных. Причем не обязательно для этого использовать стандартные СУБД. Вместе с BDE и Delphi поставляется программа Database Desktop (файл DBD32.EXE для 32-разрядных Delphi), которая позволяет создавать таблицы баз данных некоторых СУБД, задавать и изменять их структуру. Обычно вызов Database Desktop включен в главное меню Delphi в раздел Tools. Если это не сделано, то полезно включить его туда с помощью команды Tools | Configure Tools. Вызовите Database Desktop. Вы увидите окно вида, показанного на рис. 9.4 (если в предыдущем сеансе работы с Database Desktop не была открыта какая-то таблица, то таблицы в середине окна не будет видно, и разделов меню будет поменьше). Рис. 9.4 Главное окно программы Database Desktop
Давайте создадим с помощью Database Desktop таблицу базу данных СУБД Paradox 7. В Paradox 7 база данных — это каталог, в котором лежат таблицы файлы с расширением ЛЬ. Поэтому прежде надо создать соответствующий каталог с помощью любой программы Windows, например, с помощью «Проводника*. Далее выполните команду Database Desktop File | New. Вам откроется меню с разделами: QBE Query
Визуальный построитель запросов и запись этих запросов в файл.
SQL File
Создание запроса на SQL и запись его в файл.
ТоЫе
Создание новой таблицы.
Выберите Table. Откроется небольшое диалоговое окно (рис. 9.5). В нем из выпадающего списка вы можете выбрать СУБД, для которой хотите создать таблицу. Можете посмотреть в нем, таблицы каких СУБД могут создаваться с помощью Database Desktop. Выберите Paradox 7. Вы увидите окно, представленное на рис. 9.6. В этом окне вы можете задать структуру таблицы (поля и их типы), создать вторичные индексы, ввести диапазоны допустимых значений полей, значения по умолчанию и ввести много иной полезной информации о создаваемой таблице. Рис. 9.5 Окно выбора СУБД
Глава 9
562 Рис. 9.6 Окно создания структуры таблицы Paradox
nWuc,,.,e P,^d., 5.0 ,„ W^s ^.K*™*
'
field rosier
Table вориьк :
FieWName .
Typa
1 Num 2 UB[J . 3 Faw 4 Wsm С
I
6
Size -Key
i •*" : A i A i A
|л j
Par
S«* 3 Charatl
L " M G -
s Photo
| |vab»vCheck!
_^J
Г,/"г.
li* 20 20
'j ri-Reg»edFieU
70
1
20
гм™™-** ,| З.Мвшпит*** ГГочт ^~~" 1 I 1 1 Defoul value . -
-
J1970
EntHafeldrmeuploJSchjracrersigng
5 Fetiiei
Г Рас* Table
;?
Save
Assta... SaveAt.. j
Caneri
|
H*
9.2.2 Задание полей Для каждого поля создаваемой таблицы прежде всего указывает имя (Field Nome) — идентификатор поля. Он может включать до 25 символов и не может начинаться с пробела (но внутри пробелы допускаются). Затем надо выбрать тип (Туре) данных этого поля. Для этого перейдите в раздел Туре поля и щелкните правой кнопкой мыши. Появится список доступных типов, из которого вы можете выбрать необходимый вам. Приведем пояснения типов данных, используемых в Paradox. Обозна- Размер чение (Size)
Обозначение в списке
Пояснение
А
Alpha
Строковое поле, содержащее любые печатаемые ASCII символы. Размер — число символов.
N
Number
Действительные числа от -10307 до 10808 с 15 значащими разрядами. Для выбора формата представления надо использовать Paradox.
$
Money
Положительные или отрицательные числа, отличающиеся от Number формой представления и символом денежной единицы. Для выбора формата представления надо использовать Paradox.
S I
Short
Короткие целые числа от -32 767 до 32 767.
№
1-255
Long Integer Длинные целые числа от -2 147 483 648 до 2 147 483 647.
0-32
BCD
Числа в формате BCD (Binary Coded Decimal). Вычисления с этими числами проводятся с повышенной точностью по сравнению с другими типами чисел, но медленнее. Этот тип введен для совместимости с другими приложе-. ниями, использующими BCD. В поле типа BCD можно вводить до 15 значащих разрядов.
Приложения для работы с локальными базами данных
563
Обозна- Размер чение (Size)
Обозначение в списке
Пояснение
D
Date
Т
Time
Значения, представляющие собой даты. Для выбора формата представления надо использовать Paradox. Значения, представляющие собой время. Для выбора формата представления надо использовать Paradox. Значения, хранящие время и дату. Для выбора формата представления надо использовать Paradox. При вводе значения в поле типа Timestamp пользователь может последовательно нажимать клавишу пробела, чтобы ввести текущее время и дату.
Times tamp
И
1-240
Memo
F
0-240
Formatted Memo
G
Graphic
0
OLE
L
Logical
+
Autoincrement
В
Y
-
1-255
Binary
Bytes
•
Поля для хранения текстов неограниченной длины. Тексты хранятся в отдельных файлах .mb. Указываемый размер — это число первых символов текста, хранящихся непосредственно s таблице. Просмотр полей Memo возможен в Paradox или в приложениях Delphi. Поля для хранения форматированных текстов неограниченной длины. Тексты хранятся в отдельных файлах .mb. Указываемый размер — это число первых символов текста, хранящихся непосредственно в таблице. Просмотр полей Formatted Memo возможен в Paradox или в приложениях Delphi. Изображения из файлов в форматах .Ьтр, .рсх, .tif, .gif или .eps. Database Desktop преобразует их в формат .BMP . Просмотр полей Graphic возможен в Paradox или в приложениях Delphi. Данные типа OLE — изображения, звуки, документы. Database Desktop не поддерживает поля этого типа. Просмотр полей OLE возможен в Paradox или в приложениях Delphi. Логические поля. По умолчанию возможные значения. — true и false. При вводе данных пользователь может ввести только первый символ из возможных значений. Автоматически увеличивающееся на 1 длинное целое. Только для чтения. При удалении записей значения полей в оставшихся записях не изменяются. Данные, хранящиеся в отдельных двоичных файлах .тЪ, которые Database Desktop не интерпретирует. В файлах могут храниться звуки и любые другие данные. Данные, которые Database Desktop не интерпретирует. В отличие от полей Binary хранятся в таблице, а не во внешних файлах.
Глава 9
564
В нашем примере таблицы Pers (см. таблицу 9.1) для поля Num целесообразно выбрать тип Autoincrement, обеспечивающий уникальность каждой записи. Для полей Dep, Fam, Nam, Par и Sex необходимо задать тип Alpha, для поля Year_b — тип Short, для поля Charact — Memo, для поля Photo — Graphic. В таблице Dep (см. табл. 9.2) для поля Dep надо задать тип Alpha, а для поля Proisv — Logical. Для некоторых типов необходимо задавать размер (Size). Например, для строкового типа Alpha размер — это число символов. Для полей типов Memo, Graphic, Binary и некоторых других тоже нужно задавать размер. Это число байтов, хранящихся непосредственно в таблице. Практически все равно, какое число задать (например, 10). Но что-то задать надо, иначе Database Desktop сообщит об ошибке. Ключевые поля должны быть отмечены символом "*" в последней колонке. Для того чтобы поставить или удалить этот символ, надо или сделать двойной щелчок в соответствующей графе информации о поле, или выделить эту графу и нажать клавишу пробела. Если имеется несколько ключевых полей, то в таблицах Paradox они должны быть первыми. В нашем примере для таблицы Pers ключевым является поле Num, а для таблицы Dep — поле Dep.
9.2.3 Задание свойств таблицы Теперь обратите внимание на правую часть окна (рис. 9.6). В нем задаются свойства таблицы (Table properties). Вверху имеется выпадающий список с рядом разделов. 9.2.3.1 Validity Checks — проверка правильности значений Начнем с первого из них: Validity Checks — проверка правильности значений. Вид правой части окна при выборе этого раздела показан на рис. 9.6 и может несколько изменяться в зависимости от того, какой тип у поля, выделенного курсором. Вы можете задать следующие характеристики поля: Required Field
В этом индикаторе отмечаются те поля, значения которых обязательно должны содержаться в каждой записи. Для нашего примера такими полями, вероятно, должны быть поля Fam, Nam и Par.
Minimum
Минимальное значение. Это свойство полезно задавать для числовых полей. В нашем примере надо задать минимальное значение для поля Уеаг_Ь.
Maximum
Максимально значение. Это свойство полезно задавать для числовых полей. В нашем примере надо задать максимальное значение для поля Year_b.
Default
Значение по умолчанию. Это свойство полезно задавать для числовых и логических полей, а также для некоторых символьных. В нашем примере полезно задать значения по умолчанию для поля Year_b и поля Sex (например, "м" — иначе у пользователя могут возникнуть проблемы при вводе информации).
Picture
Шаблон для ввода данных. Например, можно задать шаблон номера телефона (###-##•##) и т.п. Подробнее о составлении шаблонов вы можете узнать во встроенной справке Database Desktop.
Assist
Эта кнопка вызывает диалоговое окно, помогающее создать шаблон Picture.
Приложения для работы с локальными базами данных
565
9.2.3.2 Table Lookup — таблица просмотра Следующий раздел в выпадающем списке свойств таблицы s правом верхнем углу экрана на рис. 9.6: Table Lookup — таблица просмотра. Этот раздел позволяет связать с полем данной таблицы какое-то поле другой, просматриваемой таблицы, из которого будут браться допустимые значения. При выборе Table Lookup на экране появляется кнопка Define — определить. При ее нажатии открывается диалоговое окно, показанное на рис. 9.7. В нем вы можете для данного поля задать таблицу просмотра (Lookup table). При этом вы можете воспользоваться выпадающим списком драйверов или псевдонимов (Drive or Alias) и кнопкой просмотра (Browse). А затем кнопкой со стрелкой занести поле просматриваемой таблицы, из которого будут браться допустимые значения. В нашем примере пока все это сделать невозможно, поскольку еще не создана вторая таблица Dep. Однако после того, как она будет создана, полезно вернуться к таблице Pers и для поля Dep задать таблицу Dep как просматриваемую и ее поле Dep как множество возможных значений. Это предотвратит ошибочное появление в таблице Pers каких-то значений подразделений, не содержащихся в таблице Dep. Рис 9.7 Окно задания таблицы просмотра
1 ,ihile Lookup Field паи IDEPDB В CURRENCY DB HD'cdbDB • PERSDB ff Just cure™ ItU f Al pjiesponding (elds Lookup есии • f^ Flrjohe^ Я Kjlp and fill
Qrius (31 flhaii i \da3C3!e\dbparV
Cancel
Hab
9.2.3.3 Secondary Indexes — вторичные индексы Следующий раздел в выпадающем списке свойств таблицы: Secondary Indexes — вторичные индексы. Этот раздел позволяет создать необходимые для дальнейшей работы вторичные индексы (первичный индекс создается по ключевым полям). Например, для дальнейшего использования нашей таблицы Pers полезны будут следующие индексы: Пояснение Упорядочивание таблицы сотрудников по алфавиту. Dep, Fam, Nam, Par Упорядочивание таблицы по подразделениям, а внутри каждого подразделения упорядочивание сотрудников по алфавиту.
Имя индекса Поля fio Fam, Nam, Par depfio year
Year b, Fam, Nam, Par "
Упорядочивание таблицы по году рождения сотрудников, а для ровесников — по алфавиту.
Глава 9
566
Чтобы создать новый вторичный индекс, нажмите кнопку Define — определить. Откроется диалоговое окно, представленное на рис. 9.8. В его левом окне Fields содержится список доступных полей, в правом окне Indexed fields вы можете подобрать и упорядочить список полей, включаемых в индекс. Для переноса поля из левого окна в правое надо выделить интересующее вас поле или группу полей и нажать кнопку со стрелкой вправо. Стрелками Change order (изменить последовательность) можно изменить порядок следования полей в индексе. Рис. 9.8 Окно задания вторичного индекса
Deftire SeLDndsry Index
Dep •am Jam
rlndexOpticrBl Г~ Unique
\ "_ V Ease iem*we
•
|P
Г" Descending OK
Caned J
Help _j
Панель радиокнопок Index Options (опции индекса) позволяют установить следующие характеристики; Unique
Установка этой опции не позволяет индексировать таблицу, если в ней находятся дубликаты совокупности включенных в индекс полей. Например, установка этой опции для индекса fio не допустила бы наличия в. таблице сотрудников с совпадающими фамилией, именем и отчеством.
Descending
При установке этой опции таблица будет упорядочиваться по степени убывания значений (по умолчанию упорядочивание производится по степени нарастания значений).
Cose Sensilive
При установке этой опции будет приниматься во внимание регистр, в котором введены символы.
Maintained
Если эта опция установлена, то индекс обновляется при каждом изменении в таблице. В противном случае индекс обновляется только в момент связывания с таблицей или передачи в нее запроса. Это несколько замедляет обработку запросов. Поэтому полезно включать эту опцию для обновляемых таблиц. Если таблица используется только для чтения, эту опцию лучше не включать.
После того как индекс сформирован, щелкаете на кнопке ОК. Открывается окно (рис. 9.9), в котором вы задаете имя индекса. Рис. 9.9
Задание имени индекса
save Index Us Index паля. fdSSo
OK
Cancel
Heto
567
Приложения для работы с локальными базами данных
9.2.3.4 Referential Integrity — целостность на уровне ссылок Следующий раздел в выпадающем списке свойств таблицы в правом верхнем углу экрана на рис. 9.6: Referential Integrity — целостность на уровне ссылок. Речь идет о способах, позволяющих обеспечить постоянные связи между данными отдельных таблиц. Если устанавливается целостность на уровне ссылок между двумя таблицами, одна из которых — головная (родительская), а другая — вспомогательная (дочерняя), то во вспомогательной таблице указывается поле (или группа полей), которые могут брать свои значения только из ключевого поля (или полей) головной таблицы. Можно также указать операции, которые должны автоматически выполняться во вспомогательной таблице при изменении значения ключевого поля в головной таблице. Подобная целостность на уровне ссылок допустима не для всех типов таблиц, но в Paradox они предусмотрены. Прежде, чем создавать Referential Integrity, надо иметь обе связываемые таблицы — родительскую и дочернюю. Если бы мы уже имели в нашем примере обе таблицы — Pers и Dep (вообще то говоря, на приложенном к книге диске они имеются), мы могли бы задать целостность, связав поле Dep таблиц Pers с ключевым полем Dep головной таблицы Dep. Чтобы ввести подобную связь надо сначала установить в качестве рабочего каталог, содержащий обе таблицы {это делается командой File Working Directory). Затем надо открыть дочернюю таблицу Pers (команда File Open), войти в режим ее реструктуризации (команда Table | Restructure) и в окне Table properties выбрать раздел Referential Integrity. Затем щелкнуть на кнопке Define, и перед вами откроется диалоговое окно, представленное на рис. 9.10. На его левой панели Fields вы можете выбрать поле или группу полей, связываемых с ключевыми полями головной таблицы, и кнопкой со стрелкой перенести их в список дочерних полей Child fields. Затем на правой панели Table вы можете указать головную панель (если ее там нет, значит вы неверно установили рабочий каталог) и кнопкой со стрелкой перенести в список ключей родительской таблицы Parent's key. Группа радиокнопок Update rule определяет, что будет, если в головной таблице вы удалите или измените значение ключевого поля, с которым связаны какие-то записи во вспомогательной таблице. Если вы установили опцию Prohibit, то Database Desktop просто не разрешит подобную операцию. Если же вы установили опцию Cascade, то при смене значения ключевого поля в головной таблице аналогичные изменения автоматически произойдут в записях дочерней таблицы. А если вы удалите запись в головной таблице, содержащую некоторое значение ключевого поля, то во вспомогательной таблице автоматически удалятся все записи, связанные с этим значением ключевого поля. Установка индикатора Strict Referential Integrity в нижней части диалогового окна не позволит ранним версиям Paradox (в частности, версиям Paradox для DOS) открыть и испортить таблицы, в которых введена целостность на уровне ссылок. Рис. 9.10 Окно установления целостности на уровне ссылок
ft eferenhal T"l ennly Print* key
Feld>
FamlWO] Nan [=20]
Pi°H"t .
Г [Strja
Глава 9
Когда вы провели все необходимые операции, щелкните на кнопке ОК и в открывшемся окне (рис. 9.11) введите имя созданной ссылки.
HKv -^
Рис. 9.11 Ввод имени созданной ссыпки
Rs-ie.e.-il L! г-е^п у паша:
JRd |
OK
j
Caned
|
Help
9.2.3.5 Password Security — пароли доступа Следующий раздел в выпадающем списке свойств таблицы в правом верхнем углу экрана на рис. 9,6: Password Security — пароли доступа. Paradox позволяет задать для таблицы пароли, и для каждого из них определить разрешенные операции, как для таблицы в целом, так и для отдельных ее полей. Щелчок на копке Define откроет вам окно, показанное на рис. 9.12. В нем вы можете ввести главный пароль (окно Master password), подтвердить его (окно Verify master password), после чего щелчком на копке Auxiliary Passwords (вспомогательные пароли) открыть новое диалоговое окно (рис. 9.13), позволяющее ввести вспомогательные пароли и определить правила доступа по ним. Рис. 9.12
xj
Ввод главного пароля
Ы«1« pestwcwt
OK | Caned
J
Help
Рис 9.13 Ввод вспомогательного пароля
Ди* 1 1 1 , 1 Г У PJS414 ГЭГ JS
.
Caned
Hep
В окне Current Password (текущий пароль) вы указываете пароль (он совершенно не обязательно должен совпадать С тем, под которым вы вошли в это окно), для которого намереваетесь сформировать правила доступа. В группе радиокнопок
Приложения для работы с локальными базами данных
569
Table Rights (права доступа к таблице) вы можете определить общий уровень доступа к таблице: АИ
Допускаются любые операции, вплоть до изменения ее структуры, удаления таблицы, изменения и удаления паролей.
Insert & Delete
Допускаются любые операции с записями (редактирование, вставка, удаление), но не разрешается изменение структуры таблицы и ее удаление.
Data Entry
Допускается редактирование данных и вставка записей, но запрещено удаление записей и не разрешается изменение структуры таблицы и ее удаление.
Update
Допускается только просмотр таблицы и изменение неключевых полей. Допускается только просмотр таблицы.
Read Only
В окне Field Rights (права доступа к полю) вы можете определить дополнительные права доступа к каждому полю, но не превышающие заданный уровень доступа к таблице: АИ
Дает все права доступа к полю, предусмотренные заданными правами доступа к таблице.
Read Only None
Позволяет только читать данные этого поля. Не позволяет ни наблюдать, ни редактировать данное поле.
После того как вы установили все права доступа для данного вспомогательного пароля, щелкните на кнопке Add и пароль занесется в окно списка паролей Passwords. Далее кнопкой New вы можете начать задание нового вспомогательного пароля. Кнопкой Change вы можете изменить выделенный в списке ранее введенный вспомогательный пароль или удалить его, щелкнув после кнопки Change на кнопке Delete. 9.2.3.6 Table Language — язык таблицы Этот раздел в выпадающем списке Table Properties позволяет задать (если он не задан) или переопределить (кнопкой Modify) язык таблицы, установленный по умолчанию в драйвере данной СУБД с помощью программы BDE Administrator (см. разд. 9.3.3). Правильный выбор языка определяет, будут ли нормально читаться в таблице русские тексты. 9.2.3.7 Dependent Tables — зависимые таблицы Этот последний раздел в выпадающем списке Table Properties позволяет просмотреть список зависимых таблиц, связанных с данной целостностью на уровне ссылок Referential Integrity.
9.2.4 Завершение создания таблицы После того как все необходимые данные о структуре таблицы внесены, щелкните на кнопке Save as (сохранить как) и перед вами откроется окно (рис. 9-14), напоминающее обычный диалог сохранения в файле. От обычного это окно отличается выпадающим списком Alias. Этот список содержит псевдонимы различных баз данных (о них пойдет речь позднее), из которого вы можете выбрать базу данных.
Глава 9
570
в которую будете сохранять свою таблицу. Если вам не надо сохранять таблицу в одной из существующих баз данных, то вы можете воспользоваться обычным списком Сохранить в в верхней части окна. При этом вы с помощью обычной быстрой кнопки можете создать новую папку (каталог). Вспомните, что для Paradox база данных — это каталог, в котором сохраняется таблица. Рис. 9.14 Сохранение таблицы в базе данных
Save Table A* Пажа. |_iDBP«l
^j *• И
Н-
jjOJRRENCY.Cte DEP.DB
f
ucdb.DB
•€|PERS. DB
Имяфайла'
PERS.DB
1ип Файл*
I Paradox (".*)
Ajat.
jl^WORk
Qpbont.
p D«JajtabJa
"I
Отм
R Add data to new table
Внизу окна на рис. 9.14 имеются еще две опции. Первая из них — Display Table обеспечивает немедленное автоматическое открытие таблицы после ее сохранения. Вторая опция — Add Data to New Table доступна в случае, если производилось не создание таблицы, а изменение ее структуры. Эта опция обеспечивает, что в измененную структуру из прежней таблицы перенесутся все данные, которые вписываются в новую структуру. Мы рассмотрели создание таблицы Paradox. Для других СУБД диалоги отличаются от рассмотренного и учитывают возможности различных СУБД. Однако эти отличия касаются только отдельных деталей, и рассматривать их мы не будем.
9.2.5 Изменение структуры и заполнение таблицы с помощью Database Desktop После того как вы создали таблицу, вы можете ее открыть командой File | Open. Впрочем, если при сохранении структуры таблицы вы использовали описанную выше опцию Display Table, то таблица откроется автоматически. В обоих случаях вы увидите окно вида, представленного ранее на рис. 9.4. С помощью разделов меню Table вы можете смотреть содержимое таблицы (команда Table View Data) или редактировать его (команда Table Edit Data). Впрочем, вряд ли это целесообразно делать. Программа Database Desktop автоматически не настраивается на русский язык (в книге [2] рассказано, как все-таки можно провести такую настройку), так что все, вводимое русскими буквами, выглядит абракадаброй. Впрочем, в дальнейшем при использовании такой таблицы в приложении все надписи будут выглядеть нормально. Команда Table | Info Structure позволяет просмотреть информацию о структуре таблицы, а команда Table Restructure позволяет изменить структуру таблицы или какие-то ее характеристики. При выполнении этой команды вы попадаете в окно, аналогичное используемому ранее при разработке структуры.
Приложения для работы с локальными базами данных
571
9.3 Создание и редактирование псевдонимов баз данных и каталогов Имеется три альтернативных пути просмотра, создания и редактирования псевдонимов с помощью трех различных программ: Database Desktop, BDE Administrator и Database Explorer. Рассмотрим их все, наиболее подробно остановившись на Database Desktop.
9.3.1 Автоматически создаваемые псевдонимы рабочего и частного каталогов Уже говорилось (см. разд. 9.1.1) о значении присваивания псевдонимов базам данных и каталогам. Но прежде, чем говорить о создании новых псевдонимов, надо упомянуть о двух псевдонимах, автоматически создаваемых BDE. Эти псевдонимы относятся к двум каталогам: рабочему (working) и частному (private). Рабочий каталог используется для совместной работы всех пользователей. Database Desktop создает его в момент установки в своем рабочем каталоге с путем ...\Program Files\Common Files\Borland Shared\Database Desktop\WorkDir в Delphi 7 или ...\Program Files\Borland\Database Desktop\WorkDir в более ранних версиях. Он имеет псевдоним WORK. Изменить рабочий каталог можно с помощью Database Desktop, выполнив команду File Working Directory. Откроется окно, приведенное на рис. 9.15. В нем вы можете задать новый рабочий каталог {Working Directory), или найти его поиском по кнопке Browse, или выбором из выпадающего списка Aliases — псевдонимы. При смене рабочего каталога псевдоним WORK автоматически будет подразумевать этот новый каталог. Если вы — единственный или основной пользователь Database Desktop, то полезно в качестве рабочего установить тот каталог, внутри которого или в подкаталогах которого сосредоточено большинство ваших баз данных. Это сократит время на открытие таблиц и другие операции, которые предлагают в качестве каталога прежде всего псевдоним WORK. Кроме того, как будет сказано ниже, полезно изменять рабочий каталог, чтобы иметь доступ к своим файлам конфигурации Database Desktop. Рис. 9.15
Установка псевдонима рабочего каталога
irecToy -[F. \DATA6AS ЕЩЕ PAR
States: |HdbP DC
-iJ Cancel
Hefc
Частный (private) каталог также создается автоматически при установке Database Desktop там же, где и рабочий каталог. Он служит для хранения личных таблиц и других объектов пользователя, не предназначенных для всеобщего обозрения. Он имеет псевдоним PRIV. Изменить частный каталог можно с помощью Database Desktop, выполнив команду File Private Directory. Откроется окно, аналогичное приведенному на рис. 9.15. В нем вы можете задать новый личный каталог. При этом псевдоним PRIV автоматически будет подразумевать этот новый каталог.
Глава 9
572
9.3.2 Создание и просмотр псевдонимов баз данных в Database Desktop Теперь обсудим создание новых псевдонимов баз данных. Могут создаваться псевдонимы двух видов: открытые, доступные при работе из любого каталога, и псевдонимы проекта, доступные только при работе в конкретном рабочем каталоге. Открытые псевдонимы сохраняются в каталоге ...\Borland\Borland Shared\Bde в файле IDAPI32.CFG в Delphi 5-7 или в каталоге ..ДBorland\Common Files\Bde в файле IDAPI.CFG в Delphi 4. Они доступны из любого рабочего каталога. Псевдонимы проекта сохраняются в файле в рабочем каталоге и доступны только из этого каталога. Так что вы можете в разных каталогах разместить файлы с разными псевдонимами проекта и в зависимости от того, какой каталог вы назначите рабочим, будете иметь разные наборы псевдонимов. Псевдонимы можно просматривать и создавать в Database Desktop, выполнив команду Tools | Alias Manager. Вы увидите диалоговое окно Alias Manager (диспетчера псевдонимов), вид которого представлен на рис. 9.16. Впрочем, вид этого окна существенно зависит от того, псевдоним какой базы данных просматривается, Индикатор Public alias (открытый псевдоним) в верхней части окна показывает, будет ли создаваться открытый псевдоним, или псевдоним проекта. Ниже расположен выпадающий список Daiabase Alias, в котором вы можете выбрать интересующий вас псевдоним из числа уже созданных. То, какие именно псевдонимы в нем видны, определяется группой радиокнопок справа. Если выбрана кнопка Show Public Aliases Only, то в списке отображаются только открытые псевдонимы; если выбрана кнопка Show Projecl Aliases Only, то отображаются только псевдонимы проекта; при выбранной кнопке Show All Aliases отображаются псевдонимы обоих типов. Рис. 9.16 Просмотр псевдонимов баз данных
i7 РцЫсАи
Datebssesis! .SERVER NAME' |FADATA8ASE\IBASEV USER КАМЕ: [s
5СНЕЫА CACHE |8 LANfiDRIVER SQLQRYMODE.
При выборе псевдонима в списке Database Alias автоматически изменяется тип драйвера в выпадающем списке Driver type и расположенная ниже информация о драйвере. На рис. 9.16 изображен случай базы данных INTERBASE с драйвером INTRBASE (аналогично выглядит экран и для любого драйвера SQL Link — ORACLE, SYBASE). В информации указывается: Server Name имя сервера
Задается путь и файл базы данных или имя сервера.
User Name имя пользователя
Задается имя пользователя.
Приложения для работы с локальными базами данных
573
Open Mode режим открытия
Возможные значения: READ/WRITE (для чтения и записи) или READ ONLY (только для чтения).
Schema Cache Size размер кэша схем
Определяет число каптируемых схем информации. По умолчанию — 5, может выбираться от 0 до 32.
La ngd river драйвер языка
Определяет множество отображаемых символов.
SQLqryrnode режим обработки запросов SQL
Может принимать значения: NULL (по умолчанию) — обращение сначала к серверу, а если он не может обработать запрос, то к Desktop, SERVER — только к серверу и, если он не может обработать запрос, то отказ, LOCAL — только к Desktop.
SQLpassthru Mode
Определяет, может ли пользователь Database Desktop иметь доступ к QBE и SQL редакторам в пределах одного соединения. Возможные значения: NOT SHARED — не может, SHARED AUTOCOMMIT (по умолчанию) может и результаты запроса SQL автоматически фиксируются в базе данных, SHARED NO AUTOCOMMIT — может, но результаты запроса SQL автоматически не фиксируются в базе данных.
password пароль
Пароль может задаваться, а может и не задаваться.
Кнопка Conned (соединение) позволяет немедленно соединиться с базой данных. Кнопка Remove (удаление) позволяет удалить псевдоним. Кнопка Save as позволяет сохранить список псевдонимов (если он изменялся) в файлах конфигурации заданного каталога. А кнопка New (новый) позволяет создать новый псевдоним. При щелчке на этой кнопке диалоговое окно несколько преобразуется (рис. 9.17). Правда, различие рис. 9.16 и рис. 9.17 объясняется не только этим, но и выбором разных драйверов. Основное же отличие — изменение кнопок (в частности, New меняется на Keep New — сохранить новый псевдоним) и возможность в списке Database Alias не только выбирать существующий псевдоним, но и писать новый. Рис. 9.17 Задание нового псевдонима базы данных
_yj
Alias Manager You are Innowrig from an enslmg
IV РнЫеа1в
о«ь™ *?!)ect
Edit
& ;
$BW Oeftors
-
ЦеЬ
-
Alt Database Aliases Databases
| Definitional dbP
Conli Duration I
b; C^ Databases
Definition] -|
Tjipe PATH
Etl ?o BCDEMOS ®-Tf BenchAccess92 ~ Ё- Tf dBASE Filet
STANDARD FWTABASESDBPAR ^j
НМЙ DBDEMOS ; ••« *P „| «I l H IV
—
JLnj.
.Database Location.
-_J
-.
.
.
.
Для драйвера STANDARD, используемого, в частности, для баз данных Paradox, набор характеристик псевдонима минимальный: Туре — имя драйвера и PATH — путь к базе данных. Щелкнув на параметре PATH, вы увидите кнопку с многоточием (рис. 9.18). При ее нажатии отобразится обычный диалог Windows, позволяющий выбрать новый каталог. Таким образом, вы можете указать характеристики псевдонима. Правда, если, например, изменилось расположение базы данных на диске и вам требуется изменить путь, указанный в псевдониме, то такое изменение невозможно, так как характеристики, имена которых указаны в правой панели окна рис. 9.18 жирным шрифтом, не подлежат изменению. Но вы можете
Приложенияjtmi работы с локальными базами данных
575
удалить прежний псевдоним, и создать новый с тем же именем, но другим путем. После этого все приложения, использующие этот псевдоним, автоматически будут работать с данными, даже не заметив изменения их месторасположения. Для создания нового псевдонима надо выполнить команду Objecl | New или щелкнуть правой кнопкой мыши на вершине Databases в левой панели окна рис. 9.18 и во всплывшем меню выбрать раздел New — новый псевдоним. Перед вами появится небольшое диалоговое окно, показанное на рис. 9.19. Б его выпадающем списке ВЫ должны выбрать драйвер для создаваемого псевдонима. Рис. 9.19 Диалоговое окно выбора драйвера для нового псевдонима
Тип драйвера STANDARD можно использовать для таблиц Paradox, dBASE, FoxPro, для текстов ASCII. Выбрав драйвер, щелкните на ОК, и в дереве псевдонимов появится новая вершина, для которой вы можете задать имя — псевдоним. В правой панели появятся параметры, которые вы должны установить для создаваемого псевдонима. Для типа STANDARD эти параметры следующие: PATH
Путь к базе данных.
DEFAULT DRIVER
Один из указанных в предыдущей таблице драйверов: PARADOX для таблиц Paradox (файлов .db), DBASE для таблиц dBASE (файлов .dbf), FOXPRO для таблиц FoxPro (файлов ЛЬП, ASCIIDRV для текстов ASCII (файлов -Ш).
ENABLE BCD
Определяет, должна ли ВОЕ транслировать числовые поля в значения с плавающей запятой, или в коды BCD. BCD позволяет избежать ошибок округления. Если ENABLE BCD = true, то поля DECIMAL и NUMERIC преобразуются в коды BCD.
После задания параметров щелкните правой кнопкой мыши и из всплывшего меню выберите раздел Apply. Новый псевдоним будет зафиксирован. Для удаления существующего псевдонима выделите его в левой панели, щелкните правой кнопкой мыши и из всплывшего меню выберите раздел Delete, Мы рассмотрели страницу Dolabases окна Администратора BDE. Страница Configuration (конфигурация) показана на рис. 9.20. На этой странице в ее левой панели вы можете выбрать драйвер из группы Native — собственные («родные») драйверы различных систем, или из группы ODBC — драйверы ODBC. Для выбранного драйвера в левой панели можно изменить какие-то из его параметров. Вершины дерева в левой панели Date, Time и Number позволяют установить системные параметры, определяющие, как преобразовываются строки с обозначением дат, времени и чисел в соответствующие значения. Параметры такого преобразования не вызывают особых сложностей и вы можете посмотреть их в контекстной справке Администратора BDE. При нажатии клавиши F1 всплывает тема справки, соответствующая вершине, выделенной вами в этот момент в левой панели.
9.3.4 Создание и просмотр псевдонимов в SQL Explorer Вызов этой программы осуществляется из главного меню Delphi команде Database I Explore. Окно программы SQL Explorer представлено на рис. 9.21. В
576
Рис. 9.20 Страница Configuration Администратора BDE
Глава 9 [*;м>Е Administrate» D:\PrantJim RJ«iCnm™r<
ПВМЕ
Object . £dt Vjafi Cations Це1р
p;_Xj^£l_ ; Owen arid System Daiabases
JDefinttonofOaW Defbtion j
CoruguvlH" [
FOURDIGITYEAR LEADINGZEROD LEADINGZEROM MODE SEPARATOR YEARBWSED
c.-Qj Drivers ;
ffl ^ ODBC
"d иs*i!lem
FALSE FALSE FALSE 0 / TRUE
В Ж Formal^
OiemtiiDate
f
Time Nimba
,
.
.
^
левой панели на странице Databases вы можете видеть дерево зарегистрированных псевдонимов. Выделив интересующий вас псевдоним (на рис. 9.21 это псевдоним dbP), вы увидите на правой панели его параметры. При желании вы можете их изменить. Рис. 9.21 Окно программы SQL Explorer просмотр параметров псевдонима
7» SW. EK|)h»« Object ее&опаг/ Ed* Jjew Optttn Help |"&
X
Em«SQLJ H ^ D^atases
lyne PATH
л ;g ecoEMos ,T 'g' ВепсгАсеекЭг 2 "^ dBASEFtes •-- !@ DBDEM05
STANDARD F:\EWTABAS£\OePAR
_
а о'ДЕЗ
.-J ^g Tables & Щ CURRENCY DB i В DEPDB li И DerbDB ,j
: — : =-: tararjttP.
,
^= — : ^W —
^^^^-^-^'.^='^=^=.-=^ •' , ^
Выделив корневой узел в левой панели, вы можете .С помощью команды Objecl New создать новый псевдоним. При этом вначале вам будет показано окно выбора драйвера, приведенное ранее на рис. 9.19 при рассмотрении работы с Администратором BDE. Все дальнейшие действия не отличаются от тех, которые необходимы при создании псевдонима с помощью Администратора ВСЕ. Так что посмотрите разд. 9.3.3, в котором вся эта процедура описана достаточно подробно. Однако возможности SQL Explorer по просмотру и модификации баз данных не ограничиваются модификацией существующих и созданием новых псевдонимов. Вы можете выбрать в левой панели интересующий вас псевдоним базы данных, и просмотреть, что хранится в этой базе данных (таблицы, индексы и т.п.), просмотреть таблицы (этот момент отображен на рис. 9.22). При этом в правой панели на странице Definition вы можете просмотреть общую информацию О таблице, на странице Тех! вы можете увидеть текст запроса SQL (см. гл. 10), создавшего данную таблицу (если использовался драйвер SQL). На странице Data вы можете просмотреть хранящуюся в таблице информацию. При этом кнопки навигатора ввер-
Прип ожени я для работы с локальными базами данных
577
ху диалогового окна позволяют вам редактировать записи, вставлять новые записи и т.п. На странице Enter SQL можете сформировать в выполнить запрос к таблице. Рис. 9.22 Окно программы SQL Explorer — просмотр данных в таблице Pers
:1й|х|
Al Database Aliases
Conlsnlt ol PERS DS
;
Databases ,
ОоМют Data |EmeSQL| |Farri Num [Deo 1 Бухгалтера Иванов Петров 2 U«1 3 Цек? Сидоров 4 llsiil Иванова 5 '---i-^,. • - Hl*dHMB 6 Пек 2 Андреев 7 Uexl Борисов E Uexl Павпов I
,»s НИ CURRENCY.I I '*! Ш DEP.DB .4 SI D.cdb DB -J
QJ
jWam Иван Петр Снаор Hpwa Андрея Борис Павел
(Par Иванович Петрович СЩОРСГБИЧ
И&вноеиа Ниюпаевт Андреева БорисоБич Пйелое4*ч
,1
i. U
•6«em5fiPERS.DB.
Вы можете продвинуться далее и раскрыть структуру таблицы. На рис. 9.23 изображен просмотр поля Dep таблицы Pers, причем вы можете изменить соответствующую информацию. Рис. 9.23 Окно программы SQL Explorer просмотр объявления поля Dep
Obptt
Btttonary E*
Ottiore
Д1Ва1аЬ4мА6iical Length 15
-: в PERS.DB
^ га Ni^n LU — __
,,.....J
a tens In Dep.
С помощью команды Object New вы можете в зависимости от того, на каком уровне просмотра дерева информации вы находитесь, создать новый псевдоним, или новую таблицу, или новое поле существующей таблицы. Программа SQL Explorer имеет еще много возможностей, которые будут рассмотрены в дальнейшем. А пока, как итог рассмотрения основных операций по созданию и сопровождению таблиц локальных баз данных, опробуйте все это, создавая две базы Pers a Dep, описанные в разд. 9.1 в табл. 9.1 и 9.2. Задайте каталогу, s который вы поместите файлы этих таблиц, псевдоним dbP. Это необходимо для работы с примерами приложений, которые мы будем рассматривать далее в этой главе. А теперь перейдем к построению приложений для работы с базами данных.
Про|-раммнрованне в Delphi 7
578
Глава 9
9.4 Обзор компонентов, используемых в ВОЕ для связи с базами данных Размещение компонентов для работы с базами данных на страницах библиотеки VCL в Delphi 7 и 6 существенно отличается от предшествующих версий Delphi. В версиях, младше Delphi 6, компоненты, обеспечивающие доступ к данным через BDE, расположены на странице Dalo Access. В Delphi 7 и 6 на этой странице остался только компонент DataSource, а остальные перенесены на страницу BDE. Компоненты отображения и редактирования данных во всех версиях размещены на странице Dale Control. Каждое приложение, использующее базы данных, обычно имеет, по крайней мере, по одному компоненту следующих трех типов: • Компоненты — наборы данных (data set), непосредственно связывающиеся с базой данных. Для BDE это такие компоненты, как Table, Query, StoredPгос. Для других технологий, рассмотренных в гл. 10, имеются аналогичные компоненты наборов данных. • Компонент — источник данных (data source), осуществляющий обмен информацией между компонентами первого типа и компонентами визуализации и управления данными. Таким компонентом является Data Source. • Компоненты визуализации и управления данными, такие, как DBGrid, DBText, DBEdit и множество других. Связь этих компонентов друг с другом и с базой данных можно представить схемой, приведенной на рис. 9.24.
таблица базы данных
doio set: Table, Query или StoredP гас
data source: Data Source
визуализация и управление: DBGrid, DflTexl, DBNovigalor ...
Рис 9.24. Схема взаимодействия компонентов Delphi с базой данных
Помимо указанных компонентов, в приложении может размещаться компонент Database. Этот компонент в основном используется в приложениях, работающих на платформе клиент/сервер. Он организует общение с удаленным сервером, реализует транзакции, работает с паролями. Компонент Database целесообразно вводить в приложение только в сравнительно редких случаях. Если он не введен язно, Delphi автоматически создает его для каждой используемой в приложении базы данных. Подробнее этот компонент будет рассмотрен в разд. 10.2.2 следующей главы. Еще один компонент, который тоже автоматически создается Delphi — компонент Session. Это главный компонент любого приложения, работающего с базами данных. Но в явном виде эти компоненты имеет смысл вводить только в многозадачные приложения, в которых параллельно обрабатывается несколько потоков информации. Компонент Session будет рассмотрен в разд. 9.8.
Приложения для работы с локальными базами данных
579
9.5 Основные свойства компонента Table и простейшие приложения на его основе 9.5.1 Установка связей между компонентами и базой данных, навигация по таблице Давайте построим простейшее приложение, работающее с базой данных. Будем использовать ту таблицу Paradox Pers, которую вы создавали ранее в базе данных с псевдонимом dbP. Она имеется на прилагаемом к книге диске. В нашем простейшем приложении мы будем в качестве набора данных использовать компонент Table. Откройте новое приложение и перенесите на форму компонент Table со страницы библиотеки ВОЕ. Перенесите также на форму со страницы Data Access компонент DataSource, который будет являться источником данных. Оба эти компоненты невизуальные, пользователю они будут не видны, так что их можно разместить в любом месте формы. В качестве компонента визуализации данных возьмите компонент DBGrid со страницы Dalo Control. Это визуальный компонент, в котором будут отображаться данные формы. Поэтому растяните его пошире, или можете в его свойстве Align установить alClient (рис. 9.25). Рис. 9,25
Форма простого приложения, работающего с базой данных
Теперь нам надо установить цепочку связей между этими компонентами, показанную выше на рис. 9.24. Главное свойство DBGrid и других компонентов визуализации и управления данными — DataSource. Выделите на форме компонент DBGridl и щелкните на его свойстве DataSource в Инспекторе Объектов. Вы увидите выпадающий список, В котором перечислены все имеющиеся на форме источники данных. В нашем случае имеется только один источник данных — DataSourcel. Установите его в качестве значения свойства DataSource. Далее надо установить связь между источником данных и набором данных. Выделите компонент DataSourcel и найдите в Инспекторе Объектов его главное свойство — DataSet. Щелкните на этом свойстве и из выпадающего списка выберите Tablel (если бы у вас было несколько компонентов — наборов данных, то все они были бы в этом списке). Теперь осталось связать компонент Tablel с необходимой таблицей базы данных. Для этого служат два свойства компонента Table: DatabaseName н TableName. Прежде всего надо установить свойство DatabaseName. В выпадающем списке этого свойства в Инспекторе Объектов вы можете видеть все доступные BDE псевдонимы баз данных. Выберите из этого списка псевдоним dbP, который вы сами ввели ранее. Если этого псевдонима там нет, значит вы забыли его создать. В этом случае создайте его с помощью, например, Database Desktop, как описано в разд. 9.3.2. После этого можно устанавливать значение свойства TableName. В выпадающем списке этого свойства перечислены таблицы, доступные в данной базе данных. Выберите таблицу Pers. I*.
Глава Э
5SO
А теперь наступает самый ответственный момент. Вы можете прямо в процессе проектирования соединиться с базой данных. Соединение осуществляется свойством Active. По умолчанию оно равно false. Установите его в true. Если все сделано вами правильно, то вы увидите в поле компонента DBGridl данные из таблицы (рис. 9.26) и сможете просмотреть их. . Рис. 9.26 форма простого приложения после соединения компонента Table с базой данных
3 Ия2 4 5 Бучгаягерия
Б Ц* 2 7 U ВЦяГ 3 Бугаигерня
Вы можете прямо сейчас, хотя приложение еще не закончено, сохранить проект, запустить его на выполнение и убедиться, что с ним можно работать. Вы можете просматривать данные, редактировать их (редактированные данные будут помещаться в базу данных в момент перехода от редактируемой записи к любой другой). Вы можете также убедиться, что в таблице Регз базы данных dbP невозможно изменить поле Num, поскольку оно автоматически изменяется и доступно только для чтения (см. разд. 9.2.2). Вы увидите также, что нельзя задать произвольное имя подразделения Dep, поскольку имеется таблица просмотра Dep, задана целостность на уровне ссылок и допустимы только те значения Dep, которые имеются в головной таблице Dep (см. разд. 9.2.3.2 и 9.2.3.4). Правда, еслк вы хотите проверять все эти возможности, то надо бы отключить остановы при генерации исключений, выключив опцию Stop On Delphi Exceptions на странице Language Exceptions в окне опций отладки (см. разд. 2.9.8). Следует сразу отметить, что заранее выставлять для таблиц Active = true допустимо только в процессе настройки и отладки приложения, работающего с локальными базами данных. Хороший стиль программирования В законченном приложении во всех таблицах сначала должно быть установлено Active = false, затем при событии формы OnCreafe эти свойства могут быть установлены в true, о при событии формы OnDestroy эти свойства опять должны быть установлены в false. Это исключит неоправданное поддержание связи с базой данных, которое занимает ресурсы, а при работе в сети мешает доступу к базе данных других пользователей.
Разрешить пользователю так просто редактировать данные в таблице в большинстве случаев недопустимо. Слишком велика вероятность, что без соответствующей проверки вводимых данных будут сделаны ошибки. Предотвратить редактирование данных вы можете, установив свойство ReadOnly компонента DBGridl в true. Другой способ сделать то же самое — установить в свойстве Options подсвойство dgEditing в false. Попробуйте оба варианта и вы увидите некоторое различие между ними во время выполнения. В свойстве Options есть еще много полезных подсвойств. Посмотрите сведения о них во встроенной справке Delphi или в [4]. Отметим еще одно свойство компонента Table — Exclusive. Это свойство определяет доступ к используемой таблице при одновременном обращении к ней нескольких приложений (например, при работе в сети или в многозадачном режи-
Приложения для работы с локальными базами данных
581
ме). Если задать значение этого свойства true, то таблица будет закрыта для других приложений. Свойство можно изменять только при Active = false. В спроектированное вами простейшее приложение можно добавить еще один компонент, управляющий работой с таблицей — навигатор DBNavigator, расположенный на странице Data Corvtrol библиотеки компонентов. Измените свойство Align компонента DBGridl на alBoUom, сдвиньте верхний край этого компонента немного вниз и на верх формы поместите компонент DBNavigator (см. рис. 9.27). Рис. 9.27 Форма простого приложения с навигатором
i.,-
IN«I 1 Ь^алг^рня
Иване*
Ива.
2Це*1
Пгтрм
Пего
3 Ц*'2
ELJ
fjdOD
4 Цы1
Иванова
5 Бухгалтерий 6 Цех 2
И «она»
Николай
Андреев
Ашрей
7 Цех!
i
-
Борис
Компонент имеет ряд кнопок, служащих для управления данными. Перечислим их названия и назначение, начиная с левой кнопки: nbFirst
перемещение к первой записи
nbPrior
перемещение к предыдущей записи
nhNext
перемещение к следующей записи
nbLast
перемещение к последней записи
nblnsert
вставить новую запись перед текущей
nbDelcte
удалить текущую запись
nbEdit
редактировать текущую запись
nbPost
послать отредактированную информацию в базу данных
nbCancel
отменить результаты редактирования или добавления новой записи
nb Re fresh очистить буфер, связанный с набором данных Пользуясь свойством навигатора VisibleButtons, можно убрать любые ненужные в данном приложении кнопки. Например, если вы не хотите разрешить пользователю вводить в базу данных новые записи, то можете установить в false кнопку nblnsert. Если вы хотите вообще запретить редактирование, то можно оставить только кнопки nbFirst, nbPrior, nbNext и nbLast, а все остальные убрать. Чтобы приложение с навигатором работало, надо установить его основное свойство — DataSource — источник данных (имя компонента DataSource). Отметим еще одно свойство навигатора — Hints. Это список строк типа TStrings, содержащий тексты всплывающих ярлычков кнопок навигатора. По умолчанию эти тексты, конечно, английские. Щелкнув на кнопке с многоточием около свойства Hints в окне Инспектора Объектов, вы можете перевести эти тексты на русский язык. Поскольку навигатором вы, наверняка, будете пользоваться во многих своих приложениях, имеет смысл один раз осуществить подобный перевод и далее сохранить навигатор в виде шаблона (см. разд. 8.2), который можно будет использовать в последующих приложениях.
582
Глава 9
Чтобы грамотно оформить наше первое, пока еще очень несовершенное приложение, работающее с базой данных, давайте добавим в него соединение с базой данных в момент начала работы и разрыв этого соединения в момент окончания работы. Выделите форму, перейдите в Инспекторе Объектов на страницу событий и в событие формы OnCreate вставьте обработчик вида: procedure TForml.ForrnCreate(Sender: begin Tablel.Active := true;
TObject];
end;
Аналогичным образом в событие формы OnDestroy вставьте обработчик вида: procedure TForral.FormDestroy(Sender: TObject); begin Tablel-Active := f a l s e ; end;
Теперь в момент создания формы ваше приложение будет соединяться с базой данных, а в момент разрушения формы соединение будет прерываться. Б компоненте Table! следует установить свойство Active в false, чтобы не занимать базу данных в процессе проектирования. Правда, если вы работаете не в сети или если вы единственный пользователь этой базы данных, то это не имеет особого значения. Но все-таки лучше сразу привыкать к грамотной работе. Откомпилируйте приложение, выполните его и посмотрите в работе. Пользуясь последовательно кнопками nbEdit и nbPost, вы можете редактировать данные. Если вы отредактировали текущую запись, а затем передумали заносить изменения в базу данных, то можете нажать кнопку nbCancel, и результаты редактирования будут отменены. Кнопка nblnsert позволит вам ввести в базу данных новую запись. Эта запись зафиксируется в таблице после нажатия кнопки nbPost,
9.5.2 Свойства полей Наше приложение выглядит, конечно, очень плохо. Во-первых, последовательность записей определяется ключевым полем Num, а хотелось бы, чтобы записи были расположены по алфавиту или по отделам и алфавиту. Первое поле с номерами записей вообще пользователю не нужно и надо бы, чтобы его не было видно. Шапка таблицы содержит непонятные пользователю имена полей Num, Fam и т.д., а надо, чтобы были написаны нормальные заголовки по-русски. Все это можно легко поправить. Начнем с упорядочивания записей. Выделите на форме компонент Tablel. В Инспекторе Объектов вы увидите среди прочих свойства IndexName и IndexFieldName. Первое из них содержит выпадающий список индексов, созданных для вашей таблицы. Выберите, например, индекс fio, и увидите, что записи окажутся упорядоченными по алфавиту, поскольку в этот индекс включены поля Fam, Nam и Par. При индексе depfio упорядочивание будет по подразделениям, а внутри каждого подразделения — по алфавиту. Альтернативный вариант индексации предоставляет свойство IndexFieldName. В его выпадающем списке просто перечислены предусмотренные е индексах комбинации полей и вы можете выбрать необходимую, если забыли, что обозначают имена индексов. Теперь займемся отдельными полями. Для их редактирования служит Редактор Полей. Вызвать его проще всего двойным щелчком на компоненте Tablel. Сначала вы увидите пустое поле этого редактора (рис. 9.28 а). Щелкните на нем правой кнопкой мыши и из всплывающего меню выберите раздел Add fields (добавить поля). Вы увидите окно, изображенное на рис. 9.28 б, в котором содержится список всех полей таблицы. Выберите из него курсором мыши интересующие вас полл. Если вы при этом будете держать нажатой клавишу Ctrl, то может выделить любую комбинацию полей. Однако имейте в виду, что только к тем полям, которые вы добавите, вы сможете в дальнейшем обращаться. Так что а данном случае
583
Приложения^ для^рабдты^локальнь1ми^азами данных
вам имеет смысл выделить все поля, кроме Charact, Photo и, может быть, Num. Выделив поля, щелкните на ОК, и вы вернетесь к основному окну Редактору Полей, но в нем уже будет содержаться список добавленных полей (рис. 9.28 в).
а)
6)
B) Hum
Pep Fatn
Pom i.?-Ps Yea Ь Sn
Nam Pa
Yearb
Owed
Cancel
Help
Рис. 9.28. Редактор Полей: исходное состояние (а), окно выбора полей (б), состояние после выбора (в)
Эти поля будут соответствовать колонкам таблицы. Изменить последовательность нх расположения можно, перетащив мышью идентификатор какого-то поля на нужное место. Как мы увидим далее, те поля, которые не должны отображаться в таблице, могут быть сделаны невидимыми. Выделите в списке какое-то поле и посмотрите его свойства в Инспекторе Объектов. Вы увидите, что каждое поле — это объект, причем его класс зависит от типа поля: TStringField, TSmallintField, TBooleanField и т.п. Все эти классы являются производными от TField — базового класса полей. Таким образом, каждое поле является объектом и обладает множеством свойств. Рассмотрим основные из них, которые чаще всего необходимо задавать. Свойство Alignment определяет выравнивание отображаемого текста внутри колонки таблицы: влево, вправо или по центру. Свойство DisplayLabel соответствует заголовку столбца данного поля. Например, для поля Fam значение DisplayLabel можно задать равным "Фамилия", для Nam — "Имя" и т.д. Свойство DisplayWldth определяет ширину колонки — число символов. Свойства EditMask для строк и EditFormat для чисел определяют форматы отображения данныхСвойств о Readonly, установленное в true, запрещает пользователю вводить в данное поле значения. Свойство Visible определяет, будет ли видно пользователю соответствующее поле. В нашем примере, вероятно, можно задать Visible = false для поля Num, если вы его не исключили из списка полей Tablet. После установки всех необходимых свойств приложение уже приобретает приемлемый вид (рис. 9.29).
9.5.3 Перенос полей на форму из Редактора Полей Отметим еще одну особенность Редактора Полей — возможность перетаскивать из него поля на форму с помощью мыши. Чтобы опробовать эту возможность, сохраните приложение, над которым вы работали (оно нам еще потребуется) и начните новое приложение. -
Глава 9
584
Рис 9.29 Приложение с установленными свойствами полей
(7'Forr»!
ШЯВ! '-I 'I-
ОТДЕЛ i гТОЯп]
м£*
(Фамилий |Имя
*| '
! Отчество
Дкгоччна /1нтсмов«л Иван Иванович
jyxraTTff •ид Иванов
-
—
Ечхгапта лич Николаев Цек! Бормсое Цея1 Иванова Ue*1 Плепов Цвк1 Петров
Николай Николаевич Бооиссвнч Ивановна Павлович Павел Борис Ирнпа Петр
Петрович
;
-Н.
|гр. 1Э55 1950
1330
JlsJ-t, ж м . М
_ц_
1ЭЭ7 м 1961
л
1375 м 1Э60
м
Поместите на форму компонент ТаЫе и свяжите его с таблицей Pers базы данных dbP так же, как вы делали это в предыдущем приложении. Сделайте двойной щелчок на Tablel, затем щелкните правой кнопкой мыши в окне Редактора Полей (рис. 9.28 а) и из всплывшего меню выберите раздел Add all fields — добавить все поля. В окне появятся имена всех полей таблицы. Установите их свойства DisplayLabel так, как вы делали это в предыдущем приложении. А теперь щелкните правой кнопкой мышя и из всплывшего меню выберите раздел Selecl all — выделить все поля. Все поля окажутся выделенными. Перетащите их мышью на форму. Вы увидите, что на форме будут автоматически созданы компоненты, отображающие данные каждого поля и снабженные метками, которые вы указали в свойствах DisplayLabcl полей. Остается только разместить их должным образом на площади формы, установить в компоненте Tablel свойство Active в true, добавить навигатор и приложение готово к работе (см. рис. 9,30).
Рис. 9.30
7- Forml
Приложение, сформированное перетаскиванием из Редаюора Полей
:
Фамилия
Имя
|Иванов
|Ив
Отчество
X арактермлнка Характеристика Иванова Ивана Ивановича, 1Э50г.р.. сотрудника бухгалтерии И.И. Иванов отличный работник. Начальник О К Иванов
И.И.
Присмотритесь внимательнее к тем компонентам, которые создались на форме. Вы можете увидеть, что на форме появился источник данных DataS(mrcel. связанный своим свойством DataSet с набором данных Tablel. Кроме того на форме разместились 7 окон редактирований DBEdit, отображающие данные полей Num, Dep, Fam, Nam, Par. Year_b и Sex. Это компоненты, имеющие в основном те же свойства, что и обычные окна редактирования Edit, но связанные с данными. Эта связь устанавливается свойством DataSource. Посмотрите это свойство, и вы увидите, что оно автоматически установилось равным DataSourcel. Значение свойства DataField каждого компонента соответствует имени поля, отображаемого компонентом.
Приложения для работы с локальными базами данных
585
В окнах будут отображаться значения соответствующих полей текущей записи. А если вы во время работы приложения измените текст в окне, то соответствующее изменение будет занесено в базу данных. Одно из отличий компонентов DBEdit от Edit заключается в том, что в Инспекторе Объектов для DBEdit вы не увидите основного свойства окна редактирования — Text, обозначающего текст в окне. Это не значит, что у DBEdit этого свойства нет. Оно есть, но не объявлено как published — отображаемое в окне Инспектора Объектов. Вы можете программно во время выполнения устанавливать и читать свойство Text, но не можете установить его во время проектирования. Для отображения поля Charact на форме разместился компонент DBMcmol. Это связанное с данными многострочное окно редактирования, аналогичное обычному компоненту Memo, Поле Photo отображается в компоненте DBImagel — связанном с данными аналоге обычного компонента Image, используемого для просмотра и редактирования изображений. Отличие DBMemo и DBImage от их аналогов Memo и Image заключается в том, что их свойства, связанные с отображаемой информацией (соответственно Lines a Picture) отсутствуют в окне Инспектора Объектов. Эти свойства можно читать или устанавливать только во время выполнения, Если бы у нас в таблице было булево поле (вы можете посмотреть это на примере таблицы Dep), то для его отображения на форме создался бы компонент DBCheckBox. Это индикатор, аналогичный обычному индикатору CheckBox, но связанный с источником данных своим свойством DataSource. В этом компоненте будет отображаться флажок, если значение поля в текущей записи равно true. Если во время выполнения приложения вы установите или снимете этот флажок, соответствующее изменение будет произведено в текущей записи таблицы. Булево поле можно было бы также отобразить компонентом DBRadioGroup — группой радиокнопок. Этот компонент мы рассмотрим позднее. Опробуйте ваше автоматически сгенерированное приложение в работе. Можете воспользоваться им, чтобы заполнить поля Charact — характеристика и Photo — фотография. Характеристику можете просто написать в соответствующем окне и сохранить ее кнопкой nbPost навигатора. А для заполнения поля Photo фотографиями можно воспользоваться буфером обмена Clipboard. Откройте с помощью любой программы (например, с помощью программ Windows «Проводник», *Paint» и т.п.) подходящий графический файл, сохраните его в буфере обмена (горячие клавиши Grl-C), перейдите в ваше работающее приложение, выделите компонент DBImagel и нажмите горячие клавиши Clrl-V — вставить из буфера обмена. В окно фотографии перенесется соответствующее изображение. Кнопкой навигатора nbPost можете зафиксировать этот результат в базе данных. Конечно, рассмотренный в данном разделе упрощенный вариант создания приложения перетаскиванием из Редактора Полей не является оптимальным. Действительно удобное и грамотно построенное приложение можно создать только в результате целенаправленного проектирования с использованием всех возможностей компонентов Delphi, Однако такой упрощенный вариант иногда полезен, если вам нужно просто посмотреть, что находится в каких-то неизвестных вам таблицах. В качестве примера попробуйте создать приложение для просмотра таблицы bioiife.db поставляемой с Delphi демонстрационной базы данных DBDEMOS. Создайте описанным выше элементарным путем подобное приложение. Оно показано на рис. 9.31, но для большей ясности английские надписи в нем заменены русскими. Вы сможете полюбоваться изображениями различных экзотических рыб. Только, вероятно, следует установить свойство Readonly компонента Table в false, чтобы случайно не испортить демонстрационную таблицу. Впрочем, если вы достаточно хорошо знаете английский язык, можете этого не делать, а воспользоваться данным приложением, чтобы перевести описания рыб на русский язык.
Глава 9
586 Рис. 9.31 Пример приложения для просмотра базы данных
7- Просмотр базы данный И эойра кенн с Класс ; G г OUPCP Семейство |N а»аи
Epinephelui striah» Л чина (см)
Описание -ourid around i
coral ieefi and igagrait bffdj, fee
W*. Thii it (he moil Irtandry of el дюирпв. If offered foodL it «wi return •igajn and again, looking lor more Ai л defen», the Naiiau pauper can change crJori to tfend perfectly Bermuda, Noilh Carolina to Brazil. end the GuH ol Mexico.
9.5.4 Ограничения вводимых значений Теперь давайте вернемся к приложению, которое мы создали ранее в разд. 9.5.2, и опробуем возможности его дальнейшего совершенствования. Займемся проблемой ограничения вводимых пользователем значений полей. Ранее в разд. 9.2.3.1 мы обсуждали вопрос об ограничении диапазона возможных значений полей при создании таблиц. Однако Delphi предусматривает еще разнообразные возможности дополнительного ограничения значений в приложении. Дело в том, что в таблице устанавливаются обычно достаточно широкие пределы, пригодные для любых ее применений. А в конкретном приложении могут потребоваться более узкие пределы. Несколько возможностей ограничения предоставляют свойства полей, которые вы можете увидеть, сделав двойной щелчок на компоненте Table и выделив в окне Редактора Полей требуемое поле. Для числовых полей имеются свойства MinValue и MaxValue, устанавливающие допустимые пределы вводимых в поле значений. Например, если вам требуется принимать на работу сотрудником только в узком возрастном диапазоне, вы можете для поля Year_b установить ограничения Min Value = 1970 и MaxValue = 1980, При нарушении этих пределов будет генерироваться исключение EDatabaseError, которое лучше перехватывать в приложении (см. разд. 11.10), чтобы выдать пользователю понятное пояснение на русском языке. Другая возможность ограничения — использование свойств Custom Constraint и Const rain t Err orMessage. Свойство CustomConstraint позволяет написать ограничение на значение поля в виде строки SQL (см. разд. 10.1). Например, для того же поля Year_b вы можете написать в свойстве CustomConstraint: х < 1980 and X > 1970
Имя поля (в данном выражении оно обозначено как X) может быть произвольным. Если вы задали свойство CustomConstraint, то необходимо задать и свойство ConstraintErrorMessage. Оно содержит строку текста, который будет показан пользователю в случае, если он вводит данные, не удовлетворяющие поставленным ограничениям. Например, «Возраст претендента на должность не подходит». Таким образом, этот вариант обычно удобнее задания Min Value и MaxValue, поскольку не требует перехвата исключений. К тому же он может применяться не только к числовым полям.
Приложения для работы с локальными базами данных
587
Еще одна возможность проверять данные на уровне поля — использовать обработку события поля OnValidate, Это событие возникает перед записью введенного значения поля в буфер текущей записи. Тут можно предусмотреть любые проверки, при появлении недопустимых данных выдать пользователю сообщение и,. например, сгенерировать исключение EAbort функцией Abort. Если асе нормально, то после события OnValidate возникает еще событие OnChange, в обработчике которого тоже еще не поздно сгенерировать исключение. Описанные выше способы проверки данных относятся к конкретному полю. Имеется также возможность осуществлять проверку на уровне записи, анализируя различные ее поля. Для этого служит свойство Constraints компонента Table. Нажмите кнопку в этом свойстве, и перед вами откроется окно, представленное на рис. 9.32. Щелкая в нем на кнопке Add, вы можете занести в свойство Constraints набор ограничений. Каждое из них представляет собой самостоятельный объект. Выделив одно из ограничений, вы увидите в Инспекторе Объектов его свойства. Свойство Custom Constraint представляет собой строку SQL, определяющую допустимые значения. Свойство ErrorMessage определяет строку текста, которая будет предъявлена пользователю в случае нарушения ограничений. Например, вы можете написать в свойстве Cos t omC oust rain t: p
' м ' ) and (Year_b >1955) ) cc ( {Sex='sc ) and (Ysar_b>1965) )
а в свойстве ErrorMessage: «Принимаем только мужчин >1955 г. р. и женщин >1965 г. р.». Это обеспечит вам различные границы отбора по возрасту для мужчин и женщин. Рис. 9.32 Окно редактирования ограничений для записи
% Editing )abtel.Conitra*nUt
•о
Для комплексной проверки данных можно также использовать различные события компонента Table, но на этом мы остановимся в последующих разделах при рассмотрении методов программирования работы с данными.
9.5.5 Вычисляемые поля Теперь попробуем сформировать в таблице новое поле, не предусмотренное при ее создании, значение которого вычисляется на основании значений других полей записи. Подобные поля называются вычисляемыми полями (calculated f i e l d s ) . Пусть в нашем примере мы хотим добавить поле, вычисляющее возраст сотрудника по его году рождения. Сделайте двойной щелчок на Tablet, чтобы вызвать Редактор Полей. Щелкните в Редакторе Полей правой кнопкой мыши и во всплывшем меню выберите раздел New (новое поле). Появится окно добавления нового поля, приведенное на рис. 9.33. В разделе Field properties (свойства поля) вы должны указать имя поля (Name) — в нашем случае назовем это поле Age, тип данных (Туре) — в нашем случае это Sm'allinl, и для некоторых типов — размер (Size). Размер указывается для строк и других полей неопределенных размеров. После ввода всех данных проверьте, переключилась ли группа радиокнопок Field type но Calculated (это переключение делается автоматически). Затем щелкните на ОК и вы вернетесь в окно Редактора Полей, причем там появится новое поле Аде. Задайте для него в Инспекторе Объектов значение Display Label равным «Возраст».
Глава 9
588
Рис. 9.33 Ввод информации о вычисляемом поле
• field pfopeiQes" yams:
In*
ComponerJ- [TablelAgeZ
JAjie .
'
FieHlype Г Qala
Г
соответственно. Правда, этот оператор рассчитан только на работу в 2001 году и в дальнейшем его придется ежегодно менять. Можно, добавив в обработчик пару строк, сделать расчет возраста универсальным. Это делает следующий обработчик: procedure TForml.TableICalcFields(DataSet: TDataSet); var Year, Month, Day: Word; begin DecodeDate(Date,Year,Month,Day); TablelAge.Value := Year - TablelYear_b.Value; end;
В этом коде введены переменные Year, Month и Day для хранения текущего года, месяца и дня. Использована процедура DecodeDate для преобразования своего первого аргумента, имеющего тип TDateTirae (этот тип используется в Delphi для хранения дат и времени), в целые значения года, месяца и дня. А в качестве первого аргумента этой процедуры указана функция Date, возвращающая текущую дату. В результате переменная Year становится равной текущему году (переменные Month и Day нам не нужны и определены только для того, чтобы можно было обратиться к процедуре DecodeDate). Запустите приложение и посмотрите, как оно теперь выглядят (рис. 9.34). Рис. 9.34 Приложение с введенном вычисляемым полем «Возраст»
Г
Отпэ>1
'1 '
Фамилия
^ Бухгалтерии Антонова Бухгалтерия Пеанов Бч*га1Тгерня НИКОЛАМ _ Цеч 1
__L£J
"1 " ^ - . - . . _
Лма
[Отчество
го. |Пол|Вс5расг -
Алгоич-а Алгоновнз
1955 *
48
Иван
1350 м
53
Иванович
Николай Николаевич 133D м
73
Борисов
Борис
Борисович
.1937 ч
66
Цех1
Иванова
Ирина
Ивановна
19^1 *
42
JBK 1
Пяте
Павел
Павл>св1рн
1975 м
28
Петров
Петр
Петрович
1Э60 м
43
JllexT
__
Приложения^
для
работы
с
локальными
базамиданных
_
589
9.5.6 Фильтрация данных Компонент Table позволяет не только отображать, редактировать и упорядочивать данные, но и отфильтровывать записи по определенным критериям. Пользователю в нашем примере могло бы захотеться иметь возможность просматривать не всю базу данных, а отдельно записи по тому или иному отделу, или, например, просмотреть записи сотрудников, имеющих возраст в определенном диапазоне. Фильтрация может задаваться свойствами Filter, Filtered и FHterOptions компонента Table. Свойство Filtered включает или выключает использование фильтра, А сам фильтр записывается в свойство Filter в виде строки, содержащей определенные ограничения на значения полей. Например, вы можете задать в свойстве Filter установить свойство Filtered в true, и увидите, что уже в процессе проектирования в таблице отобразятся только те записи, в которых полеОер имеет значение "Цех 1". В условиях сравнения строк можно использовать символ звездочки "*"', который как в обычных шаблонах означает: «любое количество любых символов». Например, фильтр Оер='Цех*'
приведет к отображению всех записей, в которых значение поля Dep начинается с "Цех" . В нашем примере будут отображены записи, относящиеся к первому и второму цехам. Но для того, чтобы это сработало, надо, чтобы в опциях, содержащихся в свойстве FilterOptions, была выключена опция foNoPartialCompare, запрещающая частичное совпадение при сравнении (эта опция выключена по умолчанию). Другая опция в свойстве FilterOptions — foCase Insensitive делает сравнение строк нечувствительным к регистру, в котором записано условие фильтра. Если включить эту опцию, то слова "Цех 1" и "цех 1" будут считаться идентичными. При записи условий можно использовать операции отношения =, >, >=, 0) или к началу (при i) равен заданному пользователем в окне редактирования ЕУеаг, вы можете написать операторы:
Приложения для работы с локальным^ базами данных
613
Tablel.IndexFieldNames := 'Yearjj'; Tablel.SetKey; Tablel.FieldByNsme('Year_b').Asstring := EYear.Text; Tablel.GoToKey;
Если вы хотите найти в таблице сотрудника по его фамилии, заданной пользователем в окне редактирование EFam, вы можете выполнить код: Tablel.IndexFieldNames : = ' F a m ' ; Tablel.SetKey; Tablel . F i e l d B y N a m e ( ' F a m ' ] . A s S t r i n g : = E F a m . T e x t ; Tablel.GoloNearest;
Даже если точно такой фамилии не найдется, курсор перейдет на наиболее похожую (совпадающую по первым символам). Методика поиска FindKey еще богаче по своим возможностям. В этой методике таблица также должна быть проиндексирована по тем ключевым полям, по которым осуществляется поиск. В метол FindKey передается массив значений ключевых полей в той последовательности, в которой они входят в индекс. При этом не обязательно перечислять все поля — достаточно перечислить первое или несколько первых. Вместо FindKey для полей строкового типа можно использовать аналогичный метод FindNearest, обеспечивающий переход к наиболее совпадающей строке, если полного совпадения не получено. Рассмотрим примеры. Пусть мы хотим выполнить тот же поиск по фамилии, что и приведенный выше. В данном случае соответствующий код может иметь вид: Tablel.IndexFieldNames := ' F a m ' ; Tablel.FindNearest([EFara.text]);
Если мы хотим выполнить аналогичный поиск, но нас интересует не просто один из сотрудников с заданной фамилией, а работающий в конкретном подразделении, заданном пользователем в окне редактирования EDep, то поиск осуществляется следующим образом: Tablel .IndexFieldNames := ' Dep.-Fam'; Tablel.FindNearest([EDep.Text,EFam.text]) ;
Первый оператор индексирует таблицу по полям Dep и Fam, а второй задает ключи для этих полей. Конечно, не надо индексировать таблицу каждый раз перед осуществлением поиска- Достаточно выполнить это один раз. Приведем еще один пример. Пусть мы хотим предоставить пользователю ускоренный поиск фамилии. Пользователь будет набирать фамилию в окне EFam и при вводе каждого нового символа курсор должен перемещаться на запись, наиболее близко совпадающую с-уже введенными символами. Для этого сначала надо индексировать таблицу по полю Fam. А затем достаточно в событие OnChange окна редактирования EFam вставить обработчик Tablel.FindNearestHEFam.text]);
Теперь остановимся на методе Locate, появившемся в последних версиях Delphi. Этот метод объявлен следующим образом: f u n c t i o n L o c a t e ( c o n s t K e y F i e l d s : String; const KeyVaLues: Variant; Options: T L o c a t e O p t i o n s ) : Boolean;
В качестве первого параметра KeyFields передается строка, содержащая список ключевых полей. В качестве второго параметра передается KeyValues — массив ключевых значений. А третий параметр Options является множеством опций, элементами которого могут быть loCaseInsensitive — нечувствительность поиска к регистру, в котором введены символы, и loPartialKey — допустимость частичного совпадения. Метод возвращает false, если искомая запись не найдена. В простейшем случае применение Locate отличается от рассмотренных ранее методов только отсутствием необходимости индексировать набор данных опреде!
614
Глава 9
ленным образом. Например, поиск (в том числе и рассмотренный выше ускоренный поиск) по фамилии может осуществляться оператором 1
Tablel,Locate!'Fam ,EFam.Text, [loCaseInsensitive,loPartialKey]];-
причем он будет работать независимо от того, как индексирована база данных. Впрочем, если набор данных соответствующим образом индексирован, то поиск производится быстрее. При поиске по нескольким полям можно воспользоваться функцией VarArrayOf, которая формирует тип Variant из задаваемого ей массива параметров любого типа. Например, рассмотренный ранее поиск по отделу и фамилии может быть осуществлен оператором Table!.Locate!'Pep;Fam',VararrayOf([EDep. Text, EFain. Text)i , [loCaselnsensitive,loPartialKey]i;
И в заключение о последнем методе поиска — Lookup. Этот метод определен следующим образом: function Lookup {const KeyE'ields: string; const K e y V a l u e s : V a r i a n t ; const ResultFields: s t r i n g ) : Variant,-
Первые два параметра аналогичны методу Locate. Третий параметр — строка, перечисляющая поля, значения которых возвращаются в виде массива Variant. Например, если вы хотите найти запись, относящуюся к сотруднику, фамилия которого указана в окне EFam, и вывести в окно EDep название отдела, в котором он работает, то эти операции можно осуществить следующим образом: EDep.Техt;=Tablel.Lookup('Fara1,EFam.Text,'Dep1];
Метод Lookup не изменяет положения курсора. Он только возвращает значения указанных полей. Они могут использоваться любым способом. В частности, они могут использоваться вместо параметра KeyValues в другом операторе Lookup или Locate. Это открывает широкие возможности формирования сложных запросов по нескольким таблицам.
9.11.7 Методы установки диапазона допустимых значений Ранее рассматривались различные способы задания критериев отбора установкой соответствующих значений свойств полей и наборов данных. Теперь рассмотрим коротко несколько методов, позволяющих задавать диапазон допустимых значений поля во время выполнения приложения. Метод SetRangeStart переводит набор данных в состояние dsSetKcy и следующий оператор присваивания значения полю воспринимается как задание для поля нижней границы диапазона возможных значений. Метод SetRangeEnd действует аналогично, но последующий оператор присваивания воспринимается как задание верхней границы диапазона. После того как пределы диапазона установлены, можно выполнить метод ApplyRange. При этом начнут использоваться установленные границы диапазона и доступными для просмотра и редактирования будут только те записи, в которых значения указанного поля находятся внутри диапазона. Например, операторы with Tablel do begin IridexFieldNaraes := Тага'; SetRangeStart; Fields/Name I ' F a m ' I .flsString := ' A ' ; SetRangeEnd; F i e l d s y K a r a e ( ' F a m ' ) . f l s S t r i n q := ' Г ' ; ApplyRange; end;
Приложения для работы ^локальными базами данных
61!>
приведут к тому, что доступными будут только записи сотрудников, фамилии которых начинаются с букв "А", "Б", "В". На результаты работы методов SetRangeStart и SetRangeEnd для числовых полей влияет свойство набора данных KeyExclusive. Оно определяет, будут ли считаться сами заданные границы входящими в диапазон (при KeyExclusive = false, это значение принято по умолчанию), или не входящими (при KeyExclusive = true). Иначе говоря, при KeyExclusive = false используются операции отношения >, S а при KeyExclusive = true - >, . Заданным символам может предшествовать и их может завершать символ процента "% ", который означает — любое количество любых символов. Если символ процента не указан, то заданная последовательность символов должна соответствовать только целому слову. Например, условие Fam LIKE
'A%'
означает, что будут отобраны все записи, начинающиеся с заглавной русской буквы "А" (операция Like различает строчные и прописные символы). Условию Fam LIKE 'Иванов*'
будут удовлетворять фамилии "Иванов" и "Иванова", а условию Fam LIKE ' % в а н % ' кроме этих фамилий будет удовлетворять, например, фамилия "Иванников". Операция between ... and имеет синтаксис: between and и задает для указанного поля диапазон отбираемых значений. Например, оператор SELECT Fam, Year^b FROM Pers WHERE Year_b BETWEEN 1960 AND 1970
отберет записи сотрудников в заданном диапазоне возраста. Операция In имеет синтаксис; in
()
и отбирает записи, в которых значение указанного поля является одним из элементов указанного множества. Например, оператор SELECT Fam, Year_b FROM Pers WHERE Fam IN('Иванов','Петров','Сидоров') отберет записи сотрудников с заданными фамилиями, а оператор SELECT Fam, YearjD FROM pers WHERE Year_b IN(1350,1960)
отберет записи сотрудников указанных годов рождения. Элемент оператора Select, начинающийся с ключевых слов ORDER BY, определяет упорядочивание (сортировку) записей. После этих ключевых слов следует
Создание приложений для работы с базами данных в сети
639
список полей, определяющих сортировку. Можно указывать только поля, фигурирующие в списке отобранных (в списке после ключевого слова SELECT). Причем эти поля могут быть и вычисляемыми. Если в списке сортировки указано только одно поле, то сортировка производится по умолчанию в порядке нарастания значений этого поля. Например, оператор SELECT Dep, Fam, Year_b FROM Pecs ORDER BY Year_b
задает упорядочивание возвращаемых значений по нарастанию года рождения. Если желательно располагать результаты по убыванию значений, то после имени поля добавляется ключевое слово DESC: SELECT Dep,
Fam, Year__b FROM Pgrs ORDER BY Year_b DESC
Поскольку в списке можно использовать и вычисляемые поля, то можно записать, например, и такой оператор: SELECT Fam AS Фамилия, 2003-Year_b AS Возраст FROM Pets ORDER BY Возраст DESC
Если в списке после ORDER BY перечисляется ти= шько полей, то первое из них — главное и сортировка проводится прежде всего по значениям этого поля. Записи, имеющие одинаковое значение первого поля упорядочиваются по значениям второго поля и т.д. Например, оператор SELECT Dep, Fam, Year_b FROM Pers ORDER BY Dep, Fam
сортирует записи прежде всего по отделам (значениям поля Dep), а внутри каждого отдела — по алфавиту. Оператор SELECT Dep, Fam, Y e a r _ b , Sex PROM Pers ORDER BY Dep, Sex, Fam
сортирует записи по отделам, полу и алфавиту. Оператор SELECT Faro as Фамилия, 2003-Year_b as Возраст, Sex as Пол FROM Pers ORDER BY Пол, Возраст
сортирует записи по полу и возрасту сотрудников. После ключевого слова Select в оператор могут вставляться ключевые слова DISTINCT или ALL. Первое из них означает, что в результирующий набор данных не включаются повторяющиеся записи. Повторяющимися считаются те записи, в которых совпадают значения полей, перечисленных в списке оператора Select. Ключевое слово ALL означает включение всех записей. Оно подразумевается по умолчанию, так что вставлять его в оператор не имеет смысла. Приведем пример использования DISTINCT. Оператор SELECT DISTINCT Dep FROM Pecs
выдаст список подразделении, в которых работают сотрудники.
10.1.2.2 Совокупные характеристики Оператор Select позволяет возвращать не только множество значений полей, но и некоторые совокупные (агрегированные) характеристики, подсчитанные по всем или по указанным записям таблицы. Одна из функций, возвращающих такие совокупные характеристики, соип1( ) — количество записей в таблице, удовлетворяющих заданным условиям. Например, оператор SELECT count С) FROM Pers подсчитает полное количество записей в таблице Pers. А оператор SELECT count(*)FROM Pers WHERE Dep='Uex 1'
выдаст число записей сотрудников цеха 1. Оператор, использующий ключевое слово DISTINCT (уникальный), выдаст число неповторяющихся значений в указанном поле. Например, оператор SELECT count(DISTINCT Dep) FROM Pers
вернет число различных подразделений, упомянутых в поле Dep таблицы Pers.
640
Глава 10
Функции 1шп(ле>), тах(), ау#(), 8иш() возвращают соответственно минимальное, максимальное, среднее и суммарное значении указанного поля. Например, оператор • SELECT rnin(Year_b) , max (Year_b), avgdfearj)) FROM Pers вернет минимальное, максимальное и среднее значение года рождения, а оператор SELECT
rain(2003-Year_b), max(2003-Year_b), avg(2003-Year_b) FROM Pars WHERE Dep='Бухгалтерия'
выдаст вам аналогичные данные, но относящиеся к возрасту сотрудников бухгалтерии. В операторе Select вы можете указывать не только суммарные характеристики, но и любые выражения от них. Например, оператор SELECT 2003-(min(Year_b)+max(Year_b)I II FROM Pers WHERE Dep='Бухгалтерия'
выдаст моду (среднее между максимальным и минимальным значениями) возраста сотрудников бухгалтерии. Здесь надо обратить внимание на то, что при работе с базой данных dbP оператор вернет округленное до целого значение моды, поскольку в выражении использованы только целые числа и поэтому осуществляется целочисленное деление. Если же вы в том же операторе замените делитель "2" на "2.", т.е. укажите его как действительное значение, то и результат будет представлен действительным числом. Драйвер InterBase ведет себя иначе и в любом случае возвращает действительное число. При использовании суммарных характеристик надо учитывать, что в списке возвращаемых значений после ключевого слова SELECT могут фигурировать или поля (в том числе вычисляемые), или совокупные характеристики, но не могут фигурировать и те, и другие (без указания на группирование данных, о чем будет сказано ниже). Это очевидно, так как оператор может возвращать или множество значений полей записей, или суммарные характеристики по таблице, но не может возвращать эти несовместимые друг с другом данные. Поэтому нельзя, например, записать оператор SELECT Farn, max(Year_b) FROM Pers
в котором мы пытаемся определить фамилию самого молодого сотрудника. Впрочем, эту задачу можно решить с помощью вложенных запросов, которые рассмотрены ниже. Смешение в одном операторе полей и совокупных характеристик возможно, если использовать группировку записей, задаваемую ключевыми словами GROUP BY. После этих ключевых слов перечисляются все поля, входящие в список SELECT. В этом случае смысл совокупных характеристик изменяется: они проводят вычисления не по всем записям таблицы, а по тем, которые соответствуют одинаковым значениям указанных полей. Например, оператор SELECT Dep Отдел, count (*) Всего__сотрудникое FROM Pers GROUP BY Dep вернет таблицу, в которой будет 2 столбца — столбец Отдел с названиями отделов, и столбец Всего_сотрудников, в котором будет отображено число сотрудников в каждом отделе: Отдел
Всего_сотрудников
Бухгалтерия
3
Цех!
4
Цех 2
4
При группировании записей с помощью GROUP ВУ можно вводить условия отбора записей с помощью ключевого слова HAVING. Например, если переписать приведенный выше оператор следующим образом:
Создание приложений для работы с базами данных в сети
641
SELECT Dep Отдел, countf*) Всего_сотрудников FROM Pers GROUP Bf Dep HAVING Dep о 'Бухгалтерия 1
то первая строка в приведенной выше таблице должна исчезнуть. При использовании базы данных dbP (Paradox) компонент Query не работает с HAVING. Для базы данных InterBase никаких проблем с HAVING не возникает. 10.1.2.3 Вложенные запросы Результаты, возвращаемые оператором Select, можно использовать в другом операторе Select. Причем это относится и к операторам, возвращающим совокупные характеристики, и к операторам, возвращающим множество значений. Например, в предыдущем разделе нам не удалось узнать фамилию самого молодого сотрудника. Теперь это можно сделать с помощью вложенных запросов: SELECT Fam,Year_b FROM Pers WHERE Year_b=(SELECT max(Year_b] FROM Pers)
В этом операторе второй вложенный оператор SELECT max(Year_b) FROM Pers возвращает максимальный год рождения, который используется в элементе WHERE основного оператора Select для поиска сотрудника (или сотрудников), чей год рождения совпадает с максимальным. Вложенные запросы могут обращаться к разным таблицам. Пусть, например, мы имеем две аналогичных по структуре таблицы Pers и Persl, относящиеся к разным организациям, и хотим в таблице Pers найти всех однофамильцев сотрудников другой организации. Чтобь! проверить работу с несколькими таблицами, вы можете открыть в Database Desktop свою таблицу Pers, выполнить команду Table I Restructure, ничего не меняя в структуре, щелкнуть на кнопке Save as и сохранить ту же таблицу в том же каталоге, где была исходная таблица Pers, но под новым именем Persl. Если хотите, можно в ней внести записи, отличные от таблицы Pers, чтобы эти таблицы чем-то различались. Впрочем, можно всего этого и не делать, заменив в приведенных ниже примерах таблицу Persl таблицей Pers, т.е. работая с одной таблицей. Смысл операторов все равно будет понятен. Итак, вернемся к задаче определения всех однофамильцев в этих двух таблицах. Это можно сделать оператором SELECT * FROM Pers WHERE Fam IN (SELECT Fam FROM Persl}
Вложенный оператор Select Fam from Persl возвращает множество фамилий из таблицы Persl, а конструкция WHERE основного оператора Select отбирает из фамилий в таблице Pers те, которые имеются в множестве фамилий из Persl. При работе в условии WHERE с множествами записей можно использовать ключевые слова: All и Any. All означает, что условие выполняется для всех записей, a Any — хотя бы для одной записи. Например, оператор SELECT Fain,
Year_b FROM Pers
WHERE Year_b >= ALL (SELECT Year_b FROM Persl)
ищет сотрудников в Pers, которые не старше любого сотрудника в Persl. Кстати, если в этом операторе заменить Persl на Pers, то получим список самых молодых сотрудников организации, который мы получали ранее другим способом. А оператор SELECT Fam, Year_b FROM Pers WHERE Year_b > ANY (SELECT Year_b FROM Persl)
ищет сотрудников в Pers, которые моложе хотя бы одного сотрудника в Persl. В условиях, содержащих вложенные операторы Select, может использоваться ключевое слово EXISTS, которое означает отбор только тех записей, для которых вложенный запрос возвращает одно или более значений. Например, оператор SELECT Num. Fam, Year_b FROM Pers pi WHERE EXISTS (Select Hum, Year_b FROM Pers p2 WHERE (pl.Year_b - p2.Year_b] and (pi.Hum != p2.Num)l
вернет список сотрудников, которые имеют хотя бы одного сверстника. 21 Программирование в Delphi ?
642
Глава 10
Имеется также ключевое слово SINGULAR, которое означает отбор только тех записей, для которых вложенный запрос возвращает только одно значение. Например, оператор SELECT Hum, Fara, Year_b From Pers pi WHERE SINGULAR(Select Nuffl, Year_b FROM Pers p2 WHERE p l . Y e a r J a = p 2 . Y e a r _ _ b )
вернет список сотрудников, которые не имеют ни одного сверстника (год рождения которых совпадает только с собственным годом рождения и больше не повторятся в таблице). 10.1.2.4 Объединения таблиц В запросе можно объединить данные двух или более таблиц. Пусть, например, вы хотите получить список сотрудников всех производственных подразделений. В таблице Pers мы имеем список сотрудников с указанием в поле Dep подразделений, в которых они работают. А в таблице Dep мы имеем список всех подразделений в поле Dep и характеристику каждого подразделения в поле Proisv (true, если подразделение производственное). Тогда получить список сотрудников всех производственных подразделений можно оператором: SELECT Pers.' FROM Pers, Dep WHERE [Pers.Dep=Dep.Dep)AND(Dep.Proisv=true)
В нем мы обращаемся сразу к двум таблицам Pers и Dep, которые перечислены после ключевого слова FROM. Поэтому каждое имя поля предваряется ссылкой на таблицу, к которой оно относится. Впрочем, это надо делать только для полей, имя которых повторяется в разных таблицах (поле Dep). Перед полем Proisv ссылку на таблицу можно опустить. В конструкции WHERE условие Pers.Dep=Dep.Dep ищет запись в таблице Dep, в которой поле Dep совпадает с полем Dep текущей записи таблицы Pers. А условие Dep.Proisv=true отбирает те записи, в которых в таблице Dep найденному подразделению соответствует поле Proisv = true. В операторах, работающих с несколькими таблицами, обычно каждой таблице дается псевдоним, сокращающий ссылки на таблицы, а иногда придающий им некоторый смысл, вытекающий из данного применения. Псевдоним таблицы может записываться в списке таблиц после слова FROM, отделяясь от имени таблицы пробелом. Например, приведенный выше оператор может быть переписан следующим образом: SELECT P." FROM Pers P, Dep D WHERE (P.Dep-D.Dep)AND(D.Proisv-true)
В этом примере таблице Pers дан псевдоним Р, а таблице Оер — D. Конечно, эти псевдонимы действуют только в данном операторе и не имеют никакого отношения к псевдонимам баз данных, которые мы постоянно используем. Возможно самообъединение таблицы. В этом случае одной таблице даются два псевдонима. Пусть, например, мы хотим найти всех ровесников в организации. Это можно сделать оператором SELECT p l . f a m , p2.faro, pl.year__b FROM Pers pi, Pers p2 WHERE (pl.year_b - p 2 . y e a r _ b ) AND ( p l . f a m != p 2 . f a m )
В этом примере для таблицы Pers мы ввели два псевдонима: р! и р2. В конструкции WHERE мы ищем в этих якобы разных таблицах записи с одинаковым годом рождения. Второе условие pl.fam != p2.fam нужно, чтобы сотрудник не отображался в результатах как ровесник сам себя. Правда, приведенный оператор выдает в результате по две записи на каждую пару ровесников, сначала, например, «Николаев — Андреев», а потом «Андреев — Николаев*. Чтобы исключить такое дублирование можно добавить еще одно условие — pl.Fam < p2.Fam: SELECT pl.fam, p2.fam, pl.year__b FROM Pers pi, Pers p2 WHERE ( p l . y e a r _ b = p2.year_b) AND ( p l . f a m != p 2 . f a m ) and(pl.Fara < p2.Fam)
Создание приложений для работы с базами данных в сети
643
Дополнительное условие упорядочивает появление фамилий в р! и р2 и исключает дублирование результатов. До сих пор мы рассматривали объединения, основанные на однозначном соответствия записей двух таблиц, когда каждой записи в первой таблице находилась соответствующая ей запись во второй таблице. Возможны и другие виды объединений, которые выдают записи независимо от того, есть ли соответствующее поле во второй таблице. Это внешние объединения (outer join). Их три типа: левое, правое и полное. Левое объединение (обозначается ключевыми словами LEFT OUTER JOIN ... ON) включает в результат все записи первой таблицы, даже те, для которых не имеется соответствия во второй. Правое объединение (обозначается ключевыми словами RIGHT OUTER JOIN ... ON) включает в результат все записи второй таблицы, даже если им нет соответствия в записях первой. Полное объединение (обозначается ключевыми словами FULL OUTER JOIN ... ON) включает в результат объединение записей обеих таблиц, независимо от их соответствия. Пусть, например, у вас есть таблица сотрудников некоей компании Pers и есть таблица Chei, в которой занесены данные на членов совета директоров этой компании. В число членов совета входят и сотрудники компании, и посторонние лица. Для определенности положим, что в таблице Pers имеются записи на сотрудников "Иванов" а "Петров", причем Петров является членом совета, а Иванов — нет. В таблице Che( имеются записи на членов совета "Петров" и "Сидоров", причем Сидоров — не сотрудник компании. Тогда оператор SELECT * FROM PerE LEFT OUTER JOIN Chef ON Pers.Fam «= Chef.Fam
выдаст результат вида: Поля таблицы Pers
Поля таблицы Chef
Иванов Петпов
Петоов
Оператор задал левое объединение таблицы Pers (она указана после ключевого слова FROM) С таблицей Chef (она указана после ключевых слов LEFT OUTER JOIN). Условие объединения указано после ключевого слова ON и заключается в совпадении фамилий. Как показано, результат включает все поля и таблицы Pers, и таблицы Chef. Число строк соответствует числу записей таблицы Pers. В строках, относящихся к записям, для которых в Chef не нашлось соответствие, поля таблицы Chef остаются пустые. Оператор правого объединения SELECT * FROM Pers RIGHT OUTER JOIN Chef ON Pers.Fam = Chef.Fam
выдаст результат вида: Поля таблицы Pers Петров
Поля таблицы Chef Петров Сшгооов
Число строк соответствует числу записей таблицы Chef. В строках, относящихся к записям, для которых в Pers HI; нашлось соответствие, поля таблицы Pers остаются пустые. Оператор полного объединения SELECT * FROM Pers FULL OUTER JOIN Chef OK P e r s . F a m = Chef.Fam
выдаст результат вида:
644
Глава 10
Поля таблицы Pcrs
Поля таблицы Chef
Иванов Петров
Петров Сидоров
В нем к строкам, относящимся к таблице Pers, добавлены строки, относящиеся к таблице Chef, для которых не нашлось соответствия в таблице Pers. .
10.1.3 Операции с записями В этом и нескольких последующих разделах вы уже не сможете проверять операторы SQL с помощью вашего тестового приложения. Или обойдитесь пока без Проверки, или посмотрите сначала разд. 10.3.3 и проверяйте операторы с помощью программы WISQL. Вставка новой записи в таблицу осуществляется оператором Insert, который может иметь вид: INSERT INTO (| VALUES (|
В списке перечисляются только те поля, значения которых известны. Остальные могут опускаться. Для пропущенных полей значения берутся по умолчанию (если значения по умолчанию заданы) или поля остаются пустыми. Например: INSERT INTO Pers [Mum,Fara, Mam, Par, Sex) VALUES (12,'Иванов', 'Андрей', 'Андреевич', 'и')
В этом примере не указан год рождения. Он подставится по умолчанию и в дальнейшем может быть уточнен. Другая форма оператора Insert использует множество значений, возвращаемых оператором Select. Этот оператор может выбирать записи из какой-то другой таблицы и вставлять их в данную. Синтаксис этой формы Insert: INSERT INTO
Пусть, например, вы создали таблицу Old_Pers пожилых людей вашей организации и хотите заполнить ее соответствующими записями из таблицы Pers. Это можно сделать одним оператором: INSERT INTO Old_Pers SELECT * FROM Pers WHERE Year_b < 1940
Таблица Old_Pers сразу заполнится множеством соответствующих записей из Pers. Приведенную форму оператора Insert можно использовать для копирования всех данных одной таблицы в другую, причем эти таблицы могут быть созданы разными СУБД. Редактирование записей осуществляется оператором Update: UPDATE ЗЕТ FAM. Type • 1747-ГИ SQL Еив^е IMTRBBSE - SELECT MUM .DEP .FAM .MAM PAR .YEAR В SEX .CHARACT.PHOTO FROM PERS ORDER BY DEPASC .FAM ASC.MAM ASC PARASC Trace Envied
:з jj
Pro]e«:l
6)
Ereosed Quay Steferwnli £.«eDjted Duwv SI Inpuf P*ame)erf
15 £onretl I Кнщтей
fv в.вы/о p Vamtoi Enc« 13
значения полей, совпадение которых идентифицирует изменяемую запись. Эти значения берутся из копии записи и, следовательно, не зависят от того, не изменились ли за это время поля реальной записи. В компонентах Table и Query имеется свойство UpdatcModc — режим обновления. Значения этого свойства и определяют, какие поля будут включены в элемент Where команды Update. Возможные значения: цр Where All Where включает все поля записи (принято по умолчанию) upWhereChanged Where включает ключевые поля и поля, которые были изменены UpWhereKeyOnly Where включает только ключевые поля Вариант upWhereAll наиболее надежен, поскольку распознает запись по значениям всех ее полей. Но он и наиболее трудоемок, поскольку при большом количестве полей элемент Where получается очень большим. К тому же, как уже рассматривалось в разд. 10.1.6.7, не по всем полям идентификация записи возможна. Вариант upWhereChanged требует меньших затрат, но он и менее надежен. Если, пока шло редактирование записи в данной транзакции, другая транзакция изменила в этой записи поля, отличные от измененных данной транзакцией, то фикси-
672
Глава 1Q
роваться результаты будут уже практически для другой записи. Еще менее надежен вариант up Where Key Only, хотя он наиболее быстрый. Если есть уверенность, что данное приложение — единственное, модифицирующее таблицу на данном отрезке времени, то можно рекомендовать применение именно этого варианта. Впрочем, и в других ситуациях его можно применять. Только надо иметь в виду, что в этом случае пройдут незамеченными конфликты, связанные с параллельным изменением записи несколькими пользователями. Просто изменения, внесенные позднее, ликвидируют предыдущие изменения.
10.3 InterBase — работа на платформе клиент/сервер 10.3.1 Общие сведения Все рассмотренное выше по созданию сетевых приложений, работающих с базами данных, относится и к платформе клиент/сервер. Но некоторые особенности этой платформы, в частности, создание просмотров — Views и использование хранимых на сервере процедур, еще не обсуждались. Прежде, чем заняться этим, рассмотрим в качестве примера работу с сервером InterBase. Это тем более оправдано, что в Delphi имеется локальный сервер InterBase — Borland InterBase Server. Он является усеченной локальной версией Borland Workgroup сервера, который находит применение в больших реальных задачах. Borland InterBase Server позволяет в локальном варианте разрабатывать программы, которые в дальнейшем будут работать на реальных системах. При этом во время разработки" отпадает нужда в реальном отдельном сервере, можно не манипулировать реальными данными, рискуя их испортить, и не решать сложных сетевых проблем. В этом и состоят преимущества локального сервера. В то же время приложение, отлаженное с использованием Borland InterBase Server, можно затем легко перенести на реальный сервер InterBase, а, учтя некоторые особенности локальных диалектов SQL, можно перенести и на другие имеющиеся на рынке системы, такие, как Informix, Microsoft SQL Server, Oracle, Sybase и др. Базой данных в InterBase, в отличие от Paradox и dBase, является не каталог, а файл со стандартным расширением .gdb. В этом файле хранятся все таблицы базы данных, просмотры, хранимые процедуры и другие объекты. Borland InterBase Server — сложная система, обладающая широкими возможностями построения больших корпоративных баз данных. Невозможно в рамках данной книги рассматривать все эти возможности. Да и не совсем корректно — почему бы тогда не рассматривать подробно иные, не менее мощные СУБД? Так что в последующих разделах я ограничусь только основами работы с InterBase, необходимыми, чтобы продемонстрировать некоторые особенности работы на платформе клиент/сервер.
10.3.2 Программа IBConsole Прежде, чем начинать работать с базами данных в Borland InterBase Server, вам понадобится программа, которая позволит зарегистрировать вас как пользователя. Это необходимо, так как создание и использование баз данных в InterBase потребует от вас указания пользователя, создавшего базу данных, и его пароля. В версиях InterBase, поставляемых с Delphi 7 и 6, такой программой является IBConsole (в ранних версиях аналогичные функции выполняла программа Server Manager —- файл Ibmgr32.exe, располагавшийся обычно в каталоге ...\Program Files\IntrBase Corp\InterBase\BIN или в ...\Program Files\Borland\IntrBase\BIN).
Создание приложений для работы с базами данных в сети
673
Рис. 10.10 Окно программы IBConsole Savai lSevs Databases
Narne
....Очпа1 Desdiptim
Щ OEPJ A Щ DEP_2 A Щ DEP.3 A
Slofed Procedures E .lend Puncture GenoalB; Q) Enceptions Btofiers
Вы можете запустить IBConsole кнопкой Windows Пуск | Программы из раздела InterBase. Но, поскольку при работе с InterBase эта программа может использоваться довольно часто, полезно включить ее в меню Tools ИСР Delphi. После вызова программы IBConsole перед вами откроется окно, представленное на рис. 10.10. Точнее, если вы запускаете программу в первый раз, в левой панели окна будет только одна вершина InterBase Servers. Так что вам надо начать с регистрации локального или удаленного сервера. Если вы еще не регистрировали сервер, выполните команду Server | Register или просто сделайте двойной щелчок на вершине InterBase Servers. Перед вами откроется окно, показанное на рис. 10.11. При регистрации локального сервера вы должны включить радиокнопку Local Server. В окнах редактирования надо ввести имя администратора баз данных (User Name) и его пароль (Password). Это имя — SYSDBA. Пароль администратора (если, конечно, он не изменен на вашем компьютере) — masterkcy. Пароль чувствителен к регистру, так что вводите этот пароль в нижнем регистре. Щелкните на ОК, и в окне IBConsole (рис. 10.10) появится вершина локального сервера. Если требуется дополнительно зарегистрировать удаленный сервер, это делается так же, как описано выше, но в окне рис. 10.11 надо указать имя сервера (его сетевой адрес) и протокол сети. В дальнейшем снять какой-то сервер с регистрации можно командой Server | Un-Register. Описанная процедура регистрации выполняется для каждого сервера только один раз. При последующих выполнениях IBConsole серверы уже будут зарегистрированы и их вершины сразу будет появляться в левой панели окна на рис. 10.10. Но в первый момент они будут перечеркнуты красными крестами, что означает отсутствие соединения с серверами. Чтобы установить соединение, надо выполнить команду Server Login или команду Login контекстного меню, всплывающего при щелчке правой кнопкой мыши на вершине сервера. Если в данный момент сервер не выполняется, вам будет выдано сообщение об этом и задан вопрос: надо ли его запустить. Ваш ответ, конечно, должен быть положительным. Тогда откроется окно, идентичное нижней панели окна рис. 10.11. В нем надо будет указать имя и пароль. После этого соединение с сервером установится. После регистрации сервера надо зарегистрировать на нем требуемые базы данных. В частности, если вы собираетесь пользоваться базой данных InterBase — ib, которую вы можете найти на прилагаемом к книге диске, вам надо зарегистриро22 Программирование Б Delphi 1
674
Рис. 10.11 Окно регистрации сервера
Глава 10 Ч Register Surer ami Connect*
31*1
Serva Infamtfion " К i,OCjlSevn Sgve N от
Uelwak PlUOHt
P
- Login Idc-mafcm
вать ее. Регистрация осуществляется командой Database Register или соответствующей командой меню, всплывающего при щелчке на вершине Databases. По этой команде вы увидите окно, представленное на рис. 10.12. В верхнем окошке File (учтите, что надписи окошек размещены, почему-то, под окошками, а не над ними) вы должны указать имя файла базы данных (напоминаю, что это файл .gdb) с полным путем к нему. Если база данных регистрируется на локальном сервере и, значит, размешена на вашем компьютере, можете для поиска и задания файла воспользоваться кнопкой с многоточием. Если регистрация проводится на удаленном сервере, то путь к файлу должен включать соответствующий сетевой адрес. В окошко Alias Name вы можете занести псевдоним, под которым база данных будет отображаться в левой панели окна рис. 10.10. По умолчанию псевдоним идентичен имени файла базы данных. Если вы хотите сразу после регистрации соединиться с базой данных, надо указать свое имя пользователя (User Name) и пароль (Password). Тогда сразу после регистрации в левой панели окна рис. 10.10. отобразится вершина с развернутым множеством дочерних вершин. Перемещаясь по ним, вы можете получить развернутую информацию о таблицах, просмотрах, процедурах и т.п., хранящихся в базе данных. Если вы при регистрации базы данных не указывали имя пользователя и пароль, или если вы при последующих запусках IBConsole только что соединились с сервером, вершина базы данных будет отображаться перечеркнутой. Тогда соединение с базой данных осуществляется командой Database | Connect или аналогичной командой контекстного меню, всплывающей при щелчке правой кнопкой мыши на вершине базы данных. Можно также соединяться с базой данных командой Connect As. В этом случае в диалоговом окне вы можете указать пользователя, от имени которого хотите соединиться, и его пароль. Это имеет смысл делать в том случае, если у разных пользователей имеются разные права доступа к базе данных. Поскольку я все время говорю об именах пользователей, самое время вам попробовать зарегистрировать себя как пользователя. Заодно, если вы собираетесь пользоваться базой данных InterBase — ib, которую вы можете найти на прилагаемом к книге диске, зарегистрируйте и меня: мое имя как пользователя, на которое создана база данных — «А» (латинская буква), а пароль — «1». Кстати, поскольку в дальнейшем система не раз будет запрашивать у вас имя и пароль, то для учебных целей полезно имя делать предельно коротким, а пароль — тоже коротким и цифровым (это избавит вас от необходимости следить за тем, какой язык в данный момент использует Windows — русский или английский).
Создание приложений для работы с базами данных в сети
675
LJ^rFPHCTIF.FSm ,.ИВрД
Рис. 10.12 Окно регистрации базы данных
Seven
Local Sevw
• Database F \DATABaS EM tWSEUB GD8 Fin
Defau» Ojiatler = w
Чтобы зарегистрировать нового пользователя, щелкните правой кнопкой мыши на вершине Users в левой панели окна на рис. 10.10. Выберите из контекстного меню раздел Add User. Перед вами откроется диалоговое окно, представленное на рис. 10.13. Введите в нем имя пользователя {User Name), пароль (Password), его подтверждение (Confirm Password) — т.е. повторите пароль еще раз. В нижних окнах редактирования можете (но не обязательно) написать свои имя, отчество и фамилию. Щелкните на ОК и в окне рис. 10.10 должно появиться ваше имя как пользователя. Рис. 10.13 Окно ввода данных о новом пользователе E'anHd Paawad
1 futHsme Ц-ldeNon: ;
|
В последующем вы сможете изменять пароли пользователей с помощью команды Server | User Sequrity. В открывающемся диалоговом окне вы можете выбрать из выпадающего списка того пользователя, для которого хотите сменить пароль, и ввести новый пароль. В частности, можете сменить пароль системного администратора SYSDBA. Вообще учтите, что все, что вы сейчас проделывали, это функции системного администратора. Еще одна команда, которую мы рассмотрим — Database | Creole Database или эквивалентная ей команда меню, всплывающего при щелчке правой кнопкой мыши на вершине Databases. Эти команды обеспечивают создание новой базы данных. Они открывают диалоговое окно, показанное на рис. 10.14. В панели File(s) вы
Глава 10
676
Рис. 10.14 Окно формирования новой базы данных ftW
^1
Rtyatr «•*
ЕГ
должны записать имя файла создаваемой базы данных с полным путем к нему. Файл должен быть расположен на том же компьютере, на котором размещается сервер. Если путь к файлу указывает на другой компьютер, работа с базой данных будет осуществляться сервером, расположенным на том компьютере (если там имеется сервер). Сам файл с указанным именем не должен существовать во время создания базы данных. Он будет автоматически создан после того, как вы заполните информацию в окне рис. 10.14 и щелкнете на кнопке ОК. InterBase позволяет размещать базу данных в нескольких файлах. В этом случае в первой строке панели Rle(s) записывается имя первого файла, а имена вторичных файлов записываются в следующих строках. Тогда для всех файлов, кроме последнего, надо указывать их размер. В окне Alias записывается псевдоним, под которым будет фигурировать созданная база данных в окне рис. 10.10. В панели Options вы можете изменить значения опций создаваемой базы данных. В частности, имеет смысл установить в множестве символов (Defaull Character Set) значение CYRL.
10.3.3 Interactive SQL Программное средство Interactive SQL представляет собой интерфейс для выполнения запросов SQL в интерактивном режиме или из специальных файлов. Вызов Interactive SQL в Delphi 7 и 6 осуществляется из окна рассмотренной в разд. 10.3.2 программы IBConsole (рис. 10.10) командой Tools | Interactive SQL или третьей справа быстрой кнопкой. Предварительно в этом окне должна быть выделена вершина базы данных, с которой вы хотите работать. В младших версиях Delphi программа Interactive SQL была оформлена самостоятельным исполняемым файлом Wisql32.exe, размещенным в каталоге ...\Progrom Files\lnlerBase Corp\in!erBase\BIN). После вызова Interactive SQL перед вами откроется окно, показанное на рис. 10.15. В верхней панели этого окна вы можете писать запросы SQL. Можно записать один запрос или сразу несколько запросов. Если пишется несколько запросов, каждый из них должен завершаться символом точки с запятой. Когда запросы введены, выполните команду Query Execute или щелкните на соответствующей быстрой кнопке — третья слева. Если в запросах были синтаксические ошибки, вам будет показано окно, пример которого можно видеть на рис. 10.16. В его третьей строке указывается местоположение нераспознанного
677
Создание приложений для работы с базами данных в сети Рис. 10.15 Окно Interactive SQL
-IDUI
1 1nter a1 _ **1/ чгЛ . Ы\ >^:£^ ^J
Cl ^ф
O« i
^ — !
r*^ ^"^ rU i гтд - "^
^S. — ^^
Select * ttrm Pens
_-J
„
1
1
,_
Ckent rJeJect 1
traresrtion H ACTIVE.
Data |plsn | Statistet |
NUM IDEP t
1 Б^о-аптегша
|ЧДМ
IYEAB в JSEX-
JPAR
Пеанов Петров
Иван Петр
Иванович
1Э50 «
Петрова
1960м
3 U»2 4 Це«1
СИДЩРОВ
Сидоц
Сичороечч
Иванова
Ирина
5 Бчч-апгериа
Николаев
Николай
Ивановна HuKorraeurfi
1ЭЕ5 м 1961 ж
6 Ц«2
Атреее 1.
Андрей
2 Ueil
ULJ
IFAM
РЛР«ДвА5£1гВА5Е\1В.ЙОВ
Андреевич 1
,
|
_
1330 м
1эя м Ч
JJ
-.
^
идентификатора — в приведенном примере строка 1, символ 9. В четвертой строке приводится сам нераспознанный идентификатор — в данном примере «fronrni». A в пятой строке дается текст того запроса, в котором обнаружена ошибка — «Select * fromm Pers» в приведенном примере. Если никаких ошибок нет, то после выполнения запросов в нижней панели окна рис. 10.15 вы сможете увидеть результаты его выполнения. Запросы могут быть связаны как с просмотром, так и с изменением данных. В последнем случае все изменения хранятся в памяти и не заносятся в базу данных. Для переноса их в базу данных надо выполнить команду Transactors Commit. Команда Transactions j Rollback отменяет все изменения, сделанные после последней выполненной команды Commit. Впрочем, команды Commit и Rollback вы можете выполнять иначе, просто записывая их в верхней панели окна рис. 10.15. Две левые быстрые кнопки со стрелками позволяют вам перемещаться по ранее введенным и выполненным запросам SQL. Это удобно, если вам надо выполнять похожие запросы с несколько измененными данными. Например, такая ситуация возникает при заполнении таблицы базы данных. Можете посмотреть все эти возможности Interactive SQL, соединившись с базой данных Ib.gdb, содержащейся на прилагаемом к книге диске. Только предварительно надо зарегистрировать ее на локальном сервере, как описывалось в разд. 10.3.2. А можете попробовать сами создать похожую базу данных. Для этого, прежде всего, надо создать новую базу данных, как описывалось в разд. 10.3.2. Впрочем, создать базу данных аналогичным образом можно и из окна рис. 10.15, выполнив команду Database Create Database. Далее можно занести в верхнюю панель окна рис. 10,15 запрос на создание таблицы, например, такой: Рис. 10.16
Окно с сообщением о синтаксической ошибке
еЫ Massage
.
Jjnaisic SOL Error SOL ела code--104 Token ur*ncw*n • hne Ltha Э rum Slaleme»* Select • frrjvni Pels i
Глава 10
678 create table Pers ( Hum smallint Not Null Primary Key,
Dep char (15) , Pam char (20) Not Null,
Nam char (20) Not Null,
Par char (20) Not Null, Year_b smallint DEFAULT 1950, Sex char(l) DEFAULT Чл' , Charact blob, Photo blob
create table Dep ( Dep char (15) Not Null Primary Key, Proisv char (1) );
Более полный вариант создания таблиц базы данных ib имеет вид: create table Dep ( Dep char (15) Not N u l l Primary Key, Proisv char (1) create table Pers( Hum smallint Not Null Primary Key, Dep char(15) DEFAULT "Неизвестный", Fam c h a r ( 2 0 ) Not N u l l , Nam char (20) Nou Null, Par char (20) Not N u l l , Year_b smallint DEFAULT 1950 CHECK] (Year_b>1917)and(Year_bисточник данных (компонент DataSource) ^компоненты управления и отображения данных (DBGrid, DBEdit и др.). Только в первом звене этой цепочки используются компоненты, расположенные на странице ADO. Большинство компонентов, предназначенных для работы с ADO, аналогичны уже известным вам компонентам, работающим с BDE: Компонент ADO
Компонент BDE
ADOTable
Table
ADOQuery
Query
ADOStoredProc
StoredProc
ADOConnection
Database
ADODataSet
Table, Query, StoredProc
ADOCommand
-
RDS Connection
-
Ниже приведена краткая характеристика основных компонентов ADO. ADOConnection
Используется для связи с набором данных ADO, Может работать с несколькими компонентами наборов данных как диспетчер выполнение их команд.
ADODataSet
Универсальный компонент связи с наборами данных, который может работать в различных режимах, заменяя связанные с BDE компоненты Table, Query, StoredProc. Может связываться с одной или множеством таблиц. Связь осуществляется непосредственно, или через ADOConnection.
ADOTable
Используется для работы с одной таблицей. Может связываться с ней непосредственно, или через ADOConnection.
ADOQuery
Используется для работы с набором данных с помощью запросов SQL, включая такие запросы языка DDL (data definition language), как CREATE TABLE. Может связываться с набором данных непосредственно, или через ADOConnection.
ADOStoredProc
Используется для выполнения процедур, хранимых на сервере. Может связываться с набором данных непосредственно, или через ADOConnection.
ADOCommand
Используется в основном для выполнения команд SQL, не возвращающих множество результатов. Может также совместно с другими компонентами использоваться для работы с таблицами. Может связываться с набором данных непосредственно, или через ADOConnection.
686
Глава 10
В последующих разделах отдельные компоненты ADO будут рассмотрены подробнее. А в качестве общей характеристики можно сказать, что в некоторых отношениях компоненты ADO мощнее компонентов BDE. В этих компонентах не возникает проблем с кириллицей (во всяком случае, я о таких проблемах не знаю). А в компонентах BDE при работе с некоторыми современными базами данных, содержащими символы Unicode, такие проблемы могут возникать. Другим преимуществом ADO является то, что это разработка Microsoft, и поэтому требуемое программное обеспечение включено во все поставки современных версий Windows. Так что не приходится заботиться о постановке BDE на компьютерах пользователя. Компоненты ADO могут работать, например, с Excel как с базой данных (см. в книге [2]). Но в то же время ряд возможностей компонентов BDE в компонентах ADO не реализован. Например, они не могут использовать словари свойств (см. гл. 9, разд. 9.6), что приводит к лишней работе При создании приложений. Не все источники данных ADO могут работать со всеми типами полей. Например, источник данных Paradox ADO не работает с графикой. Так что надо серьезно рассматривать все за и против, прежде чем переходить от BDE к ADO. Думаю, что там, где проблем с BDE не возникает, с компонентами BDE работать все-таки удобнее.
10.4.2 Задание соединения компонентов ADO с базой данных В отличие от компонентов BDE — Table, Query и других, в компонентах ADO нет свойства DatabaseName, указывающего базу данных. Доступ к базе данных осуществляется или с помощью строки соединения — свойства Connection String, или с помощью отдельного компонента ADOConnection, имя которого задается в свойстве Connection других компонентов. Рассмотрим соединение с базой данных с помощью свойства Connection String на примере компонента ADOTable. Свойство Connection String представляет собой строку, содержащую параметры соединения. Отдельные параметры отделяются друг от друга точками с запятой. ADO поддерживает четыре параметра: Provider
имя провайдера, используемое для соединения
File name
имя файла, содержащего информацию о соединении
Remote Provider
имя провайдера, используемое со стороны клиента
Remote Server
путь и имя сервера
Помимо этих параметров строка соединения может включать еще другие параметры, специфичные для используемой системы: идентификатор пользователя, пароль и многое другое. Конечно, составлять подобную строку вручную очень сложно. Поэтому в Delphi предусмотрено специальное диалоговое окно, облегчающее эту работу. Посмотрим, как с помощью этого диалога задается строка соединения. Перенесите на форму компонент ADOTable и в Инспекторе Объектов нажмите кнопку с многоточием около свойства Connection String. Перед вами откроется окно, показанное на рис. 10.17. Верхняя радиокнопка Use Data Link File позволяет использовать файл связи .udl. Об этих файлах мы поговорим позднее. Нижняя радиокнопка Use Connection String, применение которой мы сейчас рассмотрим, позволяет в режиме диалога сформировать строку соединения. Включите эту радиокнопку и нажмите кнопку Build (Сформировать). Перед вами откроется многостраничное окно задания свойств соединения. Надо отметить, что это окно и другие окна, открывающиеся в процессе формирования строки соединения, не имеют отношения к самой системе Delphi. Это окна формирования источников данных ODBC. Доступ к ним и формирование соответствующих источников данных может осуществляться непосредственно из Панели
Создание приложений для работы с базами данных в сети
687
Управления Windows. В окне Панели Управления или имеется непосредственно пиктограмма источников данных ODBC, или такая пиктограмма становится доступна после щелчка на пиктограмме Администрирование. Поскольку вид и состав диалоговых окон задания источников данных ODBC зависит от версии Windows, я не буду описывать все окна, а ограничусь изложением общей последовательности действий. Приведенные ниже рисунки иллюстрируют окна в Windows XP. Итак, после того, как в окне рис. 10.17 вы нажмете кнопку Build, перед вами откроется окно установки свойств связи. На его странице Поставщик Данных (Provider — здесь и далее в скобках даются названия, используемые в нерусифицированных диалогах) вы должны указать провайдер OLE DB, который собираетесь использовать для доступа к данным. Во многих случаях вас устроит выбор Microsoft OLE DB Provider for ODBC Drivers. Однако например, для работы с Microsoft SQL Server или Oracle надо выбрать другие разделы списка. Выбрав провайдер, вы должны перейти на страницу Подключение (Connection) (рис. 10.18). Впрочем, если вы на странице Поставщик Данных нажмете кнопку Далее, то этот переход свершится автоматически. На странице Подключение вы должны указать, как вы будете соединяться с ODBC. Выбрав кнопку Использовать имя источника данных (Use data source name), вы можете задать из выпадающего списка имя источника данных (data source name — DSN), зарегистрированного в ODBC. Впрочем, для того, чтобы использовать этот список, надо сначала создать и зарегистрировать требуемый источник данных. Кнопка Использовать строку подключения (Use connection siring) позволяет вам задать строку соединения самостоятельно, не прибегая к зарегистрированным DSN. Выберите кнопку Использовать строку подключения (Use connection string) и нажмите Сборка (Build). Перед вами откроется окно, представленное на рис. 10.19. В нем вам предоставляется одна из двух возможностей: работать с файловым источником данных — страница Файловый источник данных (File Data Source), или с источником данных компьютера — страница Источник донных компьютера (Machine Data Source). Впрочем, и в том, и в другом случае у вас пока нет требуемого источника. Так что на одной из страниц вам надо нажать кнопку Создать (New). Нажмите эту кнопку на странице Источник данных компьютера. Перед вами пройдет череда диалоговых окон, в которых вы должны указать характеристики создаваемого источника данных. При создании источника данных Paradox для используемой в этой книге базы данных dbP выберите драйвер Microsoft Paradox Driver (*,db). После этого вам надо задать произвольное имя нового источника данных. Далее вам потребуется указать базу данных, для которой вы создаете источник, и такие характеристики, как имя пользователя, пароль и т.п. После всего этого вы опять увидите окно рис. 10.19, но в нем уже будет созданный вами источник данных. После того как в окне рис. 10.19 вы выбрали существовавший ранее или созданный новый файл источника данных, щелкнули на ОК и в последующем диалоге указали каталог сохранения файла, вы вернетесь в основное окно задании свойств соединения на страницу Подключение (Connection) — см. рис. 10.18. Если вы щелкРис. 10.17
Первое диалоговое окно задания строки соединения
JSl " Б сосе с* Connection'-—-
Г
Глава 10
688
Рис. 10.18 Страница «Подключение» основного окна задании свойств соединений
ф Свойстве соях t данными Претивший дзммык Подкиочэине Датюштге^ьн} | Все \
*
• О DEC угажмтс сладоцеа
? Для кода в сервер ипвпьэоеать
Г" Пустой пароль & Разкшнгь со*рйнам«е паил
|F 4iAT
Справка
нете наОК, то вернетесь в окно рис. 10.17, в котором в нижнем окошке будет записана сформированная вами строка соединения. Для Paradox она может иметь вид: Provider=MSDASQL.1; Passwoird=l; Persist Security I n f o = T r u e ; User ID=a;Mode=ReadWrite;Extended Properties="DSN=dbP; DBQ-F:\DATABASE\DBPAR;DefaultDir=F:\DATABASE\DBPAR; PriverId=538;FIL=!>aradox 5.x; MaxBufferSize=2048; F a g e T i m e o u t = 5 ; " ; I n i t i a l Catalog=F:\DATABASE\DBPAR
Как видите, в результате не слишком сложного диалога сформировалась строка, которую иным способом записать было бы весьма трудно. В окне рис. 10.18 вы можете также занести дополнительную информацию: имя пользователя (User name), пароль доступа (Password), ввести начальный каталог (Enter Ihe inilial catalog lo use). Введенное вами имя пользователя занесется в строку соединения в виде параметра (см. в приведенном выше тексте элемент "User ID=a"). В дальнейшем это имя (в данном примере —- "а") будет отображаться в окне запроса пароля при соединении с базой данных. Но пароль сам по себе в строку соединения не занесется, если только вы не включите индикатор Разрешить сохранение пароля (Allov/ saving password). Тогда в строку соединения занесется соответствующий параметр, например: "Password=l". В этом случае в дальнейшем при соединениях пароль может не запрашиваться, а если и будет запрошен, то пользователь сможет его не вводить. Но учтите, что тем самым вы рассекретите свой пароль для пользователя, который имеет доступ к вашим исходным файлам Delphi. Включение индикатора Пустой пароль (Blank password) также разрешит пользователю при запросе пароля во время соединения с базой данных не вводить пароль. Но при этом, если пароль не запомнен в строке соединения, то с базой данных, защищенной этим паролем, соединение установить не удастся. Кнопка Проверить подключение (Test Connection) позволяет вам проверить правильность всей информации. При нажатии на эту кнопку происходит соединение с базой данных. Если все нормально, будет показано окно с надписью: «Проверка подключения выполнена (Test connection succeeded)», свидетельствующей о нормально произведенном тестировании соединения. Если в сформированной строке соединения что-то неправильно, вам будут выданы сообщения об ошибках. После завершения формирования строки соединения вы можете перейти в окне рис. 10.18 на страницу Дополнительно (Advanced) и задать режимы работы
689
Создание приложений для работы с базами данных в сети
Рис. 10.19 Выбор источника данных
Выбоо иповд^в явииыи Я>аняовь4й источник дакых
dBASEPin
91
E.celFfcs FoxPro Ftes
ИГШ1 ИстО'-чик даг*ы гомпыцтера T*fi ._ 1 Описании П ьльзоьатепьскчй
^_
.
1 ользоветельс* чй Системный
Access $ал(4е dff аЬаи
E«Ml Е>!се1_бо XTieme Sample Database
•T "°' '"" "
"'"
1
-Г
Исгочцк naiMfcifc ког-вькл^рй под«п>" только для этот о компостера н ие может использоваться совместно Исго-викнланчон- nDrteaoiHrenfl шщйцот TOHDKQ длч много о1-№аелет*йго пользователе измпыотера Снсте**тв* ncro»4iFH - щм всех п01ьэоБ4те1ейилиС1клегч*«! сл^чб
ОК
Отчину
|
Сграека
|
с сетью. Страница Все (АИ) сообщает итоговую информацию о соединении и позволяет ее отредактировать. После того как вы завершили все операции по формированию строки соединения, нажмите ОК и сформированная строка появится в свойстве Connections tring компонента. Описанные выше операции формирования строки соединения достаточно громоздки. Но если вы их проделали один раз, то можете в дальнейшем использовать созданные источники данных при задании соединения других компонентов. Если в окне на рис. 10.19 вы создавали источник данных компьютера, то в дальнейшем при задании соединения какого-то компонента вам достаточно в окне рис. 10,18 включить радиокнопку Использовагь имя источника данных (Use data source name) и выбрать из выпадающего списка ваш источник. Если вы.создавали в окне рис. 10.19 файловый источник данных, то при установке соединения с ним нового компонента вам придется опять дойти до этого окна и выбрать на странице Файловый источник данных (File Data Source) ваш источник. Наконец, следует сказать еще об одной возможности — создании файла .udl. Посмотрите о том, как это делается, в справке вашей версии Windows. Если вы создадите этот файл в каталоге, отведенном для подобных файлов в вашей версии Windows (обычно ...Common Files\ System\Ole DB\Data Links), то впоследствии для установки соединения вам достаточно в окне рис. 10.17 включить радиокнопку Use Data Liknk File и выбрать из выпадающего списка нужный файл. .
10.4.3 Соединение с помощью компонента ADOConnection, управление транзакциями Соединение компонентов с базами данных можно осуществлять не только через свойство Connection String, как описано в предыдущем разделе, но и через свойство Connection, связывающее данный компонент с компонентом ADOConnection. В этом компоненте ADOConnection, осуществляющем диспетчеризацию работы с набором данных, соединение задается свойством ConnectionString. А во всех прочих компонентах наборов данных достаточно установить в свойстве Connection имя компонента ADOConnection. Соединение с базой данных компонентов наборов данных, связанных с ADOConnection, происходит, даже если в самом ADOConnection не предпринимается никаких действий для открытия базы данных. Достаточно в компоненте набора
690
rnaaaJQ
данных установить свойство Active = true, и он свяжется с набором данных. При этом свойство Connected компонента ADOConnection, показывающее наличие соединения, автоматически установится в true. Тогда встает вопрос: «Зачем же нужен компонент ADOConnection?». Компонент ADOConnection инкапсулирует объект соединения ADO. Он позволяет управлять атрибутами и условиями соединения подключенных к нему компонентов наборов данных. Свойства ADOConnection дают возможность задавать схему блокировки записей, тип курсора, уровень изоляции и многое другое. Методы ADOConnection обеспечивают управление транзакциями. Именно из-за этих особенностей и используется ADOConnection. Во время выполнения соединение ADOConnection с базой данных осуществляется методом Open: procedure Open(const UserlD: WideString; const Password: W i d e S t r i n g l ;
Параметры UserlD — идентификатор пользователя и Password — пароль не обязательны. Они, как было описано в разд. 10.4.2, могут быть заданы в строке соединения — свойстве Connection String. Эту информацию приложение может почерпнуть также из диалогового окна, предъявляемого пользователю при соединении с базой данных. Это окно отображается, если установить свойство LoginPrompt компонента ADOConnection в true. Если используется это значение LoginPrompt, то имя пользователя и пароль можно не задавать ни в строке соединения, ни в вызове Open. В компоненте ADOConnection может быть задано свойство Default Database — база данных по умолчанию. Эта база данных используется, если с основной базой данных не удалось получить соединение. Впрочем, база данных по умолчанию может быть задана и в строке соединения — в свойстве Connection String. Тогда значение этой базы данных по умолчанию насильственно заменяет значение, установленное в DefaultDatabase. Закрывается соединение с базой данных методом Close. Свойство KeepConnection определяет, сохраняется ли соединение с базой данных даже если база данных не открыта. Установка KeepConnection в true (по умолчанию) сокращает затраты времени и загрузку сети при соединениях с базой данных, но увеличивает затраты ресурсов компьютера. Существует также метод CloseDataSets, закрывающий соединение всех компонентов наборов данных, соединенных с данным компонентом ADOConnection, но не закрывающий соединение самого ADOConnection. Альтернативным способом установления и разрыва соединения с базой данных является установка соответственно в true или false свойства Connected. Это свойство можно также использовать для проверки успешности соединения. Если значение Connected равно true, значит соединение активно. Если значение Connected равно false и KeepConnection также равно false, то соединение неактивно. Свойство Connected компонента ADOConnection связано со свойствами Active компонентов наборов данных, подключенных к данному ADOConnection. Если Connected = false, а в одном из подключенных компонентов набора данных свойство Active переключается в true, то Connected автоматически устанавливается в true. Если же ADOConnection разрывает соединение методом Close или установкой Connected в true, то свойства Active всех подсоединенных компонентов сбрасывается в false. Этим и ограничивается связь свойств Connected и Active. Если после разрыва соединения компонент ADOConnection снова устанавливает соединение методом Open или заданием Connected = true, то свойства Active подсоединенных компонентов остаются равными false. Следовательно эти компоненты не подключаются к базе данных. В этом случае надо принять специальные меры для их подключения. Для этого можно воспользоваться свойством DataSets компонента ADOConnection, которое является массивом всех компонентов наборов данных, подсоединен-
Создание приложений для работы с базами данных в сети
691
ных к данному компоненту ADO Connection. Количество таких объектов определяется свойством Data Set Count. Эти свойства можно использовать, чтобы управлять всеми подсоединенными компонентами наборов данных. Например, код: vac i: Integer; begin f o r i := 0 to (ADOCormectionl.DataSetCount-1) do ADOConnectionl .DataSets [i] -Active := true; end;
обеспечивает активацию соединения с базой данных всех компонентов наборов данных. Свойство ConnectOptions компонента ADOCoimection позволяет выбрать синхронный (при значении coConnectUnspecified) или асинхронный (при значении coAsyncConnect) режим соединения. Обычно используется синхронный режим. Асинхронный полезно использовать только для серверов с малым быстродействием. Свойство Curs or Location определяет, какую библиотеку использует курсор при соединении с базой данных: клиентскую или сервера. Значение clUscrClient (no умолчанию) обеспечивает большую гибкость. Все данные располагаются на компьютере — клиенте и тут же обрабатываются. При этом возможны операции сортировки и другие, которые могут не поддерживаться сервером, Впрочем, предложения SQL и в этом случае выполняются на сервере и на компьютер клиента передаются уже отобранные данные, соответствующие условию WHERE предложения SQL. Значение свойства Curs orL о cat ion, равное ctUseServer, желательно использовать в командах, возвращающих большой объем данных. В подобных случаях использование клиентского курсора может потребовать недопустимых затрат дискового пространства клиента. Свойство ConnectionTimeout задает в секундах время (по умолчанию 15), отводимое на установление соединения. Если за это время соединения не произошло, то оно считается безуспешным. Свойство Mode определяет режимы, доступные после соединения: cmUnknown
режим еще не установлен
cmRead
разрешается режим «только для чтения»
era Write
разрешается режим «только для записи»
cmRead Write
разрешается режим чтения и записи
cmS hareDenyRead другие приложения могут открывать соединения для •чтения cmShareD enyW rite другие приложения могут открывать соединения для записи cmShareExclusive
другие приложения не могут открывать соединение
cm S bar eD eny N one другие приложения могут открывать соединения в любом режиме Свойство только для чтения State позволяет узнать текущее состояние соединения: stCloscd в t Open
st Connect ing
объект соединения не активен и не соединен с базой данных объект соединения не активен , но соединен с базой данных
st Executing
объект соединения в процессе установления соединения с базой данных объект соединения выполняет работу с базой данных
stFetching
объект соединения извлекает данные из базы данных
692
ГлаваТр
Теперь рассмотрим управление транзакциями. Транзакция начинается методом BeginTrans, который возвращает целое число, показывающее уровень вложенности данной транзакции. Успешное выполнение BeginTrans приводит к генерации события OnBeginTransComplete и установке свойства InTransaction в true. По значению этого свойства можно определить, находится ли компонент в состоянии формирования транзакции. А обработчик события OnBeginTransComplete можно использовать для выполнения каких-то действий перед началом транзакции. Метод CommitTrans завершает транзакцию и сохраняет ее результаты в базе данных. При успешном выполнении CommitTrans генерируется событие OnCommitTransComplete и свойство InTransaction устанавливается в false. Метод RollbackTrans осуществляет откат: отменяет все изменения, сделанные на протяжении транзакции. При успешном выполнении RollbackTrans генерируется событие OnRollbackTransComplete и свойство InTransaction устанавливается в true. Свойство Attributes описывает, как при соединении обрабатываются транзакции, оставшиеся незавершенными. Свойство является множеством, которое может быть пустым, или содержать одно или два значения: xaCommitRetaming и ха1 AbortRetaining . Значение xaCommitRetaining приводит к тому, что при соединении незавершенные транзакции завершаются (срабатывает метод CommitTrans). Завершение транзакции автоматически вызывает начало новой транзакции. Значение xaAbortRetaining приводит к тому, что при соединении незавершенные транзакции отменяются с откатом назад (срабатывает метод RollbackTrans). Отмена транзакции автоматически вызывает начало новой транзакции. Свойство IsolationLevel устанавливает уровень изоляции транзакции. Основные уровни: ilRcadtlncommitted, ilBrowse
видимы незафиксированные изменения, сделанные другими транзакциями
ilReadCommitted, il Curs or S tab i 1 it у
видимы только зафиксированные изменения, сделанные другими транзакциями
ilRepeatableRead
изменения, сделанные другими транзакциями, не видны, но повторный запрос может выдать новые данные
il Serializ able, il Isolated полная изоляция от других транзакций
10.4.4 Компоненты наборов данных ADOTable, ADOQuery, ADOStoredProc, ADODataSet Рассмотрим сначала особенности компонентов наборов данных ADO на примере ADOTable. Этот компонент может использоваться в приложениях вместо компонента Table, выполняющего аналогичные функции. Он вступает в контакт с указанной таблицей базы данных. База данных задается свойствами ConnectionString или Connection, как описано в разд. 10.4.2. Для управления таблицей в приложение вводится, помимо компонента ADOTable, обычный компонент источника данных DataSource, в свойстве DataSet которого задается имя компонента ADOTable. Далее к этому источнику данных DataSource подключаются любые компоненты отображения данных. Имя таблицы, как и в компоненте Table, задается свойством TableName. Однако не все провайдеры поддерживают непосредственный доступ к таблице по ее имени. Они могут требовать доступ с помощью оператора SQL SELECT. Какой именно вариант доступа: прямой или посредством оператора SELECT будет использоваться, определяется свойством TableDirect. По умолчанию TablcDirect =
Создание приложений для работы с базами данных в сети
693
false, что означает автоматическое создание компонентом ADOTable соответствующего оператора SELECT. Соединение с базой данных осуществляется так же, как и в компонентах BDE, методом Open или установкой в true свойства Active. Но при этом, если связь с базой данных осуществляется через компонент ADOConnection, надо учитывать описанную в разд. 10.4.3 взаимосвязь свойства Active компонента ADOTable и свойства Connected компонента ADOConnection. В компоненте ADOTable имеется два свойства, характеризующие курсор, используемый при навигации по таблице. Одно из них — Cursor Location описано в разд. 10.4.3. Другое — CursorType описывает иные характеристики курсора. Это свойство может иметь значения: ct Unspecified
.,... ..лг„„г_
r
-rt
ctOpenForwardOnly Курсор может перемещаться по таблице только вперед. Этот тип курсора повышает производительность приложения. ct Key set
При этом типе курсора записи, добавленные другими пользователями, невидимы, а записи, удаленные другими пользователями, недоступны. Этот тип используется по умолчанию.
ctDynamic
Этот тип динамического курсора обеспечивает видимость всех изменений, сделанных другими пользователями: модификаций, удалений, вставок. Курсор может перемещаться по таблице вперед и назад.
ct Static
Этот тип статического курсора обеспечивает копирование записей. Изменения данных, сделанные другими пользователями, невидимы.
Свойство Marshal Options определяет, какие именно записи возвращаются на сервер, если при работе используется клиентский курсор. При значении MarshalOptions = moMarshalAll (значение по умолчанию) на сервер возвращаются все записи, считанные в локальный набор записей клиента. При значении MarshalOptions = mo Marshal Modi fiedOnly на сервер возвращаются только измененные записи. Свойство CachcSize указывает, сколько записей заносится в локальный буфер оперативной памяти. По умолчанию CacheSize = 1. Если задать, например, CacheSize = 10, то при открытии базы данных в буфер загрузятся первые 10 записей. Пока будет идти работа с этими записями, все операции будут проводится в оперативной памяти без обращения к базе данных. Если указатель таблицы вышел за пределы 10, то в память загрузятся следующие 10 записей и т.д. Естественно, что буферизация записей повышает эффективность работы. Основные способы работы с ADOTable не отличаются от способов, описанных в гл. 9 для Table. Точно так же двойной щелчок на компоненте вызывает Редактор Полей, в котором можно задать свойства отдельных полей, ничем не отличающиеся от полей компонентов BDE. Впрочем, одно печальное отличие есть: в компонентах ADO невозможно работать со словарями (см. разд. 9.6). Так что в каждом компоненте свойства полей приходится задавать вручную. Кроме того, надо иметь в виду, что не все драйверы ADO могут работать с любыми типами полей. Например, драйвер Paradox ADO не работает с полями изображений. Так что в таблице Pers базы данных dbP, используемой в данной книге, не будет доступно поле Photo — фотографии сотрудников. Для драйвера InterBase (в наших примерах для базы данных ib) такого ограничения нет.
694
Глава 10
Из редактора полей так же, как в компонентах BDE, можно перетаскивать поля мышью на форму. При этом на форме автоматически будут создаваться соответствующие компоненты отображения данных. Программный доступ к полям осуществляется так нее, как в компоненте Table: по индексу через свойство Fields[i:integer], по имени поля с помощью метода FieldByName(''), по имени объекта поля. Ограничения на вводимые значения в компоненте ADOTable можно обеспечивать только на уровне полей (см. разд. 9.£.4). Свойства, аналогичного Constraints в компоненте Table, в ADOTable нет. Связь друг с другом компонентов ADOTable, работающих с разными таблицами, одна из которых главная, а другая — вспомогательная, осуществляется так же, как в компонентах Table (см. разд. 9.10.1), с помощью свойств MasterSource и Master Fields. Упорядочивание отображаемых записей производится установкой свойства IndexFieldNames. В этом свойстве можно задавать любое сочетание имен полей, по которым вы хотите упорядочить отображение, разделяя их точками с запятой. Например, строка 'Dep' упорядочит записи в таблице Pers по значению поля Dep — отдел. А строка 'Dep;Fam;Nam;Par' упорядочит записи по значению поля Dep — отдел, а внутри каждого отдела упорядочит по фамилии, имени и отчеству сотрудников. В отличие от свойства IndexFieldNames компонентов BDE, в компонентах ADO можно задавать любые сочетания полей, независимо от того, была ли индексирована таблица при ее создании по этим полям. В этом проявляется дополнительная гибкость компонентов ADO. Но зато в этих компонентах не работает свойство Index Name (хотя оно присутствует в Инспекторе Объектов), позволяющее использовать индексы, сформированные при создании таблицы. Свойство только для чтения IndexFieldCount позволяет определить число полей, использованных при индексации. Фильтрация отображаемых данных может осуществляться так же, как в компонентах BDE, с помощью свойства Filter, в котором записываются условия отбора (см. разд. 9.5.6). Отличие от компонентов BDE заключается в том, что в компонентах ADO в строке Filter имена полей обязательно должны отделяться пробелами от операций отношения. Также пробелами должны окружаться логические операции and и or. Например, если в компонентах BDE фильтр может быть записан в виде: (Year_b=1940) то в компонентах ADO эта строка должна иметь вид: [Year_b aitaa". Поскольку "http://" браузеры обычно добавляют к адресу автоматически, в качестве адреса достаточно задавать "mycomputer/cgi-bin/HMH_cpaiMa". .
12.3 Описание документов на HTML Страницы Web оформляются на языке HTML (Hypertext Markup Language) — это язык описания документов, используемый в WWW. Его описание выходит за рамки данной книги. Имеется немало литературы, по которой вы можете ознакомиться с этим языком. Кроме того, имеется множество редакторов, позволяющих создавать файлы .html непосредственно из текста, форматируемого обычным образом. В частности, нельзя не сказать о широко распространенной системе Microsoft FrontPage, которая позволяет готовить и отлаживать страницы Web. To же, только с меньшими возможностями, позволяют делать и более простые программные средства, в частности, Word, начиная с версии Word 97. Вы можете обычным образом отформатировать страницу текста и сохранить ее командой Сохранить в формате HTML. Впрочем, для тех, кто никогда не сталкивался с HTML, стоит немного рассказать о нем. Описание документа состоит из «тегов» (операторов). Начало каждого тега обозна.чается его именем, заключенным в угловые скобки. Например, тег или <strong> означает начало жирного шрифта. Окончание тега обозначается также его именем, заключенным в угловые скобки, но перед именем ставится символ обратного слэша "/"• В качестве примера составьте в любом текстовом редакторе документ: Это жирный шрифт
В этом документе к тегу добавлен тег , обозначающий начало документа HTML. Сохраните составленный вами документ как «только текст». Дайте вашему файлу расширение .html. Запустите на выполнение какой-нибудь браузер, например, Internet Explorer, и откройте в нем сохраненный файл. Вы увидите текст "Это жирный шрифт". Начиная с Delphi 6, составлять и отлаживать документы HTML можно, не выходя из ИСР Delphi. Откройте текстовый файл (команда File | New Other, пиктограмма Texl на странице New) и занесите в него приведенный текст. Сохраните его (команда File Save As), дав расширение .html. В Delphi 7 вы можете в окне Редактора Кода перейти на закладку Preview и увидеть, как выглядит ваша страница. А в Delphi 6 вы можете посмотреть свою страницу в браузере. Для этого щелкните в окне Редактора Кода правой кнопкой мыши и выберите из контекстного меню команду html Editor. Если вы провели настройку страницы Interne! окна опций среды так, как указано в разд. 15.2.2, гл. 15, задав там команду «Open», то откроется браузер, являющийся на вашем компьютере браузером по умолчанию. Приведем немного более сложный пример: Информация о новых книгах по Delphi HciBbie книги издательства Бином
Глава 12
734 Готовится к изданию книга Архангельского А.Я. "Програмыирование в Delphi 7" 3воните нам по телефону 123-45-67
-
Если Вас интересует информация о ценах - нажмите здесь
Подготовьте подобный документ, конечно, при желании изменив его текст. А если вы текст не изменили, то не звоните, пожалуйста, по указанному в нем телефону: его номер просто придуман! Текст можно готовить в любом текстовом редакторе. Но в Delphi 7 очень удобно выполнить команду File New Other и в Депозитарии на странице Web Documents выбрать пиктограмму HTML document. В Редактор Кода загрузится документ вида: Untitledl.html
Это полноценная заготовка документа HTML, в который вы можете заносить свой текст. Сформировав указанный ранее документ, сохраните его в каком-то каталоге (в дальнейшем я буду полагать, что это каталог d:\tests), дав, например, имя HTMLl.html. Запустите на выполнение какой-нибудь браузер, например, Internet Explorer, и откройте в нем сохраненный файл. Можно поступить проще — открыть соответствующую папку и сделать двойной щелчок на файле HTMLl.html. На рис. 12.1 показан результат просмотра этого документа в Internet Explorer. Рис 12.1 Отображение страницы в браузере
•З^Тнфорнацкя о новый нпигаи по Delphi - t-fcrosoft fntemft fxplnrar
Новые книги издательства Бином Готовится к изданию книга Архангельского А. Я. "Программирование в Delphi ~" Звоните нам по телефону 12J-45-6"
Ef лч Вдг .i
yf г 1шфо1)ма1Д111 о н*мя х- нажьдпе Ч ': j ЧОЙ FOtffbOTflp
Разработка
приложений,
использующих
сети
Интернет
и
интранет
_
735
В приведенном тексте введен ряд еще не обсуждавшихся тегов. Тег определяет текст, который виден в полосе заголовка окна браузера (см. рис. 12.1). Тег определяет основной текст документа. Тег задает формат заголовка первого уровня. Тег задает строку в виде линии, отделяющей одну часть текста от другой. Тег определяет очередной абзац. Тег определяет ссылку на другой документ. После символов " обработчик которого ничем не отличается от рассмотренного ранее. Сохраните модуль этого сервера под именем "TwoTwo" и перенесите его выполняемый файл в выполняемый каталог сервера. Откройте любым браузером подготовленный ранее документ TwoTwo.html и убедитесь, что все работает так же, как в предыдущем примере.
12.7 Отладка серверов и преобразование их типов Сервер, рассмотренный в разд. 12.6.2, настолько простой, что вряд ли нуждается в отладке. Однако логика работы серверов может быть достаточно сложной, включать значительные фрагменты кода. В этих случаях очень хорошо бы было иметь возможность отладить программу: пройти ее по шагам, посмотреть значения переменных и т.п. В Delphi это делается достаточно легко. Рассмотрим в качестве примера отладку того же сервере TwiceTwo, который рассмотрен в раз. 12.6.2. Создайте опять модуль сервера, но в окне рис. 12.5 включите на этот раз кнопку Web App Debugger executable. Тем самым вы говорите, что хотите создать модуль, который может отлаживаться специальным отладчиком Web Application Debugger, поставляемым с Delphi. При включении кнопки Web App Debugger executable станут доступны окно Class Name и индикатор Cross Platform — кроссплатформное приложение. В окне Class Name можете написать любое имя, например "MyServer". Это просто расширение, которое будет добавляться к имени сервера и по которому Web App Debugger будет ссылаться на ваше приложение.
748
Глава 12
После того как вы щелкнете ОК в окне рис. 12.5, создастся приложение, которое, помимо модуля сервера Web будет содержать модуль обычной формы. На ней ничего не требуется размещать. Она просто будет появляться на экране в первый момент отладки. Так что можете сделать форму достаточно малой. В модуле Web введите те же действия, которые вводили в разд. 12.6.2, и напишите те же самые обработчики этих действий. Впрочем, удобнее несколько изменить код. Дело s том, что имя сервера во время отладки и во время работы после его превращения в сервер CGI будут различными. Во время отладки к нему надо будет обращаться "TwiceTwo.MyServer". "MyServer" — это имя, которое вы ввели в окне. А после преобразования в CGI его имя будет "TwiceTwo.exe". Так что избежать последующего редактирования имени во всех местах кода, где оно встречается, можно, введя в код константу с именем сервера и используя его в соответствующих операторах: const ServerName = 'TwiceTwo.MyServer'; procedure TWebModule2.HebModule2WebflctionItem2Action( Sender: TObject; Request: TWebRequest; Response: TWebResponse,- var Handled: Boolean); begin Response-Content := ' < h l > Знаете ли вы дважды два? < / h l > ' -t• Сколько будет 2 x 2 ?'+ ' 4 или ' + '5 ?, '; Handled := true; end;
Тогда после преобразования сервера в CGI достаточно будет изменить значение константы ServerName на '"TwiceTwo.exe". Впрочем, можно было бы поступить еще проще: указать в окошке Class Nome (рис. 12.5) имя "ехе". Тогда проблемы с именем исчезнут, и в коде ничего изменять не потребуется. Я не сделал этого ранее, чтобы обратить ваше внимание на смысл имени в окошке Class Name. Сформировав код сервера, сохраните программу и выполните ее из среды Delphi. Перед вами откроется ее пустая форма. Можете в коде вашего сервера установить какие-то точки прерывания. Затем выполните команду Tools Web App Debugger. Вы увидите окно отладчика, показанное на рис. 12.8. Нажмите в нем кнопку Start и затем щелкните на активизировавшейся ссылке Default URL. Рис. 12.8
» Web App П ebuyyri
Окно отладчика Web Application Debugger
RequestCounF 0 Total Response Time- Q *vg Response Time: Q Last Response Time. 0 Mm Response Time: 0 • Max Reap out slimes U
;
.JQlJSl
749
Разработка приложений, использующих^е™ Интернет и интранет
Эта ссылка вызовет приложение Serverlnfo, которое откроется окно браузера, зарегистрированного в системе по умолчанию, и загрузит в него страницу, вид которой показан на рис. 12.9. Страница содержит список зарегистрированных в отладчике серверов. Среди них будет и ваш сервер TwiceTwo. Выделите его и щелкните на кнопке Go. Сервер начнет выполняться. При этом будут происходить остановы в заданных точках прерывания. Вы сможете пройти приложение но шагам, смотреть любые переменные. Словом, сможете производить обычную отладку. Если отладка окончена, можете закрыть свое приложение TwiceTwo и, вернувшись в среду Delphi, исправить что-то в коде и снова провести его компиляцию. Затем в окне отладчика (рис. 12.9) надо опять щелкнуть на кнопке Go, и сервер снова начнет выполняться. После завершения работы с сервером может в окне рис. 12.8 щелкнуть на кнопке Stop, которая появляется на месте кнопки Start после запуска Serverlnfo. Затем надо закрыть программу Serverlnfo. Рис. 12.9 Страница программы Serverlnfo
I'll Registered 5егич Details Файл .-!
[Травка --•
•
8ид I
'
Дранное '=*!
>«1
Сервис С
Адрес! |.JQ httpT//lDcaho5t:B081/5er'_3J
Спрэш ПОИСК
ЬСсылки " _L - —j^^— _:
Registered Servers View List | View Petals Ph oto G el le tyT e stSvr. Ph oto G ell e ryTe stS vr |Go| serverinto Setveiltito TwiceTwo MvSeiver
\ Готово
!*BJ Местная ннтрэсе
Когда вся работа с сервером завершена, вы можете снять его с регистрации в отладчике. Для этого надо на странице, показанной на рис. 12.9, щелкнуть на ссылке View Details и на открывшейся странице нажать кнопку Clean около имени вашего сервера. Теперь посмотрим, как превратить наше отладочное приложение в сервер CGI. Щелкните на модуле Web правой кнопкой мыши и выберите из всплывшего меню раздел Add To Repository. В открывшемся окне задайте имя и страницу Депозитария (см. разд. 2.6) для размещения вашего модуля. Модуль будет помещен в Депозитарий. Далее выполните команду File | New | Other и в диалоговом окне New llems на странице New выберите пиктограмму Web Server Application. В окне рис. 12.5 выберите тип CGI Stand-alone executable —• выполняемый модуль CGI. Удалите из проекта созданный пустой модуль Web (команда Project | Remove from Project). А затем командой File ] New I Other включите в проект из Депозитарии ранее занесенный туда вами модуль Web. И это все! Только если а отладочном варианте вы задавали расширение отладочного сервера равным "MyServer", надо заменить его на "ехе". А если вы сразу задавали "ехе", то вообще ничего изменять не надо. Аналогичным образом вы можете преобразовать ваше приложение в DLL ISAPI. Этот вид приложений имеет, как уже говорилось, преимущество в смысле производительности. Но имейте в виду, что при тестировании библиотеки ISAPI DLL, если вы внесете изменения в модуль DLL и захотите заменить этот модуль на Web-сервере, то вы не сможете этого сделать, пока не остановите и не перезагрузи-
750
Гпава_12
те Web-сервер. Этого не требуется делать для CGI-приложений, потому что в них для выполнения запроса каждый раз запускается новый процесс. Чтобы выполнить приложение DLL, клиент должен задать ее имя в URL. Например; http://www.mysite.com/scripts/webtime.dll
12.8 Использование форм и таблиц в HTML 12.8.1 Обработка ответа пользователя В примере разд. 12.6 варианты ответа клиента различались ссылками. Это возможно только при конечном и небольшом числе вариантов ответа. Хотелось бы предоставить пользователю возможность записывать ответ в произвольном формате в окне редактирования. Подобную возможность предоставляют формы, описываемые тегом HTML. В формах могут использоваться стандартные компоненты ввода типа однострочных и многострочных окон редактирования, кнопок, выпадающих списков, радиокнопок, индикаторов и других компонентов. Введенные клиентом в форму данные кодируются в специальный формат и посылаются на сервер. Форма начинается с тега , в котором надо задать несколько атрибутов. Атрибуты задаются в формате "имя^значение". Адрес URL получателя данных, внесенных пользователем в компоненты формы, указывается атрибутом action. Например: action="http://mycomputer/cgl-bin/test.exe"
Атрибут method задает имя используемого метода протокола HTTP. Возможны два значения метода — "get" и "post". Метод get используется для передачи данных формы серверному приложению, которое должно сформировать ответ на основе этого запроса, и вернуть его клиенту. Метод Post предназначен для публикации данных в Web. Если форма использует метод Post, то в серверном приложении данные можно прочитать в свойстве ContentFields объекта, передаваемого в обработчик события OnAction действия параметром Request. Если в запросе применяется метод Get, то данные запроса будут находиться в свойстве QueryFields объекта Request. Компоненты вводятся в форму с помощью тегов < input ="text"> однострочное окно редактирования, — кнопка, при щелчке на которой осуществляется пересылка данных серверному приложению, — многострочное окно редактирования с полосами прокрутки, и многими другими. Вы можете посмотреть их в любом редакторе HTML, например, пробуя в редакторе FrontPage вводить компоненты с помощью команды Insert | Form Field. Компоненты имеют ряд атрибутов. Атрибут type определяет тип компонента. Атрибут name определяет имя компонента. Атрибут value определяет значение данных компонента — текст в окне, надпись на кнопке и т.п. Серверному приложению посылаются строки вида "name=value", так что по имени name можно прочитать значение value. Как уже говорилось, в зависимости от метода Post или Get эти строки лежат в свойстве ContentFields или QueryFields объекта Request. Так что, например, выражения Request.ContentFields.values!'result'] или
Request.QuetyFields.Values['result'] вернут значения атрибута result.
В однострочном окне редактирования имеется атрибут size, задающий размер окна — количество размещаемых в нем без прокрутки символов.
Разработка приложений, использующих сети Интернет и интранету
751
Чтобы упорядочить в форме размещение компонентов, их обычно размещают в ячейках таблицы. Таблица начинается тегом
. Между ними вставляются пары тегов
и
, определяющие очередную строку таблицы. А между этими тегами вставляются пары тегов
и | , определяющие очередную ячейку в строке. Между этими тегами размещается содержимое ячейки. Поясним все это примером. Пусть мы хотим усовершенствовать созданное в предыдущих разделах приложение и сделать его универсальным для проверки знания клиентом таблицы умножения. Вид документа HTML, с которым будет работать пользователь, показан на рис, 12.10. Рис, 12.10
•Э Таблица умножен™ - F-finoneft fe^TM
Форма проверки знания таблицы умножения 'Чая::. 1
Проверьте свое знание таблицы умножения
"TJ
Запишите в окне, чему равно произведение, и нахинте кнопку "Отеет" J7
я|э~~=[Ответ | Новые числа "л.
^
Серверное приложение должно заносить в документ HTML значения двух перемножаемых одноразрядных чисел. Для этого надо предусмотреть два окна редактирования, которым мы дадим имена numl и тшт2. Ответ пользователь должен помещать в окно редактирования, которому мы дадим имя result. В документе должна быть кнопка Ответ, по которой данные пересылаются в серверное приложение mult.exe с Pathlnfo равным "/!"• Внизу должна быть кнопка Новые числа, при нажатии которой вызывается приложение tnult.exe. Ниже приведен текст страницы Web, обеспечивающей изображенную на рис. 12.10 форму документа. В ней использованы описанные выше теги форм и таблиц.
Проверьте свое знание таблицы умножения 3апишите в окне, чему равно произведение, и нажмите кнопку "Ответ" "POST" acti.on="http://mycornputer/cgi-bin/mult.exe/l">
12.8.2 Обмен данными меду клиентом и сервером Теперь построим серверное приложение, выполняющее проверку знания таблицы умножения. Проблема, возникающая в этом приложении, характерна для большинства задач обмена информацией между клиентом и сервером. Предположим, диалог с клиентом начинается с того, что приложение посылает ему два случайных числа, которые надо перемножить. После этого связь серверного приложения с клиентом прерывается. Когда клиент занесет в окно результат и нажмет кнопку Ответ, серверное приложение опять будет вызвано. Но для того, чтобы проверить ответ пользователя, надо знать, какие числа были заданы ему для перемножения. А приложение этого не знает, поскольку оно снова запущено на выполнение, и никакие глобальные переменные не могут хранить данные, переданные при его прошлом запуске. Значит, в приложении должна быть организована память. Один из вариантов — хранить значения заданных чисел на внешнем носителе — в файле или базе данных. Другой вариант — читать их из документа, из которого вызывается приложение. Давайте выберем второй вариант. Именно для его реализации в приведенной выше форме документа предусмотрены окна лшп! и пшп2. В них передается информация при создании документа HTML и из них же читается информация при вызове приложения из этого документа. Заодно расширяются возможности приложения: пользователь сам может задавать перемножаемые числа. Итак, чтобы построить подобное приложение, создайте новый модуль сервера и задайте в нем два действия. Первое действие со значением Pathlnfo = "/1" будет анализировать ответ клиента. Второе действие с пустым значением P a t h l n f o и с Default = true будет создавать основной документ HTML. Его обработчик события OnAction может иметь следующий вид; procedure TWebModulel.WebModulelWebActionltemSAction( Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean]; var Huinl, Nurn2: word; begin Randomise; Nurnl := Random(B) -t-1; Nura2 := R a n d o m ( В ) -И ; Response.Content := ' < i n p u t type="text" name="num2." s i z e = " l " value=' + IntToStr (Num2) + ' x / t d x t d > =* ' t ' ' + ' < t d x i n p u t type="submit" name""post" уа!ие="0твет">' + '' +
Разработка приложений, использующих сети Интернет и интраиет
753
'' + '
' ; Handled := true; end;
Первые операторы заносят с помощью функции Random в локальные переменные Numl и Num2 случайные целые числа, равномерно распределенные в интервале от 1 до 9. Предварительно функцией Randomize производится рандомизация, чтобы при каждом запуске приложения генерировались новые числа. Если вам не совсем понятны эти функции, посмотрите их в гл. 16 а разд. 16.10. А после этого генерируется документ HTML, который был приведен выше и который при загрузке в браузер имеет вид, представленный на рис. 12.10. При этом в окна с именами numl и пшп2 в атрибут value заносятся в текстовом представлении значения чисел Numl и Num2. Возможно, вас смутит необходимость формировать в обработчике события OnAction такой длинный текст страницы HTML. Действительно, это довольно утомительно и не наглядно. А ведь документы HTML бывают на порядок более сложными, чем в нашем примере. Но не огорчайтесь! В разд. 12.9 будет показано, как красиво и просто можно решить эту задачу, кардинально сократив обработчик события. А пока продолжим. Обработчик события OnAction первого действия может иметь вид: procedure TWebModulel.WebModulelWebActionltemlAction( Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); begin if Request-ContentFields.Values['result'] = IntToStrtStrToInt(Request-ContentFields.Values['numl1]) * StrTolnt (Request.ContentFields.Values ['пшпЗ' ]) ) then Response.Content := 'Поздравляю! ! !Вы блестящий математик! ' else Response.Content := 'Вы немного забыли таблицу умножения' -* '' + 1 Полробуйте еще раз=;/аХ/р> т ,Handled := true; end,-
Условие в операторе if сравнивает ответ пользователя в окне result с произведением чисел, введенных в окнах numl и пшп2. Ответы, формируемые при верном и неверном ответах пользователя, вы можете видеть на рис. 12.11. а!
6} L ШЯИЦЯдй^тдв^не
•a hllpy/mytoniputer/ Фапл
Цзбмниое
Правка
Cgpe
У n/mJt.eie/l_*] £Д Переход ; Ссыпки
Поэдравляю!!!
Фаин
Правка
&чл
И*5оаяйе
ij^nbjt.eie/ljj ^Перетд > Ссы™м
Вы немного забыли таблицу умножения
Вы блестящий математик! Попробуйте ешеоаэ A J Местная ишрасегь
Cjpe
!^f~ |— ^
^местиля интрасвтв
Рис. 12.11: Страницы HTML при верном (а) и неверном (б) ответах
Глава \Z
754
/ Сохраните приложение под именем Mult (это'имя в приведенном коде используется в ссылках), скомпилируйте его и поместите выполняемый модуль в выполняемый каталог сервера. Проверьте его в работе, давая правильные и неправильные ответы.
12.9 Использование шаблонов HTML В разд. 12.8.2 вы видели, как неудобно формировать сколько-нибудь сложные документы HTML в обработчике событий сервера OnAction. Выходом из положения является использование шаблонов HTML. Шаблонами называются специальные теги, которые приложение сервера заменяет на какие-то выражения HTML. Тег шаблона выглядит следующим образом: OlTagName Paraml^Valuel Param2=Value2
...>
Шаблон помещается между символами угловых скобок. За открывающейся скобкой без пробела должен следовать символ "#", а за ним тоже без пробела — имя шаблона. По этому имени приложение может распознать шаблон. Есть некоторые общепринятые имена шаблонов. Например, имя Link применяется для задания ссылок, имя Image — для задания элементов графики и т.п. Но вы можете задавать любые имена и сами решать, что скрывается за тем или иным именем. Шаблон может включать в себя ряд параметров с некоторыми значениями, по которым приложение может получить более подробную информацию, необходимую для замены шаблона. Для использования шаблонов применяется компонент PageProducer, размещенный на странице Internet палитры компонентов. Давайте с помощью этого компонента повторим пример, рассмотренный в разд. 12.8.2, и вы увидите, как облегчает жизнь использование шаблонов. Создайте новый модуль сервера и задайте в нем два действия. Первое действие со значением Pathlnfo = "/1", как и раньше, будет анализировать ответ клиента. Обработчик его события OnAction ничем не будет отличаться от приведенного в разд. 12.8.2. Второе действие С пустым значением Pathlnfo и с Default = true будет, как и раньше, создавать основной документ HTML. Но не спешите писать очень длинный текст его обработчика события OnAction. В написании этого текста нам поможет компонент PageProducer. Перенесите этот компонент в окно сервера WebModuIel, которое у нас во всех предыдущих примерах не использовалось (в Delphi 5 надо перенести компонент на страницу Components этого окна). Посмотрите в Инспекторе Объектов свойства компонента PageProducer. Основными свойствами являются HTMLDoc и HTMLFile. Первое из них имеет тип TStrings и позволяет записать документ HTML, пользуясь обычным редактором строк, вызываемым нажатием клавиши с многоточием около этого свойства. Второе свойство HTMLFile — позволяет указать имя используемого файле документа .html или .htm. Для поиска этого файла можно воспользоваться диалогом открытия файла, вызываемым нажатием кнопки с многоточием. Таким образом, документ HTML с включенными в него тегами шаблонов может включаться в выполняемый модуль (если заполнить свойство HTMLDoc) или быть отдельным файлом. Каждый из этих подходов имеет свои достоинства и недостатки. Хранение документа в отдельном файле (использование свойства HTMLFile) позволяет использовать один документ нескольким приложениям. Кроме того, это облегчает модернизацию документа. Если вы решите изменить страницу Web, отредактировать текст, добавить или сменить картинки, то вы легко сможете сделать это, изменяя файл документа и не компилируя заново приложение сервера. А если документ предназначен только для данного приложения и вы не намерены в будущем изменять его, то естественнее не иметь дело с лишними файлами и воспользоваться свойством HTMLDoc. Это не мешает вам создать исходный документ в любом ис-
Разработка приложений, использующих сети Интернет и интранет
755
пользуемом вами редакторе HTML. А затем вы можете скопировать весь текст подготовленной страницы в свойство HTMLDoc. Хранение документа в отдельном файле имеет некоторые недостатки. Во первых, если вы передаете свое серверное приложение каким-то пользователям, вы должны одновременно передавать и файлы документов. Кроме того, при смене адреса сервера могут потребоваться изменения ссылок на документы и перекомпиляция приложения- Всех этих трудностей не будет, если документ хранится в приложении, т.е. используется свойство HTMLDoc. Единственное событие компонента PageProducer — OnHTMLTag. Это событие наступает, когда в тексте документа HTML встречается шаблон. Заголовок обработчика события OnHTMLTag имеет вид: procedure TWebModulel.PageProducerlHTMLTag[Sender: TObject; Tag: TTag; const TagString: String; T a g P a r a m s : TStrings; var ReplaceText: S t r i n g ) ;
Параметр Tag определяет тип шаблона, если имя шаблона — параметр TagString равно одному из общепринятых. При имени, отличном от общепринятых, тип равен tgCustom. Параметр TagParams определяет параметры шаблона, если они заданы. А в параметр ReplaceText обработчик должен занести текст, заменяющий шаблон. Если текст не будет занесен, шаблон заменится пустой строкой. Соотношение возможных значений параметров определяется следующей таблицей:
Tag
TagString
TagParams
ReplaceText
tgLink
LINK
Ссылка
Последовательность команд HTML, начинающаяся с тега и кончающаяся тегом
tglmage
IMAGE
Описание графиче- Тег ского изображения
tgTable
TABLE
Описание элементов таблицы
Последовательность команд HTML, начинающаяся с тега
Графическое изображение, связанное с «горячейв областью
Последовательность команд HTML, начинающаяся с тега и кончающаяся тегом Последовательность команд HTML, начинающаяся с тега и кончающаяся тегом
tglmageMap IMAGEMAP
tgObject
OBJECT
Ссылка на компонент ActiveX (см. разд. 12.11)
tgEmbed
EMBED
Ссылка на элемент Последовательность команд стиля Netscape HTML, начинающаяся с тега и кончающаяся тегом
tgCustom
Произвольное имя, отличное от указанных выше
Может быть пусПроизвольная последоватетым или определя- льность команд, определяеться приложением мая приложением
756
_
Глава
12
В большинстве случаев наиболее удобно произвольное имя шаблона, дешифруемое приложением. Впрочем, ничто не мешает использовать и общепринятые имена по своему усмотрению. Привязка компонента PageProducer к тому или иному действию серверного приложения Web может осуществляться двумя путями. Можно в действии указать а свойстве Producer имя одного из включенных в модуль компонентов PageProducer. В этом случае текст документа HTML, поставляемый компонентом PageProducer, передается в свойство Response. Content обработчика события действия OnAction, после чего он может быть изменен в этом обработчике. А если вам ничего не надо изменять в этом документе, то вообще можете не писать обработчик события OnAction. Таким образом, подобное связывание действия и PagcProducer позволяет не описывать в обработчике OnAction документ HTML. Это существенно облегчает написание обработчика. Каждый компонент PageProducer в некоторый момент времени может быть привязан свойством Producer только к одному действию. Если окажется, что он привязан к нескольким действиям, то во всех, кроме первого, свойствам Producer будет принудительно присвоено значение nil. Более интересный способ связи обработчика события действия OnAction с компонентом PageProducer является вызов из обработчика метода PageProducer. Content. При вызове этого метода PagcProducer начинает анализировать документ, генерирует события OnHTMLTag и в обработчиках этих событий может производить замены шаблонов с учетом каких-то установок, произведенных в обработчике OnAction. Посмотрим, как это все можно использовать при воспроизведении того примера серверного приложения, которое было рассмотрено в разд. 12.8.2. Выше уже говорилось, что в модуле сервера надо создать те же действия, что и ранее, и перенести в модуль компонент PageProducer. Текст основного документа HTML, который ранее генерировался в обработчике второго действия, может быть записан в отдельном файле или в свойстве HTMLDoc компонента Page Producer. В данном случае это текст может иметь следующий вид: Таблица
•
Проверьте свое знание таблицы умножениж/Ы> 3апишите в окне, чему равно произведение, и нажмите кнопку "Ответ" 1980) then begin // реакция на противоречащее ограничениям значение года Response.Content := 'Простите, Вы не подходите на эту должность' + 1 по возрасту'; Handled := true; exit; end; Tablel.Active := true; Tablel.Insert; TablelFam.Asstring :«= Request.ContentFields.Values['Fam']; TablelMam.AsString := Request.ContentFields.Values['Nam']; TablelPar.AsString := Request.ContentFields.Values['Par']; TablelYear^b.AsInteger := year; TablelSex.Value := ContentFields.Values['Sex'];; // Пересыпка новой записи в Базу данных Tablel. Post,Tablel.Active := false; Response.Content :'Спасибо!Сведения о Вас включены в базу данных' + 'Мы рассмотрим Ващу кандидатуру и сообщим результаты'; Handled := true; end; end;
768
Глава 12
В этом коде первый оператор if проверяет, нет ли хотя бы одного незаполненного поля. Если есть, то пользователю выдается страница, содержащая сообщение об этом и предложение повторить ввод. Пользователь может в браузере вериться на предыдущую страницу и ввести на ней недостающие данные. Далее в коде следует оператор, читающий в локальную переменную year текст, введенный пользователем в поле Year. Если текст не соответствует формату целого числа (например, в текст по ошибке введены буквенные символы), то генерируется исключение, которое перехватывается в блоке except и пользователю выдается соответствующее замечание. Если год прочитан нормально, то следующий оператор if проверяет, не выходит ли его значение за ограничения, имеющиеся в таблице. Если выходит, то пользователю выдается сообщение, что он по возрасту не подходит. Эта проверка необходима, так как если в момент записи в базу данных выяснится, что ограничения нарушены, будет сгенерировано исключение. Если все нормально, то далее открывается соединение с базой данных, методом Insert в наборе данных создается новая пустая запись, ее поля заполняются данными клиента, после чего запись пересылается в базу данных методом Post и пользователю выдается сообщение о включении его в базу данных. Конечно, в настоящем приложении надо бы предусмотреть реакцию на исключение, которое может быть сгенерировано при пересылке записи в базу данных. Для простоты соответствующие операторы в приведенный текст не включены. Сохраните ваш проект под именем DB4, скомпилируйте, перенесите в исполняемый каталог сервера и проверьте в работе (см. рис. 12.17). Как видим, включение в Интернет в базу данных новых записей, как и редактирование существующих записей, никаких сложностей не представляет. Для решения этих задач не требуются какие-то специфические компоненты, кроме обычных компонентов наборов данных. Все рассмотренные примеры работы с базами данных в Интернет использовали компонент набора данных Table. Если вы хотите работать с запросами SQL с помощью компонента Query (что предпочтительнее — см. разд. 10,1.6.1 гл. 10), то для просмотра записей таблицы вместо компонента DataSetTableProducer вам надо использовать компонент QueryTaWeProducer, Его свойство Query должно содержать ссылку на набор данных типа TQuery. В остальном свойства QueryTableProducer и DataSetTableProducer не различаются.
12.11 Технология WebSnap 12.11.1 Web Broker или WebSnap? Все, что было рассмотрено выше относилось только к одной технологии разработки приложений в Интернет, называемой Web Broker. В Delphi имеется возможность создавать приложения с помощью ряда других технологи. Остановимся сначала на одной из них — WebSnap. Основа функционирования серверных приложений Web, спроектированных по технологии Web Broker или WebSnap, в некоторых отношениях одинакова. Всю основную работу выполняет сервер. Итоговую информацию он представляет в виде страниц HTML. Эти страницы передаются пользователю с помощью протокола HTTP. А в качестве клиентского приложения выступает браузер. Он отображает пользователю полученную страницу Web. Пользователь просматривает ее и производит какие-то действия. Эти действия сводятся к тому, что он направляет серверу очередной запрос или переходит по ссылке на другую страницу Web. Возможности обеих технологий Web Broker и WebSnap во многом совпадают. Обе поддерживают различные типы приложений, в частности, CGI and Apache DSO
Разработка приложений, использующих сети Интернет и интранет
769
Web. Обе поддерживают многопоточный обмен информацией между сервером и клиентами, обрабатывая запросы клиентов в отдельных потоках. Обе обеспечивают достаточно простой переход между платформами Windows и Linux. Такое сходство технологий не случайно, так как WebSnap построена на основе Web Broker и, значит, может все, что доступно в Web Broker. Но возможности WebSnap заметно шире. В WebSnap имеются более мощные средства генерации страниц. В частности, на стороне сервера могут использоваться скрипты. Для тех, кто не знаком с этим понятием, ограничусь кратким пояснением. Это элементы таких языков, как VBScript, JavaScript и ряда других. Скрипты представляют собой конструкции, обеспечивающие на основе объектно-ориентированного подхода выполнение некоторых операций в процессе отображения страницы. Должен сказать, что сейчас редко можно встретить страницы Web, в которых нет скриптов. Имеется обширная литература по соответствующим языкам, и в рамках данной книги уделять внимание описанию этих языков невозможно. Поэтому далее ограничимся только такими приложениями WebSnap, которые не потребуют знания скриптов. Итак, одним из преимуществ WebSnap является возможность использовать скрипты. Другое достоинство WebSnap — возможность иметь в приложении несколько модулей Web. Технология WebSnap позволяет также сохранять информацию о пользователе, работающем с клиентом. Это дает возможность проще организовать парольную защиту приложения. Наконец, программирование в WebSnap более визуально, чем то, что вы могли видеть в предыдущих разделах для Web Broker. Так что во всех отношениях технология WebSnap превосходит Web Broker. Впрочем, многое из того, что рассмотрено в предыдущих разделах, доступно и в WebSnap.
12.11.2 Сервер WebSnap
•
Приложение WebSnap, как и приложение Web Broker, состоит из исполняемого файла (.ехе ила ЛИ в зависимости от типа приложения) и ряда файлов с шаблонами страниц HTML. Так что содержания страниц можно изменять, не компилируя повторно исполняемый файл. Программная часть приложения содержит ряд модулей разных типов. Основной модуль — это модуль приложения Web типа TWebAppPageModule. Помимо него в приложение может быть включено несколько модулей страниц Web типа TWebPageModule. Несколько модулей имеет смысл вводить в тех случаях, когда имеется несколько групп страниц, существенно отличающихся по стилю и функциям. Приложение может также содержать модуль данных приложения TWebAppDataModule и несколько модулей данных страниц TWebDataModule. Основной модуль приложения и модули страниц содержат компоненты различных видов, которые вы можете видеть на странице библиотеки WebSnap. Прежде всего, это диспетчеры, обеспечивающие анализ поступающих запросов и передачу нх другим компонентам для обработки. Диспетчер страниц типа TPageDispatcher анализирует запрос и решает, какому модулю страниц он должен быть передан. Диспетчер типа Т Adapter Dispatcher обеспечивает передачу запроса адаптерам. Адаптеры — это особый вид компонентов, входящих s состав модулей страниц. Их задача — обеспечить выполнение скриптов, транслируемых в вызовы методов и свойств адаптеров. От адаптеров информация поступает продюсерам, генерирующим страницу на основе полученной информации и на основе указанного шаблона HTML. Начать создание приложения WebSnap можно двумя эквивалентными способами. Можно взять соответствующие модули из Депозитария (команда File [ New | Other) со страницы WebSnap. А можно сделать видимой панель быстрых кнопок Interne! (щелчок правой кнопкой мыши на любой панели быстрых кнопок и вклю2S Програн иированне в Delphi 7
Глава 12
770
чение индикатора около Internet). В этой панели доступны пиктограммы требуемых типов модулей. Выберите тем или иным способом пиктограмму WebSnap Application. Перед вами откроется окно, показанное на рис. 12.18. Его верхняя панель позволяет выбрать тип приложения. Различные типы приложений уже были рассмотрены в предыдущих разделах. Выберем тип CGI stand-abne executable. Панель Application Module components позволяет задать вид модуля: модуль страницы, или модуль данных. Выберем модуль страницы — Page Module. Кнопка Components показывает окно, в котором вы можете выбрать компоненты, помещаемые в модуль. Оставим набор компонентов, задаваемый по умолчанию. Тем более что впоследствии ничто не мешает вам изменить или дополнить этот комплект переносом в модуль требуемых компонентов из библиотеки. Рис. 12.18
Окно выбора типа приложения WebSnap
JCI
New VrtbSreao Application You may select from one of У* foBowmo. types ol Worid Wids Web server appbcatiorB. С ISAPI/NSAPDjnsmcUrkUbraip
-,
Г Ap«he !* Shared Motk« |DLL) Г Apsrf»2«Sbai«(Mo P Лившие typed constant
r
- bR-jr-jJ
|
0,
|
СагЫ
Ht*
Группа индикаторов Code generation (опции генерации кода): Индикатор
Опция Описание
Optimizations {$0}
Slack frames
{$W}
Компиляция с оптимизацией. Оптимизация повышает эффективность программы, иногда сокращает выполняемый файл, но может затруднять отладку из-за устранения из кода некоторых операторов и переменных. Заставляет компилятор генерировать стек для всех процедур и функций.
|
Глава 13
810
Индикатор Опция Описание Pentium-Safe FDIV
{$UJ
Генерирует код, проверяющий наличие ошибок деления, свойственных ранним версиям Pentium.
Record field alignment
Overflow Checking {$Q}
Проверка ошибок ввода/вывода после каждой операции ввода /вывода. Проверка переполнения при целочисленных операциях.
Группа индикаторов Syntax options (опции синтаксиса): Индикатор
Опция Описание
Strict Var-Strings
{$v>
Проверка параметров типа строк; если установлен индикатор Open parameters, то данная опция не действует.
Complete Boolean Evol
{$B}
Вычисление всех элементов булева выражения, даже если результат выражения очевиден после вычисления первых элементов (например, в операции И вычисляются оба операнда, даже если первый из них равен false).
Extended Syntax
{$X}
Typed @ Operator
{$T>
Разрешение использования вызова функций как процедур (с игнорированием возвращаемого результата); поддержка типа Pchar. Проверка типа оператора, возвращаемого операцией @
Open Parameters
{$P}
Huge Strings
{$H}
Assignable Typed Constants
{$•1}
Разрешение параметров процедур и функций в виде открытых строк (тип string эквивалентен OpenString — см. разд. 13.6.1); открытые параметры обычно более наделены и эффективны. Разрешение сборки «мусора» строк; при включении этой опции тип string эквивалентен типу AnsiString, а при выключенной — ShortString. Разрешение присваиваний типизированным константам (опция оставлена для обратной совместимости с Delphi 1).
Справочные данные по языку Object Pascal в Delphi 7
811
Группа индикаторов Debugging (опции отладки): Индикатор
Опция
Описание
Debug Information
{$D}
Размещение отладочной информации в объектных файлах модулей ,dcu.
Local Symbols
{$L}
Генерация информации о локальных символах.
Reference Info/Definilions Only
Включает в код директивы проверки утверждений; при выключении этого индикатора код Assert удаляется из файла, но для этого надо повторно откомпилировать модуль.
Use Debug DCUs
Разрешает использовать отладочную версию компонентов VCL; при включении этого индикатора Delphi использует вместо пути Search path (устанавливается командой Project Options на странице Directories/Condiiionals) путь Debug DCU (устанавливается командой Tools Debugger Options на странице General).
До Delphi 7 на странице Compiler была еще группа индикаторов Messages (опции сообщений компилятора); Индикатор
Описание
Show Hints
Вызывает генерацию замечаний компилятора — Hint.
Show W a r n i n g s Вызывает генерацию предупреждений компилятора - W a r n i n g . Начиная с Delphi 7, эти индикаторы перенесены на отдельную страницу Compiler Messages. На ней, помимо этих индикаторов, имеется список замечаний компилятора Warnings, каждое из которых можно включать и выключать. В частности, в Delphi 7 появились предупреждения Unscife_Type, Unsafe_Code и Unsafe_Cost — ненадежный, опасный тип, код или приведение типов (директива препроцессора {$WARN UNSAFE_CODE ON}), Эти предупреждения, выключенные по умолчанию, означают, что соответствующие конструкции языка вызовут проблемы при переносе на платформу Microsoft .NET. Индикатор Default, доступный на всех страницах окна опций проекта, устанавливает выбранный набор опций как набор по умолчанию для последующих проектов.
812
_
Глава
13
13.2.3 Директивы условной компиляции Условная компиляция дает возможность программисту управлять выполнением директив препроцессора и компиляцией программного кода. Она может строиться по двум схемам. Первая из них имеет вид: {SENDIF}
Начинается она с условной директивы {$IFxxx}, которая проверяет некоторое условие. Эти директивы будут рассмотрены ниже. Если проверяемое условие выполняется, то код, расположенный между условной директивой и директивой {$ENDIF}, компилируется. В противном случае этот фрагмент кода не компилируется, т.е. все расположенные в нем директивы препроцессора и операторы Object Pascal игнорируются и в результирующий файл не попадают. Вторая схема задания условной компиляции имеет вид: {SIFxxx} {SELSE} {SENDIF}
В этом случае, если условие, проверяемое директивой, истинно, то компилируется фрагмент кода, расположенный между ней и директивой {$ELSE}. Если же условие ложно, то компилируется фрагмент кода, расположенный между директивой {$ELSE} и директивой {$ENDIF}. Теперь рассмотрим сами условные директивы. Первая из них {$IFDEF}: {S1FDEF условный идентификатор)
Эта директива проверяет, был ли определен указанный в ней условный идентификатор. Определение условного идентификатора задается директивой ($DEF1NE}: {SDEF1NE
условный идентификатор}
Таким образом, если некоторый условный идентификатор был задан директивой {SDEFINE}, то директива {$IFDEF}, проверяющая этот идентификатор, выдает истину и следующий за ней фрагмент кода будет компилироваться. А если директивы {SDEFINE} не было или она была отменена противоположной ей директивой {$UNDEF}: •(SUNDEF условный идентификатор)
то директива {$IFDEF}, проверяющая этот идентификатор, выдаст ложь и последующий фрагмент кода компилироваться не будет. Имеется также директива {SIFNDEF} , противоположная по смыслу директиве {$IFDEF}. Она возвращает истину, если указанный в ней условный идентификатор не определен. Работая с условными идентификаторами, имейте в виду, что они не имеют никакого отношения к идентификаторам, используемым в программе. Например, в директиве {$IFDEF} вы можете проверять некий условный идентификатор Debug, а в программе у вас может быть переменная Debug булева типа. Это совершенно разные идентификаторы. Результат директивы {$IFDEF} никак не связан со значением true или false переменной Debug. Имеется еще одна условная директива — {$IFOPT}: JSIFOPT ключевая опция}
Она возвращает истину, если включена указанная ключевая опция. Например, во фрагменте кода
Справочные данные по языку Object Pascal в Delphi 7
813
(SlFOPT Q+l {SENDIF}
код между опциями {$IFOPT} и {$ENDIFJ будет компилироваться, только если включена опция Q проверки переполнения при целочисленных операциях. Условная компиляция, обеспечиваемая условными директивами, может быть полезна во многих случаях. Например, нередко в процессе отладки приложения в него полезно ввести различные отладочные печати, позволяющие следить за ходом выполнения программы. Если вы не хотите, чтобы эти печати оставались в окончательном варианте программы, вы можете в разных местах приложения ввести конструкции вида (SIFDEF Debug) операторы отладки
(SENDIF}
Тогда, если в аачале программы вы введете директиву {$DEFINE Debug)
операторы отладки будут компилироваться и выполняться. Но когда вы уберете эту директиву, или закомментируете ее: //{SDEFINE
Debug)
или введете после нее директиву {SUNDEF Debug)
.
все операторы отладки исчезнут из текста. Конечно, вы могли бы поступить иначе: ввести переменную булева типа Debug, задать ей в начале выполнения приложения значение true и оформлять отладки следующим образом: if (Debug) then begin операторы отладки end;
Если в дальнейшем заменить задаваемое значение Debug на false, то операторы отладки перестанут выполняться. Отличие этого подхода от использования директив препроцессора заключается в том, что коды операторов отладки в этом случае останутся в тексте программы, увеличивая размер выполняемого модуля. А директивы условной компиляции просто уберут отладочный код из программы.
13.2.4 Некоторые ключевые директивы В разд. 13.2.1 пояснялся синтаксис и назначение ключевых директив. В разд. 13.2.2 при описании настройки компилятора описаны многие ключевые опции. В данном разделе приводятся более подробные сведения по тем из них, с которыми вы можете встретиться на страницах данной книги. Полный перечень ключевых директив и директив параметров содержится в книге [1].
{$С+} и {$C-J, {$ASSERTIONS ON} и {$ASSERTIONS OFF} — директивы проверки утверждений Директивы компилятора, разрешающие или запрещающие проверку утверждений. . Синтаксис {$С+} или {$С-> {$ASSERTIONS ON} или {$ASSERT1ONS OFF}
814
Глава 13
По умолчанию {$С+} или {$ASSERTIONS ON} Область действия локальная Описание Директивы разрешают или запрещают проверку утверждений, влияют на работу процедуры Assert (см. разд. 13.2.5 и 2.8.9), используемой при отладке программ. По умолчанию действует директива {$С+} и процедура Assert генерирует исключение EAssertionFailcd, если проверяемое утверждение ложно. Так как эти проверки используются только в процессе отладки программы, то перед ее окончательной компиляцией следует указать директиву {$С-}. При этом работа процедур Assert будет блокирована и генерация исключений EAssertionFailed производиться не будет. {$!+} и {$!-}, {$IOCHECKS ON} и {$IOCHECKS OFF} --директивы контроля ввода-вывода _ Директивы компилятора, включающие и выключающие контроль файлового в во да- вы во да. Синтаксис {$!+} или {«IOCHECKS ON} {$!-} ила {$IOCHECKS OFF} По умолчанию {$!+} или {$IOCHECKS ON} Область действия локальная Описание Директивы включают или выключают автоматический контроль результата вызова процедур ввода-вывода Object Pascal. Если действует директива {$!+}, то при возвращении процедурой ввода-рывода ненулевого значения генерируется исключение EInOutError и в его свойство errorcode заносится код ошибки. Таким образом, при действующей директиве {$!+} операции ввода-вывода располагаются в блоке try.. .except (см. разд. 13.10,6), имеющем обработчик исключения EInOutError. Если такого блока нет, то обработка производится методом ТАррНса tio n . Han dl еЕ хс ер tion . Если действует директива {$!-}, то исключение не генерируется. В этом случае проверить, была ли ошибка, или ее не было, можно, обратившись к функции lOBesult. Эта функция очищает ошибку и возвращает ее код, который затем можно анализировать. Типичное применение директивы {$!-} и функции lOResult демонстрирует следующий пример: 1*1-) AssignFile (F, s] ; Rewrite (F) ; ii -lOResult;
if i0 then case i of
2: зг-'ФаЙл ''• +s+ ' ' ' не найден'; 3 : s : = ' Ошибочное имя файла ' ' ' +s+ ' ' ' ' ; 4: з:='Слишком много открытых файлов'; 5 : з:= 'Файл ' ' ' +s+' ' ' не доступен ' ; 100: s:= 'Достигнут конец файла ''' -t-s-i-' ' ' ' ,- Т 1 101: s: = 'Диск переполнен при работе с файлом ' +Б+'' |Г ; 106: s:= 'Ошибка ввода при работе с файлом | Т | +в+ ' ' ' ' ; end; MessageDlg (s, mt Warning, [mbOk] , 0) ,•
В этом примере на время открытия файла отключается проверка ошибок ввода вывода, затем она опять включается, переменной i присваивается значение, воз-
Справочные данные по языку Object Pascal в Delphi 7
815
вращаемое функцией lOResult и, если это значение не равно нулю (есть ошибка), то предпринимаются какие-то действия в зависимости от кода ошибки. Подобный стиль программирования был типичен до введения в Object Pascal механизма обработки исключений. Однако сейчас, по-видимому, подобный стиль устарел и применение директив $1 потеряло былое значение, все более уступая место обработке исключений (см. разд. 13.10). {$R}, {$RESOURCE} — директива связывания ресурсов Директива компилятора, связывающая с выполняемым модулем файлы ресурсов Синтаксис {$R } или {$RESOURCE } где — имя или шаблон файла. Область действия локальная Описание Директива указывает файлы ресурсов (.dfm, .res), которые должны быть включены в выполняемый модуль или в библиотеку. Указанный файл должен быть файлом ресурсов Windows. По умолчанию расширение файлов ресурсов — .res. В процессе компоновки компилированной программы или библиотеки файлы, указанные в директивах {$R}, копируются в выполняемый модуль. Компоновщик Delphi ищет эти файлы сначала в том каталоге, в котором расположен модуль, содержащий директиву {$R}, а затем в каталогах, указанных при выполнении команды главного меню Project | Options на странице Directories/Conditionals диалогового окна в опции Search path или в опции /R командной строки DCC32. При генерации кода модуля, содержащего форму, Delphi автоматически включает в файл .pas директиву {$R *.dfm), обеспечивающую компоновку файлов ресурсов форм. Эту директиву нельзя удалять из текста модуля, так как в противном случае загрузочный модуль не будет создан и сгенерируется исключение EResNotFound.
13.2.5 Процедура Assert Процедура Assert является полезным инструментом отладки. Она объявлена в модуле System следующим образом: procedure Assert [expr : Boolean ]; const msg: s t r i n g ] ) ; Процедура применяется при отладке программ для проверки истинности утверждений, которые по замыслу должны быть истинны, но в силу каких-то ошибок могут нарушаться. Если проверяемое утверждение окажется ложным, процедура приводит к прекращению работы, генерации исключения EAssertionF ailed и выдаче сообщения об ошибке. Процедура Assert получает в качестве первого параметра булево выражение ехрг, которое и проверяется на истинность. В процедуру может быть также передана строка сообщения msg. Если проверяемое утверждение оказывается ложным, то генерируется исключения EAssertionF ailed с заданной строкой сообщения. Если строка в вызове процедуры Assert не указана, то исключение генерируется со стандартной строкой сообщения. Сообщение отображается с указанием имени файла, полным путем к файлу и номером строки, в которой находится вызов Assert, соответствующий ложному выражению. Пусть, например, вы хотите проверять, не окажется ли в результате какой-то ошибки введенный вами указатель Р равным nil (см. гл. 13 разд. 13.13). Тогда вы в соответствующем месте кода можете ввести оператор: A s s e r t f P о nil,
'Указатель Р = n i l ' ) ;
Глава 13
816
Если при выполнении этого оператора значение Р окажется равным nil, то будет отображено диалоговое окно (см. рис. 13.2), содержащее сообщение «Указатель Р = nil» и указание файла и строки в нем, содержащей вызов Assert. Рис. 13.2 Пример сообщения, генерируемого процедурой Assert
Уканте» Р - nl (& \Projam FHs\BoriandlO«lphi?(fiinlunl] pas, Ire 24)
Компоненты библиотеки Delphi перехватывают все исключения. Но если все-таки исключение ЕAssertionFailed не будет перехвачено, оно вызовет прерывание работы программы с кодом 227. Процедура Assert работает только при включенной директиве проверки утверждений {$С+} или {$ASSERTIONS ON} (см. разд. 13.2.4). Поскольку в отлаженной программе эта процедура обычно работать не должна, то перед окончательным оформлением выполняемого модуля надо скомпилировать файл с отключенной директивой проверки ({$С-} или {$ASSERTIONS OFF}. При этом компиляция проверок не производятся, так что в отлаженной программе не остается ничего лишнего.
13.3 Файлы проекта Delphi 13.3.1 Основные файлы проекта Проект Delphi состоит из форм, модулей, установок параметров проекта, ресурсов и т.д. Вся эта информация размещается в файлах. Многие из этих файлов автоматически создаются Delphi. Ресурсы, такие, как битовые матрицы, пиктограммы и т.д., находятся В файлах, которые вы получаете из других источников или создаете при помощи многочисленных инструментов и редакторов ресурсов, имеющихся в вашем распоряжении. Кроме того, компилятор также создает файлы. Ниже приведена характеристика основных файлов, используемых при создании выполняемого файла приложения. Более подробный перечень файлов, используемых в проектах Delphi, содержится в гл. 2 в разд. 2,3.1. Головной файл проекта (.dpr)
Этот текстовый файл используется для хранения информации о формах и модулях. В нем содержатся операторы инициализации и запуска программы на выполнение.
Файл модуля (.pas) Каждой создаваемой вами форме и каждому фрейму соответствует текстовый файл модуля, используемый для хранения кода. Иногда вы можете создавать модули, не связанные с формами. Многие из функций и процедур Delphi хранятся в модулях. Файл формы (.dfm) Это двоичный или текстовый файл, который создается Delphi для хранения информации о ваших формах. Каждому файлу формы соответствует файл модуля (.pas). Файл параметров проекта (.dfo)
В этом файле хранятся установки параметров проекта.
Файл ресурсов (.res)
Этот бинарный файл содержит используемую проектом пиктограмму и прочие ресурсы.
Справочные данные по языку Object Pascal в Delphi 7
817
Файл группы файлов (.bpg)
Этот файл, создается, если вы работаете с группой проектов.
Файл пакета (.dpk)
Это двоичный файл пакета (package).
Файлы резервных копий (-~dp, ,-df, -pa)
Это соответственно файлы резервных копий для файлов проекта, формы и модуля. Если вы что-то безнадежно испортили в своем проекте, можете соответственно изменить расширения этих файлов и таким образом вернуться к предыдущему не испорченному варианту.
Исполняемый файл (.ехе)
Это исполняемый файл вашего приложения. Он является автономным исполняемым файлом, для которого больше ничего не требуется, если только вы не используете библиотеки, содержащиеся в DLL, OCX и т.д., а также если вы не используете поддержку пакетов времени выполнения (см. гл. 8 разд. 8.5).
Объектный файл модуля (.dcu)
Это откомпилированный объектный файл модуля (.pas), который компонуется в окончательный исполняемый файл.
13.3.2 Структура головного файла программы Головной файл программы имеет следующую структуру: Program ;
begin
end.
В Delphi головной файл обычно не содержит ничего, кроме операторов инициализации приложения, создания форм и запуска приложения. Типичный головной файл в Delphi имеет вид: program Projectl; uses Forms, Unitl in 'Unit 1.pas' (Forml}, Unit2 in 'UnitZ.pas' {FormZ}, Unit3 in 'Unit3.pas' {Framed: TFtame); {$R *.RES} {Здесь можно поместить описания каких-то констанг,'переменных, функций, процедур. Все это будет доступно только в пределах данного файла.) begin
Application.Initialize; Application.CreateFormfTForml, F o r m l ) ; Application.CreateForrri(TForm2, Forra2) ; Application.Run; end.
Имя программы совпадает с именем файла, в котором сохранен проект. Это же имя присваивается выполняемому файлу приложения.
818
Глава 13
В процессе выполнения приложения вы можете программно определить имя выполняемого файла и путь к нему с помощью функции ParamStr(O) (см. гл. 16, разд. 16.10). После заголовка в тексте программы располагается предложение uses (см. разд. 13.3.4). В этом предложении перечисляются модули, загружаемые программой — системные и модули приложения (в частности, модули всех форм). В приведенном примере подразумевает, что в проекте созданы две формы с именами Forml и Form2 в модулях с именами Unitl и Unlt2 и фрейм FrameS в модуле с именем Unit3. Названия форм включаются В текст в виде комментариев. Следующая строка текста — {$R *.RESJ представляет собой директиву компилятора (см. разд. 13.2.4). Затем после ключевого слова begin и до последнего завершающего программу оператора end с точкой (end.) записано тело программы. Первый выполняемый оператор в теле программы инициализирует приложение, два следующих — создают объекты форм Forml и Form2, последний — начинает выполнение приложения. Если вам надо ввести какой-то свой текст в головную программу, вы мажете сделать это, введя описания необходимых констант, переменных, функций и процедур в место программы, отмеченное соответствующим комментарием в приведенном выше тексте. Кроме того, вы можете добавить или изменить операторы в теле программы. Вам может потребоваться при запуске приложения на выполнение провести какие-то настройки (например, настроить формы на тот или иной язык — русский или английский). Или сделать какой-то запрос пользователю и в зависимости от ответа создавать или не создавать те или иные формы. В этом случае вы можете ввести в код соответствующие процедуры, обратиться к ним после выполнения инициализации приложения, но до создания объектов форм, а затем, например, создавать или не создавать отдельные формы исходя из результатов работы ваших процедур. Пусть, например, вы хотите, чтобы вторая форма вашего приложения Form2 создавалась только в случае, если при запуске приложения через командную строку в него передана опция Y. В этом случае вы можете заменить приведенный выше оператор Application.CreSteForm(TForro2, Form2);
на оператор i f ( P a r a m S t r [1] - ' Y ' ]
then Application.CreatelTorm(TForm2, Form2); Этот оператор анализирует функцией ParamStr (см. гл. 16 разд. 16.10) первый параметр командной строки. Если ваше приложение Projectl будет запускаться командой Projectl Y, то форма Form2 будет создаваться. В остальных случаях этой формы не будет. Если вместо анализа командной строки вы хотите сделать запрос пользователю о создании формы Form2, то приведенный выше оператор можно заменить, например, следующим (конечно, тексты в нем носят чисто демонстрационный характер): if A p p l i c a t i o n . M e s s a g e B o x ( ' Х о т и т е иметь вторую ф о р м у ? ' , 'Подтвердите создание формы', MB_YESNOCAHCEL+MB_ICONQUESTION] = IDYES then Application.CreateForm(TForm2, F o r m 2 ) ;
Этот код использует метод MessageBox объекта Application (см. гл. 16 разд. 16.11.3). В результате пользователю будет предъявлено диалоговое окно, показанное на рис. 13.3. В зависимости от ответа пользователя, форма Form2 будет, или не будет создаваться. Только не забудьте, что если вы используете метод MessageBox, добавить в оператор uses ссылку на модуль Windows:
Справочные данные по языку Object Pascal в Delphi 7
819
Рис. 13.3 Запрос пользователю о создании второй формы
VJ . Хотитв ияеть вторую форму!
!
М"" Л!
И*т
Отпепа
uses Forms, windows, ... В противном случае компилятор не поймет констант, которые вы используете при вызове MessageBox. Все описанное выше можно делать, но это будет плохой стиль программирования, поскольку он противоречит принципу модульности. Все необходимые вам в начале выполнения процедуры и функции настройки помещайте в отдельный модуль без формы. Такой модуль, не связанный с какой-то формой, можно включить в приложение, выполнив команду File New и щелкнув на пиктограмме Unil. В частности, в этот модуль можно перенести из головного файла все выполняемые модули, так что головной файл будет вообще практически пустым. Пусть, например, вы включили в проект модуль с именем UnitS, в котором записали приведенные выше операторы создания форм. Тогда головной модуль может иметь вид: p r o g r a m Project2; uses Unit3 in ' U n i t 3 . p a s ' ; {SR -.RES)
begin end. А модуль UnitS может выглядеть так: interface implementation uses Forms, Windows, uriitl,unit2; Initialization Application.Initialize; Application.CreateFQCTMTForml, Forral) ,if Application.MessageBox( 'Хотите иметь вторую форму?', 'Подтвердите создание формы', MB__YESNOCANCEL+MB_ICQNQUE5TION) = IDYES then Application.CreateFormlTFortn2, Form2); Forral . SetFocus.Application.Run; end.
Раздел Initialization этого модуля (см. разд. 13.3.3) выполняет все функции, которые ранее выполнялись операторами головного файла. Конечно, это крайний случай и вряд ли стоит настолько принижать функции головного файла. Но определенные преимущества в таком варианте есть. Вы можете в модуле UnitS ввести в раздел interface какие-то переменные, в которых запомнить результаты входного диалога с пользователем, и затем использовать эти переменные в модуле главной формы Unitl. При диалоге в головном файле это невозможно. Учтите, что все определенные вами в головном файле приложения константы, переменные, процедуры, функции, типы будут доступны только в пределах этого головного файла и не доступны в модулях.
820
_
Глава
13
13.3.3 Структура модуля Каждый модуль в общем случае имеет структуру: unit ; interface
// Открытый интерфейс модуля
могут помещаться списки подключаемых модулей, объявления типов, констант, переменных, функций и процедур, к которым будет доступ из других модулем.} implementation // Реализация модуля {Сюда могут помещаться списки подключаемых модулей, объявления типов, констант, переменных, к которым не будет доступа из других модулей. Тут же должны быть реализации всех объявленных в разделе interface функций и процедур, а также могут быть реализации любых дополнительных, не объявленных ранее функций и процедур.} initialization // не обязательный
>=
Тип результата Пример Boolean I = Max
простые, множества, указатели, классы, ссылки классов, интерфейсы, строки, упакованные строки Boolean Не равно простые, множества, указатели, классы, ссылки классов, интерфейсы, строки, упакованные строки Меньше чем простые, строки, упако- Boolean ванные строки, PChar
о
0 Cnt = 1
Операнды должны иметь совместимые типы, за исключением типов real и integer, которые могут сравниваться друг с другом. Строки сравниваются по расширенным кодам множества символов ASCII. Символьный тип трактуется как строка единичной длины. Упакованные строки можно сравнивать друг с другом только при одинаковом числе символов. Если упакованная строка из п символов сравнивается с типом string, то она трактуется как строка длиной И. Операции , = можно применять к операндам PChar, только если оба указателя указывают на один и тот же массив символов. Операции = и о могут применяться к операндам типа классов и ссылок на классы. В этом случае для классов С = D вернет true, только если С и D указывают на один и тот же объект класса. Применительно к ссылкам на классы С = D вернет true, только если С и D указывают на один и тот же класс. Аналогичные правила действуют и при операндах типа указателей.
Справочные данные по языку Object Pascal в Delphi 7
837
13.7.4 Булевы операции Булевы операции принимают операнды булевых типов и возвращают результат тоже булева типа. Обозначение Операция
Типы операндов Тип результата Пример
not
Отрицание
Boolean
Boolean
not (C in MySet)
and
Логическое И
Boolean
Boolean
Done and (Total > 0)
or
Логическое ИЛИ Boolean
Boolean
A or В
хог
Логическое иск- Boolean лючающее ИЛИ
Boolean
A хог В
Компилятор Delphi поддерживает два режима вычисления операций and и ог: полный и сокращенный. В режиме полного вычисления все логическое выражение вычисляется до конца, даже если после вычисления первого операнда результат ясен. В режиме сокращенного вычисления расчет прерывается, как только результат ясен. Например, операция ог дает результат true, если хотя бы один операнд равен true. Значит, если первый операнд равен true, то результат ясен и в режиме сокращенного вычисления расчет прервется без вычисления второго операнда. Опция компилятора {$В—}, работающая по умолчанию, обеспечивает сокращенный режим вычисления, а опция ($В+} — полный. Режим полного вычисления можно также установить опцией Complete Booleon Evoluolion в окне опций компилятора. -
13.7.5 Логические поразрядные операции
Логические поразрядные операции работают с целыми числами и оперируют с их двоичными представлениями, т.е. работают с двоичными разрядами операндов. Обозначение Операция
Типы операндов Тип результата
Пример
not
поразрядное отрицание
целый
целый
not X
and
поразрядное И
целый
целый
X and Y
or
поразрядное ИЛИ
целый
целый
X or Y
хог
поразрядное иск- целый лючающее ИЛИ
целый
X xor Y
shl
поразрядный сдвиг влево
целый
целый
X shl 2
shr
поразрядный сдвиг вправо
целый
целый
У shl I
Глава 13
838
Операции выполняются поразрядно. Например:
Y
001101 100001
ХогУ
101101
X
'
При поразрядных операциях действуют следующие правила. Результат операции not имеет тот же тип, что и ее операнд. Результат операций and, or, хог имеет наименьший целый тип, включающий все возможные значения типов обоих операндов. Выражения X shl Y и X shr Y сдвигают значение X влево или вправо на Y битов. Это эквивалентно умножению или делению X на 2Y. Результат имеет тот же тип, что X.
13.7.6 Операции со строками Для строк определены операции отношения =, о, , = (см. разд. 13.7.3 и 14.8). Кроме того, определена еще одна операция — сцепление (конкатенация): Тип результата Пример Обозначение Операция Типы операндов S + S1 + сцепление строка, упакован- строка ная строка, символ
+ ' . '
Результат сцепления равен последовательности символов первого операнда, после которой размещается последовательность символов второго операнда. Операндами могут быть строки, упакованные строки (массивы типа Char) или символы. Но если один операнд имеет тип WideChar, то и другой операнд должен быть длинной строкой. Результат операции совместим с любым типом строк. Однако если оба операнда имеют тип короткой строки или символьный и длина результата не превышает 255, то результат усекается до первых 255 символов.
13.7.7 Операции с указателями Для указателей PChar определены операции отношения =, ,! , = (см. разд. 14.13). Кроме того для различных указателей определены еще следующие операции: Обозначение Операция +
Типы операндов Тип результата Пример сложение ука- указатель на указатель на Р + Г зателей символ, целое символ
-
вычитание указателей
указатель на символ, целое
~
разыменование указателя
указатель
равенство неравенство
указатель указатель
=
о
указатель на символ, целое указатель на базовый тип
Р-Q
Boolean Boolean
Р = Q
Р" ,
Р Q
839
Справочные данные по языку Object Pascal в Delphi 7
Операнд операции разыменования " может быть указателем на любой тип. Указатель типа Pointer должен быть сначала преобразован к конкретному типу, после чего его можно разыменовывать. Выражение Р = Q дает true только в случае, если Р и Q указывают на один адрес. В противном случае выражение Р Q дает true. Операции + и — применяются для увеличения или уменьшения сдвига указателя на символ. Операция вычитания кроме того позволяет найти разность смещения двух указателей. Эти операции подчиняются следующим правилам. Если I — целое число, а Р — указатель на символ, то Р + I увеличивает на I адрес, на который указывает Р; т.е. возвращается указатель на адрес, отстоящий на I символов от Р. Аналогично, Р — I возвращается указатель на адрес, предшествующий Р на I символов. Если Р и Q — указатели на символы, то Р — Q определяет число символов между Р и Q.
13.7.8 Операции с множествами Множества могут использоваться как операнды в следующих операциях: Обозначение Операция
Типы операндов Тип результата Пример
+
объединение
множество
множество
Setl + 3et2
*
разность
множество
множество
S - Т
пересечение
множество
множество
s * т
= 32
=
эквивалентность
множество
Boolean
S2 = MySet
о
неэквивалент- множество ность
Boolean
MySet SI
in
является эле- порядковый, ментом множество
Boolean
A in Setl
и Y.
В этих операциях действуют следующие правил а. Z является элементом X + Y, если он является элементом X, или Y, или и X,
Z является элементом X — Y, если он является элементом X, но не является элементом Y. Z является элементом X * Y, если он является элементом и X, и Y. Выражение X = Y эквивалентно выражению Y и являются выражениями, совместимыми по типу с управляющей переменной. Если заданные начальное и конечное значения равны друг другу, тело цикла выполняется только один раз. Если в форме с to начальное значение больше конечного или в форме с downto начальное значение меньше конечного, то тело цикла не выполняется ни разу. Внутри цикла значение управляющей переменной может использоваться в выражениях. Однако после окончания выполнения структуры for значение управляющей переменной не определено. Приведем пример использования оператора for. Следующие операторы вычисляют максимальное значение элементов, расположенных в массиве Data: Max:-Data[1] ,for I := 2 to 63 do if Data[I] > Max then Max := Data[I];
13.8.7 Оператор цикла repeat Структура repeat...until используется для организации циклического выполнения совокупности операторов, называемой телом цикла, до тех пор, пока не выполнится некоторое условие. Синтаксис управляющей структуры repeat...until: repeat until ;
Точка с запятой после последнего оператора тела цикла (перед ключевым словом until) может опускаться. Структура работает следующим образом. Выполняются операторы тела цикла. Затем вычисляется , которое должно возвращать результат булева типа. Если выражение условия возвращает false, то повторяется выполнение операторов тела цикла и после этого снова вычисляется условие. Такое циклическое повторение цикла продолжается до тех пор, пока проверяемое условие не вернет true. После этого цикл завершается и управление передается оператору, следующему за структурой repeat...until. Поскольку проверка условия осуществляется после выполнения операторов тела цикла, то эти операторы заведомо будут выполнены хотя бы один раз, даже если условие сразу истинно. С другой стороны, программист должен быть уверен, что условие рано или поздно вернет true. Если этого не произойдет, то программа «зациклится», т.е. цикл будет выполняться бесконечно. Иногда такие бесконеч-
Справочные данные по языку Object Pascal в Delphi 7
847
ные циклы используются. Но в этом случае внутри тела цикла должно быть предусмотрено его прерывание в какой-то момент, например, оператором break, прерывающим цикл, или функциями Exit или Abort, вызывающими прерывание не только цикла, но и функции или процедуры, внутри которой выполняется данный цикл. Обычно оператор repeat целесообразно использовать для организации поиска среди множества объектов такого, который обладает каким-то определенным свойством. Причем заранее должно быть известно, что множество объектов не пустое, т.е. хотя бы один объект в нем имеется. К тому же должен быть критерий, позволяющий проверить, не является ли текущий объект последним. Тогда тело цикла включает операторы перехода к новому объекту и какой-то его обработки, а условие until включает проверку, является ли объект последним или текущий объект обладает искомым свойством. И в том, и в другом случае выполнение цикла прерывается. Если же ни одно из этих условий не выполнено, осуществляется переход к следующему объекту. Если множество проверяемых объектов может быть пустым, следует использовать другой оператор цикла — while (см. разд. 13.8.8). Если число повторений циклов заранее известно, лучше применять оператор for (см. разд. 13.8.6). Ниже приведен пример, в котором в файле Filel.txt ищется строка, содержащая фрагмент текста (последовательность символов), указанный пользователем в окне редактирования Editl. Проверка наличия в строке заданного фрагмента проверяется функцией Роз. Окончание файла проверяется функцией EOF. var F:TextFile; S,SKey:string;
SKey:-Editl. Text.» AssignFilefF,'Filel.txt']; Reset(F); repeat ReadlnfF,s); until EOF(F) or (Pos(Skey,S)>0); , if S-" then Labell .Caption : = ""+SKey+'" в файле отсутствует' else ...;
13.8.8 Оператор цикла while Структура while...do используется для организации циклического выполнения оператора, называемого телом цикла, пока выполняется некоторое условие. Синтаксис управляющей структуры while.„do: while do ; Структура работает следующим образом. Сначала вычисляется , которое должно возвращать результат булева типа. Если условие возвращает true, то выполняется оператор тела цикла, после чего опять вычисляется выражение, определяющее условие. Такое циклическое повторение выполнения оператора и проверки условия продолжается до тех пор, пока условие не вернет false. После этого цикл завершается и управление передается оператору, следующему за структурой while... do. Поскольку проверка выражения осуществляется перед выполнением оператора тела цикла, то, если условие сразу ложно, оператор не будет выполнен ни одного раза. В этом основное отличие структуры while...do от структуры repeat...until (см. разд. 13.8.7), в котором тело цикла заведомо выполняется хотя бы один раз.
848
Глава 13
Программист должен быть уверен, что условие рано или поздно вернет false. Если этого не произойдет, то программа «зациклится», т.е. цикл будет выполняться бесконечно. Иногда такие бесконечные циклы используются. Но я этом случае внутри тела цикла должно быть предусмотрено его прерывание в какой-то момент, например, оператором break, прерывающим цикл, или функциями Exit или Abort, вызывающими прерывания не только цикла, но и функции или процедуры, внутри которой выполняется данный цикл. Обычно оператор while целесообразно использовать для организации поиска среди множества объектов такого, который обладает каким-то определенным свойством. Причем не исключается, что множество объектов может быть пустым, т.е. не содержащим ни одного объекта. К тому же должен быть критерий, позволяющий проверить, не является ли текущий объект последним. Тогда тело цикла включает операторы перехода к новому объекту и какой-то его обработки, а условие while включает проверку, является ли объект не последним и отсутствует ли в нем искомое свойство. Бели одно из этих условий нарушается (объект последний или имеет искомое свойство), выполнение цикла прерывается. Если множество проверяемых объектов не может быть пустым, то можно использовать другой оператор цикла — repeat (см, разд. 13.8.7). Если число повторений циклов заранее известно, лучше применять оператор for (см. разд. 13.8.6). Ниже приведен пример копирования текстового файла Filel.txt в файл File2.txt. Проверка окончания копируемого файла проверяется функцией EOF. Программа будет нормально работать, даже если копируемый файл окажется пустым. В этом случае тело цикла ни разу не будет выполнено. var Finput, FOutput:TextFile; S:string; AssignFile (Finput, ' Filel. t*t') ,Reset(Finput); AssignFile(FOutput,'File2.txt'); Rewrite(FOutputi;
while not EoflFInputi do begin Readln(Flnput,S); Writeln(FOutput,S); end; CloseFile(Finput); CloseFile(FQutput);
13.8.9 Прерывание цикла: оператор break, процедуры Continue, Exit и Abort В некоторых случаях желательно прервать повторение цикла, проанализировав какие-то условия внутри него. Это может потребоваться в тех случаях, когда проверки условия окончания цикла громоздкие, требуют многоэтапного сравнения и сопоставления каких-то данных и все эти проверки просто невозможно разместить в выражении условия операторов for, repeat или while. Один из возможных вариантов решения этой задачи — ввести в код какой-то флаг окончания (переменную). При выполнении всех условий окончания этой переменной присваивается некоторое условное значение. Тогда условие в операторах for, repeat или while сводится к проверке, не равно ли значение этого флага принятому условному значению. Другой способ решения задачи — использование оператора break. Этот оператор прерывает выполнение тела любого цикла for, repeat или while и передает управление следующему за циклом выполняемому оператору. Еще один способ
Справочные данные по языку Object Pascal в Delphi 7
849
прерывания -г- использование оператора goto, передающего управление какому-то оператору, расположенному вне тела цикла. Для прерывания циклов, размещенных в процедурах или функциях, можно воспользоваться процедурой Exit. В отличие от оператора break, процедура Exit прервет не только выполнение цикла, но и выполнение той процедуры или функции, в которой расположен цикл. Прервать выполнение цикла, а заодно — и блока, в котором расположен цикл, можно также генерацией какого-то исключения (см. разд. 13.10.7). Наиболее часто в этих целях используется процедура Abort, генерирующая «молчаливое» исключение, не связанное с каким-то сообщением об ошибке. Описанные способы прерывали выполнение цикла. Имеется еще процедура Continue, которая прерывает только выполнение текущей итерации, текущего выполнения тела цикла и передает управление на следующую итерацию,
13.9 Динамическое распределение памяти Динамическое распределение памяти широко используется для экономии вычислительных ресурсоа. Те переменные или объекты, которые становятся ненужными, уничтожаются, а освобожденное место используется для новых переменных или объектов. Это особенно эффективно в задачах, в которых число необходимых объектов зависит от обрабатываемых данных или от действий пользователя, т.е. заранее не известно. В этих ситуациях остается только два выхода: заранее с запасом отвести место под множество объектов или использовать динамическое распределение памяти, создавая новые объекты по мере надобности. Первый путь, конечно, неудовлетворительный, поскольку связан с излишними затратами памяти и в то же время накладывает на размерность задачи необоснованные ограничения. Для динамического распределения выделяется специальная область памяти — heap (куча). Динамическое распределение памяти в этой области может производиться двумя различными способами: с помощью процедур New и Dispose и процедурами GetMem и FreeMem. При первом способе выделение памяти производится процедурой procedure Ыеи(еЬифи-г EjueiiliQn ^ohli, .
Сообщение отладчика об исключении
Piolect Praiectl ,ек* raised exception da» EDrwB>Ze'o wth muugi T&alan by rtfo1. Process stopped. Use St«J or Run to continue.
Если хотите, то можете отключить появление этих сообщений. Для этого надо выполнить команду Tools | Debugger Options, в открывшемся диалоговом окне выбрать страницу Language Exceptions и на ней выключить опцию Stop On Delphi Exceptions. Если не принять соответствующих мер, то при генерации исключений к неприятностям прекращения вычислений могут добавиться еще неприятности, связанные с так называемой у течкой ресурсов. Под этим подразумеваются потери динамически распределяемой памяти, незакрытые файлы, не уничтоженные временные файлы на диске и прочий «мусор». Например, пусть вы выполняете некоторую программу, в которой имеются следующие операторы: AssignFile IF,'а.Cmp'); Rewrite(F); New(P);
Глава 13
852
Erase(F); Dispose(P);
• I
Вы открываете временный файл (см. разд. 14.14.1) с именем a.tmp, чтобы хранить в нем какие-то промежуточные данные вычислений. В конце работы вы намерены уничтожить его процедурой Erase. Вы динамически выделяете (см. разд. 13.9) некоторую память процедурой New, намереваясь освободить ее, когда она вам больше не будет нужна, процедурой Dispose. Но если в промежуточных операторах, помеченных выше точками, возникнет исключение, то вычисления прервутся и процедуры Erase и Dispose не будут выполнены. В результате память, выделенная процедурой New, останется недоступной, а на диске сохранится временный и уже ненужный файл a.tmp. Помимо указанного, стандартная обработка исключений программой имеет еще один недостаток — пользователь остается в полном недоумении, что же ему дальше делать? И не только не очень квалифицированный пользователь, которого приведенные на рис. 13.4 сообщения на английском языке могут повергнуть в шок. Даже опытному человеку невозможно порой догадаться, что же в вашей программе делится на нуль и как этого можно избежать. Наверное, каждый попадал в подобные ситуации, даже применяя профессионально сделанные программы, включая Windows. Работа с исключениями и обеспечение бессбойного выполнения приложений — достаточно сложный вопрос и подробно обсуждать его в рамках данной книги невозможно. Ниже изложены только те аспекты этой проблемы, которые используются в предыдущих главах. А более подробные сведения вы найдете в источниках [1] и [4].
13.10.2 Иерархия классов исключений в Delphi Ниже приведена таблица иерархии некоторых предопределенных в Delphi классов исключений с краткими пояснениями. Полную таблицу иерархии вы мо жете найти в источниках [1] и [4]. Exception EAbort
ЕАсс ess Violation
EAssertionFailed EControlC
EConvertError EDatabaseError
Базовый класс исключений «Молчаливое» исключение, предназначенное для намеренного прерывания вычислений и быстрого выхода из глубоко вложенных процедур и функций. Генерируется процедурой Abort. Ошибочный доступ к памяти; генерируется при попытке разыменования нулевого указателя nil, попытке записи а кодовую страницу, попытке доступа к адресу вне памяти, распределенной приложению. Ложное выражение, проверяемое процедурой Assert. Нажатие пользователем клавиш Clrl-C при выполнении консольного приложения. При обработке этого исключения можно выдать запрос пользователю, действительно ли он хочет прервать работу, и предпринять действия в зависимости от его ответа. Ошибка преобразования строк или объектов (в частности, в функциях StrToInt, StrToFIoat, StrToDate). Ошибка работы с базами данных.
Справочные данные по_языку Object Pascal в Delphi 7
Exception EDBCIient
853
Базовый класс исключений Ошибка в наборе данных клиента. Свойство ErrorCode содержит код ошибки, возвращаемый BDE.
EReconcileEr- Ошибка обновления данных компонента TClientDataror set; свойство Context содержит информацию в виде сообщения об ошибке.
EDBEngineError Ошибка в BDE. Свойство Errors содержит информацию об ошибке — объект типа TDBErrors. Свойство ErrorCount хранит число ошибок.
ENoResultSet
Генерируется компонентом TQuery при попытке открыть запрос без оператора SELECT.
EUpdateError EDateTimeError
Ошибка при обновлении в TProvider.
EInOutError
Ошибка ввода-вывода из файла; поле errorcode содержит информацию о конкретном виде ошибки (см. раздел 17.1.2).
EIntError
Базовый класс исключений целочисленных математических операций (см. директивы компилятора {$!+} и {$!•} в разделе 14.2.4).
Ошибка ввода даты или времени в компоненте TDateTimePicker.
EDivByZero
Попытка целочисленного деления на нуль.
ERangeErrnr
Целочисленное значение или индекс вне допустимого диапазона.
EInt Overflow EInvalidCast
Переполнение при операции с целыми числами.
EInvalidOperation
Ошибочная операция с компонентом; генерируется при попытке выполнить операцию, которая требует обработчика окна, над компонентом, не имеющем родителя (свойство Parent = nil). Это исключение также генерируется при выполнении операций перетаскивания формы (например, при попытке выполнить операцию Form 1 . В е ginD rag) .
EInvalidPointcr EListError
Ошибочная операция с указателем.
EMathError
Базовый класс исключений операций с плавающей запятой; всегда генерируются только потомки этого исключения; обработка исключения EMathError может использоваться для перехвата асех исключений операций с плавающей запятой.
EIn valid Argument
Ошибка преобразования типа объекта операцией as.
Ошибка работы с объектом типа списка TStringList и TList: попытке сослаться на элемент с индексом вне допустимых пределов, попытке добавления дубликата строки в объект TStringList, в котором значение свойства Duplicates равно dupError, попытке вставить элемент в сортированный список, так как это может нарушить правильную последовательность элементов.
Недопустимое значение параметра при обращении к математической функции.
854
Глава 13
Exception
Базовый класс исключений
EInvalidOp
Неопределенная операция с плавающей запятой: процессор наталкивается на неопределенную инструкцию, ошибочную операцию или переполняется стек процессора с плавающей запятой.
EOverflow
Переполнение регистра при операциях с плавающей запятой.
EUnderflow
Потеря значащих разрядов при выполнении операции с плавающей запятой.
EZeroDividc
Деление на нуль числа с плавающей запятой.
EMcnuError
Ошибка, связанная с элементами меню.
E Out line Error
Ошибка при работе с компонентом Outline.
EOutOfMemory
Неудачная попытка динамически выделить память; может генерироваться процедурой OutOfMemoryError.
EPrinter
Ошибка печати; например, приложение пытается использовать принтер, которого нет, или задание по какой-то причине не может быть послано на принтер.
EStackOverflow
Переполнение стека; размеры стека можно регулировать директивами компилятора {$М},
E Stream Error
Базовый класс исключений ошибок потоков.
EFCreateError
Ошибка создания файла; например, пользователь указал недопустимое имя файла или указанный файл уже существует и не может быть перезаписан, так как пользователь не обладает соответствующим уровнем доступа.
EFOpenError
Ошибка открытия файла.
EFiler Error
Базовый класс исключений файловых потоков.
'
EReadError
Невозможно прочесть заданное число байтов.
EWriteError
Невозможно записать заданное число байтов.
EStringListError
Ошибочный доступ к окну списка с неверным индексом.
EThread
Конфликт в многопоточном приложении (например, вызов метода Synchronize объекта Thread до успешного завершения его предыдущего вызова).
ETreeViewError
Ошибка индекса при работе с компонентом Tree View.
EVariantError
Ошибка, связанная с типом данных variant.
EWin32Error
Ошибка Windows.
13.10.3 Базовый класс исключений Exception Все предопределенные в Delphi классы исключений, как видно из их иерархии, приведенной в разд. 13.10.2, являются прямыми или косвенными наследниками класса Exception, объявленного в модуле SysUtils и наследующего непосредственно TObject.
Справочные данные по языку Object Pascal в Delphi 7
855
13.10.3.1 Свойства исключений В классе Exception объявлено два свойства: Свойство
Тип
Message
string
Описание Строка сообщения, которая в дальнейшем при обработке исключения системным обработчиком отображается в окне сообщений; устанавливается конструктором с умолчанием.
HelpContext Integer Целый идентификатор экрана контекст но- зависимо и справки. Этот экран справки отображается, если пользователь, находясь в окне с сообщением об ошибке, нажимает клавишу F1 . По умолчанию равен 0. Свойство Message имеет значение по умолчанию, которое присваивается при автоматической генерации исключения. При преднамеренной генерации исключений их конструкторы, описанные в следующем разделе, могут задавать значение свойства Message в виде переменной типа string или литеральной константы. Свойство HelpContext хранит целый идентификатор экрана контекст но-зависимой справки. Этот экран справки отображается, если пользователь, находясь в окне с сообщением об ошибке, нажимает клавишу Я!. По умолчанию значение свойства HelpContext равно 0. Это значение может изменяться теми конструкторами (см. следующий раздел), в названии которых встречается элемент Help. Например, оператор Ехс ;= ЕМу.CreateHelp('Не хватает исходных данных',42);
задает создаваемому экземпляру исключения значение свойства Message, равное тексту «Не хватает исходных данных», и значение свойства HelpContext, равное 42. При получении сообщения об этом исключении пользователь может нажать клавишу F1 и получить пояснения, что ему делать в этом случае. Конечно, чтобы это работало, надо создать соответствующий файл справки и связать его с приложением, установив соответствующую опцию Help file (файл справки) в окне Project Options (опции проекта) на странице Application (приложение). 13.10.3.2 Конструкторы исключений Класс Exception наследует все функции своего базового класса TObject, в частности, полезную для идентификации неизвестного исключения функцию ClassName. Кроме того, в интерфейсе класса Exception описаны 8 конструкторов, наследуемых всеми исключениями: Конструктор
Описание Конструктор, передает строку сообщения Msg Or eat e( const Msg: string) свойству Message. Сте at eFmt( const Msg: string; Конструктор формирует строку свойства Messaconst Args: array of const) ge, исходя из строки описания формата Msg и массива аргументов Args. Конструктор задает строку свойства Message CreateHes(Ident: Integer) идентификатором Ident строки сообщения в ресурсах проекта (см. разд. 13.3.5),
Глава 13
856
CreateResFmt{Ident: Integer; Конструктор задает строку свойства Message const Args: array of const) идентификатором Ident строки описания форма-
та в ресурсах проекта и массивом аргументов Args.
CreateHelp( const Msg: string; HelpContext; Integer)
Конструктор передает строку сообщения Msg свойству Message; передает свойству HelpContext идентификатор HelpContext экрана контекстно-зависимой справки по этому исключению.
CreateFmtHelp( const Msg: string; const Args: array of const; HelpContext: Integer)
Конструктор формирует строку свойства Message, исходя из строки описания формата Msg и массива аргументов Args; передает свойству HelpContext идентификатор HelpContext экрана контекстно-зависимой справки по этому исключению.
CreateResHelp( Ident,HelpContext: Integer)
Конструктор задает строку свойства Message идентификатором Ident строки сообщения в ресурсах проекта; передает свойству HelpContext идентификатор HelpContext экрана контекстно-зависимой справки по этому исключению.
CrflateResFmtHelp{ Ident: Integer; const Args: array of const; HelpContext: Integer)
Конструктор формирует строку свойства Message исходя из строки описания формата в ресурсах проекта, указываемой идентификатором Ident, и массива аргументов Args; передает свойству HelpContext идентификатор HelpContext экрана контекстно-зависимой справки по этому исключению.
Приведенные в таблице данные можно просуммировать следующим образом. Конструкторы, не имеющие в своем имени компонентов Fmt или Res, принимают параметр Msg типа string, являющийся текстом, который заносится в свойство Message и в дальнейшем при обработке исключения системным обработчиком будет отображен в окне сообщений. Конструкторы, в имя которых входит Fmt, принимают параметр Msg типа string, являющийся строкой описания формата текста, и параметр Argg, являющийся массивом переменных, значения которых включаются в текст в соответствии с форматом, записанным в Msg. Значение свойства Message формируется обращением к функции Format в модуле SysUtils, которая и формирует текст сообщет ния в соответствии со строкой описания формата и списком массива (см. гл. 16 разд. 16.1.3). Конструкторы, в имя которых входит Help, принимают целый параметр HelpContext, который является идентификатором экрана контекстно-зависимой справки. Принятый параметр сохраняется в свойстве HelpContext. Этот экран справки отображается, если пользователь, находясь в окне с сообщением об ошибке, нажимает клавишу F1. Конструкторы, в имя которых входит Res, принимают целый параметр Ident, который является идентификатором строки сообщения или строки описания формата (если в имя конструктора входит Fmt) в ресурсах проекта- Во время проектирования ресурсы хранятся в отдельном файле с расширением .res, но при компиляции ресурсы включаются в выполняемый файл. Идентификатор строки вы можете взять из текстового файла .drc, информация о котором дана в разд. 13.3.5. Рассмотрим примеры использования различных конструкторов. В этих примерах используется ключевое слово raise, генерирующее исключение. Примеры применения конструктора Create:
Справочные данные по языку Object Pascal в Delphi 7
857
// Генерация собственного исключения ЕМу raise ЕМу.Create('Не хватает исходных данных'); / Генерация стандартного исключения E2eroOivide с измененным сообщением } raise E2eroDivide.Create('Деление на н у л ь ' ) ; // Использование функции Format raise Е М у . C r e a t e ( f o r m a t I ' З а д а н о %d параметров и з I d ' ,
[Nl,
N2]));
Последний пример использует функцию Format (см. гл. 16 разд. 16.1.3) для форматированного вывода информации о значениях переменных N1 и N2. Например, при значениях переменных N1 = 5 и N2 = 7 будет сгенерировано исключение, в диалоговом окне которого появится текст: «Задано 5 параметров из 7». Пример применения конструктора CreateFmt: raise ЕМу.CreateFmt('Задано %d параметров из %d', [HI, N 2 ] ) ; Отличие от предыдущего примера заключается в том, что конструкторы, имеющие в своем имени Fmt, сами неявно обращаются к функции Format, так что запись конструктора несколько упрощается, Пример применения конструктора CreateHelp: raise ЕМу.CreateHelp('Не кватаег исходных данных',
10);
Этот оператор генерирует исключение с тем же текстом, что и в одном из приведенных выше примеров конструктора Create, но если в диалоговом окне с сообщением об этом исключении пользователь нажмет клавишу F1, ему будет предъявлена контекстная справка с идентификатором 10. Примеры применения конструкторов, использующих строки ресурсов: raise Е М у . C r e a t e R e s ( 6 5 3 6 9 ) ;
Этот оператор передает в свойство Message строку из файла ресурсов с номером 65369. Оператор raise EMy.CreateResFmt(65369,
(N1, К 2 ] ) ;
берет из файла ресурсов строку с номером 65369 как строку описания формата и передает в свойство Message сформатированные с ее помощью значения переменных N1 и N2.
13.10.4 Обработка исключений в блоках try ... except 13.10.4.1 Синтаксис блоков try ... except и операторов on...do Наиболее кардинальный путь борьбы с исключениями — обработка их с помощью блоков try ... except. Синтаксис этих блоков следующий: try // Исполняемый кол except // Код, исполняемой в случае генерации исключения end,-
Операторы раздела except выполняются только в случае генерации исключения в операторах блока try. Таким образом, вы можете предпринять какие-то действия: известить пользователя о возникшей проблеме и подсказать ему пути ее решения, принять какие-то меры к исправлению ошибки (например, при делении на нуль заслать в результат очень большое число соответствующего знака) и т.д. Наиболее ценным является то, что в разделе except вы можете определить тип сгенерированного исключения и дифференцированно реагировать на различные исключительные ситуации. Это делается с помощью оператора: on do ;
858
Глава^З
Таблица классов исключений, которые вы можете использовать в этом операторе, была приведена в разд. 13.10.2. Операторы on...do позволяют проводить выборочную обработку различных исключений. Приведем пример такой обработки: var А : shortint; try
C'fi StrTolnMEditl.text); A r= В div С; except on EConvertError do MessageDlg('Вы ввели ошибочное число,- повторите ввод', mtWarning, ImbOk], 0); on EDivByZeto do MessageDlg('Вы ввели нуль; повторите ввод', mtWarning, [mbOkj, 0); on EIntOverflow do if (B*C) >= 0 then A : = 32767 else A : = -32767;
end; В этом примере вы осуществляете чтение целого числа, введенного пользователем в окно редактирования Editl, и делите на него переменную В. Если пользователь ввел не целое число (например, по ошибке нажал не цифру, а какой-то буквенный символ), то при выполнении функции StrToInt возникнет исключение класса EConvertError. Соответствующий обработчик, исключения сообщает пользователю о сделанной ошибке и советует повторить ввод. Аналогичная реакция следует на ввод пользователем нуля (класс исключения EDivByZero). Если же при делении возникает целочисленное переполнение (класс исключения EIntOverflow), то вы присваиваете результату максимальное положительное или отрицательное значение. Некоторые исключения имеют дополнительные поля (свойства), уточняющие вид ошибки. Например, это относится к исключению файлового ввода/вывода ElnOutError, которое имеет свойство errorcode типа integer. Это свойство содержит стандартный для Object Pascal номер ошибки файлового ввода/вывода. Список этих номеров приводится в гл. 16 в разд. 16.1.2. Чтобы воспользоваться полями исключений, оператор он надо записывать в виде on : do MainWndProc =>WndPeoc =эDispatch =>обработчик события
Для сообщений, обрабатываемых компонентами Delphi, вам достаточно написать свой обработчик события. Для сообщений, не предусмотренных в Delphi, вам надо вмешаться в эту цепочку раньше, перегрузив метод WndProc. Если объект не имеет дескриптора, то цепочка остается той же, только сообщение получает оконный компонент, вмещающий.данный объект (например, форма). В Windows API определен ряд функций, позволяющих послать сообщение. Некоторые из них описаны в гл. 7 в разд. 7.4.2. Это прежде всего функция SendMessagc, которая посылает указанное в ней сообщение окну или множеству окон и не возвращается, пока это сообщение обрабатывается. Этим она отличается от похожей на нее функции PostMessage, которая возвращается сразу после передачи сообщения. Объявление функции SendMessage: function SendMessage(hWnd: HWnd; Msg, wParam: word; lParam:LongIntl: Longlnt;
Параметр hWnd — дескриптор окна, которому передается сообщение. Параметр Msg определяет передаваемое сообщение. Параметры wParam и IParam могут содержать дополнительную информацию. Например, оператор SendMessage IFindWindow ( ' S c i C a l c ' , ' К а л ь к у л я т о р ' ) , W M _ C L O S E , 0 , 0 ) ;
посылает с помощью функции SendMessage окну программы «Калькуляторе сообщение WM_CLOSE. Это сообщение — сигнал, что окно должно закрываться. Поскольку первым параметром функции SendMessage должен быть дескриптор окна, которому посылается сообщение, то в приведенном операторе этот дескрип-
862
Глава 13
тор находится с помощью функции FindWindow (см. гл. 7 разд. 7.3.1 и гл. 16 разд. 16.15.3). Во всех оконных компонентах предусмотрены обработчики сообщений Windows по умолчанию. Однако вы можете определить и свои собственные обработчики, заменив ими обработчики по умолчанию, или дополнив их. Объявление своего обработчика помещается в описание класса оконного компонента, как правило, в раздел private (см. гл. 14 разд. 14.17.1), Синтаксис объявления: procedure (var : = ; Приведем примеры. Объявление var Mode : (mRead, niEdit, raWrite) ; ТТ
Л=
объявляет переменную Mode, задавая для нее вводимый пользователем перечислимый тип (см. разд. 14.9). А объявления type TMode ; (mRead, mEdit, m W r i t e ) ; var Model, Mode2 : TMode;
'
задают имя нового типа — TMode и определяют две переменные введенного пользователем типа — Model и Mode2. Объявление типа можно использовать и для создания псевдонима встроенного типа. Например, объявление type TMyString = string; создает тип TMyString, являющийся псевдонимом встроенного типа строк string. Ниже приведена классификация типов Object Pascal, учитывающая некоторые общие свойства различных типоа: простые
порядковые
целые символы
'
.
булевы перечислимые ограниченные
действительные строки структуры
множества массивы записи
Глава 14
864
файлы классы ссылки на класс (метаклассы) интерфейсы
t
указатели процедурные variant В таблице в отдельную группу выделены порядковые типы, имеющие много общего, как показано в разд. 14.3. Не все перечисленные типы данных рассмотрены в данной главе. Не рассмотрены ссылки на класс, интерфейсы, процедурные типы. Подробную информацию об этих и других типах вы можете найти во встроенной справке Delphi или в источниках [1], [31 и [4].
14.2 Приведение типов В выражениях и в операторах присваивания могут фигурировать переменные и константы разных типов. В этом случае осуществляется приведение типов, что означает преобразование одного типа в другой. Приведение типов может быть неявным и явным. Неявное приведение совместимых типов автоматически осуществляет компилятор. При этом всегда более младший тип, занимающий меньший объем памяти, приводится к типу, занимающему больший объем. Например, если I — целая переменная, а А — действительная, то допустимы операторы А := I; А := Д + I;
но недопустим оператор ' I := Л + I;
который требует приведения действительного значения к целому. Явное приведение типов осуществляется программистом с помощью следующей конструкции: Идентификатор типа> ()
Если выражение представляет собой просто переменную, то говорят о приведении типа переменной. В противном случае говорят о приведении типа значения. Явное приведение типа переменной можно проводить для любых типов, имеющих одинаковый размер. При этом не допускается взаимное преобразование целых и действительных чисел. Для преобразования действительных чисел в целые надо использовать функции Int и Trunc. А целые переменные преобразуются В действительные неявно. Явное приведение типа переменной может использоваться и в правой, и в левой частях оператора присваивания. Например, могут быть записаны такие операторы (предполагается, что переменная СЬ. имеет тип Char, a I — integer): Ch := C h a r d ) ; S h o r t i n t ( C h ) ;= 1;
865
Типы данных в языке Object Pascal
Эти два оператора дают один и тот же результат. Например, если I = 122 (код ASCII символа « z » ) , то значение переменной Ch в результате выполнения любого из этих операторов станет равно символу «z». Явное приведение типа значения некоторого выражения осуществляется после его вычисления. Например, в операторе Ch := Char ( I - l ) ;
сначала вычисляется выражение в скобках, а потом производится приведение результата к типу Char. .
14.3 Порядковые типы данных Порядковыми (ordinal) типами называются те, в которых значения упорядочены и для каждого из них можно указать предшествующее и последующее значения. Для порядковых типов предопределен ряд функций: Функция Параметр
Возвращаемое значение
Замечания
Ord
Выражение порядкового типа
Порядковый номер значения данного выражения
Не воспринимает аргумент типа Int64
Pred
Выражение порядкового типа
Величина, предшествующая значению данного выражения
Нельзя использовать в саойствах, имеющих процедуру Write
Succ
Выражение порядкового типа
Величина, следующая для значения данного выражения
Нельзя использовать в свойствах, имеющих процедуру Write
High
Идентификатор порядко- Максимально возвого типа или переменможное значение ная порядкового типа
Используется также для типов коротких строк и массивов
Low
Идентификатор порядко- Минимально возвого типа или переменможное значение ная порядкового типа
Используется также для типов коротких строк и массивов
Для порядковых типов определены также процедуры инкремента 1нс и декремента Dec. Эти процедуры соответственно увеличивают или уменьшают на единицу порядковый номер своего аргумента. Таким образом, оператор Inc(l); эквивалентен оператору I
:= S l i c e d ) S
а оператор Dec(II; эквивалентен оператору I := P r e d ( I ) ;
Если I — целая переменная, то приведенные операторы эквивалентны соответственно 2S Программирование в Delphi 7
Глава 14
866 I := I
1; • -
14.4 Целые типы данных Целые типы данных используются для представления целых чисел. Они относятся к целым порядковым типам (см. разд. 14.1 и 14.3). Ниже приведена таблица, в которой перечисляются эти типы для Delphi 7 и даются диапазоны их изменений. Тип
Диапазон значении
Требования к памяти в байтах
Знаковый (может .ли хранить отрицательные числа)
Byte
0 + 255
1
нет
Word
0 + 65535
2
нет
Longword Он- 4294967295
4
нет
Shortint
-128- 127
1
да
Smalllnt
-32768 -*• 32767
2
да
Cardinal
0 •*• 4294967295
4
нет
Integer
-2147483648 •*• 2147483647 4
да
Longlnt
-2147483648 * 2147483647 4
да
Int64
_263 - 2
63
-1
8
да
Родовыми типами (т.е. обеспечивающими максимальную производительность) среди перечисленных являются Integer и Cardinal. В настоящий момент тип Integer эквивалентен типу Longlnt, но в последующих версиях это может быть изменено. Приведенные в таблице затраты памяти могут изменяться от версии к версии и от системы к системе. Поэтому, если требуется достоверно знать затраты памяти для того или иного типа, следует пользоваться функцией SizeOf. Арифметические операции над целыми числами возвращают тип Integer. Только если оба операнда имеют тип IntC-l, результат тоже имеет тип Int64. Большинство стандартных процедур и функций, работающих с целыми аргументами, усекают аргументы типа IntG4 до 4 байтов. Исключение составляют функции High, Low, Succ, Pred, Inc, Dec, IntToStr, IntToIIex, полностью поддерживающие аргументы типа Int64. Функции Round, Trunc, StrToInt64, StrToInt64Def также возвращают результат типа Int64. Функция Ord к типу 1л 164 применяться не может. Если пытаться увеличить значение переменной, уже имеющей максимальное для данного типа значение, то произойдет циклический переход к минимальному значению. Аналогично при попытке уменьшить минимальное значение произойдет переход к максимальному значению. Например, поскольку тип Shortint может принимать значения только в диапазоне от —128 до 127, то код var I:
Shortint;
I := High(Shortint) ; I := I + 2;
Типы данных в языке Object Pascal
867
даст в результате I = —127. Это произойдет, если не включена директива проверки диапазона целочисленных значений {$R+} и не установлена опция Range Checking в окне опций проекта (см. разд. 13.2.2). Иначе в подобных случаях в процессе выполнения будет генерироваться исключение с сообщением «Range check error».
14.5 Действительные типы данных Действительные типы данных предназначены для хранения чисел, имеющих дробную часть. Ниже приведена таблица, в которой перечисляются эти типы для Delphi 7 и даются диапазоны их изменений. Число значащих Требования разрядов к памяти в байтах
Тип
Диапазон значений
Real48
±2. 9-Ю- + ±1.7-10
Real
39
38
32
308
±5.0-10- *- ±1.7-10
Single
±1.5-10- -±3.4-10
Double
±5.0-10-
45
38
324
+ ±1.7-10
Extended ±3.6'10-
4932
308 439г
•*• ±1.1-10
62
Comp
_263 + 2
Currency
-922337203685477.5808 + 922337203685477.5807
11-12
6
15-16
8
7-8
4
15-16
8
19-20
10
19-20
8
19-20
8
Родовым (т.е. обеспечивающим максимальную производительность) является тип Real, который в настоящий момент эквивалентен типу Double, но в последующих версиях это может быть изменено. В ранних версиях он использовал 6 байтов, т.е. был эквивалентен современному Real48. Если при перекомпиляции старых кодов есть необходимость вернуться к 6-байтовому представлению Real, компиляцию надо вести с директивой компилятора {$REALCOMPATIBILITY ON}. Наименьшую производительность обеспечивает тип Real48, сохраняемый только для обратной совместимости с более ранними версиями языка. Тип Extended обладает максимальной точностью, но могут возникать проблемы с его переносимостью на другие платформы. Тип Comp является, строго говоря, большим целым, а не действительным числом, но отличается от целого, например, тем, что к нему нельзя применять функции инкремента и декремента. Этот тип оставлен только для обратной совместимости с более ранними версиями языка, поскольку его возможности перекрываются целым типом Int64. Тип Currency используется для представления денежных величин. В памяти он хранится как масштабированное в 10000 раз 8-байтовое целое. Благодаря этому при операциях с величинами типа Currency минимизируются ошибки округления, что очень важно для денежных расчетов. В выражениях, в которых смешаны величины типа Currency с величинами других действительных типов, значения Currency автоматически умножаются или делятся на 10000. Приведенные в таблице затраты памяти могут изменяться от версии к версии и от системы к системе. Поэтому, если требуется достоверно знать затраты памяти для того или иного типа, следует пользоваться функцией SizeOf.
Глава 14
868
14.6 Булевы типы данных Переменные булевых типов данных представляют логические значения, например, true (истина) и false (ложь). Булевы типы относятся к целым порядковым типам (см. разд. 14.1 и 14.3). Ниже приведена таблица, в которой перечисляются эти типы для Delphi 7. Столь большое число одинаковых по смыслу типов связано с желанием обеспечить совместимость Delphi с другими системами. Предпочтительнее всегда использовать тип Boolean, кроме обращений к каким-нибудь процедурам, явным образом требующим другой тип. Тип
Значения
Требования к памяти в байтах
Boolean
Булева величина в один байт
1
ByteBool
Булева величина в один байт
1
WordBool
Булева величина в одно слово
2
LongBool
Булевская величина в два слова 4
Предопределенные в языке константы true и false обладают в разных булевых типах несколько разными свойствами, которые просуммированы в приведенной ниже таблице. тип Boolean
типы ByteBool, WordBool, LongBool
false < true
false о true
Ord(false) = 0
Ord(false) = 0
Ord(true) = 1
Ord(true) 0
Succ(I'alse) = true
Succ(false) = true
Pred(true) — false
Pred( false) = true
В отличие от некоторых других языков. Object Pascal не позволяет трактовать целое значение как булеву величину. Поэтому, например, если X — целая переменная, то оператор if x then . . .
будет воспринят компилятором как синтаксическая ошибка. Для булевых типов определены операции and (И), ог (ИЛИ), not (HE, отрицание) и хог (исключающее ИЛИ) — см. разд. 13.7.4. Использование этих операций расширяет возможности по формированию сложных условий в ряде операторов.
14.7 Символьные типы данных Символьные типы предназначены для хранения одного символа. Они относятся к целым порядковым типам (см. разд. 14.1 и 14.3). Ниже приведена таблица символьных типов. Наличие двух разных типов — ANSIChar и WideChar связано с двумя различными множествами символов: ANSI, каждый символ которого занимает 1 байт, и Unicode, каждый символ которого занимает 2 байта. Первые 256 символов в этих множествах одинаковы и соответствуют символам ASCII от 0 до 255.
Типы данных в языке Object Pascal Тип
869
Размер в байтах Что может хранить
ANSIChar 1
Один символ ANSI
WideChar
о
Один символ Unicode
Char
1
Сейчас эквивалентен ANSIChar. В будущих версиях Delphi может быть будет эквивалентен WideChar
Родовым (т.е. обеспечивающим максимальную производительность) является тип Char, который в настоящий момент эквивалентен типу ANSIChar, но в последующих версиях это может быть изменено. Именно тип Char имеет смысл использовать во всех случаях, кроме обращений к функциям, требующим другой тип символьных данных. Приведенные в таблице затраты памяти могут изменяться от версии к версии и от системы к системе. Поэтому, если требуется достоверно знать затраты памяти для того или иного типа, следует пользоваться функцией SizeOf. Для символьного типа предопределена функция Chr, возвращающая символ любого целого значения в пределах AnsiChar или WideChar. Например, Chr(65) возвращает букву «А». Поскольку символьные типы относятся к порядковым (см. разд. 14.3), для них предопределены такие функции и процедуры, как Ord, Pred, Succ, Inc. Dec и др. Функция Ord, возвращающая номер символа, противоположна по смыслу функции Chr. Т.е. Ord(Chr(65)) вернет 65, a Chr(Ord('A')) вернет символ «А».
14.8 Типы строк Строки представляют собой последовательность символов. Ниже приведена таблица типов строк. Тип строки
M а ксима л ьна я Используется для длина
ShortString
255 31
Нулевой символ в конце
обратной совместимости
нет
Ansi String
-2 (-2 Гб)
символов ANSI
есть
String
или 255, или до ~2 Гб
символов ANSI или Unicode
есть или нет
WideString
-230 (-1 Гб)
символов Unicode, в серверах есть СОМ и интерфейсах
Родовым является тип String, который имеет разный смысл в зависимости от директивы компилятора $Н. Если включена директива {$Н+} (она включена по умолчанию), то String интерпретируется компилятором как тип AnsiString — длинная строка с нулевым символом в конце. Если же включить директиву {$Н-}, то String интерпретируется компилятором как тип ShortString — короткая строка без нулевого символа в конце. Если в объявлении типа после ключевого слова String следует число символов в квадратных скобках (например, String[4]), то, независимо от директив компилятора, тип трактуется как строка без нулевого символа в конце с указанным числом символов. Стандартная функция Length возвращает число символов в строке, переданной ей в качестве параметра. Процедура SetLength устанавливает длину строки.
870
Глава J4
- Для строк определены операции сравнения > , ) ;
Например var Mode :
(raRead, mEdit, m W r i t e ] ;
Если переменная определена так, то ей можно присваивать указанные значения, можно проверять ее величину, сравнивая с возможными значениями. Кроме того, надо учитывать, что перечислимые типы относятся к целым порядковым типам (см. разд. 14.3) и к ним применимы любые операции сравнения (>, < и т.п.), а также процедуры и функции, определенные для порядковых типов. Если возможные значения типа определены, как указано выше, в переменной при ее объявлении, то нельзя будет ввести другую переменную с теми же значениями. Если такая потребность имеется, то перечислимый тип надо определять именно как тип: type = (, . . . , < значение п>| ,-
Типы данных в языке Object Pascal
мер:
873
Тогда можно ввести в программе несколько переменных этого типа. Наприtype TMode = (mRead, mEdit, mWritel; var Model, Mode2 : TMode; 1
14.10 Ограниченные типы Для порядковых типов (см. разд. 14.3) можно задать поддиапазон их возможных значений для вводимого вами типа или переменной — это и будет ограниченный тип. Задается диапазон значений ограниченного типа выражением вида ,. Например: var type
Letter : 'а'..'z' ; Mum : I. .12; Caps = 'A'..'2';
В этих примерах переменная Letter может принимать только символы латинских букв в. нижнем регистре, переменная Num — только целые числа в диапазоне 1*12 (это могут быть, например, номера месяцев), переменные, чей тип будет объявлен как Caps, будут принимать только символы латинских букв в верхнем регистре. Ограниченные типы используются, например, при объявлениях размеров массивов (см. разд. 14.15). Но они могут использоваться и самостоятельно. В компиляторе Object Pascal имеется опция, позволяющая включить проверку диапазона при присваивании значения переменной ограниченного типа. Это опция {$К+}. Вы можете ее включить в том месте вашей программы, где хотите начать проверку диапазона, и выключить проверку, где захотите, опцией {$В-}. Можно также включить опцию проверки в окне Project Options на странице Compiler (см. разд. 13.2.2). Это надежнее, так как опция {$R+} срабатывает только при ошибках диапазона, очевидных для компилятора, а установка опции проверки диапазона в окне Project Options действует в процессе выполнения (в режиме отладки) и при попытке присвоить переменной ограниченного типа значение, выходящее за пределы заданного поддиапазона, генерирует исключение с сообщением «Range check error». Таким образом, введение ограниченных типов является неплохим средством отладки. При использовании в объявлении ограниченного типа константных выражений (см. разд. 13.5.1) возможны некоторые ошибки компилятора. Например, объявления const Х=100; Y-200; type Scale = (X + Y) div 2 . - Y . ;
вызовут сообщение компилятора об ошибке, поскольку, если после знака равенства в объявлении типа следует скобка, то компилятор считает, что описывается перечислимый тип (см. разд. 14.9). Чтобы избежать такой ошибки, приведенные выше объявления можно заменить следующими: const
Х=100; Y=200; type Scale - 1 * (X + Y] div 2 . . Y ;
874
Глава 14
14.11 Множества Множество — это группа элементов, которая ассоциируется с ее именем и с которой можно сравнивать другие величины, чтобы определить, принадлежат ли они этому множеству. Один и тот же элемент не может входить в множество более одного раза. Как частный случай, множество может быть пустым. Множество определяется перечислением его элементов, заключенным.в прямоугольные скобки. Такая форма определения называется конструктором множества. Например, если множество возможных единичных символов, которые могут быть получены в ответ на вопрос программы «Yes/No», содержит символы «у», «Y», «п» и «N», то это множество можно описать таким конструктором: ! ' у ' , ' I 1 , 'п','N'1 Для определения, принадлежит ли переменная множеству, служит операция in. В предыдущем примере проверить, дал ли пользователь один из допустимых ответов, можно оператором: 1
if [Key in [ ' у ' , ' У , ' n ' , ' N ] ) then
Множества могут содержать не только отдельные значения, но и ограниченные типы. Например, если вы хотите контролировать символы, вводимые пользователем При вводе целого положительного или отрицательного числа, вы можете определить множество ['0'..'9', '+', '-'] и использовать его, например, при обработке события On Key Press какого-то окна редактирования: if n o t ( K e y in [ ' 0 ' - . ' 9 ' ,
' + ',
' - ' ] ) then Key := 10;
Подобный оператор не позволяет пользователю ввести какие-то символы, отличные от имеющихся в множестве. В приведенных операторах множество заранее не объявлялось в виде типа. Но если, например, в приложении в ряде мест надо проводить проверки, аналогичные приведенным выше, то целесообразнее объявить переменную или типизированную константу типа множества или тип множества и несколько переменных этого типа. Объявление типа множества делается в форме set of
Приведем примеры, Объявление глобальной переменной с инициализацией: var К : set o f C h a r = [ ' 0 ' . . ' 9 ' ,
'+',
'-'];
if (Key in К) then ... Объявление типизированной константы: . const К: set of C h a r = [ ' 0 ' . . ' 9 - , ' + ' , '-']; Объявление типа множества и переменных этого типа: type TDigit = set of ' 0 ' . . ' 9 ' ; var Dl, D2: TDigit; Dl := [ ' 0 ', ' 1' ]; D2 : = { ' 2 ' . . т Э ' ] ;
Помимо операции in для множеств определен еще ряд операций: объединение, пересечение, операции отношения и ряд других {см. разд. 13.7.8).
Типы данных в языке Object Pascal
875
14.12 Тип variant Object Pascal является строго типизированным языком. По в нем имеется тип variant, отступающий от этого правила. В переменных типа variant могут храниться данные любых типов, кроме записей, множеств, статических массивов, файлов, классов, ссылок на классы, указателей и Int64. Иначе говоря, переменные типа variant могут хранить все, кроме структур (см. разд. 14.L), указателей и Int64. Они могут также хранить объекты СОМ и CORBA, обеспечивая доступ к их методам и свойствам. Могут они также хранить динамические массивы и специальные массивы variant, о которых будет рассказано ниже. Например, если определить тип переменной V как variant, то могут выполняться такие операторы: V: variant;
// тип V не определен (равен Unessignedl
V := 5;
/ / тип V становится равным integer
V := l e l O ;
// тип V становится равным real
V := E d i t l . T e x t ;
// тип V становится равным string
Тип variant имеет смысл использовать в тех случаях, когда тип того или иного объекта заранее не известен или когда какие-то функции и процедуры требуют именно такой тип аргументов. При этом надо иметь в виду, что затраты памяти и времени на работу с переменными типа variant больше, чем при работе с обычными типами. К тому же недопустимые операции с переменными типа variant приводят к ошибкам времени выполнения, тогда как аналогичные недопустимые операции с переменными других типов выявляются на этапе компиляции. Переменные типа variant занимают 16 битов и содержат код типа и значение переменной или указатель на это значение. В момент создания эти переменные инициализируются специальным значением Unassigned. Значение переменной null свидетельствует о неизвестном или ошибочном значении переменной. Переменные типа variant могут использоваться в выражениях как операнды любых операций, кроме ", is и in, совместно с другими величинами типов variant, integer, real, string, Boolean. Компилятор в этих случаях автоматически осуществляет приведение типов величин в выражении. Если оба операнда операции имеют тип variant и один или оба операнда равны null, то результат операции тоже рааен null. Если один или оба операнда имеют значение Unassigned, то генерируется исключение. Если в бинарной операции только один из операндов имеет тип variant, то другой тоже преобразуется в тип variant. Приведение типов при вычислении выражений, содержащих операнды variant, осуществляется обычным образом (см. разд. 14.2). Например, если VI и V2 типа variant содержат данные типов integer и real, то выражение VI + V2 вернет значение real. Узнать действительный тип значения переменной variant можно с помощью функции VarType, возвращающей значение типа TVarData, содержащее код типа. Запись TVarData определена в модуле Sysfem. Имеется также предопределенная константа varTypeMask, сложение которой по операции and с переменной типа TVarData возвращает истинный тип переменной. Например, выражение VarType(V)
and varTypeMask = varDouble
вернет true, если в переменной V в данный момент хранится значение типа double или массив значений типа double. Возможные значения типов, возвращаемые функцией VarType, следующие:
Глава 14
876
varEmpty
переменная в состоянии Unassigned
varNull
значение переменной равно null
VarSmallint
тип Smallint
varlnteger
тип Integer
var Single
тип Single
var Double
тип Double
varCurrency
тип Currency
varDate
тип TDateTime (дата И время)
varOLEStr
ссылка на динамически размещенную строку UNICODE
varD is patch
ссылка на автоматический объект (указатель на интерфейс IDispatch)
varError
код ошибки операционной системы
var Boolean
тип WordBool
varUnknown
ссылка на неизвестный объект СОМ (указатель на интерфейс IlJnknown)
varByte varString
тип Byte ссылка на динамически размещенную строку в стиле Pascal (тип AnsiString).
varTypeMask
битовая маска для извлечения кода типа
var Array
бит, устанавливаемый при ссылке на массив variant
varByRef
бит, устанавливаемый при ссылке на тип variant
Теперь остановимся на массивах типа variant. Переменной типа variant нельзя присвоить значение обычного статического массива. Это можно сделать только специальными функциями Var Array Create и VarArrayOf. Функция VarArrayCreate определена следующим образом: f u n c t i o n V a r A r r a y C r e a t e ( c o n s t Bounds: array of Integer; VarType: Integer): Variant;
Здесь параметр Bounds является массивом, содержащим четное количество целых чисел, каждая пара которых определяет нижнее и верхнее значения индекса массива (точнее, размерности массива, если массив многомерный). Параметр VarType определяет тип элементов массива- Он может принимать значения, приведенные выше в таблице, кроме varString, varArray, varByRef. Например, операторы var V: Variant; V := V a r A r r a y C r e a t e ( [ 0 , 9 ] , v a r l n t e g e r ) ;
создают в переменной V типа variant массив из десяти целых чисел. Операторы var A: Variant; А := V a r A r r a y C r e a t e ( [ 0 , 3), v a r V a r i a n t ) ; А [ 0 ] := 1; А [ 1 ] :- 1 2 3 4 . 5 6 7 8 ; А[2] := 'Привет!'; А [ 3 ] := True; L a b e l l . C a p t i o n := А [ 0 ] ; Label2.Caption := A [ 2 ] ;
Типы данных в языке Object Pascal
877
создают в переменной А типа массив из четырех значений типа variant, в которые можно заносить и затем использовать значения различных типов. Функция VarArrayOf определена следующим образом: function VarArrayOf(const V a l u e s : array of V a r i a n t ) : Variant; Она возвращает одномерный массив элементов, задаваемых параметром Values. Например, приведенный выше пример можно дополнить операторами А [ 3 ) := V a r A r r a y O f ([1, 'Приветик! ' , Label3.Caption : = А [ 3 ] { 1 ] ;
100,
1000]);
14.13 Указатели Указатель является величиной, указывающей на некоторый адрес в памяти, где хранятся какие-то данные. Указатели бывают типизированные, указывающие на данные определенного типа, и нетипизированные (типа pointer), которые могут указывать на данные произвольного типа. Чаще всего указатели используются для работы с объектами в динамически распределяемой области памяти (см. разд. 13.9). В Object Pascal имеется ряд предопределенных типов указателей. Это прежде всего типы указателей на строки: PAnsiChar и PWideChar, представляющие собой соответственно указатели на значения AnsiChar и WideChar. Имеется также родовой тип PChar, определяющий указатель на Char (т.е. пока указывающий на AnsiChar)\ Эти указатели используются при работе со строками с нулевым символом в конце. Тип указателя
Tun переменной, на которую указывает указатель
PAnsiStrtng, PString Ansi String PByteArray
ByteArray (объявлен в модуле SysUtils). Используется для доступа к динамически размещаемым массивам
Р Currency
Currency
PExtended
Extended
POleVariant
OleVariant
PShortString
ShortS tring
PTextBuf
TextBuf (объявлен в модуле SysUtils). Внутренний тип буфера в записи файлов TTextRec
PVarRec
TVarRec (объявлен в модуле System)
PVariant
Variant
PWideString
WideString
PWordArray
Т Word Array (объявлен в модуле Syslltils^. Используется для доступа к динамически размещаемым массивам 2 -байтных величин
Объявление своего типизированного указателя на любой тип имеет вид: type = Л ; Например, предложения: type Pint •= "integer; var PI, P 2 : Pint;
878
Глава 14
объявляют тип Pint указателя на величину типа integer и две переменные Р1 и Р2, являющиеся указателями на значения типа integer. Однако надо понимать, что объявление переменных Р1 и Р2 не создает самих величин, на которые они указывают. Выделяется только память под хранение указателей, но сами эти указатели ни на что не указывают. Имеется предопределенная константа nil, которая обычно присваивается указателям, которые в данный момент ни на что не указывают. Чтобы получить доступ к данным, на которые указывает типизированный указатель, надо применить операцию его разыменования. Она записывается с помощью символа ~, помещаемого после указателя. Например, если переменная Р1 является указателем приведенного выше типа Pint, то выражение Р1~ — это та целая величина, на которую указывает указатель Р1. Если I — переменная целого типа, то после выполнения оператора Р1" := I; Р1 начнет указывать на переменную I и выражение Р1" будет возвращать значение этой переменной. Того же результата можно добиться операцией адресации. Например, приведенный выше оператор можно заменить эквивалентным ему оператором Р1 := @1;
Этот оператор присваивает указателю Р1 адрес переменной I. Таким образом, применение операций разыменования или адресации — один из способов присвоить указателю ссылку на конкретную область памяти. Другой, более распространенный способ — применение процедуры new (см. разд. 13.9), которая выделяет память под переменную соответствующего типа и присваивает указателю ссылку на эту область. Например, оператор new{Pl);
выделит память для хранения целого значения и присвоит указателю Р1 ссылку на это значение. Операция разыменования не применима к указателям типа pointer. Чтобы разыменовать указатель pointer, надо сначала привести его к другому типу указателя (см. разд. 14.2). Например, если указатель Р объявлен как указатель типа pointer, то выражение Plnt(P) приведет его тип к объявленному выше типу Pint, после чего его можно будет разыменовывать. Таким образом, с помощью операции приведения типа можно записать такие операторы: Pint ( Р ) :=Р1; I := P l n t ( P ) " + Р 2 А ;
Указатели широко используются в Object Pascal и Delphi, причем часто неявно для пользователя. Например, передача параметров по ссылке в процедуры и функции осуществляется именно через указатели. Применение специализированных указателей для обработки строк рассмотрено в разд. 14.8. Использование указателей для динамического распределения памяти рассмотрено в разд. 13.9.
14.14 Файлы 14.14.1 Способы организации файлового ввода/вывода Файлы представляют собой множество упорядоченных элементов одного типа. Для доступа к файлам чаще всего (альтернативный вариант рассмотрен в разд. 14.14.3) используется специальная файловая переменная. Она связывается с указанным файлом процедурой AssignFile. Эта процедура имеет синтаксис: procedure AssignFile(var F: File, S: stringl;
Типы данных в языке Object Pascal
879
где F — файловая переменная любого типа, S — строка, содержащая имя файла. Например, операторы AssignFile (F1, 'Test.txt1); AssignFile(F2, OpenDialogl.FileName);
связывают файловые переменные F1 и F2 соответственно с файлом «Test.txt» и с файлом, имя которого записано в свойстве FileNarae компонента — диалога OpenDialogl. Открытие существующего файла осуществляется процедурой Reset, формат которой (кроме нетипизированных файлов — см. разд. 14.14.2.3) следующий: procedure R e s e t ( v a r F : F i l e ) ;
Файловая переменная F перед обращением к этой процедуре должна быть связана с файлом. Создание и открытие нового файла осуществляется процедурой Rewrite, формат которой (кроме нетипизированных файлов — см. разд. 14.14.2.3) следующий: procedure R e w r i t e ( v a r F: F i l e ) ;
После выполнения различных операций чтения и записи файл должен быть закрыт процедурой CloseFile: procedure CloseFile(var F : F i l e ) ;
Определен еще ряд процедур работы с файлами, которые частично указаны ниже, а полный их перечень вы можете найти в гл. 16 в разд. 16.7 и 16.8. При работе с файлами, при чтении и записи в них каких-то данных возможны различные ошибки. Если не принять соответствующих мер, то эти ошибки приведут к прерыванию работы программы. Предотвратить это можно двумя путями. Первый предполагает обработку исключений InOutError, поле errorcode которых содержит информацию о конкретном виде ошибки (см. пример и подробное описание в разд. 13.10.4.1, а коды ошибок — в разд. 16.1.2). Второй предполагает применение опции {$!-} (см. разд. 13.2.4), отключающей генерацию исключений ошибок ввода/вывода, и дальнейшее обращение к функции lOResull, возвращающей код ошибки и сбрасывающей его в 0 (так что повторное обращение к lOResuIt бессмысленно). После обработки операции ввода/вывода контроль ошибок можно снова включить опцией {$!+}. В этом случае общая организация работы с файлами строится примерно по такой схеме: AssignFile IF, OpenDialogl.FileName); 1ST-} Reset(F); if I о 0 then else ...
I L- lOReault; if I о 0 then C l a s e F i l e f v a r F: File); При операциях ввода/вывода проверять окончание файла можно функцией Eof(F: File), возвращающей true при достижении конца файла.
880
Глава 14
14.14.2 Типы файлов Различают файлы трех видов: текстовые, файлы, типизированные файлы и нетипизированные файлы. 14.14.2.1 Текстовые файлы Текстовые файлы состоят из последовательностей символов, разбитых на строки. В Object Pascal предопределен тип TextFile, соответствующий текстовому файлу. Таким образом, объявление файловой переменной может иметь вид: var -£иыя файловой переменной>: TextFile;
Запись данных в текстовый файл осуществляется процедурой procedure W r i t e ( v a r F: TextFile; );
Выражения могут быть типов Char, Integer, Real, string, упакованных строк, Boolean. При этом может использоваться форматирование (см. гл. 16 разд. 16.1.3). Например: WritelF,
'Ban ', I,
1
' лет !;
Аналогичная процедура Writeln отличается от Write только тем, что после записи пишет символ перехода на новую строку, т.е. Writeln формирует одну строку. Чтение данных из текстового файла осуществляется последовательно от его начала процедурой procedure Read(var F: TextFile; );
где в списке перечисляются переменные, в которые читаются данные из файла. Например, если определить переменные S1 И S2 как var S l , S 2 : s t r i n g [ 4 ] ,-
то чтение строки, записанной в файл приведенным выше оператором, если открыть этот файл и использовать оператор ReadlF, SI, I, S2];
даст значение S1, равное «Вам », значение I равное записанному числу лет, и значение S2, равное » лет». Аналогичная процедура Readln отличается от Read только тем, что после чтения переводит текущую позицию в файле на новую строку. Если в процедуре Readln не задай список переменных, то она просто пропускает текущую строку и переходит к следующей. 14.14.2.2 файловый ввод/вывод с помощью компонентов В разд. 14.14.2.1 описана работа с текстовыми файлами средствами Object Pascal. Однако в Delphi работа с текстовыми файлами может осуществляться и без непосредственного обращения к функциям Object Pascal. Для этого можно использовать методы LoadFromFile и SaveToFile, имеющиеся у классов TStrings и TStringList. Эти классы описывают списки строк и обладают множеством методов, позволяющих манипулировать строками. Многие свойства компонентов имеют тип TStrings и, следовательно, в них можно загружать текстовые файлы. Например, прочитать тестовый файл, а также файл документа в обогащенном формате RTF можно в компонент R i c h E d i t : RichEditl.Lines.LoadFroraFile('Test.rtf) ;
После того как пользователь что-то изменил в тексте, его можно сохранить в файле оператором RichEditl.Lines.SaveToFile('Test.rtf');
Типы данных в языке Object Pascal
881
Формат, в котором сохраняется текст, определяется значением свойства Р1ашText компонента RichEdit. Если PiainText = false (это значение принято по умолчанию), то файл записывается в формате RTF. Если вам желательно сохранить файл в текстовом формате, то перед сохранением текста в файл надо установить PiainText в true. Аналогичным образом вы можете работать и с компонентом Memo. Только В этом случае нет необходимости думать о формате, так как Memo работает только с текстовым форматом. Если вам не требуется отображать текст пользователю, а надо просто прочитать содержимое некоторого текстового файла, обработать текст и сохранить его в файле, вы можете сделать это, например, следующим образом. Объявите переменную списка строк типа TStringList, в которой будет храниться текст файла. В момент создания формы или в момент, когда вам надо прочитать файл, разместите в памяти эту переменную. Например: var L i s t : TStringList; List
:= TStringList.Create;
После этого можете читать в переменную List файл: List.LoadFromFile('test.txt');
Далее можете работать с этим текстом методами, свойственными типу TStringList (см. соответствующий раздел в гл. 17): Add, Delete и т.п. В заключение можете сохранить измененный текст в файле и удалить из памяти уже ненужный список: List,SaveToFile('test.txt'); List.Free; Через компоненты Delphi можно работать не только с текстовыми файлами, но и с файлами изображений и мультимедиа. Этим вопросам посвящена гл. 6. 14.14.2.3 Типизированные файлы Типизированные файлы являются двоичными файлами, содержащими последовательность однотипных данных. Объявление файловых переменных таких файлов имеет вид: var
8*
56
$38
ord('S')
9(
57
$39
ord('9')
192
$СО
-
189
$BD
=+
187
$ВВ
[{
219
$DB
]}
221
$DD
;:
\ ,< >
186
$ВА
222
$DE
220
$DC
188
$ВС
190
$ВБ
959
Процедуры и функции Object Pascal, Delphi и API Windows Клавиша Десятичное число
Шестнадцатеричыое число
Символическое имя
Сравнение по функции о I'd
/J
191
$BF
a, A
65
$41
ord('A')
b,B
66
$42
ord('B')
c,C
67
$43
ord('C')
d,D
68
$44
ord('D')
e,E
69
$45
ord('E')
f,F
70
$46
ord('F')
9,G
71
$47
ordCG')
h,H
72
$48
ord('H')
U
73
$49
ord('I')
iJ
74
$4А
ord('J')
k,K
75
$4В
ord('K')
U
76
$4С
ord('L')
m,M
77
$4D
ord('M')
n,N
78
$4Е
ord('N')
o,0
79
$4F
ord('O')
P.P
80
$50
ord('P P )
q,Q
81
$51
ord(-Q')
r,R
82
$52
ord('R')
S,S
83
$53
ordCS')
I,T
84
$54
ord('T')
u,U
85
$55
ord('U')
v,V
86
$56
ord('V')
w,W
87
$57
ord('W')
X,X
88
$58
ord('X')
89
$59
ord('Y')
90
$5А
ord('Z')
Y.Y
z,z
На правой клавиатуре при выключенной клавише NumLock
0
96
$60
VK NUMPADO
1
97
$61
VK NUMPAD1
2
98
$62
VK_NUMPAD2
3
99
$63
VK NUMPAD3
4
100
$64
VK NUMPAD4
5
101
$65
VK NUMPAD5
6
102
$66
VK NUMPAD6
960
Глава 16
Клавиша Десятичное Шестнадцатеричпое число число
Символическое имя
7
103
$67
VK_NUMPAD7
8
104
$68
VK_NUMPAD8
9 *
105
$69
VK_NUMPAD9
106
$6А
VK
+
107
$6В
VK_ADD
-
109
$6D
VK SUBTRACT
110
$6Е
VK DECIMAL
111
$6F
VK__DIVIDE
/
Сравнение по функции ord
MULTIPLY
16.12 Коды ошибок файлового ввода-вывода Ниже приведена таблица кодов ошибок файлового ввода-вывода. Эти коды присваиваются свойству errorcode исключения EIn Out Error, если действует директива компилятора {$!+}, или возвращаются функцией lOResult, если действует директива компилятора {$!-}. Код
Ошибка
2
Файл не найден
3
Неправильное имя файла
4
Слишком много открытых файлов
5
Файл не доступен
100
Достигнут конец файла (EOF)
101
Диск переполнен
106
Ошибка ввода
Пример использования этих кодов см. в гл. 13 разд. 13,2.4 в описании директивы компилятора {$!}.
16.1.3 Строка описания формата и функция Format Строка описания формата определяет способ форматирования данных и используется в таких функциях, как Format, FormatBuf, FmtStr, StrFmt, StrLFmt, конструкторах исключений и др. Строка описания формата содержит два типа объектов: обычные символы, которые непосредственно копируются в форматированную строку, и спецификаторы формата, которые определяют формат записи в результирующую строку списка аргументов. Спецификатор формата имеет вид %[:] [-] [] [ .]
Спецификатор формата начинается с символа « % *. Затем без пробелов следует ряд необязательных полей:
.
Процедуры и функции Object Pascal, Delphi и API Windows
[< индекс>:]
н
961
определяет индекс (номер) аргумента в заданном списке, к которому относится данный спецификатор формата индикатор выравнивания влево
[]
устанавливает ширину поля
[]
спецификатор точности
Затем так же без пробела размещается единственное обязательное поле , определяющее, как и в каком формате будет интерпретироваться аргумент. Ниже приводится таблица спецификаторов типа. Десятичное целое. Значение преобразуется в строку десятичных цифр. Если спецификатор формата содержит поле точности, то результирующая строка должна содержать количество цифр, не менее указанного значения. Если форматируемое значение содержит меньше цифр, оно дополняется слева нулями. Научный формат значения с плавающей запятой. Значение преобразуется в формат вида «— d.ddd...E+ddd», где d означает цифру. Иначе говоря, чис+ddd ло представляется в виде -d.ddd...' 10 . Отрицательные числа начинаются со знака «-». Перед десятичной точкой всегда имеется одна цифра. Общее число цифр (включая цифру перед точкой) равно числу, указанному спецификатором точности. По умолчанию (в отсутствие спецификатора точности) точность равна 15. После символа «Е» обязательно следует знак « + » или «-» и не менее трех цифр. Формат с фиксированной точкой значения с плавающей запятой. Значение преобразуется в формат вида «—ddd.ddd...». Отрицательные числа начинаются со знака *-». Число цифр после десятичной точки равно числу, указанному спецификатором точности. По умолчанию (в отсутствие спецификатора точности) точность равна 2. Обобщенный формат чисел с плавающей запятой. Значение преобразуется в формат научный или с фиксированной точкой, в зависимости от того, какой из них дает более короткую запись. В научном формате общее число цифр (включая цифру перед точкой) равно числу, указанному спецификатором точности. По умолчанию (в отсутствие спецификатора точности) точность равна 15. Заключающие нули после десятичной точки в результирующей строке отбрасываются, а сама десятичная точка появляется, только если это необходимо. Результирующая строка использует формат с фиксированной точкой, если число цифр слева от десятичной точки не превышает заданной точности и если значение числа не меньше 0.00001. В остальных случаях используется научный формат. Обобщенный формат чисел с плавающей запятой наиболее удобен в большинстве случаев. Формат, подобный формату с фиксированной точкой для чисел с плавающей запятой, но отличающийся наличием в результирующей строке разделителей тысяч. Иными словами, число представляется в форме «-d.ddd,ddd.ddd..... Монетарный формат чисел с плавающей запятой. Значение преобразуется в строку, представляющую собой денежную сумму. Преобразование определяется глобальными переменными CurrencyString, Currency Format, NegCurrFormat, Thousand Separator, Decimal Separator и CurrencyDecimals, которые инициализируются форматом Currency Formal в разделе International программы «Контрольная панел» (Control Panel) Windows. Если задан спецификатор точности, то он заменяет значение, содержащееся в глобальной переменной CurrencyDecimals. 31 Программирование е Delphi 7
962
Глава 16
Формат отображения указателей. Значение преобразуется в строку вида *XXXX:YYYY», где ХХХХ И YYYY — сегмент и смещение указателя, выражаемые четырьмя щестнадцатеричными цифрами. Формат строки для аргументов вида символ, строка или строка типа PChar. Строка или символ просто вставляются в результирующую строку. Если задан спецификатор точности, то он определяет максимальное число вставляемых символов. Если вставляемая строка длиннее, она усекается. Шестнадцатеричный формат целых чисел. Значение аргумента преобразуется в строку шестнадцатеричных цифр. Если задан спецификатор точности, то он указывает минимальное число цифр в строке; если строка оказывается короче, она дополняется слева нулями. Все спецификаторы типа могут записываться как строчными, так и прописными буквами. Спецификатор индекса задает порядковый номер аргумента в списке, к которому применяется преобразование. Индекс первого аргумента считается равным 0. Применение индексов позволяет пропускать какие-то аргументы в списке или форматировать один и тот же аргумент несколько раз. Спецификатор ширины задает минимальное число символов результата данного преобразования. Если результирующая строка короче заданной ширины, то лишние позиции заполняются пробелами. По умолчанию используется выравнивание вправо и пробелы ставятся перед преобразованным значением. Но если указан индикатор левого выравнивания (символ «-» перед спецификатором ширины), то пробелы вносятся после преобразованного значения. Действие спецификатора точности на различные форматы уже рассмотрено выше. Во всех форматах чисел с плавающей запятой символы, используемые для отделения целой части числа от дробной и для разделения тысяч, берутся из глобальных переменных Decimal Separate г и Thousand Separator. Имеется функция Format, объявленная следующим образом: function Format(const SForrnat: string; const A r g s : array of c o n s t ) : string;
Она возвращает отформатированную строку, представляющую собой результат применения строки описания формата SFormat к открытому массиву аргументов Args. Приведем примеры форматирования. Формат * Format('Задано Id и % d ' ,
[5,2])
дает результат Задано 5 и 2
Формат Format('%d ~ % 0 : х ' , [ 3 0 ] ) дает результат
30 = IE
В этом примере использование индекса позволило представить один и тот же аргумент в десятичном и шестиадцатеричном видах. Формат Format('%e
%0:f
%0:n % 0 : g ' , [ 10000 . ] )
Процедуры и функции Object Pascal, Delphi и API Windows
963
дает результат 1,OOOOOOOOOOOOOQE+004 а формат Format('%e
ID:f
10000,00
10 000,00
10000
40:g',[0.0001])
дает результат 1,000000000000ООЕ-004
0,00
0.0001
Эти два примера показывают, что применение формата е с точностью по умолчанию дает очень неудобную форму представления. Второй из примеров показывает, что применение формата f с точностью по умолчанию (2) к числам, меньшим 1, может отсечь значащие цифры. Наиболее удобен формат g. Формат Format('%ш',[10000.]> дает результат 10 000р.
•
16.2 Математические функции Функция
Описание
Аргументы
Abs(X)
абсолютное значение
целое или действительное выражение
Ceil(X)
округление до наименьшего целого, превышающего или равного аргументу
выражение Extended
CompareVaIue( A, B[,Epsilon])
сравнение А и В с точностью Epsilon
целые или действительные выражения
DivMod(Dividend, целочисленное 16-разрядное целые выражения Divisor, Result, деление с вычислением остатRemainder) ка EnsureRange( ближайшее к AValue из диа- целые или действительные выражения AValue, AMin, AMax) пазона AMin. - AMax Cos(X)
косинус
выражение Extended — угол в радианах
Exp(X)
экспонента
действительное выражение
Floor(X)
выражение Extended округление до наибольшего целого, меньшего или равного аргументу
Frac(X)
дробная часть аргумента: X-Int(X)
выражение Extended
Frexp(X, Mantissa, Exponent)
выделяет мантиссу и показатель степени аргумента X
выражение Extended
1 n R a 11 g c( A V a 1 u e , определяет, лежит ли AValue целые или действительAMin, AMax) в диапазоне AMin — AMax ные выражения
Int(X)
целая часть аргумента
действительное выражение
Глава 16
964
Аргументы
Функция
Описание
IntPower (X, Е)
возведение Х в целую степень выражения Extended Е и Integer Е: Х проверка, является ли X бес- действительное выражеконечной величиной ние
IsInfinitc(X)
действительное выражение
IsNan(X)
проверка, является ли X нечисловой величиной NaN
IsZero( A.Epsilon)
проверка, является ли X с за- действительное выражеданной погрешностью Epsilon ние
Ldexp(X,P)
умножение X на 2 в целой Р стенени Р: X • 2
выражения Extended и Integer
Ln(X)
натуральный логарифм от X
действительное выражение
LnXPl(X)
натуральный логарифм от X-fl выражение Extended
LoglO(X)
десятичный логарифм от X
выражение Extended
Log2(X)
логарифм от X по основанию 2 выражение Extended
LogN (N, X)
логарифм от X по основанию N выражения Extended
Max(A,B>
максимум двух чисел
выражения Integer, Int64, Single, Double, Extended
Min(A,B)
минимум двух чисел
выражения Integer, Int64, Single, Double, Extended
Pi
число Пи: 3.1415926535897932385
—
PoIy(X, C)
вычисляет полином X с массивом коэффициентов С
выражение Extended и массив Double
Power(X, E)
возведение X в произвольную выражения Extended степень Е: ХЕ
Eound(X)
ближайшее целое аргумента
RoundTo(X, ADigit)
округление X до ADigit цифр выражение Double
SameValue(A, B, Epsilon)
проверка, равны ли друг дру- действительное выражегу А и В с точностью Epsilon ние
Sqr{X)
квадрат аргумента: Х*Х
выражение Extended
Sqrt(X)
квадратный корень
выражение Extended
Trunc(X)
возвращает целую часть дейст- выражение Extended вительного выражения
выражения Extended
Комментарии Математические функции описаны в модуле Math. Этот модуль должен быть подключен к приложению оператором uses. Функции Ceil, Floor, Round, Trunc обеспечивают округление действительного числа до целого, но в разные стороны: Ceil — в сторону увеличения, Floor — в сторону уменьшения, Round — до ближайшего целого, Trunc — в сторону нуля. Если
965
Процедуры и функции Object Pascal. Delphi и API Windows
число точно посередине между целыми, то функция Round всегда округляет до четного числа. Ниже приведены примеры округления
функция \ X Ceil
]3.5
-3.5
3
A
-3
3
Floor
3
-4
3
Trunc Round
3
—Ч
3
4
-4
3
Функции Ceil и Floor при округлении возвращают целое типа Integer. Значение аргумента этих функций не должно превышать Maxlnt. Функции Round и Тгцпс при округлении возвращают целое типа Int64. Если целое значение выходит за пределы, допустимые для Int64, генерируется исключение ElnvalidOp. Функции Ln, LnXPl, LoglO, Log2, LogN вычисляют логарифмы но различным основаниям. Если аргумент отрицательный, генерируется исключение ElnvalidOp. Функция Sqrt при извлечении корня из отрицательного числа возвращает значение NAN. Функции IntPower и Power: f u n c t i o n IritPower (Base : Extended; Exponent; I n t e g e r ) : Extended register; f u n c t i o n Power(Base, Exponent: Extended): Extended;
возводят Base в степень Exponent. При отрицательном значении Base степень Exponent в Power должна быть целой. В противном случае Power возвращает значение NAN. При переполнениях обе функции возвращают значение INF. Процедура Frexp объявлена следующим образом: procedure F r e x p ( X : Extended; var M a n t i s s a : Extended; var Exponent: I n t e g e r ) r e g i s t e r ;
Она разделяет значение X на мантиссу Mantissa и показатель степени Exponent. Функция Poly: function Poly(X: Extended; const C o e f f i c i e n t s :
a r r a y of D o u b l e ) :
Extended;
вычисляет полином от X с коэффициентами, расположенными в массиве Coefficients. Коэффициенты должны располагаться по нарастанию степени X: Coefficients[0] + Coef£icients[1] • X + . . . , + Coefficients[H] • (XH) . Об особенностях применения математических функций см. также гл. 13 разд. 13.7.3.
16.3 Тригонометрические и гиперболические функции Функция ArcCos(X)
Описание
Модуль
арккосинус
ArcCosh(X)
арккосинус гиперболический
Math Math
Глава 16
966
Функция
Описание
Модуль
ArcSin(X)
арксинус
ArcSinh(X)
арксинус гиперболический
Math Math
АгсТап(Х)
арктангенс
ArcTan2(Y, X)
арктангенс от Y / X
Math
ArcTanh(X)
арктангенс гиперболический
Math
Cos(X)
косинус
Cosh(X)
косинус гиперболический
Cotan(X)
котангенс
Hypot(X, Y)
вычисление гипотенузы по заданным катетам X и Y
Sin(X)
синус
System Math Math Math System Math Math Math Math
System
SinCos(X, S, C) синус и косинус Sinh(X)
синус гиперболический
Tan(X)
тангенс
Tanh(X)
тангенс гиперболический
Комментарии Во всех функциях тип аргумента и тип возвращаемого значения — Extended. Во всех тригонометрических функциях аргумент X — угол в радианах. Обратные тригонометрические функции возвращают главное значение угла в радианах. В функциях ArcSin и ArcCos аргумент должен лежать в пределах от -1 до 1. Функции ArcSin и ArcTan возвращают результат в пределах [-Pi/2 .. Pi/2], ArcCos - в пределах [0 .. Pi]. Функция Art-Tan2: function A r c T a n 2 ( Y , X : E x t e n d e d ! : Extended;
вычисляет арктангенс ArcTan(Y/X) и возвращает угол с учетом квадранта. Возвращаемые значения угла в радианах лежат в пределах [-Pi .. Pi]. Значения X и Y должны лежать в пределах [-264 .. 264]. Кроме того X не должен равняться нулю. Процедура SinCos: procedure S i n C o s ( T h e C a :
Extended; var Sin, Cos: Extended!; register;
вычисляет одновременно синус Sin и косинус Cos угла Theta. Эта функция работает вдвое быстрее, чем раздельное вычисление сначала синуса и затем косинуса.
16.4 Процедуры и функции преобразования дат и времени Date: TDateTime Возвращает текущую дату. DateTimeToStr(DateTime: TDateTime): string Преобразует дату и время DateTime в строку.
Процедуры и функции Object_Pascal_. Delphi и_АР1 Windows
967
DateTimeToString(var Result: string; const Format: string; DateTime: TDateTime) Преобразует DateTime с помощью строки форматирования Format в строку Result (описание строки форматирования для дат и времени см. во встроенной справке Delphi, в [1] и [4]). DateToStr(Date: TDateTime): string
Преобразует дату и время DateTime в строку, используя формат, заданный глобальной переменной^ Short Da teFormat. DayOfWeek(Date: TDateTime): Integer
Возвращает текущий день недели (1 — воскресенье, 7 — суббота). DecodeDate(Date: TDateTime; var Year, Month, Day: Word) Разбивает дату Date на год — Year, месяц — Month и день — Day. DecodeTime(Time: TDateTime; var Hour, Min, Sec, MSec: Word)
Разбивает время Time на час •— Hour, минуту — Min, секунду — Sec, миллисекунду — MSec. EncodeDate(Year, Month, Day: Word): TDateTime
Объединяет год Year, месяц Month и день Day в значение типа TDateTime. EncodeTime(Hour, Min, Sec, MSec: Word): TDateTime Объединяет час Hour, минуту Min, секунду Sec и миллисекунду MSec в значение типа TDateTime. FormatDateTime(const Format: string; DateTime: TDateTime): string Возвращает значение DateTime, преобразованное в строку с помощью строки форматирования Format (описание строки форматирования для дат и времени см, во встроенной справке Delphi, в [1] и [4]). Now: TDateTime Возвращает текущую дату и время. StrToDate(const S: string): TDateTime Преобразует строку в дату. Строка должна содержать 2 или три двузначных числа, разделенных символами, определенными в глобальной переменной DateSeparator (для русифицированных версий Windows это обычно точка ».»), в последовательности, определенной глобальной переменной ShortDateFormat (или месяц/день/год - по умолчанию, или день/месяц/год - обычно принято в русифицированных версиях Windows, или год/месяц/день). Если заданы только 2 числа, они воспринимаются как месяц и день текущего года. Двузначное число, обозначающее две последние цифры года, может лежать в пределах 00 - 99. При его преобразовании в год используется глобальная переменная TwoDigitYearCent игу Window. Если TwoDigitYearCentnryWindow = О, то число обозначает год текущего столетия. Например, в 1999 году числа 99 и 00 обозначают соответственно 1999 и 1900 годы, а в 2000 году они обозначают 2099 и 2000 годы. Если же задать TwoDigitYearCent игу Window > О, то заданное значение вычитается из текущего года, сдвигая точку отсчета. Тогда годы, превышающие эту новую точку отсчета, относятся к текущему столетию, а годы, предшествующие новой точке отсчета, переносятся в следующее столетне. Например, при TwoDigitYearCent ur у Window = 50 и в 1999 году, и в 2000 году (точки отсчета 1949 и 1950) числа 99 и 00 обозначают соответственно 1999 и 2000 годы. StrToDateTime(const S: string): TDateTime
Преобразует строку в формат даты и времени; по умолчанию формат строки (для русифицированных версий Windows): день.месяц.год час:минута:секунда. Для дополнительной информации см. StrToDate, встроенную справку Delphi или [1], [4]. _
Глава 16
968
StrToTime(const S: string): TDateTime Преобразует строку в формат времени. 0 формате см. StrToDateTime, встроенную справку Delphi или [1], [4], Time: TDateTime Возвращает текущее время. TimeToStr(Time: TDateTime): string Преобразует время в строку, используя формат, заданный глобальной переменной LongTime Format. Комментарии
Процедуры и функции этой категории используют тип TDateTime, представляющий собой число с плавающей запятой, целая часть которого содержит число дней, отсчитанное от некоторого начала календаря, а дробная часть равна части 24-часового дня, т.е. характеризует время и не относится к дате. Ра начало календаря принята дата 00 часов 30 декабря 1899 года. Реальная форма строкового представления дат и времени, а также используемые в ней разделители определяются системными переменными Windows, установленными на данном компьютере.
16.5 Функции обработки строк с нулевым символом в конце AnsiStrIComp(Strl, Str2:PChar): Integer
Сравнивает две строки Strl и Str2 без учета регистра. Возвращает значение < 0, если Strl < Str2, 0, если Strl = Str2, и > 0, если Strl > Str2. Применима к символам кириллицы. AnsiStrLIComp(Strl, Str2: PChar; MaxLen: Cardinal): Integer
Сравнивает до MaxLen символов двух строк Strl и Str2 без учета регистра. Возвращает значение < 0, если Strl < Str2, 0, если Strl = Str2, и > 0, если Strl > Str2. Применима к символам кириллицы. AnsiStrLower(Str: PChar): PChar Приводит символы строки Str к нижнему регистру и возвращает указатель на Str. Может работать с символами кириллицы. AnsiStrUpper(Str: PChar): PChar
Приводит символы строки Str к верхнему регистру и возвращает указатель на Str. Может работать с символами кириллицы. StrAUoc(Size: Cardinal): РСЬаг Размещает буфер символов заданного размера Size (число символов — Size — 1) в динамически распределяемой памяти. StrBufSizefStr; PChar): Cardinal
Возвращает размер буфера символов (включая нулевой в конце), размещенного функциями StrAlloc или StrNew. StrCat(Dcst, Source: PChar): PChar Склеивает две строки, добавляя строку Source в конец Dest. Возвращает указатель на результирующую строку. Проверка длины не проводится.
Процедуры и функции Object Pascal. Delphi и API Windows
969
StrComp(Strl, Str2 t PChar): Integer Сравнивает две строки Strl и Str2 с учетом регистра. Возвращает значение < 0, если Strl < Str2, 0, если Strl = Str2, и > 0, если Strl > Str2. StrCopy(Dest, Source: PChar): PChar
Копирует строку Source в Dest и возвращает Dest. StrDispose(Str: PChar)
Освобождает память от буфера символов Str, динамически размещенного процедурами StrAlloc или StrNew. Процедура сохраняется только для обратной совместимости. StrECopy(Dest, Source: PChar): PChar
Копирует строку Source в Dest и возвращает указатель на конец строки Dest, т.е. на нулевой символ в ее конце. StrEnd(Str: PChar): PChar
Возвращает указатель на конец строки Str, т.е. на нулевой символ в ее конце. StrFmt(Buffer, Format: PChar; const Args: array of const): PChar
Форматирует одно или более значений, заданных параметром Args, с помощью строки форматирования Format (см. разд. 16.1.3) в строку Buffer и возвращает указатель на Buffer, Превышение размера буфера Buffer не контролируется. StrIComp(Strl, Str2:PChar): Integer
Сравнивает две строки Strl и Str2 без учета регистра. Возвращает значение < О, если Strl < Str2, 0, если Strl = Str2, и > 0, если Strl > Str2. He применима к символам кириллицы в отношении игнорирования регистра (см. AnsiStrlComp). StringToWideChar(const Source: string; Dest: PWideChar; DestSize: Integer): PWideChar
Преобразует до DestSize — 1 символов строки ANSI Source в строку Unicode Dest размером DestSize с нулевым конечным символом. Возвращает указатель на Dest. StrLCat(Dest, Source: PChar; MaxLen: Cardinal): PChar
Склеивает две строки, добавляя не более MaxLen — StrLen(Dest) символов из Source в конец Dest и возвращая Dest. Для определения значения MaxLen можно использовать функцию SizeOf. StrLComp(Strl, Stt2: PChar; MaxLen: Cardinal): Integer
Сравнивает до MaxLen символов двух строк Strl и Str2 с учетом регистра. Возвращает значение < 0, если Strl < Str2, 0, если Strl = Str2, и > 0, если Strl > Str2. StrLCopy(Dest, Source: PChar; MaxLen: Cardinal): PChar
Копирует до MaxLen символов из Source в Dest и возвращает Dest. Для определения значения MaxLen можно использовать функцию SizeOf. StrLen(Str: PChar): Cardinal
Возвращает число символов в строке Str, не учитывая конечного нулевого символа. StrLFmt(Buffer: PChar; MaxLen: Cardinal; Format: PChar; const Args: array of const): PChar Форматирует одно или более значений, заданных параметром Args, с помощью строки форматирования Format (см. разд. 16.1.3) в строку Buffer и возвращает указатель на Buffer. Контролируется длина результирующей строки: не более MaxLen.
970
Глава 16
StrLIComp(Strl, Str2: PChar; MaxLen: Cardinal): Integer Сравнивает до MaxLen символов двух строк Strl и Str2 без учета регистра. Возвращает значение < 0, если Strl < Str2, 0, если Strl = Str2, и > 0, если Strl > Str2. He применима к символам кириллицы в отношении игнорирования регистра (см. AnsiStrLIComp). StrLower(Str: PChar): PChar
Приводит символы строки Str к нижнему регистру и возвращает указатель на Str. На символы кириллицы не действует (см. AnsiStrLower). StrMove(Pest, Source; PChar; Count: Cardinal): PChar Копирует ровно Count символов из Source в Dest и возвращает Dest. Dest и Source могут перекрывать друг друга в памяти. ^^^ StrNewfStr: PChar): PChar Выделяет место для строки Str в динамически распределяемой области памяти, копирует символы строки и возвращает указатель на новую строку. StrPCopy(Dest: PChar; const Source: string): PChar Копирует строку Source в стиле Pascal в строку Dest с нулевым символом в конце и возвращает Dest. Размер Dest должен по крайней мере на 1 превышать длину строки Source. StrPLCopy(Dest: PChar; const Source: string; MaxLen: Cardinal); PChar
Копирует до MaxLen символов строки Source в стиле Pascal в строку Dest с нулевым символом в конце и возвращает Dest. StrPos 0, если SI > S2. Применима к русским текстам. AnsiCompareText(const SI, S2: string): Integer
Сравнивает две строки ANSI SI и 82 без учета регистра. Возвращает значение < 0, если SI < S2, 0, если SI = S2, и > 0, если SI > S2. AnsiLowerCase(const S: string): string Возвращает строку ANSI S, преобразованную к нижнему регистру. Применима к русским текстам. AnsiUpperCasefconst S: string): string Возвращает строку S, преобразованную к верхнему регистру. Применима к русским текстам. CompareStr(const SI, S2: string): Integer
Сравнивает две строки SI и S2 с учетом регистра. Возвращает значение < О, если SI < S2, 0, если SI = S2, и > 0, если SI > S2. Не применима к русским текстам. CompareTextfconst SI, S2: string): Integer
Сравнивает две строки SI и S2 без учета регистра. Возвращает значение < О, если SI < S2, 0, если SI = S2, и > 0, если SI > S2. С он ua l( si [, s2,.... sn]: string): string Возвращает строку, склеенную из строк si, .... sn. Идентична операции «+» для строк. Copy(S: string; Index, Count: Integer): string
Возвращает подстроку строки S, начинающуюся с S[Index] и содержащую до Count символов. CurrToStr(Value: Currency): string Преобразует монетарное значение Value в строку. Dcletc(var S: string; Index, Count: Integer)
Удаляет из S подстроку, начинающуюся с S[Index] и содержащую до Count символов. Dispose Str(P: PString)
Удаляет строку, ранее динамически размещенную процедурой NewStr,
972
__
Глава 16
FloatToStr(Value: Extended): string Преобразует Value в строку с точностью 15 цифр (см. FloatToStrF). FIoatToStrF(Value: Extended; Format: TFloatFormat; Precision, Digits: Integer): string
Преобразует Value в строку, используя формат Format с точностью Precision и числом цифр Digits. Возможные значения Format: ffGeneral — формат g, ffExponent — формат e, ffFixed — формат f, ffNumber - формат n, ffCurrency формат m (обозначения форматов соответствуют приведенным в разд. 16Л..З). FormatFloat(const Format: string; Value: Extended): string
Преобразует Value в строку, используя строку форматирования Format (см. разд. 16.1.3). InsertfSource: string; var S: string; Index: Integer)
Вставляет строку Source в S, начиная с Sfindex]. IntToHex(Value: Integer; Digits: Integer): string
Возвращает строку, содержащую шестнадцатеричное представление Value с числом возвращаемых цифр Digits. IntToStr(Value: Integer): string Возвращает строку, содержащую преобразованное целое значение Value. Is Validldent( const I dent: string): Boolean
Проверяет, является ли Ident допустимым идентификатором Object Pascal. Length(S: string): Integer
Возвращает число символов в S. LowerCasc{const S: string): string
Возвращает строку S, преобразованную к нижнему регистру. Не применима для русских текстов (см. AnsiLowerCase). NewStr(const S: string): PString Динамически размещает строку S, которая может быть в дальнейшем удалена процедурой DisposeStr. Сохраняется только для обратной совместимости. Pos(Substr: string; S: string): Integer
Возвращает позицию (индекс) первого вхождения Substr в S; Если Substr нет в S, возвращается 0. Str(X [: Width £: Decimals ]]; var S)
Преобразует целое или действительное значение X в строку S. Не обязательные параметры: Width — ширина поля. Decimals — число цифр. StrToCurr(const S: string): Currency
Преобразует строку S в монетарное число. StrToFloat integer(List.Items[i]")) then List.Exchange [0, i);
FillRect Заполняет указанный прямоугольник канвы, используя текущее значение Brush Класс TCanvas Прототип procedure FillRect(const Rect: TRectl;
Описание Метод FillRect заполняет прямоугольник канвы, указанный параметром Rect, используя текущее значение Brush. Заполняемая область включает верхнюю и левую стороны прямоугольника, но не включает правую и нижнюю стороны. При использовании FillRect параметр Rect часто задается функцией Rect. Пример Оператор with Image 1.Canvas do FillRect (Rect [0, 0, Width, Height)) ,очищает всю канву компонента Image 1, заполняя ее фоном, если он установлен в свойстве Brush.
FindNextControl Возвращает следующий в последовательности табуляции оконный дочерний компонент Класс TWlnControl Определение f u n c t i o n FindNextControl[CurControl: TWinControl; GoForward, CheckTabStop, CheckParent: Boolean) : TWinControl; Описание Метод FindNextControl находит н возвращает следующий за указанным в параметре CurControl дочерний оконный компонент в соответствии с последовательностью табуляции. Если CurControl не является дочерним компонентом данного оконного элемента, то возвращается компонент, первый в последовательности табуляции. То же самое происходит, если CurControl является последним компонентом в последовательности табуляции. Параметр GoForward определяет направление поиска. Если он равен true, то поиск проводится вперед и возвращается компонент, следующий за CurControl. Если же параметр GoForward равен false, то возвращается предшествующий компонент.
Свойства, методы, события, типы, классы
1071
Параметры CheckTabStop и CheckParent определяют условия поиска. Если ChecbTabStop равен true, то просматриваются только компоненты, в которых свойство TabStop установлено в true. При CheckTabStop равном false значение Tab Stop не принимается во внимание. Если параметр CheckParent равен true, то просматриваются только компоненты, в свойстве Parent которых указан данный оконный элемент, т.е. просматриваются только прямые потомки. Если CheckParent равен false, то просматриваются все, даже косвенные потомки данного элемента. Метод Find Next Control вызывает метод GetTab Order List и из полученного таким способом списка черпает последовательность компонентов. Примеры var obj:TWinControl; obj := Forml.FindNextControl(obj,true, true, true);
В этом примере переменная obj поочередно принимает значение всех прямых наследников формы Forml, включенных в последовательность табуляции, т.е. имеющих свойство TabStop равным true. Например, в эту последовательность войдут окна редактирования, кнопки, панели, расположенные непосредственно на форме и имеющие TabStop равным true, но не войдут кнопки и окна редактирования, расположенные на панелях. Если в приведенном операторе изменить параметр CheckParent на false: obj :- Forml.FindNextControl(obj,true, true, false I;
то в последовательность войдут и непрямые наследники, имеющие TahStop равным true, s частности, компоненты, содержащиеся в панелях, расположенных на форме, причем независимо от значения TabStop этих панелей. Если в приведенном операторе изменить параметр CheckTabStop на false: obj :- Forml.FindNextControl(obj,true, f a l s e , f a l s e ) ; то в последовательность войдут компоненты, независимо от значения их свойства TabStop. FloodFill Закрашивает текущей кистью замкнутую область канвы, определенную указанным цветом Класс ТСапиав Прототип type TFillStyle - (fsSurface, fsBorder); procedure FloodFill[X, Y: Integer; Color; TColor; FillStyle: TFillStyle);
Описание
Метод FloodFill закрашивает текущей кистью Brush замкнутую область канвы, определенную цветом и начальной точкой закрашивания (X, Y). Точка с координатами X и Y является произвольной внутренней точкой заполняемой области, которая может иметь произвольную форму. Граница этой области определяется сочетанием параметров Color и FillStyle. Параметр Color типа TCplor указывает цвет, который используется при определении границы закрашиваемой области, а параметр FillStyle определяет, как именно по этому цвету определяется граница. Если FillStyle - fsSurface, то заполняется область, окрашенная цветом Color, а на других цветах метод останавливается. Если FillStyle = fsBorder, то наоборот, заполняется область окрашенная любыми цветами, не равными Color, а на цвете Color метод останавливается.
_
1072
—
_
Глава 17
Примеры 1.
with Imagel.Canvas do begin
Brush.Color:-clWhite; FloodFill(X,Y,Pixels[X,Y],fsSurface) ; end; Приведенные операторы закрашивают белым цветом на канве компонента Imagel все пикселы, прилегающие к пикселу с координатами (X, Y) и имеющие тот же цвет, что и этот пиксел. 2. with Imagel.Canvas do begin Brush.Color:=ciwhite; F l o o d F i l l ( X , Y , c l B l a c k , fsBorder); end; Приведенные операторы закрашивают белым цветом на канве компонента Imagel все пикселы, прилегающие к пикселу с координатами (X, Y) и имеющие цвет, отличный от черного. При достижении черной границы области закраска останавливается. FrameRect Рисует на канве текущей кистью прямоугольную рамку Класс TCanuas Прототип procedure FrameRect(const Rect: TRect); Описание Метод FrameRect рисует на канве прямоугольную рамку вокруг области Rect, используя установку текущей кисти Brush. Толщина рамки — 1 пиксел. Область внутри рамки кистью не заполняется. Отличается от метода Rectangle тем, что рамка рисуется цветом кисти (в методе Rectangle — цветом пера Реп) и область не закрашивается (в методе Rectangle закрашивается). Пример Оператор with Imagel.Canvas do begin Brush.Color :- clBlack; FrameRect(Rect(10,10,100,100)); end;
рисует на канве компонента Imagel черную рамку. Free Вызывает деструктор объекта и освобождает память Определение procedure Free;
Описание Процедура Free применяется для освобождения памяти, динамически выделенной под объект, который уже не нужен для дальнейшей работы программы. Процедуру Free полезно использовать для освобождения памяти в разделах finally блоков try..finally. Это гарантирует освобождение памяти даже в случае генерации исключения. Процедура Free проверяет, не была ли ранее уже освобождена выделенная под объект память и вообще был ли данный объект создан (не равен ли указатель на объект nil). После этого вызывается метод Destroy — деструктор данного объекта.
Свойства, jjeroflbi. события, типы^классы
1073
Для осуществления такой проверки лучше всегда вызывать Free, а не сам деструктор Destroy. К тому же вызов метода Free порождает более компактный код. Пример
MyQbject.Free; Hide Делает компонент невидимым Класс TControl Определение procedure Hide;
.
Описание Метод Hide делает компонент невидимым, задавая значение False его свойству Visible. Если компонент является контейнером для других компонентов, то эти дочерние компоненты также делаются невидимыми. Хотя компонент становится невидимым, его свойства и методы остаются доступными. IndexOf Определение первого вхождения в список заданного элемента Классы TList, TStringList, TStrings „ Прототипы
Для TList: function IndexOf(Item:
Pointer):
Integer;
Для TStrings: function IndexOf(const S: s t r i n g ) : Integer; v i r t u a l ; для TStringList: f u n c t i o n IndexOf (const S: s t r i n g ) ; Integer,- override;
Описание Вызов IndexOf возвращает индекс первого вхождения в массив списка заданного элемента (указателя Item для TLjat или строки S для TStrinffLiat и TStrings). Индексация начинается с 0 (0 — первый элемент массива). Если заданного элемента в списке нет, возвращается —1. Пример В приведенном ниже примере определяется, есть ли в списке сотрудник, фамилия которого задана пользователем в окне Editl. var LPerson: TStringList; LPerson:= TStringList.Create; if (LPergon.IndexOf(Editl.Text) Х2 — XI, то верхняя и нижняя границы рамки окажутся целиком скругленными (без прямолинейной части). Если Y3 > Y2 — Y1, то же самое произойдет с левой и правой границами рамки. Если же оба измерения эллипса не меньше размеров рамки, то будет рисоваться просто эллипс. Но, конечно, для рисования эллипса лучше использовать метод Ellipse. Если один из размеров эллипса задать нулевым, то будет рисоваться прямоугольная рамка. Но, конечно, для такой рамки лучше использовать метод Rectangle. Пример Следующие операторы вызывают изображение, показанное на рис. 17.3: with Imagel-Canvas do begin RoundRect(10,10,110,210, 50,100); RoundRect(160,10,260,210,100,100); RoundRect(310,10,410,210,50,200); RoundRect(460,10,560,210,100,200) ; end;
Рис 17.3 Примеры метода RoundRect
SaveToClipboardFormat Создает копню изображения в формате Clipboard Класс
TGraphic
Свойства, методы, события, типы, классы
108Л
Прототип procedure SaveToClipboardFormat(var AFormat: Word; var AData: THandle; v a r A F a l e t t e : HPALETTE];
Описание Метод SaveToClipboardFormat создает копию изображения в формате Clipboard. Формат, указатель на данные и палитру возвращаются как параметры AFormat, AData и APalette. Стандартно зарегистрированные форматы: CF_BITМАР для битовых карт и CF_METAFILEPICT для метафайлов. Формат для нового типа графического объекта предварительно должен быть зарегистрирован методом RegisterClipboardFormat. После применения метода SaveToClipboardFormat надо передать объекту ClipBoard полученные значения AFormat и AData методом Set AsHandle. При этом надо не забыть включить в оператор uses приложения ссылку на модуль Clipbrd. Впрочем, записать изображение в Clipboard можно и проще, воспользовавшись методом Assign объекта Clipboard для объектов типов TGraphic. TBitMap. TIcon. IMetafile. Примеры var MyFormat; Word; AData : THandle.APalette: HPALETTE,-
procedure TForml.ButtonlClick(Sender: TObject); begin Imagel.Picture.Bitmap.SaveToClipBoardFormat(MyFormat,AData, APalette); ClipBoard. SetAsHandle (MyFormat, AData) ,end;
Приведенные операторы записывают в буфер обмена изображение, хранящееся в свойстве Picture.Bitmap компонента Imagel, вместе с палитрой. Впрочем, записать изображение в буфер обмена можно и оператором: ClipBoard.As sign(Image2.Picture.Bitmap);
или ClipBoard.из sign(Image2.Picture.Graphic);
SaveToFile Сохраняет графическое изображение в файле Класс TGraphic Прототип procedure SaveToFile(const FileName: s t r i n g ) ;
Описание Метод SaveToFile сохраняет изображение графического объекта в файле FileName. ScaleBy Масштабирует оконный элемент и все содержащиеся в нем компоненты Класс TWlnControl
1082
Глава 17
Определение procedure ScaleBy(М, D: Integer);
Описание Метод ScaleBy масштабирует оконный элемент и все содержащиеся в нем компоненты. Масштабируются такие свойства компонента, как Width и Height, определяющие его размер. Свойства То_В и Left остаются неизменными. Масштабируется также размер шрифта, если только в компоненте не установлено Pa rent Font = true. В последнем случае шрифт наследуется от родительского компонента и поэтому не изменяется. Если компонент является контейнером, содержащим другие компоненты, то эти дочерние компоненты также масштабируются. Причем у них изменяются не только Width и Height, но также пропорционально изменяются Тор и Left, определяющие их местоположение. Если во всех дочерних компонентах установлено ParentFont = true, а в компоненте-контейнере ParentFont = false, то пропорционально изменяются и шрифты всех компонентов (но, конечно, не непрерывно, а скачками, доступными тому или иному типу шрифта). Параметры М and D определяют соответственно множитель и делитель масштаба. Например, чтобы уменьшить размеры на 10% начального значения, можно задать М равным 9, a D равным 10 (9/10). Если же вы хотите увеличить размер на 1/3, то можно задать М-133 и D=100 (133/100) или М=4 и D=3 (4/3). Подробнее метод рассмотрен в гл. 5 разд. 5.2.5. Примеры 1.
Оператор Editl.ScaleBy(11,10);
2.
масштабирует окно редактирования Editl. В любом случае при выполнении этого оператора увеличивается на 10% длина окна (свойство Width), что обеспечивает возможность наблюдать и редактировать в нем более длинный текст. Высота окна (свойство Height) будет изменяться пропорционально, если свойство компонента AutoSize равно false. В противном случае высота определяется только размером шрифта и при постоянном шрифте будет неизменной. А размер шрифта будет меняться, только если свойство компонента ParentFont равно false, т.к. иначе шрифт определяется родительским компонентом. Приведенный ниже обработчик события OnKgyUp окна редактирования Editl дает пользователю возможность менять длину окна. При нажатии комбинаций клавиш Alt-U и Alt-D пользователь увеличивает или уменьшает длину окна. procedure T F o r m l - E d i t l K e y U p ( s e n d e r ; TObject; var Key: Shift: TShiftState); begin 1 if (Key = o r d l ' U ) ) and [ssAlt in S h i f t ) then
Word;
Editl.ScaleBy (11,10) //Editl.width .-- 11 *Edi tl. Width div 10 e l s e if (Key = o r d ( ' D ' ) ) and (ssAlt in S h i f t ) then Editl.ScaleBy(10,11) end; Когда Editl находится в фокусе, при нажатии пользователем клавиши Alt и клавиши U (в любом регистре и независимо от переключения на латинский или русский язык) длина окна редактирования увеличится на 10%, а при нажатии Alt и D соответственно уменьшится.
ScaleControls Масштабирует дочерние компоненты оконного элемента, не изменяя масштаба самого элемента
Свойства, методы, события, типы, классы
1083
Класс TWinControl Определение procedure ScaleControls(м, D: Integer);
Описание Метод ScaleControls масштабирует все компоненты, содержащиеся в оконном элементе, не изменяя масштаба самого элемента. Метод ScaleControls вызывает метод ChangeScale для каждого дочернего компонента. Отличается от метода Scale By только тем, что не изменяет масштаба самого элемента. Параметры М and D определяют соответственно множитель и делитель масштаба. Например, чтобы уменьшить размеры на 10% начального значения, можно задать М равным 9, a D равным 10 (9/10). Если же вы хотите увеличить размер на 1/3, то можно задать М=Ш и D=100 (133/100) или М=4 и D=3 (4/3). Подробности см. в разд. ScaleBy.
ScreenToClient Преобразует координаты экрана в координаты клиентской области компонента Класс TControl On редел ение TPoint = record X: Longint; Y: Longint; end; function ScreenToClient (const Point: TPoint): TPoint;
Описание Метод ScreenToClient преобразует координаты точки в системе координат экрана (начало координат — левый верхний угол экрана) в систему координат клиентской области компонента (начало координат — левый верхний угол клиентской области). Совместно с обратной функцией ClientToScreen метод может использоваться для пересчета координат точки экрана из системы координат клиентской области одного компонента в систему координат клиентской области другого компонента. Пример Р := C o m p 2 . S c r e e n T o C l i e n t [ C o m p l . C l i e n t T o S c r e e n ( Р ] ) ;
Оператор пересчитывает координату точки Р из системы координат компонента Compl в систему координат компонента Сотр2. SelectNext Передает фокус дочернему компоненту, следующему в последовательности табуляции за указанным Класс TWinControl Определение procedure S e l e c t N e x t ( C u r C o n t r o l : T W i n C o n t r o l ; GoForward, CheckTabStop: Boolean) ;
Описание Метод SelectNext передает фокус дочернему компоненту, следующему в последовательности табуляции за тем, который указан параметром CurControl. Параметр GoForward определяет направление поиска: при значении true поиск ведется вперед, при значении false — назад. -
1084
Глава 17
Параметр CheckTabStop указывает, должен ли искомый компонент иметь свойство TabS top, равным true. Если значение CheckTabStop равно true, очередной компонент должен иметь значение TabSton. равное true, или поиск прекращается. Если метод SelectNext не смог найти компонент в соответствии с заданными значениями GoPorward и CheckTabStop, то фокус остается на компоненте CurControl.
SendToBack Переносит компонент ниже других компонентов в Z-последовательности Класс TControl Прототип procedure SendToBack; Описание Метод SendToBack позволяет изменять последовательность перекрытия компонентов на форме и тем самым управлять видимостью компонентов. Перекрывающиеся компоненты на форме размещаются поверх друг друга в последовательности (называемой Z-последовательностью), соответствующей порядку размещения компонентов в процессе проектирования. Например, если вы поместили в одно и то же место формы две кнопки одинаковых размеров, то видна будет только вторая из размещенных кнопок, поскольку она расположена в Z-последовательности выше. Применение во время выполнения приложения метода SendToBack к верхней кнопке переместит ее вниз в Z-последовательности и пользователю станет видна нижняя кнопка. Если переносимый вниз компонент имел фокус, то он его потеряет при переносе. Это справедливо по отношению к неоконным объектам, таким, как кнопки, метки, изображения и т.д., а также и к оконным компонентам, таким, как Memo, ComboBox и др. Но все неоконные компоенты всегда расположены в Z-последовательности ниже оконных и метод SendToBack не может изменить это правило. Например, попытка перенести вниз методом SendToBack оконный компонент, под которым размещена метка, ни к чему не приведет. Примеры В разделе, посвященном методу BringToFront, также изменяющему последовательность компонентов, приведен ряд примеров. Во всех них вместо метода BringToFront. применяемому к нижнему компоненту, можно применять метод SendToBack, но к верхнему компоненту.
Set Bounds Устанавливает одновременно свойства Left. Тор_, Width и Height Класс TControl Определение procedure SetBounds(ALeft, ATop, AWidth, AHeight: Integer); virtual;
Описание Метод SetBounds изменяет одновременно все свойства компонента, определяющие его границу. Тот же эффект может быть достигнут совокупностью операторов изменения Lfift, Top. Width и Height. To, что метод SetBounds одновременно задает эти значения, не только позволяет получить более компактный код, но и дает возможность избежать перерисовки компонента после изменения каждого параметра в отдельности.
Свойства, методы, события, типы, классы
1Q85
Значения Left, Top. Width и Height задаются при вызове SetBounds как соответственно параметры ALeft, ATop, AWidth и AHeight. Примеры, в которых требуется изменять сразу все эти свойства, см. в разд. Visible и BringToFrpnt. SetFocus Передает фокус элементу Класс TWinControl Опред е л е н и е
.
•
procedure SetFocus; virtual;
Описание Метод SetFocus передает фокус.данному компоненту, активизирует его. Пример Оператор Editl.SetFocus; передает фокус компоненту Editl.
Show Делает видимым невидимый компонент Класс TControl Определение procedure
Show;
Описание Метод Show делает видимым ранее невидимый компонент. Он задает значение true свойству Visible и проверяет, является ли видимым родительский компонент. Примеры использования видимых и невидимых компонентов см. в разд. Visible.
StretchDraw Рисует графическое изображение в указанную прямоугольную область канвы, подгоняя размер изображения под заданную область Класс TCanvas
Прототип
procedure StretchDraw(const Rect: TPect; Graphic: TGraphic); Описание Метод StretchDraw рисует на канве изображение, содержащееся в объекте, указанном параметром Graphic, в прямоугольную область, указанную параметром Rect. При этом размер изображения подгоняется под размер заданной области. Этим метод StretchDraw отличается от метода Draw, который оставляет размер неизменным. Объект Graphic может быть типа битовой матрицы, пиктограммы или метафайла. Если объект — битовая матрица типа TBitMao. то при переносе изображения учитывается режим копирования, установленный свойством канвы CopyMode.
1086
Глава 17
Пример Оператор Imagel.Canvas.StretehDraw[Rect(10,10,110,110),Bitmapl); рисует на канве компонента Imagel изображение из компонента Bitmapl в область с координатами углов (10, 10} и (110, 110). При зтом размер изображения подгоняется под заданный размер области — квадрат со стороной 100.
TextExtent Возвращает длину и высоту в пикселах текста, который предполагается написать на канве текущим шрифтом Класс TCanvas _ Прототип type TSize = record ex: Longint; су: Longint; end; function TextExtent(const Text: string): TSize; Описание Функция TextExtent возвращает структуру типа TSize, содержащую длину и высоту в пикселах текста Text, который предполагается написать на канве (см. Canvas) текущим шрифтом. Это позволяет перед выводом текста на канву определить размер надписи и расположить ее и другие элементы изображения наилучшим образом. Только высоту или только длину текста можно определять соответственно методами TextHeight и TextWidth. Пример var st:string; st:-Edit1.Text; with irnagel .Canvas do TextOut