LiveBindings in XE3: TAdapterBindSource and binding to Objects

In the last post I introduced BindSources, a new set of classes in the LiveBinding system in RAD Studio XE3, and in particular, looked at TBindSourceDB. That’s fine if what you are binding to is a Dataset, but what happens if what you want to bind to is an object?

Binding to objects was possible in LiveBindings in XE2, and that approach will still work, however it doesn’t leverage all the LiveBinding Designer goodness.

Well, an easy answer would be “Do it the same way you did it in XE2”. Binding to objects was possible in LiveBindings in XE2, and that approach will still work, however it doesn’t leverage all the LiveBinding Designer goodness.

A better answer, if we want to both use the LiveBinding Designer and do the minimal amount of work, is to use another of the BindSource components: the TAdapterBindSource.

Whereas the TBindSourceDB acted as a gateway to allow us to connect the LiveBindings engine to fields in a dataset, a TAdapterBindSource allows us to connect it to elements in an Adapter. What’s an Adapter? Well, it’s a descendant of TBindSourceAdapter, who’s responsibility it is to take your custom data source (in this case an object) and make it available (ie. adapt it) to the TAdapterBindSource, and by extension, the LiveBinding engine.

An example is probably in order, then we’ll come back and drill in a bit further.

Create a new FireMonkey project (LiveBindings are available in VCL too), and in the form source define a new class like this:

type
  TPerson = class
  private
    FAge: Integer;
    FLastname: string;
    FFirstname: string;
  public
    constructor Create(const Firstname, Lastname : string; Age : Integer); virtual;
    property Firstname : string read FFirstname write FFirstname;
    property Lastname : string read FLastname write FLastname;
    property Age : Integer read FAge write FAge;
  end;

The constructor, not surprisingly, is implemented like this:

constructor TPerson.Create(const Firstname, Lastname: string; Age : Integer);
begin
  FFirstname := Firstname;
  FLastname := Lastname;
  FAge := Age;
end;

Note, I didn’t have to use properties to make this work, it works just as well with Fields. Old habits die hard I guess.

Also, add a private field to your form like so:

MyPerson : TPerson;

Next, drop a TAdapterBindSource on the form and put the following code in its OnCreateAdapter event:

procedure TForm4.AdapterBindSource1CreateAdapter(Sender: TObject;
  var ABindSourceAdapter: TBindSourceAdapter);
begin
  MyPerson := TPerson.Create('Fred', 'Flintstone', 40);
  ABindSourceAdapter := TObjectBindSourceAdapter.Create(self, MyPerson, True);
end;

The first line is not that interesting: I’m creating an instance of our TPerson class. However it’s the second line where we encounter our first adapter. The OnCreateAdapter event has a var parameter of type TBindSourceAdapter called ABindSourceAdapter. I need to create an instance of a TBindSourceAdapter (usually of a descendant) and pass it back to the TAdapterBindSource via this var parameter.

That’s what the second line does. You can see we’re creating an instance of a generic TObjectBindSourceAdapter<T>, specifically a TObjectBindSourceAdapter<TPerson>. I’m passing the following values in to the constructor:

  • self, as the TObjectBindSource’s owner (it’s a TComponent descendant)
  • MyPerson, which is the instance of the TPerson I want to adapt.
  • True, indicating that I want to make the TObjectBindSourceAdapter responsible for freeing my TPerson instance (this is actually the default, but I included it here for completeness)

This is actually enough to get started. If you drop a Grid down on the form, drag a link between the * field in the AdapterBindSource to the * in the Grid in the LiveBindings Designer (see the bottom of the screenshot below) and run your app you should see the values of our Person instance in the Grid.

That’s useful as far as it goes, but there is a problem: I can’t see the Firstname, Lastname and Age properties in the LiveBindings Designer, which makes it a manual process to bind one of those values to say an Edit box or Label. We’ve cheated by using the * property of the AdapterBindSource but unless we’re happy using a grid to display a single TPerson, we’re going to need to solve this. (We’ll come back to collections of objects later, I promise)

To get the fields of my object to show up in the LiveBindings designer, we can leverage another new component in XE3, a TDataGeneratorAdapter.

To get the fields of my object to show up in the LiveBindings designer, we can leverage another new component in XE3, a TDataGeneratorAdapter. This is another TBindSourceAdapter descendant, and in fact this one is installed on the component palette (I did mention before that TBindSourceAdapters were TComponents). A TDataGeneratorAdapter allows you to define one or more fields in your adapter, along with an accompanying generator that will generate some format of values for your field, at both design time and run time if you like. Drop one onto your form and then bring up the property editor for the TDataGeneratorAdapter.FieldDefs property and have a look:

