大家都知道,C#打印图片可以直接调用PrintDocument控件的PrintPage事件,通过画刷对image对象直接进行绘制。但是这种方法存在局限,例如如果打印的图片需要按纸张大小进行缩放的话,那么图片显示比例和图片显示位置等都需要动态计算,如果还要添加水印或者其他的图片操作,基本上要添加很多额外的逻辑,并且效率不高,严重限制了程序的性能。如果要在图片上绘制个性化的文本或者定制其他内容,则基本没办法实现,严重限制了程序的可扩展性和可维护性。
常规写法如下所示:
1 //例如这是PrintDocument的PrintPage事件 2 Graphics g = null; 3 g = e.Graphics; 4 g.SmoothingMode = SmoothingMode.HighQuality; //设置画刷高质量 5 6 //绘制已经处理过的Bitmap对象(假设它已经从服务器或者某个地方下载下来并且已经算好了在纸张上指定的打印位置) 7 //此种写法在激光高速打印机中存在明显缺陷,因此不建议大业务量的程序使用 8 g.DrawImage(bmp, locationX, locationY, bmp.Width, bmp.Height);
笔者使用某款佳能的普通喷墨打印机速度不是很理想,在实际业务需求量很大的情况下,采用了利盟牌某款高速激光打印机,但是调用PrintDocument控件对图像对象进行绘制时,打印机处于等待状态,虽然打印任务已经发送过去,但是由于图片尚未绘制完成,所以打印机停滞不出纸张,效率还不如普通喷墨打印机,可以说这是这种方法的局限性导致的,因此在大业务量的情况下,使用这种方法进行打印明显并不合适。
所以我就开始研究使用模板进行打印的方法。
本文所探讨的是使用FastReport第三方控件对图片打印进行个性化的模板定制。首先在项目中引入此控件的相关dll。
第一步:添加FastReport模板打印相关类和方法:
在打印类中,我们定义一个打印模板的方法,关键代码如下:
1 /// <summary> 2 /// 打印报表 3 /// </summary> 4 /// <param name="ht">fastreport参数(key)及值(value)</param> 5 /// <param name="ds">数据集</param> 6 /// <param name="functionCode">模板名称</param> 7 /// <param name="modelCode">模板类型</param> 9 /// <param name="selectPrint">是否选择打印机bool</param> 10 public void Print(NoSortHashTable ht, DataSet ds, string functionCode, string modelCode, bool selectPrint) 11 { 12 //假设模板数据存在数据库中,此时先获取模板数据 13 Model.FR_Template m = this.GetFastReportModel(functionCode, modelCode, orgid); 14 15 if (m == null) 16 { 17 throw new Exception("调用的模版不能为空!"); 18 } 19 //设置模版数据 20 this.TempContent = string.IsNullOrEmpty(this.TempContent) ? m.TEMPCONTENT : this.TempContent; 21 22 //TempInf为空时报错 23 if (!string.IsNullOrEmpty(this.TempContent)) 24 { 25 //导入模版数据 26 this.report.LoadFromString(this.TempContent); 27 } 29 31 if (FRds != null) 32 { 33 // 注册报表数据 34 this.report.RegisterData(ds, FRds.DataSetName); 35 36 //加载可用的数据源 37 foreach (DataTable dt in FRds.Tables) 38 { 39 this.report.GetDataSource(dt.TableName).Enabled = true; 40 } 41 }44 45 //动态添加fastreport参数 46 foreach (DictionaryEntry de in ht) 47 { 48 string ParamName = de.Key.ToString(); 49 //获取参数 50 FastReport.Data.Parameter param = this.report.Parameters.FindByName(ParamName); 51 if (param != null) 52 { 53 param.Value = de.Value; 54 } 55 } 56 57 this.report.PrintSettings.ShowDialog = selectPrint; 58 59 string printerName = ConfigurationManager.AppSettings[functionCode] == null ? "" : ConfigurationManager.AppSettings[functionCode].ToString(); 60 if (!string.IsNullOrEmpty(printerName)) 61 { 62 this.report.PrintSettings.Printer = printerName; 63 } 64 // 运行报表打印 65 this.report.Print(); 66 // 释放使用的资源 67 this.report.Dispose(); 68 }
在设置打印模板的界面中,打开FastReport设计器的代码如下:
SaveFRTemplateFrm saveFRTfrm = new SaveFRTemplateFrm(VoidNameEnum.Update, dgvr); saveFRTfrm.Owner = this; saveFRTfrm.StartPosition = FormStartPosition.CenterScreen; if (saveFRTfrm.ShowDialog() == DialogResult.OK) { //保存设计好的打印模板 }
第二步:传递参数和数据,调用打印
模板设计好之后,在打印的界面中需调用刚刚封装的打印方法对模板进行传参打印。一下为打印方法:
/// <summary> /// 从打印模板打印数据 /// </summary> private void PrintPaperByTemplet( DataTable dsRSPrint) { NoSortHashTable nht = new NoSortHashTable(); SavePrintTempFile(); //添加打印参数 nht.Add("打印页码", (pagenum == 1 ? "" : Currentpagenum + "/" + pagenum)); nht.Add("打印时间", AppData.SysDate.ToString("yyyy-MM-dd hh:mm:ss")); DataSet ds = new DataSet("DataPrint"); ds.Tables.Add(dsRSPrint.Copy()); try { Print(nht, ds, functionCode, "A4", false); Application.DoEvents(); } catch (Exception ex) { //errorMsg += "图片数据出现问题,无法输出到打印模板!\n"; } }
那么关键的地方是,打印的图片数据如传入到FastReport模板中呢?有如下两种方法供你参考:
方法一:
在FastReport模板中添加图片对象的控件,指定本地或网络路径(注意必须是固定链接)的图片名称,每次打印之前先把需要打印的图片存放到这个路径并命名成指定的文件名。
/// <summary> /// 保存打印模板用到的临时缓存文件 /// </summary> private void SavePrintTempFile() { bool isSaveFlag = true; do { try { if (File.Exists("某个文件.jpg")) { File.Delete("某个文件.jpg"); Thread.Sleep(100); //休眠 避免保存文件时图片尚未删除 } img.Save(printTempFile); using (Bitmap bmpPrint = new Bitmap(img)) { //对图片进行一些处理,例如压缩大小,调整对比度等等 } } catch (Exception ex) { isSaveFlag = false; } } while (!isSaveFlag); //将文件设置为隐藏 FileInfo fi = new FileInfo(printTempFile); File.SetAttributes(printTempFile, fi.Attributes | FileAttributes.Hidden); }
当然如果你想在图片上添加水印,在模板中也可以实现,例如下图片所示,在图片层上面指定水印图片,注意必须是PNG格式的矢量图形,否则会盖住原始的图片内容。
水印的添加设置方法同上面的图片添加,在模板中设置指定路径即可。
方法二:
将图片对象通过数据列或参数形式传递到模板中,注意需要将image对象格式转化为64位字符串。
Byte[] streamByte = ImageBytesHelper.GetByteImage(img); //先将image对象转化为二进制字节(过程略)
dataRow["图片数据"] = Convert.ToBase64String(streamByte); //再将字节转换为64为字符
在模板中,你需要添加部分事件代码解析传过来的图片数据。
//在模板的DataPrintBefore事件中写下如下代码 string imgStr = (string)Report.GetColumnValue("ds.图片数据"); byte[] imgData=Convert.FromBase64String(imgStr); MemoryStream ms = new MemoryStream(imgData); Image img = System.Drawing.Image.FromStream(ms); //PictureObject pic=Report.FindObject('Picture1') as PictureObject; Picture1.Image=img;
此时image对象的64位字符即可以解析为图片显示在模板上了。
这两种方法是我研究了一段时间的结果,第一种直接存文件每次读取简单有效,并不影响打印效率。第二种方法传参设置,比第一种方法稍微复杂,但不需要读盘,稳定型更好。
注意:本文为Healer007原创,署名为小萝卜,本人站点:itoku.cn,欢迎交流学习,转载文章请注明出处。