Book Image

Delphi Cookbook - Second Edition

By : Daniele Teti
Book Image

Delphi Cookbook - Second Edition

By: Daniele Teti

Overview of this book

Delphi is a cross-platform Integrated Development Environment (IDE) that supports rapid application development for Microsoft Windows, Apple Mac OS X, Google Android, and Apple iOS. It helps you to concentrate on the real business and save yourself the pain of wandering amid GUI widget details, or having to tackle inter-platform incompatibilities. It also has a wide range of drag-and-drop controls, helping you code your business logic into your business model, and it compiles natively for desktop and mobile platforms. This book will teach you how to design and develop applications, deploy them on the cloud platform, and distribute them within an organization via Google Play and other similar platforms. You will begin with the basics of Delphi and get acquainted with JSON format strings, XSLT transformations, unicode encodings and various types of streams. We then move on to more advanced topics such as developing higher-order functions and using enumerators and RTTI. You will get an understanding of how Delphi RTL functions and how to use FireMonkey in a VCL application. We will then cover topics such as multithreading, using the parallel programming library and putting Delphi on a server. We will also take a look at the new feature of WebBroker Apache modules and then ride the mobile revolution with FireMonkey. By the end of the book, you will be able to develop and deploy cross-platform applications using Delphi .
Table of Contents (15 chapters)
Delphi Cookbook Second Edition
Credits
About the Author
About the Reviewer
www.PacktPub.com
Preface
Index

Making an owner draw control aware of the VCL styles


Owner draw controls are powerful. They allow you to completely tune your GUI for 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 experience with your application. However, owner draw controls do not always fit 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. Doing so, your owner draw controls will be not 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 remaining being in topic with the selected VCL style.

Getting ready

Let's say you are in charge of developing a controller panel for a hotel's light 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 have been detected, showing a red icon to indicate that the lamp is off even when 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 needed details in each cell, using the cell also as a button. Using TDrawGrid, you have a grid of buttons for free. You have also the greatest flexibility about 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 to solve this problem? How to draw custom graphics by 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 5.1 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 one:

  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 the TLampInfo object 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 the lamps (the model) of 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 the FLamps and to toggle its state. That's it. If the lamp is on, then the lamp will be powered down, else 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 TDrawGrid OnDrawCell 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 correspond 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 will 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 will 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:

  • Get the current color for the selected element of the UI according to the style

  • Draw the graphics using that color

Clicking on the Simulate Problems button, it is possible to see how the graphics is 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 on VCL styles. Here are some screenshots:

Fig. 5.2 The application while it is using the Windows style

Fig. 5.3 The application while it is using the Luna style

Fig. 5.4 The application while it is using the Charcoal Dark Slate style

As you 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 you 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. As 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 the VCL styles. Check out the related documentation at http://docwiki.embarcadero.com/Libraries/en/Vcl.Themes.TCustomStyleServices_Methods to see all the possibilities