Эффект бабочки — незначительное влияние на систему может иметь большие и непредсказуемые последствия, в том числе в совершенно другом месте.
Завершив работу над первой версией программы “ClassExplorer”, я решил немного усовершенствовать её: разнести программный код по отдельным модулям, а также добавить механизм управления стилями, который был описан в статье “Хамелеон”. Но эти казалось бы небольшие изменения привели к появлению улучшенной технологии загрузки изображений для кнопок и создали кучу хлопот по настройке связей между модулями.
Модули
Как вам, наверное, уже известно, My Visual Database позволяет размещать исходные тексты скриптов в нескольких файлах, что очень удобно, если число строк кода превышает пару тысяч. Для того, чтобы подключить модуль (файл с расширением .pas) к основному коду, необходимо указать его в команде uses:
uses // уопрядочивать по степени зависимости: сначала те, которые ни на кого не ссылаются, затем зависимые 'ConstVar.pas', // глобавльные константы и переменные приложения 'System\Utils.pas', // системные процедуры 'Tools\Tools.pas', // инструменты 'VClass\VClass.pas', // виртуальные классы и расширения 'Forms\Forms.pas', // формы 'UserApp.pas'; // общие процедуры и функции приложения
Обязательным условием является нахождение файла модуля внутри папки Script, также допускается вложенное хранение файлов модулей в подпапках.

// перечень подключенных форм uses 'Forms\ClassType.pas', 'Forms\Event.pas', 'Forms\Example.pas', 'Forms\FuncProc.pas', 'Forms\FuncProcParam.pas', 'Forms\FunctionParam.pas', 'Forms\Main.pas', 'Forms\Method.pas', 'Forms\MethodParamList.pas', 'Forms\Property.pas', 'Forms\SearchResult.pas', 'Forms\Task.pas', 'Forms\TypeConst.pas'; begin end.
К сожалению, доподлинно неизвестно, как именно происходит связывание модулей, так как нет никаких ограничений на перекрёстные ссылки (как в Delphi). Иногда достаточно, чтобы используемый модуль присутствовал хотя бы в одной команде uses, а иногда приходится добавлять ссылки по зависимостям: если в модуле А вызывается процедура из модуля B, то в модуле А нужно прописать: uses “B.pas”;
Ещё один важный момент состоит в том, чтобы в каждом модуле имелся блок основной программы:
begin end.
Если вы про него забудете или в конце вместо точки напишите точку с запятой, то при сборке проекта вы будете получать самые невероятные сообщения об ошибке, но без всякого намёка на данную оплошность. Пожалуй, тут весьма явно наблюдается эффект бабочки: маленькая точка может испортить настроение на целый день, а то и больше.
Картинки на кнопках
Использование стилей выявило потребность менять стандартные картинки на кнопках, так как они не всегда сочетаются со цветовой гаммой, предлагаемой выбранным стилем оформления приложения.
В статье “Семейный альбом” подробно рассматривается механизм загрузки изображений для кнопок, который активируется при запуске приложения и не требует дополнительного кода, если вы решили добавить ещё несколько кнопок. Достаточно поместить в папку нужные файлы в формате PNG, а механизм заменить изображения, используя соглашение по наименованию кнопок.
Но одна только загрузка не решает проблему, так как цвет картинок на кнопках должен сочетаться с фоном самих кнопок. И это проблема, решить которую элегантным способом не получится: в MVDB нет инструментов редактирования изображений в формате PNG, а изображения в формате BMP выглядят топорно из-за отсутствия полутонов по краю изображения, которое дает управление прозрачностью. Поэтому проблему пришлось решать не качеством, а количеством: если изображения кнопок не подходят, то для такой темы создается отдельная папка с изображениями. У меня получилась такая вот структура папок

