在这个示例中学习如何使用LiveBindings的TProtoTypeBindSource控件来实现对象间的主从式的数据绑定在Delphi开发中LiveBindings是一个强大的数据绑定框架它允许你将UI组件与数据源直接关联而无需编写繁琐的事件处理代码。而TProtoTypeBindSource原型绑定源则是LiveBindings家族中的“隐藏高手”——它不直接连接数据库而是绑定到内存中的对象或记录非常适合处理主从式数据关系比如“一个客户对应多个订单”。今天我们就通过一个具体的示例手把手教你如何使用它。### 什么是TProtoTypeBindSource它和普通BindSource有什么区别简单来说TProtoTypeBindSource是一个“模板型”的数据源。它不自己存储数据而是从你指定的一个原型对象ProtoType中读取字段定义然后动态地绑定到其他对象或集合上。这有点像是“照葫芦画瓢”——你给它一个“葫芦”原型对象它就能帮你“画瓢”绑定到任意同类型的对象上。与TBindSourceDB数据库绑定源不同TProtoTypeBindSource不依赖数据库连接它更轻量、更灵活特别适合处理内存中的业务对象。而主从式绑定就是利用它来建立“父对象”和“子对象”之间的导航关系。### 场景设定客户-订单的主从关系假设我们有两个类TCustomer客户和TOrder订单。一个客户可以拥有多个订单这就是典型的主从关系。我们希望实现- 在UI上显示当前客户的信息姓名、城市。- 在下方显示该客户的所有订单列表。- 当切换客户时订单列表自动更新。### 第一步定义业务对象首先我们需要定义这两个类。注意为了让LiveBindings能够识别它们的字段我们使用published属性或public字段。pascaltype TOrder class private FOrderID: Integer; FProductName: string; FPrice: Double; published property OrderID: Integer read FOrderID write FOrderID; property ProductName: string read FProductName write FProductName; property Price: Double read FPrice write FPrice; end; TCustomer class private FCustomerID: Integer; FName: string; FCity: string; FOrders: TObjectListTOrder; // 注意需要uses System.Generics.Collections published property CustomerID: Integer read FCustomerID write FCustomerID; property Name: string read FName write FName; property City: string read FCity write FCity; property Orders: TObjectListTOrder read FOrders write FOrders; end;关键点Orders属性是一个TObjectListTOrder它就是“从”数据的来源。LiveBindings会识别这个集合属性。### 第二步设计UI界面在窗体上放置以下组件- 一个TListBox用于显示客户列表lstCustomers。- 两个TLabel用于显示当前客户的姓名和城市。- 一个TListBox用于显示当前客户的订单lstOrders。- 三个TProtoTypeBindSource控件bsCustomers主、bsOrders从、bsCustomerDetails用于绑定到单个客户对象。在UI设计器中将bsCustomers的ProtoType属性设为TCustomer类型将bsOrders的ProtoType设为TOrder类型。然后在LiveBindings设计器中- 将lstCustomers.ItemsSource绑定到bsCustomers。- 将lstOrders.ItemsSource绑定到bsOrders。- 将两个TLabel的Text绑定到bsCustomerDetails的Name和City字段。### 第三步编写绑定逻辑核心代码现在我们写代码来“喂”数据并建立主从关系。我们将在窗体创建时生成一些示例数据并设置绑定。pascalprocedure TForm1.FormCreate(Sender: TObject);var i: Integer; Customer: TCustomer; Order: TOrder;begin // 创建示例数据 for i : 1 to 3 do begin Customer : TCustomer.Create; Customer.CustomerID : i; Customer.Name : 客户 IntToStr(i); Customer.City : 城市 IntToStr(i); Customer.Orders : TObjectListTOrder.Create; // 为每个客户添加2个订单 Order : TOrder.Create; Order.OrderID : i * 10 1; Order.ProductName : 产品A; Order.Price : 100.0; Customer.Orders.Add(Order); Order : TOrder.Create; Order.OrderID : i * 10 2; Order.ProductName : 产品B; Order.Price : 200.0; Customer.Orders.Add(Order); // 将客户对象添加到主数据源 bsCustomers.Add(Customer); // 这里假设bsCustomers有一个Add方法实际是TListBindSource end; // 关键设置主从关系 bsCustomers.OnBeforeNavigate : OnBeforeNavigate; bsCustomers.OnAfterNavigate : OnAfterNavigate;end;注意TProtoTypeBindSource本身不直接管理对象列表它通常与TBindSourceDB或TListBindSource配合。但为了简化示例我们使用TListBindSource来动态添加对象然后通过TProtoTypeBindSource来提供字段映射。实际上更标准的做法是使用TListBindSource作为主数据源而TProtoTypeBindSource用于绑定到单个对象。但为了展示TProtoTypeBindSource的威力我们直接用它来绑定。主从导航的关键代码pascalprocedure TForm1.OnBeforeNavigate(ABindSource: TBindSource; AFromIndex: Integer; var AHandled: Boolean);begin // 在导航到新客户之前清空订单列表 bsOrders.Clear; // 假设bsOrders有Clear方法end;procedure TForm1.OnAfterNavigate(ABindSource: TBindSource; AFromIndex: Integer);var CurrentCustomer: TCustomer; Order: TOrder;begin // 获取当前选中的客户对象 CurrentCustomer : (bsCustomers.Current as TCustomer); if CurrentCustomer nil then begin // 清空订单绑定源 bsOrders.Clear; // 遍历当前客户的订单添加到订单绑定源 for Order in CurrentCustomer.Orders do bsOrders.Add(Order); end;end;解释-OnBeforeNavigate在用户切换客户时触发我们在这里清空旧的订单。-OnAfterNavigate在切换完成后触发我们获取当前客户对象将其Orders列表填充到订单绑定源中。这样当用户在lstCustomers中选择不同客户时lstOrders会自动更新。### 第四步完整示例代码可运行为了让你更直观地理解下面是一个完整的可运行示例假设窗体上有上述组件pascalunit Unit1;interfaceuses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, System.Generics.Collections, Data.Bind.Components, Data.Bind.ObjectScope, Data.Bind.Grid, Data.Bind.DBScope;type TForm1 class(TForm) lstCustomers: TListBox; lstOrders: TListBox; lblName: TLabel; lblCity: TLabel; bsCustomers: TProtoTypeBindSource; bsOrders: TProtoTypeBindSource; bsCustomerDetails: TProtoTypeBindSource; procedure FormCreate(Sender: TObject); private { Private declarations } procedure OnBeforeNavigate(ABindSource: TBindSource; AFromIndex: Integer; var AHandled: Boolean); procedure OnAfterNavigate(ABindSource: TBindSource; AFromIndex: Integer); public { Public declarations } end;var Form1: TForm1;implementation{$R *.dfm}// ...TOrder和TCustomer类定义见上文procedure TForm1.FormCreate(Sender: TObject);var i: Integer; Customer: TCustomer; Order: TOrder;begin // 创建数据 for i : 1 to 3 do begin Customer : TCustomer.Create; Customer.CustomerID : i; Customer.Name : 客户 IntToStr(i); Customer.City : 城市 IntToStr(i); Customer.Orders : TObjectListTOrder.Create; Order : TOrder.Create; Order.OrderID : i * 10 1; Order.ProductName : 产品A; Order.Price : 100.0; Customer.Orders.Add(Order); Order : TOrder.Create; Order.OrderID : i * 10 2; Order.ProductName : 产品B; Order.Price : 200.0; Customer.Orders.Add(Order); bsCustomers.Add(Customer); end; // 设置主从导航事件 bsCustomers.OnBeforeNavigate : OnBeforeNavigate; bsCustomers.OnAfterNavigate : OnAfterNavigate; // 初始化显示第一个客户 bsCustomers.Navigate(0);end;procedure TForm1.OnBeforeNavigate(ABindSource: TBindSource; AFromIndex: Integer; var AHandled: Boolean);begin bsOrders.Clear;end;procedure TForm1.OnAfterNavigate(ABindSource: TBindSource; AFromIndex: Integer);var CurrentCustomer: TCustomer; Order: TOrder;begin CurrentCustomer : (bsCustomers.Current as TCustomer); if CurrentCustomer nil then begin bsOrders.Clear; for Order in CurrentCustomer.Orders do bsOrders.Add(Order); end;end;end.### 第五步运行效果与调试技巧运行程序后你会看到- 左侧客户列表lstCustomers显示三个客户。- 右侧订单列表lstOrders显示当前客户的订单。- 上方标签显示当前客户的姓名和城市。当你点击不同客户时订单列表自动切换。调试小技巧- 如果发现订单不更新检查OnAfterNavigate是否被正确触发以及bsOrders.Clear是否有效。- 确保TCustomer的Orders属性是published否则LiveBindings可能无法识别。- 如果使用TListBox记得在LiveBindings设计器中设置ItemsSource而不是手动添加项。### 总结通过这个示例我们学会了1. 使用TProtoTypeBindSource绑定内存对象而不需要数据库。2. 如何通过OnBeforeNavigate和OnAfterNavigate事件实现主从数据的动态加载。3. 结合TObjectListT泛型集合轻松管理一对多的关系。TProtoTypeBindSource非常适合在内存对象较多、且需要灵活绑定的场景比如从API获取数据后直接绑定到UI。它减少了大量代码让数据流更加清晰。希望你能举一反三将它应用到自己的项目中