1

I am trying to use livebindings on a VCL form in which the object to be bound to the controls on the form is passed to the form as a property. I am using 10.1 Berlin. The property where in the object is passed is ordinary:

 Public
      Property ProjectObject: TProject Read fProjectObject Write fProjectObject;

I have used DataGeneratiorAdapter and AdapterBindSource to set up the links on the form using the designer.

Where I am having a lack of understanding is at the AdapterBindSource in the OnCreateAdapter method. All the examples I can find show how to create a new object to be populated by the controls, but I fail to find a way to bind at runtinme fProjectObject (the passed object).

My current code in the OnCreateAdapter method is:

ABindSourceAdapter := TObjectBindSourceAdapter<TProject>.Create(Self);

Which is acceptable to the compiler, but does not allow the controls to display and update the properties in fProjectObject.

The one of the sections of code that displays this form (the project edit form) looks like this:

ProjEdit.ProjectObject := Proj;
ProjEdit.ShowModal;
StoreProject(Proj);

Where ProjEdit is the project edit form, ProjectObject is the property where the project object is passed and Proj is the project object to be edited. The project object is simply passed to this form and stored after any alterations to the information have been made. This object was stored in a database before being passed to this form for editing.

How do I connect the livebindings to the passed object?

Thanks in advance for your help

SysJames
  • 411
  • 3
  • 10

2 Answers2

1

I think the thing which may have tripped you up is the not-very-obvious point that your ProjectObject needs to be created before the CreateAdapter event fires. To ensure that that happens, you need to override your form's Create method and create your ProjectObject there.

The following works fine for me:

type

  TPerson = class
  private
    FLastName: String;
    FFirstName: String;
  public
    property FirstName : String read FFirstName write FFirstName;
    property LastName : String read FLastName write FLastName;
  end;

  TForm1 = class(TForm)
    edFieldA: TEdit;
    edFieldB: TEdit;
    BindNavigator1: TBindNavigator;
    PrototypeBindSource1: TPrototypeBindSource;
    BindingsList1: TBindingsList;
    LinkControlToField1: TLinkControlToField;
    LinkControlToField2: TLinkControlToField;
    procedure PrototypeBindSource1CreateAdapter(Sender: TObject; var
        ABindSourceAdapter: TBindSourceAdapter);
  private
  public
    Person : TPerson;
    constructor Create(AOwner : TComponent);  override;
  end;

[...]

constructor TForm1.Create(AOwner: TComponent);
begin
  Person := TPerson.Create;
  Person.FirstName := 'John';
  Person.LastName := 'Smith';
  inherited;
end;

procedure TForm1.PrototypeBindSource1CreateAdapter(Sender: TObject; var
    ABindSourceAdapter: TBindSourceAdapter);
begin
  ABindSourceAdapter := TObjectBindSourceAdapter<TPerson>.Create(Self, Person, False);
end;

Update The Person object on the form does NOT have to be created on the form. It can simply be assigned to a previously-existing object, as in

constructor TForm1.Create(AOwner: TComponent);
begin
  Person := SomeTPersonObjectCreatedAlreadyInOtherCode;
  inherited;
end;

If you want to verify that in my example, create an instance of TPerson in the initialization section of the unit and assign Form1.Person to it in the form's Create constructor. The thing you may not have realised is that a Delphi object variable is actually a pointer, so that it can freely be "pointed at" an existing instance of the object.

The important thing is to set the final parameter of TObjectBindSourceAdapter to False so that the adapter does not own the Person object, otherwise it will destroy the Person object when it (the adapter) is destroyed.

Btw, the need to override the form's constructor is explained in this video:

https://delphiaball.co.uk/2015/10/19/livebindings-in-vcl-part-2-livebinding-objects/

He explains that if you do not create the object you want to bind to before the CreateAdapter event, the binding will clear out any contents the object already had in the bound field(s).

MartynA
  • 30,454
  • 4
  • 32
  • 73
  • I'm sorry, I wasn't clear. The project object is passed to the form. If it is created in the form, it doesn't have the information that was passed to the form via the project object. – SysJames Mar 05 '17 at 13:33
  • Sorry, now I am confused because I'm not sure exactly what you mean by "information that was passed to the form via the project object". In any case, like I said, your project object must exist before the `CreateAdapter` event fires. Overriding the form's Create constructor is one way to do that. Btw, you *are* setting the final param to TObjectBindSourceAdapter to False, yes? Does my example not work for you? – MartynA Mar 05 '17 at 13:40
  • Sorry to have added to the confusion. I edited the original question to hopefully make it clearer what my problem is. I did watch your video, and I did not set the final parameter to TObjectBindSourceAdapter in my example, because it was at that point that I realized that I had no way of connecting with the project object that was passed to the form. Thanks for taking the time to help me. I really appreciate it. – SysJames Mar 05 '17 at 18:40
  • @sysjames: I think you are missing MartynA's point. In you q edit "ProjEdit.ProjectObject := Proj; ProjEdit.ShowModal; StoreProject(Proj);" your ProjectObject assignment takes place **after** your ProjEdit form has been created, but that is too late! You need to do it before your OnCreateAdapter occurs. – Alex James Mar 05 '17 at 18:49
  • @AlexJames: Thank you. I didn't fully understand that point. But, I still have no idea how to get the information in Proj (the project object) to be the object that is displayed / edited in the components. I'm beginning to think that live bindings will not be a benefit in this situation. If I understand, I cannot bind to an existing object, only to a newly created one. Is this correct? – SysJames Mar 05 '17 at 19:08
  • No that is definitely wrong. It is easily possible, as the code in the answer shows - I've tried it and it runs without any problems. – Alex James Mar 05 '17 at 19:12
  • @AlexJames The object in the answer above is created with the form. It is not a pre-existing object. I'm sorry, I still don't understand how to bind to a pre-existing object that exists prior to the form being created. I apologize for not "getting it". – SysJames Mar 05 '17 at 19:22
  • Shall I just delete this question? It seems I am not communicating well enough to form the question properly. I'll leave this up for a few hours and delete it if there no objections. – SysJames Mar 05 '17 at 19:29
  • @SysJames: First thing: hope you don't mind me saying but SO q & a is not primarily for the benefit of the person who asked the question, but for future readers, and it would be bad form to delete a q after someone has gone to the trouble of answering it. I have added an update to my answer which answers your query "I still don't understand how to bind to a pre-existing object" - it is as simple as assigning the Person variable on the form to an already created instance of TPerson, or `Proj` in your case. – MartynA Mar 05 '17 at 19:50
1

Here's what I suggest:

First: In the CreateAdapter of the AdapterBindSource use the following:

procedure TfrmProjectEdit.AdapterBindSource1CreateAdapter(Sender: TObject; var ABindSourceAdapter: TBindSourceAdapter);
begin
  fProjectObject:=TProject.Create;
  ABindSourceAdapter:=TObjectBindSourceAdapter<TProject>.Create(self, fProjectObject, True);
end;

Second: Use a setter for the project property such as:

procedure TfrmProjectEdit.SetProject (aProject: TProject);
begin
  fProjectObject:=aProject;
  AdapterBindSource1.Refresh;
end;

Quick explanation: The AdapterBindSource will own the fProjectObject and release it when the ABS is released. We simply assign a new value to the fProjectObject and Refresh the ABS in the setter.

I have not tested out this code - but I think this should work...

Rohit
  • 909
  • 1
  • 9
  • 20