Оконные сообщения в FireMonkey
В обычных Windows приложениях (VCL) мы достаточно часто можем использовать передачу своих собственных сообщений между окнами в собственных целях, и собственно обрабатывать эти сообщения. FireMonkey в силу своей кроссплатформенности исключила поддержку такого функционала. С точки зрения языка программирования функционал данный реализуется очень просто. Нам нужен метод с определенной сигнатурой и код самого сообщения. Чтобы обеспечить прием сообщения классом формы нужно всего лишь расширить класс формы, добавив метод подобный следующему:
procedure wmTest(var msg : TMessage); message WM_TEST;
Подобный функционал можно использовать в случае, если коды сообещений мы определяем заранее в виде констант в диапазоне от WM_USER до WM_APP. Значения выше этой границы мы также можем использовать, эти значения регистриуются в системе с помощью системного метода RegisterWindowMessage(). Подозреваю, что подобный синтаксис описания классов/методов в языке был введен специально для этих целей, поскольку в Windows архитектуре сообщения используются повсеместно. Однако переходя к фреймворку (или платформе, а вернее к кросс-платформе FireMonkey/FMX) мы отдаляемся от истоков, и фактически, в нашем приложении окнами, имеющими HWND дескриптор являются только непосредственно сами окна приложения. Код передачи и обработки цикла сообщений скрыт в implementation секции модля FMX.Platform.Win. Собственно, итог здесь один: если в приложении FireMonkey мы определим метод описанный выше, то он никогда не получит отправленного с помощью Send/PostMessage() сообщения.
Поскольку, повторюсь, реалиазция цикла обработки скрыта в implementation секции модуля, то вклиниться в ее обработку мы попросту никак не можем. Следовательно с этого момента есть два пути:
- Использовать ловушки сообщений (hooks)
- Заменить оконную процедуру на свою
Использовать первый метод гораздо проще. Поскольку ловушка устаналивается для приложения в целом и получает сообщения для всех окон. Минус этого метода - ловушка будет вызываться для каждого сообщения, а их многие-многие тысячи. Установка ловушки происходит с помощью метода SetWindowsHookEx.
Второй метод не шибко сложнее. При создании окна нам необходимо переопределить указатель на оконную процедуру. Как это часто бывало в VCL (пр обработке собщений выше WM_APP), наша новая процедура обработки цикла сообщений WndProc, должна вызывать старую процедуру, а затем можем выполнить какие то свои действия.
Код для использования ловушки я вынес в отдельный класс и модуль. Посольку ловушка одна, то методы класса, простите за каламбур, классовые. Код ловушки передает сообщения только диапазона WM_USER..WM_APP. Собствено метод ловушки сначала проверяет значения кода nCode на равенство HC_ACTION, которое говорит нам что мы должны что то обрабатывать. Затем отфильтровыываются значения сообщения не выходящий в диапазон. Затем проверяется дескриптор окна (с помощью метода FindWindow(), это не класический FindWindow из WinApi, а метод возвращающий FMX-дескриптор по значенияю HWND-дескриптора окна). И если форма с нужным дескриптором найдена, то сообщение передается экземпляру формы используя обычный механизм TObject.Dispatch():
unit fmxMessages; interface type TFmxMessageHook = class(TObject) strict private class var FHook : THandle; public class procedure InitMsgHook(); class destructor Destroy(); class property Hook : THandle read FHook; end; implementation uses WinApi.Windows, WinApi.Messages, fmx.Forms, fmx.Platform.Win; function FmxMsgHookProc(nCode : integer; wParam : WParam; lParam : LPARAM):LResult; stdcall; var m : PMsg; msg : TMessage; cf : TCommonCustomForm; begin if nCode = HC_ACTION then begin m := PMsg(lParam); if (m.message >= WM_USER) and (m.message <= WM_APP) then begin cf := Fmx.Platform.Win.FindWindow(m.hwnd); if assigned(cf) then begin msg.Msg := m.message; msg.WParam := m.wParam; msg.LParam := m.lParam; msg.Result := 0; try cf.Dispatch(msg); except end; end; end; end; result := CallNextHookEx(TFmxMessageHook.Hook, nCode, wParam, lParam); end; class procedure TFmxMessageHook.InitMsgHook(); begin FHook := SetWindowsHookEx(WH_GETMESSAGE, fmxMsgHookProc, 0, GetCurrentThreadId()); end; class destructor TFmxMessageHook.Destroy(); begin if FHook > 0 then UnhookWindowsHookEx(FHook); end; end.
Для того чтобы модуль начал работу в приложении, следует видоизменить код dpr файла вставив строку вызова метода InitHook(), например, после создания главной формы (Application.CreateForm()), и до начала цикцла обработки сообщений Application.Run().
begin Application.Initialize; Application.CreateForm(TMainForm, MainForm); TFmxMessageHook.InitMsgHook(); Application.Run; end.
Все это я к чему. Кросс-платформенность это хорошо, но в Delphi XE6 хотелось бы увидеть маленькое дополнение в методе WndProc модуля fmx.Platform.Windows. Например, такое:
case uMsg of ........ WM_USER..WM_APP : begin msg.Msg := uMsg; msg.WParam := WParam; msg.LParam := LParam; msg.Result := 0; try LForm.Dispatch(msg); except end; end; ....... end;
В общем-то ограничиваться данными константами смысла нет, но это значительно упростит жизнь разработчикам которые хотят писать Windows-проложения в FMX. Фактически, сейчас если вы хотите в приложении использовать, например списки переходов, то вам необходимо для начала получить сообщение о том, что кнопка на панели задач была создана. А для этого вам и потребуется использовать либо хук, либо переопределять оконную процедуру. В VCL переопределение было гораздо проще, поскольку можно было перекрыть метод DefaultHandler класса формы, добавив туда необходимый код.
16.11.2013 в 14:54
18.11.2013 в 13:50
У меня вопрос по SetWindowsHookEx. Нужно ли еще дополнительно работать с манифестом, чтобы эта функция смогла сделать хук?
Потому что на Windows 7 эта функция работает не так как на XP. А именно не дает просто задать хук. А требует повышенных привелегий, которые по-моему задаются как раз через манифест.
Спасибо.
19.11.2013 в 11:51
с манифестом ничего не делал, в MSDN про разницу в вин7 и винХп не видел.
допускаю что привилегии нужны при установке DEBUG хука, а так то какая кому разница, какой хук я на свое же окно повешу.
19.11.2013 в 01:27
19.11.2013 в 11:26
04.09.2017 в 12:55
B195868 я закрывал :-)
20.11.2013 в 14:50
27.03.2014 в 03:33
когда берёшься или за какой то из углов или за сторону
03.05.2017 в 21:40
impressed! Very helpful info particularly the
last part :) I care for such information much.
I was looking for this particular info for a long time.
Thank you and good luck.