备忘录:基于rdlc报表实现打印产品标签

志铭-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. 参考

posted @ 2022-01-17 08:38  shanzm  阅读(1368)  评论(0编辑  收藏  举报
TOP