Создание компонентов для KOL и MCK - Часть 1 - Создание невизуального KOL компонента
Ведущий раздела KOL и MCK: Анатолий aka XVeL
Автор: Жаров Дмитрий aka Gandalf
WEB-сайт: http://kol.mastak.ru
Полную версию библиотеки KOL и MCK можно скачать здесь
Введение: Дорога из желтого кирпича
С появлением KOL, а после и MCK в программировании под Delphi
началась новая эпоха - стало возможно создавать быстро, быстрый и легкий
код, не отказываясь от любимого языка. Но среди достоинств этого, на
данной стадии развития - есть и недостатки, нехватка компонентов,
сравнимая почти с нехваткой воздуха, и что самое главное, нехватка людей,
которые могут их написать. Данная статья ставит себе целью изменить
ситуацию и перевести написание компонентов для KOL и MCK из разряда "для
избранных" в разряд "для всех". При этом она не в коей мере не ставит себе
целью обучением вас Delphi, KOL, MCK считается, что эти знания у вас уже
есть. Материал преподносится в форме "от простого к сложному", и так
начнем.
Создание не визуального KOL компонента: Шапка невидимка
Кладов при
создании KOL оставил возможность создавать невизуальные компоненты. Для
этого нам необходимо объявить новый объект, наследуясь от TObj (из модуля
KOL): TNewKOLUnVisual = object(TObj)
private
public
end;
Заметьте, что когда я сказал объект, я имел в виду именно объект, а не
класс! Такова особенность модели наследования KOL, к ней придется
привыкнуть. Данный переход на "устаревшую" модель обусловлен ее
эффективностью и спецификой внутренней реализации, что дало возможность
построить на ее основе KOL. Далее, чтобы наше повествование не было некой
абстракцией, давайте перейдем на построение компонента, я взял за пример
постройку AboutDialog. Хочу сделать небольшое отступление по поводу
названия файла, ныне принят негласный стандарт kolComponentName.pas либо
KOLComponentName.pas, я придерживаюсь второго, поскольку KOL - это
аббревиатура и по правилам ее надо писать в верхнем регистре (тем более
так она логически выделяется в имени). Я и далее буду вставлять
рекомендации, они сделаны на фоне моего опыта и наблюдений, и исходят из
неких соображений, я ни в коем случаи их вам не навязываю, причем, если вы
не согласны с моими посылами - дискуссия открыта, адрес вы знаете.
unit KOLMHAboutDialog;
interface
uses
KOL;
type
PMHAboutDialog =^TMHAboutDialog;
TKOLMHAboutDialog = PMHAboutDialog;
TMHAboutDialog = object(TObj)
private
public
end;
implementation
end.
Как можно заметить модуль называется, как и файл, знакомьтесь это
шаблон компонента. После беглого просмотра можно увидеть две строки
значение коих еще не объяснялось:
PMHAboutDialog =^TMHAboutDialog;
TKOLMHAboutDialog = PMHAboutDialog;
Эти строки также результат специфики KOL, поскольку мы в KOL работаем
не с самими объектами, а с указателями на них. Потому мы и написали первую
строку. Вторая нужна для психологического успокоения нас и компилятора,
поскольку механизм MCK, как мы увидим позже, использует именно этот тип.
Хочу обратить ваше внимание на череду имен (PMHAboutDialog,
TMHAboutDialog, TKOLMHAboutDialog) и правила их образования (думаю их
комментировать не стоит) - это тоже устоявшееся положение. Далее
давайте добавим немного свойств, для нашего нового компонента, получим
нечто такое. При создании компонентов для KOL и MCK (будь то
визуальные компоненты или нет) я руководствуюсь следующими
правилами: 1. Раньше мы работали на VCL, потому переход на KOL и MCK
будет безболезненным, если KOL и MCK компонент будет повторять VCL (его
свойства, методы и прочее). 2. Мы делаем KOL и MCK фактически как
инструмент для упрощения работы с API, поэтому логично, что мы должны
перенести все возможности API. 3. Код который будет в KOL части большей
частью перейдет в наш проект и от качества кода (и его размера) будет
зависеть качество полученного exe-файла (или модуля или dll-библиотеки),
по этому: " Не бойтесь использовать ассемблер " Не делайте лишнего
или громоздкого кода " Но оптимизация не должна идти за счет
удобства
unit KOLMHAboutDialog;
interface
uses
KOL, Windows, ShellAPI;
type
TIconType = (itShell, itApplication, itCustom);
PMHAboutDialog =^TMHAboutDialog;
TKOLMHAboutDialog = PMHAboutDialog;
TMHAboutDialog = object(TObj)
private
FTitle: String;
FCopyRight: String;
FText: String;
FIcon: HIcon;
FIconType: TIconType;
public
property Title: String read FTitle write FTitle;
property CopyRight: String read FCopyRight write FCopyRight;
property Text: String read FText write FText;
property Icon: HIcon read FIcon write FIcon;
property IconType: TIconType read FIconType write FIconType;
end;
implementation
end.
Думаю, что никто для себя ничего нового не нашел. Но мог возникнуть
вопрос, зачем все вынесено в свойства, когда в этом нет необходимости
(чтение и запись идут напрямую), отвечу - как пример, и поскольку
необходимости в этом действительно нет, мы переделаем код:
unit KOLMHAboutDialog;
interface
uses
KOL, Windows, ShellAPI;
type
TIconType = (itShell, itApplication, itCustom);
PMHAboutDialog = ^TMHAboutDialog;
TKOLMHAboutDialog = PMHAboutDialog;
TMHAboutDialog = object(TObj)
private
public
Title: String;
CopyRight: String;
Text: String;
Icon: String;
IconType: TIconType;
end;
implementation
end.
Если посмотреть размер, после этих несложных манипуляций уменьшился
почти в 2 раза. Да, если кого удивило появление модулей Windows и
ShellAPI, не пугайтесь, они понадобиться чуть позже. Ну что же пора
вносить работоспособность в компонент, сделаем это добавив два метода
Destroy и Execute:
unit KOLMHAboutDialog;
interface
uses
KOL, Windows, ShellAPI;
type
TIconType = (itShell,itApplication,itCustom);
PMHAboutDialog = ^TMHAboutDialog;
TKOLMHAboutDialog = PMHAboutDialog;
TMHAboutDialog = object(TObj)
private
public
Title: String;
CopyRight: String;
Text: String;
Icon: HIcon;
IconType: TIconType;
destructor Destroy; virtual;
procedure Execute;
end;
implementation
destructor TMHAboutDialog.Destroy;
begin
DestroyIcon(Icon);
inherited;
end;
procedure TMHAboutDialog.Execute;
var
HWndOwner: THandle;
TMPIcon: HIcon;
begin
if Assigned(Applet) then
HWndOwner := Applet.Handle
else
HWndOwner := 0;
case IconType of
itShell: TMPIcon := 0;
itApplication: TMPIcon := Applet.Icon;
itCustom: TMPIcon := Icon;
end;//case
ShellAbout(HWndOwner, PChar(Title + '#' + Text), PChar(CopyRight), TMPIcon);
end;
end.
Начнем разбираться, Destroy (не забывайте virtual, а то компилятор не
пустит). Фактически в нем идет освобождение ресурса иконки (Проверку на
наличие иконки делать не надо, поскольку мы используем функцию DestroyIcon
- если иконку уничтожить нельзя она вернет не нуль, но программа будет
работать корректно), а далее выполнения Destroy предка. Метод Execute не
должен вызывать испуга у людей знакомых с API. Вопрос может возникнуть
только по поводу переменной Applet - это глобальная переменная из KOL типа
PControl, аналог Application из Delphi. У наблюдательных читателей,
возможно, возник вопрос: Destroy есть, а где Create? Будет, вот он:
unit KOLMHAboutDialog;
interface
uses
KOL, Windows, ShellAPI;
type
TIconType = (itShell, itApplication, itCustom);
PMHAboutDialog = ^TMHAboutDialog;
TKOLMHAboutDialog = PMHAboutDialog;
TMHAboutDialog = object(TObj)
private
public
Title: String;
CopyRight: String;
Text: String;
Icon: HIcon;
IconType: TIconType;
destructor Destroy; virtual;
procedure Execute;
end;
function NewMHAboutDialog: PMHAboutDialog;
implementation
function NewMHAboutDialog:PMHAboutDialog;
begin
New(Result, Create);
end;
destructor TMHAboutDialog.Destroy;
begin
DestroyIcon(Icon);
inherited;
end;
procedure TMHAboutDialog.Execute;
var
HWndOwner: THandle;
TMPIcon: HIcon;
begin
if Assigned(Applet) then
HWndOwner := Applet.Handle
else
HWndOwner := 0;
case IconType of
itShell: TMPIcon := 0;
itApplication: TMPIcon := Applet.Icon;
itCustom: TMPIcon := Icon;
end;//case
ShellAbout(HWndOwner, PChar(Title + '#' + Text), PChar(CopyRight), TMPIcon);
end;
end.
Те кто задавали этот вопрос, наверное, ждали нечто иное чем внешнюю
функцию NewMHAboutDialog, но ничего не поделаешь KOL. Помните, я говорил,
что нам придется работать с указателями, да и ООП модель у нас на базе
объектов, а не классов. Сама функция как мы видим, выделяет память для
объекта. Теперь немного о самой форме New функции, опять же обратите
внимание на название - без комментариев. Могут возникнуть вопросы: что
делать внутри функции, и какие передавать ей параметры? Ответ, внутри
функция должна выполнять минимум операций, т.е. тот минимум, который
необходим для начала работы с компонентом. С параметром так же, передаются
только необходимые непосредственно функции New и обязательно используемые
с нею (обычно эти группы неразделимы). Вы можете сказать, что тогда надо
добавить в New загрузку иконки, ведь мы ее всегда уничтожаем - но это
неправильно, мы ее всегда уничтожаем, Но не всегда создаем! И как можно
заметить не одно из свойств не является обязательным, т.е. метод Execute
можно вызвать сразу после New. Возможно, это не самый удачный пример,
будут и лучше. Таким образом, мы экономим немного кода, а в итоге
выигрываем в скорости и размере. Надеюсь, я вас убедил. Хммм…
неслышу возгласа "ура". Почему? Так ведь мы KOL компонент сделали! Теперь
давайте проверим его в деле. Возьмем, какой-нибудь проект (KOL конечно) и
добавим строки:
uses … {$ENDIF}, KOLMHAboutDialog; …
var
TMP: TKOLMHAboutDialog;
begin
TMP := NewMHAboutDialog;
TMP.Execute;
TMP.Free;
end;
Запустили, посмотрели - работает. Вернусь к нашим баранам, если бы
мы сделали функцию New с параметром-иконкой, код, был бы таким:
var
TMP: TKOLMHAboutDialog;
begin
TMP := NewMHAboutDialog(0);
TMP.Execute;
TMP.Free;
end;
Замечаете, мы фактически задаем ненужный параметр, да к тому же сама
функция New потолстела (правда несильно). Тут мы выигрываем не много
(байты), но это принципиальный момент. Много помалу - много!
|