Шаманський метод Geo, Різне, Програмування, статті

 Автор: © George Judkin  

Я відчуваю певну незручність, починаючи писати цю статтю. З одного боку, начебто, як обіцяв її написати (давно вже обіцяв, більше двох років тому). З іншого, дещо дивно писати про те, що вже давно добре відомо (принаймні, на Королівстві Delphi). Давно обговорили, висловили все “За” і “Проти” і навіть назва стихійно склалося (винесене в заголовок цієї статті). Спочатку хотів включити цей матеріал у велику публікацію з розробки власних компонент. Але там і так матеріалу вище даху. А часу вільного все менше і менше, так що я просто боюся вже братися за ту роботу. Загалом, вирішив підготувати цю невелику публікацію, в якій зібрати всі результати обговорення воєдино.


Отже, про що, власне, мова. Всіх нас, напевно, коли-небудь відвідувала думка, переробити будь стандартні компоненти. Ну, захотіли, розібралися і зробили. Наприклад, захотілося, щоб стандартний TLabel мав кольорову рамку (як і в більшості моїх прикладів, змістовний сенс принесений в жертву простоті). Пишемо щось на зразок такого:










unit Labels;
interface
uses
StdCtrls,Graphics;
type
TMyLabel = class(TLabel)
private
FBorderColor : TColor;
procedure SetBorderColor(Val : TColor);
protected
procedure Paint; override;
published
property BorderColor : TColor read FBorderColor write SetBorderColor default clBlack;
end;
procedure Register;
implementation
uses
Classes;
procedure TMyLabel.SetBorderColor(Val : TColor);
begin
if Val = FBorderColor then Exit;
FBorderColor:=Val;
Invalidate;
end;
procedure TMyLabel.Paint;
begin
inherited;
with Canvas do
begin
Brush.Color:=BorderColor;
FrameRect(ClientRect);
end;
end;
procedure Register;
begin
RegisterComponents(“Samples”,[TMyLabel])
end;
end.


Пишемо, компілюємо, додаємо в палітру і, вуаля, можна кидати на форму і використовувати новий компонент. Але у цього рішення є один мінус: палітра компонент і так містить багато-багато всякого різного. Добре, якщо зробили якийсь корисний універсальний компонент, який потім будемо використовувати постійно. Тоді, начебто, і не шкода. А якщо це разова розробка під конкретну задачу. А під наступний проект ми ще компонентів наробимо. А потім ще. Пройде рік-другий, і нам потрібно розробляти довідково-інформаційну систему, щоб орієнтуватися в панелі компонент.


Є альтернативне рішення: не реєструвати новий компонент, відмовившись від використання в design-time. Замість цього будемо створювати його в run-time, задаючи значення властивостей в коді. Це добре, але за умови, що дизайн форми простий, і відразу зрозуміло, куди і як новий компонент повинен бути вставлений. Якщо це не так, то неможливість візуального проектування призведе до великих витрат часу на переписування коду, компіляцію і перегляд того, що при цьому вийшло. До того ж, не знаю кого як, а мене сильно дратує необхідність писати довжелезний обробник події створення форми, в якому створювати компоненти, а потім довго і нудно задавати значення їх властивостей.


Але виявляється, є можливість і компонент модифікований в палітрі не реєструвати, і зберегти (частково) можливості візуального проектування. Ось тут і починається “шаманізм”. Кидаємо на форму стандартний TLabel і TSpeedButton, задаємо потрібні властивості, а код модифікуємо ось таким чином:










unit Unit1;
interface
uses
Forms, Classes, Controls, StdCtrls, Buttons, Graphics;
type
TLabel = class(StdCtrls.TLabel)
private
FBorderColor : TColor;
procedure SetBorderColor(Val : TColor);
protected
procedure Paint; override;
public
property BorderColor : TColor read FBorderColor write SetBorderColor;
end;
TForm1 = class(TForm)
Label1: TLabel;
SpeedButton1: TSpeedButton;
procedure SpeedButton1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TLabel.SetBorderColor(Val : TColor);
begin
if Val = FBorderColor then Exit;
FBorderColor:=Val;
Invalidate;
end;
procedure TLabel.Paint;
begin
inherited;
with Canvas do
begin
Brush.Color:=BorderColor;
FrameRect(ClientRect);
end;
end;
procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
if Label1.BorderColor = clRed
then
Label1.BorderColor:=clBlue
else
Label1.BorderColor:=clRed;
end;
end.


Тут TSpeedButton доданий, щоб подивитися, як змінюватиметься колір рамки у модифікованого TLabel. StdCtrls – юніт, в якому розташований оригінальний TLabel. Використання імен з конкретизацією через ім’я модуля – стандартна можливість Паскаля. Властивість BorderColor перенесено з published-секції в public, так як воно не може бути змінено через Object Inspector, бо той знає тільки про властивості стандартного TLabel, в яких жодного BorderColor немає.


При цьому в дизайнері ми бачимо звичайний TLabel (ліва картинка), а в запущеній програмі – модифікований (права картинка).

Тепер кілька слів, чому так відбувається, і чому так можна робити.


Як працює дизайнер? Коли ми розміщуємо компоненти на формі і ставимо їх властивості, дизайнер робить дві речі: вставляє в певні місця коду потрібні рядки і створює DFM-файл з інформацією про компоненти та їх властивості, за яким буде створюватися форма. У компіляції дизайнер не бере участь, відповідно, компілятор орієнтується тільки на код. А в коді у нас використовується інший клас – нащадок первісного – З тим же ім’ям і тим же набором властивостей. У Паскалі використання однакових імен помилкою не є, тому що існують формальні правила вирішення таких ситуацій.


