Search code examples
delphimodel-view-controllerdelphi-xe3object-lifetime

How do I destroy an adapter before a view when the view is non modal?


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?


Solution

  • 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;