По просьбе читателя моего блога в данной статье подробно рассматривается алгоритм отображения прогресса запуска приложения.

В статье “Пограничная заставка” я рассказал о том, как создать форму-заставку, которая должна появляться раньше основной формы и оставаться какое-то время на экране. А в это самое время программа выполняет действия, связанные с подготовкой к работе: инициализирует переменные и массивы, загружает графические ресурсы – одним словом, готовит приложение к работе. Особенно это востребовано для сложных приложений, в которых формы создаются или модифицируются с помощью скриптов. Также этот прием используется в проектах, которые используют СУБД MySQL, так как появляется возможность выполнить инициализацию компонентов до запуска основой формы.

Форма-заставка создается скриптами. Она содержит фоновое изображение (1), название проекта с тенью (2), номер версии (3) и строку прогресса запуска (4).

Splash.pas

В проекте “Справочник разработчика” применяется модульная структура – скрипты, необходимые для выполнения определенного класса задач, хранятся в отдельных файлах – модулях. В модуле splash.pas находятся функция создания формы-заставки Splash_Create() и процедура отображения формы с информацией о прогрессе загрузки Splash_ShowProgress(), а также константы, определяющие работу формы-заставки.

Модуль Splash.pas ссылается на другие модули: AppConstVar.pas, Label.pas и License.pas, которые находятся в папке Script\VClass. То есть для использования данного модуля в вашем проекте также должны присутствовать связанные с ним модули. Кроме явных ссылок, имеются неявные зависимости от других модулей, таких как System\Utils.pas. Все исходные файлы проекта можно найти в описании программы.

Ниже приводится содержание модуля с подробными комментариями и указаниями на связанные модули

// Заставка
// 05.05.2023
// function Splash_Create:TForm; - создание формы заставки
// procedure Splash_ShowProgress( AText: string ); - отобразить прогресс загрузки

uses 'VClass\AppConstVar.pas', 'VClass\Label.pas', 'VClass\License.pas';

const
  SPLASH_FORM_NAME = 'frmSplash'; // имя формы
  SPLASH_DELAY_MIN = 2000; // минимальное время отображения заставки;
  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; // цвет шрифта для версии

procedure Splash_ShowProgress( AText: string );
// отобразить прогресс загрузки
var
  tmpLabel: TLabel;
  tmpForm: TForm;
begin
  // находим форму заставки по имени; такой поиск позволяет обходиться без глобальной переменной
  tmpForm := GetFormByName(SPLASH_FORM_NAME); // {System\Utils.pas}
  // находим на форме компонент-метку; поиск также осуществляется по имени компонента
  FindC(tmpForm,'labProgress',tmpLabel); // {System\Utils.pas}
  // прописываем текст прогресса в заголовок метки; пробел в конце добавляется для эстетики, 
  // так как содержимое метки выравнивается по правому краю.
  tmpLabel.Caption := AText + '  ';
  // вызываем метод, который позволяет обработать все системные сообщения;
  // это необходимо для прорисовки нового содержимого заголовка метки
  Application.ProcessMessages;
end;

function Splash_Create: TForm;
// создание формы заставки
var
  tmpImage: TImage;
  tmpLabel: TLabel;
  tmpImageFileName: string;
begin
  Result := TForm.Create(Application);
  with Result do
  begin
    Name := SPLASH_FORM_NAME;
    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;
    // название приложения берём из константы APP_NAME
    Caption := APP_NAME; // {VClass\AppConstVar.pas}
    Font.Size := SPLASH_FONT_SIZE;
    Font.Color := SPLASH_FONT_COLOR
  end;
  // добавить тень
  Label_AddShadow(tmpLabel, SPLASH_SHADOW_COLOR); // {VClass\Label.pas}
  // добавить номер версии
  tmpLabel := TLabel.Create(Result);
  with tmpLabel do
  begin
    Name := 'AppVer';
    Parent := Result;
    // номер версии связан с системой лицензирования; кроме номера может содержать признак демо-версии
    Caption := License_GetVersion; // {VClass\License.pas}
    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); // {VClass\Label.pas} 
  // добавить строку прогресса
  tmpLabel := TLabel.Create(Result);
  with tmpLabel do
  begin
    Name := 'labProgress';
    Parent := Result;
    Caption := '';
    StyleElements := 0;
    Font.Size := SPLASH_VER_FONT_SIZE;
    Font.Color := SPLASH_VER_FONT_COLOR;
    Align := alBottom;
    Alignment := taRightJustify;
    Layout := tlTop;
    Autosize := False;
    Height := Height + Height div 2;
  end;
end;

begin
end.Code language: Delphi (delphi)

Использование

Хронология запуска приложения, созданного в My Visual Database выглядит так:

#ПроцессВремя
1Загрузка приложения (*.exe) в памятьНеизменно. Зависит от производительности компьютера
2Загрузка графических ресурсов (graphics.dll) в памятьЗависит от количества статических изображений в проекте – размерафайла graphics.dll
3Создание всех форм, созданных в конструкторе форм (forms.xml)Зависит от количества и сложности форм проекта – размера файла forms.xml.
4Инициализация скриптовВыполнение кода, написанного в секции begin end. модуля script.pas и всех подключенных модулей
5Подключение к базе данныхЗависит от количества компонент TdbComboBox.
6Отображение формы аутентификации и авторизации (опционально)Форма отображается, если задействована штатная система настройки прав доступа.
7Отображение главной формы

Таким образом, заставку можно отобразить либо на этапе 4, либо на этапе 7. В нижеприведенном примере заставка отображается на этапе отображения главной формы, в обработчике события OnShow.

В процедуре frmMain_OnShow много условностей, связанных с работой приложения, но вам нужно обратить внимание на последовательность вызовов функции Splash_Create() и метода Splash_ShowProgress().

