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

Let¡¯s Talk about Delphi Application Normalization (DAN)

1,675 views
Skip to first unread message

Johnson Lau

unread,
Aug 3, 2002, 9:01:11 AM8/3/02
to
Let’s Talk about Delphi Application Normalization (DAN)

By Johnson Lau (laozho...@163.com)

As we know, applications made by Delphi have different appearances compared
to the other Win32 applications. Its title of application can differ from
the caption of the main form, and the pop-up menu of the taskbar button only
contains Restore, Minimize and Close.

The cause is that there is a special class called TApplication, and an
instance of the class, a global variable Application object, in Delphi,
which controls the base behaviors of the Delphi applications. It brings us
many conveniences to control the applications, but it sometimes confuses our
users, especially the beginners of computer – “Why this application cannot
be maximized (through the button on the taskbar)!” And this is not the most
important point. When we are building applications with special styles, it
stops us. Because an application can only have an instance of TApplication,
and the main form doesn’t have its own taskbar button, if we build a
application of Word 2002 style, we will get a very strange application in
operation. It behaves the same when making a DirectX game. So it is
necessary to make our Delphi applications behave the same with traditional
Win32 applications (what are using MFC or plain Win32 APIs). We call this
Delphi Application Normalization (DAN). And our normalized Delphi
applications are called Delphi Normalized Architecture applications (DNA
application). This article is to discuss how to DAN a DNA application.

1. Removing the taskbar button of Application object

For the most-likely appearance, firstly, we must remove the button of
Application object. To achieve this, we should change the extended style of
the invisible Application window, through adding WS_EX_TOOLWINDOW.

Now add an OnCreate message procedure to the main form, if the class name of
the main form is TMainForm, it will be TMainForm.FormCreate automatically.
Make it like what is shown below:

procedure TMainForm.FormCreate(Sender: TObject);

begin

SetWindowLong ( Application.Handle, GWL_EXSTYLE,

GetWindowLong (Application.Handle, GWL_EXSTYLE) or
WS_EX_TOOLWINDOW);

end;

Code 1.1 Codes to remove the button of Application from taskbar

Test the application. You can see that the taskbar button of Application
object is removed. But now there is no taskbar button owned by our
application. On the next step we will change this.

2. A common way to show the taskbar button of the form

If we want to show a taskbar button of a form (even a non-VCL window), just
simply adds WS_EX_APPWINDOW to the extended style. This is a very common
way. Now we will show how it works.

1) Override the CreateParams of TMainForm

procedure CreateParams(var Params: TCreateParams); override;

2) Add our codes

procedure TMainForm.CreateParams(var Params: TCreateParams);

begin

inherited;

Params.ExStyle := Params.ExStyle or WS_EX_APPWINDOW;

end;

Code 2.1 Very common codes to show the taskbar button of the form

But there are many potential problems. The first, when showing a modal
dialog, if we use mouse buttons to click the taskbar button, we will lose
the focus of the whole application, and it cannot be found through Alt+Tab
or other simple ways. We need to use Ctrl+Alt+Del to kill it at last. This
is a very difficult problem to solve. We will use another way to overcome it
at the 5th part of this article.

The second, when the application is running, minimizes the application. The
application will disappear as what it is being in the first problem.
Fortunately it’s a simply problem. We will solve this problem at first. The
other problem is left to solve later.

3. Solving the problem of disappearing when minimized

The cause is that the message is not processed properly. In the default
style of Delphi applications, the minimize behavior is handled to
Application object, which is an invisible window. In our codes, we haven’t
done any modifications to it. When the main form is minimized, it still call
minimize handler of Application object, not the main form. So our main form
disappear after minimize. Now we must change the message handler of TForm,
to response the minimize message, the WM_SYSCOMMAND message.

1) Process WM_SYSCOMMAND message

procedure WMSysCommand(var Message: TWMSysCommand); message WM_SYSCOMMAND;

2) Add codes

procedure TMainForm.WMSysCommand(var Message: TWMSysCommand);

begin

if (Message.CmdType and $FFF0 = SC_MINIMIZE) and

(Application.MainForm = Self) then

