The research for this post has been a ton of fun. I was puzzled as to how the TBindList did it’s thing so I have been digging around in the Delphi source code for answers and I also enlisted the help of one of our R&D Engineers to help me over the line (Thanks Jim!). But enough emotion, lets construct the example.
The goal of this example is to populate a TListBox with a collection of ‘business objects’ (A TList of TDamo) and to use LiveBindings to link everything together. Create a FireMonkey HD Application and add the following components to it:
TBindingsList (blDamoExpressions) TBindScope (bsDamoCollection) TListBox (lbDamoObjects) TEdit (edtDamoObject) TCheckBox (cbBindListEnabled)
My UI looks a little like this:
Save All and save the name of the form as FLiveBindingList and the name of the project as LiveBindingList. Make a copy of UDamoObject.pas that we created in our last example, paste it into our new project directory and add it to the project by right clicking your project in the Project Manager window and selecting Add… We will create one more unit that will manage our collection of TDamo. Save this unit as UDamoObjectManager.pas and replace the existing code in the unit with the following:
interface uses Generics.Collections, UDamoObject; type TDamoObjectManager = class private lDamoObjectList : TList<TDamo>; public Constructor Create(); property DamoObjectList : TList<TDamo> read lDamoObjectList; procedure AddADamo(Damo : TDamo); function GetADamo(DamoIndex : Integer) : TDamo; end; implementation procedure TDamoObjectManager.AddADamo(Damo: TDamo); begin lDamoObjectList.Add(Damo); end; constructor TDamoObjectManager.Create; var TempDamoObj : TDamo; begin lDamoObjectList := TList<TDamo>.Create(); end; function TDamoObjectManager.GetADamo(DamoIndex: Integer): TDamo; begin Result := lDamoObjectList.Items[DamoIndex] end; end.
As you can see from the code above, this unit contains a TList (lDamoObjectList) that will hold all of my TDamo objects, a property to expose my TList (DamoObjectList), a procedure to add an object to the list (AddADamo) and a function to return one of the objects from the list (GetADamo).
The next step is to create an OnCreate event for the form. In this event handler we want to instantiate TDamo objects, add them to our TList and link the TList to our TBindScope (bsDamoCollection). Select Form1 from the structure pane, click the Events tab in the Object Inspector and double click the OnCreate event. Implement the event handler as follows:
procedure TForm1.FormCreate(Sender: TObject); var Damo : TDamo; begin DamoObjManager := TDamoObjectManager.Create(); Damo := TDamo.Create('Damo'); DamoObjManager.AddADamo(Damo); Damo := TDamo.Create('Jeanette'); DamoObjManager.AddADamo(Damo); bsDamoCollection.DataObject := DamoObjManager.DamoObjectList; end;
I don’t think that my wife would like to be classified as a TDamo, I’ll take that one on the chin when I get home
The next step is to now create a bind expression that will link our collection of business objects to the TListBox. Go back to the Form Designer and double click blDamoCollection to open the LiveBinding Editor, click the new button and select the TBindList expression type. Set the following properties:
ControlComponent: lbDamoObjects Name: bndlstDamoList SourceComponent: bsDamoCollection
Double click bndlstDamoList to open the Expression Editor. You will notice that in the left hand navigation window there are three ‘Collections’. These are the different types of expressions that we can create between our source and control objects in a TBindList:
FormatControl: Changes the look and feel of the TListBox itself based on data from the source.
ClearControl: Performs some user-defined functionality when the LiveBinding associated to the TListBox is no longer enabled.
Format: Modifies the contents of our TListBox.
So, just to clarify, the first two types of expressions are at the Control level whereas the third type is at the Item level of the Control.
We are going to add one of each expression to show how they work. Select the FormatControl collection, click the new button and add the following to the source and control expressions:
Control Expression: Height Source Expression: Count * 20
When the LiveBinding is active, the height of lbDamoObjects will be 20 multiplied by the amount of objects in the collection of TDamo objects. (Probably not the best example to use if you have a shed load of TDamo objects, but again, it’s to show the concept of the FormatControl expression)
Select the ClearControl collection, click the new button and add the following to the source and control expressions:
ControlExpression: Height SourceExpression: 180
Again, I’m focusing on the look and feel of the TListBox. When the LiveBinding is no longer enabled, the TListBox will go back to the original height of 180.
Finally, let’s add a Format control. Select the Format collection, click the New button and add the following to the Source and Control expressions:
ControlExpression: Text SourceExpression: Current.Name
Run the application. You should see the TListBox shrink to a size of 40 and the names of two TDamo objects should be displayed.
At this point, I think that there are a number of questions that need to be answered:
1. How am I iterating over my collection of TDamo objects? TBindScope takes care of this. TBindScope descends from TCustomBindScope. TCustomBindScope implements an Interface called IScopeRecordEnumerable which has one function called GetEnumerator. Within TCustomBindScope, GetEnumerator creates a TEnumerableWrapper and our DataObject (TList of TDamo) is passed as a parameter to the TEnumerableWrapper constructor. TEnumerableWrapper then uses RTTI against our DataObject to access it’s contents.
2. How are the TDamo objects being added to the TListBox? A TBindListListBoxEditor is created and supports the necessary methods and functions to traverse and access data in a TListBox. The Fmx.Bind.Editors unit contains the implementation and registration of this class for our FireMonkey TListBox. TBindList descends from TCustomBindList. TCustomBindList has a function called TryGetBindListEditor. This function will use a factory to create a TBindListListBoxEditor based on our TListBox and the IBindListEditor interface.
3. Where do the Current and Count properties come from? The Current property is made available through the IScopeRecordEnumerator Interface that is provided by the TBindScope. I believe that the Count property is made available directly from the TList, which is our DataObject. (Like the Name property of TDamo that we exposed directly through a TBindScope in the last post)
4. Can I fit all of this into one blog post? Yes, but it will be a short novel
I have added a couple of other LiveBindings to this example which are worth looking at too. The first is a way of taking items from our TListBox and setting them in a TEdit. Double click blDamoExpressions, create a new TBindExpression and set the following properties:
ControlComponent: edtDamoObject ControlExpression: Text Name: bxTDamoNameToEdit SourceComponent: lbDamoObjects SourceExpression: Selected.Text
When a user of the application clicks one of the items in the TListBox, the name of the item selected will be displayed in edtDamoObject. In order to complete this LiveBinding, we need to add a Notify call in the OnChange event handler of lbDamoObjects. Select lbDamoObjects in the Object Inspector, double click the OnChange event handler and add the following line of code:
Run the application and select one of the items in the TListBox and it should be displayed in the TEdit:
Our final LiveBinding will control whether our TBindList binding is enabled or disabled. To do this we will use the IsChecked property of the TCheckBox as our source. Create a new TBindExpression and set the following properties:
ControlComponent: bndlstDamoList ControlExpression: Active Name: bxEnableLiveBinding SourceComponent: cbBindListEnabled SourceExpression: IsChecked
Before you run this example, make sure that the AutoActivate property of bndlstDamoList is false and the IsChecked property of cbBindListEnabled is also false. We also need to add an OnChange event handler to cbBindListEnabled and make a Notify call:
procedure TForm1.cbBindListEnabledChange(Sender: TObject); begin blDamoExpressions.Notify(Sender, 'IsChecked'); end;
When run, you should see an empty TListBox. Check cbBindListEnabled and the TBindList should become active and populate our TList.
If you would like the source code, drop a comment and I will get it out to you.