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

Создание заставок


Таинственная фигура закрыла дневник и потянулась к телефону. Аппарат с готовностью проглотил семь набранных цифр, а затем выдал серию гудков. Где-то на другом конце линии зазвонил телефон. Раздался щелчок, в трубке послышался уже знакомый нам обворожительный голос, и Мститель заговорил.

— Привет, Крошка… Да, это я. Подумал, что тебе захочется узнать, как прошло дело ночью. Мне удалось вломиться в контору Эйса Брейкпойнта, как и было задумано, и украсть Дневник прямо у него из-под носа. Все прошло почти идеально… А твоя роль в этом дельце была просто бесценной. Без тебя у меня бы ничего не вышло… Что? Да, ждать пришлось долго— но поверь, тем слаще оказалась месть. Верно. Послушай, Крошка, бросай все и встречай меня в 9 часов у мотеля «Гейтс», возле шоссе 101. Точно — прямо на холме, сразу за Нортон Сити. Угу… Сегодня я покажу тебе книгу, которая изменит нашу жизнь и сделает меня самым гениальным программистом в мире. Да, Крошка, меня — Дельфийского Мстителя. Встречаемся в 9 вечера. Не опаздывай. Пока.

Мститель повесил трубку, взял дневник и, громко хихикая, снова принялся перелистывать его.

Дневник №16, 26 марта. Сегодня снова позвонил Торговец. На этот раз он хочет, чтобы я создал компонент-заставку, которым можно будет пользовать ся в разных программах. В общих чертах идея состоит в следующем:
поместить компонент на главную форму приложения, задать значения нескольких свойств и выдать окно заставки перед отображением главной формы.

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

Но как превратить ее в компонент? После пары неудачных попыток я решил, что лучший вариант — создать новый компонент, построенный на
основе TForm, но с добавлением промежуточного объекта-оболочки, управляющего работой TForm. Объект-оболочка может ограничиться управлением свойствами, связанными с объектом-заставкой, что позволит наделить заставку простым пользовательским интерфейсом. Кроме того, оболочка может заниматься созданием и уничтожением формы по простой команде,
выданной владельцем объекта-оболочки. Я решил назвать этот класс TSplashDialog.

В рамках исходной спецификации я решил написать несложное тестовое приложение ы— форму, которая содержит всего одну кнопку и которой будет принадлежать TSplashDialog. Исходный текст тестового приложения приведен в листинге 15.2.

Рис. 15.4. Исходная форма заставки

Листинг 15.2. Тестовое приложение для проверки TSplashDialog



{——————————} {Компонент-заставка } {SPLSHMN.PAS : Главная форма } {Автор: Эйс Брейкпойнт, N.T.P. } {При содействии Дона Тейлора } { } {Простейшая программа, демонстрирующая } использование } {компонента TSplashDialog. Попробуйте задать} другие } {временные задержки, размеры, графические } изображения } {и убедитесь в богатстве возможностей. } { } { Написано для *High Performance Delphi 3 } Programming* } {Copyright (c) 1997 The Coriolis Group, Inc.} { Дата последней редакции 3/5/97 } {————————} unit SplshMn; {$define Test } interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, SplshDlg; type TForm1 = class(TForm) QuitBtn: TButton; procedure QuitBtnClick(Sender: TObject); procedure FormCreate(Sender: TObject); private { Private declarations } {$ifdef Test } SplashDialog1: TSplashDialog; {$endif } public { Public declarations } end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.QuitBtnClick(Sender: TObject); begin Close; end; procedure TForm1.FormCreate(Sender: TObject); begin {$ifdef Test} SplashDialog1 := TSplashDialog.Create (Application); {$endif} SplashDialog1.Execute; end; end.

Здесь есть один момент, на который следует обратить внимание: никогда не тестируйте создаваемый компонент, помещая его в библиотеку Delphi. Ошибки могут привести к неприятным, иногда даже очень неприятным последствиям. Кроме того, обновление библиотеки компонентов — процесс слишком медленный и нудный, чтобы заниматься этим перед каждым запуском программы. Вместо этого следует включить модуль компонента в тестовую программу и затем создать экземпляр компонента программным путем.

Именно это и происходит в данном случае. Использование условной директивы компилятора и константы с именем Test позволяет компилировать эту простую программу в двух режимах. Когда константа определена, условный код активен и в форме объявляется поле типа TSplashDialog с тем же именем (SplashDialog1), которое IDE присваивает компоненту при его помещении на форму. Использование условной проверки в обработчике OnCreate создает экземпляр SplashDialog1. В этом случае программа будет использовать небибли отечный объект TSplashDialog из скомпилированного модуля SplshDlg.

Когда компонент будет закончен и занесен в библиотеку, перед знаком $ в директиве ставится точка. В этом случае $define превращается в обычный комментарий, и программой можно будет пользоваться для тестирования установленной версии компонента.

Как видно из листинга, я решил воспользоваться диалоговым окном с помощью метода Execute — в соответствии с гордыми традициями специализи рованных системных диалоговых окон (например, TOpenDialog).

