Создание программ в среде delphi. Инспектор объектов: использование возможностей

Все компоненты 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

Форма "О программе" используется в качестве информативных целей. Чаще всего в ней пишется наименование программы, назначение, авторы и лицензионное соглашение. Чаще всего размещается в главном меню в пункте Помощь->О программе....

Для создание формы "О программе" в Delphi (Делфи, Дельфи) нужно создать обычную форму, как на рисунке ниже:

Для создания любой формы в Delphi 7 используем следующую команду из меню File->New->Form, а при разработке проекта в более новой среде, к примеру Delphi XE2, нужно использовать немного другую команду: File->New->VCL Form - Delphi.

После создания формы нужно выставить нужные размеры и рекомендуется выполнить следующие настройки:

  • Выставить свойство Position. По умолчанию здесь указано значение poDesigned, которое диктует открываемой форме появляться там, где она находилась в момент создания программы. Поэтому нужно выставить, так чтобы форма появлялась по центру относительно формы, из которой она была вызвана. Для этого в свойство Position надо выставить значение poOwnerFormCenter.
  • Также можно отключить свойство AutoScroll, чтобы не появлялись полосы прокрутки.
  • Так как в форме будет краткая статическая информация, то иметь возможность изменять размеры формы во время выполнения нет никакой необходимости. Поэтому в свойстве BorderStyle выставить значение bsSingle.
  • Еще нужно настроить вложенные свойства в BorderIcons. Нужно в свойствах biMinimize и biMaximize выставить значения в false, что не позволит минимизировать и развернуть форму соответственно.

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

Также при использовании TLabel можно выставить Transparent в true. Данная опция сделает так, чтобы фон метки был прозрачен, что будет выглядеть намного красивее, если используется вспомогательный фон.

Заключение

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

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

Состав Delphi и требования к системе

Прежде, чем приступать к работе с какой-либо программой, было бы полезно ознакомиться с ее требованиями к компьютеру. Разумеется, требования у разных версий Delphi разнятся, постепенно повышаясь от версии к версии. В частности, в рассматриваемой нами Delphi 7 рекомендуется процессор не ниже Pentium II и не менее 256 Мбайт оперативной памяти. Более ранние версии требовали меньший объем памяти, однако для комфортной работы я в любом случае рекомендовал бы не менее 256 Мбайт, а для Delphi 7 и выше, да еще и под управлением ОС Windows XP, не помешало бы иметь 512 Мбайт ОЗУ.

Что касается требований к операционной системе, то хотя формально Delphi может работать на любой 32-разрядной версии Windows, я бы настоятельно рекомендовал использовать Windows из линии NT, т.е. Windows 2000 или XP. Дело в том, что Windows 9x, из-за своего 16-разрядного наследия, имеет серьезные ограничения на количество доступных системных ресурсов, вне зависимости от того, насколько мощный ПК вы используете. Кроме того, Windows 9x не может эффективно задействовать даже относительно большие - свыше 128 Мбайт - объемы оперативной памяти. Я уже не говорю о том, что в Windows 9x не поддерживаются ни многопоточность, ни набирающие в последнее время популярность двуядерные процессоры, а производители аппаратных компонентов ПК давно уже забросили оптимизацию драйверов для данного семейства ОС. Результатом всего этого является низкая производительность на современных компьютерах и вполне ощутимый риск "повесить" систему в процессе работы над сложным и ресурсоемким приложением.

Еще один важный вопрос - это монитор. Опять-таки, формально достаточно любого SVGA-монитора. Но работать в среде Delphi при разрешении экрана ниже, чем 1024 на 768 точек, крайне затруднительно: учтите, что вам постоянно надо видеть как элементы управления самой Delphi, так и собственное (разрабатываемое) приложение. Для комфортной работы я бы рекомендовал качественный 19" монитор с рабочим разрешением 1280 на 1024 точки. Причем, если это будет обычный монитор на ЭЛТ (или даже ЖК, но с аналоговым подключением), то вам понадобится еще и качественная видеокарта, способная обеспечить кристально четкую, без "замыливания" картинку. Для ЭЛТ-мониторов также важно обеспечивать поддержку указанного разрешения при частоте регенерации изображения не ниже 85 Гц.

ПРИМЕЧАНИЕ
Помните, что программирование - это напряженная работа с текстом. И если ваша связка "видеокарта-кабель-монитор" не может выдать четкий текст и (или) отсутствие видимого мерцания в нужном вам разрешении, то со временем вы рискуете испортить себе зрение.

Определившись с компьютером, перейдем к установке. В процессе установки про-грамма спросит вас, для каких версий тех или иных третьесторонних приложений следует устанавливать компоненты. Прежде всего, это версии MS Office, для одной из них вы сможете установить набор компонент, обеспечивающих взаимодействие между приложениями office и Delphi. Если вы устанавливаете старшую версию Del-phi (Client/Server, Enterprise, Architect), то вас спросят еще и о том, для каких версий баз данных следует установить компоненты. Наконец, в процессе установки, помимо самой Delphi будут установлено множество дополнительных программ, в основном, связанных с базами данных. Причем некоторые из них (например, сервер InterBase или виртуальная Java-машина) вообще устанавливаются отдельно, хотя и в процессе общего хода инсталляции.

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

  • Image editor - простой графический редактор для рисования иконок и курсоров. За время, прошедшее с момента последнего обновления (в 1996 году), морально устарел, но может пригодиться, если нет ничего другого;
  • WinSight - позволяет просматривать отладочную информацию в любых работающих приложениях;
  • BDE Administrator - позволяет настраивать базы данных;
  • Data Pump - позволяет переносить данных между БД;
  • Database Explorer или SQL Explorer - средство просмотра БД;
  • SQL Monitor (только старшие версии) - позволяет отслеживать обращения приложений к SQL-серверу.

Кроме того, в этой группе будет находиться подгруппа Help, а в ней, среди множества справочных файлов, - еще одна, с еще большим их количеством - MS SDK Help Files. Так вот, все эти файлы вам придется регулярно использовать, причем положение усугубляется не только их количеством и объемами, но и тем, что в русском варианте их не существует. Таким образом, знание английского языка будет вам хорошим подспорьем при изучении как Delphi, так и программирования вообще.

