Each of us has a lot of photographs that keep the memory of our loved ones, friends, relatives, memorable events and beautiful places that we visited. Modern technology allows you to take high-quality photos with high resolution quickly and easily, besides, the camera is now always at hand – all modern mobile phones have the ability to take pictures. And it would be great to have a simple and functional system for storing and viewing these photos.

General description

This program will allow you not only to view your photos, but also provide them with a description, as well as tags – keys, by combining which you can quickly find the desired image.

The second interesting feature is the compilation of albums, which are a presentation: each screen can include several photos, decorative images and text descriptions that will be stored in the database. When you start a presentation, the screens change at a specified interval using visual transitions. You can also adjust the soundtrack, and by clicking on the photo, you can expand it to full screen.

Business model

Adding and storing images: photos (jpg files), decorative elements (png, jpg files).

Images are tagged: title, text description, creation date, audio description.

You can attach one or more tags to images.

The tag has the following properties: title, category. Tag category options: places, times, persons, emotions, decor, etc.

When you add a photo, it is copied to the storage (a special folder on the disk) and renamed. The name in the repository matches the record ID in the database.

A thumbnail is created for the image: a smaller version of the file that is used in the image list view. The thumbnail is stored in the repository, has a name that matches the name of the main image, but with the prefix “m”.

Images can be organized into albums. The album has a title and description. The album consists of pages.

The page contains one or more elements: an image from a database or free text. The location of the elements is set statically when the page is created.

A cover thumbnail is created for the album – the image of the first page of the album.

For each album page, a thumbnail is created and used when editing the album.

The album can be viewed as a presentation: for each page, you can set the display time, you can set the sound for the album, the page changes through the specified visual effect.

For the image search result, you can create a dynamic album – a presentation based on a fixed template.

For images, you can set a hint area – a rectangular area, when you hover over it, a frame and an inscription appear. For example, this can be used to refer to who is in the photo. Tips are displayed in single image full screen view. The tooltip area is relative to the image size, so it scales easily with the image.

Data Model

Image

  • Name
  • Description
  • Date of creation

Tag

  • Tag Category
  • Title

Tag category

  • Name

Image tags

  • Image
  • Tag

Album

  • Name
  • Description
  • Audio (playlist)
  • Default page display time

Album page

  • Album
  • Sequence number
  • Page time
  • Visual transition effect

Visual transition effect

  • Name

Album item

  • Image
  • X Position
  • Y Position
  • Width
  • Height
  • Text
  • Font name
  • Font size
  • Font color

Image hints

  • Image
  • X Position
  • Y Position
  • Width
  • Height
  • Text

Data Presentation

The main form is the albums form.

On the form of albums there is an area for viewing thumbnails of album covers and a search panel, a toolbar with control buttons: adding, editing, deleting, switching to the image database editing mode, launching an album in presentation mode.

On the form of the image base there is an area for viewing thumbnails of images and a search panel, a toolbar with control buttons: adding, editing, deleting, switching to the mode of working with albums.

Project implementation

Database

Data Schema

Result

The project turned out to be quite large in terms of implementation, so my Napoleonic plans to create one project a day were wrecked. So far, the outlines for two forms are ready: the list of albums and the list of images.

Creating a data structure and forms in My Visual Database is undoubtedly a fast and exciting process, but in this case it is necessary to implement quite complex functionality, namely:

  • Creating thumbnails for images
  • Creating a thumbnail display routine
  • Album editor
  • Image hint editor

It is possible to display thumbnails using standard interface elements (table), but I want to make an adaptive display: so that the number of elements horizontally and vertically changes depending on the size of the form. The level of technology is growing rapidly, and FullHD resolution is not the limit, the user may have a 2K or even 4K display at his disposal. And vice versa – someone only has a laptop that barely pulls HD. 

The album editor is similar in complexity to MS PowerPoint, which can also delay the development process somewhat, so the first version of the program will most likely be released without albums, but with a full-featured image storage and an advanced search system.

Image gallery

View images as a gallery of mini-images. Such functionality will be useful not only in the Family Album, but also in other programs focused on storing images.

Thumbnails

The principle of the gallery is based on the fact that for each image a thumbnail is created – a reduced copy of the image with a given size. The standard program “Explorer” does the same: for each image in the folder, it automatically creates a thumbnail, which displays in the main part of the screen if the “Large Icons” view mode is selected. And on the right, the viewport of the selected image can be reflected, the size of which can be changed.

Unfortunately, My Visual Database will not be able to automatically create thumbnails due to the limited ability to work with image components. Therefore, the thumbnails in the Family Album will be created by the user himself, manually defining the area that will be displayed on the thumbnail image. However, this is even good, since the gallery should consist of square images, and the original images can be with any aspect ratio. Therefore, only the user can decide what exactly should be displayed on the thumbnail.

Data storage

The second important point is how the images are stored. Although the SQLite and MySQL databases allow you to store images directly in the database, this method is not suitable for storing large amounts of data, as it will noticeably slow down the system. Therefore, the original pictures, as well as thumbnails, will be stored in the file storage – a folder on the disk next to the database.

To simplify the search system, the name of the original image file will consist of the post ID and extension, and the thumbnail ID will have the thumbnail type added to the ID. 

The thumbnail type is the ability to extend the functionality of the storage system in the future so that it can be used in other projects. For example, an HR department might want an additional thumbnail view of a 3×4 profile photo, while a department structure tree might need circle thumbnails.

The thumbnail file extension (storage format) will be fixed – JPG is well suited for such purposes. But we will write the extension of the original file to the database – it will be needed to load the image into the visual components.

The family album will use three folders for storing images, which are named the same as the corresponding database tables:

  • album – album cover thumbnails
  • image – original images and thumbnails of photographs and decorative images
  • page – album page thumbnails

There will be only one thumbnail type – a square picture for display in a gallery of 150×150 pixels. The type is encoded with the number 0, which is separated from the name by an underscore character. Therefore, the folder with image files looks like this:

Display form

