Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Delphi Cookbook

You're reading from   Delphi Cookbook Recipes to master Delphi for IoT integrations, cross-platform, mobile and server-side development

Arrow left icon
Product type Paperback
Published in Jul 2018
Publisher
ISBN-13 9781788621304
Length 668 pages
Edition 3rd Edition
Languages
Tools
Arrow right icon
Authors (2):
Arrow left icon
Daniele Teti Daniele Teti
Author Profile Icon Daniele Teti
Daniele Teti
Daniele Spinetti Daniele Spinetti
Author Profile Icon Daniele Spinetti
Daniele Spinetti
Arrow right icon
View More author details
Toc

Table of Contents (12) Chapters Close

Preface 1. Delphi Basics FREE CHAPTER 2. Becoming a Delphi Language Ninja 3. Knowing Your Friends – The Delphi RTL 4. Going Cross-Platform with FireMonkey 5. The Thousand Faces of Multithreading 6. Putting Delphi on the Server 7. Linux Development 8. Riding the Mobile Revolution with FireMonkey 9. Using specific platform features 10. Delphi and IoT 11. Other Books You May Enjoy

Making an owner-draw control aware of the VCL styles

Owner-draw controls are powerful. They allow you to completely adapt your GUI to the needs of your users and potentially enable your application to display data in a more familiar way. In the end, owner-draw controls improve the user's experience with the application. However, owner-draw controls do not always fit in well with the VCL custom styles. Why? Because if you try to draw something by yourself, you could be tempted to use a fixed color, such clRed or clYellow, or you could be tempted to use the operating system color, such as clBtnFace or clWindow. In doing so, your owner-draw controls will not be style aware and will be drawn in the same way regardless of the current VCL style. In this recipe, you'll learn how to make custom graphics that remain relevant to the selected VCL style.

Getting ready

Let's say you are in charge of developing a control panel for a hotel's lighting system. You have a list of lamps to power on and you, using some hardware, have to power on some lamps by clicking on a button. Customers tell you that buttons should show some additional information about the lamp, for example:

  • Served zone (corridor, hall, room number, and so on)
  • State (on/off, using some fancy graphics)
  • The time the lamp was powered on
  • The time when electrical problems were detected, and a red icon to indicate that the lamp is off even when a current supplies the line, so the circuit is interrupted somewhere
  • Other custom information not currently known, such as small graphs showing lamp state history during the last 24 hours

The question is how to implement this kind of UI. One of the possible ways is to use TDrawGrid and draw all the required details in each cell, using the cell as a button. Using TDrawGrid, you get a grid of buttons for free. You have also the greatest flexibility in terms of the information displayed because you are using the TCanvas method to custom draw each cell. This is quite a popular solution for this kind of non-standard UI.

However, when you deploy this application, the customers ask about the possibility of changing the style of the application to fit the needs of the current user. So you think about VCL styles, and you are right. However, the graphics drawn into the cells don't follow the currently selected VCL style, and your beautiful application becomes a bad mix of colors. In other words, when users change the selected VCL style, all the controls reflect the new style, but the owner-drawn grid, which is unaware to the selected style, doesn't look as nice as the rest of the UI. How do you solve this problem? How do you draw custom graphics adhering to the selected VCL style? In this recipe, you'll learn how to do it using the lamp control grid example.

How it works...

At design time, the form looks like the one shown in the following screenshot:

Figure 1.8: The form as it looks at design time

When the form is created, the list of available styles is loaded in the Radio group using code similar to the following:

  RadioGroup1.Items.Clear; 
  RadioGroup1.Columns := Length(TStyleManager.StyleNames); 
  for LStyleName in TStyleManager.StyleNames do 
    RadioGroup1.Items.Add(LStyleName); 
  RadioGroup1.ItemIndex := 0; 
  TStyleManager.SetStyle('Windows'); 

Then, a list of TLampInfo objects is created and initialized using the information contained in the Zones array. After that, the draw grid is initialized according to the LAMPS_FOR_EACH_ROW constant. Here's the relevant code:

  FLamps := TObjectList<TLampInfo>.Create(True); 
  for I := 1 to LAMPS_FOR_EACH_ROW * 4 do 
  begin 
    FLamps.Add(TLampInfo.Create(Zones[I])); 
  end; 
 
  DrawGrid1.DefaultColWidth := 128; 
  DrawGrid1.DefaultRowHeight := 64; 
  DrawGrid1.ColCount := LAMPS_FOR_EACH_ROW; 
  DrawGrid1.RowCount := FLamps.Count div LAMPS_FOR_EACH_ROW; 

