【问题标题】:Workaround for anchors being broken when recreating a window?重新创建窗口时锚点被破坏的解决方法?
【发布时间】:2013-02-25 08:08:25
【问题描述】:

这发生在所有 Delphi 到 XE3 中:

  1. 创建一个表单并在其上放置一个面板。将面板锚定到 [akLeft, akTop, akRight, akBottom],但在其与边框之间留出空间。
  2. 添加一个调用RecreateWnd()的按钮
  3. 运行应用程序。调整窗体的大小以隐藏面板,因为它的大小小于 0 像素,因为锚定。 按下 RecreateWnd 按钮。
  4. 重新调整表单大小并注意面板的锚定已损坏。

只要我记得自己使用 Delphi,锚就总是无法使用。调整表单大小,然后将其停靠:重新创建窗口,您的布局已损坏。

我想知道是否有某种解决方法?

更新

在 cmets 中提供了两种解决方法,一种经过验证且稳定,但会闪烁,另一种是实验性的,但可能更彻底、更干净。

我暂时不会投票给任何一个,因为其中一个是我的,我什至不确定它是否稳定。相反,我会等待一些公众意见。

【问题讨论】:

  • “无法使用”我认为有点过分了。很多用例都不会重新创建表单的句柄。
  • 我不这么认为。即使你现在看起来不这样做,一个月后你做了一些触发重新创建和爆炸的事情,你在不知不觉中引入了错误。锚点是不可预测的。
  • 嗯,是的,当然,这就是它们被如此广泛使用的原因......锚功能非常好,并且多年来一直为我和其他许多人做到这一点。显然你的里程是不同的,但你无法使用它们并不意味着它们无法为其他人使用。

标签: delphi


【解决方案1】:

我使用的两个选项都不是解决底部和右侧锚点问题的理想选择:

  1. 在调用或导致被调用RecreateWnd(); 之前将窗口再次变大,然后再次将其变小。然而,在你再次变小之前必须是可见的。
  2. 设置表单的约束,使其不能被重新调整到太小以至于东西最终被隐藏。

一个闪烁较大表单的示例,使用足够大的高度和宽度值以使面板不被隐藏:

procedure TForm1.Button1Click(Sender: TObject);
Var
  OldWidth, OldHeight : integer;
begin
  OldWidth := Form1.Width;
  OldHeight := Form1.Height;
  Form1.Visible := false;
  Form1.Width := 1000;
  Form1.Height := 800;
  RecreateWnd();
  Form1.Visible := true;
  Form1.Width := OldWidth;
  Form1.Height := OldHeight;
end;

【讨论】:

  • 我想知道使用一种“主”面板是否有助于解决闪烁问题。我的意思是一个面板设置为alClient 并持有 all 的内容。然后,在娱乐之前,您将放大面板而不是表格。一些闪烁可能仍然可见,但程度可能比放大表格时要小。
【解决方案2】:

原来破坏一切的函数是UpdateAnchorRulesTControl 存储 FOriginalParentSize 和它自己的原始大小在 FAnchorRules,并使用它来自动调整大小作为父调整大小。 UpdateAnchorRules() 获取当前父级大小和当前控制 WidthHeight 并将它们保存到 FOriginalParentSizeFAnchorRules

如果一切正常,在正常调整大小期间不会有任何影响,因为控件及其父级会一致地更改大小。

但是当控件Width由于锚定而小于零时,Windows,因此Delphi仍然认为它0。如果此时调用UpdateAnchorRules,它会保存错误的、不符合标准的0 原始宽度值。在此之后,布局无法修复。

(如果未调用,Width 会继续更新,因为保留了原始大小,因此与父级 Width 保持适当的关系)

结果是任何涉及创建窗口句柄调用 UpdateAnchorRules 两次:首先在 WinAPI 中 CreateWindow,因为它在返回之前调度 WM_SIZE(和 WM_SIZE 处理程序调用 UpdateAnchorRules),其次,显式在 @ 987654341@ 句柄创建后。

看来只要我们可以在CreateHandle 期间禁用UpdateAnchorRules,我们就会成功。但是在CreateHandle 中有对UpdateAnchorRules 的显式调用,这意味着有人认为需要在句柄创建后调整Anchor 规则。

所以也许我遗漏了一些东西,并且禁用它会破坏一些东西?

无论如何,有两种方法可以禁用UpdateAnchorRules:设置FAnchorMove 或设置csLoading。第一个不好,因为有代码在RecreateWnd 中途清除它,然后再次调用UpdateAnchorRules

第二个有效,这里有一个解决方案:

type
  TComponentHack = class helper for TComponent
  public
    procedure SetCsLoading(Value: boolean);
  end;

procedure TComponentHack.SetCsLoading(Value: boolean);
var i: integer;
begin
  if Value then
    Self.FComponentState := Self.FComponentState + [csLoading]
  else
    Self.FComponentState := Self.FComponentState - [csLoading];
  for i := 0 to Self.ComponentCount-1 do
    if Self.Components[i] is TControl then
      TControl(Self.Components[i]).SetCsLoading(Value);
end;

procedure SafeRecreateWnd();
begin
  MyControl.SetCsLoading(true);
  try
    MyControl.RecreateWnd(); //or any operation which triggers it -- such as docking or making the window visible first time after RecreateWnd()
  finally
    MyControl.SetCsLoading(false);
  end;
end;

免责声明

我不知道在设置 csLoading 的情况下运行 TControl 操作还会破坏什么。

更好的选择是挂钩UpdateAnchorRules 过程并专门为此目的添加另一个标志检查,但这需要完全重新实现UpdateAnchorRules(容易破坏具有不同原始UpdateAnchorRules 的不同版本的Delphi)或发明某种方式来调用原始的UpdateAnchorRules,这通常会通过用钩子重写来破坏。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-06
    • 1970-01-01
    • 2015-11-17
    • 1970-01-01
    • 2015-12-16
    相关资源
    最近更新 更多