procedure Images_Init; // инициализация данных var i: integer; begin // инициализация массивов данных SetLength(ImagesDir,IMAGES_FORMAT_COUNT); SetLength(ImagesSize,IMAGES_FORMAT_COUNT); SetLength(ImagesList,IMAGES_FORMAT_COUNT); SetLength(ImagesNames,IMAGES_FORMAT_COUNT); ImagesDir[IMAGES_FORMAT_BUTTON] := IMAGES_DIR + 'Buttons\'; // магия стиля Images_AddStyle(ImagesDir[IMAGES_FORMAT_BUTTON]); // размер ImagesSize[IMAGES_FORMAT_BUTTON] := 24; // загрузка данных for i := 0 to IMAGES_FORMAT_COUNT-1 do begin ImagesNames[i] := TStringList.Create; ImagesList[i] := TImageList.Create(frmMain); Images_Load(i); // загрузить изображения end; Images_ButtonsAssign( IMAGES_FORMAT_BUTTON ); end; procedure Images_AddStyle(var ADir:string;); // добавление "стильности" изображениям var tmpDir:string; begin tmpDir := ADir + Style_GetStyle() + '\'; if DirectoryExists(tmpDir) then ADir := tmpDir; end;
Работает это так: если для выбранного стиля существует вложенная папка с таким же названием, то изображения будут грузиться из неё, если нет – то из основной папки.
Стили
Система стилей была доработана таким образом, чтобы её легко можно было использовать в различных приложениях: добавлена процедура Style_SetStyle( AStyleName: string ); – переключение стиля.
procedure Style_SetStyle( AStyleName: string ); // переключение стиля var tmpCurFile : string; tmpNewFile : string; begin currentStyleName := AStyleName; IniFile_Write( STYLE_INI_SECTION, STYLE_INI_CUR_STYLE, currentStyleName ); tmpCurFile := ExtractFilePath(Application.ExeName)+STYLE_FILE_NAME; tmpNewFile := ExtractFilePath(Application.ExeName)+STYLE_DIR+currentStyleName+STYLE_FILE_EXT; // удаляем текущий стиль DeleteFile( tmpCurFile ); // копировать файл со стилем if FileExists(tmpNewFile) then CopyFile(tmpNewFile, tmpCurFile); // предупреждение if MessageBox('Для применения изменений требуется перезапуск программы. Выполнить сейчас?','Смена стиля ',MB_OKCANCEL ) = mrOK then begin frmMain.Close; // закрытие текущего приложения. На самом деле эта команда только создаёт сообщение, фактически закрытие произойдет после завершения текущей процедуры. OpenFile( Application.ExeName ); // запуск приложения end; end;
А процедура инициализации сделана таким образом, чтобы автоматически формировать список доступных для приложения стилей по наличию файлов .VSF
procedure Style_Init; // инициализация var i: integer; s: string; tmpFileList: array of string; begin s := GetFilesList ( ExtractFilePath(Application.ExeName)+STYLE_DIR ); if s <> '' then begin tmpFileList := SplitString(s, CR ); // инициализщация списка стилей SetLength( arStyles, 1 ); arStyles[0] := 'По умолчанию'; for i:=0 to Length(tmpFileList)-1 do begin s := Trim(ExtractFileName( tmpFileList[i] )); if s<>'' then begin delete(s,length(s)-3,4); SetLength( arStyles, i+2 ); arStyles[i+1] := s; // записываем название файла без расширения end; end; // текущий стиль - прочитать из настройки currentStyleName := IniFile_Read( STYLE_INI_SECTION, STYLE_INI_CUR_STYLE, arStyles[0] ); end; end;
В результате получилось приложение, которое поддерживает все стили, для меня остаются загадкой любители темных тем в оформлении интерфейса программ. Видимо из-за того, что я перестал работать по ночам 🙂



О программе
И на последок немного магии, которая позволяет использовать встроенную форму “О программе” для своих личных целей: разместить информацию о версии программы, авторском праве и небольшую статистику по содержимому базы данных, чтобы было понятно, что внутри.

