C# PrintDocument 打印事件深度解析:3步实现自定义图文混排与分页

📅 2026/7/9 16:50:42
C# PrintDocument 打印事件深度解析:3步实现自定义图文混排与分页
C# PrintDocument 打印事件深度解析3步实现自定义图文混排与分页在Windows应用程序开发中打印功能一直是业务系统不可或缺的组成部分。从简单的文本输出到复杂的报表生成打印功能的实现质量直接影响用户体验。本文将聚焦PrintDocument控件的核心事件机制通过实战演示如何构建支持图文混排、表格绘制和智能分页的打印解决方案。1. PrintDocument事件模型解析PrintDocument控件作为.NET打印体系的核心其事件驱动模型决定了打印过程的每个关键节点。理解这三个核心事件的触发时机和作用是构建复杂打印功能的基础。BeginPrint事件打印作业开始前的初始化工作站。典型应用场景包括初始化数据源数据库连接、文件读取等计算总页数用于进度显示设置打印作业的全局变量private void printDocument_BeginPrint(object sender, PrintEventArgs e) { // 初始化数据读取器 dataReader GetReportData(); currentPageIndex 0; totalPages CalculateTotalPages(); }PrintPage事件每页内容的绘制中枢。开发者需要重点处理使用Graphics对象进行绘制操作计算内容布局和分页位置设置HasMorePages属性决定是否继续打印EndPrint事件打印作业的收尾处理。常见用途释放数据源等资源打印完成提示日志记录三个事件的执行顺序形成完整的打印生命周期BeginPrint → (PrintPage × N) → EndPrint2. 图文混排绘制技术Graphics对象是打印内容绘制的核心工具它提供丰富的绘制方法支持各种内容类型的输出。下面通过具体示例展示混合内容绘制的最佳实践。2.1 基础绘制方法文本绘制e.Graphics.DrawString( 销售报表, new Font(微软雅黑, 16, FontStyle.Bold), Brushes.Black, new PointF(100, 50));图像绘制Image logo Image.FromFile(company_logo.png); e.Graphics.DrawImage(logo, new Rectangle(400, 50, 120, 60));表格绘制// 绘制表头 e.Graphics.DrawRectangle(Pens.Black, new Rectangle(100, 150, 600, 30)); e.Graphics.DrawString(产品名称, tableFont, Brushes.Black, new PointF(110, 155)); // 绘制表格内容 for(int i0; iproducts.Count; i) { float yPos 180 i * 25; e.Graphics.DrawString(products[i].Name, tableFont, Brushes.Black, new PointF(110, yPos)); e.Graphics.DrawString(products[i].Price.ToString(C), tableFont, Brushes.Black, new PointF(310, yPos)); }2.2 布局计算技巧实现专业级打印输出的关键在于精确的布局计算边距处理float leftMargin e.MarginBounds.Left; float topMargin e.MarginBounds.Top; float printableWidth e.MarginBounds.Width;内容高度计算float lineHeight font.GetHeight(e.Graphics); float currentY topMargin; foreach(var line in contentLines) { if(currentY lineHeight e.MarginBounds.Bottom) { e.HasMorePages true; return; } e.Graphics.DrawString(line, font, Brushes.Black, leftMargin, currentY); currentY lineHeight; }多列布局float columnWidth (printableWidth - columnGap) / 2; // 第一列 DrawColumn(e.Graphics, leftMargin, currentY, columnWidth, leftColumnData); // 第二列 DrawColumn(e.Graphics, leftMargin columnWidth columnGap, currentY, columnWidth, rightColumnData);3. 智能分页控制策略复杂文档的分页处理需要综合考虑内容类型、页面剩余空间和用户偏好。以下是三种典型场景的实现方案。3.1 文本流分页处理长文本时的分页算法private void PrintTextWithPagination(PrintPageEventArgs e) { float yPos topMargin; int linesPrinted 0; while (linesPrinted linesPerPage currentLine documentLines.Count) { string line documentLines[currentLine]; SizeF size e.Graphics.MeasureString(line, printFont, printableWidth); if(yPos size.Height e.MarginBounds.Bottom) { e.HasMorePages true; return; } e.Graphics.DrawString(line, printFont, Brushes.Black, leftMargin, yPos); yPos size.Height; linesPrinted; currentLine; } e.HasMorePages currentLine documentLines.Count; }3.2 表格跨页处理当表格行数超过单页容量时需要特殊处理记录已打印行索引在下一页继续打印剩余行重复表头可选private void PrintTableWithPagination(PrintPageEventArgs e) { float yPos topMargin; // 打印表头 PrintTableHeader(e.Graphics, ref yPos); // 打印表格内容 while(currentRow dataTable.Rows.Count yPos e.MarginBounds.Bottom - rowHeight) { PrintTableRow(e.Graphics, dataTable.Rows[currentRow], ref yPos); currentRow; } e.HasMorePages currentRow dataTable.Rows.Count; }3.3 图文混排分页混合内容的分页需要更复杂的逻辑private void PrintMixedContent(PrintPageEventArgs e) { float yPos topMargin; // 1. 打印标题和logo PrintHeader(e.Graphics, ref yPos); // 2. 打印文本内容 yPos PrintTextContent(e.Graphics, yPos, e.MarginBounds.Bottom - 100); // 3. 检查剩余空间是否足够打印图表 if(e.MarginBounds.Bottom - yPos chartHeight) { PrintChart(e.Graphics, yPos); } else { e.HasMorePages true; return; } // 4. 打印页脚 PrintFooter(e.Graphics, e.MarginBounds.Bottom - 30); }4. 完整实现示例下面是一个完整的销售报表打印实现包含上述所有技术要点public class SalesReportPrinter { private ListSaleRecord records; private int currentRecordIndex; private Font headerFont new Font(Arial, 14, FontStyle.Bold); private Font bodyFont new Font(Arial, 10); public void PrintReport(ListSaleRecord data) { records data; currentRecordIndex 0; PrintDocument doc new PrintDocument(); doc.BeginPrint Doc_BeginPrint; doc.PrintPage Doc_PrintPage; doc.EndPrint Doc_EndPrint; PrintPreviewDialog preview new PrintPreviewDialog(); preview.Document doc; preview.ShowDialog(); } private void Doc_BeginPrint(object sender, PrintEventArgs e) { // 初始化打印作业 currentRecordIndex 0; } private void Doc_PrintPage(object sender, PrintPageEventArgs e) { float leftMargin e.MarginBounds.Left; float topMargin e.MarginBounds.Top; float yPos topMargin; // 打印报表标题 e.Graphics.DrawString(月度销售报表, headerFont, Brushes.Black, new PointF(leftMargin, yPos)); yPos headerFont.GetHeight(e.Graphics) 20; // 打印表格标题行 float[] columnWidths { 150, 100, 100, 100 }; string[] headers { 产品名称, 数量, 单价, 总价 }; DrawTableRow(e.Graphics, headers, columnWidths, leftMargin, ref yPos, true); yPos 5; // 打印表格内容 while(currentRecordIndex records.Count yPos e.MarginBounds.Bottom - 50) { var record records[currentRecordIndex]; string[] row { record.ProductName, record.Quantity.ToString(), record.UnitPrice.ToString(C), record.TotalPrice.ToString(C) }; DrawTableRow(e.Graphics, row, columnWidths, leftMargin, ref yPos, false); currentRecordIndex; } // 打印页脚 string footer $页码: {e.PageNumber}; e.Graphics.DrawString(footer, bodyFont, Brushes.Black, new PointF(leftMargin, e.MarginBounds.Bottom - 30)); // 设置是否还有后续页面 e.HasMorePages currentRecordIndex records.Count; } private void DrawTableRow(Graphics g, string[] cells, float[] widths, float startX, ref float yPos, bool isHeader) { float xPos startX; Font font isHeader ? new Font(bodyFont, FontStyle.Bold) : bodyFont; Brush brush isHeader ? Brushes.DarkBlue : Brushes.Black; for(int i0; icells.Length; i) { RectangleF cellRect new RectangleF(xPos, yPos, widths[i], font.GetHeight(g) 5); g.DrawString(cells[i], font, brush, cellRect); // 绘制单元格边框 g.DrawRectangle(Pens.Gray, Rectangle.Round(cellRect)); xPos widths[i]; } yPos font.GetHeight(g) 5; } private void Doc_EndPrint(object sender, PrintEventArgs e) { // 清理资源 records null; } }实际项目中遇到的典型问题包括图片分辨率适配、跨页表格的边框连贯性处理以及特殊字符的打印异常等。通过合理设置Graphics对象的插值模式和文本渲染提示可以显著提升输出质量// 优化打印质量设置 e.Graphics.InterpolationMode InterpolationMode.HighQualityBicubic; e.Graphics.TextRenderingHint TextRenderingHint.AntiAliasGridFit; e.Graphics.SmoothingMode SmoothingMode.HighQuality;