procedure frmMain_OnShow(Sender: TObject; Action: string);
// отображение главной формы
var
  tmpForm: TForm;
  tmpStartTime: TTime;
  tmpSleepTime: integer;
begin
  //
  tmpForm := Splash_Create;
  tmpForm.Show;
  Application.ProcessMessages;
  tmpStartTime := Time();
  //
  // тут должна быть всяческая инициализация, которая может занять продолжительное время
  App_InitSystemVar; // инициализация системных переменных
  Resource_Init();
  Resource_CreateMenu(frmMain);
  Images_Init; // инициализация подcистемы загрузки картинок
  Style_Init; // активация стиля
  Style_AddToMenu(frmMain);
  UserApp_InitForm; // создать формы
  UserApp_InitVar; // настроить переменные
  CodeHL_Init; // настройка подсветки кода
  // инициализация, которая идет после настройки переменных
  CE_Init(MainForm); // главное меню находится на главной форме, но отображается там, где нужно
  Hotkey_Init(MainForm); // подключить горячие клавиши
  UserApp_InitBase; // инициализация базы данных
  //
  // гарантированная задержка, но не более указанной в APP_SPLASH_DELAY
  tmpSleepTime := SPLASH_DELAY_MIN - Trunc((Time() - tmpStartTime) * 24 * 60 * 60 * 1000);
  if tmpSleepTime > 0 then
    Sleep(tmpSleepTime);
  tmpForm.Close;
  tmpForm.Free;
  //
  // отобразить в заголовке главной формы название и версию программы
  frmMain.Caption := R('APP_NAME', APP_NAME) + ' ' + License_GetVersion;
  frmMain.pgcMain.ActivePage := frmMain.tshNavigation; // открыть вкладку "Навигация"
  frmMain.pgcNavigation.ActivePage := frmMain.tshTask; // открыть вкладку "Задачи"
end;

procedure UserApp_InitForm;
// инициализация форм
var
  tmpItem: TMenuItem;
begin
  try
    Splash_ShowProgress('Инициализация форм...' );
    // App_SetDoubleBuffer;
    UserApp_AddFrame;
    // добавляем сплиттеры и выравнивание
    Splitter_Create(frmMain.panUnit, frmMain.pgcUnitProc, alTop); //
    Splitter_Create(frmMain.panProject, frmMain.pgcProject, alLeft); //
    Splitter_Create(frmMain.panClass, frmMain.pgcClass, alLeft); // классы
    Splitter_Create(frmMain.panType, frmMain.pgcType, alLeft); // типы
    Splitter_Create(frmMain.panFunction, frmMain.pgcFunction, alLeft); // функции
    Splitter_Create(frmMain.pgcMethodParamView, frmMain.panMethod, alBottom); // методы
    Splash_ShowProgress('Создание форм...' );
    DTF_CreateForms; // загрузить формы из файла настроек форм
    Splash_ShowProgress('Подготовка...' );
    frmMain.panProjectToolbar.Align := alBottom;
    // статические формы
    Form_ShowOnWinControl(frmSearchResult, frmMain.tshSearchResult);
    Form_ShowOnWinControl(frmExampleView, frmMain.tshExample);
    //
    UserApp_InitAboutForm;
  except
    RaiseException('UserApp_InitForm() ' + ExceptionMessage);
  end;
end;

procedure DTF_CreateForms;
// создание динамических форм по списку, находящемуся в файле dforms.ini
var
  tmpIniFile: TIniFile;
  tmpFormList: TStringList;
  i: integer;
  tmpForm: TForm;
  tmpSName: string;

  function S(AName: string): string;
  begin
    Result := tmpIniFile.ReadString(tmpFormList.Strings(i), AName, '');
  end;

begin
  try
    tmpIniFile := TIniFile.Create(ExtractFilePath(Application.ExeName) + DTF_FORMS_INI_FILE);
    tmpFormList := TStringList.Create;
    tmpIniFile.ReadSections(tmpFormList);
    for i := 0 to tmpFormList.count - 1 do
    begin
      Splash_ShowProgress('Создание формы для таблицы '+ S('table') );
      tmpSName := 'DTF_' + UpperCase(tmpFormList.Strings(i)) + '_CAPTIONS'; // формат ресурсов
      tmpForm := DTF_Create(tmpFormList.Strings(i), S('table'), S('fields'), R(tmpSName, S('captions')), S('sort'),
        S('parentField'), S('masterField'), S('filter'), S('isDetail') = 'True');
      Form_ShowOnWinControl(tmpForm, TWinControl(FindCF(S('parentControl'))));
    end;
    tmpFormList.Free;
    tmpIniFile.Free;
  except
    RaiseException('DTF_CreateForms() - ' + ExceptionMessage);
  end;
end;

Code language: Delphi (delphi)

В строке 9 создается форма заставки, а затем делается видимой (строка 10). Далее выполняются различные действия, и там, где задержки реально большие, используются вызовы процедуры Splash_ShowProgress(): строки 48, 58, 60 и 93. После того, как инициализация завершена, форма заставки уничтожается (строки 33-34).

Время отображения

Время отображения заставки задается константой SPLASH_DELAY_MIN. Это – минимальное гарантированное время отображения в миллисекундах. Разумеется, если фактическое время инициализации будет больше заявленного, заставка будет отображаться до завершения инициализации.

В строке 12 запоминается время начала отображения заставки, а после инициализации сверяется с текущим и при необходимости делается дополнительная задержка (строки 30-32), чтобы обеспечить заданное время отображения заставки.

3 комментария к «Прогресс запуска»
    1. А я благодарю вас за обратную связь, которая позволяет мне писать более понятные статьи 🙂

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

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