.Net C# 打印功能学习笔记

参考文档地址:https://blog.csdn.net/dzweather/article/details/10171105

目标:打印一些复杂度较低的页面,使用.Net自带的类解决。

 

一、打印文件类:class PrintDocument

首先创建一个打印文件实例:var doc = new PrintDocument();

用于输出到打印机的打印文件内容,包含打印设置、所有绘制好的页面、以及页面设置。

当所有下述设计工作完成后,可以使用doc.Print()方法进行打印,也可以让用户自行在打印预览窗口里进行打印。

 

二、打印设置类:class PrinterSettings

可以让用户使用class PrintDialog进行设置,也可以在代码里设定,实现自动化打印。

a) 使用PrintDialog:

        /// <summary>
        /// 进行打印机设置
        /// </summary>
        public DialogResult PrinterSetting(Form owner = null)
        {
            var pd = new PrintDialog();
            pd.Document = doc;
            pd.AllowSomePages = true;
            pd.AllowCurrentPage = true;
            pd.AllowSelection = true;
            DialogResult r;
            if (owner == null)
                r = pd.ShowDialog();
            else
                r = pd.ShowDialog(owner);
            if (r != DialogResult.OK)
                OnCancel();
            return r;
        }

用户点击确认后,会更新doc.PrinterSettings引用的PrinterSettings实例;否则引发OnCancel事件。

b) 使用代码设定:

var ps = doc.PrinterSettings;

ps.PrinterName = "..."; //打印机名称

//其他设置...

 

三、页面设置类:class PaperSettings

同样也可以使用两种方式来进行设置。

a) 使用PageSetupDialog:

        /// <summary>
        /// 进行页面设置
        /// </summary>
        public DialogResult PaperSetting(Form owner = null)
        {
            var ps = new PageSetupDialog();
            ps.Document = doc;
            ps.AllowPrinter = true;
            ps.EnableMetric = true;
            ps.AllowPaper = true;
            ps.AllowMargins = true;
            DialogResult r;
            if (owner == null)
                r = ps.ShowDialog();
            else
                r = ps.ShowDialog(owner);
            if (r != DialogResult.OK)
                OnCancel();
            return r;
        }

用户点击确认后,会更新doc.DefaultPageSettings引用的PageSettings实例;否则会引发OnCancel事件。

b) 使用代码设定:

var pas = doc.DefaultPageSettings;

pas.Margins.Left = 39;//左页边距,单位百分之一英寸

 //其他设置...

 

四、打印预览类:class PrintPreviewDialog

 .Net自带的打印预览窗口比较简陋,尤其不能忍的是上一页、下一页太难点了,这里我增加了两个用于切换页的按钮。

要注意的是,这个页不是打印时的页,只是在预览窗体里显示的页,也就是说,页数取决于打印总页数n和当前窗口预览页数m,那么预览的总页数就是n/m。比如总共6页,同时预览2页,那么打印预览的页就是共3页。

