Discussion:
Child forms remove main form from Alt-Tab list
(too old to reply)
p***@childcaremanager.com
2007-01-23 23:04:27 UTC
Permalink
Raw Message
I am working on making my Delphi 7 application compatible with Windows
Vista (much in line with the instructions given at
http://www.installationexcellence.com/articles/VistaWithDelphi/index.html).
I am most of the way there, but have run into a frustrating issue.
Here's what I've done so far:

I have overridden the CreateParams of the main form as follows:

inherited CreateParams(Params);
Params.ExStyle := (Params.ExStyle and (not WS_EX_TOOLWINDOW)) or
WS_EX_APPWINDOW;

I have overridden WM_SYSCOMMAND to simulate proper window behavior with
the main form (i.e. animated minimization and restoration).

I have also written code to do the following when it catches the
WM_ACTIVATE message:
Cycle through all child forms and display them in front of the parent
windows,
Cycle through all orphaned forms and display them in front of other
forms.

So far, this has produced desired visibility results, displaying forms
in their proper orders and all.
Here's the frustrating issue:
Whenever a child form of the main form is visible, the program is not
available in the Alt-Tab list. This is likely due to the changes that
I have made to the main form for compatibility reasons. Anyway, the
taskbar button is visible and clicking on it will bring up the program
correctly, but using Alt-Tab will not show the program at all. This
problem affects both Windows XP and Windows Vista.
p***@childcaremanager.com
2007-01-23 23:53:31 UTC
Permalink
Raw Message
Clarification: I could solve this problem by giving each child form the
same CreateParams I gave the main form, but I don't want the child
forms to have taskbar buttons.
p***@childcaremanager.com
2007-01-25 21:54:04 UTC
Permalink
Raw Message
RESOLUTION REACHED.

First, I will present the explanation of the solution. Then, I will
provide some sample code.

These are the causes for the problem above.

First, in order to accommodate proper appearance in Flip3D and the like
in Windows Vista, the main form included WS_EX_APPWINDOW in its ExStyle
parameter (herein, "AppWindow") while clearing WS_EX_TOOLWINDOW
(herein, "ToolWindow"). The inverse was being done to the hidden
application window.

Second, the main form was being disabled when a child form was shown
modally. While this didn't prevent it from having a taskbar button, it
did prevent it from showing up in TaskSwitcher.

Enabling the main form when the child form is being shown allows it to
show up in the TaskSwitcher, but it also allows the main form to
receive focus. You can set the focus to a child form immediately upon
receiving it, but that doesn't prevent the user from clicking a button
on the main form and having it take action. This is not acceptable.

As mentioned in the first reply, the child forms could each have the
WS_EX_APPWINDOW flag set, but this creates a taskbar button for each of
the forms and that was not desirable.

The main reason for setting AppWindow on the main form is to
accommodate proper appearance in Flip3D. When the main form is
visible, it no longer needs to have AppWindow set and the hidden
application window can be "shown". When the application is minimized,
the main form needs to have AppWindow set and, to prevent a duplicate
taskbar button, the hidden application window needs to have AppWindow
reset. To prevent TaskSwitcher from listing both the main form and the
hidden application window, the hidden application window should have
ToolWindow set.

Here is the example code:

type TMyForm = class (TForm)
private
// This stores the WindowState before the form was minimized.
wsPrevState : TWindowState;
...
end;
...
{
This is the hook on the application's restore event. (You will see
where it is set further down.) Even though the taskbar button is for
the main form, the application window seems to be the one to catch the
restore message. Since the main form is not visible when the
application is minimized (also, see below), it won't be restored unless
we do it ourselves.
}
procedure TMyForm.OnAppRestore(Sender : TObject);
procedure SetAppWindow(wndToSet : HWND);
var
nWindowStyle : LongInt;
begin
nWindowStyle := GetWindowLong(wndToSet, GWL_EXSTYLE);
SetWindowLong(wndToSet, GWL_EXSTYLE, (nWindowStyle and not
WS_EX_TOOLWINDOW) or WS_EX_APPWINDOW);
end;
procedure ClearAppWindow(wndToSet : HWND);
var
nWindowStyle : LongInt;
begin
nWindowStyle := GetWindowLong(wndToSet, GWL_EXSTYLE);
nWindowStyle := nWindowStyle and not WS_EX_APPWINDOW;
if (wndToSet = Application.Handle) then
nWindowStyle := nWindowStyle or WS_EX_TOOLWINDOW;
SetWindowLong(wndToSet, GWL_EXSTYLE, nWindowStyle);
end;
begin
if ((Assigned(Application.MainForm)) and (Application.MainForm.Handle
= Handle)) then
begin
WindowState := wsPrevState;
ClearAppWindow(Handle);
SetAppWindow(Application.Handle);
ShowWindow(Application.Handle, SW_HIDE);
end;
end;

