找回密码
 立即注册
首页 业界区 业界 一步一步学习使用LiveBindings(16)使用代码创建LiveBi ...

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

云卦逾 7 天前
本系列多数时间都是在使用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,在设计时可以对和中个绑定进行调整。
1.png

可以单击工具栏上的“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个按钮的事件处理代码如下:
  1. unit uMainForm;
  2. interface
  3. uses
  4.   System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  5.   FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Objects,
  6.   FMX.StdCtrls, FMX.Controls.Presentation, Data.Bind.EngExt, Fmx.Bind.DBEngExt,
  7.   System.Rtti, System.Bindings.Outputs, Fmx.Bind.Editors, Data.Bind.Components,
  8.   FMX.Edit;
  9. type
  10.   TfrmMain = class(TForm)
  11.     ProgressBar1: TProgressBar;
  12.     TrackBar1: TTrackBar;
  13.     ArcDial1: TArcDial;
  14.     Line1: TLine;
  15.     BindingsList1: TBindingsList;
  16.     LinkControlToPropertyValue: TLinkControlToProperty;
  17.     LinkControlToPropertyRotationAngle: TLinkControlToProperty;
  18.     Button1: TButton;
  19.     Button2: TButton;
  20.     Edit1: TEdit;
  21.     Edit2: TEdit;
  22.     procedure Button1Click(Sender: TObject);
  23.     procedure TrackBar1Change(Sender: TObject);
  24.     procedure ArcDial1Change(Sender: TObject);
  25.     procedure Button2Click(Sender: TObject);
  26.     procedure Edit1Change(Sender: TObject);
  27.     procedure Edit2Change(Sender: TObject);
  28.   private
  29.     { Private declarations }
  30.   public
  31.     { Public declarations }
  32.   end;
  33. var
  34.   frmMain: TfrmMain;
  35. implementation
  36. {$R *.fmx}
  37. procedure TfrmMain.ArcDial1Change(Sender: TObject);
  38. begin
  39.    BindingsList1.Notify(Sender,'');              //控件值改变时通知更新状态
  40. end;
  41. procedure TfrmMain.Button1Click(Sender: TObject);
  42. var
  43.   LinkArcDial, LinkTrackBar: TLinkControlToProperty;
  44. begin
  45. // 创建并配置控件到属性的绑定
  46.   LinkArcDial := TLinkControlToProperty.Create(BindingsList1);
  47.   LinkArcDial.Control := Edit1;               // 绑定到Edit1控件
  48.   LinkArcDial.Component := ArcDial1;          // 绑定到目标控件
  49.   LinkArcDial.ComponentProperty := 'Value';  // 绑定到目标控件的属性
  50.   LinkArcDial.Active := True;                 // 激活绑定
  51.   LinkTrackBar := TLinkControlToProperty.Create(BindingsList1);
  52.   LinkTrackBar.Control := Edit1;               //  绑定到Edit1控件
  53.   LinkTrackBar.Component := TrackBar1;         // 绑定到目标控件
  54.   LinkTrackBar.ComponentProperty := 'Value';  // 绑定到目标控件的属性
  55.   LinkTrackBar.Active := True;                 // 激活绑定
  56. end;
  57. procedure TfrmMain.Button2Click(Sender: TObject);
  58. begin
  59.   //使用TBindExpression来创建帮定链接
  60.   with TBindExpression.Create(BindingsList1) do
  61.   begin
  62.     ControlComponent := Edit2;                              //绑定到Edit2控件
  63.     ControlExpression := 'Text';                            //指定控件的表达式为Text属性
  64.     SourceComponent := Edit1;                               //指定源控件为Edit1
  65.     SourceExpression := 'Text';                             //指定源控件的表达式为Text属性
  66.     Direction := TExpressionDirection.dirBidirectional;     //双向绑定
  67.     Active := True;                                         // 激活绑定
  68.   end;
  69. end;
  70. procedure TfrmMain.Edit1Change(Sender: TObject);
  71. begin
  72.   BindingsList1.Notify(Sender,'');      //控件值改变时通知更新状态
  73. end;
  74. procedure TfrmMain.Edit2Change(Sender: TObject);
  75. begin
  76.   BindingsList1.Notify(Sender,'');      //控件值改变时通知更新状态
  77. end;
  78. procedure TfrmMain.TrackBar1Change(Sender: TObject);
  79. begin
  80.   BindingsList1.Notify(Sender,'');       //控件值改变时通知更新状态
  81. end;
  82. end.
复制代码
在控件的值发生改变后,这里调用了BindingsList1.Notify(Sender,'');来通知绑定源数据的变更,在运行时可以看到,果然绑定已经成功定义。
2.gif

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

这是一个第3方的开源的辅助单元,用来帮助开发人员快速写绑定代码的。
可以在github去下载并使用。
https://github.com/malcolmgroves/FluentLiveBindings
3.1 基本用法如下:

在一个已经包含 TBindingsList 的表单中,将 LiveBindings.Fluent 添加到 uses 子句。然后可以像这样编写代码来将编辑框绑定到标签:
  1. BindingsList1.BindComponent(Edit2).ToComponent(Label2, 'Text');