The FormCreate event handler initializes the styles list and the list of lamps (the model) on the form. Now, we'll see how the other event handlers will use them.

The TDrawGrid OnSelectCell event, as the name suggests, is used to address the current lamp from FLamps and to toggle its state. That's it. If the lamp is on, then the lamp will be powered down, otherwise the lamp will be powered on. After that, the code forces the grid to redraw using the Invalidate method:

procedure TMainForm.DrawGrid1SelectCell(Sender: TObject; ACol,
ARow: Integer; var CanSelect: Boolean); begin FLamps[ACol + ARow * LAMPS_FOR_EACH_ROW].ToggleState; DrawGrid1.Invalidate; end;

Now, really interesting things happened in the DrawThemed method called inside the TDrawGridOnDrawCell event. This method receives information about the coordinates of the cell to draw, and then it draws a button on the canvas using the information contained in the corresponding TLampInfo instance. The code is quite long, but an interesting concept is that no specific colors are used. When it is necessary to draw something, the code asks StyleService to get the correct color according to the current style. This approach is also used for font color and for system colors. Here's a handy table that summarizes these concepts:

Method name

Description

StyleServices.GetStyleColor(Color: TStyleColor)

Returns the color defined in the style for the element specified by Color

StyleServices.StyleFontColor(Font: TStyleFont)

Returns the font color for the element specified by Font

StyleServices.GetSystemColor(Color: TColor)

Returns the system color defined in the current style

So, when we have to highlight the (pseudo) button if there are electrical problems on the power line, we use the following code:

if LLamp.ThereAreElectricalProblems then 
   LCanvas.Brush.Color := StyleServices.GetStyleColor(scButtonHot) 
else 
   LCanvas.Brush.Color := StyleServices.GetStyleColor(scWindow); 
LCanvas.FillRect(LRect); 

When we've got to draw normal text, we use the following code:

LCanvas.Font.Color := 
StyleServices.GetStyleFontColor(sfButtonTextNormal); LCanvas.TextRect(LRect, LValue, [TTextFormats.tfCenter, TTextFormats.tfVerticalCenter]);

It is clear that the paradigm is this:

  1. Get the current color for the selected element of the UI according to the style
  2. Draw the graphics using that color

Clicking on the Simulate Problems button, it is possible to see how the graphics are drawn in the case of problems on the power line. The images are drawn directly from the image list using the following code:

procedure TMainForm.DrawImageOnCanvas(ACanvas: TCanvas; 
var ARect: TRect; ImageIndex: Integer); begin ImageList1.Draw(ACanvas, ARect.Left + 4,
ARect.Top + ((ARect.Bottom - ARect.Top) div 2) - 16,
ImageIndex); end;

Using this approach, the application created in this recipe, which has a lot of custom graphics, behaves very well even for VCL styles. Here are some screenshots:

Figure 1.9: The application while it is using the Windows style
Figure 1.10: The application while it is using the Luna style
Figure 1.11: The application while it is using the Charcoal Dark Slate style

As you can see, the application correctly draws the owner-draw parts of the UI using the right colors from the selected style.

There's more...

The VCL style infrastructure is very powerful. In the case of TWinControl descendants, you can even define specific hooks for your components using TStyleHook. TStyleHook is a class that handles messages for controls acting as a wrapper for the hooked control. If you have a custom control that you want to be style enabled, inherit from TStyleHook and provide custom processing for that control. For examples, see TEditStyleHook and TComboBoxStyleHook. You need to register the style hook class with the style engine using the RegisterStyleHook method, as shown in the following code:

TCustomStyleEngine.RegisterStyleHook(TCustomEdit, TEditStyleHook); 

Moreover, the StyleServices function returns an instance of TCustomStyleServices, which provides a lot of customization methods related to VCL styles. Check out the related documentation at http://docwiki.embarcadero.com/Libraries/en/Vcl.Themes.TCustomStyleServices_Methods to see all the possibilities.

You have been reading a chapter from
Delphi Cookbook - Third Edition
Published in: Jul 2018
Publisher:
ISBN-13: 9781788621304
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime
Banner background image