刘吉书 發表於 2025-8-9 15:34:00

一步一步学习使用LiveBindings(9) LiveBindings图像绑定与自定义绑定方法(2)

<p>这是《一步一步学习使用LiveBindings(8)》的接续,本章将学习如下知识点:</p>
<ul>
<li>为TGrid应用列格式。</li>
<li>创建自定义的绑定方法。</li>
<li>实现表单级别的格式化方法。</li>
</ul>
<p>上一节使用向导将TGrid绑定到ProtoTypeBindSource,它会创建TLinkGridToDataSource这个绑定链接。向导创建的绑定只是将PrototypeBindSource1的*与TGrid的*进行了绑定,这种绑定模式在运行时会自动创建Column,所以设计时没有办法去操控列。<br>
绑定的正确方式应该是在PrototypeBindSource1的Field与TGrid的Column进行绑定,</p>
<h3 id="1-grid的列格式化">1. Grid的列格式化</h3>
<p>如果需要对单独的列进行操作,可以使用两种方法:</p>
<ol>
<li>选中LinkGridToDataSourcePrototypeBindSource1,然后在属性编辑器中选择“Columns”属性,单击右侧的按钮,打开列编辑器,在这里手动添加新的列,并设置DataMember为相应的字段,或者单击“Add All Fields”按钮,将一次性添加所有的字段。</li>
</ol>

<p><img src="https://img2024.cnblogs.com/blog/22554/202508/22554-20250809101806005-1349455332.png"></p>
<ol start="2">
<li>在LiveBindings Designer中将Field直接拖动到TGrid上,完成绑定操作。</li>
</ol>

<p><img src="https://img2024.cnblogs.com/blog/22554/202508/22554-20250808072626905-1014578624.gif"></p>
<p>在完成后,在LiveBindings Designer选中任意一条连接线,属性面板上均只显示LinkGridToDataSourcePrototypeBindSource1这个绑定链接,因此如果要添加公式,需要在属性面板中,打开Columns编辑器,选中某一个具体的Column进行设置。</p>
<p>每一个Column具有如下的一些属性:</p>
<ul>
<li><strong>ColumnStyle</strong> : 默认情况下,列被创建为 TStringColumn 。但是,您可能希望选择一个与其内容相关的更好值(数字、日期值等)。</li>
<li><strong>CustomFormat</strong> : 此属性允许您指定用于在网格中渲染值的表达式。</li>
<li><strong>CustomParse</strong> : 此属性允许您指定用于将用户输入的值转换为列的适当值的表达式。</li>
<li><strong>Header</strong> : 该属性设置列的标题。</li>
<li><strong>MemberName</strong> : 该属性设置列的对应字段。</li>
<li><strong>ReadOnly</strong> : 该属性定义列是否可以被用户编辑。</li>
<li><strong>Visible</strong> : 该属性设置列的可见性(你可以创建列,然后决定每列的可见性)。</li>
<li><strong>Width</strong> : 该属性指定列的宽度。<br>
CustomFormat和CustomParse格式表达式,与本系列在《一步一步学习使用LiveBindings(8)》中介绍的使用方式完全相同。<br>
下图是接下来实现的效果:</li>
</ul>

<p><img src="https://img2024.cnblogs.com/blog/22554/202508/22554-20250809111837365-522044695.gif"></p>
<p>这里为HireDate列应用了日期格式化,<br>
CustomFormat:</p>
<pre><code class="language-Pascal">FormatDateTime('MMMM yyyy',self.Value)
</code></pre>
<p>Salary列应用了货币格式化。<br>
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>在Grid最后添加了2列,一列是薪资等级,如果薪资高的,就加了一个绿色向上的手势,如果薪资不是那么高的,就加了一个绿色的手势。看上去就显得专业多了。</p>
<p>要实现这样的目的,在这里添加了2个新的列,一个列的ColumnStyle指定为GlyphColumn,另一个列指定为ProgressColumn列类型。</p>
<p><em>注意:虽然属性编辑器中,ColumnStyle下拉框并没有提供一个ProgressColumn类型,但仍然可以通过输入ProgressColumn来显示进度条。</em></p>
<p>GlyphColumn可以显示来自TImageList控件中的图片,因此在主窗体上添加了一个TImageList控件,将TGrid的Images属性指向它。<br>
在这个ImageList控件中,仅放了2张图片,分别是2个手势,因此其ImageIndex分别为0和1。在CustomFormat中,写下如下的公式:</p>
<pre><code class="language-Pascal">IfThen(Self.Value&gt;60000, 0, 1)
</code></pre>
<p>如果年薪大于6万,则认为是高薪了,打个绿色的手势,否则显示红色的手势。</p>
<p>ProgressColumn将使用表达式给它一个当前最高薪资的比率。如果是Manager,则最高薪资为20万,否则最高薪资设为10万。公式如下:</p>
<pre><code class="language-Pascal">100*(Self.Value /IfThen(Owner.Title.Value='Manager', 200000, 100000) )
</code></pre>
<p>可以看到在表达式中又套用了表达式,这个可以灵活应用。</p>
<h3 id="2-自定义绑定方法">2. 自定义绑定方法</h3>
<p>货币格式目前的解析公式不能满足需求,SubString(%s, 1, 15)虽然简单的移除了前缀的货币符号,但是对于数字中的分隔符号就不能识别,因此会发生错误。系统内置的方法并没有好的解决方案,好在还可以创建自定义的绑定方法。</p>
<p>请按下面的步骤实现自定义的绑定方法:</p>
<p><strong>1. 单击主菜单中的 File &gt; New &gt; Package ,创建一个新的Delphi包。</strong></p>
<p>建议立即单击工具栏上的Save All按钮,将包保存为CurrencyBindingMethod.dproj。</p>
<p>然后在包中添加一个新的Unit,保存为Methods.FormatCurrency.pas。</p>
<p>创建自定义绑定方法需要调用System.Bindings.Methods单元中的MakeInvokable方法,它返回定义在System.Bindings.EvalProtocol单元中的IInvokable接口,需要在uses区中添加对这2个单元的引用。</p>
<p>完整代码如下所示,请参考代码步骤:</p>
<pre><code class="language-Pascal">unit Methods.FormatCurrency;

