Category: Styles

News and information about FireMonkey styles

Adding Images to a FireMonkey TreeView

If you’re familiar with the TreeView in VCL you’ll probably know that it’s possible to add an image to each item, but that that behaviour isn’t supported out of the box in FireMonkey. So, lets add the support.

There are a number of ways this could be done, but the simplest is probably by adding a TImage to the style for TTreeViewItem. A TImage contains a TBitmap which has a StyleLookup property. This property is a string which takes the StyleName of another element style element - for another TImage. What we’ll do then is simply add any images to be used to the style, and point the TImage in the style of each TreeViewItem to the one we want to use.

The Style
Start by copying the style for a TTreeViewItem (TreeViewItemStyle) and call it TreeViewImageItemStyle. The TImage needs to go in the same TLayout as the checkbox, but we want to add it inside another TLayout - this means the item can be resized while we keep the TImage at the same size.

(To copy the style element in the IDE, double click on the TTreeView in the form designer and add an item. Back out, then right click on the item and select Edit Custom Style. Make the changes and click Apply and Close).

So, add the TLayout and set the properties, Align := alLeft and Width := 20.

And add the TImage under it and set the properties, StyleName := ‘image’, Align := alCentre, WrapMode := iwStretch, HitTest := False and Height and Width := 16.

The Class

Now we need to create a custom TreeViewItem class, which we’ll call TTreeViewImageItem (note our style is TreeViewImageItemStyle). Here’s our class definition:

type TTreeViewImageItem = class(TTreeViewItem)
  private
    
FImageTImage;
    
FShowImageBoolean;
    
FImageStyleLookupString;
    
procedure SetShowImage(const ValueBoolean);
    
procedure SetImageStyleLookup(const ValueString);
  protected
    
procedure ApplyStyle;override;
    
procedure FreeStyle;override;
  public
    
constructor Create(AOwnerTComponent);override;
  
published
    property ImageStyleLookup
String read FImageStyleLookup write SetImageStyleLookup;
    
property ShowImageBoolean read FShowImage write SetShowImage default True;
  
end


The property ImageStyleLookup takes the name of the style element to show as an image.

ApplyStyle and FreeStyle are pretty standard, simply fetching or nilling FImage and passing across the ImageStyleLookup.

procedure TTreeViewImageItem.ApplyStyle;
var 
OTFMXObject;
begin
  inherited
;

  
:= FindStyleResource('image');
  if 
O is TImage then
  begin
    FImage 
:= TImage(O);
    
FImage.Visible := ShowImage;
    
FImage.Bitmap.StyleLookup := FImageStyleLookup;
  
end;
end;

procedure TTreeViewImageItem.FreeStyle;
begin
  inherited
;
  
FImage := nil;
end

Also simple are the two property setters,

procedure TTreeViewImageItem.SetImageStyleLookup(const ValueString);
var 
OTFMXObject;
begin
  FImageStyleLookup 
:= Value;
  if 
