Руководство по созданию своих компонентов на delphi

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

  Дельфи имеет
открытую архитектуру — это значит, что
каждый программист волен
усовершенствовать эту среду
разработки, как он захочет. К
стандартным наборам компонентов,
которые поставляются вместе с Дельфи
можно создать еще массу своих
интересных компонентов, которые
заметно упростят вам жизнь (это я вам
гарантирую). А еще можно зайти на какой-нибудь
крутой сайт о Дельфи и там скачать кучу
крутых компонентов, и на их основе
сделать какую-нибудь крутую прогу. Так
же компоненты освобождают вас от
написания «тысячи тонн словесной
руды». Пример: вы создали компонент —
кнопку, при щелчке на которую данные из
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;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;

type
 TCountBtn = class(TButton)

  private
   { Private declarations }

  protected
   { Protected declarations }

  public
   { Public declarations }

  published
   { Published declarations }
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('MyComponents', [TCountBtn]);
end;

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; //Конструктор
property Count:integer read FCount write FCount; //Свойство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
  Showmessage('По кнопке '+ caption+' вы сделали: '+inttostr(FCount)+' клик(а/ов)');
end;

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

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

unit CountBtn;
 
interface
 
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;
  constructor Create(aowner:Tcomponent); override;
 end;
 
procedure Register;
 
implementation
 
procedure Register;
begin
 RegisterComponents('Mihan Components', [TCountBtn]);
end;
 
constructor TCountBtn.Create(aowner:Tcomponent);
begin
 inherited create(Aowner);
end;
 
procedure Tcountbtn.Click;
begin
 inherited click;
 FCount:=FCount+1;
end;
 
procedure TCountBtn.ShowCount;
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)

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

type
 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;
 if ShowType = CountToCaption then
   Caption:='Count= '+inttostr(count);
end;

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

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

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

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

unit CountBtn;

interface
        
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ExtCtrls;
        
var Timer: TTimer;
type

Дальше в директиву Protected необходимо добавить обработчик события OnTimer для нашего таймера. Это делается так:

		
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;
  
interface
  
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', [TCountBtn]);
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;
  
end.

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

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

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

Удачи!
Встретимся в следующем уроке!

Андрей Бреслав

С Borland Delphi на «ты»


Создание компонент

(Материалы проверены с Borland Delphi v5.0 Enterprise
UP1)


Часть 1: Простые компроненты и
редакторы свойств


1. Подготовка

Как мы знаем из истории, промышленный переворот
происходит тогда, когда люди переходят к
производству средств производства. Это занятие
применительно к Delphi — не есть осознанная
необходимость, но есть весьма полезное в
практике умение, позволяющее не падать духом при
виде несовершенства того, что уже написали
программисты Borland или третьих фирм.

Итак, с чего начинается проектирование
визуальных (и не только визуальных) компонент для
Delphi? В принципе — со знания Object Pascal, но коль скоро
это само собой разумеется, то создадим новый
пакет (dpk.gif (409 bytes)File/New/Package(Object Repository/New))
назовём его как понравится, скажем, custom.dpk, в options.gif (111 bytes)
опциях проекта установим описаниe (Description): «Custom
components», сохраним. Дальнейшая работа будет
проходить в пределах этого пакета — так удобнее
локализовать данные.

00.jpg (7889 bytes)

Скажем немного о пакетах в Delphi. Пакет — это
логическая единица (модуль, физически — файл),
содержащая ссылки на другие модули и
интегрируемая (устанавливаемя) в IDE при
компиляции (Component/Install packages). Проще говоря,
просто сборник файлов, компилируемых вместе и
доступных во время проектирования. Наш пакет
будет содержать файл с исходным кодом компоненты
и два файла для среды: регистрационный модуль и
ресурс (Delphi Component Resource — *.dcr).

Теперь создаём новый компонент (Component/New component),
задаём родительский класс (Ancestor type) TComponent,
имя класса (Class nameTMgsBox(это связано с
характером проекта), страницу на палитре
компонент (Palette page) Custom, сохраняем
модуль в той же директории, где и пакет (не
обязательно, но удобно) под именем, скажем, MsgBox.pas.

01.jpg (18339 bytes)

И получаем в итоге следующий код:

unit MsgBox;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;

type
TMsgBox = class(TComponent)
private
{ Private declarations }
protected
{ Protected declarations }
public
{ Public declarations }
published
{ Published declarations }
end;

procedure Register;

implementation

procedure Register;
begin
RegisterComponents(‘Custom’, [TMsgBox]);
end;

end.

О сути увиденного мы будем говорить в
самом конце, а сейчас не забудем addf.gif (137 bytes) добавить созданный файл в
пакет и сообразим всё-таки, какого же чёрта мы
будем делать.

2. Суть проблемы

Собственно, какую компоненту мы будем
создавать? Класс мы наследовали от TComponent,
это говорит о том, что наша первая компонента
будет невизуальной, она просто будет оболочкой
на функции MessageBox из глубин Win32 API. По чести
сказать, глубокого практического смысла в такой
компоненте нет, более того, нерационально писать
компоненту ради оболочки на функцию — она
занимает в стеке много больше места, однако мы
возьмём эту задачу, как удобную для примера.

Функция MessageBox, экспортируемая из
библиотеки user32.dll, имеет сигнатуру

function MessageBox(hWnh: HWND; lpText: PChar;
lpCaption: PChar; uType: Cardinal): Integer;

и занимается выводом на экран сообщений
типа

03.jpg (3181 bytes)

что очень полезно любой программе. Она обладает
кандовым Borland’овским аналогом MessageDlg, но он уж
очень левый, и к тому же кнопки подписывает
всегда по-английски, а не на языке ОС.

Параметры функции
MessageBox

hWnd Идентификатор вызывающего окна —
удобно ставить в Application.Handle
lpText Указатель на строку с сообщением
lpCaption Указатель на строку с заголовком
uType Флаги (сумма целых констант), задающие
поведение и содержание окна

Значение uType
сумма следующих констант:

MB_ABORTRETRYIGNORE The message box contains three push buttons: Abort, Retry, and Ignore.
MB_OK The message box contains one push button: OK. This is the default.
MB_OKCANCEL The message box contains two push buttons: OK and Cancel.
MB_RETRYCANCEL The message box contains two push buttons: Retry and Cancel.
MB_YESNO The message box contains two push buttons: Yes and No.
MB_YESNOCANCEL The message box contains three push buttons: Yes, No, and Cancel.
MB_ICONEXCLAMATION An exclamation-point icon appears in the message box.
MB_ICONWARNING An exclamation-point icon appears in the message box.
MB_ICONINFORMATION An icon consisting of a lowercase letter i in a circle appears in the
message box.
MB_ICONASTERISK An icon consisting of a lowercase letter i in a circle appears in the
message box.
MB_ICONQUESTION A question-mark icon appears in the message box.
MB_ICONSTOP A stop-sign icon appears in the message box.
MB_ICONERROR A stop-sign icon appears in the message box.
MB_ICONHAND A stop-sign icon appears in the message box.
MB_DEFBUTTON1 The first button is the default button. MB_DEFBUTTON1 is the default
unless MB_DEFBUTTON2, MB_DEFBUTTON3, or MB_DEFBUTTON4 is specified.
MB_DEFBUTTON2 The second button is the default button.
MB_DEFBUTTON3 The third button is the default button.
MB_DEFBUTTON4 The fourth button is the default button.
MB_APPLMODAL The user must respond to the message box before continuing work in the
window identified by the hWnd parameter. However, the user can move to the windows of
other applications and work in those windows. Depending on the hierarchy of windows in the
application, the user may be able to move to other windows within the application. All
child windows of the parent of the message box are automatically disabled, but popup
windows are not.MB_APPLMODAL is the default if neither MB_SYSTEMMODAL nor MB_TASKMODAL is
specified.
MB_SYSTEMMODAL Same as MB_APPLMODAL except that the message box has the WS_EX_TOPMOST
style. Use system-modal message boxes to notify the user of serious, potentially damaging
errors that require immediate attention (for example, running out of memory). This flag
has no effect on the user’s ability to interact with windows other than those associated
with hWnd.
MB_TASKMODAL Same as MB_APPLMODAL except that all the top-level windows belonging to
the current task are disabled if the hWnd parameter is NULL. Use this flag when the
calling application or library does not have a window handle available but still needs to
prevent input to other windows in the current application without suspending other
applications.
MB_DEFAULT_DESKTOP_ONLY The desktop currently receiving input must be a default desktop;
otherwise, the function fails. A default desktop is one an application runs on after the
user has logged on.
MB_HELP Adds a Help button to the message box. Choosing the Help button or
pressing F1 generates a Help event.
MB_RIGHT The text is right-justified.
MB_RTLREADING Displays message and caption text using right-to-left reading order on
Hebrew and Arabic systems.
MB_SETFOREGROUND The message box becomes the foreground window. Internally, Windows calls
the SetForegroundWindow function for the message box.
MB_TOPMOST The message box is created with the WS_EX_TOPMOST window style.

Остальные флаги —
только для Windows NT

Возвращаемые
значения функции MessageBox

IDABORT Abort button was selected.
IDCANCEL Cancel button was selected.
IDIGNORE Ignore button was selected.
IDNO No button was selected.
IDOK OK button was selected.
IDRETRY Retry button was selected.
IDYES Yes button was selected.

Как видно, эта функция тоже не отличается
особенным изяществом, но всё же будет весьма
полезно её освоить.

3. Первый шаг

3. Общее место

С чего начнём? С концепции интерфейса нашей
компоненты: нам нужно задать функции все четыре
её параметра через поля объекта. Пишем:

protected
{ Protected declarations }
  fhWnd: HWND;   //Ссылка на окно
  fCaption: String;   //Заголовок окна
  fText: String;   //Текст сообщения
  fFlags: Integer;   //Флаги

Задаём эти поля как protected из уважения к
несчастным, желающим что-нибудь наследовать от
нашего объекта. Далее сразу устанавливаем
свойства (properties), соответсвующие этим
полям.

3.2 Свойства

Свойства (properties) объекта — это особое
представление полей, для которого заданы
определённый способ чтения и записи, а также
другие интересные вещи. Только свойства (к
которым относятся и события (events)) могут
отображаться в Object Inspector. Да и то только те,
которые описаны в разделе published и не
являются свойствами только для чтения (read only). Для
таких свойств генерируется RTTI (RunTime type information),
о которой желающие могут почитать Help к Delphi на
слово «published».

Свойство декларируется ключевым словом property

published
{Published declarations}
  property hWnd: HWND read fhWnd
write fhWnd default 1;
  //Ссылка на окно
  property Caption: String read
fCaption write fCaption;
  //Заголовок окна
  property Text: String read
fText write fText;
  //Текст сообщения

  property Flags: DWord read
fFlags write fFlags default
MB_APPLMODAL+MB_OK+MB_ICONERROR;

  //Флаги

Как видно, тип свойства необходимо указывать,
как и тип поля (кроме наследуемых свойств, о
которых скажем чуть ниже). Далее: за ключевым
словом read указываем
идентификатор поля или метода на запись
свойства; метод должен быть процедурой,
получающей параметр одного с полем типа. За
ключевым словом write указываем
идентификатор поля или метода на чтение объекта;
метод должен быть функцией, возвращающей
значение одного со свойством типа. Одна из этих
директив может быть опущена, и тогда значение в
поле нельзя будет читать или писать извне
(свойство уже не попадёт в Object Inspector), того же
эффекта можно добиться и создав пустой метод:
тогда, если Delphi будет в хорошем настроении,
сойство в Object Inspector попадёт.

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

3.3 Наследование

Теперь займёмся наследованными от
родительского класса методами и свойствами.
Собственно, нас интересуют конструктор,
деструктор и published свойства нашего
объекта. Таких свойств всего два (посмотрите Help): Name
и Tag, их можно не указывать как
наследованные, они отобразятся в Object Inspector и
так, но если бы мы наследовали наш объект, скажем,
от TActionList, то его свойство Images
необходимо было бы внести в раздел published:

published
{Published declarations}
  property Images; //Не указывая ни тип, ни атрибуты
чтения/записи — они указаны в родительском
классе.

К такому свойству можно добавлять не указанные
в родительском классе директивы (например default).

Теперь перейдём к методам, и в первую очередь —
к конструктору. Наш конструктор не должен делать
ничего выдающегося — просто создать экземпляр
объекта. Но это неинтересно! И мы создадим ещё
один конструктор: он создаёт обект с уже
заданными свойствами Text,Caption и Flags
и может сразу показать диалог. Надо сказать, что,
если мы хотим использовать объект как компоненту,
то сигнатура его конструктора (хотя бы одного)
должна соответствовать стандартной:

constructor Create(AOwner: TComponent);

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

constructor Create(AOwner: TComponent; Param1:
Integer = 0; Param2: String = »);

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

public
{Public declarations}
constructor
CreateShow(AOwner: TComponent; Msg, Cpt: String; Flags: Integer = 0;
Show: Boolean = false);

Деструктор мы вообще трогать не будем — убьёт
объект, и пусть.

Реализуем конструкторы:

public
{Public declarations}
  constructor
Create(AOwner: TComponent); override;
  constructor CreateShow(AOwner: TComponent; Msg, Cpt: String; 
Flags: Integer = 0; Show: Boolean = false);
{…}
constructor TMsgBox.Create(AOwner: TComponent);
begin
  inherited
Create(AOwner);
  fFlags:= MB_APPLMODAL+MB_OK+MB_ICONERROR;
  fhWnd:= 1;
end;
constructor TMsgBox.CreateShow(AOwner: TComponent; Msg,
Cpt: String;  Flags: Integer = 0; Show: Boolean = false);
begin
 
Create(AOwner);
  fText:= Msg;
  fCaption:= Cpt;
  fFlags:= Flags;
  if
Show then Execute;
end;

В коде конструктора мы забежали вперёд: мы не
декларировали пока функцию Execute, но у нас всё
ещё впереди…

4. Переходим к рабочей части

4.1 Свойства DResult и TrueResults

Теперь займёмся реализаций рабочей части
объекта. Собственно, об интересующем
пользователя в первую очередь методе уже
сказано: это функция Execute, возвращающая Boolean.
Так, какое Boolean — это хитро: мы введём ещё
одно свойство — TrueResults, в котором будет
лежать список возвращаемых MessageBox значений,
в случае которых Execute возвращает true.
Реализуем TrueResults поле DResult, содержащее
возвращённое MessageBox значение:

type
  ids = idOK..idHelp;
  TMBTRes = set of ids;

{…}
protected
  fDResult: Integer; //В действительности
результат запросто укладывается  в Byte, но
уважим Borland
  fTrueResults: TMBTRes;
public
  property
DResult: Integer read fDresult;
//Read Only!
published
  property
TrueResult: TMBTRes read fTrueResults
write
fTrueResults default [idOK, idYes];

Как видно, поле DResult представлено
свойством только для чтения (ради того и
свойство), и декларировано свойство в разделе public,
чтобы не нагружать Delphi не нужной работой по
проверке на RTTI совместимость. Не забудем внести в
текст конструктора инициализацию свойств по
умолчанию: fTrueResults:= [idOk, idYes].

4.2 Метод Execute

Теперь реализуем главный метод объекта:

public
  function
Execute: Boolean;
{…}
function TMsgBox.Execute: Boolean;
begin
  fDResult:= MessageBox(fhWnd, PChar(fText), PChar(fCaption), fFlags);
  Result:= fDResult in fTrueResults;
end
;

К этому методу мы ещё вернёмся…

4.3 Дополнительные свойства — стиль

Если обратиться к пункту 2,
можно заметить, что все настройки делятся на 7
категорий

  1. Набор кнопок
  2. Тип сообщения
  3. Кнопка по умолчанмю
  4. Относительно чего модально окно
  5. Наличие кнопки «Помощь»
  6. Стиль окна
  7. Атрибуты текста

Задаём соответствующие свойства:

published
  property
Buttons: TMBBtns read GetButtons write
SetButtons default btnOK;
  property Icon: TMBIcon read GetIcon write
SetIcon default icoError;
  property DefaultButton: Integer read GetDefButton
write SetDefButton default 1;
  property Modality: TMBModality read GetModality write
SetModality default modApplModal;
  property Help: Boolean read GetHelp write
SetHelp default false;
  property WinStyle: TMBWinStyle read GetWinStyle write
SetWinStyle default [];
  property TextAttr: TMBTextAttr read GetTextAttr write
SetTextAttr default [];

Вносим в type до класса необходимые
типы:

type
  TMBBtns = (btnOK, btnOKCancel, btnAbortRetryIgnore, btnYesNo,
btnYesNoCancel, btnRetryCancel);
  TMBTextAttr = set of (taRight, taRTLReading);
  TMBModality = (modApplModal, modSystemModal, modTaskModal);
  TMBWinStyles = (modSetForeground, modDefaultDesktopOnly, modTopMost);
  TMBWinStyle = set of TMBWinStyles;
  TMBIcon = (icoNone, icoError, icoQuestion, icoWarning, icoInfo);

Вносим в private декларации методов, а в implementation —
их реализации:

private
{ Private declarations }
  function
GetButtons: TMBBtns;
  procedure SetButtons(Value: TMBBtns);
  function GetIcon: TMBIcon;
  procedure SetIcon(Value: TMBIcon);
  function GetDefButton: Integer;
  procedure SetDefButton(Value: Integer);
  function GetModality: TMBModality;
  procedure SetModality(Value: TMBModality);
  function GetHelp: Boolean;
  procedure SetHelp(Value: Boolean);
  function GetWinStyle: TMBWinStyle;
  procedure SetWinStyle(Value: TMBWinStyle);
  function GetTextAttr: TMBTextAttr;
  procedure SetTextAttr(Value: TMBTextAttr);
{…}
implementation
{…}
function TMsgBox.GetButtons: TMBBtns;
begin
{…Код, возврщаем значение} 
end;

procedure

TMsgBox.SetButtons(Value: TMBBtns);
begin
{…Код, записываем значение в поле} 

end;

{etc…}

Код для этих подпрограмм довольно громоздкий и
однообразный, желающие вникнуть, читайте моё Руководство
по Булевой Алгебе
и, если хочется, вникайте в исходники. В принципе, содержание их
имеет опосредованное отношение к делу, скажу
только, что все эти свойства читают и пишут в fFlags
какой-нибудь бит, задающий то или другое
свойство.

Итак, настал момент, когда, скопировав из исходников код, можно
откомпилировать проект, однако сперва — главная
интерфейсная задача: нарисуем компоненте
иконку…

5. Переходим к интерфейсу в IDE

5.1 Пиктограмма для панели компонент

Это делается просто: из меню Tools (можно и из
меню ПУСК) запускаем iedit.gif (397 bytes)Borland Image Editor. Там
выбираем пункт меню File/New…/Component Resource File(.dcr),
и сохраняем созданный ресурс в каталоге с
модулем и пакетом, под именем MsgBox.dcr. Теперь в
контекстном меню выбираем New/Bitmap, и видим вот
такой диалог:

04.jpg (8210 bytes)

главная задача — это выставить размер в
24×24, остальное — Ваше личное дело: хотите — 2,
хотите — 256, а хотите — и 16 цветов, и воля для
фантазии в рисовании… Созданную картинку
называем командой Rename контекстного меню по
имени компонента — TMSGBOX.

Теперь всё, что осталось — это включить
в модуль директиву {$R}, добавляющую ресурс:

{$R *.dcr}

Теперь настало время remf.gif (159 bytes)
удалить и пакета модуль, а потом снова addf.gif (137 bytes) добавить его — он
будет включен уже с ресурсом.

Острый момент — comp.gif (125 bytes) компилируем пакет… Если не
получилось — обратитесь к разработчику или
проверьтесь на исходниках.

Должно получиться следующее:

  1. Появилась вкладка Custom (последняя)
    на политре компонент

  2. На ней (если этой вкладки раньше не
    было) одна msg.bmp (2406 bytes) пиктограмма — та, которую Вы
    нарисовали, если поленились, Delphi использует def.gif (162 bytes)
    значок по умолчанию

  3. Компонента при добавлении к форме
    работает.

5.2 Что дальше…

Теперь мы переходим к части, сулящей нам
многократные компиляции и новые возможности.
Собственно, возможностей две: контекстное меню
для всей компоненты и редакторы её свойств. Ими и
займёмся. Для этого создаём новый чистый модуль (File/New/Module(Object
Repository/New))
и сохраняем его в каталоге с модулем MsgBox,
под именем MsgReg.pas.

В этот модуль нужно перенести текст процедуры Register
модуля MsgBox, не забыв включить MsgBox в
раздел Uses. На будущее пропишем там
модули времени проектирования — Dsgnintf и TypInfo,
а также модули Classes, содержащий процедуры
регистрации, Forms, Controls, Windows, Graphics, SysUtils, Dialogs — уверяю,
они все нам пригодятся.

6. Редактор компоненты

6.1 Кратко о классах TComponentEditor и TDefaultEditor

В IDE Delphi что-нибудь редактировать позволяют
разные EDITOR’ы, за компоненты отвечают TComponentEditor и
TDefaultEditor — с их помощью можно контролировать
поведение компоненты при DblClick’е в IDE и
формирование контекстного меню компоненты.
Вообще, для понимания всего этого не мешает
заглянуть в Help по теме Making components avalible at design time,
ибо там описано (хотя и бестолково), кроме всего
выше и ниже сказанного, ещё, например, добавление
Help’а компоненте.

Собственно, TDefaultEditor происходит от TComponentEditor,
но это вовсе не говорит о нём ничего хорошего. Это
редактор для компонент, для которых не
зарегистрирован никакой редактор: в контекстном
меню ветер свищет, а по DblClick’у — редактирует OnClick,
если нет, то OnChange, если нет, то OnCreate, если
нет, то первое попавшееся событие. От него можно
что-нибудь наследовать, если хочется по DblClick’у
редактировать чей-нибудь обработчик.

TComponentEditor — родительский класс для всех
редакторов компонент, предоставляет все
вышеописанные возможности. Делается это так
наследуем новый класс TMsgBoxEditor от TComponentEditor,
и начинаем переопределять ему разные методы…

TMsgBoxEditor = class(TComponentEditor)
  function GetVerbCount: Integer; override;
  function GetVerb(Index: Integer): String; override;
  procedure ExecuteVerb(Index: Integer); override;
  procedure Edit; override;
end;

6.2 Создаём меню компоненты

За контекстное меню компоненты отвечают три
метода класса TComponentEditor, в имени которых
присутствует слово Verb (в принципе, это
«глагол», но можно предположить, что
«действие»):

  1. GetVerbCount — сколько пунктов есть в меню
  2. GetVerb — возвращает по индексу текст пункта
  3. EvecuteVerb — совершает по индексу действие
    пункта

Вот этих троих мы и переопределяем (в принципе,
если концепция ясна, можно не придерживаться
текста примера):

function TMsgBoxEditor.GetVerbCount: Integer;
begin
  Result:= 1;
end;

function TMsgBoxEditor.GetVerb(Index: Integer): String;
begin
  case Index of
    0: Result:= ‘&View message’;
  end;
end;

procedure TMsgBoxEditor.ExecuteVerb(Index: Integer);
begin
  case Index of
    0: //’&View message’
      TMsgBox(Component).Execute;
  end;
end;

Мы говорим среде, что в нашем меню один пункт,
что зовут его View message и что по нажатию
этого пункта следует запустить метод Execute
редактируемой компоненты. Доступ к компоненте
соуществляется через свойство Component класса TComponentEditor,
естественно, для вызова специализированных
методов надо сообщить объекту, что он TMsgBox, а
не TComponent: TMsgBox(Component).

Регистрируем наш редактор процедуой RegisterComponentEditor,
в процедуре Register:

procedure Register;
begin
  RegisterComponents(‘Custom’, [TMsgBox]);
  RegisterComponentEditor(TMsgBox, TMsgBoxEditor);
end;

Теперь IDE знает, что для всех компонент TMsgBox
нужно использовать редактор TMsgBoxEditor, а не TDefaultEditor.

6.3 Создаём обработчик DblClick’а компоненты

За DblClick отвечает один-единственный метод Edit класса
TComponentEditor — по сути, это обработчик события,
и мы пишем:

procedure TMsgBoxEditor.Edit;
begin
  TMsgBox(Component).Execute;
end;

Здесь мы по DblClick’у просто вызываем
метод Execute (показываем диалог).

Снова comp.gif (125 bytes) компиляция, по DblClick’у — диалог.

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

7. Создаём редакторы свойств

7.1 Категории свойств

Если вспомнить контекстное меню Object Inspector,
можно вспомнить и то, что в секци Arrange есть
пункт By category, и если его выбрать. получится
очень неудобный список свойств. Несмотря на эту
его особенность, находятся люди, использующие
именно такое расположение. Оно основано на
категориях свойств, их всего 12 и каждая
представляет собой класс, в котором нужно
зарегистрировать свойство.

Название класса Строка в Object Inspector Описание
TActionCategory Action Разнообразные настройки времени
выполнения: Enabled, Hint, Visible, HelpContext
TDatabaseCategory Database Всё, связанное с базами данных: DataBaseName,
SQL, BeforeScroll, OnCalcFields
TDragNDropCategory Drag, Drop and Docking Всё про «оторви и брось:-)»: DragKind,
DragCursor, OnStartDrag
THelpCategory Help and Hints Всё про помощь итд: HelpContext, Hint, OnHelp
TLayoutCategory Layout Всё про отображение во время
проектирования: Top, Left, Align, AutoScroll
TLegacyCategory Legacy Устаревшее: Ctl3D
TLinkageCategory Linkage Связи между компонентами: DataSource, DataSet,
FileEdit
TLocaleCategory Locale Всё про локализацию в языковой среде:
BiDiMode, taRTLReading
TLocalizableCategory Localizable То, что может меняться при локализации:
всякие строки (Caption), размеры (Height, Width) и пр.
TMiscellaneousCategory Miscellaneous Всё, что не попало ни в какую другую
категорию
TVisualCategory Visual Всё, что связано с отображением: Align, Visible,
Autosize, BorderIcons
TInputCategory Input Всё, связанное с вводом: Enabled, ReadOnly,
OnKeyPressed, OnClick

