Creating a Metropolis UI ToolTip
From RAD Studio
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: