时间:2025-08-07 21:41
人气:
作者:admin
在多数真实的应用场景中,用户对于显示是比较挑剔的。比如货币要显示货币符号,日期要显示成特定的格式,可能要根据字段值显示图片等等。
在这个课程中,将构建一个简单的雇员列表程序,这个程序将向用户展式员工名称、入职时间、薪资和、薪资的比率等数据。非常简单的一个程序,重点在于格式化与解析的基础知识,学完本课,在LiveBindings方面会大有收益。
好了,模板化的文章开头介绍完了,现在开始跟着本课的脚步一步一步的操作,首先打开Delphi 12.3。
1. 单击主菜单中的 File > New > Multi-Device Application - Delphi > Blank Application ,创建一个新的多设备应用程序。
建议立即单击工具栏上的Save All按钮,将单元文件保存为uMainForm.pas,将项目保存为LiveBinding_BindToJSON.dproj。
你的项目结构应该像这样:

请在属性编辑器中将表单的Name属性更改为"frmMain",表单的Catpion指定为“LiveBindings Demo”,虽然不是必须,但是给每个元素一个友好的具有语义性的名称是一个好的习惯。
2. 本课将与之前的课不同,直接使用LiveBindings向导来构建所有的用户界面和原型数据。直接右击鼠标,在弹出的菜单中找到“LiveBindings Wizard...”菜单项,打开LiveBindings向导。
注意:假如在鼠标右键菜单上找不到“LiveBindings Wizard...”菜单项,可以单击主菜单上的 Toos > Options > LiveBindings,勾选 Display LiveBindings Wizard in context menu 复选框。

3. 在第1个向导页上可以看到5个功能项,选中不同的单选框,左侧的向导栏会出现变化,表示向导的任务页面是不同的。这5种类型的绑定任务作用如下所示:

在这里使用默认的第1个选项Link a control with a field,单击“Next”按钮。
在第2个任务页面要求选择绑定控件或者是新建控件,由于目前还没有创建任何控件,因此在这里选择“New Control”标签页,然后选择TEdit控件。
单击“Next”按钮,进入到DataSource任务页,同样的,选择“New DataSource”菜单项,在这个标签页包含了“FireDAC、TBindSourceDBX和TProtoTypeBindSource”三个项,在这里选择使用第三个项,这将进入到为TProtoTypeBindSource定义字段窗口。

4. 在TProtoTypeBindSource数据源的的字段列表窗口,添加如下的字段名,类型和生成器。

最后在Options任务页面,勾选2个复选框。

单击“Finish”按钮后,向导只是将在Fields Editor中添加的自后一个字段和TEdit进行了绑定,而且还需要进行一番排列,使得UI好看一些。
由于笔者添加的顺序有些不同,HireDate作为绑定字段绑定到了TEdit控件上,应该将TDateEdit作为日期控件才是最优选项。因此在LiveBindings Designer中,将TEdit控件的箭头拖到了ContactName字段上。
5 重新打开LiveBindings Wizard向导窗口, 接下来为控件选择TDateEdit控件,选择“Exists DataSource”为ProtoTypeBindSource1,在接下来的页面选择“HireDate”字段。这样就添加了一个绑定到HireDate的TDateEdit控件
同样的,反复多次打开LiveBindings Wizard向导页:
在这里还额外使用了一次Wizard新增了一个TLable控件绑定到ContactName字段。指定其Name属性为lblContactName,TextSettings.FontColor为clWhite,HorzAlign属性为center。
然后在主窗体上放一个TRectangle控件,指定其Fill.Color属性值为Brown,其align属性为alTop。
在Struct结构面板上,将新建的TLable控件拖到TRectangle下面,并设置TLabel控件的align属性为alcient。

6 再次打开LiveBindings Wizard向导窗口, 这一次选择"Link a grid with a data source"菜单项,将一个TGrid与现存的ProtoTypeBindingSource1进行绑定。

操作完这一切,再经过一些简单的布局工作,一个简单的,具有增删改查的UI就已经做出来了。说实话,这对于快速开发或UI的原型开发来说,真的是太方便了。

