Динамическое создание форм Delphi. Создание программ в среде delphi

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

Как правило, используются в виде предков TComponent, TControl, TWinControl, TGraphicControl, TCustomXXXXXX, а также все компоненты палитры компонентов.
Возьмем для примера компонент TOpenDialog, который находится на странице Dialogs палитры компонентов. Он хорошо справляется со своей задачей, но у него есть одно маленькое неудобство. Каждый раз, когда его используешь необходимо каждый раз изменять значение свойства Options. И причем это, как правило, одни и те же действия.
OpenDialog1.Options:= OpenDialog1.Options + ;

чтобы файл, который мы пытаемся открыть с помощью этого диалогового окна, действительно существовал на диске.
Задание для себя мы уже выбрали, осталось за малым - создать компонент. Заготовку для компонента создаем, выбирая из меню команду Component/New Component... и в диалоговом окне выбираем
Ancestor type: TOpenDialog
Class Name: TOurOpenDialog
Palette Page: Our Test
Нажали Ok и у нас появился шаблон нашего будущего компонента.

Переопределяем конструктор у этого компонента, т.е. в секции public вставляем строку:

constructor Create(AOwner: TComponent); override;

нажатие на этой строке Ctrl + Shift + C создает шаблон для этого метода, внутри которого мы вставляем такие строки:

inherited Create(AOwner); {Вызываем унаследованный конструктор}
Options:= Options + ; {Выполняем необходимые нам действия}

Обратите внимание: Комбинации клавиш Ctrl + Shift + стрелки вверх/вниз позволяют перемещаться между объявлением метода и его реализацией.

Установка созданного компонента Component/Install Component...
Install Into New Package
Package file name: C:Program FilesBorlandDelphi4LibOurTest.dpk
Package description: Our tested package

Вам не нравится, что у нашего компонента иконка такая же как у стандартного? Тогда создадим для него свою собственную.
Для этого нам необходимо вызвать Tools/Image Editor. Создаем новый *.dcr файл.
Вставляем в него рисунок Resource/New/Bitmap. Устанавливаем размер картинки 24x24 точек. А дальше - ваше творчество...
Обратите внимание: цвет точек, совпадающий с цветом точки в левом нижнем углу рисунка, будет считаться ПРОЗРАЧНЫМ!
После того как вы создали свой рисунок, переименуйте его из Bitmap1 в TOurOpenDialog и сохраните файл с именем OurOpenDialog.dcr.
Удалите компонент из пакета и установите его снова (только в этом случае добавится и ссылка на *.dcr файл).
Compile, Install и удачи!

unit OurOpenDialog; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs; type TOurOpenDialog = class (TOpenDialog) private { Private declarations } protected public { Public declarations } constructor Create(AOwner: TComponent); override ; published end ; procedure register ; implementation procedure register ; begin RegisterComponents("Samples", ); end ; { TOurOpenDialog } constructor TOurOpenDialog.Create(AOwner: TComponent); begin inherited Create(AOwner); {Вызываем унаследованный конструктор} Options:= Options + ; {Выполняем необходимые нам действия} end ; end .

Объявление компонента состоит из секций, таких как private, protected, public и published . Что они означают?
Это директивы видимости.
Все что объявлено в секции private , доступно только внутри модуля в котором объявлен класс (приватные объявления). Здесь как правило объявляются переменные, в которых хранятся значения свойств, а также методы (процедуры или функции) доступа к ним.
Все что объявлено в секции protected , доступно как и в секции private, а также наследникам данного класса (интерфейс разработчика).
Здесь можно объявить методы доступа к значениям свойств (если вы хотите позволить изменять эти методы потомкам вашего компенента),
а также свойства, методы и события (методы реакции на события) в компонентах типа TCustomXXX.
Все что объявлено в секции public , доступно любому пользователю компонента (интерфейс этапа выполнения).
Здесь объявляются, как правило методы. В секции published можно объявлять только свойства и события (они объявляются в виде свойств).
Они доступны во время проектирования приложения (интерфейс этапа проектирования).

Свойства

Свойства типа масив - обычные массива Object Pascal, но в отличии от последних могут индексироваться не только числовыми значениями но и строковыми. К сожалению этот тип свойства требует пользовательского редактора свойств (в инспекторе объектов редактор свойства имеет кнопку с тремя точками [...]), по-этому в указанном ниже примере свойство ArrayProp объявлено в секции public .

type TOurComponent = class (TComponent) private { Private declarations } FArrayProp: array of integer; function GetArrayProp(aIndex: integer): integer; procedure SetArrayProp(aIndex: integer; const Value: integer); protected { Protected declarations } public { Public declarations } property ArrayProp: integer read GetArrayProp write SetArrayProp; published { Published declarations } end ;

Спецификаторы свойств

Спецификатор default указывает сохранять значение свойства в файле формы или нет. Если значение свойства совпадает со значением default - значение в файле формы не сохраняется, если значения не равны - сохраняется. Это можно проверить, положив компонент на форму и выбрать правой кнопкой мыши пункт меню "View as Text". Default не устанавливает первоначальное значение свойства к указанному. Это необходимо сделать в конструкторе компонента.

unit OurComponent; interface uses Windows, SysUtils, Classes, Graphics, Forms, Controls; type TOurComponent = class (TComponent) private { Private declarations } FMyInteger: Integer; protected { Protected declarations } public { Public declarations } constructor Create(AOwner: TComponent); override ; published { Published declarations } property MyInteger: Integer read FMyInteger write FMyInteger default 10; end ; implementation constructor TOurComponent.Create(AOwner: TComponent); begin inherited Create(AOwner); FInteger:= 10; end ; end .

Спецификатор nodefault отменяет заданное по умолчанию значение свойства. Этот спецификатор, как правило, используется для отмены заданого по умолчанию значения унаследованного свойства.
Например: property AutoSize nodefault ;

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

property OneProp: integer read FOneProp write SetOneProp stored False; property TwoProp: integer read FTwoProp write SetTwoProp stored True; property ThreeProp: integer read FThreeProp write SetThreeProp stored Fuct;

И последнее:
Чтобы добавить картинку в компонент для демонстрации в панели компонентов надо: - создать ее размером 24*24 с именем файла.dcr (в ресурсе имя картинки равно имени компонента, заглавными буками)
- картинку положить рядом с компонентом.

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

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

Понятное дело, для начала нам нужно запустить сам Delphi. После того как перед нами открылось окно оболочки - создаем новый проект File | New | VCL Forms Application - Delphi .

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

Рисунок. Менеджер проекта, создаваемого приложения

В менеджере проектов появилось целое дерево. Давайте рассмотрим каждый пункт этого дерева.

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

Project1.exe - имя проекта (приложения). Когда мы создаем новое приложение, Delphi дает ему имя Project плюс порядковый номер.

Unit1.pas - модуль. Проект состоит из модулей. Каждое окно программы хранится в отдельном модуле, а мы видим на экране, что у нашего приложения есть окно, и именно оно находится в нем. Файлы с расширением pas содержат исходный код модуля. Имя файла такое же, как и у модуля.

Unit1.dfm - это визуальная форма. Она сохраняется в файле с таким же именем, как у и модуля, но с расширением dfm .

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

Но сейчас мы будем работать только с одним приложением, поэтому если вы создали два, то второе надо удалить. Для этого выделяем имя приложения и нажимаем на клавиатуре кнопку Delete. Перед нами появляется окно с подтверждением на удаление. Если нажать Yes (Да), то приложение будет удалено из группы проектов.

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

Давайте сохраним наше новое приложение. Для этого выберем из главного меню File | SaveAll . Перед нами откроется стандартное окно сохранения файла. Для начала Delphi запросит ввести имя модуля. По умолчанию указано текущее имя - Unit1.pas. Давайте изменим его на MainUnit и нажмем кнопку Save (Сохранить).

Здесь следует отметить, что нельзя вводить имена с пробелами или на русском языке. Если вы попытаетесь ввести нечто подобное, то произойдет ошибка, ваш файл не будет сохранен и придется заново повторять процедуру сохранения. Не забудьте выбрать папку, куда вы хотите сохранить модуль и проекты. Желательно, чтобы все файлы одного проекта хранились в одной папке, по крайней мере на начальном этапе, пока вы не наберетесь опыта.

После того как мы сохранили MainUnit, Delphi запросит у нас имя будущего проекта. Поскольку мы решили писать новое приложение под названием HelloDelphi , то здесь давайте и введем HelloDelphi. Имя проекта также как и имя модуля должно вводиться без пробелов и только латиницей. После того как ввели имя проекта, можем нажимать кнопку Save (Сохранить). Проект сохранится в файле под именем HelloDelphi.dpr . Когда мы захотим вновь открыть этот проект, то нам нужно открывать именно этот файл. Не нужно открывать файлы с расширением pas , потому что это всего лишь составляющая часть проекта. Открывать нужно файлы с расширением dpr .

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

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

Давайте теперь посмотрим, как изменился наш менеджер проектов. Как видите, имя проекта изменилось на HelloDelphi, а имя модуля на MainUnit.

Теперь давайте откроем папку, в которую мы сохранили наш проект, и посмотрим, какие файлы там присутствуют.

1. HelloDelphi.cfg - файлы с расширением cfg , содержат конфигурацию проекта.

2. HelloDelphi.dof - файлы с расширением dof , содержат опции проекта.

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

4. HelloDelphi.res - файлы с расширением res , содержат ресурсы проекта, например, такие как иконки, курсоры и др. По умолчанию Delphi помещает в этот файл только иконку, но это не значит, что вы не можете использовать файл для хранения других ресурсов.

5. MainUnit.pas - файлы с расширением pas , содержат исходный код модулей.

6. MainUnit.dfm - файлы с расширением dfm , содержат визуальную информацию о форме.

7. MainUnit.ddp - файлы с расширением ddp , определяют вспомогательные файлы модуля, например, диаграммы. Если вы не используете диаграммы, то можете удалять эти файлы, хотя они все равно будут генерироваться.

