18 February, 2007

How to remove an application button from the taskbar in Delphi?

This is a question which each Delphi programmer ask himself at least once in his life. If you use Google or any other search engine you will easily find this solution:


1 procedure TForm1.CreateParams(var Params: TCreateParams);
2 begin
3 inherited CreateParams(Params);
4 with Params do begin
5 ExStyle := ExStyle or WS_EX_TOPMOST;
6 WndParent := GetDesktopwindow;
7 end;
8 end;

So now you definitely have a question: "If it is so easy to find a solution then why I am talking about this?" An answer is simple. I have merely encountered a situation when approach which has been shown above is not suitable and decided to share my solution of this case with you.

Well what is the intention of the problem? According to the MSDN if you add WS_EX_TOOLWINDOW to the window ExStyle then window will act like this:

WS_EX_TOOLWINDOW

Creates a tool window; that is, a window intended to be used as a floating toolbar. A tool window has a title bar that is shorter than a normal title bar, and the window title is drawn using a smaller font. A tool window does not appear in the taskbar or in the dialog that appears when the user presses ALT+TAB. If a tool window has a system menu, its icon is not displayed on the title bar. However, you can display the system menu by right-clicking or by typing ALT+SPACE.

But my client asked me to remove the button from the taskbar and leave the ability to see the window in the ALT+TAB dialog. Thus I had to refuse from above method and create my own. The solution in principle is not hard. Here is the list of steps you should do if you want to achieve an appropriate behavior:

  1. Set FormStyle property of your form to fsStayOnTop (this helps us to hold our window on top of the rest windows of our application);
  2. Write next piece of code:
 1 type
2 TfrmSamples = class(TForm)
3 { ... }
4 public
5 procedure FormDeactivate(Sender: TObject);
6 { Public declarations }
7 end;
8
9 { ... }
10
11 procedure TfrmSamples.FormCreate(Sender: TObject);
12 begin
13 { force floating window to be on top }
14 Application.OnDeactivate := FormDeactivate;
15 end;
16
17 procedure TfrmSamples.FormShow(Sender: TObject);
18 begin
19 ShowWindow(Application.Handle, SW_HIDE);
20 end;
21
22 procedure TfrmSamples.FormDeactivate;
23 begin
24 Application.BringToFront;
25 end;

According to the Borland help TApplication.OnDeactivate event occurs when an application becomes inactive therefore if we want to keep our window above all other applications (windows) then this is exec place to act.

That's all! Quick and easy.

If you have some other solution of this task please post them in the comments.

15 February, 2007

Global mouse hook in Delphi

Hello everyone!

On my primary job I have changed my mouse. At home I have A4Tech optical mouse with a scroll but on my job I have Genius mouse (optical and with a scroll as well). At home I use my scroll also as a middle button and when I press it the window which is under cursor getting minimized. It was easy to achieve this behavior because software which comes with my A4Tech mouse supports this functionality. Another case is my Genius mouse at work. Software which bundled with it does not allow to minimize the window under the cursor when the scroll button is pressed. Pity!

But solution always can be found! I decided to write a global mouse hook on Delphi which will intercept middle (scroll) button click (WM_NCMBUTTONDOWN and WM_MBUTTONDOWN messages), check if any top level window is under the cursor and if yes then minimize that window.

The code is pretty simple.

We need two projects: one - which runs the hook and then kills it; the other - the hook itself (it is supposed to be a DLL because it is a global hook). Nothing difficult (at least if you what is DLL and how to use them)!

Here is the mouse hook (WH_MOUSE) implementation:

library MiddleButton;

uses
Windows,
Messages;

const
MemMapFile
= 'Igor_thief';
type
PDLLGlobal
= ^TDLLGlobal;
TDLLGlobal
= packed record
HookHandle: HHOOK;
end;

var
GlobalData: PDLLGlobal;
MMF: THandle;

{$R *.res}

