Jump to content
Ian Branch

Close App on PC Shutdown??

Recommended Posts

Hi Team,

D11.2.  Apps residing on a Win 2012 Server, running on the User's PC.

Most of the Users are using Win 7 but several are on Win 10.

My Apps write to a log file when they open and when they close.  Auditable.

I have been trying to find out how/why some 'sessions' stay open in the Log overnight.

The Apps have an Idle Timeout that closes them after x minutes of no mouse or keyboard activity in the App.

I have discovered that some Users are not closing the App at the end of the day and are simply shutting down their PCs, while the App(s) are still open.  😞

Is there some way to detect that the PC has been set to shutdown and stall it while the App(s) close gracefully?

 

 

Regards & TIA,

Ian

Share this post


Link to post

Windows broadcasts power event messages before close down, WM_POWERBROADCAST or WM_POWER, I have a component for checking them. 

 

In theory you can suspend close down, but this can be ignored by Windows. 

 

If someone turns the PC power off, no events will occur, so if you really need to know it's best to write the date and time or something to a file every few seconds and on normal close down, and check it on starting up, so you know there was a clean close down and for how long.  

 

Angus

 

Share this post


Link to post

OT: I rather think that they don't turn it off, but put it to sleep or hibernate. I also use hibernation. I am writing this just to make you aware.

  • Like 2

Share this post


Link to post

Hi Team,

I am advised, although I take it with a grain of salt, that they shut down the PC rather than just turn it off.

Angus, what is the component you use please?

 

Ian

Share this post


Link to post
8 hours ago, Ian Branch said:

Hi Team,

D11.2.  Apps residing on a Win 2012 Server, running on the User's PC.

Most of the Users are using Win 7 but several are on Win 10.

My Apps write to a log file when they open and when they close.  Auditable.

I have been trying to find out how/why some 'sessions' stay open in the Log overnight.

The Apps have an Idle Timeout that closes them after x minutes of no mouse or keyboard activity in the App.

I have discovered that some Users are not closing the App at the end of the day and are simply shutting down their PCs, while the App(s) are still open.  😞

Is there some way to detect that the PC has been set to shutdown and stall it while the App(s) close gracefully?

 

 

Regards & TIA,

Ian

On normal shutdown running apps get a WM_QUERYENDSESSION message, followed eventually by WM_ENDSESSION. The VCL handles WM_QUERYENDSESSION by firing the main form's OnCloseQuery event but OnClose or OnDestroy may not fire on system shutdown. So OnCloseQuery is the best place for detecting app closing.

Share this post


Link to post

Magenta Hardware Events Component is part of https://www.magsys.co.uk/delphi/maghardware.asp .  It warns if a PC is power sispending or restoring, or closing down, nothing complicated, this is just simple windows messages. 

 

As Peter says, OnCloseQuery should fire on normal forms, but belt and braces is often better is you really need a clean close down. And no events fire on forced termination.

 

Angus

 

Share this post


Link to post

Thanks Guys,

Angus - I will have a look at the component.

Peter - In FormCloseQuery, how can I tell if it was triggered by WM_QUERYENDSESSION ?

 

Regards,

Ian

Share this post


Link to post

Nowadays power switches on PCs are no longer switches, they are buttons that send the users desire to power off (or on) to the ACPI. Only if the button is held longer than 5(?) seconds, it will really cut off the power.

And @Ian Branch I suggest consulting your thread from two years ago:

😉

 

Share this post


Link to post

Hi Sherlock,

Yes I did go back to that and I am just as confused now as I was then.

I have the closing actions on the OnCloseQuery event.

Under normal circumstances it works fine, but when the User shuts down their PC while the App is running/open it doesn't.

The following is my OnCloseQuery..

procedure TMainForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
var
  TS: TTimeSpan;
  StartDateTime, FinishDateTime: TDateTime;
  lUsersLogWritten: Boolean;
