Для чего нужны компоненты
Дельфи имеет
открытую архитектуру — это значит, что
каждый программист волен
усовершенствовать эту среду
разработки, как он захочет. К
стандартным наборам компонентов,
которые поставляются вместе с Дельфи
можно создать еще массу своих
интересных компонентов, которые
заметно упростят вам жизнь (это я вам
гарантирую). А еще можно зайти на какой-нибудь
крутой сайт о Дельфи и там скачать кучу
крутых компонентов, и на их основе
сделать какую-нибудь крутую прогу. Так
же компоненты освобождают вас от
написания «тысячи тонн словесной
руды». Пример: вы создали компонент —
кнопку, при щелчке на которую данные из
Memo сохранятся во временный файл. Теперь
как только вам понадобится эта функция
вы просто ставите этот компонент на
форму и наслаждаетесь результатом. И не
надо будет каждый раз прописывать это,
для ваших новых программ — просто
воспользуйтесь компонентом.
Шаг 1. Придумывание идеи
Первым шагом нужно ответить себе на вопрос: «Для
чего мне этот компонент и что он будет
делать?». Затем необходимо в общих
чертах продумать его свойства, события,
на которые он будет реагировать и те
функции и процедуры, которыми
компонент должен обладать. Затем очень
важно выбрать «предка» компонента, то
есть наследником какого класса он
будет являться. Тут есть два пути. Либо в
качестве наследника взять уже готовый
компонент (то есть модифицировать уже
существующий класс), либо создать
новый класс.
Для создания нового класса можно выделить 4 случая:
- Создание Windows-элемента управления (TWinControl)
- Создание
графического элемента управления (TGraphicControl) - Создание
нового класса или элемента управления (TCustomControl) - Создание
невизуального компонента (не видимого) (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, но коль скоро
это само собой разумеется, то создадим новый
пакет (File/New/Package(Object Repository/New))
назовём его как понравится, скажем, custom.dpk, в
опциях проекта установим описаниe (Description): «Custom
components», сохраним. Дальнейшая работа будет
проходить в пределах этого пакета — так удобнее
локализовать данные.
Скажем немного о пакетах в Delphi. Пакет — это
логическая единица (модуль, физически — файл),
содержащая ссылки на другие модули и
интегрируемая (устанавливаемя) в IDE при
компиляции (Component/Install packages). Проще говоря,
просто сборник файлов, компилируемых вместе и
доступных во время проектирования. Наш пакет
будет содержать файл с исходным кодом компоненты
и два файла для среды: регистрационный модуль и
ресурс (Delphi Component Resource — *.dcr).
Теперь создаём новый компонент (Component/New component),
задаём родительский класс (Ancestor type) TComponent,
имя класса (Class name) TMgsBox(это связано с
характером проекта), страницу на палитре
компонент (Palette page) Custom, сохраняем
модуль в той же директории, где и пакет (не
обязательно, но удобно) под именем, скажем, MsgBox.pas.
И получаем в итоге следующий код:
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.
О сути увиденного мы будем говорить в
самом конце, а сейчас не забудем добавить созданный файл в
пакет и сообразим всё-таки, какого же чёрта мы
будем делать.
2. Суть проблемы
Собственно, какую компоненту мы будем
создавать? Класс мы наследовали от TComponent,
это говорит о том, что наша первая компонента
будет невизуальной, она просто будет оболочкой
на функции MessageBox из глубин Win32 API. По чести
сказать, глубокого практического смысла в такой
компоненте нет, более того, нерационально писать
компоненту ради оболочки на функцию — она
занимает в стеке много больше места, однако мы
возьмём эту задачу, как удобную для примера.
Функция MessageBox, экспортируемая из
библиотеки user32.dll, имеет сигнатуру
function MessageBox(hWnh: HWND; lpText: PChar;
lpCaption: PChar; uType: Cardinal): Integer;
и занимается выводом на экран сообщений
типа
что очень полезно любой программе. Она обладает
кандовым Borland’овским аналогом MessageDlg, но он уж
очень левый, и к тому же кнопки подписывает
всегда по-английски, а не на языке ОС.
Параметры функции |
|
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. |
Остальные флаги — |
Возвращаемые |
|
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 |
//Флаги |
Как видно, тип свойства необходимо указывать,
как и тип поля (кроме наследуемых свойств, о
которых скажем чуть ниже). Далее: за ключевым
словом 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 значение:
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
категорий
- Набор кнопок
- Тип сообщения
- Кнопка по умолчанмю
- Относительно чего модально окно
- Наличие кнопки «Помощь»
- Стиль окна
- Атрибуты текста
Задаём соответствующие свойства:
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 (можно и из
меню ПУСК) запускаем Borland Image Editor. Там
выбираем пункт меню File/New…/Component Resource File(.dcr),
и сохраняем созданный ресурс в каталоге с
модулем и пакетом, под именем MsgBox.dcr. Теперь в
контекстном меню выбираем New/Bitmap, и видим вот
такой диалог:
главная задача — это выставить размер в
24×24, остальное — Ваше личное дело: хотите — 2,
хотите — 256, а хотите — и 16 цветов, и воля для
фантазии в рисовании… Созданную картинку
называем командой Rename контекстного меню по
имени компонента — TMSGBOX.
Теперь всё, что осталось — это включить
в модуль директиву {$R}, добавляющую ресурс:
{$R *.dcr}
Теперь настало время
удалить и пакета модуль, а потом снова добавить его — он
будет включен уже с ресурсом.
Острый момент — компилируем пакет… Если не
получилось — обратитесь к разработчику или
проверьтесь на исходниках.
Должно получиться следующее:
-
Появилась вкладка Custom (последняя)
на политре компонент -
На ней (если этой вкладки раньше не
было) однапиктограмма — та, которую Вы
нарисовали, если поленились, Delphi использует
значок по умолчанию -
Компонента при добавлении к форме
работает.
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 (в принципе, это
«глагол», но можно предположить, что
«действие»):
- GetVerbCount — сколько пунктов есть в меню
- GetVerb — возвращает по индексу текст пункта
- EvecuteVerb — совершает по индексу действие
пункта
Вот этих троих мы и переопределяем (в принципе,
если концепция ясна, можно не придерживаться
текста примера):
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 (показываем диалог).
Снова компиляция, по 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,
потому что при регистрации его не был указан
класс, для которого оно регистрировалось.
Регистрация может осуществляться двумя путями, у
которых есть «подпути»:
- Функция RegisterPropertyInCategory регистрирует одно
свойство в одной категории за вызов
RegisterPropertyInCategory(THelpCategory, TMyButton, ‘HelpContext’); —
данный класс: свойство по имени
RegisterPropertyInCategory(TVisualCategory, ‘AutoSize’); — глобально:
свойство по имени
RegisterPropertyInCategory(TVisualCategory, TypeInfo(Integer)); —
глобально: свойства по типу
RegisterPropertyInCategory(TVisualCategory, TypeInfo(Integer), ‘Width’); —
глобально: свойство по имени и типу - Функция 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_USERSoftwareBorlandDelphix.0GlobalsPrivateDir) |
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 может быть:
|
||||
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, когда она не активна |
* — не |
Теперь в дополнение — что может возвращать
главная, на мой взгляд функция — 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;
Компиляция — свойство не редактируется!
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:
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;
Компиляция — всё должно работать.
7.8 Работа с ресурсами: редактор свойства Modality
Отличие редактора свойства Modality в том, что
символы только двух из трёх его значений
доступны как системные, третий символ придётся
включить самим. Отличие составит только метод ListDrawValue.
Картинку, в принципе, можно подгружать откуда
угодно, но надёжнее всего — из прилинкованного к
пакету ресурса.
Создайте ресурс чем угодно: Delphi Image Editor, Resources
WorkShop, Symantec Resource Studio, etc…
Положите в него под именем «icoTaskModal» соответствующую
идее иконку, я взял её из набора от MS Visual Studio (честное
слово, кроме иконок, этот пакет представляет мало
хорошего): . Теперь сохраним ресурс под
именем 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 — задача чисто
эстетическая, не имеющая под собой практически
никаких технических целей (только что, если Вы
поставляете скомпилированный пакет, никто не
сможет его украсть, да и то, я Вас уверяю, не
перевелись ещё умельцы в земле Русской…).
Поэтому здесь можно просто подурачиться и
применить мало или узко применимые методы.
Сделаем так: пусть…
- в Object Inspector отображается не лаконичное «About»,
а развёрнутое «About this component» - этот текст выводится красным цветом
- ему предшествует пиктограмма компоненты
- по DblClick’у показывается диалог с текстом
- вместо выпадающего списка демонстрируется Ваш
логотип
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: зависит от того, что Вы нарисовали
себе в логотип. Я изобразил следующее:
имеет оно размеры 110×40, исходя из этого:
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;
Компиляция — весёленький 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;
Компиляция — теперь лучше.
7.10 Усложнение задачи: создание редактора
свойства hWnd
Как мы помним, для это свойство обладает
некоторой спецификой: его значение
интерпретируется перед тем, как быть передано в
функцию MessageBox. Поскольку задать значене
ссылки руками, то есть числом, и во время
проектированя невозможно, не и смысла
предоставлять такой интерфейс. Лучше красиво
обработать три варианта значения: ноль,
приложение, Desktop. Хорошо то, что для этих значений
не нужно вводить типов и идентификаторов: они
известны, и их число конечно: три.
Итак, что мы хотим:
- Свойство вручную не редактируемо
- Выпадающий список с пиктограммами
- Строки в списке: «Application», «Desktop»,
«NULL» - По выбору строки свойство устанавливается
соответственно в 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: интепретировать число в строку и
обратно:
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;
Компиляция — должно работать.
7.11 Высокий уровень сложности: свойство TrueResults
Вся сложность состоит в том что
- нет идентификаторов для элементов множества (кто
не заметил — свойства до сих пор нет в Object Inspector) - редактор должен состоять из двух: для множества
и для элементов, но класс 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
(подчёркнуты привнесённые места):
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
(это и есть то самое поле!) — из жалости к
ближнему.
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;
Компиляция — должно работать.
8. Конец первой части
Должен сообщить Вам приятную вещь: если
прочитанное Вам не прошло бесследно, то Вы должны
знать, как писать пока простые компоненты и
редакторы свойств к ним (мы не разбирали свойства-классы
(никак не удалось их запихать в TMsgBox) — если
торопитесь, загляните вперёд, если чувствуете
уверенность, то в модуль Dsgnintf: TFontProperty)).
Далее, во второй части мы займёмся
проектированием визуальной компоненты, и тут уже
главным будет объект, а не редакторы свойств.
Правда и всей широты вопроса охватить не удастся.
Выбор базового класса
Приступая к разработке нового компонента, следует четко сформулировать назначение компонента. Затем необходимо определить, какой из компонентов 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.
end
;
end
.
В объявлении нового класса указан только тип родительского класса. В раздел реализации помещена процедура 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.
end
;
end
.
В описание класса 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, необходимо внести следующие дополнения:
- В список используемых модулей (раздел uses) добавить имя модуля тестируемого компонента (NkEdit).
- В раздел объявления переменных (var) добавить инструкцию объявления компонента. Здесь следует вспомнить, что компонент является объектом, поэтому объявление компонента в разделе переменных не обеспечивает созданиекомпонента, а только генерирует указатель на компонент, следовательно необходима инструкция вызова конструктора объекта, которая действительно создает компонент (объект).
-
Для формы приложения создать процедуру обработки события 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.
end
;
end
.
Тестируемый компонент создается процедурой обработки события 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. Создание компонента
Далее — придерживаемся какое-то время той самой инструкции.
- Открываем меню Component и выбираем New Component
- Появляется окно со всеми классами визуальных компонентов, нам нужно выбрать «предка», которому собираемся создать «потомка» с новыми функциями
- С помощью поиска сразу находим TPanel
- Далее — выберем название и расположение на панели инструментов для создания компонентов
После этого появляется стандартный модуль компонента с уже вставленным названием, код которого мы и будем дополнять.
Этап 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. Создание группы свойств
Эту часть я написал не сразу, потому что мне нужно было все проверить и потестировать, будет ли оно работать вообще хоть как-нибудь. В итоге — работает, смотрим результаты.
Пособиепонаписаниюсвоихкомпонентовна Delphi дляначинающих
Почемуяселписатьэтопособие
Во-первых, потому что когда я очень хотел написать свой первый компонент, я прочитал две книги, и у меня ничего интересного собственно не вышло. Потом я прочитал еще одну книгу (в ней хотя бы пример рабочий был), вроде разобрался. Но там был разобран такой простой компонент, что все более сложное мне приходилось делать самому, иногда методом тыка, иногда сидел разбирался и так далее. Результат — разобрался, чего и вам желаю и надеюсь помочь этим пособием.
Все мои готовые компоненты можно найти на сайте https://delphid.dax
Длячегонужныкомпоненты
Дельфи имеет открытую архитектуру — это значит, что каждый программист волен усовершенствовать эту среду разработки, как он захочет. К стандартным наборам компонентов, которые поставляются вместе с Дельфи можно создать еще массу своих интересных компонентов, которые заметно упростят вам жизнь (это я вам гарантирую). А еще можно зайти на какой-нибудь крутой сайт о Дельфи и там скачать кучу крутых компонентов, и на их основе сделать какую-нибудь крутую прогу. Так же компоненты освобождают вас от написания «тысячи тонн словесной руды». Пример: вы создали компонент — кнопку, при щелчке на которую данные из 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 и получите следующее: |
Code: |
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; procedureRegister; implementation procedureRegister; begin RegisterComponents(‘MyComponents’, [TCountBtn]); end; end. |
Шаг 3. Начинаемразбиратьсявовсехдирективах
Что же здесь написано? да собственно пока ничего интересного. Здесь объявлен новый класс TCountBtn и процедура регистрации вашего компонента в палитре компонентов.
Директива Private Здесь вы будете писать все скрытые поля которые вам понадобятся для создания компонента. Так же в этой директиве описываются процедуры и функции, необходимые для работы своего компонента, эти процедуры и функции пользователю не доступны. Для нашего компонент мы напишем туда следующее (запись должна состоять из буквы «F» имени поля: тип этого поля):
Буква «F» должна присутсвовать обязательно. Здесь мы создали скрытое поле Count, в котором и будет храниться число кликов по кнопке.
Директива Protected. Обычно я здесь пишу различные обработчики событий мыши и клавиатуры. Мы напишем здесь следующую строку:
Code: |
procedure Click; override; |
Это указывает на то, что мы будем обрабатывать щелчок мыши по компоненту. Слово «override» указывает на то, что мы перекроем стандартное событие OnClick для компонента предка.
В директиве Public описываются те процедуры и функции компонента, которые будут доступны пользователю. (Например, в процессе написания кода вы пишите имя компонента, ставите точку и перед вами список доступных функций, объявленных в диретиве Public). Для нашего компонента, чтобы показать принцип использования этой директивы создадим функцию — ShowCount, которая покажет сообщение, уведомляя пользователя сколько раз он уже нажал на кнопку. Для этого в директиве Public напишем такой код:
Code: |
procedure ShowCount; |
Осталась последняя директива Published. В ней также используется объявления доступных пользователю, свойств и методов компонента. Для того, чтобы наш компонент появился на форме необходимо описать метод создания компонента (конструктор), можно прописать и деструктор, но это не обязательно. Следует обратить внимание на то, что если вы хотите, чтобы какие-то свойства вашего компонента появились в Инспекторе Объектов (Object Inspector) вам необходимо описать эти свойства в директиве Published. Это делается так: property Имя_свойства (но помните здесь букву «F» уже не нужно писать), затем ставиться двоеточие «:» тип свойства, read процедура для чтения значения, write функция для записи значения;. Но похоже это все сильно запутано. Посмотрите, что нужно написать для нашего компонента и все поймете:
Code: |
constructor Create(aowner:Tcomponent);override; //Конструктор property Count:integer read FCount write FCount; //Свойство Count |
Итак все объявления сделаны и мы можем приступить к написанию непосредственно всех объявленных процедур.
Шаг 4. Пишемпроцедурыифункции.
Начнем с написания конструктора. Это делается примерно так:
Code: |
constructor TCountBtn.Create(aowner:Tcomponent); begin inherited create(Aowner); end; |
Здесь в принципе понимать ничего не надо. Во всех своих компонентах я писал именно это (только класс компонента менял и все). Также сюда можно записывать любые действия, которые вы хотите сделать в самом начале работы компонента, то есть в момент установки компонента на форму. Например можно установить начальное значение нашего свойства Count. Но мы этого делать не будем.
Теперь мы напишем процедуру обработки щелчка мышкой по кнопке:
Code: |
procedure Tcountbtn.Click; begin inherited click; FCount:=FCount+1; end; |
«Inherited click» означает, что мы повторяем стандартные методы обработки щелчка мышью (зачем напрягаться и делать лишнюю работу:)).
У нас осталась последняя процедура ShowCount. Она может выглядеть примерно так:
Code: |
procedure TCountBtn.ShowCount; begin Showmessage(‘По кнопке ‘+ caption+‘ вы сделали: ‘+inttostr(FCount)+‘ клик(а/ов)’); end; |
Здесь выводится сообщение в котором показывается количество кликов по кнопке (к тому же выводится имя этой кнопки, ну это я добавил только с эстетической целью).
И если вы все поняли и сделали правильно, то у вас должно получится следующее:
Code: |
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; procedureRegister; implementation procedureRegister; 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. Вот так это выглядело вначале:
Code: |
type TCountBtn = class(TButton) |
Воттакэтодолжновыглядеть:
Code: |
type TShowTp = (Normal, CountToCaption); TCountBtn = class(TButton) |
Здесь мы объявили новый тип TShowTp, который может принимать только два значения. Все значения, которые вы хотите добавить перечисляются через запятую. Теперь нам понадобиться создать поле этого типа. Это мы уже умеем и делать и поэтому не должно вызвать никаких сложностей. В директиву Private напишите:
Мы создали поле ShowType, типа TShowTp.
Конечно же необходимо добавить это свойство в инспектор объектов:
property ShowType: TshowTp read FshowType write FShowType;
Ну и наконец, чтобы наш компонент реагировал на изменение этого свойства пользователем надо слегка изменить обработчик события OnClick. После небольшой модификации он может иметь примерно такой вид:
Code: |
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. Приведу небольшой участок кода:
Code: |
unit CountBtn; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls; var Timer: TTimer; type |
Дальше в директиву Protected необходимо добавить обработчик события OnTimer для нашего таймера. Это делается так:
procedure OnTimer(Sender: TObject);
Поскольку наш таймер это не переменная, а компонент, его тоже надо создать, для этого в конструктор нашей кнопки напишем:
Code: |
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. Я сделал это так:
Code: |
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; procedureRegister; implementation procedureRegister; 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). Теперь в пакете уже нет вашего компонента. Затем проделайте стандартную процедуру по установке компонента.
Редактированиезначения, котороеввелпользователь, изменяякакое—нибудьсвойство.
Простой пример. Допустим у нас есть компонент (основанный на Tedit), у него есть два свойства: FirstNumber и SecondNumber. И у него есть процедура Division, в которой первое число делится на второе и результат присаивается свойству текст нашего компонента. Вот код этого компонента:
Code: |
unit DivEdit; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TDivEdit = class(Tedit) private { Private declarations } FFirstNumber:integer; FSecondNumber:integer; FResult:Single; //в компонентах нельзя использовать Real!!! protected { Protected declarations } public { Public declarations } procedure Division; published { Published declarations } constructor create(aowner:Tcomponent);override; property FirstNumber:integer read FFirstNumber write FFirstNumber; property SecondNumber:integer read FSecondNumber write FSecondNumber; property Result:Single read Fresult write FResult; end; procedureRegister; implementation Constructor TDivEdit.create(aowner:Tcomponent); begin inherited create(aowner); FFirtsNumber:=1; FSecondNumber:=1; end; procedure TDivEdit.Division; begin FResult:=FFirstNumber/FSecondNumber; text:=floattostr(FResult); end; procedureRegister; begin RegisterComponents(‘Mihan Components’, [TDivEdit]); end; end. |
Хочется обратить ваше внимание на то, что в компонентах нельзя использовать переменные и поля типа Real, вместо него нужно брать переменные типов Single, Double, Extended.
Здесь все просто. Но вот если пользователю вздумается поделить на ноль (ну вдруг он математики не знает), то компонент выдаст ошибку DivisionByZero, а кому они нужны. Обойти эту проблему можно так: в код компонента добавить процедуру, которая проанализирует данные введенные пользователь и если все будет в порядке, то она присвоит значения соответствующим свойтсвам. В директиве Private объявите такую процедуру:
Code: |
procedure SetSecondNumber(value:integer); |
Обычно такие процедуры начинаются с приставки Set, затем идет имя свойства, и в конце тип переменной. Теперь в директиве Published надо сделать небольшие изменения:
Code: |
property SecondNumber:integer read FSecondNumber write SetSecondNumber; |
А теперь напишем саму процедуру:
Code: |
procedure TDivEdit.SetSecondNumber(value:Integer); begin if value<>FSecondNumber then//надо проверить совпадают ли исходное и вводимое значения FSecondNumber:=value; //если нет, то изменить значение if FSecondNumber=0then FSecondNumber:=1; end; |
Теперь сколько бы пользователь не вводил нулей значение SecondNumber будет единицей. Такие процедуры проверки рекомендуется использовать везде, где только допустимо появление исключительной ситуации.
Использованиедругогокомпонентаввашем
Попробуем создать такой компонент. Это будет обычная метка (Label), у которой будет две процедуры: ChangeBackColor и ChangeFontColor, которые соответственно будут менять цвет фона метки и цвет текста. Для этого нам понадобиться ColorDialog, который будет создаваться вместе с компонентом, а потом с помощью процедур он будет активироваться. Назовем компонент ColorLabel. Вначале добавим в uses два модуля: Dialogs, StdCtrls (в них находятся описания классаов диалога и метки). Теперь нам надо объявить переменную типа TColorDialog. Объявление идет сразу после секции Uses.
Примерно это выглядит так:
Code: |
uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,StdCtrls; var ColorDialog:TColorDialog; type … |
Теперь в конструкторе (Create), нам надо создать этот компонент:
Code: |
constructor TColorLabel.create(aowner:Tcomponent); begin Inherited Create(aowner); ColorDialog:=TColorDialog.Create(self); end; |
Теперь надо объявить процедуры ChangeBackColor, ChangeFontColor. Чтобы они были доступны пользователю их надо поместить в директиву Public:
Code: |
public { Public declarations } procedure ChangeBackColor; procedure ChangeFontColor; published |
Осталось написать сами процедуры. Все очень просто: открываете диалог методом Execute, а затем присваиваете полученное значение цвета метке. У меня эти процедуры имеют такой вид:
Code: |
procedure TColorLabel.ChangeBackColor; begin if ColorDialog.Execute then color:=ColorDialog.color; end; procedure TColorLabel.ChangeFontColor; begin if ColorDialog.Execute then font.color:=ColorDialog.color; end; Если у вас вдруг что-то не получилось, то взгляните на мой код целиком: unit ColorLabel; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,StdCtrls; var ColorDialog:TColorDialog; type TColorLabel = class(Tlabel) private { Private declarations } protected { Protected declarations } public { Public declarations } procedure ChangeBackColor; procedure ChangeFontColor; published { Published declarations } constructor create(aowner:tcomponent);override; end; procedureRegister; implementation constructor TColorLabel.create(aowner:Tcomponent); begin Inherited Create(aowner); ColorDialog:=TColorDialog.Create(self); end; procedure TColorLabel.ChangeBackColor; begin if ColorDialog.Execute then color:=ColorDialog.color; end; procedure TColorLabel.ChangeFontColor; begin if ColorDialog.Execute then font.color:=ColorDialog.color; end; procedureRegister; begin RegisterComponents(‘Mihan Components’, [TColorLabel]); end; end. |
Доступксвойствамдругогокомпонента
Сейчас нам предстоит более сложная задача. Мы будем создавать компонент, вместе с которым будет создаваться какой-нибудь визуальный компонент. Например создадим кнопку, которая будет сопровождаться поясняющей надписью сверху. За основу возмем тип TButton. Нам надо будет создать еще и Label. Здесь существует одна проблемка: при перемещении компонента по форме, метка должна двигаться вместе с кнопкой, поэтому нам придется обрабатывать сообщение WmMove. Итак, объявляем переменную Label (в данном примере она объявлена в директиве Private, что тоже допустимо):
Code: |
uses SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls,buttons; type TLabelButton = class(TButton) private FLabel : TLabel ; |
Теперь я приведу весь код этого компонента и походу буду вставлять необходимые пояснения:
Code: |
unit LabelBtn; interface uses SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls,buttons; type TLabelButton = class(TButton) private FLabel : TLabel ; {создаем поле типа Tlabel} procedure WMMove( var Msg : TWMMove ) ; message WM_MOVE ;{процедура для обработки сообщения Wm_move, чтобы метка перемещалась вместе с кнопкой} protected procedure SetParent( Value : TWinControl ) ; override ;{необходимо воспользоваться и этой процедурой, так как нужно убедиться, имеют ли кнопка и метка общего предка} function GetLabelCaption : string ; virtual ; {Вот пример доступа из компонента к свойствам другого. Эти две процедуры для изменения текста метки} procedure SetLabelCaption( const Value : string ) ; virtual ; public constructor Create( AOwner : TComponent ) ; override ; destructor Destroy ; override ; published property LabelCaption : stringread GetLabelCaption write SetLabelCaption ; end; procedureRegister; implementation constructor TLabelButton.Create( AOwner : TComponent ) ; begin inherited Create( AOwner ) ; { создаем TLabel } FLabel := TLabel.Create( NIL ) ; FLabel.Caption := ‘Описание:’ ; end ; procedure TLabelButton.SetParent( Value : TWinControl ) ; begin {надо убедиться, что у них предок один, чтоб проблем потом не было} if ( Owner = NIL ) ornot ( csDestroying in Owner.ComponentState ) then FLabel.Parent := Value ; inherited SetParent( Value ) ; end ; destructor TLabelButton.Destroy ; begin if ( FLabel <> NIL ) and ( FLabel.Parent = NIL ) then FLabel.Free ;{Уничтожаем метку, т.к. она нам больше не нужна} inherited Destroy ; end ; function TLabelButton.GetLabelCaption : string ; begin Result := FLabel.Caption ; end ; procedure TLabelButton.SetLabelCaption( const Value : string ) ; begin FLabel.Caption := Value ; end ; procedure TLabelButton.WMMove( var Msg : TWMMove ) ; begin inherited ; if FLabel <> NILthenwith Flabel do SetBounds( Msg.XPos, Msg.YPos — Height, Width,Height ) ; {изменяем левое и верхнее положение метки исходя из полученных координат} end; procedureRegister; begin RegisterComponents(‘Mihan Components’, [TLabelButton]); end; initialization RegisterClass( TLabel ) ; {Это делается для обеспечения поточности, но об этом не думайте, этим редко придется пользоваться} end.{Вы можете пользоваться этим компонентом сколько угодно, но распространять его можно только указывая авторство} |
Можно сделать доступ к любым свойствам метки, например, к шрифту, цвету и так далее, используя необходимые процедуры.
Использованиевкачествепредкакласс TWinControl
Предыдущий пример был очень сложным, к тому же пришлось обрабатывать системные сообщения. Есть другое решение этой проблемы, более простое для понимания и для реализации: использовать в качестве контейнера класс TWinControl и в этот контейнер помещать другие компоненты. Теперь попробуем совместить Edit и Label. Давайте вместе создадим такой компонент. В качестве предка нужно выбрать класс TWinControl, а в качестве типа вашего компонента выберите TlabelEdit. Будем разбирать код по кусочкам.
Code: |
unit LabelEdit; interface uses SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, Dialogs, stdctrls; type TLabelEdit = class(TWinControl) private { Private declarations } FEdit: TEdit; FLabel: TLabel; //Здесь объявляются поля для метки и для Edita. function GetLabelCaption: string; procedure SetLabelCaption(LabelCaption: string); function GetEditText: string; procedure SetEditText(EditText: string); //Здесь объявлены функции для работы со свойствами Caption у метки и Text у Edita. protected { Protected declarations } public { Public declarations }constructor Create(AOwner: TComponent); override; published property LabelCaption: stringread GetLabelCaption write SetLabelCaption; property EditText: stringread GetEditText write SetEditText; { Published declarations } end; procedureRegister; implementation constructor TLabelEdit.Create(AOwner: TComponent); begin inherited Create(AOwner); FEdit := TEdit.Create(self);{создаем поле редактирования Edit} FLabel := TLabel.Create(self);{создаем Label} with FLabel dobegin Width := FEdit.Width; visible := true; Parent := self; Caption := ‘Описание:’; end; with FEdit dobegin Top := FLabel.Height+2; Parent := self; Visible := true; end; Top := 0; Left := 0; Width := FEdit.Width; Height := FEdit.Height+FLabel.Height;{определяются размеры и положение компонентов} Visible := true; end; function TLabelEdit.GetLabelCaption: string; begin Result := FLabel.Caption; end; procedure TLabelEdit.SetLabelCaption(LabelCaption: string); begin FLabel.Caption := LabelCaption; end; function TLabelEdit.GetEditText: string; begin Result := FEdit.Text; end; procedure TLabelEdit.SetEditText(EditText: string); begin FEdit.Text := EditText; end; procedureRegister; begin RegisterComponents(‘Mihan Components’, [TLabelEdit]); end; end. |
Попробуйте установить этот компонент. Когда вы будете размещать его на форме, то будет виден «контейнер», на котором располагаются Edit и Label. Использование в качестве предка компонента класса TWinControl, очень удобно если вы хотите объединить несколько визуальных компонентов.
Обработкасобытий OnMouseDown, OnMouseMove и OnMouseUp
Часто возникает необходимость обработки событий нажатия и отпускания кнопки в вашем компоненте. Сейчас мы это и рассмотрим. Только ради примера сделаем компонент, который будет считать количество нажатий и отпусканий кнопки в его области, допустим это будет панель (Tpanel). Для этого в директиве Private надо объявить следующие процедуры и поля:
Code: |
FClickCount:integer; FUpCount:integer; procedure MouseDown(Button:TMouseButton; Shift: TShiftState; X,Y: Integer); override; procedure MouseMove(Shift: TShiftState; X, Y: Integer); override; procedure MouseUp(Button:TMouseButton; Shift:TShiftState; X, Y: Integer); override; |
А в директиве Published надо написать:
Code: |
constructor create(aowner:tcomponent);override; property ClickCount:integer read FclickCount write FClickCount; property UpCount:integer read FUpCount write FUpCount; property OnMouseDown; property OnMouseMove; property OnMouseUp; |
Ну и теперь осталось описать нужные процедуры:
Code: |
procedure TMpanel.MouseDown(Button:TMouseButton; Shift: TShiftState; X,Y: Integer); begin FClickCount:=FClickCount+1; end; procedure TMpanel.MouseMove(Shift: TShiftState; X, Y: Integer); begin caption:=inttostr(x)+‘ ‘+inttostr(y);{для демонстрации работы этой процедуры. Надпись на панели будет отражать координаты курсора мыши над этой панелью} end; procedure TMpanel.MouseUp(Button:TMouseButton; Shift:TShiftState; X, Y: Integer); begin FUpCount:=FUpCount+1; end; |
Таким образом весь код компонента был таким:
Code: |
unit Mpanel; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls; type TMpanel = class(TPanel) private { Private declarations } FClickCount:integer; FUpCount:integer; procedure MouseDown(Button:TMouseButton; Shift: TShiftState; X,Y: Integer); override; procedure MouseMove(Shift: TShiftState; X, Y: Integer); override; procedure MouseUp(Button:TMouseButton; Shift:TShiftState; X, Y: Integer); override; protected { Protected declarations } public { Public declarations } published { Published declarations } constructor create(aowner:tcomponent);override; property ClickCount:integer read FclickCount write FClickCount; property UpCount:integer read FUpCount write FUpCount; property OnMouseDown; property OnMouseMove; property OnMouseUp; end; procedureRegister; implementation constructor TMpanel.create(aowner:Tcomponent); begin inherited create(aowner); end; procedure TMpanel.MouseDown(Button:TMouseButton; Shift: TShiftState; X,Y: Integer); begin FClickCount:=FClickCount+1; end; procedure TMpanel.MouseMove(Shift: TShiftState; X, Y: Integer); begin caption:=inttostr(x)+‘ ‘+inttostr(y); end; procedure TMpanel.MouseUp(Button:TMouseButton; Shift:TShiftState; X, Y: Integer); begin FUpCount:=FUpCount+1; end; procedureRegister; begin RegisterComponents(‘Mihan Components’, [TMpanel]); end; end. |
Созданиеииспользованиесвоейиконкидлякомпонента
Когда вы создали свой компонент и установили его, та на палитре компонентов, его иконка будет такой же как и у компонента, который вы выбрали в качестве предка. Конечно же вам хотелось бы видеть свой компонент со своей иконкой. Для этого необходимо создать файл ресурсов компонента. Сейчас я расскажу вам как это делается.
Откройте Image Editor (Tools->Image Editor) и выберите File->New->Component Resourse File. Перед вами появится небольшое окно с надписью Untitled.dcr в нем будет только одно слово: Contents. Нажмите на него правой кнопкой и в появившемся меню выберите New->Bitmap. Откроется диалоговое окно для настройки параметров изображения. Они должны быть такими: Размер 32×32, цветовой режим VGA (16 colors). Теперь нажмите ok. Теперь надо нажать правой кнопкой на появившейся надписи Bitmap1 и выбрать пункт Rename. Название картинки должно совпадать с названием класса компонента, для которого вы делаете эту иконку (например, TMPanel). Нажмите два раза на Bitmap1 и перед вами появится окно для рисования. Нарисуйте, что вам надо и перейдите на окно с надписью Untitled.dcr и в меню File выберите Save. Имя файла ресурса компонента должно совпадать с именем модуля компонента (без расширения конечно же, например, Mpanel). Файл ресурса готов. Теперь установите ваш компонент заново и в палитре компонентов ваш компонент будет уже с новой иконкой.
Источник: https://delphid.dax