Как видно, события тоже делятся на категории, а
те в свою очередь перекрываются: Enabled встречается
и как Action, и как Input.

Все категории-классы наследованы от TPropertyCategory,
следовательно, от него (или от одного из
вышеперечисленных) можно наследовать что-то
своё. Повторяю, классификация такого рода
кажется мне очень неудобной, однако, по
категориям свойства легко скрыть, чтобы они не
мешались в Object Inspector (тут актуальна
категория Legacy), и вообще, полезно
поддерживать все возможности, предоставляемые
IDE.

Итак, большинство свойств нашего объекта
относятся к визуальным категориям. Delphi большую
их часть по незнанию отправит в категорию Miscellaneous.
Надо сказать, что свойства могут
регистрироваться в категориях глобально, то есть
любое свойство сименем Enabled любой
компоненты обязательно попадёт в категорию Action,
потому что при регистрации его не был указан
класс, для которого оно регистрировалось.
Регистрация может осуществляться двумя путями, у
которых есть «подпути»:

  1. Функция RegisterPropertyInCategory регистрирует одно
    свойство в одной категории за вызов
    RegisterPropertyInCategory(THelpCategory, TMyButton, ‘HelpContext’);
    данный класс: свойство по имени
    RegisterPropertyInCategory(TVisualCategory, ‘AutoSize’); — глобально:
    свойство по имени
    RegisterPropertyInCategory(TVisualCategory, TypeInfo(Integer));
    глобально: свойства по типу
    RegisterPropertyInCategory(TVisualCategory, TypeInfo(Integer), ‘Width’);
    глобально: свойство по имени и типу
  2. Функция RegisterPropertiesInCategory регистрирует
    несколько свойств в одной категории за вызов
    RegisterPropertiesInCategory(THelpCategory, TMyButton, [‘HelpContext’, ‘Hint’,
    ‘ParentShowHint’, ‘ShowHint’]);
    — данный класс: свойства по
    именам
    RegisterPropertiesInCategory(THelpCategory, [‘HelpContext’, ‘Hint’, ‘ParentShowHint’,
    ‘ShowHint’]);
    — глобально: свойства по именам
    RegisterPropertiesInCategory(TLocalizableCategory, TypeInfo(String));
    — глобально: свойства по типу
    RegisterPropertiesInCategory(TLocalizableCategory, [‘Text’, TEdit]);
    глобально: свойства по именам, принадлежности к
    классу или типу (или имя ‘Text’ или это свойство
    класса TEdit)

Как видно, вариантов великое множество. Мы
воспользуемся тем, что нужно в нашем случае:
локальной регистрацией многих свойств. Снова
процедура Register:

procedure Register;
begin
  RegisterComponents(‘Custom’, [TMsgBox]);
  RegisterComponentEditor(TMsgBox, TMsgBoxEditor);
  RegisterPropertyInCategory(TCopyrightCategory, TMsgBox, ‘About’);
  RegisterPropertiesInCategory(TVisualCategory, TMsgBox, [‘Buttons’, ‘DefaultButton’,
‘WinStyle’
]);
  RegisterPropertiesInCategory(TLocalizableCategory, TMsgBox, [‘TextAttr’,
‘Modality’
, ‘Flags’]);
end;

Третья строчка в теле процедуры повергает в
смятение: не бывает ни свойства About у TMsgBox,
ни категории TCopyrightCategory вообще. Это не
страшно: сейчас они появятся.

7.2 Создание новых категорий свойств

Как понятно, наследовать новый класс мы будем
от TPropertyCategory. Собственно, ничего особенного
переопределять не придётся, нас интересует
только то, что будет написано в Object Inspector. Мы
пишем:

TCopyrightCategory = class(TPropertyCategory)
  class function Name: String; override;
end;
{…}
implementation
{…}
class function TCopyrightCategory.Name: String;
begin
  Result:= ‘Copyright, etc’;
end;

Отмечаю, что функция Name является атрибутом
класса, а не объекта, что говорит о способе
использования категорий самой IDE.

Вообще, ввести такую категорию,
зарегистрировать глобально для всех свойств
типа About, Copyright итд, а потом исключить из
показываемых в Object Inspector может быть удобно.

7.3 Добавление свойства About

Теперь добавим свойство About к нашему
объекту (надо же как-то закрепить за собой
авторство!).

private
  {…}
  fAbout: String;
{…}
published
  {…}
  property About: String read fAbout write SetAbout;
{…}
implementation
{…}
constructor TMsgBox.Create(AOwner: TComponent);
begin
  {…}
  fAbout:= ‘TMsgBox by Andrew Breslav, 2000’;
end;
{…}
procedure TMsgBox.SetAbout(Value: String);
begin end;

Применена хитрость, уже упоминавшаяся выше:
чтобы свойство попало в Object Inspector, но его не
могли редактировать нигде, создан пустой метод
на запись SetAbout. К этому свойству мы ещё
вернёмся.

7.4 Окончательный вариант компоненты

Теперь, перед тем, как заняться редакторами
свойств,  допишем, наконец до конца нашу
компоненту. Добавим три события: OnSuccess (если Execute
= true
), BeforeExecute и AfterExecute (до и после
запуска) и свойство Dialog (показывать или не
показывать диалог), а так же — модификацию
свойства hWnd.

7.4.1 События

Событие — это свойство процедурного типа.
Любое published-свойство, имеющее тип procedure(…)
of object или function(…) of object,
является событием. События, как правило, не имеют
методов на чтение и запись — просто поля.
Проверка на предмет, задан обработчик события
или нет, осуществляется функцией Assigned(var P),
возвращающей true, если задан, и false, если
нет.

Итак, пишем:

private
  {…}
  fOnSuccess: TNotifyEvent;
  fBExecute: TNotifyEvent;
  fAExecute: TNotifyEvent;
{…}
published
  {…}
  property OnSuccess: TNotifyEvent read fOnSuccess write
fOnSuccess;
  property BeforeExecute: TNotifyEvent read fBExecute
write fBExecute;
  property AfterExecute: TNotifyEvent read fAExecute
write fAExecute;
{…}
implementation
{…}
function TMsgBox.Execute: Boolean;
begin
  if Assigned(fBExecute) then fBExecute(Self);
  fDResult:= MessageBox(fhWnd, PChar(fText), PChar(fCaption), fFlags);
  Result:= fDResult in fTrueResults;
  if (Result) and (Assigned(fOnSuccess)) then
fOnSuccess(Self);
  if Assigned(fAExecute) then fAExecute(Self);
end;

Зарегистрируйте события, как вам понравится,
скажем в категории Action:

RegisterPropertiesInCategory(TActionCategory, TMsgBox, [‘OnSuccess’,
‘BeforeExecute’, ‘AfterExecute’]);

7.4.2 Свойства Dialog и hWnd

Не всегда оповещение должно быть визуальным,
иногда нужен лишь звук. За звук в Windows API отвечает
функция MessageBeep, параметром которой служат
ICON-флаги функции MessageBox, этим и
воспользуемся.

private
  {…}
  fDialog: Boolean;
{…}
published
  {…}
  property Dialog: Boolean read fDialog write
fDialog default true;
{…}
implementation
{…}
function TMsgBox.Execute: Boolean;
var
  Wnd: THandle;
  i: Integer;
begin
  if
Assigned(fBExecute) then fBExecute(Self);
  if not fDialog then
    begin
      if GetIcon = icoNone then i:=
MB_OK
      else i:= 16*GetNumBit(fFlags, 2);
      MessageBeep(i);
    end
  else begin
            Wnd:= fhWnd;
            if fhWnd
<= 32 then
             
case
fhWnd of
               
1: Wnd:= Application.Handle;
               
2: Wnd:= GetDesktopWindow;
              
end
;
            fDResult:=
MessageBox(Wnd, PChar(fText), PChar(fCaption), fFlags);
          end;
  Result:= fDResult in fTrueResults;
  if (Result) and (Assigned(fOnSuccess)) then
fOnSuccess(Self);
  if Assigned(fAExecute) then fAExecute(Self);
end;

В тело функции уже внесена модификация
свойства hWnd: если значение меньше 32 (ссылка
фиктивная), то по значению 1 присвоить ссылку на
приложение, а по значению 2 — ссылку на Desktop Windows.
Далее мы используем эту модификацию.

7.5 Редакторы свойств

