【问题标题】:wsMaximized forms do not appear maximizedwsMaximized 表单未最大化
【发布时间】:2013-10-29 02:40:17
【问题描述】:

将表单设置为WindowState = wsMaximized 有时会导致表单最大化但不会:

长期错误:这是我在 2003 年首次在 Borland 新闻组中提出的问题:

然后在 2006 年再次:

然后在 2008 年再次:

2012 年有人在 Embarcadero 论坛上问过这个问题:

现在是时候将这个存在 18 年之久的错误移植到 Stackoverflow。也许有人终于找到了解决方法。

复制步骤

我的帖子包含六种失败模式,但最简单的是:

  • 在表单上放置 LabelEdit

  • TEdit 添加OnEnter 事件:

    procedure TForm1.Edit1Enter(Sender: TObject);
    begin
       Label1.Font.Style := Label1.Font.Style + [fsBold];
    end;
    
  • 并设置表格:

    • WindowStatewsMaximized
    • AutoScroll

而 bazinga,失败了。

2008 年帖子的另一组步骤之一:

  1. 创建一个新应用和一个表单。
  2. 在设计时将表单设置为最大化 (WindowState = wsMaximized)。
  3. 在窗体上拖放一个 ListView 控件
  4. 在 OnShow 期间,将 20 个空项添加到列表视图中:

    procedure TForm1.FormShow(Sender: TObject);
    var
         i: Integer;
    begin
         for i := 1 to 20 do
              ListView1.Items.Add;
    
    end;
    
  5. 在设计时将表单的 AutoScroll 属性设置为 false (AutoScroll = False)

当然,我不是追求的是“已在 RadStudio 的 n 版本中修复。只需使用它”。我正在寻找一个实际的修复(如果有的话);这可能包括在 CodeGear 最终修复它时引用 VCL 源的相关更改。 (如果它甚至是固定的)。

注意:PositionpoDesigned 更改为 anything else 并不能解决问题。

解决方法

我一直在使用的一个可怕、丑陋、可怕、恶心的解决方法是在OnShow 期间启动一个计时器,然后当计时器触发时,最大化表单:

procedure TForm1.tmrVclMaximizeHackTimer(Sender: TObject);
begin
   Self.WindowState := wsMaximized;
end;

我后来改进了这个技巧,以便在OnShow 期间发布一条消息;这与定时器消息本质上相同,无需使用定时器:

const
  WM_MaximizeWindow = WM_APP + $03;

procedure TForm1.FormShow(Sender: TObject);
begin
  if (Self.WindowState = wsMaximized) then
  begin
     Self.WindowState := wsNormal;
     PostMessage(Self.Handle, WM_MaximizeWindow , 0, 0);
  end;
end;

private
   procedure WMMaximizeWindow(var Message: TMessage); message WM_MaximizeWindow;

procedure TForm1.WMMaximizeWindow(var Message: TMessage);
begin
   Self.WindowState := wsMaximized;
end;

有时我发明了 Delphi 从未做过的 OnAfterShow 事件:

const
  WM_AfterShow = WM_APP + $02;

procedure TForm1.FormShow(Sender: TObject);
begin
  PostMessage(Self.Handle, WM_AfterShow, 0, 0);
  if (Self.WindowState = wsMaximized) then
  begin
     Self.WindowState := wsNormal;
     FMaximizeNeeded := True;
  end;
end;

private
   procedure WMAfterShow(var Message: TMessage); message WM_AfterShow;

procedure TForm1.WMAfterShow(var Message: TMessage);
begin
   if FMaximizeNeeded then
   begin    
      FMaximizeNeeded := False;
      Self.WindowState := wsMaximized;
   end;
end;

但没有比 hack 更好的 hack。

【问题讨论】:

  • 是否有需要注意的版本差异?标记 D5 和 D7?两者都失败?我也没有,只有D6。如果我在那里调试,那可能就足够了。
  • 好吧,我无法在 D6 中重现。我需要使用 Windows 2000 吗? ;-)
  • 它在 Windows 7 上的 Delphi 7 中失败(我昨天制作的屏幕截图)。如果它在 Delphi 8 中的 Windows 8 上失败,则没有任何消息;或 Delphi 8.1 中的 Windows 8.1。
  • 对我来说不是。虽然我使用的是 D6。请使用@myname 以便我收到通知。
  • 无法在 Windows 7 上使用 Delphi XE4 重现

标签: delphi delphi-7 vcl delphi-5


【解决方案1】:

我可以用 D7/Win7 重现。

我根本不使用wsMaximized(与您描述的类似随机问题)。

解决方法:使用OnActivate -> ShowWindow(Handle, SW_MAXIMIZE) 例如:

procedure TForm1.FormActivate(Sender: TObject);
begin
  // Maximize only once when the Form is first activated
  if not FMaxsimized then
  begin
    FMaxsimized := True;
    ShowWindow(Handle, SW_MAXIMIZE);
  end;
end;

此方法在OnShow 期间不会工作。

更好的解决方法:OnShowOnCreate 期间使用ShowWindowAsync 例如:

procedure TForm1.FormCreate(Sender: TObject);
begin
  ShowWindowAsync(Handle, SW_MAXIMIZE);
end;

这会设置窗口的显示状态而不等待操作完成。

【讨论】:

  • 您不会希望在OnActivate 期间最大化表单,否则表单会在激活时尝试最大化(即每次激活时)跨度>
  • @Ian Boyd 使用 ShowWindowAsync 的解决方法相当优雅,不是吗?肯定比 hack 好。
  • @IanBoyd,在实践中,您将使用布尔标志来指示表单已在第一次最大化/激活。我会进行编辑。
  • 并不是说它不是一个很好的解决方法,但基本上 ShowWindowAsync 是系统本身实现的 Ian 答案中的 PostMessage hack。使用这两种解决方法,如果您将断点放置在正确的位置,您可以看到窗口在最大化之前正常显示。
【解决方案2】:

我只测试了第一个复制案例(D7、D2007、XE2),并且能够复制 D7 和 D2007 的问题,但不能复制 XE2。

在我看来,问题在于标签的字体已更改,它要求其父级重新对齐自身。这最终导致在表单(TWinControl.AdjustSize)上调用SetWindowPos 并恢复宽度/高度,即使表单已经最大化 - 这会导致奇怪的行为最大化但视觉上没有最大化 , 形式坐在屏幕上。


我跟踪了 D2007 和 XE2 中的代码以找出不同之处。 TWinControl.AlignControls 中的代码在两个版本之间是不同的。特别重要的是最后一句话。

D2007:

procedure TWinControl.AlignControls(AControl: TControl; var Rect: TRect);

  ..
  { Apply any constraints }
  if Showing then AdjustSize;
end;

XE2:

procedure TWinControl.AlignControls(AControl: TControl; var Rect: TRect);

  ..
    // Apply any constraints
    if FAutoSize and Showing then
      DoAdjustSize;
end;

我希望这能以某种方式帮助您设计/决定要使用的解决方法。



我可以建议的解决方法(尽管我没有彻底测试过)是强制显示表单尽早最大化:
procedure TForm1.FormCreate(Sender: TObject);
var
  wplc: TWindowPlacement;
begin
  if not AutoScroll and (WindowState = wsMaximized) then begin
    wplc.length := SizeOf(wplc);
    GetWindowPlacement(Handle, @wplc);
    wplc.rcNormalPosition.Right := wplc.rcNormalPosition.Left + Width;
    wplc.rcNormalPosition.Bottom := wplc.rcNormalPosition.Top + Height;
    wplc.showCmd := SW_MAXIMIZE;
    SetWindowPlacement(Handle, @wplc);
  end;
end;

上述方法之所以有效,是因为它强制在 VCL 为表单设置可见标志之前将焦点设置到编辑控件(OnEnter 事件)。反过来,标签的对齐请求不会导致表单大小调整。此外,由于在 VCL 调用 ShowWindow 时表单的窗口已经可见,因此不会导致表单在任何阶段以恢复状态显示。

但是,我不知道它是否有助于不同的复制场景。


最后,虽然我可以看到在较新的 Delphi 版本中该行为已更正,但我不认为这是 VCL 中的错误。在我看来,用户代码应该负责在窗口显示状态发生变化时不引起窗口调整。对于特定场景,我将采取的措施是推迟修改标签的字体,直到 VCL 完成显示表单。

【讨论】:

  • +1!我更喜欢您使用SetWindowPlacement 的解决方案,因为表单会立即以最大化状态显示(与我的解决方案相反,在我的解决方案中,窗口在最大化之前显示正常)。此外,无论wsMaximized 是否预设,您的方法都有效。 (我个人不会使用wsMaximized,省略if not AutoScroll 检查,并使用SetWindowPlacement 最大化)。此外,分析做得很好:)。
  • @kobik - 谢谢! - ".. 省略 if not AutoScroll .." - 只是想尽可能减少对 VCL 的干扰。
  • 我的重现步骤不再使用 XE6 重现。所以看起来 CodeGear(或者他们当时仍然是 Imprise,或者他们已经改名为 Embargadero)在 XE2 时间范围内修复了它。
【解决方案3】:

我不认为这是 Delphi 中的错误,而是 Windows CreateWindow 函数中的错误(或只是奇怪的行为)。如果您搜索 CreateWindow 和 WS_MAXIMIZE 不工作,您会发现类似的非常古老的线程和来自调用 CreateWindow 或 CreateWindowEx 的人在样式参数中传递 WS_MAXIMIZE 并且在运行应用程序时没有看到最大化的窗口。

