動態

詳情 返回 返回

一步一步學習使用LiveBindings(12) LiveBindings與具有動態呈現的TListView - 動態 詳情

在《一步一步學習使用LiveBindings(3)》中,曾經簡單介紹過TListView的綁定,在那一課中,將TListView的ItemAppearance.ItemAppearance屬性設置為ImageListItemRightButton,這將使用預設置的TListViewItem的項外觀。

在這一課中,將學習如下的內容:

  • 1.TListView.ItemAppearance外觀基礎。
  • 2.使用DynamicAppearance進行動態外觀的定義。
  • 3.通過一步一步的操作案例來學習TListView的設計和事件處理。

本文的最終實現效果如下所示:

img

它包含豐富的樣式,圖片,分組,小計以及數字和日期的格式化等功能。

1. TListView.ItemAppearance外觀基礎

在屬性編輯器中,ItemAppearance屬性下面,包含諸多外觀項的集合,我們應該重點看的是包含很多預置項的Item外觀項:

  • ItemAppearance:瀏覽模式下的TListViewItem的項外觀名稱,默認值為:ListItem。
  • ItemEditAppearance:編輯模式下的TListViewItem的項外觀名稱:默認值為:ListItemShowCheck。

img

這些預定義的類型其實是對幾種可繪製的樣式類型進行的顯示與隱藏以及顯示位置的排列,可繪製樣式類型的基類名稱為:TListItemDrawable,它包含:TListItemText 、 TListItemImage 、 TListItemAccessory 、 TListItemGlyphButton 和 TListItemTextButton子類。每個子類又有對應的TObjectAppearance子類,用來提供呈現的外觀。

換句話説,列表視圖中的項將被渲染為一組可繪製項( TListItemDrawable 的子類),這些項的組合稱為 ItemAppearance ,而存儲在 ItemAppearance 中的關於每個可繪製項的詳細信息(設置)在設計時通過 TObjectApperance 的子類提供。

所以,回到可視上來,當在ItemAppearance上選中一種呈現時,在屬性編輯器上的ItemAppearanceObjects中的ItemObjects的呈現對象也會發生相應的變化。一些對象被隱藏,一些對象被顯示。

img

我們可以在屬性編輯器上,展開ItemAppearanceObjects.ItemObjects下面的對象屬性,對每一個項的外觀特性進行編輯。

可以看到,預定義的ItemAppearance項的變化,下面的ItemEditAppearance項也會發生變化,它們有個對應的關係。

ItemAppearance ItemEditAppearance
Custom Custom
DynamicAppearance DynamicAppearance
ImageListItem ImageListItemDelete,ImageListItemShowCheck
ImageListItemBottomDetail ImageListItemBottomDetailShowCheck
ImageListItemBottomDetailRightButton ImageListItemBottomDetailRightButtonShowCheck
ImageListItemRightButton ImageListItemRightButtonDelete,ImageListItemRightButtonShowCheck
ListItem ListItemDelete,ListItemShowCheck
ListItemRightDetail ListItemRightDetailDelete,ListItemRightDetailShowCheck

如果將ItemAppearance.ItemAppearance屬性指定為Custom,則所有的項都會顯示出來,開發人員可以控制哪些顯示,哪些隱藏。

Structure提供更加直觀的視圖,當在屬性編輯器中更改ItemAppearance後,Structure可以直觀的看到元素的變化。

img

下面是一張代表各個預定外觀效果的圖:

img

本圖片來自《Delphi GUI Programming with FireMonkey》

這個圖顯示了同一 TListView 實例的八個不同設置(使用相同的數據)。在截圖的第一列,從上到下,可以看到 ListItem 、 ListItemRightDetail 、 Custom 和 DynamicAppearance 。在第二列,從上到下,可以看到 ImageListItem 、 ImageListItemRightButton 、 ImageListItemBottomDetail 和 ImageListItemBottomDetailRightButton 。

