R – Prevent Form Deactivate in Delphi 6

delphievent handlingformsmessage

We have a Delphi 6 application that uses a non modal form with in-grid editing. Within the FormClose event we check that the entries are square and prevent closure if they're not.

However, if the user clicks on the main form behind then the original form disappears behind (as you'd expect) but this allows the user to move to a new record on the main screen, without their changes in the grid having been validated.

I've tried the FormDeactivate event, which does fire but doesn't seem to have any mechanism to prevent deactivation (unlike the FormClose events Action parameter).
I tried the OnExit from the grid, but it doesn't fire on deactivation.
I tried trapping the WM_ACTIVATE message and setting Msg.Result = 1 but this has no effect (possibly because another WM_ACTIVATE message is being sent to the main form?).

So, I'm looking for ideas on how to (conditionally) prevent the deactivation of a form when the user clicks on another form.
(PS I don't want to change the form style to fsStayOnTop)

Thanks

Best Answer

A classic rule in Windows is that you can't change the focus during a focus-changing event. The OnDeactivate event occurs during a focus-changing event. Your form is being told that it is being deactivated — the OS is not asking permission — and at the same time, the other form is being told that it is being activated. Neither window has any say in the matter, and attempting to change the focus while these events are going on will only get all the windows confused. Symptoms include having two windows painting themselves as though they have focus, and having keyboard messages go nowhere despite the input cursor blinking. MSDN is even more dire, although I've never witnessed anything that bad:

While processing this message [WM_KILLFOCUS], do not make any function calls that display or activate a window. This causes the thread to yield control and can cause the application to stop responding to messages. For more information, see Message Deadlocks.

Since you can't deny a focus change after it's already started, the thing to do is to delay handling of the event until after things have settled down. When your editing form gets deactivated and the data on it isn't valid yet, post the form a message. Posting puts the message on the end of the message queue, so it won't get handled until all previous messages — the focus-changing notifications in particular — have already been handled. When the message arrives, indicate that data is invalid and set focus back to the editing form:

const
  efm_InvalidData = wm_User + 1;

type
  TEditForm = class(TForm)
  ...
  private
    procedure EFMInvalidData(var Msg: TMessage); message efm_InvalidData;
  end;

procedure TEditForm.FormDeactivate(Sender: TObject);
begin
  if DataNotValid then
    PostMessage(Handle, efm_InvalidData, 0, 0);
end;

procedure TEditForm.EFMInvalidData(var Msg: TMessage);
begin
  Self.SetFocus;
  ShowMessage('Invalid data');
end;

I should point out that this answer doesn't technically answer your question since it does nothing to prevent form deactivation, but you rejected my other answer that really does prevent deactivation.

Related Topic