procedure UserApp_InitAboutForm; // настройка формы "О программе" var tmpLabel:TLabel; tmpLabel2:TLabel; i: integer; begin frmdbCoreAbout.Caption := 'О программе'; tmpLabel:=TLabel( frmdbCoreAbout.FindComponent('LinkLabel1') ); tmpLabel.Caption := frmMain.Caption; // 'My Visual Database Class Explorer'; tmpLabel.Font.Size := 14; tmpLabel.Font.Style := fsBold; tmpLabel2 := TLabel.Create( frmdbCoreAbout ); tmpLabel2.Name := 'labVersion'; tmpLabel2.Parent := tmpLabel.Parent; tmpLabel2.Left := tmpLabel.Left; tmpLabel2.Top := tmpLabel.Top + 30; tmpLabel2.Caption := 'Версия 1.1'; tmpLabel2.Font.Size := 11; tmpLabel := TLabel( frmdbCoreAbout.FindComponent('Label2') ); tmpLabel.Caption := 'Copyright '+chr($A9)+' 2022 Константин Паньков'; tmpLabel.Top := tmpLabel.Top + 50; tmpLabel.Font.Size := 11; tmpLabel2 := TLabel.Create( frmdbCoreAbout ); tmpLabel2.Name := 'labLink'; tmpLabel2.Parent := tmpLabel.Parent; tmpLabel2.Left := tmpLabel.Left; tmpLabel2.Top := tmpLabel.Top + 24; tmpLabel2.Font := tmpLabel.Font; Label_LinkCreate( tmpLabel2, 'https://k245.ru'); tmpLabel2 := TLabel.Create( frmdbCoreAbout ); tmpLabel2.Name := 'labDatabaseDate'; tmpLabel2.Parent := tmpLabel.Parent; tmpLabel2.Left := tmpLabel.Left; tmpLabel2.Top := tmpLabel.Top + 224; tmpLabel2.Font := tmpLabel.Font; tmpLabel2.Hint := App_GetDBFileName(True); tmpLabel2.ShowHint := True; for i:=1 to 4 do begin tmpLabel2 := TLabel.Create( frmdbCoreAbout ); tmpLabel2.Name := 'labStat_'+IntToStr(i); tmpLabel2.Parent := tmpLabel.Parent; tmpLabel2.Left := tmpLabel.Left; tmpLabel2.Top := tmpLabel.Top + 60+ i*20; tmpLabel2.Font := tmpLabel.Font; end; TAForm(frmdbCoreAbout).OnShow := 'UserApp_UpdateStatistic'; end; procedure UserApp_UpdateStatistic(Sender: TObject; Action: string); var tmpLabel:TLabel; begin // сведения о количестве записей FindC(TAForm(frmdbCoreAbout),'labStat_1',tmpLabel ); tmpLabel.Caption := 'Задачи: '+IntToStr(SQLExecute('SELECT count(*) FROM task')); FindC(TAForm(frmdbCoreAbout),'labStat_2',tmpLabel ); tmpLabel.Caption := 'Классы/типы: '+IntToStr(SQLExecute('SELECT count(*) FROM classType')); FindC(TAForm(frmdbCoreAbout),'labStat_3',tmpLabel ); tmpLabel.Caption := 'Функции: '+IntToStr(SQLExecute('SELECT count(*) FROM funcProc')); FindC(TAForm(frmdbCoreAbout),'labStat_4',tmpLabel ); tmpLabel.Caption := 'Примеры: '+IntToStr(SQLExecute('SELECT count(*) FROM example')); // сведения о файле БД FindC(TAForm(frmdbCoreAbout),'labDatabaseDate',tmpLabel ); tmpLabel.Caption := 'Дата обновления базы: '+DateToStr(GetFileLastWriteTime(tmpLabel.Hint)); end;
Ссылки
- Энциклопедия My Visual Database v.1.1 – добавлены задачи, решение которых я находил на форуме или придумывал сам
- Файлы проекта – доступны для читателей библиотеки “Визуальное программирование”
- “Хамелеон” – статья про стили
- “Семейный альбом” – статья с описанием загрузки изображений для кнопок