СОВЕТ
Если вы не знаете английского, но владеете немецким или французским, то вы можете установить версию Delphi на том языке, который знаете лучше. Русских версий Delphi, равно как и других серьезных средств разработки, нет, никогда не было, и, увы, даже не предвидится.

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

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

Интегрированная среда разработки Delphi (Delphi IDE) является многооконной системой. Она включает в себя все необходимое для быстрой разработки Windows-приложений, и может гибко настраиваться.

Тем не менее, как и всякая другая программа, Delphi имеет некоторый стандартный, предусмотренный разработчиками вид, в котором она предстает вам при первом запуске. В таком "стандартном" варианте среда Delphi имеет 6 окон (рис. 2.1). Это: главное окно (Delphi 7 - Project1), окно дерева объектов (Object TreeView), окно инспектора объектов (Object Inspector), окно конструктора форм (Form1), а так же совмещенное окно редактора кода и проводника кода (на заднем плане, под Form1). При этом окно проводника пристыковано к левому краю окна редактора. Впрочем, ничего не мешает отсоединить проводник от редактора, или, наоборот, состыковать все окна, кроме главного и конструктора форм, в одном окне, или объединить их по какому-либо иному принципу.

Рис. 2.1. Вид Delphi 7 IDE по умолчанию

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

Рис. 2.2. "Оптимизированный" вид главного окна Delphi

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

  • Debug - отладка. Позволяет запустить программу (Run), приостановить ее выполнение (Pause), а так же выполнять построчное выполнение программы;
  • Standard - стандартные. Служит для таких операций, как сохранение, созда-ние, добавление и удаление файлов;
  • View - вид. Используется для быстрого нахождения форм и файлов проекта;
  • Desktops - рабочая среда. С помощью этих инструментов можно переклю-чаться между различными настройками рабочей среды Delphi;
  • Custom - произвольная. Изначально содержит одну-единственную кнопку - для вызова справки;
  • Component palette - палитра компонентов. Содержит все доступные для разработки приложений компоненты.

Отметим, что все инструментальные панели настраиваются: кнопки можно перемещать между панелями, добавлять на них новые, или же удалять. Для обычных панелей (Standard, View, Debug) это делается точно таким же образом, как во многих других современных Windows-приложениях (например, как в Word, т.е. при помощи окна настройки - Customize).

Что касается самой большой панели - палитры компонентов, то для ее настройки следует использовать специальное окном свойств палитры (рис. 2.3). Это окно доступно через пункт Configure Palette из меню Component. Однако учтите, что при настройке важно знать как предназначение компонент, так и понимать принципы их организации, поэтому максимум, что можно себе позволить для начала - это поменять местами группы, перетаскивая их в списке страниц (Pages).


Рис. 2.3. Настройка палитры компонентов требует знания VCL

ПРИМЕЧАНИЕ
Следует учитывать, что поскольку палитра компонентов является ничем иным, как визуальным представлением VCL, то ее вид и состав могут меняться в зависимости от того, какие модули подключены, устанавливались или нет дополнительные компоненты или их наборы, и т.д. В любом случае, в начале изучения Delphi, экспериментов в этой области лучше не производить.

Все компоненты сгруппированы по вкладкам, число и состав которых несколько разнятся в зависимости от версии и варианта поставки. Так, в Delphi 7 Enterprise имеется 33 вкладки, содержащие компоненты, принадлежащие к той или иной группе VCL (табл. 2.1).

Таблица 2.1. Страницы палитры компонент Delphi 7 Enterprise

Страница

Название

Описание

Стандартные

Основные элементы интерфейса приложений Windows (меню, кнопки, подписи и т.п.)

Дополнительные

Набор улучшенных элементов управления, имеющихся в VCL

32- разрядные Windows

Элементы интерфейса приложений, характерные для Windows 95 и последующих версий этой ОС

Системные

Элементы управления и доступа к системным 16 ункцииям Windows (таймер, OLE , DDE )

Доступ к данным

Стандартный набор компонент для доступа к БД

Элементы данных

Элементы пользовательского интерфейса для доступа к БД

Компоненты для доступа к БД при помощи SQL -драйвера dbExpres

Компоненты для взаимодействия с удаленный web -сервером через SOAP

Компоненты для взаимодействия с сервером через DCOM

Borland Database Engine

Компоненты для доступа к БД посредством BDE (классический вариант для простых БД)

Компоненты для взаимодействия с БД через ADO

Компоненты для прямого взаимодействия с БД InterBase

Администрирование InterBase

Компоненты для взаимодействия и управления сервером БД InterBase

Компоненты для взаимодействия с данными через XML

Компоненты для работы с данными через различные протоколы Интернета

Набор ActiveX -компонент для работы через Интернет

Набор компонент для обработки информации в БД

Стандартные и расширенные диалоговые окна

Компоненты пользовательского интерфейса, характерные для Windows 3.1

Несколько визуальных компонент, не являющихся официально поддерживаемыми

Несколько встраиваемых ActiveX- приложений

Rave Repo rts

Набор компонент для построения отчетов

Клиенты Indy

Набор компонент-клиентов для различных протоколов и служб Интернета

Серверы Indy

Набор компонент-серверов для различных протоколов и служб Интернета

Обработчики Indy

Набор компонент, позволяющих отлавливать сообщения от клиентов и серверов Indy

Indy i/o ha ndlers

Ввод-вывод Indy

Компоненты для отслеживания активности соединений других компонент Indy

Утилиты Indy

Набор вспомогательных компонент, полезных при разработке различных TCP -приложений

Содержит компонент, позволяющий создать управляющий сервер COM +

IW Standard , Data, Client Side, Control

Набор специальных кросс платформенных компонент для создания Web -приложений для любых Web -клиентов, включая КПК и смартфоны

MS Office Servers

Набор ActiveX -компонент для взаимодействия с приложениями Microsoft Office

