МИНИСТЕРСТВО ОБРАЗОВАНИЯ РОССИЙСКОЙ ФЕДЕРАЦИИ Государственное образовательное учреждение высшего профессионального образования "Ижевский государственный технический университет" УТВЕРЖДАЮ Ректор ______________ И.В. Абрамов "_____"________________ 200__г.
УДК 519.682.1
ПРОГРАММИРОВАНИЕ В DELPHI Методические указания для проведения практических и лабораторных занятий по дисциплине "Программирование на языке высокого уровня" для студентов специальностей 220200 Автоматизированные системы обработки информации и управления, 220300 Системы автоматизированного проектирования, направления 552800 Информатика и вычислительная техника Форма обучения очная и заочная
Ижевск 2004
Кафедра "Автоматизированные системы обработки информации и управления". Составители: Барков И.А., к.т.н., доцент, Шутов Е.А., ст. преподаватель. Методические указания составлены на основании государственного образовательного стандарта высшего профессионального образования и утверждены на заседании кафедры Протокол от "____" ________________ 200__ г. № ______.
Заведующий кафедрой
____________________ В.Н. Кучуганов "____" ________________ 200__ г.
СОГЛАСОВАНО: Председатель учебно-методической комиссии по специальности ____________________ В.Н. Кучуганов "____" ________________ 200__ г.
Методические указания предназначены для проведения практических и лабораторных занятий со студентами специальностей 220300 Системы автоматизированного проектирования, 220200 Автоматизированные системы обработки информации и управления, направления 552800 Информатика и вычислительная техника.
Начальник учебно-инженерного отдела
____________________ А.М. Ефимова "____" ________________ 200__ г. 2
СОДЕРЖАНИЕ 1. СВЕДЕНИЯ О СРЕДЕ РАЗРАБОТКЕ DELPHI……………………….. 2. ОБЩИЕ СВЕДЕНИЯ О КОМПОНЕНТАХ…………………………… 2.1. Списки…………………………………………………………………… 2.2. Элементы управления…………………………………………………... 2.3. Положение, размеры и выравнивание элементов управления………. 2.4. Активность и видимость элементов управления…………………….. 2.5. Оконные элементы управления……………………………………….. 2.6. Реакция на события от мыши и клавиатуры………………………….. 2.7. Фокус ввода……………………………………………………………... 2.8. Графическая подсистема……………………………………………….. 3. РАБОТА С КОМПОНЕНТАМИ………………………………………… 3.1. Работа с меню…………………………………………………………… 3.2. Работа с кнопками………………………………………………………. 3.3. Ввод и редактирование текста…………………………………………. 3.4. Ввод и выбор значений…………………………………………………. 3.5. Группирование компонентов…………………………………………… 3.6. Компоненты – стандартные диалоговые окна Windows……………… 3.7. Компоненты по работе с файловой системой…………………………. 3.8. Вспомогательные компоненты…………………………………………. 3.9. Форма и ее свойства…………………………………………………….. 3.10. Управление дочерними компонентами………………………………. 3.11. Приложение и среда его выполнения. Объект TApplication……… 3.12. Системные объекты TClipboard и TScreen…………………………. 3.13. Файлы инициализации………………………………………………. 3.14. Печать данных из приложения……………………………………… 4. СПРАВОЧНИК ПО ФУНКЦИЯМ DELPHI…………………………… 4.1. Функции работы со строками…………………………………………. 4.2. Функции работы с файлами……………………………………………. 4.3. Функции форматирования строк………………………………………. 4.4. Функции преобразования чисел с плавающей точкой……………….. 4.5. Функции работы с датами и временем…………………………………
3
4 9 9 13 15 17 18 20 21 23 26 26 32 34 36 44 45 48 51 52 57 60 64 66 67 68 69 71 74 77 80
1. СВЕДЕНИЯ О СРЕДЕ РАЗРАБОТКЕ DELPHI Delphi - это комбинация нескольких важнейших технологий: •
Высокопроизводительный компилятор в машинный код
•
Объектно-ориентированная модель компонент
•
Визуальное (а, следовательно, и скоростное) построение приложений из программных прототипов
•
Масштабируемые средства для построения баз данных
Объектно-ориентированная модель программных компонент Основной упор этой модели в Delphi делается на максимальном реиспользовании кода. Это позволяет разработчикам строить приложения весьма быстро из заранее подготовленных объектов, а также дает им возможность создавать свои собственные объекты для среды Delphi. Никаких ограничений по типам объектов, которые могут создавать разработчики, не существует. Действительно, все в Delphi написано на нем же, поэтому разработчики имеют доступ к тем же объектам и инструментам, которые использовались для создания среды разработки. В результате нет никакой разницы между объектами, поставляемыми Borland или третьими фирмами, и объектами, которые вы можете создать. В стандартную поставку Delphi входят основные объекты, которые образуют удачно подобранную иерархию из 270 базовых классов. Для начала - неплохо. Но если возникнет необходимость в решении какой-то специфической проблемы на Delphi, советуем, прежде чем попытаться начинать решать проблему “с нуля”, просмотреть список свободно распространяемых или коммерческих компонент, разработанных третьими фирмами, количество этих фирм в настоящее время превышает число 250, хотя, возможно, я не обо всех знаю. Скептики, возможно, не поверят мне, когда я скажу, что на Delphi можно одинаково хорошо писать как приложения к корпоративным базам данных, так и, к примеру, игровые программы. Тем не менее, это так. Во многом это объясняется тем, что традиционно в среде Windows было достаточно сложно реализовывать пользовательский интерфейс. Событийная модель в Windows всегда была сложна для понимания и отладки. Но именно разработка интерфейса в Delphi является самой простой задачей для программиста.
Быстрая разработка работающего приложения из прототипов Игровая программа Rendzu была собрана моим коллегой из готовых кусков за рабочий день, причем большая часть времени была посвящена прихорашиванию и приукрашиванию. Screen Saver в виде прыгающих часиков был также изготовлен на Delphi за весьма незначительное время. Теперь эти часики украшают почти каждую IBMсовместимую машину в нашем Демо-центре клиент-серверных технологий. Конечно, на разработку
4
серьезной информационно-поисковой системы в архитектуре клиент-сервер может уйти гораздо большее время, чем на разработку программы-игрушки. Тем не менее многие наши коллеги, до Delphi программировавшие на других языках, утверждают, что на Delphi скорость изготовления сложного проекта выше раз в 10. Cреда Delphi включает в себя полный набор визуальных инструментов для скоростной разработки приложений (RAD - rapid application development), поддерживающей разработку пользовательского интерфейса и подключение к корпоративным базам данных. VCL - библиотека визуальных компонент, включает в себя стандартные объекты построения пользовательского интерфейса, объекты управления данными, графические объекты, объекты мультимедиа, диалоги и объекты управления файлами, управление DDE и OLE. Единственное, что можно поставить в вину Delphi, это то, что готовых компонент, поставляемых Borland, могло бы быть и больше. Однако, разработки других фирм, а также свободно распространяемые программистами freeware-компоненты уже восполнили этот недостаток. Постойте, - скажете вы, ведь это уже было. Да, это было в Visual Basic. Соответствующий стандарт компонент назывался VBX. И этот стандарт так же поддерживается в Delphi. Однако, визуальные компоненты в Delphi обладают большей гибкостью. Вспомним, в чем была проблема в VB. Прикладной программист программировал, вообще говоря, в среде языка бэйсик. А компоненты в стандарте VBX готовили ему его коллеги-профессионалы на С++. VBX’ы приходили, “как есть”, и ни исправить, ни добавить ничего было нельзя. А для изготовления VBX надо было осваивать “кухню” языка C++. В Delphi визуальные компоненты пишутся на объектном паскале, на том же паскале, на котором пишется алгоритмическая часть приложения. И визуальные компоненты Delphi получаются открытыми для надстройки и переписывания. Чувствуете разницу? Delphi: настраиваемая cреда разработчика
После запуска Delphi в верхнем окне горизонтально располагаются иконки палитры компонент. Если курсор задерживается на одной из иконок, под ней в желтом прямоугольнике появляется подсказка 5
Из этой палитры компонент вы можете выбирать компоненты, из которых можно строить приложения. Компоненты включают в себя как визуальные, так и логические компоненты. Такие вещи, как кнопки, поля редактирования - это визуальные компоненты; а таблицы, отчеты - это логические. Понятно, что поскольку в Delphi вы визуальным образом строите свою программу, все эти компоненты имеют свое графическое представление в поле форм для того, чтобы можно было бы ими соответствующим образом оперировать. Но для работающей программы видимыми остаются только визуальные компоненты. Компоненты сгруппированы на страницах палитры по своим функциям. К примеру, компоненты, представляющие Windows “common dialogs” все размещены на странице палитры с названием “Dialogs”. Delphi позволяет разработчикам настроить среду для максимального удобства. Вы можете легко изменить палитру компонент, инструментальную линейку, а также настраивать выделение синтаксиса цветом. Заметим, что в Delphi вы можете определить свою группу компонент и разместить ее на странице палитры, а если возникнет необходимость, перегруппировать компоненты или удалить неиспользуемые. Интеллектуальный редактор Редактирование программ можно осуществлять, используя запись и исполнение макросов, работу с текстовыми блоками, настраиваемые комбинации клавиш и цветовое выделение строк .
Графический отладчик Delphi обладает мощнейшим, встроенным в редактор графическим отладчиком, позволяющим находить и устранять ошибки в коде. Вы можете установить точки останова, проверить и изменить переменные, при помощи пошагового выполнения в точности понять поведение программы. Если же требуются возможности более тонкой отладки, Вы можете использовать отдельно доступный Turbo Debugger, проверив ассемблерные инструкции и регистры процессора. Инспектор объектов Этот инст6
румент представляет из себя отдельное окно, где вы можете в период проектирования программы устанавливать значения свойств и событий объектов (Properties & Events).
Менеджер проектов. Дает возможность разработчику просмотреть все модули в соответствующем проекте и снабжает удобным механизмом для управления проектами. Менеджер проектов показывает имена файлов, время/дату выбранных форм и пр. Можно немедленно попась в текст или форму, просто щелкнув мышкой на соответствующее имя.
Навигатор объектов
Показывает библиотеку доступных объектов и осуществляет навигацию по вашему приложению. Можно посмотреть иерархию объектов, прекомпилированные модули в библиотеке, список глобальных имен вашего кода. Формы, модули и метод разработки “Two-Way Tools” Формы - это объекты, в которые вы помещаете другие объекты для создания пользовательского интерфейса вашего приложения. Модули состоят из кода, который реализует функционирование вашего приложения, обработчики событий для форм и их компонент. Информация о формах хранится в двух типах файлов - .dfm и .pas, причем первый тип файла - двоичный - хранит образ формы и ее свойства, второй тип описывает функционирование обработчиков событий и поведение компонент. Оба файла автоматически синхронизируются Delphi, так что если доба-
7
вить новую форму в ваш проект, связанный с ним файл .pas автоматически будет создан, и его имя будет добавлено в проект. Такая синхронизация и делает Delphi two-way-инструментом, обеспечивая полное соответствие между кодом и визуальным представлением. Как только вы добавите новый объект или код, Delphi устанавливает т.н. “кодовую синхронизацию” между визуальными элементами и соответствующими им кодовыми представлениями. Например, предположим, вы добавили описание поведения формы (соотв. обработчик событий), чтобы показывать окно сообщения по нажатию кнопки. Такое описание появляется, если дважды щелкнуть мышкой непосредственно на оъект Button в форме или дважды щелкнуть мышью на строчку OnClick на странице Events в Инспекторе объектов. В любом случае Delphi создаст процедуру или заголовок метода, куда вы можете добавить код. procedure TForm1.Button1Click(Sender: TObject); begin end; Cоздавая этот код, Delphi автоматически формирует декларацию объекта TForm1, которая содержит процедуру ButtonClick, представляющую из себя собственно обработчик события. TForm1 = class (TForm) Button1: Tbutton; procedure Button1Click(Sender: TObject); private { Private declarations } public { Public declarations } end; Конечно вы запросто можете решить после получения этого кода, что автоматически созданные имена Вас не устраивают, и заменить их. Например, Button1 на Warning. Это можно сделать изменив свойство Name для Button1 при помощи Инспектора объектов. Как только вы нажмете Enter, Delphi автоматически произведет соответствующую синхронизацию в коде. Так как объект TForm1 существует в коде, вы свободно можете добавлять любые другие поля, процедуры, функции или object definition. К примеру, вы можете дописать в коде свою собственную процедуру, обрабатывающую событие, а не делать это визуальным методом. Следующий пример показывает, как это можно сделать. Обработчик принимает аргумент типа TObject, который позволяет нам определить, если необходимо, кто инициировал событие. Это полезно в случае, когда несколько кнопок вызывают общую процедуру для обработки. TForm1 = class(TForm) Warning: TButton; Button1: TButton; procedure WarningClick(Sender: TObject); procedure NewHandler(Sender: TObject); private { Private declarations } public { Public declarations } end; Здесь мы имеем дело уже с второй стороной синхронизации. Визуальная среда в данном случае распознает, что новая процедура добавлена к объекту и соответствующие имена появляются в Инспекторе объектов.
8
Обработка исключительных ситуаций Серьезные приложения должны надежным образом обрабатывать исключительные ситуации, сохранять, если возможно, выполнение программы или, если это невозможно, аккуратно ее завершать. Написание кода, обрабатывающего исключительные ситуации, всегда было непростой задачей, и являлось источником дополнительных ошибок. В Delphi это устроено в стиле С++. Исключения представлены в виде объектов, содержащих специфическую информацию о соответствующей ошибке (тип и место- нахождение ошибки). Разработчик может оставить обработку ошибки, существо-вавшую по умолчанию, или написать свой собственный обработчик. Обработка исключений реализована в виде exception-handling blocks (также еще называется protected blocks), которые устанавливаются ключевыми словами try и end. Существуют два типа таких блоков: try...except и try...finally. Общая конструкция выглядит примерно так: try { выполняемые операторы } except on exception1 do statement1; { реакция на ситуации } on exception2 do statement2; else { операторы по умолчанию } end; Конструкция try....finally предназначена для того, чтобы разработчик мог быть полностью уверен в том, что, что бы ни случилось, перед обработкой исключительной ситуации всегда будет выполнен некоторый код (например, освобождение ресурсов). try { выполняемые операторы } finally { операторы, выполняемые безусловно } end;
2. ОБЩИЕ СВЕДЕНИЯ О КОМПОНЕНТАХ 2.1. Списки Класс TList Класс TList — универсальный список. Он представляет собой массив нетипированных указателей и поэтому годится для хранения набора любых, в том числе разнотипных, данных и объектов. При добавлении/удалении в список данные не создаются и не уничтожаются — эта обязанность лежит на программисте. Приведем доступные ему методы и свойства класса: property Items[Index: Integer]: Pointer;
property Count: Integer;
Возвращает указатель на содержимое элемента списка с индексом Index. Это свойство является векторным свойством, принимаемым по умолчанию, и его имя можно при записи опускать (см. раздел "Свойства"). Определяет число элементов в списке.
9
property Capacity: Integer;
Определяет максимальное число элементов в списке. Оно может изменяться как явно — пользователем, так и при добавлении элементов в список, в том случае, когда Count>=Capacity. Максимальная емкость списка — 16380 элементов.
Управляют списком следующие методы: function Add(Item: Pointer): Добавляет в конец списка элемент, который будет равен Item (т. е. укаInteger; зывать на те же данные). function Remove(Item: Удаляет из списка элемент, который равен Item. Pointer): Integer; procedure Insert(Index: Inte- Вставляет элемент, равный Item, перед элементом с индексом Index. ger; Item: Pointer) ; procedure Delete(Index: Inte- Удаляет из списка элемент с индексом Index. ger); procedure Clear; Очищает список, устанавливая величины Count и Capacity в 0. procedure Exchange(Indexl, Index2: Integer); function Expand: TList;
function First: Pointer; function Last: Pointer; function IndexOf(Item: Pointer): Integer; procedure Move(CurIndex, Newlndex: Integer) ; procedure Pack;
Меняет местами элементы списка с индексами Indexl и Index2. При соблюдении равенства Count=Capacity расширяет список. При емкости списка менее пяти элементов, он по умолчанию расширяется на четыре элемента, при пяти-восьми — на восемь, более восьми — на шестнадцать. Возвращают значения первого и последнего (с индексом Count-1) элементов списка соответственно. Возвращает индекс элемента, равного Item. Перемещает элемент списка с положения Curlndex в положение Newlndex. Упаковывает список, сдвигая элементы к началу на пустующие места.
Наконец, если приведенных методов почему-либо недостаточно, то свойство (RC; property List: pPointerList; pPointerList = ^TPointerList; TPointerList = array[0..65520 div SizeOf(Pointer)] of Pointer; возвращает указатель непосредственно на список указателей ((ко) означает, что свойство доступно только для чтения). Класс TStrings Многофункциональный класс, предназначенный для хранения текстовых строк и связанных с ними объектов (любых потомков TObject). TStrings — абстрактный класс; он только описывает методы работы с наборами строк и сопутствующих им объектов, но как именно они хранятся, на его уровне не определено. Его потомки очень многочисленны; они играют основную роль в компонентах-списках (TListBox, TComboBox), редакторе (TMemo) и других. Так что вам чаще всего придется иметь дело с TStrings как со свойством одного из компонентов. В дальнейшем экземпляры этого класса и порожденных от него классов мы-будем называть наборами строк. Для создания собственных наборов строк вне компонентов предназначен потомок TStrings — TStringList, который будет рассмотрен ниже. К строкам и объектам соответственно можно получить доступ через свойства:
10
property Strings[Index: Integer]: string; property Objects[Index: Integer]: TObject; Первое из них является векторным свойством, принимаемым по умолчанию. Общее количество пар в списке равно значению свойства: (Ro'l property Count: Integer; Класс TStrings также предназначен для хранения пар вида 'параметр=значение', например, в файлах инициализации (.INI). Эту возможность реализует следующее свойство: property Values[const Name: string]: string; При обращении к этому свойству для чтения ищется строка, содержащая подстроку (параметр) Name и символ '='. Если она найдена, возвращается то, что находится в этой строке после '='. Если нет, Values[Name] равно пустой строке. При записи: если строка, содержащая параметр Name, найдена — ее значение после '=' заменяется новым значением, если нет — строка добавляется. Если существующему параметру присваивается пустая строка (Valu-es[Name] := ";), то он удаляется из набора строк. Методы класса приведены в таблице: procedure BeginUpdate; procedure En- Пара процедур, которые устанавливают и сбрасывают флаг обdUpdate; новления набора. Между ними, для ускорения работы, нужно заключать все операции по копированию, удалению и т. д. большого количества элементов. procedure Clear;
Осуществляет полную очистку набора.
procedure Insert(Index: Integer; const S: Вставляет строку S под индексом Index. string); procedure Delete(Index: Integer); Удаляет строку с индексом Index. function IndexOf(const S: string): Inte- Возвращает индекс (номер в наборе) строки S. Если она не ger; найдена, функция возвращает -1. function IndexOfObject(AObject: TObject): Integer; function Equals(Strings: TStrings): Boolean;
Возвращает индекс объекта в наборе. В случае неудачи возвращает -1. Сравнивает строки вызвавшего его объекта со строками объекта Strings и возвращает True в случае равенства (сравниваются число строк и все строки попарно).
function Add(const S: string): Integer-
Добавляет строку S в конец набора и в случае успеха возвращает присвоенный ей индекс (он должен быть равен значению Count до добавления строки).
function AddObject(const S: string; AObject: TObject): Integer; procedure Exchange(Indexl, Index2: Integer); procedure Move(Curlndex, Newlndex: Integer); procedure InsertObject(Index: Integer; const S: string; AObject: TObject);
Добавляет строку в паре с объектом. Возвращает то же, что и метод Add. Меняет местами пары строка+объект с индексами Indexl и Index2. Перемещает пару строка+объект с позиции Curlndex в позицию Newlndex. Вставляет объект AObject и соответствующую ему строку S в набор под индексом Index.
Шесть методов предназначены для экспорта/импорта наборов строк: а) в поток: procedure LoadFromStream(Stream: TStream); procedure SaveToStream(Stream: TStream);
11
б) в файл (создавая поток и вызывая два предыдущих метода): procedure LoadFrornFile (const FileName: strings-procedure SaveToFile(const FileName: string); в) в данные в формате текстового редактора (подряд расположенные строки, оканчивающиеся парой символов CR/LF (16-ричные коды SOD/SOA)). procedure AddScrings(Strings: Добавляет в конец набора другой набор Strings. TStrings! ; procedure Assign!Source: T'Persisier-t l Уничтожает прежнее содержимое набора и подставляет вместо ; него Source, если источник имеет тип TStrings. В противном случае возникает исключительная ситуация EConvertError.
При этом метод function GetText: PChar; выгружает строки в единый массив, где они разделены парами символов CR/LF; в конце такого массива ставится нулевой байт. Размер массива не может превышать 65520 байт; поэтому строки выгружаются до тех пор, пока их суммарная длина не превосходит этого значения. Метод procedure SetText(Text: PChar); читает строки из массива Text. Строки в массиве должны быть отделены друг от друга парой символов CR/LF; допускается и один символ LF (16-ричный код $ОА). Символы с кодами 0, $lA(
+) воспринимаются как конец текста. При этом прежнее содержимое набора уничтожается. Класс TStringList Этот класс объединяет в себе свойства TStrings и TList простейшим способом — указатель на объект и соответствующая строка объединяются в запись, указатель на которую хранится в списке. В классе переопределены многие виртуальные методы TStrings: Add, Clear, Delete, Exchange, IndexOf, Insert; он является полностью функциональным и вы можете создавать экземпляры TStringList в своей программе для работы с наборами строк и объектов (помимо тех, которые уже есть в компонентах). Кроме унаследованных от TStrings, определены дополнительно полезные методы и свойства: function Find(const S: string; var Index: Метод ищет в наборе строку S и в случае успеха возвращает Integer): Boolean; результат True, а в параметре Index — ее индекс. property Sorted: Boolean;
Свойство — признак отсортированности элементов (сортировка осуществляется через посимвольное сравнение строк). Установка Sort := True вызывает процедуру сортировки, которую можно вызвать и явно при помощи метода:
procedure Sort;
Попытка добавить или вставить элемент в отсортированный список вызывает исключительную ситуацию EListError; в этом случае до выполнения действия свойству Sorted нужно присвоить значение False.
property Duplicates: TDuplicates; TDu- Свойство определяет, что происходит при попытке добавить в plicates = (duplgnore, dupAccept, список дубликат уже имеющейся строки: duplgnore — добавdupError); ление игнорируется (отклоняется); dupError — добавление приводит к созданию исключительной ситуации EListError; dupAccept — одинаковые строки разрешены. В этом случае при поиске в неотсортированном списке не определено, которая из строк будет найдена первой.
12
property OnChange: TNotiДва свойства, предусмотренные для определения польfyEvent; property OnChanging: TNoti- зователем своей реакции на изменение данных. Событие fyEvent; OnChanging вызывается во многих рассмотренных выше методах до внесения первого изменения, OnChange — после последнего.
2.2. Элементы управления Потомком TComponent является класс TControl — элемент управления Windows. Все то, что видит (или может увидеть) пользователь в клиентской области вашей формы во время выполнения, порождено от класса TControl (клиентская область — вся рабочая поверхность окна Windows, исключая заголовок, полосу меню и обрамление). Таким образом, потомки TControl являются визуалъньши компонентами. Далее будем называть их, как принято в Windows, элементами управления, или, где это уместно, просто элементами. Отметим также подвох, связанный с названием "Библиотека визуальных компонентов" (Visual Components Library, VCL). Называя так свою библиотеку, разработчики из фирмы Borland были абсолютно правы, так как речь идет в первую очередь о новой — визуальной — технологии программирования. Но нужно помнить, что в VCL входит множество полноправных невизуальных компонентов. Например, меню — оно, конечно, видимо на экране, но не в клиентской области; поэтому формально меню не является визуальным компонентом. Большинство из свойств, которые вы будете видеть и изменять в визуальных компонентах, помещенных в Инспектор объектов, впервые описаны в классе TControl. Этот класс уже обладает "поведением" — в нем предусматривается реакция на основные события. Объект класса TControl не является окном Windows и в силу этого не может получить фокус ввода. Однако у него обязательно имеется родительский элемент (см. главу 2, раздел "Наследование. Методы"), обладающий этим свойством и отвечающий за показ дочернего элемента и обработку некоторых поступающих ему сообщений. (Раз TControl не является окном, то он не имеет контекста устройства для рисования. Этот контекст обеспечивает ему родитель. Тип родительского элемента — TWinControl — будет рассмотрен ниже). Обратите внимание на терминологию: Owner — это владелец компонента, а Parent — его родитель, определяемый свойством property Parent: TWinControl; Элемент управления может обрабатывать сообщения Windows. Чтобы послать собственной функции обработки элемента сообщение Windows вида Msg с параметрами WParam и LParam, можно воспользоваться методом: function Perform(Msg, WParam: Word; LParam: Longint): Longint; С каждым элементом управления связан некий текст, который может играть роль заголовка или редактироваться. В зависимости от роли текст может содержаться в свойствах Caption: (РЬ) property Caption: TCaption; TCaption = string[255]; либо Text: property Text: TCaption; Не задумываясь о том, в каком именно свойстве содержится текст, получить доступ к нему можно посредством методов: function GetTextBuf(Buffer: PChar; BufSize: Integer): Integer; procedure SetTextBuftBuffer: PChar); function GetTextLen: Integer; Все три метода работают, посылая в собственную функцию-обработчик сообщений объекта (через вызов метода Perform) сообщения Windows вида WM_GETTEXT, WM_SETTEXT и WM_GETTEXTLENGTH. Курсор, который будет устанавливаться на компоненте, определен свойством: property Cursor: TCursor; 13
TCursor = -32768..32767; В Delphi предопределены стандартные типы курсоров. Их имена: crDefault, crNone, crArrow, crCross, crIBeam, crSize, crSizeNESW, crSizeNS, crSizeNWSE, crSizeWE, crUpArrow, crHourGlass, crDrag, crNoDrop, crHSplit, crVSplit, crMultiDrag, crSQLWait. Этим именам соответствуют константы со значениями от 0 до -17; под этими идентификаторами все курсоры доступны в свойстве Cursors глобального объекта Screen. Можно добавить к ним собственные курсоры, загрузив их из ресурсов и присвоив положительные идентификаторы: {$R cursors.RES} const Curl = 1; Cur2 = 2;
procedure TFormI. For-mCreate (Sender: TObject); begin Screen.Cursors[Curl] := LoadCursor(hinstance, 'CUR_1'); Screen.Cursors[Cur2] := LoadCursor(hinstance, 'CUR_2<); end; Познакомиться с имеющимися видами курсоров можно, скомпилировав прилагаемый на дискете пример CURSORS. У каждого элемента есть два свойства, отражающие его стиль и состояние. Они могут сослужить программисту хорошую службу. Первый из них — набор флагов, управляющих поведением компонента: property ControlStyle: TControlStyle; TControlStyle = set of (csAcceptsControls, csCaptureMouse, csDesignInteractive, csClickEvents, csFramed, csSetCaption, csOpaque, csDoubleClicks, csFixedWidth, csFixedHeight); Эти флаги означают, что данный компонент имеет следующие особенности: с sAccept sControls Может содержать другие (дочерние) элементы управления. Таким свойством обладают не все элементы: обладающие им называются группирующими и рассмотрены в отдельном разделе. csCaptureMouse Может получать сообщения от мыши. csDesignInteractive Транслирует нажатия правой кнопки мыши в нажатия левой во время разработки. csFramed
Имеет обрамление (черным прямоугольником единичной толщины).
csSetCaption
Позволяет при изменении имени менять синхронно и свойство Text (если Text не был явно переустановлен). Фон элемента непрозрачен. Это означает, что при пересечении нескольких компонентов на экране располо„.:мный под ним виден не будет.
csOpaque csClickEvents с sDoubleC1i cks csFixedWidth, csFixedHeight
Воспринимает щелчки мышью. Воспринимает двойные щелчки мышью. Если этот флаг отсутствует, двойные щелчки воспринимаются как простые. Имеет фиксированную ширину или высоту соответственно. Она не изменяется при масштабировании компонента.
Набор флагов, отражающих состояние элемента, описывается свойством: property ControlState: TControlState; TControlState = set of (csLButtonDown, csClicked, csPalette, csReadingState, csAlignmentNeeded, csFocusing, csCreating); Они означают следующее: csLButtonDown Над элементом в данный момент нажата левая кнопка мыши. csClicked
Если элемент может воспринимать щелчки мышью, этот флаг устанавливается, пока кнопка мыши находится в нажатом состоянии.
14
csPalette csReadingState
Элемент поддерживает собственную палитру и должен получать извещения о перерисовке в необходимых случаях. Элемент в данный момент читается из потока.
сsAlignmentNeeded Элемент требует выравнивания относительно родительского (см. раздел "Положение, размеры и выравнивание элементов управления"). csFocusing
В данный момент происходит получение элементом фокуса ввода.
csCreating
Элемент создается (этот флаг в настоящий момент не задействован).
Свойства ControlStyle и ControlState описаны не в пользовательской документации, а в документации разработчика новых компонентов. Вы можете читать их значения для получения информации об элементе управления, но изменять их стоит только тогда, когда вы полностью отдаете себе отчет в том, к чему это приведет. Подавляющее большинство элементов управления имеет собственное вспльша-ющее меню, появление которого связано с нажатием правой кнопки мыши. Доступ к нему возможен через свойство: property PopupMenu: TPopupMenu; Подробно о его создании и использовании рассказано в разделе, посвященном меню.
2.3. Положение, размеры и выравнивание элементов управления О каждом визуальном компоненте должно быть известно, где он будет показан и какой будет иметь размер. Свойство property BoundsRect: TRect; определяет прямоугольник, содержащий координаты верхнего левого и правого нижнего углов компонента в системе координат клиентской области родительского элемента. Для формы верхний левый угол выражен в системе координат экрана. Также можно установить положение и размер компонента, изменяя координаты верхнего левого угла, длины и ширины методом: procedure SetBounds(ALeft, АТор, AWidth, AHeight: Integer); К каждой из этих величин есть и раздельный доступ во время разработки с помощью свойств: (pb) property Left: Integer; J property Top: Integer; property Width: Integer; (Pb) property Height: Integer; Другое свойство задает прямоугольник, определяющий положение и размеры клиентской области окна элемента управления: (Ro) property ClientRect: TRect; Эта величина доступна только для чтения. Если необходимо переустановить размеры клиентской области, нужно воспользоваться парой свойств: property ClientHeight: Integer; property ClientWidth: Integer; Свойство ClientOrigin задает положение начала клиентской области относительно экрана: (Ro) property ClientOrigin: TPoint; Если же нужно связать с координатной системой экрана произвольную точку, пользуйтесь парой методов (не путать с одноименными функциями Windows API): function ClientToScreen(const Point: TPoint): TPoint; function ScreenToClient(const Point: TPoint): TPoint; Очень важную часть работы по управлению размерами и расположением элементов выполняет свойство:
15
(Pb) property Align: TAlign; Оно определяет выравнивание компонента относительно границ родителя. Может принимать одно из предопределенных значений: TAlign = (aiNone, alTop, alBottom, alLeft, alRight, alClient); aINone — выравнивание отсутствует; alTop, alBottom, alLeft, alRight — выравнивание происходит по соответствующей стороне родителя; alClient — компонент занимает все пространство клиентской области родителя. Выравнивание гарантирует, что при изменении размеров родителя относительная позиция дочернего элемента не меняется. Это свойство имеет приоритет над простым изменением положения и размеров. Если новые координаты элемента противоречат способу его выравнивания (например, перемещение вверх при alBottom), изменения отвергаются и элемент возвращается к первоначальным координатам. Свойство Align незаменимо при организации панелей инструментов и строк состояния — они могут перемещаться и видоизменяться вместе с содержащей их формой. Для временного отключения действия свойства Align предназначены методы: procedure DisableAlign; procedure EnableAlign; Эти методы управляют возможностью выравнивания потомков данного эле-мекга, они должны вызьшаться в паре. Для восстановления выравнивания элементов в соответствии с Align есть метод: procedure Realign; В следующем примере использование методов DisableAlign и EnableAlign позволяет настроить выравнивание панели по тому краю формы, на который пользователь перетащит ее мышью: procedure TFormI.FormCreate(Sender: TObject); begin Panell.Align := alBottom; Moving := False; end; procedure TFormI.PanellMouseDown(Sender: TObject;Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin if not Moving then begin Formi.DisableAlign; Moving := True; end; end; procedure TFonnl.PanellMouseMove(Sender: TObject;Shift: TShiftState; X, Y: Integers); begin if Moving then with Panell do begin Left := Left + X - Width div 2; Top := Top + Y - Height div 2; Panell.Caption := Format('%d,%d',[Left,Top]); end; end; procedure TFormI.PanellMouseUp(Sender: TObject;Button: TMouseButton; Shift: TShiftState; X, Y: Integer); var LastPos : TPoint; rO, rl, r2 : real; begin if Moving then begin Moving := False;
16
Panell.Caption := ''; LastPos := Point(Panell.Left + X, Panell.Top + Y) ; if LastPos.X<=0 then LastPos.X := 1; if LastPos.X>=ClientWidth then LastPos.X := ClientWidth-1; if LastPos.Y<=0 then LastPos.Y := 1; if LastPos.Y>=ClientHeight then LastPos.Y := ClientHeight-1; rO := ClientWidth/ClientHeight; rl := LastPos.X/LastPos.Y; r2 := LastPos.X/(ClientHeight - LastPos.Y); with Panell do if rl < rO then if r2 < rO then Align := alLeft else Align := alBottom else if r2 < rO then Align := alTop else Align := alRight; Formi.EnableAlign; end; end;
2.4. Активность и видимость элементов управления Активность элемента позволяет ему получать и обрабатывать сообщения от клавиатуры, мыши и таймера. Она определяется свойством: (Pb) property Enabled: Boolean; Значение True делает управляющий элемент активным. При смене состояния Enabled выполняется перерисовка его на экране, при которой пассивные элементы, как правило, изображаются серьм цветом. Свойство, определяющее возможность видимости элемента во время исполнения: (Pb) property Visible: Boolean; Во время разработки все компоненты являются видимыми. Изменять это свойство непосредственно во время выполнения можно, используя два следующих метода: procedure Show; procedure Hide; Напомним, что, так как Visible является свойством, то выражение visible: =True не является простым присваиванием. Оно неявно содержит все необходимые операции для показа элемента управления. Это же касается всех остальных свойств компонентов, реакция на изменение которых должна произойти немедленно. Почему выше была применена формулировка "возможность видимости", а не "видимость"? Чтобы элемент был виден на экране, одного значения свойства Visible недостаточно. Нужно, чтобы видимыми были все предки элемента в иерархии. Реально видимость элемента можно узнать, пользуясь свойством: (Ro) property Showing: Boolean; Это свойство устанавливается при изменении свойства Visible. Оно доступно только для чтения. Нужно иметь в вицу, что при изменении видимости родительского элемента Showing не изменяется и может в какой-то момент не соответствовать истинному состоянию. Обновляет состояние свойства Showing для компонента метод: procedure UpdateControlState; Отрисовкой (изменением изображения на экране) элемента управления "заведуют" следующие методы: procedure Invalidate; Отрисовывает компонент, вызывая функцию API InvalidateRect. procedure update;
Предусматривает дополнительные операции, необходимые Windows при отрисовке окна. Для компонентов-окон, например, в ней вызывается функция UpdateWindow.
17
procedure Repaint; procedure Refresh;
Этот метод объединяет два предыдущих. Если элемент управления непрозрачный (имеет флаг csOpaque), занимаемый им прямоугольник предварительно очищается. Представляет собой вызов Repaint. Рекомендуется для вызова отрисовки.
2.5. Оконные элементы управления Понятие окна Windows инкапсулировано в потомке TControl — классе TWinControl. Такой компонент получает соответствующий атрибут _ дескриптор окна, определяемый свойством: (Ro) property Handle: HWnd; С помощью этого дескриптора вы можете вызывать функции API Windows, если средств VCL вам недостаточно для решения задачи. Компоненты-потомки TWinControl — в дальнейшем будем называть оконными элементами управления, а элементы управления, не имеющие дескриптора окна, — неоконными. Возможны ситуации, когда компонент уже создан, но еще не имеет дескриптора как окно. Два метода управляют созданием дескриптора: function HandleAllocated:Boolean; procedure HandleNeeded; Первая сообщает о наличии выделенного дескриптора, а вторая при его отсутствии посылает запрос на его выделение. Такой метод должен применяться перед каждой операцией, требующей дескриптора. Важным свойством TWinControl является то, что он может содержать другие — дочерние — элементы управления. Они упорядочены в виде списка. Если быть точным, то списков на самом деле два — для неоконных и оконных дочерних элементов. Но "видны" они как один объединенный — сначала первый, потом второй. Методы и свойства для работы с этим списком приведены в таблице: (Ro) property Controls[Index: Integer]: Содержит список дочерних элементов. TControl; (Ro) property ControlCount: Integer; Содержит число элементов в списке. function ContainsControl(Control: TControl): Boolean; function ControlAtPos(const Pos: TPoint; AllowDisabled: Boolean): TControl ;
procedure InsertControl(AControl: TControl) ; procedure RemoveControl(AControl: TControl); procedure Broadcast(var Message);
Проверяет наличие элемента в списке. Отыскивает в списке элемент, которому принадлежит заданная точка (в системе координат собственной клиентской области). Флаг AllowDisabled показывает, разрешен ли поиск среди пассивных (свойство Enabled которых равно False) элементов. Вставляет элемент в конец списка. Удаляет элемент из списка. Рассылает всем дочерним элементам из списка сообщение Message.
С каждым оконным компонентом можно связать контекстную помощь. Контекст помощи — это индекс, указывающий на определенную информацию в файле справочной системы, связанном с приложением: property HelpContext: THelpContext; Когда компонент находится в фокусе, то при нажатии клавиши загружается система контекстной помощи, и пользователь сразу получает информацию, связанную с заданным контекстом. Если контекст равен нулю, то система пытается отыскать в цепочке родительских компонентов первый,
18
имеющий ненулевой контекст. Оконный компонент может управлять положением и размерами своих дочерних компонентов. Прокрутку (скроллинг) элементов на интервал DeltaX, DeltaY осуществляет метод: procedure ScrollBy(DeltaX, DeltaY: Integer); Прибегая к вызову этой процедуры, можно при желании осуществить прокрутку нестандартным способом, т. е. без привлечения полос прокрутки. Приведенный ниже фрагмент кода — составная часть примера IMGSCROL на дискете— позволяет "тащить" изображение Imagel вслед за мышью с нажатой кнопкой: type TMouseState = (msNormal, msDragging); var OldPos, NewPos, MaxShift: TPoint; PMouseState : TMouseState;
procedure TFormI.ScrollMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integers); begin MaxShift.X := Imagel.Parent.Width - Imagel.Width; MaxShift.Y := Imagel.Parent.Height - Imagel.Height; if (MaxShift.X > 0) and (MaxShift.Y > 0) then Exit; FMouseState := msDragging; OldPos := Point(X, Y) ; Screen.Cursor := crDrag; end; procedure TFormI.ScrollMouseMove(Sender : TObject; Shift: TShiftState; X, Y: Integers); begin if FMouseState = msDragging then begin NewPos := Point(X - OldPos.X, Y - OldPos.Y) ; if Imagel.Left + NewPos.X > 0 then NewPos.X := - Imagel.Left; if Imagel.Left + NewPos.X < MaxShift.X then NewPos.X := MaxShift.X - Imagel.Left; if Imagel.Top + NewPos.Y > 0 then NewPos.Y := - Imagel.Top; if Imagel.Top + NewPos.Y < MaxShift.Y then NewPos.Y := MaxShift.Y - Imagel.Top; Imagel.Parent.ScrollBy(NewPos.X, NewPos. Y) ; end; end; procedure TFormI.ScrollMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin FMouseState -.= msNormal; Screen.Cursor := crDefault; end; Обратите внимание, что прокрутка неоконного компонента Imagel осуществляется посредством вызова Image l.Parent.ScrollBy. Это свидетельствует о том, что конкретный родительский тип для этого безразличен. В примере изображение помещено на панель (TPanel). Впрочем, метод ScrollBy используется также и полосами прокрутки, которые есть в компоненте TScrollingWinControl и его потомках, например, в TForm. В VCL предусмотрена возможность написания приложений, которые будут сохранять относительный размер и положение при всех разрешениях дисплея. Более подробно эти механизмы описаны в разделе, посвященном формам; для TWinControl упомянем лишь метод procedure ScaleBy(M, D: Integer); который изменяет масштаб элемента управления в M/D раз, при этом верхний левый угол остается неподвижным. Так же изменяются и размеры всех дочерних элементов. Соответственно изменяется и масштаб шрифта (свойство Font). Флаги csFixedWidth и csFixedHeight в свойстве ControlStyle предот-
19
вращают изменение ширины или высоты. При изображении большинства оконных элементов управления используют эффект "трехмерности", создающий иллюзию приподнятости или вдавленное™ за счет подбора внешнего вида обрамления. Наличие "трехмерности" задается свойством: (Рb) property Ctl3D: Boolean; Нужно уточнить, что это свойство есть не у всех компонентов. Для части компонентов трехмерность реализована средствами VCL; другая же часть (радиокнопки, флажки и др.) требует для создания трехмерного эффекта доступа к библиотеке CTL3DV2.DLL. Шрифт, которым выводится текст, связанный с элементом управления: property Font: TFont; Кисть, используемая для закрашивания рабочей области оконного элемента управления, представлена свойством: (Ro) property Brush: TBrush; Она имеет цвет, содержащийся в свойстве Color (по умолчанию clWindow). На уровне TControl оно доступно только по чтению: property Color: TColor;
2.6. Реакция на события от мыши и клавиатуры Традиционно пользователь может предусмотреть реакцию на нажатие и отпускание любой из кнопок и перемещение курсора мыши. Эти три события обеспечивают интерфейс каждого элемента управления с мышью. Первые два из них имеют формат: (р^) property OnMouseDown: TMouseEvent; (Pb) property OnMouseUp: TMouseEvent; TMouseEvent = procedure(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer) of object; Параметры: Sender — элемент-источник сообщения (обычно равен Self); Button — идентификатор одной из кнопок; TMouseButton = (mbLeft, mbRight, mbMiddle); Shift — множество, которое может содержать элементы: ssAlt, ssCtrl, ssShift — в зависимости от состояния этих клавиш; ssLeft, ssRight, ssMiddle, ssDouble — в зависимости от нажатия кнопок мыши (ssDouble — нажать! и правая, и левая кнопки); X, Y — координаты нажатия (в системе координат клиентской области получателя). При перемещении мыши возникает событие: (Pb) property OnMouseMove: TMouseMoveEvent ; TMouseMoveEvent = procedure(Sender: TObject; Shift: TShiftState; X, Y: Integer) of object; Использование сообщений от мьшш уже встречалось в примерах, приведенных вьппе (например, см. разд. "Положение, размеры и выравнивание элементов управления"). Два события извещают о щелчке и двойном щелчке левой кнопкой мыши над компонентом: (pt) property OnClick: TNotifyEvent; (Pb) property OnDblClick: TNotifyEvent; Отменить генерацию этих событий можно, удалив флаг csClickEvents из слова состояния элемента (ControlStyle). Для некоторых компонентов (например, кнопок) OnClick возникает еще и при нажатии определенных клавиш на клавиатуре, а также вследствие вызова метода Click. События, связанные с мышью, могут быть получены потомками TControl. В отличие от них, реакцию на события от клавиатуры могут иметь только оконные элементы управления ("могут", т. к. на уровне TControl и TWinControl эти события только описаны, но не опубликованы). Таким образом, есть компоненты (в том числе в Палитре компонентов), не имеющие связи с этими событиями из-за ее ненадобности. Впрочем, их меньшинство, и материал в этом разделе обобщен вполне обоснованно. Нажатие и отпускание клавиш клавиатуры могут инициировать следующие события: property OnKeyDown: TKeyEvent;
20
property OnKeyUp: TKeyEvent; eyEvent = procedure(Sender: TObject; var Key: Word; Shift: TShiftState) of object; Генерация этих событий встроена в обработчики сообщений Windows WMJCEYDOWN, WMJSYSKEYDOWN и WM_KEYUP, WM_SYSKEYUP соответственно. Обработчику передаются: Sender — источник сообщения; Shift — состояние специальных клавиш и кнопок мыши во время нажатия (отпускания); Key — код нажатой клавиши, представляющий собой виртуальный код клавиши Windows (константы вида VK_XX, например, VK_F1, VK_ESCAPE и т. п.). Обратите внимание, что Key является var-параметром; т. е. его значение может быть изменено программистом. Другое событие, возникающее вследствие нажатия клавиши: property OnKeyPress :. TKeyPressEvent; TKeyPressEvent = procedure(Sender: TObject; var Key: Char) of object; Это событие возникает при вводе с клавиатуры символа ASCII, т. е. оно не генерируется, например, при нажатии функциональных клавиш или . Обработчик события вызывается при нажатии буквенных (в т. ч. вместе с <Shift>), цифровых клавиш, комбинаций + .. + (коды ASCII #1..#26), <Enter>, <Esc>, , + (код #3) и некоторых других. Также код ASCII можно сгенерировать, нажав <А11>+<десятичньш код символа> на числовой клавиатуре (Numeric Pad). Событие OnKeyPress соответствует сообщению Windows WM_CHAR. Все сообщения клавиатуры поступают тому элементу управления, который в данный момент имеет фокус ввода. Однако из этого правила возможно одно исключение. Если у формы, которая содержит этот элемент управления, свойство (Pb) property KeyPreview: boolean; установлено в True, то сначала все три вида сообщений поступают к ее обработчикам, и только потом — к элементу управления. Если при этом в них обнулить параметр Key, то в элемент сообщение не поступит вообще. В приведенном ниже примере клавиша резервируется для изменения состояния формы: procedure TFormI.FormCreate(Sender: TObject); begin KeyPreview := True; end; procedure TFonnl.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); begin if Key = VK_F5 then begin if ssCtrl in Shift then WindowState := wsNormal else if Shift = [] then WindowState := wsMaximized; Key : = 0 ; end; end;
2.8. Фокус ввода Будучи окнами Windows, TWinControl и его потомки должны управлять фокусом ввода (состоянием, когда они получают и обрабатывают входные сообщения). Они имеют предназначенные для этого методы: Поскольку оконные и неоконные элементы управления фактически находятся в разных списках, эти операции касаются только элементов соответствующего списка. Оконные элементы всегда имеют приоритет над неоконными: вы можете поместить первый над вторым, наоборот — никогда.
21
function Focused: Boolean-
Показывает, имеет ли элемент в данный момент фокус ввода.
function CanFocus: Boolean;
Возвращает True, если оконный элемент может получить фокус ввода (для этого он и все его родительские оконные элементы управления должны быть активными (Enabled) и видимыми).
procedure SetFocus;
Запрашивает у родительской формы получение фокуса ввода.
При получении и утере фокуса оконными компонентами происходят события: (Pb) property OnEnter: TNotifyEvent; (Р1э) property OnExit: TNotifyEvent; TNotifyEvent — простейшее событие — извещение, не имеющее параметров. Свойство (Pb) property TabStop: Boolean; показывает, есть ли на данном элементе табулостоп. Между элементами формы, у которых TabStop установлено в Тше, можно передвигаться (перемещать фокус ввода) нажатиями клавиш /<Shift>+. Очередность перехода фокуса определяется специальным списком. Положение элемента управления в родительском списке табулостопов определяется при помощи свойства: (Pb) property TabOrder: TTabOrder; TTabOrder = -1..32767; При разработке формы номера присваиваются последовательно в порядке добавления компонентов, начиная с 0. Нулевой компонент первым получит фокус при активизации. Программист может менять значение этого свойства, не заботясь об учете других элементов, т. к. перенумерация производится автоматически. При отсутствии табулостопа на элементе его свойство TabOrder равно -1. Можно получить и весь список, содержащий иерархию дочерних элементов, имеющих табулостоп: procedure GetTabOrderList(List: TList) ; Этот метод добавляет в уже существующий список List все дочерние компоненты, имеющие табулостоп; каждый из них при добавлении делает то же самое. Таким образом, элементы списка имеют тип TWinControl. Два метода предназначены для управления показом перекрывающихся дочерних элементов управления: procedure BringToFront; procedure SendToBack; Метод BringToFront переносит элемент в конец родительского списка (визуализация начинается с первого) и показывает его поверх остальных. SendToBack, наоборот, переносит элемент в начало и таким образом сверху окажутся все перекрывающиеся с ним. При этом элемент теряет фокус ввода (если имел). Класс инкапсулирует шрифт Windows. В Delphi допускаются только горизонтально расположенные шрифты. В конструкторе объекта по умолчанию принимается шрифт System цвета clWindowText размера 10 пунктов. Привязка к родительским свойствам Для придания приложению строгого внешнего вида требуется, чтобы все визуальные компоненты имели единый стиль. Чтобы избавиться от необходимости изменять код элементов-потомков при изменении внешнего вида предка, в них нужно правильно установить свойства: (PDI property ParentColor: boolean; @ property ParentCtl3D: boolean; (Pb) property ParentFont: boolean; (pS) property ParentShowHint: boolean; Все четыре свойства отвечают за наличие связи между соответствующими свойствами в родительских и дочерних элементах. Если какое-то из них у элемента установлено в True, это означает, что 22
он наследует свойство от родителя и меняет цвет, трехмерность, шрифт или показ ярлычков вместе и вслед за ним. Но если само свойство дочернего элемента (например, Color или ParentColor) переустановить явно, то связь с родительским свойством разрьюается.
2.8. Графическая подсистема Разработчики Delphi уделили большое внимание возможностям работы с деловой графикой: простота и удобство ее использования напрямую сказывается на простоте и удобстве созданных приложений. Вместо дебрей графического интерфейса Windows разработчик получил несколько инструментов, сколь понятных, столь же и мощных. Другой бич работы с графикой в Windows — проблема рационального использования ресурсов. Для больших программных комплексов она стала в последнее время нешуточной (это касается, по крайней мере, версий Windows до 3.11 включительно). Такие монстры, как Microsoft Office, потребляют во время работы львиную долю ресурсов. В Delphi ресурсами GDI незримо для пользователя "заведуют" специальные менеджеры, ведущие списки всех размещенных кистей, перьев, шрифтов и т. п. и управляющие их использованием. Обязательным для любого объекта, связанного с графикой в Delphi является событие property OnChange: TNotifyEvent; Его обработчик вызывается всякий раз, когда изменились какие-то характеристики объекта, влияющие на его внешний вид. В стандартном GDI основой для рисования служит дескриптор контекста устройства hDC и связанные с ним шрифт, перо и кисть. В состав VCL входят объектно-ориентированные надстройки над последними, назначением которых является удобный доступ к свойствам инструментов и прозрачная для пользователя обработка всех их изменений. Сначала опишем три этих класса.
Класс TCanvas TCanvas = class(TPersistent) Этот класс — сердцевина графической подсистемы Delphi. Он объединяет в себе и "холст" (контекст конкретного устройства GDI), и "рабочие инструменты" (перо, кисть, шрифт) и даже "подмастерьев" (набор функций по рисованию типовых геометрических фигур). В дальнейшем для обозначения этого класса мы будем пользоваться термином "канва". Сознавая неоднозначность такого перевода, авторы тем не менее считают, что у него наилучшие шансы прижиться. Канва не является компонентом, но она входит в качестве свойства во многие другие компоненты, которые должны уметь нарисовать себя и отобразить какую-либо информацию. Дескриптор контекста устройства, над которьм "построена" канва, может быть востребован для различных низкоуровневых операций. Он задается свойством: property Handle: HDC; Для рисования канва включает в себя шрифт, перо и кисть: (р^) property Font: TFont ; (Pt^ property Pen: TPen; (РЙ property Brush: TBrush; Кроме того, можно рисовать и поточечно, получив доступ к каждому пикселу. Значение свойства property Pixels[X, Y: Integer]: TColor; соответствует цвету точки с координатами (X,Y). Канва содержит методы-надстройки над всеми основными функциями рисования GDI Windows и свойства, которые приведены ниже в таблице. При их рассмотрении имейте в виду, что все геометрические фигуры рисуются текущим пером. Те из них, которые можно закрашивать, закрашиваются с
23
помощью текущей кисти. Кисть и перо при этом имеют текущий цвет. procedure Arc (XI, Yl, Х2, Y2, ХЗ, Y3, Метод рисует сегмент эллипса. Эллипс определяется описыХ4, Y4: Integer) ; вающим прямоугольником (X1,Y1) — (X2,Y2); его размеры должны лежать в диапазоне от 2 до 32767 точек. Начальная точка сегмента лежит на пересечении эллипса и луча, проведенного из его центра через точку (X3.Y3). Конечная точка сегмента лежит на пересечении эллипса и луча, проведенного из его центра через точку (X4.Y4). Сегмент рисуется против часовой стрелки.
procedure Chord(Xl, Yl, Х2, Y2, ХЗ, Y3, Х4, Y4: Integer);
Рисует хорду и заливает отсекаемую ею часть эллипса. Эллипс, начальная и конечная точки определяются, как в методе Arc.
procedure EllipsefXl, Yl, Х2, Y2: Integer) ; procedure LineTo(X, Y: Integer);
Рисует и закрашивает эллипс, вписанный в прямоугольник (X1.Y1) — (X2.Y2). Проводит линию текущим пером из текущей точки в (X,Y).
procedure MoveTo(X, Y: Integer);
Перемещает текущее положение пера (свойство PenPos) в точку (X,Y). procedure BrushCopy(const Dest: Производит специальное копирование. Прямоугольник Source из TRect; Bitmap: TBitmap; const Source: битовой карты Bitmap копируется в прямоугольник Dest на канTRect; Color: TColor); ве; при этом цвет Color заменяется на цвет текущей кисти (Brush.Color). С помощью этого метода можно нарисовать "прозрачную" картинку. Для этого нужно выбрать соответствующий фону цвет кисти и затем заменить на него фоновый или наиболее часто встречающийся цвет битовой карты (см. Bitmap. TransparentColor).
procedure CopyRect(const Dest: TRect; Производит копирование прямоугольника Source из канвы CanCanvas: TCanvas; const Source: TRect) vas в прямоугольник Dest в области самого объекта. ; procedure FillRect(const Rect: TRect) ; Производит заливку прямоугольника (текущей кистью). procedure FrameRectfconst Rect: TRect); procedure Draw(X, Y: Integer; Graphic: TGraphic) ;
Производит оконтуривание прямоугольника цветом текущей кисти (без заполнения). Осуществляет рисование графического объекта Graphic (точнее, вызов метода его рисования) в области с верхним левым углом (X,Y).
procedure StretchDraw(const Rect: TRect; Graphic: TGraphic);
Осуществляет рисование объекта Graphic в заданном прямоугольнике Rect. Если размеры их не совпадают, Graphic масштабируется.
procedure DrawFocusRect(const Rect: TRect);
Производит отрисовку прямоугольной рамки из точек (как на элементе, имеющем фокус ввода). Поскольку метод использует логическую операцию XOR (исключающее ИЛИ), повторный вызов для того же прямоугольника приводит изображение к начальному виду.
24
procedure FloodFilKX, Y: Integer; Color: TColor; FillStyle: TFillStyle); TFillStyle = (fsSurface, fsBorder) ;
Производит заливку области текущей кистью. Процесс начинается с точки (X,Y). Если режим FillStyle равен fsSurface, то он продолжается до тех пор, пока есть соседние точки с цветом Color. В режиме fsBorder закрашивание, наоборот, прекращается при выходе на границу с цветом Color.
procedure Pie (XI, Yl, Х2, Y2, ХЗ, Y3, Рисует сектор эллипса, описываемого прямоугольником (X1,Y1) Х4, Y4: Integers— (X2,Y2). Стороны сектора лежат на лучах, проходящих из центра эллипса через точки (X3.Y3) и (X4,Y4). procedure Polygon(const Points: array of TPoint) ;
Строит многоугольник, используя массив координат точек Points. При этом последняя точка соединяется с первой и внутренняя область закрашивается.
procedure Polyline(const Points: array of TPoint) ; procedure Rectangle(XI, Yl, Х2, Y2 : Integer) ; procedure RoundRect (XI, Yl, Х2, Y2, ХЗ, Y3: Integer);
Строит ломаную линию, используя массив координат точек Points. Рисует прямоугольник с верхним левым углом в (XI ,Y1) и нижним правым в (X2.Y2). Рисует прямоугольник с закругленными углами. Координаты вершин — те же, что и в методе Rectangle. Закругления рисуются как сегменты эллипса с размерами осей по горизонтали и вертикали ХЗ и Y3.
function. TextHeight(const Text: string): Integer; function TextWidth(const Text: string): Integer; procedure TextOut(X, Y: Integer; const Text: string);
Возвращает высоту строки Text в пикселах. Возвращает ширину строки Text в пикселах. Производит вывод строки Text. Левый верхний угол помещается в точку канвы (X,Y).
procedure TextRect(Rect: TRect; X, Y: Производит вывод текста с отсечением. Как и в TextOut, строка Integer; const Text: stringi ; Text выводится с позиции (X,Y); при этом часть текста, лежащая вне пределов прямоугольника Rect, отсекается и не будет видна. (Ro) property ClipRect: TRect;
Определяет область отсечения канвы. То, что при рисовании попадает за пределы этого прямоугольника, не будет изображено. Свойство доступно только для чтения — его значение переустанавливается системой в контексте устройства канвы.
property PenPos: TPoint;
Содержит текущую позицию пера канвы (изменяется посредством метода MoveTo).
Метод procedure Refresh; сбрасывает текущие шрифт, перо и кисть, заменяя их на стандартные, заимствованные из Windows (BLACK.PEN, HOLLOW_BRUSH, SYSTEM.FONT). Предусмотрено два события для пользовательской реакции на изменение канвы: property OnChange: TNotifyEvent; property OnChanging: TNotifyEvent;
25
Эти события возникают при изменениях свойств и вызовах методов TCanvas, меняющих вид канвы (то есть при любом рисовании. В MoveTo, например, они не возникают). Отличие их в том, что OnChanging вызывается до начала изменений, a OnChange — после их завершения. Идентификатор (код) растровой операции при копировании прямоугольных блоков содержится в свойстве (Pb) property CopyMode: TCopyMode; TCopyMode = Longint; и определяет правило сочетания пикселов, копируемых на канву, с ее текущим содержимым. При этом можно создавать разные изобразительные эффекты. В Delphi определены следующие константы кодов: cmBlackness, cmDstInvert, cmMergeCopy, cmMergePaint, cmNotSrcCopy, cmNotSrcErase, cmPatCopy, cmPatInvert, cmPatPaint, cmSrcAnd, cmSrcCopy, cmSrcErase, cmSrcInvert, cmSrcPaint, cmWhiteness. Все они стандартно определены в Windows, и подробное их описание можно найти в документации по GDI. Значением CopyMode по умолчанию является cmSrcCopy — копирование пикселов источника поверх существующих. Использование графики иллюстрируют несколько примеров, имеющихся на дискете, прилагаемой к книге. Обратите внимание на пример MOVLINES — в нем показано, как создавать и использовать канву для всего экрана. Помимо графических примитивов, таких как линии и фигуры, на канве можно разместить готовые изображения. Для их описания создан класс TGraphic.
3. РАБОТА С КОМПОНЕНТАМИ После знакомства с общими принципами работы компонентов перейдем к их предметному рассмотрению. В этом разделе описаны все элементы Палитры компонентов, сгруппированные по функциональному назначению. Вы встретите здесь и компоненты, не входящие в Палитру; как правило, они являются предками других компонентов, важными для понимания. Для каждого из компонентов приводятся все методы и свойства, которые доступны программисту, работающему с этим компонентом, то есть описанные как public и published (в т. ч. недокументированные). Мы попытались проиллюстрировать некоторые неочевидные вещи хотя бы коротким фрагментом кода. В развернутом виде примеры использования компонентов можно найти на дискете, прилагаемой к книге.
3.1. Работа с меню В приложениях, написанных на Delphi, могут быть реализованы меню двух основных видов: • Главное меню. Такое меню принадлежит форме и отображается вместе с ней под ее панелью заголовка. Если в приложении несколько форм, то для удобства можно объединять меню всех активных форм в одном. • Всплывающее меню. Такое меню предусмотрено почти у всех компонентов — элементов управления Windows. Оно возникает (всплывает) при нажатии правой кнопки мыши на этом компоненте. Предусмотрено такое меню и у формы. Меню являются иерархическими структурами, состоящими из пунктов меню. Каждый пункт может быть выбран. Это может произойти вследствие щелчка кнопкой мыши, нажатия соответствующих клавиш на клавиатуре или вызова процедуры в программе. На нижнем уровне лежат команды меню — пункты, с выбором которых должна быть связана та или иная реакция приложения. Команда! объединяются в подменю. Подменю — это пункты, выбор которых означает показ или свертывание списка входящих в него команд и подменю. Принципы создания и работы с меню в Delphi очень просты. Каждому пункту меню соответствует свой компонент класса TMenuItem. Вы добавляете к меню новые пункты (а к форме — новые компоненты) либо во время разработки (при помощи Конструктора меню), либо во время исполнения. При выборе пункта меню для описывающего его компонента инициируется событие OnClick, в обработчике которого и нужно предусмотреть соответствующие действия.
26
Компонент TMenuItem TObject—>TPersistent—”TComponent—>TMenuItein Модуль MENUS В Палитру компонентов не входит Этот компонент, который является основой системы меню в Delphi, вы не встретите в Палитре компонентов — он входит в состав имеющихся там компонентов TMainMenu и TPopupMenu. Текст, содержащийся в пункте меню, определяется свойством: (Pb) property Caption: string; Помимо основной, он несет еще две дополнительные нагрузки. Во-первых, если в строке имеется амперсанд ('&'), то он вместе с следующим за ним символом является акселератором. Например, для строки '&File' нажатие + означает выбор этого пункта. Во-вторых, если текст состоит из одного символа '-', то этот пункт служит в меню разделителем (имеет стиль MFJSEPARATOR); Помимо акселератора может быть описана еще одна комбинация клавиш, нажатие которой равнозначно выбору пункта. Она должна содержаться в свойстве: • (Pb) property Shortcut: TShortCut; TShortCut = Low(Word)..High(Word); Способы выбора пункта меню — щелчок мышью, нажатие <Enter> на сфокусированном пункте, нажатие акселератора или горячей комбинации, наконец, вызов метода procedure Click; приводят к возникновению события: (Р1э) property OnClick: TNotifyEvent; Компонент TMenuItem может различать только это событие, с которьм должны быть связаны действия, ради которых вы ввели данный пункт в меню. Воспринимают это событие те пункты меню, которые в данный момент активны, что означает-установленное в True свойство: (РЬ) property Enabled: Boolean; Если Enabled равно False, пункт изображается серьм цветом и не воспринимает сообщений. Часто пункты меню используются для переключения каких-либо режимов работы программы. При этом они могут быть отмечены "галочкой" перед началом текста. Свойство (Pb) property Checked: Boolean; отвечает за то, является ли этот пункт отмеченным. Если в меню слишком много команд, то их расположение одна под другой может вызвать серьезные неудобства для пользователя. Свойство (Pb) property Break: TMenuBreak; TMenuBreak = (mbNone, mbBreak, mbBarBreak) ; призвано решить эту проблему. Если оно установлено в mbBreak, то команды меню, начиная с этой, располагаются в новом — соседнем с прежним — столбце (при этом их принадлежность не изменяется). Если оно равно mbBarBreak, столбцы при этом разделяются вертикальной чертой. В Delphi все компоненты меню типа TMenuItem могут быть как простыми командами, так и подменю, имеющими собственный список пунктов. В случае, если у компонента дочерних подменю и пунктов нет, для него имеет смысл свойство: (Ro) property Command: Word; Прежде при написании меню нужно было выдумывать и присваивать уникальный идентификатор каждому его пункту — то есть собственно код команды, который посылался в качестве параметра сообщения WM.COMMAND. Сейчас эта задача возложена на Delphi — программист не только не определяет, но может и не знать кода команды, связанного с этим пунктом меню. Система выберет уникальное значение автоматически и присвоит это значение свойству Command. Изменять его не разрешается. Интерпретация сообщений меню скрыта от программиста. Система сама определяет, какой пункт выбран, и вызывает его обработчик события OnClick. Если для каких-то целей понадобился код команды, можно воспользоваться вышеуказанным
27
свойством (см. пример в описании компонента TMenu). Если у компонента TMenuItem имеются дочерние пункты, то он является подменю, имеет статус MF_POPUP и дескриптор меню Windows, доступный для чтения через свойство: (RO) property Handle: HMENU; Пункты меню иерархически связаны. Методы и свойства для работы с дочерними пунктами меню приведены в таблице. (До) property Parent: TMenuItem; Содержит указатель на родительское подменю. (Ro) property I terns[Index: Integer]: TMenuItem; fRo) property Count: Integer;
Содержит список дочерних пунктов меню.
procedure Insert(Index: Integer; Item: TMenuItem) ;
Вставляет пункт Item в меню на место Index. Поскольку структура меню строго иерархическая, вставляемый пункт не должен быть частью другого меню (его свойство Parent должно быть равно nil).
procedure Delete(Index: Integer) ;
Удаляет пункт с индексом Index из меню.
function IndexOf(Item: TMenuItem): Integer; procedure Add(Item: TMenuItem);
Возвращает индекс пункта Item.
Содержит количество дочерних пунктов меню.
Добавляет пункт Item в конец меню.
procedure Remove(Item: TMenuItem); Удаляет пункт Item из меню. Если пункт меню находится в фокусе, нажатие вызовет систему помощи с контекстом, определенным в свойстве: (Pb) property HelpContext: THelpContext; Свойство (Pb) property Grouplndex: Byte; используется при объединении нескольких меню между собой. Подробное объяснение его назначения см. в описании компонента TMainMenu. Компонент TMenu TObject—”TPersistent->TComponent->TMenu Модуль MENUS В Палитру компонентов не входит Этот компонент отсутствует в Палитре компонентов, но содержит методы и свойства, приведенные в таблице, общие для двух потомков — TMainMenu и TPopupMenu, которые туда входят. (Ro) property Handle: HMENU; Содержит дескриптор меню. property WindowHandle: HWND; Содержит дескриптор окна Windows (формы или оконного элемента управления), с которым связано меню. (Ro) property Items: TMenuItem;
Содержит список элементов меню. На самом верху иерархии меню есть единственный элемент (со статусом MFPOPUP), чей список и используется.
function DispatchCoinn>and(ACommand: Word): Boolean; function DispatchPopupfAHandle: HMENU): Boolean;
Отыскивает пункт меню с заданной командой, в случае успеха вызывает его метод Click и возвращает True. Работает как Di spatchCommand, но отыскивает пункт меню с дескриптором AHandle.
28
function Findltem(Value: Word; Kind: TFindItemKind): TMenuItem; TFindItemKind = (fkComrriand, fkHandle, fkShortCut);
Возвращает указатель на объект, соответствующий заданному пункту меню. Параметр Value должен содержать величину, которая интерпретируется в соответствии с одним из трех способов поиска (по команде, дескриптору или комбинации горячих клавиш).
function GetHelpContext(Value: Word; ByComniand: Boolean) : THelpContext;
Возвращает значение контекста помощи элемента меню. Если параметр ByCommand установлен в True, Value содержит связанную с пунктом команду, в противном случае — дескриптор. Если контекст у пункта отсутствует (равен 0), то отыскивается ближайший родительский ненулевой контекст. Если и он не найден, возвращается 0.
function IsShortCut(var Message: TWMKey): Boolean;
Определяет, какая комбинация горячих клавиш ShortCut нажата, и отыскивает соответствующий пункт меню. Если пункт с таким значением ShortCut найден, и его метод Click отработал успешно, метод возвращает True.
В следующем примере метод обрабатывает сообщение Windows WM_MENUSELECT, которое возникает при перемещении фокуса между пунктами меню. В зависимости от типа пункта показывается его дескриптор или команда: procedure TFormI.wmMenuSelect(var Msg :TWMMenuSelect) ; var Anitem : TMenuItem; begin with Msg do begin if (MenuFlag and MF_POPUP <>0 ) then begin Anitem := Formi.Menu.Findltem(Idltem, fkHandle); if Anitemonil then Labell .Caption := 'Handle:'+IntToHex(Anitem.Handle,4) ; end else begin Anitem := Formi.Menu.Findltem(Idltem, fkCommand); if Anitemonil then Labell .Caption := 'Command:'+IntToHex(Anitem.Command,4) ; end; end; inherited; end; Компонент TMainMenu
TObj ect—”TPersi stent-”TCornponent->TMenu->TMainMenu Модуль MENUS Страница Палитры компонентов Standard Этот компонент представляет главное меню формы и наследует все методы и свойства TMenu. Особенность его в том, что в нем реализован сложный механизм объединения меню. Это необходимо по следующим причинам: • Если в приложении имеется несколько форм со своими меню, то для упрощения работы целесообразно соединить их в одно и управлять меню из главной формы. • Объединение меню нужно при работе с интерфейсом MDI и его подокнами.
29
• Механизм объединения меню используется серверами OLE, запускаемыми по месту нахождения объекта OLE. Загружаясь, сервер дописывает осуществляемые им операции к меню другого приложения. Для того чтобы реализовать объединение меню, у тех форм, меню которых будут присоединены к главному, установите в True свойство: (Р1э) property AutoMerge: Boolean; При этом у главного меню оно должно оставаться равным False, иначе главное меню будет вообще невидимым. Объединение будет происходить автоматически при активизации новых форм или серверов OLE. Кроме автоматического режима, объединение меню можно выполнить при вызове метода: procedure Merge(Menu: TMainMenu); Присоединяемое меню при необходимости может быть легко отсоединено вызовом метода: procedure Unmerge(Menu: TMainMenu) ; При установленном в True свойстве AutoMerge ссылка на присоединенное меню будет сохраняться в специальном поле компонента и отсоединяться в нужных случаях автоматически (например, при закрытии формы, которой оно принадлежит). Объединение меню происходит по специальным правилам, в основе которых лежит использование группового индекса (свойства Group Index) каждого объекта TMenuItem. У пунктов меню одного уровня, в частности всех подменю верхнего уровня в главном меню, свойство Grouplndex является неубывающим, т. е. у последующего пункта групповой индекс больше либо равен индексу предыдущего. Это требование отслеживается как на этапе разработки, так и на этапе исполнения. Например, пусть пункты меню имеют индексы О, 3, 4, 5, 6. Если вы включите пункт меню с индексом 5 между пунктами с индексами 0 и 3, то 3 и 4 будут изменены на 5. А вот изменить большее значение Х на меньшее Y, если впереди есть пункты с индексом, большим Y, невозможно. Если в этом примере попытаться изменить индекс 6 на 4, то это приведет к возникновению исключительной ситуации EMenuError. Для обычных форм объединение происходит только на верхнем уровне в главном меню во время их активизации. В объединенном меню все подменю будут располагаться по возрастанию номера группового индекса, при этом: • если в присоединяемом меню есть пункты с таким же групповым индексом, что и в исходном, то все их множество заменяет все множество таких пунктов в исходном меню; • все пункты присоединяемого меню, групповой индекс которых не встречается в исходном, добавляются к нему и вставляются на соответствующие их индексу места. К окнам интерфейса MDI все сказанное относится только при запуске приложения. Если в формах приложения со стилем fsMDIChild есть свои главные меню, то в этот момент они автоматически сольются с главным меню формы fsMDIForm независимо от состояния AutoMerge. На уровне работы с серверами OLE предусмотрены дополнительные возможности по объединению меню. Если в компонент TOLEContainer загружен объект OLE, то в конец подменю Edit обычно добавляется подменю, из которого можно вызвать функции открытия и редактирования этого объекта. После активизации сервера он может не только вставлять свои подменю в главное, но и добавлять новые пункты к уже существующим подменю. Три метода TMainMenu используются для работы с меню OLE: procedure PopulateOle2Menu(SharedMenu: HMenu; Groups:array of Integer; var Widths: array of Longint); procedure GetOle2AcceleratorTable(var hAccel : THandle; var numAccels: Word; Groups: array of Integer) ; procedure Set01e2MenuHandle(Handle: HMENU); Конструктор меню Delphi поможет значительно упростить разработку меню. В нем имеются готовые шаблоны типовых подменю верхнего уровня: File, Edit и др; пример их использования см. в проекте DEMOEDIT на дискете, прилагаемой к книге. Можно также определить свои шаблоны. Компонент TPopupMenu
30
TObject-”TPersistent—”TComponent->TMenu->TPopupMenu Модуль MENUS Страница Палитры компонентов Standard Этот компонент описывает всплывающее меню. В отличие от главного, собственное меню такого типа может быть почти у каждого оконного элемента управления на форме (кроме переключателей), а также у самой формы. Всплывающее меню обычно связывают с нажатием правой кнопки мьппи. Чтобы это правило соблюдалось, нужно установить в True свойство (Pb) property AutoPopup: Boolean; Для вызова этого меню из программы используйте метод: procedure Popup(X, Y: Integer); который показывает его, помещая в точку экрана (X,Y) точку панели меню, определенную свойством: (Pb) property Alignment: TPopupAlignment; TPopupAlignment = (paLeft, paRight, paCenter) ; В зависимости от его значения в точке щелчка появится: paLeft — левый верхний угол; paRight — правый верхний угол; paCenter — середина верхней стороны. Разумеется, если нажатие произошло в самом низу экрана и развернуться вниз невозможно, меню автоматически будет сдвинуто вверх. При вызове всплывающего меню перед его показом на экране программист извещается событием: (Pb) property OnPopup: TNotifyEvent; Одно и то же всплывающее меню могут разделять несколько компонентов. Свойство property PopupComponent: TComponent ; показывает, который из них инициировал меню (на котором был щелчок правой кнопки мыши). Если меню вызвано из метода Popup, значение этого свойства не определено, и присвоить ему значение должен сам программист. Контекст помощи всплывающего меню задается свойством: (Pb) property HelpContext: THelpContext; Функции для работы с меню Рассмотрим также описанные в модуле Menus полезные функции для управления меню. Четыре функции предназначены для преобразования типа TShortCut, представляющего горячую комбинацию клавиш: а) в символ+состояние специальных клавиш и обратно: procedure ShortCutToKey(Shortcut: TShortCut; var Key: Word; var Shift: TShiftState); function Shortcut(Key: Word; Shift: TShiftState): TShortCut; б) в текст и обратно: function ShortCutToText(Shortcut: TShortCut): string; function TextToShortCut(Text: string): TShortCut; Новые меню и их составные части можно создавать, пользуясь функциями: function NewMenu(Owner: TCompo- Создает новое главное меню с именем AName и пунктами Items, nent; const AName: string; Items: array которое будет принадлежать владельцу Owner. of TMenuItem): TMairiMenu; function NewPopupMenu(Owner: TCoiriponent; const AName: string; Alignment: TPopupAlignment; AutoPopup: Boolean; Items: array of TMenuItem): TPopupMenu;
Создает новое всплывающее меню. Назначение дополнительных параметров см. описание TPopupMenu.
31
function NewSubMenu(const ACaption: string; hCtx: Word; const AName: string; Items: array of TMenuItem): TMenuItem; function Newltemfconst ACaption: string; AShortCut: TShortCut; AChecked, AEnabled: Boolean; AOnClick: TNotifyEvent; hCtx: Word; const AName: string): function NewLine: TMenuItem;
Создает новое подменю. Здесь AName — имя объекта, ACaption — его текст, hCtx — контекст системы помощи. Создает новый объект типа TMenuItem. Параметры метода соответствуют свойствам класса.
Создает новый элемент меню типа разделитель (TMenuItem с Caption = '-').
Все функции в случае успешного завершения возвращают указатель на созданный объект.
3.2. Работа с кнопками Группа элементов управления-кнопок в VCL велика и разнообразна. Здесь и стандартные кнопки Windows, и популярные в последнее время кнопки с картинками, и специальные кнопки для работы в модальных формах (диалогах), и даже группы кнопок. Многие из них имеют одинаковые свойства, которые мы и рассмотрим сначала. Основным событием, связанным с нажатием кнопки (любым способом) является: (Pb) property OnClick: TNotifyEvent; Под "любым способом" здесь подразумевается щелчок мышью, нажатие комбинации клавиши — акселератора, нажатие <Enter> или <Esc> (только для некоторых кнопок) или вызов метода Click из программы. Текст кнопки, появляющийся на ее поверхности, определен в свойстве: (Pb) property Caption: string; Если в составе текста есть амперсанд ('&'), то следующий за ним символ используется в акселераторе, и нажатие комбинации клавиш <АН>+<символ> вызывает нажатие кнопки. Водораздел среди кнопок проходит по тому, что именно означает нажатие. Ряд кнопок — TRadioButton, TCheckBox и группа TRadioGroup — предназначен для ввода или переключения фиксированных параметров, и программиста более интересует их состояние в некоторый момент времени, чем сам факт нажатия. Кнопки TButton, TSpinButton и TBitBtn напротив, как правило, своим нажатием инициируют немедленные действия. Кнопка TSpeedButton может успешно служить для решения обеих задач. Как уже было сказано, нажатие влечет за собой OnClick. Но это не означает, что всегда нужно писать код для обработки всех нажатий. Например, исключением из этого правила является использование кнопок в модальных формах. Модальная форма, или модальный диалог — специальная разновидность окон Windows (и форм Delphi), предназначенная для ввода пользователем необходимых программе данных или выбора одного ответа из нескольких вариантов. Обычно при этом ему предоставляется несколько кнопок, соответствующих вариантам. Вплоть до получения ответа в модальном диалоге пользователь не может переключиться на другие окна той же задачи, а в системном модальном диалоге — и на другие задачи. Для использования в таких случаях предназначены стандартная кнопка Windows TButton и ее младшая сестра TBitBtn, отличающаяся только наличием картинки на ее поверхности. При их нажатии значение свойства ModalResult кнопки передается одноименному свойству формы, где такое изменение означает вывод формы из модального состояния и ее закрытие: (Pb) property ModalResult: TModalResult; TModalResult = Low(Integer) ..High(Integer); В модальной форме могут быть предусмотрены две специальные кнопки, соответствующие положительному и отрицательному решениям. Одна из них срабатывает при нажатии на клавиатуре <Enter>, другая — <Esc>. Любая кнопка может получить такой статус, установив в True соответственно
32
одно из свойств: (Pb) property Default: Boolean; (Pb) property Cancel: Boolean; У двух рассмотренных кнопок результат, который при нажатии кнопки передается модальной форме, будет соответственно равен mrOk и mrCancel. Ниже рассмотрим имеющиеся в распоряжении программиста варианты кнопок. Описанные выше свойства перечисляются, но не комментируются. Компонент TButton TObject-”TPersistent->TCornponent->TControl->TWinControl-> -*TButtonControl—>TButton Модуль STDCTRLS Страница Палитры компонентов Standard Обычная кнопка Windows. В этом компоненте опубликованы только приведенные выше свойства Default, Cancel, Caption, ModalResult и OnClick. Двойной щелчок на кнопке не предусмотрен.
Компонент TRadioGroup
TObject->TPersistent->TComponent-”TControl-”TWinControl-”TCustomControl-> -”TCustomGroupBox—”TCustomRadioGroup-”TRadioGroup Модуль EXTCTRLS Страница Палитры компонентов Standard Готовая группа радиокнопок, содержащая все средства для управления ими. Каждая радиокнопка в группе наследует все свойства TRadioButton. Радиокнопки могут располагаться в несколько столбцов. Свойство (Pb) property Columns: Integer; устанавливает число столбцов с радиокнопками. Оно не должно превышать 16. Индекс нажатой радиокнопки в группе определяется свойством: (Pb) property Itemlndex: Integer; Индекс исчисляется от 0. Если он равен -1, mi одна радиокнопка в группе не нажата. Набор строк с заголовками радиокнопок содержится в свойстве: (Pb) property Items: TStrings; Все изменения этого свойства — добавление, удаление, переименование и т. п. — немедленно отражаются на радиокнопках в составе группы. Но доступ к методам и свойствам каждой радиокнопки пользователь компонента получить не может, т. к. сами объекты-радиокнопки содержатся в отдельном скрытом списке. Компонент TSpinButton TObject->TPersistent—”TComponent-”'TControl-”TWinControl-”TSpeenButton Модуль SPIN Страница Палитры компонентов Samples Пара кнопок с двумя противоположно направленными стрелками, предназначенная для увеличения или уменьшения какой-то величины нажатием. Компонент не имеет своего заголовка. Рисунки на кнопках по умолчанию представляют собой треугольники, указывающие вверх и вниз. Их можно изменить, используя свойства: (Pb) property DownGlyph: TBitmap; (Pb) property UpGlyph: TBitmap; Кнопка не имеет события OnClick. При нажатии нижней и верхней кнопок соответственно возникают события: J property OnDownClick: TNotifyEvent;
33
property OnUpClick: TNotifyEvent; Этот компонент может работать в паре с другими, например, редактором (см. компонент TSpinEdit). В этом случае, получая фокус, он передает его "напарнику", указатель на который содержится в свойстве: (Pb) property FocusControl: TWinControl;
3.3. Ввод и редактирование текста В Палитру компонентов входят три компонента, позволяющие вводить и редактировать текст (далее — редактирующие элементы). На базе стандартного редактирующего элемента управления Windows построены два основных компонента — строка ввода TEdit и многострочный редактор TMemo. На базе первого из них для ввода данных по шаблону создан третий компонент — TMaskEdit. В начале раздела опишем компонент TCustomEdit. Хотя вы не найдете его в Палитре компонентов, он является общим предком для трех доступных вам редактирующих элементов. Поэтому здесь рассмотрим только его свойства, общие для всех трех. В отличие от других визуальных компонентов, у перечисленных в этой группе текст содержится не в свойстве Caption, а в свойстве Text: property Text: TCaption; TCaption = string[255] ; Часть текста может быть выделена. Свойства property SelStart: Integer; property SelLength: Integer; определяют начало и длину выделенного в редакторе текста (измеряемые в количестве символов). Сам выделенный текст содержится в строке, определяемой свойством: property SelText: string; Метод procedure ClearSelection; исключает из текста весь выделенный фрагмент, а метод procedure SelectAll; выделяет весь текст в редакторе. Доступны также рабочие методы по чтению/записи выделенного текста, которые используются свойством SelText: function GetSelTextBuf(Buffer: PChar; BufSize: Integer): Integer-procedure SetSelTextBuf(Buffer: PChar); Они могут быть полезны для получения текста типа pChar, который применяется в функциях API Windows. Текст можно передавать и принимать из буфера обмена Windows — для этого предназначены три следующих метода. Если в окне редактора выделен текст, то передается (заменяется) именно он. В противном случае в операции участвует весь текст: procedure CopyToClipboard; procedure CutToClipboard; procedure PasteFromClipboard; Очистить весь текст в редакторе можно при помощи метода: procedure Clear; Наконец, свойство property Modified: Boolean; устанавливает, изменялся ли текст в процессе редактирования. Компонент TEdit
TObject-^TPersistent^TComponent-”TControl->TWinControl-”TCustomEdit-”TEdit Модуль STDCTRLS Страница Палитры компонентов Standard Этот компонент не содержит собственного кода, в нем только опубликованы свойства его предка TCustomEdit. Он представляет собой редактируемую строку (далее — просто редактор). Стиль обрамления этого компонента (Pb) property BorderStyle: TBorderStyle;
34
по умолчанию равен bsSingle. Если свойство (Pb) property AutoSize: Boolean; равно True, компонент изменяет свою высоту в зависимости от размера шрифта (свойство Font). Для того чтобы изменения имели место, должен еще быть установлен стиль обрамления bsSingle. Вводимый в редактор текст может автоматически подвергаться некоторым преобразованиям. Преобразование, задаваемое свойством CharCase, позволяет автоматически преобразовывать все символы только в верхний или только в нижний регистр: (Pb) property CharCase: TEditCharCase; TEditCharCase = (ecNormal, ecUpperCase, ecLowerCase) ; По умолчанию установлен стиль ecNormal и преобразования не происходит. Аналогично, свойство (Pb) property OEMConvert: Boolean; определяет необходимость автоматического преобразования вводимых символов из кодировки OEM в ANSI и обратно. Такое свойство часто бывает нужно при обработке текста в кодировке MSDOS. Два следующих свойства описывают поведение выделенного текста при переходе фокуса. Первое из них (Pb) property AutoSelect: Boolean; описывает реакцию редактирующего элемента при получении фокуса. Если оно установлено в True (по умолчанию это так), то при получении фокуса ввода весь текст в нем выделяется независимо от состояния свойства SelText. Если AutoSelect установлено в False, то при получении фокуса выделяется лишь то, что было выделено до его утери. После утери фокуса редактором выделенный в нем текст обычно теряет цветовое выделение. Чтобы оно оставалось, установите в False второе свойство: (Pb) property HideSelection: Boolean; На длину текста может быть наложено ограничение. Свойство (Pb) property MaxLength: Integer; определяет максимальную длину текста редактора в символах. Если значение этого свойства равно 0, то ограничений на длину текста нет. Свойство PasswordChar предназначено для ввода пароля с использованием редактора: (Pb) property PasswordChar: Char; Его значение — это символ, используемый для отображения вместо любых вводимых символов. Можно запретить возможность редактирования текста. Если значение свойства: (Pb) property Readonly: Boolean; равно True, текст изменить нельзя. Вы можете отследить изменения текста в редакторе, обрабатывая поступающие события: (Pb) property OnChange: TNotifyEvent; Компонент ТМеmo
TObject->TPersistent->TComponent->TControl->TWinControl->TCustomEdit-> —”TCu s t omMerno-”TMemo Модуль STDCTRLS Страница Палитры компонентов Standard Компонент представляет собой многострочный редактор текста. Содержимое редактора представлено как объект, содержащий текст в виде набора строк: (Pb) property Lines: TStrings; Текст в редакторе может выравниваться по левому, правому краям и по центру: (Р1э) property Alignment: TAlignment; TAlignment = (taLeftJustify, taRightJustify, taCenter) ; При наборе текста пользователь может ввести различные управляющие символы, в частности,
35
клавишами <Enter> и <ТаЬ>. Эти символы могут быть обработаны редактором, а могут быть сразу переданы форме. В случае, если свойства § property WantReturns: Boolean; property WantTabs: Boolean; обращены в True, символы передаются редактору. Обратим внимание на то, что если установлено WantTabs, то с помощью клавиатуры передать фокус такому редактору можно, а после этого отдать другому компоненту — нельзя. Если свойства равны False, символы передаются форме. В этом случае для ввода этих символов в редактор можно воспользоваться комбинациями +<Enter> и + соответственно. Два свойства отвечают за организацию прокрутки текста в окне редактора: (Pb) property Wordwrap: Boolean ; — отвечает за поведение редактора при достижении правой границы во время набора текста. Если свойство равно True, то при этом происходит переход на новую строку. В случае False при достижении правой границы происходит горизонтальная прокрутка текста и пользователь может продолжать набор; на новую строку можно перейти, нажав <Enter>; (Pb) property ScrollBars: TScrollStyle; TScrollStyle = (ssNone, ssHorizontal, ssVertical, ssBoth) ; — устанавливает наличие полос прокрутки в вертикальном и горизонтальном направлениях. Если есть горизонтальная полоса, то свойство Wordwrap теряет смысл: вместо переноса происходит прокрутка. Следующие свойства аналогичны определенным в TEdit — BorderStyle, HideSelection, MaxLength, OEMConvert и ReadOnly. Для получения полноценного приложения — текстового редактора, в него нужно включить компонент TMemo и снабдить средствами чтения, записи и печати файлов, поиска и замены текста и т. п. Такой редактор вы найдете в примере DEMOEDIT на дискете, прилагаемой к книге.
3.4. Ввод и выбор значений Общим для описанных в этом разделе компонентов является то, что с их помощью можно интерактивно выбрать или установить значения каких-то величин, которые доступны через их свойства и могут в нужный момент быть считаны. Для выбора эти компоненты предоставляют пользователю различные упрощающие приемы, поэтому "чистые" редакторы не рассматриваются в этой группе. Компонент TListBox
TObject—>TPer si stent—”TComponent—>TControl—>TWinControl—> —>TCustomListBox—>TListBox Модуль STDCTRLS Страница Палитры компонентов Standard Этот компонент соответствует списку выбора — стандартному элементу управления Windows. С его помощью пользователь может выбрать одну из строк, которые хранятся в свойстве: (Pb) property Items: TStrings; В списке Items. Strings хранится текст строк, а список Items.Objects пользователь может использовать для хранения связанных с ними объектов, например, картинок для нестандартно изображаемого списка. Индекс текущего (сфокусированного) элемента списка содержится в свойстве: property Itemlndex: Integer; Не путайте сфокусированный элемент (стандартно он помещается в рамку из точек) и выделенный (цветным фоном), они могут не совпадать. Смысл этих понятий будет объяснен ниже. Значение индекса Itemlndex лежит в диапазоне от 0 до Items.Count-1. Он доступен для чтения и записи. Индекс первого видимого элемента в списке представлен свойством: property Toplndex: Integer;
36
Он будет ненулевым в том случае, когда все элементы не помещаются в окне списка, и была сделана прокрутка. Список выбора имеет свою канву: (Ro) property Canvas: TCanvas; и на его поверхности можно рисовать. Когда в списке нужно предусмотреть выделение одновременно более одного элемента, оперируйте свойствами: (Pb) property MultiSelect: Boolean; (Pb) property ExtendedSelect: Boolean; Если MultiSelect установлено в False, то в списке одновременно не может быть выделено несколько элементов и значение ExtendedSelect не играет роли. В противном случае дело обстоит так. При ExtendedSelect = False каждый раз изменяется состояние только сфокусированного элемента. Каждый щелчок мышью или нажатие пробела меняет его состояние выделения на противоположное. Если ExtendedSelect = True, то выбор происходит при передвижении мыши с нажатой левой кнопкой, каждом щелчке мышью на новом элементе списка при нажатых или <Shift>, или при нажатии <Shift>+. Количество выделенных элементов можно узнать из свойства: (Ro) property SelCount: Integer; Проверку и установку выделения для каждого элемента можно провести, используя свойство: property Selected[Index: Integer]: Boolean; При задании ошибочного индекса при доступе к списку возникает исключительная ситуация EList Error. Чтобы расположить строки в алфавитном порядке, нужно установить в True свойство: (Pb) property Sorted: Boolean; Элементы списка могут появляться как в одном столбце — Друг под другом, так и в нескольких соседних. В этом случае список может иметь горизонтальную полосу прокрутки. Число столбцов определяется свойством: (Pb) property Columns: Integer; Если столбец один, то значение этого свойства равно 0. Очистить список можно при помощи метода: procedure Clear; Стиль обрамления компонента определяется свойством: (Pb) property BorderStyle: TBorderStyle; Найти индекс элемента, которьш содержит точку Pos, можно при помощи метода: function ItemAtPos(Pos: TPoint; Existing: Boolean): Integer; Параметр Existing определяет, что возвращается в случае неудачи (значение Items.Count либо 1). Прямоугольник, отведенньш элементу списка с индексом Index, определяется с помощью метода: function ItemRect(Index: Integer): TRect; При создании и визуализации списка система обычно подгоняет его высоту таким образом, чтобы в видимое поле помещалось целое число элементов. Это соответствует значению True свойства: (Pb) property IntegralHeight: Boolean; Если IntegralHeight равно False, то высота списка не изменяется. Это свойство не играет роли при стиле списка IbOwnerDrawVariable. Стиль списка может быть стандартным или определенным пользователем через свойство: (Pi-y property Style; TListBoxStyle = (IbStandard, IbOwnerDrawFixed, IbOwnerDrawVariable) ; Рассмотрим назначение этого свойства более подробно. В стандартном варианте (IbStandard) в списке отображаются только строки из свойства Items; в двух других случаях рисуемые пользователем списки могут иметь фиксированную (IbOwnerDrawFixed)
37
или переменную (IbOwnerDrawVariable) высоту элемента. В первом случае нужно задать свойство: (Pb) property ItemHeight: Integer; Для стиля IbOwnerDrawVariable высота каждого элемента определяется программистом, которьш должен предусмотреть обработку события: (Pb) property OnMeasureItem: TMeasureItemEvent; TMeasureItemEvent = procedure(ListBox: TListBox; Index: Integer; var Height: Integer) of object; Имея указатель на список, индекс измеряемого элемента и начальную высоту, необходимо переустановить Height так, чтобы в элементе поместилось все, что нужно в нем нарисовать. Для рисования каждого элемента инициируется событие: (Pb) property OnDrawItem: TDrawItemEvent; TDrawItemEVent = procedure(ListBox: TListBox; Index: Integer; Rect: TRect; State: TOwnerDrawState) of object; Обработчик этого события получает указатель на список ListBox, индекс элемента Index, отведенньш для рисования прямоугольник Rect, и состояние элемента в параметре State: TOwnerDrawState = set of (odSelected, odGrayed, odDisabled, odChecked, odFocused) ; Для списка выбора из этого множества действительны только флаги odSelected, odDisabled, odFocused. Компонент TComboBox
TObject—>TPersistent->TComponent—”TControl—”TWinControl—> —>TCustomCornboBox—>TComboBox Модуль STDCTRLS Страница Палитры компонентов Standard Этот стандартный элемент управления Windows — комбинированный список -— имеет много общего с TListBox. Он состоит из собственно списка и помещенного рядом поля ввода редактирующего элемента. Таким образом, пользователь может выбрать одно из готовых значений какой-то величины или ввести свое. Стиль компонента в части правил взаимного сочетания списка и редактора определяется свойством: (РЁ) property Style: TComboBoxStyle; TComboBoxStyle = (csDropDown, csSimple, csDropDownList, csOwnerDrawPixed, csOv'nerDrawVariable) ; Его значения имеют следующий смысл: csSimple — весь список виден все время. Текущий выбор отображается в редакторе, его можно изменить; csDropDown — список открывается (выпадает) и скрывается при нажатии кнопки, расположенной справа от текста. Такой список будем называть вьша дающим; csDropDownList — список вьшадающий, но редактирующий элемент заменен статическим текстом и вводить свои данные пользователь не может; csOwnerDrawFixed — определяемьш пользователем стиль списка с постоянной высотой элемента; csOwnerDrawVariable — определяемьш пользователем стиль списка с переменной высотой элемента. Принципы пользовательской отрисовки списка для csOwnerDrawFixed и csOwnerDrawVariable, а также связанные с этим методы и события остались такими же, как в TListBox. По умолчанию устанавливается сталь csDropDown. Организация списка также сходна с предыдущей. Приведенные свойства и методы имеют то же назначение: (Pb) property Items: TStrings; property Itemlndex: Integer; procedure Clear;
38
(Pb) property Sorted: Boolean; (RcS) property Canvas: TCanvas; Текст, содержащийся в редактирующем элементе списка, доступен через свойство: (р5) property Text: TCaption; Его максимальная длина ограничена числом символов, равным значению свойства: (Р1э) property MaxLength: Integer; Если MaxLength равно 0, то ограничений на длину строки нет (до 255 символов). При изменеюш текста (а также при смене выбранного элемента) возникает событие OnChange. Часть текста может быть выделена. Три свойства содержат выделенный текст, его положение и длину: property SelText: strings-property SelStart: Integers-property SelLength: Integer; Метод procedure SelectAll; выделяет весь текст. Значение свойства property DroppedDown: Boolean; соответствует состоянию списка: True соответствует открытому ("выпавшему") списку. С его помощью можно показывать или скрывать список из программы. При изменении состояния списка возникает событие: (Р}з\ property OnDropDown: TNotifyEvent ; Максимальное число элементов, которые показываются при выпадении списка: (Pb) property DropDownCount: integer; По умолчанию оно равно 8. В первой версии VCL это свойство описано, но не реализовано (не играет роли). Компонент TStringGrid TObject-”TPersistent—>TComponent-*TControl-”TWinControl-> —”TCustomControl”TCustomGrid-”TDrawGrid->TStringGrid Модуль GRIDS Страница Палитры компонентов Additional Этот компонент реализует возможности своего предка TDrawGrid применительно к таблице строк. В состав компонента добавлен объект класса TStrings, в котором хранится содержимое ячеек. Он доступен как векторное свойство — двумерный массив текстовых строк (размерностью ColCount x RowCount), соответствующих ячейкам таблицы: property Cells[ACol, ARow: Integer]: string; Доступен и двумерньш массив объектов, соответствующих ячейкам: property Objects[ACol, ARow: Integer]: TObject; Необходимо помнить, что самих объектов в таблице нет, и программист должен создавать, а по окончании использования таблицы удалять объекты самостоятельно. Можно работать отдельно как со строками, так и со столбцами таблицы. Свойства property Cols[Index: Integer]: TStrings; property Rows[Index: Integer]: TStrings; описывают наборы строк (также в виде TStrings), содержащие текст и объекты столбцов и строк таблицы. При значении True свойства DefaultDrawing для этого компонента происходит вывод строки в соответствующей ячейке. Так что если кроме текста ничего отображать не требуется, то менять значение DefaultDrawing и определять обработчик события OnDrawCell не нужно. Отметим, что перенос строк и столбцов таблицы (при установленных опциях goColMoving или goRowMoving) осуществляется вместе с их текстовыми строками. Компонент TOutline TObject->TPersistent—>TCoinponent—”TControl—”TWinControl-> —>TCustomControl>TCustomGrid->TCustomOutline-”TOutline Модуль OUTLINE Страница Палитры компонентов Additional
39
TOutline создан для ведения и отображения иерархических структур данных — деревьев. Это один из наиболее сложных и интересных компонентов. В этот раздел он попал потому, что является потомком таблицы (TCustomGrid), хотя и мало чем ее напоминает. Типичным примером применения TOutline можно назвать отображение структуры файловой системы (для этого даже есть отдельный компонент TDirectory Outline). В описанном ниже примере OBJTREE этот компонент используется для отображения дерева классов библиотеки VCL. Дерево состоит из элементов (будем называть их узлами), упорядоченных по уровням. Каждый из них имеет родительский узел (на более высоком уровне) и список дочерних узлов-потомков. Исключением являются корневой узел (нулевого уровня) — он не имеет предка, и узлы последнего уровня, не имеющие потомков. Каждый узел является объектом класса TOutlineNode: TOutlineNode = class(TPersistent) Рассмотрим сначала методы и свойства этого объекта. С каждым узлом можно связать информацию — имя и произвольные данные: property Text: string; property Data: Pointer; Указатель на родительский узел определяется свойством: (Ro) property Parent: TOutlineNode; Если список дочерних узлов не пуст, что можно проверить при помощи свойства (Ro) property Hasltems: Boolean; то для получения информации о них есть метода,!: function GetFirstChild: Longint; function GetLastChild: Longint; function GetNextChild(Value: Longint): Longing-function GetPrevChiId(Value: Longint): Longint; В последних двух методах параметр Value содержит индекс предыдущего найденного потомка. Индекс — это уникальный идентификатор узла в дереве, определенный свойством: (Ro) property Index: Longint; Используя его, можно получить указатель на узел (см. ниже сам компонент TOutline). Узлы пронумерованы таким образом, что если родитель имеет номер N, то первый из его потомков — номер (N+1). Можно узнать и полное имя (путь) узла, представляющее собой конкатенацию всех родительских имен и имени узла, разделенных специальным символом. Этот символ определен в свойстве ItemSeparator объекта TCustomOutline, которому принадлежат узлы. Полный путь определяется свойством: (Ro) property FullPath: string; Каждый узел расположен на своем иерархическом уровне. На самом верху находится узел уровня 0. Все другие являются для пего дочерними; он не виден и не доступен пользователю. Поэтому на первом доступном уровне — с номером 1 — могут находиться по вашему желанию один или несколько узлов. Уровень иерархии узла можно узнать в его свойстве: (RO) property Level: Cardinal; Чтобы изменить уровень, надо вызвать метод: procedure ChangeLevelBy(Value: TChangeRange); которьш перемещает узел в список другого уровня. Диапазон перемещения TChangeRange ограничен: TChangeRange = -1..1; Чтобы переместить узел в дереве, нужно вызвать следующий метод procedure MoveTo(Destination: Longint; AttachMode: TAttachMode); TAttachMode = (oaAdd, oaAddChild, oalnsert) ; Он перемещает узел (вместе со всеми потомками) в положение с индексом Destination. Родительский узел не может переместиться на место любого из своих потомков. Режим перемещения AttachMode означает: oaAdd — добавить последним на том же уровне, что и Destination; oaAddChild — добавить последним к потомкам узла Destination; oalnsert — заменить в положении Destination прежний узел, которьш смещается дальше по спи-
40
ску на том же уровне. Индекс того из предков узла, которьш находится в списке самого высокого уровня, известен из свойства: (Ro) property Topltem: Longint; Для корневого узла это свойство равно 0. Компонент TOutline не только хранит древовидную структуру, он еще и отображает ее. На экране каждый узел может находиться в двух состояниях, свер нутом и развернутом. В свернутом состоянии потомки узла не видны, в развернутом они изображаются чуть ниже и правее друг под другом. Состояние узла может иллюстрироваться значком (плюс/минус, открытая/закрытая папка — см. описание свойства OutlineStyle компонента TOutline). В таблице приведены методы и свойства, управляющие состоянием узла на экране: property Expanded: Boolean; Указывает, в каком состоянии находится узел; True — развернутое состояние. procedure Collapse; Сворачивает узел, пряча все дочерние узлы. procedure Expand; Разворачивает узел списка, показывая дочерние узлы. procedure FullExpand;
Полностью разворачивает узел, показывая все дочерние узлы всех подуровней.
Сворачивать/разворачивать узлы дерева, помимо щелчков мышью, можно нажатием клавиш: <+> — соответствует Expand; <-> — соответствует Collapse; <*> — соответствует FullExpand. Свойство (Ro) property IsVisible: Boolean; означает, может ли быть виден узел. Это возможно только в том случае, если виднь! все его родители. При отрисовке ширина узла сообщается методом: function GetDisplayWidth: Integer; Теперь перейдем от описания узла к описанию самого дерева — компонента TOutline. Он представляет собой совокупность узлов типа TOutlineNode. Всего в дереве содержится число узлов, равное значению свойства: (Ro) property ItemCount: Longint; К каждому из них можно обратиться, используя свойство: (Ro) property Items[Index: Longint]: TOutlineNode; На текущий (выделенный) узел можно сослаться через свойство: property Selectedltem: Longint; Например, выполнение оператора Items[Items[Selectedltem].Topltem].FullExpand; приведет в развернутое состояние ту часть дерева, в которой находится выделенный узел (начиная от его самого далекого предка Topltem). Свойство property Row: Longint; показывает, какая строка дерева в данный момент имеет фокус. Зная "содержимое" узла, то есть его имя или данные, можно найти его в дереве и узнать индекс. Для этого нужно вызвать один из методов, возвращающих его: function GetDataItem(Value: Pointer): Longint; function GetTextItem(const Value: string): Longint; Можно найти узел и по координатам точки в клиентской области компонента (на самом деле играет роль только координата Y) с помощью следующего метода: function GetItemfX, Y: Integer): LongInC; Целых три пары методов добавляют новый узел в дерево. Его имя инициализируется параметром Text. Различие между первым и вторым методами в парах в том, что второй позволяет связать с
41
узлом данные (параметр Data), а в первом вместо них записывается nil. function Add(Index: Longint; const Text: string): Longint; function AddObject(Index: Longint; const Text: string; const Data: Pointer): Longint; — добавляют новый узел в качестве последнего в тот же список, где находится узел Index. Поскольку на самом верху иерархии может быть только один узел, то в случае задания Index = 0 он добавляется в список верхнего уровня. Соответствуют режиму oaAdd; function AddChild(Index: Longint; const Text: string): Longint; function AddChildObject(Index: Longint; const To" : string; const Data: Pointer): Longint; — добавляют новый узел в качестве последнего потомка узла Index. Соответствуют режиму oaAddChild; function Insert(Index: Longint; const Text: string): Longint; function InsertObject(Index: Longint; const Text: string; const Data: Pointer): Longint; — вставляют узел в положение Index. При этом прежний узел с таким индексом и все другие на этом уровне сдвигаются вниз. Единственное исключение — задание в качестве параметра нулевого индекса. В этом случае узел добавляется последним в список самого верхнего уровня. Соответствуют режиму oalnsert. Все методы вызывают переиндексирование дерева и возвращают уже новый индекс для вставленного узла. Если нужно удалить узел, то метод procedure Delete(Index: Longint); удаляет узел с индексом Index, а метод procedure Clear; очищает все дерево. Если нужно вставить или удалить сразу много узлов и при этом избежать трудоемких операций переиндексации и перерисовки дерева, то соответствующий код надо заключить между вызовами методов: procedure BeginUpdate; procedure EndUpdate; Первый устанавливает специальное состояние обновления и на время запрещает все пересчеты и перерисовки, а второй сбрасывает его и обновляет дерево. Это состояние можно изменить и при помощи метода: procedure SetUpdateSCate(Value: Boolean); Дерево можно полностью свернуть или полностью развернуть вызовом методов: procedure FullExpand; procedure FullCollapse; Каждый узел, изменяя свое состояние, предупреждает об этом родительское дерево. В зависимости от того, свернулся он или развернулся, вызываются обработчики событий (pb) property OnExpand: EOutlineChange; (РЬ) property OnCollapse: EOutlineChange; EOutlineChange = procedure (Sender: TObject; Index: Longint) of object; где параметр Index означает индекс узла, измешшшего состояние. Метод function GetNodeDisplayWidth(Node: TOutlineNode): Integer; возвращает ширину, занимаемую изображением узла (если установленньш стиль не osOwnerDraw, см. ниже). Дерево может быть загружено и выгружено в поток и файл при помощи методов: procedure LoadFromFile(const FileName: strings-procedure LoadFromStream(Stream: TStream); procedure SaveToFile(const FileName: strings-procedure SaveToStream(Stream: TStream) ; Для прокрутки информации TOutline по умолчанию имеет обе полосы, определяемые свойством: (Pb) property ScrollBars: TScrollStyle; TScrollStyle = (ssNone, ssHorizontal, ssVertical, ssBoth); Строка (символ), разделяющий имена узлов при составлении полного имени узла, содержится в
42
свойстве: (Pb) property ItemSeparator: string; На стадии разработки можно набрать строки, соответствующие будущим узлам дерева, в специальном редакторе. При этом узел попадет на уровень, соответствующий количеству пробелов или знаков табуляции перед его названием. Набранные строки текста содержит свойство: (Pb) property Lines: TStrings; Это свойство предназначено для использования именно на этапе разработки, так как свойство Items в это время недоступно. Во время исполнения с информацией об узлах нужно работать, используя свойство Items. При рисовании дерева, помимо собственно текста, вместе с ним может изображаться одна из пиктограмм: Из) property PicturePlus: TBitmap; Соответствует свернутому узлу. По умолчанию — "плюс". (Pb) property PictureMinus: TBitmap;
Соответствует развернутому узлу. По умолчанию — "минус".
(Pb) property PictureOpen: TBitmap;
Соответствует развернутому узлу. По умолчанию — "открытая папка". Соответствует свернутому узлу. По умолчанию — "закрытая папка". Соответствует "листу" — узлу без потомков. По умолчанию — "документ".
(Pb) property PictureClosed: TBitmap; (Pb) property PictureLeaf: TBitmap;
Желательно, чтобы картинки имели "прозрачный" фон, то есть чтобы их свойство TransparentColor соответствовало цвету рабочей области компонента. Эти пиктограммы можно переопределить в случае необходимости. Свойство OutlineStyle определяет, в частности, когда и какие пиктограммы будут показаны: (Pb) property OutlineStyle: TOutlineStyle; TOutlineStyle = (osText, osPlusMinusText, osPictureText, osPlusMinusPictureText, osTreeText, osTreePictureText); В зависимости от значения этого свойства изображаются: osText — только текст; osPlusMinusText — текст и пиктограммы PicturePlus и PictureMinus в зависимости от состояния узла. "Лист" не имеет значка; osPictureText — текст и пиктограммы PictureOpen, PictureClosed и PictureLeaf в зависимости от состояния узла; osPlusMinusPictureText — объединяет в себе два предыдущих стиля; osTreeText — текст и специальные линии, иллюстрирующие связь между родительским и дочерними узлами; osTreePictureText — объединяет в себе стили osTreeText и osPictureText. По умолчанию установлен стиль osTreePictureText; На внешний вид дерева влияют и опции, содержащиеся в свойстве: (Pb) property Options: TOutlineOptions; TOutlineOption =- (ooDrawTreeRoot, ooDrawFocusRect, ooSCretchBitmaps) ; TOutlineOptions =- set of TOutlineOption; Это множество может содержать элементы: ooDrawTreeRoot — задает соединение линией всех узлов верхнего уровня. В случае отсутствия опции каждый из них выглядит как вершина отдельного дерева. Эта опция играет роль только для стилей osTreeText, osTreePictureText; ooDrawFocusRect — задает выделение сфокусированного узла рамкой из точек; ooStretchBitmaps — задает приведение размеров пиктограмм к размерам шрифта текста путем масштабирования. В противном случае либо из пиктограммы вырезается часть (если она больше), либо остается свободное место (если меньше). Рисование дерева по умолчанию осуществляется системой, но может быть возложено и на про-
43
граммиста. Определяется это свойством: (№) property Style: TOutlineType; TOutlineType = (otStandard, otOwnerDraw) ; Стиль osStandard подразумевает, что дта каждого узла будет изображено то, что предусмотрено стилем OutlineStyle. Для реализации стиля otOwnerDraw нужно нарисовать содержимое узла в обработчике события: (Pb) property OnDrawItern: TDrawItemEvent; TDrawItemEvent = procedure(ListBox: TListBox; Index: Integer; Rect: TRect; State: TOwnerDrawState) of object; Параметры: Index — индекс узла; Rect — отведенный ему прямоугольник; State — множество, в которое могут входить состояния odSelected, odFocused. Высота каждого узла постоянна и в этом случае определяется свойством: (Pb) property ItemHeight: Integer; Для рисования у компонента есть своя канва: property Canvas: TCanvas ; Установить ее можно только для объекта стиля osOwnerDraw; при osStandard канва игнорируется. Обрамление компонента задается свойством: (Pb) property BorderStyle: TBorderStyle; Разобраться с применением этого компонента поможет пример OBJTREE. В нем по заранее заданному массиву компонентов ClassesSet выстраивается иерархическое дерево, в которое добавляются и все объекты-предки. Для примера были выбраны только 32 компонента, при желании можно включить и все остальные. Не забывайте при этом добавить содержащие их модули к тем, что содержатся в операторе uses. Когда вы перемещаете фокус по дереву, отображается имя объекта и имя модуля, в котором он описан. Для этого используется информация, возвращаемая недокументированным (пока?) методом класса TObject.ClassInfo. Возвращаемая им структура описана в исходных текстах VCL.
3.5. Группирование компонентов По умолчанию родителем для большинства компонентов, размещенных на форме, является она сама. В ряде случаев, однако, есть необходимость отказаться от этого правила, как визуально, так и функционально подчеркнув обособленность каких-то элементов управления. Для этого вы можете использовать специальные группирующие компоненты TGroupBox и TPanel. Мы также отнесли в эту группу компонент TScrollBox, он также обычно имеет дочерние компоненты, которые все вместе подвергаются прокрутке. Общим признаком для всех группирующих компонентов является наличие опции csAcceptsControls в свойстве ControlStyle. Компонент TGroupBox
TObject—^TPersistent—”TCorr]ponent—”TControl—>TWinControl—> —>TCustomControl—>TCustomGroupBox—^TGroupBox Модуль STDCTRLS Страница Палитры компонентов Standard Единственное назначение этого компонента — служить предком (Parent) для других компонентов. Это играет роль тогда, когда они должны быть объединены в группу по какому-либо признаку. Типичный пример — радиокнопки. Когда одна из них нажимается, все остальные радиокнопки в группе (т. е. с одним значением свойства Parent) автоматически отпускаются. Поэтому, для того чтобы на форме иметь несколько независимых групп радиокнопок, их нужно поместить на разные объекты типа TGroupBox. (Кстати, именно для решения задачи с радиокнопками
44