interface

uses
SysUtils;

implementation

uses
//MakeInvokable 函数、 TMethodDescription 类型定义和 TBindingsMethodFactory 单例
System.Bindings.Methods,
//IValue,TValueWrapper 和 IInvokable 类型定义
System.Bindings.EvalProtocol;

//将货币类型专换为字符串类型
function CurrencyToStrInvokable: IInvokable;
begin
Result := MakeInvokable(
    function (Args: TArray&lt;IValue&gt;): IValue
    var
       AValue:Currency;
       CurrencyStr:string;
    begin
      // 1 - 输入参数识别并进行验证
      if Length(Args) &lt; 1 then
      raise EEvaluatorError.Create('CurrencyToStr: 至少需要一个参数');
      // 2 - 调用真实的转换CurrToStrF方法
      AValue:=Args.GetValue.AsCurrency;
      CurrencyStr:= CurrToStrF(AValue, ffCurrency, 2);
      // 3 - 将结果值打包为TValueWrapper
      Exit(TValueWrapper.Create(CurrencyStr));
    end
);
end;

//将字符串类型专换为货币类型。
function StrToCurrencyInvokable: IInvokable;
begin
Result := MakeInvokable(
    function (Args: TArray&lt;IValue&gt;): IValue
    var
       AValue:String;
       CurrencyVal:Currency;
       C: char;
       LDigits: string;
    begin
      // 1 - 输入参数识别并进行验证
      if Length(Args) &lt; 1 then
      raise EEvaluatorError.Create('StrToCurrency: 至少需要一个参数');
      // 2 - 将参数转换为真实的参数类型,再进行实际的业务处理
      AValue:=Args.GetValue.AsString;
      for C in AValue do
          case C of
         '0'..'9',
         '.':
             LDigits := LDigits + C;
          end;
      CurrencyVal := StrToCurr(LDigits);
      // 3 - 将结果装包装起来
      Exit(TValueWrapper.Create(CurrencyVal));
    end
);
end;

initialization
// 4 -注册方法
TBindingMethodsFactory.RegisterMethod(
    TMethodDescription.Create(
      CurrencyToStrInvokable
      , 'CurrencyToStr', 'CurrencyToStr', '', True
      , '货币转换为字符串'
      , nil
    )
);

TBindingMethodsFactory.RegisterMethod(
    TMethodDescription.Create(
      StrToCurrencyInvokable
      , 'StrToCurrency', 'StrToCurrency', '', True
      , '字符串转换为货币'
      , nil
    )
);

finalization
// 5 - 卸载已经注册的方法
TBindingMethodsFactory.UnRegisterMethod('CurrencyToStr');
TBindingMethodsFactory.UnRegisterMethod('StrToCurrency');

end.

</code></pre>
<p>可以看到,代码中定义了CurrencyToStr和StrToCurrency这两个方法,它们都接收一个参数,并且调用了SysUtil中的CurrToStrF和StrToCurr函数进行了转换。</p>
<p><strong>2. 在Project Manager窗口右击CurrencyBindingMethod项目名称,先Build一次,再单击"Install"菜单项进行安装。</strong></p>

<p><img src="https://img2024.cnblogs.com/blog/22554/202508/22554-20250809135457108-1807078818.png"></p>
<p>安装完成后,回到LiveBindings_BindFormat.dproj项目,选中TBindingList控件,在属性编辑器中找到Methods属性,单击右侧的按钮,可以看到CurrencyToStr和StrToCurrency这两个方法已经出现在了列表中。</p>