8. MainUnit.dcu - файл с расширением dcu , представляет откомпилированный модуль проекта в промежуточном формате. Когда компилируется программа, все модули компилируются в файлы формата DCU, а потом собираются в один и получается один исполняемый файл. Если модуль не изменялся с последней компиляции, то Delphi пропустит его, а во время сборки будет использовать существующий файл формата DCU, чтобы увеличить скорость компиляции. У вас пока не может быть этого файла, потому что вы еще не компилировали свой проект.

Файлы исходных кодов (с расширением pas) — это текстовые файлы, которые мы будем редактировать в редакторе кода. Файлы визуальных форм (с расширением dfm) создаются автоматически, и в них будет храниться информация о том, что находится на форме, а также настройки самой формы. Я открыл этот файл с помощью Блокнота, и вот что я там увидел:

object Form1: TForm1
Left = 0
Top = 0
Caption = "Form1"
ClientHeight = 213
ClientWidth = 455
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = "Tahoma"
Font.Style =
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
End

Первая строка идентифицирует объект, его имя и тип. Потом идет перечисление свойств объекта и значения, которые им присвоены. Описание свойств компонента заканчивается ключевым словом end .

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

А теперь давайте вернемся к нашей программе HelloDelphi, которую мы должны написать. Сначала посмотрим, что у нас уже есть. В принципе, мы ничего особого не сделали, но у нас уже есть готовое окно, которое можно превратить в программу. Для этого нужно скомпилировать проект. Чтобы скомпилировать проект, выбираем из меню пункт Project | Compile HelloDelphi , или нажимаем Ctrl+F9 . Если мы ничего не меняли в настройках Delphi, то у нас должно появится окно состояния компиляции.

Рисунок. Окно состояния компиляции в Delphi

Давайте разберемся, что показывает нам это окно.

В этом окне довольно хорошо отображается состояние компиляции. Интерес представляют три значения.

Hints - сообщения. Это простые сообщения, которые указывают на места, где можно улучшить код. Например, вы объявили переменную, но не пользовались ею. В этом случае появится соответствующее сообщение. Это, конечно же, не ошибка, и программа все же будет скомпилирована. Но благодаря этим сообщениям вы сможете увидеть, где была объявлена лишняя переменная или, возможно, просто что-то было забыто.

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

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

Как видим, никаких сообщений, предупреждений или ошибок у нас нет. И это понятно, ведь мы еще ничего не делали и не успели испортить созданную для нас заготовку. Программа скомпилирована. Просто нажимаем кнопку ОК, и это окно закрывается. Можно сделать так, чтобы окно автоматически исчезало по завершении компиляции, для этого нужно поставить галочку в Automatically close on successful compile (Автоматически закрывать после удачной компиляции), но я не рекомендую этого делать.

Теперь давайте вернемся в папку, где мы сохранили проект и посмотрим, что у нас изменилось. В этой папке появился исполняемый файл HelloDelphi.exe . Запустим его, и мы увидим пустое окно.

Давайте изменим заголовок нашей формы на HelloDelphi . Как известно, в Delphi все объекты, а значит, и окно программы тоже объект. Заголовок окна - это скорей всего свойство окна. Для того чтобы изменить это свойство, нужно перейти в объектный инспектор (вкладка Properties (Свойства)), найти свойство Caption (Заголовок) и ввести в его поле ввода слово HelloDelphi (сейчас у нас там написано Form1 и при запуске HelloDelphi.exe у нас появляется форма с заголовком Form1). После этого можно нажать Enter или просто перейти на другую строку с другим свойством.

После всего этого давайте запустим нашу программу. Для этого можно вновь скомпилировать ее и запустить файл. Однако на этот раз мы будем действовать иначе. Выбираем из меню Run (Выполнить) пункт Run (или нажимаем на клавиатуре клавишу F9). Delphi сразу откомпилирует и запустит готовую программу. Как видим, программирование не настолько страшно, как кажется на первый взгляд.

Рисунок. Объектный инспектор. Изменение свойства Caption

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

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

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

В состав проекта обязательно входят следующие элементы :

· основной файл проекта, имеющий расширение.DPR (D elphi PR oject ). В проекте может существовать только один файл с таким расширением. Это небольшой текстовый файл на языке Object Pascal , который содержит ссылки на все формы проекта и инициализирует приложение;

· файлы всех форм проекта. Для каждой формы формируется пара одноименных файлов – файл Pascal -модуля с обычным расширением.PAS и файл ресурсов формы с расширением.DFM (D elphi F orM ). Любая форма проекта всегда должна иметь свою пару файлов модуль-ресурс. Обратное не обязательно, т.е. проект может включать в себя модули и файлы ресурсов не относящиеся ни к одной из форм приложения;

· файл ресурсов приложения (*.RES). В нем содержатся ресурсы приложения, не вошедшие в формы;

файл опций проекта (*.DOF – D elphi O ptions F ile ). В этом файле сохраняются значения директив и опций компилятора, настроек компоновщика, названия рабочих каталогов, параметры командной строки запуска приложения.

Пример программы

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

Запускаем Delphi . Если загрузка прошла успешно, то на экране монитора мы увидим то, что представлено на рис.1. В строке заголовка главного окна Delphi присутствует надпись Delphi 3 – Project1 . Это название нашего проекта и программы. В строке заголовка окна формы приложения написано Form1 . Если нажать клавишу F12 или щелкнуть мышью на кнопку Toggle
Form/Unit
(панель быстрого доступа) или выбрать пункт меню View/Toggle Form/Unit , то окно редактора кода переместится на передний план и мы увидим содержимое страницы Unit1 редактора, в которой представлен Pascal -код модуля Unit1 . Добавим в окно редактора кода еще одну страницу, в которой расположится код основного программы проекта. Для этого выберем пункт меню View/Project Source . Ниже представлено содержимое двух страниц редактора кода: Pascal-код главной программы и Pascal-код модуля главной формы проекта, соответственно.

Сохраним файлы проекта в созданном директории PO_EVM . Для этого нажмем кнопку Save all на панели быстрого доступа или выберем пункт меню File/Save Project As… . В появившемся стандартном Windows- окне сохранения файлов выберем папку PO_EVM и последовательно сохраним Pascal -код модуля формы и Pascal -код программы в файлах с именами IDE_Un1.pas и IDE_Pr.dpr соответственно.

program IDE_Pr;

IDE_Un1 in ‘IDE_Un1.pas’ {Form1};

Application.Initialize;

Application.CreateForm(TForm1, Form1);

Application.Run;

unit IDE_Un1;

Windows, Messages, SysUtils,

lasses, Graphics, Controls,

TForm1 = class(TForm)

{ Private declarations }

{ Public declarations }

Если сейчас посмотреть содержимое папки PO_EVM , то в ней будут находиться следующие файлы проекта: IDE_Pr.dof – файл опций проекта; IDE_Pr.dpr – основной файл проекта; IDE_Pr.res – файл ресурсов проекта; IDE_Un1.pas – файл Pascal-кода модуля формы; IDE_Un1.dfm – файл ресурсов формы.

Желательно изменить некоторые стандартные настройки среды, в частности для того, чтобы на экране отображался процесс компиляции и компоновки программы, а также, чтобы перед каждым запуском программы на исполнение автоматически сохранялись все модифицированные файлы проекта. Сделать это можно в диалоговом окне Environment Options… , которое можно вызвать из меню Tools/ Environment Options… . На страничке Preferences в разделе Desktop contens установить переключатель в положение Desktop only , в разделе Autosave options установить выключатели в пунктах Editor files и Desktop , а в разделе Compiling and Running установить выключатель Show compiler progress .

Простейшая программа в Delphi уже готова к исполнению. Нажав кнопку Run на панели быстрого доступа или клавишу F9 , можно наблюдать процессы компиляции и компоновки программы, после которых начнет исполняться наша программа. Визуально программа представлена на экране пустым окном со стандартной Windows- строкой заголовка, содержащей пиктограмму приложения, наименование формы и три кнопки управления окном. Выход из программы и возврат в среду Delphi осуществляется стандартным для Windows способом – комбинацией клавиш Alt-F4 или нажатием на кнопку закрытия приложения. Если сейчас просмотреть содержимое папки файлов проекта PO_EVM , то можно заметить, что в ней появились еще два файла: IDE_Un1.dcu и IDE_Pr.exe – откомпилированный файл модуля формы и загрузочный (исполняемый) файл приложения. Файл IDE_Pr.exe можно запускать на исполнение автономно, т.е. вне среды Delphi . После выхода из среды Delphi , в папке образуется еще один файл IDE_Pr.dsk – файл с индивидуальными пользовательскими настройками среды.

Приведем примерную последовательность действий программиста, который создает программу “Калькулятор +/- “. Это, с позволения сказать, приложение предназначено для выполнения операций сложения и вычитания вещественных чисел. При запуске окно (форма) программы должно выглядеть как на рис. 7.

На форме расположены три строки ввода/вывода (компоненты типа TEdit ) для ввода двух операндов и вывода результата; пять кнопок (четыре компоненты типа TButton и одна – TBitBtn ) – сброс, указание типа и результата операции (C , + , , = ), выхода из программы (Close); три надписи для обозначения операндов и результата (компоненты типа TLabel ); разделительный интерфейсный элемент для выделения поля вывода результат счета (компонент TBevel ).

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

1. Свойство формы Caption в инспекторе объектов изменяем со значения Form1 на строковое значение Калькулятор +/- . Изменение значения названия формы сразу заметно в строке заголовка формы;

2. На странице Standard палитры компонентов щелкаем левой кнопкой мыши на изображении компонента Label , затем перемещаем указатель мыши в район левого верхнего угла формы и щелкаем левой кнопкой там. В этом месте на форме появится изображение компонента Label с надписью Label1 . Изображение компонента выделяется по периметру шестью черными квадратиками – маркерами изменения размеров (размерные маркеры). Выделение маркерами означает, что данный компонент сейчас является активным. С помощью мыши стандартными Windows- приемами можно изменять размеры компонента, перемещать его по форме. Для активизации другого компонента формы необходимо просто щелкнуть на нем левой кнопкой мыши. Содержимое закладок инспектора объектов всегда соответствует активному компоненту, при активизации другого компонента, состав полей инспектора объектов автоматически меняется. Изменим свойство Caption компонента Label со значения Label1 на значение 1-ый операнд . Свойству Name этого компонента придадим значение lb_1 .

