I am using a certain (MVA like) pattern for all my application modules.
Views are TForm
descendents:
TSomeView = class(TForm)
...
end;
Data is managed in models:
TSomeModel = class
public
property DataSet: TDataSet read ...;
end;
View and model are glued together by an adapter.
uses
Some.Model, Some.View;
type
TSomeAdapter = class
private
FView : TSomeView;
FModel : TSomeModel;
procedure ClickHandler(Sender: TObject);
public
constructor Create(AOwner: TComponent);
destructor Destroy; override;
procedure Run;
end;
While this might seem a bit tedious, it works well to seperate things.
Up until now I have always used modal forms, so that the adapter implementation looks like this:
constructor TSomeAdapter.Create(AOwner: TComponent);
begin
inherited Create;
FModel := TSomeModel.Create,
FView := TSomeView.Create(AOwner);
FView.DataSource.DataSet := FModel.DataSet;
FView.SomeButton.OnClick := ClickHandler;
end;
procedure TSomeAdapter.Run;
begin
FView.ShowModal;
end;
destructor TSomeAdapter.Destroy;
begin
FView.Free;
FModel.Free;
inherited;
end;
The important thing here is that I do not disconnect event handlers and datasets in the destructor, because the view is always destroyed first.
The caller creates an application module using this pattern:
procedure CallSome;
var
Adapter: TSomeAdapter;
begin
Adapter := TSomeAdapter.Create(...);
try
Adapter.Run;
finally
Adapter.Free;
end;
end;
I am trying to adapt this to non-modal forms.
The caller can not free the adapter, because it does not know when. So the caller code now look like this:
procedure CallSome;
var
Adapter: TSomeAdapter;
begin
Adapter := TSomeAdapter.Create(...);
Adapter.Run;
// Adapter is now a memory leak
end;
I do not want to change the destruction order, because I rely on the fact that I do not need to disconnect handlers and datasets.
How can I keep the destruction order (View < Model < Adapter) when using a non-modal form?
Pass a main window handle to all adapters:
procedure CallSome;
var
Adapter: TSomeAdapter;
begin
Adapter := TSomeAdapter.Create(..., FMainView.Handle);
FMainAdapter.Add(Adapter);
Adapter.Run;
end;
Attach OnClose
and set CloseAction
to caNone
:
procedure TSomeAdapter.ViewClose(Sender: TObject; var CloseAction: TCloseAction);
begin
CloseAction := TCloseAction.caNone;
PostMessage(FMainViewHandle, WM_FREE_ADATER, NativeUInt(@Self), 0);
end;
Use a TObjectList
in the main adapter and handle WM_FREE_ADAPTER:
constructor TMainAdapter.Create(...);
begin
inherited;
FAdapters := TObjectList.Create;
end;
procedure TMainAdapter.WMFreeAdapter(var Msg: TMessage);
begin
FAdapters.Remove(PAdapter(Msg.WParam)^);
end;
destructor TMainAdapter.Destroy;
begin
FAdapters.Free;
inherited;
end;