为什么TForm.SetBounds仅在设计时将TForm.Position设置为poDefault时才能正常工作

Car*_*sen 6 delphi position tform setbounds

我注意到一些非常奇怪的事情.我在表单关闭时保持表单的top,left,width和height属性,并使用此信息通过使用以前存储的信息调用SetBounds再次打开表单时恢复表单的最后位置.这很有效,但前提是表单的Position属性在设计时设置为poDefault.如果设置为其他内容,例如poDesigned,poScreenCenter或poMainFormCenter,则SetBounds不会恢复表单的先前位置和大小.

这是奇怪的部分.看似重要的是在设计时将Position属性设置为什么.我可以在运行时将此属性的值更改为poDefault,并且对SetBounds的调用仍然无法正常工作.我尝试过类似下面的内容

if Self.Position <> poDefault then
  Self.Position := poDefault;
Run Code Online (Sandbox Code Playgroud)

在表单的OnCreate事件处理程序中,以及从重写的构造函数(并在构造函数中将Position设置为poDefault,并在OnCreate事件处理程序中调用SetBounds).在所有情况下,在运行时将表单的Position属性更改为poDefault并不能解决我在SetBounds中观察到的问题.我发现的唯一一致模式是SetBounds只有在设计时表单的Position属性为poDefault才能正常工作.

当表单的Position属性在设计时未设置为poDefault时,还有其他一些我注意到SetBounds的工作原理.例如,如果调用SetBounds,则在设计时将Position属性设置为poScreenCenter的表单不一定会显示在屏幕的中心.但是,它不会出现在由SetBounds定义的左上角位置,也不会出现在调用SetBounds时指定的宽度和高度.但是,让我重复一遍,我在调用SetBounds之前将表单的Position属性设置为poDefault.我甚至在两个操作之间调用了Application.ProcessMessages,但这并没有解决问题.

我已经在Windows 10上运行Delphi 10.1 Berlin进行了广泛的测试.我还在Windows 7上使用Delphi XE6对其进行了测试.结果相同.

如果您有疑问,请创建一个包含四种表单的VCL应用程序.在第一个表单上放置三个按钮,并为每个按钮添加如下OnClick:

 with TForm2.Create(nil) do
 try
   ShowModal;
 finally
   Release;
 end;
Run Code Online (Sandbox Code Playgroud)

构造函数创建TForm2,然后TForm3和TForm4.

在表单2到4的OnCreate上,添加以下代码:

if Self.Position <> poDefault then
  Self.Position := poDefault;
Self.SetBounds(500,500,500,500);
Run Code Online (Sandbox Code Playgroud)

在form2上,将Position设置为poDefault,在form3上将Position设置为poScreenCenter,并在form4上将Position设置为默认值poDefaultPosOnly.只有form2将出现在500,500,宽度为500,高度为500.

有没有人对这个结果有合理的解释?

Ari*_*The 4

poDefault和朋友的意思是“当表单创建并显示它时,让 Microsoft Windows 定位该表单的窗口”。

您刚刚创建了 Delphi 对象 - 但我想知道它是否也创建/显示了 Windows 对象(HWND句柄和所有相应的 Windows 内部结构)。特别是对于主题应用程序,而不是使用标准的 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移动它。