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 |
| Exists | Exists | Exists | Exists | Exists | Exists | Exists | Exists | Exists | Exists | Exists | Fixed | Fixed | Fixed | N/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.
|
|