Why TForm.SetBounds only works correctly when TForm.Position is set to poDefault at design time

I noticed something very strange. I save the properties of the top, left, width and height of the form when it is closed and using this information to restore the last position of the form when it opens again, calling SetBounds using the previously saved information. This works well, but only if the Position form property is set to poDefault at design time. If something else is set, such as poDesigned, poScreenCenter or poMainFormCenter, SetBounds does not restore the previous position and size of the form.

Here is the weird part. Which, apparently, matters when the Position property is set at design time. I can change the value of this property at runtime to poDefault, and calling SetBounds is still not working correctly. I tried something like the following

if Self.Position <> poDefault then Self.Position := poDefault; 

both in the OnCreate event handler and from the overridden constructor (and set Position to poDefault in the constructor and called SetBounds in the OnCreate event handler). In all cases, changing the Position property to poDefault at runtime does not fix the problem that I observed with SetBounds. The only consistent model I found is that SetBounds works as it should, only if the Position property was poDefault at design time.

There are other things that I noticed regarding how SetBounds works when the Position property of the form is not set to poDefault at design time. For example, a form whose Position property is set to poScreenCenter at design time will not necessarily be centered on the screen if you call SetBounds. However, it does not appear in the upper left location specified by SetBounds, and does not take into account the width and height specified in the SetBounds call. Let me repeat, however, that I set the Position property of the form to poDefault before calling SetBounds. I even ran an Application.ProcessMessages call between two operations, but this does not fix the problem.

I tested this with Delphi 10.1 Berlin running on Windows 10. I also tested it with Delphi XE6 on Windows 7. Same results.

If in doubt, create a VCL application with four forms. On the first form, place the three buttons and add something like the following OnClick to each button:

  with TForm2.Create(nil) do try ShowModal; finally Release; end; 

where the constructor creates TForm2, then TForm3 and TForm4.

In OnCreate Forms 2 through 4, add the following code:

 if Self.Position <> poDefault then Self.Position := poDefault; Self.SetBounds(500,500,500,500); 

In form2, set Position to poDefault, in form3 set Position to poScreenCenter and in form4 leave the default position, poDefaultPosOnly. Only form2 will appear at 500, 500, with a width of 500 and a height of 500.

Does anyone have a logical explanation for this result?

+5
source share
1 answer

poDefault and friends mean "let Microsoft Windows position this form window when the form creates and displays it."

You just created a Delphi object, but I wonder if it created and showed a Windows object ( HWND handle and all the corresponding internal Windows structures). Especially with thematic applications, and not with the usual forms of pre-XP - they tend to show ReCreateHWND , since preloading these fancy Windows is relatively expensive and should only be done when necessary.

I think that your default restrictions (each property value set in the constructor can be considered a non-configured default value, which should be set later after building the object) are correctly ignored when you (or TApplication - this is not much different for the topic), finally FormXXX.Show .

In the “make me a window and show it” process, when your form looks at its properties and tells MS Windows something like “now I want to create my internal HWND object and put it in the default coordinates / size of your choice.”

And this is absolutely correct behavior - otherwise WHEN and HOW could TForm apply the Position property ??? It just doesn’t make sense to ask Windows about the coordinates of a window that does not yet exist on the screen, and may never be. Windows offers default default coordinates / sizes for this very second when they are asked, looking at how many other windows are and where they are located (and AMD / NVidia video drivers can also apply their correction to them).

It would make little sense to acquire defaults now and apply them in two hours, when everything is likely to be different - different numbers of other windows and different positions of those with which many sets of monitors are connected and with different resolutions, etc.

Just think of a desktop replacement laptop. It was mounted on a table connected to a large stationary external monitor. Then - imagine - I launched your application and created the Delphi tform object, and in the constructor he requested MS Windows for the position - and Windows rightfully suggested the position on this very extra large monitor. But after an hour I disconnected the notebook and left with it. Now in an hour, I will tell your application to show the form - and he will do what? display it with coordinates related to this remote external display? Outside the viewport of the laptop’s internal display, which I only have now? Should this form be displayed in an “invisible” position only because when I started the application, then this place was still visible? I believe that you can confuse users without benefits.

So, the only correct behavior would be to ask Windows by default for the codes this second. WHEN the form goes from hidden to visible, and not the second before.

And this means that if you want to move the form, you must do it after it is shown. Put your Self.SetBounds(500,500,500,500); into the OnShow event OnShow . Thus, let MS Windows materialize your form to the default position, as required by poDefault in Position , property - and after that move your window. Attempting to move a window that does not exist yet seems futile to me.

Either PRESET your form (when constructing a sequence) to explicitly ignore MS Windows by default and use predefined cords (via a poDesigned value), or let the form request Windows coordinates, but MOVE using SetBounds after it is visible using the OnShow handler.

+2
source

All Articles