Подтвердить что ты не робот

Как получить событие завершения 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 отключится, мое приложение закроется без сохранения каких-либо данных.

4b9b3361

Ответ 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?)