wsMaximized forms do not appear maximized

15,936

Solution 1

I Can reproduce with D7/Win7.

I don't use wsMaximized at all (similar random problems as you describe).

Workaround: use OnActivate -> ShowWindow(Handle, SW_MAXIMIZE) e.g.:

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;

This method will not work during OnShow.

Better Workaround: use ShowWindowAsync during OnShow or OnCreate e.g:

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

This sets the show state of a window without waiting for the operation to complete.

Solution 2

I only tested the first reproduction case (with D7, D2007, XE2), and am able to duplicate the problem with D7 and D2007 but not with XE2.

The problem, as I see it, is that the label, having its font changed, requests its parent to re-align itself. This eventually leads to a SetWindowPos call on the form (in TWinControl.AdjustSize) with restored width/height even though the form is already maximized - which leads to the strange, behaviorally maximized but not visually maximized, form sitting on the screen.


I traced the code in D2007 and XE2 to be able to come up with what is different. The code in TWinControl.AlignControls is different between the two versions. What specifically matters is the last statement.

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;

I hope this, somehow, helps you devising/deciding what workaround to use.



The workaround I could suggest (although I haven't tested it throughly) is to force show the form maximized early:
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;

The above works because it forces to set the focus to the edit control (OnEnter event) before the VCL sets the visible flag for the form. In turn, the label's alignment request does not result with form size adjustment. Also, since, by the time VCL calls ShowWindow the form's window is already visible, it doesn't cause the form to be shown in a restored state at any stage.

However, I don't know if it would help with different reproduction scenarios.


Finally, although I can see that the behavior is corrected in newer Delphi versions, I wouldn't consider this to be a bug in the VCL. In my opinion, user code should be responsible not to cause window adjustment while window showing state is changing. The course of action I'd take for the specific scenario would be to defer to modify label's font until the VCL is done displaying the form.

Solution 3

I don't think this is a bug in Delphi but rather a bug (or just odd behavior) in the Windows CreateWindow function. If you search for CreateWindow and WS_MAXIMIZE not working you'll find similarly very old threads and discussions from people calling CreateWindow or CreateWindowEx passing WS_MAXIMIZE in the style parameter and not seeing a maximized window when they run the application.

Excerpt from an old gamedev.net thread

the problem is that WS_MAXIMIZE apparently does not apply when using WS_OVERLAPPEDWINDOW. if you replace WS_OVERLAPPEDWINDOW with WS_POPUP you will get a maximized window. of course this may not apply to all versions of Windows, or even all versions of the Windows shell UI for that matter.

WS_OVERLAPPEDWINDOW is MS's old default window "type" and they apparently coded CreateWindow/Ex to ignore certain styles, thinking that ShowWindow would be called with SW_SHOWDEFAULT, which causes the window to be displayed according to the CreateProcess startup info parms. this ultimately gives the user the control of how an app's main window would be displayed by using the shell's shortcut settings.

The workaround is just to call ShowWindow. It should work in Delphi, too:

procedure TForm1.FormShow(Sender: TObject);
begin
   ShowWindow(Handle, SW_MAXIMIZE);
end;
Share:
15,936
mistertodd
Author by

mistertodd

Any code is public domain. No attribution required. జ్ఞా <sup>🕗</sup>🕗 Yes, i do write i with a lowercase i. The Meta Stackexchange answer that I am most proud of

Updated on July 14, 2022

Comments

  • mistertodd
    mistertodd almost 2 years

    Setting a form to WindowState = wsMaximized will sometimes cause the form to be maximized but not:

    enter image description here

    Long-time bug: this is a question I first asked in the Borland newsgroups in 2003:

    and then again in 2006:

    and then again in 2008:

    Someone asked it on the Embarcadero forums in 2012:

    Now it's time to port the 18 year old bug to Stackoverflow. Maybe someone's finally figured out a workaround.

    Steps to reproduce:

    My posts contained half a dozen failure modes, but the easiest is:

    • Drop a Label and an Edit on a form:

      enter image description here

    • Add an OnEnter event for the TEdit:

      procedure TForm1.Edit1Enter(Sender: TObject);
      begin
         Label1.Font.Style := Label1.Font.Style + [fsBold];
      end;
      
    • and set the form:

      • WindowState to wsMaximized
      • AutoScroll to False

    And bazinga, fails.

    One of the other set of steps from the 2008 post:

    1. Create a new app and a form.
    2. Set the form to maximized (WindowState = wsMaximized) at design time.
    3. Drop a ListView control on the form
    4. During OnShow, add 20 empty items to the list view:

      procedure TForm1.FormShow(Sender: TObject);
      var
           i: Integer;
      begin
           for i := 1 to 20 do
                ListView1.Items.Add;
      
      end;
      
    5. Set the form's AutoScroll property to false (AutoScroll = False) at design time

    Of course what I'm not after is "fixed in version n of RadStudio. Just use that". I'm looking for an actual fix (if there is one); which could include quoting relevant changes to the VCL source when CodeGear finally did fix it. (If it is even fixed).

    Note: Changing Position from poDesigned to anything else doesn't fix it.

    Workaround

    A horrible, ugly, awful, disgusting, workaround I had been using was to start a timer during OnShow, and then when the timer fires, maximize the form:

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

    I later improved this hack to post a message during OnShow; which is essentially the same as a timer message, without having to use a timer:

    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;
    

    Sometimes I invent the OnAfterShow event that Delphi never did:

    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;
    

    But no hacks are better than hacks.