Using LiveBindings to Connect the UI to Objects

After listening in on the recent Embarcadero webinar Understanding RAD Studio LiveBindings with Cary Jensen I asked a few questions in the Q&A session, like how to notify the framework of changes to property values from within a property setter. I had a keen interest in the webinar after experimenting with LiveBindings as I belive they have great potential.

LiveBinding support was added in Delphi and C++Builder XE2. Several articles and videos have been published showing how to connect UI controls to data sources and to each other, including LiveBindings for databases Part 1 and Part 2 on this blog however most of my development with Delphi and C++Builder is heavily OO. I prefer not to use TDataSet, TDataSource or data aware controls and currently use a Model View Presenter framework or object aware controls to connect the UI to objects for display and editing.

This short article follows up on further investigation and experimentation after the LiveBindings webinar and demonstrates a simple way to add a bi-directional link between UI controls and objects using the new LiveBindings support in XE2.

Binding Objects

To connect a UI control to a user defined object the binding must be created at runtime (after the object and control have been created). The TBindings class in the System.Bindings.Helper unit provides utility functions to accomplish this. One of these is CreateManagedBinding.

  TBindings.CreateManagedBinding(
      { inputs }
      [TBindings.CreateAssociationScope([Associate(SourceObject, 'src')])],
      'src.SourcePropertyName',
      { outputs }
      [TBindings.CreateAssociationScope([Associate(DestObject, 'dst')])],
      'dst.DestPropertyName',
      nil);

This creates a TBindingExpression to bind SourceObject to DestObject using simple expressions that will transfer the value of SourceObject.SourcePropertyName to DestObject.DestPropertyName.

To get bi-directional functionality, so that the object updates the control and the control updates the object, two bindings would need to be created between them using opposite inputs and outputs.

Change Notification

When the property of the source object changes the LiveBindings framework needs to be notified of the change to trigger the re-evaluation of the binding expression and update the destination object with the new value. Note that above I used the method CreateManagedBinding. Managed bindings rely on explicit notification of changes. It is also possible to create unmanaged bindings which use observer functionality to receive automatic change notifications but appear to be of limited use in their current form.

Explicit change notification can be performed in a couple of ways. One of these is by calling the TBindings helper method Notify.

  TBindings.Notify(SourceObject, 'SourcePropertyName');

The second parameter specifies the name of the property that changed. It is used to determine which binding expressions to re-evaluate. If an empty string is passed then all binding expressions for the object are re-evaluated.

It is also possible to notify the framework by calling the Notify method of the TBindingsList component. This is a better technique if you have configured bindings at design time.

Creating a Simple Binding System

Putting this together we can create a bi-directional link between a UI control property and an object property as follows. I have defined a base TBindingObject class that manages the property bindings (to avoid growing memory usage when objects are bound and later freed) and to add convenience methods to make binding and change notification a little easier.

interface

uses
  Generics.Collections, System.Bindings.Expression, System.Bindings.Helper;

type
  TBoundObject = class
  protected
    type
      TExpressionList = TObjectList<TBindingExpression>;
  private
    FBindings: TExpressionList;
  protected
    procedure Notify(const APropertyName: string = '');
    property Bindings: TExpressionList read FBindings;
  public
    constructor Create; virtual;
    destructor Destroy; override;
    procedure Bind(const AProperty: string; const ABindToObject: TObject;
        const ABindToProperty: string; const ACreateOptions:
        TBindings.TCreateOptions = [coNotifyOutput, coEvaluate]);
    procedure ClearBindings;
  end;

implementation

constructor TBoundObject.Create;
begin
  inherited;
  FBindings := TExpressionList.Create(false {AOwnsObjects});
end;

destructor TBoundObject.Destroy;
begin
  ClearBindings;
  FBindings.Free;
  inherited;
end;

procedure TBoundObject.ClearBindings;
var
  i: TBindingExpression;
begin
  for i in FBindings do
    TBindings.RemoveBinding(i);
  FBindings.Clear;
end;

procedure TBoundObject.Notify(const APropertyName: string);
begin
  TBindings.Notify(Self, APropertyName);
end;

