Delphi 3. Библиотека программиста

       

Наследование форм


Вместо использования описанной выше методики я создаю самые обычные формы, производные от TEmbeddedForm. Как видно из листинга 10.1 (фрагмент модуля EMBEDDED.PAS), у внедренных форм имеется специальный конструктор, позволяющий рассматривать их как элементы, которые можно разместить на любом элементе-контейнере (панели, вкладке или групповом поле) во время выполнения программы. Поскольку формы сами по себе являются объектами, вы можете по своему усмотрению добавить любые методы, необходимые для придания им функций видов. Поскольку такие элементы являются формами, внедренную форму при необходимости можно так же легко изменить, как и любую другую.

Листинг 10.1. Специальный конструктор для внедренных форм

type EmbeddedFormMode = (efmZoomed, efmTopLeft, efmCentered); function ALZ(Number: integer): Cardinal; // Проверка положительности begin if Number > 0 then Result := Number else Result := 0; end; constructor TEmbeddedForm.CreateEmbedded( _Owner: TComponent; Frame: TWinControl; Mode: EmbeddedFormMode ); begin Inherited Create(_Owner); Parent := Frame; BorderIcons := []; BorderStyle := bsNone; case Mode of efmZoomed: Align := alClient; efmTopLeft: begin Top := 0; Left := 0; end; // efmTopLeft efmCentered: begin Top := ALZ((Frame.Height - Height) div 2); Left := ALZ((Frame.Width - Width) div 2); end; // efmCentered else Assert(False); end; // case Visible := True; end; // TEmbeddedForm.CreateEmbedded

В листинге 10.1 самой важной является строка Parent := Frame, которая назначает элемент Frame родителем внедренной формы. Именно это происходит «за кулисами» с обычными элементами управления при загрузке формы. Назначение родителя имеет три важных последствия. Во-первых, дочерний элемент отображается тогда, когда отображается его родитель. Следовательно, скрытие фрейма приводит к скрытию вида, а отображение фрейма или перевод его на передний план приводит к отображению вида. Во-вторых, дочерние элементы обрезаются по клиентской области родителя, поэтому большой вид автоматически вписывается в границы фрейма. Втретьих, дочерний элемент позиционируется относительно клиентской области своего родителя; свойства Top и Left для внедренной формы, как и для любого другого элемента, измеряются по отношению к содержащему его контейнеру.

Последнее означает, что при масштабировании внедренной формы установкой свойства Align равным alClient форма ведет себя так же, как и любой другой элемент, выровненный подобным образом: она заполняет собой весь фрейм и автоматически масштабируется (с вызовом обработчика OnResize) при масштабировании фрейма. Без масштабирования вид сохраняет размеры, заданные в режиме конструирования, и даже может быть выровнен по центру или помещен в левый верхний угол фрейма. Начальный размер вида можно привести в соответствие с начальным размером фрейма, для этого следует задать свойства ClientHeight и ClientWidth в режиме конструирования. Можно, наоборот, изменять размеры окна фрейма в соответствии с размерами внедренных форм, как это происходит в мастерах и списках свойств (см. раздел «Редакторы моделей» этой главы).

Пока листинг 10.1 находится под рукой, стоит пояснить смысл строк BorderIcons := [] и BorderStyle := bsNone. Они означают, что во время выполнения отображается лишь клиентская область формы вида — на ней нет ни заголовка, ни рамки, которые бы сообщали о том, что фрейм содержит независимую форму. Как видно из рис. 10.3 и 10.4, свойство Caption внедренной формы в режиме выполнения никак не проявляется.

Рис. 10.3. В режиме конструирования вид ничем


не отличается от обычной формы

Рис. 10.4. Во время выполнения вид не похож на форму От внедренных форм к видам

Несомненно, возможность использования форм как элементов — хорошее начало. Теперь мы можем разместить форму там, где считаем нужным, и сделать сколько угодно копий. В объект формы можно включить методы, благодаря которым форма начинает «вести себя» как вид.

Итак, как же ведут себя виды?

Вид должен уметь читать данные от объекта модели и записывать их в этот объект.
Вид должен уметь проверить свою правильность — мастер обычно не разрешает пользователю перейти к следующей странице при наличии неправильных данных на текущей, а список свойств не позволяет сохранить неверные данные. С другой стороны, это требование не является обязательным — например, Memo-поле Примечания может содержать любую информацию или вообще быть пустым.
Вид должен иметь возможность сообщить своему фрейму об изменении свойства Valid, чтобы фрейм мог разрешить или запретить кнопки Next и OK.
Виду может понадобиться такое отображение данных, при котором пользователь не сможет их редактировать. Список свойств может быть доступен только для чтения, если текущему пользователю не разрешается редактировать объект модели или просто потому, что пользователь не заблокировал объект и редактирование может привести к возникновению конфликтов.

Все эти «правила» отражены в листинге 10.2, который представляет собой выдержку из файла MODELS.PAS.

Листинг 10.2. Поведение модели, вида и фрейма

type TModel = TObject; // И IView, и IModelEdit обладают свойством ReadOnly IReadOnly = interface function GetReadOnly: boolean; procedure SetReadOnly(Value: boolean); property ReadOnly: boolean read GetReadOnly write SetReadOnly; end; // Заполнить вид по данным модели и записать изменения обратно; // взаимодействия фрейм/вид IFrame = interface; IView = interface (IReadOnly) procedure ReadFromModel(Model: TModel); procedure WriteToModel(Model: TModel); function GetValid: boolean; procedure SetValid(Value: boolean); property Valid: boolean read GetValid write SetValid; procedure AddNotifiee( Notify: IFrame); procedure RemoveNotifiee(Notify: IFrame); end; IFrame = interface procedure OnValidChanged( ChangingObject: TObject; View: IView ); end; // Мастера и списки свойств являются «редакторами моделей» IModelEdit = interface (IReadOnly) // Процедуры низкого уровня, которые позволяют // приложению один раз подготовить редактор // и несколько раз использовать его. procedure Initialize; function RunEditor(Model: TModel): boolean; procedure Finalize; // Initialize/RunEditor/Finalize function EditModel(Model: TModel): boolean; end;

Наверное, вы догадываетесь, что мы имеем дело с достаточно простой архитектурой. Модели представляют собой абсолютно пассивные контейнеры данных, которые обычно создаются, загружаются и сохраняются в модуле данных. Виды могут читать и записывать модели по требованию, а также сообщать своим фреймам о правильности или неправильности ввода данных. Редакторы моделей (мастера и списки свойств) просто инициализируются и запускаются, а после нажатия пользователем кнопки OK сообщают, были ли внесены какие-либо изменения в модель.

Мы не пытаемся воспроизвести здесь полноценную архитектуру «Модель-Вид-Контроллер», где модель при внесении изменений в нее может приказать виду обновить себя, и т. д. Разумеется, реализация таких возможностей будет не столь уж сложной, но она лишь отвлечет нас от основной темы этой главы — внедренных форм. Кроме того, нашу упрощенную архитектуру «Модель-Вид -Фрейм» нельзя назвать слабой или примитивной. Я с большим успехом применял ее в нескольких проектах.



Содержание раздела