C# 收银机打印

前两天领导说不用web端的打印插件(C-Lodop),想用C#来实现,让我研究一下调用打印机的方法,就有了这篇总结

.NET Core或.NET 5的话要引用一下NuGet包 System.Drawing.Common

获取打印机列表

PrinterSettings.InstalledPrinters.Cast<string>();

定义打印接口

public interface IPrinter
{
  /// <summary>
  /// 设置页面大小
  /// </summary>
  /// <param name="paperWidth"></param>
  /// <param name="paperHight"></param>
  void SetPageSize(double paperWidth, int? paperHight);

  /// <summary>
  /// 打印文字
  /// </summary>
  /// <param name="content"></param>
  /// <param name="fontSize"></param>
  /// <param name="stringAlignment"></param>
  /// <param name="width"></param>
  /// <param name="offset"></param>
  void PrintText(
      string content,
      FontSize fontSize = FontSize.Normal,
      StringAlignment stringAlignment = StringAlignment.Near,
      float width = 1,
      float offset = 0
      );

  /// <summary>
  /// 打印图片
  /// </summary>
  /// <param name="image"></param>
  /// <param name="stringAlignment"></param>
  void PrintImage(Image image, StringAlignment stringAlignment = StringAlignment.Near);

  /// <summary>
  /// 打印一行
  /// </summary>
  void PrintSolidLine();

  /// <summary>
  /// 打印一行虚线
  /// </summary>
  /// <param name="fontSize"></param>
  void PrintDottedLine();

  /// <summary>
  /// 新起一行
  /// </summary>
  void NewLine();

  /// <summary>
  /// 开始打印
  /// </summary>
  void Print();
}

打印接口的实现