3. Действуя аналогично, расположим второй компонент-метку немного ниже первой, задав, соответственно, свойствам Caption и Name значения 2-ой операнд и lb_2 .

4. На той же странице Standard палитры компонентов выберем компонент Edit (строка редактирования) и поместим его на форме правее первой метки. Свойству Name присвоим значение ed_1 , а свойство Text сделаем пустым.

5. Действуя аналогично, расположим вторую строку редактирования правее второй метки, задав, соответственно, свойствам Text и Name значения пустой строки и ed_2 .

6. На странице Additional палитры компонентов выберем компонент Bevel и поместим его так, чтобы он изображал “итоговую” черту под второй меткой и второй строкой ввода. Свойство Name, равное Bevel1 , изменять не будем.

7. Под компонентом Bevel разместим еще одну пару “метка – строка ввода” для вывода результата вычислений. Свойствам Name присвоим значения lb_3 и ed_3 , свойству lb_3.Caption – значение Результат , а свойству Text компонента ed_3 – значение пустой строки.

8. Поместим еще одну метку для изображения текущей арифметической операции: (?, +, –) - операция не определена, операция сложения, операция вычитания. Расположим эту метку между двумя первыми метками, ближе к левым границам компонентов для ввода операндов (см. рис.7). Свойству Name присвоим значение lb_oper , а свойству Caption – значение ? . Установим также подсвойство Size в свойстве Font для этой метки, равным 14 ;

9. Подравняем компоненты. Стандартным Windows- приемом выделим компоненты – метки. Для этого, держа нажатой кнопку Shift , последовательно щелкая левой кнопкой мыши по компонентам-меткам, активизируем одновременно все три метки. Если теперь щелкнуть правой кнопкой мыши, то по законам Windows 95 должно появиться контекстное меню – оно и появляется. Выберем в нем пункт A lign… (выравнивание). Теперь на экране мы видим окно Alignment . Выберем на панели Horizontal пункт Left sides и нажмем кнопку Ok . В результате этого все три компонента – метки выровняются по левой границе самой левой компоненты. Аналогичными действиями подравняем и все три строки редактирования. Строки редактирования можно выровнять и по размерам, выделив их одновременно все три и выбрав в контекстном меню пункт Size… . “Тонкую” работу по изменению размеров и перемещению компонент выполняют обычно не мышью, а клавишами управления курсором при нажатых, соответственно, сдвиговых клавишах Shift и Ctrl . Эти действия можно производить не только с одним компонентом, но и с выделенной группой компонентов.

10. Теперь расставим на форме управляющие кнопки. Их у нас пять (см. рис.7). Первые четыре кнопки – кнопка сброса, кнопки операций (сложение (+) и вычитание (–)) и кнопка результата. Пятая кнопка – кнопка завершения работы программы. На странице Standard палитры компонентов выберем компонент Button (кнопка) и поместим его правее первой строки редактирования. Свойствам Caption и Name кнопки присвоим соответственно значения C и btn_Clear . Аналогичным образом располагаем и три другие кнопки на форме, назвав (свойство Name ) их btn_sum, btn_sub и btn_rez , с наименованиями (свойство Caption ) + , и = (см. рис.7). Выделив кнопки в группу, дважды щелкнем на составном свойстве Font в инспекторе объектов. В поле Size свойства Font зададим размер шрифта 14 пунктов. После установки этого значения размер символов на кнопках увеличится.

11. Пятая кнопка – кнопка завершения программы – выбрана со страницы Additional . Это первый по порядку компонент на этой странице – кнопка типа BitBtn (командная кнопка с надписью и пиктограммой). Расположив кнопку на показано на рис.7, выберем из списка значений свойства Kind значение bkClose . Этим выбором полностью определяются визуальные атрибуты и функциональное назначение кнопки.

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

На этом заканчивается первый этап создания приложения – построения интерфейса. Необходимо отметить, что на любом этапе построения интерфейса можно в любой момент запустить программу на исполнение. В процессе выполнения вышеописанных шагов мы не написали ни одной строчки, ни одного Pascal-оператора. Все записи в тексте модуля формы приложения Delphi делает сама. К этому моменту в интерфейсной части модуля формы произошли изменения – добавились описания компонентов, помещенных нами в форму и стандартные модули Delphi – Buttons, StdCtrls, ExtCtrls.

Windows, Messages, SysUtils, Classes,

Graphics, Controls, Forms, Dialogs,

Buttons, StdCtrls, ExtCtrls;

TForm1 = class(TForm)

lb_Oper: TLabel;

btn_Clear: TButton;

btn_sum: TButton;

btn_sub: TButton;

btn_rez: TButton;

bb_Close: TBitBtn;

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

Сначала словесно оговорим то, что должно происходить при нажатии той или иной кнопки, в каких случаях и какого содержания сообщения должны появляться на экране:

1. Нажатие на кнопку “Очистить” (С ) - очистить все три строки редактирования и в качестве знака операции отобразить знак вопроса (?) ;

2. Нажатие на кнопку “Сложить” (+ ) - изменить изображение знака операции на знак плюс (+) ;

3. Нажатие на кнопку “Вычесть” () - изменить изображение знака операции на знак минус (–) ;

4. Нажатие на кнопку “Вычислить” (= ) - провести проверку на правильность данных в первых двух строках редактирования. Если данные правильные (числа), то провести соответствующую арифметическую операцию и вывести результат в строку вывода. Если обнаружена ошибка в исходных данных, то следует вывести сообщение об этом с указанием места ошибки.

Теперь переведём (транслируем) на Pascal-код каждый из вышеперечисленных пунктов. Каждый компонент имеет список событий (список приводится на второй странице (Events ) инспектора объектов), на которые он может реагировать (которые он может обрабатывать). Наша задача – написать код, который будет выполняться при возникновении того или иного события. Для кнопок в Delphi определены несколько обработчиков событий, нас будет интересовать обработчик события “Однократное нажатие на кнопку” (щелчок левой кнопкой мыши в момент нахождения указателя мыши над компонентом Button ). Этот обработчик называется OnClick . Каждый обработчик события оформляется в отдельную процедуру. Название этой процедуре формирует сама среда Delphi .

Напишем обработчик события однократного нажатия на кнопку “Очистить”. Для этого выделим на форме кнопку “Очистить” (С ). Активизируем страницу Events в окне инспектора объектов. Дважды щелкнем на пустом поле правого столбца рядом с надписью OnClick . После этого Delphi автоматически отображает окно редактора кода IDE_Un1 и помещает текстовый курсор для вставки текста внутри процедуры TForm1.btn_ClearClick:

procedure TForm1.btn_ClearClick(Sender: TObject);

как бы приглашая нас начать печатать с этого места Pascal -код этой процедуры. Обратите внимание на название процедуры, которое сформировала среда Delphi . Оно состоит из названия формы, на которой расположена компонента (кнопка), имени этой компоненты (btn_Clear) и названия обработчика события – Click . Следуя содержанию первого пункта нашего словесного алгоритма, вставим в тело процедуры следующие строки Pascal -кода:

lb_Oper.Caption:=’?’; {тип операции неопределен (метка – ?)}

ed_1.Text:=”; {очистить строку для ввода первого операнда}

ed_2.Text:=”; {очистить строку для ввода второго операнда}

ed_3.Text:=”; {очистить строку для вывода результата}

Действуя аналогично, сформируем обработчики события “Однократный щелчок левой кнопкой мыши на компоненте” для интерфейсных кнопок “Сложить” (+ ) и “Вычесть” ():

– для кнопки “Сложить” строка кода - lb_Oper.Caption:=’+’;

– для кнопки “Вычесть” строка кода - lb_Oper.Caption:=’-‘;

Обработчик события OnClick для кнопки “Вычислить” (= ) будет содержать следующий Pascal -код:

procedure TForm1.btn_rezClick(Sender: TObject);

Val(ed_1.Text,r1,i);

if i<>0 then begin

ShowMessage(‘Ошибка в первом операнде’);

Val(ed_2.Text,r2,i);

if i<>0 then begin

ShowMessage(‘Ошибка во втором операнде’);

case lb_oper.Caption of

‘+’ : ed_3.Text:=FloatToStr(r1+r2);

‘-‘ : ed_3.Text:=FloatToStr(r1-r2);

else ShowMessage(‘Не определен тип операции’);

В обработчике события TForm1.btn_rezClick введены две локальных вещественных переменных r1 и r2 для запоминания числовых значений двух операндов и целочисленная переменная i для использования в Pascal -процедуре Val преобразования строковой переменной в числовое представление. Этот обработчик реализует четвертый пункт словесного алгоритма работы программы. Сначала проверяется на правильность строка, введенных пользователем, символов первого операнда. Если это не число, то процедурой ShowMessage выводится соответствующее ситуации сообщение и по процедуре Exit заканчивается выполнение кода процедуры. В случае корректности данных переменная r1 примет числовое значение первого операнда. Аналогичным образом проверяется второй операнд и, если здесь все нормально, то переменная r2 примет числовое значение второго операнда.

Оператор Case реализует арифметическую операцию над переменными r1 и r2 в зависимости от того, какой символ (+ , , ? ) определяет значение свойства Caption метки lb_oper . Если знак арифметической операции не определен (у нас это символ ? ), то выдается соответствующее сообщение и операция не производится.

Следует заметить, что, как и положено по правилам Pascal , в интерфейсную часть модуля формы автоматически средой Delphi были добавлены заголовки процедур – обработчиков событий нажатий на клавиши:

procedure btn_sumClick(Sender: TObject);

procedure btn_ClearClick(Sender: TObject);

procedure btn_subClick(Sender: TObject);

procedure btn_rezClick(Sender: TObject);

