Как получить событие завершения Windows в проекте Fmx как WM_QUERYENDSESSION и WM_ENDSESSION для проекта VCL?
Мне нужно перехватить выключение Windows и выполнить какой-то запрос БД, до этого мое приложение закроется.
Я использую Delphi XE10 под Windows 10 в проекте FMX
То, что я пробовал, - это код ниже, но он не работает.
private
{ Private declarations }
{$IFDEF MSWINDOWS}
procedure WMQueryEndSession(var Msg: TWMQueryEndSession); message WM_QUERYENDSESSION;
procedure WMEndSession(var Msg : TWMQueryEndSession); message WM_ENDSESSION ;
{$ENDIF}
end;
procedure TfMain.WMQueryEndSession(var Msg: TWMQueryEndSession);
var
lista:TStringList;
begin
{$IFDEF MSWINDOWS}
try
lista:=TStringList.Create;
lista.Add(FOrmatDateTime('DD/MM/YYYY HH:NN:SS',now)+' event WMQueryEndSession');
Lista.SaveToFile(froot+formatdatetime('YYMMDDHHNNSSZZZ',now)+'.log');
SincroClose();
lista.Add(FOrmatDateTime('DD/MM/YYYY HH:NN:SS',now)+' Done');
Lista.SaveToFile(froot+formatdatetime('YYMMDDHHNNSSZZZ',now)+'.log');
finally
lista.Free;
end;
{$ENDIF}
inherited;
end;
procedure TfMain.WMEndSession(var Msg: TWMQueryEndSession);
var
lista:TStringList;
begin
{$IFDEF MSWINDOWS}
try
lista:=TStringList.Create;
lista.Add(FOrmatDateTime('DD/MM/YYYY HH:NN:SS',now)+' WMEndSession');
Lista.SaveToFile(froot+formatdatetime('YYMMDDHHNNSSZZZ',now)+'.log');
SincroClose();
lista.Add(FOrmatDateTime('DD/MM/YYYY HH:NN:SS',now)+' Done');
Lista.SaveToFile(froot+formatdatetime('YYMMDDHHNNSSZZZ',now)+'.log');
finally
lista.Free;
end;
{$ENDIF}
inherited;
end;
procedure TfMain.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
var lista:TStringList;
begin
{$IFDEF MSWINDOWS}
CanClose:=false;
try
lista:=TStringList.Create;
lista.Add(FOrmatDateTime('DD/MM/YYYY HH:NN:SS',now)+' FormCloseQuery');
Lista.SaveToFile(froot+formatdatetime('YYMMDDHHNNSSZZZ',now)+'.log');
SincroClose();
lista.Add(FOrmatDateTime('DD/MM/YYYY HH:NN:SS',now)+' Done');
Lista.SaveToFile(froot+formatdatetime('YYMMDDHHNNSSZZZ',now)+'.log');
CanClose:=true;
finally
lista.Free;
end;
{$ENDIF}
end;
Только нормальное закрывающее приложение будет работать нормально, в рамках события FormCloseQuery, но когда Windows отключится, мое приложение закроется без сохранения каких-либо данных.
Ответы
Ответ 1
FormCloseQuery
работает, потому что он отображается через фреймворк. Ваше приложение не сохраняет данные, когда Windows завершает работу, потому что ваши обработчики сообщений никогда не вызываются. Обработка сообщений доступна только для приложений VCL, приложения fmx имеют другой механизм обмена сообщениями как документально.
Краткое описание здесь подразумевает, что можно получать уведомления от ОС в fmx framework. Однако я не знаю, включает ли это уведомления о завершении работы, и если можно установить возврат, так как в документации упоминается объект сообщения только для чтения.
Пока вы не узнаете, как работает механизм обмена сообщениями fmx, и если он соответствует требованиям, вы можете подклассировать окно формы обычными способами. В приведенном ниже примере используется SetWindowSubclass
.
...
protected
{$IFDEF MSWINDOWS}
procedure CreateHandle; override;
procedure DestroyHandle; override;
procedure WMQueryEndSession(var Msg: TWMQueryEndSession); message WM_QUERYENDSESSION;
procedure WMEndSession(var Msg: TWMEndSession); message WM_ENDSESSION;
{$ENDIF}
...
implementation
{$IFDEF MSWINDOWS}
uses
FMX.Platform.Win, Winapi.Commctrl;
function SubclassProc(Wnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM;
uIdSubclass: UINT_PTR; RefData: DWORD_PTR): LRESULT; stdcall;
var
Self: TfMain;
Message: TMessage;
begin
Result := DefSubclassProc(Wnd, Msg, wParam, lParam);
case Msg of
WM_QUERYENDSESSION, WM_ENDSESSION:
begin
Self := TfMain(RefData);
Message.Msg := Msg;
Message.WParam := wParam;
Message.LParam := lParam;
Message.Result := Result;
Self.Dispatch(Message);
Result := Message.Result;
end;
end;
end;
procedure TfMain.CreateHandle;
var
Wnd: HWND;
begin
inherited;
Wnd := WindowHandleToPlatform(Self.Handle).Wnd;
SetWindowSubclass(Wnd, SubclassProc, 1, DWORD_PTR(Self));
end;
procedure TfMain.DestroyHandle;
var
Wnd: HWND;
begin
Wnd := WindowHandleToPlatform(Self.Handle).Wnd;
RemoveWindowSubclass(Wnd, SubclassProc, 1);
inherited;
end;
procedure TfMain.WMQueryEndSession(var Msg: TWMQueryEndSession);
begin
// do not call inherited here, there no inherited handling
end;
procedure TfMain.WMEndSession(var Msg: TWMEndSession);
begin
// do not call inherited here, there no inherited handling
end;
var
ICC: TInitCommonControlsEx;
initialization
ICC.dwSize := SizeOf(ICC);
ICC.dwICC := ICC_STANDARD_CLASSES;
InitCommonControlsEx(ICC);
{$ENDIF}
Ответ 2
В этой области в Windows произошли некоторые изменения (относительно) последних выпусков, т.е. возврат к Windows XP. Кроме того, способ, которым окна Delphi управляются приложением, был изменен, чтобы лучше вести себя по отношению к другим изменениям ОС, и в FMX все изменилось.
WM_QUERYENDSESSION теперь отправляется только в окна верхнего уровня. Если ваше приложение является VCL-приложением и имеет MainFormOnTaskbar
set TRUE, ваша основная форма приложения - это окно верхнего уровня и должно получать сообщение. Если MainFormOnTaskbar
установлено FALSE или если ваша форма не является основной формой (несмотря на имя), то она не является окном верхнего уровня и не получит сообщение.
Если ваше приложение использует FMX, вам нужно будет копаться внутри FMX.Platform.Win
WindowService, чтобы точно определить, как определяется родительский процесс вашей основной формы. Основываясь на проверке источника FMX [XE4], кажется, что в этой области (относительно VCL) все пошло назад, и здесь есть некоторые уродливые кодовые запахи.
Проблемы, которые возникают в более мелких деталях в этой области, связаны с тем, что начиная с Vista, WM_QUERYENDSESSION больше не отправляется в приложения без видимых окон верхнего уровня. Даже если ваша основная форма - это окно верхнего уровня, если оно не отображается в точке, когда Windows завершает работу, это может быть причиной того, что вы не получаете сообщение.
Если проблема в том, что ваше окно не является окном верхнего уровня в вашем приложении, тогда здесь должно быть достаточно информации, чтобы вы могли хотя бы выяснить, почему.
В приложении VCL, чтобы ваша основная форма открывала окно панели задач, необходимо устранить проблему. Есть ли аналогичные средства для решения проблемы в приложении FMX, я не знаю.
Если у вас действительно действующее окно верхнего уровня, и проблема в том, что ваше окно верхнего уровня (иногда) не видно, вам нужно будет найти какой-то другой механизм, чтобы подключиться к процессу выключения, но должен знать, что любой поведение, основанное на других процессах, должно учитывать тот факт, что эти другие процессы сами закрываются и могут быть недоступны.
Конечно, все это очень специфично для уведомлений Windows. Если вы намерены поддерживать другие платформы с помощью своего приложения FMX, вам придется иметь дело с поведением по отключению по-разному там, предполагая, что FMX не предоставляет кросс-платформенное уведомление о выключениях (в противном случае вы использовали бы это, нет?).
(И если вы на самом деле нацелены только на Windows, почему на Земле вы используете FMX?)