public class Printer : IPrinter
    {

        #region fields
        private PrintDocument _printDoc = new PrintDocument();

        /// <summary>
        /// 打印对象打印宽度(根据英寸换算而来,paperWidth * 3.937)
        /// </summary>
        private int _paperWidth;
        private int _paperHight;
        private const float _charProportion = 0.7352f;
        private const float _lineHeightProportion = 1.6f;
        private const string _fontName = "SimHei";
        private int _printIndex = 0;
        /// <summary>
        /// 打印模式:1、自动分页模式  2、连续不分页模式
        /// </summary>
        private int _mode;
        private IList<Action<Graphics>> _printActions = new List<Action<Graphics>>();

        /// <summary>
        /// 当前的打印高度,当调用换行或者图片打印时会增加此字段值
        /// </summary>
        private float _currentHeight = 0;

        public float NewLineOffset { get; set; } = (int)FontSize.Normal * _lineHeightProportion;

        #endregion

        #region ctor

        /// <summary>
        /// 初始化机打印对象
        /// </summary>
        /// <param name="printerName">打印机名称</param>
        /// <param name="paperWidth">打印纸宽度</param>
        /// <param name="paperHight">打印纸高度</param>
        internal Printer(string printerName)
        {
            _printDoc.PrinterSettings.PrinterName = printerName;
            _printDoc.PrintPage += PrintPageDetails;

            //默认为58mm
            SetPageSize(58, null);            

            _printDoc.PrintController = new StandardPrintController();
        }


        #endregion

        #region eventHandler
        void PrintPageDetails(object sender, PrintPageEventArgs e)
        {
            while (_printIndex < _printActions.Count)
            {
                var originHeight = _currentHeight;
                _printActions[_printIndex](e.Graphics);
                _printIndex++;
                if (_currentHeight > e.PageBounds.Height - 20)
                {
                    if (_mode == 1)
                    {
                        _currentHeight = 0;
                        // HasMorePages 设置为true后,会继续触发PrintPage,因此_printIndex需要在外部进行定义
                        e.HasMorePages = true;
                        return;
                    }
                }
            }

            e.HasMorePages = false;
        }
        #endregion

        #region IPrinterImplement

        /// <summary>
        /// 设置页面大小
        /// </summary>
        /// <param name="paperWidth"></param>
        /// <param name="paperHight"></param>
        public void SetPageSize(double paperWidth, int? paperHight)
        {
            switch (paperWidth)
            {
                case 80:
                    //80打印纸扣去两边内距实际可打的宽度为72.1
                    paperWidth = 72.1;
                    break;
                case 76:
                    //76打印纸扣去两边内距实际可打的宽度为63.5
                    paperWidth = 63.5;
                    break;
                case 58:
                    //58打印纸扣去两边内距实际可打的宽度为48
                    paperWidth = 48;
                    break;
                default:
                    paperWidth = paperWidth - 10;
                    break;
            }

            _paperWidth = Convert.ToInt32(Math.Ceiling(paperWidth * 3.937));
            _mode = 1;
            if (!paperHight.HasValue)
            {
                paperHight = 297;
                _mode = 2; // 设置为连续不分页模式
            }

            _paperHight = Convert.ToInt32(Math.Ceiling(paperHight.Value * 3.937));
            _printDoc.DefaultPageSettings.PaperSize = new PaperSize("", _paperWidth, _paperHight);
        }

        /// <summary>
        /// 开始打印
        /// </summary>
        public void Print()
        {
            _printIndex = 0;
            _currentHeight = 0;
            _printDoc.EndPrint += (_, _) => Console.WriteLine("打印完成!");
            if (_mode == 2)
            {
                // 通过下面的方式计算出实际页面高度
                using (Bitmap img = new Bitmap(_paperWidth, _paperHight))
                {
                    var g = Graphics.FromImage(img);
                    foreach (var item in _printActions)
                    {
                        item(g);
                    }
                    _paperHight = Convert.ToInt32(Math.Ceiling(_currentHeight)) + 5;
                    _printDoc.DefaultPageSettings.PaperSize = new PaperSize("", _paperWidth, _paperHight);
                    _printIndex = 0;
                    _currentHeight = 0;
                }
            }
            _printDoc.Print();
            _printDoc.Dispose();
            _printDoc = new PrintDocument();
            _printActions.Clear();
        }

        /// <summary>
        /// 新起一行
        /// </summary>
        public void NewLine()
        {
            _printActions.Add(g =>
            {
                _currentHeight += NewLineOffset;
                NewLineOffset = (int)FontSize.Normal * _lineHeightProportion;
            });
        }

        /// <summary>
        /// 打印文字
        /// </summary>
        /// <param name="content"></param>
        /// <param name="fontSize"></param>
        /// <param name="alignment"></param>
        /// <param name="width"></param>
        /// <param name="offset"></param>
        public void PrintText(string content, FontSize fontSize = FontSize.Normal, StringAlignment alignment = StringAlignment.Near, float width = 1, float offset = 0)
        {
            _printActions.Add(g =>
            {
                float contentWidth = width == 1 ? _paperWidth * (1 - offset) : width * _paperWidth;
                string newContent = ContentWarp(content, fontSize, contentWidth, out var rowNum);
                var font = new Font(_fontName, (int)fontSize, FontStyle.Regular);
                var point = new PointF(offset * _paperWidth, _currentHeight);
                var size = new SizeF(contentWidth, (int)fontSize * _lineHeightProportion * rowNum);
                var layoutRectangle = new RectangleF(point, size);
                var format = new StringFormat
                {
                    Alignment = alignment,
                    FormatFlags = StringFormatFlags.NoWrap
                };
                g.DrawString(newContent, font, Brushes.Black, layoutRectangle, format);
                float thisHeightOffset = rowNum * (int)fontSize * _lineHeightProportion;
                if (thisHeightOffset > NewLineOffset) NewLineOffset = thisHeightOffset;
            });
        }

        /// <summary>
        /// 打印图片
        /// </summary>
        /// <param name="image"></param>
        /// <param name="stringAlignment"></param>
        public void PrintImage(Image image, StringAlignment stringAlignment = StringAlignment.Near)
        {
            _printActions.Add(g =>
            {
                int x = 0;
                switch (stringAlignment)
                {
                    case StringAlignment.Near:
                        break;
                    case StringAlignment.Center:
                        x = (_paperWidth - image.Width) / 2;
                        break;
                    case StringAlignment.Far:
                        x = _paperWidth - image.Width;
                        break;
                    default:
                        break;
                }
                var point = new Point(x, Convert.ToInt32(_currentHeight));
                var size = new Size(image.Width, image.Height);
                var rectangle = new Rectangle(point, size);
                g.DrawImage(image, rectangle);
                NewLineOffset = image.Height;
            });
        }

        /// <summary>
        /// 打印一行实线
        /// </summary>
        public void PrintSolidLine()
        {
            _printActions.Add(g =>
            {
                var pen = new Pen(new SolidBrush(Color.Black));
                pen.Width = 1f;
                g.DrawLine(pen, new Point(0, Convert.ToInt32(Math.Ceiling(_currentHeight))), new Point(_paperWidth, Convert.ToInt32(Math.Ceiling(_currentHeight))));
                _currentHeight += 3;
            });
        }

        /// <summary>
        /// 打印一行虚线
        /// </summary>
        public void PrintDottedLine()
        {
            var fontSize = FontSize.Normal;
            int charNum = (int)(_paperWidth / ((int)fontSize * _charProportion));
            var builder = new StringBuilder();
            for (int i = 0; i < charNum; i++)
            {
                builder.Append('-');
            }
            PrintText(builder.ToString(), fontSize, StringAlignment.Center);
        }
        #endregion

        #region methods
        /// <summary>
        /// 对内容进行分行,并返回行数
        /// </summary>
        /// <param name="content">内容</param>
        /// <param name="fontSize">文字大小</param>
        /// <param name="width">内容区宽度</param>
        /// <returns>行数</returns>
        private static string ContentWarp(string content, FontSize fontSize, float width, out int row)
        {
            content = content.Replace(Environment.NewLine, string.Empty);

            //0.7282 字符比例
            var builder = new StringBuilder();
            float nowWidth = 0;
            row = 1;
            foreach (char item in content)
            {
                int code = Convert.ToInt32(item);
                float charWidth = code < 128 ? _charProportion * (int)fontSize : _charProportion * (int)fontSize * 2;
                nowWidth += charWidth;
                if (nowWidth > width)
                {
                    builder.Append(Environment.NewLine);
                    nowWidth = charWidth;
                    row++;
                }
                builder.Append(item);
            }
            return builder.ToString();
        }

        #endregion
    }

创建打印机的工厂类

public static class PrinterFactory
{
    public static IEnumerable<string> GetAllPrints()
    {
        return PrinterSettings.InstalledPrinters.Cast<string>();
    }

    public static Printer GetPrinter(string printerName)
    {
        if (string.IsNullOrEmpty(printerName)) throw new ArgumentException(nameof(printerName));
        return new Printer(printerName);
    }
}

使用

static void Main(string[] args)
{
    // Microsoft XPS Document Writer 是测试时使用的,实际使用中,要替换成真正的打印机
    var printer = PrinterFactory.GetPrinter("Microsoft XPS Document Writer");
    printer.SetPageSize(80, null);
    printer.NewLine();
    var img = GetLogo();
    printer.PrintImage(img, StringAlignment.Center);
    printer.NewLine();
    printer.NewLine();
    printer.PrintText("永辉超市", FontSize.Large, alignment: StringAlignment.Center);
    printer.NewLine();
    printer.NewLine();
    printer.PrintText("单号:XD000269");
    printer.PrintText("流水号:000269", offset: 0.5f);
    printer.NewLine();
    printer.PrintText("收银员:***");
    printer.PrintText("日期:" + DateTime.Now.ToString("yyyy/MM/dd"), offset: 0.5f);
    printer.NewLine();
    printer.PrintText("VIP客户卡号:001");
    printer.NewLine();
    printer.PrintSolidLine();
    printer.NewLine();
    printer.PrintText("名称");
    printer.PrintText("单价", offset: 0.35f);
    printer.PrintText("数量", offset: 0.65f);
    printer.PrintText("金额", alignment: StringAlignment.Far);
    printer.NewLine();
    printer.PrintText("芹菜", width: 0.35f);
    printer.PrintText("2.9", width: 0.2f, offset: 0.35f);
    printer.PrintText("1", width: 0.2f, offset: 0.65F);
    printer.PrintText("2.9", alignment: StringAlignment.Far);
    printer.NewLine();
    printer.PrintDottedLine();
    printer.NewLine();
    printer.PrintText("合计");
    printer.PrintText("1", offset: 0.65f);
    printer.PrintText("2.90", alignment: StringAlignment.Far);
    printer.NewLine();
    printer.PrintText("满0.00减0.00折扣");
    printer.PrintText("-0.00", alignment: StringAlignment.Far);
    printer.NewLine();
    printer.PrintText("优惠金额:2.90");
    printer.PrintText("实收金额:0", offset: 0.5f);
    printer.NewLine();
    printer.PrintText("收款金额:0.00");
    printer.PrintText("找零金额:-2.90", offset: 0.5f);
    printer.NewLine();
    printer.PrintDottedLine();
    printer.NewLine();
    printer.PrintText("会员卡:001");
    printer.NewLine();
    printer.PrintText("本次积分:");
    printer.PrintText("会员余额:43.87", offset: 0.5f);
    printer.NewLine();
    printer.PrintText("可用积分:");
    printer.NewLine();
    printer.PrintSolidLine();
    printer.NewLine();
    printer.PrintText("永辉超市", FontSize.Large, alignment: StringAlignment.Center);
    printer.NewLine();
    printer.PrintText("欢迎光临,谢谢惠顾!", FontSize.Large, alignment: StringAlignment.Center);
    printer.NewLine();

    printer.Print();
    GC.Collect();
    
    Console.ReadKey();
}

private static Image GetLogo()
{
    var path = Path.Combine(Directory.GetCurrentDirectory(), "logo.jpg");
    using (var fileStream = File.OpenRead(path))
    {
        fileStream.Seek(0, SeekOrigin.Begin);
        return Image.FromStream(fileStream);
    }
}

效果如下

C# 收银机打印

demo已上传至 github

上一篇:java初学


下一篇:Acwing 3827. 最小正整数 数学思维gcm或推导