Excerpt from an old gamedev.net thread

问题是 WS_MAXIMIZE 在使用 WS_OVERLAPPEDWINDOW 时显然不适用。如果您将 WS_OVERLAPPEDWINDOW 替换为 WS_POPUP,您将获得一个最大化的窗口。当然,这可能不适用于所有版本的 Windows,甚至所有版本的 Windows shell UI。

WS_OVERLAPPEDWINDOW 是 MS 的旧默认窗口“类型”,他们显然将 CreateWindow/Ex 编码为忽略某些样式,认为 ShowWindow 将使用 SW_SHOWDEFAULT 调用,这会导致窗口根据 CreateProcess 启动信息参数显示。这最终使用户能够通过使用 shell 的快捷方式设置来控制应用程序主窗口的显示方式。

解决方法就是调用 ShowWindow。它也应该在 Delphi 中工作:

procedure TForm1.FormShow(Sender: TObject);
begin
   ShowWindow(Handle, SW_MAXIMIZE);
end;

【讨论】:

  • 我不这么认为。切换“自动滚动”修复问题表明操作系统没有任何问题。如果您跟踪 VCL 代码,您会发现问题中的场景导致在具有恢复的宽度/高度的最大化窗口上调用 SetWindowsPos,这可能是问题的一部分。
  • 还要注意窗口认为它被最大化了;边框图标使其处于最大化状态。除了它不是真的。我什至会看到 3rd 方应用程序中的错误;一个死的赠品,它是一个 Delphi 应用程序(那个和错误绘制的工具提示窗口)
【解决方案4】:

希望我使用的解决方案可以帮助其他人(我知道窗口首先显示设计时大小):

  • 添加一个间隔小于 1 的计时器(不要输入 0)。
  • 代码:theTimer.Enabled:=False;WindowState:=wsMaximized;

我永远不会失败。

一旦表格显示出来并且所有待处理的任务都完成了,计时器就会触发并且窗口最大化。

前段时间,我在最大化按钮所在的位置使用发送鼠标单击的技巧,但我发现检查 Windows 操作系统版本、插件(在 Linux 上)等让事情变得如此困难。就像用户要求最大化窗口一样。

我现在使用的 Timer 完全一样,但要避免操作系统检查等。

更不用说:在 DesignTime 上将 WindowState 设置为 wsNormal(不要将其设置为 wsMinimized,也不要设置为 wsMaximized)。

【讨论】:

    【解决方案5】:

    哇!我没有在帖子上看到:

    ShowWindowAsync(Handle,SW_MAXIMIZE);

    谢谢你,有了它我的问题比使用定时器解决得更好,但并不完美,它仍然会导致一点点闪烁(窗口显示在不完整的渲染上,然后进入最大化状态),它好多了比计时器,在非最大化状态下显示的时间更少。

    并且它与 OnShow 方法上的先前 SetBounds() 兼容。

    我希望:在显示之前设置表单的初始大小(宽度、高度)和初始位置(左、上),但该表单必须最大化显示;这样的位置和大小适用于用户未最大化窗口时。

    ShowWindowAsync 非常适合这样的目标,并且无需添加丑陋的计时器(在其代码中使用 interval=1 和 .Enabled=False 作为第一句)。

    我怎么会错过这篇文章!

    所以,现在我将使用(就像示例操作系统相对于监视器的初始大小):

    procedure TtheForm.FormShow(Sender: TObject);
    var
       theInitialDefaultWidth,theInitialDefaultHeight:Integer;
    begin
         theInitialDefaultWidth:=Round(Screen.Width*3/5);
         theInitialDefaultHeight:=Round(Screen.Height*3/5);
         WindowState:=wsNormal; // So it can still have at design time wsMaximized, this is for the SetBounds to work on a non maximized state
         SetBounds((Screen.Width-theInitialDefaultWidth)div 2,(Screen.Height-theInitialDefaultHeight)div 2,theInitialDefaultWidth,theInitialDefaultHeight); // Set default position and default size as i wish
         ShowWindowAsync(Handle,SW_MAXIMIZE); // Make the window to be shown maximized when it will be visible
         // ... // Rest of actions for the FormShow method
    end;
    

    完美运行!我不需要接触设计时属性,我可以让它们保持原样(WindowState=wsMaximized、Position=poScreenCenter 等).. 100% 代码解决问题。

    非常感谢!

    P.D.:它可以在 Linux 上运行吗?我的意思是,当为 Linux(在 Lazarus 中)编译代码时,我必须对其进行测试,看看它是否有效,它将大大改进我目前使用的内容。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多