C# PrintDocument点击事件打印全页 如何实现指定页码范围打印
问题根因
你配置页码范围后仍然打印全部页面,核心是三个问题:
BeginPrint事件中硬编码把起止页码都写死为1,没有读取用户实际输入的起止页码值- 没有在
PrintPage事件中维护当前打印页码的计数,也没有做页码范围判断,PrintDocument组件不会自动跳过范围外的内容,必须自行在绘制逻辑中控制 - 每次点击打印按钮都重复绑定事件,多次点击后事件会重复执行,引发逻辑异常
排查思路
- 先检查事件绑定逻辑:打印相关事件只需要在窗体初始化时绑定一次,不要放在按钮点击事件中重复绑定
- 检查打印参数传递:
PrintRange.SomePages模式下必须保证FromPage和ToPage值合法(大于0,且起始页≤结束页),否则打印引擎会直接忽略范围配置 - 检查打印页控制逻辑:必须单独维护当前打印页码的变量,在页码小于起始页时跳过内容绘制但保留排版计算,在页码超过结束页时立即终止打印
范围打印实现方案
1. 新增全局控制变量
在窗体类中新增页码相关的控制变量,和原有打印位置变量放在一起:
// 原有打印位置变量 private int curRow = 0; private int curCopy = 0; // 新增页码控制变量 private int currentPrintPage = 0; // 当前正在生成的打印页码 private int userFromPage = 1; // 用户指定的打印起始页 private int userToPage = int.MaxValue; // 用户指定的打印结束页,默认给最大值适配全量打印
2. 修正事件绑定位置
把打印事件绑定移动到窗体构造函数中,仅绑定一次:
public YourFormClassName() { InitializeComponent(); // 仅在窗体初始化时绑定一次事件 printDocument1.BeginPrint += printDocument1_BeginPrint; printDocument1.PrintPage += printDocument1_PrintPage; }
3. 修改BeginPrint事件逻辑
去掉硬编码的页码值,初始化打印状态,读取用户输入的页码范围:
private void printDocument1_BeginPrint(object sender, System.Drawing.Printing.PrintEventArgs e) { // 重置打印位置和页码计数 curRow = 0; curCopy = 0; currentPrintPage = 0; if (printrange) { // 如果你用系统自带PrintDialog获取页码,直接取printDialog.PrinterSettings的FromPage/ToPage即可 // 如果是自定义页码输入框,在这里做输入校验后给userFromPage、userToPage赋值 // 示例校验逻辑: // if (userFromPage <1 || userToPage < userFromPage) // { // MessageBox.Show("页码范围不合法"); // e.Cancel = true; // return; // } PrintDocument printDoc = (PrintDocument)sender; printDoc.PrinterSettings.PrintRange = PrintRange.SomePages; printDoc.PrinterSettings.FromPage = userFromPage; printDoc.PrinterSettings.ToPage = userToPage; printrange = false; } else { // 全量打印时重置范围为所有页 userFromPage = 1; userToPage = int.MaxValue; } }
4. 修改PrintPage事件逻辑
增加页码计数和范围判断,非打印范围的页面只做排版计算不绘制内容,到达结束页后终止打印:
private void printDocument1_PrintPage(object sender, PrintPageEventArgs e) { // 每进入一次PrintPage事件,当前页码+1 currentPrintPage++; // 判断当前页是否需要绘制内容 bool needDraw = currentPrintPage >= userFromPage && currentPrintPage <= userToPage; var curY = e.MarginBounds.Y; var curX = e.MarginBounds.X + 30; // 补充Pen的using释放,避免GDI资源泄漏 using (Pen pen = new Pen(System.Drawing.ColorTranslator.FromHtml("#FFBD2D"), 1)) using (var fontNormal = new Font("Arial", 10)) using (var sf = new StringFormat()) { sf.Alignment = sf.LineAlignment = StringAlignment.Center; int itemHeight = (int)fontNormal.GetHeight(e.Graphics); for (int row = curRow; row < dt.Rows.Count; row++) { DataRow dr = dt.Rows[row]; if (!string.IsNullOrEmpty(dr.Field<string>(1)) && int.TryParse(dr.Field<string>(4)?.ToString(), out int copies)) { for (int i = curCopy; i < copies; i++) { var imgRect = new Rectangle(curX, curY, 156, 156); var labelRect = new Rectangle( imgRect.X + 180 + 20, imgRect.Bottom - 162, itemHeight, imgRect.Width); // 仅在指定页码范围内才实际绘制内容 if (needDraw) { using (var qrImage = GenerateQRCODE(dr[1].ToString())) { e.Graphics.DrawImage(RotateImage(qrImage, -90), imgRect); } SizeF labelSize = e.Graphics.MeasureString(dr[1].ToString(), fontNormal); using (Bitmap stringmap = new Bitmap((int)labelSize.Height + 1, (int)labelSize.Width + 1)) using (Graphics gbitmap = Graphics.FromImage(stringmap)) { gbitmap.TranslateTransform(0, labelSize.Width); gbitmap.RotateTransform(-90); gbitmap.DrawString(dr[1].ToString(), fontNormal, Brushes.Black, new PointF(0, 0), new StringFormat()); e.Graphics.DrawImage(stringmap, labelRect); } e.Graphics.DrawLine(pen, curX, curY, curX, curY + 156); e.Graphics.DrawLine(pen, curX, curY + 156, curX + 156 + 220, curY + 156); e.Graphics.DrawLine(pen, curX + 156 + 220, curY + 156, curX + 156 + 220, curY); e.Graphics.DrawLine(pen, curX, curY, curX + 156 + 220, curY); } curX = imgRect.Right + 230; if (curX + imgRect.Width > e.MarginBounds.Right) { curX = e.MarginBounds.X + 30; curY = labelRect.Bottom + 30; } // 页面填满时判断是否需要继续打印 if (curY + imgRect.Height >= e.MarginBounds.Bottom) { curCopy = i + 1; // 当前页已经是用户指定的最后一页时,终止打印 e.HasMorePages = currentPrintPage < userToPage; return; } } } curRow = row + 1; curCopy = 0; } } // 所有内容遍历完成,终止打印 e.HasMorePages = false; refreshprintbtn.Enabled = true; printdialogbtn.Enabled = true; }
5. 简化按钮点击事件
移除点击事件中的重复绑定逻辑,仅保留打印触发逻辑:
bool printrange = false; private void printrangebtn_Click(object sender, EventArgs e) { printrange = true; refreshprintbtn.Enabled = false; printdialogbtn.Enabled = false; printDocument1.Print(); }
注意事项
- 如果使用系统自带的
PrintDialog控件让用户选择页码,不需要自行开发输入框,只要在对话框确认后直接读取PrintDialog.PrinterSettings.FromPage和PrintDialog.PrinterSettings.ToPage赋值即可,注意要提前把PrintDocument的PrinterSettings属性赋值给PrintDialog的对应属性,否则无法正确获取参数 - 建议提前计算文档总页数,对用户输入的页码做合法性校验,避免输入超出总页数的页码导致打印空白页
- 起始页之前的页面虽然不绘制内容,但必须正常执行排版位置计算,否则到达起始页时内容位置会错乱
内容的提问来源于stack exchange,提问作者Molnar Istvan
相关产品推荐
相关产品推荐

