или Да здравствует Юникод!

Технология загрузки картинок на кнопки долгое время была безупречна, пока не встали ребром два вопроса: использование стилей и экранное масштабирование.

Так как цвет фона кнопки у разных стилей может сильно отличаться, вплоть до полного контраста, то необходимо каким-то образом менять цвет картинки для разных стилей. К сожалению, в 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;

Результат

Картинки Юникод в светлой теме
Картинки Юникод в тёмной теме
Сохранение пропорций картинок при изменении масштаба отображения приложений.

Ссылки

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

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