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?

有帮助吗?

解决方案 2

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;

其他提示

You have to change your adapter, so that the adapter is the owner of the view and make the view self destruct on close. The adapter gets notified, when the view will be destroyed.

type
  TSomeAdapter = class( TComponent )
  private
    FView : TSomeView;
    FModel : TSomeModel;
    FOnEndsRunning : TNotifyEvent;
    function GetModel : TSomeModel;
    function GetView : TSomeView;
    function GetIsRunning : Boolean;
  protected
    procedure Notification( AComponent : TComponent; Operation : TOperation ); override;
    property View : TSomeView read GetView;
    property Model : TSomeModel read GetModel;
    procedure ClickHandler( Sender : TObject );
  public
    destructor Destroy; override;
    procedure Run;
    property IsRunning : Boolean read GetIsRunning;
    property OnEndsRunning : TNotifyEvent read FOnEndsRunning write FOnEndsRunning;
  end;

{ TSomeAdapter }

destructor TSomeAdapter.Destroy;
begin
  FModel.Free;
  inherited;
end;

procedure TSomeAdapter.ClickHandler( Sender : TObject );
begin
  // DoSomething
end;

function TSomeAdapter.GetIsRunning : Boolean;
begin
  Result := Assigned( FView );
end;

function TSomeAdapter.GetModel : TSomeModel;
begin
  // lazy initialization of model
  if not Assigned( FModel ) then
    FModel := TSomeModel.Create;
  Result := FModel;
end;

function TSomeAdapter.GetView : TSomeView;
begin
  // lazy initialization of view
  if not Assigned( FView ) then
  begin
    FView := TSomeView.Create( Self ); // Owner is this Adapter
    FView.DataSource.DataSet := Model.DataSet;
    FView.SomeButton.OnClick := ClickHandler;
  end;
  Result := FView;
end;

procedure TSomeAdapter.Notification( AComponent : TComponent; Operation : TOperation );
begin
  inherited;
  case Operation of
    opInsert :
      ;
    opRemove :
      if AComponent = FView then
      begin
        // forget the view reference
        FView := nil;
        // destroy the model (optional)
        FreeAndNil( FModel );
        // notify
        if Assigned( OnEndsRunning ) then
          OnEndsRunning( Self );
      end;
  end;

end;

procedure TSomeAdapter.Run;
begin
  View.Show;
end;

The view should free itself on closing

TSomeView = class( TForm )
  procedure FormClose(Sender: TObject; var Action: TCloseAction);
end;

procedure TSomeView.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Action := caFree;
end;

Now you will be notified if the adapter ends running

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top