The form has a thumbnail display panel (1) and a preview panel (2). In addition to the usual database editing buttons, the toolbar has a checkbox for controlling the visibility of the preview panel (3) and an invisible btnSearch button, the click handler of which builds an image gallery. On the preview panel, in addition to the image itself, there are a couple of labels for displaying the title (5) and description (6).

The checkbox has been styled like a button using a panel. This decision is due to the sad fact that standard buttons (TdbButton) do not have the ability to set the Down property. Perhaps in the future we will implement the toolbar using the TToolbar component, but for now, in order not to overly complicate the program code, we will restrict ourselves to a checkbox on the panel.

Using a button called btnSeach is a nod to a standard that we might also need in the future.

In order for the blank form to acquire the necessary conditions (add a splitter to control the size of the preview panel, make the size of the label responsible for the description of the photo adaptive), you need to “finish” it using a script.

procedure InitForm;
var
  tmpSplitter:TSplitter;
begin
  frmImage.panToolbar.Align := alTop;
  frmImage.panImagePreview.Align := alRight;
  frmImage.panPreview.Align := alClient;
  // add preview area separator
  tmpSplitter := TSplitter.Create(frmImage);
  tmpSplitter.Name := 'splMain';
  tmpSplitter.Parent := frmImage;
  tmpSplitter.Left := frmImage.panImagePreview.Left - 1;
  tmpSplitter.Align := alRight;
  tmpSplitter.Width := 7;
  // make adaptive sizes of the label with the description of the image
  frmImage.labName.align := alTop;
  frmImage.labDescription.align := alBottom;
  frmImage.imgPreview.align := alClient;
  // work out hiding the panel
  frmImage_chbShowPreview_OnClick( frmImage.chbShowPreview );
end;Code language: Delphi (delphi)

Building a gallery

A simple database query is used to build the data. In the future, this query will become more complex to filter data according to the specified criteria.

procedure frmImage_btnSearch_OnClick (Sender: TObject; var Cancel: boolean);
var
  tmpSQL: string;
  tmpDataSet: TDataSet;
  tmpTop: integer;
  tmpLeft: integer;
  tmpImage:TdbImage;
  tmpScrollBox: TScrollBox;
begin
  Control_ClearChild( frmImage.panPreview );
  // for vertical scrolling, add a scroll area
  tmpScrollBox := TScrollBox.Create(frmImage);
  tmpScrollBox.Parent := frmImage.panPreview;
  tmpScrollBox.Align := alClient;
  tmpScrollBox.Name := 'scbPreview';
  //
  frmImage.panPreview.Tag := 0; // ID selected item
  tmpTop := 0;
  tmpLeft := 0;
  // build preview images on palen
  tmpSQL := 'SELECT id, name, description FROM image';
  SQLQuery(tmpSQL,tmpDataSet);
  try
    while not tmpDataSet.EOF do
    begin
      // add image
      tmpImage := TdbImage.Create(frmImage);
      tmpImage.Parent := tmpScrollBox;
      tmpImage.Top := tmpTop + 2;
      tmpImage.Left := tmpLeft + 2;
      tmpImage.Width := 150;
      tmpImage.Height := 150;
      tmpImage.Proportional := False;
      tmpImage.Stretch := False;
      // assign event handlers on buttons
      AssignEvents(tmpImage);
      tmpImage.dbOnClick := 'PreviewImage_OnClick';
      tmpImage.dbOnDoubleClick := 'PreviewImage_OnDoubleClick';
      // use some image properties to store data
      tmpImage.Tag := tmpDataSet.FieldByName('id').asInteger;
      tmpImage.TagString := tmpDataSet.FieldByName('name').asString;
      tmpImage.Hint := tmpDataSet.FieldByName('description').asString;
      // loading image from file storage
      ImageEdit_LoadImageFromBase( tmpImage, tmpDataSet.FieldByName('id').asInteger, PI_SMALL, TM_IMAGE);
      tmpDataSet.Next;
      // calculate the next position for the image
      tmpLeft := tmpLeft + 154; // grid step
      // if the image goes beyond the width of the panel + the width of the scroll, then
      if frmImage.panPreview.Width < tmpLeft + 154 + 20 then
      begin // go to next row
        tmpLeft := 0;
        tmpTop := tmpTop + 154;
      end;
    end;
  except
    tmpDataSet.Free;
  end;
end;Code language: Delphi (delphi)

This procedure adds handlers for click and double click on the image. Please note that instead of the onDoubleClick property (which for some reason is not available in scripts), the similar dbonDoubleClick property is used to set the handler.

Attention! When assigning handlers through the properties of the db*  be sure to use the AssignEvents() procedure.

Single click is used to select the image (display the frame and load the image data in the preview panel), and double click is used to edit the image.

To load an image from disk, the  ImageEdit_LoadImageFromBase() procedure is called, which is passed as a parameter the component to load, the entry id, and additional parameters, which we will discuss later. This is due to the fact that it is necessary to consider a set of scripts that provide the creation of thumbnails. 

Responsive Size

When changing the panel width, the thumbnails are moved so that they take up the full width of the scroll area. If the elements do not fit in the viewport, a vertical scroll bar automatically appears.

procedure frmImage_panPreview_OnResize (Sender: TObject);
// change of size
var
  i: integer;
  tmpImage:TdbImage;
  tmpFrame:TShape;
  C: TControl;
  tmpPanel: TdbPanel;
  tmpTop: integer;
  tmpLeft: integer;
  tmpForm:TAForm;
  tmpScrollBox: TScrollBox;
begin
  CForm(Sender,tmpForm);
  tmpPanel := TdbPanel(Sender);
  tmpTop := 0;
  tmpLeft := 0;
  FindC(tmpForm,'scbPreview',tmpScrollBox,False);
  // processing is needed only for the filled panel
  if tmpScrollBox <> nil then
  begin
    // move images
    for i := tmpScrollBox.ControlCount - 1 downto 0 do
    begin
      C := tmpScrollBox.Controls[i];
      if C is TdbImage then
      begin
        c.Top := tmpTop + 2;
        c.Left := tmpLeft + 2;
        tmpLeft := tmpLeft + 154;
        if tmpPanel.Width < tmpLeft + 154 + 20 then
        begin
          tmpLeft := 0;
          tmpTop := tmpTop + 154;
        end;
      end;
    end;
    // move frame if then exists
    FindC(tmpForm,'shpSelector',tmpFrame,False);
    if tmpFrame <> nil then
    begin
      tmpImage := TdbImage(frmImage.panPreview.Tag);
      tmpFrame.Top := tmpImage.Top - 2;
      tmpFrame.Left := tmpImage.Left - 2;
    end;
  end;