Сцена для TSplashDialog подготовлена. Теперь следует решить, какие свойства ему необходимы. Программист должен иметь возможность указать размер заставки, хотя я предполагаю, что она всегда будет выводиться в центре экрана. Необходимо передавать информацию о том, есть ли на форме кнопка, и если есть — ее название. Если диалоговое окно должно пропадать
по тайм-ауту, необходимо задать величину задержки. Кроме того, нам понадобится объект TPicture, подключаемый к компоненту TImage. Чтобы работа с
графикой была достаточно гибкой, программист должен иметь возможность задать выравнивание, определить, должен ли компонент TImage автоматически подгоняться под размеры изображения и следует ли растягивать изображе ние до размеров TImage.

Через пару часов у меня появился более или менее готовый компонент. Исходный текст приведен в листинге 15.3.

Листинг 15.3. Исходный текст компонента TSplashDialog

{——————————} { Компонент-заставка } { SPLSHDLG.PAS : Модуль компонента } { Автор: Эйс Брейкпойнт, N.T.P. } { При содействии Дона Тейлора } { } { Модуль описывает специализированный компонент, } { отображающий окно-заставку в тот момент, когда } { программа захочет это сделать (обычно при запуске } { программы). } { } { Написано для *High Performance Delphi 3 Programming* } { Copyright (c) 1997 The Coriolis Group, Inc. } { Дата последней редакции 3/5/97 } {——————————————————————————————————————————————————————} unit SplshDlg; {$define Test } interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls, StdCtrls; type ESplashConflict = class(Exception); TImageAlign = (iaNone, iaTop, iaBottom, iaLeft, iaRight, iaClient, iaAllAboveButton); { TSplashForm - форма, отображаемая на экране. Она содержит TImage, TButton и TTimer, чтобы программист мог гибко использовать заставку. } TSplashForm = class(TForm) CloseBtn: TButton; Image: TImage; DelayTimer: TTimer; procedure CloseBtnClick(Sender: TObject); procedure Button1Click(Sender: TObject); procedure DelayTimerTimer(Sender: TObject); private { Private declarations } public { Public declarations } end; { TSplashDialog - оболочка, окружающая TSplashForm. Форма принадлежит TSplashDialog, поэтому она может "автоматически" создаваться, настраиваться, выполняться и уничтожаться в любой момент. TSplashDialog открывает доступ лишь к тем свойствам, которые используются заставкой, а затем передает их форме TSplashForm при ее создании. } TSplashDialog = class(TComponent) private FAlign : TImageAlign; FAutoSize : Boolean; FButtonCaption : String; FCaption : String; FDelay : Word; FHasButton : Boolean; FHasDelay : Boolean; FHeight : Word; FPicture : TPicture; FStretch : Boolean; FWidth : Word; procedure SetCaption(Value : String); procedure SetDelay(Value : Word); procedure SetHasButton(Value : Boolean); procedure SetHasDelay(Value : Boolean); procedure SetHeight(Value : Word); procedure SetPicture(Value : TPicture); procedure SetWidth(Value : Word); public constructor Create(AOwner : TComponent); override; destructor Destroy; override; function Execute : Boolean; virtual; published property Align : TImageAlign read FAlign write FAlign; property AutoSize : Boolean read FAutoSize write FAutoSize; property ButtonCaption : String read FButtonCaption write FButtonCaption; property Caption : String read FCaption write SetCaption; property Delay : Word read FDelay write SetDelay; property HasButton : Boolean read FHasButton write SetHasButton; property HasDelay : Boolean read FHasDelay write SetHasDelay; property Height : Word read FHeight write SetHeight; property Picture : TPicture read FPicture write SetPicture; property Stretch : Boolean read FStretch write FStretch; property Width : Word read FWidth write SetWidth; end; procedure Register; implementation {$R *.DFM} procedure TSplashDialog.SetCaption(Value : String); begin if Value <> FCaption then FCaption := Value; end; { Задаем значение FHasButton. Если пользователь указал, что в заставке не должно быть ни кнопки, ни таймера, инициируем исключение - без них не удастся очистить экран! } procedure TSplashDialog.SetHasButton(Value : Boolean); begin if not Value and not FHasDelay then raise ESplashConflict.Create('Must have either a button or a delay!') else FHasButton := Value; end; { Задаем значение FHasDelay, защищаясь от аномального случая, описанного выше. } procedure TSplashDialog.SetHasDelay(Value : Boolean); begin if not Value and not FHasButton then raise ESplashConflict.Create('Must have either a button or a delay!') else FHasDelay := Value; end; procedure TSplashDialog.SetHeight(Value : Word); begin if (Value <> FHeight) and (Value > 10) then FHeight := Value; end; procedure TSplashDialog.SetWidth(Value : Word); begin if (Value <> FWidth) and (Value > 20) then FWidth := Value; end; procedure TSplashDialog.SetDelay(Value : Word); begin if (Value <> FDelay) and (Value > 0) then FDelay := Value; end; procedure TSplashDialog.SetPicture(Value : TPicture); begin if Value <> nil then FPicture.Assign (Value); end; constructor TSplashDialog.Create(AOwner : TComponent); begin inherited Create(AOwner); { Задаем значения по умолчанию} FAlign := iaAllAboveButton; FAutoSize := False; FStretch := False; FButtonCaption := 'OK'; FCaption := copy(ClassName, 2, Length(ClassName) - 1); FDelay := 3500; FHasButton := True; FHasDelay := True; FHeight := 200; FWidth := 300; FPicture := TPicture.Create; {$ifdef Test } FPicture.LoadFromFile('splash.bmp'); FAlign := iaClient; FHasDelay := False; {$endif } end; destructor TSplashDialog.Destroy; begin FPicture.Free; inherited Destroy; end; { Самое важное происходит в методе Execute. Он вызывается владельцем TSplashDialog в тот момент, когда необходимо вывести заставку. Execute создает объект SplashForm и изменяет его в соответствии с параметрами, передаваемыми SplashDialog. При закрытии SplashForm уничтожается. } function TSplashDialog.Execute : Boolean; var SplashForm : TSplashForm; begin try SplashForm := TSplashForm.Create(Application); except on E:Exception do begin MessageBeep(MB_ICONERROR); Result := False; Exit; end; end; { try } with SplashForm do begin Position := poScreenCenter; Caption := FCaption; Height := FHeight; Width := FWidth; if FAlign = iaAllAboveButton then begin if FHasButton then begin Image.Align := alTop; Image.Height := ClientHeight - CloseBtn.Height - 15; end else Image.Align := alClient; end else Image.Align := TAlign(Ord(FAlign)); Image.AutoSize := FAutoSize; Image.Stretch := FStretch; if Image.Picture <> nil then Image.Picture.Assign(FPicture); if FHasButton then begin CloseBtn.Caption := FButtonCaption; CloseBtn.Left := (ClientWidth - CloseBtn.Width) div 2; CloseBtn.Top := ClientHeight - CloseBtn.Height - 10; end else CloseBtn.Visible := False; if FHasDelay then begin DelayTimer.Interval := FDelay; DelayTimer.Enabled := True; end; try ShowModal; finally Free; Result := True; end; { try } end; { with } end; procedure TSplashForm.CloseBtnClick(Sender: TObject); begin Close; end; procedure Register; begin RegisterComponents('Ace''s Stuff', [TSplashDialog]); end; procedure TSplashForm.Button1Click(Sender: TObject); begin Close; end; procedure TSplashForm.DelayTimerTimer(Sender: TObject); begin Enabled := False; Close; end; end.

