Використання візуального наслідування форм в Delphi

Як читати вихідний код dfm форм?


Ось приклад dfm:










      object frmMain: TfrmMain
Left = 223
Top = 228
Width = 387
Height = 272
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = “MS Sans Serif”
Font.Style = []
Menu = mmMain
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
object alMain: TActionList
Left = 136
Top = 96
object acExit: TAction
Caption = #1042#1099#1093#1086#1076
OnExecute = acExitExecute
end
end
object ilMain: TImageList
Left = 168
Top = 96
end
object mmMain: TMainMenu
Images = ilMain
Left = 40
Top = 16
object mmiFile: TMenuItem
Caption = #1060#1072#1081#1083
object miExit: TMenuItem
Action = acExit
end
end
end
end


Що ми тут можемо зрозуміти: У нас є форма frmMain. у неї задані властивості: Left, Right, Width, Height, Color, Font, Menu, OldCreateOrder, PixelsPerInch, TextHeight їх значення задані після знака одно. А інші значення відповідають значенням за замовчуванням, по цьому їх тут немає.


Більш того, можна визначити, що у нас на формі зараз 3 об'єкти, визначити це дуже просто у нас слово object c відступом у два пропуски міститься 3 рази.


Більше того ми можемо легко визначити, що у нас на форму додано: alMain – TActionList; ilMain – TImageList; mmMain – TMainMenu;


У alMain – задані властивості Left і Top. У цьому ActionList міститься одну дію. Дія з ім'ям (Name) acExit і якимось Caption російською мовою, а на onExecute у нас виконується метод acExitExecute. А оскільки це у нас не візуальний компонент, то Left і Top це положення компонента на формі під час Design-Time. До речі зверніть увагу, що властивість Image не задано.


У ilMain задані властивості Left і Top. І в нього ще не додано жодних картинок, оскільки інакше було б відповідне властивість.


У mmMain задані властивості Left, Top і Image. Дане меню містить один верхній пункт меню з ім'ям (Name) – mmiFile і текстом на русcком. У цьому меню є один пункт з ім'ям (Name) – miExit і дією (Action) acExit.


Зверніть увагу, що всі відступи по два пропуски. Тобто ступінь вкладеності дуже легко порахувати. Властивості мають аналогічну назву, що і в Object Inspector, за винятком властивості Name, яка пишеться відразу після слова object. Складність з тим, що російський текст зберігається так, що його дуже важко читати. Після закінчення значень властивостей або вкладених об'єктів йде слово end. Тобто структура наступна: object Name: Type значення властивостей, вкладені об'єкти end;


Відповідна частина коду у файлі pas виглядає наступним чином:










      TfrmMain = class(TForm)
alMain: TActionList;
ilMain: TImageList;
mmMain: TMainMenu;
acExit: TAction;
mmiFile: TMenuItem;
miExit: TMenuItem;
procedure acExitExecute(Sender: TObject);
……..
end;


Повинен виникнути питання, що відбувається якщо файли неправильно відредагувати, тобто те, що знаходиться в pas розходиться з тим, що в dfm. Тоді маємо проблему, що при компіляції Delphi не відловити помилку, але коли ви запустите додаток і спробуєте викликати відповідне вікно, то буде помилка і вікно не відкриється. Якщо ж ви відкриєте це форму в Delphi, то Delphi буде як вміє приводити dfm і pas в відповідність в Delphi 6 і Delphi 7 файл pas приводиться до dfm. Тобто, якщо в dfm ви вилучили якийсь об'єкт, то Delphi запропонує вам видалити його з pas файлу, а якщо ви видалите об'єкт з pas файлу, то Delphi запропонує його відновити. Власне такий алгоритм роботи створює основні неприємності при роботі з візуальним спадкуванням. Доводиться відслідковувати відповідність між dfm і pas файлами самостійно, а це далеко не таке тривіальне заняття, як може здатися на перший погляд.


Як це виглядає візуальне спадкування у вихідних файлах *. dfm та *. pas


Припустимо у нас є звичайна форма в якій не використовується візуальне спадкування форм. Те цікавлять місця вихідного коду будуть виглядати наступним чином


перший рядок EditOrder.dfm










      object frmEditOrder: TfrmEditOrder


вихідний код EditOrder.pas










      unit EditOrder;

interface

uses
……..
type
TfrmEditOrder = class(TForm)
…..
end;
……..
implementation

{$R *.DFM}
…..
end.


А ось так буде виглядати код у разі, якщо ми використовували візуальне спадкування. Як форма від якої будемо виробляти візуальне спадкування виберемо EditOrder.


перший рядок EditOrderFirm.dfm










      inherited frmEditOrderFirm: TfrmEditOrderFirm