Суммарно Delphi включает в себя сотни компонент, однако не стоит переживать по поводу огромного их количества: Delphi применяется во многих областях, и вряд ли хоть один разработчик в действительности использовал все доступные компоненты. Так что мы выделим наиболее полезные для нас группы, а именно: стандартные, дополнительные, 32-разрядные Windows, системные и диалоги. Этого набора будет более чем достаточно для начала изучения Delphi. Со временем мы ознакомимся так же с классическими компонентами для доступа к БД (Data Access и Data Controls), а так же с несколькими компонентами из богатой коллекции Indy. На этом введение в палитру компонент можно считать завершенным и перейти к дальнейшему ознакомления со средой, для чего перейдем к детальному исследованию главного окна, начав с его меню, которое состоит из 11 пунктов:

  • File - файл. Операции с файлами, вроде создать, открыть, сохранить;
  • Edit - правка. Операции редактирования, как стандартные для текстового процессора (отмена, копирование-вставка), так и специфические для редактирования разрабатываемых окон приложений (выравнивание, порядок со-здания и т.п.);
  • Search - поиск. Различные варианты поиска и замены;
  • View - вид. Переключение между различными окнами - как относящимися к IDE, так и к разрабатываемому приложению;
  • Project - проект. Все операции по работе с проектом, как то добавление и удаление файлов, настройки, сборка и компиляция;
  • Run - выполнить. Средства для отладки программ;
  • Component - компоненты. Средства для работы с компонентами, включая настройку палитры компонент;
  • Database - Данные. Некоторые средства для работы с БД;
  • Tools - сервис. Настройка параметров IDE, а так же вызов вспомогательных программ (Image editor и др.);
  • Windows - окно. Содержит список всех открытых в текущий момент окон и позволяет переключаться между ними (актуально, когда окон много и одни загораживают другие);
  • Help - справка.

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

Проекты в Delphi

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

  • dpr - собственно файл проекта;
  • pas - модули приложения, содержащие код на Object Pascal;
  • dfm - модули приложения, содержащие информацию об окнах приложения;
  • res - файлы со встраиваемыми ресурсами приложения (например, иконками);
  • obj - файлы, содержащие готовый к компиляции объектный код;
  • cfg, dof, dsk - служебные файлы Delphi.

Основными составными частями проекта, помимо самого файла проекта (dpr), являются модули pas и dfm. При этом для каждого модуля окна (dfm) имеется собственный программный модуль (pas).

Чтобы лучше во всем этом разобраться, попробуем создать собственный проект в Delphi. Для этого достаточно запустить Delphi - в том случае, если вы не изменяли настроек, новый проект создастся автоматически. Но на всякий случай, мы все-таки создадим новый проект самостоятельно, для чего следует из меню File зайти в группу New и выбрать в ней пункт Application.

ВНИМАНИЕ
Чтобы постоянно не повторяться, в дальнейшем подобные последовательности действий мы будем обозначать следующим образом: File > New > Application.

В результате мы получим новый проект, полностью готовый к дальнейшему использованию (см. рис. 2.1). Более того, его уже на этом этапе можно выполнить! Для этого достаточно нажать на кнопку Run, находящуюся на панели инструментов отладки, или же выбрать пункт Run из одноименного меню (Run > Run), но лучше всего нажать на клавишу F9: для быстрой разработки приложений в среде Delphi знание хотя бы основных сочетаний "горячих клавиш" просто необходимо.

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


Рис. 2.4. Самое первое приложение в Дельфи

Теперь немного модернизируем свое приложение, заодно изучив еще одно важное окно среды Delphi - Object Inspector. Для этого вернитесь в рабочую среду Delphi, закрыв запущенное приложение, и щелкните по окну Form1, чтобы оно стало актив-ным. Это окно, как и любые другие окна, относящиеся непосредственно к разрабаты-ваемому приложению, называют формой. Теперь обратите внимание на окно инспек-тора объекта (рис. 2.5), по умолчанию оно расположено по левому краю экрана.


Рис. 2.5. Окно инспектора объектов

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

ПРИМЕЧАНИЕ
Все свойства объектов в Delphi, доступные для изменения, делятся на run-time (времени выполнения) и на design-time (времени разработки). При этом первые можно изменять только во время работы программы, а вторые доступны уже во время визуального редактирования.

Попробуем заменить значение одного из свойств. Наиболее безопасным будет изменение такого свойства, как Caption - оно отвечает за текст, находящийся в заголовке окна. Для изменения значения этого свойства, щелкните по строке с ним и вместо "Form1" введите какой-либо собственный текст, например, "Мое первое приложение в Delphi". В данном случае вы сразу же увидите результат своей работы: заголовок окна формы изменится на новый.

Некоторые свойства меняются не путем непосредственного ввода значений, а путем выбора одного из заранее предусмотренных. В простейшем случае это может быть выбор ложь-истина (False или True), включающим или отключающим ту или иную опцию. Иногда списки бывают гораздо более объемными. Например, для выбора цвета предлагается множество цветов, включая системные. Например, свойство Color имеет в нашем случае значение clBtnFace, что означает цвет кнопки, установленный в настройках Windows. Мы можем изменить его на любой другой цвет, как системный (например, clCaptionText - цвет текста заголовка окна), так и на явный, например, clWhite (белый).

Можно заметить, что некоторые из свойств отмечены значком "+". Это означает, что такое свойство является составным, и если щелкнуть по значку, то раскроются строки, содержащие отдельные параметры. Например, можно раскрыть таким образом свойство BorderIcons и поменять значение параметра biMinimize с True на False. Тем самым мы изменим значение свойства, отвечающего за системные элементы управления окна таким образом, что функция разворачивания окна на весь экран будет недоступной.

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

Ну а пока что сведем воедино все измененные нами свойства формы Form1:

Caption: Мое первое приложение в Delphi Color: clWhite BorderIcons:

Последнее свойство - составное и такое его значение получается в результате установки таких его составляющих, как biSystemMenu и biMinimize в True, а biMaximize и biHelp - в False.

Часть из сделанных изменений - цвет окна и его заголовок - видна сразу же, в рабочей среде Delphi, т.е. на этапе разработки. А вот изменения в системных кнопках, хотя и могут быть сделаны на данном этапе, визуально себя не проявляют. Поэтому, чтобы увидеть сразу все произошедшие изменения, запустим приложение на выполнение, нажав клавишу F9, и вы увидите, что не только цвет и заголовок окна изменились, но и кнопка "развернуть" стала неактивной (рис. 2.6).


Рис. 2.6. Первое приложение после небольшой доработки