复制代码
如果想让编辑框跟踪更改,添加对 Track 的调用。
  1. BindingsList1.BindComponent(Edit2).Track.ToComponent(Label2, 'Text');
复制代码
如果想在编辑框中显示之前对其进行格式化,请添加 Format 调用:
  1. BindingsList1.BindComponent(Edit2).Format('"Foo " + %s').ToComponent(Label2, 'Text');
复制代码
还可以控制方向:
  1. BindingsList1.BindComponent(Edit2).ToComponent(Label2, 'Text').BiDirectional;
复制代码
也可以直接在两个 Edit 框之间进行绑定,而 LiveBindings 设计器不允许这样做:
  1. BindingsList1.BindComponent(Edit2).ToComponent(Edit3, 'Text').BiDirectional;
复制代码
通过 LiveBindings 设计器可以完成的大多数事情,应该能够使用 Fluent LiveBindings 来完成。
3.2 Fluent LiveBindings的基本结构

Fluent LiveBindings定义了一系列的xxxSource和xxxTarget的接口,然后通过Delphi的类助手 class helper for TBindingsList语法创建了对TBindingsList的扩展,为其添加了链式语法。
  1.   TBindingsListHelper = class helper for TBindingsList
  2.     function BindComponent(const Target : TComponent) : IComponentTarget; // 绑定组件
  3.     function BindList(const Target : TComponent) : IListComponentTarget; virtual; // 绑定列表
  4.     function BindGrid(const Target : TCustomGrid) : IGridTarget; virtual; // 绑定网格
  5.     function BindExpression(const Scope : TComponent; Expression : string) : IExpressionTarget; experimental; // 绑定表达式
  6.   end;
  7.   
复制代码
每当调用BindComponent或BindList...方法时,会先创建一个TBindingState对象实例,用来保存BindComponent传过来的Target控件,然后再返回IComponentTarget的实例,以提供链式语法中的下一层代码提示。
  1.   IComponentTarget = interface // 组件目标接口
  2.   ['{D16A2933-9497-4E8F-AB39-20B3D350D6D6}'] // 接口GUID
  3.     function Format(CustomFormat : string) : IComponentTarget; // 设置自定义格式
  4.     function Parse(CustomParse : string) : IComponentTarget; // 设置自定义解析
  5.     function Track : IComponentTarget; // 启用跟踪
  6.     function ToComponent(Name : TComponent; PropertyName : string): IComponentSource; // 绑定到组件属性
  7.     function ToField(Name : TBindSourceDB; Field : String): IFieldSource; // 绑定到数据库字段
  8.     function ToObject(Name : TAdapterBindSource; Member : string): IObjectSource; // 绑定到对象成员
  9.   end;
复制代码
IComponentTarget提供了绑定到Source控件的一系列语法,比如ToComponent,ToObject和ToField等方法,它们是返回IxxxSource接口,提供了对绑定源的进一步封装。
几种类型的IxxxSource的定义如下所示:
  1.   IComponentSource = interface // 组件源接口
  2.   ['{225A2C76-E6C0-40EC-9396-69150EBE96C8}']
  3.     function Active : IComponentSource; // 激活绑定
  4.     function Inactive : IComponentSource; // 禁用绑定
  5.     function BiDirectional : IComponentSource; // 设置双向绑定
  6.     function FromComponentToSource : IComponentSource; // 设置组件到源的绑定方向
  7.     function FromSourceToComponent : IComponentSource; // 设置源到组件的绑定方向
  8.   end;
  9.   IBindSourceSource = interface // 绑定源接口
  10.   ['{D25D3FE7-9BB1-4E4E-8510-9457762AF067}'] // 接口GUID
  11.     function Active : IBindSourceSource; // 激活绑定
  12.     function Inactive : IBindSourceSource; // 禁用绑定
  13.   end;
  14.   IFieldSource = interface (IBindSourceSource) // 字段源接口(继承自IBindSourceSource)
  15.   ['{9FC9A36D-0DE6-482A-BF93-B32BC377335E}'] // 接口GUID
  16.     function BiDirectional : IFieldSource; // 设置双向绑定
  17.     function FromComponentToData : IFieldSource; // 设置组件到数据的绑定方向
  18.     function FromDataToComponent : IFieldSource; // 设置数据到组件的绑定方向
  19.   end;
  20.   IObjectSource = interface (IBindSourceSource) // 对象源接口(继承自IBindSourceSource)
  21.   ['{FCD6AC1E-CB2D-409D-A433-82E04AD21401}'] // 接口GUID
  22.     function BiDirectional : IObjectSource; // 设置双向绑定
  23.     function FromComponentToData : IObjectSource; // 设置组件到数据的绑定方向
  24.     function FromDataToComponent : IObjectSource; // 设置数据到组件的绑定方向
  25.   end;
  26.   IExpressionSource = interface // 表达式源接口
  27.   ['{56247E48-C735-4FD8-831E-6AB36C0C3C71}'] // 接口GUID
  28.     function Active : IExpressionSource; // 激活绑定
  29.     function Inactive : IExpressionSource; // 禁用绑定
  30.     function BiDirectional : IExpressionSource; // 设置双向绑定
  31.     function FromComponentToData : IExpressionSource; // 设置组件到数据的绑定方向
  32.     function FromDataToComponent : IExpressionSource; // 设置数据到组件的绑定方向
  33.   end;