На этом можно закончить программную реализацию задачи создания простейшего калькулятора, осуществляющего операции сложения и вычитания вещественных чисел. Отметим также, что наше приложение на 99% защищено от ошибок, связанных с операциями над некорректными исходными данными (а почему не 100% ?).

Все компоненты Delphi являются частью иерархии, которая называется Visual Component Library (VCL). Общим предком всех компонентов является класс TComponent (рис. 9.1.1), в котором собран минимальный набор общих для всех компонентов Delphi свойств.

Свойство ComponentState содержит набор значений, указывающих на текущее состояние компонента. Приведем некоторые значения свойства:

Класс TComponent вводит концепцию принадлежности. Каждый компонент имеет свойство Owner (владелец), ссылающееся на другой компонент как на своего владельца. В свою очередь, компоненту могут принадлежать другие компоненты, ссылки на которые хранятся в свойстве Components. Конструктор ком­понента принимает один параметр, который используется для задания владельца компонента. Если передаваемый владелец су­ществует, то новый компонент добавляется к списку Components владельца. Свойство Components обеспечивает автоматическое разрушение компонентов, принадлежащих владельцу. Свойст­во ComponentCount показывает количество принадлежащих компонентов, a Componentlndex - номер компонента в массиве Components.

В классе TComponent определено большое количество мето­дов. Наибольший интерес представляет метод Notification. Он вызывается всегда, когда компонент вставляется или удаляется из списка Components владельца. Владелец посылает уведомле­ние каждому члену списка Components. Этот метод переопределя­ется в порождаемых классах для того, чтобы обеспечить действи­тельность ссылок компонента на другие компоненты. Например, при удалении компонента Tablel с формы свойство DataSet компонента DataSourcel, равное Tablel, устанавливается в Nil.

Процесс разработки компонента включает пять этапов:

выбор класса-предка;

создание модуля компонента;

добавление в новый компонент свойств, методов и событий;

тестирование;

регистрацию компонента в среде Delphi;

9.1. Выбор класса-предка

На рис. 9.1.1 изображены базовые классы, формирующие структуру VCL. В самом верху расположен TObject, который является предком для всех классов в Object Pascal. От него про­исходит TPersistent, обеспечивающий методы, необходимые для создания потоковых объектов. Потоковый объект - объект, ко­торый может запоминаться в потоке. Поток представляет собой объект, способный хранить двоичные данные (файлы). Поскольку Delphi реализует файлы форм, используя потоки, то TComponent порождается от TPersistent, предоставляя всем компонентам способность сохраняться в файле формы.


Класс TComponent представляет собой вершину иерархии компонентов и является первым из четырех базовых классов, используемых для создания новых компонентов. Прямые по­томки TComponent - невизуальные компоненты.

9.1.1. Класс TControl

Вершину иерархии визуальных компонентов представляет класс TControl.

Класс TControl вводит понятие родительских элементов управ­ления (parent control). Свойство Parent является окном, кото­рое содержит элемент управления. Например, если компонент Panel 1 содержит Button 1, то свойство Parent компонента Button 1 равно Panel 1.

Свойство ControlStyle определяет различные стили, приме­нимые только к визуальным компонентам, например:

В классе TControl определено большинство свойств, использу­емых визуальными компонентами: свойства позиционирования (Align, Left, Top, Height, Width), свойства клиентской области (ClientHeight, ClientWidth), свойства внешнего вида (Color, Enabled, Font, ShowHint, Visible), строковые свойства (Caption, Name, Text, Hint), свойства мыши (Cursor, DragCursor, DragKind, DragMode).

Кроме того, класс TControl реализует методы диспетчеризации событий.

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

Оконные компоненты далее разбиваются на две категории. Прямые потомки TWinControl являются оболочками вокруг су­ществующих элементов управления, реализованных в Windows (например, TEdit, TButton, и др.) и, следовательно, знают, как себя рисовать.

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

9.1.2. Класс TGraphicControl

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

По умолчанию объекты TGraphicControl не имеют собствен­ного визуального отображения, но для наследников обеспечи­ваются виртуальный метод Paint (вызывается всегда, когда элемент управления должен быть нарисован) и свойство Canvas (используется как «поверхность» для рисования).

9.1.3. Класс TWinControl

Класс TWinControl используется как базовый для создания компонентов, инкапсулирующих соответствующие оконные эле­менты управления Windows, которые сами себя рисуют.

Класс TWinControl обеспечивает свойство Handle, являюще­еся ссылкой на идентификатор окна базового элемента управ­ления. Кроме этого свойства класс реализует свойства, методы и события, поддерживающие клавиатурные события и измене­ния фокуса:

Создание любого потомка этого класса начинается с вызова ме­тода CreateWnd, который вначале вызывает CreateParams для инициализации записи параметров создания окна, а затем вызыва­ет CreateWindowHandle для создания реального идентификатора окна, использующего запись параметров. Затем CreateWnd настра­ивает размеры окна и устанавливает шрифт элемента управления.

9.1.4. Класс TCustomControl

Класс TCustomControl представляет собой комбинацию клас­сов TWinControl и TGraphicControl. Являясь прямым потомком класса TWinControl, TCustomControl наследует способность управления идентификатором окна и всеми сопутствующими возможностями. Кроме этого, как и класс TGraphicControl, класс TCustomControl обеспечивает потомков виртуальным ме­тодом Paint, ассоциированным со свойством Canvas.

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

создание Windows-элемента управления (TWinControl);

создание графического элемента управления (TGraphic-Control);

создание нового элемента управления (TCustomControl); О создание невизуального компонента (TComponent).

9.2. Создание модуля компонента и тестового приложения

Определившись с выбором компонента, можно приступить к созданию модуля компонента. Для этого необходимо выпол­нить следующие шаги.

Выполните команду File/ New.../ Component или Component/ New Component.

В диалоговом окне New Component (рис. 9.2.1.) установите основные параметры создания компонента: Ancestor type (имя класса-предка), Class Name (имя класса компонента), Palette Page (вкладка палитры, на которой должен отображаться ком­понент) и Unit file name (имя модуля компонента).

После щелчка на кнопке ОК будет сгенерирован каркас но­вого класса.

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

Упражнение 9.2.1. Разработайте новый компонент, который объединяет компоненты TEdit и TLabel. Компонент Label рас­полагается выше поля редактирования (TEdit). При перемеще­нии поля редактирования TLabel следует за ним. При удалении поля редактирования TLabel также удаляется.

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

Выполните команду Component/ New component. Установите следующие значения параметров окна: Ancestor type TEdit

Class Name TLabelEdit

Palette Page Test

Unit file name ...\LabelEdit\LabelEdit.pas

Щелкните на кнопке ОК, автоматически будет сгенерирован следующий код:

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type

TLabelEdit = class(TEdit)

{ Private declarations }

{ Protected declarations }

{ Public declarations }

{ Published declarations }

procedure Register;

procedure Register;

RegisterComponents("Test", );

В модуле описан каркас нового класса и написана процедура регистрации компонента (Register), которая помещает его на страницу Test. Сохраните файл модуля компонента.

Разработка тестового приложения

Создайте новый проект. Сохраните его файлы в папке...\LabelEdit: файл модуля - под именем Main.pas, файл про­екта - Test Application, dpr.

Добавьте имя модуля разрабатываемого компонента в раздел Uses формы тестового приложения:

uses ..., LabelEdit;

В общедоступный раздел класса TForml добавьте поле

В обработчике события OnCreate формы динамически со­здайте новый компонент:

procedure TForml.FormCreate(Sender: TObject);

le:=TLabelEdit.Create(Self);

Сохраните файлы проекта.

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

9.3. Добавление свойств, методов и событий

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

Добавление свойства происходит в три этапа.

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

2. Описание и разработка методов доступа к значению свойства.

3. Описание свойства.

В классе TControl свойства Caption/Text, Parent и Hint опре­деляются так:

TControl = class (TComponent)

FParent: TWinControl; {внутреннее поле свойства Parent}

FText: PChar; {внутреннее поле свойства Text/Caption}

FHint: string; {внутреннее поле свойства Hint}

function GetText: Tcaption; {метод чтения свойства Text/Caption}

function IsCaptionStored: Boolean;

function IsHintStored: Boolean;

procedure SetText(const Value: TCaption);

{метод записи свойства Text/Caption}

procedure SetParent{AParent: TWinControl); virtual;

property Caption: TCaption read GetText write SetText stored IsCaptionStored;

property Text: TCaption read GetText write SetText;

property Parent: TWinControl read FParent write SetParent;

Объявление свойства имеет следующий синтаксис: property <имя свойства>: тип определители;

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

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

Для указания метода, который будет использоваться для осуществления выборки значения свойства, используется ди­ректива read. Метод должен быть функцией, чей возвращае­мый тип является тем же самым, что и тип свойства.

Однако вместо метода доступа для чтения можно указать внутреннее поле хранения данных, как, например, при описа­нии свойств Hint и Parent. Подобная форма записи приводит к тому, что значение свойства извлекается прямо из внутреннего поля данных.

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

При обращении к значению свойства происходит перена­правление на соответствующий метод. Например, оператор s: =Editl. Text; автоматически будет преобразован в оператор s: =Editl. GetText; а оператор Editl. Text: =" Test" - в опе­ратор Editl.Text("Test").

Описание свойства должно содержать определитель read или write или сразу оба. Если описание свойства включает в себя только определитель read, то оно является свойством только для чтения. В свою очередь, свойство, чье описание включает в себя только определитель write, является свойством только для записи. При присвоении свойству, определенному с директивой только для чтения, какого-либо значения или при использова­нии в выражении свойства с директивой только для записи все­гда возникает ошибка.

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

Когда программист использует Инспектор объектов для измене­ния свойств формы или свойств компонентов, то результирующие изменения заносятся в файл формы. Файлы форм представляют собой файлы ресурсов Windows, и когда приложение запускается, то описание формы подгружается из этого файла. Для определения того, что должно сохраняться в файле формы, служат специфика­торы памяти - необязательные директивы stored, default и node-fault. Эти директивы влияют на информацию о типе во время вы­полнения, генерируемую для свойств published.

