Для запуска приложения порой достаточно одного клика. Но иногда время запуска оказывается больше ожидаемых одной-двух секунд, и тогда у пользователя может возникнуть ощущение, что программа зависла и что-то пошло не так. Избежать этого можно, если сократить время до появления первого окна, которое называют заставкой (англ. splash screen).

Для этого все действия по подготовке программы к работе выполняются после отображения заставки. А сама заставка становится границей, отделяющей серые будни пользователя Windows от фантастического мира вашего приложения.

Визуальная часть

Заставка – это визитная карточка приложения. Единых правил по её оформлению нет и быть не может. И если при проектировании дизайна основного приложения необходимо придерживаться определенных правил и соглашений, то при отображении заставки можно себя не сдерживать и позволить себе проявление своих творческих дарований в полной мере: используйте яркие палитры или черно-белые скетчи, добавьте анимацию или сделайте заставку полупрозрачной, откажитесь от прямоугольной формы. Но, если вы привыкли к сдержанности и экономному использованию ресурсов, я могу предложить решение, которое для своей реализации использует скрипты и файл изображения.

В качестве фонового изображения используется картинка, которая находится в папке Images\Logo, поверх фона отображается название приложения. Название имеет черную тень, которая улучшает читабельность текста. В нижнем левом углу выводится номер версии программы. Так как время инициализации приложения зависит от производительности компьютера, то желательно отображать заставку в течение указанного в настройке времени, но не более чем.

Скрипты

Для создания формы-заставки используется функция Splash_Create(). Она возвращает созданную форму в качестве результата. Параметры создания вынесены в константы.

const
  SPLASH_LOGO_WIDTH = 350; // Ширина изображения
  SPLASH_LOGO_HEIGHT = 169; // Высота изображения
  SPLASH_BORDER = 2; // толщина рамки вокруг изображения
  SPLASH_BORDER_COLOR = clBlack; // цвет рамки
  SPLASH_FILE_NAME = 'Images\Logo\logo_350x169.jpg'; // файл фонового изображения
  SPLASH_FONT_SIZE = 28; // размер шрифта для названия
  SPLASH_FONT_COLOR = clWhite; // цвет для названия
  SPLASH_SHADOW_COLOR = clBlack; // цвет тени
  SPLASH_VER_FONT_SIZE = 11; // размер шрифта для версии
  SPLASH_VER_FONT_COLOR = clWhite; // цвет шрифта для версии
  SPLASH_DELAY_MIN = 2000; // минимальное время отображения заставки;
  // другие константы проекта
  LABEL_SHADOW_SHIFT_DEF = 2; // сдвиг тени по умолчанию
  SX_SHADOW = '_Shadow'; // тень
  APP_NAME = 'Заставка'; // название проекта
  APP_VERSION = '1.0'; // Версия проекта

function Splash_Create:TForm;
var
  tmpImage:TImage;
  tmpLabel:TLabel;
  tmpImageFileName:string;
begin
  Result := TForm.Create(Application);
  with Result do
  begin
    Width := SPLASH_LOGO_WIDTH + SPLASH_BORDER*2;
    Height := SPLASH_LOGO_HEIGHT + SPLASH_BORDER*2;
    BorderStyle := bsNone;
    Position := poScreenCenter;
    Color := SPLASH_BORDER_COLOR;
    StyleElements := 0;
  end;
  // добавить картинку
  tmpImage := TImage.Create(Result);
  with tmpImage do
  begin
    Parent := Result;
    Width := SPLASH_LOGO_WIDTH;
    Height := SPLASH_LOGO_HEIGHT;
    Top := SPLASH_BORDER;
    Left := SPLASH_BORDER;
    tmpImageFileName := ExtractFilePath(Application.ExeName)+SPLASH_FILE_NAME;
    if not FileExists(tmpImageFileName) then
      RaiseException('Splash_Create() Не найден файл '+tmpImageFileName)
    else
     Picture.LoadFromFile(tmpImageFileName);
  end;
  // добавить название
  tmpLabel := TLabel.Create(Result);
  with tmpLabel do
  begin
    Name := 'AppName';
    Parent := Result;
    Top := 0;
    Left := 0;
    Autosize := False;
    Width := Result.ClientWidth;
    Height := Result.ClientHeight;
    Alignment := taCenter;
    Layout := tlCenter;
    WordWrap := True;
    StyleElements := 0;
    Caption := APP_NAME;
    Font.Size := SPLASH_FONT_SIZE;
    Font.Color := SPLASH_FONT_COLOR
  end;
  Label_AddShadow( tmpLabel, SPLASH_SHADOW_COLOR ); // добавить тень
  // добавить номер версии
  tmpLabel := TLabel.Create(Result);
  with tmpLabel do
  begin
    Name := 'AppVer';
    Parent := Result;
    Caption := APP_VERSION;
    StyleElements := 0;
    Font.Size := SPLASH_VER_FONT_SIZE;
    Font.Color := SPLASH_VER_FONT_COLOR;
    Left := Height div 2;
    Top := Result.ClientHeight - Height - (Height div 2);
  end;
  Label_AddShadow( tmpLabel, SPLASH_SHADOW_COLOR, 1 ); // добавить тень
