我注意到一件很奇怪的事情。我将在窗体关闭时持久化窗体的top、left、width和height属性,并使用这些信息在窗体再次打开时通过使用先前存储的信息调用SetBounds来恢复窗体的最后位置。这很有效,但前提是在设计时将窗体的Position属性设置为poDefault。如果设置为其他值,如poDesigned, poScreenCenter,或poMainFormCenter, SetBounds不会恢复表单之前的位置和大小。
奇怪的是。似乎重要的是在设计时将Position属性设置为什么。我可以在运行时将该属性的值更改为poDefault,而对SetBounds的调用仍然不能正确工作。我已经尝试了如下操作
if Self.Position <> poDefault then
Self.Position := poDefault;
在表单的OnCreate事件处理程序中,以及从一个重写的构造函数(并在构造函数中设置Position为poDefault,并在OnCreate事件处理程序中调用SetBounds)。在所有情况下,在运行时更改表单的位置属性为poDefault并不能解决我观察到的SetBounds问题。我发现的唯一一致的模式是,SetBounds工作,因为它应该只有当表单的位置属性是poDefault在设计时。
还有其他的事情,我已经注意到关于如何SetBounds工作时,窗体的位置属性不设置为poDefault在设计时。例如,如果在设计时将位置属性设置为poScreenCenter的窗体调用SetBounds,则窗体不一定会出现在屏幕中央。然而,它不会出现在SetBounds定义的左上角位置,也不会尊重SetBounds调用中指定的宽度和高度。但是,让我重复一下,在调用SetBounds之前,我将窗体的Position属性设置为poDefault。我甚至给申请部门打了个电话。两个操作之间的ProcessMessages,但这并不能解决问题。
我在Windows 10上运行的Delphi 10.1 Berlin上进行了广泛的测试。我还在Windows 7上使用Delphi XE6进行了测试。同样的结果。
如果您有疑问,可以创建一个包含四个表单的VCL应用程序。在第一个表单上放置三个按钮,并为每个按钮添加如下OnClick:
with TForm2.Create(nil) do
try
ShowModal;
finally
Release;
end;
,其中构造函数先创建TForm2,然后创建TForm3和TForm4。
在表单2到表单4的OnCreate中,添加以下代码:
if Self.Position <> poDefault then
Self.Position := poDefault;
Self.SetBounds(500,500,500,500);
在form2上,将位置设置为poDefault,在form3上将位置设置为poScreenCenter,在form4上将位置设置为默认值poDefaultPosOnly。只有form2会显示在500,500,宽度为500,高度为500。
有人对这个结果有一个合理的解释吗?
poDefault
和friends的意思是"当窗体创建并显示时,让Microsoft Windows定位此窗体的窗口"。
你刚刚创建了Delphi对象-但我想知道它是否也创建/显示了Windows对象(HWND
句柄和所有相应的Windows内部结构)。特别是有主题的应用程序,而不是那些使用标准的pre-XP外观和感觉的应用程序-它们在显示时倾向于ReCreateHWND
,因为预加载那些花哨的Windows主题是相对昂贵的操作,只有在需要的时候才应该做。
我认为您的默认边界(在构造函数中设置的每个属性值可能被认为是默认的非调优值,在对象构造后稍后进行调优)被正确忽略时,您(或TApplication
-这对主题几乎没有区别)最终执行FormXXX.Show
。
它是在"让我一个窗口并显示它"序列当你的窗体看着它的属性,并告诉MS Windows的东西,如"现在我想创建你的内部hwnd对象,并在默认坐标/大小的位置在你的自由裁量权"。
这是绝对正确的行为-否则TForm
何时以及如何应用Position
属性?向Windows请求一个屏幕上还不存在的窗口的坐标是没有意义的,也许永远也不会。Windows在被请求的这一秒提供了默认的坐标/大小,看看有多少其他窗口在那里以及它们的位置(AMD/NVidia视频驱动程序也可能会应用它们的校正)。
现在获取默认值,并在两个小时后应用它们是没有意义的,因为一切都可能是不同的——不同数量的其他窗口和它们的不同位置,不同的显示器连接集和不同的分辨率,等等。
考虑一下"桌面替代品"类型的笔记本。它被放在桌子上,连接着一个巨大的固定外部监视器。然后——让我们想象一下——我运行你的应用程序,它创建了tform Delphi对象,并在构造函数中向MS Windows请求位置——Windows正确地提供了在那个非常大的显示器上的位置。但一个小时后,我拔掉了笔记本的电源,把它带走了。现在一个小时后,我告诉你的应用程序显示表单——它会做什么?用现在已经分离的外部显示器的坐标来显示吗?在我目前只有的笔记本内部显示器的视窗之外?这个表单应该显示在现在"不可见"的位置,只是因为当我启动应用程序时,那个位置仍然可见吗?我认为这是一种毫无意义的混淆用户的方式。
所以唯一正确的行为是在窗体从隐藏变为可见时立即向Windows询问默认值,而不是早一秒。
这意味着如果你想移动你的表单-你应该在它被显示之后做。将Self.SetBounds(500,500,500,500);
放入OnShow
事件处理程序中。因此,让MS Windows物化您的窗体到默认位置,就像poDefault
在Position
属性中所要求的那样-然后移动您的窗口。试图移动不存在的窗口对我来说是徒劳的。
预设您的窗体(在构造序列)显式忽略MS Windows默认值并使用预设的线(通过poDesigned
值),或让窗体询问Windows坐标,但移动它与SetBounds
后,它通过OnShow
处理程序可见。