3.5.2.1.11. 数据网格

本章内容:

DataGrid - 数据网格组件,与 表格 组件类似, 适合用于展示、分类、排序表格类数据,由于使用了在滚动时加载数据的延迟加载方式,所以此组件具有更好的数据行、列操作性能。

gui dataGrid 1

该组件的 XML 名称为 dataGrid

以下为在 XML 文件中定义数据网格的示例:

<data>
    <collection id="ordersDc" class="com.company.sales.entity.Order" view="order-with-customer">
        <loader id="ordersDl">
            <query>
                <![CDATA[select e from sales_Order e order by e.date]]>
            </query>
        </loader>
    </collection>
</data>
<layout>
    <dataGrid id="ordersDataGrid" dataContainer="ordersDc" height="100%" width="100%">
        <columns>
            <column id="date" property="date"/>
            <column id="customer" property="customer.name"/>
            <column id="amount" property="amount"/>
        </columns>
    </dataGrid>
</layout>

其中 <column> 元素里的 id 属性是列的标识,property 指示数据容器实体中的属性,对应的数据库中的数据会展示在该列。

如需以编程的方式在界面控制器定义数据源,则可以在 XML 中使用 metaClass 属性替换声明式的设置 dataContainer

dataGrid 元素

  • columns - 必要元素,定义 DataGrid 的所有列。columns 元素有如下属性:

    • includeAll – 加载 dataContainer 中定义的 view 的所有属性。

      在下面的例子中,我们显示了 customersDc 中使用视图的所有属性。如果视图包含系统属性,也同样会显示。

      <dataGrid id="dataGrid"
                width="100%"
                height="100%"
                dataContainer="customersDc">
          <columns includeAll="true"/>
      </dataGrid>

      如果实体的视图包含引用属性,该属性会按照其实例名称进行展示。如果需要展示一个特别的属性,则需要在视图和 column 元素中定义:

      <columns includeAll="true">
          <column id="address.street"/>
      </columns>

      如果未指定视图,includeAll 会加载给定实体及其祖先的所有属性。

    • exclude – 英文逗号分隔的属性列表,这些属性不会被加载到数据网格。

      在下面的例子中,我们会显示除了 nameorder 之外的所有属性:

      <dataGrid id="dataGrid"
                width="100%"
                height="100%"
                dataContainer="customersDc">
          <columns includeAll="true"
                   exclude="name, order"/>
      </dataGrid>

    每一列是在嵌套的 column 元素中描述,该元素有如下属性:

    • id - 非必须属性,标识一列。如果没有设置,对应的 property 值会被用作该列的标识,所以这种时候 property 值是必须的。否则会抛出 GuiDevelopmentException 异常。如果列是在界面控制器代码中创建的,则 id 属性是必须的。

    • property - 指对应的实体属性。可以是数据源/数据容器实体的属性,也可以是关联实体的属性,关联实体属性前面需要加上关联类名字并通过“.”连接。例如:

      <columns>
          <column id="date" property="date"/>
          <column id="customer" property="customer"/>
          <column id="customerName" property="customer.name"/>
          <column id="customerCountry" property="customer.address.country"/>
      </columns>
    • caption - 可选属性,定义列标题。如果未设置,对应的本地化属性名称会被做为列标题显示。

    • expandRatio - 设置列宽占比。默认情况下,所有列等宽(expandRatio = 1)。如果至少有一列设置了其它值,则忽略所有的隐式值,并且只使用设置的值。

    • collapsible - 定义用户是否可以通过 DataGrid 表格组件右上角的侧边栏菜单隐藏/显示该列。默认为 true

    • collapsed - 可选属性,设置为 true 时自动隐藏该列。该属性默认值为 false

    • collapsingToggleCaption - 设置在侧边栏菜单中该列的标题。默认为 null, 此时侧边栏菜单中该列的标题与数据网格中该列的标题一致。

      gui dataGrid 2
    • resizable - 定义用户是否可以调整列宽。

    • sortable - 可选属性,可以用来关闭针对该列的排序。当整个 DataGrid 数据网格控件的 sortable 属性设置为 true(默认值)时生效。

    • width - 可选属性,定义列宽。只支持以像素为单位的数值类型。

    • minimumWidth - 设置最小列宽,以像素为单位。

    • maximumWidth - 设置最大列宽,以像素为单位。

    column 元素可以包含一个内嵌的 formatter 元素,通过它可以用不同于数据类型的格式展示数据:

    <column id="date" property="date">
        <formatter class="com.haulmont.cuba.gui.components.formatters.DateFormatter"
                   format="yyyy-MM-dd HH:mm:ss"
                   useUserTimezone="true"/>
    </column>
  • actions - 可选元素,定义 DataGrid 数据网格的操作。除了自定义的操作,ListActionType 枚举类中定义的标准操作也支持,它们是: create(创建)、 edit(编辑)、 remove(删除)、 refresh(刷新)、 add(添加,从数据库中选择一条记录放入当前数据网格)、 exclude(移出,将所选行从当前数据网格中移出,但不会从数据库删除).

  • buttonsPanel - 按钮区 ButtonsPanel,位于 DataGrid 数据网格的上方,其中包含各操作对应的按钮。

  • rowsCount - 可选元素,会为数据网格控件创建一个行数( RowsCount )控件。行数控件会启用数据分页,在界面控制器中调用 CollectionLoader.setMaxResults() 方法可以控制数据加载器中的数据量,进而能控制每页最大行数。另外,绑定到相同数据源的通用过滤器组件也能实现此功能。

    rowsCount 控件也会显示数据结果总数,但不需要把这些数据全部加载出来。用户可以点击 "?" 按钮, 它会调用 com.haulmont.cuba.core.global.DataManager#getCount 方法,该方法使用相同的参数请求数据库,同时使用 COUNT(*) 聚合函数代替加载数据。返回的数值会显示在 "?" 位置。

    RowsCount 组件的 autoLoad 属性如果设置为 true,启用自动加载行数。可以在 XML 描述中设置:

    <rowsCount autoLoad="true"/>

    另外,在界面控制器中也可以通过 RowsCount API 启用或禁用该功能:

    boolean autoLoadEnabled = rowsCount.getAutoLoad();
    rowsCount.setAutoLoad(false);

dataGrid 属性

  • aggregatable 属性可以启用对 DataGrid 的行进行聚合运算,支持下列运算符:

    • SUM − 和

    • AVG − 均值

    • COUNT − 计数

    • MIN − 最小值

    • MAX − 最大值

    聚合 DataGrid 列的 aggregation 元素需要设置 type 属性,表示聚合的函数。 默认情况下,聚合列只支持数字类型,比如 IntegerDoubleLongBigDecimal。聚合值会显示在 DataGrid 顶部的附加行内。聚合的功能与 表格 组件一致。也就是说,同样可以使用 strategyClassvalueDescriptionformatter

    带聚合列的 DataGrid XML 描述示例:

    <dataGrid id="ordersDataGrid"
              dataContainer="ordersDc"
              aggregationPosition="BOTTOM"
              aggregatable="true">
        <columns>
            <column id="customerGrade" property="customer.grade">
                <aggregation strategyClass="com.company.sample.CustomerGradeAggregation"
                             valueDescription="msg://customerGradeAggregationDesc"/>
            </column>
            <column id="amount" property="amount">
                <aggregation type="SUM">
                    <formatter class="com.company.sample.MyFormatter"/>
                </aggregation>
            </column>
            ...
        </columns>
        ...
    </dataGrid>
  • aggregationPosition 属性可以设置聚合值行的位置: TOPBOTTOM。默认为 TOP

  • columnResizeMode - 设置调整列宽时的动画效果。支持两种效果:

    • ANIMATED - 动画效果,列宽跟随鼠标拖拽(默认)。

    • SIMPLE - 简单效果,列宽会在拖拽动作结束后才发生改变。

    列宽变化事件可以通过监听器 ColumnResizeListener 跟踪。可以使用 isUserOriginated() 方法跟踪列宽变化事件的来源。

  • columnsCollapsingAllowed - 定义用户是否可以在表格侧边菜单中隐藏/折叠某些列。侧边菜单中,显示的列会标记打勾。其他菜单项:

    • Select all − 显示所有列

    • Deselect all − 隐藏所有列

      gui dataGrid 16

      当列名选中/非选中是,每列的 collapsed 属性会更新。当 columnsCollapsingAllowedfalse 时,列的 collapsed 属性就不能被设置为 true

      列折叠状态的变化可以通过监听器 ColumnCollapsingChangeListener 跟踪。列折叠事件的来源可以使用 isUserOriginated() 方法进行跟踪.

  • contextMenuEnabled - 开启或关闭右键菜单。默认为 true

    DataGrid 数据网格控件的右键点击事件可以通过监听器 ContextClickListener 跟踪。

  • editorBuffered - 编辑器缓冲模式开启或关闭。默认为 true

  • editorCancelCaption - 设置 DataGrid 数据网组件编辑器中取消(cancel)按钮的名称。

  • editorCrossFieldValidate - 在行内编辑器启用跨字段验证。默认为 true

  • editorEnabled - 启用数据项的行内编辑器。默认为 false。如果数据网格组件是跟 KeyValueCollectionContainer 绑定的,则该数据是只读的,此时设置 editorEnabled 属性便没有意义。

  • editorSaveCaption - 设置数据网格组件编辑器中保存(save)按钮的名称。

  • frozenColumnCount - 设置固定列的个数。0 表示不需要固定任何列,除了开启多选模式时的选择列。设为 -1 的时候即使选择列也不固定。

  • headerVisible - 定义是否显示表头。默认为 true

  • htmlSanitizerEnabled - 启用或禁用 HTML 清理。DataGrid 组件有些 provider 可以渲染 HTML:

    • HtmlRenderer

    • RowDescriptionProvider,在设置了 ContentMode.HTML 时。

    • DescriptionProvider 在设置了 ContentMode.HTML 时。

      htmlSanitizerEnabled 属性设置为 true 时,这些 provider 的执行结果会被清理,返回安全的 HTML。

      protected static final String UNSAFE_HTML = "<i>Jackdaws </i><u>love</u> <font size=\"javascript:alert(1)\" " +
                  "color=\"moccasin\">my</font> " +
                  "<font size=\"7\">big</font> <sup>sphinx</sup> " +
                  "<font face=\"Verdana\">of</font> <span style=\"background-color: " +
                  "red;\">quartz</span><svg/onload=alert(\"XSS\")>";
      
      @Inject
      private DataGrid<Customer> customersDataGrid;
      @Inject
      private DataGrid<Customer> customersDataGrid2;
      @Inject
      private DataGrid<Customer> customersDataGrid3;
      
      @Subscribe
      public void onInit(InitEvent event) {
          customersDataGrid.setHtmlSanitizerEnabled(true);
          customersDataGrid.getColumn("name")
                  .setRenderer(customersDataGrid.createRenderer(DataGrid.HtmlRenderer.class));
      
          customersDataGrid2.setHtmlSanitizerEnabled(true);
          customersDataGrid2.setRowDescriptionProvider(customer -> UNSAFE_HTML, ContentMode.HTML);
      
          customersDataGrid3.setHtmlSanitizerEnabled(true);
          customersDataGrid3.getColumn("name").setDescriptionProvider(customer -> UNSAFE_HTML, ContentMode.HTML);
      }

      htmlSanitizerEnabled 属性会覆盖全局的 cuba.web.htmlSanitizerEnabled 配置。

      如果使用 HtmlRenderer 时,想用自定义的 presentationProvider,则展示的值默认不会被清理。如需清理 HTML,则需要手动做一下:

      protected static final String UNSAFE_HTML = "<i>Jackdaws </i><u>love</u> <font size=\"javascript:alert(1)\" " +
                  "color=\"moccasin\">my</font> " +
                  "<font size=\"7\">big</font> <sup>sphinx</sup> " +
                  "<font face=\"Verdana\">of</font> <span style=\"background-color: " +
                  "red;\">quartz</span><svg/onload=alert(\"XSS\")>";
      
      @Inject
      private DataGrid<Customer> customersDataGrid;
      
      @Inject
      private HtmlSanitizer htmlSanitizer;
      
      @Subscribe
      public void onInit(InitEvent event) {
          customersDataGrid.getColumn("name")
                  .setRenderer(customersDataGrid.createRenderer(DataGrid.HtmlRenderer.class),
                          (Function<String, String>) nameValue -> htmlSanitizer.sanitize(UNSAFE_HTML));
      }
  • reorderingAllowed - 定义用户是否可以通过鼠标拖拽重新设置列的顺序。默认值为 true

    列排序的改变事件可以通过监听器 ColumnReorderListener 跟踪。排序改变事件的来源可以通过 isUserOriginated() 方法跟踪。

  • selectionMode - 设置行选择模式,支持以下四种:

    • SINGLE - 单行选择。

    • MULTI - 多行选择。

    • MULTI_CHECK - 通过内嵌复选框列进行多选。

    • NONE - 不支持选择。

      行选中事件可以通过监听器 SelectionListener 跟踪。行选中事件的来源可以使用 isUserOriginated() 方法跟踪.

      gui dataGrid 3
  • sortable - 开启/关闭数据网格控件的排序功能。默认为 true。开启后,点击列名会在列名右边显示排序图标。使用列的 sortable 属性可以禁用该列的排序功能。

    DataGrid 的排序事件可以通过监听器 SortListener 跟踪。排序事件的来源可以通过 isUserOriginated() 方法跟踪。

  • textSelectionEnabled - 开启/关闭数据网格单元格中的文字选择功能。默认为 false

DataGrid 接口方法

  • getColumns() - 按当前界面的展示顺序获取列集合。

  • getSelected()getSingleSelected() - 返回所选行对应实体的实例。getSelected() 返回一个集合。如果没有选择任何行,则返回一个空的集合。如果设置的是 SelectionMode.SINGLE 单选模式,用 getSingleSelected() 会更方便,它直接返回一个被选择的实体实例,或者 null(没有选择任何行)。

  • getVisibleColumns() - 按当前界面中列的显示顺序获取用户可见的列集合。

  • scrollTo() - 将 DataGrid 滚动到指定行。需要一个实体实例做为输入参数来指定滚动到哪一行。除了实体实例参数,另有重载方法支持 ScrollDestination 参数,该参数可以为以下值:

    • ANY - 滚动尽量少的位置来展示所需要的数据。

    • START - 滚动 DataGrid ,使所需要的数据展示在可见部分的顶端。

    • MIDDLE - 滚动 DataGrid ,使所需要的数据展示在可见部分的中部。

    • END - 滚动 DataGrid ,使所需要的数据展示在可见部分的底部。

  • scrollToStart() and scrollToEnd() - 将 DataGrid 滚动到开头或结尾。

  • addCellStyleProvider() - 为 DataGrid 单元格添加 style provider。

  • addRowStyleProvider() - 为 DataGrid 行添加 style provider。

  • setEnterPressAction() - 设置按下 回车键 时需要执行的操作。如果没有定义这种操作,控件会尝试按以下顺序找一个合适的操作:

    • setItemClickAction() 方法定义的操作。

    • 通过 shortcut 快捷键属性定义给 回车键 的操作。

    • edit(编辑) 操作。

    • view(查看) 操作。

    如果找到一个操作,并且其属性 enabled = true,则会执行它。

  • setItemClickAction() - 设置双击时的操作。如果没有定义,组件会按以下顺序找一个合适的操作:

    • 通过 shortcut 快捷键属性定义给 回车键 的操作。

    • edit(编辑) 操作。

    • view(查看) 操作。

    如果找到一个操作并且属性 enabled = true,则会执行它。

    单击事件可以通过监听器 ItemClickListener 跟踪。

  • sort() - 根据指定列对数据进行排序,通过枚举值 SortDirection 控制排序方式:

    • ASCENDING - 升序 (A-Z, 1..9)。

    • DESCENDING - 降序 (Z-A, 9..1)。

  • getAggregationResults() 方法返回聚合结果的映射(map),键值为 DataGrid 的列标识符,值为聚合值。

使用 description providers

  • setDescriptionProvider() 方法用来为每个 DataGrid 列的单元格生成可选的描述(提示)。描述支持 HTML 标记。

    @Inject
    private DataGrid<Customer> customersDataGrid;
    
    @Subscribe
    protected void onInit(InitEvent event) {
        customersDataGrid.getColumnNN("age").setDescriptionProvider(customer ->
                        getPropertyCaption(customer, "age") +
                                customer.getAge(),
                ContentMode.HTML);
    
        customersDataGrid.getColumnNN("active").setDescriptionProvider(customer ->
                        getPropertyCaption(customer, "active") +
                                getMessage(customer.getActive() ? "trueString" : "falseString"),
                ContentMode.HTML);
    
        customersDataGrid.getColumnNN("grade").setDescriptionProvider(customer ->
                        getPropertyCaption(customer, "grade") +
                                messages.getMessage(customer.getGrade()),
                ContentMode.HTML);
    }
    gui dataGrid 11
  • setRowDescriptionProvider() 方法用来为每个 DataGrid 行生成可选的描述(提示)。如果同时也设置了列描述提供者,只有在列描述提供者返回 null 时显示行描述提供器实例。

    customersDataGrid.setRowDescriptionProvider(Instance::getInstanceName);
    gui dataGrid 10

使用 DetailsGenerator 接口

使用 setDetailsGenerator() 方法设置 DetailsGenerator 接口,可以生成自定义控件来展示对应行的明细:

@Inject
private DataGrid<Order> ordersDataGrid;
@Inject
private UiComponents uiComponents;

@Install(to = "ordersDataGrid", subject = "detailsGenerator")
protected Component ordersDataGridDetailsGenerator(Order order) {
    VBoxLayout mainLayout = uiComponents.create(VBoxLayout.NAME);
    mainLayout.setWidth("100%");
    mainLayout.setMargin(true);

    HBoxLayout headerBox = uiComponents.create(HBoxLayout.NAME);
    headerBox.setWidth("100%");

    Label infoLabel = uiComponents.create(Label.NAME);
    infoLabel.setHtmlEnabled(true);
    infoLabel.setStyleName("h1");
    infoLabel.setValue("Order info:");

    Component closeButton = createCloseButton(order);
    headerBox.add(infoLabel);
    headerBox.add(closeButton);
    headerBox.expand(infoLabel);

    Component content = getContent(order);

    mainLayout.add(headerBox);
    mainLayout.add(content);
    mainLayout.expand(content);

    return mainLayout;
}

private Component createCloseButton(Order entity) {
    Button closeButton = uiComponents.create(Button.class);
    // ... (1)
    return closeButton;
}

private Component getContent(Order entity) {
    Label<String> content = uiComponents.create(Label.TYPE_STRING);
    content.setHtmlEnabled(true);
    StringBuilder sb = new StringBuilder();
    // ... (2)
    content.setValue(sb.toString());
    return content;
}
1 – 参考 DataGridDetailsGeneratorSample 类中的 createCloseButton 方法全部代码。
2 – 参考 DataGridDetailsGeneratorSample 类中的 getContent 方法全部代码。

结果如图所示:

gui dataGrid 15

使用 DataGrid 行内编辑器

DataGrid 组件支持行内编辑器来编辑单元格数据。当用户要编辑一个数据项时,行内编辑界面会显示并自带默认的保存和取消按钮。

行内编辑器对应的方法有:

  • getEditedItem() - 返回正在被编辑的数据项。

  • isEditorActive() - 是否正在行内编辑界面编辑某个数据项。

  • editItem(Object itemId)(废弃) - 为提供了 id 的数据项打开编辑界面。如果数据项在当前界面区域不可见,数据网格会将数据项滚动到可视区域。

  • edit(Entity item) - 为指定的数据项打开编辑界面。如果数据项在当前界面区域不可见,数据网格会将数据项滚动到可视区域。

DataGrid 行内编辑器可以使用实体约束(跨字段验证)。如果有验证错误,DataGrid 会显示错误消息。开启/禁用该功能或者获取当前状态可以使用下面方法:

  • setEditorCrossFieldValidate(boolean validate) - 启用、禁用行内编辑器的跨字段验证。默认为 true

  • isEditorCrossFieldValidate() - 如果行内编辑器的跨字段验证开启,则返回 true

使用以下方法添加/删除行内编辑界面打的监听器:

  • addEditorOpenListener(), removeEditorCloseListener() - 添加/删除行内编辑界面打开监听器。

    当用户双击 DataGrid 数据网格中某个区域时,行内编辑界面打开,使用上述监听器,可以获取被编辑行的其它字段并进行需要的修改。这种方法可以使得不用关闭当前行内编辑器就能修改其它字段。

    例如:

    customersTable.addEditorOpenListener(editorOpenEvent -> {
        Map<String, Field> fieldMap = editorOpenEvent.getFields();
        Field active = fieldMap.get("active");
        Field grade = fieldMap.get("grade");
    
        ValueChangeListener listener = e ->
                active.setValue(true);
        grade.addValueChangeListener(listener);
    });
  • addEditorCloseListener(), removeEditorCloseListener() - 添加/删除行内编辑界面关闭监听器。

  • addEditorPreCommitListener(), removeEditorPreCommitListener() - 添加/删除行内编辑界面数据提交前监听器。

  • addEditorPostCommitListener(), removeEditorPostCommitListener() - 添加/删除行内编辑界面数据提交后监听器。

行内编辑器所做的数据修改只提交到数据源或者数据容器。需要额外的代码把他们持久化到数据库。

可以通过 EditorFieldGenerationContext 类对编辑器组件进行定制,在某一列上使用 setEditFieldGenerator() 方法设置该列数据的编辑器组件:

@Inject
private DataGrid<Order> ordersDataGrid;
@Inject
private UiComponents uiComponents;

@Subscribe
protected void onInit(InitEvent event) {
    ordersDataGrid.getColumnNN("amount").setEditFieldGenerator(orderEditorFieldGenerationContext -> {
        LookupField<BigDecimal> lookupField = uiComponents.create(LookupField.NAME);
        lookupField.setValueSource((ValueSource<BigDecimal>) orderEditorFieldGenerationContext
                .getValueSourceProvider().getValueSource("amount"));
        lookupField.setOptionsList(Arrays.asList(BigDecimal.ZERO, BigDecimal.ONE, BigDecimal.TEN));

        return lookupField;
    });
}

结果如下:

gui dataGrid 14

使用 ColumnGenerator 接口

DataGrid 组件可以添加自定义列,有两种方式:

  • 声明式,在界面控制器使用 @Install 注解:

    @Inject
    private UiComponents uiComponents;
    
    @Install(to = "dataGrid.fullName", subject = "columnGenerator")
    protected Component fullNameColumnGenerator(DataGrid.ColumnGeneratorEvent<Customer> e) {
        Label<String> label = uiComponents.create(Label.TYPE_STRING);
        label.setValue(e.getItem().getFirstName() + " " + e.getItem().getLastName());
        return label;
    }

    ColumnGeneratorEvent 包含实体信息以及列标识符,该实体显示在 DataGrid 的当前行。

  • 使用下面的方法编程创建:

    • addGeneratedColumn(String columnId, ColumnGenerator generator)

    • addGeneratedColumn(String columnId, ColumnGenerator generator, int index)

    ColumnGenerator 是用来定义生成的列或者计算出的列的接口:

    • 该列每一行的数据值,

    • 该列数据类型。

    下面是一个生成列的示例,这个列显示大写的用户登录名:

    @Subscribe
    protected void onInit(InitEvent event) {
        DataGrid.Column column = usersGrid.addGeneratedColumn("loginUpperCase", new DataGrid.ColumnGenerator<User, String>(){
            @Override
            public String getValue(DataGrid.ColumnGeneratorEvent<User> event){
                return event.getItem().getLogin().toUpperCase();
            }
    
            @Override
            public Class<String> getType(){
                return String.class;
            }
        }, 1);
        column.setCaption("Login Upper Case");
    }

    结果如下:

    gui dataGrid 7

    默认情况下,新生成的列加在数据网格的最右边。有两种方法可以管理列的位置:代码中使用 index 或者在界面 XML 文件中提前定义好并设置 id, 然后在 addGeneratedColumn 方法中使用该 id。

使用 renderers

数据在列中的显示方式可以使用带参数的 renderer 以声明式的方法自定义。一些 DataGrid 的 renderer 通过特定的 XML 元素设置,并且定义了对应的属性作为参数。对非自定义生成列,可以声明 renderer。

平台支持的 renderer 列表:

  • ButtonRenderer – 以字符串值形式展示按钮标题。

    ButtonRenderer 不能在 XML 描述中定义,因为没有办法在 XML 描述中定义 renderer 点击监听器。 Studio 会在界面控制器的 init() 中生成 ButtonRenderer 的声明代码:

    @Inject
    private DataGrid<Customer> customersDataGrid;
    
    @Inject
    private Notifications notifications;
    
    @Subscribe
    public void onInit(InitEvent event) {
        DataGrid.ButtonRenderer<Customer> customersDataGridNameRenderer =
                    customersDataGrid.createRenderer(DataGrid.ButtonRenderer.class);
        customersDataGridNameRenderer.setRendererClickListener(clickableButtonRendererClickEvent ->
            {
                notifications.create()
                    .withType(Notifications.NotificationType.TRAY)
                    .withCaption("ButtonRenderer")
                    .withDescription("Column id: " + clickableButtonRendererClickEvent.getColumnId())
                    .show();
            });
        customersDataGrid.getColumn("name").setRenderer(customersDataGridNameRenderer);
    }
  • CheckBoxRenderer – 将 boolean 值展示为复选框图标。

    DataGridcolumn 元素有子元素 checkBoxRenderer

    <column property="checkBoxRenderer" id="checkBoxRendererColumn">
        <checkBoxRenderer/>
    </column>
  • ClickableTextRenderer – 将普通的文本字符串显示为链接,并有回调处理器。

    ClickableTextRenderer 不能在 XML 描述中定义,因为没有办法在 XML 描述中定义 renderer 点击监听器。 Studio 会在界面控制器的 init() 中生成 ClickableTextRenderer 的声明代码:

    @Inject
    private DataGrid<Customer> customersDataGrid;
    
    @Inject
    private Notifications notifications;
    
    @Subscribe
    public void onInit(InitEvent event) {
        DataGrid.ClickableTextRenderer<Customer> customersDataGridNameRenderer =
                    customersDataGrid.createRenderer(DataGrid.ClickableTextRenderer.class);
        customersDataGridNameRenderer.setRendererClickListener(clickableTextRendererClickEvent -> {
            notifications.create()
                    .withType(Notifications.NotificationType.TRAY)
                    .withCaption("ClickableTextRenderer")
                    .withDescription("Column id: " + clickableTextRendererClickEvent.getColumnId())
                    .show();
        });
        customersDataGrid.getColumn("name").setRenderer(customersDataGridNameRenderer);
    }
  • ComponentRenderer – UI 组件 renderer

    DataGridcolumn 元素有子元素 componentRenderer

    <column property="componentRenderer" id="componentRendererColumn">
        <componentRenderer/>
    </column>
  • DateRenderer – 用定义的格式显示日期

    DataGridcolumn 元素有子元素 dateRenderer,该元素有非必要的 nullRepresentation 属性和必填的 format 字符串属性:

    <column property="dateRenderer" id="dateRendererColumn">
        <dateRenderer nullRepresentation="null" format="yyyy-MM-dd HH:mm:ss"/>
    </column>
  • IconRenderer – 展示 CubaIcon 的 renderer。

    DataGridcolumn 元素有子元素 iconRenderer

    下面的例子展示将实体 String 类型的属性渲染成 CubaIcon

    <column id="iconOS" property="iconOS">
        <iconRenderer/>
    </column>
    @Install(to = "devicesTable.iconOS", subject = "columnGenerator")
    private Icons.Icon devicesTableIconOSColumnGenerator(DataGrid.ColumnGeneratorEvent<Device> event) {
        return CubaIcon.valueOf(event.getItem().getIconOS());
    }

    结果如下:

    gui dataGrid iconColumn
  • ImageRenderer – 使用图片的路径渲染图片。

    ImageRenderer 不能在 XML 描述中定义,因为没有办法在 XML 描述中定义 renderer 点击监听器。 Studio 会在界面控制器的 init() 中生成 ImageRenderer 的声明代码:

    @Inject
    private DataGrid<TestEntity> testEntitiesDataGrid;
    @Inject
    private Notifications notifications;
    
    @Subscribe
    public void onInit(InitEvent event) {
        DataGrid.ImageRenderer<TestEntity> imageRenderer =
                testEntitiesDataGrid.createRenderer(DataGrid.ImageRenderer.class);
        imageRenderer.setRendererClickListener(imageRendererClickEvent -> notifications.create()
                .withType(Notifications.NotificationType.TRAY)
                .withCaption("ImageRenderer")
                .withDescription("Column id: " + imageRendererClickEvent.getColumnId())
                .show());
        testEntitiesDataGrid.getColumn("imageRendererColumn").setRenderer(imageRenderer);
    }
  • HtmlRenderer – 展示 HTML

    DataGridcolumn 元素有子元素 htmlRenderer,该元素有非必要的属性 nullRepresentation

    <column property="htmlRenderer" id="htmlRendererColumn">
        <htmlRenderer nullRepresentation="null"/>
    </column>
  • LocalDateRenderer – 以 LocalDate 值显示日期

    DataGridcolumn 元素有子元素 localDateRenderer,该元素有非必要的 nullRepresentation 属性和必填的 format 字符串属性:

    <column property="localDateRenderer" id="localDateRendererColumn">
        <localDateRenderer nullRepresentation="null" format="dd/MM/YYYY"/>
    </column>
  • LocalDateTimeRenderer – 以 LocalDateTime 值显示日期。

    DataGridcolumn 元素有子元素 localDateTimeRenderer,该元素有非必要的 nullRepresentation 属性和必填的 format 字符串属性:

    <column property="localDateTimeRenderer" id="localDateTimeRendererColumn">
        <localDateTimeRenderer nullRepresentation="null" format="dd/MM/YYYY HH:mm:ss"/>
    </column>
  • NumberRenderer – 以定义的格式显示数字。

    DataGridcolumn 元素有子元素 numberRenderer,该元素有非必要的 nullRepresentation 属性和必填的 format 字符串属性:

    <column property="numberRenderer" id="numberRendererColumn">
        <numberRenderer nullRepresentation="null" format="%f"/>
    </column>
  • ProgressBarRenderer – 将 0~1 之间 double 类型的值展示为 ProgressBar 组件。

    DataGridcolumn 元素有子元素 progressBarRenderer

    <column property="progressBar" id="progressBarColumn">
        <progressBarRenderer/>
    </column>
  • TextRenderer – 展示字符串

    DataGridcolumn 元素有子元素 textRenderer,该元素有非必要的 nullRepresentation 属性:

    <column property="textRenderer" id="textRendererColumn">
        <textRenderer nullRepresentation="null"/>
    </column>

WebComponentRenderer 接口可以使得在数据网格单元格中显示不同的 Web 控件。以下是生成一个带查找控件的列的例子:

@Inject
private DataGrid<User> usersGrid;
@Inject
private UiComponents uiComponents;
@Inject
private Configuration configuration;
@Inject
private Messages messages;

@Subscribe
protected void onInit(InitEvent event) {
    Map<String, Locale> locales = configuration.getConfig(GlobalConfig.class).getAvailableLocales();
    Map<String, String> options = new TreeMap<>();
    for (Map.Entry<String, Locale> entry : locales.entrySet()) {
        options.put(entry.getKey(), messages.getTools().localeToString(entry.getValue()));
    }

    DataGrid.Column column = usersGrid.addGeneratedColumn("language",
            new DataGrid.ColumnGenerator<User, Component>() {
                @Override
                public Component getValue(DataGrid.ColumnGeneratorEvent<User> event) {
                    LookupField<String> component = uiComponents.create(LookupField.NAME);
                    component.setOptionsMap(options);
                    component.setWidth("100%");

                    User user = event.getItem();
                    component.setValue(user.getLanguage());

                    component.addValueChangeListener(e -> user.setLanguage(e.getValue()));

                    return component;
                }

                @Override
                public Class<Component> getType() {
                    return Component.class;
                }
            });

    column.setRenderer(new WebComponentRenderer());
}

结果如下:

gui dataGrid 13

当字段类型与 renderer 支持的类型不匹配时,可以创建一个 Function 来匹配模型和视图的数据类型。比如,想把布尔类型用图标展示时,可以巧妙的使用 HtmlRenderer 来做 HTML 渲染以及实现布尔类型转换为图标展示的逻辑。

@Inject
private DataGrid<User> usersGrid;

@Subscribe
protected void onInit(InitEvent event) {

    DataGrid.Column<User> hasEmail = usersGrid.addGeneratedColumn("hasEmail", new DataGrid.ColumnGenerator<User, Boolean>() {
        @Override
        public Boolean getValue(DataGrid.ColumnGeneratorEvent<User> event) {
            return StringUtils.isNotEmpty(event.getItem().getEmail());
        }

        @Override
        public Class<Boolean> getType() {
            return Boolean.class;
        }
    });

    hasEmail.setCaption("Has Email");
    hasEmail.setRenderer(
        usersGrid.createRenderer(DataGrid.HtmlRenderer.class),
        (Function<Boolean, String>) hasEmailValue -> {
            return BooleanUtils.isTrue(hasEmailValue)
                    ? FontAwesome.PLUS_SQUARE.getHtml()
                    : FontAwesome.MINUS_SQUARE.getHtml();
        });
}

结果如下:

gui dataGrid 9

Renderer 可以通过三种方式创建:

  • 声明式的通过 DataGridcolumn 元素的特定元素。

  • DataGrid 接口的 set 方法中直接设置 renderer 接口。

  • 为对应的模块直接创建 renderer 实现:

    dataGrid.createRenderer(DataGrid.ImageRenderer.class) → new WebImageRenderer()

    目前,该方式只适合 Web 模块。

目前平台支持以下 renderer 接口:

Header 和 Footer

HeaderRowFooterRow 接口受制于分别展示表头和表尾单元格,支持跨列合并单元格。

DataGrid 的以下方法用于创建和管理表头、表尾:

  • appendHeaderRow()appendFooterRow() - 在表头或表尾区底部添加一个新行。

  • prependHeaderRow()prependFooterRow() - 在表头或表尾区顶部添加一个新行。

  • addHeaderRowAt()addFooterRowAt() - 在表头或表尾区的指定位置添加新行。该位置及其后面的行位置顺序下移,行索引增加。

  • removeHeaderRow()removeFooterRow() - 从表头或表尾区删除指定行。

  • getHeaderRowCount()getFooterRowCount() - 获取表头或表尾区行数。

  • setDefaultHeaderRow() - 设置表头默认行。默认表头行为用户提供排序功能。

HeaderCellFooterCell 接口提供自定义静态单元格功能:

  • setStyleName() - 为单元格设置自定义样式。

  • getCellType() - 返回单元格内容类型。静态单元格枚举类型 DataGridStaticCellType 有三个值:

    • TEXT - 文本

    • HTML - HTML

    • COMPONENT - 组件

  • getComponent()getHtml()getText() - 不同类型单元格获取内容的方法。

下面这个例子中,表头包含合并的单元格,表尾显示经计算得出的值:

<dataGrid id="dataGrid" dataContainer="countryGrowthDs" width="100%">
    <columns>
        <column property="country"/>
        <column property="year2017"/>
        <column property="year2018"/>
    </columns>
</dataGrid>
@Inject
private DataGrid<CountryGrowth> dataGrid;
@Inject
private UserSessionSource userSessionSource;
@Inject
private Messages messages;
@Inject
private CollectionContainer<CountryGrowth> countryGrowthsDc;

private DecimalFormat percentFormat;

@Subscribe
protected void onBeforeShow(BeforeShowEvent event) {
    initPercentFormat();
    initHeader();
    initFooter();
    initRenderers();
}

private DecimalFormat initPercentFormat() {
    percentFormat = (DecimalFormat) NumberFormat.getPercentInstance(userSessionSource.getLocale());
    percentFormat.setMultiplier(1);
    percentFormat.setMaximumFractionDigits(2);
    return percentFormat;
}

private void initRenderers() {
    dataGrid.getColumnNN("year2017").setRenderer(new WebNumberRenderer(percentFormat));
    dataGrid.getColumnNN("year2018").setRenderer(new WebNumberRenderer(percentFormat));
}

private void initHeader() {
    DataGrid.HeaderRow headerRow = dataGrid.prependHeaderRow();
    DataGrid.HeaderCell headerCell = headerRow.join("year2017", "year2018");
    headerCell.setText("GDP growth");
    headerCell.setStyleName("center-bold");
}

private void initFooter() {
    DataGrid.FooterRow footerRow = dataGrid.appendFooterRow();
    footerRow.getCell("country").setHtml("<strong>" + messages.getMainMessage("average") + "</strong>");
    footerRow.getCell("year2017").setText(percentFormat.format(getAverage("year2017")));
    footerRow.getCell("year2018").setText(percentFormat.format(getAverage("year2018")));
}

private double getAverage(String propertyId) {
    double average = 0.0;
    List<CountryGrowth> items = countryGrowthsDc.getItems();
    for (CountryGrowth countryGrowth : items) {
        Double value = countryGrowth.getValue(propertyId);
        average += value != null ? value : 0.0;
    }
    return average / items.size();
}
gui dataGrid 12

DataGrid 样式

可以在 XML 描述中使用 stylename 属性为 DataGrid 组件设置预定义样式。

<dataGrid id="dataGrid"
          width="100%"
          height="100%"
          stylename="borderless"
          dataContainer="customersDc">
</dataGrid>

或者在界面控制器使用编程的方式设置。

dataGrid.setStyleName("borderless");

预定义样式:

  • borderless - DataGrid 无边线。

  • no-horizontal-lines - DataGrid 行之间无水平分割线。

  • no-vertical-lines - DataGrid 列之间无垂直分割线。

  • no-stripes - 每行背景颜色统一。

DataGrid 的展示可以使用带 $cuba-datagrid-* 前缀的 SCSS 变量进行自定义。可以在创建一个 主题扩展 或者一个 自定义主题 之后在可视化编辑器里修改这些变量的值。


DataGrid 的属性列表

aggregatable - aggregationPosition - align - caption - captionAsHtml - colspan - columnResizeMode - columnsCollapsingAllowed - contextHelpText - contextHelpTextHtmlEnabled - contextMenuEnabled - css - dataContainer - description - descriptionAsHtml - editorBuffered - editorCancelCaption - editorCrossFieldValidate - editorEnabled - editorSaveCaption - emptyStateLinkMessage - emptyStateMessage - enable - box.expandRatio - frozenColumnCount - headerVisible - height - htmlSanitizerEnabled - icon - id - metaClass - reorderingAllowed - responsive - rowspan - selectionMode - settingsEnabled - sortable - stylename - tabIndex - textSelectionEnabled - visible - width

DataGrid 的元素

actions - buttonsPanel - columns - rowsCount

columns 元素属性列表

includeAll - exclude

column 元素的属性列表

caption - collapsed - collapsible - collapsingToggleCaption - editable - expandRatio - id - maximumWidth - minimumWidth - property - resizable - sort - sortable - width

column 的元素

aggregation - checkBoxRenderer - componentRenderer - dateRenderer - formatter - iconRenderer - htmlRenderer - localDateRenderer - localDateTimeRenderer - numberRenderer - progressBarRenderer - textRenderer

aggregation 的属性

strategyClass - type - valueDescription

API

addGeneratedColumn - applySettings - createRenderer - edit - getAggregationResults - saveSettings - getColumns - setDescriptionProvider - addCellStyleProvider - setConverter - setDetailsGenerator - setEditorCrossFieldValidate - setEmptyStateLinkClickHandler - setEnterPressAction - setItemClickAction - setRenderer - setRowDescriptionProvider - addRowStyleProvider - sort

DataGrid 监听器

ColumnCollapsingChangeListener - ColumnReorderListener - ColumnResizeListener - ContextClickListener - EditorCloseListener - EditorOpenListener - EditorPostCommitListener - EditorPreCommitListener - ItemClickListener - SelectionListener - SortListener

预定义样式

borderless - no-horizontal-lines - no-vertical-lines - no-stripes