備忘錄——基於rdlc報表實現列印產品標籤

shanzm發表於2022-01-17

志銘-2022年1月13日 21:40:39

0. 背景說明

  • 產品紙箱 包裝箱需要貼一張箱標,標註產品的如下一些資訊

    • 產品檢驗資訊
    • 產品公司資訊
    • 產品SKU集小程式商城二維碼連結
  • 最終測試Demo效果

    最終效果



1. 條形碼生成

使用ZXing.NET 生成產品的批次和SKU的條形碼,簡單的封裝一個輔助類用於建立條形碼

   using ZXing;
   using ZXing.Common;

   public static class BarCodeHelper
   {
       /// <summary>
       /// 建立條形碼
       /// </summary>
       /// <param name="barCodeNo">條碼</param>
       /// <param name="height">高度</param>
       /// <param name="width">寬度</param>
       /// <returns>圖片位元組陣列</returns>
       public static byte[] CreateBarCode(string barCodeNo, int height = 120, int width = 310)
       {
           EncodingOptions encoding = new EncodingOptions()
           {
               Height = height,
               Width = width,
               Margin = 1,
               PureBarcode = false//在條碼下顯示條碼,true則不顯示
           };
           BarcodeWriter wr = new BarcodeWriter()
           {
               Options = encoding,
               Format = BarcodeFormat.CODE_128,

           };
           Bitmap img = wr.Write(barCodeNo);

           //儲存在當前專案路徑下
           // string filepath = AppDomain.CurrentDomain.BaseDirectory + "\\" + barCodeNo + ".jpg";
           // img.Save(filepath, ImageFormat.Jpeg);

           return BitmapToBytes(img);
       }

       /// <summary>
       /// 點陣圖轉為位元組陣列
       /// </summary>
       /// <param name="bitmap">點陣圖</param>
       /// <returns></returns>

       private static byte[] BitmapToBytes(Bitmap bitmap)
       {
           using (MemoryStream ms = new MemoryStream())
           {
               bitmap.Save(ms, ImageFormat.Gif);
               byte[] byteImage = new byte[ms.Length];
               byteImage = ms.ToArray();
               return byteImage;
           }
       }

   }


2. 獲取產品的小程式碼

  • 根據當前產品的SKU,動態的獲取當前產品的微信小程式商城中頁面的小程式碼

  • 獲取小程式碼,適用於需要的碼數量極多的業務場景。通過該介面生成的小程式碼,永久有效,數量暫無限制

    • POST https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=ACCESS_TOKEN
    • 具體引數及返回值參考:微信小程式介面文件
  • WebRequest傳送POST請求到微信小程式介面,接受返回的圖片butter

    /// <summary>
    /// 傳送http Post請求圖片
    /// </summary>
    /// <param name="url">請求地址</param>
    /// <param name="messsage">請求引數</param>
    /// <returns>圖片的位元組陣列</returns>
    public static byte[] PostRequestReturnImage(string url, string messsage)
    {
        byte[] byteData = Encoding.UTF8.GetBytes(messsage);
        HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(url);
        webRequest.Method = "POST";
        webRequest.ContentType = "image/jpeg";
        webRequest.ContentLength = byteData.Length;
        webRequest.AddRange(0, 10000000);
        using (Stream stream = webRequest.GetRequestStream())
        {
            stream.Write(byteData, 0, byteData.Length);
        }
        using (Stream stream = webRequest.GetResponse().GetResponseStream())
        {
            using (BinaryReader br = new BinaryReader(stream))
            {
                byte[] butter = br.ReadBytes(10000000);
                return butter;
            }
        }
    }
    


3. 報表設計器設計標籤模版

3.1 為WinForm控制元件工具箱新增ReportViewer控制元件

  • NuGet:PM>Install-Package Microsoft.ReportingServices.ReportViewerControl.WinForms -Pre

    • 注意ReportView有許多依賴,執行上述命令執行安裝,不要在在NuGet管理介面搜尋安裝,減少不必要的麻煩
  • 安裝後,在winform工具箱 Micsoft SQL Server選項卡下有ReportView 控制元件

