WPF如何实现点击按钮打印DataGrid全量内容并自动分页
问题根因
默认状态下DataGrid开启了UI虚拟化,仅渲染当前屏幕可视区域内的行元素,可视区域外的行根本没有生成对应的可视化实例,直接调用PrintVisual抓取控件画面打印,自然只能拿到当前屏幕显示的内容;而且直接打印可视化对象的逻辑不会自动处理分页,超出单页打印区域的内容会被直接裁切。
实现步骤
- 打印触发后先临时关闭DataGrid的行虚拟化,强制控件渲染所有行的可视化内容
对应代码如下,必须调用UpdateLayout()触发即时渲染,否则拿不到全量行元素:dataGrid.EnableRowVirtualization = false; dataGrid.UpdateLayout(); - 基于
FixedDocument构造可自动分页的打印文档,不要直接用PrintVisual打印DataGrid控件本身
核心逻辑是逐行测量渲染完成的DataGrid行高,累计高度达到单页可打印区域阈值时自动新建页面,剩余内容顺延到新页,同时每页可以保留表头方便阅读。核心实现代码:PrintDialog printDialog = new PrintDialog(); if (printDialog.ShowDialog() != true) return; // 临时关闭虚拟化渲染全量行 dataGrid.EnableRowVirtualization = false; dataGrid.UpdateLayout(); // 初始化固定文档,设置页面尺寸和打印区域一致 FixedDocument fixedDoc = new FixedDocument(); fixedDoc.DocumentPaginator.PageSize = new Size(printDialog.PrintableAreaWidth, printDialog.PrintableAreaHeight); double pageMargin = 24; double usablePageWidth = printDialog.PrintableAreaWidth - pageMargin * 2; double usablePageHeight = printDialog.PrintableAreaHeight - pageMargin * 2; // 初始化第一页 PageContent currentPageContent = new PageContent(); FixedPage currentPage = new FixedPage { Width = printDialog.PrintableAreaWidth, Height = printDialog.PrintableAreaHeight }; double currentUsedHeight = 0; // 构造通用表头 List<TextBlock> headerElements = new List<TextBlock>(); foreach (var col in dataGrid.Columns) { headerElements.Add(new TextBlock { Text = col.Header?.ToString() ?? string.Empty, Width = col.ActualWidth, FontWeight = FontWeights.Bold }); } StackPanel pageHeader = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(pageMargin, pageMargin, pageMargin, 0) }; headerElements.ForEach(h => pageHeader.Children.Add(h)); currentPage.Children.Add(pageHeader); currentUsedHeight += pageHeader.ActualHeight + 12; // 逐行填充内容 for (int i = 0; i < dataGrid.Items.Count; i++) { DataGridRow row = dataGrid.ItemContainerGenerator.ContainerFromIndex(i) as DataGridRow; if (row == null) continue; // 强制测量行尺寸 row.Measure(new Size(usablePageWidth, double.PositiveInfinity)); row.Arrange(new Rect(new Point(0,0), row.DesiredSize)); // 当前页剩余空间放不下当前行时,封存当前页,新建下一页 if (currentUsedHeight + row.DesiredSize.Height > usablePageHeight) { ((IAddChild)currentPageContent).AddChild(currentPage); fixedDoc.Pages.Add(currentPageContent); // 重置新页状态 currentPageContent = new PageContent(); currentPage = new FixedPage { Width = printDialog.PrintableAreaWidth, Height = printDialog.PrintableAreaHeight }; currentUsedHeight = 0; // 新页追加表头 StackPanel newPageHeader = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(pageMargin, pageMargin, pageMargin, 0) }; headerElements.ForEach(h => newPageHeader.Children.Add(new TextBlock { Text = h.Text, Width = h.Width, FontWeight = FontWeights.Bold })); currentPage.Children.Add(newPageHeader); currentUsedHeight += newPageHeader.ActualHeight + 12; } // 把行添加到当前页指定位置 FixedPage.SetLeft(row, pageMargin); FixedPage.SetTop(row, currentUsedHeight); currentPage.Children.Add(row); currentUsedHeight += row.DesiredSize.Height; } // 把最后一页加入文档 ((IAddChild)currentPageContent).AddChild(currentPage); fixedDoc.Pages.Add(currentPageContent); // 提交打印任务 printDialog.PrintDocument(fixedDoc.DocumentPaginator, $"DataGrid数据打印_{DateTime.Now:yyyyMMddHHmm}"); // 打印完成恢复行虚拟化,避免大数据量下UI卡顿 dataGrid.EnableRowVirtualization = true; - 打印任务提交后立刻恢复DataGrid的虚拟化配置,全量渲染行元素会占用较高内存,数据量较大时长期关闭虚拟化会导致UI操作卡顿。
踩坑提示
- 如果DataGrid使用了自定义单元格模板,逐行测量前要确保模板内的可视化元素完成加载,否则行高计算会偏小,出现内容截断问题。
- 不要用截图拼接的方案实现打印,输出的位图内容放大后会模糊,也不支持文字选中复制;基于
FixedDocument输出的是矢量内容,打印清晰度和屏幕显示效果完全一致。 - 单表数据量超过10万行时,临时渲染全量行会有几百毫秒到几秒的卡顿,可以在打印触发时加个简单的加载遮罩,避免用户误判程序无响应。
内容的提问来源于stack exchange,提问作者Hulises
相关产品推荐
相关产品推荐

