Apache Poi 实现Excel多级联动下拉框

由于最近做的功能,需要将接口返回的数据列表,输出到excel中,以供后续导入,且网上现有的封装,使用起来都较为麻烦,故参考已有做法封装了工具类。

使用apache poi实现excel联动下拉框思路

  1. 创建隐藏单元格,存储下拉数据
  2. 创建名称管理器
  3. 使用indirect表达式进行联动

添加依赖

xml 复制代码
<!--Java程序对Microsoft Office格式档案读和写的功能-->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>5.2.2</version>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>5.2.2</version>
</dependency>

直接上代码

java 复制代码
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.apache.poi.xssf.usermodel.*;

import java.util.List;
import java.util.Map;
import java.util.Set;


/**
 * excel验证工具类
 *
 * @author chenchuancheng github.com/meethigher
 * @since 2023/08/20 23:55
 */
public class ExcelValidationUtils {


    private static final int minRow = 1;

    private static final int maxRow = 100;

    private static final boolean debugHideSheet = true;


    /**
     * 创建一个xlsx
     *
     * @return {@link XSSFWorkbook}
     */
    public static XSSFWorkbook createOneXLSX() {
        return new XSSFWorkbook();
    }

    /**
     * 为xlsx添加一个sheet
     *
     * @param wb        xlsx
     * @param sheetName sheet名
     * @param headers   首行标题头
     * @return sheet
     */
    public static XSSFSheet addOneSheet(XSSFWorkbook wb, String sheetName, String[] headers) {
        XSSFSheet st = wb.createSheet(sheetName);
        //表头样式
        CellStyle style = wb.createCellStyle();
        style.setAlignment(HorizontalAlignment.CENTER); // 创建一个居中格式
        //字体样式
        Font fontStyle = wb.createFont();
        fontStyle.setFontName("微软雅黑");
        fontStyle.setFontHeightInPoints((short) 12);
        style.setFont(fontStyle);
        //单元格格式为文本
        XSSFDataFormat format = wb.createDataFormat();
        style.setDataFormat(format.getFormat("@"));
        //写标题
        XSSFRow row = st.createRow(0);
        st.createFreezePane(0, 1, 0, 1);
        for (int i = 0; i < headers.length; i++) {
            String value = headers[i];
            XSSFCell cell = row.createCell(i);
            st.setColumnWidth(i, value.length() * 1000);
            cell.setCellStyle(style);
            st.setDefaultColumnStyle(i, style);
            cell.setCellValue(value);
        }
        return st;
    }


    /**
     * 添加两层级联数据
     *
     * @param wb                  xlsx
     * @param targetSheet         目标sheet
     * @param linkageData         两层级联数据
     * @param parentCol           父列
     * @param childCol            孩子列
     * @param parentColIdentifier 父列标识符
     * @return {@link XSSFSheet}
     */
    public static XSSFSheet addLinkageDataValidation(XSSFWorkbook wb, XSSFSheet targetSheet, Map<String, List<String>> linkageData,
                                                     int parentCol, int childCol, String parentColIdentifier) {
        XSSFSheet hideSt = wb.createSheet();
        wb.setSheetHidden(wb.getSheetIndex(hideSt), !debugHideSheet);
        int rowId = 0;
        Set<String> keySet = linkageData.keySet();
        for (String parent : keySet) {
            List<String> sonList = linkageData.get(parent);
            XSSFRow row = hideSt.createRow(rowId++);
            row.createCell(0).setCellValue(parent);
            for (int i = 0; i < sonList.size(); i++) {
                XSSFCell cell = row.createCell(i + 1);
                cell.setCellValue(sonList.get(i));
            }
            // 添加名称管理器,1表示b列,从b列开始往后,都是子级
            String range = getRange(1, rowId, sonList.size());
            Name name = wb.createName();
            name.setNameName(parent);
            String formula = hideSt.getSheetName() + "!" + range;
            name.setRefersToFormula(formula);
        }
        //创建表达式校验
        XSSFDataValidationHelper helper = new XSSFDataValidationHelper(targetSheet);

//        //父级校验,如需生成更多,用户手动拖拽下拉即可。此操作会导致数组内容总长度超过255时报错
//        DataValidation parentValidation = helper.createValidation(helper.createExplicitListConstraint(keySet.toArray(new String[0])),
//                new CellRangeAddressList(minRow, maxRow, parentCol, parentCol));
//        parentValidation.createErrorBox("错误", "请选择正确的父级类型");
//        parentValidation.setShowErrorBox(true);
//        parentValidation.setSuppressDropDownArrow(true);
//        targetSheet.addValidationData(parentValidation);

        //解决长度为255的问题
        Name name = wb.createName();
        name.setNameName(hideSt.getSheetName());
        name.setRefersToFormula(hideSt.getSheetName() + "!$A$1:$A$" + keySet.size());
        DataValidation parentValidation = helper.createValidation(helper.createFormulaListConstraint(hideSt.getSheetName()), new CellRangeAddressList(minRow, maxRow, parentCol, parentCol));
        parentValidation.createErrorBox("错误", "请选择正确的父级类型");
        parentValidation.setShowErrorBox(true);
        targetSheet.addValidationData(parentValidation);

        //子级校验,如需生成更多,用户手动拖拽下拉即可
        for (int i = minRow; i < maxRow; i++) {
            DataValidation childValidation = helper.createValidation(helper.createFormulaListConstraint("INDIRECT(" + parentColIdentifier + "" + (i + 1) + ")"),
                    new CellRangeAddressList(i, i, childCol, childCol));
            childValidation.createErrorBox("错误", "请选择正确的子级类型");
            childValidation.setShowErrorBox(true);
            childValidation.setSuppressDropDownArrow(true);
            targetSheet.addValidationData(childValidation);
        }

        return hideSt;
    }

