Creating a Metropolis UI ToolTip
Go Up to Developing Metropolis UI Applications
A Metropolis UI style tooltip is a pop-up window that displays Help-like information when the mouse or touch device hovers over or touches a control. This tutorial explains how to create and use a tooltip using FireMonkey.
Create a new FireMonkey project:
- Create a new Metropolis UI Desktop Application:
- For Delphi: File > New > FireMonkey Metropolis UI Desktop Application - Delphi > Blank Metropolis UI Desktop Application.
- For C++: File > New > FireMonkey Metropolis UI Desktop Application - C++ > Blank Metropolis UI Desktop Application.
- Save the project.
Define ToolTip Class Object
Create a new class TToolTipPanel for your application.
In the your unit, do the following steps:
- 1. In the uses section, add the following units:
- Delphi:
uses
FMX.Edit;
- C++:
- In the header file (.h file), add the following code:
#include <FMX.Edit.hpp>
- 2. Define the TToolTipPanel class derived from TPanel.
- Delphi:
TToolTipPanel = class(TPanel)
- C++: define this new class in the header file (.h file):
class TToolTipPanel : public TPanel {
//class definition goes here
}
- 3. To add text to the tooltip, add a TLabel as a private field: FLabel. To access the tooltip text, define a public Text property along with the getter and setter functions of the Text property.
- Delphi:
type
TToolTipPanel = class(TPanel)
private
FLabel: TLabel;
function GetToolTipText: string;
procedure SetToolTipText(const Value: string);
public
property Text: string read GetToolTipText write SetToolTipText;
end;
- C++:
- In the header file (.h file), add the following code:
class TToolTipPanel : public TPanel {
private:
TLabel *FLabel;
UnicodeString _fastcall GetToolTipText();
void _fastcall SetToolTipText (const UnicodeString Value);
public:
__published:
__property UnicodeString Text = {read = GetToolTipText, write = SetToolTipText};
};
- 4. Implement the getter and setter functions of the Text property as following:
function TToolTipPanel.GetToolTipText: string;
begin
Result := FLabel.Text;
end;
procedure TToolTipPanel.SetToolTipText(const Value: string);
begin
FLabel.Text := Value ;
end;
- C++:
- In the .cpp file, add the following code:
UnicodeString _fastcall TToolTipPanel::GetToolTipText() {
return FLabel->Text;
}
// ---------------------------------------------------------------------------
void _fastcall TToolTipPanel::SetToolTipText(const UnicodeString Value) {
FLabel->Text = Value;
}
- 5. Add the following private members to the TToolTipPanel class, in addition to the ones added above:
- Delphi:
private
FMousePoint : TPointF; //keeps the current position for the mouse cursor
FActiveControl : TFmxObject; //keeps the current active control for which the tooltip is displayed
FTimer : TTimer; //the timer is used to decide when, in time, the tooltip is displayed and for how long,
// for the FActiveControl
FCounter : Cardinal;//keeps a counter used on timer execution
FOnlyInputFields : Boolean ; //this flag is used to decides if the tooltip is displayed only for
// input controls
procedure OnTimer(Sender: TObject); //define the OnTime event handler for the FTimer
- C++:
- In the header file (.h file), add the following code to the TToolTipPanel class:
TPointF FMousePoint;
TFmxObject *FActiveControl;
TTimer *FTimer;
unsigned FCounter;
bool FOnlyInputFields;
void _fastcall OnTimer (TObject *Sender);
- To expose the FOnlyInputFields flag:
- Delphi: add the OnlyInputFields public property as follows:
public
property OnlyInputFields : Boolean read FOnlyInputFields write FOnlyInputFields;
- C++: add the OnlyInputFields as a published property, as follows:
__property bool OnlyInputFields = {read = FOnlyInputFields, write = FOnlyInputFields };
- 6. For design purposes, add a private field FBorderWidth and a public property BorderWidth to set and get the tooltip borders' width. The borders are taken into consideration when the tooltip is displayed.
- Delphi:
private
FBorderWidth : Single;
public
property BorderWidth : Single read FBorderWidth write FBorderWidth;
- C++:
- In the header file (.h file), add the following code to the TToolTipPanel class:
private:
float FBorderWidth;
__published:
__property float BorderWidth = {read = FBorderWidth, write = FBorderWidth };
- 7. Define and implement the constructor and destructor for the TToolTipPanel class as follows:
- Declaration
- Delphi:
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
- C++:
- In the header file (.h file), add the following code to the TToolTipPanel class:
- C++:
public:
__fastcall TToolTipPanel(TComponent* Owner);
__fastcall virtual ~TToolTipPanel(void);
- Implementation
- Delphi:
constructor TToolTipPanel.Create(AOwner: TComponent);
begin
inherited; //inherits the behavior from TPanel
Visible := False; //initially, the tootltip is not visible
StyleLookup := 'tooltippanel'; // sets the name of the ToolTip style
//initialize FLabel
FLabel := TLabel.Create(AOwner);
FLabel.Parent := Self;
FLabel.StyleLookup := 'tooltiplabel';
FLabel.Text := Self.ToString;
if assigned(FLabel.Canvas) then
Height := FLabel.Canvas.TextHeight(FLabel.Text);
FLabel.Align := TAlignLayout.Client;
FLabel.TextAlign := TTextAlign.Center;
FLabel.VertTextAlign := TTextAlign.Center;
//initialize FTimer
FTimer := TTimer.Create(AOwner);
FTimer.OnTimer := OnTimer;
FTimer.Enabled := True;
FTimer.Interval := 500;
FActiveControl := nil; //initially, there is no control for which to display the tootltip
FCounter := 1000; //FCounter is initially set to a high value
FBorderWidth := 10; //an initial value for the tooltip borders
end;
destructor TToolTipPanel.Destroy;
begin
inherited;
end;
- C++:
- In the .cpp file, add the following code to the TToolTipPanel class:
- C++:
__fastcall TToolTipPanel::TToolTipPanel(TComponent* Owner) : TPanel(Owner){
Visible = False;
StyleLookup = "tooltippanel";
FLabel = new TLabel(this);
FLabel->Parent = this;
FLabel->StyleLookup = "tooltiplabel";
FLabel->Text = this->ToString();
if (FLabel->Canvas != NULL) {
Height = FLabel->Canvas->TextHeight(FLabel->Text);
}
FLabel->Align = TAlignLayout::Client;
FLabel->TextAlign = TTextAlign::Center;
FLabel->VertTextAlign = TTextAlign::Center;
FTimer = new TTimer(Owner);
FTimer->OnTimer = OnTimer;
FTimer->Enabled = true;
FTimer->Interval = 500;
FActiveControl = NULL;
FCounter = 1000;
FBorderWidth = 10;
}
//---------------------------------------------------------------------------
__fastcall TToolTipPanel::~TToolTipPanel() {
}
- 8. Add a public method to display the tooltip at a specified position:
- Delphi:
public
procedure ShowToolTip(AX, AY: Single);
implementation
procedure TToolTipPanel.ShowToolTip(AX, AY: Single);
begin
Position.Point := PointF(AX,AY); //sets the tooltip position to the specified point
//calculates the size of the tooltip depending on the text to be displayed
Height := FLabel.Canvas.TextHeight(FLabel.Text) + 2 * FBorderWidth;
Width := FLabel.Canvas.TextWidth(FLabel.Text) + 2 * FBorderWidth;
//sets the tooltip as visible
Visible := True;
end;
- C++:
- In the header file (.h file), add the following code to the TToolTipPanel class:
public:
__fastcall void ShowToolTip(float AX, float AY);
- In the .cpp file , add the following code to the TToolTipPanel class:
void __fastcall TToolTipPanel::ShowToolTip(float AX, float AY){
Position->Point = PointF(AX,AY);
Height = FLabel->Canvas->TextHeight(FLabel->Text)+ 2 * FBorderWidth;
Width = FLabel->Canvas->TextWidth(FLabel->Text) + 2 * FBorderWidth;
Visible = True;
}
- 9. A tooltip is typically displayed for the current position of the cursor. A tooltip also displays text that provides some information about the current position.
- In this case, the tooltip displays the following text:
- "ToolTip for component: "
- and the name of the component on which the mouse cursor is positioned.
- If no component is found, the display is:
- "ToolTip for mouse pos "
- and the coordinates of the mouse cursor.
- If the tooltip is set to be displayed only for input controls, the tooltip displays the text:
- "ToolTip for "
- and the name of the input control in focus. Also, the tooltip is positioned under the input control, to avoid covering the control and to let the user set the input text.
- The displayed text can, of course, be changed to suit your needs.
The OnTimer implementation should look as follows:
- Delphi:
procedure TToolTipPanel.OnTimer;
var
LActiveControl : IControl;
LControl : TControl;
LMousePos : TPointF;
LObject : IControl ;
begin
//test to detect for which kind of controls to display the control
if not FOnlyInputFields then
begin
// tests if the FMousePoint is actualized
if Screen.MousePos <> FMousePoint then
begin
FMousePoint := Screen.MousePos ;
FCounter := 0;
Visible := False;
end ;
Inc(FCounter);
case FCounter of
0..2: Visible := False ;//simulates a delay on displaying the tooltip
3:
begin
Text := '';
if Parent is TForm then
begin
//identifies the object on which the mouse cursor is located
LObject := (Parent as TForm).ObjectAtPoint(FMousePoint) ;
if Assigned(LObject) then
Text := LObject.GetObject.Name;
end;
//if no object is found, the tooltip displays the mouse cursor coordinates
if Text = '' then
Text := 'ToolTip for mouse pos ' + PointToString(FMousePoint)
else
Text := 'ToolTip for component: ' + Text ;
LMousePos := (Parent as TForm).ScreenToClient(FMousePoint);
//displays the tooltip
ShowToolTip(LMousePos.X, LMousePos.Y);
end;
// the tooltip is displayed for a limited time. In this case it is displayed until FCounter reaches 10
4..10:;
else
FCounter := 1000;
Visible := False ;
end;
end
else
begin
//identifies the active control (the control in focus)
if Parent is TForm then
LActiveControl := (Parent as TForm).Focused;
// checks if the object in focus is an input control (and a TEdit or a TEdit descendant)
if Assigned(LActiveControl) and (LActiveControl.GetObject <> FActiveControl) then
begin
Visible := False ;
FActiveControl := LActiveControl.GetObject;
if (FActiveControl is TEdit) then
FCounter := 0;
end;
Inc(FCounter);
case FCounter of
0..2: Visible := False ;//simulates a delay on displaying the tooltip
3..10: // the tooltip is displayed for the FActiveControl control, if it exists, under the input control,
// so the tooltip doesn't cover the input area
begin
if assigned(LActiveControl) then
begin
LControl := (LActiveControl as TControl);
Text := 'ToolTip for ' + LControl.Name ;
ShowToolTip(LControl.Position.X + 20, LControl.Position.Y + LControl.Height);
end;
end
else
FCounter := 1000;
Visible := False ;
end;
end;
end;
- C++:
- In the header file (.h file), add the following code to the TToolTipPanel class:
void __fastcall TToolTipPanel::OnTimer(TObject* Sender) {
IControl *LActiveControl;
TControl *LControl;
TPointF LMousePos;
IControl *LObject;
if (!FOnlyInputFields) {
if (Screen->MousePos() != FMousePoint) {
FMousePoint = Screen->MousePos();
FCounter = 0;
Visible = False;
}
FCounter++;
switch (FCounter) {
case 0:
case 1:
case 2:
Visible = False;
break;
case 3:
Text = "";
if ((dynamic_cast<TForm*>(Parent)) != NULL) {
TForm& ref_object = dynamic_cast<TForm&>(*Parent);
LObject = ref_object.ObjectAtPoint(FMousePoint);
if (LObject != NULL) {
Text = LObject->GetObjectW()->Name;
}
}
if (Text == "") {
Text = "ToolTip for mouse pos " + PointToString(FMousePoint);
}
else {
Text = "ToolTip for component: " + Text;
}
LMousePos = dynamic_cast<TForm*>(Parent)->ScreenToClient(FMousePoint);
ShowToolTip(LMousePos.X, LMousePos.Y);
break;
case 4:
case 5:
case 6:
case 7:
case 8:
case 9:
case 10:
break;
default:
FCounter = 1000;
Visible = False; ;
}
}
else {
if ((dynamic_cast<TForm*>(Parent)) != NULL) {
TForm& ref_LObject = dynamic_cast<TForm&>(*Parent);
LActiveControl = ref_LObject.Focused;
if ((LActiveControl != NULL) & (LActiveControl->GetObjectW()!= FActiveControl)) {
Visible = False;
FActiveControl = LActiveControl->GetObjectW();
if ((dynamic_cast<TEdit*>(FActiveControl)) != NULL) {
FCounter = 0;
}
}
FCounter++;
switch (FCounter) {
case 0:
case 1:
case 2:
Visible = False;
break;
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
case 9:
case 10:
if (LActiveControl != NULL) {
LControl = System::interface_cast<TControl, IControl>(LActiveControl);
Text = "ToolTip for " + LControl->Name;
ShowToolTip(LControl->Position->X + 20, LControl->Position->Y + LControl->Height);
}
break;
default:
FCounter = 1000;
Visible = False; ;
}
}
}
}
- 10. The final interface of the TToolTipPanel should look like this:
- Delphi:
type
TToolTipPanel = class(TPanel)
private
FOnlyInputFields: Boolean;
FMousePoint: TPointF;
FCounter: Cardinal;
FActiveControl: TFmxObject;
FLabel: TLabel;
FTimer: TTimer;
FBorderWidth: Single;
function GetToolTipText: string;
procedure SetToolTipText(const Value: string);
procedure OnTimer(Sender: TObject);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure ShowToolTip(AX, AY: Single);
property Text: string read GetToolTipText write SetToolTipText;
property BorderWidth: Single read FBorderWidth write FBorderWidth;
property OnlyInputFields: Boolean read FOnlyInputFields
write FOnlyInputFields;
end;
- C++:
class TToolTipPanel : public TPanel{
private:
TLabel *FLabel;
TPointF FMousePoint;
TFmxObject *FActiveControl;
TTimer *FTimer;
unsigned FCounter;
bool FOnlyInputFields;
float FBorderWidth;
UnicodeString _fastcall GetToolTipText();
void _fastcall SetToolTipText (const UnicodeString Value);
void _fastcall OnTimer (TObject *Sender);
public:
__fastcall TToolTipPanel(TComponent* Owner);
__fastcall virtual ~TToolTipPanel(void);
__fastcall void ShowToolTip(float AX, float AY);
__published:
__property UnicodeString Text = {read = GetToolTipText, write = SetToolTipText};
__property bool OnlyInputFields = {read = FOnlyInputFields, write = FOnlyInputFields };
__property float BorderWidth = {read = FBorderWidth, write = FBorderWidth };
};
Using the ToolTip
- 1. In the Form Designer, add your components, for example: a TButton, a TCheckBox, a TEdit, a TMemo, a TRectangle.
- 2. Include in the form unit declaration the ToolTip unit defined above.
- 3. Add a private member, of type TToolTipPanel, to the form.
- Delphi:
private
TT : TToolTipPanel;
- C++:
- In the private declaration of TForm, in the header file (.h file) add the following code:
- C++:
private: // User declarations
TToolTipPanel *TT;
- 4. In the OnCreate event of the form, create a TToolTipPanel.
- Delphi:
procedure TForm1.FormCreate(Sender: TObject);
begin
TT := TToolTipPanel.Create(Form1);
TT.Parent := Self ;
end;
- C++:
__fastcall TForm1::TForm1(TComponent* Owner): TForm(Owner){
TT = new TToolTipPanel(this);
TT->Parent = this;
}
- 5. To test the tooltip behavior when the OnlyInputFields property is set to True or False, add the following in the OnChange event of the TCheckBox:
- Delphi:
procedure TForm1.CheckBox1Change(Sender: TObject);
begin
TT.OnlyInputFields := CheckBox1.IsChecked;
end;
- C++:
void __fastcall TForm1::CheckBox1Change(TObject *Sender){
TT->OnlyInputFields = CheckBox1->IsChecked;
}
- 6. Run the application.
- Here are some sample images of the created tooltip:
- The next image shows the inference of a tooltip for an input component for the OnlyInputFields property set to True: