delphi - pestaña - ¿Cómo obtener el evento de cierre de Windows en el proyecto Fmx como WM_QUERYENDSESSION y WM_ENDSESSION en un proyecto de VCL?
onbeforeunload (2)
Necesito interceptar el cierre de Windows y ejecutar alguna consulta DB, antes de que se cierre mi aplicación. Estoy usando Delphi XE10 en Windows 10 en un proyecto de FMX
Lo que probé es el siguiente código, pero no funciona
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;
Solo la aplicación de cierre normal funcionará correctamente en el evento FormCloseQuery, pero cuando Windows se cierre, Mi aplicación se cerrará sin guardar ningún dato
Se han producido una serie de cambios en esta área en Windows en versiones (relativamente) recientes, es decir, volviendo a Windows XP. Además, la forma en que las ventanas de Delphi son administradas por la aplicación han sido modificadas para comportarse mejor con respecto a otros cambios del sistema operativo y las cosas son diferentes nuevamente en FMX.
WM_QUERYENDSESSION ahora solo se envía a las ventanas de nivel superior. Si su aplicación es una aplicación VCL y tiene MainFormOnTaskbar
configurado como TRUE , su formulario principal de aplicación es una ventana de nivel superior y debería recibir el mensaje. Si MainFormOnTaskbar
se establece FALSE , o si su formulario no es el principal (a pesar del nombre), entonces no es una ventana de nivel superior y no recibirá el mensaje.
Si su aplicación usa FMX, tendrá que buscar dentro de FMX.Platform.Win
WindowService para determinar exactamente cómo se determina la crianza de su formulario principal. Según una inspección de la fuente [XE4] FMX, parece que las cosas han ido hacia atrás en esta área (en relación con el VCL) y aquí hay algunos olores desagradables.
Los problemas que causan los detalles más finos en esta área son que desde Vista en adelante, WM_QUERYENDSESSION ya no se envía a aplicaciones sin ventanas visibles de nivel superior. Incluso si su formulario principal es una ventana de nivel superior, si no está visible en el punto en que Windows se está cerrando, entonces podría ser por eso que no está recibiendo el mensaje.
Si el problema es que su ventana no es la ventana de nivel superior en su aplicación, entonces debería haber suficiente información aquí para que pueda al menos descubrir por qué.
En una aplicación de VCL, hacer que su Formulario principal sea la ventana de la barra de tareas debería resolver el problema. No sé si hay un medio similar para abordar el problema en una aplicación FMX.
Si tiene una ventana de nivel superior válida y el problema es que su ventana de nivel superior es (a veces) no visible, entonces deberá encontrar algún otro mecanismo para enganchar en el proceso de apagado, pero debe tener en cuenta que cualquier comportamiento que dependa en otros procesos debe tenerse en cuenta el hecho de que esos otros procesos pueden cerrarse y pueden no estar disponibles.
Por supuesto, todo esto es altamente específico para las notificaciones de apagado de Windows . Si tiene la intención de admitir otras plataformas con su aplicación FMX, tendrá que lidiar con el comportamiento de apagado de forma diferente allí, asumiendo que FMX no proporciona una solución de notificación de apagado multiplataforma (de lo contrario, estaría usando eso, ¿no?).
(Y si en realidad solo está apuntando a Windows, ¿por qué en la Tierra está usando FMX?)
FormCloseQuery
funciona porque está expuesto por el marco. Su aplicación no guarda ningún dato cuando Windows se cierra porque sus manejadores de mensajes nunca son llamados. El manejo de mensajes solo está disponible para las aplicaciones de VCL, las aplicaciones de fmx tienen un mecanismo diferente para la mensajería según lo documentado .
Breve explicación aquí implica que es posible recibir notificaciones del sistema operativo en el marco de fmx. Sin embargo, no sé si esto incluye las notificaciones de cierre y si es posible establecer su devolución, ya que la documentación menciona que el objeto de mensaje debe ser de solo lectura.
Hasta que descubra cómo funciona el mecanismo de mensajería de fmx y si cumple con los requisitos, puede crear una subclase de la ventana de su formulario por medios convencionales. El ejemplo siguiente usa 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''s no inherited handling
end;
procedure TfMain.WMEndSession(var Msg: TWMEndSession);
begin
// do not call inherited here, there''s no inherited handling
end;
var
ICC: TInitCommonControlsEx;
initialization
ICC.dwSize := SizeOf(ICC);
ICC.dwICC := ICC_STANDARD_CLASSES;
InitCommonControlsEx(ICC);
{$ENDIF}