Java ApachePOI:为包含不同样式的列设置边框的优雅方式

Java ApachePOI:为包含不同样式的列设置边框的优雅方式,java,apache-poi,Java,Apache Poi,我正在使用ApachePOI动态创建一个新的xlsx文件。任何列都可以包含不同的值类型数字、字符串、布尔值等。。。。将数据插入poi文档时,我根据数据类型设置单元格样式: public final XSSFCellStyle cellStyleString; public final XSSFCellStyle cellStyleNumber; public final XSSFCellStyle cellStyleDate; public final XSSFCellStyle cellSty

我正在使用ApachePOI动态创建一个新的xlsx文件。任何列都可以包含不同的值类型数字、字符串、布尔值等。。。。将数据插入poi文档时,我根据数据类型设置单元格样式:

public final XSSFCellStyle cellStyleString;
public final XSSFCellStyle cellStyleNumber;
public final XSSFCellStyle cellStyleDate;
public final XSSFCellStyle cellStyleHeader;
这是我的标题行的外观:

|   |   |   | Shared Header |
| H1| H2| H3|SH1|SH2|SH3|SH4|
有简单标题和包含子标题的共享标题。共享标头驻留在合并的单元格中

不,我希望在SH1列有一个左边框,在SH4列有一个右边框,以强调分组。但由于任何列都可能包含所有cellstyles的混合,因此我似乎必须创建如下cellstyles

public final XSSFCellStyle cellStyleString;
public final XSSFCellStyle cellStyleStringBorderLeft;
public final XSSFCellStyle cellStyleStringBorderRight;
//and so on for the other styles...
此外,可能有嵌套的共享头,我想区分不同的边界大小。所以我需要像这样的东西

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...
是否有一种更优雅的方式来设置柱的边框,而不考虑已存在的样式

编辑

虽然我更喜欢干净简单的方法,也更喜欢减少所创建样式的数量,但我还是坚持使用删除重复单元格样式的方法。我不知道那门课。尽管我更喜欢避免使用此实用程序,但它符合问题,值得在此提及。

编辑:

那么,如何利用POI对象的散列来缓存和跟踪装饰对象呢。其他创建的未使用的CellStyles将被垃圾收集丢弃

这是我们的缓存:

private Map<Integer, MyCellStyle> styleCache = new HashMap<>();
现在为了避免创建新对象,我们编写了一个小函数

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());

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

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

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是一个枚举,它保存用于对值进行FORMATT的默认模式


我希望这是您自己实现的一个良好开端。请随时询问是否有不清楚的地方

我即将完成对POI的增强,该增强将允许您使用特定样式填写值,然后在其周围绘制边框,而无需手动创建所有必要的样式。同时,有一种方法可以使用CellUtil.setCellStyleProperties来实现。这使您可以向已存在的单元格样式添加一组属性

从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);
  }
}
这允许您基本上填写电子表格,然后一次绘制一个单元格的边框。请注意,如果您的所有边框都是类似的,那么这将适用于您的整个范围。但是,如果要在表的外部绘制中间边框,则必须创建一些其他特性集。注意,对于电子表格中已有的行和单元格,不必使用createRow和createCell。这将适用于合并的单元格

注意:CellUtil.setCellStyleProperties出现在POI 3.14中,允许您在一个快照中添加多个单元格属性,从而避免创建多个未使用的样式。较旧的CellUtil.setCellStyleProperty一次设置一个属性,并意外地在电子表格中创建中间CellStyle对象,但这些对象从未被使用过。这在较大的图纸中可能是一个问题

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

如果我要为每个带边框的单元格创建新样式,我最终会得到成千上万个冗余单元格样式。由于许多原因,这是不可接受的。谢谢您的编辑。但是
由于它仍然依赖于为每个装饰创建新的样式,因此此解决方案也不可接受。这将创建许多、许多、许多相同CellStyle的实例,并将增加excel文件的大小。缓存似乎确实是答案。尽管如此,我还是不喜欢手工创建独特的散列。cellstyles的领域似乎相当大,所以int可能是确保唯一性的小方法。我将采用yours和@Christoph Tobias Schenke的想法,为map创建一个自定义的key对象,它将在内部覆盖hash和Equals。在找到这个主题之前,我刚刚创建了类似的东西。我已经创建了一个CellStyleBuilder类生成器模式,它可以以可读的方式创建单元格样式。它会在地图中缓存已经创建的单元格样式。映射的键是一个简单的字符串,其中包含单元格样式的所有属性以及分隔值的分隔符。这对我来说非常有效,创建单元格样式变得更加容易。这个解决方案听起来很棒。可悲的是,我现在无法测试它,因为我不得不转移到另一个任务。。。CellStyles会以这种方式重复使用吗?换言之:将为每个遇到的单元格或每个遇到的单元格样式创建新的单元格样式吗?我刚刚阅读了我们的依赖关系层次结构:我们仍然使用poi 3.9。但我认为使用旧方法setCellStyleProperty创建未使用的中间样式是可以接受的。总共只有很少的样式,唯一的修改是每个单元格最多有2个边框。拥有30%的未使用样式仍然比拥有>30.000个伪唯一样式要好。当我阅读setCellStyleProperty的源代码并找到重用已知样式的部分时,我前面的问题已经过时了。谢谢!使用setCellStyleProperty时会发生的情况是,它从单元格中检索CellStyle,创建合并了新属性的模拟CellStyle,并尝试在工作簿中查找等效的CellStyle。如果不存在新的单元样式,将创建一个新的单元样式并应用于该单元。setCellStyleProperties允许使用一整组属性而不是一次一组的方法来实现这一点。如果您正在创建垂直边框,您将得到一堆未使用的样式,这些样式只有一个左边框,或者只有一个右边框,具体取决于添加边框的顺序。您还可以使用它设置其他单元格样式属性,如填充。
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);
  }
}