вихідний код EditOrderFirm.pas










      unit EditOrderFirm;

interface

uses
……..
type
TfrmEditOrderFirm = class(TfrmEditOrder)
…..
end;
……..
implementation

{$R *.DFM}
…..
end.


Зверніть увагу, що замість слова object в dfm у нас слово inherited це і є позначення, що відбувається візуальне спадкування. А також у pas замість TForm у нас TfrmEditOrder. Якщо поміняти у файлі ці дві речі, то Delphi буде сприймати дану форму, як спадкоємця, тому навіть, якщо ви в своєму проекті не використовуєте візуальне спадкування, то приклавши відповідні зусилля, його можна додати, при цьому насправді все не так вже й складно.


Для того щоб зробити візуальний спадкоємець, якщо у вас ще немає форм, необхідно вибрати File-New-Other-Вкладка з ім'ям вашого проекту-там буде вибір форм. Вибрати потрібну форму і натиснути OK. Приклад на малюнку.



Вікно вибору предка

Як і для чого можна використовувати візуальне наслідування?



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


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


Delphi перевіряє відповідність між dfm і pas тільки при відкритті форми в Delphi. Відповідно звідси і лізуть всі проблеми.



Хочу звернути увагу, що в разі зміни вихідного коду методів, не треба перевідкривати всі форми, а це сама поширене зміна.


Візуальне спадкування в дії


Приклад нашого вихідного коду:


EditOrder.pas, базовий клас










      unit EditOrder;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls, StdCtrls;

type
TfrmEditOrder = class(TForm)
ButtonPanel: TPanel;
btnOK: TButton;
btnCancel: TButton;
procedure FormShow(Sender: TObject);
private
{ Private declarations }
protected
FVisiblebtnOK: boolean;
procedure Customize; virtual;
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
end;

var
frmEditOrder: TfrmEditOrder;

implementation

{$R *.dfm}

{ TfrmEditOrder }

constructor TfrmEditOrder.Create(AOwner: TComponent);
begin
inherited;

FVisiblebtnOK := True;
end;

procedure TfrmEditOrder.Customize;
begin
btnOK.Visible := FVisiblebtnOK;
end;

procedure TfrmEditOrder.FormShow(Sender: TObject);
begin
Customize;
end;

end.


EditOrderFirm.pas, клас спадкоємець.










      unit EditOrderFirm;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, EditOrder, StdCtrls, ExtCtrls;

type
TfrmEditOrderFirm = class(TfrmEditOrder)
private
{ Private declarations }
protected
procedure Customize; override;
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
end;

var
frmEditOrderFirm: TfrmEditOrderFirm;

implementation

{$R *.dfm}

constructor TfrmEditOrderFirm.Create(AOwner: TComponent);
begin
inherited;

FVisiblebtnOK := False;
end;

procedure TfrmEditOrderFirm.Customize;
begin
inherited;

if not FVisiblebtnOK then begin
btnCancel.Caption := “Exit”;
Self.Caption: = "Перегляд Замовлення"
end;
end;

end.


Що відбувається в базовому класі? Є мінлива відповідає за те, щоб не відображати кнопку btnOK. При створенні цієї форми ця змінна за замовчуванням ставиться в значення True. При цьому це властивість спеціально поміщений до protected, щоб спадкоємець мав до нього доступ. Далі на Show викликається метод Customize, в якому відповідно до цього властивістю показується чи ні кнопка btnOK. При цьому метод Customize зроблений virtual, щоб його можна було визначити у спадкоємців.


Що відбувається в спадкоємця? При створенні цієї форми мінлива відповідає за відображення кнопки btnOK ставиться в False. Перевизначається метод Customize. Далі на Show викликається метод Customize, в якому спочатку викликається, частина з базового класу, а потім ще наша додаткова частина коду. Якщо б ми просто кожен раз перевизначати метод Customize, то нам би довелося дублювати код і його було б дуже складно переписувати.


Що важливо. Спочатку ми встановлюємо параметри в базовому класі, потім якщо треба перевстановлюємо їх у класі спадкоємця, потім викликаємо частина відповідальну за відображення в базовому класі, а потім частину відповідальну за відображення наступнику. При цьому в спадкоємця ми з відображенням можемо робити, що завгодно. Але параметри які спільні для всіх форм (Показувати "Застосувати" чи ні) у нас вже є і ми їх можемо враховувати. Здається, що я не врахував, що при повторному показі форми знову зголоситься метод Customize, але це легко вирішується проблема, тому я не став ускладнювати приклад.