procedure TBoundObject.Bind(const AProperty: string;
  const ABindToObject: TObject; const ABindToProperty: string;
  const ACreateOptions: TBindings.TCreateOptions);
begin
  // From source to dest
  FBindings.Add(TBindings.CreateManagedBinding(
      { inputs }
      [TBindings.CreateAssociationScope([Associate(Self, 'src')])],
      'src.' + AProperty,
      { outputs }
      [TBindings.CreateAssociationScope([Associate(ABindToObject, 'dst')])],
      'dst.' + ABindToProperty,
      nil, nil, ACreateOptions));
  // From dest to source
  FBindings.Add(TBindings.CreateManagedBinding(
      { inputs }
      [TBindings.CreateAssociationScope([Associate(ABindToObject, 'src')])],
      'src.' + ABindToProperty,
      { outputs }
      [TBindings.CreateAssociationScope([Associate(Self, 'dst')])],
      'dst.' + AProperty,
      nil, nil, ACreateOptions));
end;

To make use of this our business objects descend from TBoundObject, implementing property setters to provide the change notification so that regardless of how or where the property changes the framework is notified, relieving this task from the object user, simplifying the code for the UI.

  TPerson = class(TBoundObject)
  private
    FName: string;
    procedure SetName(const AValue: string);
  public
    property Name: string read FName write SetName;
  end;

implementation

procedure TPerson.SetName(const AValue: string);
begin
  if AValue <> FName then
  begin
    FName := AValue;
    Notify('Name');
  end;
end;

Binding to the UI

The following example binds the Name property to the Text property of an edit box.

type
  TForm1 = class(TForm)
    edtName: TEdit;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure edtNameChange(Sender: TObject);
  private
    FPerson: TPerson;
  end;

implementation

procedure TForm1.FormCreate(Sender: TObject);
begin
  FPerson := TPerson.Create;
  FPerson.Name := 'Fred';
  FPerson.Bind('Name', edtName, 'Text');
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  FPerson.Free;
end;

procedure TForm1.edtNameChange(Sender: TObject);
begin
  TBindings.Notify(Sender, 'Text');
end;

When the example application is run the edit box will display the name “Fred”. Any changes to the name made by the user through the UI will be pushed to the FPerson instance. This can be demonstrated by calling ShowMessage(FPerson.Name) from a button click event handler. Any changes to FPerson.Name such as in some other event handler or the framework code or business logic, will be automatically reflected in the UI.

Extending the Object Bindings

The binding demonstrated in this article is quite simple. It would be possible to extend the code to pass in custom expressions to evaluate (to format the name for example) or combine multiple property values from the business object. It could also be extended to support list and grid controls though the code required is quite a bit more complex.

Further Comments

During my investigation I tried in vain to find a way of simply linking two components together without any code with automatic change notification, such as have the caption of a label updated with the contents of an edit box. I tried several methods to get this to work, using different bindings and settings, stepping into VCL and FMX code, and even tried hooking in to the undocumented TComponent Observer system but as far as I could tell, with the exception of the unmanaged DB bindings with data sources, there is currently no notification built in to make this generally possible for all controls. For now at least you must write an OnChange event handler and call Notify.

My closing comment is that Embarcadero have defined the terms managed and unmanaged the wrong way around. Managed should refer to being managed by the framework (like managed code in .NET), not by the user. When I made this comment during the webinar Cary agreed. Embarcadero – please change it before it’s too late 🙂

