FireMonkey Component Design
Go Up to FireMonkey Components Guide
This topic discusses how to design components using FireMonkey.
A Tour of Selected Built-In Controls
An examination of a few of the FireMonkey controls demonstrates a range of designs in FireMonkey.
TPanel: Styling with Primitives
The source code for TPanel is short enough to be reproduced in its entirety here, both the interface:
TPanel = class(TStyledControl) public constructor Create(AOwner: TComponent); override; published property StyleLookup; end;
and the implementation:
constructor TPanel.Create(AOwner: TComponent); begin inherited; Width := 120; Height := 100; end;
TPanel subclasses TStyledControl, the base class for all user-level controls. TPanel publishes the StyleLookup property, which contains the name of the style to lookup. The constructor sets the Width and Height, properties of TControl. But how does this control render itself?
One of FireMonkey's key concepts is the use of styles. The default style for TPanel on Windows can be seen by saving to a .style file and is roughly as follows:
object TRectangle StyleName = 'panelstyle' Width = 50 Height = 50 HitTest = False Fill.Color = $FFffFFff Stroke.Color = $FF8e8e8e end
TPanel is a solid white rectangle with a grey border. (On the Mac, the fill is a gradient.)
As a styled control, TStyledControl contains an ResourceLink property that points to a TFmxObject. ResourceLink is a clone of the control's style-resource, either one found by name (specified in the control's StyleLookup property to match the resource's StyleName property) or by default. This resource-link object is also inserted as the first child of the control, when the control is first loaded or whenever its StyleLookup property is changed. This is done in TStyledControl.ApplyStyleLookup.
So when, as a TControl, the control paints itself, it paints its children. The first child is what the control itself looks like, and then the other "real" children (if any) are painted.
TCalloutPanel: Style Contracts
object TCalloutRectangle StyleName = 'calloutpanelstyle' Width = 50 Height = 50 HitTest = False Fill.Color = $FFffFFff Stroke.Color = $FF8e8e8e end
Instead of a TRectangle, the top level style is a TCalloutRectangle, which is a rectangle with a triangular peak on one side. The primitive has the exact same additional properties for the call-out as the styled control, which it uses to draw that triangle. The mapping is performed by the ApplyStyle method:
procedure TCalloutPanel.ApplyStyle; var Back: TFmxObject; begin inherited; Back := FindStyleResource('Background'); if (Back = nil) and (ResourceLink is TCalloutRectangle) then Back := ResourceLink; if (Back <> nil) and (Back is TCalloutRectangle) then begin TCalloutRectangle(Back).CalloutWidth := FCalloutWidth; TCalloutRectangle(Back).CalloutLength := FCalloutLength; TCalloutRectangle(Back).CalloutPosition := FCalloutPosition; TCalloutRectangle(Back).CalloutOffset := FCalloutOffset; end; end;
- the root object is a TCalloutRectangle, which is true for the default style; or
- somewhere in the component tree for the style is an object with the StyleName "Background" that is a TCalloutRectangle.
If the proper object cannot be found, TCalloutPanel does not work: the extra properties it declares have no effect.
This is an example of a style contract in which the components used to embody a style for a control must conform to certain expectations. If you develop a component that requires a specific style contract like this, then it is important that this contract be documented to other users of your component so that they can develop proper styles for it.
TCalendar: Constructed Complexity
TCalendar has no default style. Instead it builds itself in its constructor (which unlike TPanel, is far too long to include here). In effect, TCalendar creates the following component tree:
- Top-most TLayout
- Three left-pinned TButton controls using the style "transparentcirclebuttonstyle", for...
- TPopupBox for the month using style "labelstyle", filled with strings for the months of the year, set to fill the remaining client area not occupied by those three buttons and...
- TPopupBox for the year using style "labelstyle", filled with strings for the years from ten years in the past to ten years in the future, right-pinned
- Top-pinned TGridLayout
- Seven TLabel controls for the days of the week (Sunday, Monday, etc.)
- Left-most TGridLayout, initially invisible, sized for a single column
- Six TLabel controls for week numbers, shown by the WeekNumbers property
- Top-pinned 7-Columns TListBox using style 'transparentlistboxstyle' and AlternatingRowBackground
- Forty-two TListBoxItem controls, which end up in six rows for six weeks
Every object in the component tree has its Stored property set to False and its Locked property set to True. Disabling Stored prevents the object from being streamed out to the
.fmx file by the Form Designer. If the Stored property is not disabled, subcomponents would be redundantly created when loading. Enabling Locked changes the way hit testing works and triggers fire, so that the subcomponent is part of the larger whole.
In TCalendar, the relative position of all the components is hard-coded, but their appearance is almost completely controlled by styles. The exceptions are the two triangles and circle inside the three buttons in the top-left. The buttons themselves are styled. The month and year popups to the right have their default style overridden to look like labels (instead of a button) with the default label style. The days of the week and week numbers are instances of TLabel and use that label style automatically. The multi-column listbox used to display the days of the month is styled. Finally, the individual day controls will adopt the default style for list box items.
Even though it has no default style, what happens if the Resource property of a TCalendar is set? As always, the resulting style-resource will be set as the first child of the control, causing it to be rendered as its background. So TCalendar as whole can be styled to a degree.
To summarize, when creating FireMonkey components:
- Use FireMonkey styles, so that the look can be altered by application developers.
- Encapsulate any direct painting with a primitive control, which can be swapped out or subclassed.
- Style contracts must be documented.
- Components of interest are most easily found by their StyleName, so set the StyleName appropriately.
- Complex controls can be built through code with styled components.