6 现在UI看起来虽然很像是一个应用程序,但是数据是随机生成的,接下来将创建自定义的类,处理ProtoTypeBindSource1.OnCreateAdapter事件,将真正的底层数据源赋给ProtoTypeBindSource1。在Project Manager上选中项目名称,右击鼠标选择 AddNew > Unit 菜单项,将其Save为EmployeeObjectU.pas,代码如下所示:
type
TEmployee = class
private
FContactBitmap: TBitmap; //联系人图片
FContactName: string; //联系人名称
FTitle: string; //职位
FHireDate: TDate; //雇佣日期
FSalary: Integer; //薪水
FAvailNow: Boolean; //是否在职
public
constructor Create(const NewName: string;
const NewTitle: string;
const NewHireDate: TDate;
const NewSalary: Integer;
const NewAvail: Boolean);
property ContactBitmap: TBitmap read FContactBitmap write FContactBitmap;
property ContactName: string read FContactName write FContactName;
property Title: string read FTitle write FTitle;
property HireDate: TDate read FHireDate write FHireDate;
property Salary: Integer read FSalary write FSalary;
property AvailNow: Boolean read FAvailNow write FAvailNow;
end;
implementation
{ TEmployee }
constructor TEmployee.Create(const NewName, NewTitle: string;
const NewHireDate: TDate; const NewSalary: Integer; const NewAvail: Boolean);
var
NewBitmap: TBitmap;
ResStream: TResourceStream;
begin
//将根据联系人名称姓来关联资源文件
ResStream := TResourceStream.Create(HINSTANCE, 'Bitmap_' + LeftStr(NewName, Pos(' ', NewName) - 1), RT_RCDATA);
try
NewBitmap := TBitmap.Create;
NewBitmap.LoadFromStream(ResStream);
finally
ResStream.Free;
end;
FContactName := NewName;
FTitle := NewTitle;
FContactBitmap := NewBitmap; //来自资源的图片
FHireDate := NewHireDate;
FSalary := NewSalary;
FAvailNow := NewAvail;
end;
end.
由于ContactBitmap是一张图片,在代码中将使用来自资源文件中存储的位图。因此需要先将位图加载到资源中去。这可以通过Delphi主菜单的 Project > Resouces and Images菜单项来实现,如下图:

注意:这里的Type是RCDATA,Resource_identifier将被代码引用,因此注意其命名
回到uMainForm.pas主窗口,按F12键切换到代码视图。在Interface的uses区添加如下的引用:
uses
//添加对泛型列表和业务实体类的引用
System.Generics.Collections,EmployeeObjectU;
在private区定义一个泛型集合类
private
{ Private declarations }
//定义员工集合类
FEmployeeList: TObjectList<TEmployee>;
最后处理OnCreateAdapter事件,代码如下:
procedure TfrmMain.PrototypeBindSource1CreateAdapter(Sender: TObject;
var ABindSourceAdapter: TBindSourceAdapter);
begin
{ 出于演示,这里使用了硬编码的数据}
FEmployeeList := TObjectList<TEmployee>.Create;
//添加5个员工数据
FEmployeeList.Add(TEmployee.Create('Adam Anderson', 'Manager', EncodeDate(2012, 1, 1), 50000, True));
FEmployeeList.Add(TEmployee.Create('George Grossman', 'Driver', EncodeDate(2017, 7, 11), 75000, False));
FEmployeeList.Add(TEmployee.Create('Brenda Benton', 'Coder', EncodeDate(2014, 11, 5), 68000, True));
FEmployeeList.Add(TEmployee.Create('Jack Jackson', 'Janitor', EncodeDate(2019, 5, 20), 35000, False));
FEmployeeList.Add(TEmployee.Create('William Werner', 'Manager', EncodeDate(2012, 2, 2), 82000, False));
//赋值给TBindSourceAdapter
ABindSourceAdapter := TListBindSourceAdapter<TEmployee>.Create(self, FEmployeeList, True);
end;
现在运行这个示例,可以看到现在它确实具有了现代应用程序的雏形。如下图所示:

尽管如此,离真实的应用程序还是有一些距离,最显然的就是缺乏格式指定。比如对于薪资Salary字段,最好是显示一个货币符号,横幅的联系人名称可以用大写显示等等。
当使用设计器添加了绑定后,在TBindingList中会添加很多的绑定项,双击主窗体的TBindingList控件,将会弹出如下图所示的绑定项列表。

仔细观察这个列表,它们都是用Link开头:
对于LinkControlTo这样的双向绑定链接,选中之后,在属性编辑器中可以看到它具有CustomFormat和CustomParse这两个属性,LinkPropertyTo开头的链接则只具有一个CusomFormat。
7. 现在首先将横幅的联系人大写,并且如果在职的话,显示一个*号。在CustomFormat中写了如下的表达式:
UpperCase(self.%s) + IfThen(Owner.AvailNow.Value, ' (*)', "")

这个表达式中,一些关键元素的作用如下:
%s表示当前控件的文本值,还可以使用一个表示当前字段值的Value,由于Value是Variant类型,因此通常使用ToStr(Value)达到相同的效果。或者,也可以使用Owner.字段名称,比如:
Owner.ContactName.Value也能得到当前的绑定的值。
UpperCase和IfThen称为绑定方法。
Owner.AvailNow.Value,Variant类型的值,访问的是当前绑定对象相同属主的AvailNow字段的值。
Owner表示当前绑定对象的属主,在设计时它是一个TCustomDataGenerateAdapter的引用,在运行时它是TListBindSourceAdapter<EmployeeObjectU.TEmployee>的类型。如果是数据数据库的绑定,它还可以是一个DataSet对象。在对象绑定中,可以将其当作是一个列表中当前的TEmployee对象实例。
self表示当前绑定对象自已,可以使用self.className()访问到当前类的属性。比如横幅的Label绑定的类型是:TBindSourceAdapterReadWriteField<System.string>类型。可以使用self.value访问自己的值,或者就如之前的例子self.%s。

