最近加入一家新公司,主要负责了导出模块的编写。从最开始的懵懂状态到现在有点一知半解了。今天做了一个带图片的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集合里 Listlist = 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; } } /// /// 设定图片单元格 /// /// 目标workbook /// 图片容器 /// 图片地址 /// 行 /// 列 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();勿使用 } /// /// 设定每一行单元格的值(指定行高) /// /// 目标Sheet /// 行数 /// 列数 /// 设定值 /// 单元格的Style /// 单元格的Type 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); } }
在这里由于要导出图片所以主要用到了下面这行代码
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/20,所以*20以便达到设置效果;
- 设置单元格的宽度实际上是设置其所在列宽,所以要在单元格所在列上设置(列的设置在工作表上),宽度数值好像是字符的1/256,所以*256以便达到设置效果。
对此,在我这里不适用。因为是为了导出清晰的图片而调整的长宽,则是以图片像素为基准。上面的说法里提到了像素点,开始误导了我,但在代码里实践之后并不行。
这里设置单元格像素的方法是:
// qrCodeSize为二维码图片的像素50px*50px
// 设置列宽,excel列宽 像素*32 sheet.SetColumnWidth(col, qrCodeSize * 32); // 改变行高 ,excel行高度 像素*15 row.Height = (short)(qrCodeSize * 15);
这里qrCodeSize为导出二维码图片的尺寸。生成的Excel单元格的效果图如下:
列宽(50像素):
行高(50像素):
图片覆盖整个单元格后,就是一张清晰的二维码图片了。