同时增加页面设置、打印机设置两个按钮,允许用户在查看打印预览时,修改打印设置。

        /// <summary>
        /// 进行打印预览
        /// </summary>
        public void Preview(Form owner = null)
        {
            var pd = new PrintPreviewDialog();
            PreviewAddButton(pd);
            pd.Document = doc;
            if (owner == null)
                pd.ShowDialog();
            else
                pd.ShowDialog(owner);
        }
        /// <summary>
        /// 为打印预览窗口增加上一页、下一页、页面设置、打印机设置按钮
        /// </summary>
        /// <param name="pd"></param>
        private void PreviewAddButton(PrintPreviewDialog pd)
        {
            var toolstrip = pd.Controls.OfType<ToolStrip>().First();
            var next = new ToolStripButton();
            next.Text = "下一页";
            next.Click += new EventHandler((x, y) =>
              {
                  var a = pd.PrintPreviewControl.StartPage;
                  pd.PrintPreviewControl.StartPage++;
                  if (pd.PrintPreviewControl.StartPage == a)
                      MessageBox.Show("已经是最后一页啦!", "没有下一页", MessageBoxButtons.OK, MessageBoxIcon.Information);
              });
            var back = new ToolStripButton();
            back.Text = "上一页";
            back.Click += new EventHandler((x, y) =>
              {
                  var a = pd.PrintPreviewControl.StartPage - 1;
                  if (a >= 0)
                      pd.PrintPreviewControl.StartPage = a;
                  else
                      MessageBox.Show("已经是第一页啦!", "没有上一页", MessageBoxButtons.OK, MessageBoxIcon.Information);
              });
            var paperSet = new ToolStripButton();
            paperSet.Text = "页面设置";
            paperSet.Click += new EventHandler((x, y) =>
             {
                 var r = PaperSetting();
                 if (r == DialogResult.OK)//点击确认后更新打印预览
                     pd.PrintPreviewControl.InvalidatePreview();
             });
            var printSet = new ToolStripButton();
            printSet.Text = "打印机设置";
            printSet.Click += new EventHandler((x, y) =>
              {
                  var r = PrinterSetting();
                  if (r == DialogResult.OK)//点击确认后更新打印预览
                      pd.PrintPreviewControl.InvalidatePreview();
              });
            toolstrip.Items.Add(back);
            toolstrip.Items.Add(next);
            toolstrip.Items.Add(paperSet);
            toolstrip.Items.Add(printSet);
            pd.PrintPreviewControl.MouseWheel += PrintPreview_MouseWheel;//可以使用鼠标滚轮翻页
        }
        /// <summary>
        /// 打印预览区域鼠标滚轮事件订阅器
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void PrintPreview_MouseWheel(object sender, MouseEventArgs e)
        {
            if (sender is PrintPreviewControl ct)
            {
                if (e.Delta > 0)
                {
                    var a = ct.StartPage - 1;
                    if (a >= 0)
                        ct.StartPage = a;
                }
                else
                {
                    ct.StartPage++;
                }
            }
        }

 

五、页面的打印/打印预览事件:PrintPageEventHandler PrintPage(object sender, PrintPageEventArgs e)

在这个事件的订阅器中,编写打印输出页面绘制的代码。

下面这个是我的条码打印页面绘制方法:

        /// <summary>
        /// 打印条码时的每页打印方法
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Barcode_PrintPage(object sender, PrintPageEventArgs e)
        {
            //当前页的二维码Image
            var ncode = BarcodeList[NowPage - 1];
            //可打印区域的左边界坐标
            int left = e.MarginBounds.Left;
            //可打印区域的右边界坐标
            int right = e.MarginBounds.Right;
            //可打印区域的上边界坐标
            int top = e.MarginBounds.Top;
            //可打印区域的下边界坐标
            int bottom = e.MarginBounds.Bottom;
            //可打印区域的总高度
            int height = bottom - top;
            //可打印区域的总宽度
            int width = right - left;
            //页眉高度
            int headerHeight = Convert.ToInt32(Math.Ceiling(HeaderFont.GetHeight(e.Graphics)));
            //页脚高度
            int footerHeight = Convert.ToInt32(Math.Ceiling(FooterFont.GetHeight(e.Graphics)));
            //创建页眉矩形区域
            var headerR = new Rectangle(left, top, width, headerHeight);
            //创建主体矩形区域
            var bodyR = new Rectangle(left, headerR.Bottom, width, height - headerR.Height - footerHeight);
            //创建页脚矩形区域
            var footerR = new Rectangle(left, bodyR.Bottom, width, footerHeight);
            //绘制三个区域的边框
            e.Graphics.DrawRectangles(Pens.Black, new Rectangle[] { headerR, bodyR, footerR });
            //绘制页眉文字
            CenterText(e.Graphics, HeaderText[NowPage - 1], HeaderFont, Brushes.Black, headerR);
            //绘制页脚文字
            CenterText(e.Graphics, FooterText[NowPage - 1], FooterFont, Brushes.Black, footerR);
            //原始二维码宽度
            var cwidth = ncode.Width;
            //原始二维码高度
            var cheight = ncode.Height;
            //缩放后的二维码宽度
            var crwidth = width - MillimetertoCentinch(20f);
            //缩放后的二维码高度
            var crheight = Convert.ToInt32(Math.Ceiling(crwidth * 1.0 / cwidth * cheight));
            //二维码绘制矩形区域,控制其与主体的边距为10mm,并等比例缩放
            var codeR = new Rectangle(left + MillimetertoCentinch(10f), bodyR.Top + Convert.ToInt32(Math.Ceiling((bodyR.Height - crheight) * 1.0 / 2)), crwidth, crheight);
            //绘制二维码
            e.Graphics.DrawImage(ncode, codeR);
            //页码计数加1
            NowPage++;
            //判断是否还有页
            if (NowPage > TotalPage)
                e.HasMorePages = false;
            else
                e.HasMorePages = true;
        }
      /// <summary>
      /// 在指定绘图区域的指定矩形区域绘制居中的文本
      /// </summary>
      /// <param name="g">绘图区域</param>
      /// <param name="t">要绘制的字符串</param>
      /// <param name="f">字体对象</param>
      /// <param name="b">填充对象</param>
      /// <param name="rect">矩形区域对象</param>
      private void CenterText(Graphics g, string t, Font f, Brush b, RectangleF rect)
      {
        var sf = new StringFormat();
        sf.Alignment = StringAlignment.Center;
        g.DrawString(t, f, b, rect, sf);
      }
        /// <summary>
        /// 毫米转英寸
        /// </summary>
        /// <param name="millimeter">毫米</param>
        /// <returns></returns>
        public float MillimetertoInch(float millimeter)
        {
            return millimeter / 25.4f;
        }
        /// <summary>
        /// 毫米转百分之一英寸
        /// </summary>
        /// <param name="millimeter">毫米</param>
        /// <returns></returns>
        public int MillimetertoCentinch(float millimeter)
        {
            return Convert.ToInt32(MillimetertoInch(millimeter) * 100f);
        }

