【问题标题】:Creating a checkbox in XLSX using Apache POI (Java)使用 Apache POI (Java) 在 XLSX 中创建复选框
【发布时间】:2020-03-03 03:07:20
【问题描述】:

我需要在 XSSFSheet 中创建一个 Excel 复选框,但我在 Java Apache POI 库 (4.0.1) 中没有找到明显的类/方法,也没有任何示例。有什么建议吗?

【问题讨论】:

  • 目前还不支持。有什么建议?使用ExcelGUI 创建您想要的内容并将其保存到*.xlsx 文件中。然后解压缩*.xlsx 文件(它是一个ZIP 存档,其中包含XML 文件)并查看需要创建什么XML。现在您知道需要创建什么 XML 并且也可以使用 Java 来执行此操作。

标签: excel checkbox apache-poi xssf apache-poi-4


【解决方案1】:

Microsoft Excel 中有两种可能的控件。有旧版表单控件和ActiveX 控件。创建遗留表单控件是可能的。 ActiveX 控件会复杂得多。

旧版表单控件存储在每个工作表的 VML 工程图中。 Apache poi 已经有一个 XSSFVMLDrawing 类,因为单元 cmets 也部分存储在 VML 图形中。但在apache poi 中,该类通常不完整,并且设置了其方法的可访问性,因此该类无法轻松扩展。似乎apache poi 开发人员经常明确地想要阻止扩展他们的类。

对于旧版Checkbox 控件,我们需要一个复选框形状类型,然后在此 VML 绘图中使用复选框形状。

以下代码创建两个旧复选框。​​

XSSFVMLDrawing getVMLDrawing(XSSFSheet sheet) 方法从图纸中获取 VML 图纸,如果还没有,则创建一个新图纸。

void addCheckboxShapetype(XSSFVMLDrawing drawing) 方法将新的复选框形状类型添加到绘图中。

void addCheckbox(XSSFVMLDrawing drawing, int col1, int dx1, int row1, int dy1, int col2, int dx2, int row2, int dy2, String label, boolean checked) 方法将一个新的复选框形状添加到具有给定位置和标签的绘图中,并具有一个 Checked 元素,该元素在选中时包含 1,在未选中时包含 0。

完整示例:

import java.io.*;

import org.apache.poi.openxml4j.opc.*;
import org.apache.poi.ooxml.POIXMLDocumentPart;
import org.apache.xmlbeans.*;

import org.apache.poi.xssf.usermodel.*;

import com.microsoft.schemas.vml.*;
import com.microsoft.schemas.office.excel.CTClientData;

import java.lang.reflect.Field;
import javax.xml.namespace.QName;

import java.util.List;

class CreateExcelLegacyDrawingControls {

 private static XSSFVMLDrawing getVMLDrawing(XSSFSheet sheet) throws Exception {
  XSSFVMLDrawing drawing = null;
  if (sheet.getCTWorksheet().getLegacyDrawing() != null) { 
   String legacyDrawingId = sheet.getCTWorksheet().getLegacyDrawing().getId();
   drawing = (XSSFVMLDrawing)sheet.getRelationById(legacyDrawingId);
  } else {
   int drawingNumber = sheet.getPackagePart().getPackage()
    .getPartsByContentType(XSSFRelation.VML_DRAWINGS.getContentType()).size() + 1;
   POIXMLDocumentPart.RelationPart rp = 
    sheet.createRelationship(XSSFRelation.VML_DRAWINGS, XSSFFactory.getInstance(), drawingNumber, false);
   drawing = rp.getDocumentPart();
   String rId = rp.getRelationship().getId();
   sheet.getCTWorksheet().addNewLegacyDrawing().setId(rId);
  }
  return drawing;
 }

 private static void addCheckboxShapetype(XSSFVMLDrawing drawing) throws Exception {
  String shapeTypeId = "_x0000_t201";
  CTShapetype shapetype = CTShapetype.Factory.newInstance();
  shapetype.setId(shapeTypeId);
  shapetype.setCoordsize("21600,21600");
  shapetype.setSpt(201);
  shapetype.setPath2("m,l,21600r21600,l21600,xe");

  Field _items = XSSFVMLDrawing.class.getDeclaredField("_items");
  _items.setAccessible(true);
  @SuppressWarnings("unchecked") //we know the problem and expect runtime error if it possibly occurs
  List<XmlObject> items = (List<XmlObject>)_items.get(drawing);

  Field _qnames = XSSFVMLDrawing.class.getDeclaredField("_qnames");
  _qnames.setAccessible(true);
  @SuppressWarnings("unchecked") //we know the problem and expect runtime error if it possibly occurs
  List<QName> qnames = (List<QName>)_qnames.get(drawing);

  items.add(shapetype);
  qnames.add(new QName("urn:schemas-microsoft-com:vml", "shapetype"));
 }