Редакторы свойств — пожалуй самая обширная из
легкодоступных возможностей в IDE Delphi. Собственно,
редактор — это всё, что находится правее
названия свойства  в Object Inspector. Как Вы,
наверное, заметили для большинства свойств
нашего объекта Delphi назначила редакторы по
умолчанию, но оин не всегда устраивают нас.
Редактор свойства — это, как понятно, класс,
наследованный от TPropertyEditor (описанный, как и
все встречавшиеся ранее, в модуле Dsgnintf).
Есть, как понятно несколько стандартных
редакторов:

Создавая свои редакторы свойств, обращайтесь к
Borland’овским реализациям стандартных — все
примеры налицо.

Теперь стоит подробно разобрать класс TPropertyEditor:

RT Метод/свойство Параметры Тип Описание
p Designer IFormDesigner Интерфейс редактора формы (не
понадобится)
p PrivateDirectory String Каталог, где можно хранить данные,
загружать библиотеки
(HKEY_CURRENT_USER\Software\Borland\Delphi\x.0\Globals\PrivateDir)
p PropCount Integer Сколько объектов, редактируемых этим
редактора выделено на форме
p Value String То, что отображено в строке Object Inspector (read
GetValue write SetValure)
m Activate Обработчик события выбора свойства в
Object Inspector. Переопределив, можно менять свойства
редактора динамически.
m AllEqual Boolean Показывает, у всех ли выбранных объектов
значение свойства одинаково
m AutoFill Boolean Говорит, может ли Object Inspector дополнять
недовведённые значения из выпадающего списка
m Destroy Деструктор (самому вызывать
противопоказано!)
m Edit Обработчик DblClick’а по свойству в ObjectInspector
m GetAttributes TPropertyAttributes Возвращает среде параметры редактора.
Лучше переопределять и настраивать заново
m GetComponent Index: Integer TPersistent Возвращает компонент с номером Index из
всех выбранных
m GetEditLimit Integer Возвращает среде количество знаков,
которые можно ввести как значение свойства
m GetFloatValue Extended Возвращает значение свойства типа с
плавающей точкой (первой из выбранных компонент)
m GetFloatValueAt Index: Integer Extended Возвращает значение свойства типа с
плавающей точкой (компоненты с номером Index)
m GetXXXValue XXX Возвращает значение свойства типа XXX
(первой из выбранных компонент)
m GetXXXValueAt Index: Integer XXX Возвращает значение свойства типа XXX
(компоненты с номером Index)
m SetXXXValue Value: XXX Усианавливает значение свойства типа XXX
(всех выбранных компонент)

XXX может быть:

  1. Float — плавающая точка

  2. Int64 — тип Int64

  3. Method — процедурный тип

  4. Ord — перечислимый тип

  5. Str — строковый тип

  6. Var — тип Variant

m GetName String Возвращает среде имя свойства, точнее —
что написать в Object Inspector
m GetProperties Proc: TGetPropEditProc Даёт среде знать, что создавать для
подсвойств данного свойства (актуально для
классов и множеств). Процедуру, переданную в
параметре следует запустить с каждым редактором
подсвойства
m GetPropInfo PPropInfo Возвращает указатель на информацию о
свойстве: тип, значение по умолчанию итд
m GetPropType PTypeInfo Возвращает тип свойства (переопределять
себе дороже)
m GetValue String Read для Value — строковое
представление значения
m SetValue Value: String Write для Value — интерпретация
строкового представления значения
m GetValues Proc: TGetStrProc Даёт среде знать, что отображать в
выпадающем списке. Переданную процедуру нужно
вызвать для каждой строки
m GetVisualValue* String Говорит Object Inspector, что вывести в строке
свойства
m Initialize Обработчик события OnCreate-BeforeUsed
вызывается перед использованием редактора.
Можно переопределять, если нужно совершить
что-нибудь исключительное перед использованием.
m ListDrawValue const Value: string; Canvas: TCanvas; Rect: TRect;
Selected: Boolean
Рисует пункт в выпадающем списке — OwnerDraw
чистой воды, но не верьте Help’у: никакого атрибута
paOwnerDrawList в природе не бывает!
m ListMeasureHeight const Value: string; Canvas: TCanvas; var AHeight: Integer В AHeight хранится значение высоты элемента
выпадающего списка: его можно изменить.
m ListMeasureWidth const Value: string; Canvas: TCanvas; var AWidth: Integer В AWidth хранится значение ширины элемента
выпадающего списка: его можно изменить.
m Modified Процедура, вызов которой сообщает Object
Inspector, что значение свойства изменилось.
m Revert Возвращение начальных значений
свойства всем выбренным компонентам
m ValueAvailable Boolean Если в ComponentStyle выбранных объектов есть
csCheckPropAvail, определяет, для всех ли таких компонент
есть значения свойства.
m PropDrawName* ACanvas: TCanvas; const ARect: TRect;
ASelected: Boolean
Процедура рисует имя свойства в Object
Inspector
m PropDrawValue* ACanvas: TCanvas; const ARect: TRect;
ASelected: Boolean
Процедура рисует строку значения
свойства в Object Inspector, когда она не активна

* — не
документировано
m — метод
p — свойство

Теперь в дополнение — что может возвращать
главная, на мой взгляд функция — GetAttributes, и
что это значит:

Теперь теоритическую часть можно считать
законченной, переходим к созданию этих самых
редакторов свойств.

7.6 Первый  опыт

Как, видимо, стало понятно, основной метод
создание чего-то нового — это переопределение
параметральных методов старого.

Этим и займёмся. Первое, что мы сделаем будет
редактор для свойства Flags, призванный просто
запретить редактирование свойства во время
проектирования — всё равно ничего не понятно.

Наследуем новый класс от TIntegerProperty и
переопределяем метод GetAttributes:

TROIntProperty = class(TIntegerProperty)
  function GetAttributes: TPropertyAttributes; override;
end;
implementation
{…}
function TROIntProperty.GetAttributes:
TPropertyAttributes;
begin
  Result:= inherited GetAttributes + [paReadOnly];
end;

Вот и всё. Удобно, не правда ли? Регистрируем
созданный редактор в процедуре Register:

procedure Register;
begin
  RegisterComponents(‘Custom’, [TMsgBox]);
  {…}
  RegisterPropertyEditor(TypeInfo(DWord), TMsgBox, ‘Flags’, TROIntProperty);
end;

comp.gif (125 bytes)
Компиляция — свойство не редактируется!

7.7 Редактор свойства Icon

Теперь займёмся действительно созданием
чего-то интерерсного, и сперва это будет редактор
свойства Icon. Задача наша состоит в том, чтобы
в списке отображались те иконки, которые
впоследствии отобразятся на окне диалога. Скажем
сразу, что эти иконки — стандартные, входят в
число 6 стандартных пиктограмм модуля user32.dll ядра
Windows. Достать их можно, если передать нулевую
ссылку и предопределённый идентификатор функции
LoadIcon Win32 API, которая с радосстью вернёт нам HICON.
Впоследствии его можно рисовать где угодно с
помощью функции того же API DrawIcon.

Итак, наследуем от TEnumProperty новый класс и
переопределяем его методы:

TMBIconProperty = class(TEnumProperty)
  function GetAttributes: TPropertyAttributes; override;
  procedure ListDrawValue(const Value: string;
ACanvas: TCanvas;
                                           
const
ARect: TRect; ASelected: Boolean); override;
  procedure ListMeasureWidth(const Value: string;
ACanvas: TCanvas;
                                                
var
AWidth: Integer); override;
  procedure ListMeasureHeight(const Value: string;
ACanvas: TCanvas;
                                                
var AHeight: Integer); override;
  procedure PropDrawValue(ACanvas: TCanvas; const ARect:
TRect;
                                             
ASelected: Boolean); override;
end;

Реализуем GetAttributes: список, можно много
выбирать, можно и отменить:

function TMBIconProperty.GetAttributes: TPropertyAttributes;
begin
  Result:= [paValueList, paMultiSelect, paRevertable];
end;

Реализуем ListMeasureHeight и ListMeasureWidth:

procedure TMBIconProperty.ListMeasureWidth(const Value:
string; ACanvas: TCanvas; var AWidth: Integer);
begin
  AWidth:= AWidth+GetSystemMetrics(SM_CXSMICON);
end;

procedure TMBIconProperty.ListMeasureHeight(const Value:
string; ACanvas: TCanvas; var AHeight: Integer);
begin
  if AHeight < GetSystemMetrics(SM_CYSMICON) then
    AHeight:= GetSystemMetrics(SM_CYSMICON);
end;

Смысл кода этих методов а том, чтобы всё, что мы
хотим нарисовать, поместилось в пункт списка, и
при этом уже определённые сваойства не
пострадали. GetSystemMetrics возарщает размерв
системных элементов (в данном случае маленьких
иконок) — его мы и используемдля определения
ширины и высоты области элемента.

Реализуем ListDrawValue:

procedure TMBIconProperty.ListDrawValue(const Value:
string; ACanvas: TCanvas; const ARect: TRect; ASelected:
Boolean);
var
  IDI: PChar;
begin
  IDI:= nil;
  if Value = ‘icoError’ then
    IDI:= IDI_ERROR
  else if Value = ‘icoQuestion’ then
            IDI:= IDI_QUESTION
  else if Value = ‘icoWarning’ then
            IDI:= IDI_WARNING
  else if Value = ‘icoInformation’ then
            IDI:= IDI_INFORMATION;
  with ACanvas do
    try
      if ASelected then Brush.Color:=
clHighlight;
      FillRect(ARect);
      DrawIconEx(ACanvas.Handle, ARect.Left, Arect.Top,
LoadIcon(0, IDI), GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0,
                            
0, DI_NORMAL or DI_DEFAULTSIZE);
    finally
      inherited ListDrawValue(Value, ACanvas,
Rect(ARect.Left+GetSystemMetrics(SM_CXSMICON), Arect.Top, ARect.Right, ARect.Bottom),
ASelected);
    end;
end;

Суть в том, чтобы в зависимости от значения
рисуемого пункта, отобразить ту или другую
иконку из числа стандартных (IDI_XXX
идентификаторы, определённые в модуле Windows), а
потом — пусть Delphi рисует то, что хотела раньше, но
чуть-чуть правее. Размеры картинки нам даёт
небезызвестная GetSystemMetrics.

Теперь, всё, что мы хотим, кроме всеобщего
счастья — это чтобы пиктограмма отображалась и
тогда, когда списка нет. К сожалению, непосильной
представляется задача рисовать иконку во время
редактирования свойства, однако, в любое другое
время — метод PropDrawValue всегда с нами:

procedure TMBIconProperty.PropDrawValue(ACanvas: TCanvas; const
ARect: TRect;
ASelected: Boolean);
begin
  if
GetVisualValue <> » then
    ListDrawValue(GetVisualValue, ACanvas, ARect, false)
  else
    inherited PropDrawValue(ACanvas, ARect, ASelected);
end;

(Этот метод честно скопирован с Borland’овского
оригинала.) Здесь всё, что мы делаем, это говорим,
что когда в строке значения свойства что-нибудь
отображено (у всех выбранных объектов значение
свойства одинаково), можно нарисовать на ней то
же, что и при рисовании в списке невыбранного
элемента (мы нигде ничего не говорим про фон — он
остаётся по умолчанию).

Регистрируем созданный редактор в процедуре Register:

procedure Register;
begin
  RegisterComponents(‘Custom’, [TMsgBox]);
  {…}
  RegisterPropertyEditor(TypeInfo(TMBIcon), TMsgBox, ‘Icon’,
TMBIconProperty);
end;

comp.gif (125 bytes)
Компиляция — всё должно работать.

7.8 Работа с ресурсами: редактор свойства Modality

Отличие редактора свойства Modality в том, что
символы только двух из трёх его значений
доступны как системные, третий символ придётся
включить самим. Отличие составит только метод ListDrawValue.
Картинку, в принципе, можно подгружать откуда
угодно, но надёжнее всего — из прилинкованного к
пакету ресурса.

Создайте ресурс чем угодно: Delphi Image Editor, Resources
WorkShop, Symantec Resource Studio, etc…

Положите в него под именем «icoTaskModal» соответствующую
идее иконку, я взял её из набора от MS Visual Studio (честное
слово, кроме иконок, этот пакет представляет мало
хорошего): icoTaskModal.gif (246 bytes). Теперь сохраним ресурс под
именем MsgReg.res и прилинкуем его упоминавшейся
директивой {$R}:

{$R *.res}

В код включено условное изменение ссылки на
экземпляр приложения: 0 для стандартных и hInstance для
ресурсной иконки:

procedure TMBModalProperty.ListDrawValue(const Value:
string; ACanvas: TCanvas; const ARect: TRect; ASelected:
Boolean);
var
  IDI: PChar;
  Inst: THandle;
begin
  IDI:= IDI_APPLICATION;
  Inst:= 0;
  if Value = ‘modTaskModal’ then
    begin
      IDI:= ‘icoTaskModal’;
      Inst:= hInstance;
    end
  else if Value = ‘modSystemModal’ then
            IDI:= IDI_WINLOGO;
  with ACanvas do
    try
      if ASelected then Brush.Color:=
clHighlight;
      FillRect(ARect);
      DrawIconEx(ACanvas.Handle, ARect.Left, Arect.Top,
LoadIcon(Inst, IDI), GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
                             0,0,
DI_NORMAL or DI_DEFAULTSIZE);
    finally
      inherited ListDrawValue(Value, ACanvas,
Rect(ARect.Left+GetSystemMetrics(SM_CXSMICON), Arect.Top, ARect.Right, ARect.Bottom),
ASelected);
    end;
end;

Надо сказать, что при работе с ресурсами вообще,
а тем более — в пакетах, стоит соблюдать
предельную осторожность, использовать как можно
больше Win32 API, а из VCL — только специализированные
методы. Это важно, потому что ошибка системного
уровня в Вашем пакете немедленно вызовет
критическую ошибку Delphi, и хорошо, если Вы, а не
нервный заказчик, потеряете несохранённые
данные.

7.9 Расслабимся и развлечёмся: создание
редактора свойства About

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

Сделаем так: пусть…

  1. в Object Inspector отображается не лаконичное «About»,
    а развёрнутое «About this component»
  2. этот текст выводится красным цветом
  3. ему предшествует пиктограмма компоненты
  4. по DblClick’у показывается диалог с текстом
  5. вместо выпадающего списка демонстрируется Ваш
    логотип

TAboutProperty = class(TPropertyEditor)
  function GetAttributes: TPropertyAttributes; override;
  function GetName: String; override;
  procedure PropDrawName(ACanvas: TCanvas; const ARect:
TRect;
                                             ASelected:
Boolean); override;
  procedure Edit; override;
  procedure ListDrawValue(const Value: string;
ACanvas: TCanvas;
                                           const
ARect: TRect; ASelected: Boolean); override;
  procedure ListMeasureWidth(const Value: string;
ACanvas: TCanvas;
                                                var
AWidth: Integer); override;
  procedure ListMeasureHeight(const Value: string;
ACanvas: TCanvas;
                                                var
AHeight: Integer); override;
  function GetValue: string; override;
  procedure GetValues(Proc: TGetStrProc); override;
end;

GetAttributes: список, не редактируемо, выделяйте
сколько угодно:

function TAboutProperty.GetAttributes:
TPropertyAttributes;
begin
  Result:= [paValueList, paReadOnly, paMultiSelect];
end;

GetName: «About this component»:

function TAboutProperty.GetName: String;

begin
  Result:= ‘About this component…’;
end;

PropDrawName: рисуем иконку, и сразу за ней текст
(красный):

procedure TAboutProperty.PropDrawName(ACanvas: TCanvas; const
ARect: TRect; ASelected: Boolean);
begin
  DrawIconEx(ACanvas.Handle, 0, ARect.Top, LoadIcon(hInstance, ‘logo’),
GetSysTemMetrics(SM_CXSMICON), GetSysTemMetrics(SM_CXSMICON),
                         0,0,DI_NORMAL
or DI_DEFAULTSIZE);
  ACanvas.Font.Color:= clRed;
  inherited PropDrawName(ACanvas,
Rect(GetSystemMetrics(SM_CXSMICON), ARect.Top, ARect.Right, ARect.Bottom), ASelected);
end;

Edit: показываем незамысловатый диалог: ShowMessage
(если не нравится, применяйте MessageBox):

procedure TAboutProperty.Edit;
begin
  ShowMessage(‘TMsgBox — Delphi VCL component’#13#10 +

                          ‘Created
and used as an exaple’#13#10
+

                          ‘in
the Tutorial of designing components’#13#10
+

                          ‘©
Andrew Breslav, St.Petersburg, Russia, y. 2000’
);
end;

ListMeasureXXX: зависит от того, что Вы нарисовали
себе в логотип. Я изобразил следующее:

logo.gif (2166 bytes)

имеет оно размеры 110×40, исходя из этого:

procedure TAboutProperty.ListMeasureWidth(const Value:
string; ACanvas: TCanvas;

                                                                         var
AWidth: Integer);
begin
  AWidth:= 110;
end;

procedure TAboutProperty.ListMeasureHeight(const Value: string;
ACanvas: TCanvas;

                                                                         var
AHeight: Integer);
begin
  AHeight:= 40;
end;

