delphi - tokyo - embarcadero technologies




wsMaximized表單似乎沒有最大化 (4)

將窗體設置為WindowState = wsMaximized有時會導致窗體最大化但不會:

長期錯誤:這是我在2003年首次在Borland新聞組中提出的一個問題:

然後在2006年再次:

然後在2008年再次:

有人在2012年的Embarcadero論壇上提出這個問題:

現在是時候將18歲的bug移植到Stackoverflow了。 也許某人終於想出了一個解決方法。

重現的步驟

我的帖子包含了六種失敗模式,但最簡單的是:

  • 在表單上刪除LabelEdit

  • TEdit添加一個OnEnter事件:

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

    • WindowStatewsMaximized
    • AutoScrollFalse

而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;

我後來改進了這個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;

但沒有黑客比黑客更好。


哇! 我沒看到帖子:

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%代碼解決方案。

非常感謝!

PD:它可以在Linux上運行嗎? 我的意思是當代碼編譯為Linux(在Lazarus中)時,我必須對它進行測試並看看,如果它確實有效,它將會對我現在使用的內容產生很大的影響。


希望我使用的解決方案有助於其他人(我知道窗口首先顯示設計時間大小):

  • 添加一個間隔小於1的計時器(不要放0)。
  • 代碼: theTimer.Enabled:=False;WindowState:=wsMaximized;

它永遠不會對我失敗。

一旦顯示表單並且完成此類節目的所有待處理任務,定時器觸發並且窗口最大化。

前一段時間,我正在使用發送鼠標單擊的技巧,其中最大化按鈕是,但我發現檢查Windows操作系統版本,插件(在Linux上)等使事情變得如此困難。 那個工作就好像用戶要求最大化窗口一樣。

我現在使用的Timer完全相同,但避免操作系統檢查等。

更不用說:在DesignTime上將WindowState設置為wsNormal(不要將其設置為wsMinimized,也不要設置為wsMaximized)。


我只測試了第一個複制案例(使用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完成顯示表格。


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

更好的解決方法:OnShowOnCreate期間使用ShowWindowAsync ,例如:

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

這將設置窗口的顯示狀態,而無需等待操作完成。