end;Code language: Delphi (delphi)

Thumbnail Editor

A thumbnail image of the original can be used for various purposes. For example, to display in a photo album gallery or as an employee’s avatar in a personnel management program, to display in a family tree, and so on.

Key properties of the TdbImage component

The TdbImage component is inherited from the standard TImage component, which can “scale” the image loaded into it, reducing or stretching it to fit the canvas. At the same time, anti-aliasing algorithms are used to make the picture look natural. What properties control this process?

Stretch

The stretch property allows you to stretch the image, making it larger or smaller than the original.

  • False – the image is displayed without scaling
  • True – the image is stretched to the full size of the component; however, it can be increased or decreased.

Please note that the image was distorted when scaling, because when resizing, the proportion was violated: the ratio of the width and height of the image. In some cases, this is not critical (for example, when stretching a monochrome image or a gradient), but for photos and drawings, you need to maintain proportions. The Proportional property is responsible for this, which complements the Stretch property.

Proportional

False – proportions are not preserved, the image is stretched to fit the component

True – proportions are preserved

Center

This property allows you to center the image

False – the image is displayed from the upper left corner of the component
True – the image is displayed in the center of the component

Algorithm

To get the thumbnail, we need two TdbImage components. The first component will contain the original image.  The second component is for the thumbnail. Both components will have their properties set to scale the image while maintaining aspect ratio: Stretch = True, Proportional = True and Center = True.

To get a copy of the image, use the canvas’s CopyRect() method. It copies to the canvas an image that is on another canvas. To get focus, the TdbImage components must be placed on the panels, and the canvas of the parent panel should be used as the copy source. The explanation for this is simple: in order for the image loaded in the TdbImage component to become visible, it is drawn on the canvas of the parent component, in our case, the panel. And it is drawn taking into account the scaling properties described above. And the TdbImage canvas contains the original image.

It is also necessary to somehow define the copy area, it is desirable that the mechanism be intuitive. For example, shade the image and make the copy area transparent by providing it with “helpers” – small squares for dragging with the mouse, with which you can change the size of the transparent “window”.

For shading, you will have to use 4 images loaded with a translucent png image, and for helpers, you will need four opaque black marks. We will also need another “image without an image” – a completely transparent image, which we will drag across the screen to select the copy area, and all other elements (translucent curtains, helpers) will move after it, creating the illusion of a transparent “hole”.

For a tool to be universal, all its settings must be described by constants. In addition, it must be able to create several thumbnails of different sizes at once from one given image.

Implementation

We will need a form on which we will place the key elements: the source image panel, the thumbnail panel and auxiliary buttons to control the process. All other elements will be created by the program, the scripts of which we will place in a separate module. This is done so that the thumbnail editor can be easily integrated into any project created on the My Visual Database platform.

Since My Visual Database does not have the ability to create custom classes, you will have to create a “virtual class” – a script library that provides the required functionality for an instance of a component of an accessible class.

Within this article, it is not possible to comment on all class methods, so I will only talk about the key ones.

TImageEdit

Actually speaking, the image editor into which the image will be loaded. Methods for saving an image, methods for manipulating curtains and helpers to control the copy area will also be implemented there.

Virtual class methods:

  • ImageEdit_GetFileExt() – file extension being edited
  • ImageEdit_GetImageDir() – get image path by type –
  • ImageEdit_AjustHelpers() – set helpers by frame
  • ImageEdit_Create() – Create an image editor
  • ImageEdit_CreatePreviews() – Update thumbnails
  • ImageEdit_Frame_OnMouseDown() – Start moving
  • ImageEdit_Frame_OnMouseMove() – Move selector frame
  • ImageEdit_Frame_OnMouseUp() – stop moving
  • ImageEdit_GetPhoto() – remove image from canvas at coordinates specified by frame
  • ImageEdit_Helper_OnMouseDown() – start moving helper
  • ImageEdit_Helper_OnMouseMove() – move helpers
  • ImageEdit_Helper_OnMouseUp() – end move helper
  • ImageEdit_Init() – set edit mode
  • ImageEdit_LoadImage() – load image for editing
  • ImageEdit_LoadImageFromBase() – load image from server
  • ImageEdit_PhotoPanel_OnResize() – panel resize – frame adjustment
  • ImageEdit_Reset() – reset to original state
  • ImageEdit_RestoreImages() – download photos from file server
  • ImageEdit_SaveImages( ) – save photos on file server
  • ImageEdit_SetFrame() – set helpers and set screen for photo editing
  • ImageEdit_SetFrameDef() – reset to default state
  • ImageEdit_SetTargetImage () – setting the target image

Images are stored in folders whose names are the same as the names of the tables, and the file name consists of the record ID and extension. For original files, the original extension is retained, for thumbnail files, the jpg extension.

procedure ImageEdit_LoadImageFromBase(AImage: TdbImage; AID: integer; APhotoType: integer; AImageType: integer);
// load image from base
// if the file exists, then load it; if not, then insert the default image
// AImage - where to download
// AID - Image ID 
// APhotoType - type of image size
// AImageType - image type
var
  tmpFileName: string;
  tmpFolder: string;
  tmpFileExt: string;