function HookProc(Code: integer; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
var
CurrWND: THandle;
begin
if Code < 0 then
begin
Result :
= CallNextHookEx(GlobalData^.HookHandle, Code, wParam, lParam);
exit;
end; // if

if (wParam = WM_NCMBUTTONDOWN) or (wParam = WM_MBUTTONDOWN) then
begin
CurrWND :
= PMouseHookStruct(lParam)^.hwnd;
CurrWND :
= GetAncestor(CurrWND, GA_ROOTOWNER);
SendMessage(CurrWND, WM_SYSCOMMAND, SC_MINIMIZE,
0);
end; // if

Result :
= CallNextHookEx(GlobalData^.HookHandle, Code, wParam, lParam);
end;

procedure CreateGlobalHeap;
begin
MMF:
= CreateFileMapping(INVALID_HANDLE_VALUE, nil, PAGE_READWRITE, 0,
SizeOf(TDLLGlobal), MemMapFile);

if MMF = 0 then begin
MessageBox(
0, 'CreateFileMapping -', '', 0);
exit;
end;

GlobalData:
= MapViewOfFile(MMF, FILE_MAP_ALL_ACCESS, 0, 0, SizeOf(TDLLGlobal));
if GlobalData = nil then begin
// не смогди создать отображение
CloseHandle(MMF);
MessageBox(
0, 'MapViewOfFile -', '', 0);
end;
end;

procedure DeleteGlobalHeap;
begin
if GlobalData<>nil then
UnmapViewOfFile(GlobalData);

if MMF<> INVALID_HANDLE_VALUE then
CloseHandle(MMF);
end;

procedure RunHook; stdcall;
begin
GlobalData^.HookHandle:
= SetWindowsHookEx(WH_MOUSE, @HookProc, HInstance, 0);
if GlobalData^.HookHandle = INVALID_HANDLE_VALUE then
begin
MessageBox(
0, 'Error :)' , '' , MB_OK);
Exit;
end;
end;

procedure KillHook; stdcall;
begin
if (GlobalData<>nil) and (GlobalData^.HookHandle<>INVALID_HANDLE_VALUE) then
UnhookWindowsHookEx(GlobalData^.HookHandle);
end;

procedure DLLEntry(dwReason: DWORD);
begin
case dwReason of
DLL_PROCESS_ATTACH: CreateGlobalHeap;
DLL_PROCESS_DETACH: DeleteGlobalHeap;
end;
end;

exports
KillHook,
RunHook;

begin
DLLProc:
= @DLLEntry;
DLLEntry(DLL_PROCESS_ATTACH);
end.
And here is an implementation of the hook launcher:
unit RunMiddleButton;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls;

type
TfrmMain
= class(TForm)
btnRunHook: TButton;
btnKillHook: TButton;
procedure btnRunHookClick(Sender: TObject);
procedure btnKillHookClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

procedure RunHook; stdcall; external 'MiddleButton.dll' name 'RunHook';
procedure KillHook; stdcall; external 'MiddleButton.dll' name 'KillHook';

var
frmMain: TfrmMain;

implementation

{$R *.dfm}

procedure TfrmMain.btnRunHookClick(Sender: TObject);
begin
RunHook;
end;

procedure TfrmMain.btnKillHookClick(Sender: TObject);
begin
KillHook;
end;

end.

If you don't know what is HOOK and how it works at all then you shoulf go and read MSDN.
Good luck! Study hard!

07 February, 2007

Debugging DLL in Delphi

Yesterday I spend about an hour to figure out how to debug DLL in Delphi. It is really long time considering that I was doing this before. Google didn't help me a lot because all I have found wasn't really helpful.

I have found this solution in the Internet:

Until up to Win2k, you had to set the HostApplication on Start=>Parameter to debug a DLL. This will not work on Windows XP. That Delphi will know the DLL if you run the program, you have to do the following:
The program must load the DLL. After this, press Ctrl-Alt-M in Delphi, to list all modules. Sometimes there will be the DLL with path.
Solution:
Right click on the DLL, select "Reload symbols" and set the full path to the DLL. Now the breakpoints should be active.
When the DLL will be compiled in the system path (directory in PATH) this problem don't occur.

Not bad! But I have discovered for myself more easy and fast way. Next steps need to be done:

  1. In the Project Manager make your DLL project active and press Ctrl+Shift+F11;
  2. In the Project Options window which just appeared select Debugger node and on the right side of the window in the Host Application field specify the host application (press Browse button and select the application which use your library) and then close the dialog by pressing the Ok button;
  3. Then simply press F9 and be happy!

BTW, it is also good to set up dependencies of your main application which use the DLL you want to debug. You can read about this here.