или Да здравствует Юникод!
Технология загрузки картинок на кнопки долгое время была безупречна, пока не встали ребром два вопроса: использование стилей и экранное масштабирование.
Так как цвет фона кнопки у разных стилей может сильно отличаться, вплоть до полного контраста, то необходимо каким-то образом менять цвет картинки для разных стилей. К сожалению, в My Visual Database нет полноценной поддержки работы с форматом PNG, поэтому единственным решением оказалось дополнительное хранение изображения кнопок для тех стилей, которые контрастируют со стилем по умолчанию, а это больше половины темных стилей. И, хотя решение было найдено, с практической точки зрения оно сложно в исполнении, особенно, если требуется много различных изображений.
А вот с масштабированием договориться не удалось: в My Visual Database отсутствуют средства управления размером изображений на кнопках, кроме как загружать изображения нужного размера, а это ещё более накладная по затратам технология, чем создание кнопок для каждого стиля. Мониторов с высоким разрешением становится всё больше, и все чаще пользователи используют масштабирование приложений. Революционная ситуация налицо: надо что-то в технологиях менять.
На форуме разработчиков My Visual Database все чаще стали появляться примеры использования в качестве изображений символов Юникод (Unicode), поддержка которых полностью реализована в новых операционных системах. Поэтому я принял решение добавить в подсистему управления изображениями специальный параметр, который позволит использовать символы юникод на кнопках по той же технологии, что и изображения: достаточно дать кнопке название, для которого зарегистрировано изображение, чтобы оно автоматически появилось на кнопке.
Напомню, что доработки ведутся в рамках проекта DataKeeper.
Images.pas
Поскольку все доработки были выполнены в данном модуле, я счел необходимым освежить информацию о нем. Тем более, что функциональность картинок PNG вполне актуальная для большинства проектов.
// Утилиты для работы с изображениями
// 04.11.2022
// procedure Images_AddStyle(var ADir:string;); - добавление "стильности" изображениям
// procedure Images_ButtonsAssign( AIndex: integer; AWhiteList: string; ABlackList:string ); - присвоение картинок копкам на формах
// procedure Images_Init; - инициализация данных
// procedure Images_Load( AIndex:integer ); - загрузка картинок сделана процедурой с параметрами, так как в приложении может быть несколько размеров изображений, которые должны храниться в разных списках; картинки в формате png
// procedure Images_Set( AButton:TdbButton; AImageName:string; AIndexOfSize:integer; AModel: integer ); - назначить индекс изображения по названию файла
// procedure Images_SetButton( AButton:TdbButton); - упрощенная процедура назначения картинки на кнопку
// procedure Images_UCButtonsAssign; - присвоение символов-картинок копкам на формах
uses 'VClass\VCUtils.pas', 'VClass\Style.pas';
const
IMAGES_UNICODE = True; // использовать в качестве изображений символы UNICODE. Хорошо работает с масштабированием и стилями.
// папки указаны относительно папки с приложением
IMAGES_UNICODE_INI_FILENAME = 'ucimages.ini'; // файл с настойками картинок-символов
IMAGES_UNICODE_RATIO = 0.8; // размер картинки Unicode относительно размера кнопки
IMAGES_DIR = 'Images\'; // папка, в которой находятся изображения
//
IMAGES_SELECTED_SUFFIX = '_S'; // суффикс в названии для изображения выбранной кнопки
IMAGES_HOT_SUFFIX = '_H'; // суффикс в названии для изображения кнопки при наведении курсора
IMAGES_DISABLE_SUFFIX = '_D'; // суффикс в названии для изображения недоступной кнопки
IMAGES_PRESSED_SUFFIX = '_P'; // суффикс в названии для изображения нажатой кнопки
// модели отображения
IMAGES_SIMPLE_MODEL = 1; // используется два изображения
IMAGES_FULL_MODEL = 2; // используется четыре изображения
// (!) настраивается для конкретного проекта; см. процедуру Images_Init;
IMAGES_FORMAT_COUNT = 1; // сколько форматов в библиотеке
// перечень форматов
IMAGES_FORMAT_BUTTON = 0; //
IMAGES_FORMAT_BIGBUTTON = 1; //
// выравнивание картинки в кнопке
iaLeft = 0;
iaRight = 1;
iaTop = 2;
iaBottom = 3;
iaCenter = 4;
var
ImagesDir: array of string; // папки хранения
ImagesSize: array of integer; // размеры изображений
ImagesList: array of TImageList; // хранилища картинок
ImagesNames: array of TStringList; // имена загруженных картинок
ImagesUCNames: TStringList; // список для хранения названий символов и их отображения
Code language: Delphi (delphi)
Модуль Images.pas ссылается на модули VCUtils.pas и Style.pas, в которых находятся утилиты для работы с виртуальными классами и библиотека для работы со стилями.
Новая константа IMAGES_UNICODE управляет режимом использования символов Юникода в качестве картинок на кнопке.
procedure Images_Init;
// инициализация данных
var
i: integer;
tmpFileName: string;
begin
if IMAGES_UNICODE then // если картинки - символы UNICODE
begin // то загружаем нужный файл
ImagesUCNames := TStringList.Create;
tmpFileName := ExtractFilePath(Application.ExeName)+IMAGES_UNICODE_INI_FILENAME;
if not FileExists(tmpFileName) then
RaiseException('Не найден файл ' + tmpFileName);
ImagesUCNames.LoadFromFile(tmpFileName);
Images_UCButtonsAssign;
end
else
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\';
// магия стиля
Style_AddImagesStyle( ImagesDir[IMAGES_FORMAT_BUTTON] );
// размер
ImagesSize[IMAGES_FORMAT_BUTTON] := 24;
{
ImagesDir[IMAGES_FORMAT_BIGBUTTON] := IMAGES_DIR + 'BigButtons\';
ImagesSize[IMAGES_FORMAT_BIGBUTTON] := 48;
}
// (!)
// загрузка данных
for i := 0 to IMAGES_FORMAT_COUNT - 1 do
begin
ImagesNames[i] := TStringList.Create;
ImagesList[i] := TImageList.Create(frmdbCoreAbout); // используем форму "О программе"
Images_Load(i); // загрузить изображения
end;
// (!) Настраивается для проекта
Images_ButtonsAssign(IMAGES_FORMAT_BUTTON);
// Images_ButtonsAssign( IMAGES_FORMAT_BIGBUTTON,'frmShow','' );
// (!)
end;
end;
Code language: Delphi (delphi)
Процедура инициализации вызывает загрузку ресурсов, а также вызов обработки для установки изображений на кнопки на существующих к моменту запуска формах. Для Юникода предусмотрена отдельная новая процедура Images_UCButtonsAssign(), у которой нет параметров.
В файле ucimages.ini находятся пары <название>=<символ>. В первой строке должна быть пара, которая не используется. Это связано с особенностью реализации метода TStrings.LoadFromFile() и формата хранения текста в кодировке UTF-8.
UnicodeImages=
New=✚
Edit=✍
Delete=🗑
Save=✔
Cancel=✖
Search=🔎
Code language: PHP (php)
Инициализация картинок PNG все ещё имеет архитектурные проблемы: если в проекте используются кнопки различных размеров, то требуется дописывание кода в теле процедуры Images_Init(). А вот технология использования Юникод в этом смысле более совершенна, так как размер “изображения” адаптируется к размеру кнопки внутри процедуры Images_Set().
procedure Images_UCButtonsAssign;
// присвоение символов-картинок копкам на формах
var
tmpForm: TAForm;
tmpButton: TdbButton;
i: integer;
j: integer;
begin
// по всем формам приложения
for i := 0 to Screen.FormCount - 1 do
begin
tmpForm := TAForm(Screen.Forms[i]);
for j := 0 to tmpForm.ComponentCount - 1 do
begin
if tmpForm.Components[j] is TdbButton then
begin
tmpButton := TdbButton(tmpForm.Components[j]);
Images_SetButton(tmpButton);
end;
end;
end;
end;
Code language: Delphi (delphi)
Инициализация простая: находятся все кнопки, затем вызывается упрощенная процедура назначения изображения Images_SetButton().
procedure Images_SetButton( AButton:TdbButton);
// упрощенная процедура назначения картинки на кнопку
begin
Images_Set(AButton, DeleteSuffix(DeleteClassName(AButton.Name)), IMAGES_FORMAT_BUTTON, IMAGES_SIMPLE_MODEL);
end;
Code language: Delphi (delphi)
Для совместимости используется универсальная процедура Images_Set(), которая работает как Юникодом, так и с изображениями PNG.
procedure Images_Set(AButton: TdbButton; AImageName: string; AIndexOfSize: integer; AModel: integer);
// назначить индекс изображения по названию файла
// AButton:TdbButton; - кнопка
// AImageName:string; - имя изображения
// AIndexOfSize:integer; - индекс размера
// AModel: integer - модель
var
tmpImageIndex: integer;
tmpImageSelectedIndex: integer;
tmpImageHotIndex: integer;
tmpImageDisableIndex: integer;
tmpImagePressedIndex: integer;
tmpCaption: string;
begin
AImageName := UpperCase(AImageName);
if AImageName <> '' then
begin
if IMAGES_UNICODE then // если картинки - символы UNICODE
begin
AButton.ImageIndex := -1;
AButton.Images := nil;
tmpCaption := AButton.Caption;
AButton.Caption := ImagesUCNames.Values(AImageName);
if tmpCaption <> '' then
begin
AButton.Caption := AButton.Caption + '' + tmpCaption;
end
else
begin
AButton.Font.Height := trunc(AButton.Height*IMAGES_UNICODE_RATIO);
end;
end
else
begin
// основное изображение
tmpImageIndex := ImagesNames[AIndexOfSize].IndexOf(AImageName);
tmpImageSelectedIndex := ImagesNames[AIndexOfSize].IndexOf(AImageName + IMAGES_SELECTED_SUFFIX);
tmpImageHotIndex := ImagesNames[AIndexOfSize].IndexOf(AImageName + IMAGES_HOT_SUFFIX);
tmpImageDisableIndex := ImagesNames[AIndexOfSize].IndexOf(AImageName + IMAGES_DISABLE_SUFFIX);
tmpImagePressedIndex := ImagesNames[AIndexOfSize].IndexOf(AImageName + IMAGES_PRESSED_SUFFIX);
if tmpImageIndex >= 0 then
begin
AButton.Images := ImagesList[AIndexOfSize];
AButton.ImageIndex := tmpImageIndex;
case AModel of
IMAGES_SIMPLE_MODEL:
begin
AButton.SelectedImageIndex := tmpImageSelectedIndex;
AButton.HotImageIndex := tmpImageSelectedIndex;
AButton.DisabledImageIndex := tmpImageIndex;
AButton.PressedImageIndex := tmpImageSelectedIndex;
end;
IMAGES_FULL_MODEL:
begin
AButton.SelectedImageIndex := tmpImageSelectedIndex;
AButton.HotImageIndex := tmpImageHotIndex;
AButton.DisabledImageIndex := tmpImageDisableIndex;
AButton.PressedImageIndex := tmpImagePressedIndex;
end;
end;
end;
end;
end;
end;
Code language: Delphi (delphi)
Если на кнопке есть надпись, то символ-картинка добавляется перед надписью. Если же надписи нет, то размер символа-картинки меняется в соотвествии с пропорцией к размеру кнопки, которая задается константой IMAGES_UNICODE_RATIO .
Для полноты обзора модуля Images.pas ниже приведены процедура загрузки картинок из папок и процедура инициализации кнопок картинками PNG.
procedure Images_Load(AIndex: integer);
// загрузка картинок сделана процедурой с параметрами, так как в приложении может быть несколько размеров изображений, которые должны храниться в разных списках; картинки в формате png
var
tmpList: TStringList; // список файлов в папке с картинками
tmpImageList: TImageList;
tmpImageNames: TStringList;
i: integer;
tmpImageDir: string;
s: string;
begin
tmpImageDir := ExtractFilePath(Application.ExeName) + ImagesDir[AIndex];
if DirectoryExists(tmpImageDir) then
begin
tmpImageList := ImagesList[AIndex];
tmpImageNames := ImagesNames[AIndex];
//
tmpImageList.Masked := false;
tmpImageList.ColorDepth := cd32bit;
// размер картинок
tmpImageList.Width := ImagesSize[AIndex];
tmpImageList.Height := ImagesSize[AIndex];
tmpList := TStringList.Create;
try
// брать только файлы из указанной папки
// во вложенных папках могут храниться картинки для стилей
tmpList.Text := GetFilesList(tmpImageDir, '*.*', false);
for i := 0 to tmpList.Count - 1 do
begin
tmpImageList.AddPng(tmpList.strings[i]);
s := UpperCase(ExtractFileName(tmpList.strings[i]));
s := copy(s, 1, Length(s) - 4);
tmpImageNames.Add(s);
end;
finally
tmpList.Free;
end;
end
else
RaiseException('Не найдена папка ' + tmpImageDir);
end;
procedure Images_ButtonsAssign(AIndex: integer; AWhiteList: string = ''; ABlackList: string = '');
// присвоение картинок копкам на формах
// присвоение идет по имени, откидывается имя класса и суффикс
// AIndex - формат картинок
// AWhiteList - белый список, изменять только указанные формы
// ABlackList - черный список, исключить формы
var
tmpForm: TAForm;
tmpButton: TdbButton;
tmpName: string;
i: integer;
j: integer;
tmpByName: boolean;
tmpFormList: string;
begin
// добавить разделители (запятые) в белый и черный список, чтобы поиск был корректным
if AWhiteList <> '' then
begin
tmpByName := True;
tmpFormList := ',' + AWhiteList + ',';
end
else
begin
tmpByName := false;
tmpFormList := ',' + ABlackList + ',';
end;
// по всем формам приложения
for i := 0 to Screen.FormCount - 1 do
begin
tmpForm := TAForm(Screen.Forms[i]);
// проверка на попадание формы в черный или белый список
if (tmpByName and (pos(tmpForm.name, tmpFormList) > 0)) or (not tmpByName and (pos(tmpForm.name, tmpFormList) = 0))
then
for j := 0 to tmpForm.ComponentCount - 1 do
begin
if tmpForm.Components[j] is TdbButton then
begin
tmpButton := TdbButton(tmpForm.Components[j]);
tmpName := DeleteSuffix(DeleteClassName(tmpButton.name));
Images_Set(tmpButton, tmpName, AIndex, IMAGES_SIMPLE_MODEL);
end;
end;
end;
end;
Code language: Delphi (delphi)
Масштабирование
Для автоматического масштабирования форм достаточно установить свойство Scaled = True для форм, созданных в дизайнере My Visual Database. А для элементов, создаваемых программным кодом, пришлось добавить функцию Scale(), которая используется везде, где раньше были константы, определяющие координаты или размер компонентов.
const
DEFAULT_DPI = 96; // стандартное разрешение экрана
var
ScaleRatio: double; // масштабирующий коэффициент;
function Scale( AValue:integer):integer;
// масштабирование
begin
Result := Trunc( AValue * ScaleRatio );
end;
function ScaleFontSize: integer;
// отмасштабированный и стандартный для данного приложения размер шрифта
begin
Result := frmMain.tgrStyle.Font.Size;
end;
function GetDesktopDPI: integer;
// получение текущего разрешения экрана.
// по умолчанию разрешение экрана составляет 96 DPI
var
tmpReg: TRegistry;
begin
tmpReg := TRegistry.Create;
tmpReg.RootKey := HKEY_CURRENT_USER;
tmpReg.Access := KEY_READ;
if tmpReg.OpenKey('Control Panel\Desktop\WindowMetrics', False) then
Result := tmpReg.ReadInteger('AppliedDPI')
else
Result := DEFAULT_DPI;
tmpReg.CloseKey;
tmpReg.Free;
end;
Code language: Delphi (delphi)
По началу я хотел определять DPI экрана и подстраивать масштаб под него. Но на практике оказалось, что при смене масштаба DPI не меняется. Тогда я поступил проще. На главной форме имеется компонент tgrStyle, который я использую для задания стиля создаваемым табличным сеткам. Зная исходный размер этого элемента и размер, который получился в результате стандартного масштабирования, я получаю коэффициент масштабирования.
ScaleRatio := frmMain.tgrStyle.Width / 500;
Результат



Ссылки
- Data Keeper 1.5 – исходный код проекта