Recent

Author Topic: [SOLVED] How to query the outer size of a 'TForm' ?  (Read 1861 times)

Hartmut

  • Hero Member
  • *****
  • Posts: 843
[SOLVED] How to query the outer size of a 'TForm' ?
« on: October 11, 2024, 06:44:15 pm »
When I set a size to a 'TForm' like
Code: Pascal  [Select][+][-]
  1. Form1.Width:=200;
  2. Form1.Height:=100;
then this is the inner size. The outer size of the Form is bigger, because of the Title bar and the 3 borders at the left, right and bottom side. I need these values for certain computations and because these values are different in each OS I want to query them.

I want to make a common procedure therefore in a common Unit. This means, I cannot reference to the Main Form. This is my code so far:
Code: Pascal  [Select][+][-]
  1. procedure queryFormOuterSize(out TitelHeight,BorderWidth: integer);
  2.    {determines the Height of the TitleBar and the Width of the left, right and
  3.     bottom Border of a 'TForm'}
  4.    var F: TForm;
  5.        P: TPoint;
  6.    begin
  7.    F:=TForm.CreateNew(nil);
  8.    F.Visible:=true; {neccessary for Windows and Linux}
  9. {$IFDEF LINUX}
  10.    Application.ProcessMessages; {neccessary for Linux only}
  11. {$ENDIF}
  12.    P:=F.ClientOrigin; {Screen coordinates of TopLeft pixel of client area of the Form}
  13.    TitelHeight:=P.y-F.Top;
  14.    BorderWidth:=P.x-F.Left;
  15.    F.Free;
  16.    end;

This works fine for the Height of the TitleBar on Win7 (24), Win10 (31) and Linux Ubuntu 22.04 (29). But this code has 3 disadvantages:

(a) the result for 'BorderWidth' is correct on Win7 and Linux (both 4), but wrong on Win10 (returns 8 instead of 1).

(b) this code needs 'F.Visible:=true' which results in a flicker.

(c) on Linux this code needs 'Application.ProcessMessages'. This can call/react to key strokes and Events and I don't want to have this in a common procedure in a common unit, where you would not expect such side affects.

Can somebody please help to avoid this disadvantages? Thanks in advance.
« Last Edit: October 14, 2024, 06:07:11 pm by Hartmut »

MarkMLl

  • Hero Member
  • *****
  • Posts: 7999
Re: How to query the outer size of a 'TForm' ?
« Reply #1 on: October 11, 2024, 06:55:32 pm »
You cannot, in the general case do this for "a unix" since all details relating to dimensions of the "furniture" and actual placement of the window are handled by the window manager (which in practice these days is often part of the desktop environment). In short, if the hack you've got delivers adequate results use it.

Having said which, there was a recent thread on moving windows between "desktops" etc. which delved in considerable depth into the WM API, and it might be that the properties you need to query are sufficiently-well standardised that you could do something that way.

Regarding the APM, that's probably entirely down to the widget set. What you're seeing is that the Windows widget set doesn't need it, while whichever one you're using on Linux does.

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

rvk

  • Hero Member
  • *****
  • Posts: 6572
Re: How to query the outer size of a 'TForm' ?
« Reply #2 on: October 11, 2024, 06:59:32 pm »
When I set a size to a 'TForm' like [snip] then this is the inner size.
Is this really the case? I thought TForm.Height is the outer-size. At least it is for me on Windows.

(Although newer Windows versions can have an invisible border around the window.)

I thought TForm.ClientHeight is the inner-size.

MarkMLl

  • Hero Member
  • *****
  • Posts: 7999
Re: How to query the outer size of a 'TForm' ?
« Reply #3 on: October 11, 2024, 07:05:08 pm »
Is this really the case? I thought TForm.Height is the outer-size. At least it is for me on Windows.

Fundamental difference between Windows and X11 on unix. It used to be possible to kill the window manager at which point all the furniture would disappear (without any change to the application window) and start a completely different one.

Oh, and scrollbars are traditionally part of the furniture, although I think that they might be overshadowed by controls with their own.

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

dsiders

  • Hero Member
  • *****
  • Posts: 1280
Re: How to query the outer size of a 'TForm' ?
« Reply #4 on: October 11, 2024, 09:54:27 pm »
When I set a size to a 'TForm' like [snip] then this is the inner size.
Is this really the case? I thought TForm.Height is the outer-size. At least it is for me on Windows.

