9
votes

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.

1
Can you duplicate it with a new project? I couldn't..Sertac Akyuz
I can't reproduce this either. Can you post more code? (And possibly try changing Self.Handle to Application.Handle in the RegisterHotkey call in the meantime? The WM_HOTKEY message will still get to your form, because the application's message handler doesn't do anything with it; it will be sent on to your form just like usual.)Ken White
@KenWhite: if Application.Handle is used to register the hot key, then the WM_HOTKEY messages WILL NOT be directed to the TForm, directly or indirectly. They will be directed to TApplication instead, so to catch those messages you have to use the TApplication.OnMessage event and/or the TApplication.HookMainWindow() method.Remy Lebeau
@Remy, that's not true. The message goes to TApplication.WndProc, which has nothing to deal with it. It's then dispatched to other windows. (I can positively confirm this, because I have a component that uses this method to route WM_HOTKEY messages to whatever form it's placed on via Application.Handle.) Your answer (with AllocHWnd) looks good, though.Ken White
Adding following comment for others who may run into the same issue. Thanks to this community, my problem is solved. Following is my best guess as to why I ran into this issue. I have used the same code for years without any problems. May be, setting "MainFormOnTaskBar := True " causes Main Form's handle to be destroyed and recreated at some point AFTER Form.OnCreate() is called. Obviously, this was not happening UNTIL I changed the code in DPR. Once again, thanks to everyone who responded to my message.JayM

1 Answers

15
votes

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;