Приведенный фрагмент нуждается в нескольких комментариях. Я снова воспользовался условной директивой, чтобы компонент мог работать в двух режимах. В тестовом режиме (см. листинг 15.3) он автоматически загружает специальный тестовый растр и отключает таймер. Если вставить точку перед знаком $, директива превращается в комментарий, а файл можно будет откомпилировать в виде компонента Delphi и включить его в библиотеку.

Я добавил небольшой фрагмент для предотвращения ситуации, при которой в заставке нет ни кнопки, ни таймера (это означало бы, что модальное диалоговое окно не удастся убрать с экрана!). Кроме того, я объявил перечисляемый тип (TImageAlign), который расширяет возможности типа TAlign, добавляя в него вариант iaAllAboveButton. Он означает, что пользователь желает использовать клиентскую область формы, но лишь ту часть, которая находит ся над кнопкой. Да, чуть не забыл — я также объявил специальный класс
исключения, который обрабатывает все проблемы, обнаруженные в процессе задания свойств.

Самой интересной частью проекта оказался выбор объекта TPicture и помещение его в TImage. Получив несколько системных исключений, связанных с нарушением правил доступа, я начал прочесывать исходные тексты VCL и разыскивать все, что связано с выбором и назначением растровых изображений. Когда ответ был найден, я понял, насколько упростился этот процесс благодаря предусмотрительности разработчиков Delphi. Когда вы объявляе те свойство типа TPicture, Delphi IDE заранее знает, как с ним работать. Вы создаете экземпляр Tpicture в конструкторе объекта, а IDE вызывает Picture Editor для редактирования этого свойства. После того как в Picture Editor будет выбрано растровое изображение, оно автоматически сохраняется в потоке при закрытии файла формы. Это означает, что при следующем открытии файла растр окажется в нужном месте.

В полном соответствии с целями проектирования оболочка TSplashDialog управляет важнейшими свойствами формы. При вызове метода Execute объект TSplashDialog создает экземпляр формы, задает значения ее свойств и затем вызывает ShowModal, чтобы приостановить все прочие действия программы. Когда выполнение программы возобновляется, форма уничтожается. Тестовый вариант заставки изображен на рис. 15.5.

Рис. 15.5. Заставка во время выполнения программы



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