Rambler's Top100
"Knowledge itself is power"
F.Bacon
Поиск | Карта сайта | Помощь | О проекте | ТТХ  
 Hello, World!
  
 

Фильтр по датам

 
 К н и г и
 
Книжная полка
 
 
Библиотека
 
  
  
 


Поиск
 
Поиск по КС
Поиск в статьях
Яndex© + Google©
Поиск книг

 
  
Тематический каталог
Все манускрипты

 
  
Карта VCL
ОШИБКИ
Сообщения системы

 
Форумы
 
Круглый стол
Новые вопросы

 
  
Базарная площадь
Городская площадь

 
   
С Л С

 
Летопись
 
Королевские Хроники
Рыцарский Зал
Глас народа!

 
  
ТТХ
Конкурсы
Королевская клюква

 
Разделы
 
Hello, World!
Лицей

Квинтана

 
  
Сокровищница
Подземелье Магов
Подводные камни
Свитки

 
  
Школа ОБЕРОНА

 
  
Арсенальная башня
Фолианты
Полигон

 
  
Книга Песка
Дальние земли

 
  
АРХИВЫ

 
 

Сейчас на сайте присутствуют:
 
  
 
Во Флориде и в Королевстве сейчас  00:55[Войти] | [Зарегистрироваться]

Использование визуального наследования форм в Delphi

Константин Чертков
дата публикации 15-08-2008 09:13

Использование визуального наследования форм в 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 прилагается.

StMage, cпециально для Королевства Delphi.




Смотрите также материалы по темам:


 Обсуждение материала [ 21-10-2009 06:43 ] 9 сообщений
  
Время на сайте: GMT минус 5 часов

Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter.
Функция может не работать в некоторых версиях броузеров.

Web hosting for this web site provided by DotNetPark (ASP.NET, SharePoint, MS SQL hosting)  
Software for IIS, Hyper-V, MS SQL. Tools for Windows server administrators. Server migration utilities  

 
© При использовании любых материалов «Королевства Delphi» необходимо указывать источник информации. Перепечатка авторских статей возможна только при согласии всех авторов и администрации сайта.
Все используемые на сайте торговые марки являются собственностью их производителей.

Яндекс цитирования