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

Редакторы моделей


Мастера и списки свойств являются редакторами моделей— вы передаете им объект модели, они выполняются и затем возвращают управление. Если пользователь нажал кнопку OK и изменил модель, возвращаемый результат равен True; в противном случае — False. Абстрактные шаблоны из проекта EMBEDDEDFORMS.DPR позволяют создавать реальных мастеров и списки свойств, которые могут совместно использовать объекты моделей и виды. От вас требуется следующее:

создайте новую форму путем наследования от TAbstractWizard или TAbstract PropertySheet;
задайте ее заголовок;
для мастеров — выберите изображение и отрегулируйте ширину графической панели.
напишите небольшую процедуру Initialize, которая поставляет информацию о заголовках страниц и классах вида, как показано в следующем фрагменте файла TESTSHEET.PAS: procedure TPropertySheet.Initialize; begin InitializeSheet( ['Name/Supervisor', 'Birthday', 'Address'], [TEmployeeIdView, TBirthdayView, TAddressView] ); end; // TPropertySheet.Initialize

Абстрактный мастер и абстрактный список свойств делают все остальное; оба автоматически масштабируются, чтобы вместить наибольший вид. Мастер управляется стандартными кнопками Prev/Next/OK; список свойств блокирует кнопку OK при наличии неверных данных на странице за исключением ситуации, при которой хотя бы одна страница была неверной еще до вызова EditModel. В обоих случаях на входе вызывается метод ReadFrom Model для всех видов, а на выходе — метод WriteToModel для всех видов, если пользователь нажал кнопку OK. Список свойств обладает свойством ReadOnly, поэтому вы можете разрешить пользователям просматривать объекты без возможности их изменения. И мастер, и список свойств являются «чисто интерфейсными» объектами, не имеющими public-методов, так что вам не придется беспокоиться о Free или try..finally. Например, в листинге 10.7 приведен фрагмент модуля MAIN.PAS, в котором создаются и запускаются примеры мастера и списка свойств.

Листинг 10.7. Запуск редакторов моделей



procedure TTestForm.EditModel(Editor: IModelEdit; Model: TModel); begin {$ifdef ReadOnly} Editor.ReadOnly := True; {$endif} // ReadOnly if Editor.EditModel(Model) then ShowMessage('OK!') else ShowMessage('Abort ...'); end; // TTestForm.EditModel procedure TTestForm.RunWizard(Sender: TObject); var Employee: TEmployee; begin Employee := DataModel.NewEmployee; try EditModel(TWizard.Create(Self), Employee); finally Employee.Free; end; end; procedure TTestForm.RunSheet(Sender: TObject); var Employee: TEmployee; begin Employee := DataModel.LoadEmployee(3); try EditModel(TPropertySheet.Create(Self), Employee); finally Employee.Free; end; end;

Лично меня в реализации мастера и списка свойств поражает, как просто выглядит такой обобщенный код на Delphi. Ключевым здесь является аргумент-массив array of TViewClass, передаваемый InitializeSheet() и Initialize Wizard() (см. листинг 10.8).

Листинг 10.8. Метод TAbstractPropertySheet.InitializeSheet

// из файла PropertySheets.pas

