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

news2024/11/17 19:51:29

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

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

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

添加依赖

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

直接上代码

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

使用示例

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

最终结果展示如图

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/956661.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OceanBase 4.x改装:另一种全链路追踪的尝试

本文作者&#xff1a;夏克 OceanBase 社区文档贡献者&#xff0c;曾多次参与 OceanBase 技术征文比赛&#xff0c;获得优秀名次。从事金融行业核心系统设计开发工作多年&#xff0c;服务于某交易所子公司&#xff0c;现阶段负责国产数据库调研。 本文为 OceanBase 第七期技术征…

web端调用本地摄像头麦克风+WebRTC腾讯云,实现直播功能

目录 关于直播直播流程直播视频格式封装推流和拉流 获取摄像头和麦克风权限navigator.getUserMedia()MediaDevices.getUserMedia() WebRTC腾讯云快直播 关于直播 视频直播技术大全、直播架构、技术原理和实现思路方案整理 直播流程 视频采集端&#xff1a; 1、视频采集&#…

说说Flink双流join

分析&回答 Flink双流JOIN主要分为两大类 一类是基于原生State的Connect算子操作另一类是基于窗口的JOIN操作。其中基于窗口的JOIN可细分为window join和interval join两种。 基于原生State的Connect算子操作 实现原理&#xff1a;底层原理依赖Flink的State状态存储&…

深度学习推荐系统(三)NeuralCF及其在ml-1m电影数据集上的应用

深度学习推荐系统(三)NeuralCF及其在ml-1m电影数据集上的应用 在2016年&#xff0c; 随着微软的Deep Crossing&#xff0c; 谷歌的Wide&Deep以及FNN、PNN等一大批优秀的深度学习模型被提出&#xff0c; 推荐系统全面进入了深度学习时代&#xff0c; 时至今日&#xff0c; …

JavaDemo——使用LinkedHashMap实现简单的Cache

继承LinkedHashMap&#xff0c;合理使用构造方法public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)和protected boolean removeEldestEntry(Map.Entry<K,V> eldest)方法就可以自己构建简单的Cache了&#xff1b; LinkedHashMap构造方法…

【三等奖方案】小样本数据分类任务赛题「复兴15号」团队解题思路

第十届CCF大数据与计算智能大赛&#xff08;2022 CCF BDCI&#xff09;已圆满结束。大赛官方竞赛平台DataFountain&#xff08;简称DF平台&#xff09;将陆续释出各赛题获奖队伍的方案思路。 本方案为【小样本数据分类任务】赛题的三等奖获奖方案&#xff0c;赛题地址&#xff…

【暴力DP】2021 icpc上海 I

Problem - I - Codeforces 题意&#xff1a; 思路&#xff1a; 考虑暴力DP即可 设 dp[i][j][k]表示 前 i 个物品&#xff0c;已经翻倍了 j 次&#xff0c;A点数 - B点数为 k 的最大价值和 然后分为这6种决策分类讨论就好了 注意数组里不能有负数&#xff0c;要加个偏移量 P…

[PyTorch][chapter 54][Variational Auto-Encoder 实战]

前言&#xff1a; 这里主要实现&#xff1a; Variational Autoencoders (VAEs) 变分自动编码器 其训练效果如下 训练的过程中要注意调节forward 中的kle ,调参。 整个工程两个文件&#xff1a; vae.py main.py 目录&#xff1a; vae main 一 vae 文件名&#xff1a; vae…

明年起,企业数据将作为资产被纳入会计报表

数据&#xff0c;是数字化经济时代的生产要素&#xff0c;是企业重要的资产&#xff0c;是企业发展经营的重要依据。为了规范企业数据资源相关会计处理&#xff0c;强化相关会计信息披露&#xff0c;近日财政部制定印发了《企业数据资源相关会计处理暂行规定》&#xff08;以下…

RealSense D455启动教程

