一步一步学习使用LiveBindings(12) LiveBindings与具有动态呈现的TListView

在《一步一步学习使用LiveBindings(3)》中,曾经简单介绍过TListView的绑定,在那一课中,将TListView的ItemAppearance.ItemAppearance属性设置为ImageListItemRightButton,这将使用预设置的TListViewItem的项外观。

在这一课中,将学习如下的内容:

  • 1.TListView.ItemAppearance外观基础。
  • 2.使用DynamicAppearance进行动态外观的定义。
  • 3.通过一步一步的操作案例来学习TListView的设计和事件处理。

本文的最终实现效果如下所示:

它包含丰富的样式,图片,分组,小计以及数字和日期的格式化等功能。

1. TListView.ItemAppearance外观基础

在属性编辑器中,ItemAppearance属性下面,包含诸多外观项的集合,我们应该重点看的是包含很多预置项的Item外观项:

  • ItemAppearance:浏览模式下的TListViewItem的项外观名称,默认值为:ListItem。
  • ItemEditAppearance:编辑模式下的TListViewItem的项外观名称:默认值为:ListItemShowCheck。

这些预定义的类型其实是对几种可绘制的样式类型进行的显示与隐藏以及显示位置的排列,可绘制样式类型的基类名称为:TListItemDrawable,它包含:TListItemText 、 TListItemImage 、 TListItemAccessory 、 TListItemGlyphButton 和 TListItemTextButton子类。每个子类又有对应的TObjectAppearance子类,用来提供呈现的外观。

换句话说,列表视图中的项将被渲染为一组可绘制项( TListItemDrawable 的子类),这些项的组合称为 ItemAppearance ,而存储在 ItemAppearance 中的关于每个可绘制项的详细信息(设置)在设计时通过 TObjectApperance 的子类提供。

所以,回到可视上来,当在ItemAppearance上选中一种呈现时,在属性编辑器上的ItemAppearanceObjects中的ItemObjects的呈现对象也会发生相应的变化。一些对象被隐藏,一些对象被显示。

我们可以在属性编辑器上,展开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可以直观的看到元素的变化。

下面是一张代表各个预定外观效果的图:

本图片来自《Delphi GUI Programming with FireMonkey》

这个图显示了同一 TListView 实例的八个不同设置(使用相同的数据)。在截图的第一列,从上到下,可以看到 ListItem 、 ListItemRightDetail 、 Custom 和 DynamicAppearance 。在第二列,从上到下,可以看到 ImageListItem 、 ImageListItemRightButton 、 ImageListItemBottomDetail 和 ImageListItemBottomDetailRightButton 。

而不同的ItemAppearance,也会导致在LiveBindings中的可绑定项的变化,如下图所示:

为了让我们可以更加可视化的编辑Item的外观,Delphi IDE提供了DesignMode和EditMode,它提供了可视化的外观设计。

我们可以在这里改变元素的字体、位置、外观。

注意:不要试图删除预定义的元素,Delphi IDE会提示无法删除。


2. 使用DynamicAppearance进行动态外观的定义

DynamicAppearance之外的所有其他的项,可绘制元素都是固定的,所以我们没有办法再增加更多的项或删除现有的项,于是DynamicAppearance就应需求而生了。

定义如下:

DynamicAppearance : 选择此外观以通过 Delphi IDE 手动定义您项目的外观;也就是说,可以添加任意数量的可绘制元素,并通过表单设计器和对象检查器进行配置。这是最先进的自定义技术,具有设计时支持。

对于DynamicAppearance项,在IDE上是通过属性编辑器与Structure结构视图共同来完成的,如下图所示:

通过在Structure选中Item,属性编辑器中,可以看到可以像上图一样,添加新的ObjectAppearance项,也可以单击某个项,通过"Delete Object"进行删除。

当我们添加了新的项,并且在属性编辑器中,指定AppearanceObjectName为自定义的名称后,一个非常有用的特性是在LiveBindings Designer上,可以看到新添加的可绘制对象,这意味着可以进行LiveBinding绑定,这会大大方便数据的写入。

3. TListView与LiveBindings绑定示例

接下来通过一个简单的例子来介绍如何实现通过LiveBindings绑定到一个具有动态呈现项的TListView控件。

1. 单击主菜单中的 File > New > Multi-Device Application - Delphi > Blank Application ,创建一个新的多设备应用程序。

建议立即单击工具栏上的Save All按钮,将单元文件保存为Forms.Main.pas,将项目保存为LiveBindings_BindToListView.dproj。

你的项目结构应该像这样:

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中包含的数据。

3. 接下来,右击主窗体,从弹出的菜单中打开"LiveBindings Wizard..."菜单项,将一个新TListBox控件绑定到EmployeeData的FirstName字段,如下动图。

向导跑完后,会生成一个TBindSourceDB、一个TBindingList和一个TBindNavigator和一个TListView控件。不过TBindNavigator控件被生成到了TListView控件内部,需要在Structure面板将它拖到主窗口级别。

4. 选中ListView1,在属性面板切换到ItemAppearance.ItemAppearance,选择DynamicAppearance,然后在主窗体上右击ListView1,选择"Toggle DesignMode"菜单项,主窗体将切换到设计模式。

在设计视图中,有一个已经建好的名为Text1的TTextObjectApperance对象。在Structure窗口选中ListView的Item,在属性窗口中,AppearanceObjectName为FullNameText。

设置好之后,回到主窗体的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。以确保有一些显示上的空白感。

6. 最后再添加一个TImageObjectAppearance对象,命名为ThumbImage,图片没有TextSettings相关的设置,因此属性相对简洁,指定其Align属性为Trailing,靠右对齐。并且其PlaceOffset为-100,以确保不会遮档住右侧的文本。

设置完成后,可以右击TListView控件,取消勾选"Toggle DesignMode"菜单项,在设计窗口上,应该可以看到数据已经绑定到了正确的位置上。

在设计时,也可以选中EmployeeData控件,右击选择"Edit DataSet"菜单项,在预览窗口中上下移动,ListView上的数据也能实时预览。

7. 现在开始进行格式化,打开LiveBindings设计器,选中BindingSourceDB和ListView之间的任意一条绑定链接,单击Object Inspector面板的FillExpressions右侧的小按钮,打开填充表达式的面板,在这里可以添加格式表达式。

分别为每个对象添加如下的格式:

Pascal 复制代码
* 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窗口添加如下的公式:

Pascal 复制代码
* ThumbImage:IfThen(Self.AsFloat>30000, 1, 0)

这表示薪资大于3万的显示ImageIndex为1,否则为0.

8. 更进一步,接下来试一试TListView的分组功能。分组功能的每一个组的组名就是一个ItemHeader对象,只不过通过一种中断的机制使得具有相同Header的元素显示为一个组。设置很简单,在LiveBindings Designer中选中一个连接,在属性窗口中分别指定如下的字段:

FillHeaderFileName:FirstName

FillHeaderCustomFormat:SubString(%s, 0, 1)

FillBreakFieldName:FirstName

FillBreakCustomFormat: SubString(%s,0,1)

要使用得分组功能得以成功显示,还必须为TFDMemTable设置一个索引字段,在这里指定IndexFieldNames为:FirstName,现在可以看到果然已经显示了分组效果。

8. 假如用户需要在页脚显示汇总功能,这是程序员经常要面对的需求。经过一翻代码修改,最终的效果是这样的:

这里不光产生了页脚的小计,还具有对雇佣日期小于1990年的员工进行字体红色显示。

TLinkListControlToField具有如下四个事件:

  • TLinkListControlToField.OnFilledList:在填充列表控件完成后(所有数据已绑定)。
  • TLinkListControlToField.OnFilledListItem:在填充列表控件完成后(所有数据已绑定)。
  • TLinkListControlToField.OnFillingList:在开始填充列表控件之前(即绑定数据初始化阶段)。
  • TLinkListControlToField.OnFillingListItem:在填充每一个列表项之前(逐项处理时触发)。
事件 触发时机 常见用途
OnFillingList 绑定开始前 初始化、清空列表
OnFillingListItem 处理每个项前 动态修改项数据
OnFilledList 绑定完成后 添加总计、排序
OnFilledListItem 处理每个项后 累计计算、分组处理

在这个例子中处理了3个事件:

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

Pascal 复制代码
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的事件来测量了,以后的章节中将会进一步介绍。

相关推荐
lincats7 小时前
一步一步学习使用LiveBindings(13) TListView的进阶使用(1)
delphi·livebindings·delphi 12.3·firemonkey·tlistview
chilavert3182 天前
技术演进中的开发沉思-62 DELPHI VCL系列:VCL下的设计模式
开发语言·delphi
lincats3 天前
一步一步学习使用LiveBindings(11) 绑定到自定义外观的ListBox
list·delphi·delphi 12.3·firedac·firemonkey·tlistview
lincats4 天前
# 一步一步学习使用LiveBindings(10) LiveBindings绑定到漂亮的TCombobox
ide·delphi·livebindings·delphi 12.3
lincats6 天前
一步一步学习使用LiveBindings(9) LiveBindings图像绑定与自定义绑定方法(2)
delphi·livebindings·delphi 12.3·firedac·firemonkey
lincats7 天前
一步一步学习使用LiveBindings(8) 使用向导创建用户界面,绑定格式化入门
delphi·livebindings·delphi 12.3·firedac·firemonkey
lincats11 天前
一步一步学习使用LiveBindings(7) 实现对JSON数据的绑定
android·delphi·livebindings·delphi 12.3·firedac
lincats12 天前
一步一步学习使用LiveBindings(6) 实现Master-Detail主从关系的绑定
delphi·livebindings·delphi 12.3·firedac·firemonkey
lincats13 天前
一步一步学习使用LiveBindings(5) 使用TAdapterBindSource实现对象绑定
livebindings·delphi 12.3·firedac·firemonkey