Article explains how to use standard style elements to customize LookAndFeel of own GUI elements.

Target goal: use style elements from standard GUI objects, in our own GUI

Example code: FMX application, Main form, custom GUI in Frame. The TSpeedButton style element 'background' is used for highlighting the mouse hover and mouse press events. The standard speed button style have 'background' element, implemented by TStyleButtonObject. First, we got to find it in default application style, and create a copy, owned and parented by our custom GUI. This is done in custom ApplyStyleLookup handler. For simplicity, a TPanel is dropped on frame. As soon as it's styled control, it have corresponding handler defined.

In that handler we perform 'style.substyle' lookup in global default style manager context. As soon as we find what we are looking for, we got to Clone style element, owning it, and assigning its Parent property to our panel. Do not forget to make sure the style element do not consume any mouse events by setting its HitTest := true.

The standard TStyleButtonObject defines several animation effects to reflect the mouse-to-button interaction. We're interested in Hot (AKA MouseOver) and Pressed. Now, for the tricky part. Let our TPanel handle mouse events. Inside these events we got to track panel state, i.e., is it pressed, or just higlighted, and toggle standard animation triggers defined for TSpeedButton's background, by calling:

m_bgnd.StartTriggerAnimation(Self, state);

Where state is the name of the standard button state trigger, which is known to TStyleButtonObject, like 'IsPressed' or 'IsMouseOver'. This call does the following. It checks if the object, we pass as the first parameter, has the RTTI-accessible property of the same name, as the trigger name, and launches corresponding forward or inverse animation, depending on property value, if it exists. As soon as we pass Self to the call, i.e. Frame instance reference, to make it all work, we have to define two read-only boolean properties in our Frame, IsPressed, and IsMouseOver. The values of these proprties got tracked in mouse event handlers of the Panel, and background style element later access them via RTTI automatically.

Sample project for Embarcadero RAD Studio Delphi XE8 Upd.1 


Sample project for Embarcadero RAD Studio C++ Builder XE8 Upd.1 

Sample custom GUI frame implementation, in Delphi Pascal (Click to unfold):

unit TFmeStyleUser;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, 
  FMX.Types, FMX.Graphics, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.StdCtrls,
  FMX.Controls.Presentation, FMX.Styles, FMX.Styles.Objects;

type
  TFmeStyleUserT = class(TFrame)
    pnl_: TPanel;
    procedure pnl_ApplyStyleLookup(Sender: TObject);
    procedure pnl_MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Single);
    procedure pnl_MouseEnter(Sender: TObject);
    procedure pnl_MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Single);
    procedure pnl_MouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Single);
    procedure pnl_MouseLeave(Sender: TObject);

  private
    procedure bgndDo(state: String);

  private
    m_pressed: Boolean;
    m_mouseOver: Boolean;
    m_bgnd: TButtonStyleObject;

  public
    { Public declarations }
    property IsPressed: Boolean read m_pressed;
    property IsMOuseOver: Boolean read m_mouseOver;
  end;

implementation

{$R *.fmx}

procedure TFmeStyleUserT.pnl_ApplyStyleLookup(Sender: TObject);
var
  bgndStyle: TFmxObject;
  usedStyle: TButtonStyleObject;
begin
  bgndStyle := TStyleManager.ActiveStyle(nil).FindStyleResource('speedbuttonstyle.background');
  if Assigned(bgndStyle) then begin
    usedStyle := bgndStyle.Clone(pnl_) as TButtonStyleObject;
    if Assigned(usedStyle) then begin
      usedStyle.Visible := true;
      usedStyle.Align := TAlignLayout.Contents;
      usedStyle.HitTest := false;
      usedStyle.Locked := true;
      usedStyle.Parent := pnl_;
    end;
    m_bgnd := usedStyle;
  end;
  //
end;

procedure TFmeStyleUserT.bgndDo(state: String);
begin
  if Assigned(m_bgnd) then begin
    m_bgnd.StartTriggerAnimation(Self, state);
  end;
end;

procedure TFmeStyleUserT.pnl_MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single);
begin
  if not m_pressed then begin
    m_pressed := true;
    bgndDo('IsPressed');
  end;
end;

procedure TFmeStyleUserT.pnl_MouseEnter(Sender: TObject);
begin
  if not m_mouseOver then begin
    m_mouseOver := true;
    bgndDo('IsMouseOver');
  end;
end;

procedure TFmeStyleUserT.pnl_MouseLeave(Sender: TObject);
begin
  if m_mouseOver then begin
    m_mouseOver := false;
    bgndDo('IsMouseOver');
  end;
end;

procedure TFmeStyleUserT.pnl_MouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Single);
begin
  if not m_mouseOver then begin
    m_mouseOver := true;
    bgndDo('IsMouseOver');
  end;
end;

procedure TFmeStyleUserT.pnl_MouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single);
begin
  if m_pressed then begin
    m_pressed := false;
    bgndDo('IsPressed');
  end;