end;

Code language: Delphi (delphi)

Давайте посмотрим на него внимательно. В шестьдесят третьей строке вызывается процедура Label_AddShadow(), которая добавляет тень для указанного текста.

Тень – это точная копия компонента-метки, обычно имеющая черный цвет текста (любо заданный цвет, темнее основного). Она располагается с небольшим смещением позади заданной метки. По непонятным причинам в MVDB метод Assign() для компонента TLabel не поддерживается, поэтому для получения копии пришлось писать кучу кода:

procedure Label_AddShadow( ALabel:TLabel; AColor:TColor = 0; AShift: integer = 0 );
var
  tmpLabel:TLabel;
begin
  if AShift = 0 then // если сдвиг тени не указан, то берем значение по умолчанию
    AShift := LABEL_SHADOW_SHIFT_DEF;
  tmpLabel := TLabel.Create( ALabel.Owner );
  // tmpLabel.Assign( ALabel ); // К сожалению, в MVDB данный метод не работает :(, поэтому прийдется скопировать все свойства вручную....
  // все, что ниже, можно заменить одной командой Assign, но в MVD она не работает...
  tmpLabel.Parent := ALabel.Parent;
  tmpLabel.Name := ALabel.Name + SX_SHADOW;
  tmpLabel.Caption := ALabel.Caption;
  tmpLabel.AutoSize := ALabel.AutoSize;
  tmpLabel.Width := ALabel.Width;
  tmpLabel.Height := ALabel.Height;
  tmpLabel.Anchors := ALabel.Anchors;
  tmpLabel.Font := ALabel.Font;
  tmpLabel.StyleElements := ALabel.StyleElements;
  tmpLabel.Alignment := ALabel.Alignment;
  tmpLabel.Layout := ALabel.Layout;
  tmpLabel.WordWrap := ALabel.WordWrap;
  // задать смещение тени
  tmpLabel.Top := ALabel.Top + AShift;
  tmpLabel.Left := ALabel.Left + AShift;
  // задать цвет тени
  tmpLabel.Font.Color := AColor;
  ALabel.BringToFront; // поместить главную надпись перед тенью
end;
Code language: Delphi (delphi)

Инициализация и запуск

Перед тем, как главная форма приложения frmMain (её название на закладке визуального конструктора отмечается красным цветом) отобразится, происходит событие OnShow, которое мы будем использовать для создания и отображения заставки, а также выполнения действий по инициализации приложения.

procedure frmMain_OnShow (Sender: TObject; Action: string);
// отображение главной формы
begin
  // отобразить заставку
  App_Splash_Start;
end;Code language: Delphi (delphi)

Для инициализации приложения с отображением заставки вызывается процедура App_Splash_Start(), в которой создается и отображается форма – заставка, а затем вызывается процедура инициализации приложения App_Start(). После её завершения производится замер затраченного на инициализацию время и, если оно не превышает заданного, то добавляется задержка ровно на столько, чтобы суммарное время отображения заставки соответствовало заданному в константе SPLASH_DELAY_MIN. После чего заставка уничтожается, затем отображается основная форма frmMain.

procedure App_Splash_Start;
// инициализация с заставкой
var
  tmpForm:TForm;
  tmpStartTime: TTime;
  tmpSleepTime: integer;
begin
  tmpForm := Splash_Create;
  tmpForm.Show;
  Application.ProcessMessages;
  tmpStartTime := Time();
  App_Start;
  // гарантированная задержка, но не более указанной в SPLASH_DELAY_MIN
  tmpSleepTime := SPLASH_DELAY_MIN - Trunc( (Time() - tmpStartTime)*24*60*60*1000 );
  if tmpSleepTime > 0 then
    Sleep(tmpSleepTime);
  tmpForm.Close;
  tmpForm.Free;
