7

我注意到一些非常奇怪的事情。我在关闭窗体时保留窗体的顶部、左侧、宽度和高度属性,并使用此信息在窗体再次打开时恢复窗体的最后位置,方法是使用先前存储的信息调用 SetBounds。这很好用,但前提是表单的 Position 属性在设计时设置为 poDefault。如果设置为其他值,例如 poDesigned、poScreenCenter 或 poMainFormCenter,SetBounds 不会恢复表单的先前位置和大小。

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

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

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

当表单的 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;

其中构造函数创建 TForm2,然后创建 TForm3 和 TForm4。

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

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

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

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

4

1 回答 1

4

poDefault和朋友的意思是“让 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);intoOnShow事件处理程序。因此,让 MS Windows 将您的表单具体化到默认位置,如poDefaultinPosition属性所要求的 - 然后移动您的 Window。尝试移动尚不存在的窗口对我来说似乎是徒劳的。

预设您的表单(按构造顺序)以显式忽略 MS Windows 默认值并使用预设线(通过poDesigned值),或者让表单询问 Windows 坐标,但在通过处理程序可见SetBounds OnShow移动它。

于 2016-08-19T09:36:06.903 回答