or Long live Unicode!
The technology for loading images onto buttons was flawless for a long time, until two issues arose: the use of styles and screen scaling.
Since the background color of a button can vary greatly between different styles, even to the point of complete contrast, it is necessary to somehow change the color of the image for different styles. Unfortunately, My Visual Database does not have full support for working with the PNG format, so the only solution was to additionally store button images for those styles that contrast with the default style, and this is more than half of the dark styles. And, although a solution was found, from a practical point of view it is difficult to implement, especially if many different images are required.
But we couldn’t agree on scaling: My Visual Database has no means of controlling the size of images on buttons, other than loading images of the required size, and this is an even more expensive technology than creating buttons for each style. There are more and more high-resolution monitors, and more and more users are using application scaling. The revolutionary situation is obvious: something needs to be changed in technology.
On the My Visual Database developer forum, examples of the use of Unicode characters as images began to appear more and more often, support for which is fully implemented in new operating systems. Therefore, I decided to add a special parameter to the image management subsystem that will allow the use of Unicode characters on buttons using the same technology as images: just give the button a name for which an image is registered so that it automatically appears on the button.
Let me remind you that improvements are being made within the framework of the DataKeeper project.
Images.pas
Since all the improvements have been made to this module, I found it necessary to refresh the information about it. Moreover, the functionality of PNG images is quite relevant for most projects.
// Utilities for working with images
// 04.11.2022
// procedure Images_AddStyle(var ADir:string;); - adding “stylishness” to images
// procedure Images_ButtonsAssign( AIndex: integer; AWhiteList: string; ABlackList: string ); - assigning pictures to buttons on forms
// procedure Images_Init; - data initialization
// procedure Images_Load( AIndex:integer ); - loading images is done using a procedure with parameters, since the application may have several sizes of images that must be stored in different lists; pictures in png format
// procedure Images_Set( AButton:TdbButton; AImageName: string; AIndexOfSize: integer; AModel: integer ); - assign image index by file name
// procedure Images_SetButton( AButton:TdbButton); - simplified procedure for assigning an image to a button
// procedure Images_UCButtonsAssign; - assigning picture symbols to keys on forms
uses 'VClass\VCUtils.pas', 'VClass\Style.pas';
const
IMAGES_UNICODE = True; // use UNICODE characters as images. Works well with scaling and styles.
// folders are specified relative to the application folder
IMAGES_UNICODE_INI_FILENAME = 'ucimages.ini'; // file with image-symbol settings
IMAGES_UNICODE_RATIO = 0.8; // Unicode image size relative to button size
IMAGES_DIR = 'Images\'; // folder where the images are located
//
IMAGES_SELECTED_SUFFIX = '_S'; // suffix in the name for the image of the selected button
IMAGES_HOT_SUFFIX = '_H'; // suffix in the name for the button image when hovering the cursor
IMAGES_DISABLE_SUFFIX = '_D'; // suffix in the name for the image of an inaccessible button
IMAGES_PRESSED_SUFFIX = '_P'; // suffix in the name for the image of the pressed button
// display models
IMAGES_SIMPLE_MODEL = 1; // two images are used
IMAGES_FULL_MODEL = 2; // four images are used
// (!) configured for a specific project; see procedure Images_Init;
IMAGES_FORMAT_COUNT = 1; // how many formats are in the library
// list of formats
IMAGES_FORMAT_BUTTON = 0; //
IMAGES_FORMAT_BIGBUTTON = 1; //
// align the image in the button
iaLeft = 0;
iaRight = 1;
iaTop = 2;
iaBottom = 3;
iaCenter = 4;
var
ImagesDir: array of string; // storage folders
ImagesSize: array of integer; // image sizes
ImagesList: array of TImageList; // image storage
ImagesNames: array of TStringList; // names of loaded images
ImagesUCNames: TStringList; // list for storing symbol names and displaying them
Code language: Delphi (delphi)
The Images.pas module refers to the VCUtils.pas and Style.pas modules, which contain utilities for working with virtual classes and a library for working with styles.
The new IMAGES_UNICODE constant controls whether Unicode characters are used as button images.
procedure Images_Init;
// data initialization
var
i:integer;
tmpFileName: string;
begin
if IMAGES_UNICODE then // if the pictures are UNICODE characters
begin // then load the required file
ImagesUCNames := TStringList.Create;
tmpFileName := ExtractFilePath(Application.ExeName)+IMAGES_UNICODE_INI_FILENAME;
if not FileExists(tmpFileName) then
RaiseException('File not found ' + tmpFileName);
ImagesUCNames.LoadFromFile(tmpFileName);
Images_UCButtonsAssign;
end
else
begin
// initialization of data arrays
SetLength(ImagesDir, IMAGES_FORMAT_COUNT);
SetLength(ImagesSize, IMAGES_FORMAT_COUNT);
SetLength(ImagesList, IMAGES_FORMAT_COUNT);
SetLength(ImagesNames, IMAGES_FORMAT_COUNT);
// (!) configuration performed for the project
ImagesDir[IMAGES_FORMAT_BUTTON] := IMAGES_DIR + 'Buttons\';
// style magic
Style_AddImagesStyle( ImagesDir[IMAGES_FORMAT_BUTTON] );
// size
ImagesSize[IMAGES_FORMAT_BUTTON] := 24;
{
ImagesDir[IMAGES_FORMAT_BIGBUTTON] := IMAGES_DIR + 'BigButtons\';
ImagesSize[IMAGES_FORMAT_BIGBUTTON] := 48;
}
// (!)
// loading data
for i := 0 to IMAGES_FORMAT_COUNT - 1 do
begin
ImagesNames[i] := TStringList.Create;
ImagesList[i] := TImageList.Create(frmdbCoreAbout); // use the "About" form
Images_Load(i); // load images
end;
// (!) Configured for the project
Images_ButtonsAssign(IMAGES_FORMAT_BUTTON);
// Images_ButtonsAssign( IMAGES_FORMAT_BIGBUTTON,'frmShow','' );
// (!)
end;
end;
Code language: Delphi (delphi)
The initialization procedure causes the loading of resources, as well as a call to processing to install images on buttons on forms that exist at the time of launch. For Unicode, there is a separate new procedure, Images_UCButtonsAssign(), which has no parameters.
The ucimages.ini file contains <name>=<symbol> pairs. The first line should have a pair that is not used. This is due to the implementation of the TStrings.LoadFromFile() method and the UTF-8 text storage format.
UnicodeImages=
New=✚
Edit=✍
Delete=🗑
Save=✔
Cancel=✖
Search=🔎
Code language: PHP (php)
Initializing PNG images still has architectural problems: if the project uses buttons of different sizes, then adding code to the body of the Images_Init() procedure is required. But the technology of using Unicode is more advanced in this sense, since the size of the “image” adapts to the size of the button inside the Images_Set() procedure.
procedure Images_UCButtonsAssign;
// assigning picture symbols to buttons on forms
var
tmpForm: TAForm;
tmpButton: TdbButton;
i:integer;
j:integer;
begin
// for all application forms
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)
Initialization is simple: all the buttons are found, then the simplified image assignment procedure Images_SetButton() is called.
procedure Images_SetButton( AButton:TdbButton);
// simplified procedure for assigning an image to a button
begin
Images_Set(AButton, DeleteSuffix(DeleteClassName(AButton.Name)), IMAGES_FORMAT_BUTTON, IMAGES_SIMPLE_MODEL);
end;
Code language: Delphi (delphi)
For compatibility, the universal procedure Images_Set() is used, which works with both Unicode and PNG images.
procedure Images_Set(AButton: TdbButton; AImageName: string; AIndexOfSize: integer; AModel: integer);
// assign image index by file name
// AButton:TdbButton; - button
// AImageName:string; - image name
// AIndexOfSize:integer; - size index
// AModel: integer - model
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 // if the pictures are UNICODE characters
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
// main image
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)
If there is an inscription on the button, then a picture symbol is added before the inscription. If there is no inscription, then the size of the picture symbol changes in accordance with the proportion to the button size, which is specified by the IMAGES_UNICODE_RATIO constant.
For a complete overview of the Images.pas module, below is the procedure for loading images from folders and the procedure for initializing buttons with PNG images.
procedure Images_Load(AIndex: integer);
// loading of pictures is done by a procedure with parameters, since the application may have several sizes of images that must be stored in different lists; pictures in png format
var
tmpList: TStringList; // list of files in the folder with pictures
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;
// size of pictures
tmpImageList.Width := ImagesSize[AIndex];
tmpImageList.Height := ImagesSize[AIndex];
tmpList := TStringList.Create;
try
// take only files from the specified folder
// images for styles can be stored in subfolders
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('Folder not found ' + tmpImageDir);
end;
procedure Images_ButtonsAssign(AIndex: integer; AWhiteList: string = ''; ABlackList: string = '');
// assigning images to buttons on forms
// assignment goes by name, class name and suffix are removed
// AIndex - image format
// AWhiteList - white list, change only specified forms
// ABlackList - black list, exclude forms
var
tmpForm: TAForm;
tmpButton: TdbButton;
tmpName: string;
i:integer;
j:integer;
tmpByName: boolean;
tmpFormList: string;
begin
// add separators (commas) to the white and black lists so that the search is correct
if AWhiteList <> '' then
begin
tmpByName := True;
tmpFormList := ',' + AWhiteList + ',';
end
else
begin
tmpByName := false;
tmpFormList := ',' + ABlackList + ',';
end;
// for all application forms
for i := 0 to Screen.FormCount - 1 do
begin
tmpForm := TAForm(Screen.Forms[i]);
// check if the form is included in the black or white list
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)
Scaling
To automatically scale forms, just set the Scaled = True property for forms created in the My Visual Database designer. And for elements created by program code, we had to add the Scale() function, which is used wherever there were previously constants defining the coordinates or size of components.
const
DEFAULT_DPI = 96; // standard screen resolution
var
ScaleRatio: double; // scaling factor;
function Scale(AValue:integer):integer;
// scaling
begin
Result := Trunc( AValue * ScaleRatio );
end;
function ScaleFontSize: integer;
// scaled and standard font size for this application
begin
Result := frmMain.tgrStyle.Font.Size;
end;
function GetDesktopDPI: integer;
// getting the current screen resolution.
// default screen resolution is 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)
At first I wanted to determine the screen DPI and adjust the scale to it. But in practice it turned out that when changing the scale, the DPI does not change. Then I did it easier. The main form has a tgrStyle component, which I use to style the table grids I create. Knowing the original size of this element and the size that resulted from standard scaling, I get the scaling factor.
ScaleRatio := frmMain.tgrStyle.Width / 500;
Result
Links
- Data Keeper 1.5 – project source code
Hello, I think your site might be having browser compatibility issues. When I look at your blog site in Firefox, it looks fine but when opening in Internet Explorer, it has some overlapping. I just wanted to give you a quick heads up! Other then that, awesome blog!