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:
- 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 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.