本系列多数时间都是在使用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 在几个方面存在差异:
-
最明显的一点是,与 DataLinks 不同,LiveBindings 不是封装在其他控件中。LiveBindings 是独立的类。通过配置 LiveBinding,可以定义分配给目标组件属性的表达式,并选择源组件和目标组件(尽管通过 LiveBindings 设计器,这个过程基本上是透明的)。
-
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提供了类似的绑定技术。不管是哪种技术,不要囿于门户之见,取其精华弃其糟粕,不管现在有没有用,多一技之长有备无患。
本节介绍了如下的知识点:
- LiveBindings 与 VCL的不同之处。
- 不使用LiveBindings Designer或Wizard,编程创建绑定。
- 使用Fluent LiveBindings简化绑定代码
学完这个系列,对于LiveBindings将有一个全新的理解。