    /**
     * 添加简单下拉列表验证-下拉列表总内容不超过255字符
     *
     * @param st           sheet
     * @param dropDownList 下拉列表数据
     * @param firstCol     开始列,从0开始
     * @param lastCol      结束列,从0开始
     */
    public static void addSimpleDropDownListValidation(XSSFSheet st, String[] dropDownList, int firstCol, int lastCol) {
        XSSFDataValidationHelper helper = new XSSFDataValidationHelper(st);
        XSSFDataValidationConstraint constraint = (XSSFDataValidationConstraint) helper.createExplicitListConstraint(dropDownList);
        CellRangeAddressList addressList = new CellRangeAddressList(minRow, maxRow, firstCol, lastCol);
        XSSFDataValidation validation = (XSSFDataValidation) helper.createValidation(constraint, addressList);
        validation.setSuppressDropDownArrow(true);
        validation.setShowErrorBox(true);
        st.addValidationData(validation);
    }


    /**
     * 添加复杂下拉列表验证-下拉列表总内容允许超过255字符
     *
     * @param wb           xlsx
     * @param dropDownList 下拉列表数据
     * @param firstCol     开始列,从0开始
     * @param lastCol      结束列,从0开始
     */
    public static void addComplexDropDownListValidation(XSSFWorkbook wb, XSSFSheet st, String[] dropDownList, int firstCol, int lastCol) {
        XSSFSheet hideSt = wb.createSheet();
        wb.setSheetHidden(wb.getSheetIndex(hideSt), !debugHideSheet);
        XSSFDataValidationHelper helper = new XSSFDataValidationHelper(st);
        for (int i = 0, length = dropDownList.length; i < length; i++) {
            String value = dropDownList[i];
            XSSFRow row = hideSt.createRow(i);
            XSSFCell cell = row.createCell(0);
            cell.setCellValue(value);
        }
        //解决长度为255的问题
        Name name = wb.createName();
        name.setNameName(hideSt.getSheetName());
        name.setRefersToFormula(hideSt.getSheetName() + "!$A$1:$A$" + dropDownList.length);
        DataValidation parentValidation = helper.createValidation(helper.createFormulaListConstraint(hideSt.getSheetName()), new CellRangeAddressList(minRow, maxRow, firstCol, lastCol));
        parentValidation.createErrorBox("错误", "请选择正确的类型");
        parentValidation.setShowErrorBox(true);
        st.addValidationData(parentValidation);
    }


