Posts from March 2012

Call for Beta Testers

MonkeyStyler is almost ready ... or at least almost ready for it’s debut. There’s still to do: fleshing out some areas of the product (i.e. property editors); one or two features still to be added; A few minor touches which really need to be added; And a whole load of wonderful time saving features to be added after public release.

But MonkeyStyler is feature complete enough to have a beta release. I’m now working on the installer and those other little bits needed by a public product.

If you want to be among the first to try MonkeyStyler out you need to act now. I need a small number of volunteers for the beta test. If you’re interested, drop me an email to monkey@solentsoftware.com. Remember places are limited.

Enjoy.

Mike

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.