Директива stored управляет тем, будет или нет свойство дейст­вительно запоминаться в файле формы. За директивой stored дол­жны следовать либо константы True или False, либо имя поля, имеющего тип Boolean, либо имя метода, у которого нет парамет­ров, и возвращающего значение типа Boolean. Например,

property Hint: string read FHint write FHint stored IsHintStored;

Если свойство не содержит директиву stored, то оно рассмат­ривается как содержащее ее с параметром True.

Директивы default и nodefault управляют значениями свой­ства по умолчанию. За директивой default должна следовать константа того же типа, что и свойство, например:

property Tag: Longint read FTag write FTag default 0 ;

Чтобы перекрыть наследуемое значение default без указания нового значения, используется директива nodefault. Директи­вы default и nodefault работают только с порядковыми типами и множествами, нижняя и верхняя границы которых лежат в промежутке от 0 до 31. Если такое свойство описано без дирек­тив default и nodefault, то оно рассматривается как с директи­вой nodefault. Для вещественных типов, указателей и строк значение после директивы default может быть только О, NIL и

(пустая строка) соответственно.

Когда Delphi сохраняет компонент, то просматриваются спе­цификаторы памяти published свойств компонента. Если значе­ние текущего свойства отличается от default значения (или ди­ректива default отсутствует) и параметр stored равен True, то значение свойства сохраняется, иначе свойство не сохраняется.

Спецификаторы памяти не поддерживаются свойствами-мас­сивами, а директива default при описании свойства-массива имеет другое назначение.

9.3.1. Простые свойства

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

Рассмотрим создание простого свойства Color, описанного в классе TContol (модуль controls.pas):

TControl = class (TComponent)

function IsColorStored: Boolean;

property Color: TColor read FColor write SetColor stored IsColorStored default clWindow;

function TControl.IsColorStored: Boolean;

Result:= not ParentColor;

procedure TControl.SetColor (Value: TColor);

if FColor <> Value then

FParentColor:= False;

Perform(CM_COLORCHANGED, 0, 0) ;

{вызов Perform позволяет обойти очередь сообщений Windows и послать сообщение, в данном случае - изменить цвет, элементу управления}

9.3.2. Свойства перечислимого типа

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

TShapeType = (stRectangle, stSquare, stRoundRect, stRoundSquare, stEllipse, stCircle);

{вначале необходимо определить новый тип - перечислить возможные значения}

FShape: TShapeType;

procedure SetShape(Value: TShapeType);

property Shape: TShapeType read FShape write SetShape

default stRectangle;

procedure TShape.SetShape{Value: TShapeType);

if FShape <> Value then

Inva 1 idate; {гарантирует перерисовку компонента}

9.3.3. Свойства типа множества

Свойство типа множества при редактировании в окне Инспек­тора объектов выглядит так же, как множество, определенное синтаксисом языка Pascal. Простейший способ его отредактиро­вать - развернуть свойство в Инспекторе объектов, в результате каждый его элемент станет отдельным логическим значением.

При создании свойства типа множества нужно создать соот­ветствующий тип, описать методы доступа, после чего описать само свойство. В модуле Controls.pas свойсво Align описано сле­дующим образом:

TAlign = (alNone, alTop, alBottom, alLeft, alRight, alClient);

TAlignSet = set of TAlign; TControl = class(TComponent)

procedure SetAlign(Value: TAlign);

property Align: TAlign read FAlign write SetAlign default alNone;

procedure TControl.SetAlign(Value: TAlign);

var OldAlign: TAlign;

if FAlign <> Value then

OldAlign:= FAlign;

Anchors:= AnchorAlign;

(not (csDesigning in ComponentState) or (Parent <> NIL))

if ((OldAlign in )=(Value in )) and not (OldAlign in ) and not (Value in ) then SetBounds(Left, Top, Height, Width)

{изменение границ компонента}

else AdjustSize; {устанавливает заданные размеры компонента}

Request Align; {нструктирует «родителя» переставить компонент

в соответствии со значением свойства Align }

9.3.4. Свойство-объект

Свойства могут являться объектами или другими компонен­тами. Например, у компонента Shape есть свойства-объекты Brush и Реп. Когда свойство является объектом, то оно может быть развернуто в окне инспектора так, чтобы его собственные свойства также могли быть модифицированы. Свойства-объек­ты должны быть потомками класса TPersistent, чтобы их свой­ства, объявленные в разделе published, могли быть записаны в поток данных и отображены в инспекторе объектов.

Для определения объектного свойства компонента необходимо сначала определить объект, который будет использоваться в каче­стве типа свойства. В модуле graphics.pas описан класс TBrush:

TBrush = class(TGraphicsObject)

procedure GetData(var BrushData: TBrushData);

procedure SetData(const BrushData: TBrushData);

function GetBitmap: TBitmap;

procedure SetBitmap(Value: TBitmap);

function GetColor: TColor;

procedure SetColor(Value: TColor);

function GetHandle: HBrush.;

procedure SetHandle(Value: HBrush);

function GetStyle: TBrushStyle;

procedure SetStyle(Value: TBrushStyle);

constructor Create; destructor Destroy; override;

procedure Assign(Source: TPersistent); override;

property Bitmap: TBitmap read GetBitmap write SetBitmap;

property Handle: HBrush read GetHandle write SetHandle;

property Color: TColor read GetColor write SetColor

default clWhite;

property Style: TBrushStyle read GetStyle write SetStyle

default bsSolid;

Метод Assign предназначен для копирования значения свойств экземпляра TBrush:

procedure TBrush.Assign (Source: TPersistent);

if Source is TBrush then

Lock; {блокирует использование объекта}

TBrush(Source).Lock;

BrushManager.AssignResource(Self, TBrush(Source).FResource);

finally TBrush(Source).Unlock;

finally Unlock; {завершает секцию кода, начатую методом Lock,

снимая блокировку объекта}

inherited Assign(Source);

Чтобы определить свойство-объект, нужно определить внут­реннее поле. Так как свойство представляет объект, его нужно создать, а по завершении - уничтожить, поэтому в код включены конструктор Create и деструктор Destroy. Кроме того, объявлен метод доступа SetBrush, предназначенный для записи свойства Brush.

TShape = class(TGraphicControl)

procedure SetBrush(Value: TBrush);

constructor Create (AOwner: TComponent) ; overrider;

property Brush: TBrush read FBrush write SetBrush;

constructor TShape.Create(AOwner: TComponent);

inherited Create(AOwner);

FBrush:= TBrush.Create;

FBrush.OnChange:= StyleChanged;

destructor TShape.Destroy;

FBrush. Freer-inherited Destroy;

procedure TShape.SetBrush (Value: TBrush);

FBrush.Assign(Value);

9.3.5. Свойство-массив

Примерами свойств-массивов могут служить такие свойства, как TMemo.Lines, TScreen.Fonts, TStringGrid.Cells.

Особенности свойства-массива заключаются в следующем:

свойства-массивы объявляются с помощью индексных па­раметров, цель которых - указать количество и тип ин­дексов, которые будут использоваться свойством;

спецификации методов чтения и записи должны ссылать­ся на методы доступа. Методом для определителя read должна быть функция, список параметров которой совпа­дает со списком параметров, описывающих индекс свойст­ва, и возвращающей значение того же типа, что и свойст­во. В свою очередь, методом в определителе write должна быть процедура, список параметров которой совпадает со списком параметров, описывающих индекс свойства. Спи­сок параметров такой процедуры может содержать и до­полнительные свойства.

TCanvas = class (TPersistent)

function GetPixel (X, Y: Integer) : TColor; {метод чтения}

procedure SetPixel (X, Y: Integer; Value: TColor);

{метод записи}

constructor Create;

destructor Destroy; override;

property Pixels: TColor read GetPixel write SetPixel;

constructor TCanvas.Create;

inherited Create;

CanvasList. Add (Self) ; {добавляет в список ссылки на объекты}

destructor TCanvas.Destroy;

CanvasList .Remove (Self) ; {удаляет из списка ссылки на объекты}

inherited Destroy; end;

function TCanvas.GetPixel (X, Y: Integer): TColor;

RequiredState();

GetPixel:= Windows.GetPixel(FHandle, X, Y) ; end;

procedure TCanvas.SetPixel(X, Y: Integer; Value: TColor);

RequiredState();

Windows.SetPixel(FHandle, X, Y, ColorToRGB(Value));

Доступ к такому свойству-массиву осуществляется следую­щим образом:

Canvas.Pixels :=clRed; что означает:

Canvas.SetPixel (10, 20, clRed);

За описанием свойства-массива может следовать директива default. В данном случае это будет означать, что это свойство становится свойством по умолчанию для данного класса. На­пример:

TStringArray = class public property Strings: string . . . ; default;

Если у класса есть свойство по умолчанию, то доступ к это­му свойству может быть осуществлен оператором

<имя компонента>, который эквивалентен оператору

<имя компонента>.<имя свойства>.

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

9.3.6. Массив свойств

Определитель Index позволяет разным свойствам иметь один и тот же метод доступа. Его описание состоит из директивы index и последующей за ней константой целого типа в промежутке от -2147483647 до 2147483647. Если у свойства есть определи­тель Index, то определители read и write должны ссылаться на методы, а не на поля. Например:

TRectangle = class private

FCoordinates: array of Longint;

function GetCoordinate(Index: Integer): Longint;

procedure SetCoordinate{Index: Integer; Value: Longint); public

property Left: Longint index 0 read GetCoordinate

write SetCoordinate; property Top: Longint index 1 read GetCoordinate

write SetCoordinate; property Right: Longint index 2 read GetCoordinate

write SetCoordinate; property Bottom: Longint index 3 read GetCoordinate

write SetCoordinate;

Обращение к свойству, определенному с директивой index, например,

Rectangle.Right:= Rectangle.Left + 100;

{Rectangle: TRectangle}

автоматически преобразуется к вызову метода,

Rectangle.SetCoordinate (2, Rectangle.GetCoordinate(0) + 100);

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

Описание свойства без указания типа называется перекры­тием свойства. Самый простой способ перекрытия состоит в ис­пользовании зарезервированного слова property и идентифика­тора - имени свойства. Данный способ используется для смены видимости свойства.