Таким образом, мы ознакомились с Object Inspector - одним из наиболее важных окон рабочей среды Delphi. Ну а чтобы завершить тему введения в проекты, попробуем сохранить наш проект на диске. Пусть это будет папка Project1, а сам проект мы назовем first. Для этого откройте диалог сохранения проекта (File Save project as) и выберите в нем нужную папку.

СОВЕТ
По умолчанию Delphi предлагает складывать все проекты в недра своей собственно директории в Program Files. Но было бы гораздо лучше, если бы вы создали папку в другом, легко доступном месте, и назвали ее понятным для себя названием. Например, это может быть папка Work на диске C:.

А теперь внимание! Если вы сохраняете проект в первый раз, то Delphi предложит вам сначала сохранить не файл проекта, а все несохраненные рабочие файлы. В данном случае это будет программный файл формы. По умолчанию Delphi предложит назвать его unit1.pas, но лучше сразу взять за правило давать осмысленные имена все рабочим файлам. В частности, раз это окно - главное (и единственное) в нашем приложении, то назовем его файл main.pas. Таким образом будут сохранено сразу 2 файла - программный pas и файл формы dfm.

ВНИМАНИЕ
Имена любых файлов проекта должны состоять только из латинских букв и цифр, при этом начинаться должны с буквы. Также в них недопустимы пробелы и любые специсмволы, кроме знака подчеркивания.

Только после сохранения всех составных частей, будет предложено сохранить собственно файл проекта. Назовем его "first". После сохранения можно, наконец-то скомпилировать исполняемый файл, нажав Ctrl+F9 (или Project Make), закрыть Delphi и посмотреть, что мы имеем в папке Project1. А в ней, как и было обещано, будет файл main.pas - программный код для формы, main.dfm - описание формы, first.dpr - сам проект, first.res - файл ресурсов проекта, main.dcu - подготовленный к компиляции модуль, и, разумеется, first.exe - исполняемый файл готового приложения. Так же вы обнаружите в нем все служебные файлы Delphi, в которых хранится дополнительная информация о проекте и настройках рабочей среды для него - файлы first.cfg, first.dof и first.dsk.

Чтобы теперь вернуться к работе над этим проектом, достаточно дважды щелкнуть по файлу first.dpr, в результате будет загружена и Delphi IDE, и этот проект в нее.

Типы проектов и депозитарий

Только что мы рассмотрели создание наиболее распространенного типа проекта - приложения Windows со стандартным графическим интерфейсом. Но на самом деле, возможности Delphi этим не ограничены, вы можете создавать приложения самого разнообразного характера, включая консольные (для текстового режима Windows), динамически подключаемые библиотеки (DLL), сервисы для Windows NT/2000/XP, межплатформенные приложения CLX (Delphi 6,7) или приложения для платформы Microsoft .NET (Delphi 8, 2005). Чтобы создать приложение определенного типа, следует из подменю File New выбрать пункт Other. Таким образом, откроется окно, позволяющее выбрать тип нового приложения или добавить какой-либо специфический модуль к существующему проекту (рис. 2.7).


Рис. 2.7. Выбор нового приложения или модуля в Delphi 7

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

Начнем с закладки New. На ней представлены наиболее часто востребованные, по мнению разработчиков, варианты. И действительно, тут можно найти стандартное графическое Windows-приложение (Application), форму (Form), программный модуль (Unit), текстовое приложение командной строки (Console Application), и другие варианты, как-то Data Module (полезен для разработки баз данных), DLL Wizard, Component и т.д.

На закладках Forms и Dialogs моно найти ряд стандартных диалоговых окон и даже мастер по разработке диалогового окна.

Закладка Projects дает возможность начать проект того или иного типа, или даже воспользоваться мастером для создания многооконного приложения.

Чтобы создать элемент управления ActiveX или приложение для COM+, следует обратиться к шаблонам на закладке ActiveX. Ну а прочие закладки, в том числе IntraWeb, WebSnap и т.д., позволяют создавать специализированные приложения соответствующего типа или модули к ним. Их количество и названия зависят от версии Delphi и варианта поставки.

Но на самом деле, данное окно, по большому счету, подобно палитре компонент, с тем лишь исключением, что если палитра компонент являет собой представление VCL, то окно New Items - во многом является отображением депозитария (Object Repository). В депозитарии хранятся заготовки форм и иных модулей, которые вы можете многократно использовать в своих проектах. При этом для того, чтобы поместить форму в депозитарий, достаточно воспользоваться ее контекстным меню и выбрать в нем пункт Add to Repository.

Прочие средства IDE

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

Применительно ко всем современным версиям Delphi, редактор кода имеет все стандартные возможности редактирования текста (вроде работы с буфером обмена), а так же ряд особенностей, характерных для редакторов кода, а именно:

  1. Редактор всегда работает с моноширинным шрифтом (т.е. все буквы имеют одинаковую ширину). Это необходимо, поскольку в противном случае было бы тяжело ориентироваться в коде программы;
  2. Моноширинный режим позволяет использовать такой метод, как колоночную разбивку. Иначе говоря, копировать и перемещать можно не только отдельные слова или строки, но и вырезать, копировать и вставлять прямоугольные фрагменты текста;
  3. Редактор постоянно отображает позицию курсора, т.е. в какой строке и колонке находится точка ввода;
  4. Отсутствие автоматического переноса строк. Поскольку в программировании каждый символ, включая обрыв строки, что-то значит, то чтобы программисты не гадали, где в коде конец строки, а где автоматический перенос, такого режима правки нет в принципе. К тому же это мешало бы ориентироваться в номерах строк;
  5. Подсветка синтаксиса выделяет ключевые слова и прочие специфические языковые конструкции;
  6. При перемещении по тексту стрелкой вправо по окончании текста в строке, курсор не переносится на сроку вниз, а продолжает перемещаться дальше;
  7. Вы можете устанавливать закладки, т.е. помечать место в тексте при помощи сочетания горячих клавиш и быстро перемещаться между ними;
  8. Также предусмотрены такие функции, как автоподстановка кода по ключевым фразам, автозавершение кода и т.д.

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

Что касается внешнего вида окна редактора, то в нем, при стандартных параметрах IDE, помимо области редактора кода имеется еще и проводник кода, упрощающего процесс навигации по файлу (рис. 2.8).