begin
  tmpFolder := ImageEdit_GetImageDir(AImageType);
  // the original file has an unknown extension, so it needs to be pulled out by a search
  if APhotoType = IE_FT_ORIGINAL then
  begin
    // here you need to do a type check, but in our project, original pictures are stored only in photos
    tmpFileExt := SQLExecute('SELECT FileExt FROM image WHERE id ='+IntToStr(AID));
    tmpFileName := tmpFolder + IntToStr(AID) + tmpFileExt;
  end
  else // for the rest, the size is included in the name, the file format is jpg
    tmpFileName := tmpFolder + IntToStr(AID) + '_' + IntToStr(APhotoType) + '.jpg';
  // if the file exists, then load it
  if FileExists(tmpFileName) then
    AImage.Picture.LoadFromFile(tmpFileName)
  else // if not, then insert the default image
    SetImage(AImage, IE_IMG_DEFAULT);
end;
 
procedure ImageEdit_SaveImages(AImageEdit: TdbImage;);
// saving photos on a file server
var
  tmpTIPanel: TdbPanel;
  tmpForm: TAForm;
  tmpName: string;
  tmpFileName: string;
  tmpID: Integer;
begin
  tmpID := AImageEdit.Tag;
  CForm(AImageEdit, tmpForm);
  tmpName := DeleteClassName(AImageEdit.Name);
  FindC(tmpForm, T_TARGET_IMAGE_PANEL + tmpName, tmpTIPanel);
  tmpFileName := tmpTIPanel.TagString + inttostr(tmpID) + AImageEdit.TagString;
  SaveImageToFile(AImageEdit, tmpFileName, True);
  // if the picture is empty, then the file is not saved, otherwise we save the rest
  if FileExists(tmpFileName) then
  begin
    TargetImagePanel_SaveImages(tmpTIPanel, tmpID);
  end;
end;Code language: PHP (php)

TTargetImage

A module for getting a thumbnail. Accepts images from TImageEdit, works with file storage (saves and loads thumbnails in jpg format). 

Virtual class methods:

  • TargetImage_Checker_OnClick() – click on the header checker
  • TargetImage_Create() – create a panel to display / save the image of the desired size
  • TargetImage_Reset() – reset the image to default< /li>
  • TargetImage_RestoreImage() – load image
  • TargetImage_SaveImage() – save image

The image is saved in two steps. First, we prepare the image: we form the file name and remove the frame, if there was one.

procedure TargetImage_SaveImage(APanel: TdbPanel; AID: integer);
// save image
var
  tmpForm: TAForm;
  tmpName: string;
  tmpImage: TdbImage;
  tmpFrame: TdbImage;
  tmpTIPanel: TdbPanel;
  tmpFileName: string;
begin
  tmpTIPanel := TdbPanel(APanel.Parent);
  tmpName := DeleteClassName(DeleteSuffix(APanel.Name));
  CForm(APanel, tmpForm);
  FindC(tmpForm, T_IMAGE + tmpName, tmpImage);
  FindC(tmpForm, T_IMAGE + tmpName + SX_FRAME, tmpFrame, False);
  // collect the file name: folder + ID + extension
  tmpFileName := tmpTIPanel.TagString + inttostr(AID) + APanel.TagString;
  // if there is a frame, then remove it
  if tmpFrame <> nil then
  begin
    tmpFrame.Visible := False;
    Application.ProcessMessages;
  end;
  SaveImageToFile(tmpImage, tmpFileName);
  // then return the frame
  if tmpFrame <> nil then
    tmpFrame.Visible := True;
end;Code language: PHP (php)

Then we write the image to a file.

procedure SaveImageToFile(AImage: TdbImage; AFileName: string; AOriginalSize: boolean = False);
// saving an image from an image to a JPG file
// the picture must be the same size on the panel as the image is taken from the canvas of the panel.

var
  tmpBuffer: TBitMap;
  tmpJPG: TJPEGImage;
  tmpPan: TdbPanel;
begin
  if AOriginalSize then // no scaling, original resolution
    AImage.Picture.SaveToFile(AFileName)
  else // with scaling, in the size of the visible representation
  begin
    tmpBuffer := TBitMap.Create;
    tmpJPG := TJPEGImage.Create;
    tmpPan := TdbPanel(AImage.Parent); // make sure the panel exists
    tmpBuffer.Width := AImage.Width;
    tmpBuffer.Height := AImage.Height;
    tmpBuffer.Canvas.CopyRect(0, 0, tmpBuffer.Width, tmpBuffer.Height, tmpPan.Canvas, 0, 0, tmpBuffer.Width,
      tmpBuffer.Height);
    tmpJPG.Assign(tmpBuffer);
    tmpJPG.SaveToFile(AFileName);
    tmpJPG.Free;
    tmpBuffer.Free;
  end;
end;Code language: Delphi (delphi)

TTargetImagePanel

A panel for placing modules for obtaining thumbnails. Adapter – container. Link between TImageEdit and TTatgetImage set. 

Class methods

  • TargetImagePanel_Clear() – clear the panel from vignettes
  • TargetImagePanel_Reset() – reset all images on the panel to their original state
  • TargetImagePanel_RestoreImages() – restore images from the database
  • TargetImagePanel_SaveImages() – saving images to the database
  • TargetImagePanel_SetMode() – setting the operation mode – adding the necessary components to the target image panel

All methods, except for the last one, operate on the list of elements of the TTargetImage by passing it the appropriate command. And the TargetImagePanel_SetMode() method manages the list itself: it removes and creates the necessary elements on the panel.

procedure TargetImagePanel_SetMode(APanel: TdbPanel; AMode: integer);
// set the mode of operation - add the desired components to the panel of target images
var
  tmpImageEditor: TdbImage;
  tmpForm: TAForm;
  tmpName: string;
  tmpTIList: array of string;
  i: integer;
  tmpTIType: integer;
  tmpMaxWidth: integer;
  tmpParent: TWinControl;
  tmpPanel: TdbPanel;
