PrintDocument.PrintPage事件必须手动绘制每页内容,仅提供e.Graphics绘图表面,不自动解析文本或HTML;需用DrawString等方法逐项绘制,正确使用e.MarginBounds、动态分页、字体兼容性处理及PrintPreviewDialog预设Document属性。

PrintDocument.PrintPage 事件里必须手动绘制每一页内容
PrintDocument 本身不自动解析文本或 HTML,它只提供一个绘图表面(e.Graphics),所有文字、表格、图片都得你用 Graphics.DrawString、Graphics.DrawRectangle 等方法一一手动绘制。很多人以为传个字符串就能打印,结果 PrintPage 事件里什么也不写,或者只写了 e.HasMorePages = false,最后打出一张白纸。
常见错误现象:PrintPage 事件触发但无输出、打印机吐出空白页、文字重叠或截断。
- 每次触发
PrintPage,你只能画当前页的内容;画完后设e.HasMorePages = true才会继续触发下一页 - 务必检查
e.MarginBounds(不是e.PageBounds),它是真正可用的打印区域,避开页边距 - 字体推荐用
new Font("SimSun", 9)这类固定大小的实例,避免在不同 DPI 设备上缩放失真 - 长文本换行需自己调用
Graphics.MeasureString+ 拆分逻辑,DrawString不自动折行
用 PrintPreviewDialog 预览前必须先设置 Document 属性
直接 new PrintPreviewDialog() 并 Show(),界面会弹出来但显示“无法预览”,控制台没报错,调试也看不出问题——根本原因是没把 PrintDocument 实例赋给它的 Document 属性。
使用场景:开发阶段调试布局、确认分页位置、避免反复烧纸测试。
-
PrintPreviewDialog是包装器,不是独立渲染器;它完全依赖绑定的PrintDocument的PrintPage逻辑 - 必须在
Show()前完成:previewDialog.Document = printDoc; - 如果预览时文字模糊,大概率是用了
Graphics.TextRenderingHint = TextRenderingHint.ClearTypeGridFit,而预览控件不支持 ClearType,建议统一设为TextRenderingHint.AntiAliasGridFit
打印报表时分页逻辑容易崩在换页边界
比如循环打印订单明细,每行高度固定 20px,页高 800px,算下来最多 40 行——但实际第 40 行可能刚好卡在页底,再画一行就溢出,而你没判断就强行画了,结果最后一行被裁掉,还可能触发无限 PrintPage 循环(HasMorePages 一直为 true)。
性能影响:错误的分页判断会导致多次重绘、内存堆积,甚至 UI 线程卡死。
- 不要用“总行数 ÷ 每页行数”硬算页数;要模拟绘制过程,逐行累加高度,用
e.MarginBounds.Bottom - currentY动态判断剩余空间 - 在即将超出时,提前设
e.HasMorePages = true,并保存当前数据索引(如nextStartIndex),下一页从那里继续 - 避免在
PrintPage中做耗时操作(如查数据库、读大文件),所有数据应在打印前一次性加载并缓存好
中文乱码或方框字基本都是字体没嵌入或系统缺失
在客户机器上打印出“□□□”而不是汉字,不是编码问题(C# 默认 UTF-16),而是 Graphics.DrawString 找不到能显示中文的字体。尤其在 Server Core 或精简版 Windows 上,"SimSun" 或 "Microsoft YaHei" 可能根本不存在。
兼容性影响:开发机正常,部署到生产环境就挂。
- 别写死字体名;用
FontFamily.Families枚举系统已安装字体,优先选"Microsoft YaHei"、"SimSun"、"NSimSun", fallback 到"Arial Unicode MS"或"Tahoma" - 更稳妥的做法:用
new Font(new FontFamily("Microsoft YaHei"), 9f, GraphicsUnit.Point),显式指定FontFamily而非字符串 - 如果目标环境可控,可随程序部署 .ttf 文件,并用
PrivateFontCollection动态加载,但要注意 GDI+ 对私有字体的跨线程限制
分页和字体这两块最容易被跳过测试,等上线才发现某台旧打印机打不出汉字,或者 200 行数据只打了前 198 行——因为第 199 行触发了未处理的边界条件。