而不同的ItemAppearance,也會導致在LiveBindings中的可綁定項的變化,如下圖所示:

img

為了讓我們可以更加可視化的編輯Item的外觀,Delphi IDE提供了DesignMode和EditMode,它提供了可視化的外觀設計。

img

我們可以在這裏改變元素的字體、位置、外觀。

注意:不要試圖刪除預定義的元素,Delphi IDE會提示無法刪除。


2. 使用DynamicAppearance進行動態外觀的定義

DynamicAppearance之外的所有其他的項,可繪製元素都是固定的,所以我們沒有辦法再增加更多的項或刪除現有的項,於是DynamicAppearance就應需求而生了。

定義如下:

DynamicAppearance : 選擇此外觀以通過 Delphi IDE 手動定義您項目的外觀;也就是説,可以添加任意數量的可繪製元素,並通過表單設計器和對象檢查器進行配置。這是最先進的自定義技術,具有設計時支持。

對於DynamicAppearance項,在IDE上是通過屬性編輯器與Structure結構視圖共同來完成的,如下圖所示:

img

通過在Structure選中Item,屬性編輯器中,可以看到可以像上圖一樣,添加新的ObjectAppearance項,也可以單擊某個項,通過“Delete Object”進行刪除。

當我們添加了新的項,並且在屬性編輯器中,指定AppearanceObjectName為自定義的名稱後,一個非常有用的特性是在LiveBindings Designer上,可以看到新添加的可繪製對象,這意味着可以進行LiveBinding綁定,這會大大方便數據的寫入。

img

3. TListView與LiveBindings綁定示例

接下來通過一個簡單的例子來介紹如何實現通過LiveBindings綁定到一個具有動態呈現項的TListView控件。

1. 單擊主菜單中的 File > New > Multi-Device Application - Delphi > Blank Application ,創建一個新的多設備應用程序。
建議立即單擊工具欄上的Save All按鈕,將單元文件保存為Forms.Main.pas,將項目保存為LiveBindings_BindToListView.dproj。

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

img

2. 從工具欄上拖一個TFDMemTable控件到桌面,將其命名為EmployeeData,然後右擊該控件,從彈出的菜單中選擇“Load From File...”菜單,選擇如下的文件位置:

C:\Users\Public\Documents\Embarcadero\Studio\23.0\Samples\Data\employee.FDS

這是Delphi 12.3中附帶的一個示例文件,加載完成之後,再次右擊TFDMemTable控件,從彈出的菜單中選擇“Edit DataSet...”菜單項,應該能夠預覽到TFDMemTable中包含的數據。

img

3. 接下來,右擊主窗體,從彈出的菜單中打開“LiveBindings Wizard...”菜單項,將一個新TListBox控件綁定到EmployeeData的FirstName字段,如下動圖。

img

嚮導跑完後,會生成一個TBindSourceDB、一個TBindingList和一個TBindNavigator和一個TListView控件。不過TBindNavigator控件被生成到了TListView控件內部,需要在Structure面板將它拖到主窗口級別。

img

4. 選中ListView1,在屬性面板切換到ItemAppearance.ItemAppearance,選擇DynamicAppearance,然後在主窗體上右擊ListView1,選擇“Toggle DesignMode”菜單項,主窗體將切換到設計模式。

在設計視圖中,有一個已經建好的名為Text1的TTextObjectApperance對象。在Structure窗口選中ListView的Item,在屬性窗口中,AppearanceObjectName為FullNameText。

img

設置好之後,回到主窗體的ListView的設計視圖,可以看到有一個Item名為FullNameText的文本對象。在屬性窗口中,幾個主要的設置屬性如下:

  • Height 和 Width :這些屬性決定了繪製項的大小。當值為零時,繪製項將採用父項的相同大小(寬度單獨設置,高度可以保持為零),否則你可以指定繪製項的實際大小。

  • Align 和 VertAlign : 這兩個屬性都從 TListItemAlign 單元中定義的 FMX.ListView.Types 類型獲取值。有三個可用的值: Leading 、 Center 和 Trailing 。我們可以將列表項視為一個矩形形狀,並且可以確定水平和垂直方向上的對齊方式。這三個值分別表示在項目的開始處、中間和結束處。顯然,這個設置隻影響具有指定大小( Width 和/或 Height 屬性的非零值)的可繪製項。

  • PlaceOffset : 該屬性的類型為 TPosition (在 FMX.Types 單元中定義),因此有兩個子字段,分別命名為 X 和 Y 。您可以使用這些字段來設置相對於其他位置(通過 Align 、 VertAlign 、 Width 和 Height 屬性確定)的水平偏移和/或垂直偏移。

  • Opacity : 該屬性的值決定了可繪製項的不透明度。

不像在樣式設計器中有一個TLayout可以進行佈局,呈現對象主要依賴於Align、TextAlign,VertAlign和TextVertAlign這幾個屬性,由於對象可以互相重疊,還可能需要用PlaceOffset進行編移設置。

注意:應該總是考慮到列表項的寬高可能會因為屏幕的不同而變化,因此在設計時候要可慮到適應性。

通常將Leading看作是Left對齊,Trailing看作是Right對齊,不像標準控件會有一個Client對齊。當添加一個新的對象時,會佔滿整個項空間,通過調整Width屬性來調整其寬度。

5. 讓FullNameTextc對象的Align保持為Leading對齊,寬度200,指定其TextAlign為Leading,TextColor為Darkcyan,Font.size為16。TextVertAlign屬性為Leading,文本將會顯示在最頂端

接下來,在Structure視圖選中ListViewFirstName > ItemAppearance > Item 項,然後在Object Inspector(也就是本系列課程中的屬性編輯器)的最下面單擊“Add new”,選擇TTextAppearance,添加一個新的文本對象。

馬上將其AppearanceObjectName更改為SalaryText。默認情況下這個對象的Width和Height均為0,所以佔滿整個空間。指定其Width為100,Align為Trailing,讓其右對齊,默認情況下其TextVertAlign屬性為Trailing,讓其在底部顯示。

分別再添加2個TTextAppearance對象,Width為100,Align為Trailing,命名為EmpNoText的TextVertAlign屬性為Leading,顯示在最頂端,

命名為HireDateText的Width為100,Align為Trailing,TextVertAlign為Center,讓其在中間顯示。

由於這3個對象太過於靠近右側,因此在將它們的PlaceOffset.X指定為-5。以確保有一些顯示上的空白感。

img

6. 最後再添加一個TImageObjectAppearance對象,命名為ThumbImage,圖片沒有TextSettings相關的設置,因此屬性相對簡潔,指定其Align屬性為Trailing,靠右對齊。並且其PlaceOffset為-100,以確保不會遮檔住右側的文本。

img

設置完成後,可以右擊TListView控件,取消勾選“Toggle DesignMode”菜單項,在設計窗口上,應該可以看到數據已經綁定到了正確的位置上。

在設計時,也可以選中EmployeeData控件,右擊選擇“Edit DataSet”菜單項,在預覽窗口中上下移動,ListView上的數據也能實時預覽。

img

7. 現在開始進行格式化,打開LiveBindings設計器,選中BindingSourceDB和ListView之間的任意一條綁定鏈接,單擊Object Inspector面板的FillExpressions右側的小按鈕,打開填充表達式的面板,在這裏可以添加格式表達式。

img

分別為每個對象添加如下的格式:

* FullNameText: Self.AsString + ' ' + DataSet.*
* FirstName.AsString
* HireDateText: FormatDateTime('MMMM yyyy', Self.AsDateTime)
* SalaryText:Format('%%m', Self.AsFloat+0.0)
* EmpNoText: 'No. ' + Self.AsString
* PhoneExtText: ‘Ext.' + Self.AsString

