Recent

Author Topic: [SOLVED]Calculating button width with autosize True, border values return 0  (Read 664 times)

old_DOS_err

  • New Member
  • *
  • Posts: 44
Hi,

Prelim

First up, I am an old programmer. Second, I cannot use 3 words instead of 30 :). My hey day was 80s and 90s, so forms are still a mystery to me. I still prefer to program in DOS when possible, but at times forms are indispensable.

The project is in Windows 10.

Currently adding a tab system to my project using TButtons (I found TTabControl very unsound and did some very odd things, like shifting a hard coded Left to a minus value and starting the tabs way off the screen). Have got the button system basically working, including a right click menu.

But ran into the classic Autosize problem. Because the captions can vary, need Autosize True, but this returns the wrong Width value. If I turn Autosize off, then it reduces the size of the button if the caption was longer than standard button size.

So the core problem is how to calculate the length of a button when Autosize is True. What I've got which is not ideal but works is using a hidden label and using that to calc the size of the text via canvas. Then just guessed the border spacing.

Main

What I had hoped for was a more precise way to either get the actual width of the button, ideal, or get an accurate value of the border spacing. Clearly it seems I have not understood the border values, as they nearly all came back as 0.

This is a snippet from the add tab code, with the core of the working system. The following is inside a With block. If it makes any difference it uses a child to TButton (a little trick from Remy in a forum long ago as to how to know which button was pressed in a set of created).

Code: Pascal  [Select][+][-]
  1. // attempts to get width direct
  2.       //w := Width;
  3.       //r := ClientRect;  // local var TRect
  4.       //w := r.Width;
  5.       //w := ClientWidth;
  6.  
  7.       // this works, but does not look good or feel sound
  8.       hidden_Label.Font := Font;  // need to set the same font to get the accurate size
  9.       w := hidden_Label.Canvas.TextWidth( Caption );  // just the text
  10.       Inc( w, 20 );  // guess work, good value
  11.  
  12. // attempts to find the accurate border spacing or other value I could use
  13. //s := BorderWidth;
  14. //s := BorderSpacing.Left;
  15. //s := BorderSpacing.AroundLeft;
  16. //s := BorderSpacing.ControlLeft;  // returned values, but no use, but did lead to Right
  17. //s := BorderSpacing.ControlRight;  // returned values, but they were wrong, assume fixed width again
  18. //s := BorderSpacing.Around;
  19. //s := BorderSpacing.InnerBorder;
  20. //ShowMessage( 'BorderWidth = ' + s.ToString );
  21.       Inc( working_left, w );  // working_left is a Word var used to know where to start the next button
  22.  

As I mentioned, I have a working system, so not critical if I don't get an improved answer, but it would be helpful to know first how other people calculate variable button width, it must be fairly common, and secondly whether I set up something wrong which caused nearly all the values to come back 0.

Thanks for any info offered.

Phil
« Last Edit: October 09, 2025, 05:14:14 pm by old_DOS_err »

wp

  • Hero Member
  • *****
  • Posts: 13213
Re: Calculating button width with autosize True, border values return 0
« Reply #1 on: October 06, 2025, 04:04:37 pm »
Several thoughts come to my mind:

First of all, I don't fully understand what you want to achieve. A "button system"... In my opinion TTabControl should work, but if I'd have to do it myself I'd use a panel and add all the buttons needed. Then I'd go to the ChildSizing property of the panel and set Layout to cclTopToBottomThenLeftToRight -- this arranges the buttons in a row and auto-sizes them (cclTopToBottomThenLeftToRight seems wrong since there is also cclLeftToRightThenTopToBottom, but the ControlsPerLine is 0 by default which makes the first one be to have the correct behaviour; but you could also switch ControlsPerLine to the number of buttons (or larger) and select the other one). Give HorizontalSpacing a reasonable value to add some distance between the buttons.

If you have a specific common button width in mind which should be used even when the autosize width is shorter, set it in the Constraints.MinWidth of the button. This works also without the ChildSizing: When you autosize an "OK" button it becomes significantly smaller than the "Cancel" button; therefore they are often given a Constraints.MinWidth of 75 or 80 which results in about the same width but still keeps the option the grow the width when a caption becomes longer, for example after translation of the user interface.

Returning to your main question, how to get the width of an autosized button -- well, actually only set AutoSize to true and read the Width property; this will take care of the caption length as well as inner margins used by the button by default  (even without touching BorderSpacing). But don't do this too early; in the OnCreate event the result will be wrong. OnActivate or OnShow are fine.

Or do you want to measure the width of the caption only? Basically you can use the Canvas of the control and its TextWidth method. But the Canvas, for sure, exists only during the drawing cycle. Therefore, I often use a temporary bitmap whose canvas exists as long as the bitmap exists:
Code: Pascal  [Select][+][-]
  1. function GetTextWidth(AText: String; AFont: TFont): Integer;
  2. var
  3.   bmp: TBitmap;
  4. begin
  5.   bmp := TBitmap.Create;
  6.   try
  7.     bmp.SetSize(10, 10);  // exact size not important
  8.     bmp.Canvas.Font.Assign(AFont);
  9.     Result := bmp.Canvas.TextWidth(AText);
  10.   finally
  11.     bmp.Free;
  12.   end;
  13. end;


old_DOS_err

  • New Member
  • *
  • Posts: 44
