I'm looking for a way to cach a global hotkey in firemonkey app (windows only, at least for now). After some frustration and googling this is supposed to work: register hotkey with winapi call
RegisterHotKey(FmxHandleToHWND(form1.Handle), 0 , MOD_CONTROL, $41);
it returns true.
and then catch the hotkey in a forms' procedure
procedure WMHotKey(var Msg: TWMHotKey); message WM_HOTKEY;
but this one is never called. I've used to do that in vcl apps before so my guess is that firemonkey handles messages in different way. So the question is: How do I catch global hotkeys in firemonkey app?
edit: some example of applying that solution. I've created an unit with little 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 procedure for processing hotkey events:
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 is a global hotkeyGrabber:THotClass; that gets initialized on form create:
hotkeyGrabber:=THotClass.Create;
hotkeyGrabber.proc:=WMHotKey;
hotkeyGrabber.init;
after that you should register hotkeys like in usual vcl app and they will be cought http://www.swissdelphicenter.ch/torry/showcode.php?id=147 hope it makes sense
The FMX framework won't route messages to your form. So, your WMHotKey
will never be called because the FMX framework never calls Dispatch
. You can see that this is the case by inspecting the WndProc
method declared in the implementation section of the FMX.Platform.Win
unit.
The easiest way to solve this problem will be to create your own window by calling CreateWindow
. And then implementing a window procedure for that window that will handle the WM_HOTKEY
message.
I've wrapped those low-level API calls like this:
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 the VCL AllocateHWnd
which is notorious for being unusable outside the main thread.
What you need to do is create a class with a window procedure, i.e. something that implements a TWndMethod
. It can be an instance method or a class method. Then simply call ThreadAllocateHWnd
to create the window, and pass that window to RegisterHotKey
. When it's time to unwind it all, unregister your hot key, and destroy the window with a call to ThreadDeallocateHWnd
.