begin
  tmpMaxWidth := 0;
  tmpName := DeleteClassName(DeleteSuffix(APanel.Name));
  CForm(APanel, tmpForm);
  FindC(tmpForm, T_IMAGE_EDIT + tmpName, tmpImageEditor);
  // if the mode is the same as the current one, we do not change the composition of the vignettes, we only reset the images to their original ones
  if APanel.Tag <> AMode then
  begin
    if AMode > (Length(ie_List) - 1) then
    begin
      RaiseException('TargetImagePanel_SetMode - mode not supported: '+IntToStr(AMode) );
      Exit;
    end;
    // remove all vignettes
    TargetImagePanel_Clear(APanel);
    // setup folder
    APanel.TagString := ImageEdit_GetImageDir(AMode);
    // add vignettes
    // get format list
    tmpTIList := SplitString( ie_List[ AMode ] ,',' );
    for i:=0 to Length(tmpTIList) - 1 do
    begin
      tmpTIType := StrToInt( tmpTIList[i] );
      // add thumbnail to panel
      TargetImage_Create('TI'+IntToStr( tmpTIType ), ie_TICaption[tmpTIType], tmpImageEditor, APanel, ie_TIWidth[tmpTIType], ie_TIHeight[tmpTIType] );
      if ie_TIWidth[tmpTIType] > tmpMaxWidth then
      tmpMaxWidth := ie_TIWidth[tmpTIType];
    end;
    // remember the current mode here
    APanel.Tag := AMode;
  end;
  // set thumbnail bar width
  // условность: редактор и панель миниатюр должны располагаться на одном родительском компоненте
  tmpParent := TWinControl( APanel.Parent );
  APanel.Width := tmpMaxWidth + IE_TI_MARGIN * 2;
  APanel.Left := tmpParent.Width - APanel.Width;
  tmpPanel := TdbPanel( tmpImageEditor.Parent );
  tmpPanel.Width := tmpParent.Width - APanel.Width;
  TargetImagePanel_Reset(APanel);
end;Code language: Delphi (delphi)

Because the initialization data is stored in arrays, the TargetImagePanel_SetMode procedure does not have to be rewritten each time the thumbnail set or editing modes change.

Init parameters

All constants and variables of the editor are moved to a separate module IEConstVar.pas:

// Constants and variables
 
const
  // Image editor options
  IE_HELPER_SIZE = 10; // helper size
  IE_HELPER_COLOR = clBlack; // helper color
  IE_IMG_TRANSLUCENT = 'Black50'; // component name with translucent image, 50% black
  IE_IMG_DEFAULT = 'DefImage'; // default picture
  // миниатюра
  IE_TI_CHECKBOX_SIZE = 24; // active thumbnail selection checker size
  IE_TI_MARGIN = 16; // indent vertical
  IE_TI_CHECKER_NAME = 'PhotoEditMode_'; // element name with checker
  //
  // to initialize arrays
  IE_TI_COUNT = 2; // number of thumbnail formats
  IE_COUNT = 3; // number of editing formats
  //
  // to communicate with the main application
  //
  // image types (editing formats)
  IE_IT_UNKNOW = -1; // initial state
  IE_IT_IMAGE = 0; // pictures/photos
  IE_IT_ALBUM = 1; // album covers
  IE_IT_PAGE = 2; // album page preview
  // what to upload - thumbnail formats
  IE_FT_ORIGINAL = -1; // original image
  IE_FT_SMALL = 0; // small picture
  IE_FT_MIDDLE = 1; // bigger picture
 
var
  ie_MovedObject: TObject; // moveable object
  ie_pX: integer;
  ie_pY: integer;
  // thumbnail formats
  ie_TIWidth: array of integer; 
  ie_TIHeight: array of integer; 
  ie_TICaption: array of string; 
  // editing formats
  ie_Tables: array of string; // storage table
  ie_List: array of string; // list of thumbnails, numbers separated by commas
 
begin
  // initialization
  //
  SetLength(ie_TIWidth, IE_TI_COUNT);
  SetLength(ie_TIHeight, IE_TI_COUNT);
  SetLength(ie_TICaption, IE_TI_COUNT);
  //
  ie_TICaption[0] := 'Table Thumbnail';
  ie_TIWidth[0] := 150;
  ie_TIHeight[0] := 150;
  //
  ie_TICaption[1] := 'Album page';
  ie_TIWidth[1] := 250;
  ie_TIHeight[1] := 250;
  //
  // editing formats
  SetLength(ie_Tables, IE_COUNT);
  SetLength(ie_List, IE_COUNT);
  //
  ie_Tables[0] := 'Image';
  ie_List[0] := '0';
  //
  ie_Tables[1] := 'Album';
  ie_List[1] := '1';
  //
  ie_Tables[2] := 'Page';
  ie_List[2] := '1';
 
end.Code language: PHP (php)

Search by tags

Search performance depends on the algorithms used. Each image has a title, description, and a list of tags in the database. Search by text title and description is the easiest to implement. But it is also the slowest, since the LIKE condition is usually used to search, with the help of which a text fragment is found. An alternative search option, which is successfully used in many social networks, is searching by tags – keywords associated with the data you are looking for.

Data structure

To organize the search by tags, the necessary table structure was created:

Images are associated with tags by a many-to-many relationship (imageTag). The tag.Checked field is used to filter by tags, which works well for a single-user application. An additional directory of tag categories will allow you to put things in order if there are a lot of tags.

Editing image tags

According to the canons of My Visual Database, to edit the imageTag table, we would have to add a child table to the image editing form, in which the tags would be displayed. But that is not very convenient. Firstly, I am not a fan of child tables, and secondly, there will be difficulties in organizing the editing process of such a table, since it will be inconvenient to edit data in the table itself with a large number of tags. And thirdly, when adding an image, it often becomes necessary to add new tags. Therefore, we will use a text editor and a special script to edit image tags.

Two components are suitable for a text editor: TdbMemo and TdbRichEdit. However, I opted for the latter, since it is it that allows you to highlight individual words of the typed text with different colors. 

According to the idea, the user types tags in the editor, and after he finishes editing, and this moment is determined by a pause in typing, the program automatically checks them. And if such a tag is not yet in the database, then it is highlighted in a different color so that the user immediately understands: either this is a new tag, or he made a mistake in writing. And when saving data  for each new tag, an add form opens to select a tag category. 

