1
votes

I am trying to pass a DataModule to a form in the form's constructor. I also want the form to be the "Owner" of the DataModule so that the form will destroy the DataModule when it is closed. That creates the problem of both objects needing each other in their constructors.

I tried to set the owner of the DataModule after creation but that is a read only property.

My second form looks like this:

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;

There is no special code in my data module.

In my main form I want to do something like this when showing the second form:

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;

I know I can change the DataModule to be a property on the form. Then I could create the form first, then create the data module setting the owner, and finally set the property on the form. I am trying to use constructor dependency injection on this project. It had been working great when I had a shared data module that the main form passed to multiple forms. In that case the main form holds on to the data module until it exists. In this case there is only one form that needs this data module so I wanted to force it to manage the life of the data module by setting the owner.

Another option would be to explicitly free the DataModule when I close the second form. However that form has no way of knowing if the caller also passed the datamodule to a different form.

Is there a way to use the constructor to inject my object but still get the form to manage the lifetime?

Currently using Delphi XE3.

4
The owner is read only and I can't find a way to change it after creation.Mark Elder
You can change ownership, use RemoveComponent, InsertComponent. I didn't understand the question though..Sertac Akyuz
You want them to own each other?Sertac Akyuz
No - the Form uses and owns the data module. It would not be a problem if I just created it inside the Form's create. However I am trying to pass the DataModule into the Form. I've been reading too many articles on dependency injection, IoC, and unit testing. I think I just need to give up and go back to a simple setup that I know works :-).Mark Elder
This seems dumb to me. Why? Why do this?Warren P

4 Answers

4
votes

You don't have to change the DataModule's Owner. You can simply destroy the DataModule whenever you want, even if it has an Owner assigned. When the DataModule is freed, it will simply remove itself from its Owner so it is not freed a second time. If you go this approach, you should also add a call to FreeNotification() so that you are notified if the DataModule gets freed (by its Owner or anyone else) while your form is still referencing it:

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;

If you absolutely want to change the DataModule's Owner, that is also doable, via the TComponent.RemoveComponent() and TComponent.InsertComponent() methods:

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;
4
votes

Changing ownership of a component is possible with NewOwner.InsertComponent(TheComponent). And since a component can only be owned by one component at a time, the RTL takes care of removing ownership from the previous owner automatically.


But...

Another option would be to explicitly free the DataModule when I close the second form. However that form has no way of knowing if the caller also passed the datamodule to a different form.

If you want the possibility to pass a single DataModule to multiple Forms, then changing ownership of the DataModule is not the solution: the one Form that owns the DataModule will not be able to deside whether it may free the DataModule. Thus the conclusion is that the DataModule cannot be owned by any form, except by the MainForm. (Then I would prefer to let it own by the Application object, but that's a matter of taste.)

Subsequently, you will then need a reference counting mechanism within the DataModule for the Forms which it is attached to.

3
votes

You want to let the form own the data module. Since ownership is most naturally specified at construction time, then the conclusion is that the form should be created first.

So, instead of passing the data module to the form's constructor, pass something that allows the form to invoke the instantiation of the data module. For instance, you could pass a function to the form's constructor that accepts the form as a parameter and returns the newly minted data module. For instance.

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

You could perfectly well create the data module outside the form but with no owner. That pass the object to the form constructor. The form can then destroy the data module in its destructor. That sounds the cleanest solution to me. I find it hard to see past this option.

I think you considered this option already but rejected it with this reasoning:

However that form has no way of knowing if the caller also passed the data module to a different form.

If that is so, then nothing can save you. You cannot have two objects both in charge of the lifetime, unless they use reference counting or similar.

0
votes

Why create a Data Module that is meant to be used by a form, prior to creating the form? 1) Why not just add the Data Module unit to the interface uses list of the form, declare a private variable of the Data Module type in the form, and create the form's Data Module variable in the OnCreate event of the form...and then you can FreeAndNil the Data Module in the OnDestroy event. Additionally, you can further declare a public property of the form's Data Module variable, with which you can access the Data Module from the calling unit (i.e. TestForm.DataModule)

2) If you are thinking you must create the Data Module outside of the form, perhaps to do a great number of Data Module-involved initializations, processes, etc. first, and then passing the Data Module off to the form and forgetting about it ... and assuming this form you are creating, that will be utilizing the Data Module, will be a NON-modal form (that bit of information would be of great help), you could always do 1) {above} first, then access the 'TestForm.DataModule' to apply all of your initializations, processes, etc. to before calling the form's Show method.