Создание заставок
Таинственная фигура закрыла дневник и потянулась к телефону. Аппарат с готовностью проглотил семь набранных цифр, а затем выдал серию гудков. Где-то на другом конце линии зазвонил телефон. Раздался щелчок, в трубке послышался уже знакомый нам обворожительный голос, и Мститель заговорил.
— Привет, Крошка… Да, это я. Подумал, что тебе захочется узнать, как прошло дело ночью. Мне удалось вломиться в контору Эйса Брейкпойнта, как и было задумано, и украсть Дневник прямо у него из-под носа. Все прошло почти идеально… А твоя роль в этом дельце была просто бесценной. Без тебя у меня бы ничего не вышло… Что? Да, ждать пришлось долго— но поверь, тем слаще оказалась месть. Верно. Послушай, Крошка, бросай все и встречай меня в 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. Заставка во время выполнения программы