The first complication arose with the behavior of TdbRichEdit when using a style, namely, the background color of the document is not set according to the selected style. Moreover, this component does not have a property to control the background color of the editor. The solution was found: when the application starts, an HTML document is loaded into the component, in which the background color property is set for the body (but things are worse with the TdbDateTimePicker component – it glows with a bright white spot on the dark “TableDark” theme and most likely you will have to replace it with TdbEdit and a couple of scripts…).

The second difficulty, or rather the nuance of the behavior, was that the OnChange event occurred with any change in the editor (for comparison, in TdbMemo OnChange occurs only when the user manually changes the text), so I had to use the OnKeyUp event handler, in which 2 – second timer.

procedure frmImageEdit_redTags_OnKeyUp (Sender: TObject; var Key: Word; Shift, Alt, Ctrl: boolean);
// release key in tag editor
begin
  // if there is no timer yet, then create it
  if TagTimer = nil then
  begin
    TagTimer := TTimer.Create(frmImageEdit);
    TagTimer.OnTimer := @TagTimer_OnTimer;
  end;
  // restart timer for 2 seconds
  TagTimer.Enabled := False;
  TagTimer.Interval := 2000;
  TagTimer.Enabled := True;
  // set a sign that the data has been changed by the user
  frmImageEdit.redTags.Tag := 1;
end;Code language: Delphi (delphi)

And after the end of editing the tags, a handler is launched that checks the tags and highlights them if necessary.

procedure TagTimer_OnTimer (Sender: TObject);
// processing the list of tags in the tag editor
var
  i: integer;
  Tags: array of string;
  s: string;
  tmpTag: string;
  tmpETag: string;
begin
  TagTimer.Enabled := False; // stop the timer
  s := frmImageEdit.redTags.Text; // get tag data
  frmImageEdit.redTags.Clear; // clear editor
  // replace commas with spaces, remove newlines and double spaces
  s := ReplaceStr(s,',',' ');
  s := ReplaceStr(s,chr(13),' ');
  s := ReplaceStr(s,'  ',' ');
  Tags := SplitString( s,' '); // get list of tags as string array
  for i := 0 to length(Tags) - 1 do
  begin
    tmpTag := Trim(Tags[i]); // just in case, so that only the text remains
    if (tmpTag <> '') then
    begin
      // looking for a tag in the database
      tmpETag := VarToStr( SQLExecute('SELECT name FROM tag WHERE upper( name ) = upper("'+tmpTag+'")') );
      // if not found then
      if tmpETag = '' then
      begin // insert the tag in the user's spelling, but with a capital first letter, in yellow
        tmpTag := UpperCase(copy(tmpTag,1,1)) + copy(tmpTag,2,length(tmpTag)-1);
        frmImageEdit.redTags.InsertTextEx(tmpTag,$00FFFF,11,0,'Segoe UI')
      end // if found, then insert in writing from the base, in white
      else
        frmImageEdit.redTags.InsertTextEx(tmpETag,$FFFFFF,11,0,'Segoe UI');
     // add space
     frmImageEdit.redTags.InsertTextEx(' ',$FFFFFF,11,0,'Segoe UI');
    end;
  end;
end;Code language: Delphi (delphi)

You will also need a couple more scripts: to save tags to the database

procedure frmImageEdit_btnSave_OnAfterClick (Sender: TObject);
// after successful save
var
  tmpSQL: string;
  s: string;
  tmpIDTag: string;
  tmpIDImage: string;
  Tags: array of string;
  i: integer;
begin
  if frmImageEdit.redTags.Tag = 1 then
  begin
    tmpIDImage := IntToStr(frmImageEdit.btnSave.dbGeneralTableId);
    // delete old
    tmpSQL := 'DELETE FROM imageTag WHERE id_image = '+tmpIDImage;
    SQLExecute(tmpSQL);
    // save new
    s := frmImageEdit.redTags.Text;
    s := ReplaceStr(s,',',' ');
    s := ReplaceStr(s,chr(13),' ');
    s := ReplaceStr(s,'  ',' ');
    Tags := SplitString(s,' ');
    for i := 0 to length(Tags) - 1 do
    begin
      s := Trim(Tags[i]);
      if (s <> '') then
      begin
        // use case insensitive search:
        tmpSQL := 'SELECT id FROM tag WHERE upper( name ) = upper("'+s+'")';
        // ID тега
        tmpIDTag := VarToStr( SQLExecute(tmpSQL) );
        // if there is no such tag in the database, then we suggest the user to add it
        if tmpIDTag = '' then
        begin
          // TODO: сделать опцию автоматического добавления, без участия пользователя
          // add new tag
          frmTagEdit.TagString := s;
          frmTagEdit.NewRecord('tag');
          tmpIDTag := VarToStr( SQLExecute('SELECT id FROM tag WHERE upper( name ) = upper("'+s+'")') );
        end;
        if tmpIDTag <> '' then
        begin
          tmpSQL := 'INSERT INTO imageTag (id_image,id_tag) VALUES ( '+tmpIDImage+',  '+tmpIDTag+') ';
          SQLExecute(tmpSQL);
        end;
      end;
    end;
  end;
end;Code language: Delphi (delphi)

…and to restore the appearance of tags from the database when opening the edit form: after all, the database for each image does not store tag texts, but links to them, which allows you to make selections with large data arrays quickly enough, as well as use various logical operations and relevant search.

procedure frmImageEdit_OnShow (Sender: TObject; Action: string);
// show form
var
  s: string;
  tmpSQL: string;
begin
  // load images from base
  ImageEdit_LoadImageFromBase( frmImageEdit.imgImage, frmImageEdit.btnSave.dbGeneralTableId, IE_FT_ORIGINAL, IE_IT_IMAGE);
  ImageEdit_LoadImageFromBase( frmImageEdit.imgPreview, frmImageEdit.btnSave.dbGeneralTableId, IE_FT_SMALL, IE_IT_IMAGE);
  if Action = 'NewRecord' then
  begin
    frmImageEdit.edtName.Text := 'New Image';
    frmImageEdit.redTags.InsertTextEx(' ',$FFFFFF,11,0,'Segoe UI');
  end
  else
  begin
    // create a list of tags
    tmpSQL := ' SELECT GROUP_CONCAT (tag.name,'' '') FROM imageTag LEFT JOIN tag ON tag.id = imageTag.id_tag WHERE imageTag.id_image = '+IntToStr(frmImageEdit.btnSave.dbGeneralTableId);
    s := VarToStr( SQLExecute(tmpSQL) );
    frmImageEdit.redTags.InsertTextEx(s,$FFFFFF,11,0,'Segoe UI');
  end;
  frmImageEdit.redTags.Tag := 0;