begin

if not IsIconic ( Handle) then

begin

Application.NormalizeTopMosts;

SetActiveWindow(Handle);

if (Application.MainForm <> nil) and (Application.ShowMainForm

or Application.MainForm.Visible) and IsWindowEnabled( Handle) then

DefWindowProc( Handle, WM_SYSCOMMAND, SC_MINIMIZE, 0);

end;

end

else if (Message.CmdType and $FFF0 <> SC_MOVE) or (csDesigning in
ComponentState) or (Align = alNone) or (WindowState = wsMinimized) then

inherited;

if ((Message.CmdType and $FFF0 = SC_MINIMIZE) or

(Message.CmdType and $FFF0 = SC_RESTORE)) and

not (csDesigning in ComponentState) and (Align <> alNone) then

RequestAlign;

end;

Code 3.1 solving the problem of disappearing when minimized

The most important sentence is the one in bold strokes. We have passed the
message to the default handler of Windows, the DefWindowProc function. Test
the application. Now our application behaves more properly.

But we still have the problem of the showing of modal dialogs. The next part
contains some base Win32 API knowledge, which is needed to get into the
source codes of VCL.

4. Getting prepared

When we use plain Win32 API to program, we must register window class,
create window using CreateWindow or CreateWindowEx, show the window we
create, and step into message loop. Why plain Win32 API applications can
retrieve a taskbar button? Use WS_EX_APPWINDOW? No. If we use WinSight32 or
Spy++ to view the properties of the Win32 API applications main window,
there is no WS_EX_APPWINDOW extended style attached to the window.

Let’s create a plain Win32 API application with Delphi to see how a window
retrieves its taskbar button.

program Test;

uses

Windows;

{$R *.RES}

var

Msg : TMsg;

wcex : WNDCLASSEX;

HandleA, HandleB : HWND;

function WndProc(hWnd : HWND; msg : UINT; wParam : WPARAM; lParam : LPARAM):
LRESULT stdcall;

begin

if Msg = 2 then PostQuitMessage (0);

Result := DefWindowProc(hWnd, msg, wParam, lParam);

end;

const

ClassName : PChar = 'DelphiWin32API';

WndName : PChar = 'Delphi Win32 API Program';

begin

wcex.cbSize := sizeof(WNDCLASSEX);

wcex.style := CS_HREDRAW or CS_VREDRAW;

wcex.lpfnWndProc := @WndProc;

wcex.cbClsExtra := 0;

wcex.cbWndExtra := 0;

wcex.hInstance := hInstance;

wcex.hIcon := HICON (nil);

wcex.hCursor := HICON (nil);

wcex.hbrBackground := HBRUSH (COLOR_WINDOW + 1);

wcex.lpszMenuName := nil;

wcex.lpszClassName := ClassName;

wcex.hIconSm := HICON (nil);

RegisterClassEx( wcex);

HandleA := CreateWindowEx( 0, ClassName, WndName, WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
HWND(nil), HMENU(nil), hInstance, nil);

ShowWindow ( HandleA, SW_SHOW);

HandleB := CreateWindowEx( 0, ClassName, WndName, WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, HandleA,
HMENU(nil), hInstance, nil);

ShowWindow ( HandleB, SW_SHOW);

while GetMessage ( Msg, 0, 0, 0) do

begin

TranslateMessage (Msg);

DispatchMessage (Msg);

end;

end.

Code 4.1 a plain Win32 application with Delphi

Run the application. There will be two windows, one with a taskbar button,
one without. Let’s modify our codes to do another test.

HandleB := CreateWindowEx( 0, ClassName, WndName, WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
HWND(nil), HMENU(nil), hInstance, nil);

ShowWindow ( HandleB, SW_SHOW);

Code 4.2 a plain Win32 application with Delphi (modification)

Build and run the application. There will be two windows, both with a
taskbar button. What’s the difference of the two applications? Focus on
CreateWindowEx. From Win32 API Help, we know the 9th parameter is the handle
of parent window or owner window. When we set it to HandleA, a valid window
handle, the HandleB will lose the taskbar button. When we set it to nil, the
handle value of the Windows Desktop, the HandleB will retrieve a taskbar
button.

OK. Maybe you understand now, there may be some similar errors in VCL source
codes.

5. Getting into VCL

TObject

|

TPersistent

|

TComponent

|

TControl

|

TWinControl

|

TScrollingWinControl

|

TCustomForm

|

TForm

|

TMainForm (our class)

Diagram 5.1 Deriving relations

The deriving relations are shown. From Delphi Help, we can know that, there
is a procedure called CreateWindowHandle.

We can get their descriptions (including overrode procedures) from Delphi
Help.

TCustomForm.CreateWindowHandle

procedure CreateWindowHandle(const Params: TCreateParams); override;

Description

The CreateWnd method calls CreateWindowHandle to create the form
window once it has been specified in the window-creation parameters.
CreateWindowHandle creates the window by calling the CreateWindowEx API
function, passing parameters from the record passed in the Params parameter.
CreateWindowHandle removes the CS_HREDRAW and CS_VREDRAW class styles from
the window class.

Params holds information needed when telling Windows to create a
window handle.


TWinControl.CreateWindowHandle

procedure CreateWindowHandle(const Params: TCreateParams); virtual;

Description

The CreateWnd method calls CreateWindowHandle to create the window for
a control. CreateWindowHandle creates the window by calling the
CreateWindowEx API function, passing parameters from the record passed in
the Params parameter. Once the window is created, its handle is available as
the Handle property.


Diagram 5.2 Descriptions of CreateWindowHandle in Delphi Help, Copyright by
Borland

It seems that the problem may be in TWinControl.CreateWindowHandle. Open
forms.pas and controls.pas located in the directories of VCL source codes,
and find TWinControl.CreateWindowHandle. Let’s have a look at the source
code.

procedure TCustomForm.CreateWindowHandle(const Params: TCreateParams);

var

CreateStruct: TMDICreateStruct;

NewParams: TCreateParams;

begin

.

.

.

end;

procedure TWinControl.CreateWindowHandle(const Params: TCreateParams);

begin

with Params do

FHandle := CreateWindowEx(ExStyle, WinClassName, Caption, Style,

X, Y, Width, Height, WndParent, 0, WindowClass.hInstance, Param);

end;

Code 5.1 CreateWindowHandle procedure source code (from Delphi VCL source
codes), Copyright by Borland

TWinControl.CreateWindowHandle uses Params.WndParent to be its parent.
It seems be no error. Let’s find out who call this procedure.

procedure TWinControl.CreateWnd;

var

Params: TCreateParams;

TempClass: TWndClass;

ClassRegistered: Boolean;

begin

CreateParams(Params);

with Params do

begin

if (WndParent = 0) and (Style and WS_CHILD <> 0) then

if (Owner <> nil) and (csReading in Owner.ComponentState) and

(Owner is TWinControl) then

WndParent := TWinControl(Owner).Handle

else

raise EInvalidOperation.CreateFmt(SParentRequired, [Name]);

FDefWndProc := WindowClass.lpfnWndProc;

ClassRegistered := GetClassInfo(WindowClass.hInstance, WinClassName,
TempClass);

if not ClassRegistered or (TempClass.lpfnWndProc <> @InitWndProc) then

begin

if ClassRegistered then Windows.UnregisterClass(WinClassName,

WindowClass.hInstance);

WindowClass.lpfnWndProc := @InitWndProc;

WindowClass.lpszClassName := WinClassName;

if Windows.RegisterClass(WindowClass) = 0 then RaiseLastOSError;

end;

CreationControl := Self;

CreateWindowHandle(Params);

if FHandle = 0 then RaiseLastOSError;

end;

StrDispose(FText);

FText := nil;

UpdateBounds;

Perform(WM_SETFONT, FFont.Handle, 1);

if AutoSize then AdjustSize;

end;

Code 5.2 TWinControl.CreateWnd procedure source code (from Delphi VCL source
codes), Copyright by Borland

The sentences in bold are the causes! The Params.WndParent is always set
to the owner of the form, and the owner will be Application object
automatically when the application initializes (See the source code of the
project). This is why the main form cannot retrieve its taskbar button.

