NPOI导出含有二维码图片的Excel文件

时间:2024-02-19 19:30:01

最近加入一家新公司,主要负责了导出模块的编写。从最开始的懵懂状态到现在有点一知半解了。今天做了一个带图片的Excel文件的导出。在次记录一下。

使用的框架是asp.net MVC,这个框架也时进公司才开始学的。

在环境中导入NPOI dll,在程序包管理器控制台输入“Install-Package NPOI -Version 2.3.0”,回车,项目里会自动导入相关DLL。

参考网址 https://www.nuget.org/packages/NPOI/

 

下面我用到的主要代码

public FileStreamResult Export(string ids)
        {
            FileStream fs = null;
            try
            {
                string newFilePath = MyPath.ExportPhysicalPath;
                string templateFilePath = MyPath.ProductListExportTemplateFilPath;
                if (!Directory.Exists(newFilePath))
                {
                    Directory.CreateDirectory(newFilePath);
                }
                if (!System.IO.File.Exists(templateFilePath))
                {
                    //return null;
                }
                IWorkbook workbook = null;
                ISheet sheet = null;
                using (fs = System.IO.File.OpenRead(templateFilePath))
                {
                    workbook = new HSSFWorkbook(fs);
                    ICellStyle style = workbook.GetSheetAt(0).GetRow(0).GetCell(0).CellStyle;
                    sheet = workbook.CloneSheet(0);
                    short rowHeight = sheet.GetRow(1).Height;
                    //ListExcel是我专门为了出力某个模块,对应出力内容新建的一个出力Model
                    //抽出要出力的数据,放到要出力的Model集合里
                    List<ListExcel> list = dataBll.GetListByProductIds(\',\' + ids + \',\');
                    //为了设定单元的格式而初始化的对象
                    IDataFormat dataformat = workbook.CreateDataFormat();
                    // Create the drawing patriarch.  This is the top level container for all shapes. 
                    // 为了存储图片而初始化的对象  
                    HSSFPatriarch patriarch = (HSSFPatriarch)sheet.CreateDrawingPatriarch();
                    for (int i = 0; i < list.Count; i++)
                    {
                        ListExcel data = list[i];
                        string imageSavePath = "二维码图片的物理地址";
                        //利用反射取得要出力的Model的属性名
                        PropertyInfo[] opdPropertys = data.GetType().GetProperties();
                        for (int j = 0; j < opdPropertys.Length; j++)
                        {
                            ICellStyle newStyle = workbook.CreateCellStyle();
                            newStyle.CloneStyleFrom(style);
                            PropertyInfo prs = opdPropertys[j];
                            //Model的属性名
                            string fieldName = prs.Name;
                            //该属性的值
                            object val = prs.GetValue(data, null);
                            //这里我把模板文件里的表头信息给写到配置文件里了
                            //配置文件里的内容为列号,列名,列名对应着出力Model里的属性名
                            //根据属性名取得该属性的在模板里的位置信息
                            //header.Item1:列号;header.Item2:表头(目前并没有什么用);
                            Tuple<int, string> header = ConfigHelper.GetExcelHeader(fieldName);
                            if (header == null) continue;

                            //当需要设定二维码图片时
                            if (fieldName.Equals("ProductQRImage"))
                            {
                                //出力Model里ProductQRImage属性里,我存储的时图片名称
                                string imagefilePath = Path.Combine(imageSavePath, Convert.ToString(val));
                                if (System.IO.File.Exists(imagefilePath))
                                    SetPicCellValue(workbook, patriarch, imagefilePath, 1 + i, header.Item1);

                                continue;
                            }

                            //当设定除图片外的单元格时
                            CellType cellType = CellType.String;
                            string className = val.GetType().FullName;
                            if (className.Equals("System.Decimal"))
                            {
                                //这个要求我出力的数字型的单元格不带绿色小三角,所以只能指定cellType
                                //Decimal类型的要求带两位小数
                                cellType = CellType.Numeric;
                                newStyle.DataFormat = dataformat.GetFormat("0.00");
                            }
                            if (className.Equals("System.Int32"))
                            {
                                //int类型的就不带小数咯
                                cellType = CellType.Numeric;
                                newStyle.DataFormat = dataformat.GetFormat("0");
                            }
                            //往单元格里设定值
                            SetCellValue_U(sheet, 1 + i, header.Item1, Convert.ToString(val), newStyle, cellType, rowHeight);
                        }
                    }
                }
                workbook.RemoveSheetAt(0);
                string newFileName = string.Format("导出_{0}.xls", DateTime.Now.ToString("yyyyMMddHHmmssfff"));
                string newFilePath = string.Format("{0}/{1}", newFilePath, newFileName);
                //这里如果不拿出来给Dispose()一下,new FileStream(newFilePath, FileMode.Open)会报错
                var newFs = System.IO.File.OpenWrite(newFilePath);
                workbook.Write(newFs);
                newFs.Dispose();
                return File(new FileStream(newFilePath, FileMode.Open), "application/octet-stream", Server.UrlEncode(newFileName));
            }
            catch (Exception ex)
            {
                if (fs != null)
                {
                    fs.Close();
                }
                Response.Write(ex.Message);
                return null;
            }
        }
        
        /// <summary>
        /// 设定图片单元格
        /// </summary>
        /// <param name="workbook">目标workbook</param>
        /// <param name="patriarch">图片容器</param>
        /// <param name="imagePath">图片地址</param>
        /// <param name="row"></param>
        /// <param name="col"></param>
        private void SetPicCellValue(IWorkbook workbook, HSSFPatriarch patriarch, string imagePath, int row, int col)
        {
            byte[] bytes = System.IO.File.ReadAllBytes(imagePath);
            int pictureIdx = workbook.AddPicture(bytes, PictureType.PNG);
            /*插图片的位置HSSFClientAnchor(dx1,dy1,dx2,dy2,col1,row1,col2,row2) 
             * 前面四个表示在单元格中两个点的位置,后面四个表示是哪个单元格
             * 图片左上角为(col1, row1)第row+1行col+1列,
             * 右下角为(col2, row2)第col2+1行row2+1列,
             * dx2控制图片高度,dy2控制图片宽度。
             * dx2最大值1023,dy2最大值255
             */
            HSSFClientAnchor anchor = new HSSFClientAnchor(0, 0, 1023, 255, col, row, col, row);
            //把图片插到相应的位置
            HSSFPicture pict = (HSSFPicture)patriarch.CreatePicture(anchor, pictureIdx);
            //pict.Resize();勿使用
        }
        
                /// <summary>
        /// 设定每一行单元格的值(指定行高)
        /// </summary>
        /// <param name="sheet">目标Sheet</param>
        /// <param name="rowIndex">行数</param>
        /// <param name="columnIndex">列数</param>
        /// <param name="strValue">设定值</param>
        /// <param name="style">单元格的Style</param>
        /// <param name="cellType">单元格的Type</param>
        private void SetCellValue_U(ISheet sheet, int rowIndex, int columnIndex, string strValue, ICellStyle style, CellType cellType, short rowHeight =-1)
        {
            IRow row = sheet.GetRow(rowIndex);
            if (row == null)
            {
                row = sheet.CreateRow(rowIndex);
            }
            if (rowHeight > 0)
            {
                row.Height = rowHeight;
            }
            ICell cell = row.GetCell(columnIndex);
            if (cell == null)
            {
                cell = row.CreateCell(columnIndex);
            }
            if (style != null)
            {
                cell.CellStyle = style;
            }
            cell.SetCellType(cellType);
            if (cellType == CellType.Numeric)
            {
                cell.SetCellValue(Math.Round(double.Parse(strValue),2));
            }
            else
            {
                cell.SetCellValue(strValue);
            }
        }
View Code

在这里由于要导出图片所以主要用到了下面这行代码

HSSFClientAnchor anchor = new HSSFClientAnchor(0, 0, 1023, 255, col, row, col, row);

在网上查了一下,对这句代码参数的说明很少,或者说的很笼统。

方法 HSSFClientAnchor(dx1,dy1,dx2,dy2,col1,row1,col2,row2) 。前面四个表示在单元格中两个点的位置,后面四个表示是开始单元格和结束单元格。dx1,dx2最大值1023;dy1,dy2最大值255

在这里由于我的需求是插入到一个单元格内。所以这里我使用的参数效果时填充整个单元格。

若(col1,row1,col2,row2)中col1=col2,row1=row2即表示插入到一个单元格里

若(dx1,dy1,dx2,dy2)的值设为(0, 0, 1023, 255)即表示将图片填充整个单元格

 


 

 2017-08-30

上面的代码写的很早被打断后一直在草稿箱里,后面有了一些新的改动。由于导出的图片是二维码,必须保证图片的清晰度,要求导出的图片为原图尺寸。这里我开始想说使用

pict.Resize()

 这里我使用的图片为50*50像素,但是导出的图片会变形非常不清晰,而且会变形,所以别人说勿使用还真的不要使用,下面是使用这句代码的效果图

后来我直接将导出模板K列的单元格的长宽手动调整到了50*50像素,然后使用下面这句将图片填充到整个单元格。插入图片的效果蛮好的,清晰度还原的不错

HSSFClientAnchor anchor = new HSSFClientAnchor(0, 0, 1023, 255, col, row, col, row);

 下面时图片效果:

 所以最后采用了这种方案。

 

NPOI里设置单元格的长宽

这里再来扩展一下NPOI里如何设置单元格的长宽。

由于项目里导出的二维码的尺寸不可能是固定的,为了导出清晰的二维码我不可能每次都去改动模板的长宽。所以必须在代码里设置长宽。

在网上搜索了一下,有下面一种说法:

  1. 设置单元格的高度实际是设置其所在行高,所以要在单元格所在行上设置行高,行高设置数值好像是像素点的1/20,所以*20以便达到设置效果;
  2. 设置单元格的宽度实际上是设置其所在列宽,所以要在单元格所在列上设置(列的设置在工作表上),宽度数值好像是字符的1/256,所以*256以便达到设置效果。

对此,在我这里不适用。因为是为了导出清晰的图片而调整的长宽,则是以图片像素为基准。上面的说法里提到了像素点,开始误导了我,但在代码里实践之后并不行。

这里设置单元格像素的方法是:

// qrCodeSize为二维码图片的像素50px*50px
//
设置列宽,excel列宽 像素*32 sheet.SetColumnWidth(col, qrCodeSize * 32); // 改变行高 ,excel行高度 像素*15 row.Height = (short)(qrCodeSize * 15);

 

这里qrCodeSize为导出二维码图片的尺寸。生成的Excel单元格的效果图如下:

列宽(50像素):

行高(50像素):

图片覆盖整个单元格后,就是一张清晰的二维码图片了。