WPF打印票据
最近工作的内容是有关于WPF的,整体开发没有什么难度,主要是在打印上因为没有任何经验,犯了一些难,不过还好,解决起来也不是很费劲。
WPF打印票据或者是打印普通纸张区别不大,只是说打印票据要把需要打的内容摆放好位置,搞定缩放比例,就可以放入票据直接打印了。
那么关键点就是3个:
1、使用WPF提供的什么类、什么方法来执行打印
2、如何摆放位置
3、如何搞定缩放比例
1、使用WPF提供的什么类、什么方法来执行打印
这个问题很容易解决,搜索下WPF打印或WPF Print,就能找到示例代码。
那么我用的是PrintDialog的PrintVisual方法。PrintDialog从名字中可以看出是个对话框,让用户手动选择打印机。如果不想弹出对话框和选择打印机,则可以读取默认打印机或者在配置文件里配置打印机名称,然后找到它。这就需要用到另外的两个类:PrintQueue和LocalPrintServer。
使用PrintDialog打印:
1 2 3 | var printDialog = new PrintDialog(); printDialog.PrintQueue = GetPrinter(); printDialog.PrintVisual(visual, visual.Name); |
获取打印机任务队列:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | public static PrintQueue GetPrinter( string printerName = null ) { try { PrintQueue selectedPrinter = null ; if (! string .IsNullOrEmpty(printerName)) { var printers = new LocalPrintServer().GetPrintQueues(); selectedPrinter = printers.FirstOrDefault(p => p.Name == printerName); } else { selectedPrinter = LocalPrintServer.GetDefaultPrintQueue(); } return selectedPrinter; } catch { return null ; } } |
2、如何摆放位置
注意到我们上面的打印代码是使用的PrintVisual,参数是Visual,那么这个Visual是什么?
我举个WPF Grid类的继承关系:Grid : Panel : FrameworkElement : UIElement : Visual,所以WPF的控件都是继承自UIElement的,也是继承Visual的。
那么我们把Grid看作是一张票据或一张纸,在这张纸上布置好需要打印的内容,不就OK了吗。
你可以创建一个用户控件来鼠标拖拽摆放,传入实体对象绑定值,也可以动态生成一个Grid。
3、如何搞定缩放比例
仅仅摆放好,打印出来未必是我们想要的结果。因为票据的大小不同,特别是银行那种身份证或金额的小格子,打歪了只能说明技术不到家啊。
所以摆放是要有依据的,依据就是扫描票据,然后在扫描的底图上摆放,样位置就不会错位。然后缩放就是DPI(DPI是Dots Per Inch(每英寸所打印的点数)的缩写)的概念。我们扫描的图是像素的,而实际的纸张不能用像素这个单位。这个之间的换算需要依赖DPI。
具体缩放的方法:
1 2 3 4 5 6 7 8 9 10 11 | //注意,我这里DPI写死的是150,实际中你的DPI是多少要看扫描件怎么扫的。 var settings = new PrintSettings { Width = visual.Width, Height = visual.Height, DPI = 150 }; var renderTarget = new RenderTargetBitmap(( int )settings.Width, ( int )settings.Height, settings.DPI, settings.DPI, PixelFormats.Default); printDialog.PrintTicket = new PrintTicket(); printDialog.PrintTicket.PageMediaSize = new PageMediaSize(renderTarget.Width, renderTarget.Height); var capabilities = printDialog.PrintQueue.GetPrintCapabilities(printDialog.PrintTicket); var scale = Math.Max(capabilities.PageImageableArea.ExtentWidth / visual.Width, capabilities.PageImageableArea.ExtentHeight / visual.Height); visual.LayoutTransform = new ScaleTransform(scale, scale); var sz = new Size(capabilities.PageImageableArea.ExtentWidth, capabilities.PageImageableArea.ExtentHeight); visual.Measure(sz); visual.Arrange( new Rect( new Point(0, 0), sz)); |
这样我们就达到了缩放的目的,你可以查看MSDN看看具体的类和方法的含义。
其他的需求:
1、竖打
有些单据比较窄,但是宽度还可以,所以希望可以竖着打印,满足这个需求也是一句话的事情。
在visual.Measure(sz);语句之前增加下面两行代码即可。
1 2 | printDialog.PrintTicket.PageOrientation = PageOrientation.Landscape; printDialog.PrintTicket.PageMediaSize = new PageMediaSize(renderTarget.Height, renderTarget.Width); |
2、退纸(针式打印机)
退纸并不是常用的功能,但是放错了纸张想拿出来也要费一番力气,所以想让打印机自动吐出纸来。我也搜索了很多问答和文章,也没试出来一个成功的,可能是方法不正确。最终采用了一个比较鸡贼的办法,就是打印一个空白页,然后自动退纸。每种针式打印机可能不同,所以退纸的空白页的大小要调整好。
1 2 3 4 5 6 7 8 9 | var printer = GetPrinter(); var visual = new Grid() { Width = 1000, Height = 1500, VerticalAlignment = VerticalAlignment.Top, HorizontalAlignment = HorizontalAlignment.Left }; PrintVisual(printer, visual); |
3、监控打印任务状态
打印的时候肯定想知道任务有没有被打印,提醒用户放入纸张,打印完毕后提醒用户打印完成。我这里写了一个PrintJobChecker类,Start后就会根据timer的间隔时间检查任务队列,和打印时间。
但是.NET提供的方法并不能很好的做到理想的效果,只能获取到任务还有没有,这是很郁闷的事情。一旦打印机开始打印(注意还没完成),job就是null了。这无法判断纸张是不是还在打印中。如果有朋友知道怎么处理还望评论告知。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | public class PrintJobChecker { private DispatcherTimer _timer; private PrintQueue _printer; private Action< string > _checkingAction; public DateTime? StartPrintTime { get ; set ; } private int _interval = 100; public int TimerInterval { get { return _interval; } set { _interval = value; _timer.Interval = TimeSpan.FromMilliseconds(value); } } public PrintJobChecker(PrintQueue printer, Action< string > checkingAction) { if (printer == null || checkingAction == null ) { return ; } _printer = printer; _checkingAction = checkingAction; _timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(TimerInterval), }; _timer.Tick += CheckJobStatus; PrintingStatus = "正在打印" ; PrintErrorStatus = "打印出错" ; PrintOfflineStatus = "请连接打印机" ; PrintWaittingStatus = "请放入相应的表单至打印机" ; PrintUnknownStatus = "未知错误" ; } public void Start() { _timer.Start(); } public void Stop() { _timer.Stop(); } private void CheckJobStatus( object sender, EventArgs e) { if (_printer == null ) { return ; } var job = _printer.GetLastJob(); if (job == null ) { if (!StartPrintTime.HasValue) { StartPrintTime = DateTime.Now; } _checkingAction(PrintingStatus); } else { var statusText = GetJobStatus(job); _checkingAction(statusText); } } public string PrintingStatus { get ; set ; } public string PrintErrorStatus { get ; set ; } public string PrintOfflineStatus { get ; set ; } public string PrintWaittingStatus { get ; set ; } public string PrintUnknownStatus { get ; set ; } private string GetJobStatus(PrintSystemJobInfo job) { if (job == null ) return null ; if (((job.JobStatus & PrintJobStatus.Completed) == PrintJobStatus.Completed) || ((job.JobStatus & PrintJobStatus.Printed) == PrintJobStatus.Printed)) { StartPrintTime = DateTime.Now; return PrintingStatus; } if ((job.JobStatus & PrintJobStatus.Error) == PrintJobStatus.Error) { _timer.Stop(); return PrintErrorStatus; } if ((job.JobStatus & PrintJobStatus.Offline) == PrintJobStatus.Offline || job.JobStatus == PrintJobStatus.None) { return PrintOfflineStatus; } if ((job.JobStatus & PrintJobStatus.Printing) == PrintJobStatus.Printing) { if (job.TimeSinceStartedPrinting > 0) { return PrintingStatus; } else { return PrintWaittingStatus; } } return PrintUnknownStatus; } } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· 展开说说关于C#中ORM框架的用法!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?