将表单设置为WindowState = wsMaximized
有时会导致表单最大化但不会:
长期错误:这是我在2003年首次在Borland新闻组中提出的一个问题:
然后又在2006年:
然后在2008年再次:
有人在2012年的Embarcadero论坛上提出这个问题:
现在是时候将18岁的bug移植到Stackoverflow了。也许某人终于想出了一个解决方法。
重现的步骤:
我的帖子包含六种失败模式,但最简单的是:
在表单上放置Label
和Edit
:
为OnEnter
添加TEdit
个事件:
procedure TForm1.Edit1Enter(Sender: TObject);
begin
Label1.Font.Style := Label1.Font.Style + [fsBold];
end;
并设置表单:
WindowState
至 wsMaximized AutoScroll
至错误 而bazinga,失败了。
2008年帖子中的另一组步骤之一:
- 创建新的应用和表单。
- 在设计时将表单设置为最大化(WindowState = wsMaximized)。
- 删除表单上的ListView控件
在OnShow期间,将20个空项添加到列表视图中:
procedure TForm1.FormShow(Sender: TObject); var i: Integer; begin for i := 1 to 20 do ListView1.Items.Add; end;
- 醇>
在设计时将表单的AutoScroll属性设置为false(AutoScroll = False)
当然,“在RadStudio的版本n
中修复后,我不。只需使用”。我正在寻找一个实际的修复(如果有的话);这可能包括在CodeGear最终修复它时引用VCL源的相关更改。 (如果它甚至是固定的)。
注意:将Position
从 poDesigned 更改为其他任何内容并不能解决问题。
我一直在使用的一个可怕,丑陋,可怕,恶心的解决方法是在OnShow
期间启动计时器,然后当计时器触发时,最大化表单:
procedure TForm1.tmrVclMaximizeHackTimer(Sender: TObject);
begin
Self.WindowState := wsMaximized;
end;
我后来改进了这个hack,以便在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;
但没有黑客比黑客更好。
答案 0 :(得分:12)
我可以用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
期间无法正常工作。
更好的解决方法: 在OnShow
或OnCreate
期间使用ShowWindowAsync
,例如:
procedure TForm1.FormCreate(Sender: TObject);
begin
ShowWindowAsync(Handle, SW_MAXIMIZE);
end;
这将设置窗口的显示状态,而不等待操作完成。
答案 1 :(得分:10)
我只测试了第一个复制案例(使用D7,D2007,XE2),并且能够用D7和D2007复制问题,但不能用XE2复制。
正如我所看到的,问题是标签的字体发生变化,要求其父级重新对齐。这最终会导致SetWindowPos
调用表单(TWinControl.AdjustSize
)并恢复宽度/高度,即使表单已经最大化 - 这会导致奇怪的,行为最大化但不会在视觉上最大化,表格坐在屏幕上。
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完成。
答案 2 :(得分:1)
我不认为这是Delphi中的错误,而是Windows CreateWindow函数中的错误(或奇怪的行为)。如果您搜索CreateWindow并且WS_MAXIMIZE无法正常工作,您会发现类似于非常旧的线程和调用CreateWindow或CreateWindowEx的人在样式参数中传递WS_MAXIMIZE并且在运行应用程序时没有看到最大化窗口的讨论。
Excerpt from an old gamedev.net thread
问题是当使用WS_OVERLAPPEDWINDOW时,WS_MAXIMIZE显然不适用。如果用WS_POPUP替换WS_OVERLAPPEDWINDOW,您将获得最大化窗口。当然,这可能不适用于所有版本的Windows,甚至不适用于所有版本的Windows shell UI。
WS_OVERLAPPEDWINDOW是MS的旧默认窗口“type”,他们显然编写CreateWindow / Ex来忽略某些样式,认为ShowWindow将使用SW_SHOWDEFAULT调用,这会导致窗口根据CreateProcess启动信息parms显示。这最终使用户可以通过使用shell的快捷方式设置控制应用程序主窗口的显示方式。
解决方法就是调用ShowWindow。它也应该在Delphi中工作:
procedure TForm1.FormShow(Sender: TObject);
begin
ShowWindow(Handle, SW_MAXIMIZE);
end;
答案 3 :(得分:0)
希望我使用的解决方案有助于其他人(我知道窗口首先显示设计时间大小):
theTimer.Enabled:=False;WindowState:=wsMaximized;
对我来说永远不会失败。
只要显示表单并完成此类节目的所有待处理任务,定时器触发并且窗口最大化。
前段时间,我正在使用鼠标单击鼠标点击最大化按钮,但我发现检查Windows操作系统版本,插件(在Linux上)等使事情变得如此困难。那个工作就好像用户要求最大化窗口一样。
我现在使用的Timer完全相同,但避免操作系统检查等。
更不用说:在DesignTime上将WindowState放到wsNormal上(不要将它设置为wsMinimized,也不要设置为wsMaximized)。
答案 4 :(得分:0)
哇!我没看到帖子:
ShowWindowAsync(把手,SW_MAXIMIZE);
谢谢你,用它来解决我的问题比使用Timer更好,但不完美,它仍然会导致一点闪烁(窗口显示在不完整的渲染上,然后它进入最大化状态),它好多了比计时器更少的时间显示在非最大化状态。
它与OnShow方法上的先前SetBounds()兼容。
我希望:在显示之前设置表单的初始大小(宽度,高度)以及初始位置(左侧,顶部),但必须最大化显示该表单;这种位置和大小适用于用户未最大化窗口的时间。
ShowWindowAsync
适用于这样的目标,并且不需要添加一个丑陋的计时器(在其代码中将interval = 1和.Enabled = False作为第一句)。
当我阅读帖子时,我怎能错过它?
所以,现在我将使用(就像示例os初始大小相对于监视器):
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中)时,我必须对它进行测试并看看,如果它确实有效,它将对我迄今使用的内容产生很大的影响。