Search code examples
excelcheckboxapache-poixssfapache-poi-4

Creating a checkbox in XLSX using Apache POI (Java)


I need to create an Excel checkbox in an XSSFSheet, but I have found no obvious classes/methods for doing so in the Java Apache POI library (4.0.1), nor any examples. Any suggestions?


Solution

  • There are two types of controls possible in Microsoft Excel. There are the legacy form controls and ActiveX controls. Creating legacy form controls would be possible. ActiveX controls wold be much more complex.

    The legacy form controls are stored in VML drawings per sheet. Apache poi has a XSSFVMLDrawing class already because cell comments also are partially stored in VML drawings. But as often in apache poi the class is not complete and the accessibility of it's methods is set so that the class cannot easy extended. Seems as if apache poi developers often explicitly wants to prevent extension of their classes.

    For legacy Checkbox controls we need a checkbox shape type and then checkbox shapes in this VML drawing.

    Following code creates two legacy checkboxes.

    The method XSSFVMLDrawing getVMLDrawing(XSSFSheet sheet) gets the VML drawing from the sheet or creates a new one if not already present.

    The method void addCheckboxShapetype(XSSFVMLDrawing drawing) adds a new checkbox shape type to the drawing.

    The method void addCheckbox(XSSFVMLDrawing drawing, int col1, int dx1, int row1, int dy1, int col2, int dx2, int row2, int dy2, String label, boolean checked) adds a new checkbox shape to the drawing having the given position and label and has a Checked element which contains either 1 when checked or 0 when not checked.

    Complete example:

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

    Please note, for above solution the full jar of all of the schemas ooxml-schemas-1.4.jar (lower versions for older releases) is needed as mentioned in FAQ N10025.


    Note: This will work up to apache poi 4.1.2 but not if apache poi 5.0.0 is used. Reason: Apache POI has clanged code of XSSFVMLDrawing so it does not have fields _items and _qnames anymore. But it also not provides methods to add new items except comments. So the apache poi developers castrates XSSFVMLDrawing to show that it is unwilling to use it for anything other than comments.