C# 收銀機列印
阿新 • • 發佈:2021-08-17
前兩天領導說不用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