本系列多数时间都是在使用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个按钮的事件处理代码如下:

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提供了类似的绑定技术。不管是哪种技术,不要囿于门户之见,取其精华弃其糟粕,不管现在有没有用,多一技之长有备无患。

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

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

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

一步一步学习使用LiveBindings(16)使用代码创建LiveBindings绑定的更多相关文章

  1. 【ASP.NET MVC 学习笔记】- 16 Model Binding(模型绑定)

    本文参考:http://www.cnblogs.com/willick/p/3424188.html. 1.Model Binding是Http请求和Action方法之间的桥梁,是MVC框架根据Htt ...

  2. OpenCV 学习笔记(16)open创建无边框的显示窗口

    https://blog.csdn.net/weixin_41794771/article/details/93198098 讲解地址 // 1获取窗口句柄 winName 窗口名字 HWND win ...

  3. 《Lucene in Action》(第二版) 第一章节的学习总结 ---- 用最少的代码创建索引和搜索

    第一章节是介绍性质,但是通过这一章节的学习,我理解到如下概念: 1.Lucene由两部分组成:索引和搜索.索引是通过对原始数据的解析,形成索引的过程:而搜索则是针对用户输入的查找要求,从索引中找到匹配 ...

  4. 12.Linux软件安装 (一步一步学习大数据系列之 Linux)

    1.如何上传安装包到服务器 有三种方式: 1.1使用图形化工具,如: filezilla 如何使用FileZilla上传和下载文件 1.2使用 sftp 工具: 在 windows下使用CRT 软件 ...

  5. (转) 一步一步学习ASP.NET 5 (四)- ASP.NET MVC 6四大特性

    转发:微软MVP 卢建晖 的文章,希望对大家有帮助.原文:http://blog.csdn.net/kinfey/article/details/44459625 编者语 : 昨晚写好的文章居然csd ...

  6. (转) 一步一步学习ASP.NET 5 (二)- 通过命令行和sublime创建项目

    转发:微软MVP 卢建晖 的文章,希望对大家有帮助. 注:昨天转发之后很多朋友指出了vNext的命名问题,原文作者已经做出了修改,后面的标题都适用 asp.net 5这个名称. 编者语 : 昨天发了第 ...

  7. 一步一步学习SignalR进行实时通信_1_简单介绍

    一步一步学习SignalR进行实时通信\_1_简单介绍 SignalR 一步一步学习SignalR进行实时通信_1_简单介绍 前言 SignalR介绍 支持的平台 相关说明 OWIN 结束语 参考文献 ...

  8. 一步一步学习SignalR进行实时通信_8_案例2

    原文:一步一步学习SignalR进行实时通信_8_案例2 一步一步学习SignalR进行实时通信\_8_案例2 SignalR 一步一步学习SignalR进行实时通信_8_案例2 前言 配置Hub 建 ...

  9. 一步一步学习SignalR进行实时通信_9_托管在非Web应用程序

    原文:一步一步学习SignalR进行实时通信_9_托管在非Web应用程序 一步一步学习SignalR进行实时通信\_9_托管在非Web应用程序 一步一步学习SignalR进行实时通信_9_托管在非We ...

  10. 一步一步学习SignalR进行实时通信_7_非代理

    原文:一步一步学习SignalR进行实时通信_7_非代理 一步一步学习SignalR进行实时通信\_7_非代理 SignalR 一步一步学习SignalR进行实时通信_7_非代理 前言 代理与非代理 ...

随机推荐

  1. HanTTS简单文档

    先下载 https://gitee.com/dhfhub/HanTTS 然后输入命令 main.py synthesize --text 你的话 --src "syllables/" ...

  2. GHCTF 2025 web 萌新初探wp

    ctf萌新第一次写wp,如有错误请师傅们指出 [GHCTF 2025]SQL??? 打开靶机是一个用户查询的页面,结合题目名称猜测是sql注入,但是常规方法都试过了没办法注入,当时也是很懵逼,后来一个 ...

  3. 鸿蒙Next仓颉语言开发实战教程:懒加载

    今天要分享的是仓颉开发语言中的懒加载. 先和初学者朋友们解释一下什么是懒加载.懒加载在代码中叫做LazyForEach,看到名字你一定能猜到它和ForEach的功能类似.只不过和ForEach的一次性 ...

  4. 数栈云MSP运维服务案例:某客户生产服务器CPU异常抖动

    一.问题背景 某日袋鼠云运维小哥进行例行运维巡检,通过监控视图发现客户应用服务器cpu使用率突然呈上升趋势.通过专属服务群第一时间与业务方联系,与业务方确认是否有正在执行的定时任务,或者大范围拉取账单 ...

  5. ChunJun 顺利晋级“2022 年中国开源创新大赛”决赛,并荣获“优秀开源项目/社区”奖项

    日前,"2022年中国开源创新大赛"初赛晋级名单公布,易用.稳定.高效的批流统一的数据集成框架 ChunJun顺利晋级决赛,并荣获"2022年中国开源创新大赛优秀开源项目 ...

  6. Function AI 工作流发布:以 AI 重塑企业流程自动化

    AI 工作流如何重塑企业自动化流程 在 AI 技术飞速发展的今天,企业的流程自动化方式也正在发生深刻变革.过去,流程自动化往往依赖于人工配置和固定规则,难以适应复杂.多变的业务场景.而如今, 随着 L ...

  7. ArkUI-X通过Stage模型开发Android端应用指南(一)

    简介 本文介绍将ArkUI框架扩展到Android平台所需要的必要的类及其使用说明,开发者基于OpenHarmony,可复用大部分的应用代码(生命周期等)并可以部署到Android平台,降低跨平台应用 ...

  8. MongoDB入门实战教程:学习总结目录

    2021年Edison总结了MongoDB的入门实战教程,2022年整理了一份目录索引,希望对你有帮助. 1 MongoDB学习路径 在去年学习<MongoDB高手课>的途中,我总结了一个 ...

  9. Excel中的数字转文本和文本转数字

    公式方法: 数字转文本: =TEXT(A1,"?") 文本转数字: 直接乘以1即可 数字转文本: =A1*1 或者使用value函数 =value() 分列方法: 在数据工具&qu ...

  10. 在 django-ninja 中实现类似腾讯阿里云的应用鉴权机制

    前言 本文章介绍如何使用基于 AppClient 模型的 Django-Ninja API 鉴权机制. 这也是上次说的中台项目衍生物 中台项目相关的文章,我大概还会再写一篇 这个系列的文章注定是没什么 ...