Використання візуального наслідування форм в 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>

*

*