3.2 為VS2019安裝RDLC報表專案模版

使用的VS2019預設沒有報表專案,需要安裝擴充套件:Microsoft Reporting Designer

  • 擴充套件-->管理擴充套件-->聯機搜尋:Microsoft Rdlc Report Designer

  • 之後右鍵專案-->新增 會顯示:報表

3.3 建立報表檔案

  • 建立報表檔案MyReport.rdlc,注意事項

    • 開啟報表檔案,會自動顯示報表資料視窗,沒有在重新開啟VS
    • 報表資料視窗-->資料集-->新增資料集
      • 定義資料物件:ReportModel類
      • 設定資料來源名稱:ReportModelObject
      • 資料來源:選擇物件ReportModel
  • 佈局:我使用列表佈局,右鍵插入列表

  • 資料:繫結資料來源ReportModelObject的欄位

  • 關於影像:首先右鍵插入影像,影像屬性設定:

    • 工具提示:value=System.Convert.FromBase64String(Fields!Image1.Value)
    • 資料來源:資料庫
    • 使用欄位:ReportModel中的影像欄位,比如我這裡是Image1欄位(string 型別)
    • MIME型別:image/jpeg

3.4 ReportView初始化

    private void InitReport()
    {
        myReportViewer.LocalReport.ReportPath = "MyReport.rdlc";//報表檔名稱
        ReportDataSource rds = new ReportDataSource
        {
            Name = "ReportModelObject",
            Value = GetDataSource()
        };
        myReportViewer.LocalReport.DataSources.Add(rds);
        myReportViewer.RefreshReport();
    }

    /// <summary>
    /// 測試使用的資料來源
    /// </summary>
    /// <returns></returns>
    private List<ReportModel> GetDataSource()
    {
        return new List<ReportModel>()
            {
                new ReportModel()
                {
                    //這裡就是將圖片的位元組陣列轉為字串,從而實現繫結在報表中
                    Image1=Convert.ToBase64String(BarCodeHelper.CreateBarCode("123456789012")),
                }
            };
    }


4. 直接列印ReportView中報表,不要彈出選擇印表機視窗

ViewReport控制元件上自帶的列印按鈕,點選會彈出選擇印表機的視窗,不希望如此

在配置檔案中設定預設印表機

<configuration>
	<appSettings >
		<add key="printer" value ="印表機名稱"/>
	</appSettings>	
</configuration>