环境&#xff1a; ubuntu20.04 ros:noetic 视觉传感器&#xff1a;Intel RealSense D455 通过命令安装不成功后改为下面源码安装 1. 安装Intel RealSense SDK 2.0 1.1源码安装 1. 下载源码git clone https://github.com/IntelRealSense/librealsense cd librealsense…

OpenCV c++ 使用imshow显示灰色窗口

OpenCV使用imshow显示灰色窗口 原因是使用了system(‘pause’);函数&#xff0c;只需要将该函数去掉&#xff0c;使用opencv中的对应函数 waitKey(0) 即可实现同样效果。 system(“pause”); 改为&#xff1a; cv::waitKey(0); 显示效果&#xff1a;

【初识Git工具】Git工具的基本介绍

【初识Git工具】Git工具的基本介绍 一、什么是Git?1.1 Git简介1.2 Git和SVN区别1.3 常用的Git工具二、Git的起源三、Git的优点四、Git的架构五、Git的基本概念5.1 仓库(Repository)5.2 版本(Commit)5.3 分支(Branch)5.4 合并(Merge)5.5 标签(Tag)六、Git的基本使用命…

引用(个人学习笔记黑马学习)

1、引用的基本语法 #include <iostream> using namespace std;int main() {int a 10;//创建引用int& b a;cout << "a " << a << endl;cout << "b " << b << endl;b 100;cout << "a "…

大模型综述论文笔记6-15

这里写自定义目录标题 KeywordsBackgroud for LLMsTechnical Evolution of GPT-series ModelsResearch of OpenAI on LLMs can be roughly divided into the following stagesEarly ExplorationsCapacity LeapCapacity EnhancementThe Milestones of Language Models Resources…

Scala的特质trait与java的interface接口的区别,以及Scala特质的自身类型和依赖注入

1. Scala的特质trait与java接口的区别 Scala中的特质&#xff08;trait&#xff09;和Java中的接口&#xff08;interface&#xff09;在概念和使用上有一些区别&#xff1a; 默认实现&#xff1a;在Java中&#xff0c;接口只能定义方法的签名&#xff0c;而没有默认实现。而在…

Android基础之Activity生命周期(一)

Activity是Android四大组件之一、称为之首也恰如其分。 Activity直接翻译为中文叫活动。在Android系统中Activity就是我看到的一个完整的界面。 界面中看到的TextView(文字)、Button(按钮)、ImageView(图片)都是需要Activity来承载的。 总结一句话,Activity负责界面的呈…

面试官如何考察与CAP相关的理论?

在互联网技术面试中&#xff0c;考察分布式技术已经是面试的标配了。很多招聘信息中&#xff0c;你能发现&#xff0c;一线互联网公司在对候选人的要求中都有“分布式系统设计”这一关键词。无论你是程序员&#xff0c;还是架构师&#xff0c;都要掌握分布式系统设计。 案例背…

深度探索JavaScript中的原型链机制

&#x1f3c6;作者简介&#xff0c;黑夜开发者&#xff0c;全栈领域新星创作者✌&#xff0c;CSDN博客专家&#xff0c;阿里云社区专家博主&#xff0c;2023年6月csdn上海赛道top4。 &#x1f3c6;数年电商行业从业经验&#xff0c;历任核心研发工程师&#xff0c;项目技术负责…

手游排行前十名,手游排行榜2023前十名

今天为大家带来手游排行前十名&#xff0c;如今流行的手机游戏专注于在画面和游戏性方面为玩家提供更逼真、更流畅的游戏体验。在画面方面&#xff0c;手游开发商经常使用先进的游戏引擎和技术来提高游戏的图形质量和细节&#xff0c;以及增加游戏的动态照明和物理效果&#xf…

day-37 代码随想录算法训练营(19)贪心part06

738.单调递增的数字 思路&#xff1a;在给的数字中找到第一个开始递减的两个数字 &#xff1b; 将前一个数字减1 &#xff1b; 后面的数字全部变为最大值9 968.监控二叉树 思路&#xff1a;分三种状态&#xff1a;0无覆盖 1有监控 2有覆盖 分四种情况&#xff1a;1.两…