Stories

Detail Return Return

一步一步學習使用LiveBindings(8) 使用嚮導創建用户界面,綁定格式化入門(1) - Stories Detail

在多數真實的應用場景中,用户對於顯示是比較挑剔的。比如貨幣要顯示貨幣符號,日期要顯示成特定的格式,可能要根據字段值顯示圖片等等。

本課程包含如下知識點:
  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。

你的項目結構應該像這樣:

img

請在屬性編輯器中將表單的Name屬性更改為"frmMain",表單的Catpion指定為“LiveBindings Demo”,雖然不是必須,但是給每個元素一個友好的具有語義性的名稱是一個好的習慣。

2. 本課將與之前的課不同,直接使用LiveBindings嚮導來構建所有的用户界面和原型數據。直接右擊鼠標,在彈出的菜單中找到“LiveBindings Wizard...”菜單項,打開LiveBindings嚮導。

注意:假如在鼠標右鍵菜單上找不到“LiveBindings Wizard...”菜單項,可以單擊主菜單上的 Toos > Options > LiveBindings,勾選 Display LiveBindings Wizard in context menu 複選框。

img

3. 在第1個嚮導頁上可以看到5個功能項,選中不同的單選框,左側的嚮導欄會出現變化,表示嚮導的任務頁面是不同的。這5種類型的綁定任務作用如下所示:

img

  • Create a data source:創建新的數據源。

在這裏使用默認的第1個選項Link a control with a field,單擊“Next”按鈕。

在第2個任務頁面要求選擇綁定控件或者是新建控件,由於目前還沒有創建任何控件,因此在這裏選擇“New Control”標籤頁,然後選擇TEdit控件。

單擊“Next”按鈕,進入到DataSource任務頁,同樣的,選擇“New DataSource”菜單項,在這個標籤頁包含了“FireDAC、TBindSourceDBX和TProtoTypeBindSource”三個項,在這裏選擇使用第三個項,這將進入到為TProtoTypeBindSource定義字段窗口。

img

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

img

最後在Options任務頁面,勾選2個複選框。

  • Add data source navigate 添加TBindNavigator控件。
  • Add control label 添加控件標籤

img

單擊“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。

img

6 再次打開LiveBindings Wizard嚮導窗口, 這一次選擇"Link a grid with a data source"菜單項,將一個TGrid與現存的ProtoTypeBindingSource1進行綁定。

img

操作完這一切,再經過一些簡單的佈局工作,一個簡單的,具有增刪改查的UI就已經做出來了。説實話,這對於快速開發或UI的原型開發來説,真的是太方便了。

img

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菜單項來實現,如下圖:

img

注意:這裏的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;

現在運行這個示例,可以看到現在它確實具有了現代應用程序的雛形。如下圖所示:

img

儘管如此,離真實的應用程序還是有一些距離,最顯然的就是缺乏格式指定。比如對於薪資Salary字段,最好是顯示一個貨幣符號,橫幅的聯繫人名稱可以用大寫顯示等等。

當使用設計器添加了綁定後,在TBindingList中會添加很多的綁定項,雙擊主窗體的TBindingList控件,將會彈出如下圖所示的綁定項列表。

img

仔細觀察這個列表,它們都是用Link開頭:

  • 對於可編輯的雙向鏈接,上是以LinkControlTo...這樣的命名。
  • 對於不可編輯的單向鏈接,上是以LinkPropertyTo開頭。
  • 對於Grid,這裏有一個專用的LinkGridToDataSource來實現。

對於LinkControlTo這樣的雙向綁定鏈接,選中之後,在屬性編輯器中可以看到它具有CustomFormat和CustomParse這兩個屬性,LinkPropertyTo開頭的鏈接則只具有一個CusomFormat。

7. 現在首先將橫幅的聯繫人大寫,並且如果在職的話,顯示一個*號。在CustomFormat中寫了如下的表達式:

UpperCase(self.%s) + IfThen(Owner.AvailNow.Value, ' (*)', "")

img
這個表達式中,一些關鍵元素的作用如下:

  • %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。

img

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

則可以綁定到窗體級別定義的變量,這就可以實現很多業務邏輯的處理工作了。

當然具體的Owner所處的層次,需要視程序的層次而定。

選中主窗體上的TBindingList,在屬性編輯器中找到method屬性,單擊編輯器中的按鈕,可以看到所有可以使用的綁定方法列表。

img

現在運行程序,可以看到橫幅果然應用到了格式化。

img

7. 現在讓Salary顯示一個貨幣符號,並且在輸入時也能夠解析這個貨幣符號。

CustomFormat:

Format('%%m', self.Value + 0.0)

CustomParse:

SubString(%s, 1, 15)

這是一個雙向的綁定,因此在這裏指定了CustomParse,運行效果如下:

img

應該接近預期了,不過這個CustomParse就有點簡單。

可以看到在表達式中使用了Format,還可以使用FormatDateTime來格式化日期,如下所示:

FormatDateTime('yyyy-mm-dd', Owner.HireDate.AsDateTime)

除了上面的方法之外,筆者在這裏整理了一份方法列表參考:

LiveBindings方法列表:

IfThen(Condition, Value1, Value2):

實現了內聯 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 中選定項的前綴字img符串,如果未選擇項,則警告用户。

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

如果所有傳入的條件都計算為 True (空值或非布爾條件將被視為 False 值),則返回 True 。這是一個實用函數,你可以用它來模擬 AND 運算符及其參數,其中一些可能未提供(null)。最多可以擁有 100 個參數(硬編碼)。以下是一個示例:

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也進行了一番格式化後,最終的效果如下所示:

img

格式化的內容,在下一課,將繼續進行介紹。

Add a new Comments

Some HTML is okay.