一步一步学习使用LiveBindings(16)使用代码创建LiveBindings绑定

本系列多数时间都是在使用LiveBindings Wizard或LiveBindings Designer来创建链接,在《一步一步学习使用LiveBindings(8)》节起,介绍了几种快速绑定的类型,借助于绑定向导,完成了多数复杂的工作。

由于LiveBinding是一项范围广范的技术,在这里总结了一些关于LiveBindings相关的知识点。

1. LiveBindings与VCL数据绑定的差异

1.1 LiveBindings基础

LiveBindings最初在Delphi XE2中引入,是一种特殊的类,其作用是将字符串表达式与类的属性相关联,这些表达式会在运行时由Delphi的表达式引擎进行计算。被计算的表达式通常包含来自另一个类的数据,这些数据通过读取属性或执行方法获得(甚至可以包含读取多个属性和/或执行多个方法)。

LiveBinding的功能是将与一个类相关联的数据分配给另一个类,从而使目标类具备数据感知能力。

传统的VCL封装了一系列的数据感知TDBxxxx控件,这些数据感知控件通过TDataSource控件与数据源数据比如TQuery或TTable数据集组件进行连接。而TDataSource则使用了一系列的TDataLink类来处理数据感知控件与数据源的连接。

LiveBindings 的引入是为了在 FireMonkey 组件中提供数据感知能力,而 FireMonkey 组件并非设计用来支持通过 DataLinks 实现的数据感知。然而,LiveBindings 也适用于 VCL,并且可以用于几乎任何类,而不仅仅是支持 DataLinks 的类。

1.2 LiveBindings与VCL数据绑定的差异

DataLinks 和 LiveBindings 在几个方面存在差异:

  1. 最明显的一点是,与 DataLinks 不同,LiveBindings 不是封装在其他控件中。LiveBindings 是独立的类。通过配置 LiveBinding,可以定义分配给目标组件属性的表达式,并选择源组件和目标组件(尽管通过 LiveBindings 设计器,这个过程基本上是透明的)。

  2. DataLinks 和 LiveBindings 之间的第二个主要区别在于,DataLinks 将数据感知控件连接到 DataSource。相比之下,LiveBindings 通过 BindSource 访问底层的 DataSet,而 BindSource 几乎总是 BindSourceDB 类的实例。BindSourceDB 类的设计目的是通过属性使 LiveBinding 和表达式引擎能够访问其关联数据集中的 Fields 和其他相关数据。

1.3 LiveBindings的核心TBindingList

由于不再具有类似VCL的TDBxxxx数据感知控件,为了保存绑定表达式,Delphi提供了TBindingList控件。

每次使用对象检查器创建新的 LiveBinding 时,TBindingsList 组件将自动放置在的表单上(无论是 VCL 表单还是 FMX 表单)。

所有的绑定链接或绑定表达式都保存在TBindingList控件中,它提供了一个非常有用的Bindings List Editor,在设计时可以对和中个绑定进行调整。

可以单击工具栏上的"New"按钮来创建LiveBinding。可以看到它分为"Quick Bindings"、"Binding Expressions"和"Links"为主。

  • Quick Bindings类型是LiveBindings Designer中使用的高阶绑定类型,属于较常用类型。它会自动生成表达式代码,并且绑定上表达式为只读,大大降低了绑定的使用门槛。

  • Links提供了类似TDataLinks这样的功能,可以完成从组件到数据源的链接。

  • Binding Expressions属于低阶类型,当需要对绑定表达式进行高度定制的功能时,才使用Binding Expressions。它也是前面2种类型的基础类型。也就是说LiveBindings的核心是绑定表达式。

Quick Bindings组件能自动生成表达式,而其他组件需手动编辑表达式。因此在快速绑定组件的表达式编辑器中,您仅可查看自动生成的表达式,无法直接修改。

每个Quick Bindings组件都委托给另一个LiveBindings组件执行任务:

委托组件负责执行表达式并监控用户输入

Quick Bindings组件负责向委托组件生成正确的表达式字符串

