重複造輪子系列——基於FastReport設計列印模板實現桌面端WPF套打和商超POS高度自適應小票列印

sylla發表於2019-07-29

重複造輪子系列——基於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就可以直接引用,作為報表功能有刪減,但針對列印功能完全夠用了。

 

demo下載

感謝閱讀,希望這篇文章能給你帶來幫助!

相關文章