The Delphi Bug List

Entry No.
372
VCL - MDI
With multiple child windows, if you maximize and close one, the other child windows will have their close ('x') button greyed out, however the button is still active, and will close the window if clicked.
1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 4.03 5.0 5.01 6.0 6.01 6.02 Kylix 1.0
ExistsExistsExistsExistsExistsExistsExistsExistsExistsExistsExistsFixedFixedFixedN/A
Description
Reported by Matthew Estela; checked by comment by Brad Stowers; fix by Raoul DeKezel and others
Reproducing the bug:
The bug is evident even in imagedit. Start imagedit, and create 3 new bitmaps. Maximize the last bitmap, then close it. The one behind will be maximised (I've always thought it to be strange windows behavior, but not delphi's fault), and the X will be greyed out. If you click the X, it will close the window. The same holds for the other windows.

A sample program in Delphi has been prepared by Raoul DeKezel. You can download the sources (which demonstrate the bug AND a workaround) here: Test372.zip.

Why it's a bug:
The correct behavior would be for the button not to be greyed. This is evident in products not developed in delphi, eg word, photoshop.

Possible cause:
Don't know. A friend supposed it has something to do with the way delphi creates an invisible window to control behavior of the visible ones, and that this was complicating things somehow.

Version:
I'm using delphi 3.0, searching dejanews has revealed it is common to all versions of delphi.
I've tested on win95, win95sp1, NT4workstation sp3, NT4server sp3.

Additional information, by Brad Stowers:
I've found that this only occurs with MDI apps that use menu merging. That is, the MDI parent form has a menu, and the MDI child form has a menu that is merged into the parent. If the MDI child does not have a menu, the problem does not occur. It seems to indicate the bug is somewhere in the menu VCL code, but I haven't been able to pinpoint the exact cause of the problem.

Solution / workaround
Use Raoul DeKezel's workaround; it's in the source code of project Test372.
To install the patch in your application :
  • Copy the method TForm1.OnAppIdle below in your MDI main form.
  • Call that method at idle time. A way of doing that is to copy the TForm1.FormCreate method below.
  • MDI children dont need any change.

Greg Chapman provided a VCL solution:

As noted, this bug only occurs when rebuilding a menu while an MDI child window is maximized. FWIW, the following change to the TCustomForm.MergeMenu routine fixes it for every version of Delphi through ver. 4:
--- D4MergeMenu
+++ D4MergeMenuFix
 procedure TCustomForm.MergeMenu(MergeState: Boolean);
 var
   AMergeMenu: TMainMenu;
   Size: Longint;
+  FixMaximize: boolean;
 begin
   if not (fsModal in FFormState) and
     (Application.MainForm <> nil) and
     (Application.MainForm.Menu <> nil) and
     (Application.MainForm <> Self) and
     ((FormStyle = fsMDIChild) or (Application.MainForm.FormStyle <> fsMDIForm)) then
   begin
     AMergeMenu := nil;
     if not (csDesigning in ComponentState) and (Menu <> nil) and
       (Menu.AutoMerge or (FormStyle = fsMDIChild)) then AMergeMenu := Menu;
-    with Application.MainForm.Menu do
-      if MergeState then Merge(AMergeMenu) else Unmerge(AMergeMenu);
-    if MergeState and (FormStyle = fsMDIChild) and (WindowState = wsMaximized) then
-    begin
+    FixMaximize:= MergeState and (FormStyle = fsMDIChild) and
+       (WindowState = wsMaximized);
+    if FixMaximize then begin
       { Force MDI to put back the system menu of a maximized child }
       Size := ClientWidth + (Longint(ClientHeight) shl 16);
       SendMessage(Handle, WM_SIZE, SIZE_RESTORED, Size);
+    end;
+    try
+      with Application.MainForm.Menu do
+        if MergeState then Merge(AMergeMenu) else Unmerge(AMergeMenu);
+    finally
+      if FixMaximize then
       SendMessage(Handle, WM_SIZE, SIZE_MAXIMIZED, Size);
     end;
   end;
 end;
Since the code was already restoring and re-maximizing MDI child windows, the idea of the patch is to first restore, then merge, then re-maximize. This should also work under Delphi 5, but I see the restore/re-maximize code has been taken out of TCustomForm.MergeMenu -- I wonder why?

Chris Cheney submitted an alternate workaround (12 August 2000):

A workaround, not requiring the VCL source (and therefore applicable to Delphi Desktop users) to bug 372 is possible.

Trap the non-client paint message for the MDI child form(s) and set WS_SYSMENU in the windows style before calling inherited. (The idea of forcing WS_SYSMENU to fix this bug is from a posting with subject 'Fixing MDI bug in Delphi 5' by "tom" on 21 April 2000 to the alt.lang.delphi and alt.comp.lang.borland-delphi newsgroups.)

For example:

type
  TMyMDIChildForm = class(TForm)
  ...
  private
    procedure WMNCPaint(Msg: TWMNCPaint); message WM_NCPAINT;
  ...
  end;

...

implementation

...

