【问题标题】:How can I change the owner of a DataModule after creation?创建后如何更改 DataModule 的所有者?
【发布时间】:2013-12-12 21:08:08
【问题描述】:

我正在尝试将 DataModule 传递给表单构造函数中的表单。我还希望表单成为 DataModule 的“所有者”,以便表单在关闭时破坏 DataModule。这产生了两个对象在其构造函数中相互需要的问题。

我试图在创建后设置 DataModule 的所有者,但这是一个只读属性。

我的第二个表单如下所示:

type
  TSecondPopup = class(TForm)
  private
    FMyData: TMyData;
  public
    constructor Create(MyData: TMyData); reintroduce;
  end;

var
  SecondPopup: TSecondPopup;

implementation

{$R *.dfm}


constructor TSecondPopup.Create(MyData: TMyData);
begin
  FMyData := MyData;

  inherited Create(nil);
end;

我的数据模块中没有特殊代码。

在我的主窗体中,我想在显示第二个窗体时做这样的事情:

procedure TMainApp.Button1Click(Sender: TObject);
var
  MyData: TMyData;
  SecondPopup: TSecondPopup;
begin
  MyData := TMyData.Create(nil);
  SecondPopup := TSecondPopup.Create(MyData);

  // Can't change owner now.  It is a read only property. 
  // MyData.Owner := SecondPopup;

  SecondPopup.Show;
end;

我知道我可以将 DataModule 更改为表单上的一个属性。然后我可以先创建表单,然后创建设置所有者的数据模块,最后在表单上设置属性。我正在尝试在这个项目上使用构造函数依赖注入。当我有一个将主表单传递给多个表单的共享数据模块时,它一直工作得很好。在这种情况下,主窗体会保留数据模块,直到它存在。在这种情况下,只有一个表单需要这个数据模块,所以我想通过设置所有者来强制它管理数据模块的生命周期。

另一种选择是在我关闭第二个表单时显式释放 DataModule。但是,该表单无法知道调用者是否也将数据模块传递给了不同的表单。

有没有办法使用构造函数来注入我的对象,但仍然获得表单来管理生命周期?

目前使用 Delphi XE3。

【问题讨论】:

  • 所有者是只读的,创建后我找不到更改它的方法。
  • 您可以更改所有权,使用 RemoveComponent、InsertComponent。虽然我不明白这个问题..
  • 你想让他们拥有彼此?
  • 否 - 表单使用并拥有数据模块。如果我只是在表单的创​​建中创建它,那将不是问题。但是我试图将 DataModule 传递到表单中。我已经阅读了太多关于依赖注入、IoC 和单元测试的文章。我想我只需要放弃并回到我知道可行的简单设置:-)。
  • 这对我来说似乎很愚蠢。为什么?为什么要这样做?

标签: delphi


【解决方案1】:

使用NewOwner.InsertComponent(TheComponent) 可以更改组件的所有权。而且由于一个组件一次只能由一个组件拥有,因此 RTL 会自动从前一个所有者那里移除所有权。


但是……

另一种选择是在我关闭第二个表单时显式释放 DataModule。但是,该表单无法知道调用者是否也将数据模块传递给了不同的表单。

如果您希望将单个 DataModule 传递给多个 Form,那么更改 DataModule 的所有权不是解决方案:拥有 DataModule 的一个 Form 将无法决定是否可以释放 DataModule。因此,结论是 DataModule 不能被任何表单拥有,除了 MainForm。 (那么我宁愿让它由 Application 对象拥有,但这只是个人喜好问题。)

随后,您需要在 DataModule 中为它所附加的表单提供一个引用计数机制。

【讨论】:

  • NewOwner.InsertComponent 非常适合我。 cmets 中缺少的答案是调用 InsertComponent 的选择。模块的生命周期始终由调用者控制。如果数据模块用于其他形式,它将保存在成员变量中并根据需要进行跟踪。但是,如果这是我使用特定数据模块的唯一地方,我可以调用 InsertComponent 并相信它会在表单被销毁时消失。 对我来说关键部分是我的表单不需要知道其中的区别。 连接代码决定如何销毁数据模块。
  • 如果数据模块用于其他形式,它将被保存在成员变量中并根据需要进行跟踪。 那么FreeNotification from Remy's answer 会很方便!
  • Oshoot,David 在他的编辑中击败了我,但现在不能再删除了......
  • @Mark 你让这件事变得比它应该做的要复杂得多。
【解决方案2】:

您不必更改 DataModule 的 Owner。您可以随时简单地销毁 DataModule,即使它分配了 Owner。当 DataModule 被释放时,它会简单地将自己从其Owner 中删除,因此不会再次被释放。如果您采用这种方法,您还应该添加对 FreeNotification() 的调用,以便在您的表单仍在引用 DataModule 时(通过其 Owner 或其他任何人)获得通知:

protected
  procedure Notification(AComponent: TComponent; Operation: TOperation); override;

constructor TSecondPopup.Create(MyData: TMyData);
begin
  inherited Create(nil);
  FMyData := MyData;
  if FMyData <> nil then
    FMyData.FreeNotification(Self);
end;

destructor TSecondPopup.Destroy;
begin
  if FMyData <> nil then
    FMyData.RemoveFreeNotification(Self);
  inherited Destroy;
end;

procedure TSecondPopup.Notification(AComponent: TComponent; Operation: TOperation);
begin
  inherited Notification(AComponent, Operation);
  if (Operation = opRemove) and (AComponent = FMyData) then
    FMyData := nil;
end;

如果您绝对想更改 DataModule 的 Owner,这也是可行的,通过 TComponent.RemoveComponent()TComponent.InsertComponent() 方法:

constructor TSecondPopup.Create(MyData: TMyData);
begin
  inherited Create(nil);
  FMyData := MyData;
  if FMyData <> nil then
  begin
    // InsertComponent() will call RemoveComponent() internally for you...
    Self.InsertComponent(FMyData);
  end;
end;

【讨论】:

  • 在我的情况下,我希望第二种形式来释放 DataModule,因此 InsertComponent 似乎是要走的路。尽管在第一个表单按钮单击事件中,但我正在从 SecondPopup 外部进行调用。那让我的外部容器控制模块的生命。如果需要,我可以保留它或将其绑定到表格。我的表单不需要知道区别。
  • 那么为什么还要给数据模块一个所有者呢?
  • 第二种形式是一个独立的形式,它会自行关闭。我不想尝试从我的主要来源中跟踪它以了解何时释放数据模块。
  • 所以,当表单被销毁时,只需获取该表单以释放 dm。再简单不过了。这个答案中的所有机器,以及我的第一部分,以及 cmets 都是过大的。将 dm 的引用传递给表单构造函数,并在表单析构函数中调用 dm ref 上的 Free。
  • @MarkElder:如果您的表单不需要知道差异,那么他们根本没有必要拥有 DM,因为那样他们就会引入必须知道的差异。相反,您应该在创建任何表单之前创建 DM,然后将其传递给需要使用它但在表单之外保持所有权的每个表单,然后在所有表单关闭并使用 DM 完成后释放 DM。
【解决方案3】:

您想让表单拥有数据模块。由于所有权是在构建时最自然地指定的,因此结论是应该首先创建表单。

因此,不要将数据模块传递给表单的构造函数,而是传递一些允许表单调用数据模块的实例化的东西。例如,您可以将一个函数传递给表单的构造函数,该函数接受表单作为参数并返回新生成的数据模块。例如。

type
  TCreateDataModule = function(Owner: TMyForm): TMyDataModule of object;

您完全可以在表单之外创建没有所有者的数据模块。将对象传递给表单构造函数。然后表单可以在其析构函数中销毁数据模块。这对我来说听起来是最干净的解决方案。我发现很难看到这个选项。

我认为您已经考虑过这个选项,但出于以下原因拒绝了它:

但是该表单无法知道调用者是否也将数据模块传递给了不同的表单。

如果是这样,那么没有什么可以拯救你。您不能让两个对象都负责生命周期,除非它们使用引用计数或类似方法。

【讨论】:

  • 将 TMyDataModule 的可能后代返回到此函数是否满足“构造函数依赖注入”(我认为很明显我不知道它是什么)?
  • @Sertac 典型的构造函数注入将对象传递给构造函数并以这种方式连接依赖项。这里因为依赖是循环的,我们被引导在另一个对象的构造函数中创建一个对象。因此是工厂方法。
【解决方案4】:

为什么要在创建表单之前创建一个供表单使用的数据模块? 1)为什么不直接将Data Module单元添加到表单的接口使用列表中,在表单中声明一个Data Module类型的私有变量,并在表单的OnCreate事件中创建表单的Data Module变量......和然后你可以在 OnDestroy 事件中 FreeAndNil 数据模块。 此外,您可以进一步声明表单数据模块变量的公共属性,您可以使用它从调用单元(即TestForm.DataModule)访问数据模块

2) 如果您认为必须在表单之外创建数据模块,可能首先进行大量涉及数据模块的初始化、处理等,然后将数据模块传递给表单并忘记它......并假设您正在创建的这个表单,它将使用数据模块,将是一个非模态表单(那一点信息会有很大帮助),你总是可以做 1){以上}首先,然后在调用表单的 Show 方法之前访问“TestForm.DataModule”以应用所有初始化、流程等。

【讨论】:

  • 不回答问题。 Mark 有一个由多个表单共享的 DataModule,这种情况并不少见。这个答案将如何帮助他决定何时销毁 DataModule?
  • "Mark 有一个 DataModule 由多个表单共享" 不,实际上他没有。我读的越多,我就越确信这是一个课堂项目,一个由他的课堂导师向他展示的特定场景。此外,他的问题不是关于“何时”销毁 DataModule。我们在看同一个问题吗?
  • 是的,这个问题有点自相矛盾,但在这里你有它:另一种选择是在我关闭第二个表单时显式释放 DataModule。但是,该表单无法知道调用者是否也将数据模块传递给了不同的表单。 因此,使用 DataModule 的表单无法决定何时释放 DataModule。
  • 不是课堂项目——我很久以前就毕业了:)。我试图在我开始的一个新项目上使用依赖注入。有问题的数据模块只有数据库连接。然后表单创建自己的具有数据集的数据模块。我试图避免使用全局单例进行连接。我实际上连接到多个数据库。主数据库通过一个共享连接保持打开状态。其他连接特定于一种形式。我想注入连接,而表单不需要知道该连接是否应该关闭或保持打开状态。
猜你喜欢
  • 2018-08-29
  • 1970-01-01
  • 2015-06-18
  • 1970-01-01
  • 1970-01-01
  • 2016-07-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多