Search code examples
delphidelphi-xe4livebindings

Using TPrototypeBindSource to automatically update properties in an object


I'm trying to use livebindings to update properties of my (non-component) objects. I have a TPrototypeBindSource that I am using to bind components to my objects fields, and using a TObjectBindSourceAdapter at run time. I can get it to work if call MyPrototypeBindSource.Refresh in the onchange event of the edit components, but is there a way to get this to happen automatically, without setting up the onchange event for every component on my form?


Solution

  • Although there is TPrototypeBindSource.AutoPost which I suspect to handle the automatic post of the control data to the data object it does not ... well looking at the source, this property just influence the internal data generator.

    Seems to be, we have to set this property by hand when creating the adapter (and because we are just at this point we will set the AutoEdit as well):

    procedure TForm1.PrototypeBindSource1CreateAdapter( Sender: TObject; var ABindSourceAdapter: TBindSourceAdapter );
    begin
      FPerson := TPerson.Create;
      ABindSourceAdapter := TObjectBindSourceAdapter<TPerson>.Create( Self, FPerson );
      ABindSourceAdapter.AutoEdit := True;
      ABindSourceAdapter.AutoPost := True;
    end;
    

    This will do the job, every time you leave an TEdit but a TCheckBox will post the data immediately.

    To change this just use a published method

    procedure TForm1.ControlChanged( Sender: TObject );
    begin
      if Sender is TComponent
      then
        TLinkObservers.ControlChanged( Sender as TComponent );
    end;
    

    and assign this to each needed control (e.g. TEdit.OnChange) to get the data immediately to the data object.

    Here the whole in one go

    type
      TPerson = class
      private
        FFirstname: string;
        FLastname: string;
        FActive: Boolean;
      public
        function ToString: string; override;
    
        property Active: Boolean read FActive write FActive;
        property Firstname: string read FFirstname write FFirstname;
        property Lastname: string read FLastname write FLastname;
      end;
    
      TForm1 = class( TForm )
        PersonSource: TPrototypeBindSource; { OnCreateAdapter -> PersonSourceCreateAdapter }
        Edit1: TEdit; { OnChange -> ControlChanged }
        Edit2: TEdit; { OnChange -> ControlChanged }
        BindingsList1: TBindingsList;
        LinkControlToField1: TLinkControlToField;
        LinkControlToField2: TLinkControlToField;
        Label1: TLabel;
        ApplicationEvents1: TApplicationEvents; { OnIdle -> ApplicationEvents1Idle }
        CheckBox1: TCheckBox;
        LinkControlToField3: TLinkControlToField;
        procedure PersonSourceCreateAdapter( Sender: TObject; var ABindSourceAdapter: TBindSourceAdapter );
        procedure ApplicationEvents1Idle( Sender: TObject; var Done: Boolean );
      private
        FPerson: TPerson;
      published
        procedure ControlChanged( Sender: TObject );
      end;
    
    var
      Form1: TForm1;
    
    implementation
    
    {$R *.dfm}
    
    procedure TForm1.ApplicationEvents1Idle( Sender: TObject; var Done: Boolean );
    begin
      // just for checking then object data
      Label1.Caption := FPerson.ToString;
    end;
    
    procedure TForm1.ControlChanged( Sender: TObject );
    begin
      if Sender is TComponent
      then
        TLinkObservers.ControlChanged( Sender as TComponent );
    end;
    
    procedure TForm1.PersonSourceCreateAdapter( Sender: TObject; var ABindSourceAdapter: TBindSourceAdapter );
    begin
      FPerson := TPerson.Create;
      ABindSourceAdapter := TObjectBindSourceAdapter<TPerson>.Create( Self, FPerson );
      ABindSourceAdapter.AutoEdit := True;
      ABindSourceAdapter.AutoPost := True;
    end;
    
    { TPerson }
    
    function TPerson.ToString: string;
    begin
      Result := FLastname + ', ' + FFirstname + ' ' + BoolToStr( FActive );
    end;
    

    LiveBindings:

    Active    : ftBoolean -> CheckBox1/CheckedState(Self)
    Firstname : ftString  -> Edit1/Text
    Lastname  : ftString  -> Edit2/Text
    

    If you do not like to assign the ControlChanged method to all controls you can force the TPrototypeBindSource to post the data by calling TPrototypeBindSource.Post. But you have to check first if it is in edit mode:

    if PersonSource.Editing
    then
      PersonSource.Post;
    

    Call this whenever you need to have the data posted ... if at any time just call it within TApplicationEvents.OnIdle.