封裝一個單獨列印的輔助類,這個解放方法非我原創,是參考博文空白畫映:C# WinForm RDLC報表不預覽直接連續列印

    public class PrintHelper
    {
        /// <summary>
        /// 用來記錄當前列印到第幾頁了
        /// </summary>
        private int m_currentPageIndex;

        /// <summary>
        /// 宣告一個Stream物件的列表用來儲存報表的輸出資料,LocalReport物件的Render方法會將報表按頁輸出為多個Stream物件。
        /// </summary>
        private IList<Stream> m_streams;

        private bool isLandSapces = false;

        /// <summary>
        /// 用來提供Stream物件的函式,用於LocalReport物件的Render方法的第三個引數。
        /// </summary>
        /// <param name="name"></param>
        /// <param name="fileNameExtension"></param>
        /// <param name="encoding"></param>
        /// <param name="mimeType"></param>
        /// <param name="willSeek"></param>
        /// <returns></returns>
        private Stream CreateStream(string name, string fileNameExtension, Encoding encoding, string mimeType, bool willSeek)
        {
            //如果需要將報表輸出的資料儲存為檔案,請使用FileStream物件。
            Stream stream = new MemoryStream();
            m_streams.Add(stream);
            return stream;
        }

        /// <summary>
        /// 為Report.rdlc建立本地報告載入資料,輸出報告到.emf檔案,並列印,同時釋放資源
        /// </summary>
        /// <param name="rv">引數:ReportViewer.LocalReport</param>
        public void PrintStream(LocalReport rvDoc)
        {
            //獲取LocalReport中的報表頁面方向
            isLandSapces = rvDoc.GetDefaultPageSettings().IsLandscape;
            Export(rvDoc);
            PrintSetting();
            Dispose();
        }

        private void Export(LocalReport report)
        {
            string deviceInfo =
            @"<DeviceInfo>
                 <OutputFormat>EMF</OutputFormat>
             </DeviceInfo>";
            m_streams = new List<Stream>();
            //將報表的內容按照deviceInfo指定的格式輸出到CreateStream函式提供的Stream中。
            report.Render("Image", deviceInfo, CreateStream, out Warning[] warnings);
            foreach (Stream stream in m_streams)
            {
                stream.Position = 0;
            }
        }

        private void PrintSetting()
        {
            if (m_streams == null || m_streams.Count == 0)
            {
                throw new Exception("錯誤:沒有檢測到列印資料流");
            }
            //宣告PrintDocument物件用於資料的列印
            PrintDocument printDoc = new PrintDocument();
            //獲取配置檔案的清單印表機名稱
            System.Configuration.AppSettingsReader appSettings = new System.Configuration.AppSettingsReader();
            printDoc.PrinterSettings.PrinterName = appSettings.GetValue("printer", Type.GetType("System.String")).ToString();
            printDoc.PrintController = new StandardPrintController();//指定印表機不顯示頁碼 
            //判斷指定的印表機是否可用
            if (!printDoc.PrinterSettings.IsValid)
            {
                throw new Exception("錯誤:找不到印表機");
            }
            else
            {
                //設定印表機方向遵從報表方向
                printDoc.DefaultPageSettings.Landscape = isLandSapces;
                //宣告PrintDocument物件的PrintPage事件,具體的列印操作需要在這個事件中處理。
                printDoc.PrintPage += new PrintPageEventHandler(PrintPage);
                m_currentPageIndex = 0;
                //設定印表機列印份數
                printDoc.PrinterSettings.Copies = 1;
                //執行列印操作,Print方法將觸發PrintPage事件。
                printDoc.Print();
            }
        }

        /// <summary>
        /// 處理程式PrintPageEvents
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="ev"></param>
        private void PrintPage(object sender, PrintPageEventArgs ev)
        {
            //Metafile物件用來儲存EMF或WMF格式的圖形,
            //我們在前面將報表的內容輸出為EMF圖形格式的資料流。
            Metafile pageImage = new Metafile(m_streams[m_currentPageIndex]);

            //調整印表機區域的邊距
            System.Drawing.Rectangle adjustedRect = new System.Drawing.Rectangle(
                ev.PageBounds.Left - (int)ev.PageSettings.HardMarginX,
                ev.PageBounds.Top - (int)ev.PageSettings.HardMarginY,
                ev.PageBounds.Width,
                ev.PageBounds.Height);

            //繪製一個白色背景的報告
            //ev.Graphics.FillRectangle(Brushes.White, adjustedRect);

            //獲取報告內容
            //這裡的Graphics物件實際指向了印表機
            ev.Graphics.DrawImage(pageImage, adjustedRect);
            //ev.Graphics.DrawImage(pageImage, ev.PageBounds);

            // 準備下一個頁,已確定操作尚未結束
            m_currentPageIndex++;

            //設定是否需要繼續列印
            ev.HasMorePages = (m_currentPageIndex < m_streams.Count);
        }

        public void Dispose()
        {
            if (m_streams != null)
            {
                foreach (Stream stream in m_streams)
                {
                    stream.Close();
                }

                m_streams = null;
            }
        }
    }

自定義列印按鈕:btnPrint,新增其點選事件

    private void btnPrint_Click(object sender, EventArgs e)
    {
        PrintHelper printHelper = new PrintHelper();
        printHelper.PrintStream(myReportViewer.LocalReport);//myReportViewer是當前的ReportViewk控制元件名稱
    }


5. 參考

相關文章