Як результат повторно використовується величезна частина коду, але при цьому коли виникає необхідність реалізувати якесь особливе поведінки у нас з цим не виникає жодних проблем. Зазвичай досить просто в Create перевизначити потрібні параметри, а методи базового класу зроблять все за нас. Автоматизацію можна довести аж до того, що задаєте властивості IdName і TableName і отримуєте відразу редагування таблиці. У тому проекті, де я використав візуальне спадкування було більше 200 форм, близько третини всього вихідного коду в базових класах (їх було кілька), і там був спеціальний компонент, в якому зберігалися всі необхідні властивості й вони редагувалися в Delphi.


Як би я рекомендував це організувати на сайті:



  1. Компонент (и), який (і) зберігає (ят) всі необхідні властивості, зі значеннями за замовчуванням редагованими в Delphi.
  2. Компонент (и), який (і) вміє (ють) завантажувати потрібні властивості з ini, БД, реєстру, звідки це необхідно.
  3. Компонент (и), який (і) відображає (ють) вже все необхідне на форму, по суті аналог Layout в Java.

Проблеми, що виникають при зміні базового класу


Найпростіша ми додали новий елемент у базовий клас, з ім'ям (Name), яке 100% відсутня у всіх спадкоємців. Потрібно банально перевідкрити всі форми і добитися, щоб вона там з'явилася. Робимо Пошук class (тип базової форми). Можна не сумніватися ми знайдемо всі форми. Тепер відкриваємо їх переконуємося, що все ок, ставимо і стираємо де-небудь пробіл і зберігаємо. Все буде OK.


Ми вирішили винести якийсь вже існуючий елемент з спадкоємця в базову форму. Тоді закриваємо спадкоємця. Відкриваємо базову форму і додаємо в неї потрібний елемент. Тепер ліземо в спадкоємця НЕ Delphi засобами в dfm, замість слова object пишемо inherited і по максимуму видаляємо значення властивостей інакше, потім їх доведеться правити руками, в pas видаляємо рядок в якій визначено відповідний об'єкт. Тепер відкриваємо форму спадкоємець в Delphi, якщо все нормально значить ми все зробили правильно. Якщо виникли якісь проблеми, то краще закрити не зберігаючи. Досить часта ситуація, що при такій операції змінюється тип об'єкта, тоді потрібно додатково його скоригувати в dfm і перевірити відповідність властивостей у цих типів об'єктів.


Ми додали новий елемент у базовий клас, з ім'ям (Name), яке 100% відсутня у всіх спадкоємців. Але при відкритті, якогось спадкоємця у нас посипалися помилки. Це означає, що насправді ми помилилися і на цій формі вже є такий елемент. Доведеться знову-таки ред dfm і pas вищеописаним способом. Або всі відкочувати ….


Ми випадково, що якось зрушили в спадкоємці, що не треба було робити. Можна або по правій кнопці вибрати Revert to Inherited, але тоді всі властивості будуть скинуті, або залізти в dfm і видалити непотрібне властивість.


Резюме


Не бійтеся проблем, ви дуже швидко навчитеся їх вирішувати, і вони не будуть вам здаватися, якимось шаманством. Це допоможе навчиться працювати з dfm, а це стане в нагоді надалі. Наприклад, дуже зручно у випадку контролю версій, можна відразу зрозуміти, що у формі змінили, не відкриваючи Delphi, а порівнюючи dfm, як текстові файли. Або ще досить рідко буває, але дуже корисно, потрібно якийсь тип компонентів (TВutton) замінити на (TImageButton) у всьому проекті. Просто робимо пошук або можна навіть автозаміну по dfm і pas. Справа майже зроблено. Залишилося тільки переконається, що ми, десь якось дуже хитро не прив'язали до типом TButton. Найпростіший випадок inherited (TCustomButton). Це просто приклад. Взагалі не бійтеся ред dfm, це не якісь магічні буковки, це добре читабельний вихідний код. Чому в. Net і Java до цих пір не зроблено аналогічно, мені не зрозуміло. Спеціально перед написанням статті глянув NetBeans 6.1, щоб не бути голослівним. Щиро вважаю, що то як реалізовано візуальне спадкування у Delphi робить його найбільш зручним інструментом для написання GUI-інтерфейсу. А не використовувати його безглуздо позбавляти себе переваг розробки GUI на Delphi. Навіть якщо ви зараз не використовуєте візуальне спадкування, то його не так складно впровадити. Особисто в мене це зайняло на всьому моєму проекті, близько двох тижнів з моменту старту і до моменту випуску стабільної робочої версії, і це при більше 200 формах і при врахуванні того, що я робив все в перший раз і в мене не було ніякої теоретичної підготовки.


Звертаю увагу, що я активно використовував візуальне спадкування у Delphi 6 і 7. І не можу гарантувати, що в більш нових версіях Delphi, щось не змінилося.


Приклад вихідного коду на Delphi 7 додається.

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


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

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

Ваш отзыв

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

*

*