Last week I introduced Delphi RTL expression engine in the blog post at https://blog.marcocantu.com/blog/2021-may-delphi-expression-engine.html. Now let's make an additional step. I want to explain how components and their properties can be part of these expressions. In this case we don't simply evaluate an expression, but want to associate two separate expressions in a "binding". 

Binding Two Components

In practical terms, suppose you have a form with a SpinEdit and a ProgressBar. A managed binding allows you to provide an input expression (with one or more input objects) and an output expression (again with one or more objects). As you can see in the code below, each object is associate to the expression providing a name, like spin1 for the SpinEdit1 component, and the expression can refer to the object and its properties, as in spin1.Value. The same happens for the output. You can also specify an output converter, which I've omitted here:

  BindingExpression1 := TBindings.CreateManagedBinding(
    { inputs }
    [TBindings.CreateAssociationScope([
      Associate(SpinEdit1, 'spin1')
      ])],
    'spin1.Value',
    { outputs }
    [TBindings.CreateAssociationScope([
      Associate(ProgressBar1, 'progress')
      ])],
    'progress.Position',
    {OutputConverter}
    nil);

With this configuration code, if you call BindingExpression1.Evaluate the value of the SpinEdit will be copied to the ProgressBar. You can make the same call to refresh the value, but you can also abstract the concept by indicating to the bindings architecture that the value property of the spin edit has changes. The system will automatically determine if there is one or more expressions involving the property and refresh it:

procedure TFormBindings.SpinEdit1Change(Sender: TObject);
begin
  TBindings.Notify(Sender, 'Value');
end;

This change of perspective (a value has changed rather than an expression needs to be recalculated) is of a fundamental importance, as it fully abstracts the data model from the UI it is associated with. We'll get to it, but let me show the simple application UI first:

Binding Objects and UI Controls

This is better explained in the second part of the example, which has an edit box bound to an object in memory. The TMyObject class has a Name and a Value property. Here is how you can bind an object to the UI:

  BindingExpressionObj := TBindings.CreateManagedBinding(
    { inputs }
    [TBindings.CreateAssociationScope([
      Associate(MyObj, 'obj')
      ])],
    'obj.Name',
    { outputs }
    [TBindings.CreateAssociationScope([
      Associate(edName, 'edit')
      ])],
    'edit.Text',
    {OutputConverter}
    nil);

In this code MyObj is an object of the TMyObject class. Its Name property is associate with the edName component Text. Again, refreshing the expression updates the output. But the object should have no knowledge of the user interface controls or the UI bindings. In fact, all you need to do to instrument the object is to notify the system that the value has changed:

procedure TMyObject.SetName(const Value: string);
begin
  FName := Value;
  TBindings.Notify(self, 'Name');
end;

With this code, when the data changes, the UI refreshes automatically to reflect it:

procedure TFormBindings.btnUpdateObjClick(Sender: TObject);
begin
  myobj.Name := myobj.Name + 'Monkey';
end;

I'm not 100% sure how you want to call the patterns implemented with bindings in Delphi, but it certainly offers a complete separation and abstraction of the data model with the user interface view.

There is Much More To Explore

Next, we can start looking into how you can work with bi-directional bindings, and how you can define all of the logic above using specific component designers, reducing the code required. There is a lot of technology behind Visual Live Bindings, which is the potential ultimate destination and what we often discuss. Live Bindings are nice, but the binding technology in the Delphi RTL is where the power lives.