3.5.2.1.11. 数据网格
DataGrid
- 数据网格组件类似于表格组件, 适合用于展示、分类、排序表格类数据,由于使用了在滚动时加载数据的延迟加载方式,所以此组件具有更好的数据行、列操作性能。
该组件的 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
指示数据容器实体中的属性,对应的数据库中的数据会展示在该列。
dataGrid - 数据网格中的元素:
columns
- 必要元素,定义数据网格的所有列。其中每一列在单独内嵌的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
, 此时侧边栏菜单中该列的标题与数据网格中该列的标题一致。
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()
方法可以控制数据加载器中的数据量,进而能控制每页最大行数。另外,绑定到相同数据源的通用过滤器组件也能实现此功能。
行数控件也会显示数据结果总数,但不需要把这些数据全部加载出来。用户可以点击 "?" 按钮, 它会调用 com.haulmont.cuba.core.global.DataManager#getCount
方法,该方法使用相同的参数请求数据库,同时使用 COUNT()
聚合函数代替加载数据。返回的数值会显示在 "*?" 位置。
数据网格控件属性:
columnResizeMode
- 设置调整列宽时的动画效果。支持两种效果:
ANIMATED
- 动画效果,列宽跟随鼠标拖拽(默认)。SIMPLE
- 简单效果,列宽会在拖拽动作结束后才发生改变。
列宽变化事件可以通过监听器 ColumnResizeListener
跟踪。可以使用 isUserOriginated() 方法跟踪列宽变化事件的来源。
columnsCollapsingAllowed
- 允许隐藏/折叠列,定义用户是否可以在侧边栏菜单中隐藏/折叠某些列。侧边栏菜单中显示的列旁边会有复选框。当用户选择或者取消选择某列时,对应列的collapsed
属性值会变化。当columnsCollapsingAllowed
属性为false
时,列对应的collapsed
属性不能被设置为true
。
列折叠状态的变化可以通过监听器 ColumnCollapsingChangeListener
跟踪。列折叠事件的来源可以使用 isUserOriginated() 方法进行跟踪.
contextMenuEnabled
- 开启或关闭右键菜单。默认为true
。
DataGrid
数据网格控件的右键点击事件可以通过监听器 ContextClickListener
跟踪。
editorBuffered
- 编辑器缓冲模式开启或关闭。默认为true
。
editorCancelCaption
- 设置DataGrid
数据网组件编辑器中取消(cancel)按钮的名称。
editorEnabled
- 启用数据项的行内编辑器。默认为false
。如果数据网格组件是跟 KeyValueCollectionContainer 或者 ValueCollectionDatasource 绑定的,则该数据是只读的,此时设置editorEnabled
属性便没有意义。
editorSaveCaption
- 设置数据网格组件编辑器中保存(save)按钮的名称。
frozenColumnCount
- 设置固定列的个数。0
表示不需要固定任何列,除了开启多选模式时的选择列。设为-1
的时候即使选择列也不固定。
headerVisible
- 定义是否显示表头。默认为true
。
reorderingAllowed
- 定义用户是否可以通过鼠标拖拽重新设置列的顺序。默认值为true
。
列排序的改变事件可以通过监听器 ColumnReorderListener
跟踪。排序改变事件的来源可以通过 isUserOriginated() 方法跟踪。
selectionMode
- 设置行选择模式,支持以下四种:
SINGLE
- 单行选择。MULTI
- 多行选择。MULTI_CHECK
- 通过内嵌复选框列进行多选。NONE
- 不支持选择。
行选中事件可以通过监听器 SelectionListener
跟踪。行选中事件的来源可以使用 isUserOriginated() 方法跟踪.
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()
andscrollToEnd()
- 将DataGrid
滚动到开头或结尾。
setCellStyleProvider()
- 设置DataGrid
单元格的显示样式。
setRowStyleProvider()
- 设置DataGrid
行显示样式。
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)。
使用描述提供者:
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);
}
setRowDescriptionProvider()
方法用来为每个DataGrid
行生成可选的描述(提示)。如果同时也设置了列描述提供者,只有在列描述提供者返回 null 时显示行描述提供器实例。
customersDataGrid.setRowDescriptionProvider(Instance::getInstanceName);
使用 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;
}
结果如图所示:
使用数据网格行内编辑器:
DataGrid
组件支持行内编辑器来编辑单元格数据。当用户要编辑一个数据项时,行内编辑界面会显示并自带默认的保存和取消按钮。
行内编辑器对应的方法有:
getEditedItemId()
- 返回正在被编辑的数据项的id
。isEditorActive()
- 是否正在行内编辑界面编辑某个数据项。
editItem()
- 为某个数据项打开行内编辑界面。如果数据项在当前界面区域不可见,数据网格会将数据项滚动到可视区域。
使用以下方法添加/删除行内编辑界面打的监听器:
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;
});
}
结果如下:
使用 ColumnGenerator(列生成器) 接口:
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");
}
结果如下:
ColumnGeneratorEvent
通过 getValue
方法传入, 它包含实体信息,在当前行的 ID 为 loginUpperCase
的列显示需要的数据。
默认情况下,新生成的列加在数据网格的最右边。有两种方法可以管理列的位置:代码中使用 index 或者在界面 XML 文件中提前定义好并设置 id
, 然后在 addGeneratedColumn
方法中使用该 id。
使用渲染器:
数据在列中的显示方式可以通过渲染器自定义。比如想在单元格中显示图标,可以使用 ImageRenderer
类和图标路径实现:
@Subscribe
protected void onInit(InitEvent event) {
DataGrid.Column avatar = usersGrid.addGeneratedColumn("userAvatar", new DataGrid.ColumnGenerator<User, String>() {
@Override
public String getValue(DataGrid.ColumnGeneratorEvent<User> event) {
return "icons/user.png";
}
@Override
public Class<String> getType() {
return String.class;
}
}, 0);
avatar.setCaption("Avatar");
avatar.setRenderer(usersGrid.createRenderer(DataGrid.ImageRenderer.class));
}
结果如下:
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());
}
结果如下:
当字段类型与渲染器支持的类型不匹配时,可以创建一个 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.CHECK_SQUARE_O.getHtml()
: FontAwesome.SQUARE_O.getHtml();
});
}
结果如下:
渲染器可以通过两种方式创建:
在
DataGrid
接口的 set 方法中直接设置渲染器接口。为对应的模块直接创建渲染器实现:
dataGrid.createRenderer(DataGrid.ImageRenderer.class) → new WebImageRenderer()
目前平台支持以下渲染器接口:
TextRenderer
- 显示文本。HtmlRenderer
- 显示 HTML 布局。ProgressBarRenderer
- 把 0 到 1 之间的double
浮点值作为ProgressBar
进度条组件显示。DateRenderer
- 以预定义格式显示日期。NumberRenderer
- 以预定义格式显示数字。ButtonRenderer
- 把字符串值做为按钮文字展示。ImageRenderer
- 将指定路径的图像显示。CheckBoxRenderer
- 将布尔值做为复选框显示。
表头(Header)和表尾(Footer):
HeaderRow
和 FooterRow
接口受制于分别展示表头和表尾单元格,支持跨列合并单元格。
DataGrid
的以下方法用于创建和管理表头、表尾:
appendHeaderRow()
、appendFooterRow()
- 在表头或表尾区底部添加一个新行。prependHeaderRow()
、prependFooterRow()
- 在表头或表尾区顶部添加一个新行。addHeaderRowAt()
、addFooterRowAt()
- 在表头或表尾区的指定位置添加新行。该位置及其后面的行位置顺序下移,行索引增加。removeHeaderRow()
、removeFooterRow()
- 从表头或表尾区删除指定行。getHeaderRowCount()
、getFooterRowCount()
- 获取表头或表尾区行数。setDefaultHeaderRow()
- 设置表头默认行。默认表头行为用户提供排序功能。
HeaderCell
和 FooterCell
接口提供自定义静态单元格功能:
setStyleName()
- 为单元格设置自定义样式。getCellType()
- 返回单元格内容类型。静态单元格枚举类型DataGridStaticCellType
有三个值:
TEXT
- 文本HTML
- HTMLCOMPONENT
- 组件
getComponent()
、getHtml()
、getText()
- 不同类型单元格获取内容的方法。
下面这个例子中,表头包含合并的单元格,表尾显示经计算得出的值:
<dataGrid id="dataGrid" datasource="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();
}
- DataGrid 的属性列表
align - caption - captionAsHtml - colspan - columnResizeMode - columnsCollapsingAllowed - contextHelpText - contextHelpTextHtmlEnabled - contextMenuEnabled - css - dataContainer - datasource - description - descriptionAsHtml - editorBuffered - editorCancelCaption - editorEnabled - editorSaveCaption - enable - box.expandRatio - frozenColumnCount - headerVisible - height - icon - id - reorderingAllowed - responsive - rowspan - selectionMode - settingsEnabled - sortable - stylename - tabIndex - textSelectionEnabled - visible - width- DataGrid 的元素
actions - buttonsPanel - columns - rowsCount- column 元素的属性列表
caption - collapsed - collapsible - collapsingToggleCaption - editable - expandRatio - id - maximumWidth - minimumWidth - property - resizable - sortable - width- column 的元素
formatter- API
addGeneratedColumn - applySettings - createRenderer - editItem - saveSettings - getColumns - setDescriptionProvider - setCellStyleProvider - setConverter - setDetailsGenerator - setEnterPressAction - setItemClickAction - setRenderer - setRowDescriptionProvider - setRowStyleProvider - sort- DataGrid 监听器
ColumnCollapsingChangeListener - ColumnReorderListener - ColumnResizeListener - ContextClickListener - EditorCloseListener - EditorOpenListener - EditorPostCommitListener - EditorPreCommitListener - ItemClickListener - SelectionListener - SortListener