end;Code language: Delphi (delphi)

Search system

The search bar is located on the toolbar. It includes a text input field (1), a checker for enabling the search by tags mode (2), a checker for enabling the logical “OR” when searching by tags (3), a button for selecting tags from the directory (4) and, in fact, a search button (5).

procedure frmImage_btnSearch_OnClick (Sender: TObject; var Cancel: boolean);
// building a gallery
var
  tmpSQL: string;
  tmpDataSet: TDataSet;
  tmpTop: integer;
  tmpLeft: integer;
  tmpImage:TdbImage;
  tmpScrollBox: TScrollBox;
begin
  Control_ClearChild( frmImage.panPreview );
  // add scrollbar for vertical scrolling
  tmpScrollBox := TScrollBox.Create(frmImage);
  tmpScrollBox.Parent := frmImage.panPreview;
  tmpScrollBox.Align := alClient;
  tmpScrollBox.Name := 'scbPreview';
  //
  frmImage.panPreview.Tag := 0; 
  tmpTop := 0;
  tmpLeft := 0;
  // building previews on the panel
  if frmImage.edtFilter.Text = '' then
    // all
    tmpSQL := 'SELECT id, name, description FROM image LIMIT 30'
  else
  if not frmImage.chbTag.Checked then
    // by name or by description by entry
    tmpSQL := 'SELECT id, name, description FROM image WHERE (name LIKE "%'+frmImage.edtFilter.Text+'%") OR (description LIKE "%'+frmImage.edtFilter.Text+'%") LIMIT 30'
  else
  if frmImage.chbOR.Checked then
  // "OR", sorted by relevance
    tmpSQL := ' SELECT id, name, description FROM ( SELECT count(image.id) as c, image.id, image.name, image.description FROM tag LEFT JOIN imageTag ON imageTag.id_tag = tag.id LEFT JOIN image ON image.id = imageTag.id_image WHERE tag.checked = 1 AND image.id IS NOT NULL GROUP BY image.id ORDER BY 1 DESC LIMIT 30 )  '
  else
    // strict match "AND"
    tmpSQL := 'SELECT id, name, description FROM ( SELECT count(image.id) as c, image.id, image.name, image.description FROM tag LEFT JOIN imageTag ON imageTag.id_tag = tag.id LEFT JOIN image ON image.id = imageTag.id_image WHERE tag.checked = 1 AND image.id IS NOT NULL GROUP BY image.id ) WHERE c = (SELECT count(*) FROM tag WHERE tag.checked = 1) LIMIT 30';
  SQLQuery(tmpSQL,tmpDataSet);
  try
    while not tmpDataSet.EOF do
    begin
      // add image
      tmpImage := TdbImage.Create(frmImage);
      tmpImage.Parent := tmpScrollBox;
      tmpImage.Top := tmpTop + 2;
      tmpImage.Left := tmpLeft + 2;
      tmpImage.Width := 150;
      tmpImage.Height := 150;
      tmpImage.Proportional := False;
      tmpImage.Stretch := False;
      // assign handlers to buttons
      AssignEvents(tmpImage);
      tmpImage.dbOnClick := 'frmImage_PreviewImage_OnClick';
      tmpImage.dbOnDoubleClick := 'frmImage_PreviewImage_OnDoubleClick';
      // use some image properties to store data
      tmpImage.Tag := tmpDataSet.FieldByName('id').asInteger;
      tmpImage.TagString := tmpDataSet.FieldByName('name').asString;
      tmpImage.Hint := tmpDataSet.FieldByName('description').asString;
      // loading image from file storage
      ImageEdit_LoadImageFromBase( tmpImage, tmpDataSet.FieldByName('id').asInteger, IE_FT_SMALL, IE_IT_IMAGE);
      tmpDataSet.Next;
      // calculate the next position for the image
      tmpLeft := tmpLeft + 154; // grid step
      // if the image goes beyond the width of the panel + the width of the scroll, then
      if frmImage.panPreview.Width < tmpLeft + 154 + 20 then
      begin // move to new row
        tmpLeft := 0;
        tmpTop := tmpTop + 154;
      end;
    end;
  finally
    tmpDataSet.Free;
  end;
end;Code language: Delphi (delphi)

If you type text in the search bar and click the “Search” button, then the data will be selected through an SQL query with text search by occurrence in the fields image.name and image.description.

When the “Tags” checker is activated, the text typed in the search bar is interpreted as a set of tags separated by a space or a comma. The result includes images that have all the specified tags.

If you activate the “Or” checker, the result will include images that have at least one of the searched tags. The images themselves will be sorted in order of relevance (most hits first).

When you click the “Select tags from the directory” button, the tag directory opens, which is also a custom filter.

A column with checkers is used to select tags (1), while tags can be filtered by category (2). To reset all checkers, the “Clear selection” button (3) is provided. And to confirm the choice, the “Select” button (4), when pressed, the selected tags are transferred to the search filter.

The data selection request contains a fixed limit on the number of records, so that building the gallery takes a finite and relatively short time. In the future, it will be necessary to add a special “Show more” button to load the next batch of pictures. The request will be similar, but include the OFFSET command – an offset from the start of the selection.

Pictures on buttons

After some of the main functionality is implemented, you can take a break and think about improving the appearance of the program. For example, replace the standard images on the buttons with more modern ones that match the selected color scheme.

The task is solved in two stages: creating (search) images and loading them into the program. The first step I recommend using a handy and free tool: the Pichon for Windows Image Library, which can be found at https://icons8.com

