spreadsheet 电子表格
|
该组件需要安装商业版 Vaadin Spreadsheet 插件。 |
spreadsheet 组件可以在视图中嵌入兼容 Excel 的电子表格。在浏览器中渲染 Apache POI 工作簿,支持用户编辑,并为视图提供对底层 POI 模型的完整编程式访问。
XML 元素 |
|
|---|---|
Java 类 |
|
基本用法
在视图 XML 中声明 sprd 命名空间并添加 spreadsheet 组件:
<view xmlns="http://jmix.io/schema/flowui/view"
xmlns:sprd="http://jmix.io/schema/vaadin-spreadsheet/ui"
title="msg://spreadsheetBasicsView.title">
<layout expand="spreadsheet">
<sprd:spreadsheet id="spreadsheet" width="100%"/>
</layout>
</view>
|
当使用顶部操作面板中的 Add Component 操作添加组件时,Studio 会自动添加该命名空间。请参见 组件面板。 |
以常规方式在控制器中注入组件:
@ViewComponent
private Spreadsheet spreadsheet;
加载 XLSX
使用 read() 方法可以将文件内容加载到组件中,并替换其当前内容。该方法接收 InputStream,因此可以是从任何来源读取的文件:如下例中的应用程序资源、文件存储,或使用 fileStorageUploadField 上传的文件。
@Subscribe
public void onInit(final InitEvent event) {
try (InputStream inputStream =
resources.getResourceAsStream("com/company/vaadinspreadsheetex1/budget.xlsx")) {
spreadsheet.read(inputStream);
} catch (IOException e) {
throw new RuntimeException("Unable to read the budget template", e);
}
}
而 write() 方法则相反,将工作簿写入输出流。然后将字节传递给 下载器,即可让用户保存当前文档:
@Subscribe("downloadBtn")
public void onDownloadBtnClick(final ClickEvent<JmixButton> event) {
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
spreadsheet.write(outputStream);
downloader.download(outputStream.toByteArray(), "budget.xlsx", DownloadFormat.XLSX);
} catch (IOException e) {
throw new RuntimeException("Unable to write the spreadsheet", e);
}
}
使用实体数据
电子表格还可以使用实体的数据填充。单元格没有与属性绑定,因此在打开时需要填充工作表,在保存时中表中读取值。
下面的示例中,从 集合容器 读取实体然后填充单元格:
@ViewComponent
private CollectionContainer<User> usersDc;
@Subscribe
public void onBeforeShow(final BeforeShowEvent event) {
spreadsheet.createCell(0, 0, "Username");
spreadsheet.createCell(0, 1, "First name");
spreadsheet.createCell(0, 2, "Last name");
List<User> users = usersDc.getItems();
for (int i = 0; i < users.size(); i++) {
User user = users.get(i);
int row = i + 1;
spreadsheet.createCell(row, 0, user.getUsername());
spreadsheet.createCell(row, 1, user.getFirstName());
spreadsheet.createCell(row, 2, user.getLastName());
}
spreadsheet.createFreezePane(1, 0);
}
保存时,遍历每一行获取单元格的值,设置给相应的属性。实体在数据容器中,因此视图的 数据上下文 可以跟踪实体的改动,调用其 save() 保存所有更改:
@Subscribe("saveBtn")
public void onSaveBtnClick(final ClickEvent<JmixButton> event) {
List<User> users = usersDc.getItems();
for (int i = 0; i < users.size(); i++) {
User user = users.get(i);
int row = i + 1;
user.setFirstName(getCellValue(row, 1));
user.setLastName(getCellValue(row, 2));
}
dataContext.save();
}
@Nullable
private String getCellValue(int row, int column) {
Cell cell = spreadsheet.getCell(row, column);
return cell != null
// DataFormatter returns the text of a cell whatever its type
? spreadsheet.getDataFormatter().formatCellValue(cell)
: null;
}
| 这种方式只保存值。对工作表本身的更改(例如新列、格式或公式)在视图关闭时会丢失。 |
配置单元格
通过组件(而不是 POI)创建单元格,以便组件知晓渲染的内容。行和列的索引从零开始,下面的代码填充范围 A1:B2:
spreadsheet.createCell(0, 0, "Product");
spreadsheet.createCell(0, 1, "Amount");
spreadsheet.createCell(1, 0, "Widget");
spreadsheet.createCell(1, 1, 1200);
值可以是 String、Double、Boolean、Date 或 Calendar 类型。
已有单元格通过 getCell() 获取,可以按索引或按 A1 表示法中的地址获取。如果通过 POI API 更改单元格,需要刷新以显示新值。refreshCells() 的参数可以是多个单元格或一个集合:
Cell cell = spreadsheet.getCell("B2");
cell.setCellValue(1500);
spreadsheet.refreshCells(cell);
公式由单独的方法创建并在服务器上计算。编写时不需要以 = 开头:
spreadsheet.createFormulaCell(3, 1, "SUM(B2:B3)");
若要一次性重新计算所有公式(例如在替换一组值之后),请调用 refreshAllCellValues()。
设置单元格样式
字体、颜色、边框和数字格式属于工作簿,因此使用 POI API。创建样式,然后应用在单元格,最后刷新单元格:
Workbook workbook = spreadsheet.getWorkbook();
CellStyle headerStyle = workbook.createCellStyle();
Font boldFont = workbook.createFont();
boldFont.setBold(true);
headerStyle.setFont(boldFont);
CellStyle amountStyle = workbook.createCellStyle();
DataFormat dataFormat = workbook.createDataFormat();
amountStyle.setDataFormat(dataFormat.getFormat("#,##0.00"));
Cell headerCell = spreadsheet.getCell(0, 0);
headerCell.setCellStyle(headerStyle);
Cell amountCell = spreadsheet.getCell(1, 1);
amountCell.setCellStyle(amountStyle);
spreadsheet.refreshCells(headerCell, amountCell);
| 工作簿对单元格样式有数量上的限制,因此,每种样式仅创建一次,而不是每个单元格都新建样式。 |
选择
初始选中的单元格或范围由 selection 属性以 A1 表示法设置,例如 A1 或 B2:D8。每次用户选择其他内容时,组件都会通知视图:
@Subscribe("spreadsheet")
public void onSpreadsheetSelectionChange(final Spreadsheet.SelectionChangeEvent event) {
CellReference reference = event.getSelectedCellReference();
selectionLabel.setText(reference.formatAsString());
}
事件还包含:getIndividualSelectedCells() 返回单独选中的单元格,getCellRangeAddresses() 返回选中的范围。
在单元格添加组件
单元格可以显示 UI 组件,例如布尔列显示复选框。电子表格会询问工厂在单元格中放置什么组件,提供一个 spreadsheetComponentFactory 并为需要组件的单元格返回一个组件:
@Supply(to = "spreadsheet", subject = "spreadsheetComponentFactory")
private SpreadsheetComponentFactory spreadsheetComponentFactory() {
return new SpreadsheetComponentFactory() {
@Override
public Component getCustomComponentForCell(Cell cell, int rowIndex, int columnIndex,
Spreadsheet spreadsheet, Sheet sheet) {
if (columnIndex == 3 && rowIndex > 0) {
Checkbox checkbox = uiComponents.create(Checkbox.class);
checkbox.setValue(cell != null && cell.getBooleanCellValue());
return checkbox;
}
return null; // leave the cell as it is
}
@Override
public Component getCustomEditorForCell(Cell cell, int rowIndex, int columnIndex,
Spreadsheet spreadsheet, Sheet sheet) {
return null;
}
@Override
public void onCustomEditorDisplayed(Cell cell, int rowIndex, int columnIndex,
Spreadsheet spreadsheet, Sheet sheet,
Component customEditor) {
}
};
}
先调用 getCustomComponentForCell(),仅当返回 null 时才调用 getCustomEditorForCell()。
包含组件的行高不会低于 minimumRowHeightForComponents,因此组件总是可见。
添加和删除工作表
工作簿包含一个或多个工作表(Sheet),组件一次显示其中一个。新工作表通过名称、行数和列数创建:
spreadsheet.createNewSheet("Summary", 100, 20); // name, rows, columns
spreadsheet.setSheetName(1, "Details");
spreadsheet.setActiveSheetIndex(1);
工作表的删除:
if (spreadsheet.getNumberOfSheets() > 1) {
spreadsheet.deleteSheet(spreadsheet.getActiveSheetIndex());
}
工作表按索引寻址,有两种索引。不带 POIIndex 后缀的方法仅计算可见工作表,即用户在工作表选择栏中看到的内容。带后缀的方法(如 setActiveSheetWithPOIIndex() 和 deleteSheetWithPOIIndex())计算工作簿的所有工作表,包括隐藏的。activeSheetIndex 和 activeSheetWithPOIIndex 属性也是同样的工作原理。
锁定行列
锁定的行和列在其他工作表滚动时保持不动,能在长表格中保持标题行可见:
spreadsheet.createFreezePane(1, 0); // the first row, no columns
参数依次是锁定的行数和列数。POI 的 Sheet.createFreezePane() 方法参数顺序相反。
|
行列分组
如果加载的文档使用了分组,则在应用程序中保留。分组会以标题旁的大纲控件显示,用户在浏览器中可以像在 Excel 中一样折叠和展开。
下面的示例加载一个将国家按地区分组的文档:
@Subscribe
public void onInit(final InitEvent event) {
try (InputStream inputStream =
resources.getResourceAsStream("com/company/vaadinspreadsheetex1/countries.xlsx")) {
spreadsheet.read(inputStream);
} catch (IOException e) {
throw new RuntimeException("Unable to read the countries document", e);
}
}
分组也可以通过代码创建。需要使用 POI API(组件没有自己的分组 API),然后重新加载组件。重新加载才能显示大纲控件:
@Subscribe("groupBtn")
public void onGroupBtnClick(final ClickEvent<JmixButton> event) {
Sheet sheet = spreadsheet.getActiveSheet();
sheet.groupColumn(1, 2); // columns B and C
spreadsheet.reload();
}
| 分组仅适用于 XLSX 文档。 |
报表模式
reportStyle 属性隐藏组件的两个工具栏:工作表上方的函数栏和下方的工作表选择栏。
<sprd:spreadsheet id="spreadsheet"
width="100%"
reportStyle="true"/>
网格线以及行和列标题不属于报表模式。它们属于工作表,从源文档中加载。每次加载或激活另一个工作表时,组件都会再次加载这些信息。加载文档后调用 setGridlinesVisible(false) 和 setRowColHeadingsVisible(false) 将其关闭,如果不希望用户更改工作表,还可以保护工作表:
spreadsheet.setGridlinesVisible(false);
spreadsheet.setRowColHeadingsVisible(false);
spreadsheet.setActiveSheetProtected("");
电子表格中的图表
spreadsheet 组件可以渲染 Excel 工作簿中已包含的图表。图表默认显示。设置 chartsEnabled 为 false 可隐藏。组件可以反映图表数据点的变化,但不支持更改图表配置、源单元格范围或位置。需要在原始 Excel 文件中进行此类更改。
更多信息,请参考 Vaadin 文档中的 电子表格中的图表。
XML 属性
下面是 spreadsheet 的特殊属性:
| 名称 | 描述 | 默认值 |
|---|---|---|
最初显示的工作表索引,仅计算可见工作表。 |
— |
|
最初显示的工作表索引,计算底层 POI 工作簿的所有工作表,包括隐藏的。 |
— |
|
如果为 |
|
|
在可见区域左右渲染的水平缓冲区大小,单位为像素。较大的缓冲区使滚动更平滑,但会增加负载。 |
|
|
新建工作表的列数。 |
|
|
列的默认宽度,单位为像素。 |
— |
|
默认的百分比值格式。 |
|
|
新建工作表的行数。 |
|
|
行的默认高度,单位为磅。 |
— |
|
如果为 |
|
|
用户输入无法解析的公式时显示的消息。支持消息键值。 |
— |
|
活跃工作表中渲染的列数。 |
— |
|
活跃工作表中渲染的行数。 |
— |
|
包含嵌入组件的行的最小高度(单位为磅),需要保持组件可见。请参见 在单元格添加组件。 |
|
|
如果为 |
|
|
在可见区域上下渲染的垂直缓冲区大小,单位为像素。 |
|
|
初始选中的单元格或范围,使用 A1 表示法,例如 |
— |
|
如果为 |
|
|
组件状态区域中显示的文本。支持消息键值。 |
— |
|
电子表格的主题。支持: |
|
下面是 spreadsheet 支持的其他通用属性:
事件和处理器
下面是 spreadsheet 的特殊事件和处理器:
|
在 Jmix Studio 生成处理器桩代码时,可以使用 Jmix UI 组件面板的 Handlers 标签页或者视图类顶部面板的 Generate Handler 添加,也可以通过 Code → Generate 菜单(Alt+Insert / Cmd+N)生成。 |
| 名称 | 描述 |
|---|---|
当用户更改了一个或多个单元格的值时发送。 |
|
当用户通过默认文本编辑器更新单元格时调用 |
|
|
|
当用户更改了一个或多个单元格中的公式时发送。 |
|
|
|
当用户尝试编辑受保护工作表上的锁定单元格时发送。 |
|
当用户双击行标题时发送。事件的 |
|
当选中的单元格或范围发生变化时发送。请参见 选择。 |
|
当用户切换到另一个工作表时发送。 |
|
|
|
|
下面是 spreadsheet 支持的其他通用事件:
参考
To fill the sheet, create cells from the items of a collection container:
@ViewComponent
private CollectionContainer<User> usersDc;
@Subscribe
public void onBeforeShow(final BeforeShowEvent event) {
spreadsheet.createCell(0, 0, "Username");
spreadsheet.createCell(0, 1, "First name");
spreadsheet.createCell(0, 2, "Last name");
List<User> users = usersDc.getItems();
for (int i = 0; i < users.size(); i++) {
User user = users.get(i);
int row = i + 1;
spreadsheet.createCell(row, 0, user.getUsername());
spreadsheet.createCell(row, 1, user.getFirstName());
spreadsheet.createCell(row, 2, user.getLastName());
}
spreadsheet.createFreezePane(1, 0);
}