    /**
     * 计算formula
     *
     * @param offset   偏移量,如果给0,表示从A列开始,1,就是从B列
     * @param rowId    第几行
     * @param colCount 一共多少列
     * @return 如果给入参 1,1,10. 表示从B1-K1。最终返回 $B$1:$K$1
     */
    private static String getRange(int offset, int rowId, int colCount) {
        char start = (char) ('A' + offset);
        if (colCount <= 25) {
            char end = (char) (start + colCount - 1);
            return "$" + start + "$" + rowId + ":$" + end + "$" + rowId;
        } else {
            char endPrefix = 'A', endSuffix;
            if ((colCount - 25) / 26 == 0 || colCount == 51) {// 26-51之间,包括边界(仅两次字母表计算)
                if ((colCount - 25) % 26 == 0) {// 边界值
                    endSuffix = (char) ('A' + 25);
                } else {
                    endSuffix = (char) ('A' + (colCount - 25) % 26 - 1);
                }
            } else {// 51以上
                if ((colCount - 25) % 26 == 0) {
                    endSuffix = (char) ('A' + 25);
                    endPrefix = (char) (endPrefix + (colCount - 25) / 26 - 1);
                } else {
                    endSuffix = (char) ('A' + (colCount - 25) % 26 - 1);
                    endPrefix = (char) (endPrefix + (colCount - 25) / 26);
                }
            }
            return "$" + start + "$" + rowId + ":$" + endPrefix + endSuffix + "$" + rowId;
        }
    }
}

使用示例

java 复制代码
public class TestExportExcelWithValidation {


    private final static String[] headers = new String[]{
            "性别",
            "省",
            "市",
            "区",
    };


    private static Map<String, List<String>> 省级() {
        Map<String, List<String>> map = new HashMap<>();
        map.put("湖北省", Arrays.asList("武汉市", "襄阳市"));
        map.put("吉林省", Arrays.asList("长春市", "吉林市"));
        return map;
    }

    private static Map<String, List<String>> 市级() {
        Map<String, List<String>> map = new HashMap<>();
        map.put("武汉市", Arrays.asList("洪山区", "江夏区"));
        map.put("长春市", Arrays.asList("宽城区", "南关区"));
        return map;
    }

    public static void main(String[] args) throws Exception {
        XSSFWorkbook wb = createOneXLSX();
        XSSFSheet st = addOneSheet(wb, "data", headers);
        addSimpleDropDownListValidation(st, new String[]{"男", "女"}, 0, 0);
        addLinkageDataValidation(wb, st, 省级(), 1, 2, "B");
        addLinkageDataValidation(wb, st, 市级(), 2, 3, "C");


        wb.write(new FileOutputStream("aaa.xlsx"));
    }
}

最终结果展示如图

idation(st, new String[]{"男", "女"}, 0, 0);

addLinkageDataValidation(wb, st, 省级(), 1, 2, "B");

addLinkageDataValidation(wb, st, 市级(), 2, 3, "C");

    wb.write(new FileOutputStream("aaa.xlsx"));
}

}

最终结果展示如图

[外链图片转存中...(img-zQCvRcAm-1693070160923)]
相关推荐
ruleslol1 小时前
VBA02-初识宏——EXCEL录像机
excel·vba
IT铺子1 小时前
制定Excel使用规范和指导,提升数据处理的效率和准确性,减少错误和数据丢失的风险
excel
是萝卜干呀1 小时前
Backend - Python 爬取网页数据并保存在Excel文件中
python·excel·table·xlwt·爬取网页数据
Aloudata2 小时前
从Apache Atlas到Aloudata BIG,数据血缘解析有何改变?
大数据·apache·数据血缘·主动元数据·数据链路
神奇夜光杯7 小时前
Python酷库之旅-第三方库Pandas(202)
开发语言·人工智能·python·excel·pandas·标准库及第三方库·学习与成长
小c君tt8 小时前
MFC中Excel的导入以及使用步骤
c++·excel·mfc
小钱c79 小时前
Mac下安装Apache JMeter并启动
jmeter·macos·apache
一名技术极客10 小时前
Vue2 doc、excel、pdf、ppt、txt、图片以及视频等在线预览
pdf·powerpoint·excel·文件在线预览
用余生去守护10 小时前
【反射率】-- Lab 转换(excel)
excel
进击的六角龙10 小时前
Python中处理Excel的基本概念(如工作簿、工作表等)
开发语言·python·excel