14
votes

I have a bunch of forms and I want to automate them so they would open and close by themselves.

I know how to get them to open (by having an OnActivate function), but I'm having trouble closing them.

So, for example, I have

procedure TProgressForm.FormActivate(Sender: TObject);
begin
  inherited;
  if FModItem.IsInQueue then
    begin
      RunBtnClick(Self);
      ModalResult := mrOK;
    end;    
end;

which runs a function. I want to close the window after the function has been run, which is what ModalResult should do.

(I've also tried adding the ModalResult line at the very end of the RunBtnClick procedure, but that didn't work either)

and I'm creating the form like this:

ProgForm := TProgressForm.Create(Self, FModItem);
Self.Visible := False;
try
 if ProgForm.ShowModal = mrOK then
  begin
    Left := ProgForm.Left;
    Top := ProgForm.Top;
  end;

I've been able to create buttons to close the form just by adding mrOK to the Modal Result in Object Inspector, but I can't seem to do it explicitly

Can anyone see why it's not working?

Thanks

4
I don't get it. You "get [a form] to open" by writing an OnActivate event handler?Andreas Rejbrand
@ Andreas I create a form and have an OnActivate handler so that the created form runs a procedure that opens another formKingKong

4 Answers

16
votes

The reason for not working is that the VCL actively sets ModalResult to 0 in TCustomForm.ShowModal áfter showing the form, but prior to starting checking changes to ModalResult. So in OnActivate and in OnShow, you are to early.

The solution is to delay the notification. This can be done by a PostMessage, as follows:

const
  UM_ACTIVATED = WM_USER + 1;

type
  TProgressForm = class(TForm)
    procedure FormActivate(Sender: TObject);
  private
    procedure UMActivated(var Message: TMessage); message UM_ACTIVATED;
  end;

...

procedure TProgressForm.FormActivate(Sender: TObject);
begin
  PostMessage(Handle, UM_ACTIVATED, 0, 0);
end;

procedure TProgressForm.UMActivated(var Message: TMessage);
begin
  { Your code here }
  ModalResult := mrOk;
end;

Source: NLDelphi

6
votes

I'd override ShowModal and do the tests you now do in OnActvate from over there. This has two big advantages:

  • Doesn't show the form at all if it doesn't need to be shown. Initiating the form shutdown from OnActivate causes the form to "flicker" on screen: It's shown and immediately taken down.
  • Doesn't rely on code that's not under your control. You no longer care about the order of operation in the ancestor ShowModal, because you only call it if the form needs to actually be shown.

Of course, using a GUI element (a form) this way is a bit of code smell because it basically uses a GUI without the need for user interaction. This can undoubtedly be refactored to use an intermediary function that returns mrOk and does what RunBtnClick() does without needing the GUI, and creates the Form only if needed. I guess it's a cost-benefit kind of situation.

Code:

TMyForm = class(TForm)
....
public
  function ShowModal:Integer;override;
end;

function TMyForm.ShowModal:Integer;
begin
  if FModItem.IsInQueue then
    begin
      RunBtnClick(Self);
      Result := mrOK;
    end
  else
    Result := inherited ShowModal;
end;
3
votes

The OnActivate event is triggered before the ModalResult is reset to mrNone in TCustomForm.ShowModal. This means that changing ModalResult in your OnActivate handler is ignored.

  function TCustomForm.ShowModal: Integer;
  Show;
  try
    SendMessage(Handle, CM_ACTIVATE, 0, 0);  << Your onActivate is called here
    ModalResult := 0; << ModalResult is reset
2
votes

Look at TCustomForm.ShowModal (inside forms.pas): ModalResult is not first checked until after the CM_ACTIVATE message is sent (said message triggers your OnActivate call); in fact, it's set to 0 immediately after your OnActivate call returns, so it's no wonder your assignment isn't working.

I don't want to mess too much with this (your code definitely fails the smell test), but you could try adding something like:

if ModalResult=0 then 
     SendMessage(Handle, CM_ACTIVATE, 0, 0);

to the top of your event handler.