self有一个Owner,表示当前对象的属主,因此可以%s也可以这样写:
self.owner.contactname.value
如果再向上走一层:
self.owner.owner.classname()
可以看到是TFrmMain类型了。如果在窗体级别的public区域定义一个属性比如:
public
{ Public declarations }
property MyProgName:string read GetProgName;
那么可以这样写来进行绑定:
self.Owner.Owner.MyProgName
则可以绑定到窗体级别定义的变量,这就可以实现很多业务逻辑的处理工作了。
选中主窗体上的TBindingList,在属性编辑器中找到method属性,单击编辑器中的按钮,可以看到所有可以使用的绑定方法列表。

现在运行程序,可以看到横幅果然应用到了格式化。

7. 现在让Salary显示一个货币符号,并且在输入时也能够解析这个货币符号。
CustomFormat:
Format('%%m', self.Value + 0.0)
CustomParse:
SubString(%s, 1, 15)
这是一个双向的绑定,因此在这里指定了CustomParse,运行效果如下:

应该接近预期了,不过这个CustomParse就有点简单。
可以看到在表达式中使用了Format,还可以使用FormatDateTime来格式化日期,如下所示:
FormatDateTime('yyyy-mm-dd', Owner.HireDate.AsDateTime)
除了上面的方法之外,笔者在这里整理了一份方法列表参考:
实现了内联 if (三元)运算符。它要求指定所有三个参数,并且它们可以是值或表达式。如果 Condition 参数计算结果为 True ,函数返回 Value1 ;否则(当 Condition 是 False 时),返回 Value2 。显然,如果 Value1 和/或 Value2 是表达式,结果将是该表达式的计算结果。
IfThen(DataSet.Salary.AsFloat > 50000, '高薪', '低薪')
将返回字符串而不是工资值。
IfThen(ListItemIndex(Owner.ComboBox1) <> -1, '从Combobox中选择一个值', SelectedValue(Owner.ComboBox1) + ' ' + DataSet.Salary.AsString)
将使用 ComboBox1 中选定项的前缀字
符串,如果未选择项,则警告用户。
如果所有传入的条件都计算为 True (空值或非布尔条件将被视为 False 值),则返回 True 。这是一个实用函数,你可以用它来模拟 AND 运算符及其参数,其中一些可能未提供(null)。最多可以拥有 100 个参数(硬编码)。以下是一个示例:
IfThen(IfAll(Self.AsFloat > 0, Self.AsFloat < 10, Round(Self.AsFloat) <> 6), 'OK', 'ERR')
对于所有大于零且小于 10 的值将显示 OK,排除四舍五入为六的值。
如果至少有一个传入的条件计算为 True (空值或非布尔条件将被视为 False 值),则返回 True 。这是一个实用函数,你可以用它来模拟 OR 运算符及其参数,其中一些可能未提供(null)。第一个返回 True 的条件将中断计算或后续条件(这是一个短路布尔计算,因此请记住可能产生的副作用)。最多可以拥有 100 个参数(硬编码)。
提供了对 SysUtils.Format 函数的封装(非常流行且在所有 Delphi 应用程序中使用)。 FormatString 参数可以是一个字符串(或返回字符串的表达式),它用作 SysUtils.Format 函数调用的第一个参数(请参考 Delphi 的帮助指南以获取完整概述:
http://docwiki.embarcadero.com/Libraries/en/System.SysUtils.Format
)。
后续参数( Value1 到 ValueN )用于构建传递给 Format 函数的开放数组参数(即,它们代表将替换 FormatString 占位符的实际值)
提供了一个围绕 SysUtils.FormatDateTime 函数的包装器,允许我们将日期/时间值格式化为字符串
(请参考官方文档以了解所有可能性:
http://docwiki.embarcadero.com/Libraries/en/System.SysUtils.FormatDateTime
)。
是 SysUtils.TStringHelper.SubString 函数的包装器,当你在 Delphi 代码中编写 'My string'.SubString(0, 2) (其中 'My' 是结果值)时会调用它。基本上,它提取给定字符串的一部分。第一个参数( StringValue )可以是字符串值或表达式,第二个( Index )是你想要复制的字符串中第一个字符的索引,最后一个参数( Length )是你想要复制的字符数。
当然如果System.Bindings.Methods提供的方法无法满足业务的需求,还可以创建自定义的方法提供复杂的逻辑格式化的显示。
在对TGrid也进行了一番格式化后,最终的效果如下所示:

格式化的内容,在下一课,将继续进行介绍。
IDE工具RAD Studio 13 Florence重磅发布:64 位