Posted in RTL, VCL Tagged with: ,
7 comments on “Using LiveBindings to Connect the UI to Objects
  1. Hashi says:

    Great article. Just a quick question are live binding thread safe? I mean can a thread update the object which will in turn update the control

    • Jarrod Hollingworth says:

      No, LiveBindings is not thread safe. The business object property setter, the LiveBindings notification, expression evaluation, setting the property of the destination control/object, and even things like the OnChange event handler for the control are executed in the context of the calling thread that changed the original BO property. But SendMessage is often used in standard and common controls when changing properties that have a visible effect (such as TEdit.Text, TScrollBar.Position and so on) in which case the GUI repaint occurs in the context of the main thread (which processes the message loop).

      What this means is that you might get away with simple scenarios but in most cases you will need to manage the threads carefully. Because the BO doesn’t (and shouldn’t) directly know what it is bound to a simple solution would be to synchronize all threads with a wrapper TBindings.Notify call. Hardly efficient but neither is the necessarily complex LiveBindings system.

  2. Paul Foster says:

    Nice article, just a thought though, what happens when you free a visual control that you have associated with a object – the object being freed is handled but what about (for example) if you dynamically create a form and add a binding to it?

    In quick testing I found that exceptions are raised because the bindings system still contains a reference to a now freed component. Short of making the bound object also track any bound controls (and allowing you to then tell it when they are freed) I can’t see an easy way to work out if a binding references a control that you are about to free.

    • Jarrod Hollingworth says:

      It’s possible for the TBoundObject to get notification that a control is freed by using an internal TComponent descendant and FreeNotification. That won’t work for binding to other objects though (which the current TBoundObject supports) – you could implement a generic observer system in TBoundObject and support both.

      In addition to Bind, UnBind methods are also required in TBoundObject to allow explicit unbinding. One to unbind all expressions for the given object, perhaps a second overload to unbind only those matching the given properties. You could call this when the GUI is freed (in the Form destructor or OnDestroy for example).

      In either case finding the binding expression with the business object as input and control as output is easy. Unfortunately there is no way to find the appropriate reverse binding expression with the control as input and business object as output, as the necessary data is stored in private data in LiveBindings classes. TBoundObject would need to maintain its own list of mappings between ABindToObject and the TBindingExpression pairs so that the binding expressions could be removed in the UnBind call.

      It ‘s starting to sound like a separate bindings manager class is needed to perform all the bind/unbind work.

      • Paul Foster says:

        This is true, it would depend on the requirements I guess. I wonder if this can integrate with GUI livebindings which are created at design time; perhaps all you really need to do is to create the association?

        I’ll have to try it – it would make more sense to me to use the livebinding system as is it in the IDE and then use your BO to bind into known points – during the design process you would choose a schema/dictionary which the UI design would use to control display logic and BO would hook into to feed information in or process it back out – this feels like the right way to be doing these things (to me anyway).

        As an aside I have already started to try and deal with the issue by using a dictionary of bindto’s and expressions; funnily enough I started to build a manager class to deal with it – great minds think alike 🙂

        • Paul Foster says:

          it would appear that Binding Scopes might fit the bill, you can use these to create binding “points” into a UI which can then be filled by a business object at run time. It has the advantage that the binding itself remains managed by the UI code with binding lists etc but still allows you to map in your own objects, wrap the process up in a manager which allows you to register an object to a binding scope (and unregister it later) and I think we have something which would work. Added bonus – the scopes can be shared between UI forms and elements at design time and filled at run time.

          • Jarrod Hollingworth says:

            I did some quick testing with TBindScope – it looks promising.

            I use the Model GUI Mediator (Model View Presenter) framework in tiOPF. With it you can set up the mappings at design-time or runtime without requiring a BO instance. You can then assign the BO once created, or change BO’s quite simply (like having a TListBox display a list of objects and allow the user to select one of them which then easily binds the selected BO to a group of controls for the various properties to display/edit). tiOPF also has a single component that makes it easier to manage the mappings for multiple BO’s on one form. There needs to be a TBindScopeList in LiveBindings for this purpose.

            I released a 3-part video series that demonstrates how MGM in tiOPF works:
            Part 1: http://www.youtube.com/watch?v=FBw_3gXxyeI
            Part 2: http://www.youtube.com/watch?v=cI7XkZ0GR0s
            Part 3: http://www.youtube.com/watch?v=CdsJ6FHs3vM

            The final demo is at 8:40 in part 3. Earlier in the videos I explain how MGM works and develop a simple app from scratch to demonstrate how to use it.

1 Pings/Trackbacks for "Using LiveBindings to Connect the UI to Objects"
  1. […] este grupo Australiano sobre Delphi he encontrado un interesante artículo sobre cómo utilizar los LiveBindings con Objetos. Utilización de eventos de notificación y de creación de expresiones por código. Buen […]