3.5.2.1.44. 表格

Table 组件以表格的方式展示信息,对数据进行排序 、管理表格列和表头,并对选中的行执行操作。

gui table

组件对应的 XML 名称: table

在界面 XML 描述中定义组件的示例:

<data readOnly="true">
    <collection id="ordersDc" class="com.company.sales.entity.Order" view="order-with-customer">
        <loader id="ordersDl">
            <query>
                <![CDATA[select e from sales_Order e]]>
            </query>
        </loader>
    </collection>
</data>
<layout>
<table id="ordersTable" dataContainer="ordersDc" width="100%">
    <columns>
        <column id="date"/>
        <column id="amount"/>
        <column id="customer"/>
    </columns>
    <rowsCount/>
</table>
</layout>

在上面的示例中,data 元素定义集合数据容器,它使用 JPQL 查询 Order 实体。table 元素定义数据容器,而 columns 元素定义哪些实体属性用作表格列。

table 元素:

  • rows – 如果使用 datasource 属性来做数据绑定,则必须设置此元素。

    每行可以在左侧的附加列中有一个图标。在界面控制器中创建 ListComponent.IconProvider 接口的实现,并将其设置给表格:

    @Inject
    private Table<Customer> table;
    
    @Subscribe
    protected void onInit(InitEvent event) {
        table.setIconProvider(new ListComponent.IconProvider<Customer>() {
            @Nullable
            @Override
            public String getItemIcon(Customer entity) {
                CustomerGrade grade = entity.getGrade();
                switch (grade) {
                    case PREMIUM: return "icons/premium_grade.png";
                    case HIGH: return "icons/high_grade.png";
                    case MEDIUM: return "icons/medium_grade.png";
                    default: return null;
                }
            }
        });
    }
  • columns – 定义表格列的必须元素。

    每个列都在嵌套的 column 元素中描述,column 元素具有以下属性:

    • id − 必须属性,包含列中要显示的实体属性的名称。可以是来自数据容器的实体的属性,也可以是关联实体的属性(使用 "." 来指定属性在对象关系图中的路径)。例如:

      <columns>
          <column id="date"/>
          <column id="customer"/>
          <column id="customer.name"/>
          <column id="customer.address.country"/>
      </columns>
    • collapsed − 可选属性;当设置为 true 时默认隐藏列。当表格的 columnControlVisible 属性不是 false 时,用户可以通过表格右上角的菜单中的按钮 gui_table_columnControl 控制列的可见性。默认情况下,collapsedfalse

    • width − 可选属性,控制默认列宽。只能是以像素为单位的数值。

    • align − 可选属性,用于设置单元格的文本对齐方式。可选值:LEFTRIGHTCENTER。默认为 LEFT

    • editable − 可选属性,允许编辑表中的相应列。为了使列可编辑,整个表的 editable 属性也应设置为 true。不支持在运行时更改此属性。

    • sortable − 可选属性,用于禁用列的排序。整个表的 sortable 属性为 true 此属性有效(默认为 true)。

    • maxTextLength – 可选属性,允许限制单元格中的字符数。如果实际值和最大允许字符数之间的差异不超过 10 个字符,则多出来的字符不会被隐藏。用户可以点击可见部分来查看完整的文本。例如一列的字符数限制为 10 个字符:

      gui table column maxTextLength
    • linkScreen - 设置单击 link 属性为 true 的列中的链接时打开的界面的标识符。

    • linkScreenOpenType - 设置界面打开模式(THIS_TABNEW_TAB 或者 DIALOG)。

    • linkInvoke - 单击链接时调用控制器方法而不是打开界面。

      @Inject
      private Notifications notifications;
      
      public void linkedMethod(Entity item, String columnId) {
          Customer customer = (Customer) item;
          notifications.create()
                  .withCaption(customer.getName())
                  .show();
      }
    • captionProperty - 指定一个要显示在列中的实体属性名称,而不是显示 id 指定的实体属性值。例如,如果有一个包含 nameorderNo 属性的实体 Priority,则可以定义以下列:

      <column id="priority.orderNo" captionProperty="priority.name" caption="msg://priority" />

      此时,列中将会显示 Priority 实体的 name 属性,但是列的排序是根据 Priority 实体的 orderNo 属性。

    • 可选的 generator 属性包含指向界面控制器中方法,该方法可创建一个可视化组件显示在表格单元格中:

      <columns>
          <column id="name"/>
          <column id="imageFile"
                  generator="generateImageFileCell"/>
      </columns>
      public Component generateImageFileCell(Employee entity){
          Image image = uiComponents.create(Image.NAME);
          image.setSource(FileDescriptorResource.class).setFileDescriptor(entity.getImageFile());
          return image;
      }

      它可以用来为 addGeneratedColumn() 方法提供一个 Table.ColumnGenerator 的实现

    • column 元素可能包含一个嵌套的formatter元素,它允许以不同于Datatype的标准格式显示属性值:

      <column id="date">
          <formatter class="com.haulmont.cuba.gui.components.formatters.DateFormatter"
                     format="yyyy-MM-dd HH:mm:ss"
                     useUserTimezone="true"/>
      </column>
  • rowsCount − 可选元素,为表格添加 RowsCount 组件;此组件能够分页加载表格数据。可以使用数据加载器setMaxResults() 方法限制数据容器中的记录数来定义分页的大小。这个方法通常是由链接到表格数据加载器的过滤器组件来执行的。如果表格没有通用过滤器,则可以直接从界面控制器调用此方法。

    RowsCount 组件还可以显示当前数据容器查询的记录总数,而无需提取这些记录。当用户单击 ? 图标时,它会调用 com.haulmont.cuba.core.global.DataManager#getCount 方法,执行与当前查询条件相同的数据库查询,不过会使用 COUNT(*) 聚合函数代替查询列。然后显示检索到的数字,代替 ? 图标。

  • actions − 可选元素,用于描述和表格相关的操作。除了自定义的操作外,该元素还支持以下在 com.haulmont.cuba.gui.actions.list 里定义标准操作createeditremoverefreshaddexcludeexcel

  • 可选元素,在表格上方添加一个 ButtonsPanel 容器来显示操作按钮。

table 属性:

  • multiselect 属性可以为表格行设置多选模式。如果 multiselecttrue,用户可以按住 CtrlShift 键在表格中选择多行。默认情况下关闭多选模式。

  • sortable 属性可以对表中的数据进行排序。默认情况下,它设置为 true 。如果允许排序,单击列标题在列名称右侧将显示图标 gui_sortable_down / gui_sortable_up。可以使用sortable属性禁用特定列的排序。

    根据是否将所有记录放在了一页上来使用不同的方式进行排序。如果所有记录在一页,则在内存中执行排序而不需要数据库查询。如果数据有多页,则通过发送具有相应 ORDER BY 条件的新的查询请求在数据库中执行排序。

    一个表格中的列可能包含本地属性或实体链接。例如:

    <table id="ordersTable"
           dataContainer="ordersDc">
        <columns>
            <column id="customer.name"/> <!-- the 'name' attribute of the 'Customer' entity -->
            <column id="contract"/>      <!-- the 'Contract' entity -->
        </columns>
    </table>

    在后一种情况下,数据排序是根据关联实体的 @NamePattern 注解中定义的属性进行的。如果实体中没有这个注解,则仅仅在内存中对当前页的数据进行排序。

    如果列引用了非持久化实体属性,则数据排序将根据 @MetaProperty 注解的 related() 参数中定义的属性执行。如果未指定相关属性,则仅仅在内存中对当前页的数据进行排序。

    如果表格链接到一个嵌套的属性容器,这个属性容器包含相关实体的集合。这个集合属性必须是有序类型(ListLinkedHashSet)才能使表格支持排序。如果属性的类型为 Set,则 sortable 属性不起作用,并且用户无法对表格进行排序。

  • presentations 属性控制展示设置。默认情况下,该值为 false。如果属性值为 true,则会在表格的右上角添加相应的图标 gui_presentation

  • 如果 columnControlVisible 属性设置为 false,则用户无法使用位于表头的右侧的下拉菜单按钮 gui_table_columnControl 隐藏列:gui_table_columnControl 按钮位于表头的右侧。当前显示的列在菜单中标记为选中状态。

gui table columnControl all
  • 如果 reorderingAllowed 属性设置为 false,则用户不能通过用鼠标拖动来更改列顺序。

  • 如果 columnHeaderVisible 属性设置为 false,则该表没有列标题。

  • 如果 showSelection 属性设置为 false,则不突出显示当前行。

  • contextMenuEnabled 属性启用右键菜单。默认情况下,此属性设置为 true。右键菜单中会列出表格操作(如果有的话)和 System Information 菜单项(如果用户具有 cuba.gui.showInfo 权限),通过 System Information 菜单项可查看选中实体的详细信息。

  • multiLineCells 设置为 true 可以让包含多行文本的单元格显示多行文本。在这种模式下,浏览器会一次加载表格中当前页的所有行,而不是延迟加载表格的可见部分。这就要求在 Web 客户端中适当的滚动。默认值为“false”。

  • aggregatable 属性启用表格行的聚合运算。支持以下操作:

    • SUM – 计算总和

    • AVG – 计算平均值

    • COUNT – 计算总数

    • MIN – 找到最小值

    • MAX – 找到最大值

    聚合列的 aggregation 元素应该设置 type 属性,在这个属性中设置聚合函数。默认情况下,聚合列仅支持数值类型,例如 Integer 、 Double 、 LongBigDecimal。聚合表格值显示在表格顶部的附加行中。这是一个定义聚合表示例:

    <table id="itemsTable" aggregatable="true" dataContainer="itemsDc">
        <columns>
            <column id="product"/>
            <column id="quantity"/>
            <column id="amount">
                <aggregation type="SUM"/>
            </column>
        </columns>
    </table>

    aggregation 元素还可以包含 strategyClass 属性,指定一个实现 AggregationStrategy 接口的类(参阅下面以编程方式设置聚合策略的示例)。

    可以指定不同于 Datatype 标准格式的格式化器显示聚合值:

    <column id="amount">
        <aggregation type="SUM">
            <formatter class="com.company.sample.MyFormatter"/>
        </aggregation>
    </column>

    aggregationStyle 属性允许指定聚合行的位置:TOPBOTTOM。默认情况下使用 TOP

    除了上面列出的操作之外,还可以自定义聚合策略,通过实现 AggregationStrategy 接口并将其传递给 AggregationInfo 实例中 Table.Column 类的 setAggregation() 方法。例如:

    public class TimeEntryAggregation implements AggregationStrategy<List<TimeEntry>, String> {
        @Override
        public String aggregate(Collection<List<TimeEntry>> propertyValues) {
            HoursAndMinutes total = new HoursAndMinutes();
            for (List<TimeEntry> list : propertyValues) {
                for (TimeEntry timeEntry : list) {
                    total.add(HoursAndMinutes.fromTimeEntry(timeEntry));
                }
            }
            return StringFormatHelper.getTotalDayAggregationString(total);
        }
        @Override
        public Class<String> getResultClass() {
            return String.class;
        }
    }
    AggregationInfo info = new AggregationInfo();
    info.setPropertyPath(metaPropertyPath);
    info.setStrategy(new TimeEntryAggregation());
    
    Table.Column column = weeklyReportsTable.getColumn(columnId);
    column.setAggregation(info);
  • editable 属性可以将表格转换为即时编辑模式。在这种模式下,具有 editable = true 属性的列显示用于编辑相应实体属性的组件。

    根据相应实体属性的类型自动选择每个可编辑列的组件类型。例如,对于字符串和数字属性,应用程序将使用 TextField;对于 Date 将使用 DateField;对于列表将使用 LookupField;对于指向其它实体的链接将使用 PickerField

    对于 Date 类型的可编辑列,还可以定义 dateFormatresolution 属性,类似于为 DateField 的属性。

    可以为显示链接实体的可编辑列定义 optionsContainercaptionProperty 属性。如果设置了 optionsContainer 属性,应用程序将使用 LookupField 而不是 PickerField

    可以使用 Table.addGeneratedColumn() 方法实现单元格的自定义配置(包括编辑) - 见下文。

  • 在具有基于 Halo-based 主题的 Web 客户端中,stylename 属性可以在 XML 描述中或者界面控制器中为 Table 组件设置预定义样式:

    <table id="table"
           dataContainer="itemsDc"
           stylename="no-stripes">
        <columns>
            <column id="product"/>
            <column id="quantity"/>
        </columns>
    </table>

    当以编程方式设置样式时,需要选择 HaloTheme 类的一个以 TABLE_ 为前缀的常量:

    table.setStyleName(HaloTheme.TABLE_NO_STRIPES);

    表格样式:

    • borderless - 不显示表格的外部边线。

    • compact - 减少表格单元格内的空白区域。

    • no-header - 隐藏表格的列标题。

    • no-horizontal-lines - 删除行之间的水平分隔线。

    • no-stripes - 删除交替的行颜色。

    • no-vertical-lines - 删除列之间的垂直分隔线。

    • small - 使用小字体并减少表格单元格内的空白区域。

Table 接口的方法:

  • 可以使用 addColumnCollapsedListener 方法和 ColumnCollapsedListener 接口的实现跟踪列的可视化状态。

  • getSelected()getSingleSelected() 返回表格中的选定行对应的实体实例。可以通过调用 getSelected() 方法来获得集合。如果未选择任何内容,则程序将返回空集。如果禁用了 multiselect,应该使用 getSingleSelected() 方法返回一个选定实体,如果没有选择任何内容则返回 null

  • addSelectionListener() 可以跟踪表格选中行的变化,示例:

    customersTable.addSelectionListener(customerSelectionEvent ->
            notifications.create()
                    .withCaption("You selected " + customerSelectionEvent.getSelected().size() + " customers")
                    .show());

    也可以通过订阅相应的事件来跟踪选中行的变化:

    @Subscribe("customersTable")
    protected void onCustomersTableSelection(Table.SelectionEvent<Customer> event) {
        notifications.create()
                .withCaption("You selected " + customerSelectionEvent.getSelected().size() + " customers")
                .show();
    }

    可以使用isUserOriginated() 方法跟踪 SelectionEvent 事件的来源。

  • addGeneratedColumn() 方法允许在列中自定义数据的表现方式。它需要两个参数:列的标识符和 Table.ColumnGenerator 接口的实现。如果标识符可以匹配 XML 描述中为表格列设置的标识符 - 在这种情况下,插入新列代替 XML 中定义的列。如果标识符与任何列都不匹配,则会在右侧添加新列。

    对于表的每一行将调用 Table.ColumnGenerator 接口的 generateCell() 方法。该方法接受在相应行中显示的实体实例作为参数。generateCell() 方法应该返回一个可视化组件,该组件将显示在单元格中。

    使用组件的示例:

    @Inject
    private GroupTable<Car> carsTable;
    @Inject
    private CollectionContainer<Car> carsDc;
    @Inject
    private CollectionContainer<Color> colorsDc;
    @Inject
    private UiComponents uiComponents;
    @Inject
    private Actions actions;
    
    @Subscribe
    protected void onInit(InitEvent event) {
        carsTable.addGeneratedColumn("color", entity -> {
            LookupPickerField<Color> field = uiComponents.create(LookupPickerField.NAME);
            field.setValueSource(new ContainerValueSource<>(carsDc, "color"));
            field.setOptions(new ContainerOptions<>(colorsDc));
            field.addAction(actions.create(LookupAction.class));
            field.addAction(actions.create(OpenAction.class));
            return field;
        });
    }

    在上面的示例中,表中 color 列中的所有单元格都显示了 LookupPickerField 组件。组件应将它的值保存到相应的行中的实体的 color 属性中。

    如果要显示动态文本,请使用特殊类 Table.PlainTextCell 而不是 Label 组件。它将简化渲染过程并使表格运行更快。

    如果 addGeneratedColumn() 方法接收到的参数是未在 XML 描述中声明的列的标识符,则新列的标题将设置如下:

    carsTable.getColumn("colour").setCaption("Colour");

    还可以考虑使用 XML 的 generator 属性做更具声明性的设置方案。

  • requestFocus() 方法允许将焦点设置在某一行的具体的可编辑字段上。需要两个参数:表示行的实体实例和列的标识符。请求焦点的示例如下:

    table.requestFocus(item, "count");
  • scrollTo() 方法允许将表格滚动到具体行。需要一个参数:表示行的实体实例。

    滚动条的示例:

    table.scrollTo(item);
  • 如果需要在单元格中显示自定义内容并且在用户单击单元格的时候能收到通知,可以使用 setClickListener() 方法实现这些功能。CellClickListener 接口的实现接收选中实体和列标识符作为参数。这些单元格的内容将被包装在一个 span 元素中,这个 span 元素带有 cuba-table-clickable-cell 样式,可以利用该样式来定义单元格外观。

    使用 CellClickListener 的示例:

    @Inject
    private Table<Customer> customersTable;
    @Inject
    private Notifications notifications;
    
    @Subscribe
    protected void onInit(InitEvent event) {
        customersTable.setCellClickListener("name", customerCellClickEvent ->
                notifications.create()
                        .withCaption(customerCellClickEvent.getItem().getName())
                        .show());
    }
  • setStyleProvider() 方法可以设置表格单元格显示样式。该方法接受 Table.StyleProvider 接口的实现类作为参数。表格的每一行和每个单元分别调用这个接口的 getStyleName() 方法。如果某一行调用该方法,则第一个参数包含该行显示的实体实例,第二个参数为 null。如果单元格调用该方法,则第二个参数包含单元格显示的属性的名称。

    设置样式的示例:

    @Inject
    protected Table customersTable;
    
    @Subscribe
    protected void onInit(InitEvent event) {
        customersTable.setStyleProvider((customer, property) -> {
            if (property == null) {
            // style for row
            if (hasComplaints(customer)) {
                return "unsatisfied-customer";
            }
        } else if (property.equals("grade")) {
            // style for column "grade"
            switch (customer.getGrade()) {
                case PREMIUM: return "premium-grade";
                case HIGH: return "high-grade";
                case MEDIUM: return "medium-grade";
                default: return null;
            }
        }
            return null;
        });
    }

    然后应该在应用程序主题中设置的单元格和行样式。有关创建主题的详细信息,请参阅 主题。对于 Web 客户端,新样式在 styles.scss 文件中。在控制器中定义的样式名称,以及表格行和列的前缀标识符构成 CSS 选择器。例如:

    .v-table-row.unsatisfied-customer {
      font-weight: bold;
    }
    .v-table-cell-content.premium-grade {
      background-color: red;
    }
    .v-table-cell-content.high-grade {
      background-color: green;
    }
    .v-table-cell-content.medium-grade {
      background-color: blue;
    }
  • addPrintable() 当通过 excel 标准操作或直接使用 ExcelExporter 类导出数据到 XLS 文件时,此方法可以给列中数据设置自定义展现。该方法接收的两个参数为列标识符和为列提供的 Table.Printable 接口实现。例如:

    ordersTable.addPrintable("customer", new Table.Printable<Customer, String>() {
        @Override
        public String getValue(Customer customer) {
            return "Name: " + customer.getName;
        }
    });

    Table.Printable 接口的 getValue() 方法应该返回在表格单元格中显示的数据。返回的数据不一定是字符串类型,该方法可以返回其它类型的值,比如数字或日期,它们将在 XLS 文件中以相应的类型展示。

    如果生成的列需要在输出到 XLS 时带有格式,则应该使用 addGeneratedColumn() 方法,传递一个 Table.PrintableColumnGenerator 接口的实现作为参数。XLS 文档中单元格的值在这个接口的 getValue() 方法中定义:

    ordersTable.addGeneratedColumn("product", new Table.PrintableColumnGenerator<Order, String>() {
        @Override
        public Component generateCell(Order entity) {
            Label label = uiComponents.create(Label.NAME);
            Product product = order.getProduct();
            label.setValue(product.getName() + ", " + product.getCost());
            return label;
        }
    
        @Override
        public String getValue(Order entity) {
            Product product = order.getProduct();
            return product.getName() + ", " + product.getCost();
        }
    });

    如果没有以某种方式为生成的列定义 Printable 描述,那么该列将显示相应实体属性的值,如果没有关联的实体属性,则不显示任何内容。

  • setItemClickAction() 方法能够定义一个双击表格行时将执行的操作。如果未定义此操作,表格将尝试按以下顺序在其操作列表中查找适当的操作:

    • shortcut 属性指定给 Enter 键的操作

    • edit 操作

    • view 操作

      如果找到此操作,并且操作具有 enabled=true 属性,则执行该操作。

  • setEnterPressAction() 方法可以定义按下 Enter 键时执行的操作。如果未定义此操作,则表将尝试按以下顺序在其操作列表中查找适当的操作:

    • setItemClickAction() 方法定义的动作

    • shortcut 属性指定给 Enter 键的操作

    • edit 操作

    • view 操作

    如果找到此操作,并且操作具有 enabled=true 属性,则执行该操作。