procedure TAbstractPropertySheet.InitializeSheet( Captions: array of string; Views: array of TViewClass ); var MaxSpan: TSpan; Index: integer; Sheet: TTabSheet; ActualView: TAbstractView; begin Assert( fViews.Count = 0, 'Should only call ' + Name + '.InitializeSheet once' ); Assert( High(Captions) >= Low(Captions), // можно использовать 'Must have at least one tab' ); // Slice() для передачи // пустых массивов Assert( High(Captions) = High(Views), 'Must have same number of Captions as of Views' ); MaxSpan := Point(0, 0); for Index := Low(Captions) to High(Captions) do begin Sheet := TTabSheet.Create(Self); with Sheet do begin PageControl := Self.PageControl; Caption := Captions[Index]; end; // with Sheet ActualView := Views[Index].CreateEmbedded ( Self, Sheet, efmTopLeft ); fViews.Add(ActualView); ActualView.AddNotifiee(Self); MaxSpan := UnionSpan(MaxSpan, ActualView.Span); end; // for Sheet := PageControl.ActivePage; Width := (Width - Sheet.Width) + MaxSpan.X; Height := (Height - Sheet.Height) + MaxSpan.Y; end; // TAbstractPropertySheet.InitializeSheet

Три оператора Assert проверяют, что список свойств еще не настроен, что в нем имеется хотя бы один заголовок и что количество заголовков совпадает с количеством классов вида. Обожаю Assert — лишь после того, как необходимость в конструкции {$IfOpt D+} {$Endif} отпала, я понял, как громоздко она выглядит. Assert проще ввести, он компактен и легко читается.

Габариты (spans) определяются в файле EMBEDDED.PAS. Они представляют собой обычную пару «ширина/высота», то есть BottomRight прямоугольника TRect, у которого Top и Left равны 0:

function TEmbeddedForm.Span: TSpan; begin Result.X := Width; Result.Y := Height; end; // TEmbeddedForm.Span

Функция UnionSpan очень похожа на функцию Windows API UnionRect за исключением того, что она работает с габаритами, а не с прямоугольниками. Присваивая MaxSpan пару (0, 0), мы готовимся к определению минимального прямоугольника, вмещающего все виды из массива Views.

Вся настоящая работа выполняется в цикле при переборе элементов массива Captions. Для каждого элемента массива мы создаем новую вкладку (TTabSheet), размещаем ее на элементе-странице (TPageControl) и задаем текст заголовка. Затем аргумент Views используется для создания нового вида. Мы добавляем новый вид в общий список (Tlist), приказываем ему обращаться к фрейму при каждом изменении Valid и настраиваем MaxSpan.

После того как все виды будут включены в список, мы определяем, сколько места следует выделить «вокруг» MaxSpan для фрейма, заголовка, кнопок и корешков вкладок. Для этого мы вычисляем разность между габаритами формы и габаритами PageControl.ActivePage.

TAbstractWizard выглядит почти так же, но оказывается чуть более сложным, потому что вместо вкладок мы используем три панели: внешнюю панель, панель заголовка (прижатую к верхнему краю — top-aligned) и панель фрейма (заполняющую клиентскую область — client-aligned). При активизации конкретной страницы мы просто переводим на передний план нужную внешнюю панель (листинг 10.9).

Листинг 10.9. Метод TAbstractVizard.SetCurrentPage

// из файла Wizards.pas

property CurrentPage: integer read fCurrentPage write SetCurrentPage; procedure TAbstractWizard.SetCurrentPage (Value: integer); var LastPage, PageIsValid: boolean; begin Assert(TObject(fPanels[Value]) is TPanel); Assert(TObject(fViews[Value]) is TAbstractView); // Сочетание Assert(is) со 'слепыми' преобразованиями типов // обеспечивает отладочную безопасность конструкции "as" // без (особой) потери производительности fCurrentPage := Value; TPanel(fPanels[Value]).BringToFront; LastPage := Value = fPageCount; PageIsValid := TAbstractView(fViews[Value]).Valid; PrevBtn.Enabled := Value > 0; NextBtn.Enabled := PageIsValid and (not LastPage); OkBtn.Enabled := PageIsValid and LastPage; end; // TAbstractWizard.SetCurrentPage

Как видно из листинга 10.9, еще одна приятная особенность Assert заключается в том, что пара «Assert/слепое преобразование типа» обеспечивает полноценную проверку на совместимость типов при отладке, но не отражается на производительности окончательной (поставляемой заказчику) версии. Во всем остальном код несложен: мы задаем fCurrentPage и переводим соответствующую панель на передний план. Затем проверяем, является ли данная страница первой или последней и корректно ли она заполнена (Valid), после чего соответствующим образом задаем состояние кнопок Prev, Next и OK.

Оставшийся код в файлах WIZARDS.PAS и PROPERTYSHEETS.PAS не содержит никаких хитростей. Хотя я буду рад и польщен, если вы сочтете его достойным изучения, для успешного использования в нем совершенно не обязательно разбираться. Поэтому я не буду переводить на него бумагу; если этот код вас действительно заинтересует, найдите его на CD-ROM.



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