C# WinForms递归实现PrintDocument多页打印图片丢失问题求助
问题描述
副业从事金属加工,开发了基于C# Windows Forms + SQL Server的管理程序,其中Category表支持多级子分类,配套Product表存储产品信息。需求是通过PrintDocument功能动态生成层级分明的产品目录,Category类结构如下:
public class ProductCategory { private int iD = -1; private int parentID = -1; private string catName = string.Empty; private List<ProductCategory> childCategories = new List<ProductCategory>(); }
采用递归函数遍历多级嵌套分类,但多页打印时出现问题:当图片即将跨页时无法正常打印,设置e.HasMorePages = true; return;后,后续未完成的图片无法继续处理。附当前打印功能代码:
private void CreateDocument() { PrintDialog printDialog1 = new PrintDialog(); printDialog1.Document = printDocument1; DialogResult result = printDialog1.ShowDialog(); if (result == DialogResult.OK) { printDocument1.Print(); } } private void printDocument1_PrintPage(object sender, PrintPageEventArgs e) { pageNumber++; PrintPageNumber(pageNumber, e); yPos = e.MarginBounds.Top; if (pageNumber == 1) { PrintCompanyInfo(e); } for (int j = catIndex; j < orderedCategories.Count; j++) { categoryFontSize = categoryDefaultFontSize; yPos += mainCategoryPaddingY; DoCategoryRecursive(orderedCategories[j], e); catIndex = j; } } private int catIndex = 0; private void PrintCompanyInfo(PrintPageEventArgs e) { // Create image. Image newImage = Image.FromFile(companyLogo); // Create coordinates for upper-left corner of image. int x = e.MarginBounds.Left; int y = e.MarginBounds.Top; // Draw image to screen. e.Graphics.DrawImage(newImage, x, y, photoWidth, photoHeight); e.Graphics.DrawString("Leo InnoTech (Pty) Ltd", new Font("Calibri", 28, FontStyle.Regular), Brushes.Black, new Point(350, 100)); e.Graphics.DrawString("Registration Number: 2024 / 378584 / 07", new Font("Arial", 9, FontStyle.Bold), Brushes.Black, new Point(410, 160)); e.Graphics.DrawString("Phone:", new Font("Calibri", 14, FontStyle.Regular), Brushes.Black, new Point(410, 210)); e.Graphics.DrawString("0611 864 142", new Font("Calibri", 14, FontStyle.Regular), Brushes.Black, new Point(490, 210)); e.Graphics.DrawString("Email Address:", new Font("Calibri", 14, FontStyle.Regular), Brushes.Black, new Point(350, 235)); e.Graphics.DrawString("leoinnotechsa@gmail.com", new Font("Calibri", 14, FontStyle.Regular), Brushes.Blue, new Point(490, 235)); yPos = y + photoHeight + productPaddingY; } private void PrintPageNumber(int pageNr, PrintPageEventArgs e) { int x = e.MarginBounds.Right; int y = e.MarginBounds.Bottom; e.Graphics.DrawString(pageNr.ToString(), new Font("Calibri", 14, FontStyle.Regular), Brushes.Black, new Point(x, y)); } private void DoCategoryRecursive(ProductCategory cat, PrintPageEventArgs e) { // Load all Products for cat List<Product> products = Products_Manager_SqlServer.Load_Products_ByCategoryID_Controller(connectionString, cat.ID); if (products.Count > 0 || cat.ChildCategories.Count > 0) { if (yPos > e.MarginBounds.Bottom) { e.HasMorePages = true; return; } // Add category name to document e.Graphics.DrawString(cat.CatName, new Font("Calibri", categoryFontSize, FontStyle.Bold), Brushes.Black, new Point(xPos, yPos)); PrintProducts(products, e); if (cat.ChildCategories.Count > 0) { foreach (ProductCategory child in cat.ChildCategories) { yPos += minorCategoryPaddingY; categoryFontSize -= fontSizeDecrement; DoCategoryRecursive(child, e); } } else { return; } } else { return; } } private void PrintProducts(List<Product> products, PrintPageEventArgs e) { string photoPath = defaultPhotoPath; Image newImage = null; foreach (Product product in products) { yPos += productPaddingY; if (yPos + photoHeight > e.MarginBounds.Bottom) { e.HasMorePages = true; return; } // Get primary Photo List<ProductPhoto> photos = ProductPhotos_Manager_SqlServer.Load_PhotoPrimary_ByProduct_Controller(connectionString, product.ID); if (photos.Count > 0) { photoPath = photos[0].Photo; } // Create image. newImage = Image.FromFile(photoPath); // Draw image to document. e.Graphics.DrawImage(newImage, xPos + photoOffsetX, yPos, photoWidth, photoHeight); yPos += photoHeight; if (yPos > e.MarginBounds.Bottom) { e.HasMorePages = true; return; } } }
需求:获取问题解决方向,或更合适的遍历处理方式,目标目录为层级分明的分类+产品图片样式。
解决方向
核心问题根源
当前递归打印的逻辑与打印状态强耦合,中断打印返回后,无法记录递归的当前位置(如正在处理的分类、已打印到第几个产品),导致下一页打印时要么重复处理,要么直接跳过未完成内容。
推荐解决方案
1. 提前展平打印任务(最优方案)
脱离打印过程中的递归,提前将所有需要打印的内容(分类标题、产品图片)按顺序展平为一个打印任务列表,每个任务包含类型、内容、所需高度等信息。打印时逐页从列表中取任务,记录当前任务索引,下一页从该索引继续处理。
示例实现思路:
// 提前构建打印任务列表 List<PrintTask> printTasks = new List<PrintTask>(); BuildPrintTasks(orderedCategories, printTasks, categoryDefaultFontSize); // 打印时维护当前任务索引 private int currentTaskIndex = 0; private void printDocument1_PrintPage(object sender, PrintPageEventArgs e) { pageNumber++; PrintPageNumber(pageNumber, e); yPos = e.MarginBounds.Top; if (pageNumber == 1) { PrintCompanyInfo(e); } while (currentTaskIndex < printTasks.Count) { var task = printTasks[currentTaskIndex]; if (task.Type == PrintTaskType.Category) { // 检查当前位置是否能放下分类标题 if (yPos + task.Height > e.MarginBounds.Bottom) { e.HasMorePages = true; return; } // 绘制分类标题 e.Graphics.DrawString(task.Text, task.Font, Brushes.Black, xPos, yPos); yPos += task.Height + task.Padding; currentTaskIndex++; } else if (task.Type == PrintTaskType.Product) { // 检查是否能放下产品图片 if (yPos + productPaddingY + photoHeight > e.MarginBounds.Bottom) { e.HasMorePages = true; return; } // 绘制产品图片(注意释放资源) using (Image img = Image.FromFile(task.ImagePath)) { e.Graphics.DrawImage(img, xPos + photoOffsetX, yPos + productPaddingY, photoWidth, photoHeight); } yPos += productPaddingY + photoHeight; currentTaskIndex++; } } e.HasMorePages = false; } // 辅助类:定义打印任务类型和属性 public enum PrintTaskType { Category, Product } public class PrintTask { public PrintTaskType Type { get; set; } public string Text { get; set; } public string ImagePath { get; set; } public Font Font { get; set; } public int Height { get; set; } public int Padding { get; set; } } // 构建打印任务列表(这里递归构建任务没问题,因为不在打印过程中执行) private void BuildPrintTasks(List<ProductCategory> categories, List<PrintTask> tasks, int fontSize) { foreach (var cat in categories) { // 添加分类任务 Font catFont = new Font("Calibri", fontSize, FontStyle.Bold); int catHeight = (int)Graphics.FromImage(new Bitmap(1, 1)).MeasureString(cat.CatName, catFont).Height; tasks.Add(new PrintTask { Type = PrintTaskType.Category, Text = cat.CatName, Font = catFont, Height = catHeight, Padding = mainCategoryPaddingY }); // 添加该分类下的产品任务 var products = Products_Manager_SqlServer.Load_Products_ByCategoryID_Controller(connectionString, cat.ID); foreach (var prod in products) { var photos = ProductPhotos_Manager_SqlServer.Load_PhotoPrimary_ByProduct_Controller(connectionString, prod.ID); string imgPath = photos.Count > 0 ? photos[0].Photo : defaultPhotoPath; tasks.Add(new PrintTask { Type = PrintTaskType.Product, ImagePath = imgPath, Padding = productPaddingY }); } // 递归处理子分类 if (cat.ChildCategories.Count > 0) { BuildPrintTasks(cat.ChildCategories, tasks, fontSize - fontSizeDecrement); } } }
2. 修复递归打印的状态记录(备选)
如果坚持使用递归,需要显式记录递归的当前状态:
- 给
ProductCategory添加标记字段,记录该分类是否已打印、已打印到第几个产品。 - 打印时,递归函数先检查分类的打印状态,从已打印的产品索引开始继续处理。
- 此方式复杂度高,容易出现状态混乱,仅建议在无法重构代码时使用。
3. 资源优化注意事项
- 当前代码中
Image.FromFile后未释放资源,长期运行会导致内存泄漏,需用using语句或手动调用Dispose()。 - 打印前提前加载所有产品图片路径,避免打印过程中频繁访问数据库,提升打印稳定性和速度。
内容的提问来源于stack exchange,提问作者Sabre
相关产品推荐
相关产品推荐