As you can see, there is a Booleans generator that will generate random boolean values, random currency generators, date generators for both TDate types and also strings containing date values. Scroll further down and there are even String and TStrings generators that will fill return Lorem Ipsum text.

Let’s create three fields to match the name and type of our TPerson object like so:

Now select your AdapterBindSource and set its Adapter property to point to our DataGeneratorAdapter component. Make sure the DataGeneratorAdapter is set to Active and at design time you should see something like this:

Not only do we have design-time data in our grid (courtesy of our generators), but we also now have the fields showing up in the LiveBinding Designer so we can start easily binding their values to other controls.

Because we still have the OnCreateAdapter event setup, and further, because the names of the fields we created in our DataGeneratorAdapter match the field names in our TPerson class, if we run the app now our DataGeneratorAdapter will be replaced as the adapter for the AdapterBindSource by our TObjectBindSourceAdapter, giving us an easy design-time experience while still giving us data from our object at runtime. If we removed this event, the AdapterBindSource would continue to use the DataGeneratorAdapter at runtime as well.

A few things to note here:

  • The DataGeneratorAdapter is creating these field values on the fly as requested, but once created they behave like a dataset. ie. you can edit them, post them, navigate around and they retain their values.
  • You could just as easily use the DataGeneratorAdapter when binding to a dataset. Let’s say you need to prototype a form for a table that does not yet exist. Use the DataGeneratorAdapter as above, show the prototype to the customer, and once they sign off on it then go ahead and build out the actually database table and datasets later. In fact there is another component included in XE3 called TPrototypeBindSource which is basically a combination of the TAdapterBindSource and the TDataGeneratorAdapter to make this scenario even easier. I plan on posting about this component in a future article, but in the meantime there is a tutorial available in the Help.

Now that we have fields showing up in the LiveBindings Designer, you can go ahead and replace the grid with components more appropriate for displaying a single object instance if you wish. You can do this either by adding the component to the form and dragging in the designer to create the link, or right-click on the fields in the designer and select “Bind to new control…”. (check out the previous article if you need a refresher)

However I’m going to leave the grid there, as I want to come good on the promise I made earlier: to look at binding to a collection of objects.

Let’s change the MyPerson field that we added to our form earlier to look like:

 
  MyPeople : TObjectList;

Then, go back into the source in your AdapterBindSource1.OnCreateAdapter event and change it to look like this:

procedure TForm4.AdapterBindSource1CreateAdapter(Sender: TObject;
  var ABindSourceAdapter: TBindSourceAdapter);
begin
  MyPeople := TObjectList.Create;

  MyPeople.Add(TPerson.Create('Fred', 'Flintstone', 40));
  MyPeople.Add(TPerson.Create('Wilma', 'Flintstone', 41));
  MyPeople.Add(TPerson.Create('Barney', 'Rubble', 40));
  MyPeople.Add(TPerson.Create('Betty', 'Rubble', 39));

  ABindSourceAdapter := TListBindSourceAdapter.Create(self, MyPeople, True);
end;

You can see we’re creating a TObjectList, we’re adding a few instances to it, and then rather than creating a TObjectBindSourceAdapter, we’re creating a TListBindSourceAdapter. That’s all we need to do.

Run your app, and you should see the four TPerson instances we created in the grid. Further, if you right-click on the AdapterBindSource at designtime and add a Navigator, at runtime you can click the Insert button in the Navigator and it will add a new TPerson instance to our list, ready for editing.

That’s enough for this post, but we’re not quite done with this topic. Next post I want to look at editing the data in your objects, as using the Adapters in the way we just have raises a couple of interesting issues that aren’t immediately obvious (or at least they weren’t to me).

