1. 引言

在这个快速教程中,我们将探讨如何使用Apache POI库有效地在Excel工作表中应用粗体字体样式到整行。通过清晰的示例和有价值的洞察,我们将导航每个方法的细微差别。

2. 依赖项

首先,我们需要写入和加载Excel文件的依赖项,poi:

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>5.2.5</version>
</dependency>

3. 场景与辅助方法

我们的场景是创建一个带有表头行和一些数据行的工作表。然后,我们将为表头行使用的字体定义一个粗体样式。最后,我们将创建一些方法来设置这个粗体样式。最重要的是,我们会看到为什么需要不止一种方法来完成这个任务,因为显而易见的选择(setRowStyle())并不能按预期工作。

为了方便创建工作表,我们先从一个utils类开始。 我们将编写一些方法来创建带有单元格的行:

public class PoiUtils {

    private static void newCell(Row row, String value) {
        short cellNum = row.getLastCellNum();
        if (cellNum == -1)
            cellNum = 0;

        Cell cell = row.createCell(cellNum);
        cell.setCellValue(value);
    }

    public static Row newRow(Sheet sheet, String... rowValues) {
        Row row = sheet.createRow(sheet.getLastRowNum() + 1);

        for (String value : rowValues) {
            newCell(row, value);
        }

        return row;
    }

    // ...
}

接着,为了创建粗体字体样式,我们将首先从我们的Workbook创建一个字体,然后调用setBold(true) 其次,我们将创建一个使用粗体字体的CellStyle

public static CellStyle boldFontStyle(Workbook workbook) {
    Font boldFont = workbook.createFont();
    boldFont.setBold(true);

    CellStyle boldStyle = workbook.createCellStyle();
    boldStyle.setFont(boldFont);

    return boldStyle;
}

最后,为了将我们的工作表写入文件,我们需要调用write()到我们的Workbook

public static void write(Workbook workbook, Path path) 
  throws IOException {
    try (FileOutputStream fileOut = new FileOutputStream(path.toFile())) {
        workbook.write(fileOut);
    }
}

4. 使用setRowStyle()的注意事项

在查看POI API时,对于我们的任务,最明显的选择是Row.setRowStyle()不幸的是,这种方法并不稳定,目前有一个已知的bug问题似乎在于Microsoft Office渲染器忽略了行样式,只关心单元格样式。

另一方面,在OpenOffice中,它仅在我们使用SXSSFWorkbook实现时工作,该实现适用于大型文件。 为了测试这一点,我们从一个示例工作表方法开始:

private void writeSampleSheet(Path destination, Workbook workbook) 
  throws IOException {
    Sheet sheet = workbook.createSheet();
    CellStyle boldStyle = PoiUtils.boldFontStyle(workbook);

    Row header = PoiUtils.newRow(sheet, "Name", "Value", "Details");
    header.setRowStyle(boldStyle);

    PoiUtils.newRow(sheet, "Albert", "A", "First");
    PoiUtils.newRow(sheet, "Jane", "B", "Second");

    PoiUtils.write(workbook, destination);
}

然后,使用断言方法检查第一行和第二行的样式。首先,我们断言第一行具有粗体字体样式。然后,对于其中的每个单元格,我们断言默认样式与我们为第一行设置的样式不同,这证明了我们的行样式具有优先级。 最后,我们断言第二行没有任何样式应用:

private void assertRowStyleAppliedAndDefaultCellStylesDontMatch(Path sheetFile) 
  throws IOException, InvalidFormatException {
    try (Workbook workbook = new XSSFWorkbook(sheetFile.toFile())) {
        Sheet sheet = workbook.getSheetAt(0);
        Row row0 = sheet.getRow(0);

        XSSFCellStyle rowStyle = (XSSFCellStyle) row0.getRowStyle();
        assertTrue(rowStyle.getFont().getBold());

        row0.forEach(cell -> {
            XSSFCellStyle style = (XSSFCellStyle) cell.getCellStyle();
            assertNotEquals(rowStyle, style);
        });

        Row row1 = sheet.getRow(1);
        XSSFCellStyle row1Style = (XSSFCellStyle) row1.getRowStyle();
        assertNull(row1Style);

        Files.delete(sheetFile);
    }
}

最终,我们的测试包括将工作表写入临时文件,然后读回。我们确保样式已应用,测试第一行和第二行的样式,然后删除文件:

@Test
void givenXssfWorkbook_whenSetRowStyle1stRow_thenOnly1stRowStyled() 
  throws IOException, InvalidFormatException {
    Path sheetFile = Files.createTempFile("xssf-row-style", ".xlsx");

    try (Workbook workbook = new XSSFWorkbook()) {
        writeSampleSheet(sheetFile, workbook);
    }

    assertRowStyleAppliedAndDefaultCellStylesDontMatch(sheetFile);
}

当运行此测试时,我们现在可以确认我们只在第一行获得粗体样式,这是我们期望的。

5. 使用setCellStyle()设置行中的单元格样式

鉴于setRowStyle()的问题,我们剩下的是setCellStyle()。我们需要它来为我们要应用粗体样式的行中的每个单元格设置样式。因此,**让我们修改原始代码,遍历表头行中的每一行,并使用粗体样式调用setCellStyle()**:

@Test
void givenXssfWorkbook_whenSetCellStyleForEachRow_thenAllCellsContainStyle() 
  throws IOException, InvalidFormatException {
    Path sheetFile = Files.createTempFile("xssf-cell-style", ".xlsx");

    try (Workbook workbook = new XSSFWorkbook()) {
        Sheet sheet = workbook.createSheet();
        CellStyle boldStyle = PoiUtils.boldFontStyle(workbook);

        Row header = PoiUtils.newRow(sheet, "Name", "Value", "Details");
        header.forEach(cell -> cell.setCellStyle(boldStyle));

        PoiUtils.newRow(sheet, "Albert", "A", "First");
        PoiUtils.write(workbook, sheetFile);
    }

    // ...
}

这样,我们可以保证我们的样式在整个格式和平台之间的一致性得到应用。 让我们通过断言行样式未设置以及第一行中的每个单元格都包含粗体字体样式来完成我们的测试:

try (Workbook workbook = new XSSFWorkbook(sheetFile.toFile())) {
    Sheet sheet = workbook.getSheetAt(0);
    Row row0 = sheet.getRow(0);

    XSSFCellStyle rowStyle = (XSSFCellStyle) row0.getRowStyle();
    assertNull(rowStyle);

    row0.forEach(cell -> {
        XSSFCellStyle style = (XSSFCellStyle) cell.getCellStyle();
        assertTrue(style.getFont().getBold());
    });

    Row row1 = sheet.getRow(1);
    rowStyle = (XSSFCellStyle) row1.getRowStyle();
    assertNull(rowStyle);

    Files.delete(sheetFile);
}

请注意,这里我们仅出于便利使用XSSFWorkbook。这种方法在所有Workbook实现中都是一致的。

6. 总结

在这篇文章中,我们了解到虽然setRowStyle()可能无法可靠地满足我们的目标,但我们已经发现了一个使用setCellStyle()的稳健替代方案。现在,我们可以自信地格式化Excel工作表中的行,确保在各种平台上获得一致且视觉上引人注目的结果。

如往常一样,源代码可在GitHub上找到。