Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

fix for Delphi bug (Z-order of forms & common dialogs)

0 views
Skip to first unread message

Lester Kovac

unread,
Jan 23, 1996, 3:00:00 AM1/23/96
to
Problems:
1. dialogs get under main form when switched to main form using
taskbar in Win95
2. dialogs get under main form in single instance application when
dialog is popped up in first instance and user tries to run another
instance (he gets back first one with dialog under main form)
3. application with "always on top" menu. When this is marked on,
application's main form and any dialog on top of main form should stay
always on top of desktop. The problem is, any dialog gets always under
main form and application looses "stay on top" ability.

Fix:
1. define function GetWindowOnTop(). I suggest to use for it source
code of Dialogs unit which you'll have to overwrite anyway. Because
Borland didn't provide source code to all files (you'll be missing
TOINTF.PAS) you have to rename Dialogs. I renamed it to LDialogs. This
way our fix won't get to other VCL units affected, but what can we do
about it ? So, inside of LDialogs unit declare in interface:

function GetWindowOnTop(InstHandle: THandle): HWnd;

and implement it in implementation part as:

function GetWindowOnTop(InstHandle: THandle): HWnd;
var
Wnd: HWnd; { Window handle, for iterating through
windows}
sz: array[0..250] of char;
ApplicationWnd: HWnd;
begin
Wnd := GetTopWindow(0); { Get HWnd for the desktop to start }
ApplicationWnd := 0;
{ First we must set ApplicationWnd for TApplication Delphi class }
while (Wnd <> 0) do
begin
{ If it is visible and the owner is the desktop... }
if (IsWindowVisible(Wnd) and (GetWindow(Wnd, GW_OWNER) = 0)) then
begin
{ Get the HInstance of the window Wnd }
{ if the new HInstance matches the one passed in, save it }
{ beware, Delphi programs create strange fake window which is
also }
{ visible and has ClassName TApplication; this is not main form,
however !! }
{ relationship between that fake window and main form is that
fake window }
{ is the owner of main form's window }
if GetWindowWord(Wnd, GWW_HINSTANCE) = InstHandle then
begin
GetClassName(Wnd, sz, 250);
if 0 = StrComp(sz, 'TApplication') then
begin
ApplicationWnd := Wnd;
break;
end;
end;
end;
Wnd := GetWindow(Wnd, GW_HWNDNEXT); { Get the next running window
}
end;
{ now we must find first active form based on ApplicationWnd }
Wnd := GetTopWindow(0); { Get HWnd for the desktop to start }
while (Wnd <> 0) do
begin
{ If it is visible and the owner is the desktop... }
if (IsWindowVisible(Wnd) and
(GetWindow(Wnd, GW_OWNER) = ApplicationWnd) and
(GetWindowWord(Wnd, GWW_HINSTANCE) = InstHandle)) then
begin
Wnd := GetLastActivePopup(Wnd);
break;
end;
Wnd := GetWindow(Wnd, GW_HWNDNEXT); { Get the next running window
}
end;
Result := Wnd;
if Wnd <> 0 then
begin
{ last thing: we must check for any children }
while True do
begin
Wnd := GetTopWindow(0); { Get HWnd for the desktop to start }
while Wnd <> 0 do
begin
if (IsWindowVisible(Wnd) and
(GetWindow(Wnd, GW_OWNER) = Result)) then
break;
Wnd := GetWindow(Wnd, GW_HWNDNEXT); { Get the next window
running }
end;
if 0 <> Wnd then
Result := Wnd;
if IsWindowEnabled(Wnd) or (Wnd = 0) then
break;
end;
end;
end;

2. Now, still in LDialogs.pas change each line
hWndOwner := Application.Handle;
to
hWndOwner := GetWindowOnTop(System.hInstance);

It is together 6 changes (common dialogs).

Alternative to paragraphs 1 and 2 is to define & implement
GetWindowOnTop elsewhere and change those 6 lines in Dialogs.pas to
hWndOwner := Screen.ActiveForm.Handle;

3. Now you have to rename each use of Dialogs in your source code
(units and DPR) to LDialogs. Note that Delphi has some bad feature
that whenever it starts it tries to add you Dialogs unit again
whenever you changed it. The best thing to prevent it is to set right
away such files read/only. Bit more work to make them again read/write
and back when you need any other change, but you have to do it.

4. All your forms (except the main one) must override the CreateParams
method so that it sets correctly it parent's handle. As example:

TForm2 = class(TForm)
whatever...
private
{ Private-Declarations }
procedure CreateParams(var Params: TCreateParams); override;
...
public
{ Public-Declarations }
...
end;

and in implementation:

procedure TForm2.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
Params.WndParent := Form1.Handle; { or whatever the parent's handle
is... }
end;

You must take special care of the dialogs (forms) which are possibly
displayed in one case on top on one form, in another case on another.
You may not create them in DPR file (but before they are to be
displayed) and you must destroy them afterwards. You need to rewrite
Create constructor.
Here is an example:

Unit with such form:

unit Unit3;

interface

uses WinTypes, WinProcs, Classes, Graphics, Forms, Controls, Buttons,
StdCtrls;

type
TForm3 = class(TForm)
OKBtn: TBitBtn;
private
{ Private declarations }
m_Handle: HWnd;
procedure CreateParams(var Params: TCreateParams); override;
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
end;

var
Form3: TForm3;

implementation

{$R *.DFM}


constructor TForm3.Create(AOwner: TComponent);
begin
m_Handle := TForm(AOwner).Handle; { this must be before inherited
constructor call }
inherited Create(AOwner);
end;

procedure TForm3.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
Params.WndParent := m_Handle;
end;

end.

and now the use of such form:

procedure TForm2.Button2Click(Sender: TObject);
begin
Form3 := TForm3.Create(Self);
Form3.ShowModal;
Form3.Free;
end;

5. We have to solve Win95 taskbar issue (problem 1). Declare and add
to your main form this kind of method:

Procedure TMainForm.ApplicationActivate(ASender: TObject);
begin
BringWindowToTop(GetWindowOnTop(hInstance));
end;

and anywhere inside FormCreate you have to do:

procedure TMainForm.FormCreate(Sender: TObject);
begin
...
Application.OnActivate := ApplicationActivate;
...
end;

6. If you want to create single instance application, change your
project (DPR) file:
if hPrevInst = 0 then
begin
{ do whatever was there before }
Randomize;
Application.CreateForm(TMainForm, MainForm);
Application.CreateForm(TAboutBox, AboutBox);
...
Application.CreateForm(TLastForm, LastForm);
Application.Run;
end
else
begin
PrevInstWnd := GetWindowOnTop(hPrevInst);
if IsIconic(PrevInstWnd) then
ShowWindow(PrevInstWnd, SW_RESTORE)
else
BringWindowToTop(PrevInstWnd);
end;

7. Solution to third problem: "Always on Top" application.
Example:

unit MainUnit;

interface

uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, LDialogs, StdCtrls, Menus;

type
TMainForm = class(TForm)
Edit1: TMemo;
MainMenu1: TMainMenu;
SaveAs1: TMenuItem;
About1: TMenuItem;
AlwaysOnTop1: TMenuItem;
SaveDialog1: TSaveDialog;
procedure FormCreate(Sender: TObject);
procedure SaveAs1Click(Sender: TObject);
procedure AlwaysOnTop1Click(Sender: TObject);
procedure About1Click(Sender: TObject);
private
procedure AppActivate(Sender: TObject);
public
{ Public declarations }
procedure ManageStayOnTop;
end;

var
MainForm: TMainForm;

implementation

{$R *.DFM}

uses About;

procedure TMainForm.FormCreate(Sender: TObject);
begin
Application.OnActivate := AppActivate;
end;

procedure TMainForm.SaveAs1Click(Sender: TObject);
var
f: System.Text;
i: integer;
begin
if SaveDialog1.Execute then
begin
{ in this instance, save content of memo }
System.Assign(f, SaveDialog1.FileName);
ReWrite(f);
if 0 = IOResult then
begin
if Edit1.Lines.Count > 0 then
for i := 0 to Edit1.Lines.Count - 1 do
WriteLn(f, Edit1.Lines.Strings[i]);
System.Close(f);
end;
end;
end;

procedure TMainForm.AlwaysOnTop1Click(Sender: TObject);
begin
AlwaysOnTop1.Checked := not AlwaysOnTop1.Checked;
ManageStayOnTop;
end;

procedure TMainForm.About1Click(Sender: TObject);
begin
AboutBox.ShowModal;
end;

procedure TMainForm.ManageStayOnTop;
var
WndInsertAfter: HWnd;
begin
{ this method forces our windows to stay on top or not, depending on
menu item status }
if AlwaysOnTop1.Checked then
WndInsertAfter := HWND_TOPMOST
else
WndInsertAfter := HWND_NOTOPMOST;
SetWindowPos(Application.Handle, WndInsertAfter, 0, 0, 0, 0,
SWP_NOMOVE or
SWP_NOSIZE or SWP_NOACTIVATE);
SetWindowPos(Handle, WndInsertAfter, 0, 0, 0, 0, SWP_NOMOVE or
SWP_NOSIZE or SWP_NOACTIVATE);
end;

procedure TMainForm.AppActivate(Sender: TObject);
begin
BringWindowToTop(GetWindowOnTop(HInstance));
end;

end.


You can get sample of such Z-order obedient program at
ftp://ftp.synapse.net/private/b/bps/outgoing/test.zip (temporary
location only).

Lester


Tim Shea

unread,
Jan 24, 1996, 3:00:00 AM1/24/96
to
lester...@cognos.com (Lester Kovac) wrote:

>Problems:
>1. dialogs get under main form when switched to main form using
>taskbar in Win95
>2. dialogs get under main form in single instance application when
>dialog is popped up in first instance and user tries to run another
>instance (he gets back first one with dialog under main form)
>3. application with "always on top" menu. When this is marked on,
>application's main form and any dialog on top of main form should stay
>always on top of desktop. The problem is, any dialog gets always under
>main form and application looses "stay on top" ability.

>Fix:

[fix deleted]

Great! I have been wondering about this problem.

Now we need a way to keep (modal) always-on-top dialogs from blocking
run-time error messages when Delphi stops on an exception.

--
Tim Shea
CSI
c...@citysoft.com


0 new messages