用poi在excel里画日历

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/redsoft_mymuch/article/details/52947101

  众所周知,Java里读写excel的开源包里poi是最知名的,多年前就已经能支持xlsx格式的excel了,并且对于word等office组件支持。如果项目中要开发类似excel的读写,poi可以说是一个相当不错的开源包。
  最近闲来无事,用swing为界面,poi为组件,作成一个简单的输入年份就可以往excel里生成这一年的日历。当然,如果你感兴趣,poi官网的日历sample也不错,可以借鉴一下。
  本文使用的poi是目前的最新版本3.15版。jdk为1.8.0.111版。如果要运行下面的代码需要把poi的jar包导入到本地工程。平时使用poi和swing的机会不是很多,具体代码的注释都已经标注了,有不明白的,可以留言交流。
  运行截图如下:
  这里写图片描述
  由于系统是日文系统,所以有些swing自带的jFile选择器就是日文的。
  可以说,用任何excel写入的jar包来画excel是一件非常庞大的工程,每一个值,每一个单元格的格式都要画出来还是很不容易的,特别是做日本项目,你懂的,日本的excel那是相当花哨。所以如果真正做项目时,不要死板,该用模板的地方还是要用模板,即先把一些固定格式的单元格放到excel里,然后读取excel进行拷贝格式或者直接使用。你只要把更多的时间放到数据绑定上即可。

package pers.redsoft.java.test.base;

import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Calendar;

import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;

import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.RegionUtil;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

/**
 * @author redsoft
 *
 */
public class CreateCalendarForXlsx {

    /**
     * excel的文件名
     */
    private final static String FILENAME = "CalendarForXlsx.xlsx";

    /**
     * excel的行index
     */
    private static int rows;

    /**
     * excel的book对象
     */
    private static Workbook wb;

    /**
     * 用于指定excel的A列到G列
     */
    private final static String AG = "ABCDEFG";

    /**
     * 用于输出title的周一到周日
     */
    private final static String[] WEEKEND = { "日", "一", "二", "三", "四", "五", "六" };