end;

end.

Sample custom GUI frame implementation, in C++ Builder. Header part. (Click to unfold):

//---------------------------------------------------------------------------

#ifndef TFmeStyleUserH
#define TFmeStyleUserH
//---------------------------------------------------------------------------
#include <System.Classes.hpp>
#include <FMX.Controls.hpp>
#include <FMX.Forms.hpp>
#include <FMX.Controls.Presentation.hpp>
#include <FMX.StdCtrls.hpp>
#include <FMX.Types.hpp>
//---------------------------------------------------------------------------

#pragma explicit_rtti
class DELPHICLASS TFmeStyleUser : public TFrame
{
__published:	// IDE-managed Components
  TPanel *pnl_;
  void __fastcall pnl_ApplyStyleLookup(TObject *Sender);
  void __fastcall onMouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift,
          float X, float Y);
  void __fastcall onMouseUp(TObject *Sender, TMouseButton Button, TShiftState Shift,
          float X, float Y);
  void __fastcall pnl_MouseMove(TObject *Sender, TShiftState Shift, float X, float Y);
  void __fastcall pnl_MouseEnter(TObject *Sender);
  void __fastcall pnl_MouseLeave(TObject *Sender);

public:		// User declarations
  __fastcall TFmeStyleUser(TComponent* Owner);
  __property bool IsPressed = {read=m_isPressed};
  __property bool IsMouseOver = {read=m_isMouseOver};

private:
  void mousePressTrack(bool isPressed);
  void mouseOverTrack(bool isMouseOver);
  void triggerEffect(const UnicodeString& name);

private:
  TButtonStyleObject* m_bgnd;
  bool m_isMouseOver;
  bool m_isPressed;
};
//---------------------------------------------------------------------------
extern PACKAGE TFmeStyleUser *FmeStyleUser;
//---------------------------------------------------------------------------
#endif

Sample custom GUI frame implementation, in C++ Builder. cpp code part. (Click to unfold):

//---------------------------------------------------------------------------

#include <fmx.h>
#pragma hdrstop

#include "TFmeStyleUser.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.fmx"
TFmeStyleUser *FmeStyleUser;
//---------------------------------------------------------------------------
__fastcall TFmeStyleUser::TFmeStyleUser(TComponent* Owner) :
TFrame(Owner),
m_bgnd(0),
m_isMouseOver(false),
m_isPressed(false)
{
}
//---------------------------------------------------------------------------

void __fastcall TFmeStyleUser::pnl_ApplyStyleLookup(TObject *Sender)
{
  TFmxObject* selt = TStyleManager::ActiveStyle(0)->FindStyleResource("speedbuttonstyle.background");
  if( selt )
  {
    m_bgnd = dynamic_cast<TButtonStyleObject*>(
      selt->Clone(pnl_)
    );
    if( m_bgnd )
    {
      m_bgnd->HitTest = false;
      m_bgnd->Locked = true;
      m_bgnd->Align = TAlignLayout::Contents;
      m_bgnd->Visible = true;
      m_bgnd->Parent = pnl_;
    }
  }
}
//---------------------------------------------------------------------------

void TFmeStyleUser::triggerEffect(const UnicodeString& name)
{
  if( m_bgnd )
  {
    m_bgnd->StartTriggerAnimation(this, name);
  }
}
//---------------------------------------------------------------------------

void TFmeStyleUser::mouseOverTrack(bool isMouseOver)
{
  if( m_isMouseOver != isMouseOver )
  {
    m_isMouseOver = isMouseOver;
    triggerEffect("IsMouseOver");
  }
}
//---------------------------------------------------------------------------

void TFmeStyleUser::mousePressTrack(bool isPressed)
{
  if( m_isPressed != isPressed )
  {
    m_isPressed = isPressed;
    triggerEffect("IsPressed");
  }
}
//---------------------------------------------------------------------------

void __fastcall TFmeStyleUser::onMouseDown(TObject *Sender, TMouseButton Button,
          TShiftState Shift, float X, float Y)
{
  mousePressTrack(true);
}
//---------------------------------------------------------------------------

void __fastcall TFmeStyleUser::onMouseUp(TObject *Sender, TMouseButton Button,
          TShiftState Shift, float X, float Y)
{
  mousePressTrack(false);
}
//---------------------------------------------------------------------------

void __fastcall TFmeStyleUser::pnl_MouseMove(TObject *Sender, TShiftState Shift, float X,
          float Y)
{
  mouseOverTrack(true);
}
//---------------------------------------------------------------------------

void __fastcall TFmeStyleUser::pnl_MouseEnter(TObject *Sender)
{
  mouseOverTrack(true);
}
//---------------------------------------------------------------------------

void __fastcall TFmeStyleUser::pnl_MouseLeave(TObject *Sender)
{
  mouseOverTrack(false);
}
//---------------------------------------------------------------------------