Assigned(FImagethen
    FImage
.Bitmap.StyleLookup := Value;
end;

procedure TTreeViewImageItem.SetShowImage(const ValueBoolean);
begin
  FShowImage 
:= Value;
  if 
Assigned(FImagethen
    FImage
.Visible := Value;
end

Sample Application

Now we simply need to create something to test it all. Start by adding two images to the style with StyleNames of Image1 and Image2. For my test I’ve use thumbs up and thumbs down graphics.

And our test project simply has a TTreeView and a button. The button adds a new item as the child of the selected item, and sets the image to thumbs up if the index of the item is positive and thumbs down if it is negative,

procedure TForm1.Button1Click(SenderTObject);
var 
ItemTTreeViewImageItem;
begin
  Item 
:= TTreeViewImageItem.Create(Self);
  
inc(Index);
  
Item.Text := 'Item'+IntToStr(Index);
  if (
Index mod 2) = 1 then
    Item
.ImageStyleLookup := 'Image1'
  
else
    
Item.ImageStyleLookup := 'Image2';

  if 
TreeView1.Selected <> nil then
  begin
    Item
.Parent := TreeView1.Selected;
    
TreeView1.Selected.IsExpanded := True;
  
end
  
else
    
Item.Parent := TreeView1;
end

Download Project ZIP file

Enjoy.

Full source:

unit TreeViewImage;

interface
uses FMX.TreeViewFMX.ObjectsFMX.TypesClasses;

type TTreeViewImageItem = class(TTreeViewItem)
  private
    
FImageTImage;
    
FShowImageBoolean;
    
FImageStyleLookupString;
    
procedure SetShowImage(const ValueBoolean);
    
procedure SetImageStyleLookup(const ValueString);
  protected
    
procedure ApplyStyle;override;
    
procedure FreeStyle;override;
  public
    
constructor Create(AOwnerTComponent);override;
  
published
    property ImageStyleLookup
String read FImageStyleLookup write SetImageStyleLookup;
    
property ShowImageBoolean read FShowImage write SetShowImage default True;
  
end;

implementation

{ TTreeViewImageItem }

procedure TTreeViewImageItem
.ApplyStyle;
var 
OTFMXObject;
begin
  inherited
;

  
:= FindStyleResource('image');
  if 
O is TImage then
  begin
    FImage 
:= TImage(O);
    
FImage.Visible := ShowImage;
    
FImage.Bitmap.StyleLookup := FImageStyleLookup;
  
end;
end;

constructor TTreeViewImageItem.Create(AOwnerTComponent);
begin
  inherited
;
  
ShowImage := True;
end;

procedure TTreeViewImageItem.FreeStyle;
begin
  inherited
;
  
FImage := nil;
end;

procedure TTreeViewImageItem.SetImageStyleLookup(const ValueString);
var 
OTFMXObject;
begin
  FImageStyleLookup 
:= Value;
  if 
Assigned(FImagethen
    FImage
.Bitmap.StyleLookup := Value;
end;

procedure TTreeViewImageItem.SetShowImage(const ValueBoolean);
begin
  FShowImage 
:= Value;
  if 
Assigned(FImagethen
    FImage
.Visible := Value;
end;

end

How do you Change the Color of a Panel in FireMonkey?

The above is a question which comes up frequently from newbies to FireMonkey. It’s an important question, and answering it opens the door to learning a lot of the power of FireMonkey.

The Short Answer
The short answer is: In FireMonkey the appearance of a control comes from the style. So, to change the appearance you need to change the style.

The Long Answer
Let’s look at how to do that in detail. Fire up XE3, start a new project and add two TPanels to the form.

Right click on a panel and select Edit Default Style.

The Structure Pane shows the style for the panel, ‘panelstyle’. Select it to edit it.

Now make some edits, in my case I’ve changed the Fill.Kind property to bkGradient and the Stroke.Thickness and Stroke.Dash properties.

Click Apply and Close to return to the form.

Note that the IDE has made a few changes for you here, it has:
* Added a TStyleBook to the form (StyleBook1).
* Set the form’s StyleBook property to StyleBook1.

The StyleBook contains the styles which have been modified for this form. In this case we have modified the default style for a TPanel. In other words we have added a style for a panel to the stylebook and this has overridden the default style of the app (note how both panels have changed to the new style).

Custom Styles

But suppose you only want to change the style for just one or two panels instead of all of them. This time, right click on a panel an select Edit Custom Style.

Notice now that the IDE has created a custom style for us, panel1style1.

Let’s make some more changes and again click Apply and Close. I’ve now changed the gradient to some funky colors and set the Stroke.Kind to bkNone.

Look at the properties for the panel and you’ll note that the StyleLookup is set to Panel1Style1, the name of the custom style the IDE created for us.

Changing the StyleLookup property tells FireMonkey to look for a style other than the default one. Now set the StyleLookup for the other panel to Panel1Style1 and you’ll see both panels using the same custom style.

You can use the same process to create as many different panel styles as you want, or to change the styles for other classes of controls.

Note: If you’re using XE2, some of the property names of the TRectangle will be slightly different, but otherwise everything works the same.

Resizing a FireMonkey Combo Box to Fit it’s Contents

MonkeyStyler displays two combo boxes on it’s toolbar, one to list the currently opened files and a second to list the elements in the current file. A number of users mentioned that long items in the combo box would be cropped and sometimes difficult to identify.

Creating a routine to resize a combo box made an interesting little challenge. We need to find the length of the longest item, but this needs to take account of the current font settings, and there’s no convenient ‘TextWidth’ function in FireMonkey. But we cab do it using a TText object.

I started by creating a TText object with AutoSize set to True and WordWrap set to False.

:= TText.Create(nil);
    
T.AutoSize := True;
    
T.WordWrap := False


Next we need to get the font used. In FireMonkey a TComboBox owns a TListBox which is used for the dropdown and the combo box copies the contents of the selected item into itself. So we really need the font of a TListBoxItem. I chose to use the font assigned to an actual list box item of the component:

if Items.Count 0 then
    begin
      Item 
:= ListBox.ListItems[0];
      
Item.ApplyStyleLookup;
      
:= Item.FindStyleResource('text');
      if 
O is TText then
        T
.Font.Assign(TText(O).Font);
            ... 


Note the call to ApplyStyleLookup which we need in case the item doesn’t currently have it’s styling applied. This could be because it is newly created or the styling has been removed - in FM2 the stying of non-visible controls is removed to save resources.

Next up is to iterate over the strings in Items and get the width of the widest,

for S in Items do
      
begin
        T
.Text := S;
        
//Bug in XE3: Assigning Text doesn't resize a TText, so hack around it.
        
T.AutoSize := False;
        
T.AutoSize := True;
        if 
<> nil then
          NewWidth 
:= T.Width+TControl(O).Padding.Left+TControl(O).Padding.Right;
        if 
NewWidth MinWidth then
          MinWidth 
:= NewWidth;
      
end


In the initial release of XE3 there is a bug in TText such that it doesn’t resize when you set the Text property, so we do a little dance with AutoSize to force an update. Also note we’re taking account of the Padding of the ‘text’ resource.

And finally we need to assign the new width, but since there is other stuff in the style besides the text we don’t know exactly what that is (especially if the style later changes). So, we calculate the width of the extra stuff with the current settings (Width-TControl(O).Width) and add out new width for the text,

:= FindStyleResource('content');
    if 
O is TControl then
      Width 
:= Width-TControl(O).Width+MinWidth

Putting it together, here is the complete code:

type TSolentComboBox = class(TComboBox)
  public
    
procedure ResizeToContents(MinWidthSingle);
  
end;
    
...

procedure TSolentComboBox.ResizeToContents(MinWidthSingle);
var
  
TTText;
  
NewWidthSingle;
  
OTFMXObject;
  
SString;
  
ItemTListBoxItem;
begin
  
try
    
:= TText.Create(nil);
    
T.AutoSize := True;
    
T.WordWrap := False;
    
:= nil;
    if 
Items.Count 0 then
    begin
      Item 
:= ListBox.ListItems[0];
      
Item.ApplyStyleLookup;
      
:= Item.FindStyleResource('text');
      if 
O is TText then
        T
.Font.Assign(TText(O).Font);
      
NewWidth := 0;
      for 
S in Items do
      
begin
        T
.Text := S;
        
//Bug in XE3: Assigning Text doesn't resize a TText, so hack around it.
        
T.AutoSize := False;
        
T.AutoSize := True;
        if 
<> nil then
          NewWidth 
:= T.Width+TControl(O).Padding.Left+TControl(O).Padding.Right;
        if 
NewWidth MinWidth then
          MinWidth 
:= NewWidth;
      
end;
    
end;

    
:= FindStyleResource('content');
    if 
O is TControl then
      Width 
:= Width-TControl(O).Width+MinWidth;

  finally
    
T.Free;
  
end;
end

ApplyStyle and FreeStyle in FireMonkey

If you’re writing custom controls for FireMonkey you’re probably overriding ApplyStyle with something like this:

procedure TWidget.ApplyStyle;
var 
TTFMXObject;
begin
    inherited
;
    
:= FindStyleResource('text');
    if 
T is TText then
        FText 
:= TText(T);
end

And so you should be. That’s entirely correct practice. You look for a component of the style and cache it for later re-use within your component.

procedure TWidget.SetText(const ValueString);
begin
    
if FText <> nil then
        FText
.Text := Value;
end

But as I was updating MonkeyStyler to run under XE3/FM2 I was getting some really obscure errors from my custom grid cells. The cached components would occasionally be junk, as though they had been free-ed behind my back. The errors always occured in the final cell in a row and I at first assumed there was some really odd off by one bug in FM2.

After much investigation I noticed that ApplyStyle was being called on cells which had long been created. But why would ApplyStyle be called more than once on a component?

It turns out that FM2 is being more conservative with resources and freeing style objects which are no longer needed to save memory. If a component disappears from view the library will call TStyleControl.Disappear which in turn calls the virtual TStyleControl.FreeStyle. I’ll admit I had’t even noticed these methods before.

The correct behaviour for any component which caches style resources in it’s ApplyStyle method is, therefore, to override FreeStyle and release them:

procedure TWidget.FreeStyle;
begin
    inherited
;
    
FText := nil;
end

Once I added that to MonkeyStyler’s custom cells all ran smoothly again.

Enjoy.

An XE2/FireMonkey Showcase

It’s now almost exactly twelve months since XE2 was released and with XE3 just around the corner it seems like a good time to look back on what people have achieved with the first generation of FireMonkey. Here are a selection of videos from around the internet mostly of commercial products developed in FireMonkey.

If you are developing something yourself or know of a product or demonstration I have missed please let me know. If there’s an interesting video available I’ll add it to a future post.

Crossroad task manager
A task manager, shown here running on a Mac. Sadly I can’t find a link to any kind of product page.

An SVG demo
A demonstration of the TSVG component from Apesuite for adding more advanced vector graphics than the built in TPath can manage. No video for this one, but you can follow the link to download the executable.

Exosphere
A pre-alpha version of an innovative piece of animation software with live editing of the animations. This was inspired by the classic video by Bret Victor (at about 30 minutes in) (BTW if you’re a programmer and you haven’t watched that video all the way through, you really need to).

The author writes that it only took about seven hours work to get to this stage, with most of that time spent on data formats etc. Again no link to a product page.

Multitrack Studio
This is music sequencing software which the author(s) converted to run on the Mac using FireMonkey. The video is a demonstration of the Mac OS X version. Product page

 

Erply
A retail POS system written in FireMonkey. Sadly the video doesn’t showcase FireMonkey as much as I’d like. Product page

 

2RemindMe
I’m not sure if this is a separate product or just a rebranding of Crossroads described above. Either way, here it is being demonstrated on an iPad. [url=http://2complicated.com/2remindme/]Product page

 

TuneMyApps
From the same author as 2RemindMe, this is an iPad app for analysing iTunes sales info. Product page

 

10 Things Every FireMonkey Developer Should Know About Styles

A recent Embarcadero webinar video by Eugene Kryukov (FireMonkey designer) and Vsevolod Leonov (FireMonkey evangelist) gives a really good, in depth look at FireMonkey styles and how they work. Here are some tips I distilled down from that video along with a few things I’ve learn myself.

1. How a style name is constructed

You’ll want to know this if you’re creating custom controls. If you don’t explicitly state a style name for your control (and you probably don’t want to) FireMonkey will search for a style based on te class name of your control - it removes the preceding ‘T’ and appends ‘Style’ to the end.

So, a TButton uses a style ‘ButtonStyle’. A TEdit, ‘EditStyle’, a TCalender ‘CalendarStyle’ and so on.

2. Style names can also be inherited

But if you’re subclassing a component you may not want to have to create a style for it if you haven’t modified anything which alters the styling. And if that’s the case you don’t need to.

If you create a component:

type TEditChild = class(TEdit); 

FireMonkey will first look for the style ‘EditChildStyle’ (as we saw above). If that doesn’t exist it will look for the style for the parent class, i.e, it will look for ‘EditStyle’ and apply that.

But, this doesn’t apply to grandchildren. If you then create:

type TEditGrandchild = class(TEditChild); 

FireMonkey will look for ‘EditGrandchildStyle’, then ‘EditChildStyle’, but it will stop there and not go any further up the component tree to ‘EditStyle’.

3. Loading default styles

The basic look of your application comes from the default style. If you want to change this you need to set the StyleFileName property of your forms Application object (in the FMX.Forms unit):

Application.StyleFileName := 'C:\Styles\MyStyle.style'

This is best done in your project file, before the line to Application.Initialize.

Ideally there should be some way to set the default style from within your applications project options, or to load it from a resource. Sadly, after much experimentation, I can’t find a way to do this. If you know better, please comment.

4. Where your application gets it’s styling info

As you know you can pop a StyleBook on a form to customise your style. But adding a separate stylebook to each and every form: duplicates data; increases redistributable sizes; is a mainainance nightmare (update every form when you change a style?); just isn’t DRY.

Much better to create a single master StyleBook on your main form, then link each child’s stylebook to that on the main form. You can do that with the following in each forms OnCreate event handler:[1]

StyleBook := TForm(Application.MainForm).StyleBook1

**Update: If you do this make sure you set the StyleBook property back to nil before freeing the form. If not you’ll get AVs if the style is reloaded, and possible in other scenarios due to a hanging pointer.

Note, however that this doesn’t work within the form editor. Create a custom control, add it to your child form and it won’t show the style you have loaded in your main forms StyleBook. I really hope Embarcadero find some kind of workaround for this. Or better yet, improve the style book handling within FireMonkey.

5. Set your StyleBook property

This gotcha has caught me a few times: You add a StyleBook to your form, change the style within it, set your components StyleLookup property and run your application. And you get the default style.

You should have set the forms StyleBook property to point to the StyleBook. Ouch.

Note that if you right click a control and edit it’s style, a StyleBook will be created and the StyleBook property will be set, but only if there isn’t already a StyleBook present. If you create the StyleBook manually, you need to set the StyleBook property yourself.

6. You can have multiple StyleBook objects

Your form can have multiple StyleBook objects. I figure that explains the behaviour in the above item - FireMonkey doesn’t want to assume whcih StyleBook to use. Why would you want multiple StyleBooks? I’m not really sure. The form and the components on it can only use one at a time.

7. Styles don’t have to come from style files

So, you know a style can come from the default style, or from a StyleBook, but it can also come from a control on a from. Simply set the StyleName property of a control and set the StyleLookup of another control to match.

8. Keep your StyleBooks light

The resources in a StyleBook take time up at form create time. For this reason you should keep your StyleBooks to a reasonable size. Instead you should put styling information in your default style (and load it with Application.StyleFileName - see above).

Sadly at the moment there’s no easy way to merge style files - so you’ll have to add your custom styling to the (probably Embarcadero supplied) default style and keep the merge up to date.

9. Beware the HitTest

Add a TImage to a button and you’ll notice that when the mouse is over the image the functionality of the button will be lost. I.e. mouseovers, clicks etc will be ignored. This is because the TImage is absorbing them. To fix this you needs to set the TImage’s HitTest property to False. This applies whether you’re adding controls on a form or in a style file.

10. If all else fails, set Locked := True

Every FireMonkey control has a Locked property. I’m not sure what this does. The Embarcadero doesn’t seem to know what it does either, the property reference simply says it locks controls at design time, but this page says “Enabling Locked changes the way hit testing works and triggers fire, so that the subcomponent is part of the larger whole.”. My experience (although somewhat limited in this area) is that if I’m having problems with mouse clicks going missing fiddling with Locked in association with HitTest (see above) is the best way to resolve things.

[1] An even better way to handle this is to create a custom form class, and set the StyleBook property in an overridden Create constructor. This is left as an exercise for the ready.

 

Triggering Effects and Animations in FireMonkey Components

If you’ve used FireMonkey styles, you’ve probably got used to using triggers to initiate animations and effects. I.e. setting a TColorAnamation when IsMouseOver = True and triggering a TGlowEffect when IsFocused = True. But if you’re creating your own components, how do you create triggers which you and others can use to style your component? That’s what we’re going to look at today.

Below is a form with two TEdit controls. I’ve coded them to show basic password validation. The first box shows a red background if the password is not secure enough (less that six characters here), the second shows a TInnerGlowEffect under the same conditions. Both boxes show clear when the password is adequate (see the second screenshot).

TValidateEdit

I started by subclassing TEdit as follows to create a generic edit box with validation. Validation is done by calling the OnValidate event:

type TValidateEvent = function(SenderTObject;const TextString): Boolean of object;

type TValidateEdit = class(TEdit)
  private
    
FIsInvalidBoolean;
    
FOnValidateTValidateEvent;
  protected
    
procedure ApplyStyle;override;
    
procedure KeyDown(var KeyWord; var KeyCharSystem.WideCharShiftTShiftState); override;
    
procedure DoIsValid;virtual;
  
published
    property IsInvalid
Boolean read FIsInvalid;
    
property OnValidateTValidateEvent read FOnValidate write FOnValidate;
  
end


The first thing to notice is our trigger property, IsInvalid. As with the triggers you’ve already seen it starts with ‘Is’. This is not a requirement, but it is a convention, and one you would do well to keep to, just like you start your types with a T. Following the convention means that you and others will instantly know which propetries can be used as triggers. This is important since controls with trigger properties have special code to enable the property to be used as a trigger. Other than the naming convention there is nothing special about the declaration of trigger properties.

Now, lets take a run through the code and see how things work. First up is our overridden KeyDown method.

procedure TValidateEdit.KeyDown(var KeyWord; var KeyCharSystem.WideChar;
  
ShiftTShiftState);
begin
  inherited
;
  
DoIsValid;
end

All we do here is call DoIsValid to check whether the new content is valid.

procedure TValidateEdit.ApplyStyle;
begin
  inherited
;
  
DoIsValid;
end

We need to override ApplyStyle and call DoIsValid so that IsInvalid will be set properly at startup and if the style is ever reloaded. In our case this means that when the password editor is created, and therefore empty, it will show as invalid.

procedure TValidateEdit.DoIsValid;
var 
ValueBoolean;
begin
  
if Assigned(OnValidatethen
  begin
    Value 
:= not OnValidate(SelfText);
    
FIsInvalid := Value;
    
StartTriggerAnimation(Self'IsInvalid');
    
ApplyTriggerEffect(Self'IsInvalid');
  
end;
end

And finally on to the meat of our code, DoIsValid. First we call the onIsValidate event handler and set FIsInvalid. All bog standard stuff.

Following that are the two lines which do the work:

StartTriggerAnimation(Self'IsInvalid');
    
ApplyTriggerEffect(Self'IsInvalid'); 


These are two standard methods of TFMXObject (a parent of all FireMonkey objects). StartTriggerAnimation starts any appropriate animations. ApplyTriggerEffect shows or hides any appropriate effects.

Both methods look through the child objects for effects and animations to trigger. These can be animations and effects in the control’s style, or those added directly to the control either at deisgn time or run time.

The first parameter is AInstance (of type TFMXObject). This is the object which contains the trigger property. Here we’re passing Self, but you could just as easily pass in another control with a trigger property. For example you could change a property of another control, and then trigger an animation in your own style based on that controls state.

Finally we have the trigger property itself. This takes the name of a property as a string. The property must be a boolean. Refer to the discussion above about naming conventions, but also keep in mind the previous paragraph: you could start an animation based on any boolean property on any object. But if you do this, note that the animation will not get updated when that property is changed unless you explicitly do it yourself. Caveat emptor.

Styling

Here is the style using animations, a straight copy of the EditStyle to which I added two animations, one to initiate the animation, the second to clear it:

And the properties for the first animation:

Here we have the StartValue and StopValue and the Trigger (IsInvalid=True). The second animation is simply the reverse. Note however that we also have StartFromCurrent=True. If this is not set then each time we call the StartTriggerAnimation function the animation will change to the StartValue and animate to the StopValue. In our case that means that each time we type a character the background would turn white, then fade to red. Not what we want. Starting from the current value stops this (It starts at red and stays there).

Prior to XE2 Update 4 I noticed a bug with this behaviour. Under some circumstances the animation would switch immediately to the StopValue, rather than animating. To get around this I changed DoIsValid so that the animations would only run if the trigger property had actually changed:

procedure TValidateEdit.DoIsValid;
var 
ValueBoolean;
begin
  
if Assigned(OnValidatethen
  begin
    Value 
:= not OnValidate(SelfText);
    if 
Value <> FIsInvalid then
    begin
      FIsInvalid 
:= Value;
      
StartTriggerAnimation(Self'IsInvalid');
      
ApplyTriggerEffect(Self'IsInvalid');
    
end;
  
end;
end

Now that this is fixed with Update 4, you have a choice of using either technique.

Styling for effects

Here is the style for the version which uses an effect, with the newly added TInnerGlowEffect highlighted:

The only thing to note here is that the effect must be added as a child of the object in which we want the effect to appear, the background rectangle here. I first created this code under XE2 Update 3 and added the effect as a child of the main TLayout and it worked fine. This no longer applied under Update 4: hence it’s current placement.

Round up

And that’s all there is to it. Just remember that you can be incredibly creative with FireMonkey styles. Instead of changing the background of the TEdit I could have added an image and had it change from a cross to a tick. I could have added some text (‘Password too short’) either below or beside the edit box and changed it’s opacity. The beauty with FireMonkey is that once I’ve added the trigger property you (or your designer) are free to be really creative with how that trigger affects the styling.

Download sources

Full source of ValidateEdit unit:

unit uValidateEdit;

interface
uses FMX.EditSystem.Classes;

type TValidateEvent = function(SenderTObject;const TextString): Boolean of object;

type TValidateEdit = class(TEdit)
  private
    
FIsInvalidBoolean;
    
FOnValidateTValidateEvent;
  protected
    
procedure ApplyStyle;override;
    
procedure KeyDown(var KeyWord; var KeyCharSystem.WideCharShiftTShiftState); override;
    
procedure DoIsValid;virtual;
  
published
    property IsInvalid
Boolean read FIsInvalid;
    
property OnValidateTValidateEvent read FOnValidate write FOnValidate;
  
end;

implementation
uses System
.UITypes;

{ TValidateEdit }

procedure TValidateEdit
.ApplyStyle;
begin
  inherited
;
  
DoIsValid;
end;

procedure TValidateEdit.DoIsValid;
var 
ValueBoolean;
begin
  
if Assigned(OnValidatethen
  begin
    Value 
:= not OnValidate(SelfText);
//    if Value <> FIsInvalid then
    
begin
      FIsInvalid 
:= Value;
      
StartTriggerAnimation(Self'IsInvalid');
      
ApplyTriggerEffect(Self'IsInvalid');
    
end;
  
end;
end;

procedure TValidateEdit.KeyDown(var KeyWord; var KeyCharSystem.WideChar;
  
ShiftTShiftState);
begin
  inherited
;
  
DoIsValid;
end;

end

s opacity. The beauty with FireMonkey is that once I

Anatomy of a FireMonkey Style

FireMonkey styles have a similar relationship to controls as CSS styles have to tags in a HTML file. The control handles the functionality of the application. The style tells FireMonkey how the control should look. Styles not only specify colors, border styles and fonts, but they can specify everything about the appearance. For example a style could move the button of a TComboBox from right to left and make it’s text right-aligned.

Style elements are made up of components in the same way that your application is also made up of components. Indeed, the same components that are available in the component palette are also available in styles, although most of the components you will use are made up from the more ‘primitive’ components - shapes (TRectangle, TCircle, TPath) etc., animations and effects as well as TLayout and TText.

A typical style

Lets take a look at a typical FireMonkey style. The following is the component tree for a TButton in the windows 7 style:

buttonstyleTLayout
  background
TRectangle
    a TRectangle
    four TColorAnimations
    a TInnerGlowEffect
    another TRectangle
  text
TText
  a TGlowEffect 

(Note from the indentation above that certain components are children of other components.)

So, we start with a TLayout. This is a useful container component. It is similar to a TPanel, but has no ‘styling’ itself - ie. it has no way to specify it’s appearance (because it has no appearance).

The TLayout has three children, a TRectangle for the background, a TText for the text and a TGlowEffect to show selected state. Both of these are ‘styled’ components - ie that have an appearance which can be modified. A TRectangle, for instance, has stroke and fill ‘brushes’ for the outline and infill respectively, as well as properties for stroke thickness, corner type, which sides to show and much more besides.

The background rectangle contains another two rectangles, which add extra subtlely to the style, four animations and an effect.

Animations modify a property of their parent component. For example, when you hover your mouse over a button the background changes color due to an animation modifying the Fill.Color property. However, animations are not (necessarily) instant flips from one value to another. In the example just stated the fill color ‘animates’ gently from the original color to the target color over a fraction of a second.

In styles animations are usually activated by a boolean Trigger property, which is linked to the name of property on the parent component. When the trigger property changes state, the animation fires. There are a number of properties which can be used to trigger an animation including mouse overs and clicks.

Different animation components modify different types of property. A TFloatAnimation modifies a numeric property, a TColorAnimation modifies a color property, and so on.

Effects are similar to animations, in that the are tied to a trigger property and modify the appearance of the control when the trigger property is True. Effects can do things like display a glowing border (TGlowEffect), a drop shadow (TGlowEffect) or blur a control (TBlurEffect).

Styles and Components

So, above we have the component tree for a TButton in the Windows 7 style, but the tree is not fixed across styles: we could create a style with components added or removed, with components rearranged, even with components of different types. It is entirely up to the style designer to decide how a style element is made up.

However, there do need to be links between the developers code and the designers style. For example, when the developer sets the text of a TButton, he searches the buttonstyle for a component called ‘text’, expects that it is of type TText and sets it’s Text property. So, in this case the style must have a TText component called ‘text’ somewhere within it in order to provide the full functionality of the control. When designing styles (and the code to go with it) it is important that the designer and developer communicate and document any such links.

Other components

A style may also contain any component available on the Delphi/C++Builder component palette. For example, a TScrollBox contains two TScrollBar components, for the vertical and horizontal scroll bars. In such a case the component in the style file can be used to specify things such as the width and height of the component.

However, the styling for these control will be picked up from the usual style elements for that class of component (ie. ‘scrollbarstyle’ for a TScrollBar). You can, however, override the components default styling by setting it’s StyleLookup property and creating the appropriate style element.

TBitmapSpeedButton: Loading Images from the Style

Recently recently blogged about creating a TBitmapSpeedButton - a button which could show a bitmap image. When I came to use it I realised an ideological flaw. I was using the old VCL style model of loading a bitmap at design time to use in the application.

That works fine for VCL, but in FireMonkey it’s good practice to offload the visuals to the style. What would be ideal is if, in the form designer, I can specify a style resource for the image to be used. Now I, or my visual designer, can choose an image to use without needing access to my source code. If the look of the software needs updating I can just update the style file with new images and the app will look different without having to edit the source code (or at least the FMX file, which as far as I’m concerned is the same thing.

So, I’ve updated my component with two new properties:

property ImageTypeTImageType read FImageType write SetImageType;
    
property ImageStyleLookupString read FImageStyleLookup write SetImageStyleLookup

TImageType is either itBitmap or itStyleLookup and tells the component where to get the bitmap data from.

The only interesting bit of new code is the new UpdateImage method:

procedure TBitmapSpeedButton.UpdateImage;
var 
ObjTFMXObject;
begin
  
if FImageType itBitmap then
    
if FImage <> nil then
      FImage
.Bitmap.Assign(FBitmap)
    else
  else 
//itResource
  
begin
    Obj 
:= nil;
    if (
FScene <> nil) and (FScene.GetStyleBook <> nil) and (FScene.GetStyleBook.Root <> nilthen
      Obj 
:= TControl(FScene.GetStyleBook.Root.FindStyleResource(FImageStyleLookup));
    if 
Obj nil then
      
if Application.DefaultStyles <> nil then
        Obj 
:= TControl(Application.DefaultStyles.FindStyleResource(FImageStyleLookup));
    if (
Obj <> nil) and (Obj is TImage) and (FImage <> nilthen
      FImage
.Bitmap.Assign(TImage(Obj).Bitmap);
  
end;
end

which shows how to find a style resource either from the current stylebook, or if that isn’t found, from the applications default style.

Download updated sources

 

My First FireMonkey Custom Control: TBitmapSpeedButton

One thing which surprises me about the FireMonkey library is that the TSpeedButton has no option to display an image (indeed, none of the button controls do). Since I’m at the stage where I need such a control for the interface for MonkeyStyler I decided this would be the perfect opportunity to create my first FireMonkey custom control.

So, lets look at what we want: We want to descend from TSpeedButton, so we can easily add the control to a tool bar (actually a FireMonkey toolbar can accept any control, but the styling for a speed button is closest to what I want). We want a bitmap image, and we want the option to have the image to the left, right, top or bottom of the text, or to have the image centered with no text.

I started by creating the style. This gets the basics of styling out of the way for when we create the code to interact with it. Of course, at this stage styling can be quite basic, simply adding the controls we need and setting basic properties.

The Style

Copy the styling for SpeedButtonStyle to a new style file (bitmapspeedbutton.style) from Windows7.style (Note: I did this with a couple of clicks in MonkeyStyler but at the time of writing you’ll have to make do with cutting and pasting from the source files).

Here is the style for a TSpeedButton:

We want to add our image at the highest level below the root TLayout. At this position it can interact with the TText as we adjust it’s position. We could just add the TImage directly, but if we did, adjusting the Align property would change it’s size. And unless we get messy with the padding we would get undesired stretching of the image.

So, what we’ll do is add a TLayout, and add the TImage as a child:

Set the TImage’s properties:

Height 24 (our default image size)
Width 24
Align 
alCenter (so it will be centered in the TLayout)
StyleName image (so we can access it from code)
HitTest False (to let mouse data pass through to the underlying components)
WrapMode iwFit (Should be set alreadyImages will be enlarged/reduced to the correct size

For the parent TLayout:

StyleName imagelayout (so we can access it from code

and for the root TLayout:

StyleName speedbuttonbitmapstyle (the component name minus the leading T and with style appended see below

.

Save the file, and load it into the StyleBook for your test project (also, point your form’s StyleBook property to the StyleBook).

The code

Start by creating the code for the class:

type TBitmapSpeedButton = class(TSpeedButton)
... 

and add the Create method:

constructor TBitmapSpeedButton.Create(AOwnerTComponent);
begin
  inherited
;
  
Height := 28;
  
Width := 28;
end

Add some code to your test project to create a TSpeedButton in code:

BSB := TBitmapSpeedButton.Create(Self);
BSB.Parent := Self;
BSB.Align := TAlignLayout.alCenter

Run it and you’ll see ... nothing. Okay, so the default style for a speed button is invisible unless you hover over it (perhaps not the best choice for a custom component, but we’re here now, so we’ll have to live with it). Actually you can see it by hovering your mouse over, you’ll just need to be good at finding where we centered it to.

What happened here? If you listen to some descriptions of FireMonkey custom control, they’ll give you lots of code to load a style into a FireMonkey control. But if you read such stuff ignore it. FireMonkey actually does all that stuff for you. If you dig into the FMX.Types unit and look at the source to TStyledControl.GetStyleObject you’ll see (amongst a lot of other stuff):

StyleName := ClassName 'style';
Delete(StyleName11); // just remove T 

So, FireMonkey takes the ClassName of you control (TSpeedButton), appends ‘style’ and removes the preceding ‘T’, giving us ‘SpeedButtonstyle’ and automatically loads the appropriately named style (unless you, or your users, set the StyleLookup property, in which case it automatically loads that one instead).

(Aside: TStyledControl is the parent of all controls which can have styling applied).

Functionality

So, we have a control which looks like a TBitmapSpeedButton, we just need to add some code so it behaves like one.

Lets flesh out the interface section:

type TImageAlign = (iaTopiaLeftiaRightiaBottomiaCenter);

type
  [ComponentPlatformsAttribute
(pidWin32 or pidWin64 or pidOSX32)]
  TBitmapSpeedButton 
= class(TSpeedButton)
  private
    
FImageAlignTImageAlign;
    
FTextVisibleBoolean;
    
procedure SetImageAlign(const ValueTImageAlign);
    
procedure SetTextVisible(const ValueBoolean);
  protected
    
FImageLayoutTLayout;
    
FImageTImage;
    
FBitmapTBitmap;
    
procedure ApplyStyle;override;
    
procedure EVBitmapChange(SenderTObject);
  public
    
constructor Create(AOwnerTComponent);override;
    
destructor Destroy;override;
  
published
    property ImageAlign
TImageAlign read FImageAlign write SetImageAlign default iaCenter;
    
property TextVisibleBoolean read FTextVisible write SetTextVisible;
    
property BitmapTBitmap read FBitmap write FBitmap;
  
end

The first bit of interesting code is the ApplyStyle method:

procedure TBitmapSpeedButton.ApplyStyle;
var 
TTFMXObject;
begin
  inherited
;
  
:= FindStyleResource('imagelayout');
  if (
<> nil) and (T is TLayoutthen
    FImageLayout 
:= TLayout(T);
  
:= FindStyleResource('image');
  if (
<> nil) and (T is TImagethen
  begin
    FImage 
:= TImage(T);
    
FImage.Bitmap.Assign(FBitmap);
  
end;
  
SetTextVisible(FTextVisible);
  
UpdateImageLayout;
end

Most custom controls will need to override this virtual procedure. It is called whenever a style is loaded or modified, and it is here where we need to grab any objects which we will be manipulating, in our case the TImage (image) and it’s parent TLayout (imagelayout) objects.

Go back to our interface section and look at the Bitmap property. A naive implementation might use a getter and setter to fetch/modify the bitmap object contained within the styles TImage object. There’s two problems here: first the style isn’t applied at the time the object is created, but slightly later (I presume on some kind of OnIdle event). So, anyone instantiating your object:

BSB := TBitmapSpeedButton.Create(Self);
BSB.Parent := Self;
BSB.Bitmap.LoadFromFile('MyImage.png'); 

Will at best get ignored, or at worst get an access violation, depending on whether you tested the validity of your FImage field.

The second issue is that if you style ever gets updated the bitmap data saved in the styles TImage will get deleted and you’ll get a new, empty, TImage object.

So, what we need to do is ‘cache’ any data which will be sent to styling objects. In our case, that’s the TextVisible and ImageAlign property data in addition to that for Bitmap.

Look back at the ApplyStyle code above and you’ll see that I’m reloading the Bitmap and TextVisible data and calling UpdateImageLayout which will apply the ImageAlign and a few other features still to be added. Thus if a new style gets loaded the display will be updated to reflect the components state.

But, this code only operates when the style is applied, we also need to update the styles properties when a user sets our properties. So, we also have setters for ImageAlign and TextVisible, e.g.:

procedure TBitmapSpeedButton.SetTextVisible(const ValueBoolean);
begin
  FTextVisible 
:= Value;
  if (
FTextObject <> nil) and (FTextObject is TTextthen
    TText
(FTextObject).Visible := Value;
end

(FTextObject is inherited from our TSpeedButton parent (though, oddly, it isn’t declared as a TText, even though the TSpeedButton pretty much ignores it if it isn’t one).

For FBitmap we key into it’s OnChange event, with our EVBitmapChange handler (by convention I prefix event handlers with EV):

procedure TBitmapSpeedButton.EVBitmapChange(SenderTObject);
begin
  
if FImage <> nil then
    FImage
.Bitmap.Assign(FBitmap);
end

Conclusion

So, that wraps up the interesting stuff. I’ve added a few more properties, which you can see in the full source below. And I’ll sum up that I’m rather pleased with what I can achieve in FireMonkey in only 152 lines of code (plus the style).

Links:
Full source zip (.pas and .style files).
Documentation for the completed component.

Debugging tips:
Check you have the style loaded into the forms StyleBook component.
Check that the forms StyleBook property points to the StyleBook component (it’s not set by default).

Enjoy, Mike.

Update: TBitmapSpeedButton: Loading Images from the Style

Full .pas source:

unit Solent.BitmapSpeedButton;

interface
uses FMX.ControlsFMX.LayoutsFMX.ObjectsFMX.TypesClasses;

type TImageAlign = (iaTopiaLeftiaRightiaBottomiaCenter);

type
  [ComponentPlatformsAttribute
(pidWin32 or pidWin64 or pidOSX32)]
  TBitmapSpeedButton 
= class(TSpeedButton)
  private
    
FImageAlignTImageAlign;
    
FTextVisibleBoolean;
    
FImageHeightSingle;
    
FImageWidthSingle;
    
FImagePaddingSingle;
    
procedure SetImageAlign(const ValueTImageAlign);
    
procedure SetTextVisible(const ValueBoolean);
    
procedure SetImageHeight(const ValueSingle);
    
procedure SetImagePadding(const ValueSingle);
    
procedure SetImageWidth(const ValueSingle);
  protected
    
FImageLayoutTLayout;
    
FImageTImage;
    
FBitmapTBitmap;
    
procedure ApplyStyle;override;
    
procedure EVBitmapChange(SenderTObject);
    
procedure UpdateImageLayout;
  public
    
constructor Create(AOwnerTComponent);override;
    
destructor Destroy;override;
  
published
    property ImageAlign
TImageAlign read FImageAlign write SetImageAlign default iaCenter;
    
property TextVisibleBoolean read FTextVisible write SetTextVisible;
    
property BitmapTBitmap read FBitmap write FBitmap;
    
property ImageWidthSingle read FImageWidth write SetImageWidth;
    
property ImageHeightSingle read FImageHeight write SetImageHeight;
    
property ImagePaddingSingle read FImagePadding write SetImagePadding;
  
end;

procedure Register;

implementation

procedure Register
;
begin
  RegisterComponents
('SolentFMX'[TBitmapSpeedButton]);
end;

{ TBitmapSpeedButton }

procedure TBitmapSpeedButton
.ApplyStyle;
var 
TTFMXObject;
begin
  inherited
;
  
:= FindStyleResource('imagelayout');
  if (
<> nil) and (T is TLayoutthen
    FImageLayout 
:= TLayout(T);
  
:= FindStyleResource('image');
  if (
<> nil) and (T is TImagethen
  begin
    FImage 
:= TImage(T);
    
FImage.Bitmap.Assign(FBitmap);
  
end;
  
SetTextVisible(FTextVisible);
  
UpdateImageLayout;
end;

constructor TBitmapSpeedButton.Create(AOwnerTComponent);
begin
  inherited
;
  
FBitmap := TBitmap.Create(0,0);
  
FBitmap.OnChange := EVBitmapChange;
  
FImageAlign := iaCenter;
  
Height := 28;
  
Width := 28;
  
ImageWidth := 24;
  
ImageHeight := 24;
  
ImagePadding := 2;
end;

destructor TBitmapSpeedButton.Destroy;
begin
  FBitmap
.Free;
  
inherited;
end;

procedure TBitmapSpeedButton.EVBitmapChange(SenderTObject);
begin
  
if FImage <> nil then
    FImage
.Bitmap.Assign(FBitmap);
end;

procedure TBitmapSpeedButton.SetImageAlign(const ValueTImageAlign);
begin
  FImageAlign 
:= Value;
  
UpdateImageLayout;
end;

procedure TBitmapSpeedButton.SetImageHeight(const ValueSingle);
begin
  FImageHeight 
:= Value;
  if 
FImage <> nil then
    FImage
.Height := Value;
  
UpdateImageLayout;
end;

procedure TBitmapSpeedButton.SetImagePadding(const ValueSingle);
begin
  FImagePadding 
:= Value;
  
UpdateImageLayout;
end;

procedure TBitmapSpeedButton.SetImageWidth(const ValueSingle);
begin
  FImageWidth 
:= Value;
  
UpdateImageLayout;
end;

procedure TBitmapSpeedButton.SetTextVisible(const ValueBoolean);
begin
  FTextVisible 
:= Value;
  if (
FTextObject <> nil) and (FTextObject is TTextthen
    TText
(FTextObject).Visible := Value;
end;

procedure TBitmapSpeedButton.UpdateImageLayout;
begin
  
if FImage <> nil then
  begin
    FImage
.Width := ImageWidth;
    
FImage.Height := ImageHeight;
    case 
ImageAlign of
      iaLeft
:FImageLayout.Align := TAlignLayout.alLeft;
      
iaTopFImageLayout.Align := TAlignLayout.alTop;
      
iaRightFImageLayout.Align := TAlignLayout.alRight;
      
iaBottomFImageLayout.Align := TAlignLAyout.alBottom;
    else
      
FImageLayout.Align := TAlignLayout.alCenter;
    
end;
  
end;

  if 
FImageLayout <> nil then
    
if ImageAlign in [iaLeftiaRight] then
      FImageLayout
.Width := FImageWidth+FImagePadding*2
    
else if ImageAlign in [iaTopiaBottom] then
      FImageLayout
.Height := FImageHeight+FImagePadding*2;
end;

initialization
  RegisterFMXClasses
([TBitmapSpeedButton]);
end