修改后的单元格字体无法保存(XSSF)

Modified cell font cannot be saved (XSSF)

我正在使用 Apache-POI 5.0 为 Excel 编码,但遇到了麻烦。也就是说,我迭代单元格以根据内容的长度修改它们的字体大小(文件中的默认字体大小为 20)。以下示例显示了错误:(仅核心代码)

    /**
     * Export the present seat table through an given BufferedOutputStream
     *
     * @param out the BufferedOutputStream to be written
     * @throws IOException            if <code>out</code> can not be written
     * @throws InvalidFormatException NEVER HAPPENS unless you delete <code>templateOfTable.xlsx</code>
     */
    void exportTable(BufferedOutputStream out) throws IOException, InvalidFormatException {
        InputStream in = MainWindow.class.getResourceAsStream(GlobalVariables.TABLE_TEMPLATE_P);
        assert in != null;
        OPCPackage pkg = OPCPackage.open(in);
        XSSFWorkbook wb = new XSSFWorkbook(pkg);
        XSSFSheet table = wb.getSheetAt(0);
        for (int row = 0; row < 6; row++) {
            XSSFRow r = table.getRow(row);
            for (int col = 0; col < 8; col++) {
                writeCell(students[row][col],
                        r.getCell(col, Row.MissingCellPolicy.RETURN_NULL_AND_BLANK),
                        wb.createCellStyle());
            }
        }
        // TEST POINT 1   iterate over cells
//        for (int row = 0; row < 6; row++) {
//            XSSFRow r = table.getRow(row);
//            for (int col = 0; col < 8; col++) {
//                System.out.println(r.getCell(col).getCellStyle().getFont().getFontHeightInPoints());
//            }
//        }
        wb.write(out);
        out.close();
        pkg.close();
    }

    /**
     * A method to write a cell.
     *
     * @param stu      the student to be stored
     * @param cell     the cell to be written
     * @param newStyle should be a new style, through <code>wb.createCellStyle()</code>
     */
    private void writeCell(Student stu, XSSFCell cell, XSSFCellStyle newStyle) {
        newStyle.cloneStyleFrom(cell.getCellStyle());
        cell.setCellValue(stu.getName());
        
        //KEY CODE
        newStyle.getFont().setFontHeightInPoints((short) (stu.isLongName() ? 17 : 20));
        newStyle.setFillForegroundColor(stu.isBoarding() ?
                IndexedColors.PALE_BLUE.getIndex() :
                IndexedColors.LIGHT_GREEN.getIndex());
        newStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        cell.setCellStyle(newStyle);
        //TEST POINT 0
//        System.out.println(cell.getCellStyle().getFont().getFontHeightInPoints());
    }

test point 0,输出将是这样的:

20
17
20
20
... (Several "17" and "20")
20

但是在 test point 1,事情似乎不对:

20
20
...
20 (All the lines are "20")

这是最奇怪的事情,因为在输出文件中,每个单元格的颜色和内容都是正确的,但是字体大小(以点为单位的高度)都是 20。
我在网上找了很久。但是没有用。请帮助或尝试提供一些想法如何实现这一目标。提前致谢。

方法CellStyle.cloneStyleFrom克隆单元格样式而不是字体。因此,您一直使用相同的字体将字体大小更改为 17 或 20。因此使用最后一个字体大小集。在你的情况下是 20。

Excel 存储中,单元格样式和字体存储在工作簿级别。单元格共享单元格样式,单元格样式共享字体。知道这一点,CellStyle.cloneStyleFrom其实用处不大。就像您使用它一样,它会为每个单个单元格创建一个单元格样式,即使这些单元格样式完全相同并且应该共享。另外,如前所述,它不会克隆使用的字体。而且它也不应该,因为如果它是不同单元格样式的相同字体,那么应该共享字体。背景是有limits for count of cell styles as well as for fonts in workbooks.

因此,如果需要根据条件改变单元格样式和字体,那么您需要先创建所有需要的单元格样式和字体,然后再将它们应用到单元格,或者您需要一种实际上只能改变某些样式和字体的方法单元格样式或字体的特殊属性。单元格样式有 CellUtil。但是直到现在还没有字体。

在下面的完整示例中,我提供了仅改变所用字体的一些特殊属性的方法。我还使用 CellUtil 来仅改变所用单元格样式的一些特殊属性。这是完整的、经过测试的,适用于 XSSFHSSF。并且它考虑到使用共享单元格样式和字体以避免达到限制。

import java.io.FileInputStream;
import java.io.FileOutputStream;

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellUtil;

import java.util.Map;
import java.util.HashMap;

public class ExcelVaryFonts {
    
 //method for getting current font from cell
 private static Font getFont(Cell cell) {
  Workbook workbook = cell.getSheet().getWorkbook();
  CellStyle style = cell.getCellStyle();
  return workbook.getFontAt(style.getFontIndex());
 }

 private enum FontProperty {
  BOLD, COLOR, FONTHEIGHT, FONTNAME, ITALIC, STRIKEOUT, TYPEOFFSET, UNDERLINE
 }

 //method for getting font having special settings additional to given source font
 private static Font getFont(Workbook workbook, Font fontSrc, Map<FontProperty, Object> fontproperties) {
  boolean isBold = fontSrc.getBold();
  short color = fontSrc.getColor();
  short fontHeight = fontSrc.getFontHeight();
  String fontName = fontSrc.getFontName();
  boolean isItalic = fontSrc.getItalic();
  boolean isStrikeout = fontSrc.getStrikeout();
  short typeOffset = fontSrc.getTypeOffset();
  byte underline = fontSrc.getUnderline();

  for (FontProperty property : fontproperties.keySet()) {
   switch (property) {
    case BOLD:
     isBold = (boolean)fontproperties.get(property);
    break;
    case COLOR:
     color = (short)fontproperties.get(property);
    break;
    case FONTHEIGHT:
     fontHeight = (short)fontproperties.get(property);
    break;
    case FONTNAME:
     fontName = (String)fontproperties.get(property);
    break;
    case ITALIC:
     isItalic = (boolean)fontproperties.get(property);
    break;
    case STRIKEOUT:
     isStrikeout = (boolean)fontproperties.get(property);
    break;
    case TYPEOFFSET:
     typeOffset = (short)fontproperties.get(property);
    break;
    case UNDERLINE:
     underline = (byte)fontproperties.get(property);
    break;
   }
  }

  Font font = workbook.findFont(isBold, color, fontHeight, fontName, isItalic, isStrikeout, typeOffset, underline);
  if (font == null) {
   font = workbook.createFont();
   font.setBold(isBold);
   font.setColor(color);
   font.setFontHeight(fontHeight);
   font.setFontName(fontName);
   font.setItalic(isItalic);
   font.setStrikeout(isStrikeout);
   font.setTypeOffset(typeOffset);
   font.setUnderline(underline);
  }

  return font;
 }
                
 private static void writeCell(Student stu, Cell cell) {
  Map<String, Object> styleproperties = null;
  Map<FontProperty, Object> fontproperties = null;
  
  Workbook workbook = cell.getSheet().getWorkbook();
  
  cell.setCellValue(stu.getName());
  
  //get or create the needed font 20pt
  fontproperties = new HashMap<FontProperty, Object>();
  fontproperties.put(FontProperty.FONTHEIGHT, (short)(20*20));
  Font font20 = getFont(workbook, getFont(cell), fontproperties);
  //get or create the needed font 17pt
  fontproperties = new HashMap<FontProperty, Object>();
  fontproperties.put(FontProperty.FONTHEIGHT, (short)(17*20));
  Font font17 = getFont(workbook, getFont(cell), fontproperties);
  
  //create style propertes for cell
  styleproperties = new HashMap<String, Object>();
  if (stu.isLongName()) {
   styleproperties.put(CellUtil.FONT, font17.getIndex());
  } else {
   styleproperties.put(CellUtil.FONT, font20.getIndex());
  }
  
  if (stu.isBoarding()) {
   styleproperties.put(CellUtil.FILL_FOREGROUND_COLOR, IndexedColors.PALE_BLUE.getIndex());
   styleproperties.put(CellUtil.FILL_PATTERN, FillPatternType.SOLID_FOREGROUND);
  } else {
   styleproperties.put(CellUtil.FILL_FOREGROUND_COLOR, IndexedColors.LIGHT_GREEN.getIndex());
   styleproperties.put(CellUtil.FILL_PATTERN, FillPatternType.SOLID_FOREGROUND);
  }
  
  //set style properties to cell
  CellUtil.setCellStyleProperties(cell, styleproperties);  
 }
    
 public static void main(String[] args) throws Exception {

  String inFilePath = "./ExcelExampleIn.xlsx"; String outFilePath = "./ExcelExampleOut.xlsx";
  //String inFilePath = "./ExcelExampleIn.xls"; String outFilePath = "./ExcelExampleOut.xls";
  
  Student[][] students = new Student[][] {
   new Student[]{new Student("Jane", true, false), new Student("John has a long name", false, true), new Student("Foo", true, false)},   
   new Student[]{new Student("Bar has a very long name", true, true), new Student("Stud", false, false)},   
   new Student[]{new Student("Next student having a long name", true, true), new Student("Bar", false, false), new Student("Test", true, false)},   
  };

  try (Workbook workbook = WorkbookFactory.create(new FileInputStream(inFilePath));
       FileOutputStream out = new FileOutputStream(outFilePath ) ) {

   Sheet sheet = workbook.getSheetAt(0);

   for (int r = 0; r < students.length; r++) {
    Row row = CellUtil.getRow(r, sheet);
    for (int c = 0; c < students[r].length; c++) {
     Cell cell = CellUtil.getCell(row, c);
     writeCell(students[r][c], cell);
    }
   }
   
   workbook.write(out);
  }
 }
}

为了完整性使用了Studentclass:

public class Student {
 private String name;
 private boolean boarding;
 private boolean longName;
 public Student(String name, boolean boarding, boolean longName) {
  this.name = name;
  this.boarding = boarding; 
  this.longName = longName; 
 }
 public String getName() {
  return this.name;   
 }
 public boolean isBoarding() {
  return this.boarding;   
 }
 public boolean isLongName() {
  return this.longName;   
 }
}