Resizing a FireMonkey Combo Box to Fit it’s Contents

MonkeyStyler displays two combo boxes on it’s toolbar, one to list the currently opened files and a second to list the elements in the current file. A number of users mentioned that long items in the combo box would be cropped and sometimes difficult to identify.

Creating a routine to resize a combo box made an interesting little challenge. We need to find the length of the longest item, but this needs to take account of the current font settings, and there’s no convenient ‘TextWidth’ function in FireMonkey. But we cab do it using a TText object.

I started by creating a TText object with AutoSize set to True and WordWrap set to False.

:= TText.Create(nil);
    
T.AutoSize := True;
    
T.WordWrap := False


Next we need to get the font used. In FireMonkey a TComboBox owns a TListBox which is used for the dropdown and the combo box copies the contents of the selected item into itself. So we really need the font of a TListBoxItem. I chose to use the font assigned to an actual list box item of the component:

if Items.Count 0 then
    begin
      Item 
:= ListBox.ListItems[0];
      
Item.ApplyStyleLookup;
      
:= Item.FindStyleResource('text');
      if 
O is TText then
        T
.Font.Assign(TText(O).Font);
            ... 


Note the call to ApplyStyleLookup which we need in case the item doesn’t currently have it’s styling applied. This could be because it is newly created or the styling has been removed - in FM2 the stying of non-visible controls is removed to save resources.

Next up is to iterate over the strings in Items and get the width of the widest,

for S in Items do
      
begin
        T
.Text := S;
        
//Bug in XE3: Assigning Text doesn't resize a TText, so hack around it.
        
T.AutoSize := False;
        
T.AutoSize := True;
        if 
<> nil then
          NewWidth 
:= T.Width+TControl(O).Padding.Left+TControl(O).Padding.Right;
        if 
NewWidth MinWidth then
          MinWidth 
:= NewWidth;
      
end


In the initial release of XE3 there is a bug in TText such that it doesn’t resize when you set the Text property, so we do a little dance with AutoSize to force an update. Also note we’re taking account of the Padding of the ‘text’ resource.

And finally we need to assign the new width, but since there is other stuff in the style besides the text we don’t know exactly what that is (especially if the style later changes). So, we calculate the width of the extra stuff with the current settings (Width-TControl(O).Width) and add out new width for the text,

:= FindStyleResource('content');
    if 
O is TControl then
      Width 
:= Width-TControl(O).Width+MinWidth

Putting it together, here is the complete code:

type TSolentComboBox = class(TComboBox)
  public
    
procedure ResizeToContents(MinWidthSingle);
  
end;
    
...

procedure TSolentComboBox.ResizeToContents(MinWidthSingle);
var
  
TTText;
  
NewWidthSingle;
  
OTFMXObject;
  
SString;
  
ItemTListBoxItem;
begin
  
try
    
:= TText.Create(nil);
    
T.AutoSize := True;
    
T.WordWrap := False;
    
:= nil;
    if 
Items.Count 0 then
    begin
      Item 
:= ListBox.ListItems[0];
      
Item.ApplyStyleLookup;
      
:= Item.FindStyleResource('text');
      if 
O is TText then
        T
.Font.Assign(TText(O).Font);
      
NewWidth := 0;
      for 
S in Items do
      
begin
        T
.Text := S;
        
//Bug in XE3: Assigning Text doesn't resize a TText, so hack around it.
        
T.AutoSize := False;
        
T.AutoSize := True;
        if 
<> nil then
          NewWidth 
:= T.Width+TControl(O).Padding.Left+TControl(O).Padding.Right;
        if 
NewWidth MinWidth then
          MinWidth 
:= NewWidth;
      
end;
    
end;

    
:= FindStyleResource('content');
    if 
O is TControl then
      Width 
:= Width-TControl(O).Width+MinWidth;

  finally
    
T.Free;
  
end;
end

Previous Comments

#1 from .(JavaScript must be enabled to view this email address) on December 07, 2012

I have a different problem but similar problem. Rather than resize the control I want to change the font size so the text fixs withing the designed control size.

TComboEdit has a font property but TCombBox does not. How to you set the font size on a TComboBox for all its members? It used to be so simple under VCL but FM is so much more complicate and also so poorly documented.

I would also like to know how you crop an image that is too big for TImage. In the old VCL days you got scroll bars but no more!

I would also like to know how to reliable set the back colour on a FM control. Following works on TPanel but not other controls like TTabItem

procedure FMControlBackColour(const Control: TControl; BackColour: TAlphaColor);
const
  XRadius = 0;
  YRadius = 0;
  Corners = [TCorner.crTopLeft, TCorner.crTopRight, TCorner.crBottomLeft, TCorner.crBottomRight];
  Opacity = 1;
  CornerType = TCornerType.ctRound;
var
  Rect: TRectF;
begin
  with Control do begin
    Rect.Top := 0;
    Rect.Left := 0;
    Rect.Width := Width;
    Rect.Height := Height;
    Canvas.Fill.Color := BackColour;
    Canvas.FillRect(Rect, XRadius, YRadius, Corners, Opacity, CornerType);
  end;
end;

Any help would be appreciated.

#2 from .(JavaScript must be enabled to view this email address) on December 08, 2012

One other problem I neglected to mention. TComboBox and TComboEdit to not appear to work at all on the Mac. The drop down list does not and the Comboedit box will not allow you to enter or alter existing text. What obvious thing am I missing here? It works perfectly on windows but not when loaded on a Mac. I created a form with only these two controls on it to verify it was not some interaction with my problem that was at fault. Still did not work. I am using XE2 for these tests and am reluctant to spend money on XE3 based on my experiences with XE2.

#3 from .(JavaScript must be enabled to view this email address) on December 08, 2012

The text of the combobox is that of the TListItem is is displaying, so you’d need to either change the ListBoxStyle style element, or to change an individual item, change the font settings on the ‘text’ component from the style (See FindStyleResource method).

To get an image with scrollbars, place it in a TScrollBox.

For the background you would be best off modifying the style, or using a custom style. The exact method to use will depend on the component you want to modify.

#4 from .(JavaScript must be enabled to view this email address) on December 08, 2012

I’ve never done Mac work, so can’t really comment. You may want to ask on the Embarcadero forums.

#5 from .(JavaScript must be enabled to view this email address) on December 10, 2012

TScrollBox does not work when I tried it but I discovered TImageViewer which does exactly what I want it to. Thanks.

Commenting is not available in this channel entry.