重復造輪子系列——基於FastReport設計打印模板實現桌面端WPF套打和商超POS高度自適應小票打印
一、引言
桌面端系統經常需要對接各種硬件設備,比如掃描器、讀卡器、打印機等。
這里介紹下桌面端系統打印經常使用的場景。
1、一種是類似票務方面的系統需要打印固定格式的票據。比如景點門票、車票、電影票。
這種基本是根據模板調整位置套打。
2、還有一種是交易小票,比如商超POS小票,打印長度會隨着內容的大小自動伸縮。
這種就不僅僅是固定格式的套打了,還得計算數據行以適應不同的打印長度。
打印方式也有兩種類型
1、指令打印,根據不同打印機可能需要對接不同的打印指令。
2、驅動打印,不同打印機都有自帶安裝驅動。通過驅動打印更方便。下面介紹的內容以驅動打印的方式
打印經常需要調整打印字體位置等等這些,如果有個可視化模板設計下,系統不需要任何改動就可以是最方便的,這樣方便客戶或者現場實施自己做調整。
想到各種客戶端報表工具都有可視化的界面而且可以打印,就找了個FastReport.Net工具來做。
二、固定格式的套打
這里以門票為例,講解怎么使用FastReport.Net工具來設計模板以及打印。
下載FastReport.Net工具,引用到這三個dll,FastReport.dll、FastReport.Bars.dll、FastReport.Editor.dll
封裝一個公共的PrintHelper.cs,提供兩個方法,打印和設計。代碼如下:
public class PrintHelper { /// <summary>
/// 打印 /// </summary>
/// <param name="printerName">打印機</param>
/// <param name="frxPath">模板</param>
/// <param name="dicParam">字典參數</param>
/// <param name="dsDataSource">數據源</param>
/// <param name="printNum">打印數量</param>
/// <returns></returns>
public static Tuple<bool, string> Print(string printerName, string frxPath, Dictionary<string, object> dicParam, DataSet dsDataSource, int printNum = 1) { bool flag = false; string msg = ""; FastReport.Report report = new FastReport.Report(); try { report.Load(frxPath); report.DoublePass = true; if (dicParam != null && dicParam.Count > 0) { foreach (var item in dicParam) { report.SetParameterValue(item.Key, item.Value); } } if (dsDataSource != null && dsDataSource.Tables.Count > 0) { report.RegisterData(dsDataSource); foreach (DataSourceBase dataSourceBase in report.Dictionary.DataSources) { dataSourceBase.Enabled = true; } } report.PrintSettings.ShowDialog = false; report.PrintSettings.Printer = printerName; report.PrintSettings.PrintMode = PrintMode.Split; EnvironmentSettings envSet = new EnvironmentSettings(); envSet.ReportSettings.ShowProgress = false; for (int i = 0; i < printNum; i++) { report.Print(); } flag = true; msg = "打印成功"; } catch (Exception ex) { flag = false; msg = ex.Message; } finally { report.Dispose(); } return new Tuple<bool, string>(flag, msg); } /// <summary>
/// 設計 /// </summary>
/// <param name="frxPath">模板</param>
/// <param name="dicParam">字典參數</param>
/// <param name="dsDataSource">數據源</param>
/// <returns></returns>
public static Tuple<bool, string> Design(string frxPath, Dictionary<string, object> dicParam, DataSet dsDataSource) { bool flag = false; string msg = ""; FastReport.Report report = new FastReport.Report(); try { report.Load(frxPath); report.DoublePass = true; if (dicParam != null && dicParam.Count > 0) { foreach (var item in dicParam) { report.SetParameterValue(item.Key, item.Value); } } if (dsDataSource != null && dsDataSource.Tables.Count > 0) { report.RegisterData(dsDataSource); foreach (DataSourceBase dataSourceBase in report.Dictionary.DataSources) { dataSourceBase.Enabled = true; } } report.Design(); flag = true; msg = "設計器打開成功"; } catch (Exception ex) { flag = false; msg = ex.Message; } finally { report.Dispose(); } return new Tuple<bool, string>(flag, msg); } }
Demo設計一個TicketTemp.frx模板,如下圖1
圖1
面板上面的排列可以任意拖動,字體樣式設計,還可以360旋轉。如圖2
圖2
普通文本標簽在Parameters里面直接可以取字典傳入的參數值,雙擊或者拖拉都可以。如圖3
圖3
二維碼或者條碼類型,FastReport有提供相應的標簽,看模板左邊豎着的工具條,找到Barcode,拖一個到面板上,如圖4
圖4
對應的屬性看右下角,如圖5
圖5
Barcode屬性值下拉,支持這么多類型的條碼和二維碼編碼格式
如果是使用自己生成的二維碼圖片就可以使用左邊豎着工具里面的圖片標簽Picture,使用這個就可以自定義圖片打印(當然條碼二維碼也是特殊圖片)。但這個時候傳入的數據要使用數據源了,並且在圖片標簽屬性里面找到這個屬性值,綁定數據源里面對應的圖片字段。如圖6
圖6
看下demo里面模板設計按鈕代碼,如下:
private void BtnTicketDesign_Click(object sender, RoutedEventArgs e) { if (string.IsNullOrEmpty(txtTicketTemp.Text)) { MessageBox.Show("門票模板不能為空"); return; } var data = this.CreateTicketData(); if (!data.Item1) { MessageBox.Show("模擬數據生成錯誤"); return; } var tuple = PrintHelper.Design(txtTicketTemp.Text, data.Item2, data.Item3); if (!tuple.Item1) { MessageBox.Show($"打開設計器失敗:{tuple.Item2}"); } }
看下demo里面模板打印按鈕代碼,如下:
private void BtnTicketPrint_Click(object sender, RoutedEventArgs e) { if (cbxPrinter.SelectedValue == null || string.IsNullOrEmpty(cbxPrinter.SelectedValue.ToString())) { MessageBox.Show("請選擇打印機"); return; } if (string.IsNullOrEmpty(txtTicketTemp.Text)) { MessageBox.Show("門票模板不能為空"); return; } var data = this.CreateTicketData(); if (!data.Item1) { MessageBox.Show("模擬數據生成錯誤"); return; } var tuple = PrintHelper.Print(cbxPrinter.SelectedValue.ToString(), txtTicketTemp.Text, data.Item2, data.Item3); if (!tuple.Item1) { MessageBox.Show($"打印失敗:{tuple.Item2}"); } }
打印和設計共同的組裝模擬數據的方法代碼如下
/// <summary>
/// 組裝門票打印模擬數據 /// </summary>
/// <returns></returns>
private Tuple<bool, Dictionary<string, object>, DataSet> CreateTicketData() { //注意事項 //文本內容放入字段中,傳入就可以 //圖片內容有兩種方案 //1:使用模板的Barcode標簽,這個支持標准qr碼,條碼等等多種類型,字典或者數據源傳入文本值,自動會顯示qr碼條碼圖片 //2:使用模板的Picture標簽,使用這個就是要程序生成好圖片,再把圖片顯示,這里就不僅僅限制於二維碼條碼了,各種想顯示的圖片都可以,但需要把圖片流放到數據源中傳入
Dictionary<string, object> dic = new Dictionary<string, object>(); dic.Add("ticketModelName", "XXX景點門票"); dic.Add("ticketModelKind", "成人票"); dic.Add("ticketModelPrice", "¥100"); //字典中傳入二維碼的文本,fastreport提供了生成qr碼以及各種條碼
dic.Add("barcode", "ET2018000000000000001"); //如果需要,組裝dataset數據源,這里以傳入二維碼圖片為例
DataTable dtImage = new DataTable("dtBarcode"); dtImage.Columns.Add("barcode", typeof(Byte[])); DataSet dsFrx = new DataSet(); dsFrx.Tables.Add(dtImage); //1、把二維碼碼文本生成圖片 這個有很多第三方庫可以支持 我這里用 ThoughtWorks.QRCode
QRCodeEncoder qrCodeEncoder = new QRCodeEncoder(); qrCodeEncoder.QRCodeEncodeMode = QRCodeEncoder.ENCODE_MODE.BYTE; qrCodeEncoder.QRCodeScale = 4; qrCodeEncoder.QRCodeVersion = 4; qrCodeEncoder.QRCodeErrorCorrect = QRCodeEncoder.ERROR_CORRECTION.M; using (Image image = qrCodeEncoder.Encode("ET2018000000000000001")) { //2、生成的圖片本地可以做個備份記錄,也可以不需要直接將image轉byte[]傳人數據源就可以
if (!Directory.Exists(Path.Combine(Directory.GetCurrentDirectory(), "BarCode"))) { Directory.CreateDirectory(Path.Combine(System.IO.Directory.GetCurrentDirectory(), "BarCode")); } string filename = DateTime.Now.ToString("yyyymmddhhmmssfff").ToString() + ".jpg"; string filepath = Path.Combine(System.IO.Directory.GetCurrentDirectory(), "BarCode", filename); using (FileStream fs = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Write)) { image.Save(fs, ImageFormat.Jpeg); } //3、將image轉byte[]傳人數據源 注意圖片傳入的是字節數組byte[] 不是文本也不是圖片路徑!!!
dtImage.Rows.Add(ImageToBytes(image, ImageFormat.Jpeg)); } return new Tuple<bool, Dictionary<string, object>, DataSet>(true, dic, dsFrx); }
運行,在設計器里面也可以預覽,看下最終打印效果,如圖7
圖7
三、動態格式的打印
這里以商超POS交易小票為例,講解怎么使用FastReport.Net工具來設計模板以及打印。
這種小票大部分和上面說的一樣,唯一不同的是有數量不固定的數據集動態數據。
Demo設計一個BillTemp.frx模板,如下圖8
圖8
固定大小的數據和上面的類似,放到報表頭尾,或者頁面頭尾都可以。動態數據需要放到Data里面,點擊Configure Bands,如圖9
圖9
這里可以添加刪除Band。
Data需要綁定對應的數據源,傳進來是DataSet,也可以使用多個Data這樣就可以有多個DataTable,實例這里就使用了一個。看設計器右上角數據源,如圖10
圖10
當前的data需要指定哪個數據源,如圖11
圖11
動態數據每個值也是和普通的文本類似,但不是取Parameters里面,要取DataSources里面對應的數據源字段,如圖12
圖12
小票模板里面還有一點是特殊的,由於數據集的動態的防止打印的時候分頁,需要動態的控制面板的長度,切換到code,如圖13
圖13
在code里面增加代碼計算數據布局之后的總高度,代碼如下
public class ReportScript { private float pageHeader1Height; private float reportTitle1Height; private float dataHeaderHeight; private float data1Height; private float reportSummaryHeight; private float pageFooter1Height; private void Page1_StartPage(object sender, EventArgs e) { if(Engine.FinalPass) { Page1.PaperHeight = (reportTitle1Height + pageHeader1Height +dataHeaderHeight +data1Height +reportSummaryHeight +pageFooter1Height)/Units.Millimeters +Page1.TopMargin +Page1.BottomMargin; } } private void PageHeader1_AfterLayout(object sender, EventArgs e) { pageHeader1Height=PageHeader1.Height; } private void ReportTitle1_AfterLayout(object sender, EventArgs e) { reportTitle1Height=ReportTitle1.Height; } private void PageFooter1_AfterLayout(object sender, EventArgs e) { pageFooter1Height=PageFooter1.Height; } //Data 的高度 用+=
private void Data1_AfterLayout(object sender, EventArgs e) { data1Height+=Data1.Height; } //DataHeader 的高度 用+=
private void DataHeader1_AfterLayout(object sender, EventArgs e) { dataHeaderHeight+=DataHeader1.Height; } private void ReportSummary1_AfterLayout(object sender, EventArgs e) { reportSummaryHeight=ReportSummary1.Height; } }
這段代碼就是所有的band屬性的AfterLayout事件,如圖14
圖14
算出當前band高度最后總和最為頁面的高度
Demo里面小票的設計和打印代碼和上面門票的類似,這里看下模擬數據組裝的方法代碼
/// <summary>
/// 組裝小票打印模擬數據 /// </summary>
/// <returns></returns>
private Tuple<bool, Dictionary<string, object>, DataSet> CreateBillData() { //注意事項 //小票打印和門票一樣,主要的區別是小票動態數據會變化,小票的長度也會動態改變 //這里主要演示下 動態數據源 為了動態拉伸,除了傳入數據源,在模板上面code部分需要加代碼
Dictionary<string, object> dic = new Dictionary<string, object>(); dic.Add("billNo", "2018111100002222"); dic.Add("optorName", "管理員"); //組裝dataset數據源
DataTable dtDetail = new DataTable("dtDetail"); dtDetail.Columns.Add("GOODSCODE"); dtDetail.Columns.Add("GOODSNAME"); dtDetail.Columns.Add("GOODSPRICE"); dtDetail.Columns.Add("GOODSCOUNT"); dtDetail.Columns.Add("PAYSUM"); //加10種商品
for (int i = 1; i <= 10; i++) { dtDetail.Rows.Add("10000" + 1, "測試商品" + i, 10.00m, 5, 50.00m); } DataSet dsFrx = new DataSet(); dsFrx.Tables.Add(dtDetail); return new Tuple<bool, Dictionary<string, object>, DataSet>(true, dic, dsFrx); }
運行,在設計器里面也可以預覽,看下最終打印效果,如圖15
圖15
四、總結
使用這個做打印模板還是比較方便的,在套打情況下要頻繁調整界面布局,使用這種可視化的界面操作方便。經常客戶自己就可以自定義調整。不需要程序做任何修改。
這個FastReport.Net的具體使用方法可以查看網上資料,我這里主要是作為打印模板來用。很多細節以及用法就沒展開細講。
因為FastReport是商業軟件。支持軟件版權。針對商業版權問題,FastReport提供了開源版本,在nuget就可以直接引用,作為報表功能有刪減,但針對打印功能完全夠用了。
感謝閱讀,希望這篇文章能給你帶來幫助!