36

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

在此处输入图像描述

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

然后在 2006 年再次:

然后在 2008 年再次出现:

有人在 2012 年的 Embarcadero 论坛上问过:

现在是时候将这个 18 年前的 bug 移植到 Stackoverflow 上了。也许有人终于想出了一个解决方法。

重现步骤

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

  • 将 aLabel和 anEdit放在表单上:

    在此处输入图像描述

  • 为:添加OnEnter事件TEdit

    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更改为其他任何东西都不能解决它。

解决方法

我一直在使用的一个可怕、丑陋、可怕、恶心的解决方法是在 期间启动一个计时器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;

有时我会发明OnAfterShowDelphi 从未做过的事件:

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;

但是没有黑客比黑客更好。

4

5 回答 5

13

我可以用 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

更好的解决方法:ShowWindowAsync在或OnShow例如OnCreate

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

这将设置窗口的显示状态,而无需等待操作完成。

于 2013-11-01T17:59:30.667 回答
10

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

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


我跟踪了 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;

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

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


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

于 2013-11-02T15:05:40.900 回答
1

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

摘自一个旧的 gamedev.net 线程

问题是 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;
于 2013-11-01T16:10:02.053 回答
0

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

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

它对我来说永远不会失败。

一旦显示表单并且此类显示的所有未决任务完成,计时器就会触发并且窗口最大化。

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

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

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

于 2016-10-18T12:41:51.090 回答
0

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

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% 代码解决问题。

非常感谢!

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

于 2016-10-19T06:48:15.513 回答