Использование визуального наследования форм в DelphiИсточник: Константин Чертков http://www.delphikingdom.com/asp/users.asp?ID=2079
Как читать исходный код dfm форм? Вот пример dfm:
Что мы тут можем понять: У нас есть форма 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 выглядит следующим образом:
Должен возникнуть вопрос, что происходит если файлы неправильно отредактировать, т.е. то, что находится в pas расходится с тем, что в dfm. Тогда имеем проблему, что при компиляции Delphi не отловит ошибку, но когда вы запустите приложение и попробуете вызвать соответствующее окно, то будет ошибка и окно не откроется. Если же вы откроете это форму в Delphi, то Delphi будет как умеет приводить dfm и pas в соответствие в Delphi 6 и Delphi 7 файл pas приводится к dfm. Т.е., если в dfm вы удалите какой-то объект, то Delphi предложит вам удалить его из pas файла, а если вы удалите объект из pas файла, то Delphi предложит его восстановить. Собственно такой алгоритм работы создает основные неприятности при работе с визуальным наследованием. Приходится отслеживать соответствие между dfm и pas файлами самостоятельно, а это далеко не такое тривиальное занятие, как может показаться на первый взгляд. Как это выглядит визуальное наследование в исходных файлах *.dfm и *.pas Предположим у нас есть обычная форма в которой не используется визуальное наследование форм. То интересующие места исходного кода будут выглядеть следующим образом первая строка EditOrder.dfm
исходный код EditOrder.pas
А вот так будет выглядеть код в случае, если мы использовали визуальное наследование. В качестве формы от которой будем производить визуальное наследование выберем EditOrder. первая строка EditOrderFirm.dfm
исходный код EditOrderFirm.pas
Обратите внимание, что вместо слова object в dfm у нас слово inherited это и есть обозначение, что происходит визуальное наследование. А также в pas вместо TForm у нас TfrmEditOrder. Если поменять в файле эти две вещи, то Delphi будет воспринимать данную форму, как наследника, поэтому даже, если вы в своем проекте не используете визуальное наследование, то приложив соответствующие усилия, ее можно добавить, при этом на самом деле все не так уж сложно. Для того чтобы сделать визуальный наследник, если у вас еще нет форм, необходимо выбрать File-New-Other-Вкладка с именем вашего проекта- там будет выбор форм. Выбрать нужную форму и нажать OK. Пример на рисунке. Окно выбора предка Как и для чего можно использовать визуальное наследование?
Я расскажу о том, как я это использовал. И почему остался очень доволен. Но сейчас бы я хотел обратить внимание на, то чем придется платить. Проблемы возникающие при использовании визуального наследования Delphi проверяет соответствие между dfm и pas только при открытии формы в Delphi. Соответственно отсюда и лезут все проблемы.
Хочу обратить внимание, что в случае изменения исходного кода методов, не надо переоткрывать все формы, а это самая распространенное изменение. Визуальное наследование в действии Пример нашего исходного кода: EditOrder.pas, базовый класс
EditOrderFirm.pas, класс наследник.
Что происходит в базовом классе? Есть переменная отвечающее за то, чтобы не отображать кнопку btnOK. При создании этой формы эта переменная по умолчанию ставится в значение True. При этом это свойство специально помещено в protected, чтобы наследник имел к нему доступ. Далее на Show вызывается метод Customize, в котором в соответствии с этим свойством показывается или нет кнопка btnOK. При этом метод Customize сделан virtual, чтобы его можно было переопределить в наследниках. Что происходит в наследнике? При создании этой формы переменная отвечающая за отображение кнопки btnOK ставится в False. Переопределяется метод Customize. Далее на Show вызывается метод Customize, в котором сначала вызывается, часть из базового класса, а затем еще наша дополнительная часть кода. Если бы мы просто каждый раз переопределяли метод Customize, то нам бы пришлось дублировать код и его было бы очень сложно переписывать. Что важно. Сначала мы устанавливаем параметры в базовом классе, затем если надо переустанавливаем их в классе наследнике, затем вызываем часть ответственную за отображение в базовом классе, а затем часть ответственную за отображение в наследнике. При этом в наследнике мы с отображением можем делать, что угодно. Но параметры которые общие для всех форм (Показывать "Применить" или нет) у нас уже есть и мы их можем учитывать. Кажется, что я не учел, что при повторном показе формы снова вызовется метод Customize, но это легко решаемая проблема, поэтому я не стал усложнять пример. Как результат повторно используется огромная часть кода, но при этом когда возникает необходимость реализовать какое-то особое поведения у нас с этим не возникает никаких проблем. Обычно достаточно просто в Create переопределить нужные параметры, а методы базового класса сделают все за нас. Автоматизацию можно довести вплоть до того, что задаете свойства IdName и TableName и получаете сразу редактирование таблицы. В том проекте, где я использовал визуальное наследование было более 200 форм, около трети всего исходного кода в базовых классах (их было несколько), и там был специальный компонент, в котором хранились все необходимые свойства и они редактировались в Delphi. Как бы я рекомендовал это организовать сейчас:
Проблемы, возникающие при изменении базового класса Самая простая мы добавили новый элемент в базовый класс, с именем (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 прилагается. |