Звідси випливає і безпеку використання такого ось, начебто, “шаманського” прийому. Ми обманюємо тільки дизайнер, який виконує функції часткової кодогенераціі та формування ресурсного файлу, але не бере участі в процесі компіляції. Конкретизація імені через вказівку юніта, в якому це ім’я визначено, є документованої можливістю Паскаля з тих самих пір, як в ньому було введено розбиття програм на юніти. Механізм, коли замість одного класу підставляється примірник іншого класу (нащадка первісного) називається пізніше зв’язування і є одним з основних принципів всього об’єктно-орієнтованого програмування.


Цей прийом може призвести до збоїв, якщо в майбутніх версіях: буде скасована підтримка конкретизації імені через додаткове зазначення модуля (малоймовірно), не буде підтримуватися пізніше зв’язування для класів (ще менш ймовірно), дизайнер буде більшою мірою задіяний у формуванні виконуваного модуля (більш імовірно, але поки цього немає).


Можна, до речі, провести ще такий експеримент (експеримент цей пропоную все ж проводити умоглядно, щоб не довелося потім возитися з відновленням вихідних налаштувань IDE). Давайте зафіксуємо небудь вміст DFM-файла проекту, отриманого із застосуванням шаманізму. А потім візьмемо чесний варіант нового компонента (з першого лістингу), перейменуємо клас в TLabel, скомпілюємо і встановимо в палітру, прибравши звідти попередньо стандартний TLabel. Тепер якщо створити проект, в якому точно так же розмістити новий TLabel, задати всі ті ж значення властивостей і не чіпати в design-time властивість BorderColor, то ми отримаємо в точності такий же DFM-файл. Для більшого естетизму можна і компіляцію виконати не з IDE, а безпосередньо запустивши компілятор і передавши йому потрібні параметри.


Підіб’ємо підсумки. Переваги цього методу полягають у можливості доопрацювання стандартних компонентів під потреби конкретного завдання і, зберігши можливості візуального проектування, уникнути “загажіванія” палітри компонент одноразовими виробами. Питання про переваги модифікації стандартних компонент перед використанням сторонніх компонент, перед використанням обробників подій, що дозволяють часто змінювати зовнішній вигляд стандартних компонент в досить широкому діапазоні, а також навіщо взагалі об’єднувати свій код в класи, а не обмежитися застосуванням стандартних класів, виходить за рамки даної статті. Хоча я готовий обговорити і їх теж, якщо виникне такий інтерес.


Суттєвих недоліків (крім природного відрази до застосування чітерскіх прийомів) виділено два:



  1. В design-time ми бачимо старий варіант компонента. Якщо для нас має цінність тільки взаємне розташування компонент на формі, а модифікація зовнішнього вигляду не дуже велика і може бути зрозуміла через уяву, то все добре. Якщо ми в процесі візуального проектування, наприклад, намагаємося розробити колірне рішення форми для компонента з сильно модифікованим зовнішнім виглядом, то нічого доброго не вийде. Так що застосовуйте даний прийом з урахуванням даного обмеження.
  2. Не вийде на одній формі використовувати і оригінальні, і модифіковані компоненти. Тільки щось одне.

Все це потрібно брати до уваги перш, ніж приймати рішення про використання даного методу.


Варіант подальшого розвитку даного методу був запропонований Юрієм Спектором (Ins). Припустимо, нам потрібно використовувати модифікований компонент не в одній формі проекту, а в декількох. В цьому випадку можна винести реалізацію модифікованого класу в окремий юніт і підключати цей юніт в міру необхідності через uses. Але тут є одна хитрість. Справа в тому, що з великим ступенем ймовірності Вам в проектуванні форми будуть потрібні компоненти, які визначені в тому ж юніті, де і оригінальний компонент. Щоб використовувався саме модифікований компонент, потрібно грамотно визначити порядок підключаються юнітів в розділі uses. За правилами мови якщо в uses є два модулі, які містять одне і те ж ім’я, то буде використаний елемент з того модуля, який у списку uses вказаний пізніше. Змінюючи порядок юнітів в uses, можна отримувати потрібну комбінацію оригінальних і модифікованих компонент у формі. Однак все одно зберігається обмеження, що в одній формі неможливо використовувати і оригінальний компонент, і його модифікацію.


Трохи історії. Прийом цей був придуманий мною ще за часів Delphi 1 і успішно застосовувався задовго до мого першого появи на Королівстві Delphi. Але я не впевнений, що був першим у світі, хто до такого додумався, так як прийом досить очевидний (принаймні, якщо мати досвід роботи з об’єктно-орієнтованим програмуванням і інструментами візуального проектування). Так що на авторські права не претендую. На Круглому Столі цей прийом вперше активно обговорювалося в питанні 35814. Але вперше я згадав його дещо раніше – в питанні 29175. Ну і ще було активне обговорення тут.


Наостанок хотілося б, як водиться, висловити подяки.


Юрію Спектору Мій тезка є головним популяризатором даного методу (причому, не тільки на Королівстві Delphi). До того ж, він – головний “пінатель” мене, щоб я цей метод оформив у вигляді статті.


Антону Григор’єву Антон – головний опонент. І в суперечці з ним даний прийом, який був до того якоїсь інтуїтивно зрозумілою примочкою, знайшов суворе обгрунтування.


Олексію Румянцеву Олексій видав якось одну єдину фразу (про зірочки). Але ця фраза не давала мені спокійно жити, підштовхуючи до підготовки цієї публікації.


А також всім-всім-всім, хто брав участь в обговоренні даного прийому. Незалежно від того, є ви прихильниками або супротивниками. Якщо спір ведеться конструктивно, то від грамотного противника, часто, більше користі, ніж від десятка прихильників.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*