Рис. 2.8. Окно редактора кода с проводником и загруженным файлом новой формы

Обратите внимание на то, что весь программный код файла main.pas, показанный в этом окне, был создан автоматически, равно как и код для файла проекта - first.dpr. Иначе говоря, всю работу по созданию базовых блоков приложения интегрированная среда Delphi делает за вас.

Завершая тему окна редактора, отметим, что для того, чтобы загрузить в него какой-либо произвольный файл, следует воспользоваться главным меню (File Open). При открытии проекта файлы открытых форм загружаются в него автоматически, а чтобы загрузить в него иные файл проекта, используйте кнопку View Unit (сочетание горячих клавиш - Ctrl+F12) на инструментальной панели View. Если же требуется загрузить как исходный код, так и саму форму, то воспользуйтесь находящейся по соседству кнопкой View Form (Shift+F12).

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

Создание приложения командной строки

Итак, мы уже ознакомились со всеми основными функциями IDE Delphi, и даже создали простейшее приложение для Windows. Однако целью данной части книги является все-таки изучения основы основ - языка Object Pascal. Поэтому для того, чтобы не отвлекаться по ходу его изучения на второстепенные (по отношению к самим языковым конструкциям) детали, рассмотрим вариант создания консольного приложения, т.е. фактически, программы для DOS. Не стоит думать, что это будет шагом назад, или что это давно морально устарело. На самом деле, абсолютно все правила языка действуют совершенно одинаково для любых программ, будь они под DOS, Windows, .NET, или Linux. Вместе с тем, отсутствие необходимости в параллельном изучении специфических для конкретной платформы особенностей (а уж тем более - в параллельном изучении такой обширной библиотеки, как VCL!) существенно упростит нашу задачу. Более того, консольные приложения в типичном случае могут состоять всего лишь из одного единственного файла, что так же упрощает понимание предмета изучения, коим на настоящий момент является сам язык программирования. Ну а после того, как будет выучен язык, применить его для создания полноценных Windows-приложений не составит труда, да и изучение VCL станет легче ввиду того, что будет ясно, что, как и почему в ней устроено.

После такого небольшого отступления, приступим к созданию первого консольного приложения в среде Delphi. Для этого откройте окно New Items (File New Other) и на закладке New дважды щелкните по значку Console Application. В результате откроется окно редактора с загруженным в него проектом (листинг 2.1).

Листинг 2.1. Заготовка приложения командной строки

Program Project1; {$APPTYPE CONSOLE} uses SysUtils; begin { TODO -oUser -cConsole Main: Insert code here } end.

Первой строкой идет название программы, в данном случае это Project1, затем Delphi вставило для себя указание, что это - приложение для командной строки, после чего следует ключевое слово "uses" и перечисление необходимых дополнительных файлов (sysutils), ну и после этого, со слова begin начинается собственно тело программы. Завершается любая программа в Pascal ключевым словом end с точкой. Между ключевыми словами begin и end, в фигурных скобках, вставлен автоматический комментарий, не влияющий на выполнение программы, так что при желании можно его удалить.

А теперь самостоятельно напишем первую программу в Delphi! По традиции, она у нас будет выводить фразу "Hello, World!". Для этого на том месте, где находился комментарий, напишем одну строчку кода:

Write(Hello, World!);

Все! Теперь можно сохранить и скомпилировать нашу программу. Для этого щелкните по кнопке Save или Save All на стандартной панели инструментов, в качестве пути к файлу укажите какой-либо каталог на вашем жестком диске (например, создайте папку HelloWorld на диске C:), а саму программу можно назвать hello. Таким образом, все наше приложение будет сохранено, а исходный код примет вид, показанный в листинге 2.2.

Листинг 2.2. Программа hello

Program hello; {$APPTYPE CONSOLE} uses SysUtils; begin write(Hello, World!); end.

Обратите внимание, что название изменилось автоматически. Теперь остается получить исполняемый (exe) файл, для чего скомпилируем программу, нажав Ctrl+F9. Теперь запустим программу. Поскольку она у нас рассчитана на режим командной строки, то для начала откроем командную строку (Пуск > Все программы > Стандартные > Командная строка в Windows XP, или Пуск > Программы > Стандартные > Сеанс MS-DOS в Windows 98). В командной строке вызовем нашу программу, не забыв указать полный путь к ней. Например, если вы сохранили проект в C:\HelloWorld, а саму программу назвали Hello, то и путь укажите соответствующий, т.е.:

C:\HelloWorld\hello.exe

Запустив программу (т.е. введя в командной строке путь и нажав Enter), вы сразу же увидите результат ее выполнения - она выведет фразу "Hello, World!", и завершится. Собственно говоря, именно по этой причине мы сначала открыли командную строку, а лишь затем запустили программу, поскольку непосредственный ее запуск (через Проводник Windows, или прямо из Delphi - по F9), привел бы к тому, что на экране просто мелькнул бы автоматически запущенный сеанс MS-DOS и сразу закрылся бы. Это объясняется тем, что программа завершает свою работу сразу после того, как будет достигнут ее конец, обозначенный как "end.", а это в нашем случае произойдет моментально после вывода текста. Но мы можем изменить такое ее поведение, добавив еще одну сроку кода непосредственно после "write(Hello, World!);", которая будет дожидаться момента, пока пользователь не нажмет клавишу Enter. Выглядеть она будет так:

Теперь можно запустить наше приложение прямо из среды Delphi, нажав F9. В результате можно будет наблюдать надпись "Hello, World!" на фоне черного окна автоматически запущенного сеанса консоли командной строки до тех пор, пока вы не нажмете Enter.