    /**
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {

        // ####################### 窗体设置 ##########################
        // 生成带title的JFrame窗体
        JFrame jf = new JFrame("excel中创建日历");
        // 设置叉按钮为退出动作
        jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        // 设置窗体的左边距,上边距,宽度,高度
        jf.setBounds(0, 0, 500, 100);
        // 设置窗体大小不可调整
        jf.setResizable(false);

        // ####################### 面板设置 ##########################
        // 设置JPanel面板
        JPanel jpanel = new JPanel();
        // 设置面板的布局为null
        jpanel.setLayout(null);
        // 将面板添加到窗体
        jf.add(jpanel);

        // ####################### 年份输入框 ##########################
        // 生成label标签
        JLabel jlabelyear = new JLabel("指定年份:");
        // 设置label标签的位置
        jlabelyear.setBounds(10, 10, 80, 25);
        // 将label标签添加到面板
        jpanel.add(jlabelyear);

        // 生成text控件用于输入年份
        JTextField jtext = new JTextField();
        // 设置默认内容为系统日期的年
        jtext.setText(String.valueOf(Calendar.getInstance().get(Calendar.YEAR)));
        // 对输入的内容进行键盘监听
        jtext.addKeyListener(new KeyListener() {

            @Override
            public void keyTyped(KeyEvent arg0) {
                // 如果输入的内容不为数字,删除键,回车键
                if (!Character.isDigit(arg0.getKeyChar()) && arg0.getKeyChar() != '\b' && arg0.getKeyChar() != '\n') {
                    // 消除当前键入的内容
                    arg0.consume();
                    // 弹出错误信息框
                    JOptionPane.showMessageDialog(null, "请输入整数", "友情提示", JOptionPane.ERROR_MESSAGE);
                }
                // 如果输入的内容为数字,并且长度大于4
                if (Character.isDigit(arg0.getKeyChar()) && jtext.getText().length() + 1 > 4) {
                    // 消除当前键入的内容
                    arg0.consume();
                    // 弹出错误信息框
                    JOptionPane.showMessageDialog(null, "请输入4位以内整数", "友情提示", JOptionPane.ERROR_MESSAGE);
                }
            }

            @Override
            public void keyReleased(KeyEvent arg0) {
            }

            @Override
            public void keyPressed(KeyEvent arg0) {
            }
        });
        // 设置输入框的位置
        jtext.setBounds(90, 10, 50, 25);
        // 将text控件添加到面板中
        jpanel.add(jtext);

        // ####################### 指定excel出力目录 ##########################
        // 生成按钮控件用于指定excel出力的目录
        JButton jFileButton = new JButton("指定excel出力目录");
        // 设置按钮的位置
        jFileButton.setBounds(200, 10, 160, 25);
        // 将按钮添加到面板中
        jpanel.add(jFileButton);

        // 生成label控件
        JLabel jlabel = new JLabel("出力Excel目录:");
        // 设置label的位置
        jlabel.setBounds(10, 40, 100, 25);
        // 将label添加到面板中
        jpanel.add(jlabel);

        // 生成label控件用于显示指定的出力目录
        JLabel jlabelPath = new JLabel();
        // 设置label的位置
        jlabelPath.setBounds(110, 40, 400, 25);
        // 将label添加到面板中
        jpanel.add(jlabelPath);
        // 指定目录按钮按下后的事件监听
        jFileButton.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent arg0) {
                // 生成文件选择器控件
                JFileChooser jfile = new JFileChooser();
                // 设置文件选择器为只选择目录模式
                jfile.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
                // 显示文件选择器
                jfile.showOpenDialog(jpanel);
                // 如果选了目录
                if (jfile.getSelectedFile() != null) {
                    // label控件则显示选中的目录内容
                    jlabelPath.setText(jfile.getSelectedFile().getPath());
                }
            }
        });

        // ####################### 执行按钮 ##########################
        // 生成执行按钮
        JButton jExcuteButton = new JButton("执行!");
        // 设置按钮的位置
        jExcuteButton.setBounds(400, 10, 80, 25);
        // 对按钮进行事件监听
        jExcuteButton.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent arg0) {
                // 如果没有输入年份
                if ("".equals(jtext.getText()) || jtext.getText().length() > 4) {
                    // 弹出错误信息框
                    JOptionPane.showMessageDialog(null, "请输入4位以内年份", "友情提示", JOptionPane.ERROR_MESSAGE);
                    return;
                }
                // 取得输入的年份内容
                String inputStr = jtext.getText();
                // 转换为char数组,分解每一个输入的值
                char[] numcheck = inputStr.toCharArray();
                // 循环输入的每一个字符
                for (int i = 0; i < numcheck.length; i++) {
                    // 把输入的字符转换为codepoint
                    int numPoint = (int) numcheck[i];
                    // 如果codepoint不在整数0~9的范围内
                    if (numPoint < 48 || numPoint > 57) {
                        // 弹出错误信息框
                        JOptionPane.showMessageDialog(null, "年份请输入整数", "友情提示", JOptionPane.ERROR_MESSAGE);
                        return;
                    }
                }
                // 如果没有选择目录
                if ("".equals(jlabelPath.getText())) {
                    // 弹出错误信息框
                    JOptionPane.showMessageDialog(null, "请指定excel出力目录", "友情提示", JOptionPane.ERROR_MESSAGE);
                    return;
                }
                try {
                    // 往excel文件中输出日历
                    writeToExcelFile(jlabelPath.getText(), Integer.valueOf(jtext.getText()));
                } catch (FileNotFoundException e) {
                    // 弹出错误信息框
                    JOptionPane.showMessageDialog(null, "文件创建失败!\n" + e.getMessage(), "友情提示",
                            JOptionPane.ERROR_MESSAGE);
                    return;
                } catch (IOException e) {
                    // 弹出错误信息框
                    JOptionPane.showMessageDialog(null, "文件写入失败!\n" + e.getMessage(), "友情提示",
                            JOptionPane.ERROR_MESSAGE);
                    return;
                }
                // 输出成功后弹出信息框
                int result = JOptionPane.showConfirmDialog(null,
                        "Excel作成日历成功!\n是否立即打开下面的文件?\n" + jlabelPath.getText() + "\\" + FILENAME, "确认信息",
                        JOptionPane.YES_NO_OPTION);
                if (result == JOptionPane.YES_OPTION) {
                    File file = new File(jlabelPath.getText() + "\\" + FILENAME);
                    try {
                        Desktop.getDesktop().open(file);
                    } catch (IOException e) {
                        // 弹出错误信息框
                        JOptionPane.showMessageDialog(null, "打开文件失败!\n" + e.getMessage(), "友情提示",
                                JOptionPane.ERROR_MESSAGE);
                    }
                }
            }
        });
        // 将执行按钮添加到面板
        jpanel.add(jExcuteButton);

        // ####################### 窗体居中设置 ##########################
        // 获取屏幕的尺寸
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        // 设置窗口居中显示
        jf.setLocation(screenSize.width / 2 - jf.getWidth() / 2, screenSize.height / 2 - jf.getHeight() / 2);
        // 显示窗体
        jf.setVisible(true);
    }

    /**
     * 生成Excel文件
     * 
     * @param path
     *            生成的目录path
     * @param year
     *            输入的年份
     * @throws FileNotFoundException
     * @throws IOException
     */
    /**
     * @param path
     * @param year
     * @throws FileNotFoundException
     * @throws IOException
     */
    private static void writeToExcelFile(String path, int year) throws FileNotFoundException, IOException {
        // 初始化行数
        rows = 0;
        // 创建Calendar实例
        Calendar c = Calendar.getInstance();
        // 系统当日日期
        Calendar now = (Calendar) c.clone();
        // 创建xlsx格式的book
        wb = new XSSFWorkbook();
        // 创建sheet,命名为例"Calendar2016"
        Sheet sheet = wb.createSheet("Calendar" + year);
        // 关闭网格设置
        sheet.setDisplayGridlines(false);
        // 调整页面为一页打印
        sheet.setFitToPage(true);
        // 设置整个sheet的列宽
        sheet.setDefaultColumnWidth(2);

        // 设置输入年的月日为1月1日。
        c.set(Calendar.YEAR, year);
        c.set(Calendar.MONTH, Calendar.JANUARY);
        c.set(Calendar.DAY_OF_MONTH, 1);
        // 复制一份当前的年月日
        Calendar ccopy = (Calendar) c.clone();
        // 循环处理输入年的每一天
        while (c.get(Calendar.YEAR) == year) {
            // 用于判断是否为今日
            boolean today = false;
            // 如果是系统时间,设定位true
            if (now.compareTo(c) == 0) {
                today = true;
            }
            // 获取当前月
            int month = c.get(Calendar.MONTH);
            // 获取当月的日
            int mday = c.get(Calendar.DAY_OF_MONTH);
            // 获取当日是星期几
            int wday = c.get(Calendar.DAY_OF_WEEK);
            // 如果是当月的第一天
            if (mday == 1) {
                // 输出title行
                writeTitle(month, sheet);
                // excel的行数+1
                rows++;
                // 判断当月的第一天在一周的位置,把之前的位置设置成空白
                for (int i = 1; i < wday; i++) {
                    writeDate(i, null, sheet, false);
                }
            }
            // 输出当天的日
            writeDate(wday, Double.valueOf(mday), sheet, today);

            // 设定下一天
            ccopy.add(Calendar.DAY_OF_YEAR, 1);
            // 如果该天是周六,或者下一天不是当前月,则换行
            if (wday == 7 || ccopy.get(Calendar.MONTH) != month) {
                for (int i = 0; i < AG.length(); i++) {
                    setRegionBorder(BorderStyle.THIN.getCode(),
                            CellRangeAddress.valueOf("$" + AG.charAt(i) + "$" + (rows + 1)), sheet);
                }
                rows++;
            }
            // 准备处理下一天
            c.add(Calendar.DAY_OF_YEAR, 1);
        }
        // 创建输出流文件
        FileOutputStream fos = new FileOutputStream(path + "/" + FILENAME);
        // 把book写入到该文件
        wb.write(fos);
        // 关闭book对象
        wb.close();
        // 关闭流文件
        fos.close();
    }

    /**
     * 作成title
     * 
     * @param month
     * @param sheet
     */
    private static void writeTitle(int month, Sheet sheet) {
        // 创建行
        Row rowMonth = sheet.createRow(rows);
        // 输出当前的月份
        Cell celMonth = rowMonth.createCell(0);
        celMonth.setCellValue(month + 1 + "月");
        // 生成字体对象
        Font font = wb.createFont();
        // 设置字体名称
        font.setFontName("SimSun");
        // 设置字体大小
        font.setFontHeightInPoints((short) 25);
        // 设置粗体
        font.setBold(true);
        // 创建单元格格式
        CellStyle cellStyle = wb.createCellStyle();
        // 设置居中对齐
        cellStyle.setAlignment(HorizontalAlignment.CENTER);
        // 设置垂直居中对齐
        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        // 设置单元格的字体
        cellStyle.setFont(font);
        // 设置单元格的格式
        celMonth.setCellStyle(cellStyle);
        // 合并7列单元格
        sheet.addMergedRegion(CellRangeAddress.valueOf("$A$" + (rows + 1) + ":$G$" + (rows + 1)));
        // 设置单元格边框为实线
        setRegionBorder(BorderStyle.THIN.getCode(), CellRangeAddress.valueOf("$A$" + (rows + 1) + ":$G$" + (rows + 1)),
                sheet);

        // 换行
        rows++;
        // 创建行
        Row row = sheet.createRow(rows);
        // 创建单元格格式
        CellStyle cs = wb.createCellStyle();
        // 设置填充背景色为蓝色
        cs.setFillForegroundColor(IndexedColors.BLUE.getIndex());
        // 设置填充
        cs.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        // 生成font
        Font ft = wb.createFont();
        // 设置白色字体
        ft.setColor(IndexedColors.WHITE.getIndex());
        // 将字体添加到单元格格式
        cs.setFont(ft);
        for (int i = 0; i < 7; i++) {
            // 创建单元格
            Cell cell = row.createCell(i);
            // 设定单元格的内容为"日"等
            cell.setCellValue(WEEKEND[i]);
            // 设定单元格格式
            cell.setCellStyle(cs);
        }
        // 设置单元格边框为实线
        for (int i = 0; i < AG.length(); i++) {
            setRegionBorder(BorderStyle.THIN.getCode(), CellRangeAddress.valueOf("$" + AG.charAt(i) + "$" + (rows + 1)),
                    sheet);
        }
    }