6. Solutions

Now, we’ve got the cause. And there are two ways to overcome this problem.
In one way, we can derive a new class from TFrom, and override the CreateWnd
or CreateWindowHandle procedure. Or, we can directly rewrite the VCL source
codes, to make it compatible with our codes. We recommend the second way.
Because there are many calling of private member functions in CreateWnd and
CreateWindowHandle in TWinControl or derived class, if we derive a new class
from TForm, we must copy these private functions to the new class. It will
take very long time. So we will directly rewrite the VCL source codes.

And there is a problem we must consider. Maybe all Delphi applications are
written in the default style, or called non-DNA style. If we rewrites the
VCL source codes, there will be incompatible with the current codes. So we
add a variable into the source codes, for easily switching between the two
styles (the DNA style and non-DNA style).

1) In Controls.pas

Description: In this portion of our modifications, we make two changes. The
first change is to ensure the main form will get an icon. The second change
is the solution we’ve mentioned above. We’ve added two new variables,
ShowOnTaskbar and OldApplicationStyle. For detail read the second portion of
the modifications.

procedure TWinControl.CreateWnd;

var

Params: TCreateParams;

TempClass: TWndClass;

ClassRegistered: Boolean;

begin

CreateParams(Params);

with Params do

begin

if (WndParent = 0) and (Style and WS_CHILD <> 0) then

if (Owner <> nil) and (csReading in Owner.ComponentState) and

(Owner is TWinControl) then

WndParent := TWinControl(Owner).Handle

else

raise EInvalidOperation.CreateFmt(SParentRequired, [Name]);

FDefWndProc := WindowClass.lpfnWndProc;

ClassRegistered := GetClassInfo(WindowClass.hInstance, WinClassName,
TempClass);

if not ClassRegistered or (TempClass.lpfnWndProc <> @InitWndProc) then

begin

if ClassRegistered then Windows.UnregisterClass(WinClassName,

WindowClass.hInstance);

WindowClass.lpfnWndProc := @InitWndProc;

WindowClass.lpszClassName := WinClassName;

// Change made by Johnson Lau

// --------- Begin ----------

WindowClass.hIcon := Application.Icon.Handle;

// ---------- End -----------

if Windows.RegisterClass(WindowClass) = 0 then RaiseLastOSError;

end;

CreationControl := Self;

// Change made by Johnson Lau

// --------- Begin ----------

if (Self is TCustomForm) and (OldApplicationStyle = False) then

if Application.MainForm = nil then

begin

if Params.WndParent = Application.Handle then

Params.WndParent := 0;

end

else

begin

if ShowOnTaskBar then

Params.WndParent := 0

else

Params.WndParent := Application.MainForm.Handle;

end;

// ---------- End -----------

CreateWindowHandle(Params);

if FHandle = 0 then RaiseLastOSError;

end;

StrDispose(FText);

FText := nil;

UpdateBounds;

Perform(WM_SETFONT, FFont.Handle, 1);

if AutoSize then AdjustSize;

end;

2) In Forms.pas

Description:

For the maximum compatibility with non-DNA applications (the default style
of Delphi applications), we add a variable of OldApplicationStyle. If this
variable is set to True, our application will behave as the non-DNA
application. If is set to False, the default value, our application will
behave as a DNA application. If you want to effect the application, the
variable must be set before the main form is created, so set it in the
project’s source before the “Application.CreateForm” sentence of the main
form.

Another variable ShowOnTaskBar can show the taskbar button of any form. The
default value is to hide. This will not affect the main form, or when
OldAppliationStyle is set to True. The variable must be set before a form is
created, and put it in the project’s source before the
“Application.CreateForm” sentences except the main form.

In the procedure of TCustomForm.WndProc, we avoid a bug (maybe in Delphi).
The bug is affecting our application when a modal dialog is showing, the
application not being activated from another active window by clicking left
mouse button in the area of the main form except the area of the modal
dialog, but the right mouse button actives properly. (So it is thought to be
a bug in Delphi?)