Перекрытия свойств могут содержать директивы read, write, stored, default и nodefault. Перекрытие может заменить суще­ствующие наследуемые определители доступа, добавить недо­стающие, увеличить видимость свойства, но оно не может уда­лить существующий определитель или уменьшить видимость свойства. Следующий пример демонстрирует использование пе­рекрытия свойств:

TAncestor = class

property Size: Integer read FSize; property Text: String read GetText write SetText; property Color: TColor read FColor write SetColor stored False;

TDerived = class(TAncestor)

property Size write SetSize; published property Text; property Color stored True default clBlue;

Перекрытие свойства Size добавляет определитель write, что позволяет редактировать свойство, а перекрытие свойств Text и Color меняет их видимость с protected на published. Перекры­тие свойства Color указывает, что оно должно быть сохранено, если его значение отлично от clBlue.

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

type TAncestor = class

property Value: Integer read Methodl write Method2; end;

TDescendant = class(TAncestor)

property Value: Integer read Method3 write Method4; end;

9.3.8. Создание событий

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

Свойства-события являются не более чем указателями на ме­тоды. В модуле Controls.pas определены стандартные свойст­ва-события.

Описание свойства-события начинается с описания нового типа, который представляет собой процедуру, одним из параметров которой, является Sender типа TObject, а директива of object делает эту процедуру методом:

TMouseMoveEvent = procedure(Sender: TObject; Shift: TShiftState; X, Y: Integer) of object;

Когда происходит какое-либо событие, например, перемеще­ние мыши, в систему Win32 посылается соответствующее сооб­щение, в нашем случае WM_MOUSEMOVE. Система Win32 пе­редает это событие элементу управления, для которого оно предназначено и на которое он должен тем или иным способом ответить. Элемент управления может ответить на это событие, сначала проверив наличие кода, предусмотренного для выпол­нения. Для этого он проверяет, ссылается ли свойство-событие на какой-либо код. Если да, то элемент выполняет этот код, на­зываемый обработчиком события. Операция по определению наличия метода, связанного с событием-свойством, возлагается на метод диспетчеризации. Эти методы объявляются как защи­щенные методы того компонента, которому они принадлежат.

Описание свойства-события состоит из двух частей: во-пер­вых, событие требует внутреннего поля данных, которое испо­льзуется для хранения указателя на метод; во-вторых, создает­ся соответствующее свойство, которое во время проектирования дает возможность присоединения обработчиков событий:

TControl = class(TComponent) private

FOnMouseMove: TMouseMoveEvent; {внутреннее поле события} procedure WMMouseMove(var Message: TWMMouseMove); message WM_MOUSEMOVE;

procedure MouseMove(Shift: TShiftState; X, Y: Integer);

dynamic; {метод диспетчеризации}

property OnMouseMove: TMouseMoveEvent read FOnMouseMove

write FOnMouseMove;

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

procedure TControl.MouseMove(Shift: TShiftState; X, Y: Integer); begin

if Assigned (FOnMouseMove) then FOnMouseMove (Self, Shift, X, Y) ; end;

Чтобы обеспечить возможность переопределения обработки события, необходимо перехватить возникшее событие, обрабо­тать его стандартным образом и передать управление методу диспетчеризации:

procedure TControl.WMMouseMove(var Message: TWMMouseMove); begin inherited; if not (csNoStdEvents in ControlStyle) then

{включение csNoStdEvents во множество ControlStyle заставляет игнорировать стандартные события мыши, клавиатуры. Этот флаг позволяет ускорить запуск приложения, если оно при этом не нуждается в обработке этих событий} with Message do MouseMove(KeysToShiftState(Keys), XPos, YPos) ; end;

9.3.9. Создание методов

Добавление в компонент методов не отличается от добавле­ния методов в любой другой класс. Однако следует придержи­ваться следующих правил:

исключить взаимозависимость методов;

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

метод должен иметь осмысленное имя.

Упражнение 9.2.1 (продолжение). Добавим в описание ново­го класса свойство объектного типа TLabel:

TLabelEdit = class (TEdit)

{ Private declarations }

FLabel: Tlabel; {внутреннее поле}

{ Protected declarations }

function GetLabelCaption: string; virtual;

{метод чтения свойства Caption объектного свойства Label}

procedure SetLabelCaption(Const Value: String); virtual;

{метод записи свойства

Caption объектного свойства Label} public

{ Public declarations }

constructor Create(Aowner: TComponent); override;

destructor Destroy; overrider;

{ Published declarations }

property LabelCaption: string read GetLabelCaption write SetLAbelCaption; end;

В конструкторе необходимо создать экземпляр типа TLabel, сохранить ссылку на него во внутреннем поле и задать значе­ние свойства Caption созданного объекта:

constructor TLabelEdit.Create(AOwner: TComponent); begin

inherited Create(Aowner);

FLabel:= TLabel.Create(NIL);

{владельца у свойства-объекта не существует}

FLabel.Caption:= "Label for Edit"; end;

При разрушении компонента необходимо освободить ресур­сы, занятые созданным объектным свойством:

destructor TLabelEdit.Destroy;

if (FLabel <> NIL) and (FLabel.parent = NIL) Then FLabel.free;

inherited Destroy;

Методы доступа чтения и записи соответственно считывают и записывают значение свойства Caption во внутреннее поле FLabel:

function TLabelEdit.GetLabelCaption: String;

Result:= FLabel.Caption;

procedure TLabelEdit.SetLabelCaption(Const Value: string);

Flabel.Caption:= value;

Эксперимент. Сохраните модуль компонента. Запустите тестовое приложение. Как отображается создаваемый компо­нент? ♦

По-прежнему отображается только компонент Edit. Это свя­зано с тем, что не определено свойство Parent внутреннего ком­понента Label. Напомним, свойство Parent задает компонент, который отвечает за прорисовку принадлежащих ему компо­нентов.

Добавьте в раздел protected описания класса TLabelEdit про­цедуру

procedure SetParent(value: TWinControl); override;

В разделе implementation модуля компонента опишите код процедуры:

procedure TLabelEdit.SetParent{Value: TWinControl};

if (Owner=NIL) or not(csDestroying in Owner.ComponentState)

{если владелец компонента не определен или владелец не разрушается}

then FLabel.Parent:=Value;

{устанавливаем владельца компонента}

inherited SetParent(Value);

Эксперимент. Модифицируйте тестовое приложение в соот­ветствии с рис. 9.3.1. Значения Value компонентов SpinEdit определяют положение компонента LabelEdit.

Запустите тестовое приложение.

Проверьте отображение компонента LabelEdit при различ­ных значениях свойств Left и Тор. ♦

При перемещении компонента Edit надпись (Label) должна перемещаться за ним. Для этого необходимо перехватить собы­тие перемещения - WM_MOVE. Опишите в разделе private описания компонента TLabelEdit заголовок обработчика собы­тия WMMove:

procedure WMMove(var Msg: TWMMove); message WM_MOVE;

Обработчик события WMMove, кроме стандартной обработки события, содержит операторы перемещения компонента Label:

procedure TLabelEdit.WMMove(var Msg: TWMMove); begin inherited;

if Flabel <> NIL then with FLabel do SetBounds(Msg.XPos, Msg.Ypos-Height-5, Width, Height);

{procedure SetBounds(ALeft, ATop, A Width, AHeight: Integer) устанавливает сразу все граничные свойства элемента управления} end;

Эксперимент. Сохраните код модуля компонента. Запусти­те тестовое приложение, убедитесь в правильности перемеще­ния компонента. ♦

9.4. Регистрация компонента в среде Delphi

В процессе регистрации компонент помещается в палитру компонентов Delphi.

Рассмотрим процесс установки компонента на примере ком­понента TLabelEdit, разработанного в упр. 9.2.1.

Выполните команду Component/Install Component. В диало­говом окне Install Component в строке Unit file name укажите имя модуля нового компонента - ...\LabelEdit\LabelEdit.pas (рис. 9.4.1). Щелкните на кнопке ОК.

Появится диалоговое окно Confirm с сообщением «Package dclusr50.bpl will be built then installed. Continue?» (Пакет dclusr50.bpl будет переустановлен. Продолжить?), щелкните на кнопке Yes.

Если нет ошибок в файле модуля нового компонента, то ком­понент будет зарегистрирован в палитре компонентов Delphi и будет отображено окно Information с сообщением «Package c:\program files\borland\delphis\Projects\Bpl\ dclusrSO.bpl has been installed. The following new component(s) have been registered: TLabelEdit» (). Щелкните на кнопке ОК.

В палитре компонентов на странице Test появится новый компонент (рис. 9.4.2).

Примечание. Чтобы изменить пиктограмму нового компонента, вос­пользуйтесь программой Image Editor. Выполните команду File/ New/ Component Resource File, затем Resource/ New/ Bitmap. В появившемся диалоговом окне Bitmap Properties установите размер рисунка 24x24 пикселя, установите цвет - VGA (16 color), нажмите OK и измените имя Bitmapl на имя компонента (в нашем случае TLABELEDIT, вводите обя­зательно прописными символами). Затем выполните команду Resource/ Edit и создайте нужный рисунок. После этого сохраните файл в катало­ге, в котором хранится модуль компонента, под тем же именем, но с расширением.DCR, установите компонент еще раз.

В случае повторной установки компонента или же в случае наличия ошибок в модуле компонента будет отображен редак­тор пакета компонентов Package-dclusr50.dpk (рис. 9.4.3), ко­торый позволяет удалять, добавлять, компилировать пакет.

Для сохранения изменений, проведенных в пакете Dclusr50, щелкните на кнопке ОК в диалоговом окне Confirm «Save changes to project Dclusr50?» (Сохранить изменения в проекте DclusrSO?).

Упражнение 9.4.1. Разработайте компонент SimpleTree, ото­бражающий структуру файловой системы в древовидной форме (рис. 9.4.4).

Создайте каталог SimpleTree.

Выполните команду File\New\Component. В диалоговом окне New Component установите основные параметры:

введите имя класса предка - TCustomControl, так как этот класс предоставляет возможность рисования на ком­поненте и разрешает получать фокус ввода;

имя создаваемого класса - TSimpleTree;

название страницы палитры компонентов, на которую бу­дет помещен компонент - Test;

имя файла модуля, содержащего описания создаваемого класса, - ...\SimpleTree\SimleTree.pas;

□ значение строки указания путей для поиска файла оставь­те без изменения.

После щелчка на кнопке ОК откроется окно редактирования модуля...\SimpleTree\SimleTree.pas, который содержит описа­ние класса TSimpleTree и процедуру Register.

В разделе public описания класса опишите конструктор Create: constructor Create{AOwner: TComponent); override;

в котором определим возможность обрабатывать события мыши и установим объем рамки компонента:

constructor TSimpleTree.Create(AOwner: TComponent); begin

inherited Create{AOwner);

ControlStyle:= ;

{Свойство ControlStyle отвечает за различные атрибуты компонента: csFramed - элемент управления имеет рамку и нуждается в эффектах Ctrl 3D; csCaptureMouse - данный элемент перехватывает события мыши; csDoubleClicks - когда на элементе дважды щелкнули мышью, генерируется событие OnDlClick; csClickEvents - когда на элементе нажата и отпущена мышь, генерируется событие OnClick}

FBorcier:= bsSingle; Width:= 150; Height:= 150;

Tabs top:= True; {возможность перехода на компонент

при нажатии на клавишу Tab}

9.2. Переопределите деструктор класса TSimpleTree.

Чтобы предоставить возможность пользователю компонента изменять внешний вид компонента и его положение на форме, создайте свойство Border и выполните перекрытие свойств Align, Anchors, Color, Ctl3D, Font, TabOrder, TabStop:

FBorder: TBorderStyle

published property Align; property Anchors;

property Border: TBorderStyle read FBorder write SetBorder default bsSingle;

property Color; property Ctl3D; property Font; property TabOrder; property TabStop;

procedure TSimpleTree.SetBorder(const Value: TBorderStyle);

if FBorder Value then begin FBorder:=Value ; RecreateWnd;

{разрушает существующее окно, после чего создает заново}

Эксперимент. Сохраните файл компонента.

Создайте тестовое приложение. Сохраните файл модуля под именем Main.pas, файл проекта Test.dpr.

Положите на форму компонент Button (измените свойство Caption на «Создать компонент»), создайте обработчик события onClick кнопки:

procedure TForml.buttonlclick (Sender: TObject);

Tree:=TSimpleTree.Create(Forml);

with Tree do begin Parent:= forml; Left:=5; Top:=5; end; end;

Опишите переменную Tree и подключите модуль SimpleTree.

Запустите тестовое приложение. После щелчка на кнопке на форме должен отобразиться экземпляр класса TSimpleTree. За­кройте приложение, убедитесь, что при этом не происходит ни­каких ошибок. ♦

Добавим в компонент возможность вертикального скрол­линга дерева (ScrollBar). Перекройте метод CreateParams, ко­торый вызывается перед созданием окна (перед вызовом функ­ции Win API CreateWindow):

procedure CreateParams(var Params: TCreateParams); override;

procedure TSimpleTree.CreateParams{var Params: TCreateParams);

inherited CreateParams(Params); with Params do begin

if FBorder = bsSingle then Style:=Style or WS_BORDER; Style:«Style or WS_VSCROLL; end; end;

Эксперимент. Запустите тестовое приложение. Убедитесь в появлении вертикальной полосы прокрутки на создаваемом ком­поненте.

Используя справочную систему Delphi, определите, какие стили окон управления существуют и как каждый стиль влия­ет на функциональность окна управления. ♦

Перед отображением компонента вызывается метод Paint. Для рисования древовидной структуры файловой системы не­обходимо его переопределить:

Procedure Paint; override;

procedure TSimpleTree.Paint;

Рассмотрим процесс добавления большого количества эле­ментов (узлов) в дерево. При добавлении каждого узла (до того времени, когда они отобразятся в компоненте) происходит пе­рерисовка дерева, вызывающая мигание. Чтобы предотвратить этот эффект, создадим механизм блокировки «отрисовки» при добавлении узлов в дерево, который будет содержать два мето­да BeginPaint (начало блокировки) и EndPaint (окончание блокировки):

FUpdateCount: integer;

{в конструкторе задайте начальное значение равным нулю} public

procedure BeginUpdate;

procedure EndUpdate;

procedure TSimpleTree.BeginUpdate; begin

inc(FUpdateCount); end;

procedure TSimpleTree.EndUpdate; begin

Dec(FUpdateCount);

if FUpdateCount = 0 then Invalidate; end;

Для того чтобы не происходила перерисовка дерева в про­цессе добавления узлов, в метод Paint добавляем оператор:

if FUpdateCount > 0 then exit;

Для вычисления положения узла используем значение ши­рины и высоты символа «А». Введем два поля FCharWidth и FCharHeight, соответственно, длина и высота символа текста, обнов­ление значений которых будет происходить в следующем методе:

procedure TSimpleTree.UpdateCharMetrics; begin

Canvas.Font:= Self.Font;

FCharHeight:= Canvas.TextHeight("A") + 2; FCharWidth:= Canvas.TextWidth("A1); end;

Метод UpdateCharMetrics будет вызываться в ответ на собы­тие смены шрифта и размеров компонента:

procedure CMFontChanged(var Msg: TMessage);

message CM_FONTCHANGED; procedure WMSize(var Msg: TWMSize); message WM_SIZE;

procedure TSimpleTree.CMFontChanged(var Msg: TMessage); "negin

UpdateCharMetrics; end;

procedure TsimpleTree.WMSize(var Msg: TWMSize); begin

UpdateCharMetrics; end;

Вернемся к процедуре Paint. Рисование дерева каталогов бу­дем осуществлять последовательно: сначала отобразим узлы де­рева, а затем, если нужно, - линии. Определите свойство DrawLines логического типа, значение True которого задает не­обходимость рисования линий дерева, False - рисование дере­ва без линий.

property DrawLines: boolean read FDrawLines write SetDrawLines default True;

procedure TSimpleTree.SetDrawLines(const Value: boolean); begin

if FDrawLines <> Value then begin

FDrawLines:=Value; Repaint; end; end;

He забудьте в конструкторе определить начальное значение поля FDrawLines. После этого метод Paint можно записать сле­дующим образом:

procedure TSimpleTree.Paint;

procedure DoDrawNodes;

procedure DoDrawLines;

if FUpdateCount > 0 then exit;

DoDrawNodes; {рисуем узлы}

if FDrawLines then DoDrawLines; {рисуем линии}

Процедура DoDrawNodes рисует узлы дерева каталогов. Од­нако в конкретный момент времени нужно нарисовать только раскрытые пользователем узлы дерева. Список узлов дерева бу­дем хранить в защищенном (private) поле FDrawList типа TList класса TSimpleTree.

Задание для самостоятельного выполнения

9.3. В конструкторе класса TSimpleTree создайте FDrawList (эк­земпляра класса TList), а в деструкторе освободите память, ассоциированную с этой переменной.

Список FDrawList содержит указатели на узлы дерева. Каж­дый узел представляет собой экземпляр класса TSimpleNode:

TSimpleNode = class(TObject) private

FTree: TSimpleTree/ {указатель на дерево}

FParent: TSimpleNode; {родительский узел}

FChildren: TList; {список дочерних узлов}

FCaption: string; {текст для отображения}

FLevel: integer; {уровень узла}

FIndex: integer;

{индекс в списке дочерних узлов родительского узла}

FX, FY: integer;

{последние координаты, по которым рисовался узел}

FExpanded: boolean; {развернутли}

FAbsolutelndex: integer; {индекс узла в дереве}

procedure Redraw; {перерисовка узла по последним координатам}

procedure DrawAt(X, Y: integer);

{нарисовать узел по координатам X, Y}

function GetChildren(Index: integer): TSimpleNode;

function GetChildrenCount: integer;

function GetSelected: boolean;

procedure SetSelected(const Value: boolean);

procedure SetCaption(const Value: string);

procedure SetExpanded(const Value: boolean); public

constructor Create(ATree: TSimpleTree);

destructor Destroy; override;

procedure ClearChildren; {очистить все дочерние узлы}

property Children: TSimpleNode read GetChildren;

property ChildrenCount: integer read GetChildrenCount;

property Caption: String read FCaption write SetCaption;

property Level: integer read FLevel;

property Selected: boolean read GetSelected write SetSelected;

{выбран ли узел}

property Absolutelndex: integer read FAbsolutelndex;

property Index: integer read FIndex;

property Expanded: boolean read FExpanded write SetExpanded; end;

Обновление названия каталога:

procedure TSimpleNode.SetCaption(const Value: String); begin

if reaction <> Value then

rtaction:=Value; ГТгее.Invalidate;

Задания для самостоятельного выполнения

9.4. Реализуйте методы GetChildrenCount (возвращает коли­чество элементов, содержащихся в списке FChildren) и GetChildren (возвращает элемент списка FChildren под но­мером Index) класса TSimpleNode.

Обратите внимание на то, что описание класса TSimpleTree содержит элемент типа TSimpleNode, и наоборот. Чтобы сооб­щить компилятору о существовании класса TSimpleTree в раз­деле Туре, опишите классы следующим образом:

TSimpleTree = class; TSimpleNode = class (TObject)

TSimpleTree = class(TCustomControl)

Реализуем методы класса TSimpleNode. Конструктор иници­ализирует значения полей, а также выделяет память под пере­менную, которая будет содержать ссылки на подкаталоги:

constructor TSimpleNode.Create(ATree: TSimpleTree); begin

inherited Create;

FChildren:=TList.Create;

FExpanded:=False; end;

Деструктор освобождает память, ассоциированную с пере­менной FChildren (список ссылок на дочерние каталоги):

destructor TSimpleNode.Destroy; begin

ClearChildren; FChildren. Freer-inherited Destroy; end;

Удаление всех дочерних подкаталогов выполняет рекурсив­ная процедура, которая освобождает память, занятую под хра­нение ссылок на дочерние подкаталоги текущего подкаталога:

procedure TSimpleNode.ClearChildren;

var i: Integer; begin

for i:=0 to FChildren.Count - 1 do

9.5. При отображении узлов дерева использовались следующие свойства класса TSimpleTree:

property TextColor: TColor index 0 read GetTreeColor

write SetTreeColor; property LinesColor: TColor . ..; property SelTextColor: TColor property SelBackColor: TColor ...;

Используя массив свойств, реализуйте перечисленные выше свойства. Не забудьте в конструкторе Create установить началь­ные значения этих свойств.

К реализации методов GetSelected, SetSelected, SetExpanded вернемся немного позднее.

Таким образом, внутренняя процедура DoDrawNodes метода TSimpleTree.Paint, отображающая узлы дерева, должна выпол­нить такую последовательность операторов:

var i: Integer; begin

for i:=0 to FDrawList.Count - 1 do

TSimpleNode(FDrawList[i]) .DrawAt (0, i * FCharHeight); end;

Метод NodelnView предназначен для проверки видимости узла (опишите в разделе private класса TSimpleTree):

function TSimpleTree.NodelnView (Node: TSimpleNode) : Boolean; begin

Result:=FDrawList.IndexOf(Node) > -1; end;

Для прорисовки дерева в методе Paint осуществляется рисо­вание линий) внутренняя процедура которого DoDrawLines ме­тода Paint:

procedure DoDrawLines; var MaxLevel: integer; i: integer; j: integer; begin

MaxLevel:=0; Canvas.Pen.Color:=LinesColor;

{устанавливаем цвет рисования линий} for i:=0 to FDrawList.Count - 1 do

{просматриваем все узлы дерева} with TSimpleNode(FDrawList[i]) do if FLevel > 0 then

Canvas.MoveTo(FX + FCharWidth, FY + FCharHeight div 2) ; Canvas.LineTo(FX, FY + FCharHeight div 2); if (Flndex > 0) and

(not NodeInView(FParent.Children)) then Canvas.LineTo(FX, 0) else

if FIndex=0 then Canvas .LineTo (FX, FY - FCharHeight div 2) else Canvas.LineTo (FX, FParent.Children.FY); if Flndex < FParent.ChildrenCount - 1 then if not NodelnView(FParent.Children) then Canvas.LineTo(FX, ClientHeight); if MaxLevel < FLevel then MaxLevel:=FLevel; end;

for i:=l to MaxLevel do begin j:=0; while (j < FDrawList.Count) and

(TSimpleNode(FDrawList[j]).Level <> i) do Inc(j); if j = FDrawList.Count then begin

Canvas.MoveTo((i + 1) * FCharWidth, 0) ; Canvas.LineTo((i + 1) * FCharWidth, ClientHeight); end; end; end;

Задание для самостоятельного выполнения

9.6. Поясните каждый оператор метода TSimpleTree.DoDrawLmes. Приведите все возможные варианты выполнения метода DoDrawLines.

Сформируем список FNodes узлов. В описание класса TSimple-Тгее введем следующие элементы:

FNodes: TList; {глобальный массив всех узлов}

FStartlndex: Integer; {абсолютный индекс узла,

с которого начинаем рисовать. Начальное значение равно нулю} FMaxLinesInView: Integer;

{максимальное количество отображаемых узлов} FMaxLines: Integer; {максимальное количество видимых узлов}

function GetNode(Index: integer): TSimpleNode; function GetNodeCount: integer;

property Nodes: TSimpleNode

read GetNode; default; {возвращает узел под номером Index} property NodeCount: integer read GetNodeCount;

{общее количество узлов}

Задание для самостоятельного выполнения

9.7. Реализуйте методы GetNode и GetNodeCount. He забудьте выделить память под переменную FNodes в конструкторе, а в деструкторе - освободить.

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

изменение размеров компонента,

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

сворачивание или разворачивание какого-либо узла.

procedure TSimpleTree.UpdateDrawList;

function ListFull: Boolean; {проверка на полноту списка}

Result:=FDrawList.Count >= FMaxLinesInView; end;

procedure FormDrawList(Node: TSimpleNode);

{формирование списка} var i: Integer; begin

if not ListFull then FDrawList.Add(Node); Inc(FMaxLines);

if Node . FExpanded then begin {если узел раскрыт}

for i:=0 to Node.ChildrenCount - 1 do FormDrawList(Node.Children[i]) ; Inc(FMaxLines, Node.ChildrenCount); end; end;

var i, Min: Integer; begin

FMaxLinesInView:=(ClientHeight div FCharHeight) + 1; FDrawList.Clear; FMaxLines:=0;

if FStartlndex + FMaxLinesInView > GetNodeCount then Min:=GetNodeCount -FStartlndex else Min:=FMaxLinesInView; for i:=FStartIndex to FStartlndex + Min - 1 do

FDrawList .Add (FNodes [i]) ; {добавляем в список узлы}

for i:=0 to GetNodeCount - 1 do

{вычисляем максимальное количество видимых узлов} with Nodes [i] do

if FParent = nil then Inc(FMaxLines)
else if FParent.FExpanded then Inc(FMaxLines);
UpdateScrollBar; {обновляем состояние ScrollBar}

Обновление состояния компонента ScrollBar осуществляет метод UpdateScrollBar:

procedure TSimpleTree.UpdateScrollBar; var Scrolllnfo: TScrollInfo;

{структура, которая содержит параметры отображения полосы прокрутки} begin

if FMaxLinesInView >= FMaxLines then ShowScrollBar(Handle, SBJVERT, False)

{спрятать вертикальную полосу прокрутки} else begin

FillChar(Scrolllnfo, SizeOf(TScrollInfo), 0) ; Scrolllnfo.cbSize:=SizeOf{TScrollInfo);

Scrolllnfo. fMask:=SIF_ALL; {ограничиваетразмер страницы пропорционально отображению полосы прокрутки, минимальное и максимальное значение для диапазона скроллинга } Scrolllnfo.nMax:=FMaxLines;

{максимальное количество отображаемых строк}

ScrollInfo.nPage:=FMaxLinesInView; {общее количество строк} ScrollInfo.nPos:=FStartIndex; ShowScrollBar(Handle, SB_VERT, True);

{показать вертикальную полосу прокрутки} SetScrollInfo(Handle, SB_VERT, Scrolllnfo, True);

{установить назначенные параметры} end; end;

В раздел private класса TSimpleTree добавьте описание мето­дов UpdateDrawList и UpdateScrollBar.

Метод UpdateScrollBar вызывается также и на изменение размеров компонента. Для полноценной поддержки скроллин­га необходимо обрабатывать сообщение WMVSCROLL:

procedure WMVScroll(var Msg: TWMVScroll); message WM_VSCROLL;

procedure TSimpleTree.WMVScroll(var Msg: TWMVScroll); begin

case Msg.ScrollCode of SBJTHUMBPOSITION: begin

{прокручивает на абсолютную позицию.
Текущая позиция определяется значением параметра npos}
SetScrollPos(Handle, SB_VERT, Msg.Pos, True);
FStartlndex:=Msg.Pos;
end;
SB_LINEUP: {вверх}

if FStartlndex > 0 then Dec (FStartlndex) else exit;
SB_LINEDOWN: {прокрутить на одну строку вниз}

if FStartlndex < FMaxLines - FMaxLinesInView + 1 then

Inc(FStartlndex) else exit; else exit; end;

UpdateDrawList; {обновление списка}

Invalidate; {перерисовка компонента}

Эксперимент. Сохраните модуль компонента. Запустите те­стовое приложение. Убедитесь, что при перемещении «бегун­ка» полосы прокрутки компонент ScrollBar исчезает. ♦

Добавим в обработчик события WMSize вызов методов об­новления списка узлов и перерисовки полосы прокрутки:

procedure TsimpleTree.WMSize(var Msg: TWMSize); begin inherited;

UpdateCharMetrics ; UpdateDrawList; UpdateScrollBar; end;

Эксперимент. Запустите тестовое приложение. Отображает­ся ли полоса прокрутки? Объясните, почему. ♦

Создаваемое дерево состоит как минимум из одного узла. Главный узел хранится в поле FMainNode типа TSimpleNode и доступен через свойство только для чтения MainNode.

Свойство SelectedNode типа TSimpleNode является указате­лем на выбранный узел, для записи в этот узел вызывается ме­тод SetSelectedNode.

Опишите перечисленные свойства и методы класса TSimple­Tree. Метод SetSelectedNode реализуется следующим образом:

procedure TSimpleTree.SetSelectedNode(const Value: TSimpleNode); var OldNode: TSimpleNode; begin if FSelectedNode <> Value then begin

{если выделенным должен стать другой узел} OldNode:=FSelectedNode; FSelectedNode:= Value; if (OldNode <> nil) and NodelnView(OldNode) then

{если узел, который был выделен ранее, виден -

его следует перерисовать} OldNode.Redraw;

if NodelnView(FSelectedNode) then {если выделенный сейчас узел видим, его также следует перерисовать}

FSelectedNode.Redraw; end; end;

В конструктор TSimpleTree.Create добавьте следующие опе­раторы:

FNodes:=TList.Create;

FMainNode:^TSimpleNode.Create(Self);

FSelectedNode:= FMainNode;

FNodes.Add(FMainNode);

FMainNode.FAbsolutelndex:= 0;

Чтобы обработать события мыши, выполним перекрытие ме­тода MouseDown:

procedure TSimpleTree.MouseDown (Button: TMouseButton; Shift: TShiftState; X, Y: Integer); var Node: TSimpleNode; begin

inherited MouseDown(Button, Shift, X, Y) ;

{вызываем обработчик события нажатия кнопок мыши по умолчанию}
Node:=NodeAt ; {определяем узел}

if Node <> nil then SelectedNode:=Node;

if (Shift = ) and (FSelectedNode <> nil) then
if FSelectedNode . FExpanded {если выделенный узел раскрыт}
then Collap



Публикации по теме