一步一步学习使用LiveBindings(8) 使用向导创建用户界面,绑定格式化入门

一步一步学习使用LiveBindings(8) 使用向导创建用户界面,绑定格式化入门

在多数真实的应用场景中,用户对于显示是比较挑剔的。比如货币要显示货币符号,日期要显示成特定的格式,可能要根据字段值显示图片等等。

本课程包含如下知识点:
  1. 完全使用向导生成应用程序
  2. 为绑定定义格式化表达式。

在这个课程中,将构建一个简单的雇员列表程序,这个程序将向用户展式员工名称、入职时间、薪资和、薪资的比率等数据。非常简单的一个程序,重点在于格式化与解析的基础知识,学完本课,在LiveBindings方面会大有收益。

学完这课之后,你将能够:
  • 使用LiveBindings向导快速构建数据库应用程序。
  • 为用户提供更加专业的显示格式。

好了,模板化的文章开头介绍完了,现在开始跟着本课的脚步一步一步的操作,首先打开Delphi 12.3。

注意:本系列课程具有前后关联性,如果你对LiveBindings的诸多细节没有一个大的概念,请看一步一步学习使用LiveBindings的前几课。

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种类型的绑定任务作用如下所示:

Create a data source:创建新的数据源。

在这里使用默认的第1个选项Link a control with a field,单击"Next"按钮。

在第2个任务页面要求选择绑定控件或者是新建控件,由于目前还没有创建任何控件,因此在这里选择"New Control"标签页,然后选择TEdit控件。

单击"Next"按钮,进入到DataSource任务页,同样的,选择"New DataSource"菜单项,在这个标签页包含了"FireDAC、TBindSourceDBX和TProtoTypeBindSource"三个项,在这里选择使用第三个项,这将进入到为TProtoTypeBindSource定义字段窗口。

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

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

  • Add data source navigate 添加TBindNavigator控件。
  • Add control label 添加控件标签

单击"Finish"按钮后,向导只是将在Fields Editor中添加的自后一个字段和TEdit进行了绑定,而且还需要进行一番排列,使得UI好看一些。

由于笔者添加的顺序有些不同,HireDate作为绑定字段绑定到了TEdit控件上,应该将TDateEdit作为日期控件才是最优选项。因此在LiveBindings Designer中,将TEdit控件的箭头拖到了ContactName字段上。

5 重新打开LiveBindings Wizard向导窗口, 接下来为控件选择TDateEdit控件,选择"Exists DataSource"为ProtoTypeBindSource1,在接下来的页面选择"HireDate"字段。这样就添加了一个绑定到HireDate的TDateEdit控件

同样的,反复多次打开LiveBindings Wizard向导页:

  • 将Title字段绑定到TEdit控件。
  • AvailNow字段绑定到TCheckBox控件。
  • Salary字段绑定到TEdit控件。
  • ContactBitmap字段绑定到TImage控件。

在这里还额外使用了一次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,代码如下所示:

Pascal 复制代码
 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区添加如下的引用:

Pascal 复制代码
uses
  //添加对泛型列表和业务实体类的引用
  System.Generics.Collections,EmployeeObjectU;

在private区定义一个泛型集合类

Pascal 复制代码
  private
    { Private declarations }
    //定义员工集合类
    FEmployeeList: TObjectList<TEmployee>;

最后处理OnCreateAdapter事件,代码如下:

Pascal 复制代码
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...这样的命名。
  • 对于不可编辑的单向链接,上是以LinkPropertyTo开头。
  • 对于Grid,这里有一个专用的LinkGridToDataSource来实现。

对于LinkControlTo这样的双向绑定链接,选中之后,在属性编辑器中可以看到它具有CustomFormat和CustomParse这两个属性,LinkPropertyTo开头的链接则只具有一个CusomFormat。

7. 现在首先将横幅的联系人大写,并且如果在职的话,显示一个*号。在CustomFormat中写了如下的表达式:

Pascal 复制代码
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也可以这样写:

Pascal 复制代码
self.owner.contactname.value

如果再向上走一层:

Pascal 复制代码
self.owner.owner.classname()

可以看到是TFrmMain类型了。如果在窗体级别的public区域定义一个属性比如:

Pascal 复制代码
  public
    { Public declarations }
    property MyProgName:string read GetProgName;

那么可以这样写来进行绑定:

Pascal 复制代码
self.Owner.Owner.MyProgName

则可以绑定到窗体级别定义的变量,这就可以实现很多业务逻辑的处理工作了。

当然具体的Owner所处的层次,需要视程序的层次而定。

选中主窗体上的TBindingList,在属性编辑器中找到method属性,单击编辑器中的按钮,可以看到所有可以使用的绑定方法列表。

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

7. 现在让Salary显示一个货币符号,并且在输入时也能够解析这个货币符号。

CustomFormat:

Pascal 复制代码
Format('%%m', self.Value + 0.0)

CustomParse:

Pascal 复制代码
SubString(%s, 1, 15)

这是一个双向的绑定,因此在这里指定了CustomParse,运行效果如下:

应该接近预期了,不过这个CustomParse就有点简单。