Исходный код программы вы найдете в папке Demo\Part1\HelloWorld на прилагаемом компакт-диске (или в архиве

Почему я сел писать это пособие

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

Все мои готовые компоненты можно найти на сайте http://MihanDelphi.narod.ru

Для чего нужны компоненты

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

Шаг 1. Придумывание идеи

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

Для создания нового класса можно выделить 4 случая:

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

2. Создание графического элемента управления (TGraphicControl)

3. Создание нового класса или элемента управления (TCustomControl)

4. Создание невизуального компонента (не видимого) (TComponent)

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

Невизуальные компоненты видны только во время разработки приложения (Design-Time), а во время работы приложения (Run-Time) их не видно, но они могут выполнять какую-нибудь работу. Наиболее часто используемый невизуальный компонент - это Timer.

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

Шаг 2. Создание пустого модуля компонента

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

Чтобы приступить к непосредственному написанию компонента, вам необходимо сделать следующее:

    Закройте проекты, которые вы разрабатывали (формы и модули)

    В основном меню выберите Component -> New Component...

    Перед вами откроется диалоговое окно с названием "New Component"

    В поле Ancestor Type (тип предка) выберите класс компонента, который вы хотите модифицировать. В нашем случае вам надо выбрать класс TButton

    В поле Class Name введите имя класса, который вы хотите получить. Имя обязательно должно начинаться с буквы "T". Мы напишем туда, например, TCountBtn

    В поле Palette Page укажите имя закладки на которой этот компонент появиться после установки. Введем туда MyComponents (теперь у вас в Делфьи будет своя закладка с компонентами!).

    Поле Unit File Name заполняется автоматически, в зависимости от выбранного имени компонента. Это путь куда будет сохранен ваш модуль.

    В поле Search Path ничего изменять не нужно.

    Теперь нажмите на кнопку Create Unit и получите следующее:

unit CountBtn;

Uses

StdCtrls;

Type
TCountBtn = class(TButton)

private
{ Private declarations }

protected
{ Protected declarations }

public
{ Public declarations }

published
{ Published declarations }

Procedure Register;

Implementation

Procedure Register;
begin
RegisterComponents("MyComponents", );
end;

Шаг 3. Начинаем разбираться во всех директивах

Что же здесь написано? да собственно пока ничего интересного. Здесь объявлен новый класс TCountBtn и процедура регистрации вашего компонента в палитре компонентов.

Директива Private Здесь вы будете писать все скрытые поля которые вам понадобятся для создания компонента. Так же в этой директиве описываются процедуры и функции, необходимые для работы своего компонента, эти процедуры и функции пользователю не доступны. Для нашего компонент мы напишем туда следующее (запись должна состоять из буквы "F" имени поля: тип этого поля):

FCount:integer;

Буква "F" должна присутсвовать обязательно. Здесь мы создали скрытое поле Count, в котором и будет храниться число кликов по кнопке.

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

procedure Click; override;

Это указывает на то, что мы будем обрабатывать щелчок мыши по компоненту. Слово "override" указывает на то, что мы перекроем стандартное событие OnClick для компонента предка.

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

procedure ShowCount;

Осталась последняя директива Published. В ней также используется объявления доступных пользователю, свойств и методов компонента. Для того, чтобы наш компонент появился на форме необходимо описать метод создания компонента (конструктор), можно прописать и деструктор, но это не обязательно. Следует обратить внимание на то, что если вы хотите, чтобы какие-то свойства вашего компонента появились в Инспекторе Объектов (Object Inspector) вам необходимо описать эти свойства в директиве Published. Это делается так: property Имя_свойства (но помните здесь букву "F" уже не нужно писать), затем ставиться двоеточие ":" тип свойства, read процедура для чтения значения, write функция для записи значения;. Но похоже это все сильно запутано. Посмотрите, что нужно написать для нашего компонента и все поймете:

constructor Create(aowner:Tcomponent);override; //Конструктор
//Свойство Count

Итак все объявления сделаны и мы можем приступить к написанию непосредственно всех объявленных процедур.

Шаг 4. Пишем процедуры и функции.

Начнем с написания конструктора. Это делается примерно так:

constructor TCountBtn.Create(aowner:Tcomponent);
begin
inherited create(Aowner);
end;

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

Теперь мы напишем процедуру обработки щелчка мышкой по кнопке:

procedure Tcountbtn.Click;
begin
inherited click;
FCount:=FCount+1;
end;

"Inherited click" означает, что мы повторяем стандартные методы обработки щелчка мышью (зачем напрягаться и делать лишнюю работу:)).

У нас осталась последняя процедура ShowCount. Она может выглядеть примерно так:

procedure TCountBtn.ShowCount;
begin

end;

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

И если вы все поняли и сделали правильно, то у вас должно получится следующее:

unit CountBtn;

Uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls;

Type
TCountBtn = class(TButton)
private
{ Private declarations }
FCount:integer;
protected
{ Protected declarations }
procedure Click;override;
public
{ Public declarations }
procedure ShowCount;
published
{ Published declarations }
property Count:integer read FCount write FCount;

end;

Procedure Register;

Implementation

Procedure Register;
begin

end;


begin
inherited create(Aowner);
end;

Procedure Tcountbtn.Click;
begin
inherited click;
FCount:=FCount+1;
end;


begin
Showmessage("По кнопке "+ caption+" вы сделали: "+inttostr(FCount)+" клик(а/ов)");
end;
end.

Скорее сохраняйтесь, дабы не потерять случайным образом байты набранного кода:)).

Шаг 5. Устанавливаем компонент

Если вы сумели написать и понять, все то что здесь предложено, то установка компонента не должна вызвать у вас никаких проблем. Все здесь делается очень просто. В главном меню выберите пункт Component -> Install Component. перед вами открылось диалоговое окно Install Component. В нем вы увидите две закладки: Into exsisting Package и Into new Package. Вам предоставляется выбор установить ваш компонент в уже существующий пакет или в новый пакет соответственно. Мы выберем в уже существующий пакет.

В поле Unit File Name напишите имя вашего сохранненого модуля (естественно необходимо еще и указать путь к нему), а лучше воспользуйтесь кнопкой Browse и выберите ваш файл в открывшемся окне.

В Search Path ничего изменять не нужно, Делфьи сама за вас все туда добавит.

В поле Package File Name выберите имя пакета, в который будет установлен ваш компонент. Мы согласимся с предложенным по умолчанию пакетом.

Теперь нажимаем кнопочку Ok. И тут появиться предупреждение Package dclusr30.dpk will be rebuilt. Continue? Дельфи спрашивает: "Пакет такой-то будет изменен. Продолжить?". Конечно же надо ответить "Да". И если вы все сделали правильно, то появиться сообщение, что ваш компонент установлен. Что ж можно кричать Ура! Это ваш первый компонент.

Создание свойств своего типа