{
This makes sure that the appropriate form is displayed on top when
the main form is reactivated. Probably not necessary, since the hidden
application window does this itself, but a little insurance can be a
good thing.
}
procedure TMyForm.WmActivate(var Message : TWmActivate);
var
formCurrent : TForm;
nIndex : Integer;
begin
inherited;
if (Message.Active <> WA_INACTIVE) then
begin
for nIndex := 0 to Pred(Screen.FormCount) do
begin
formCurrent := Screen.Forms[nIndex];
if ((formCurrent.Handle <> Handle) and (formCurrent.Enabled) and
(formCurrent.Visible)) then
Screen.Forms[nIndex].SetFocus;
end;
end;
end;

{
This is our special handling of the minimize message. We set up the
main form as the application window so it is the one displayed in
Flip3D. Also, we minimize the main form (instead of hiding it, like
Delphi does by default) so that it animates down to the taskbar. The
code in OnAppRestore (above) should animate the restoration of the
form.
NOTE: This does not handle the restoration of the form. OnAppRestore
handles the necessary processing of that for us.
}
procedure TMyForm.WmSysCommand(var Message : TWmSysCommand);
procedure SetAppWindow(wndToSet : HWND);
var
nWindowStyle : LongInt;
begin
nWindowStyle := GetWindowLong(wndToSet, GWL_EXSTYLE);
SetWindowLong(wndToSet, GWL_EXSTYLE, (nWindowStyle and not
WS_EX_TOOLWINDOW) or WS_EX_APPWINDOW);
end;
procedure ClearAppWindow(wndToSet : HWND);
var
nWindowStyle : LongInt;
begin
nWindowStyle := GetWindowLong(wndToSet, GWL_EXSTYLE);
nWindowStyle := nWindowStyle and not WS_EX_APPWINDOW;
if (wndToSet = Application.Handle) then
nWindowStyle := nWindowStyle or WS_EX_TOOLWINDOW;
SetWindowLong(wndToSet, GWL_EXSTYLE, nWindowStyle);
end;
begin
if (Message.CmdType and $FFF0 = SC_MINIMIZE) then
begin
wsPrevState := WindowState;
if ((Assigned(Application.MainForm)) and
(Application.MainForm.Handle = Handle)) then
begin
ShowWindow(Application.Handle, SW_SHOW);
ClearAppWindow(Application.Handle);
SetAppWindow(Handle);
end;
ShowWindow(Handle, SW_MINIMIZE);
if ((Assigned(Application.MainForm)) and
(Application.MainForm.Handle = Handle)) then
Application.Minimize;
end;
inherited;
end;

{
With the rest of the code that we have done, there was a remnant
artifact which would leave a taskbar button (for the hidden application
window?) after the main form had been closed. We don't want the
application to continue running, so we take two precautions to make
sure that the application dies when the main form is closed.
}
procedure TMyForm.DoDestroy();
begin
if ((Assigned(Application.MainForm)) and (Application.MainForm.Handle
= Handle)) then
begin
ShowWindow(Application.Handle, SW_SHOW);
Application.Terminate;
end;
inherited;
end;

