大家都知道,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,歡迎交流學習,轉載文章請注明出處。
