言成言成啊 | Kit Chen's Blog

Poi实现Excel多级联动下拉框

发布于2023-08-22 23:48:54,更新于2023-08-27 01:09:47,标签:java excel  转载随意,文章会持续修订,请注明来源地址:https://meethigher.top/blog

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

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

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

添加依赖

1
2
3
4
5
6
7
8
9
10
11
<!--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>

直接上代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
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;
}
}
}

使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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"));
}
}

最终结果展示如图

发布:2023-08-22 23:48:54
修改:2023-08-27 01:09:47
链接:https://meethigher.top/blog/2023/excel-dropdownlist/
标签:java excel 
付款码 打赏 分享
shift+ctrl+1可控制目录显示