<p><img src="https://img2024.cnblogs.com/blog/22554/202508/22554-20250809135726070-1835874252.png"></p>
<p><strong>3. 要让我们自己的项目能够正确识别这2个方法,还需要将包中的Methods.FormatCurrency.pas单元添加到uMainForm.pas的uses区,否则会提示方法无法找到的错误。</strong></p>
<p>请在Project &gt; Options &gt; Delphi Compiler &gt; Search Path路径中添加了对Package代码的引用。</p>

<p><img src="https://img2024.cnblogs.com/blog/22554/202508/22554-20250809140424698-2061736458.png"></p>
<p>在uses区中添加如下的引用:</p>
<pre><code class="language-Pascal">implementation

{$R *.fmx}

uses
System.Bindings.Helper, System.Bindings.Methods,
    System.Bindings.EvalProtocol,Methods.FormatCurrency;

</code></pre>
<p>将Salary的公式改为如下:</p>
<p>CustomFormat:</p>
<pre><code class="language-Pascal">CurrencyToStr(self.Value)
</code></pre>
<p>CustomParse</p>
<pre><code class="language-Pascal">StrToCurrency(%s)
</code></pre>
<p>经过这样的处理,现在在货币的显示和输入上,就非常理想了,如下图所示:</p>

<p><img src="https://img2024.cnblogs.com/blog/22554/202508/22554-20250809141207576-2059909378.gif"></p>
<h3 id="4-使用自定义的格式化方法">4. 使用自定义的格式化方法</h3>
<p>在《一步一步学习使用LiveBindings(8)》中曾经介绍过通过self.owner.owner,返回到了主窗体TMainForm,我们在主窗体上定义了一个属性MyProgName,可以通过self.owner.owner.MyProgName访问到此属性。同理,还可以在主窗体上定义一个函数,通过self.owner.owner进行访问,实现自定义的格式化方法。</p>
<p>实现过程如下:</p>
<p><strong>1. 首先在uMainForm的public区定义了一个函数,实现根据指定的格式化字符串格式化数字,定义与实现如下所示:</strong></p>
<pre><code class="language-Pascal">public
    { Public declarations }
    property MyProgName:string read GetProgName;

    //根据指定的格式化字符串或区域格式化符点数。
    function FormFormatFloat(const AFormat: string; const AValue: Extended; const ALocale: string): string;
end;

var
frmMain: TfrmMain;

implementation

{$R *.fmx}

uses
System.Bindings.Helper, System.Bindings.Methods,
    System.Bindings.EvalProtocol,Methods.FormatCurrency;

function TfrmMain.FormFormatFloat(const AFormat: string; const AValue: Extended;
const ALocale: string): string;
var
LFormatSettings: TFormatSettings;
begin
LFormatSettings := FormatSettings;
if not ALocale.IsEmpty then
    LFormatSettings := TFormatSettings.Create(ALocale);
Result := FormatFloat(AFormat, AValue, LFormatSettings);
end;

</code></pre>
<p>接下来,我使用LiveBindings Wizard向导新建了一个TEdit指向Salary的链接,然后为这个TEdit的绑定链接指定CustomFormat如下,使其保留4位小数:</p>
<pre><code class="language-Pascal">Self.Owner.Owner.FormFormatFloat('#,#0.0000', self.Value, '')
</code></pre>
<p>效果如下:</p>

<p><img src="https://img2024.cnblogs.com/blog/22554/202508/22554-20250809152049598-1271325777.gif"></p>
<p>可以看到,果然正确的实现了格式化显示。</p>
<p>右边的那个手势图标,是一个TGlyph组件,它可以连接到一个TImageList控件,通过在LiveBindings Designer中,将Salary绑定到其ImageIndex属性。最后应用了如下的公式:</p>
<pre><code class="language-Pascal">IfThen(Self.Value&gt;60000, 0, 1)
</code></pre>

<p><img src="https://img2024.cnblogs.com/blog/22554/202508/22554-20250809152535710-1700482953.png"></p>
<h3 id="总结">总结</h3>
<p>本文介绍了格式化的几种进阶方法:</p>
<ul>
<li>使用表达式列格式化。</li>
<li>自定义绑定方法。</li>
<li>使用自定义表单方法格式化。</li>
</ul>
<p>强烈推荐使用自定义绑定方法,不是很推荐自定义表单方法,但有时候表单级别的方法能解决很多业务逻辑相关的格式化问题。所以具体如何架构,还是要具体问题,具体分析。</p><br><br>
来源:https://www.cnblogs.com/lincats/p/19029850
頁: [1]
查看完整版本: 一步一步学习使用LiveBindings(9) LiveBindings图像绑定与自定义绑定方法(2)