当然了,需要先注册这个订阅器:

doc.PrintPage += Barcode_PrintPage;

系统在打印/打印预览时,会一直调用这个方法,直到e.HasMorePages为False。

这里的NowPage和TotalPage,是写在类级别的公共属性,用于计算当前页和总页数。

NowPage需要在doc.BeginPrint事件的订阅器里设为1:

        doc.BeginPrint += Begin_Print;

        /// <summary>
        /// 开始打印事件订阅器
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Begin_Print(object sender, PrintEventArgs e)
        {
            NowPage = 1;
        }            

TotalPage则是在类的构造函数里计算,例如:

class MyPrinter
{
        public delegate void PrintCancelHandler(); public event PrintCancelHandler OnCancel;
        /// <summary>
        /// 打印主对象
        /// </summary>
        public PrintDocument doc { get; set; }
        /// <summary>
        /// 条码List
        /// </summary>
        List<Image> _barcodes;
        /// <summary>
        /// 条码List
        /// </summary>
        public List<Image> BarcodeList
        {
            get
            {
                return _barcodes;
            }
            set
            {
                _barcodes = value;
            }
        }
        /// <summary>
        /// 总页数
        /// </summary>
        public int TotalPage { get; set; }
        /// <summary>
        /// 当前打印页数
        /// </summary>
        public int NowPage { get; set; }
        /// <summary>
        /// 页眉字体
        /// </summary>
        public Font HeaderFont { get; set; }
        /// <summary>
        /// 页脚字体
        /// </summary>
        public Font FooterFont { get; set; }
        /// <summary>
        /// 主体字体
        /// </summary>
        public Font BodyFont { get; set; }
        /// <summary>
        /// 页眉文本
        /// </summary>
        public List<string> HeaderText { get; set; }
        /// <summary>
        /// 页脚文本
        /// </summary>
        public List<string> FooterText { get; set; }
        
        public MyPrinter(List<Image> barcodes)
        {
            doc = new PrintDocument();
            BarcodeList = barcodes;
            //条码总页数
            TotalPage = BarcodeList.Count;
            //注册条码打印的订阅器
            doc.PrintPage += Barcode_PrintPage;
            doc.BeginPrint += Begin_Print; this.OnCancel += DefaultCancel;
            HeaderText = new List<string>();
            FooterText = new List<string>();
            for (int i = 0; i < TotalPage; i++)
            {
                //设置页眉、页脚文本
            }
            //页眉页脚字体
            HeaderFont = new Font("微软雅黑", 9);
            FooterFont = new Font("微软雅黑", 9);
        }
        protected virtual void DefaultCancel()
        { }
}

可以使用每页的Graphics对象,对当前页进行任意的绘制,DrawString,DrawImage,等等,.Net GDI+的使用网上资源很多,这里就不展开了。

 

总之,使用.Net打印的过程,过程是:详细设置=>绘制页面=>预览/打印。

其中绘制页面的动作发生在预览/打印动作发生之时。

 

如果需要做一些复杂的报表,当然还是水晶报表之类的扩展更为快捷。

posted @ 2020-08-25 16:12  zeahon  阅读(1377)  评论(0)    收藏  举报