procedure TMyMDIChildForm.WMNCPaint(var Msg: TWMNCPaint);
var
  Style: LongInt;
begin
  Style := GetWindowLong(Handle, GWL_STYLE);
  if (Style and WS_SYSMENU) = 0 then
    SetWindowLong(Handle, GWL_STYLE, Style or WS_SYSMENU);
  inherited;
end;

The conditional prevents infinite recursion which would occur if setting the windows style were to cause a WM_NCPAINT message to be sent - which seems likely :-).

This seems to work ok with Delphi 3.02; With other versions, your mileage may vary.

Sergio Trindade submitted another fix (19 Nov 2000):

// This piece of code fixes a bug present in all versions of Delphi,
// that occurs when switching between maximized MDI child windows,
// causing the close icon to be grayed in Delphi 3 & 4 or the system
// menu and max/min/close icons to vanish in Delphi 5.
// Tested in Delphi Client/Server 3, 4 & 5.
 

{$IFDEF VER100}
  {$DEFINE DELPHI3&4}
{$ENDIF}
 
{$IFDEF VER120}
  {$DEFINE DELPHI3&4}
{$ENDIF}
 

type
  TMDIChild = class(TForm)
  ...
  private 
    procedure WMMDIActivate(var Msg: TWMMDIActivate); message  WM_MDIACTIVATE;
  ...
  end;
 

procedure TMDIChild.WMMDIActivate;
var
  Style: Longint;
begin
  if (Msg.ActiveWnd = Handle) and
     (biSystemMenu in BorderIcons)
  then begin
    Style:= GetWindowLong(Handle, GWL_STYLE);
    if (Style and WS_MAXIMIZE <> 0) and
       (Style and WS_SYSMENU = 0)
    then
{$IFDEF DELPHI3&4}
      SetWindowLong(Handle, GWL_STYLE, Style or WS_SYSMENU);
{$ELSE}
      SendMessage(Handle, WM_SIZE, SIZE_RESTORED, 0);
{$ENDIF}
  end;
 
  inherited;
end;
User-contributed comments
Gene Fowler, acorioso@ccnet.com
13 Mar 2001  07:47 PM GMT
Bug 372 in Delphi 4 and earlier versions was due to the
way procedure TCustomForm.MergeMenu(MergeState: Boolean) was
written. If maximized windows were switched (or one closed)
the switch was made and then

{ Force MDI to put back the system menu of a maximized child }
Size := ClientWidth + (Longint(ClientHeight) shl 16);
SendMessage(Handle, WM_SIZE, SIZE_RESTORED, Size);
SendMessage(Handle, WM_SIZE, SIZE_MAXIMIZED, Size);

was executed. This is an exact quote. The programmer knew he had
failed to do a Restore before the switch and a Maximize after it.

The problem is gigantically worse in Delphi 5.0 and 5.1. (I even
put it into Borland's bug list and months later got a response
saying it was verified...but no 5.02 (or a patch, or a fix/issue
followed).

The new problem, easily verified using the TextEdit demo, is a
complete collapse of MDI capability. Have maximized windows. Use
System/Next (^F6, Shift ^F6). You can do it once. But the Min, Max,
Close ikons are then frozen (not grayed). Click on System menu and
it vanishes and you open the file menu...sort of.

What happened? Well, a programmer removed that forced fix code quoted
above. Even removed a variable declared in the routine for it, so
it was not accidental. I figure he or she did this and was going to
bracket the switch with that restore/maximize...but got layed off
during the break.

Fix? Use the routine Greg Chapman (above) used for Delphi 4 in Delphi
5 (in forms.pas). Put that in your c:\d5fix or wherever. Works fine for
my eWriter (http://www.ccnet.com/~acorioso/ew_main.htm) which I've
been migrating since Delphi 3. This note is to share some hard won
understanding of what the devil was g o i n g o n ! All notes above
involve guesses, "going to look further"s and other such. Finding that
TextEdit was a useful ready-made verifier helps. You might suspect your
own code, that you're doing something moronic. (I did.)
Fritz Philipp
28 Mar 2002  02:19 AM GMT
Hi folks,

I work with C++Builder, but I also have this problem. In fact, I HAD. Thank you for the help I found on this page. Indeed, I found a really easy way to solve the problem. Just write into your ChildForm.FormActivate() procedure
LONG Style;
Style:= GetWindowLong(Handle, GWL_STYLE);
SetWindowLong(Handle, GWL_STYLE, Style or WS_SYSMENU);

That's it! I only tested it with BCB3, so I don't know if it works with DELPHI. Just try it out - it's not that much code. ;o)
Joe Flint
06 Jul 2004  07:17 PM GMT
Fritz Philipp's solution appears to work on Delphi 4 too.
Latest update of this entry: 2002-04-03

Post a comment on this bug


Index page
Delphi Bug List home page
The Delphi Bug Lists are presently maintained by Jordan Russell, who has taken over this task from Reinier Sterkenburg since August 2000.
All feedback is appreciated. See also the feedback section of the Delphi Bug List home page.