本系列多数时间都是在使用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个按钮的事件处理代码如下:- 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 子句。然后可以像这样编写代码来将编辑框绑定到标签:- BindingsList1.BindComponent(Edit2).ToComponent(Label2, 'Text');
复制代码 如果想让编辑框跟踪更改,添加对 Track 的调用。- BindingsList1.BindComponent(Edit2).Track.ToComponent(Label2, 'Text');
复制代码 如果想在编辑框中显示之前对其进行格式化,请添加 Format 调用:- BindingsList1.BindComponent(Edit2).Format('"Foo " + %s').ToComponent(Label2, 'Text');
复制代码 还可以控制方向:- BindingsList1.BindComponent(Edit2).ToComponent(Label2, 'Text').BiDirectional;
复制代码 也可以直接在两个 Edit 框之间进行绑定,而 LiveBindings 设计器不允许这样做:- BindingsList1.BindComponent(Edit2).ToComponent(Edit3, 'Text').BiDirectional;
复制代码 通过 LiveBindings 设计器可以完成的大多数事情,应该能够使用 Fluent LiveBindings 来完成。
3.2 Fluent LiveBindings的基本结构
Fluent LiveBindings定义了一系列的xxxSource和xxxTarget的接口,然后通过Delphi的类助手 class helper for TBindingsList语法创建了对TBindingsList的扩展,为其添加了链式语法。- 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的实例,以提供链式语法中的下一层代码提示。- 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的定义如下所示:- 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事件如下所示:- // 组件源析构函数实现
- 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的代码如下:- // 绑定源析构函数
- 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将有一个全新的理解。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |