前两天领导说不用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); } }
demo已上传至 github