Re: Calculating button width with autosize True, border values return 0
« Reply #2 on: October 06, 2025, 05:51:38 pm »
Hi WP,

Thank you for your reply. Sorry, I’m not good at explaining myself. My project is a shell type, it has nested searches. That all works. But what I wanted to add was a tab system which goes below the panel with the file list. This allows the user to give a name to the search just done (it has advanced search for things like date ranges), and then the user can click on any tab and see that search. There is also a right click facility whereby the user can trim the tabs to the right, hence the requirement for the popup menu.

So the tab system is already well into the program and is added one at a time. Plus can be trimmed. TTabControl should have been ideal, and in mechanism it was, but the interface was the problem. Did have a question in Beginners since there seemed no way to set the size of the tabs to the size of the control, which left an ugly white rectangle across the screen. Jamie partly helped solve that, but even then, there were still complications. The issue with the Left value going negative was the final straw. I’d already done a couple of programs with created buttons, but had never packed them tightly, so previous width calcs could be a bit out.

I really like the look of your text width function. Forms programming is not my area, so I often just end up with bad hacks, like using the hidden label.

Anyway, I hope that helps explain the context. And thanks again for the detailed reply.

Phil

jamie

  • Hero Member
  • *****
  • Posts: 7308
Re: Calculating button width with autosize True, border values return 0
« Reply #3 on: October 07, 2025, 12:34:31 am »
just to add some fluff!

Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. Var
  3.   X,Y:Integer;
  4. begin
  5.   Button1.Font.GetTextSize('adasdas',X,Y);
  6.   Button1.Width := X;
  7.   Button1.Height:= Y;
  8. end;                  
  9.  

Crashes badly and the code it is pointing at does not look good!
The only true wisdom is knowing you know nothing

jamie

  • Hero Member
  • *****
  • Posts: 7308
Re: Calculating button width with autosize True, border values return 0
« Reply #4 on: October 07, 2025, 12:57:29 am »
But!

Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. Var
  3.   X,Y:Integer;
  4.   C:TControlCanvas;
  5. begin
  6.   C:= TControlCanvas.Create;
  7.   C.Control := Button1;
  8.   C.GetTextSize(Button1.Caption,X,Y);
  9.   Button1.Width := X+C.TextWidth(' ')*4;
  10.   Button1.Height:= Y*2;
  11.   C.Free;
  12. end;                                
  13.  
  14.  

Jamie
The only true wisdom is knowing you know nothing

old_DOS_err

  • New Member
  • *
  • Posts: 44
Re: Calculating button width with autosize True, border values return 0
« Reply #5 on: October 07, 2025, 01:33:17 pm »
Hi Jamie,

Thanks for the interesting reply.

First up, thanks to WP for that little util for text width, I put that into my forms utility library, it works perfectly and allowed me to get rid of the hidden label - nice one.

Let me see if I understood your approach. What I think you are saying is that since the easy part, oddly, is working out the length of the text length (should be the hardest part, but several ways to achieve that), and since there will be some padding on either side, rather than set Autosize True, then guess the width of the padding (as I had to do), actually set Autosize to False and  calculate a known width, ie width of text + defined padding, then set the width of the button to that.

Hang on a mo…

Well done sir. Just tried that in my testing program for the tab system (I create test front ends for all library code, even basic functions, aging has some advantages), it worked nicely.

Thank you. I notice I still view forms as something almost magical and forget underneath they are still just basic code.

Phil

old_DOS_err

  • New Member
  • *
  • Posts: 44
Ah ha, found the main problem. Sorry, rooky mistake, I think.

I was making a minimum version for the forum, which just added 2 created buttons, with long and short captions, with the second one right next to the first, by calculating the width of the first. And of course, it worked. Went back to the main test, and of course, it didn't work.

I was trying to see what was different. When I created the button in the main, I set Visible to False. It's a habit from the really old days where things changed so slowly, it would cause an unpleasant flicker or movement. So one would turn off display updates, until the updates were done. But what I found was that if Visible was False, Width was unable to work correctly and gave, I assume, the default value.

Thanks wp and Jamie for at least trying to deal with my foolishness.

The only thing not resolved, but doesn’t matter I suppose, is what is the purpose of the border variables. Looking at a free pascal page, I can see I misunderstood what border was being referred to, I thought it was the space around the text, but it seems to be where one actually adds a border to the button.

wp

  • Hero Member
  • *****
  • Posts: 13213
The only thing not resolved, but doesn’t matter I suppose, is what is the purpose of the border variables. Looking at a free pascal page, I can see I misunderstood what border was being referred to, I thought it was the space around the text, but it seems to be where one actually adds a border to the button.
The BorderSpacing values come into play when a control is positioned by the LCL. Suppose you put a TMemo into a form and client-align it (Memo1.Align := alClient). Then the memo fills the entire space of the form leaving no margin. If you think that this looks strange (like I do) and want to put some margin of - say - 8 pixels around the memo you can simply set Memo1.BorderSpacing.Around = 8. Other use cases are anchoring, childsizing, or TFlowPanel.

old_DOS_err

  • New Member
  • *
  • Posts: 44
Thanks wp, forms are still a mystery to me, I only really use them when needed. Even back when I was working, front ends were always my weakest point. The fact I always use 30 words instead of 3 doesn't help :)

 

TinyPortal © 2005-2018