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

时间:2016-05-11 07:34:23

标签: java apache-poi

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

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
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...

是否有更优雅的方式来设置列的边框,而不管现有的样式?

修改

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

3 个答案:

答案 0 :(得分: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 :(得分:1)

编辑:

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

这是我们的缓存:

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;
    }

}

我们自己的CellStyle类

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

}

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

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

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

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

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

ORIGINAL:

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

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

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

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

现在,如果你想避免创建新对象,你仍然必须将cellStyles保存在自己的变量中,你将无法避免这种情况,但根据我的经验,如果你只是装饰你的单元格,性能就足够了像这样

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() {
        ...

}

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

是有意义的
MyCellStyle base = new MyCellStyle(cellStyleStringBase);    
cell1.setCellStyle(base
    .addBorderLeftMedium()
    .addBorderRightThick()
    .addBorderBottomThin());
然后,您可以以更易读的方式构建自己的风格:

    NSString *formatedString=[NSString stringWithFormat:@"%@",yourValueFromDatabase];

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

答案 2 :(得分:1)

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

#!r6rs
(import (rnrs)
        (only (srfi :1) fold)
        (only (srfi :13) string-fold-right))

(define compose
  (let* ((apply-1
          (lambda (proc value)
            (proc value)))
         (gen
          (lambda (procs)
            (let ((initial (car procs))
                  (additional (cdr procs)))
              (lambda args
                (fold apply-1
                      (apply initial args)
                      additional))))))
    (lambda procs
      (cond ((null? procs) values)
            ((null? (cdr procs)) (car procs))
            (else (gen (reverse procs)))))))

(define (cxr receipt)
  (define (add-proc char acc)
    (cons (if (eqv? char #\a) car cdr) acc))

  (apply compose
         (string-fold-right add-proc
                            '()
                            receipt)))

;; test
(define test '(1 2 3 4 5 6))
(define my-caddr (cxr "add"))
(define compose-caddr (compose car cdr cdr))

(caddr test)         ; ==> 3
(my-caddr test)      ; ==> 3
(compose-caddr test) ; ==> 3

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

我希望这是您自己实施的良好开端。随意询问是否有不清楚的事情