7. 從工具面板拖一個TImageList控件到表單,然後隨便添加2張圖片。將ListView的Images屬性指向這個ImageList控件,在LiveBindings Designer中,將Salary連接到ThumbImage控件,在FillExpressions窗口添加如下的公式:

* ThumbImage:IfThen(Self.AsFloat>30000, 1, 0)

這表示薪資大於3萬的顯示ImageIndex為1,否則為0.

img

8. 更進一步,接下來試一試TListView的分組功能。分組功能的每一個組的組名就是一個ItemHeader對象,只不過通過一種中斷的機制使得具有相同Header的元素顯示為一個組。設置很簡單,在LiveBindings Designer中選中一個連接,在屬性窗口中分別指定如下的字段:

FillHeaderFileName:FirstName
FillHeaderCustomFormat:SubString(%s, 0, 1)
FillBreakFieldName:FirstName
FillBreakCustomFormat: SubString(%s,0,1)

img

要使用得分組功能得以成功顯示,還必須為TFDMemTable設置一個索引字段,在這裏指定IndexFieldNames為:FirstName,現在可以看到果然已經顯示了分組效果。

img

8. 假如用户需要在頁腳顯示彙總功能,這是程序員經常要面對的需求。經過一翻代碼修改,最終的效果是這樣的:

img

這裏不光產生了頁腳的小計,還具有對僱傭日期小於1990年的員工進行字體紅色顯示。

TLinkListControlToField具有如下四個事件:

  • TLinkListControlToField.OnFilledList:在填充列表控件完成後(所有數據已綁定)。
  • TLinkListControlToField.OnFilledListItem:在填充列表控件完成後(所有數據已綁定)。
  • TLinkListControlToField.OnFillingList:在開始填充列表控件之前(即綁定數據初始化階段)。
  • TLinkListControlToField.OnFillingListItem:在填充每一個列表項之前(逐項處理時觸發)。
事件 觸發時機 常見用途
OnFillingList 綁定開始前 初始化、清空列表
OnFillingListItem 處理每個項前 動態修改項數據
OnFilledList 綁定完成後 添加總計、排序
OnFilledListItem 處理每個項後 累計計算、分組處理

在這個例子中處理了3個事件:

img

下面是完成上面例子所需的代碼:

unit Forms.Main;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
  FireDAC.Stan.Intf, FireDAC.Stan.Option, FireDAC.Stan.Param,
  FireDAC.Stan.Error, FireDAC.DatS, FireDAC.Phys.Intf, FireDAC.DApt.Intf,
  Data.DB, FireDAC.Comp.DataSet, FireDAC.Comp.Client, FireDAC.Stan.StorageBin,
  Data.Bind.EngExt, Fmx.Bind.DBEngExt, FMX.ListView.Types,
  FMX.ListView.Appearances, FMX.ListView.Adapters.Base, System.Rtti,
  System.Bindings.Outputs, Fmx.Bind.Editors, Data.Bind.Controls, FMX.Layouts,
  Fmx.Bind.Navigator, Data.Bind.Components, FMX.ListView, Data.Bind.DBScope,
  System.ImageList, FMX.ImgList,System.StrUtils,System.DateUtils;

type
  TMainForm = class(TForm)
    EmployeeData: TFDMemTable;
    BindingsList1: TBindingsList;
    EmployeeBindSourceDB: TBindSourceDB;
    ListViewFirstName: TListView;
    LinkListControlToFieldFirstName: TLinkListControlToField;
    NavigatorBindSourceDB1: TBindNavigator;
    EmployeeDataEmpNo: TIntegerField;
    EmployeeDataLastName: TStringField;
    EmployeeDataFirstName: TStringField;
    EmployeeDataPhoneExt: TStringField;
    EmployeeDataHireDate: TDateTimeField;
    EmployeeDataSalary: TFloatField;
    ImageList1: TImageList;
    StyleBook1: TStyleBook;
    procedure LinkListControlToFieldFirstNameFilledList(Sender: TObject);
    procedure LinkListControlToFieldFirstNameFilledListItem(Sender: TObject;
      const AEditor: IBindListEditorItem);
    procedure LinkListControlToFieldFirstNameFillingListItem(Sender: TObject;
      const AEditor: IBindListEditorItem);
  private
    { Private declarations }
    //定義兩個用來統計用的變量
    NamePrefix:string;
    Cumul:Currency;
  public
    { Public declarations }
  end;

var
  MainForm: TMainForm;

implementation

{$R *.fmx}

procedure TMainForm.LinkListControlToFieldFirstNameFilledList(Sender: TObject);
var Item : TListViewItem;
    ItemText : String;
    I : integer;
begin
     // 將頁腳行向上移動一行
     for I := 1 to Pred(ListViewFirstName.ItemCount) do
     begin
       //下面這段代碼是為避免小計顯示在ItemHeader的下面。
       if ListViewFirstName.Items[I].Purpose=TListItemPurpose.Footer then
         begin
           // 獲取頁腳文本
           Item:=ListViewFirstName.Items[I];
           ItemText:=Item.Text;
           // 刪除已創建的頁腳(沒有其他解決方案嗎?)
           ListViewFirstName.Items.Delete(i);
           // 插入新的頁腳
           with ListViewFirstName.Items.Insert(I-1) do
             begin
               Text := ItemText;
               Purpose := TListItemPurpose.Footer;
             end;
         end;
      end;
  // 添加最後的彙總(列表結尾處)
  with ListViewFirstName.Items.Add do
    begin
      Text := Format('小計 %s %m',[NamePrefix,Cumul]);
      Purpose := TListItemPurpose.Footer;
    end;
end;

procedure TMainForm.LinkListControlToFieldFirstNameFilledListItem(
  Sender: TObject; const AEditor: IBindListEditorItem);
var AnItem : TListViewItem;
begin
AnItem:=AEditor.CurrentObject as TListViewItem;

if (AnItem.Purpose=TListItemPurpose.Header) then
 begin
  // 如果不是第一個分組,則添加分組頁腳
  if (AnItem.Index>1)  then
   begin
      with ListViewFirstName.Items.Add do
       begin
          Text := Format('小計 %s %m',[NamePrefix,Cumul]);
          Purpose := TListItemPurpose.Footer;
     end;
   end;
  NamePrefix:=Copy(EmployeeData.FieldByName('FirstName').AsString,1,1); // 獲取名字首字母
  Cumul:=0;                                                             // 重置累計值
 end
 else begin
   // 按首字母累計薪資
    Cumul:=Cumul+EmployeeData.FieldByName('Salary').AsCurrency; // 累計薪資
 end;
end;

procedure TMainForm.LinkListControlToFieldFirstNameFillingListItem(
  Sender: TObject; const AEditor: IBindListEditorItem);
var
  LTextObject:TListItemText;
  AnItem : TListViewItem;
begin
  AnItem:=AEditor.CurrentObject as TListViewItem;
  //如果員工的僱傭日期小於1990年的,則用紅色字體顯示。
  LTextObject := AnItem.Objects.FindDrawable('HireDateText') as TListItemText;
  if Assigned(LTextObject) then
    if YearOf(EmployeeData.FieldByName('HireDate').AsDateTime) < 1990 then
      LTextObject.TextColor := TAlphaColorRec.Red
    else
      LTextObject.TextColor := TAlphaColorRec.Black;
end;

end.

在FillingListItem事件中,根據HireDate的時間是否小於1990設置了字體對象的顏色為紅色,這樣會更加顯眼。

總結:

這一節深入的介紹了TListView的定製化功能,介紹了一些基本的定製的步驟和方法。TListView的Item目前是固定的,這也能滿足多數需求,但是有時候是需要計算的,並不是固定的,這就需要處理TListView的事件來測量了,以後的章節中將會進一步介紹。

Add a new 評論

Some HTML is okay.