一步一步学习使用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将有一个全新的理解。

相关推荐
BillKu2 天前
在 Delphi 5 中获取 Word 文档页数的方法
word·delphi
BillKu4 天前
Delphi 5 中操作 Word 表格时禁用鼠标交互
word·delphi
看那山瞧那水5 天前
DELPHI 利用OpenSSL实现加解密,证书(X.509)等功能
delphi·openssl
lincats15 天前
一步一步学习使用FireMonkey动画(6) 用实例理解动画的运行状态
ide·delphi·livebindings·delphi 12.3·firemonkey
lincats15 天前
一步一步学习使用FireMonkey动画(5) 动画图解11种动画插值类型
ide·移动开发·delphi 12.3·firedac·firemonkey
lincats16 天前
一步一步学习使用FireMonkey动画(4) 使用Delphi的基本动画组件类,路径和位图列表动画 弹跳小球和奔跑的小人示例
livebindings·delphi 12.3·firemonkey
lincats16 天前
一步一步学习使用FireMonkey动画(3) 使用Delphi的基本动画组件类
ide·delphi·delphi 12.3·firemonkey
lincats16 天前
一步一步学习使用FireMonkey动画(2) 使用TAnimator类创建动画
ide·delphi 12.3·firedac·firemonkey
lincats17 天前
一步一步学习使用FireMonkey动画(1) 使用动画组件为窗体添加动态效果
android·ide·delphi·livebindings·delphi 12.3·firemonkey