24 Comments

  • […] […]

  • Apologies all, there seem to be some issues with the formatting of the source on this post. Sometimes it’s stripping out all the generic parameters, sometimes it’s showing them encoded. The joys of angle brackets.  Am trying to figure it out now. 

    • OK, I think I’ve got them all, but let me know if anything doesn’t look right.

    • Hi Malcolm,
      I read the post correctly, and I find it very useful in content. I was trying to figure out how to “TBindSourceAdapter” works. For example, to hide a filed, if you wanted to see in the grid only FirstName and LastName without seeing the age.

      thank you very much
      Ivan

  • This saves so much time. Great. Thank for the bindingfunctionality in RAD Studio.

  • […] I left off in my last post, we were able to bind our UI elements to an object, or indeed a collection of objects, by using the […]

  • Hi,
    I have tested the sample above and I have an exception : raised exception class einvalid pointer with message ‘Invalid pointer operation’
    Why ?
    Thanks a lot for the article
    Eddy
    Stack :
    System.TObject.FreeInstance
    System.ErrorAt(2,$4075F9)
    System.Error(reInvalidPtr)
    System.TObject.FreeInstance
    System._ClassDestroy(???)
    System.TObject.Destroy
    System.TObject.Free
    Data.Bind.ObjectScope. {System.Generics.Collections}TList<Data.Bind.ObjectScope.TBindSourceAdapterField>.Destroy System.TObject.Free Data.Bind.ObjectScope.TBindSourceAdapter.Destroy Unit1.{Data.Bind.ObjectScope}TObjectBindSourceAdapter<Unit1.TPerson>.Destroy

    • Hi.
      Could it be, that it is because of the ownership parameter in your create?
      Did you free the adapted instance on your own? Then the adapter would trigger this error, if it is created with the true parameter in third position.

    • @epoullet Someone else reported a similar error, although I can’t reproduce. Try passing in AdapterBindSource1 as the first parameter of the constructor instead of self. I’ll be curious to know if that fixes it.

      •  @malcolmgroves  @epoullet
        Hi Malcom,
        Effectively, passing the Adapter…. as the first parameter solves the problem. No more exceptions.
        Thanks a lot for the articles. Please can you continue to write about it 🙂
        Eddy
         

    • @epoullet It seems to be a bug in XE3. If you have connected a DataGeneratorAdapter at DesignTime to the AdapterBindSource, then you should avoid to create the “real” adapter with the Form as Owner. Nil is also perfect, because it is freed by the AdapterBindSource anyway.
      Or just cut off the DataGeneratorAdapter from AdapterBindSource (in OI), then you can use the Form as Owner.
      IMHO this strange behavior is caused by TCustomAdapterBindSource.Notification

  • […] XE2 e agora melhorada com os visual livebindings facilita bastante a vida do programador, no artigo LiveBindings in XE3: TAdapterBindSource and binding to Objects o autor demonstra uma visão geral dessa poderosa tecnologia. ImprimirEmail Esta entrada foi […]

  • Some interesting stuff, but is there any way to create the TBindSourceAdapter at runtime?  It’s such a strange convention to only expose the object through an event.

  • Some interesting stuff, but is there any way to create the TBindSourceAdapter more dynamically at runtime?  It’s such a strange convention to only expose the object through an event.

  • […] Groves recently blogged how you can use the "TAdapterBindSource and binding to Objects" in the LiveBindings Designer.  TAdpaterBindSource is a bind source which supports adapters to […]

  • […] when I wrote about TAdapterBindSource, one of the adapters I used was a TDataGeneratorAdapter. This combination was useful to give me […]

  • […] mit Objekten – Video von EMBA   Heute, 20:59 Ich denke dieser Link wird euch weiterhelfen: Malcolm Groves […]

  • I had a go with a VCL version – couldn’t understand why it didn’t work until I realised FormCreate occurs AFTER the OnCreateAdapter! My list was null, so the grid was empty. I wondered why you put the initialisation code in the OnCreateAdapter event.

  • What should i do if i want to add one person to MyPeople and refresh view component?

  • The article is great! The only thing that is not ok is that the data is not dynamically changed on the bound controls if the object changes it’s state. How can I achieve this?

  • […] also apply to binding from something other than a database, such as a list of objects (see my post TAdapterBindSource and binding to Objects), but the solution applies […]

  • i’d try to make 2 Object with relation (master/detail). When i used TPrototype i found error when open the detail TPrototype.

    This my Code when Master Adapter Change with ListboxChange:

    var
    pbsHeader: TPrototypeBindSource;
    pbsDetail: TPrototypeBindSource;
    FDetailItemCast: TObjectList;
    HeaderDataList: TCUBEInvGoodsReceiptList;

    procedure TfrmInvGoodsReceipt.ListBoxChange(Sender: TObject);
    var
    I: Integer;
    begin
    inherited;
    if FDetailItemCast.Count > 0 then
    FDetailItemCast.Clear;

    if HeaderDataList.Count > 0 then
    begin
    FInvGoodsReceiptDetailList := TCUBEInvGoodsReceiptList(HeaderDataList).Items[pbsHeader.ItemIndex].DetailList;
    for I := 0 to FInvGoodsReceiptDetailList.Count – 1 do
    begin
    FDetailItemCast.Add(TCUBEInvGoodsReceiptDetail
    (FInvGoodsReceiptDetailList.Items[I]));
    end;
    end;

    try
    pbsDetail.Active := False;
    pbsDetail.Active := True;
    except

    end;
    end;

    Would you like to give a suggest how to handling this error?
    Thanks…

Leave a Reply to LiveBindings : How to treat an Integer field as a Boolean? | Malcolm Groves Cancel reply

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>