begin
  //
    if GetSystemMetrics (SM_SHUTTINGDOWN) > 0 then iTerminateAction := 3;  // app is being closed by a system shutdown
    //
{$IF  Defined(ELogging) or Defined(Codesite)}
    LogMessage('iTerminateAction = '+IntToStr(iTerminateAction));
{$ENDIF}
    //
  lUsersLogWritten := False;
  //
  LTI1.ShowBalloonHint('Closing Workflow Database...', 'Any Open Edits and/or Inserts are being cancelled and the Database closed.');
  //
  if dmC.DBC1.Connected then
  begin
    //
{$IF  Defined(ELogging) or Defined(Codesite)}
    LogMessage('DBC1 is connected att.');
{$ENDIF}
    //
    CancelEditsInsertsInDatamodules;
    CancelEditsInsertsInOpenForms;
  end;
  //
  CloseOpenForms;
  //
  dmC.DBC1.CloseDataSets;
  //
  dmC.UsersLog.IndexName := 'SessionKey';
  dmC.UsersLog.Open;
  //
  if dmC.UsersLog.FindKey([sSessionKey]) then
  begin
    dmC.UsersLog.Edit;
    //
    case iTerminateAction of
      0: dmC.UsersLog.FieldByName('Action').AsString := 'Logged Out';
      1: dmC.UsersLog.FieldByName('Action').AsString := 'Timed Out';
      2: dmC.UsersLog.FieldByName('Action').AsString := 'Browser Closed';
      3: dmC.UsersLog.FieldByName('Action').AsString := 'System Shutdown';
    end;
    //
    dmC.UsersLog.FieldByName('FinishDateTime').AsDateTime := now;
    StartDateTime := dmC.UsersLog.FieldByName('StartDateTime').AsDateTime;
    FinishDateTime := dmC.UsersLog.FieldByName('FinishDateTime').AsDateTime;
    TS := TTimeSpan.Subtract(FinishDateTime, StartDateTime);
    dmC.UsersLog.FieldByName('Duration').AsString := Format('%.3d:%.2d:%.2d:%.2d', [TS.Days, TS.Hours, TS.Minutes, TS.Seconds]);
    dmC.UsersLog.Post;
    //
    lUsersLogWritten := True;
    //
  end;
  //
  dmC.UsersLog.Close;
  //
  DBWReg.WriteString('Session Key', 'DBiWorkflow', '');
  //
  dmC.DBC1.Close;
  dmC.DBS1.Close;
  dmC.DBE1.Close;
  //
  if iTerminateAction = 0 then
  begin
    //
    if not lUsersLogWritten then
      TaskMessageDlg('Workflow Closing error!', 'Note:- The Users Log was NOT updated.  Workflow will now close..', mtError, [mbOK], 0);
    //
  end;
  //
end;

It doesn't work on my Win 11 PC.

Happy to have the error of my ways pointed out.

 

Regards,

Ian

 

 

Share this post


Link to post

Well, OnCloseQuery is simply not enough. You need to answer the OSes "Will shut down now" Message with a "Wait for me" and clear that "Wait for me" after you are done all the while keeping your main thread responsive to more OS messages that will come because Windows may not just take your word that you'll say "carry on" once you are ready, it will check on you...

