Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Delphi 2007 - Systemwide Hot key is NOT "system-wide" if setting "MainFormOnTaskBar := True"

I have a Delphi 2007 project that has run fine on Windos XP, Vista and "7" for years. It was an upgrade from Delphi 5 thus "MainFormOnTaskBar" was "false" by default (I never changed it in DPR). In this scenario, the system-wide hot key worked "system-wide" with following code in main form's OnCreate event handler.

HotKey_xyz := GlobalAddAtom('Hotkey_xyz');
if NOT RegisterHotKey(Self.Handle, HotKey_xyz, MOD_CONTROL, VK_F12) then
    ShowMessage('Unable to register Control-F12 as system-wide hot key') ;

(I have GlobalDeleteAtom() and UnregisterHotKey() in Form.OnDestroy as expected.)

Now, I need a Form to show its own button on Taskbar, so I set "Application.MainFormOnTaskBar := True" in DPR. This works as expected. However, this has the side-effect that Control-F12 does NOT work system-wide, it works ONLY IF my application has focus (so, it is NOT "system-wide" anymore.)

I have extensively searched the 'Net have found many articles regarding how/why "MainFormOnTaskBar" affects certain subform/modal form behaviors. However, I have found nothing regarding its effect on a "System-Wide Hot Key" issue that I describe above. I have tested and retested my application with MainFormOnTaskBar set to true and false while all else remains exactly the same. I can positively verify that the above described issue with System-wide hot key relates to MainFormOnTaskBar flag.

I will greatly appreciate any guidance regarding a work-around. I do need BOTH - a system-wide hot key AND a form with its own button on taskbar.

Thank You very much.

like image 912
JayM Avatar asked Apr 19 '12 00:04

JayM


1 Answers

TApplication.MainFormOnTaskbar has no effect on system-wide hotkeys at all. I can positively confirm that. I am able to receive WM_HOTKEY messages regardless of what MainFormOnTaskbar is set to, regardless of whether the app is focused or not, etc. So whatever you are seeing is not what you think is happening.

Most likely, the Form's Handle is simply being recreated behind your back after you have called RegisterHotKey(), so you lose the HWND that would receive the WM_HOTKEY messages. Instead of using the OnCreate event, you should override the Form's CreateWindowHandle() and DestroyWindowHandle() methods instead to ensure the hot key is always registered for the Form's current HWND no matter what happens to it (you should always do that whenever you tie any kind of data to the Form's Handle), eg:

type
  TForm1 = class(TForm)
  private
    HotKey_xyz: WORD;
    procedure WMHotKey(var Message: TMessage); message WM_HOTKEY;
  protected
    procedure CreateWindowHandle(const Params: TCreateParams); override;
    procedure DestroyWindowHandle; override;
  end;

procedure TForm1.CreateWindowHandle(const Params: TCreateParams);
begin
  inherited;
  HotKey_xyz := GlobalAddAtom('Hotkey_xyz'); 
  if HotKey_xyz <> 0 then
    RegisterHotKey(Self.Handle, HotKey_xyz, MOD_CONTROL, VK_F12);
end;

procedure TForm1.DestroyWindowHandle(const Params: TCreateParams);
begin
  if HotKey_xyz <> 0 then
  begin
    UnregisterHotKey(Self.Handle, HotKey_xyz);
    GlobalDeleteAtom(HotKey_xyz);
    HotKey_xyz := 0;
  end;
  inherited;
end;

procedure TForm1.WMHotKey(var Message: TMessage);
begin
  ...
end;

A better option is to use AllocateHWnd() to allocate a separate dedicated HWND just for handling the hot key messages (then you can use the OnCreate and OnDestroy events again), eg:

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    HotKey_xyz: WORD;
    HotKeyWnd: HWND;
    procedure HotKeyWndProc(var Message: TMessage);
  end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  HotKeyWnd := AllocateHwnd(HotKeyWndProc);
  HotKey_xyz := GlobalAddAtom('Hotkey_xyz'); 
  if HotKey_xyz <> 0 then
    RegisterHotKey(HotKeyWnd, HotKey_xyz, MOD_CONTROL, VK_F12);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  if HotKey_xyz <> 0 then
  begin
    UnregisterHotKey(HotKeyWnd, HotKey_xyz);
    GlobalDeleteAtom(HotKey_xyz);
    HotKey_xyz := 0;
  end;
  if HotKeyWnd <> 0 then
  begin
    DeallocateHWnd(HotKeyWnd);
    HotKeyWnd := 0;
  end;
end;

procedure TForm1.HotKeyWndProc(var Message: TMessage);
begin
  if Message.Msg = WM_HOTKEY then
  begin
    ...
  end else
    Message.Result := DefWindowProc(HotKeyWnd, Message.Msg, Message.WParam, Message.LParam);
end;
like image 73
Remy Lebeau Avatar answered Oct 23 '22 10:10

Remy Lebeau