ListDrawValue: настал тот момент, когда вновь
нужно обратиться к ресурсу (запихните туда BMP,
назовите «About»), но здесь Win32 API уже мало
полезен: придётся воспользоваться стандартным
классом TBitMap из модуля Graphics и его
методом LoadFromResourceName. Итак, заливаем фон,
загружаем, центрируем, рисуем:

procedure TAboutProperty.ListDrawValue(const Value:
string; ACanvas: TCanvas;
                                                                    const
ARect: TRect; ASelected: Boolean);
var
  Bmp: TBitMap;
begin
  ACanvas.Brush.Color:= $0033FFFF;
  ACanvas.FillRect(ARect);
  Bmp:= TBitmap.Create;
  Bmp.LoadFromResourceName(hInstance, ‘About’);
  ACanvas.Draw((ARect.Right — 110) div 2,0,Bmp);
  Bmp.Free;
end;

GetValue: Чтобы видеть текст, нужно его вернуть:
TPropertyEditor не предствляет, как это сделать:

function TAboutProperty.GetValue: string;

begin
  Result:= TMsgBox(GetComponent(0)).About;
end;

GetValues: чтобы список отображался, в нём
должен быть хотя бы один элемент:

procedure TAboutProperty.GetValues(Proc: TGetStrProc);
begin
  Proc(‘);
end;

Регистрируем созданный редактор в процедуре Register:

procedure Register;
begin
  RegisterComponents(‘Custom’, [TMsgBox]);
  {…}
  RegisterPropertyEditor(TypeInfo(String), TMsgBox, ‘About’,
TAboutProperty);
end;

comp.gif (125 bytes)
Компиляция — весёленький About!

**Кстати, если теперь посмотреть на
категории свойств, нашей категории «Copyright,
etc»
мы не увидим: Delphi распознаёт свойства по
именам, возвращаемым GetName редактора, а
поскольку мы возвращаем не лаконичное «About»,
а развёрнутое «About this component»,
регистрация краткого имени для этого свойства
недействительна, напишите:

procedure Register;
begin
  RegisterComponents(‘Custom’, [TMsgBox]);
  {…} 
 
RegisterPropertyInCategory(TCopyrightCategory, TMsgBox, ‘About this
component…’
);
  {…}

  RegisterPropertyEditor(TypeInfo(String), TMsgBox, ‘About’,
TAboutProperty);
end;

comp.gif (125 bytes)
Компиляция — теперь лучше.

7.10 Усложнение задачи: создание редактора
свойства hWnd

Как мы помним, для это свойство обладает
некоторой спецификой: его значение
интерпретируется перед тем, как быть передано в
функцию MessageBox. Поскольку задать значене
ссылки руками, то есть числом, и во время
проектированя невозможно, не и смысла
предоставлять такой интерфейс. Лучше красиво
обработать три варианта значения: ноль,
приложение, Desktop. Хорошо то, что для этих значений
не нужно вводить типов и идентификаторов: они
известны, и их число конечно: три.

Итак, что мы хотим:

  1. Свойство вручную не редактируемо
  2. Выпадающий список с пиктограммами
  3. Строки в списке: «Application», «Desktop»,
    «NULL»
  4. По выбору строки свойство устанавливается
    соответственно в 1,2,0

Декларируем:

TMBHWndProperty = class(TIntegerProperty)
  function GetAttributes: TPropertyAttributes; override;
  procedure ListDrawValue(const Value: string;
ACanvas: TCanvas;
                                            const
ARect: TRect; ASelected: Boolean); override;
  procedure ListMeasureWidth(const Value: string;
ACanvas: TCanvas;

                                                 var
AWidth: Integer); override;
  procedure ListMeasureHeight(const Value: string;
ACanvas: TCanvas;

                                                  var
AHeight: Integer); override;
  procedure PropDrawValue(ACanvas: TCanvas; const ARect:
TRect;
                                              ASelected:
Boolean); override;
  function GetValue: String; override;
  procedure SetValue(const Value: String);
override;
  procedure GetValues(Proc: TGetStrProc); override;
end;

С точки зрения рисования списка всё должно быть
ясно. Займёмся делом.

GetAttributes: список, R/O, выбирайте хоть все,
хочешь — отмени:

function TMBHWndProperty.GetAttributes:
TPropertyAttributes;
begin
  Result:= [paValueList, paReadOnly, paMultiSelect, paRevertable];
end;

GetValues: те самые три строки:

procedure TMBHWndProperty.GetValues(Proc: TGetStrProc);
begin
  Proc(‘Application’);
  Proc(‘Desktop’);
  Proc(‘NULL’);
end;

G|SetValue: интепретировать число в строку и
обратно:

function TMBHWndProperty.GetValue: String;
var
  i: Integer;
begin
  for i:= 0 to PropCount — 1 do
    begin
      Result:= IntToStr(TMsgBox(GetComponent(i)).hWnd);
      case TMsgBox(GetComponent(i)).hWnd of
        1: Result:= ‘Application’;
        2: Result:= ‘Desktop’;
        else Result:= ‘NULL’;
      end;
    end;
end;

procedure TMBHWndProperty.SetValue(const Value: String);
var
  i: Integer;
begin
  for i:= 0 to PropCount — 1 do
    begin
      if Value = ‘NULL’ then TMsgBox(GetComponent(i)).hWnd:=
0
      else if Value = ‘Application’ then
TMsgBox(GetComponent(i)).hWnd:= 1
      else if Value = ‘Desktop’ then
TMsgBox(GetComponent(i)).hWnd:= 2
      else TMsgBox(GetComponent(i)).hWnd:=
StrToInt(Value);
    end;
end;

Регистрируем созданный редактор в процедуре Register:

procedure Register;
begin
  RegisterComponents(‘Custom’, [TMsgBox]);
  {…}
  RegisterPropertyEditor(TypeInfo(HWND), TMsgBox, ‘hWnd’, TMBHWndProperty);
end;

comp.gif (125 bytes)
Компиляция — должно работать.

7.11 Высокий уровень сложности: свойство TrueResults

Вся сложность состоит в том что

  1. нет идентификаторов для элементов множества (кто
    не заметил — свойства до сих пор нет в Object Inspector)
  2. редактор должен состоять из двух: для множества
    и для элементов,  но класс TSetElementProperty написан
    безголово с точки зрения дальнейшего
    наследования: очень важное поле упрятано в private,
    следовательно придётся просто скопировать код
    этого класса и надставить своим… Занятие не
    отвечает концепциям ООП, но что делать?

Сперва решим вопрос с идентификаторами:
создадим прямо в модуле MsgReg (в секции
implementation) тип TRT:

type
  TRT = (idOK, idCancel, idAbort, idRetry, idIgnore, idYes, idNo, idClose, idHelp);

Этот тип заменит библиотеку имён.

Создаём редактор для множества:

TMBTRProperty = class(TSetProperty)
  function GetValue: String; override;
  procedure GetProperties(Proc: TGetPropEditProc); override;
end;

GetValue: идею честно крадём у Borland:

function TMBTRProperty.GetValue: String;

var
  S: TIntegerSet;
  TpInfo: PTypeInfo;
  I: Integer;
begin
  Integer(S):= GetOrdValue;
  TpInfo:= TypeInfo(TRT);
  Result := ‘[‘;
  for I := 0 to SizeOf(Integer) * 8 — 1 do
    if I in S then
      begin
        if Length(Result) <> 1 then
Result := Result + ‘,’;
        Result := Result + GetEnumName(TpInfo, I — 1);
      end;
  Result := Result + ‘]’;
end;

Собственно, изменено в оригинальном коде очнь
мало: вторая строчка в теле процедуры: сложное
определение типа заменено его непосредственным
идентификатором — мы подменяем любой тип,
редактируемого свойства типом TRT. Суть Borland’овского
кода в том, что если представить множество как TIntegerSet,
можно получить доступ к его элементам через биты
числа, хотя эта технология ограничена
множествами с не более чем 32 элементами (иначе их
не дают вносить в published).

GetProperties: тоже крадём и также реализуем
подмену:

procedure TMBTRProperty.GetProperties(Proc:
TGetPropEditProc);
var
  I: Integer;
begin
  with
GetTypeData(GetTypeData(GetPropType)^.CompType^)^ do
    for I := MinValue to MaxValue do
      Proc(TMBTResProperty.Create(Self, I));
end;

Код станет ясен до конца, когда мы реализуем TMBResProperty
— для элементов.

Им и займёмся: из модуля Dsgnintf честно
переписываем декларацию и реализацию TSetElementProperty
(подчёркнуты привнесённые места):

TMBTResProperty = class(TNestedProperty)
protected
  FElement: Integer;
  constructor Create(Parent: TPropertyEditor; AElement: Integer); reintroduce;
public
  function AllEqual: Boolean; override;
  function GetAttributes: TPropertyAttributes; override;
  function GetName: string; override;
  function GetValue: string; override;
  procedure GetValues(Proc: TGetStrProc); override;
  procedure SetValue(const Value: string); override;
end;

Директива reintroduce связана с замещением
конструктора TPropertyEditor в классе TNestedProperty
в целях самореализации. Новшеством в отношении FElement
является его перенесение из private в protected
(это и есть то самое поле!) — из жалости к
ближнему.

constructor TMBTResProperty.Create(Parent:
TPropertyEditor; AElement: Integer);
begin
  inherited Create(Parent);
  FElement := AElement;
end;

function TMBTResProperty.AllEqual: Boolean;
var
  I: Integer;
  S: TIntegerSet;
  V: Boolean;
begin
  Result := False;
  if PropCount > 1 then
    begin
      Integer(S) := GetOrdValue;
      V := FElement in S;
      for I := 1 to PropCount —
1 do
        begin
          Integer(S) := GetOrdValueAt(I);
          if (FElement in
S) <> V then Exit;
        end;
    end;
  Result := True;
end;

function TMBTResProperty.GetAttributes: TPropertyAttributes;
begin
  Result := [paMultiSelect, paValueList, paSortList];
end;

function TMBTResProperty.GetName: string;
begin
  Result:= GetEnumName(TypeInfo(

TRT), FElement — 1);
end;

function TMBTResProperty.GetValue: string;
var
  S: TIntegerSet;
begin
  Integer(S) := GetOrdValue;
  Result := BooleanIdents[FElement in S];
end;

procedure TMBTResProperty.GetValues(Proc: TGetStrProc);
begin
  Proc(BooleanIdents[False]);
  Proc(BooleanIdents[True]);
end;

procedure TMBTResProperty.SetValue(const Value: string);
var
  S: TIntegerSet;
begin
  Integer(S) := GetOrdValue;
  if CompareText(Value, ‘True’) = 0 then
    Include(S, FElement) else
    Exclude(S, FElement);
  SetOrdValue(Integer(S));
end;

Как видно, всё делалось исключительно
ради метода GetName! Господа! Умоляю вас, пишите
свои классы по-человечески!

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

procedure Register;
begin
  RegisterComponents(‘Custom’, [TMsgBox]);
  {…}
  RegisterPropertyEditor(TypeInfo(TMBTRes), TMsgBox, ‘TrueResults’,
TMBTRProperty);
end;

comp.gif (125 bytes)
Компиляция — должно работать.

8. Конец первой части

Должен сообщить Вам приятную вещь: если
прочитанное Вам не прошло бесследно, то Вы должны
знать, как писать пока простые компоненты и
редакторы свойств к ним (мы не разбирали свойства-классы
(никак не удалось их запихать в TMsgBox) — если
торопитесь, загляните вперёд, если чувствуете
уверенность, то в модуль Dsgnintf: TFontProperty)).
Далее, во второй части мы займёмся
проектированием визуальной компоненты, и тут уже
главным будет объект, а не редакторы свойств.
Правда и всей широты вопроса охватить не удастся.

Hosted by uCoz

Выбор базового класса

 Приступая к разработке нового компонента, следует четко сформулировать назначение компонента. Затем необходимо определить, какой из компонентов Delphi наиболее близок по своему назначению, виду и функциональным возможностям к компоненту, который разрабатывается. Именно этот компонент следует выбрать в качестве базового. 

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

Рис. 16.1. Диалоговое окно New Component

 

Поле Ancestor type должно содержать базовый тип для создаваемого компонента. Базовый тип компонента можно задать непосредственным вводом имени типа или выбором из раскрывающегося списка. Для разрабатываемого компонента базовым компонентом является стандартный компонент Edit (поле ввода-редактирования). Поэтому базовым типом для типа разрабатываемого компонента является тип TEdit.

В поле Class Name необходимо ввести имя класса разрабатываемого компонента, например THkedit. Вспомните, что в Delphiимена типов должны начинаться буквой т. В поле Palette Page нужно ввести имя вкладки палитры компонентов, на которую после создания компонента будет добавлен его значок. Название вкладки палитры компонентов можно выбрать из раскрывающегося списка.

Если в поле Palette Page ввести имя еще не существующей вкладки палитры компонентов, то непосредственно перед добавлением компонента вкладка с указанным именем будет создана.

В поле Unit file name находится автоматически сформированное имя файла модуля создаваемого компонента. Delphiприсваивает модулю компонента имя, которое совпадает с именем типа компонента, но без буквы т. Щелкнув на кнопке с тремя точками, можно выбрать каталог, в котором должен быть сохранен модуль компонента.

После нажатия кнопки ОК к текущему проекту добавляется сформированный Delphi-модуль, представляющий собой заготовку (шаблон) модуля компонента. Текст этого модуля приведен в листинге 16.1.

Листинг 16.1. Шаблон модуля компонента

01.unit NkEdit;

02.interface

03.uses

04.Windows, Messages, SysUtils, Classes, Controls, StdCtrls;

05.type

06.TEditl - class(TEdit)

07.private

08.

09.protected

10.

11.public

12.

13.published

14.

15.end;

16.procedure Register;

17.implementation

18.procedure Register;

19.begin

20.Register<a href="http:

21.endend.

В объявлении нового класса указан только тип родительского класса. В раздел реализации помещена процедура Register, которая используется во время установки созданного программистом компонента на указанную вкладку палитры компонентовDelphi для регистрации нового класса.

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

В листинге 16.2 приведен текст модуля компонента NkEdit после внесения всех необходимых изменений.

Листинг 16.2. Модуль компонента NkEdit

01.unit NkEdit;

02.interface

03.uses

04.Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,

05.Dialogs, StdCtrls;

06.type

