Firemonkey and global hotkey?

I am looking for a way to get a global hotkey in a firemonkey application (windows only, at least for now). After some frustration and searching on the Internet, this should work: register a hotkey with a winapi call

RegisterHotKey(FmxHandleToHWND(form1.Handle), 0 , MOD_CONTROL, $41); 

it returns true. and then catch the hotkey in the forms procedure

 procedure WMHotKey(var Msg: TWMHotKey); message WM_HOTKEY; 

but this one is never called. I used to do this in vcl applications, so I think that firemonkey handles messages differently. So the question is: how can I use global hotkeys in a firemonkey application?

edit: some example of applying this solution. I created a block with a small class

 unit fire_hotkey; interface uses windows, messages,allocatehwnd; type TMsgHandler = procedure (var Msg: TMessage) of object; THotClass = class(TObject) fMsgHandlerHWND : HWND; proc:TMsgHandler; constructor Create; procedure init; destructor Destroy; override; end; implementation { hotClass } constructor THotClass.Create; begin inherited; end; destructor THotClass.Destroy; begin ThreadDeallocateHWnd(fMsgHandlerHWND); inherited; end; procedure THotClass.init; begin fMsgHandlerHWND := ThreadAllocateHWnd(proc,true); end; end. 

then my main form has a hotkey event handling procedure:

 procedure TformEditor.WMHotKey(var Msg: TMessage); begin if Msg.Msg = WM_HOTKEY then begin //call lua function or sth //... end else Msg.Result := DefWindowProc(hotkeyGrabber.fMsgHandlerHWND, Msg.Msg, Msg.wParam, Msg.lParam); end; 

and there’s a global Grabber hotkey: THotClass; which is initialized in the form of create:

  hotkeyGrabber:=THotClass.Create; hotkeyGrabber.proc:=WMHotKey; hotkeyGrabber.init; 

after that you should register the hotkeys, as in a regular vcl application, and they will be processed http://www.swissdelphicenter.ch/torry/showcode.php?id=147 hope this makes sense

+4
source share
1 answer

The FMX framework will not send messages to your form. Thus, your WMHotKey will never be called because the FMX framework never calls Dispatch . You can see this happening by checking the WndProc method declared in the implementation section of the FMX.Platform.Win block.

The easiest way to solve this problem is to create your own window by calling CreateWindow . And then follow the window procedure for this window, which will process the WM_HOTKEY message.

I wrapped these low level API calls as follows:

 unit AllocateHWnd; interface uses System.SysUtils, System.Classes, System.SyncObjs, Winapi.Messages, Winapi.Windows; function ThreadAllocateHWnd(AMethod: TWndMethod; MessageOnly: Boolean): HWND; procedure ThreadDeallocateHWnd(Wnd: HWND); implementation const GWL_METHODCODE = SizeOf(Pointer)*0; GWL_METHODDATA = SizeOf(Pointer)*1; ThreadAllocateHWndClassName = 'MyCompanyName_ThreadAllocateHWnd'; var ThreadAllocateHWndLock: TCriticalSection; ThreadAllocateHWndClassRegistered: Boolean; function ThreadAllocateHWndProc(Window: HWND; Message: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall; var Proc: TMethod; Msg: TMessage; begin Proc.Code := Pointer(GetWindowLongPtr(Window, GWL_METHODCODE)); Proc.Data := Pointer(GetWindowLongPtr(Window, GWL_METHODDATA)); if Assigned(TWndMethod(Proc)) then begin Msg.Msg := Message; Msg.wParam := wParam; Msg.lParam := lParam; Msg.Result := 0; TWndMethod(Proc)(Msg); Result := Msg.Result end else begin Result := DefWindowProc(Window, Message, wParam, lParam); end; end; function ThreadAllocateHWnd(AMethod: TWndMethod; MessageOnly: Boolean): HWND; procedure RegisterThreadAllocateHWndClass; var WndClass: TWndClass; begin if ThreadAllocateHWndClassRegistered then begin exit; end; ZeroMemory(@WndClass, SizeOf(WndClass)); WndClass.lpszClassName := ThreadAllocateHWndClassName; WndClass.hInstance := HInstance; WndClass.lpfnWndProc := @ThreadAllocateHWndProc; WndClass.cbWndExtra := SizeOf(TMethod); Winapi.Windows.RegisterClass(WndClass); ThreadAllocateHWndClassRegistered := True; end; begin ThreadAllocateHWndLock.Acquire; Try RegisterThreadAllocateHWndClass; if MessageOnly then begin Result := CreateWindow(ThreadAllocateHWndClassName, '', 0, 0, 0, 0, 0, HWND_MESSAGE, 0, HInstance, nil); end else begin Result := CreateWindowEx(WS_EX_TOOLWINDOW, ThreadAllocateHWndClassName, '', WS_POPUP, 0, 0, 0, 0, 0, 0, HInstance, nil); end; Win32Check(Result<>0); SetWindowLongPtr(Result, GWL_METHODDATA, NativeInt(TMethod(AMethod).Data)); SetWindowLongPtr(Result, GWL_METHODCODE, NativeInt(TMethod(AMethod).Code)); Finally ThreadAllocateHWndLock.Release; End; end; procedure ThreadDeallocateHWnd(Wnd: HWND); begin Win32Check(DestroyWindow(Wnd)); end; initialization ThreadAllocateHWndLock := TCriticalSection.Create; finalization ThreadAllocateHWndLock.Free; end. 

This is a thread safe version of AllocateHWnd VCL, which is notorious for being unusable outside the main thread.

What you need to do is create a class using a window procedure, that is, something that implements TWndMethod . It can be an instance method or a class method. Then just call ThreadAllocateHWnd to create the window, and pass that window to RegisterHotKey . When the time comes to expand all this, unregister your hotkey and destroy the window by calling ThreadDeallocateHWnd .

+5
source

All Articles