Since the buttons in the selected theme are displayed in two contrasting colors, we need two sets of images: lighter ones for a dark background and darker ones for a lighter one. Strictly speaking, the choice of color for images is determined by the style: I will use the color of the text with which the labels on the buttons are displayed.

For the TabletDark style, this is white and black. For these purposes, images are well suited for which you can set the color (Material), in particular Material Two Tone – these images, in addition to the main tone, contain halftones – translucent areas of images.

The images themselves can be stored in a special graphics.dll file, which is automatically generated and contains all the graphics data used in form design mode. This method is well described in the book Modern UI. But this time, the images will be loaded at the moment the application is launched from a special folder, which is more in line with the principles of styling the program. I note that the style of the final application depends on the presence and content of the style.vsf file, that is, by changing this file, you can change the style of the application without recompiling it. The same can be done with button images by simply replacing the files in the images folder.

Virtual Class Images

To implement all the functions of working with images, let’s create a virtual class and a set of scripts. The purpose of the class is to work with sets of images that will be loaded into the program at the time the application is launched.

The class is implemented through abstract methods (without creating an instance of the class), there are only four of them:

  • Images_ButtonsAssign() – Assigning images to form buttons
  • Images_Init() – Data initialization
  • Images_Load() – Loading images
  • Images_Set – Assign image indexes to the button by file name

Data is stored in global variables, in arrays of various types. If MVDB supported the full syntax of the Pascal language, then instead of this zoo we would use structured data (Record), but, as they say, what is, is.

const
  // folders are relative to the application folder
  IMAGES_DIR = '\Images\';   // the folder that contains the images
 
  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 on hover
  IMAGES_DISABLE_SUFFIX = '_D'; // suffix in the name for the disabled button image
  IMAGES_PRESSED_SUFFIX = '_P'; // suffix in the name for the image of the pressed button
 
  IMAGES_FORMAT_COUNT = 2; // how many formats are in the library
  // list of formats
  IMAGES_FORMAT_BUTTON = 0; // 
  IMAGES_FORMAT_BIGBUTTON = 1; //   
  //
  // button image upload models
  IMAGES_SIMPLE_MODEL = 1; // simple
  IMAGES_FULL_MODEL = 2; // full
 
var
  ImagesDir: array of string; // storage folders
  ImagesSize: array of integer; // image sizes
  ImagesList: array of TImageList; // image storage
  ImagesNames: array of TStringList; // uploaded image namesCode language: Delphi (delphi)

Data initialization is performed when the application starts. It includes setting up folders for uploading images and their size. The peculiarity of using the TImageList class for storing images is that it can store images of the same size. Therefore, each size will require separate storage.

procedure Images_Init;
// init data
var
  i: integer;
begin
  // init arrays
  SetLength(ImagesDir,IMAGES_FORMAT_COUNT);
  SetLength(ImagesSize,IMAGES_FORMAT_COUNT);
  SetLength(ImagesList,IMAGES_FORMAT_COUNT);
  SetLength(ImagesNames,IMAGES_FORMAT_COUNT);
  // project setup
  ImagesDir[IMAGES_FORMAT_BUTTON] := IMAGES_DIR + 'Buttons\';
  ImagesSize[IMAGES_FORMAT_BUTTON] := 32;
  ImagesDir[IMAGES_FORMAT_BIGBUTTON] := IMAGES_DIR + 'BigButtons\';
  ImagesSize[IMAGES_FORMAT_BIGBUTTON] := 48;
  // load data
  for i := 0 to IMAGES_FORMAT_COUNT-1 do
  begin
    ImagesNames[i] := TStringList.Create;
    ImagesList[i] := TImageList.Create(MainForm);
    Images_Load(i); // load image
  end;
  Images_ButtonsAssign( IMAGES_FORMAT_BUTTON,'','frmShow' );
  Images_ButtonsAssign( IMAGES_FORMAT_BIGBUTTON,'frmShow','' );
end;Code language: PHP (php)

Initialization includes not only setting arrays for a specific project, but also loading data from disk, as well as automatically assigning images to buttons.

procedure Images_Load( AIndex:integer  );
// loading pictures
// made by a procedure with parameters, since the application can have several image sizes,
// which should be stored in different lists; png images
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;
    // размер картинок
    tmpImageList.Width := ImagesSize[AIndex];
    tmpImageList.Height := ImagesSize[AIndex];
    tmpList := TStringList.Create;
    try
      tmpList.Text := GetFilesList( tmpImageDir );
      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;Code language: Delphi (delphi)

Pictures must be in PNG format. Their names must follow certain rules: suffixes in the file names are used to designate images of various button states.

  • _S(elected) – The selected button. The selected button is the button with the focus, the focus is on the button after pressing or using the Tab
  • _H(ot) – Button with the cursor over it. On hover, if the button is available.
  • _D(isabled) – Disabled button 
  • _P(ressed) – The pressed button. When pressed, after releasing the button becomes selected

The full model uses five images for one button. In the simplified model, there are only two.

Images are assigned according to certain rules: the button name must include a three-letter prefix (class name) and the button name. If there is a picture with the same name, then the image array is bound to this button and the corresponding indexes are assigned. The assignment goes through all forms, but you can set a white and black list of forms. Thus, the maximum universality of the code is achieved.

procedure Images_ButtonsAssign( AIndex: integer; AWhiteList: string; ABlackList:string );
// assigning images to buttons on forms
// AIndex - Image format
// AWhiteList - whitelist, modify only specified forms
// ABlackList - blacklist, exclude forms
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 :=  DeleteClassName(tmpButton.Name);
        Images_Set( tmpButton, tmpName, AIndex, IMAGES_SIMPLE_MODEL );
      end;
    end;
  end;
end;Code language: Delphi (delphi)

The last method is assigning a picture by its name. It may be required not only when initializing the buttons, but also during the operation of the application in order to quickly change the images on the button.

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;
begin
  AImageName := UpperCase(AImageName);
  if AImageName <> '' then
  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;Code language: Delphi (delphi)

As a result, the application has got stylish pictures on the buttons:

Links

One thought on “Family album”

Leave a Reply

Your email address will not be published. Required fields are marked *