    /**
     * 写入日期
     * 
     * @param wday
     *            周几
     * @param date
     *            日期
     * @param sheet
     *            当前sheet
     * @param today
     *            是否为今日
     */
    private static void writeDate(int wday, Double date, Sheet sheet, boolean today) {
        // 获取指定行的内容
        Row row = sheet.getRow(rows);
        // 如果获取不到内容
        if (row == null) {
            // 创建行
            row = sheet.createRow(rows);
        }
        // 创建指定单元格
        Cell cell = row.createCell(wday - 1);
        // 创建单元格格式
        CellStyle cs = wb.createCellStyle();
        // 如果是周六或者周日并且date不为空
        if ((wday == 1 || wday == 7) && date != null) {
            // 设置填充背景色为黄色
            cs.setFillForegroundColor(IndexedColors.YELLOW.getIndex());
            // 设置填充
            cs.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        }
        // 如果是今日,则对于该单元格进行标红色的设定。
        if (today) {
            // 设置填充背景色为红色
            cs.setFillForegroundColor(IndexedColors.RED.getIndex());
            // 设置填充
            cs.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        }
        // 设置单元格的格式
        cell.setCellStyle(cs);
        // 输入当日到单元格
        if (date == null) {
            cell.setCellValue(" ");
        } else {
            cell.setCellValue(date);
        }
    }

    /**
     * 设定指定范围内单元格的边框线
     * 
     * @param border
     *            边框线的样式
     * @param region
     *            单元格范围
     * @param sheet
     *            sheet对象
     */
    private static void setRegionBorder(short border, CellRangeAddress region, Sheet sheet) {
        //设置上部边框线
        RegionUtil.setBorderTop(border, region, sheet);
        //设置下部边框线
        RegionUtil.setBorderBottom(border, region, sheet);
        //设置左边边框线
        RegionUtil.setBorderLeft(border, region, sheet);
        //设置右边边框线
        RegionUtil.setBorderRight(border, region, sheet);
    }
}

  在写上面的小程序时,有个地方需要说一下:那就是excel单元格设置四边框线的问题。
  CellStyle类中有两种方法,setBorderBottom(short border)和setBorderBottom(BorderStyle border)。前者在3.15版本中已经不推荐使用了。所以说如果要设置四边框线的问题以后就用setBorderBottom(BorderStyle border)。
  那么问题来了,如果A1到G1单元格这7个单元格需要合并起来,那么合并后设置四边边框线的话,只是显示A1单元格位置的线框,后面的线就没有了,为了解决这个问题,特地网上搜了一下,后面的6个单元格也要createcell一下,并且value设置空。否则不行。
  另外,请注意,本文中使用的方法是RegionUtil.setBorderBottom(int border, CellRangeAddress region, Sheet sheet),那么照理说如果指定范围,批量设定框线是没有问题的,但是它会在控制台打印很多行“BorderStyle short usage”。
  这个原因我小小纠结了一下,然后深入poi的源码,发现了原来是个遗留问题。比如上面提到的setBorderBottom方法,poi官方已经推荐不使用short来声明框线的类型了,但是如果你继续使用RegionUtil.setBorderBottom(border, region, sheet)方法的话,就是出现这个警告,官方解释:@deprecated 3.15 beta 2. getBorderStyle will only work on BorderStyle enums instead of codes in the future. 。也就是说poi虽然修正了short这个问题,但是util类调用的时候没有及时修正,放到以后去修正。一个字:懒,急于赶时间出新版本吧,哈哈。
这里写图片描述
  

猜你喜欢

转载自blog.csdn.net/redsoft_mymuch/article/details/52947101