具体委托关系如下:

  • TLinkPropertyToField → TCustomBindLink
  • TLinkListControlToField → TCustomBindListLink
  • TLinkControlToProperty → TCustomBindControlValue

2. 编程生成LiveBindings绑定

当你的控件并不固定,可能是动态生成的控件时,可能就需要在控件生成之后,立即编写绑定语法。

接下来在《一步一步学习使用LiveBindings(2)》的项目的基础上,添加了2个Edit控件和2个按钮,演示了如何单击按钮来编程创建绑定,2个按钮的事件处理代码如下:

Pascal 复制代码
unit uMainForm;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Objects,
  FMX.StdCtrls, FMX.Controls.Presentation, Data.Bind.EngExt, Fmx.Bind.DBEngExt,
  System.Rtti, System.Bindings.Outputs, Fmx.Bind.Editors, Data.Bind.Components,
  FMX.Edit;

type
  TfrmMain = class(TForm)
    ProgressBar1: TProgressBar;
    TrackBar1: TTrackBar;
    ArcDial1: TArcDial;
    Line1: TLine;
    BindingsList1: TBindingsList;
    LinkControlToPropertyValue: TLinkControlToProperty;
    LinkControlToPropertyRotationAngle: TLinkControlToProperty;
    Button1: TButton;
    Button2: TButton;
    Edit1: TEdit;
    Edit2: TEdit;
    procedure Button1Click(Sender: TObject);
    procedure TrackBar1Change(Sender: TObject);
    procedure ArcDial1Change(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Edit1Change(Sender: TObject);
    procedure Edit2Change(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  frmMain: TfrmMain;

implementation

{$R *.fmx}

procedure TfrmMain.ArcDial1Change(Sender: TObject);
begin
   BindingsList1.Notify(Sender,'');              //控件值改变时通知更新状态
end;

procedure TfrmMain.Button1Click(Sender: TObject);
var
  LinkArcDial, LinkTrackBar: TLinkControlToProperty;
begin
 // 创建并配置控件到属性的绑定
  LinkArcDial := TLinkControlToProperty.Create(BindingsList1);
  LinkArcDial.Control := Edit1;               // 绑定到Edit1控件
  LinkArcDial.Component := ArcDial1;          // 绑定到目标控件
  LinkArcDial.ComponentProperty := 'Value';  // 绑定到目标控件的属性
  LinkArcDial.Active := True;                 // 激活绑定

  LinkTrackBar := TLinkControlToProperty.Create(BindingsList1);
  LinkTrackBar.Control := Edit1;               //  绑定到Edit1控件
  LinkTrackBar.Component := TrackBar1;         // 绑定到目标控件
  LinkTrackBar.ComponentProperty := 'Value';  // 绑定到目标控件的属性
  LinkTrackBar.Active := True;                 // 激活绑定
end;

procedure TfrmMain.Button2Click(Sender: TObject);
begin
  //使用TBindExpression来创建帮定链接
  with TBindExpression.Create(BindingsList1) do
  begin
    ControlComponent := Edit2;                              //绑定到Edit2控件
    ControlExpression := 'Text';                            //指定控件的表达式为Text属性
    SourceComponent := Edit1;                               //指定源控件为Edit1
    SourceExpression := 'Text';                             //指定源控件的表达式为Text属性
    Direction := TExpressionDirection.dirBidirectional;     //双向绑定
    Active := True;                                         // 激活绑定
  end;
end;

procedure TfrmMain.Edit1Change(Sender: TObject);
begin
  BindingsList1.Notify(Sender,'');      //控件值改变时通知更新状态
end;

procedure TfrmMain.Edit2Change(Sender: TObject);
begin
  BindingsList1.Notify(Sender,'');      //控件值改变时通知更新状态
end;

procedure TfrmMain.TrackBar1Change(Sender: TObject);
begin
  BindingsList1.Notify(Sender,'');       //控件值改变时通知更新状态
end;

end.

在控件的值发生改变后,这里调用了BindingsList1.Notify(Sender,'');来通知绑定源数据的变更,在运行时可以看到,果然绑定已经成功定义。

在TBindExpression.Create之后,需要指定ControlExpression和SourceExpression,这是两个表达式,g还可以使用TBindingsList.Methods 提供的表达式函数。

3. 使用Livebindings.Fluent.pas

这是一个第3方的开源的辅助单元,用来帮助开发人员快速写绑定代码的。

可以在github去下载并使用。
https://github.com/malcolmgroves/FluentLiveBindings

3.1 基本用法如下:

在一个已经包含 TBindingsList 的表单中,将 LiveBindings.Fluent 添加到 uses 子句。然后可以像这样编写代码来将编辑框绑定到标签:

Pascal 复制代码
BindingsList1.BindComponent(Edit2).ToComponent(Label2, 'Text');

如果想让编辑框跟踪更改,添加对 Track 的调用。

Pascal 复制代码
BindingsList1.BindComponent(Edit2).Track.ToComponent(Label2, 'Text');

如果想在编辑框中显示之前对其进行格式化,请添加 Format 调用:

Pascal 复制代码
BindingsList1.BindComponent(Edit2).Format('"Foo " + %s').ToComponent(Label2, 'Text');

还可以控制方向:

Pascal 复制代码
BindingsList1.BindComponent(Edit2).ToComponent(Label2, 'Text').BiDirectional;

也可以直接在两个 Edit 框之间进行绑定,而 LiveBindings 设计器不允许这样做:

Pascal 复制代码
BindingsList1.BindComponent(Edit2).ToComponent(Edit3, 'Text').BiDirectional;

通过 LiveBindings 设计器可以完成的大多数事情,应该能够使用 Fluent LiveBindings 来完成。

3.2 Fluent LiveBindings的基本结构

Fluent LiveBindings定义了一系列的xxxSource和xxxTarget的接口,然后通过Delphi的类助手 class helper for TBindingsList语法创建了对TBindingsList的扩展,为其添加了链式语法。

Pascal 复制代码
  TBindingsListHelper = class helper for TBindingsList
    function BindComponent(const Target : TComponent) : IComponentTarget; // 绑定组件
    function BindList(const Target : TComponent) : IListComponentTarget; virtual; // 绑定列表
    function BindGrid(const Target : TCustomGrid) : IGridTarget; virtual; // 绑定网格
    function BindExpression(const Scope : TComponent; Expression : string) : IExpressionTarget; experimental; // 绑定表达式
  end;
  

每当调用BindComponent或BindList...方法时,会先创建一个TBindingState对象实例,用来保存BindComponent传过来的Target控件,然后再返回IComponentTarget的实例,以提供链式语法中的下一层代码提示。

Pascal 复制代码
  IComponentTarget = interface // 组件目标接口
  ['{D16A2933-9497-4E8F-AB39-20B3D350D6D6}'] // 接口GUID
    function Format(CustomFormat : string) : IComponentTarget; // 设置自定义格式
    function Parse(CustomParse : string) : IComponentTarget; // 设置自定义解析
    function Track : IComponentTarget; // 启用跟踪
    function ToComponent(Name : TComponent; PropertyName : string): IComponentSource; // 绑定到组件属性
    function ToField(Name : TBindSourceDB; Field : String): IFieldSource; // 绑定到数据库字段
    function ToObject(Name : TAdapterBindSource; Member : string): IObjectSource; // 绑定到对象成员
  end;

IComponentTarget提供了绑定到Source控件的一系列语法,比如ToComponent,ToObject和ToField等方法,它们是返回IxxxSource接口,提供了对绑定源的进一步封装。

几种类型的IxxxSource的定义如下所示:

Pascal 复制代码
  IComponentSource = interface // 组件源接口
  ['{225A2C76-E6C0-40EC-9396-69150EBE96C8}']
    function Active : IComponentSource; // 激活绑定
    function Inactive : IComponentSource; // 禁用绑定
    function BiDirectional : IComponentSource; // 设置双向绑定
    function FromComponentToSource : IComponentSource; // 设置组件到源的绑定方向
    function FromSourceToComponent : IComponentSource; // 设置源到组件的绑定方向
  end;

  IBindSourceSource = interface // 绑定源接口
  ['{D25D3FE7-9BB1-4E4E-8510-9457762AF067}'] // 接口GUID
    function Active : IBindSourceSource; // 激活绑定
    function Inactive : IBindSourceSource; // 禁用绑定
  end;

  IFieldSource = interface (IBindSourceSource) // 字段源接口(继承自IBindSourceSource)
  ['{9FC9A36D-0DE6-482A-BF93-B32BC377335E}'] // 接口GUID
    function BiDirectional : IFieldSource; // 设置双向绑定
    function FromComponentToData : IFieldSource; // 设置组件到数据的绑定方向
    function FromDataToComponent : IFieldSource; // 设置数据到组件的绑定方向
  end;

  IObjectSource = interface (IBindSourceSource) // 对象源接口(继承自IBindSourceSource)
  ['{FCD6AC1E-CB2D-409D-A433-82E04AD21401}'] // 接口GUID
    function BiDirectional : IObjectSource; // 设置双向绑定
    function FromComponentToData : IObjectSource; // 设置组件到数据的绑定方向
    function FromDataToComponent : IObjectSource; // 设置数据到组件的绑定方向
  end;

  IExpressionSource = interface // 表达式源接口
  ['{56247E48-C735-4FD8-831E-6AB36C0C3C71}'] // 接口GUID
    function Active : IExpressionSource; // 激活绑定
    function Inactive : IExpressionSource; // 禁用绑定
    function BiDirectional : IExpressionSource; // 设置双向绑定
    function FromComponentToData : IExpressionSource; // 设置组件到数据的绑定方向
    function FromDataToComponent : IExpressionSource; // 设置数据到组件的绑定方向
  end;

可以看到每一个函数又返回相应的接口,形成了链式的语法。

IComponentTarget的ToComponent,ToObject和ToField等方法会创建相应的TxxxxSource对象,由于并没有返回对象实例给任何调用方,因此在调用之后,基于接口的对象实例Target和Source对象都会被自动释放。

TxxxSource在被释放时,也就是Destroy事件中,分别创建了绑定对象实例。

以TComponentSource为例,它的Destroy事件如下所示:

Pascal 复制代码
// 组件源析构函数实现
destructor TComponentSource.Destroy;
var
  LLink : TLinkControlToProperty; // 控件到属性链接对象
begin
  // 如果绑定方向是目标到源或双向
  if (FBindingState.Direction = TBindDirection.TargetToSource) or (FBindingState.Direction = TBindDirection.Bidirectional) then
  begin
    LLink := TLinkControlToProperty.Create(nil); // 创建链接对象
    LLink.BindingsList := FBindingState.BindingsList; // 设置绑定列表
    LLink.Control := TComponent(FBindingState.Target); // 设置控制组件
    LLink.Component := TComponent(FBindingState.Source); // 设置源组件
    LLink.ComponentProperty := FBindingState.PropertyName; // 设置组件属性
    LLink.Track := FBindingState.Track; // 设置跟踪状态
    LLink.CustomFormat := FBindingState.Format; // 设置自定义格式
    LLink.CustomParse := FBindingState.Parse; // 设置自定义解析
    LLink.Active := FBindingState.Active; // 设置激活状态
  end;

  // 如果绑定方向是源到目标或双向
  if (FBindingState.Direction = TBindDirection.SourceToTarget) or (FBindingState.Direction = TBindDirection.Bidirectional) then
  begin
    LLink := TLinkControlToProperty.Create(nil); // 创建链接对象
    LLink.BindingsList := FBindingState.BindingsList; // 设置绑定列表
    LLink.Control := TComponent(FBindingState.Source); // 设置控制组件(反向)
    LLink.Component := TComponent(FBindingState.Target); // 设置目标组件(反向)
    LLink.ComponentProperty := FBindingState.PropertyName; // 设置组件属性
    LLink.Track := FBindingState.Track; // 设置跟踪状态
    LLink.CustomFormat := FBindingState.Format; // 设置自定义格式
    LLink.CustomParse := FBindingState.Parse; // 设置自定义解析
    LLink.Active := FBindingState.Active; // 设置激活状态
  end;

  inherited; // 调用父类析构函数
end;

TBindSourceSource.Destroy的代码如下:

Pascal 复制代码
// 绑定源析构函数
destructor TBindSourceSource.Destroy;
var
  LGridLink : TLinkGridToDataSource; // 网格到数据源链接对象
begin
  // 如果目标类型是网格
  if FBindingState.TargetType = Grid then
  begin
    LGridLink := TLinkGridToDataSource.Create(nil); // 创建网格链接对象
    LGridLink.BindingsList := FBindingState.BindingsList; // 设置绑定列表
    LGridLink.GridControl := TComponent(FBindingState.Target); // 设置网格控件
    LGridLink.DataSource := TBindSourceDB(FBindingState.FSource); // 设置数据源
    LGridLink.DefaultColumnWidth := FBindingState.DefaultColumnWidth; // 设置默认列宽
    // LGridLink.Columns :=  // 列设置(注释掉的代码)
    LGridLink.Active := FBindingState.Active; // 设置激活状态
  end;

  inherited; // 调用父类析构函数
end;

这几个析构函数提供了对于编程实现绑定的极好的例子。

总结

在写这个系列的时候,一直在考虑只把最简单最频繁实操的部分写出来,不要过多的涉及语法糖或者是奇思妙想。不过由于本人没有在LiveBindings上面涉足太多的项目,所以仅限于对于一些资料的简单测试和总结,工作之余进行写作,时间仓促,太多思考也来不及细说。

LiveBindings是非常棒的技术,在C# v2.0 时代已经见到过WinForm提供了类似的绑定技术。不管是哪种技术,不要囿于门户之见,取其精华弃其糟粕,不管现在有没有用,多一技之长有备无患。

本节介绍了如下的知识点:

  1. LiveBindings 与 VCL的不同之处。
  2. 不使用LiveBindings Designer或Wizard,编程创建绑定。
  3. 使用Fluent LiveBindings简化绑定代码

学完这个系列,对于LiveBindings将有一个全新的理解。

相关推荐
lincats3 天前
一步一步学习使用LiveBindings(15)TListView进阶使用(3),创建自定义的列表项打造天气预报程序
delphi 12.3·firedac·firemonkey·tlistview
lincats3 天前
一步一步学习使用LiveBindings(14)TListView进阶使用(2),打造天气预报程序
delphi·livebindings·delphi 12.3·firedac·firemonkey·tlistview
lincats5 天前
一步一步学习使用LiveBindings(13) TListView的进阶使用(1)
delphi·livebindings·delphi 12.3·firemonkey·tlistview
lincats6 天前
一步一步学习使用LiveBindings(12) LiveBindings与具有动态呈现的TListView
delphi·livebindings·delphi 12.3·firemonkey
chilavert3187 天前
技术演进中的开发沉思-62 DELPHI VCL系列:VCL下的设计模式
开发语言·delphi
lincats8 天前
一步一步学习使用LiveBindings(11) 绑定到自定义外观的ListBox
list·delphi·delphi 12.3·firedac·firemonkey·tlistview
lincats9 天前
# 一步一步学习使用LiveBindings(10) LiveBindings绑定到漂亮的TCombobox
ide·delphi·livebindings·delphi 12.3
lincats10 天前
一步一步学习使用LiveBindings(9) LiveBindings图像绑定与自定义绑定方法(2)
delphi·livebindings·delphi 12.3·firedac·firemonkey
lincats12 天前
一步一步学习使用LiveBindings(8) 使用向导创建用户界面,绑定格式化入门
delphi·livebindings·delphi 12.3·firedac·firemonkey