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);
}
//---------------------------------------------------------------------------