可以看到在表达式中使用了Format,还可以使用FormatDateTime来格式化日期,如下所示:

Pas 复制代码
FormatDateTime('yyyy-mm-dd', Owner.HireDate.AsDateTime)

除了上面的方法之外,笔者在这里整理了一份方法列表参考:

LiveBindings方法列表:

IfThen(Condition, Value1, Value2):

实现了内联 if (三元)运算符。它要求指定所有三个参数,并且它们可以是值或表达式。如果 Condition 参数计算结果为 True ,函数返回 Value1 ;否则(当 Condition 是 False 时),返回 Value2 。显然,如果 Value1 和/或 Value2 是表达式,结果将是该表达式的计算结果。

Pascal 复制代码
IfThen(DataSet.Salary.AsFloat > 50000, '高薪', '低薪') 

将返回字符串而不是工资值。

Pascal 复制代码
IfThen(ListItemIndex(Owner.ComboBox1) <> -1, '从Combobox中选择一个值', SelectedValue(Owner.ComboBox1) + ' ' + DataSet.Salary.AsString)

将使用 ComboBox1 中选定项的前缀字符串,如果未选择项,则警告用户。

IfAll(Condition1, Condition2, ..., Condition100)

如果所有传入的条件都计算为 True (空值或非布尔条件将被视为 False 值),则返回 True 。这是一个实用函数,你可以用它来模拟 AND 运算符及其参数,其中一些可能未提供(null)。最多可以拥有 100 个参数(硬编码)。以下是一个示例:

Pascal 复制代码
IfThen(IfAll(Self.AsFloat > 0, Self.AsFloat < 10, Round(Self.AsFloat) <> 6), 'OK', 'ERR') 

对于所有大于零且小于 10 的值将显示 OK,排除四舍五入为六的值。

IfAny(Condition1, Condition2, ..., Condition100)

如果至少有一个传入的条件计算为 True (空值或非布尔条件将被视为 False 值),则返回 True 。这是一个实用函数,你可以用它来模拟 OR 运算符及其参数,其中一些可能未提供(null)。第一个返回 True 的条件将中断计算或后续条件(这是一个短路布尔计算,因此请记住可能产生的副作用)。最多可以拥有 100 个参数(硬编码)。

Format(FormatString, Value1, Value2, ..., ValueN)

提供了对 SysUtils.Format 函数的封装(非常流行且在所有 Delphi 应用程序中使用)。 FormatString 参数可以是一个字符串(或返回字符串的表达式),它用作 SysUtils.Format 函数调用的第一个参数(请参考 Delphi 的帮助指南以获取完整概述:
http://docwiki.embarcadero.com/Libraries/en/System.SysUtils.Format

)。

后续参数( Value1 到 ValueN )用于构建传递给 Format 函数的开放数组参数(即,它们代表将替换 FormatString 占位符的实际值)

FormatDateTime(FormatString, DateValue)

提供了一个围绕 SysUtils.FormatDateTime 函数的包装器,允许我们将日期/时间值格式化为字符串

(请参考官方文档以了解所有可能性:

http://docwiki.embarcadero.com/Libraries/en/System.SysUtils.FormatDateTime

)。

SubString(StringValue,Index,Length)

是 SysUtils.TStringHelper.SubString 函数的包装器,当你在 Delphi 代码中编写 'My string'.SubString(0, 2) (其中 'My' 是结果值)时会调用它。基本上,它提取给定字符串的一部分。第一个参数( StringValue )可以是字符串值或表达式,第二个( Index )是你想要复制的字符串中第一个字符的索引,最后一个参数( Length )是你想要复制的字符数。

当然如果System.Bindings.Methods提供的方法无法满足业务的需求,还可以创建自定义的方法提供复杂的逻辑格式化的显示。

在对TGrid也进行了一番格式化后,最终的效果如下所示:

格式化的内容,在下一课,将继续进行介绍。

相关推荐
lincats4 天前
一步一步学习使用LiveBindings(7) 实现对JSON数据的绑定
android·delphi·livebindings·delphi 12.3·firedac
lincats5 天前
一步一步学习使用LiveBindings(6) 实现Master-Detail主从关系的绑定
delphi·livebindings·delphi 12.3·firedac·firemonkey
lincats6 天前
一步一步学习使用LiveBindings(5) 使用TAdapterBindSource实现对象绑定
livebindings·delphi 12.3·firedac·firemonkey
tanqth3 个月前
使用Delphi 和 CrossVcl 开发基于VCL的 macOS 和 Linux 应用程序简介
linux·delphi·crossvcl·vcl开发的linux
我不是代码教父4 个月前
[原创](现代Delphi 12指南): 设置、运行和调试你的第一个macOS应用程序.
macos·delphi
和码说5 个月前
编程考古-忘掉它,Delphi 8 for the Microsoft .NET Framework
delphi·编程考古
ljklxlj6 个月前
rdian是一个结构体,pdian=^Rdian,list泛型做什么用?
delphi
ljklxlj7 个月前
我的杂记一
delphi
月巴月巴白勺合鸟月半8 个月前
以前很常见的一种HTTP操作方式
网络·c++·网络协议·http·delphi