Creating a Metropolis UI ToolTip

From RAD Studio
Jump to: navigation, search

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:
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:
__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:
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:
Tooltip over a component.png Tooltip mouse position.png
The next image shows the inference of a tooltip for an input component for the OnlyInputFields property set to True:
OnlyInputFields False.png OnlyInputFields True.png

See Also

Retrieved from "https://docwiki.embarcadero.com/RADStudio/Tokyo/e/index.php?title=Creating_a_Metropolis_UI_ToolTip&oldid=232260"