9

I want button size (width and height) to be as small as possible, but I want it to fit the text. Any code example? Delphi XE4 FireMonkey Mobile Application.

Peter
  • 2,977
  • 1
  • 17
  • 29
Edijs Kolesnikovičs
  • 1,627
  • 3
  • 18
  • 34

2 Answers2

10

FireMonkey renders text via methods using TTextLayout class.
We can access this methods via a class helper and then change the buttons size based on the information provided by the layout.

uses FMX.TextLayout;

type
  TextHelper = class helper for TText
     function getLayout : TTextLayout;
  end;

function TextHelper.getLayout;
begin
  result := Self.fLayout;
end;

procedure ButtonAutoSize(Button : TButton);
var
  bCaption : TText;
  m : TBounds;
begin
  bCaption := TText(Button.FindStyleResource('text',false));
  bCaption.HorzTextAlign := TTextAlign.taLeading;
  bCaption.VertTextAlign := TTextAlign.taLeading;
  m := bCaption.Margins;
  Button.Width  := bCaption.getLayout.Width  + m.Left + m.Right;
  Button.Height := bCaption.getLayout.Height + m.Top  + m.Bottom;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
   ButtonAutoSize(Sender as TButton);
end;

Update

Here is a more future proof solution that doesn't require exposing private class fields.

uses FMX.Objects;

procedure ButtonAutoSizeEx(Button: TButton);
var
  Bitmap: TBitmap;
  Margins: TBounds;
  Width, Height: Single;
begin
  Bitmap := TBitmap.Create;
  Bitmap.Canvas.Font.Assign(Button.TextSettings.Font);
  Width := Bitmap.Canvas.TextWidth(Button.Text);
  Height := Bitmap.Canvas.TextHeight(Button.Text);
  Margins := (Button.FindStyleResource('text', false) as TText).Margins;
  Button.TextSettings.HorzAlign := TTextAlign.Leading;
  Button.Width := Width + Margins.Left + Margins.Right;
  Button.Height := Height + Margins.Top + Margins.Bottom;
end;

This example omits any word wrapping or character trimming.

Peter
  • 2,977
  • 1
  • 17
  • 29
  • It behave strangely on XE6. If I do successive calls on the same button with different text lenghts the width grows if needed but never get reduced. – Regis St-Gelais Sep 09 '14 at 14:51
  • @RegisSt-Gelais, please keep in mind that this was written for XE4. Firemonkey has went through a lot of changes since then so results may vary. You should post a new SO question for XE6 if this is important to you. – Peter Sep 09 '14 at 15:29
  • Thank you for your comment and also thank you for the original solution that you gave.The question is still valid. I don't want to create a duplicate. Maybe by reviving it and saying that it does not work on XE6, someone will post a new solution. Regards. – Regis St-Gelais Sep 09 '14 at 19:22
  • @RegisSt-Gelais, you're right, I've updated my answer with an alternative solution to the problem, hopefully something more future proof. Let me know if this fixes your issues. – Peter Sep 10 '14 at 09:48
  • 1
    It works great with one addition. Some time FindStyleResource returns nil making the app crash. I added something like Text := Button.FindStyleResource('text', false) as TText; if Text is TText then begin .... – Regis St-Gelais Sep 10 '14 at 16:09
  • and I added it before the Bitmap := TBitmap.Create; so that the bitmap is not created if not needed. – Regis St-Gelais Sep 10 '14 at 17:11
  • @RegisSt-Gelais, If you are executing this routine many times then I suggest that you cache the Style object, also probably best to create the Bitmap in the outter scope of this method. Also, you don't need to use the `is` operator, `as` already performs strict type checking automatically. – Peter Sep 10 '14 at 17:25
  • Why don't you access Button.Canvas instead? – yonojoy Sep 05 '16 at 08:27
  • I'll post my code as an other answer. That code does not need to create a bitmap and does not access any private fields and seems to work fine. Feel free to update your answer with this code, then I'll remove my answer. – yonojoy Sep 05 '16 at 08:34
1

Based on the answer of @Peter, but no need to create a bitmap:

//...

type
    TButtonHelper = class helper for TButton
        procedure FitToText(AOnlyWidth: Boolean = False);
    end;

implementation

//...

// Adapt button size to text.
// This code does not account for word wrapping or character trimming.
procedure TButtonHelper.FitToText(AOnlyWidth: Boolean = False);
var
    Margins: TBounds;
    TextWidth, TextHeight: Single;
    Obj: TFmxObject;
const
    CLONE_NO = False;
begin
    Obj := FindStyleResource('text', CLONE_NO);
    if Obj is TText then    //from Stackoverflow comments: Some time FindStyleResource returns nil making the app crash
    begin
        Margins := (Obj as TText).Margins;
        TextWidth := Canvas.TextWidth(Text);
        if not AOnlyWidth then
          TextHeight := Canvas.TextHeight(Text);
        TextSettings.HorzAlign := TTextAlign.taLeading;    //works in XE4
        //later FMX-Versions ?: TextSettings.HorzAlign := TTextAlign.Leading;
        Width := TextWidth + Margins.Left + Margins.Right;
        if not AOnlyWidth then
          Height := TextHeight + Margins.Top + Margins.Bottom;
    end;
end;
yonojoy
  • 5,486
  • 1
  • 31
  • 60
  • @Peter Feel free to update your answer with this code. Then I'll remove my answer, because you owe the original idea. – yonojoy Sep 05 '16 at 08:40
  • Works great on Android, but on Windows it comes out half the size it should be...? (D10 Seattle) – Jerry Dodge Sep 11 '17 at 21:22
  • @JerryDodge I tested this with XE4 and XE8 on Windows without any problems. Does the solution of Peter (ButtonAutoSizeEx) work for you? – yonojoy Sep 18 '17 at 08:01