首页 文章

Apache POI:将边框设置为包含不同样式的列的优雅方式

提问于
浏览
0

我正在使用apache-poi动态创建一个新的xlsx文件 . 任何列都可以包含不同的值类型(数字,字符串,布尔值,...) . 在将数据插入poi文档时,我根据数据类型设置CellStyles:

public final XSSFCellStyle cellStyleString;
public final XSSFCellStyle cellStyleNumber;
public final XSSFCellStyle cellStyleDate;
public final XSSFCellStyle cellStyleHeader;

这是我的 Headers 行的样子:

|   |   |   | Shared Header |
| H1| H2| H3|SH1|SH2|SH3|SH4|

有“简单” Headers 和“共享 Headers ”,其中包含“子 Headers ” . 共享标头位于合并的单元中 .

不,我想在列 SH1 处设置左边框,在列 SH4 处设置右边框以强调分组 . 但是,由于任何列都可以包含所有单元格的混合,似乎我必须创建像CellStyles一样

public final XSSFCellStyle cellStyleString;
public final XSSFCellStyle cellStyleStringBorderLeft;
public final XSSFCellStyle cellStyleStringBorderRight;
//and so on for the other styles...

此外,可能有嵌套的共享 Headers ,我想通过不同的边框大小区分 . 所以我需要类似的东西

public final XSSFCellStyle cellStyleString;
public final XSSFCellStyle cellStyleStringBorderLeftThickLine;
public final XSSFCellStyle cellStyleStringBorderRightThickLine;
public final XSSFCellStyle cellStyleStringBorderLeftThinLine;
public final XSSFCellStyle cellStyleStringBorderRightThinLine;
//and so on for the other styles...

无论现有样式如何,是否有更优雅的方式来设置列的边框?

Edit

虽然我更喜欢干净简单的方法,并且为了最大限度地减少创建的样式的数量,我在HSSFOptimiser上删除了重复的单元格样式 . 我不知道那堂课 . 即使我更喜欢避免这个实用程序,它也适合这个问题,值得在这里提到 .

3 回答

  • 5

    我即将结束对POI的增强,这将使您可以使用其特定样式填充值,然后在它们周围绘制边框,而无需手动创建所有必需的样式 . 在平均时间,有一种方法可以使用 CellUtil.setCellStyleProperties() . 这允许您向单元格已存在的 CellStyle 添加一组属性 .

    来自HSSF / XSSF的POI快速指南:

    Workbook workbook = new XSSFWorkbook();  // OR new HSSFWorkbook()
    Sheet sheet = workbook.createSheet("Sheet1");
    Map<String, Object> properties = new HashMap<String, Object>();
    
    // create your spreadsheet without borders
    ...
    
    // create property set for vertical borders
    properties.put(CellUtil.BORDER_LEFT, CellStyle.BORDER_MEDIUM);
    properties.put(CellUtil.BORDER_RIGHT, CellStyle.BORDER_MEDIUM);
    
    // Apply the borders to a 3x3 region starting at D4
    for (int ix=3; ix <= 5; ix++) {
      row = sheet.createRow(ix);
      for (int iy = 3; iy <= 5; iy++) {
        cell = row.createCell(iy);
        CellUtil.setCellStyleProperties(cell, properties);
      }
    }
    

    这允许您基本上填写电子表格,然后一次绘制一个单元格的边框 . 请注意,如果所有边框都相似(全部为THIN),那么这将适用于整个范围 . 但是,如果要在表格外部绘制MEDIUM边框,则必须创建一些其他属性集 . 请注意,您不必对电子表格中已有的行和单元格使用 createRow()createCell() . 这将解决合并的单元格 .

    注意: CellUtil.setCellStyleProperties() 出现在POI 3.14中,允许您在一次拍摄中添加多个单元格属性,从而避免创建多个未使用的样式 . 较旧的 CellUtil.setCellStyleProperty() 一次设置一个属性,并且作为意外结果,在电子表格中创建中间的 CellStyle 对象,结果从未被使用过 . 这可能是较大的纸张中的问题 .

    编辑: PropertyTemplate 是POI 3.15中添加的新对象,它允许您为单元格定义一组边框并将其标记到要应用它的任何图纸上 . 此对象类似于创建预打印表单以覆盖数据 . 有关如何使用 PropertyTemplate 的更多信息,请参阅POI电子表格快速指南 .

  • 1

    正如您已经提到的,创建成千上万个类似的单元格样式对象并不好 . 在我的项目中,我创建了一个简单的“样式助手”类,其中包含一个映射,它知道所有现有的样式实例

    private Workbook workbook;
    private HashMap<String, CellStyle> styleMap = new HashMap<>();
    
    public CellStyle getStyle(Font font, ExcelCellAlign hAlign, ExcelCellAlign vAlign, boolean wrapText, ExcelCellBorder border, Color color, ExcelCellFormat cellFormat) {
    
        //build unique which represents the style
        String styleKey = ((font != null) ? font.toString() : "") + "_" + hAlign + "_" + vAlign + (wrapText ? "_wrapText" : "") + ((border != null) ? "_" + border.toString() : "") + "_"
                + styleKeyColor + (cellFormat != null ? "_" + cellFormat.toString() : "");
    
        if (styleMap.containsKey(styleKey)) {
            //return existing instance from map
            return styleMap.get(styleKey);
        } else {
            //create new style from workbook
            CellStyle cellStyle = workbook.createCellStyle();
    
    
            // set all formattings to new cellStyle object
            if (font != null) {
                cellStyle.setFont(font);
            }
    
            // alignment
            if (vAlign != null) {
                cellStyle.setVerticalAlignment(vAlign.getAlign());
            }
    
            //... snip ...
    
            //border
            if (border != null) {
                if (border.getTop() > BorderFormatting.BORDER_NONE) {
                    cellStyle.setBorderTop(border.getTop());
                    cellStyle.setTopBorderColor(HSSFColor.BLACK.index);
                }
    
                //... snip ...
            }
    
                if (color != null) {
                    XSSFColor xssfColor = new XSSFColor(color);
                   ((XSSFCellStyle)cellStyle).setFillForegroundColor(xssfColor);
                }
            }
            cellStyle.setFillPattern(CellStyle.SOLID_FOREGROUND);
    
            styleMap.put(styleKey, cellStyle);
    
            return cellStyle;
        }
    }
    

    参数ExcelCellAlign是一个简单的枚举,它封装了CellStyle.ALIGN_LEFT,CellStyle.ALIGN_RIGHT,...的值.ExcelCellBorder与Align类似 . 只需隐藏值:-) ExcelCellFormat是一个枚举,它包含用于强化值的默认模式 .

    我希望这对你自己的实施来说是个好的开始 . 如果有什么不清楚,请随时询问

  • 1

    编辑:

    那么如何利用POI对象的散列进行缓存并跟踪装饰对象 . 另一个未使用的CellStyles将被垃圾收集丢弃 .

    这是我们的缓存:

    private Map<Integer, MyCellStyle> styleCache = new HashMap<>();
    

    还有我们自己的CellStyle类

    final class MyCellStyle implements Cloneable {
        private XSSFCellStyle xssfCellStyle;
    
        public MyCellStyle(XSSFCellStyle xssfCellStyle) {
            this.xssfCellStyle = xssfCellStyle;
        }
    
        @Override
        public MyCellStyle clone() {
            MyCellStyle clone = new MyCellStyle(xssfCellStyle);
            return clone;
        }
    
        public final MyCellStyle borderLeftMedium() {
            MyCellStyle result = clone();
            result.xssfCellStyle.setBorderLeft(XSSFCellStyle.BORDER_MEDIUM);
            return result;
        }
    
        ... further decorations
    
        public XSSFCellStyle getXSSFCellStyle() {
            return xssfCellStyle;
        }
    
    }
    

    现在为了避免创建新对象,我们编写了一个小函数

    private MyCellStyle getCellStyle(MyCellStyle targetStyle) {
        int targetHash = targetStyle.hashCode();
        if (styleCache.keySet().contains(targetHash)) {
            return styleCache.get(targetHash);
        } else {
            return styleCache.put(targetHash, targetStyle);
        }
    }
    

    然后我们可以像这样自己创建单元格:

    public void createCells() {
        Workbook wb = new XSSFWorkbook();
        Sheet sheet = wb.createSheet();
    
        Row row = sheet.createRow(1);
        Cell cell = row.createCell(1);
    
        MyCellStyle baseStyle = new MyCellStyle(
                (XSSFCellStyle) wb.createCellStyle());
    
        MyCellStyle decoratedStyle = getCellStyle(baseStyle.borderLeftMedium());
    
        cell.setCellStyle(decoratedStyle.getXSSFCellStyle());
    
    }
    

    如果hashCode对于MyCellStyle对象的相同属性不是唯一的,我们可能必须覆盖hashCode函数:

    @Override
    public int hashCode() {
        return hashValue;
    }
    

    并在每个装饰函数中添加样式值:

    public final MyCellStyle borderLeftMedium() {
            MyCellStyle result = clone();
            result.xssfCellStyle.setBorderLeft(XSSFCellStyle.BORDER_MEDIUM);
            hashValue += XSSFCellStyle.BORDER_MEDIUM; // simplified hash
            return result;
        }
    

    =======================

    原版的:

    我喜欢创建装饰方法,将单元格的某个方面添加到单元格样式中 . 所以首先你创建你的基本风格

    public final XSSFCellStyle cellStyleStringBase = wb.createCellStyle();
    

    并创建装饰器方法来创建某种样式

    public XSSFCellStyle addBorderLeft(XSSFCellStyle style) {
        XSSFCellStyle result = style.clone();
        result.setBorderLeft(XSSFCellStyle.BORDER_MEDIUM);
        return result;
    }
    

    现在,如果你想避免创建新对象,你仍然需要保留在自己的变量中的cellStyles,你将无法避免这种情况,但根据我的经验,如果你只是简单地装饰你的细胞,那么性能就足够了

    cell1.setCellStyle(addBorderLeft(cellStyleStringBase);
    cell2.setCellStyle(addBorderRight(addBorderRight(cellStyleStringBase));
    ...
    

    如果你用很多样式进行装饰,那么创建自己的CellStyle类是有意义的

    public final MyCellStyle implements Cloneable {
    
        private XSSFCellStyle xssfCellStyle;
    
        public MyCellStyle(XSSFCellStyle xssfCellStyle) {
             this.xssfCellStyle = xssfCellStyle;
        }
    
        @Override
        public MyCellStyle clone() {
            MyCellStyle clone = new MyCellStyle(this.xssfCellStyle);
            return clone;
        }
    
        public final MyCellStyle borderLeftMedium() {
            return this.clone().setBorderLeft(XSSFCellStyle.BORDER_MEDIUM);
        }
    
        public final MyCellStyle borderRightThick() {
            ...
    
    }
    

    然后,您可以以更易读的方式构建您的样式:

    MyCellStyle base = new MyCellStyle(cellStyleStringBase);    
    cell1.setCellStyle(base
        .addBorderLeftMedium()
        .addBorderRightThick()
        .addBorderBottomThin());
    

    未经测试,但我希望它有所帮助 .

相关问题