We will solve the problem of disappearing when minimized in the procedure of
TApplication.Minimize. And in TApplication.Restore the relevant change is
made.

The change in TApplication.MessageBox will ensure the application to
response properly when calling this function to display a message box.

A. Adding these two variable to the interface part

OldApplicationStyle : Boolean;

ShowOnTaskBar : Boolean;

B. Add this variable to the implementation part

RefCount : Integer;

C. Modify these procedures and functions

constructor TCustomForm.Create(AOwner: TComponent);

begin

GlobalNameSpace.BeginWrite;

try

.

.

.

end;

// Change made by Johnson Lau

// --------- Begin ----------

if ( OldApplicationStyle = false ) then

begin

if ( RefCount = 0) then

SetWindowLong ( Application.Handle, GWL_EXSTYLE,

GetWindowLong (Application.Handle, GWL_EXSTYLE) or
WS_EX_TOOLWINDOW);

Inc ( RefCount);

end;

// ---------- End -----------

end;

destructor TCustomForm.Destroy;

begin

// Change made by Johnson Lau

// --------- Begin ----------

if ( OldApplicationStyle = false ) then

begin

Dec ( RefCount);

if ( RefCount = 0) then

SetWindowLong ( Application.Handle, GWL_EXSTYLE,

GetWindowLong (Application.Handle, GWL_EXSTYLE) and

( not WS_EX_TOOLWINDOW));

end;

// ---------- End -----------

.

.

.

end;

procedure TCustomForm.WndProc(var Message: TMessage);

.

.

.

begin

with Message do

case Msg of

// Change made by Johnson Lau

// --------- Begin ----------

WM_SETCURSOR:

begin

if (OldApplicationStyle = False) and

(Message.LParamLo = Word (HTERROR)) then

DefWindowProc ( Handle, Message.Msg, Message.WParam,
Message.LParam);

end;

// ---------- End -----------

.

.

.

end;

procedure TApplication.Minimize;

begin

if not IsIconic(FHandle) then

begin

NormalizeTopMosts;

// Change made by Johnson Lau

// --------- Begin ----------

if OldApplicationStyle then SetActiveWindow(FHandle);

if (MainForm <> nil) and (ShowMainForm or MainForm.Visible)

and IsWindowEnabled(MainForm.Handle) then

begin

if OldApplicationStyle then

begin

SetWindowPos(FHandle, MainForm.Handle, MainForm.Left,
MainForm.Top, MainForm.Width, 0, SWP_SHOWWINDOW);

DefWindowProc(FHandle, WM_SYSCOMMAND, SC_MINIMIZE, 0)

end else

DefWindowProc( MainForm.Handle, WM_SYSCOMMAND, SC_MINIMIZE, 0);

// ---------- End -----------

end else

ShowWinNoAnimate(FHandle, SW_MINIMIZE);

if Assigned(FOnMinimize) then FOnMinimize(Self);

end;

end;

procedure TApplication.Restore;

begin

if IsIconic(FHandle) then

begin

// Change made by Johnson Lau

// --------- Begin ----------

if OldApplicationStyle then

begin

SetActiveWindow(FHandle);

if (MainForm <> nil) and (ShowMainForm or MainForm.Visible)

and IsWindowEnabled(MainForm.Handle) then

DefWindowProc(FHandle, WM_SYSCOMMAND, SC_RESTORE, 0)

else ShowWinNoAnimate(FHandle, SW_RESTORE);

SetWindowPos(FHandle, 0, GetSystemMetrics(SM_CXSCREEN) div 2,

GetSystemMetrics(SM_CYSCREEN) div 2, 0, 0, SWP_SHOWWINDOW);

end;

// ---------- End -----------

if (FMainForm <> nil) and (FMainForm.FWindowState = wsMinimized) and

not FMainForm.Visible then

begin

FMainForm.WindowState := wsNormal;

FMainForm.Show;

end;

RestoreTopMosts;

if Screen.ActiveControl <> nil then

Windows.SetFocus(Screen.ActiveControl.Handle);

if Assigned(FOnRestore) then FOnRestore(Self);

end;

end;

