FireMonkey does a lot of work to allow you to write one set of code that compiles and runs on different platforms.
Not only that, it also does a lot of work to make many of the coding patterns that some of you are used to from the VCL on Windows, work unchanged on other platforms.
That’s great, but in some cases, re-using old patterns is not desirable. ShowModal is a case in point.
If you’ve come from VCL, you’ve written code like this a million times:
procedure THeaderFooterForm.btnPickClick(Sender: TObject); var dlg: TForm1; begin dlg := TForm1.Create(nil); try if dlg.ShowModal = mrOK then // do something here ... finally dlg.Free; end; end;
The key things to note are that:
- the call to ShowModal is blocking. By the time the comparison to mrOK is done, the child form has been shown and closed.
- dlg.Free actually causes the form’s destructor to run and the memory be reclaimed.
All good. However, two things trip us up when we move this pattern to mobile:
- With ARC, a call to Free does not invoke the destructor. It decrements the object’s reference count. If it is greater than zero, the object won’t be destroyed. If you want to force the destructor to run, call DisposeOf instead.
- Android doesn’t really have the concept of modal forms. As a result, a call to ShowModal is not blocking. If we have code that we want to run after the form has closed, we can’t structure it like the code above.
So, what to do?
Fortunately, FireMonkey has an alternative pattern, one that let’s us solve both these issues. Further, it works across all the platforms that FireMonkey supports, so we can still have one set of source. We just have to learn a new pattern.
So here’s the same code above following the new pattern:
procedure TfrmParent.Button1Click(Sender: TObject);
var
dlg : TForm1;
begin
dlg := TForm1.Create(nil);
dlg.ShowModal(procedure(ModalResult : TModalResult)
begin
if ModalResult = mrOK then
// do something here
end);
end;
ShowModal has been overloaded to allow you to pass in an anonymous method that takes a ModalResult as a parameter. This anonymous method will run when the child form has been closed, giving you a chance to check the ModalResult and do whatever you need to.
So, that’s fixed half the problem, but where does my form get destroyed?
You might be tempted to call dlg.DisposeOf inside that anonymous method. Don’t. Well, not unless you want to generate a segfault. During that anonymous method, our child form is still running. If you look in the source, our anonymous method is invoked as part of the ModalResult property’s setter. After our anonymous method runs, the rest of this setter is going to continue, so destroying the object is probably a bad idea.
What do we do instead? Forms have an OnClose event, where you can specify an Action that should take place after the form is closed. Some code like this will allow the form to finish what it is doing, and then destroy itself.
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := TCloseAction.caFree;
end;
I especially like that this method of freeing a form has been around in the VCL forever (or, at least as long as I can remember). I used to use it to free MDI Child forms back in the nineties. What I don’t like so much is that I’d forgotten all about it. Thanks to Takahashi-san for reminding me.
2 Comments
A good article which I found very useful. Although I’m working in Delphi for years, I didn’t know the advantages of using
Action := TCloseAction.caFree;
in FormCLose.
Thank you!
Glad to have taught you something after all these years, Wolfgang 🙂