{
This is where we introduce our hook into the application's restore
event. And, since this is as good a time as any, we'll initialize our
WindowState variable.
}
procedure TMyForm.DoShow();
begin
inherited;
if ((Assigned(Application.MainForm)) and (Application.MainForm.Handle
= Handle)) then
Application.OnRestore := OnAppRestore;
wsPrevState := wsNormal;
end;
p***@childcaremanager.com
2007-01-25 22:50:55 UTC
Permalink
Raw Message
There was a problem in the WmSysCommand code where, on Vista, the
hidden application window would show up as a minimized toolwindow when
the application was minimized. The changes indicated below should fix
that.
Post by p***@childcaremanager.com
procedure TMyForm.WmSysCommand(var Message : TWmSysCommand);
procedure SetAppWindow(wndToSet : HWND);
var
nWindowStyle : LongInt;
begin
nWindowStyle := GetWindowLong(wndToSet, GWL_EXSTYLE);
SetWindowLong(wndToSet, GWL_EXSTYLE, (nWindowStyle and not
WS_EX_TOOLWINDOW) or WS_EX_APPWINDOW);
end;
procedure ClearAppWindow(wndToSet : HWND);
var
nWindowStyle : LongInt;
begin
nWindowStyle := GetWindowLong(wndToSet, GWL_EXSTYLE);
nWindowStyle := nWindowStyle and not WS_EX_APPWINDOW;
if (wndToSet = Application.Handle) then
nWindowStyle := nWindowStyle or WS_EX_TOOLWINDOW;
SetWindowLong(wndToSet, GWL_EXSTYLE, nWindowStyle);
end;
begin
if (Message.CmdType and $FFF0 = SC_MINIMIZE) then
begin
wsPrevState := WindowState;
if ((Assigned(Application.MainForm)) and
(Application.MainForm.Handle = Handle)) then
begin
ShowWindow(Application.Handle, SW_SHOW);
ClearAppWindow(Application.Handle);
SetAppWindow(Handle);
end;
ShowWindow(Handle, SW_MINIMIZE);
if ((Assigned(Application.MainForm)) and
(Application.MainForm.Handle = Handle)) then
begin
Post by p***@childcaremanager.com
Application.Minimize;
ShowWindow(Application.Handle, SW_HIDE);
end;
Post by p***@childcaremanager.com
end;
inherited;
end;
This results in a very short flicker of the toolwindow for the
application window before it is hidden. If someone would like to
submit a different solution, I would welcome it.
p***@childcaremanager.com
2007-02-09 15:19:20 UTC
Permalink
Raw Message
Post by p***@childcaremanager.com
RESOLUTION REACHED.
Actually, the solution provided above doesn't work as I would like.
Here is the solution that I eventually accepted.

type TMyForm = class (TForm)
protected
procedure CreateParams(var Params : TCreateParams); override;
procedure WMSysCommand(var Message : TWmSysCommand); message
WM_SYSCOMMAND;
end;

procedure TMyForm.CreateParams(var Params : TCreateParams);
var
nWindowStyle : LongInt;
begin
inherited CreateParams(Params);
if (Params.WndParent = Application.Handle) then
begin
if ((Owner <> nil) and (Owner is TForm)) then
Params.WndParent := TForm(Owner).Handle
else if (Assigned(Application.MainForm)) then
Params.WndParent := Application.MainForm.Handle
else
Params.WndParent := GetDesktopWindow;
end;
Params.ExStyle := (Params.ExStyle and not WS_EX_TOOLWINDOW) or
WS_EX_APPWINDOW;
ShowWindow(Application.Handle, SW_HIDE);
nWindowStyle := GetWindowLong(Application.Handle, GWL_EXSTYLE);
nWindowStyle := (nWindowStyle and not WS_EX_APPWINDOW) or
WS_EX_TOOLWINDOW;
SetWindowLong(Application.Handle, GWL_EXSTYLE, nWindowStyle);
ShowWindow(Application.Handle, SW_SHOW);
end;

procedure TPerSoftForm.WMSysCommand(var Message : TWmSysCommand);
begin
case (Message.CmdType and $FFF0) of
SC_MINIMIZE:
begin
ShowWindow(Handle, SW_MINIMIZE);
Message.Result := 0;
end;
SC_RESTORE:
begin
ShowWindow(Handle, SW_RESTORE);
Message.Result := 0;
end;
else
inherited;
end;
end;

What had been giving me problems before was the fact that the parent
window of the main form was still the hidden application window. As
soon as the parent window of the main form was the desktop (this could
also, theoretically, be 0 to indicate lack of a parent), things worked
like a dream. The opening and closing animation is exactly what I was
after. The program should also look just fine in Windows Vista (both
the TaskSwitcher and Flip3D). The only drawback is that, if a child
form is open, the caption of the child form is displayed in the
TaskSwitcher, though the icon from the main form is used.

Loading...