复制代码
可以看到每一个函数又返回相应的接口,形成了链式的语法。
IComponentTarget的ToComponent,ToObject和ToField等方法会创建相应的TxxxxSource对象,由于并没有返回对象实例给任何调用方,因此在调用之后,基于接口的对象实例Target和Source对象都会被自动释放。
TxxxSource在被释放时,也就是Destroy事件中,分别创建了绑定对象实例。
以TComponentSource为例,它的Destroy事件如下所示:
  1. // 组件源析构函数实现
  2. destructor TComponentSource.Destroy;
  3. var
  4.   LLink : TLinkControlToProperty; // 控件到属性链接对象
  5. begin
  6.   // 如果绑定方向是目标到源或双向
  7.   if (FBindingState.Direction = TBindDirection.TargetToSource) or (FBindingState.Direction = TBindDirection.Bidirectional) then
  8.   begin
  9.     LLink := TLinkControlToProperty.Create(nil); // 创建链接对象
  10.     LLink.BindingsList := FBindingState.BindingsList; // 设置绑定列表
  11.     LLink.Control := TComponent(FBindingState.Target); // 设置控制组件
  12.     LLink.Component := TComponent(FBindingState.Source); // 设置源组件
  13.     LLink.ComponentProperty := FBindingState.PropertyName; // 设置组件属性
  14.     LLink.Track := FBindingState.Track; // 设置跟踪状态
  15.     LLink.CustomFormat := FBindingState.Format; // 设置自定义格式
  16.     LLink.CustomParse := FBindingState.Parse; // 设置自定义解析
  17.     LLink.Active := FBindingState.Active; // 设置激活状态
  18.   end;
  19.   // 如果绑定方向是源到目标或双向
  20.   if (FBindingState.Direction = TBindDirection.SourceToTarget) or (FBindingState.Direction = TBindDirection.Bidirectional) then
  21.   begin
  22.     LLink := TLinkControlToProperty.Create(nil); // 创建链接对象
  23.     LLink.BindingsList := FBindingState.BindingsList; // 设置绑定列表
  24.     LLink.Control := TComponent(FBindingState.Source); // 设置控制组件(反向)
  25.     LLink.Component := TComponent(FBindingState.Target); // 设置目标组件(反向)
  26.     LLink.ComponentProperty := FBindingState.PropertyName; // 设置组件属性
  27.     LLink.Track := FBindingState.Track; // 设置跟踪状态
  28.     LLink.CustomFormat := FBindingState.Format; // 设置自定义格式
  29.     LLink.CustomParse := FBindingState.Parse; // 设置自定义解析
  30.     LLink.Active := FBindingState.Active; // 设置激活状态
  31.   end;
  32.   inherited; // 调用父类析构函数
  33. end;
复制代码
TBindSourceSource.Destroy的代码如下:
  1. // 绑定源析构函数
  2. destructor TBindSourceSource.Destroy;
  3. var
  4.   LGridLink : TLinkGridToDataSource; // 网格到数据源链接对象
  5. begin
  6.   // 如果目标类型是网格
  7.   if FBindingState.TargetType = Grid then
  8.   begin
  9.     LGridLink := TLinkGridToDataSource.Create(nil); // 创建网格链接对象
  10.     LGridLink.BindingsList := FBindingState.BindingsList; // 设置绑定列表
  11.     LGridLink.GridControl := TComponent(FBindingState.Target); // 设置网格控件
  12.     LGridLink.DataSource := TBindSourceDB(FBindingState.FSource); // 设置数据源
  13.     LGridLink.DefaultColumnWidth := FBindingState.DefaultColumnWidth; // 设置默认列宽
  14.     // LGridLink.Columns :=  // 列设置(注释掉的代码)
  15.     LGridLink.Active := FBindingState.Active; // 设置激活状态
  16.   end;
  17.   inherited; // 调用父类析构函数
  18. end;
复制代码
这几个析构函数提供了对于编程实现绑定的极好的例子。
总结

在写这个系列的时候,一直在考虑只把最简单最频繁实操的部分写出来,不要过多的涉及语法糖或者是奇思妙想。不过由于本人没有在LiveBindings上面涉足太多的项目,所以仅限于对于一些资料的简单测试和总结,工作之余进行写作,时间仓促,太多思考也来不及细说。
LiveBindings是非常棒的技术,在C# v2.0 时代已经见到过WinForm提供了类似的绑定技术。不管是哪种技术,不要囿于门户之见,取其精华弃其糟粕,不管现在有没有用,多一技之长有备无患。
本节介绍了如下的知识点:

  • LiveBindings 与 VCL的不同之处。
  • 不使用LiveBindings Designer或Wizard,编程创建绑定。
  • 使用Fluent LiveBindings简化绑定代码
学完这个系列,对于LiveBindings将有一个全新的理解。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册