Теперь мы попробуем создать свойство нестандартного типа. Рассмотрим это на примере метки - TLabel. У этого компонента есть такое свойство: Alignment. Оно может принимать следующие значения: taLeftJustify, taCenter, taRightJustify. Приступаем к созданию свойства. Ничего интересного мне придумать не удалось, но тем не менее я вам покажу это на примере того свойства, которое я придумал. Оно очень простое и поможет вам разобраться. Свойство будет называться ShowType (тип TShowTp), в нашем компоненте оно будет отвечать за отображение свойства Count. Если пользователь установит свойство ShowType в Normal, то кнопка будет работать, как и работала. А если пользователь присвоит этому свойтсву значение CountToCaption, то количество кликов, будет отображаться на самой кнопке.

Для начале нам необходимо объявить новый тип. Описание типа нужно добавить после слова Type. Вот так это выглядело вначале:

type
TCountBtn = class(TButton)

Вот так это должно выглядеть:

TShowTp = (Normal, CountToCaption);

TCountBtn = class(TButton)

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

Теперь нам понадобиться создать поле этого типа. Это мы уже умеем и делать и поэтому не должно вызвать никаких сложностей. В директиву Private напишите:

FShowType:TShowTp;

Мы создали поле ShowType, типа TShowTp.

Конечно же необходимо добавить это свойство в инспектор объектов:

property ShowType: TshowTp read FshowType write FShowType;

Ну и наконец, чтобы наш компонент реагировал на изменение этого свойства пользователем надо слегка изменить обработчик события OnClick. После небольшой модификации он может иметь примерно такой вид:

procedure Tcountbtn.Click;
begin
inherited click;
FCount:=Fcount+1;
if ShowType = Normal then
Caption:=Caption;


end;

Объясню что произошло. Вначале мы увеличиваем счетчик на единицу. Затем проверяем какое значение имеет свойство ShowType. Если Normal, то ничего не делаем, а если CountToCaption, то в надпись на кнопке выводим количество кликов. Не так уж и сложно как это могло показаться с первого раза.

Имплантируем таймер в компонент

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

После раздела uses, где описаны добавленные в программу модули, объявите переменную типа TTimer. Назовем ее Timer. Приведу небольшой участок кода:

unit CountBtn;

Uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls;

procedure OnTimer(Sender: TObject);

Поскольку наш таймер это не переменная, а компонент, его тоже надо создать, для этого в конструктор нашей кнопки напишем:

constructor TCountBtn.Create(aowner:Tcomponent);
begin
inherited create(Aowner);
Timer:=TTimer.Create(self);
Timer.Enabled:=true;
Timer.OnTimer:=OnTimer;
Timer.Interval:=10000;
end;

Здесь создается экземпляр нашего таймера и его свойству Iterval (измеряется в миллисекундах) присваивается значение 10000 (то есть 10 секунд если по простому).

Собственно осталось написать саму процедуру OnTimer. Я сделал это так:

procedure TCountBtn.OnTimer(Sender: TObject);
begin
FCount:=FCount*2;
end;

Вот примерно то, что у вас должно получиться в конце:

unit CountBtn;

Uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls;

Var Timer: TTimer;
type
TShowTp = (Normal, CountToCaption);
TCountBtn = class(TButton)

Private
{ Private declarations }

FCount:integer;
FShowType:TShowTp;
protected
{ Protected declarations }
procedure OnTimer(Sender: TObject);
procedure Click;override;
public
{ Public declarations }
procedure ShowCount;
published
{ Published declarations }
property Count:integer read FCount write FCount;
constructor Create(aowner:Tcomponent);override;
property ShowType: TshowTp read FshowType write FShowType;
end;

Procedure Register;

Implementation

Procedure Register;
begin
RegisterComponents("Mihan Components", );
end;

Constructor TCountBtn.Create(aowner:Tcomponent);
begin
inherited create(Aowner);
Timer:=TTimer.Create(self);
Timer.Enabled:=false;
Timer.OnTimer:=OnTimer;
Timer.Interval:=1000;
end;

Procedure Tcountbtn.Click;
begin
inherited click;
FCount:=Fcount+1;
Timer.Enabled:=true;
if ShowType = Normal then
Caption:=Caption;
if ShowType = CountToCaption then
Caption:="Count= "+inttostr(count);
end;

Procedure TCountBtn.ShowCount;
begin
Showmessage("По кнопке "+ caption+" вы сделали: "+inttostr(FCount)+" клик(а/ов)");
end;

Procedure TCountBtn.OnTimer(Sender: TObject);
begin
FCount:=FCount*2;
end;

Если у вас что-то не сработало, то в начале проверьте все ли у вас написано правильно. Затем проверьте может у вас не хватает какого-нибудь модуля в разделе Uses.

Переустановка компонента

Очень часто бывает необходимо переустановить ваш компонент. Если вы попробуете сделать это путем выбора Component->Install Component, то Дельфи вас честно предупредит о том, что пакет уже содержит модуль с таким именем. Перед вами открывается окно с содержимым пакета. В нем вы должны найти имя вашего компонента и удалить его (либо нажать кнопочку Remove). Теперь в пакете уже нет вашего компонента. Затем проделайте стандартную процедуру по установке компонента.

Удачи в программировании...

(С) Авторские права принадлежат Михаилу Христосенко! При размещении на других сайтах указание имени автора и адреса сайта (http://delphid.dax.ru) обязательно!

  • Обзор
  • Соглашения по наименованиям
  • Выбор предка
  • Пример создания компонента
  1. Обзор
  2. Поскольку Delphi является открытой средой и позволяет не только использовать объекты из Библиотеки Визуальных Компонент (VCL) в своей программе, но и создавать новые объекты. Причем, ничего другого, кроме Delphi, для этого не требуется. Создание нового объекта в Delphi не является очень сложной задачей, хотя для этого и требуется знание Windows API, объектно-ориентированного программирования и иерархии классов в VCL.

    Может возникнуть вопрос; если в Delphi уже есть своя библиотека, то зачем еще создавать какие-то объекты? Ответ прост: нельзя создать библиотеку на все случаи жизни и на все вкусы. Новые компоненты, во-первых, позволяют расширить область применения Delphi: например, с помощью библиотек объектов третьих фирм разрабатывать приложения для работы в Internet. Во-вторых, позволяют дополнить или настроить для себя имеющиеся в VCL объекты (например, переопределить значения свойств, устанавливаемые по умолчанию).

  3. Добавление новых объектов в VCL
  4. Предположим, что у вас появился уже готовый компонент. Как его добавить в VCL? Для этого выберите пункт меню Options|Install Components… Появится диалог, как на рис.1

    Рис. 1 : Диалог установки нового компонента

    Нажмите “Add” и укажите модуль, содержащий процедуру регистрации, нажмите “OK” и после успешной перекомпиляции новый объект появится в палитре.

  5. Заготовка для нового компонента
  6. В среде Delphi есть специальный эксперт, создающий заготовку для нового компонента. Вызвать его можно в пункте меню File|New Component… (см рис.2)


    Рис. 2 : Эксперт для создания нового компонента

    В диалоге нужно указать имя нового класса (например, TMyButton), предка класса (TButton) и страницу палитры, куда поместить новый компонент (Samples). Если нажать “OK”, то эксперт создаст модуль - заготовку для нового компонента:

    unit Unit1;

    interface

    uses

    SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,

    Forms, Dialogs, StdCtrls;

    type

    TMyButton = class(TButton)

    private

    { Private declarations }

    protected

    { Protected declarations }

    public

    { Public declarations }

    published

    { Published declarations }

    end;

    procedure Register;

    implementation

    procedure Register;

    begin

    RegisterComponents("Samples", );

    end;

    end.

    Модуль содержит декларацию нового класса и процедуру его регистрации в Палитре Компонент. В процедуре RegisterComponents первый параметр - имя страницы (можно указать свое имя - появится новая страница); второй параметр - множество объектов для регистрации.

    Теперь модуль нужно сохранить под новым именем (например, NEW_BTN.PAS) и приступить к дописыванию новых свойств и методов. После того, как эта работа закончена и новый компонент отлажен, можно добавить его в Палитру (см. предыдущую главу). Но перед этим желательно создать файл ресурсов, в котором будет лежать пиктограмма для представления данного объекта в Палитре Компонент. Файл ресурсов можно создать с помощью программы Resource Workshop, называться он должен точно так же, как модуль регистрации компонента и иметь расширение.DCR (т.е., если объект регистрируется в модуле NEW_B TN .PAS, то тогда имя файла ресурсов будет NEW_BTN.DCR). В файле ресурсов должен находиться ресурс типа BITMAP - картинка размером 28x28 точки (можно меньше), название картинки должно совпадать с именем класса (в нашем случае TMYBUTTON).

  7. Соглашения по наименованиям

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

Итак:

  • Все декларации типов начинаются на букву T. Еще раз, Delphi не требует этого, но это делает очевидным, что "TEdit", например, есть определение типа, а не переменная или поле класса.
  • Имена свойствам нужно давать легко читаемые и информативные. Нужно помнить, что пользователь будет их видеть в Инспекторе Объектов. И имя вроде "TextOrientation" много удобнее, нежели "TxtOr". То же самое относится к методам. Методы, доступные пользователю, должны иметь удобные названия.
  • При создании свойств типа Event, имя такого свойства должно начинаться с “On” (например, OnClick, OnCreate и т.д.).
  • Имя метода для чтения свойства должен начинаться со слова “Get”. Например, метод GetStyle должен выполнять чтение для свойства Style.
  • Имя метода для записи свойства должен начинаться со слова “Set”. Например, метод SetStyle должен выполнять запись в свойство Style.
  • Внутреннее поле для хранения данных свойства должно носить имя, начинающееся с буквы “F”. Например, свойство Handle могло бы храниться в поле FHandle.

Конечно же, есть исключения из правил. Иногда бывает удобнее их нарушить, например, класс TTable имеет свойства типа Event, которые называются BeforePost, AfterPost и т.п.

  1. Выбор предка

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

  • TObject - Можно использовать в качестве предка, если с этим компонентом не нужно работать во время дизайна. Это может быть, например, класс, содержащий значения переменных среды (environment) или класс для работы с INI файлами.
  • TComponent - Отправная точка для многих невидимых компонент. Данный класс обладает встроенной возможностью сохранять/считывать себя в потоке во время дизайна.
  • TGraphicControl - Используйте этот класс для создания видимых компонент, которым не нужен handle. Такие компоненты рисуют прямо на своей поверхности и требуют мало ресурсов Windows.
  • TWinControl - Базовый класс для компонент, которые имеют окно. Данное окно имеет свой handle, его используют при доступе к возможностям Windows через API.
  • TCustomControl - Потомок TWinControl, вводит понятие канвы (Canvas) и метод Paint() для лучшего контроля за прорисовкой компонента. Именно этот класс используется в качестве базового для построения большинства видимых компонент, имеющих оконный handle.
  • TXxxxx - Класс вроде TEdit или TButton. Используются с целью доопределения их свойств и методов или переопределения значения свойств, принимаемых по умолчанию.
  1. Пример создания компонента

Для примера создадим новый класс, мутант TButton, в котором изменим значение по умолчанию свойства ShowHint на True и добавим новое свойство - счетчик нажатий на кнопку. Заготовка модуля для создания нового компонента уже есть (см. пункт Заготовка для нового компонента ). Теперь исходный текст выглядит так:

unit New_btn;

interface

uses

SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics,

Controls, Forms, Dialogs, StdCtrls;

type

TMyButton = class(TButton)

private

{ Private declarations }

FClickCount: Longint;

protected

{ Protected declarations }

public

{ Public declarations }

constructor Create(AOwner: TComponent); override;

procedure Click; override;

property ClickCount: Longint read FClickCount write

  • FClickCount;
  • published

    { Published declarations }

    end;

    procedure Register;

    implementation

    constructor TMyButton.Create(AOwner: TComponent);

    begin

    inherited Create(AOwner);

    ShowHint:=True;

    FClickCount:=0;

    end;

    procedure TMyButton.Click;

    begin

    Inc(FClickCount);

    inherited Click;

    end;

    procedure Register;

    begin

    RegisterComponents("Samples", );

    end;

    end.

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

    Новое свойство для подсчета нажатий на клавишу называется ClickCount. Его внутреннее поле для сохранения значения - FClickCount имеет тип Longint, емкости поля хватит надолго.

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