So what it boils down to is the following (if your application does not take longer than 30 seconds to do what needs to be done your golden, otherwise you'll need to be threading):

type
  TForm1 = class(TForm)
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
  private
    procedure WMQueryEndSession(var Msg: TMessage); message WM_QUERYENDSESSION;
    procedure WMEndsession(var Msg: TMessage); message WM_ENDSESSION;
  public
    { Public-Deklarationen }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

var
  _ShutdownBlockReasonCreate: function(WindowHandle: HWND; Reason: LPCWSTR): UINT; stdcall;
  _ShutdownBlockReasonDestroy: procedure(WindowHandle: HWND); stdcall;

function ShutdownBlockReasonCreate(WindowHandle: HWND; Reason: LPCWSTR): UINT;
var
  UserLib: THandle;
begin
  if @_ShutdownBlockReasonCreate = nil then
  begin
    UserLib := GetModuleHandle(Windows.User32);
    if UserLib <> 0 then
      @_ShutdownBlockReasonCreate := GetProcAddress(UserLib, 'ShutdownBlockReasonCreate');
  end;
  if @_ShutdownBlockReasonCreate <> nil then
    Result := _ShutdownBlockReasonCreate(WindowHandle, Reason)
  else
    Result := 1;
end;

procedure ShutdownBlockReasonDestroy(WindowHandle: HWND);
var
  UserLib: THandle;
begin
  if @_ShutdownBlockReasonDestroy = nil then
  begin
    UserLib := GetModuleHandle(Windows.User32);
    if UserLib <> 0 then
      @_ShutdownBlockReasonDestroy := GetProcAddress(UserLib, 'ShutdownBlockReasonDestroy');
  end;
  if @_ShutdownBlockReasonDestroy <> nil then
    _ShutdownBlockReasonDestroy(WindowHandle);
end;

{ TForm1 }


procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  // Whatever needs to be done... 
end;

procedure TForm1.WMEndsession(var Msg: TMessage);
begin
  // Windows now informs you it will end your session
  // Free up your BlockReason
  ShutdownBlockReasonDestroy(Handle);
end;

procedure TForm1.WMQueryEndSession(var Msg: TMessage);
const
  ENDSESSION_CLOSEAPP = $00000001; // lParam - WM_QUERYENDSESSION
  ENDSESSION_CRITICAL = $40000000;
  ENDSESSION_LOGOFF = $80000000;
var
  CanClose: Boolean;
begin
  // Windows asks if it may end your session, your response is
  // "No, and here's what you can tell the user why"
  if ((Message.Unused and ENDSESSION_CRITICAL) = 0) then
  begin
    if ShutdownBlockReasonCreate(Handle, 'Saving application data...') = 0 then
    begin
      SaveLog(SysErrorMessage(GetLastError));  //Assuming you have some kind of logging
    end;
    // Now do your thing i.e. call FormCloseQuery
    FormCloseQuery(Self, CanClose);
    If CanClose then
      Close; // Woohoo
  end
  else 
  begin
    // Forcefully shutdown...no time to wait
  end;
end;

I have something like this in an application and it has worked for me since Win7. Not tested on Win11 yet though.

Share this post


Link to post
const
DBcommandstrs = ('Workstation shut down', 'Auto Close');
//  i :=   IndexText('Workstation shut down', DBcommandstrs);


var  enlightenedCanClose: Boolean = False;

// called by timer 
procedure autocloseWorkStationSession(Sender: Tobject);
begin
  if Time > 17/24 then
  begin
    CloseDB('Auto Close');
	//Close;
  end;
end;



procedure CloseDB(const ditto: string);

var
  TS: TTimeSpan;
  StartDateTime, FinishDateTime: TDateTime;
  lUsersLogWritten: Boolean;

  if dmC.DBC1.Connected then
  begin
  
{$IF  Defined(ELogging) or Defined(Codesite)}
    LogMessage(IndexText[0]);
    LogMessage('DBC1 is connected att.');
{$ENDIF}
    //
	////' check savepoints or cache status?
    CancelEditsInsertsInDatamodules;
    CancelEditsInsertsInOpenForms;
    //end;
    //
    ///CloseOpenForms; ///Pat says Boo!
    //
    dmC.DBC1.CloseDataSets;
    //
    dmC.UsersLog.IndexName := 'SessionKey';
    dmC.UsersLog.Open;
    //
    if dmC.UsersLog.FindKey([sSessionKey]) then
    begin
      dmC.UsersLog.Edit;
      //
      //case iTerminateAction of
      dmC.UsersLog.FieldByName('Action').AsString := ditto;
      dmC.UsersLog.FieldByName('FinishDateTime').AsDateTime := now;
      StartDateTime := dmC.UsersLog.FieldByName('StartDateTime').AsDateTime;
      FinishDateTime := dmC.UsersLog.FieldByName('FinishDateTime').AsDateTime;
      TS := TTimeSpan.Subtract(FinishDateTime, StartDateTime);
      dmC.UsersLog.FieldByName('Duration').AsString := Format('%.3d:%.2d:%.2d:%.2d', [TS.Days, TS.Hours, TS.Minutes, TS.Seconds]);
      dmC.UsersLog.Post;
      //
      lUsersLogWritten := True;
      //
    end;
  //
  dmC.UsersLog.Close;
  //
  DBWReg.WriteString('Session Key', 'DBiWorkflow', '');
  //
  dmC.DBC1.Close;
  dmC.DBS1.Close;
  dmC.DBE1.Close;
  //
  if iTerminateAction = 0 then
  begin
    //
    if not lUsersLogWritten then
      TaskMessageDlg('Workflow Closing error!', 'Note:- The Users Log was NOT updated.  Workflow will now close..', mtError, [mbOK], 0);
    //
  end;
  enlightenedCanClose := True;
  close;
end;


procedure TMainForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin 
  //
  CanClose := enlightenedCanClose;
  SendMessage(Handle, EM_SETMODIFY, WPARAM(True), 0); //or have a MS product open that needs saved 
  // 
  if not CanClose then 
  begin
    if (GetSystemMetrics (SM_SHUTTINGDOWN) > 0)
       and not dmC.DBC1.Connected	then 
	      closeDB('Workstation shut down');  // app is being closed by workstation shutdown
    
	enlightenedCanClose := True;
    Close;
  end;
end;

That should work in Win11. (In fair weather 🙂 )   

Pat

Share this post


Link to post

Gentlemen,

Thank you for your patience and contributions.  Very much appreciated and educational.

As it was then and still is now, this is not an area I have any expertise in.  Does it show? 😉

I will work with your suggestions.

 

Regards & Tks again,

Ian

Share this post


Link to post

Hi Guys,

I am going to try each and see which is my best option.

 

@Sherlock Where is this from - "Message.Unused", Delphi tells me "Message" is an undeclared identifier.

 

Regards,

Ian

Share this post


Link to post

@Pat Foley I have implemented this att..

procedure TMainForm.CloseDB(const ditto: string);
var
  TS: TTimeSpan;
  StartDateTime, FinishDateTime: TDateTime;
  lUsersLogWritten: Boolean;
begin
  //
{$IF  Defined(ELogging) or Defined(Codesite)}
  LogMessage('Enter CloseDB.');
{$ENDIF}
  //
  if dmC.DBC1.Connected then
  begin

{$IF  Defined(ELogging) or Defined(Codesite)}
    //LogMessage(IndexText[0]);
    LogMessage('DBC1 is connected att.');
{$ENDIF}
    //
 ////' check savepoints or cache status?
    CancelEditsInsertsInDatamodules;
    CancelEditsInsertsInOpenForms;
    //end;
    //
    ///CloseOpenForms; ///Pat says Boo!
    //
    dmC.DBC1.CloseDataSets;
    //
    dmC.UsersLog.IndexName := 'SessionKey';
    dmC.UsersLog.Open;
    //
    if dmC.UsersLog.FindKey([sSessionKey]) then
    begin
      dmC.UsersLog.Edit;
      //
      //case iTerminateAction of
      dmC.UsersLog.FieldByName('Action').AsString := ditto;
      dmC.UsersLog.FieldByName('FinishDateTime').AsDateTime := now;
      StartDateTime := dmC.UsersLog.FieldByName('StartDateTime').AsDateTime;
      FinishDateTime := dmC.UsersLog.FieldByName('FinishDateTime').AsDateTime;
      TS := TTimeSpan.Subtract(FinishDateTime, StartDateTime);
      dmC.UsersLog.FieldByName('Duration').AsString := Format('%.3d:%.2d:%.2d:%.2d', [TS.Days, TS.Hours, TS.Minutes, TS.Seconds]);
      dmC.UsersLog.Post;
      //
      lUsersLogWritten := True;
      //
    end;
    //
    dmC.UsersLog.Close;
    //
    DBWReg.WriteString('Session Key', 'DBiWorkflow', '');
    //
    dmC.DBC1.Close;
    dmC.DBS1.Close;
    dmC.DBE1.Close;
    //
    if iTerminateAction = 0 then
    begin
      //
      if not lUsersLogWritten then
        TaskMessageDlg('Workflow Closing error!', 'Note:- The Users Log was NOT updated.  Workflow will now close..', mtError, [mbOK], 0);
      //
    end;
    enlightenedCanClose := True;
    close;
  end;
end;

procedure TMainForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  //
{$IF  Defined(ELogging) or Defined(Codesite)}
  LogMessage('Enter FormCloseQuery.');
{$ENDIF}
  //
  CanClose := enlightenedCanClose;
  //
{$IF  Defined(ELogging) or Defined(Codesite)}
  if CanClose then
    LogMessage('Can Close.')
  else
    LogMessage('Can Not Close.');
{$ENDIF}
  //
  SendMessage(Handle, EM_SETMODIFY, WPARAM(True), 0); //or have a MS product open that needs saved
  //
  if not CanClose then
  begin
    if (GetSystemMetrics(SM_SHUTTINGDOWN) > 0)
      and not dmC.DBC1.Connected then
      closeDB('Workstation shut down'); // app is being closed by workstation shutdown
    //
    enlightenedCanClose := True;
    Close;
  end;
end;

From Codesite logging, neither CloseDB or FormCloseQuery are entered when I shut down the PC with the App running. 😞

May be a Win 11 thing.

 

Ian

Share this post


Link to post

Sorry about that I had a showmessage('') in my test letting the code call itself.     Plus split out the if tests to reflect the condition and the !.   

procedure TMainForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin 
  //
  CanClose := enlightenedCanClose;
  SendMessage(Handle, EM_SETMODIFY, WPARAM(True), 0); //or have a MS product open that needs saved 
  // 
  if not CanClose then 
  begin
    
	if (GetSystemMetrics (SM_SHUTTINGDOWN) > 0) and 
	{not} dmC.DBC1.Connected
       	then 
	      closeDB('Workstation shut down');  // app is being closed by workstation shutdown
    
	if dmC.DBC1.Connected then closeDB('DB still Open?');   
	 
	
	enlightenedCanClose := True;
	
    //Close;
  end;
end;

 

Share this post


Link to post

Hi Pat,

I replaced FormCloseQuery per above.

I found an end; in the wrong place but commented out that section att anyway, and added some additional logging to CloseDB.

procedure TMainForm.CloseDB(const ditto: string);
var
  TS: TTimeSpan;
  StartDateTime, FinishDateTime: TDateTime;
  lUsersLogWritten: Boolean;
begin
  //
{$IF  Defined(ELogging) or Defined(Codesite)}
  LogMessage('Enter CloseDB.');
{$ENDIF}
  //
//  if dmC.DBC1.Connected then
//  begin
//   //
//{$IF  Defined(ELogging) or Defined(Codesite)}
//    LogMessage('DBC1 is connected att.');
//{$ENDIF}
//    //
// ////' check savepoints or cache status?
//    CancelEditsInsertsInDatamodules;
//    CancelEditsInsertsInOpenForms;
//    //end;
//    //
//    ///CloseOpenForms; ///Pat says Boo!
//    //
//    dmC.DBC1.CloseDataSets;
//    //
//    end;
   //
{$IF  Defined(ELogging) or Defined(Codesite)}
    LogMessage('dmC.UsersLog.IndexName := SessionKey');
{$ENDIF}
    //
    dmC.UsersLog.IndexName := 'SessionKey';
    dmC.UsersLog.Open;
   //
{$IF  Defined(ELogging) or Defined(Codesite)}
    LogMessage('if dmC.UsersLog.FindKey([sSessionKey]) then');
{$ENDIF}
    //
    if dmC.UsersLog.FindKey([sSessionKey]) then
    begin
   //
{$IF  Defined(ELogging) or Defined(Codesite)}
    LogMessage('dmC.UsersLog.Edit;');
{$ENDIF}
    //
      dmC.UsersLog.Edit;
      //
      //case iTerminateAction of
      dmC.UsersLog.FieldByName('Action').AsString := ditto;
      dmC.UsersLog.FieldByName('FinishDateTime').AsDateTime := now;
      StartDateTime := dmC.UsersLog.FieldByName('StartDateTime').AsDateTime;
      FinishDateTime := dmC.UsersLog.FieldByName('FinishDateTime').AsDateTime;
      TS := TTimeSpan.Subtract(FinishDateTime, StartDateTime);
      dmC.UsersLog.FieldByName('Duration').AsString := Format('%.3d:%.2d:%.2d:%.2d', [TS.Days, TS.Hours, TS.Minutes, TS.Seconds]);
      dmC.UsersLog.Post;
      //
      lUsersLogWritten := True;
      //
    end;
    //
    dmC.UsersLog.Close;
    //
    DBWReg.WriteString('Session Key', 'DBiWorkflow', '');
    //
    dmC.DBC1.Close;
    dmC.DBS1.Close;
    dmC.DBE1.Close;
    //
    if iTerminateAction = 0 then
    begin
      //
      if not lUsersLogWritten then
        TaskMessageDlg('Workflow Closing error!', 'Note:- The Users Log was NOT updated.  Workflow will now close..', mtError, [mbOK], 0);
      //
    end;
    //
    enlightenedCanClose := True;
    //
    close;
    //
//  end;
end;

It is only logging/showing "Enter CloseDB", no further. 

I am going to turn off the logging and see if that is affecting it in some way.

Will advise.

Share this post


Link to post

Hi Pat,

Well I'm surprised!  It seems the logging, Codesite, was influencing/affecting it and preventing the flow of the program continuing... 😞

Some more experimenting to happen.

 

Regards,

Ian

Share this post


Link to post

Hi Angus, Pat,

I have a working solution the minimizes the amount of changes I have to make to other Apps.

I made some changes to Pat's code to facilitate this.

procedure TMainForm.CloseDB;
var
  TS: TTimeSpan;
  StartDateTime, FinishDateTime: TDateTime;
  lUsersLogWritten: Boolean;
  sAction: string;
begin
  //
  if dmC.DBC1.Connected then
  begin
    //
    // ' check savepoints or cache status?
    CancelEditsInsertsInDatamodules;
    CancelEditsInsertsInOpenForms;
    //
    CloseOpenForms; // Pat says Boo!
    //
    dmC.DBC1.CloseDataSets;
    //
  end;
  //
  case iTerminateAction of
  0: sAction := 'Logged Out';
  1: sAction := 'Timed Out';
  2: sAction := 'Browser Closed';
  3: sAction := 'PC Shut Down';
  end;
  //
  dmC.UsersLog.IndexName := 'SessionKey';
  dmC.UsersLog.Open;
  //
  if dmC.UsersLog.FindKey([sSessionKey]) then
  begin
    //
    dmC.UsersLog.Edit;
    //
    dmC.UsersLog.FieldByName('Action').AsString := sAction;
    dmC.UsersLog.FieldByName('FinishDateTime').AsDateTime := now;
    StartDateTime := dmC.UsersLog.FieldByName('StartDateTime').AsDateTime;
    FinishDateTime := dmC.UsersLog.FieldByName('FinishDateTime').AsDateTime;
    TS := TTimeSpan.Subtract(FinishDateTime, StartDateTime);
    dmC.UsersLog.FieldByName('Duration').AsString := Format('%.3d:%.2d:%.2d:%.2d', [TS.Days, TS.Hours, TS.Minutes, TS.Seconds]);
    dmC.UsersLog.Post;
    //
    lUsersLogWritten := True;
    //
  end;
  //
  dmC.UsersLog.Close;
  //
  DBWReg.WriteString('Session Key', 'DBiWorkflow', '');
  //
  dmC.DBC1.Close;
  dmC.DBS1.Close;
  dmC.DBE1.Close;
  //
  if iTerminateAction = 0 then
  begin
    //
    if not lUsersLogWritten then
      TaskMessageDlg('Workflow Closing error!', 'Note:- The Users Log was NOT updated.  Workflow will now close..', mtError, [mbOK], 0);
    //
  end;
  //
  enlightenedCanClose := True;
  //
  Close;
  //
end;

procedure TMainForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  //
  CanClose := enlightenedCanClose;
  //
  SendMessage(Handle, EM_SETMODIFY, WPARAM(True), 0); // or have a MS product open that needs saved
  //
  if not CanClose then
  begin
    //
    if (GetSystemMetrics(SM_SHUTTINGDOWN) > 0) then
    begin
      //
      iTerminateAction := 3;
      CloseDB; // app is being closed by workstation shutdown
      //
    end;
    //
    enlightenedCanClose := True;
    //
  end;
  //
end;

My thanks to both of you for your input/education.

 

Regards,

Ian

Share this post


Link to post
12 hours ago, Ian Branch said:

Hi Guys,

I am going to try each and see which is my best option.

 

@Sherlock Where is this from - "Message.Unused", Delphi tells me "Message" is an undeclared identifier.

 

Regards,

Ian

Sorry, turns you do have to be careful when you copy & paste, edit and post code all willy-nilly. That "Message" is just the parameter of the message handler so in this case the abbreviated "msg".

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×