Delphi World - это проект, являющийся сборником статей и малодокументированных возможностей  по программированию в среде Delphi. Здесь вы найдёте работы по следующим категориям: delphi, delfi, borland, bds, дельфи, делфи, дэльфи, дэлфи, programming, example, программирование, исходные коды, code, исходники, source, sources, сорцы, сорсы, soft, programs, программы, and, how, delphiworld, базы данных, графика, игры, интернет, сети, компоненты, классы, мультимедиа, ос, железо, программа, интерфейс, рабочий стол, синтаксис, технологии, файловая система...
Создание компонентов для 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 потолстела (правда несильно). Тут мы выигрываем не много (байты), но это принципиальный момент. Много помалу - много!

Проект Delphi World © Выпуск 2002 - 2004
Автор проекта: ___Nikolay