【问题标题】:How do I destroy an adapter before a view when the view is non modal?当视图是非模态视图时,如何在视图之前销毁适配器?
【发布时间】:2014-02-16 10:44:24
【问题描述】:

我正在为我的所有应用程序模块使用某种(类似于 MVA)的模式。

视图是TForm后代:

TSomeView = class(TForm)
  ...
end;

数据在模型中管理:

TSomeModel = class
public
  property DataSet: TDataSet read ...;
end;

视图和模型通过适配器粘合在一起。

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;

虽然这看起来有点乏味,但它可以很好地分隔事物。

到目前为止,我一直使用模态形式,因此适配器实现如下所示:

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;

这里重要的是我不会在析构函数中断开事件处理程序和数据集,因为视图总是首先被销毁。

调用者使用这种模式创建一个应用模块:

procedure CallSome;
var
  Adapter: TSomeAdapter;
begin
  Adapter := TSomeAdapter.Create(...);
  try
    Adapter.Run;
  finally
    Adapter.Free;
  end;
end;

我正在尝试使其适应非模态形式。

调用者无法释放适配器,因为它不知道何时。所以调用者代码现在看起来像这样:

procedure CallSome;
var
  Adapter: TSomeAdapter;
begin
  Adapter := TSomeAdapter.Create(...);
  Adapter.Run;
  // Adapter is now a memory leak
end;

我不想更改销毁顺序,因为我依赖于不需要断开处理程序和数据集的事实。

使用非模态表单时如何保持销毁顺序(视图

【问题讨论】:

  • 在您的模态方法中,您只有一个视图 -> 一个适配器。现在您有多个适配器。在列表中组织多个元素
  • 不仅如此,您可能无法再直接在 callsome 中创建适配器,可能已经有一个正在运行。看起来会变得更加乏味...
  • 您可能需要考虑以下几点:a) 将TSomeAdapter 更改为更通用的类,该类将注入TSomeViewTSomeModel,因此它之前不需要知道它们。这意味着两者都需要从抽象类派生或实现接口。 b)考虑也注入它们已经创建,但如果构造函数并不总是相同的,这可能是一个问题。 c) 创建一个类,该类包含对创建的所有TSomeAdapter 的引用。 d) 使TSomeAdapter 完成后通知持有者类,以便释放它。
  • 我不担心阻塞多个实例。该应用程序有一个主模块,我可以使用它来保存所有适配器的列表。
  • @GuillemVicens 好主意,关于通知:也许我可以在主窗口中使用PostMessage

标签: delphi model-view-controller delphi-xe3 object-lifetime


【解决方案1】:

您必须更改适配器,以便适​​配器成为视图的所有者,并使视图在关闭时自毁。当视图被销毁时,适配器会收到通知。

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;

视图应在关闭时自行释放

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

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

现在,如果适配器结束运行,您将收到通知

【讨论】:

    【解决方案2】:

    将主窗口句柄传递给所有适配器:

    procedure CallSome;
    var
      Adapter: TSomeAdapter;
    begin
      Adapter := TSomeAdapter.Create(..., FMainView.Handle);
      FMainAdapter.Add(Adapter);
      Adapter.Run;
    end;
    

    附加OnClose并将CloseAction设置为caNone

    procedure TSomeAdapter.ViewClose(Sender: TObject; var CloseAction: TCloseAction);
    begin
      CloseAction := TCloseAction.caNone;
      PostMessage(FMainViewHandle, WM_FREE_ADATER, NativeUInt(@Self), 0);
    end;
    

    在主适配器中使用TObjectList 并处理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;
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-10-25
      • 2016-12-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-01-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多