一步一步学习使用LiveBindings(8) 使用向导创建用户界面,绑定格式化入门(1)
<p>在多数真实的应用场景中,用户对于显示是比较挑剔的。比如货币要显示货币符号,日期要显示成特定的格式,可能要根据字段值显示图片等等。</p><h5 id="本课程包含如下知识点">本课程包含如下知识点:</h5>
<ol>
<li>完全使用向导生成应用程序</li>
<li>为绑定定义格式化表达式。</li>
</ol>
<p>在这个课程中,将构建一个简单的雇员列表程序,这个程序将向用户展式员工名称、入职时间、薪资和、薪资的比率等数据。非常简单的一个程序,重点在于格式化与解析的基础知识,学完本课,在LiveBindings方面会大有收益。</p>
<h5 id="学完这课之后你将能够">学完这课之后,你将能够:</h5>
<ul>
<li>使用LiveBindings向导快速构建数据库应用程序。</li>
<li>为用户提供更加专业的显示格式。</li>
</ul>
<p>好了,模板化的文章开头介绍完了,现在开始跟着本课的脚步一步一步的操作,首先打开Delphi 12.3。</p>
<h5 id="注意本系列课程具有前后关联性如果你对livebindings的诸多细节没有一个大的概念请看一步一步学习使用livebindings的前几课"><em>注意:本系列课程具有前后关联性,如果你对LiveBindings的诸多细节没有一个大的概念,请看一步一步学习使用LiveBindings的前几课。</em></h5>
<p><strong>1. 单击主菜单中的 File > New > Multi-Device Application - Delphi > Blank Application ,创建一个新的多设备应用程序。</strong></p>
<p>建议立即单击工具栏上的Save All按钮,将单元文件保存为uMainForm.pas,将项目保存为LiveBinding_BindToJSON.dproj。</p>
<p>你的项目结构应该像这样:</p>
<p><img src="https://img2024.cnblogs.com/blog/22554/202508/22554-20250805202928158-696014479.png"></p>
<p>请在属性编辑器中将表单的Name属性更改为"frmMain",表单的Catpion指定为“LiveBindings Demo”,虽然不是必须,但是给每个元素一个友好的具有语义性的名称是一个好的习惯。</p>
<p><strong>2. 本课将与之前的课不同,直接使用LiveBindings向导来构建所有的用户界面和原型数据。直接右击鼠标,在弹出的菜单中找到“LiveBindings Wizard...”菜单项,打开LiveBindings向导。</strong></p>
<p><em>注意:假如在鼠标右键菜单上找不到“LiveBindings Wizard...”菜单项,可以单击主菜单上的 Toos > Options > LiveBindings,勾选 Display LiveBindings Wizard in context menu 复选框。</em></p>
<p><img src="https://img2024.cnblogs.com/blog/22554/202508/22554-20250805204804927-925010954.png"></p>
<p><strong>3. 在第1个向导页上可以看到5个功能项,选中不同的单选框,左侧的向导栏会出现变化,表示向导的任务页面是不同的。这5种类型的绑定任务作用如下所示:</strong></p>
<p><img src="https://img2024.cnblogs.com/blog/22554/202508/22554-20250805210701038-8357203.png"></p>
<ul>
<li>
<h5 id="link-a-control-with-a-field-将表单上的控件与数据源中的字段绑定">Link a control with a field: 将表单上的控件与数据源中的字段绑定。</h5>
</li>
<li>
<h5 id="link-a-grid-with-a-data-source将网格与数据源链接将网格中的列与数据源中的字段绑定">Link a grid with a data source:将网格与数据源链接:将网格中的列与数据源中的字段绑定。</h5>
</li>
<li>
<h5 id="link-a-component-property-with-a-control将组件可视或非可视的属性与表单上的控件绑定">Link a component property with a control:将组件(可视或非可视)的属性与表单上的控件绑定。</h5>
</li>
<li>
<h5 id="link-a-component-property-with-a-field将组件可视或非可视的属性与数据源中的字段绑定">Link a component property with a field:将组件(可视或非可视)的属性与数据源中的字段绑定。</h5>
</li>
<li>
<h5 id="create-a-data-source创建新的数据源">Create a data source:创建新的数据源。</h5>
</li>
</ul>
<p>在这里使用默认的第1个选项Link a control with a field,单击“Next”按钮。</p>
<p>在第2个任务页面要求选择绑定控件或者是新建控件,由于目前还没有创建任何控件,因此在这里选择“New Control”标签页,然后选择TEdit控件。</p>
<p>单击“Next”按钮,进入到DataSource任务页,同样的,选择“New DataSource”菜单项,在这个标签页包含了“FireDAC、TBindSourceDBX和TProtoTypeBindSource”三个项,在这里选择使用第三个项,这将进入到为TProtoTypeBindSource定义字段窗口。</p>
<p><img src="https://img2024.cnblogs.com/blog/22554/202508/22554-20250805213517954-59813566.gif"></p>
<p><strong>4. 在TProtoTypeBindSource数据源的的字段列表窗口,添加如下的字段名,类型和生成器。</strong></p>
<p><img src="https://img2024.cnblogs.com/blog/22554/202508/22554-20250805214619799-2012390848.png"></p>
<p>最后在Options任务页面,勾选2个复选框。</p>
<ul>
<li>Add data source navigate添加TBindNavigator控件。</li>
<li>Add control label 添加控件标签</li>
</ul>
<p><img src="https://img2024.cnblogs.com/blog/22554/202508/22554-20250805214946185-1242885664.png"></p>
<p>单击“Finish”按钮后,向导只是将在Fields Editor中添加的自后一个字段和TEdit进行了绑定,而且还需要进行一番排列,使得UI好看一些。</p>
<p>由于笔者添加的顺序有些不同,HireDate作为绑定字段绑定到了TEdit控件上,应该将TDateEdit作为日期控件才是最优选项。因此在LiveBindings Designer中,将TEdit控件的箭头拖到了ContactName字段上。</p>
<p><strong>5 重新打开LiveBindings Wizard向导窗口, 接下来为控件选择TDateEdit控件,选择“Exists DataSource”为ProtoTypeBindSource1,在接下来的页面选择“HireDate”字段。这样就添加了一个绑定到HireDate的TDateEdit控件</strong></p>
<p>同样的,反复多次打开LiveBindings Wizard向导页:</p>
<ul>
<li>将Title字段绑定到TEdit控件。</li>
<li>AvailNow字段绑定到TCheckBox控件。</li>
<li>Salary字段绑定到TEdit控件。</li>
<li>ContactBitmap字段绑定到TImage控件。</li>
</ul>
<p>在这里还额外使用了一次Wizard新增了一个TLable控件绑定到ContactName字段。指定其Name属性为lblContactName,TextSettings.FontColor为clWhite,HorzAlign属性为center。</p>
<p>然后在主窗体上放一个TRectangle控件,指定其Fill.Color属性值为Brown,其align属性为alTop。</p>
<p>在Struct结构面板上,将新建的TLable控件拖到TRectangle下面,并设置TLabel控件的align属性为alcient。</p>
<p><img src="https://img2024.cnblogs.com/blog/22554/202508/22554-20250806055453196-1040280548.gif"></p>
<p><strong>6 再次打开LiveBindings Wizard向导窗口, 这一次选择"Link a grid with a data source"菜单项,将一个TGrid与现存的ProtoTypeBindingSource1进行绑定。</strong></p>
<p><img src="https://img2024.cnblogs.com/blog/22554/202508/22554-20250806060039240-597535819.gif"></p>
<p>操作完这一切,再经过一些简单的布局工作,一个简单的,具有增删改查的UI就已经做出来了。说实话,这对于快速开发或UI的原型开发来说,真的是太方便了。</p>
<p><img src="https://img2024.cnblogs.com/blog/22554/202508/22554-20250806061558451-2073187511.png"></p>
<p><strong>6 现在UI看起来虽然很像是一个应用程序,但是数据是随机生成的,接下来将创建自定义的类,处理ProtoTypeBindSource1.OnCreateAdapter事件,将真正的底层数据源赋给ProtoTypeBindSource1。在Project Manager上选中项目名称,右击鼠标选择 AddNew > Unit 菜单项,将其Save为EmployeeObjectU.pas,代码如下所示:</strong></p>
<pre><code class="language-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.
</code></pre>
<p>由于ContactBitmap是一张图片,在代码中将使用来自资源文件中存储的位图。因此需要先将位图加载到资源中去。这可以通过Delphi主菜单的 Project > Resouces and Images菜单项来实现,如下图:</p>
<p><img src="https://img2024.cnblogs.com/blog/22554/202508/22554-20250806072457873-681202292.png"></p>
<p><em>注意:这里的Type是RCDATA,Resource_identifier将被代码引用,因此注意其命名</em></p>
<p>回到uMainForm.pas主窗口,按F12键切换到代码视图。在Interface的uses区添加如下的引用:</p>
<pre><code class="language-Pascal">uses
//添加对泛型列表和业务实体类的引用
System.Generics.Collections,EmployeeObjectU;
</code></pre>
<p>在private区定义一个泛型集合类</p>
<pre><code class="language-Pascal">private
{ Private declarations }
//定义员工集合类
FEmployeeList: TObjectList<TEmployee>;
</code></pre>
<p>最后处理OnCreateAdapter事件,代码如下:</p>
<pre><code class="language-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;
</code></pre>
<p>现在运行这个示例,可以看到现在它确实具有了现代应用程序的雏形。如下图所示:</p>
<p><img src="https://img2024.cnblogs.com/blog/22554/202508/22554-20250806204836387-1475115474.png"></p>
<p>尽管如此,离真实的应用程序还是有一些距离,最显然的就是缺乏格式指定。比如对于薪资Salary字段,最好是显示一个货币符号,横幅的联系人名称可以用大写显示等等。</p>
<p>当使用设计器添加了绑定后,在TBindingList中会添加很多的绑定项,双击主窗体的TBindingList控件,将会弹出如下图所示的绑定项列表。</p>
<p><img src="https://img2024.cnblogs.com/blog/22554/202508/22554-20250806210615519-1780562280.png"></p>
<p>仔细观察这个列表,它们都是用Link开头:</p>
<ul>
<li>对于可编辑的双向链接,上是以LinkControlTo...这样的命名。</li>
<li>对于不可编辑的单向链接,上是以LinkPropertyTo开头。</li>
<li>对于Grid,这里有一个专用的LinkGridToDataSource来实现。</li>
</ul>
<p>对于LinkControlTo这样的双向绑定链接,选中之后,在属性编辑器中可以看到它具有CustomFormat和CustomParse这两个属性,LinkPropertyTo开头的链接则只具有一个CusomFormat。</p>
<p><strong>7. 现在首先将横幅的联系人大写,并且如果在职的话,显示一个*号。在CustomFormat中写了如下的表达式:</strong></p>
<pre><code class="language-Pascal">UpperCase(self.%s) + IfThen(Owner.AvailNow.Value, ' (*)', "")
</code></pre>
<p><img src="https://img2024.cnblogs.com/blog/22554/202508/22554-20250807050250665-1985207663.gif"><br>
这个表达式中,一些关键元素的作用如下:</p>
<ul>
<li>
<p>%s表示当前控件的文本值,还可以使用一个表示当前字段值的Value,由于Value是Variant类型,因此通常使用ToStr(Value)达到相同的效果。或者,也可以使用Owner.字段名称,比如:<br>
Owner.ContactName.Value也能得到当前的绑定的值。</p>
</li>
<li>
<p>UpperCase和IfThen称为绑定方法。</p>
</li>
<li>
<p>Owner.AvailNow.Value,Variant类型的值,访问的是当前绑定对象相同属主的AvailNow字段的值。<br>
Owner表示当前绑定对象的属主,在设计时它是一个TCustomDataGenerateAdapter的引用,在运行时它是TListBindSourceAdapter<EmployeeObjectU.TEmployee>的类型。如果是数据数据库的绑定,它还可以是一个DataSet对象。在对象绑定中,可以将其当作是一个列表中当前的TEmployee对象实例。</p>
</li>
<li>
<p>self表示当前绑定对象自已,可以使用self.className()访问到当前类的属性。比如横幅的Label绑定的类型是:TBindSourceAdapterReadWriteField<System.string>类型。可以使用self.value访问自己的值,或者就如之前的例子self.%s。</p>
</li>
</ul>
<p><img src="https://img2024.cnblogs.com/blog/22554/202508/22554-20250807052457797-116896780.png"></p>
<p>self有一个Owner,表示当前对象的属主,因此可以%s也可以这样写:</p>
<pre><code class="language-Pascal">self.owner.contactname.value
</code></pre>
<p>如果再向上走一层:</p>
<pre><code class="language-Pascal">self.owner.owner.classname()
</code></pre>
<p>可以看到是<strong>TFrmMain</strong>类型了。如果在窗体级别的public区域定义一个属性比如:</p>
<pre><code class="language-Pascal">public
{ Public declarations }
property MyProgName:string read GetProgName;
</code></pre>
<p>那么可以这样写来进行绑定:</p>
<pre><code class="language-Pascal">self.Owner.Owner.MyProgName
</code></pre>
<p>则可以绑定到窗体级别定义的变量,这就可以实现很多业务逻辑的处理工作了。</p>
<h5 id="当然具体的owner所处的层次需要视程序的层次而定">当然具体的Owner所处的层次,需要视程序的层次而定。</h5>
<p>选中主窗体上的TBindingList,在属性编辑器中找到method属性,单击编辑器中的按钮,可以看到所有可以使用的绑定方法列表。</p>
<p><img src="https://img2024.cnblogs.com/blog/22554/202508/22554-20250806220016490-1838232264.png"></p>
<p>现在运行程序,可以看到横幅果然应用到了格式化。</p>
<p><img src="https://img2024.cnblogs.com/blog/22554/202508/22554-20250806220500858-871586887.gif"></p>
<p><strong>7. 现在让Salary显示一个货币符号,并且在输入时也能够解析这个货币符号。</strong></p>
<p>CustomFormat:</p>
<pre><code class="language-Pascal">Format('%%m', self.Value + 0.0)
</code></pre>
<p>CustomParse:</p>
<pre><code class="language-Pascal">SubString(%s, 1, 15)
</code></pre>
<p>这是一个双向的绑定,因此在这里指定了CustomParse,运行效果如下:</p>
<p><img src="https://img2024.cnblogs.com/blog/22554/202508/22554-20250807065728288-278634493.gif"></p>
<p>应该接近预期了,不过这个CustomParse就有点简单。</p>
<p>可以看到在表达式中使用了Format,还可以使用FormatDateTime来格式化日期,如下所示:</p>
<pre><code class="language-Pas">FormatDateTime('yyyy-mm-dd', Owner.HireDate.AsDateTime)
</code></pre>
<p>除了上面的方法之外,笔者在这里整理了一份方法列表参考:</p>
<h4 id="livebindings方法列表">LiveBindings方法列表:</h4>
<h5 id="ifthencondition-value1-value2">IfThen(Condition, Value1, Value2):</h5>
<p>实现了内联 if (三元)运算符。它要求指定所有三个参数,并且它们可以是值或表达式。如果 Condition 参数计算结果为 True ,函数返回 Value1 ;否则(当 Condition 是 False 时),返回 Value2 。显然,如果 Value1 和/或 Value2 是表达式,结果将是该表达式的计算结果。</p>
<pre><code class="language-Pascal">IfThen(DataSet.Salary.AsFloat > 50000, '高薪', '低薪')
</code></pre>
<p>将返回字符串而不是工资值。</p>
<pre><code class="language-Pascal">IfThen(ListItemIndex(Owner.ComboBox1) <> -1, '从Combobox中选择一个值', SelectedValue(Owner.ComboBox1) + ' ' + DataSet.Salary.AsString)
</code></pre>
<p>将使用 ComboBox1 中选定项的前缀字<img src="https://img2024.cnblogs.com/blog/22554/202508/22554-20250807215102475-1375748174.gif">符串,如果未选择项,则警告用户。</p>
<h5 id="ifallcondition1-condition2--condition100">IfAll(Condition1, Condition2, ..., Condition100)</h5>
<p>如果所有传入的条件都计算为 True (空值或非布尔条件将被视为 False 值),则返回 True 。这是一个实用函数,你可以用它来模拟 AND 运算符及其参数,其中一些可能未提供(null)。最多可以拥有 100 个参数(硬编码)。以下是一个示例:</p>
<pre><code class="language-Pascal">IfThen(IfAll(Self.AsFloat > 0, Self.AsFloat < 10, Round(Self.AsFloat) <> 6), 'OK', 'ERR')
</code></pre>
<p>对于所有大于零且小于 10 的值将显示 OK,排除四舍五入为六的值。</p>
<h4 id="ifanycondition1-condition2--condition100">IfAny(Condition1, Condition2, ..., Condition100)</h4>
<p>如果至少有一个传入的条件计算为 True (空值或非布尔条件将被视为 False 值),则返回 True 。这是一个实用函数,你可以用它来模拟 OR 运算符及其参数,其中一些可能未提供(null)。第一个返回 True 的条件将中断计算或后续条件(这是一个短路布尔计算,因此请记住可能产生的副作用)。最多可以拥有 100 个参数(硬编码)。</p>
<h4 id="formatformatstring-value1-value2--valuen">Format(FormatString, Value1, Value2, ..., ValueN)</h4>
<p>提供了对 SysUtils.Format 函数的封装(非常流行且在所有 Delphi 应用程序中使用)。 FormatString 参数可以是一个字符串(或返回字符串的表达式),它用作 SysUtils.Format 函数调用的第一个参数(请参考 Delphi 的帮助指南以获取完整概述:<br>
http://docwiki.embarcadero.com/Libraries/en/System.SysUtils.Format</p>
<p>)。</p>
<p>后续参数( Value1 到 ValueN )用于构建传递给 Format 函数的开放数组参数(即,它们代表将替换 FormatString 占位符的实际值)</p>
<h4 id="formatdatetimeformatstring-datevalue">FormatDateTime(FormatString, DateValue)</h4>
<p>提供了一个围绕 SysUtils.FormatDateTime 函数的包装器,允许我们将日期/时间值格式化为字符串<br>
(请参考官方文档以了解所有可能性:</p>
<p>http://docwiki.embarcadero.com/Libraries/en/System.SysUtils.FormatDateTime</p>
<p>)。</p>
<h4 id="substringstringvalueindexlength">SubString(StringValue,Index,Length)</h4>
<p>是 SysUtils.TStringHelper.SubString 函数的包装器,当你在 Delphi 代码中编写 'My string'.SubString(0, 2) (其中 'My' 是结果值)时会调用它。基本上,它提取给定字符串的一部分。第一个参数( StringValue )可以是字符串值或表达式,第二个( Index )是你想要复制的字符串中第一个字符的索引,最后一个参数( Length )是你想要复制的字符数。</p>
<p>当然如果System.Bindings.Methods提供的方法无法满足业务的需求,还可以创建自定义的方法提供复杂的逻辑格式化的显示。</p>
<p>在对TGrid也进行了一番格式化后,最终的效果如下所示:</p>
<p><img src="https://img2024.cnblogs.com/blog/22554/202508/22554-20250807214620790-508265925.gif"></p>
<p>格式化的内容,在下一课,将继续进行介绍。</p><br><br>
来源:https://www.cnblogs.com/lincats/p/19027551
頁:
[1]