07.TNkEdit - class(TEditl

08.private

09.FNumb: single

10.

11.

12.function GetNumb: single;

13.procedure SetNumb(value:single];

14.protected

15.procedure KeyPress(var Key: Char); override;

16.public

17.published

18.constructor Create [AOwner:T<a href="http:

19.property Numb : single 

20.read GetNumb

21.write SetNumb;

22.end;

23.procedure Register;

24.implementation

25.

26.procedure Register;

27.begin

28.Register<a href="http:

29.end;

31.constructor TNkEdit. Create (AOwnsr:T<a href="http:

32.begin

33.

34.inherited Create (AOwrier) ;

35.end;

36.

37.function TNkEdit .GetNumb: single;

38.begin

39.try 

40.Result:=StrToFloat(text) ;

41.except

42.on EConvert Error do

43.begin

44.Result :=Q;

45.text : = " ;

46.end;

47.end;

48.end;

49.

50.procedure TNkEdit .3etNumb(Value:single] ;

51.begin

52.Fnumb:=Value;

53.Text :=FloatToStr (value) ;

54.end;

55.

56.procedure TNkEdit.KeyPress(var key:charl ;

57.begin

58.case key of

59.'0'..'3','#13','#0';

60.'-'if Length (text )<>0 then key:='#0';

61.else

62.if not ( (key = DecimalSeparator] and

63.(Pos(DecimalSeparator,text)=0))

64.then key:= %0;

65.end;

66.inherited KeyPress(key); 

67.

68.endend.

В описание класса TNkEdit добавлено объявление свойства Numb, которое представляет собой число, находящееся в поле редактирования. Для хранения значения свойства Numb используется поле FNumb. Функция GetNumb необходима для доступа к полю FNumb, а процедура setNumb — для установки значения свойства.

Конструктор класса TNkEdit сначала вызывает конструктор родительского класса (TEdit), присваивает значение свойству Text, затем устанавливает значение свойства Numb.

Реакция компонента NkEdit на нажатие клавиши клавиатуры определяется процедурой обработки события TNkEdit.KeyPress, которая замещает соответствующую процедуру базового класса. В качестве параметра процедура THkEdit-Keypress получает код нажатой клавиши. Перед вызовом процедуры обработки события onkeypress родительского класса код нажатой клавиши проверяется на допустимость. Если нажата недопустимая для компонента NkEdit клавиша, то код символа заменяется на ноль. Допустимыми для компонента Nkedit являются цифровые клавиши, разделитель целой и дробной частей числа (в зависимости от настройки Windows: точка или запятая), «минус», (позволяет удалить ошибочно введенный символ) и .

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

После ввода текста модуля компонента модуль нужно откомпилировать и сохранить.

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

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

Создается тестовое приложение обычным образом: сначала создается форма приложения, а затем — модуль приложения.

Вид формы приложения тестирования компонента NkEdit приведен на рис. 16.2.

Рис. 16.2. Форма приложения Тест компонента NkEdit

 

Форма содержит две метки и командную кнопку. Первая метка предназначена для вывода информационного сообщения, вторая метка (на рисунке она выделена) используется для вывода числа, введенного в поле редактирования. Самого поля редактирования компонента NkEdit в форме нет.

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

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

  1. В список используемых модулей (раздел uses) добавить имя модуля тестируемого компонента (NkEdit).
  2. В раздел объявления переменных (var) добавить инструкцию объявления компонента. Здесь следует вспомнить, что компонент является объектом, поэтому объявление компонента в разделе переменных не обеспечивает созданиекомпонента, а только генерирует указатель на компонент, следовательно необходима инструкция вызова конструктора объекта, которая действительно создает компонент (объект).
  3. Для формы приложения создать процедуру обработки события OnCreate, которая вызовом конструктора тестируемогокомпонента создаст компонент и установит значения его свойств.

В листинге 16.3 приведен модуль приложения тестирования компонента NkEdit.

Листинг 16.3. Тест компонента NkEdit

01.unit tstNkEdit_;

02.interface

03.Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls,

04.Foims, Dialogs, StdCtrls,

05.NkEdit; 

06.type

07.TForml = class(TForm)

08.Label!: TLabel;

09.Label2: TLabel;

10.Buttonl: TButton;

11.procedure FomCreate (Sender: TObject);

12.procedure ButtonlClick(Sender: TObject);

13.private

14.

15.public

16.

17.end;

18.var

19.Forml: TForml;

20.myEdit: TnkEdit; 

21.impletnen ta ti on

22.{$R *.dfm}

23.procedure TForml.FormCreate(Sender: TObject);

24.begin

25.

26.myEdit := TNkEdit.Create(self];

27.myEdit.Parent := self;

28.myEdit.Left := 8;

29.myEdit.Top := 64;

30.end;

31.procedure TForml.ButtonlClick(Sender: TObject);

32.begin

33.Iabel2.Caption :- FloatToStr(myEdit.Numb);

34.endend.

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

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

На рис. 16.3 приведено окно программы Тест компонента NkEdit во время ее работы, после ввода числа в поле редактирования и щелчка на кнопке Тест.

Рис. 16.3. Тестирование компонента. Поле ввода— компонент NkEdit

 

Для того чтобы значок компонента появился в палитре компонентов, компонент должен быть добавлен в один из пакетов (Packages) компонентов Delphi. Пакет компонентов — это файл с расширением dpk (Delphi Package File). Например, компоненты, созданные программистом, находятся в пакете DclusrVO.dpk.

Во время добавления компонента в пакет Delphi использует модуль компонента и файл ресурсов компонента, в котором должен находиться битовый образ значка компонента. Имя файла ресурсов компонента должно обязательно совпадать с именем файла модуля компонента. Файл ресурсов имеет расширение dcr (Dynamic Component Resource). Битовый образ, находящийся внутри файла ресурсов, должен иметь имя, совпадающее с именем класса компонента.

Файл ресурсов компонента можно создать при помощи утилиты Image Editor, которая запускается выбором из меню Tools команды Image Editor. Для того чтобы создать новый файл ресурса компонента, нужно из меню File выбрать команду New и из появившегося списка выбрать тип создаваемого файла — Component Resource File (рис. 16.4).

Рис. 16.4. Выбор типа создаваемого файла

 

В результате открывается окно файла ресурсов Unfitted I.dcr, а в меню диалогового окна Image Editor появляется новый пункт — Resource. Теперь нужно из меню Resource выбрать команду New/Bitmap и в открывшемся окне Bitmap Properties (рис. 16.5) установить характеристики битового образа значка компонента: Size — 24×24 пиксела, Colors — 16.

Рис. 16.5. Диалоговое окно Bitmap Properties

 

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

Изображение в окне графического редактора можно увеличить. Для этого необходимо выбрать команду Zoom In меню View. Следует обратить внимание, что нвет правой нижней точки рисунка определяет «прозрачный» цвет. Элементы значкакомпонента, закрашенные этим цветом, на палитре компонентов Delphi не видны.

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

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

На рис. 16.6 приведен вид окна Image Editor, в левой части которого содержится файл ресурсов компонента TNkEdit (nkedit.dcr), а в правой части окно редактора битового образа, в котором находится изображение значка для создаваемого компонента.

Внимание!: Имя файла ресурсов компонента (NkEdit.dcr) должно совпадать с именем модуля компонента (NkEdit. pas), а имя битового образа (TNkEdit) — с именем класса компонента (TkNEdit).

Рис. 16.6. Значок компонента NkEdit

 

После создания файла ресурсов компонента, в котором находится битовый образ значка компонента, можно приступить к установке компонента. Для этого надо из меню Component выбрать команду Install Component и заполнить поля открывшегося окна Install Component (рис. 16.7).

Рис. 16.7. Диалоговое окно Install Component

В поле Unit file name нужно ввести имя файла модуля. Для этого удобно воспользоваться кнопкой Browse. Поле Search path (Путь поиска) должно содержать разделенные точкой с запятой имена каталогов, в которых Delphi во время установкикомпонента будет искать необходимые файлы, в частности файл ресурсов компонента. Если имя файла модуля было введено в поле Unit file name выбором файла из списка, полученного при помощи кнопки Browse, то Delphi автоматически добавляет в поле Search path имена необходимых каталогов.

Примечание: Файл ресурса компонента должен находиться в одном из каталогов, перечисленных в поле Search path. Если его там нет, то компоненту будет назначен значок его родительского класса.

Поле Package file name должно содержать имя пакета, в который будет установлен компонент. По умолчанию компоненты, создаваемые программистом, добавляются в пакет Dclu.sr70.dpk.

Поле Package description содержит название пакета. Для пакета DclusrTO.dpk ЭТО Текст: Borland User’s Components. 

После заполнения перечисленных полей и нажатия кнопки ОК начинается процесс установки. Сначала на экране появляется окно Confirm (рис. 16.8), в котором Delphi просит подтвердить обновление пакета.

 Рис. 16.8. Запрос подтверждений обновления пакета в процессе установки компонента

  

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

Рис. 16.9. Сообщение об успешной установке компонента 

После установки компонента в пакет открывается диалоговое окно Package (Редактор пакета компонентов) (рис. 16.10), в котором перечислены компоненты, находящиеся в пакете.

Рис. 16.10. Окно редактора пакета компонентов 

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

Помоги проекту! Расскажи друзьям об этом сайте:

Это не очередная копия той самой статьи (сколько ей уже лет?), которая была написана еще под Borland Delphi. Ниже поэтапно описан опыт разработки своего компонента на реальном примере с более глубоким пояснением параметров компонента для Design Time. Возможно, у кого-то есть решения и получше, однако, это мой первый компонент и я очень рад, что разобрался.

Этап 1. Постановка задачи

Так как я часто использую различный диалоговые окна в своих программах, то первым же своим компонентом я решил сделать панельку с кнопками «Ок» и «Отмена«. Соответственно, мне понадобиться создавать новый компонент на основе класса TPanel и внутри него создать две кнопки (класса TButton).

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

Итак, параметры для TPanel:

  • выравнивание по умолчанию по нижнему краю формы (или родительского компонента)
  • отсутствие рельефа ( BevelOuter поставить в значение bvNone)
  • выключен показ названия панели ( ShowCaption поставить в значение False )

Параметры для кнопок на панели:

  • все выровнены по правому краю
  • каждая из кнопок может быть показана или скрыта
  • кнопка «Ok» должна возвращать в качестве ModalResult значение mrOk, а кнопка «Cancel» — mrCancel

Как это должно выглядеть (настроено вручную):

Этап 2. Создание компонента

Далее — придерживаемся какое-то время той самой инструкции.

  1. Открываем меню Component и выбираем New Component

  2. Появляется окно со всеми классами визуальных компонентов, нам нужно выбрать «предка», которому собираемся создать «потомка» с новыми функциями

  3. С помощью поиска сразу находим TPanel

  4. Далее — выберем название и расположение на панели инструментов для создания компонентов

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

Этап 3. Добавление кнопок в конструктор

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

В разделе public запишем такой код:

constructor Create(AOwner: TComponent); override;

С его помощью мы перезапишем уже существующий метод из «предка».

Также, подготовим класс к созданию кнопок, добавив в uses модуль Vcl.StdCtrls, а в класс в раздел protected две переменных:

    okButton:TButton;

    cancelButton:TButton;

Теперь описываем работу конструктора:

constructor OkCancelPanel.Create(AOwner: TComponent);

begin

{ Вызовем конструктор из «предка» }

inherited Create(AOwner);

{ Применим новые параметры по умолчанию }

  self.ShowCaption:=false;

  self.BevelOuter:=bvNone;

  self.Align:=alBottom;

{ Начнем создавать кнопки справа налево }

  { make Cancel }

  cancelButton:=TButton.Create(AOwner);

  cancelButton.Parent:=self;

  cancelButton.Caption:=’Cancel’;

  cancelButton.ModalResult:=mrCancel;

{ make Ok }

  okButton:=TButton.Create(AOwner);

  okButton.Parent:=self;

  okButton.Caption:=’Ok’;

  okButton.Align:=alRight;

  okButton.ModalResult:=mrOk;

end;

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

Если скомпилировать, а затем установить наш пакет (Package) уже сейчас, то мы можем увидеть именно то, что было показано на картинке выше. Однако, работа только началась, я ведь хотел сделать еще и включение и выключение видимости каждой кнопки.

Этап 4. Переменные для переключения видимости

Так я начинал делать, стараясь понять, как делать правильно.

В раздел private добавим две переменные:

    valueOk:boolean;

    valueCancel:boolean;

В них будет хранится информация о том, видима ли там или иная кнопка или нет. Так как эти переменные запривачены, то объявим свойства, которые будут с ними работать. А еще лучше — если работать свойства будут с функциями и процедурами, а не напрямую с переменными. Напишем так:

public

    constructor Create(AOwner: TComponent); override;

property hasOk:boolean read getOk write setOk default True;

property hasCancel:boolean read getCancel write setCancel default True;

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

Объявим эти методы выше, в private:

function getOk:boolean;

procedure setOk(value:boolean);

function getCancel:boolean;

procedure setCancel(value:boolean);

Опишем хотя бы одну процедуру получения значения, они аналогичны:

function OkCancelPanel.getOk:boolean;
begin
  getOk:=valueOk;
end;

А также — одну процедуру изменения значения, они также аналогичны:

procedure OkCancelPanel.setOk(value:boolean);
begin
  valueOk:=value;
  { Здесь же — устанавливаем видимость кнопки }
  if valueOk then
    okButton.Visible:=true
  else
    okButton.Visible:=false;
end;

Итак, что мы имеем на данный момент? У нас есть включение и отключение видимости кнопки, но менять эти значения можно только с помощью редактора кода.
Что хотелось бы иметь? Редактирование видимости в Design Time с помощью панели Object Inspector.

Этап 5. Свойства, видимые в Object Inspector (немного теории)

Немного отвлечемся от написания кода и посмотрим на то самое «глубокое понимание» свойств, о котором я писал в самом-самом начале, а также о том, как можно создать группу из нескольких свойств.

Как известно, чтобы добавить эти свойства, достаточно лишь записать их в раздел published.

Например, добавим такое:

property ButtonOkIsVisible:boolean read getOk write setOk default True;

Тогда они будут отображены таким образом:

Но так может сделать кто угодно и мне это кажется не очень красивым решением. Хотя, оно довольно наглядно и вы можете сразу проверить работу метода setOk, поменяв значение свойства ButtonOkIsVisible и проверив значение Visible у кнопки «Ok».

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

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

А чтобы это реализовать, мне пришлось внимательно читать исходники уже имеющихся компонентов, для примера взяв уже существующую группу Anchors. Как оказалось, эта группа записана во все компоненты как обычное свойство (смотрите сами в Vcl.StdCtrls), а корни идут гораздо глубже, аж в System.UITypes!

Что представляют из себя элементы группы Anchors? Всего лишь четыре константы в Vcl.Controls:

{ TAnchors elements }

const

  akLeft = System.UITypes.TAnchorKind.akLeft;

  akTop = System.UITypes.TAnchorKind.akTop;

  akRight = System.UITypes.TAnchorKind.akRight;

  akBottom = System.UITypes.TAnchorKind.akBottom;

которые занесены в список (в модуле System.UITypes), а оттуда — в множество:

  TAnchorKind = (akLeft, akTop, akRight, akBottom);

  TAnchors = set of TAnchorKind;

Именно последнее мы и видим в качестве группы свойств в панели Object Inspector.

Этап 6. Создание группы свойств

Эту часть я написал не сразу, потому что мне нужно было все проверить и потестировать, будет ли оно работать вообще хоть как-нибудь. В итоге — работает, смотрим результаты.

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

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

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

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

constructor Create(AOwner: TComponent); override;

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

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

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

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

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

unit
OurOpenDialog;
interface

uses

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

TOurOpenDialog = class
(TOpenDialog)
private

{ Private declarations }
protected
public

{ Public declarations }
constructor
Create(AOwner: TComponent); override
;
published
end
;
procedure
register
;
implementation

procedure
register
;
begin

RegisterComponents(«Samples», );
end
;
{ TOurOpenDialog }
constructor
TOurOpenDialog.Create(AOwner: TComponent);
begin

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

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

Свойства

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

type

TOurComponent = class
(TComponent)
private

{ Private declarations }
FArrayProp: array
of
integer;
function
GetArrayProp(aIndex: integer): integer;
procedure
SetArrayProp(aIndex: integer; const

Value: integer);
protected

{ Protected declarations }
public

{ Public declarations }
property
ArrayProp: integer read

GetArrayProp
write
SetArrayProp;
published

{ Published declarations }
end
;

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

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

unit
OurComponent;
interface

uses
Windows, SysUtils, Classes, Graphics, Forms, Controls;
type

TOurComponent = class
(TComponent)
private

{ Private declarations }
FMyInteger: Integer;
protected

{ Protected declarations }
public

{ Public declarations }
constructor
Create(AOwner: TComponent); override
;
published

{ Published declarations }
property
MyInteger: Integer read
FMyInteger
write
FMyInteger default
10;
end
;
implementation

constructor
TOurComponent.Create(AOwner: TComponent);
begin

inherited
Create(AOwner);
FInteger:= 10;
end
;
end
.

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

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

property
OneProp: integer read
FOneProp
write

SetOneProp
stored
False;
property
TwoProp: integer read
FTwoProp
write

SetTwoProp
stored
True;
property
ThreeProp: integer read
FThreeProp
write
SetThreeProp
stored
Fuct;

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

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

Общие сведения о Delphi

Delphi — среда разработки прикладных программ, которые предназначены для запуска в ОС Windows, MacOS, а также в мобильных операционных системах — iOS и Android. Характеризуется простотой языка и процедур генерации кода.

При необходимости обеспечивает низкоуровневую коммуникацию с ОС и библиотеками, составленными на языках C и C++. Программы, которые создаются с помощью Delphi, не требуют сторонних оболочек для запуска — таких как, например, Java Virtual Machine. Delphi — среда разработки, которая может успешно применяться как профессионалами, так и в учебных целях. Для того чтобы освоить базовые ее возможности, необязательно обладать высокой квалификацией и знанием сложных языков программирования.

Основные преимущества

Изучим то, каковы ключевые преимущества программного продукта, о котором идет речь. Когда в той или иной IT-компании осуществляется обоснование выбора среды разработки, Delphi становится выбором многих программистов и рекомендуется ими к использованию. Это связано с тем, что данная среда позволяет создавать приложения в самые оперативные сроки, обеспечивать их высокую производительность даже на тех компьютерах, которые имеют скромные аппаратные характеристики. Значимый аргумент в пользу выбора рассматриваемой среды разработки — ее можно дополнять за счет новых инструментов, не предусмотренных стандартным набором решений, присутствующим в интерфейсе Delphi.

Изучим теперь то, каковы нюансы практического пользования возможностями Delphi.

Специфика интерфейса

Прежде всего можно обратить внимание на некоторые особенности интерфейса рассматриваемой среды разработки ПО. Так, структура рабочего пространства программы предполагает одновременную работу с несколькими основными окнами. Рассмотрим данное свойство подробнее.

Среда разработки Delphi, 7 версии в частности, предполагает задействование следующих ключевых модулей: дизайнера форм, редактора, палитры, инспектора объектов, а также справочника. В некоторых модификациях Delphi отмеченные компоненты могут именоваться иначе. Например, редактору может соответствовать окно кода программы, дизайнеру — окно формы. Однако функциональное назначение их будет тем же самым. Отмеченные Delphi могут дополнять различные вспомогательные инструменты. Основными с точки зрения процедур разработки программ считаются первые два. Но важны также и остальные. Рассмотрим особенности пользования отмеченными модулями Delphi.

Дизайнер форм, редактор и палитра

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

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

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

Инспектор объектов

Другой значимый элемент, который содержит Delphi — среда разработки приложений для ОС Windows и иных распространенных платформ — инспектор объектов. Можно отметить, что информация, отображаемая в нем, меняется: на это влияет статус объекта, который выбран в области дизайнера форм.

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

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

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

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

Для того чтобы понять, как функционирует интегрированная среда разработки Delphi в части взаимодействия инспектора объектов и форм, можно попробовать внести изменения в свойства некоторых распространенных элементов интерфейса ПО в Windows — например, Memo, Button и Listbox (чуть позже мы исследуем их сущность подробнее). Для начала их нужно разместить на форме, используя доступные средства Delphi.

Можно попробовать поэкспериментировать со свойством Ctl3D. Для этого нужно щелкнуть мышкой на форме, после чего перейти в инспектор объектов и изменить значение рассматриваемого свойства. После этого форма значительно изменит Одновременно свойство Ctl3D будет изменено на каждом из элементов, что размещены в окне разработки.

После произведенных экспериментов мы можем вновь перейти на форму и активизировать значение Ctl3D. После этого обратимся к элементам Memo и Listbox. Теперь можно изменять их свойства, расположение на форме, внешний вид. Например, выбрав в пункте меню опцию Edit, а затем — Size, программист может изменить ширину и высоту объектов. Есть вариант расположить их по центру, выбрав Edit и Align. Соответствующие действия повлияют на элементы, отображаемые в инспекторе объектов.

С помощью рассматриваемого модуля Delphi можно осуществлять изменение свойств компонентов. Так, например, если стоит задача определить для них конкретный цвет, то есть варианты задействования сразу нескольких инструментов. Во-первых, можно ввести команду, соответствующую цвету — например, красному — clRed, — в область Во-вторых, пользователь может выбрать нужный цвет из списка. В-третьих, есть вариант два раза щелкнуть мышью на свойствах Color — появится окно выбра цвета. Аналогично разработчик может менять и иные атрибуты объектов — к примеру, тип шрифта, его цвет или размер.

Справочник

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

Прочие элементы интерфейса

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

Средства программирования

Delphi — среда разработки, которая включает большое количество инструментов, призванных повысить эффективность работы программиста. Так, рассмотренные нами выше ключевые модули дополняются набором из специальных инструментов. В числе таковых: отладчик, компилятор, а также компоненты WinSight и WinSpector. Отметим, что в некоторых версиях Delphi отмеченные элементы нужно инсталлировать отдельно. Изучим их специфику.

Отладчик Delphi

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

Компилятор Delphi

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

Winsight и WinSpector

Указанные модули относятся к тем, что нужно устанавливать на Delphi дополнительно. Характеризуются относительной сложностью в освоении. Однако многие программисты, осуществившие выбор среды разработки Delphi, считают, что данными компонентами обязательно нужно учиться пользоваться. Так, модуль Winsight используется с целью наблюдения за сообщениями Windows. Такой компонент, как WinSpector, нужен для того, чтобы фиксировать состояние компьютера в специальном файле. Если в ходе разработки ПО будут наблюдаться какие-либо сбои, то всегда можно открыть этот файл и посмотреть, что могло быть причиной неполадки.

Стандартные компоненты

Среда разработки Delphi, общие сведения о которой мы изучаем, включает ряд стандартных компонентов, о которых также полезно знать. Специалисты относят к таковым следующие: MainMenu, PopupMenu, Label, Edit, Memo, Button, Checkbox, Radiobutton, Listbox, Combobox, Scrollbar, Groupbox, Panel, а также Scrollbox. Изучим их специфику подробнее.

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

Компонент PopupMenu предназначен для размещения всплывающих меню в интерфейсе создаваемой программы, то есть открывающихся с помощью щелчка правой кнопкой мыши.

Компонент Label задействуется с целью отображения текста в окне программы. Его можно настраивать, например задавать нужный шрифт в инспекторе объектов.

Компонент Edit задействуется с целью отображения на экране фрагмента текста, который пользователь может редактировать в то время, как программа работает. Его дополняет компонент Memo, с помощью которого, в свою очередь, можно работать с текстами большего объема. Данный элемент включает, например, такие опции, как копирование текста.

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

Компонент Checkbox позволяет отображать на экране строки с небольшим окошком, в котором может ставиться галочка с помощью мыши. Похожий элемент — Radiobutton. Различаются они, во-первых, внешним видом — второй компонент выполняется в виде кружка, а во-вторых, первый элемент разрешает одновременный выбор нескольких опций, Radiobutton — только одной.

Компонент Listbox используется для отображения на экране списка, который пользователь может прокручивать с помощью мыши. Чем-то похож на него другой элемент — Combobox, однако он дополняется возможностью вводить текст в специальном поле.

Компонент Scrollbar — это полоса прокрутки в окнах. Как правило, появляется автоматически, как только текстовое пространство или форма с объектами становятся больше, чем окно.

Компонент Groupbox задействуется для того, чтобы фиксировать порядок перемещения между окнами при нажатии клавиши TAB. Может дополняться элементом Panel, с помощью которого может осуществляться перемещение нескольких объектов на форме.

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

Резюме

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

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

Разработка собственных компонентов

Если вас не устраивают стандартные компоненты, поставляемые вместе с Delphi, значит, вам пора попробовать себя в создtании своих собственных. Сначала мы начнем с простых и постепенно перейдем к более сложным. И так, начнем.

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

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

Установка созданного компонента Component/Install Component…

  • Install Into New Package
  • Package file name: C:\Program Files\Borland\Delphi4\Lib\OurTest.dpk
  • Package description: Our tested package

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

После того как вы создали свой рисунок, переименуйте его из Bitmap1 в TOurOpenDialog и сохраните файл с именем OurOpenDialog.dcr. Удалите компонент из пакета и установите его снова (только в этом случае добавится и ссылка на *.dcr файл).

Compile, Install и удачи!

Профессиональная разработка приложений с помощью Delphi 5 | Средства разработки | КомпьютерПресс 2″2001

Создание компонентов Delphi

Введение в создание компонентов Delphi

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

  1. Простота использования
    . Компонент помещается на форму, и для него
    необходимо устанавливать значения свойств и писать код обработчиков событий.
    Поэтому если в проекте какое-либо сочетание элементов управления и обработчиков
    связанных с ними событий встречается в двух местах, то имеет смысл подумать
    о создании соответствующего компонента. Если же сочетание элементов управления
    и обработчиков связанных с ними событий встречается более двух
    раз, то создание компонента гарантированно сэкономит усилия при разработке
    приложения.
  2. Простая организация групповой разработки проекта
    . При групповой разработке
    отдельные части проекта можно определить как компоненты и поручить эту работу
    разным программистам. Компоненты можно отладить отдельно от приложения,
    что сделать достаточно легко.
  3. Простой и эффективный способ обмена кодом с другими программистами.

    Имеется немало сайтов, например http://www.torry.net/ ,
    где можно найти свободно распространяемые компоненты или приобрести их за
    символическую плату.

Пакеты компонентов

В Delphi компоненты хранятся в пакетах (packages). Список используемых пакетов
компонентов можно вызвать с помощью пункта меню Component/Install Packages (правда,
этот диалог почему-то имеет заголовок Project Options).

При помощи этого
диалога можно добавить новый пакет (Add),
удалить имеющийся (Remove). Удаление означает
не физическое удаление файла с диска, а
удаление ссылки из среды разработки на
данный пакет. При добавлении нового пакета
компоненты, хранящиеся в нем, появляются на
палитре, а при удалении – наоборот,
исчезают. Пакет можно не удалять, а «спрятать»
его содержимое на этапе разработки
посредством снятия отметки напротив имени
пакета в списке. Можно также просмотреть
компоненты и их пиктограммы (Components). И
наконец, можно отредактировать добавленные
пользователем пакеты (Edit) – пакеты,
поставляемые вместе с Delphi, редактировать
нельзя (кнопка Edit недоступна).

В данном диалоге
можно указать, каким образом создавать
проект: с использованием runtime-пакетов или
без них. Отсюда ясно, что пакеты компонентов
бывают двух типов: runtime package
(пакет, работающий во время выполнения) и
design-time package (пакет,
используемый во время разработки). Все они
представляют собой DLL (динамически
загружаемые библиотеки).

Runtime-пакеты (расширение
*.bpl) поставляются конечному пользователю
вместе с проектом, если проект был
скомпилирован с включенной опцией Build with
runtime packages. Само приложение (*.exe или *.dll) в этом
случае получается небольшим, но вместе с
ним надо передавать довольно объемные *.bpl-файлы.
Согласно оценкам специалистов поставка
проекта с runtime-пакетами дает преимущество в
объеме поставляемых файлов, если только он
включает пять или более модулей (*.exe или *.dll),
написанных на Delphi. При совместной работе
этих модулей достигается экономия ресурсов
операционной системы, поскольку один
загруженный в ОЗУ пакет обслуживает
несколько модулей.

Design-time-пакеты (расширение *.dcp) используются только на этапе разработки.
Во время разработки они поддерживают создание компонентов на форме. В скомпилированный
проект Delphi включает код не из пакета компонентов, а из *.dcu-файлов. Хотя
*.dcp-файл генерируется из *.dcu-файла, их содержимое может не совпадать, если
в *.pas-файл были внесены изменения и пакет не был перекомпилирован. Компиляция
возможна только для пакетов, созданных программистами. Это достигается нажатием
кнопки Edit в вышеупомянутом диалоге. После этого появляется форма, которая
позволяет производить манипуляции с пакетом.

Пакет содержит две
секции. В секции Contains приведен
список модулей, формирующих компоненты
данного пакета (*.pas- и *.dcu-файлы) и их
пиктограммы (*.dcr-файлы). Секция Required
содержит ссылки на другие пакеты,
необходимые для работы этих компонентов.
Добавление нового компонента к пакету
выполняется кнопкой Add, удаление имеющегося
– кнопкой Remove. До тех пор пока пакет не
будет скомпилирован нажатием кнопки Compile,
все изменения, вносимые в пакет, не будут
появляться в среде разработки. И наконец,
команда Install доступна в том случае, когда
содержимое пакета удалено из среды
разработки посредством снятия отметки
напротив имени пакета в предыдущем диалоге.

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

Шаблоны компонентов

Delphi позволяет создавать простейшие составные компоненты из нескольких обычных
компонентов, выбранных на форме во время разработки. Соответствующий эксперт
вызывается с помощью пункта меню Components/Create Component Template. Этот
пункт меню доступен, если на форме выделен хотя бы один компонент. После его
выбора появляется диалоговая панель Component Template Information.

В этом диалоге
следует указать имя класса и имя страницы
на палитре компонентов, куда следует
поместить новый компонент. Если страница с
данным именем отсутствует на палитре
компонентов, то она будет создана. Можно
также изменить предложенную пиктограмму
нового компонента, загрузив подходящий *.bmp-файл.

При создании
шаблона запоминаются как свойства,
измененные программистом в инспекторе
объектов, так и обработчики событий,
связанные с выделенными элементами
управления. При этом обработчики событий
запоминаются полностью, без фильтрации
обращений к другим (не выделенным на форме)
компонентам, глобальным переменным,
методам и т.д. Соответственно, если в другом
проекте такие компоненты (переменные,
методы) отсутствуют, то при попытке
скомпилировать такой проект будет получено
диагностическое сообщение Unknown Identifier.

Когда следует
пользоваться шаблонами? Прежде всего, в
случаях, если необходимо изменить какие-либо
свойства, которые имеются по умолчанию в
базовом классе. Например, в каком-либо
приложении используется элемент
управления для редактирования строки
текста желтого цвета. Можно поместить
компонент TEdit на форму, изменить свойство
Color на желтый, отметить данный компонент и
сохранить как шаблон. После этого можно
обращаться к данному шаблону, и помещенный
на форму компонент будет иметь желтый цвет.
Однако не стоит злоупотреблять данной
возможностью, ведь для элемента управления
с измененным цветом будет создан новый
класс и в памяти будут размножены все
виртуальные методы. Это отрицательно
скажется на ресурсах операционной системы.

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

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

Создание простейшего компонента

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

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

Создание компонента начинается с выбора пункта меню Component/New components.
После этого сразу же появляется диалог New Component.

В этом диалоге
необходимо определить класс-предок, имя
вновь создаваемого класса, страницу на
палитре, куда будет помещен новый компонент,
имя модуля, содержащего реализацию нового
компонента, и путь к нему. Если новый
компонент использует другие модули, путь к
которым не описан, то их необходимо
определить в поле Search Path.

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

Имя вновь
создаваемого класса должно отражать
содержание компонента и ни в коем случае не
совпадать с именем уже зарегистрированного
компонента! На этапе заполнения данного
диалога имена на совпадения не проверяются
– приключения, связанные с такой ошибкой,
начнутся позже…

При выборе
страницы необходимо знать, что если задать
имя несуществующей страницы, то будет
создана новая.

И наконец, при
нажатии как кнопки Install, так и кнопки OK,
будет создана заготовка для реализации
нового компонента. Однако при нажатии
кнопки Install заготовка будет помещена на
палитру компонентов, а при нажатии кнопки OK
– просто создана. Рекомендуется
пользоваться кнопкой Install. После того как
компонент будет инсталлирован, его можно
поместить на форму. Теперь все изменения,
вносимые в код реализации компонента, будут
компилироваться вместе с проектом, и
программист сразу же будет получать
сообщения об ошибках. Если компонент не
инсталлировать, то для поиска ошибок его
необходимо компилировать через редактор
пакетов (см. выше) нажатием кнопки Compile, что
менее удобно.

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

В этом диалоге
имеются две страницы, на первой из них можно
выбрать один из существующих пакетов, а на
второй – создать новый. Весьма желательно
давать краткое текстовое описание пакета,
именно оно будет показываться в диалоге,
вызываемом по команде Component/Install packages (см.
выше). После выбора пакета и нажатия клавиши
OK вызывается
редактор пакета, куда автоматически
помещается вновь созданный модуль
реализации нового компонента. Полезно не
закрывать его, а сдвинуть в один из углов
экрана, чтобы он мог быть активирован
нажатием клавиши мыши.

Одновременно в
редакторе кода будет создана «заготовка»
для описания нового компонента:

Unit ButtonBeep;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
type
TButtonBeep = 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.

В самом новом классе объявлены четыре секции, значение которых детально описано
в разделе «Область видимости переменных и методов» предыдущей статьи данного
цикла (КомпьютерПресс № 1″2001). Кроме того, в новом классе определена процедура
Register, которая вызывается средой разработки Delphi при инсталляции данного
модуля как компонента. Она содержит имя страницы на палитре, куда помещается
данный компонент, и в квадратных скобках – имя класса. Вообще, в качестве параметра
метод Register принимает массив типов классов, ведь в одном модуле может быть
реализовано несколько компонентов. Поэтому они отделяются друг от друга запятой,
например:

Procedure Register;
begin
RegisterComponents(«Samples», );
end;

Продолжим решение поставленной задачи – создание кнопки, которая издает писк.
Поступим сначала тривиально (но как выяснится потом, неверно) – назначим обработчик
события OnClick в конструкторе кнопки. Для этого в секции private определим
заголовок нового метода BtClick(Sender:TObject) и реализуем его в секции реализации:

Procedure TButtonBeep.BtClick(Sender:TObject);
begin
Beep;
end;

constructor
Create(AOwner:TComponent); override;

с обязательной
директивой override! Реализуем его в секции
реализации:

Constructor TButtonBeep.Create(AOwner:TComponent);
begin
inherited Create(AOwner);
OnClick:=BtClick;
end;

После этого скомпилируем компонент. Поставим со страницы Samples кнопку на
форму и запустим проект на выполнение. Можно убедиться, что кнопка при нажатии
пищит!

Теперь вновь
перейдем в среду разработки и назначим
обработчик события OnClick в инспекторе
объектов. В обработчике события выведем
текст в заголовок формы:

Procedure TForm1.ButtonBeep1Click(Sender:TObject);
begin
Caption:=»Test»;
end;

Запустим проект на выполнение и попробуем нажать на кнопку. Заголовок формы
меняется, но кнопка пищать перестала! Ошибка заключается в том, что на
одно событие кнопки OnClick мы попытались определить два обработчика: один внутри
компонента BtClick, а другой назначили с помощью инспектора объектов. После
отработки конструктора TButtonBeep у нас была ссылка на первый обработчик BtClick.
Затем происходит загрузка ресурсов, обработчику события OnClick назначается
метод ButtonBeep1Click. При этом ссылка на первый обработчик
— BtClick — безвозвратно теряется.

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

Как же все-таки
корректно решить данную задачу? Один из
способов создания компонентов — переписывание уже имеющихся методов. При
рассмотрении файла StdCtrls.pas, где реализованы
исходные коды для компонента TButton, можно
отметить в нем наличие динамического
метода Click, который можно переписать.
Поэтому вновь возвращаемся к исходному
коду, созданному экспертом Delphi при создании
компонента (убираем конструктор и метод
BtClick). Затем в секции public определяем
заголовок метода:

Procedure Click; override;

и
приводим реализацию метода:

Procedure TButtonBeep.Click;
begin
inherited Click;
beep;
end;

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

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

  1. Забытая директива override при определении заголовка метода Click. Кнопка
    перестает пищать, следовательно, метод Click не вызывается.
  2. Забытый вызов метода-предка (inherited Click) в реализации процедуры Click.
    Кнопка продолжает пищать при нажатии, но код в назначенном в инспекторе объектов
    обработчике событий не выполняется. Следовательно, метод Click класса TButton
    вызывает событие OnClick.

Теперь поменяем
пиктограмму компонента TButtonBeep на палитре.
По умолчанию для нового компонента
используется пиктограмма
компонента-предка. Для этого вызовем
редактор Image Editor командой Tools/Image Editor. В
редакторе вызовем команду File/New/Component Resource
File (*.dcr). После команды Resource/New/Bitmap
появится диалог, в котором предлагается
размер пиктограммы 32х32. Эти размеры по
умолчанию следует изменить на 24х24 – такой
размер обязаны иметь пиктограммы
компонентов! После нажатия кнопки OK следует
нарисовать какое-либо изображение при
помощи стандартных инструментов, похожих
на инструменты редактора Paint. Помните, что
цвет левого нижнего пиксела является
цветом маски – данный цвет будет «прозрачным».

После этого необходимо переопределить имя ресурса с пиктограммой, по умолчанию
его имя ‑ Bitmap1. Новое имя ресурса обязано совпадать с именем класса
– в нашем случае TButtonBeep.

Теперь необходимо
сохранить файл с пиктограммой в том же
самом каталоге, где находится модуль,
содержащий процедуру Register для данного
компонента, и с тем же самым именем, что и
имя модуля. Только вот расширение у файла
будет не *.pas, а *.dcr. Файл с пиктограммой
компонента готов. Однако если мы посмотрим
на палитру компонентов, то увидим, что там
по-прежнему сохраняется старая пиктограмма.
Если перезагрузить Delphi или даже
операционную систему, старая пиктограмма
по-прежнему останется на палитре. Для того
чтобы поменять пиктограмму, необходима
повторная регистрация компонента. Для
этого необходимо:

Данный пример
следует рассматривать как тестовое
упражнение. Перед написанием нового
компонента необходимо посмотреть,
существуют ли аналогичные среди свободно
распространяемых компонентов. Имеются
практически любые кнопки: прозрачные,
убегающие, круглые, цветные и т.д. Примерно
так же обстоит дело с другими компонентами
– потомками одного класса. Поэтому чаще
всего приходится реализовывать компоненты,
состоящие из нескольких элементов
управления.

Таким образом, в
данном примере мы изучили применение
переписывания методов для создания новых
компонентов.

Создание сложного компонента

Предположим, в
приложении необходимо ввести список
фамилий клиентов. В том же самом приложении
потребуется и ввод списка телефонов. Ввод
списка ‑ довольно распространенная
операция, поэтому следует подумать о
реализации его в виде компонента.

Для ввода нового
элемента в список потребуется редактор –
компонент TEdit. Далее пользователь
должен иметь возможность просмотреть
список – понадобится компонент TListBox. Кроме
того, потребуются команды для занесения
текущего значения из TEdit в список,
редактирование выбранного элемента списка
и его удаление. Проще всего эти команды
реализовать с помощью кнопок. Для упрощения
задачи поместим на форму одну кнопку, при
нажатии которой будем добавлять содержимое
компонента TEdit в список.

Итак, мы должны
создать новый компонент, который включал бы
в себя TEdit, TListBox и TButton. Как всегда, начнем его
создание с команды Component/New Component. После
этого появляется диалог, в котором следует
определить класс-предок, имя класса, имя
модуля. С именем класса и именем модуля
никаких сложностей не возникает, а вот имя
класса-предка неясно. У нас имеются три
элемента управления. Общим классом-предком
для них является TWinControl. Но если в качестве
класса-предка выбрать его, нас ожидает
очень длительная и утомительная реализация
кода TButton, TEdit и TListBox. В таких случаях
необходимо в качестве класса-предка
выбирать компонент, способный быть «папой»
по отношению к другим компонентам. Среди
стандартных компонентов, распространяемых
вместе с Delphi, таких три: TPanel, TGroupBox, TScrollBox.
Выберем в качестве класса-предка панель, но
не сам компонент TPanel, а класс TCustomPanel.
Преимущества выбора TCustomPanel перед TPanel мы
обсудим ниже.

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

Было бы удобно
поместить наши элементы управления на
какую-либо форму и затем создать из них
компонент. В стандартной поставке Delphi такой
эксперт отсутствует. Поэтому необходимо
будет создавать компоненты самим и
размещать их на панели. Создание элементов
управления – TButton, TEdit и TListBox ‑ разумно
выполнить в конструкторе TCustomPanel, для чего,
очевидно, необходимо его переписать.
Разместим пока элементы управления в
квадрате 100х100. Координаты их также
необходимо определять в конструкторе. При
этом следует иметь в виду, что после
отработки конструктора любого элемента
управления он еще не имеет родителя, то есть
не знает, относительно какого окна ему надо
отсчитывать координаты левого верхнего
угла. Попытка изменить координаты
дочернего окна, у которого отсутствует
родитель, немедленно приведет к генерации
исключения. Поэтому первым оператором
после вызова конструктора элемента
управления будет назначение ему родителя, в
качестве которого выберем TCustomPanel. Ее же
сделаем и их владельцем, в этом случае не
понадобится переписывать деструктор.

Итак, в секции uses
добавляем модуль StdCtrls, где находятся
описания классов TEdit, TButton и TListBox, а в секции
private определяем три переменные:

Private
FEdit:TEdit;
FListBox:TListBox;
FButton:TButton;

В секции public объявляем заголовок конструктора с обязательной директивой
override:

Constructor Create(AOwner:TComponent); override;

Реализуем конструктор в секции реализации:

Constructor TListAdd.Create(AOwner:TComponent);
begin
inherited Create(AOwner);
FButton:=TButton.Create(Self);
FButton.Parent:=Self;
FButton.Left:=5;
FButton.Top:=5;
FButton.Width:=40;
FButton.Height:=25;
FEdit:=TEdit.Create(Self);
FEdit.Parent:=Self;
FEdit.Left:=50;
FEdit.Top:=5;
FEdit.Width:=45;
FEdit.Height:=25;
FListBox:=TListBox.Create(Self);
FListBox.Parent:=Self;
FListBox.Left:=5;
FListBox.Top:=35;
FListBox.Width:=90;
FListBox.Height:=60;
end;

Еще раз следует подчеркнуть, что деструктор в данном случае переписывать не
надо: панель является владельцем всех элементов управления, и при вызове
ее деструктора деструкторы элементов управления будут вызваны автоматически.

После перекомпиляции компонента при помощи редактора пакетов изменения в компоненте
уже можно увидеть визуально, на этапе разработки.

Первый недостаток,
который бросается в глаза, – неадекватное
поведение элементов управления при
масштабировании компонента. При изменении
его размеров размеры и положение элементов
не меняются. Кроме того, компонент можно
сделать маленьким, так что три элемента
управления не уместятся на нем. И наконец,
при установке компонента на форму с палитры
компонентов простым щелчком мыши его
размеры также оставляют желать лучшего.

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

Width:=100;
Height:=100;

Затем требуется улучшить поведение компонента при масштабировании. Для этого
необходимо получить сообщение о том, что размеры изменились. При изменении размера
какого-либо элемента управления система посылает ему сообщение WM_SIZE. Это
сообщение необходимо перехватить. Для этого в секции private опишем заголовок
перехватчика сообщения:

Procedure WMSize(var Message:Tmessage); message WM_SIZE;

и в секции реализации реализуем его обработчик:

Procedure TListAdd.WMSize(var Message:TMessage);
begin
inherited;
if Width<100 then Width:=100;
if Height<100 then Height:=100;
FEdit.Width:=Width-55;
FListBox.Width:=Width-10;
FListBox.Height:=Height-40;
end;

Первый оператор – вызов обработчика WM_SIZE по умолчанию (inherited). После
его вызова в свойствах Width и Height будут находиться новая ширина и высота
панели. После этого определяются минимальные размеры компонента, в данном случае
‑ 100х100. Если размер по горизонтали или вертикали меньше минимального,
то ему присваивается минимальное значение. Затем происходит масштабирование
элементов управления так, чтобы они заполняли всю панель с небольшими отступами.
Скомпилировав компонент через редактор пакетов, можно уже на этапе разработки
отметить корректное поведение элементов управления на панели при масштабировании,
а также то, что размер компонента нельзя сделать менее чем 100х100.

Теперь полезно
будет запустить весь проект на выполнение,
попробовать вводить данные в однострочный
редактор текста и нажимать кнопку. При этом
ничего в список не добавляется. И не
удивительно, что нигде в нашем компоненте
не указано, что надо делать при нажатии
кнопки. Для того чтобы сделать обработчик
события, связанного с нажатием кнопки,
можно поступить, как при написании
компонента TbuttonBeep, то есть определить новый
класс ‑ потомок TButton и переписать метод
Click. Однако определение нового класса
требует системных ресурсов (размножаются
виртуальные методы). Если мы отметим
компонент на форме и посмотрим на инспектор
объектов, то обнаружим, что компонент TlistAdd
экспонирует немного свойств и ни одного
события, в том числе ни одного обработчика
события кнопки OnClick. Поэтому то, что в
прошлой главе мы отвергли как неправильный
метод,– переопределение обработчика
кнопки OnClick в данном случае применимо,
поскольку программист не может в
инспекторе объектов назначить новый
обработчик. Итак, в секции private описываем
заголовок нового метода:

Procedure BtClick(Sender:TObject);

В реализации конструктора TListAdd присваиваем этот обработчик обработчику
событий FButton.OnClick:

FButton.OnClick:=BtClick;

И наконец, реализуем метод BtClick:

Procedure TListAdd.BtClick(Sender:TObject);
begin
if length(FEdit.Text)>0 then begin
FListBox.Items.Add(FEdit.Text);
FEdit.Text:=»»;
FEdit.SetFocus;
end;
end;

Сначала проверим, не пуст ли однострочный редактор: мы не будем добавлять в
список пустые строки. Затем переносим содержимое редактора в список (FListBox.Items.Add(FEdit.Text);)
и подготавливаем редактор к вводу следующего значения – а именно, очищаем его
от текста (который уже перенесен в список) и переносим на него фокус ввода.
Теперь после компиляции и запуска приложения можно убедиться, что оно работает
корректно – при нажатии кнопки содержимое редактора переносится в список.

Добавление свойств и методов

Если рядом с
компонентом TListAdd поместить компонент TPanel и
сравнить показываемое в инспекторе
объектов, то можно отметить, что для панели
экспонируется достаточно большое
количество свойств и событий, а для TListAdd
– только несколько свойств. Между тем класс
TCustomPanel является предком обоих компонентов.
Для того чтобы понять причину, откроем
модуль ExtCtrls.pas и рассмотрим разницу между
классами TCustomPanel и TPanel. Можно отметить, что
все методы и переменные, которые
обеспечивают функциональность панели,
определены на уровне класса TCustomPanel. В нем же
определены и свойства, которые затем
отображаются в инспекторе объектов для TPanel,
только эти свойства определены в секции
Protected. Реализация же класса TPanel чрезвычайно
проста: в качестве предка определяется
TCustomPanel, и свойства этого класса
редекларируются, но уже в секции published.
Становится понятно, что необходимо сделать
в классе TListAdd
для появления в инспекторе объектов
свойств и методов класса TcustomPanel, а именно
редекларировать свойства. В секции published
класса TListAdd запишем:

Property Align;
property OnMouseDown;

При редекларации свойства не требуется указывать его тип и ссылаться на переменные
или методы чтения или записи свойства. После компиляции компонента через редактор
пакетов в инспекторе объектов можно наблюдать появление свойства Align и события
OnMouseDown. Таким образом, для потомков TCustom…-классов программист имеет
возможность выбирать, какие свойства и события следует отображать в инспекторе
объектов, а какие нет. Именно по этой причине TCustom…-классы рекомендуется
использовать в качестве предков для создания компонентов.

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

Property BtCaption:string read FButton.Caption write FButton.Caption;

приводит к ошибке при попытке компиляции компонента. Поэтому определим заголовки
двух методов в секции private:

Function GetBtCaption:string;
procedure SetBtCaption(const Value:string);

В секции published
объявим свойство BtCaption:

Property BtCaption:string read GetBtCaption write SetBtCaption;

И наконец,
реализуем два объявленных метода в секции
реализации:

Function TListAdd.GetBtCaption:string;
begin
Result:=FButton.Caption;
end;
procedure TListAdd.SetBtCaption(const Value:string);
begin
FButton.Caption:=Value;
end;

После компиляции компонента с помощью редактора пакетов в инспекторе объектов
появляется новое свойство. Изменение значения этого свойства отражается прямо
на этапе разработки.

Теперь определим
новое событие. В данной задаче было бы
разумным создать событие, позволяющее
программисту, использующему данный
компонент, анализировать текст перед
занесением содержимого редактора в список
и разрешить или запретить добавление
текста в список. Следовательно, этот метод
обязан в качестве параметра содержать
текущее значение текста в редакторе и
зависеть от логической переменной, которой
программист может присвоить значение True
или False. Кроме того, любой обработчик
события в компоненте обязан зависеть от
параметра Sender, в котором вызывающий его
компонент передает ссылку на самого себя.
Это необходимо делать потому, что в среде
разработки Delphi один и тот же обработчик
события может вызываться из нескольких
различных компонентов и программист должен
иметь возможность проанализировать, какой
именно компонент вызвал обработчик. Итак,
после слова type в секции interface перед
определением TListAdd определяем новый тип
метода:

Type
TFilterEvent=procedure(Sender:TObject; const EditText:string; var CanAdd:boolean) of object;

FOnFilter:TFilterEvent;

И в секции published определяем свойство данного типа:

Property OnFilter:TFilterEvent read FOnFilter write FOnFilter;

При определении нового свойства ссылаемся на переменную FOnFilter, а не на
методы – они здесь не требуются. Теперь, если скомпилировать компонент с помощью
редактора пакетов, можно обнаружить появление в инспекторе объектов события
OnFilter. Однако если мы назначим ему обработчик и запустим проект на исполнение,
то он может не вызваться. Это происходит потому, что мы нигде его не вызвали
в нашем компоненте. Подходящее место для вызова события OnFilter – обработчик
события OnClick для FButton, который уже реализован. Поэтому мы изменим код
реализации ранее определенного метода BtClick:

Procedure TListAdd.BtClick(Sender:TObject);
var
CanAdd:boolean;
begin
if length(FEdit.Text)>0 then begin
CanAdd:=True;
if Assigned(FOnFilter) then FOnFilter(Self,FEdit.Text,CanAdd);
if CanAdd then begin
FListBox.Items.Add(FEdit.Text);
FEdit.Text:=»»;
FEdit.SetFocus;
end else beep;
end;
end;

Итак, в приведенном выше фрагменте кода определяется логическая переменная
CanAdd. При написании кода следует учитывать, что программист может не сделать
обработчик события OnFilter. Поэтому устанавливаем значение переменной CanAdd
по умолчанию равным True – все строки добавлять в список. Далее, перед вызовом
FonFilter, следует проверить, а сделал ли программист обработчик события. Это
достигается вызовом метода Assigned, который возвращает логическое значение.
Для указателя вызов метода Assigned эквивалентен проверке P<>nil. Для
метода объекта мы не можем использовать проверку FOnFilter<>nil, так как
метод объекта характеризуется двумя адресами и такая проверка не будет разрешена
компилятором. Но вызов метода Assigned прекрасно проверяет, был ли сделан обработчик
события. Вышеприведенный код – абсолютно стандартный способ вызова обработчика
событий из компонента.

Осталось
протестировать обработчик события.
Поместим два компонента TListAdd на форму, для
одного разрешим добавление только целых
чисел, а для другого – только слов,
начинающихся с прописных английских букв.
Соответственно код для обработчиков
событий OnFilter будет выглядеть следующим
образом:

Procedure TForm1.ListAdd1Filter(Sender: TObject; const EditText: String;
var CanAdd: Boolean);
var
I,N:integer;
begin
Val(EditText,N,I);
CanAdd:=I=0;
end;
procedure TForm1.ListAdd2Filter(Sender: TObject; const EditText: String;
var CanAdd: Boolean);
begin
CanAdd:=False;
if length(EditText)>0 then CanAdd:=(EditText>=»A») and (EditText<=»Z»);
end;

Код
прост для понимания, единственным его нюансом является проверка того, что текст
представляет собой не пустую строку, перед проверкой первой буквы текста в обработчике
события ListAdd2Filter. Проведение такой проверки обязательно: строки в Object
Pascal ‑ это объекты, и пустой строке соответствует nil-указатель. При
попытке проверить первую букву пустой строки приложение попытается дереференсировать
nil, что приведет к возникновению исключения. В данном случае это не страшно:
перед вызовом обработчика событий FOnFilter из компонента TListAdd проверяется
строка на ненулевую длину. Однако для компонентов, исходный текст которых вам
недоступен, такая проверка является обязательной!

Скрытие свойств в инспекторе объектов

Предположим, вы
делаете компонент для доступа к данным,
например, потомок класса TTable. Допустим, в
этом компоненте анализируется список
таблиц, имеющихся в базе данных, и по каким-либо
признакам (например, наличие поля
определенного типа и с определенным именем)
выбирается одна для работы. Для нормальной
работы компонента имя этой таблицы должно
заноситься в свойство TableName. Но это свойство
отображается в инспекторе объектов!
Программист, использующий этот компонент,
может изменить его значение на этапе
разработки, что, предположим, сделает
компонент неработоспособным. И он будет
прав! Если какие-то из свойств или событий
нельзя изменять, они должны быть скрыты.

Мы продолжим
работу над компонентом TListAdd и в качестве
модельной задачи уберем из инспектора
объектов свойство Cursor. Это свойство
определено в секции published в классе TСontrol и
отображается в инспекторе объектов для
TListAdd с самого начала разработки компонента.
Исходя из этого можно попытаться
переопределить данное свойство в секции
protected. Компилятор разрешит такое
переопределение, но к желаемому результату
это не приведет: свойство Cursor как было, так и
останется в инспекторе объектов… Любое
свойство, будучи однажды определенным в
секции published, будет всегда отображаться в
инспекторе объектов для всех потомков
данного класса.

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

  1. При объявлении нового свойства с именем, совпадающем с именем уже имеющегося
    свойства, ранее определенное свойство «затеняется».
  2. Свойства, которые имеют доступ только для чтения или только для записи,
    не отображаются в инспекторе объектов, даже если они объявлены в секции published.

Перед началом
работы по скрытию свойства Cursor полезно
удалить компоненты TListAdd с формы, иначе
может произойти исключение при чтении
ресурса формы. Итак, в секции private объявляем
переменную FDummy:integer (имя и тип переменной
могут быть любыми) и в секции published
определяем новое свойство:

Property Cursor:integer read FDummy;

Новое свойство
обязано называться Cursor, тип его обязан
совпадать с типом переменной, определенной
выше, свойство должно быть только на чтение
или только на запись. После компиляции
компонента с помощью редактора пакетов
следует вновь поместить компонент TListAdd на
форму. Можно обнаружить, что свойство Cursor
уже не отображается в инспекторе объектов.

Теперь немного
усложним задачу. Предположим, необходимо,
чтобы курсор был показан не в виде стрелки,
а в виде песочных часов (crHourGlass). Для того
чтобы изменить значение свойств по
умолчанию, новое значение необходимо
присвоить переменной в конструкторе. При
попытке в конструкторе присвоить новое
значение Cursor

Cursor:=crHourGlass;

компилятор Delphi выдаст диагностическое сообщение о том, что нельзя назначить
новое значение переменной, предназначенной только для чтения. Если сделать новое
свойство «только для записи», то компилятор выдаст уже другое диагностическое
сообщение – о несопоставимых типах данных. Если же объявить переменную
FDummy:TCursor и сделать ее доступной только для записи, то компилятор разрешит
данное присвоение, но при этом вид курсора не изменится: он по-прежнему будет
стрелкой.

Тривиальное
решение данной проблемы – объявить класс-потомок
TCustomPanel, в конструкторе которого нужно
присвоить новое значение переменной Cursor, а
от него уже производить наш компонент TListAdd.
У такого решения имеется два недостатка:

  1. Оно ресурсоемко – размножаются виртуальные методы.
  2. Свойство мы прятали в инспекторе объектов от программиста, который будет
    использовать данный компонент. Мы же хотим работать с данным свойством.

Поэтому решение
данной задачи выглядит следующим образом: в
конструкторе TListAdd объявляем оператор:

Inherited Cursor:=crHourGlass;

и все! Этого достаточно для изменения курсора.

Ранее мы
пользовались служебным словом inherited только
для вызова метода предка. Данная
конструкция позволяет глубже понять
значение inherited как обращение к классу-предку.
Можно обращаться и к свойствам, и к методам.
При обращении к свойству его можно как
читать, так и присваивать ему новое
значение; при этом служебное слово inherited
стоит слева от знака присваивания.
Аналогично можно вызывать скрытые методы
предка. Обращения по иерархии выше, чем
класс-предок, запрещено — конструкция

Inherited inherited Cursor:=crHourGlass;

не будет скомпилирована.

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

Использование Hook-процедур для создания компонентов

Ранее уже
упоминалось, что каждый потомок TWinControl
имеет процедуру, которая принимает и
обрабатывает сообщения. Если имеется
ссылка на дескриптор окна (HWND), то можно
определить адрес этой процедуры и, что
более важно, подменить этот адрес и таким
образом обрабатывать получаемые сообщения
своим способом. Как правило, никто не пишет
полностью обработчики всех сообщений; чаще
вызывается старый метод по умолчанию. При
этом новая процедура используется как
фильтр: при поступлении какого-либо события
выполняется код. Фактически это «шпион» в
TwinControl: нас уведомляют о приходе какого-либо
сообщения и можно выполнить какой-либо код.
При правильной реализации Hook-процедуры
TWinControl продолжает работать как обычно, не
подозревая, что своими сообщениями он
делится с кем-то еще.

Hook-процедура
определяется следующим образом:

Procedure(var Message:TMessage) of object;

Она зависит от переменной типа TMessage, в которой содержится вся информация
о сообщении. Но определить эту процедуру – недостаточно. Она должна копироваться
для каждого TWinControl, к которому будет присоединена. Это достигается вызовом
WinAPI-метода MakeObjectInstance. В качестве параметра этот метод принимает
метод объекта, делает его копию в памяти и возвращает адрес нового метода. Понятно,
что при этом резервируются системные ресурсы, которые необходимо вернуть системе.
Это достигается вызовом метода FreeObjectInstance.

Еще одно важное
условие: перед разрушением TWinControl должна
быть восстановлена связь со старой
процедурой обработки сообщений, иначе
ресурсы не будут возвращены системе. Значит,
придется запоминать указатель на старую
процедуру, который можно узнать вызовом
метода Win API GetWindowLong с параметром GWL_WNDPROC. Этот
указатель будет использоваться также для
вызова обработчиков событий TWinControl по
умолчанию. Обратный метод — SetWindowLong — используется для установки Hook-процедуры.

Итак, сформулируем
задачу для следующего упражнения.
Предположим, мы хотим создать компонент,
который будет заставлять пищать при
нажатии кнопки мыши другие компоненты –
потомки TWinControl. Понятно, что данный
компонент не следует показывать во время
выполнения приложения, поэтому в качестве
его класса-предка выберем
TComponent. Имя класса определим как TBeepWnd. В
секции private определим три переменные:

FOldProc,FNewProc:pointer;
FControl:TWinControl;

Из названий ясно, что мы будем запоминать ссылку на старую процедуру в переменной
FOldProc, ссылка на новую процедуру (после выполнения метода MakeObjectInstance)
будет храниться в переменной FNewProc. И в переменной FControl будем сохранять
ссылку на элемент управления, на который в данный момент «повешена» Hook-процедура.
Определим три метода в этой же секции:

Procedure HookProc(var Message:TMessage);
procedure HookWindow(W:TWinControl);
procedure UnhookWindow;

и в секции implementation реализуем их:

Procedure TBeepWnd.HookProc(var Message:TMessage);
begin
case Message.Msg of
WM_LBUTTONDOWN:begin {Our task}
Beep;
Message.Result:=CallWindowProc(FOldProc, FControl.Handle, Message.Msg, Message.WParam, Message.lParam);
end;
WM_DESTROY:begin {When window is about destroying, remove hook}
Message.Result:=CallWindowProc(FOldProc, FControl.Handle, Message.Msg, Message.WParam, Message.lParam);
UnhookWindow;
end;
{Call default handler}
else Message.Result:=CallWindowProc(FOldProc, FControl.Handle, Message.Msg, Message.WParam, Message.lParam);
end;
end;

В самой Hook-процедуре перехватывается сообщение, на которое происходит реакция
– WM_LBUTTONDOWN. Кроме того, любая Hook-процедура обязана обрабатывать сообщение
WM_DESTROY. Это последнее сообщение, которое передается окну перед тем, как
оно будет разрушено. Наша реакция – восстановить предыдущий метод вызовом описанного
ниже метода UnhookWindow. И наконец, везде вызываются обработчики сообщений
по умолчанию посредством метода CallWindowProc. Забыть обработчик события по
умолчанию – то же самое, что забыть inherited в обработчике события, в 80% случаев
это приведет к некорректному поведению приложения. Ни в коем случае нельзя забывать
присваивать результат вызова метода CallWindowProc полю Result переменной Message!
Код в этом случае работать не будет!

Procedure TBeepWnd.HookWindow(W:TWinControl);
begin
if csDesigning in ComponentState then begin {Checking if component at design or run-time}
FControl:=W;
Exit;
end;
if FControl<>nil then UnhookWindow; {Remove hook if it was previously installed}
if W<>nil then begin
FOldProc:=pointer(GetWindowLong(W.Handle,GWL_WNDPROC)); {Determines address of old procedure}
FNewProc:=MakeObjectInstance(HookProc); {Make copy in memory}
SetWindowLong(W.Handle,GWL_WNDPROC,integer(FNewProc)); {Set new procedure}
end;
FControl:=W; {Store reference at control}
end;

Этот метод используется для установки новой процедуры обработки сообщений.
Сначала проверяется, на каком из этапов находится данный компонент: на
этапе разработки или на этапе выполнения. Если компонент находится на этапе
разработки, то есть выставлен флаг csDesigning в свойстве ComponentState, то
сохраняется просто ссылка на компонент без установки Hook-процедуры. Это сделано
для того, чтобы избежать установки Hook-процедуры на среду разработки
Delphi. Если ранее эта процедура была установлена на другом элементе управления,
она снимается посредством вызова метода UnhookWindow. После этого запоминается
адрес старой процедуры (GetWindowLong), делается копия в памяти новой процедуры
(MakeObjectInstance) и выставляется адрес новой процедуры (SetWindowLong). Используется
приведение типов от integer к pointer, и наоборот – вызываемые методы требуют
(или возвращают) переменные не совсем подходящих типов. И наконец, ссылка на
элемент управления запоминается в переменной FControl, которую мы определили
в секции private.

Procedure TBeepWnd.UnhookWindow;
begin
if (FControl=nil) or (FOldProc=nil) or (FNewProc=nil) then Exit; {No hook was installed}
SetWindowLong(FControl.Handle,GWL_WNDPROC,integer(FOldProc)); {Set old window procedure}
FreeObjectInstance(FNewProc); {Free resources}
FControl:=nil; {Initiate variables}
FOldProc:=nil;
FNewProc:=nil;
end;

Данный метод восстанавливает старый обработчик события. Он вызывается из метода
HookProc и должен еще вызываться из деструктора компонента – снимать Hook необходимо
как при разрушении окна, так и при разрушении данного компонента. Метод SetWindowLong
c адресом старого метода восстанавливает старый обработчик сообщений. После
этого следует вернуть ресурсы системе вызовом метода FreeObjectInstance.

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

Destructor TBeepWnd.Destroy;
begin
UnhookWindow;
inherited Destroy;
end;

И наконец, в секции published определим свойство, которое будет отображаться
в инспекторе объектов:

property
Control:TWinControl read FControl write HookWindow;

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

Тестируется
компонент достаточно просто: ставится на
форму, на которую ставятся и несколько
компонентов-потомков TWinControl. После выбора
на фоне компонента TBeepWnd при щелчке мышью в
инспекторе объектов на поле Control
разворачивается список, в котором
присутствуют все определенные на форме
TWinControl. Следует выбрать один из них и
запустить приложение. При нажатии левой
кнопки мыши на выбранном компоненте он
издает писк.

Редакторы свойств и редакторы компонентов

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

Редакторы свойств

Во время
разработки приложения свойства
отображаются в инспекторе объектов.
Обратите внимание: свойства в инспекторе
объектов редактируются по-разному.
Некоторым свойствам (Width, Caption) можно
определить только новое текстовое значение.
Свойство типа Cursor предоставляет
раскрывающийся список, щелкнув по которому
можно выбрать значение. Свойство типа TFont
имеет знак «+» слева; при щелчке по нему оно
разворачивается, давая возможность
модифицировать отдельные поля. Кроме того,
справа имеется кнопка с тремя точками (elliptic
button), при щелчке на которой появляется
диалог редактора свойств.

Каждое из
вышеперечисленных свойств имеет свой
редактор, и большим преимуществом среды
разработки Delphi является возможность
создать свои редакторы свойств. Новые
редакторы свойств довольно часто
встречаются среди распространяемых
компонентов. Но к ним надо относиться
осторожно: первоначально выполнить тесты
на компьютере, где при необходимости можно
повторно инсталлировать Delphi. Как правило,
они создаются квалифицированными
программистами и претензий к коду не бывает,
но часто забывают включить в
распространяемый редактор свойств какую-либо
DLL. После инсталляции такого редактора мы
получаем ряд свойств, которые невозможно
редактировать, – старый редактор перекрыт,
а новый не работает…

Перед созданием
нового редактора свойств имеет смысл
подумать, стоит ли это делать, – среди
стандартных редакторов, вероятно, можно
найти подходящий. Если же придется делать
редактор свойств, необходимо соблюдать
правило: следует избегать создания
редакторов для стандартных типов данных
(integer, string и др.). Другие программисты
привыкли к стандартным редакторам, и ваш
может им не понравиться. Следовательно,
придется проявить скромность и
регистрировать редактор для своего класса,
а не для класса TComponent. Если ваш редактор
свойств понравится программистам,
большинство из них смогут сами изменить
регистрацию так, чтобы редактор работал для
всех компонентов. Вопрос регистрации
редактора мы обсудим ниже.

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

Прежде всего
необходимо создать компонент, в котором
будет храниться день недели. Создадим новый
компонент вызовом команды Component/New component. В
качестве класса-предка выберем TComponent и
дадим новому классу имя TDayStore. После этого
установим компонент в палитру. Теперь надо
решить, в каком виде хранить день недели.
Ясно, что для однозначной идентификации и
экономии ресурсов его следует хранить в
виде целого числа с допустимыми
диапазонами 1‑7. Однако, если мы собрались
создавать редактор свойств, следует
вспомнить правило о несоздании новых
редакторов для уже имеющихся типов. Поэтому
определим новый тип – TDayWeek, причем все
операции с ним будем производить как с
целыми числами. Определим переменную FDay в
секции private компонента. Поскольку эта
переменная будет инициализироваться
значением 0 при отработке конструктора по
умолчанию, а это
число находится за пределами допустимых
значений, необходимо переписать
конструктор. В заключение определим
свойство DayWeek в секции published для отображения
его в инспекторе объектов. Окончательный
вариант компонента выглядит следующим
образом:

Type
TDayWeek=type integer;
TDayStore = class(TComponent)
private
{ Private declarations }
FDay:TDayWeek;
protected
{ Protected declarations }
public
{ Public declarations }
constructor Create(AOwner:TComponent); override;
published
{ Published declarations }
property DayWeek:TDayWeek read FDay write FDay;
end;

implementation
constructor TDayStore.Create(AOwner:TComponent);
begin
inherited Create(Aowner);
FDay:=1;
end;

Следует обратить внимание на редкую конструкцию определения нового типа

TDayWeek=type integer;

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

Теперь создадим
редактор свойства TDayWeek. Для этого к
имеющемуся проекту добавим новую форму,
запомним ее под каким-либо подходящим
именем (DayPropE.pas) и исключим из проекта. После
этого откроем форму как отдельный файл и
будем реализовывать в ней редактор свойств.
На первом этапе форма нам не понадобится, но
позднее мы реализуем на ней диалог.

Модуль для
создания редакторов свойств называется
DsgnIntf.pas (Design Interface), в нем определены базовый
класс TPropertyEditor и классы-потомки,
предназначенные для редакции стандартных
свойств – TIntegerProperty, TFloatProperty, TStringProperty и др.
Механизм работы редакторов свойств
заключается в следующем:

  1. Он регистрируется в среде разработки Delphi вызовом метода RegisterPropertyEditor.
    В качестве параметров этот метод принимает следующие значения:

    a) информация о типе свойств, для редакции которых предназначен данный
    редактор. Из-за наличия этой информации нам пришлось определять новый тип
    TDayWeek;

    b) информация о компоненте, в котором применим данный редактор. Редактор
    будет вызываться не только для указанного компонента, но и для всех его
    потомков. Если установить это значение TComponent, редактор будет вызываться
    для любого компонента;

    c) имя свойства, для которого используется данный редактор. Если имя –
    пустая строка, используются два вышеупомянутых фильтра;

  2. Вызывается метод GetValue, когда необходимо считать текущее значение
    свойства из компонента. Этот метод для любого свойства возвращает строку,
    которая помещается в инспекторе объектов.
  3. Вызывается метод SetValue, когда программист ввел новое значение свойства
    в инспекторе объектов. В качестве параметра передается новая строка. В методе
    она должна быть проанализирована и приведена к типу редактируемого свойства.

Методы GetValue и SetValue
являются виртуальными, при их
переписывании создаются новые редакторы
свойств. Итак, теперь можно начать создание
нового редактора свойств.

Сошлемся в секции
uses модуля DayPropE.pas на модуль DsgnIntf и определим
в секции Interface новый класс:

Type
TDWPropED=class(TPropertyEditor)
public
function GetValue:string; override;
procedure SetValue(const Value:string); override;
end;

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

Const
DayWeek:array of string = («Понедельник», «Вторник», «Среда», «Четверг», «Пятница», «Суббота», «Воскресенье»);
DayWeekEn:array of string = («Monday», «Tuesday», «Wednesday», «Thursday», «Friday», «Saturday», «Sunday»);

Понравилась статья? Поделить с друзьями:
  • Гиосцина бутилбромид инструкция по применению цена отзывы аналоги цена
  • Электроплита aeg electrolux competence инструкция по применению
  • Руководство компании единство
  • Мифегин инструкция по применению цена в аптеках
  • Руководство по проектированию конструкций деревянных панельных жилых домов