一步一步学习使用LiveBindings() 使用TAdapterBindSource实现对象绑定
一步一步学习使用LiveBindings:使用TAdapterBindSource实现对象绑定
LiveBindings 是 RAD Studio(Delphi/C++Builder)中一套强大的数据绑定框架,它允许你在 UI 控件与数据源之间建立声明式连接,无需编写繁琐的事件处理代码。而TAdapterBindSource是其中极为关键的一环——它充当“适配器”,将任意对象(如类实例、记录、甚至嵌套属性)转换为 LiveBindings 可识别的数据流。本文将从原理出发,通过可运行的示例,带你逐步掌握TAdapterBindSource的使用。### 为什么需要 TAdapterBindSource?在 LiveBindings 生态中,TBindSourceDB用于绑定数据集(如TFDQuery),而TAdapterBindSource则用于绑定非数据集对象。它的核心价值在于:-统一接口:将普通对象的属性暴露为字段,供TBindingsList或TBindScope使用。-双向绑定:支持从 UI 写回对象属性,自动处理类型转换。-动态字段:可在设计期或运行期定义字段映射。TAdapterBindSource内部使用TAdapter模式:它维护一个内部列表(FAdapter),每个元素对应一个对象实例,而字段则映射到对象的属性(通过 RTTI 或手动指定)。### 环境准备- Delphi 10.3+ 或 C++Builder(以下示例以 Delphi 为例)- 创建一个 VCL 或 FMX 应用,放置以下控件: -TAdapterBindSource(组件面板 → Data Binding) -TBindingsList(组件面板 → Data Binding) - 两个TEdit(输入姓名、年龄) - 一个TButton(触发写入)### 第一步:定义对象类型我们创建一个简单的TEmployee类,包含Name和Age属性。注意属性必须声明为 published,以便 RTTI 访问。pascaltype TEmployee = class private FName: string; FAge: Integer; published property Name: string read FName write FName; property Age: Integer read FAge write FAge; end;### 第二步:设计期配置 TAdapterBindSource1. 双击TAdapterBindSource,打开“AdapterBindSource 编辑器”。2. 在“Fields”页签,点击“Add Field”两次,分别设置: - FieldName =Name,DataType =string- FieldName =Age,DataType =Integer3. 关闭编辑器。此时AdapterBindSource1已定义了两个逻辑字段。### 第三步:建立 UI 与字段的绑定在TBindingsList中创建两条绑定表达式:-Edit1.Text←→AdapterBindSource1.Name-Edit2.Text←→AdapterBindSource1.Age操作方式:在设计器中选择TBindingsList,右键 → “Binding 编辑器”,添加两条绑定。### 第四步:运行期绑定对象实例现在,我们需要将实际的TEmployee对象“挂载”到AdapterBindSource上。核心方法是AdapterBindSource1.AddObject或设置AdapterBindSource1.AutoEdit等属性。以下代码展示了完整流程:pascalprocedure TForm1.FormCreate(Sender: TObject);var Emp: TEmployee;begin // 创建对象实例 Emp := TEmployee.Create; Emp.Name := '张三'; Emp.Age := 30; // 将对象添加到 AdapterBindSource AdapterBindSource1.AddObject(Emp); // 激活绑定 AdapterBindSource1.Active := True; // 此时 Edit1.Text 自动变为 '张三',Edit2.Text 变为 '30'end;注意:AddObject会将对象加入内部列表,但AdapterBindSource默认只显示当前记录。若需管理多个对象,可结合Navigate方法。### 第五步:从 UI 写回对象属性LiveBindings 支持双向同步。当用户修改Edit1.Text时,需要触发写入。最简单的方式是设置TBindingsList的NotifyUpdates为True,或使用TEdit.OnChange事件。pascalprocedure TForm1.Edit1Change(Sender: TObject);begin // 强制将 UI 值同步到对象 BindingsList1.Notify(Sender, Edit1);end;或者,使用TAdapterBindSource的Edit和Post方法:pascalprocedure TForm1.Button1Click(Sender: TObject);begin // 开始编辑当前对象 AdapterBindSource1.Edit; // 更新字段值(通过绑定自动完成,但也可手动赋值) AdapterBindSource1.CurrentFields['Name'].Value := Edit1.Text; AdapterBindSource1.CurrentFields['Age'].Value := StrToInt(Edit2.Text); // 提交修改 AdapterBindSource1.Post;end;实际上,如果绑定表达式正确,Edit1.Text和Edit2.Text的变化会自动反映到对象中,无需手动干预。但为了演示“写回”原理,上述代码展示了底层操作。### 进阶:动态绑定与嵌套属性TAdapterBindSource不仅支持简单属性,还能绑定嵌套对象。例如,假设TEmployee有一个Department属性,其类型为TDepartment,而TDepartment有DeptName属性。我们可以这样定义字段:pascaltype TDepartment = class private FDeptName: string; published property DeptName: string read FDeptName write FDeptName; end; TEmployee = class private FName: string; FAge: Integer; FDept: TDepartment; public constructor Create; destructor Destroy; override; published property Name: string read FName write FName; property Age: Integer read FAge write FAge; property Dept: TDepartment read FDept write FDept; end;在AdapterBindSource的字段编辑器中,添加字段时路径可写为Dept.DeptName,LiveBindings 会自动解析嵌套属性。### 运行期添加字段(高级技巧)有时设计期无法预知所有字段,我们可以在运行期动态创建字段:pascalprocedure TForm1.DynamicAddField;var AField: TAdapterBindSourceField;begin AField := AdapterBindSource1.AddField('DeptName', TFieldType.ftString); // 将字段映射到对象的属性路径(通过 OnMapObjectToField 事件) AdapterBindSource1.OnMapObjectToField := DoMapObjectToField;end;procedure TForm1.DoMapObjectToField(ASender: TObject; AObject: TObject; AField: TAdapterBindSourceField; var AValue: TValue; var AHandled: Boolean);begin if AField.FieldName = 'DeptName' then begin AValue := (AObject as TEmployee).Dept.DeptName; AHandled := True; end;end;### 完整可运行示例下面是一个综合示例,演示了从创建对象、绑定、修改到写回的全过程。将以下代码放入一个 FMX 表单中,放置两个TEdit、一个TLabel和一个TButton。pascalunit Unit1;interfaceuses System.SysUtils, System.Types, System.UITypes, System.Rtti, FMX.Types, FMX.Controls, FMX.Forms, FMX.Edit, FMX.StdCtrls, Data.Bind.Components, Data.Bind.ObjectScope;type TEmployee = class private FName: string; FAge: Integer; published property Name: string read FName write FName; property Age: Integer read FAge write FAge; end; TForm1 = class(TForm) Edit1: TEdit; Edit2: TEdit; Button1: TButton; Label1: TLabel; AdapterBindSource1: TAdapterBindSource; BindingsList1: TBindingsList; procedure FormCreate(Sender: TObject); procedure Button1Click(Sender: TObject); private Emp: TEmployee; public end;var Form1: TForm1;implementation{$R *.fmx}procedure TForm1.FormCreate(Sender: TObject);begin // 创建员工对象 Emp := TEmployee.Create; Emp.Name := '李四'; Emp.Age := 25; // 添加对象到 AdapterBindSource AdapterBindSource1.AddObject(Emp); AdapterBindSource1.Active := True; // 设计期已设置绑定:Edit1.Text <-> Name,Edit2.Text <-> Age // 此时界面自动显示李四和25end;procedure TForm1.Button1Click(Sender: TObject);begin // 修改对象属性 Emp.Name := '王五'; Emp.Age := 35; // 通知绑定系统刷新 UI BindingsList1.Notify(Sender, Emp); // 或者使用 AdapterBindSource1.Refresh 强制刷新 // AdapterBindSource1.Refresh;end;end.### 性能与注意事项-TAdapterBindSource基于 RTTI,因此对象必须编译为published属性,且开启{$RTTI EXPLICIT METHODS([]) PROPERTIES([vcPublished])}或默认设置。- 频繁调用Notify或Refresh可能影响性能,建议在批量更新时暂停绑定(BindingsList1.BeginUpdate/EndUpdate)。- 对于大型数据集,考虑使用TPrototypeBindSource或直接绑定TFDMemTable以获得更好性能。### 总结TAdapterBindSource是 LiveBindings 中连接对象世界与 UI 世界的桥梁。通过它,你可以:- 将任意对象属性暴露为可绑定的字段。- 实现 UI 与对象属性的自动双向同步。- 动态扩展字段,甚至绑定嵌套对象。掌握这一工具,意味着你可以告别传统的手动赋值和事件处理,以声明式编程的方式构建更清晰、更易维护的界面逻辑。从本文的示例出发,你可以进一步探索TAdapterBindSource与TList、TObjectList的配合,实现列表导航,以及利用OnBeforeExecuteCommand等事件实现更复杂的业务逻辑。LiveBindings 的学习曲线略陡,但其带来的生产力提升是值得投入的。