 private static void addCheckbox(XSSFVMLDrawing drawing,
  int col1, int dx1, int row1, int dy1, int col2, int dx2, int row2, int dy2, 
  String label, boolean checked) throws Exception {

  String shapeTypeId = "_x0000_t201";

  Field _shapeId = XSSFVMLDrawing.class.getDeclaredField("_shapeId");
  _shapeId.setAccessible(true);
  int shapeId = (int)_shapeId.get(drawing); 
  _shapeId.set(drawing, shapeId + 1);

  CTShape shape = CTShape.Factory.newInstance();
  shape.setId("_x0000_s" + shapeId);
  shape.setType("#" + shapeTypeId);
  shape.setFilled(com.microsoft.schemas.vml.STTrueFalse.F);
  shape.setStroked(com.microsoft.schemas.vml.STTrueFalse.F);
  String textboxHTML = 
   "<div style='text-align:left'>"
  +"<font face=\"Tahoma\" size=\"160\" color=\"auto\">" + label + "</font>"
  +"</div>";
  CTTextbox[] textboxArray = new CTTextbox[1];
  textboxArray[0] = CTTextbox.Factory.parse(textboxHTML);
  textboxArray[0].setStyle("mso-direction-alt:auto");
  textboxArray[0].setSingleclick(com.microsoft.schemas.office.office.STTrueFalse.F);
  shape.setTextboxArray(textboxArray);
  CTClientData cldata = shape.addNewClientData();
  cldata.setObjectType(com.microsoft.schemas.office.excel.STObjectType.CHECKBOX);
  cldata.addNewMoveWithCells();
  cldata.addNewSizeWithCells();
  cldata.addNewAnchor().setStringValue(
   "" + col1 + ", " + dx1 + ", " + row1 + ", " +dy1 + ", " + col2 + ", " + dx2 + ", " + row2 + ", " + dy2
  );
  cldata.addAutoFill(com.microsoft.schemas.office.excel.STTrueFalseBlank.FALSE);
  cldata.addAutoLine(com.microsoft.schemas.office.excel.STTrueFalseBlank.FALSE);
  cldata.addTextVAlign("Center");
  cldata.addNoThreeD(com.microsoft.schemas.office.excel.STTrueFalseBlank.TRUE);

  cldata.addChecked((checked)?java.math.BigInteger.valueOf(1):java.math.BigInteger.valueOf(0));

  Field _items = XSSFVMLDrawing.class.getDeclaredField("_items");
  _items.setAccessible(true);
  @SuppressWarnings("unchecked") //we know the problem and expect runtime error if it possibly occurs
  List<XmlObject> items = (List<XmlObject>)_items.get(drawing);

  Field _qnames = XSSFVMLDrawing.class.getDeclaredField("_qnames");
  _qnames.setAccessible(true);
  @SuppressWarnings("unchecked") //we know the problem and expect runtime error if it possibly occurs
  List<QName> qnames = (List<QName>)_qnames.get(drawing);

  items.add(shape);
  qnames.add(new QName("urn:schemas-microsoft-com:vml", "shape"));

 }

 public static void main(String[] args) throws Exception {

  XSSFWorkbook workbook  = new XSSFWorkbook();

  //following is necessary to be textboxHTML of the CTShape compatible with Excel 2007.
  //<fileVersion appName="xl" lastEdited="4" lowestEdited="0" rupBuild="4507"/>
  workbook.getCTWorkbook().addNewFileVersion().setAppName("xl");
  workbook.getCTWorkbook().getFileVersion().setLastEdited("4");
  workbook.getCTWorkbook().getFileVersion().setLowestEdited("0");
  workbook.getCTWorkbook().getFileVersion().setRupBuild("4507");

  XSSFSheet sheet = workbook.createSheet();
  XSSFCell cell = sheet.createRow(5).createCell(5);
/*
  XSSFDrawing drawing = sheet.createDrawingPatriarch();
  XSSFClientAnchor anchor = workbook.getCreationHelper().createClientAnchor();
  anchor.setCol1(cell.getColumnIndex());
  anchor.setCol2(cell.getColumnIndex()+1);
  anchor.setRow1(cell.getRow().getRowNum());
  anchor.setRow2(cell.getRow().getRowNum()+3);
  XSSFComment comment = drawing.createCellComment(anchor);
  XSSFRichTextString str = workbook.getCreationHelper().createRichTextString("Hello, World!");
  comment.setString(str);
  comment.setAuthor("Apache POI");
  cell.setCellComment(comment);
*/
  XSSFVMLDrawing vmlDrawing = getVMLDrawing(sheet);
  addCheckboxShapetype(vmlDrawing);
  addCheckbox(vmlDrawing, 1, 0, 1, 0, 3, 0, 2, 0, "Checkbox 1", true);
  addCheckbox(vmlDrawing, 1, 0, 2, 0, 3, 0, 3, 0, "Checkbox 2", false);

  FileOutputStream out = new FileOutputStream("Excel.xlsx");
  workbook.write(out);
  out.close();
  workbook.close();

 }

}

请注意,对于上述解决方案,需要 FAQ N10025 中提到的所有架构 ooxml-schemas-1.4.jar(旧版本的较低版本)的完整 jar。


注意:这将适用于 apache poi 4.1.2,但如果使用 apache poi 5.0.0,则不会。原因:Apache POI 的代码为 XSSFVMLDrawing,因此它不再具有字段 _items_qnames。但是除了 cmets 之外,也没有提供添加新项目的方法。

【讨论】:

  • 如何以编程方式将其标记为选中/未选中?
  • @silverberry:对于复选框,CTClientData 提供了一个包含 1 或 0 的元素 Checked。我已经更新了我的示例。
  • 谢谢。我看到了 addChecked 方法,但不确定它是不是这个方法,因为它以 BigInteger 作为参数。如果只有 0 或 1,为什么要使用 BigInteger?
  • 另外,com.microsoft.schemas.office.office 似乎没有 STTrueFalse。
  • @silverberry:我不会评论apache poi 关于变量类型的决定。对于此解决方案,需要 FAQ N10025 中提到的所有模式 ooxml-schemas-1.4.jar(旧版本的较低版本)的完整 jar。
猜你喜欢
  • 1970-01-01
  • 2013-05-10
  • 2015-07-25
  • 1970-01-01
  • 2013-01-10
  • 2018-01-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多