Need bidirectional LiveBindings between a control and an object
Asked Answered
F

1

20

In Delphi XE2 LiveBindings, I need to bind a VCL control of any type to a property of any type on an arbitrary (non-component) object. I can do this unidirectionally. But I need to do it bidirectionally.

Let's say I want to bind a TPerson.PersonName: string to a TEdit.Text.

What I have now is simple.

  • Create a new VCL application, add a TBindScope, TBindingsList, TEdit.
  • Create an instance of TPerson named person1.
  • Using a BindingList, add a TBindExpression property.
  • With BindExpression
    • set ControlComponent to Edit1
    • set ControlExpression to 'Text'
    • set SourceComponent to BindScope1
    • set SourceExpression to PersonName
  • Add a button; to the Click event I add: BindScope1.DataObject := person1;
  • Add a button; to the Click event I add (only one is necessary, but until it works I will try them both).
    • TBindings.Notify(sender, '');
    • BindingsList1.Notify(sender, '');

The first button binds in the first direction. The second button never seems to write the value back to the person1.PersonName property.

I've experimented with the notification code, the binding direction, binding types, expressions, SourceMember, etc. Sometimes I get runtime errors in the bindexpression configuration, the rest of the time the binding is simply unidirectional.

I expect to click the second button and see the contents of Edit1.Text written to person1.PersonName.

If I have to do this all from code, I'll consider it and such examples are welcome, but I really want to do it through the designer if possible.

Note that I am not interested in binding between two controls.

Note too that I have already downloaded and inspected the LiveBinding sample projects, and didn't find any that do this. If this is wrong, please be specific when pointing it out. I have also read the DocWiki. It does not cover bidirectional binding except using the DB LiveBinding controls. I am not using the DB LiveBinding controls nor am I using a DataSet. So unless you can explain to me why I should use them, I won't be needing any information about those controls.

Fulcrum answered 20/9, 2011 at 0:19 Comment(1)
Use DSharp instead of LiveBindings and make this a two liner (one for implementing the setter of a property that should be binding aware and one for creating the binding)Hemiterpene
F
16

I've got this working now. I did it all in the designer, but have converted it to mostly code to share it better on SO.

Create a VCL forms project. On the form, drop each of these on the form:

TBindScope TBindingsList TButton TButton TEdit

Rename one of the buttons to btnLoad and the other to btnSave.

Paste this code over your form unit (assuming it is named Form1). Assign the click handlers for the buttons and run it. Click btnLoad to populate the edit box with TPerson object data, edit the text in the edit box to a new value then click btnSave to write it back to the TPerson object.

unit Form1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, System.Rtti,

  // LiveBinding units
  System.Bindings.Helper,     // Contains TBindings class
  Data.Bind.EngExt,
  Vcl.Bind.DBEngExt,
  Data.Bind.Components,
  System.Bindings.Outputs;

type
  TPerson = class(TObject)
  protected
    fName: string;
    fAge: integer;
    procedure SetName(const Value: string);
  public
    property Name: string read fName write SetName;
    property Age: integer read fAge write fAge;
  end;

type
  TForm1 = class(TForm)
    btnLoad: TButton;
    btnSave: TButton;
    BindScope1: TBindScope;
    BindingsList1: TBindingsList;
    Edit1: TEdit;
    procedure btnLoadClick(Sender: TObject);
    procedure btnSaveClick(Sender: TObject);
  private
    fInitialized: boolean;
    fPerson: TPerson;
    procedure Initialize;
  public
    procedure AfterConstruction; override;
    procedure BeforeDestruction; override;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.AfterConstruction;
begin
  inherited;
  Initialize;
end;

procedure TForm1.BeforeDestruction;
begin
  fPerson.Free;
  inherited;
end;

procedure TForm1.btnLoadClick(Sender: TObject);
begin
  fPerson.Name := 'Doogie Howser';
  fPerson.Age := 15;
  BindScope1.DataObject := fPerson;
end;

procedure TForm1.btnSaveClick(Sender: TObject);
begin
  TBindings.Notify(Edit1, '');

  // Could also do this:
  //BindingsList1.Notify(Edit1, '');
end;

procedure TForm1.Initialize;
var
  expression: TBindExpression;
begin
  // Create a binding expression.
  expression := TBindExpression.Create(self);
  expression.ControlComponent := Edit1;
  expression.ControlExpression := 'Text';   // The Text property of Edit1 ...
  expression.SourceComponent := BindScope1;
  expression.SourceExpression := 'Name';    // ... is bound to the Name property of fPerson
  expression.Direction := TExpressionDirection.dirBidirectional;

  // Add the expression to the bindings list.
  expression.BindingsList := BindingsList1;

  // Create a Person object.
  fPerson := TPerson.Create;
end;

{ TPerson }

procedure TPerson.SetName(const Value: string);
begin
  fName := Value;
  ShowMessage('Name changed to "'+ Value +'"');
end;

end.
Fulcrum answered 21/9, 2011 at 22:13 Comment(4)
TBindScope no longer available as design time component since XE6Geognosy
But still available at run-timeMede
TBindScope available again at design-time in RAD Studio 10.4Handsome
Replacing the TBindScope by a TPrototypeBindSource gives a more accurate relation to the TPerson, and the binding between the TControl and the TPrototypeBindSource can be done field to field, at design-timeHandsome

© 2022 - 2024 — McMap. All rights reserved.