1
votes

I have a modal form that displays the progress of a lengthy operation. The operation is triggered when the Form's OnActivate Event is triggered.

procedure TMyForm.FormActivate(Sender:TObject);
begin
  Start;
end;

The form has a cancel button with the ModalResult property set to mrCancel and the OnClick handler sets a flag that causes the operation to end.

procedure TMyForm.CancelButtonClick(Sender: TObject);
begin
  FCancel := True;
end;

When I click the cancel button it stops the operation as expected but it fails to close the form. I suspect this is because the OnActivate handler is blocking the form from closing. A second click of the button does close the form. I've tried calling Close and sending a wm_close message but nothing seems to work. Does anyone have any suggestions to get the form to respond on the first click? Perhaps another event I can use instead of OnActivate?

I know moving the operation to a thread will be suggested. That's not a possibility at this point due to a large amount of poorly written legacy code.

1
Well you could try the hacky approach and simulate another click on the cancel button if not FCancel then ...Teun Pronk
@TeunPronk Why would one choose to do that? Wouldn't it be better to understand the issue and solve the problem properly?David Heffernan
What would hapen if you would set the modal result in your work code instead? This way you could even set different modal result for different scenarios (mrWorkDone when operation finishes without being interupted before compleetion, mrWorkInterupted when you prematurely interupt the operation ,etc).SilverWarior
@Silver the value would still get overwritten by the second line of code in my answerDavid Heffernan
@DavidHeffernan I'm aware of that. That is why I have written this under the OP post as I'm wondering how it would affect its original code as I belive this might be another way to solve the OP problem.SilverWarior

1 Answers

3
votes

In the following I am going to assume that Messages are probably processed by calls to Application.ProcessMessages inside the task.

Let's take a look at the code ShowModal that is pertinent:

SendMessage(Handle, CM_ACTIVATE, 0, 0);
ModalResult := 0;
repeat
  Application.HandleMessage;
  if Application.Terminated then ModalResult := mrCancel else
    if ModalResult <> 0 then CloseModal;
until ModalResult <> 0;

The SendMessage call results in the OnActivate event firing. In your code that then starts the task and does not return until the task is complete. By which point you've assigned to ModalResult. But wait, the next line in the excerpt above sets ModalResult back to 0 and so your setting is lost. And so the modal message loop is entered and you need to assign to ModalResult again to get the form to close.

The bottom line here is that you cannot perform the task before entering the the modal message loop. One solution is to put the long running task in a separate thread. If you cannot bring yourself to do that you can post a message to the form in the OnActivate event handler. Respond to the event by starting the task. By this point the modal message loop will be running and setting ModalResult will close the form.