function TApplication.MessageBox(const Text, Caption: PChar; Flags:
Longint): Integer;

.

.

.

begin

.

.

.

try

// Change made by Johnson Lau

// --------- Begin ----------

if OldApplicationStyle or (MainForm = nil) then

Result := Windows.MessageBox(Handle, Text, Caption, Flags)

else

Result := Windows.MessageBox(MainForm.Handle, Text, Caption, Flags);

// ---------- End -----------

finally

.

.

.

end;

Code 6.1 Modifications to the VCL source codes (from Delphi VCL source
codes), Copyright by Borland, modified by Johnson Lau.

Build and run our application. Now our application behaves properly.

7. For the maximum compatible

Our application becomes DNA application, but not the non-DNA application by
standard. And there may be some components working strangely in the DNA
style, like the common dialog (the same problem with the modal dialog, and
we will give a solution below), or even more. You can change all references
of the handle of main form from Application.Handle to
Application.MainForm.Handle to solve this problem.

function TOpenDialog.DoExecute(Func: Pointer): Bool;

.

.

.

begin

.

.

.

// Change made by Johnson Lau

// --------- Begin ----------

if OldApplicationStyle then

hWndOwner := Application.Handle

else

hWndOwner := Application.MainForm.Handle;

// ---------- End -----------

Result := TaskModalDialog(Func, OpenFileName);

.

.

.

end;

function TColorDialog.Execute: Boolean;

.

.

.

begin

.

.

.

// Change made by Johnson Lau

// --------- Begin ----------

if OldApplicationStyle then

hWndOwner := Application.Handle

else

hWndOwner := Application.MainForm.Handle;

// ---------- End -----------

Result := TaskModalDialog(@ChooseColor, ChooseColorRec);

.

.

.

end;

function TFontDialog.Execute: Boolean;

.

.

.

begin

.

.

.

// Change made by Johnson Lau

// --------- Begin ----------

if OldApplicationStyle then

hWndOwner := Application.Handle

else

hWndOwner := Application.MainForm.Handle;

// ---------- End -----------

SaveFontDialog := FontDialog;

.

.

.

end;

function TPrinterSetupDialog.Execute: Boolean;

.

.

.

begin

.

.

.

// Change made by Johnson Lau

// --------- Begin ----------

if OldApplicationStyle then

hWndOwner := Application.Handle

else

hWndOwner := Application.MainForm.Handle;

// ---------- End -----------

Result := TaskModalDialog(@PrintDlg, PrintDlgRec);

.

.

.

end;

function TPrintDialog.Execute: Boolean;

.

.

.

begin

.

.

.

// Change made by Johnson Lau

// --------- Begin ----------

if OldApplicationStyle then

hWndOwner := Application.Handle

else

hWndOwner := Application.MainForm.Handle;

// ---------- End -----------

Result := TaskModalDialog(@PrintDlg, PrintDlgRec);

.

.

.

end;

function TFindDialog.Execute: Boolean;

var

Option: TFindOption;

begin

.

.

.

// Change made by Johnson Lau

// --------- Begin ----------

if OldApplicationStyle then

FFindReplace.hWndOwner := FRedirector.Handle

else

FFindReplace.hWndOwner := Application.MainForm.Handle;

// ---------- End -----------

for Option := Low(Option) to High(Option) do

.

.

.

end;

Code 7.1 Modifications in dialogs.pas (from Delphi VCL source codes),
Copyright by Borland, modified by Johnson Lau.

8. Not reflecting our current applications?

If you don’t want to reflect the current applications, you can copy your
new controls.dcu, forms.dcu, and dialogs.dcu to a new directory, and add the
directory to your environment options.

And till now, all work has been done. And we can easily get a DNA
application. There may be some incompatible, but this is the default
appearance of Win32 application, so we strongly recommend you to use it. And
we strongly recommend Borland to strongly recommend this way.

All codes are tested in Windows XP Profession + Delphi 6 Update Pack 2.

The End

Caution: All rights of this article are reserved by Johnson Lau (Zhongcheng
Lao). Unauthorized copying prohibited.


0 new messages