end;Code language: Delphi (delphi)

В App_Start() происходит инициализация подсистем приложения: управление картинками кнопок, стилями, горячими клавишами и т.д..

procedure App_Start;
// запуск приложения
begin
  // тут должна быть всяческая инициализация, которая может занять продолжительное время
end;Code language: Delphi (delphi)

Таким образом, в базовом комплекте будут две процедуры инициализации: с отображением заставки и без её отображения. Это позволит гибко подходить к выбору оптимального решения. Для лёгких приложений можно вызывать App_Start(), а если инициализация долгая, то лучше использовать App_Splash_Start();

Если вы хотите использовать статическую заставку, созданную в визуальном конструкторе My Visual Database, то откорректируйте App_Splash_Start(), вызывая в ней вашу статическую форму frmSplash:

procedure App_Splash_Start;
// инициализация с заставкой
var
  tmpStartTime: TTime;
  tmpSleepTime: integer;
begin
  frmSplash.Show;
  Application.ProcessMessages;
  tmpStartTime := Time();
  App_Start;
  // гарантированная задержка, но не более указанной в SPLASH_DELAY_MIN
  tmpSleepTime := SPLASH_DELAY_MIN - Trunc( (Time() - tmpStartTime)*24*60*60*1000 );
  if tmpSleepTime > 0 then
    Sleep(tmpSleepTime);
  frmSplash.Hide;
end;Code language: Delphi (delphi)

Ссылки

Послесловие

К сожалению, предложенное решение не является эффективным на 100%, и в некоторых случаях задержка между кликом по иконке запуска приложения в проводнике операционной системы и появлением заставки может составлять несколько секунд. Почему так происходит? Для ответа на этот вопрос нужно разобрать последовательность событий и тайминги.

  1. Клик по иконке приложения в проводнике
  2. Загрузка приложения в память
  3. Создание служебных форм приложения
  4. Считывание файла forms.xml и создание пользовательских форм приложения
  5. Запуск секции begin end. в файле script.pas и других модулях.
  6. Если используется встроенная система прав, то отображается встроенная форма авторизации
  7. Соединение с базой данных.
  8. Подключение данных к визуальным компонентам.
  9. Отображение главной формы приложения

Несмотря на солидный размер исполняемого файла (19Мб), основное время тратится не на его загрузку в память, а на создание пользовательских форм (п.4) и подключение данных к визуальным компонентам (п.8). Переместив создание и отображение формы заставки в п.5, можно исключить эту задержку, но справиться с п.4 не представляется возможным. А при большом числе форм и компонентов на формах время создания может исчисляться несколькими секундами, что я наблюдаю в некоторых своих проектах.

Мне видится три пути сокращения времени задержки отображения заставки.

Отдельное приложение

Простое решение, для реализации которого потребуется две папки: папка основного проекта и папка проекта запуска (стартер). Несмотря на громоздкость (решение будет включать два одинаковых исполняемых файла по 19Мб каждый плюс кучу служебных файлов для каждого проекта), оно вполне эффективно справляется с задачей: приложение стартер содержит только одну форму (заставку) запускается быстро и запускает основное приложение. А после обнаружения отображения формы основного приложения самоуничтожается.

Динамические формы

Сложное с точки зрения реализации и развития проекта решение, которое предполагает программное создание форм, что снижает эффективность визуальной среды разработки, превращая её в не визуальную. На практике можно комбинировать визуальное и программное создание формы и компонентов, реализуя повторяющиеся стандартные решения с помощью скрипта.

Доработка My Visual Database

Для этого в MVDB нужно добавить возможность написания обработчика на событие, возникающее до создания пользовательских форм. Но в настоящий момент развитие проекта приостановлено на неопределённый срок

3 комментария к «Пограничная заставка»
  1. Здравствуйте. В справочнике разработчика реализован прогресс загрузки. Я не совсем понимаю как он работает и много раз смотрел исходный код, который есть в справочнике разработчика, но я так и не понял. Можно пожалуйста обновить эту тему и рассказать как это сделать?

      1. Супер, спасибо большое. Благодаря вашему блогу я учусь и узнаю много нового.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *