Константин Чертков дата публикации 15-08-2008 09:13 Использование визуального наследования форм в Delphi
Вот пример 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 файлами самостоятельно, а это далеко не такое тривиальное занятие, как может показаться на первый взгляд.
Предположим у нас есть обычная форма в которой не используется визуальное наследование форм. То интересующие места исходного кода будут выглядеть следующим образом
первая строка EditOrder.dfm
object frmEditOrder: TfrmEditOrder
|
|
исходный код EditOrder.pas
unit EditOrder;
interface
uses
........
type
TfrmEditOrder = class(TForm)
.....
end;
........
implementation
.....
end.
|
|
А вот так будет выглядеть код в случае, если мы использовали визуальное наследование. В качестве формы от которой будем производить визуальное наследование выберем EditOrder.
первая строка EditOrderFirm.dfm
inherited frmEditOrderFirm: TfrmEditOrderFirm
|
|
исходный код EditOrderFirm.pas
unit EditOrderFirm;
interface
uses
........
type
TfrmEditOrderFirm = class(TfrmEditOrder)
.....
end;
........
implementation
.....
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
protected
FVisiblebtnOK: boolean;
procedure Customize; virtual;
public
constructor Create(AOwner: TComponent); override;
end;
var
frmEditOrder: TfrmEditOrder;
implementation
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
protected
procedure Customize; override;
public
constructor Create(AOwner: TComponent); override;
end;
var
frmEditOrderFirm: TfrmEditOrderFirm;
implementation
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.
Как бы я рекомендовал это организовать сейчас:
- Компонент(ы), который(е) хранит(ят) все необходимые свойства, со значениями по умолчанию редактируемыми в Delphi.
- Компонент(ы), который(е) умеет(ют) загружать нужные свойства из ini, БД, реестра, откуда это необходимо.
- Компонент(ы), который(е) отображает(ют) уже все необходимое на форму, по сути аналог 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 прилагается.
StMage, cпециально для Королевства Delphi.
Обсуждение материала [ 21-10-2009 06:43 ] 9 сообщений |