(Although newer Windows versions can have an invisible border around the window.)

I thought TForm.ClientHeight is the inner-size.

Height and Width do not reflect anything considered non-client areas - like Window titles, frames/borders, or TMainMenu. In the current LCL, ClientHeight and ClientWidth are identical to Height and Width respectively. I know... it doesn't seem right, but that's what it is.

I haven't checked on scrollbars...
« Last Edit: October 11, 2024, 10:23:27 pm by dsiders »
Preview the next Lazarus documentation release at: https://dsiders.gitlab.io/lazdocsnext

rvk

  • Hero Member
  • *****
  • Posts: 6572
Re: How to query the outer size of a 'TForm' ?
« Reply #5 on: October 11, 2024, 10:30:04 pm »
Height and Width do not reflect anything considered non-client areas - line Window titles, frames/borders, or TMainMenu. In the current LCL, ClientHeight and ClientWidth are identical to Height and Width respectively. I know... it doesn't seem right, but that's what it is.
Ok, weird, I never knew that  :-[

Yes, it seems Height is the same as what ClientHeight is in Delphi.

I had a look for SM_CYCAPTION (where it is used), which is the code for getting the title height, and it seems that GetSystemMetrics() is made cross-platform.
TWidgetSet.GetSystemMetrics(SM_CYCAPTION) should provide the title height.
Other codes could/should work too.

Can you test these on Linux?

Code: Pascal  [Select][+][-]
  1. uses
  2.   LCLIntf, LCLType;
  3. // ...
  4.  Showmessage(
  5.    'SM_CYCAPTION ' + GetSystemMetrics(SM_CYCAPTION).ToString + #13 +
  6.    'SM_CXBORDER  ' + GetSystemMetrics(SM_CXBORDER).ToString + #13 +
  7.    'SM_CYHSCROLL ' + GetSystemMetrics(SM_CYHSCROLL).ToString + #13 +
  8.    'SM_CXHSCROLL ' + GetSystemMetrics(SM_CXVSCROLL).ToString + #13
  9.    );

BTW. That borderwidth of 8 for Win10 is due to the shadow-border. That gives 7 pixels extra at each side of the window. Look into DWMWA_EXTENDED_FRAME_BOUNDS. So that 8 might actually be correct if you include that shadowborder.
https://stackoverflow.com/a/34143777/1037511
« Last Edit: October 11, 2024, 11:27:46 pm by rvk »

MarkMLl

  • Hero Member
  • *****
  • Posts: 7999
Re: How to query the outer size of a 'TForm' ?
« Reply #6 on: October 12, 2024, 10:12:30 am »
On Debian 12 ("Bookworm") with KDE

Code: [Select]
SM_CYCAPTION 30
SM_CXBORDER  4
SM_CYHSCROLL 20
SM_CXHSCROLL 20

which broadly speaking look plausible. The interesting thing is this comment related to the constants:

Code: Pascal  [Select][+][-]
  1.   {needed for accurate maximized window size, since under X11 we cannot get it until
  2.    window is decorated by wm. see issue #21119.}
  3.   SM_LCLMAXIMIZEDWIDTH = 121;
  4.   SM_LCLMAXIMIZEDHEIGHT = 122;
  5.  

I've not investigated in any greater detail, and in particular don't know whether these are "inferred" from apparent window layout or if they are extracted from window manager (i.e. WM in the above comment) metrics.

If it's the latter case, I've definitely had metrics problems in the past. The context was a program running on a Raspberry Pi under their own distro (i.e. rather than a straight Debian etc.) where one of the apps had a restriction on window size based on one of the font metrics... which wasn't provided by their homegrown WM. Needless to say neither the app authors nor the RPi maintainers admitted guilt: I eventually just moved to a PC.

So there are DEFINITELY areas here where one has to be cautious.

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

Hartmut

  • Hero Member
  • *****
  • Posts: 843
Re: How to query the outer size of a 'TForm' ?
« Reply #7 on: October 12, 2024, 12:18:30 pm »
Thanks a lot to all for your replies, especially to dsiders for clarification and rvk for your GetSystemMetrics() idea, which sounded very interesting.

Then I made several tests with GetSystemMetrics() on different OS:

Win7:
Code: [Select]
SM_CYCAPTION 20 (but correct would be 24)
SM_CXBORDER  1  (but correct would be 4)
SM_CYHSCROLL 16 (correct)
SM_CXHSCROLL 16 (correct)

Win10:
Code: [Select]
SM_CYCAPTION 23 (but correct would be 31)
SM_CXBORDER  1  (correct)
SM_CYHSCROLL 17 (dont' know if correct)
SM_CXHSCROLL 17 (dont' know if correct)

Linux Ubuntu 22.04 with KDE-Plasma desktop:
Code: [Select]
SM_CYCAPTION 30 (but correct would be 29)
SM_CXBORDER  4  (correct)
SM_CYHSCROLL 16 (but correct would be 19)
SM_CXHSCROLL 16 (but correct would be 19)

This doesn't look so good. Additionally on Linux (not on Windows) there is a difference depending on the point in time you do this GetSystemMetrics() query: started in the main FormActivate() you get the above values. Started in a Button1Click() the value for 'SM_CYCAPTION' is 32 instead of 30 (and both are wrong).

So my conclusion is, that GetSystemMetrics() unfortunately is not usable for this purpose...

I think it could be interesting, how function TForm.ClientOrigin() determines/initializes its values, which I used in my demo in my 1st post (please see above) and which returns correct values for the Height of the TitleBar. I did not find this in the sources. Does somebody know, where are the sources, where the results of TForm.ClientOrigin() are initialized?

440bx

  • Hero Member
  • *****
  • Posts: 4727
Re: How to query the outer size of a 'TForm' ?
« Reply #8 on: October 12, 2024, 03:12:16 pm »
I'm going to provide some "pointers" to the answer and it is Windows only.  I have no idea how to do any of this stuff in Linux.

The problem with determining the dimensions of window elements is that the sizes seem to vary depending on factors that are often unclear.  For instance, the Windows desktop manager and theme "manager" affect the sizes and it is not really clear how they decide the sizes.  I faced that problem in a number of occasions for different reasons.

A reasonable, but not really simple, solution was obtained using AdjustWindowRect(Ex), GetWindowRect, GetClientRect and modifying window attributes that caused scrollbars, frames and other "items" to be included/excluded.  Of course, all this done on a window that is not visible.  A lot of acrobatics but, the results were accurate and reliable.  Of course, this also implies pure API but, the knowledge obtained using pure API should be "portable" (might require a tweak or two) to "forms"/LCL.

Unfortunately, I don't have a sample program that calculates everything.  In all cases I coded the stuff "on the fly" to reliably determine one or two item sizes.  The last time I needed to figure out the Window size from the client size (which was fairly recently), I decided to "wing it" and simply used a calculated reasonable estimate which worked quite nicely (the window ended up being 2 or 3 pixels larger than it needed to be... I didn't think 3 pixels were worth the sweat to get rid of them... it looks perfectly fine, as if it had been done on purpose.)

I know the above isn't much help but, the point is: if you want accurate and reliable sizes, there is a fair amount of work involved in getting them.

Lastly, if someone has a genuinely simple solution that is portable across Windows versions that produces accurate and reliable results, I'd love to see it too.

HTH.

(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

jamie

  • Hero Member
  • *****
  • Posts: 6733
Re: How to query the outer size of a 'TForm' ?
« Reply #9 on: October 12, 2024, 03:27:40 pm »
The only sure bet is that Form Height and Client height are the same, so basically the form height is the client height in the LCL.

To the actual outside frame size, you need to use the GetWindowRect. which then reports the actual true form size.

I just happen to be in this area of coding recently where I wanted to plant a form inside a client area of another, like an MDI app.
The only true wisdom is knowing you know nothing

Hartmut

  • Hero Member
  • *****
  • Posts: 843
Re: How to query the outer size of a 'TForm' ?
« Reply #10 on: October 12, 2024, 06:56:30 pm »
Special thanks to 440bx for your detailed explanations and to jamie for also mentioning 'GetWindowRect'. With this I could successfully create this code:

Code: Pascal  [Select][+][-]
  1. {determines the outer size of a 'TForm' (Windows only!) 12.10.24}
  2.  
  3. unit Unit1;
  4.  
  5. {$mode objfpc}{$H+}
  6. {$apptype console} {neccessary for writeln}
  7.  
  8. interface
  9.  
  10. uses
  11.  Windows, Classes, SysUtils, Forms, Controls, Graphics, Dialogs;
  12.  
  13. type
  14.  
  15.  { TForm1 }
  16.  
  17.  TForm1 = class(TForm)
  18.   procedure FormActivate(Sender: TObject);
  19.  private
  20.  public
  21.  end;
  22.  
  23. var
  24.  Form1: TForm1;
  25.  
  26. implementation
  27.  
  28. {$R *.lfm}
  29.  
  30. function winGetWindowRect(h: HWND; var R: TRect): bool;
  31.    {returns the OUTER coordinates of the window with Handle 'h'}
  32.    var ok: boolean;
  33.    begin
  34.    ok:=windows.GetWindowRect(h, R);
  35.    exit(ok);
  36.    end;
  37.  
  38. procedure queryFormOuterSizeWin(out TitelHeight,BorderWidth: integer);
  39.    {determines the Height of the TitleBar and the Width of the left, right and
  40.     bottom Border of a 'TForm' on WINDOWS only}
  41.    var F: TForm;
  42.        R: TRect;
  43.    begin
  44.    F:=TForm.CreateNew(nil);
  45.    F.SetBounds(150,150,100,100); {just to avoid negative coordinates}
  46. // F.Visible:=true; {Form MUST NOT be visible!!}
  47.  
  48.    winGetWindowRect(F.Handle, R);
  49.    TitelHeight:=F.Top-R.Top;
  50.    BorderWidth:=F.Left-R.Left;
  51.    F.Free;
  52.    end; {queryFormOuterSizeWin}
  53.  
  54. { TForm1 }
  55.  
  56. procedure TForm1.FormActivate(Sender: TObject);
  57.    var TitelHeight,Border_Width: integer;
  58.    begin
  59.    queryFormOuterSizeWin(TitelHeight,Border_Width);
  60.    writeln('TitelHeight=', TitelHeight, ' BorderWidth=', Border_Width);
  61.    Close;
  62.    end;
  63.  
  64. end.

I tested on Win7 and Win10 and the results are the same as in my 1st demo in my 1st post:
 - the returned Height of the TitleBar is on Win7 = 24 and on Win10 = 31 (both correct)
 - the returned result for 'BorderWidth' is on Win7 = 4 (correct) and on Win10 = 8 (correct if you include the shadow width).
The advantage over my 1st demo in my 1st post is, that the Form now does not be visible, so no flicker.

If you have Win11: could you please run the code (attached as project) and report the result? Thanks a lot.

And for Linux:
I think it could be interesting, how function TForm.ClientOrigin() determines/initializes its values, which I used in my demo in my 1st post (please see above) and which returns correct values for the Height of the TitleBar. I did not find this in the sources. Does somebody know, where are the sources, where the results of TForm.ClientOrigin() are initialized?

440bx

  • Hero Member
  • *****
  • Posts: 4727
Re: How to query the outer size of a 'TForm' ?
« Reply #11 on: October 12, 2024, 07:16:26 pm »
@Hartmut,

You're most welcome.
(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

dseligo

  • Hero Member
  • *****
  • Posts: 1406
Re: How to query the outer size of a 'TForm' ?
« Reply #12 on: October 12, 2024, 07:32:20 pm »
If you have Win11: could you please run the code (attached as project) and report the result? Thanks a lot.

Result in console:
Code: Text  [Select][+][-]
  1. TitelHeight=0 BorderWidth=0

Hartmut

  • Hero Member
  • *****
  • Posts: 843
Re: How to query the outer size of a 'TForm' ?
« Reply #13 on: October 12, 2024, 07:34:55 pm »
Thank you dseligo for testing.

Thats frustraiting... Has anybody an idea why this program does not work on Win11?

440bx

  • Hero Member
  • *****
  • Posts: 4727
Re: How to query the outer size of a 'TForm' ?
« Reply #14 on: October 12, 2024, 07:48:58 pm »
Just a comment...

You're using GetWindowRect to get the window's outer dimension, which is fine.

However, you're using the form's top in the calculation of the title height.  I would use GetClientRect's top in the calculation instead.  (Note that the presence of a menu affects the result.)

Basically, the first thing I'd do is match the result of GetWindowRect and GetClientRect to their equivalent in the form.  That way you know what the form's top, bottom, right and left actually mean (presuming they match the results of one of the Windows Get... functions.)

IOW, I'd use only API functions to do all the calculations.  Once I've figured out everything I need to know using API functions then I'd match the values obtained from APIs with values obtained from the form. 

HTH.
(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

 

TinyPortal © 2005-2018