WPF 打印进阶
大约 13 分钟约 3837 字
WPF 打印进阶
简介
WPF 提供了强大的打印支持,包括 PrintDialog、FixedDocument、FlowDocument 和 XpsDocumentWriter。进阶打印涉及自定义分页、打印预览、报表生成和条码/二维码打印。在工业上位机场景中,打印功能常用于巡检报告、报警记录导出、设备标签和配方参数打印。理解 WPF 打印体系的各个组件,有助于构建专业级的报表打印方案。
WPF 打印体系架构
WPF 打印系统由以下核心组件构成:
数据源 → DocumentPaginator → XpsDocumentWriter → 打印队列 → 打印机
↑ ↑
FlowDocument FixedDocument
(自动分页) (手动分页)- PrintDialog:提供打印对话框,选择打印机、纸张大小、份数等。
- FixedDocument:固定页面文档,每页精确控制布局,适合报表。
- FlowDocument:流式文档,自动根据内容分页,适合长文本。
- DocumentPaginator:分页器接口,控制文档如何被分页。
- XpsDocumentWriter:将文档写入 XPS 文件或打印队列。
打印相关命名空间
using System.Windows.Controls; // PrintDialog, DocumentViewer
using System.Windows.Documents; // FixedDocument, FlowDocument, PageContent
using System.Windows.Xps; // XpsDocumentWriter, XpsDocument
using System.Windows.Media; // DrawingContext, Visual
using System.Printing; // PrintQueue, PrintTicket特点
实现
基础打印与打印预览
打印 Visual 元素
// ========== 基础打印服务 ==========
/// <summary>
/// 打印服务 — 封装常用打印操作
/// </summary>
public class PrintService
{
/// <summary>
/// 打印任意 UIElement(控件截图式打印)
/// 适合打印图表、仪表盘等可视化内容
/// </summary>
public void PrintVisual(UIElement visual, string description = "")
{
var dialog = new PrintDialog();
if (dialog.ShowDialog() != true) return;
// 获取打印机可打印区域大小
var printableArea = dialog.PrintableAreaWidth;
var dpi = 96.0; // WPF 默认 DPI
dialog.PrintVisual(visual, description);
}
/// <summary>
/// 打印 FlowDocument(自动分页)
/// 适合打印长文本、带格式的报告
/// </summary>
public void PrintFlowDocument(FlowDocument document, string description = "报表打印")
{
var dialog = new PrintDialog();
if (dialog.ShowDialog() != true) return;
// 设置文档页面大小匹配打印机
document.PageHeight = dialog.PrintableAreaHeight;
document.PageWidth = dialog.PrintableAreaWidth;
document.PagePadding = new Thickness(40);
document.ColumnGap = 0;
document.ColumnWidth = dialog.PrintableAreaWidth - 2 * 40; // 减去左右边距
var paginator = ((IDocumentPaginatorSource)document).DocumentPaginator;
dialog.PrintDocument(paginator, description);
}
/// <summary>
/// 打印 FixedDocument(手动分页)
/// 适合精确控制每页布局的报表
/// </summary>
public void PrintFixedDocument(FixedDocument document, string description = "报表打印")
{
var dialog = new PrintDialog();
if (dialog.ShowDialog() != true) return;
dialog.PrintDocument(document.DocumentPaginator, description);
}
}创建 FlowDocument 报表
// ========== 动态创建 FlowDocument 报表 ==========
/// <summary>
/// FlowDocument 报表生成器
/// 适合文本密集型、需要自动分页的报告
/// </summary>
public class FlowDocumentReportBuilder
{
private readonly FlowDocument _document = new();
private readonly Paragraph _currentParagraph = new();
public FlowDocumentReportBuilder()
{
_document.Blocks.Add(_currentParagraph);
}
/// <summary>
/// 添加标题
/// </summary>
public FlowDocumentReportBuilder AddTitle(string text, int fontSize = 24)
{
_currentParagraph.Inlines.Add(new Run(text)
{
FontSize = fontSize,
FontWeight = FontWeights.Bold,
Foreground = new SolidColorBrush(Colors.DarkBlue)
});
_currentParagraph.Inlines.Add(new LineBreak());
return this;
}
/// <summary>
/// 添加副标题
/// </summary>
public FlowDocumentReportBuilder AddSubtitle(string text)
{
_currentParagraph.Inlines.Add(new Run(text)
{
FontSize = 14,
Foreground = new SolidColorBrush(Colors.Gray)
});
_currentParagraph.Inlines.Add(new LineBreak());
_currentParagraph.Inlines.Add(new LineBreak());
return this;
}
/// <summary>
/// 添加正文段落
/// </summary>
public FlowDocumentReportBuilder AddParagraph(string text)
{
_document.Blocks.Add(new Paragraph(new Run(text))
{
FontSize = 12,
Margin = new Thickness(0, 4, 0, 4)
});
return this;
}
/// <summary>
/// 添加表格
/// </summary>
public FlowDocumentReportBuilder AddTable<T>(
IEnumerable<T> data,
string[] headers,
Func<T, string[]> rowSelector)
{
var table = new Table { CellSpacing = 0, BorderBrush = Brushes.Gray, BorderThickness = new Thickness(1) };
// 添加列
foreach (var _ in headers)
{
table.Columns.Add(new TableColumn { Width = new GridLength(1, GridUnitType.Star) });
}
// 添加表头行
var headerGroup = new TableRowGroup();
var headerRow = new TableRow();
headerRow.Background = new SolidColorBrush(Colors.LightGray);
foreach (var header in headers)
{
headerRow.Cells.Add(new TableCell(new Paragraph(new Run(header)
{
FontWeight = FontWeights.Bold, FontSize = 11
}))
{ BorderBrush = Brushes.Gray, BorderThickness = new Thickness(0, 0, 1, 1), Padding = new Thickness(4, 2, 4, 2) });
}
headerGroup.Rows.Add(headerRow);
// 添加数据行
int rowIndex = 0;
foreach (var item in data)
{
var row = new TableRow();
if (rowIndex % 2 == 1)
row.Background = new SolidColorBrush(Color.FromArgb(20, 0, 0, 0));
var values = rowSelector(item);
foreach (var value in values)
{
row.Cells.Add(new TableCell(new Paragraph(new Run(value) { FontSize = 10 }))
{ BorderBrush = Brushes.LightGray, BorderThickness = new Thickness(0, 0, 1, 1), Padding = new Thickness(4, 2, 4, 2) });
}
headerGroup.Rows.Add(row);
rowIndex++;
}
table.RowGroups.Add(headerGroup);
_document.Blocks.Add(table);
return this;
}
/// <summary>
/// 添加分隔线
/// </summary>
public FlowDocumentReportBuilder AddSeparator()
{
_currentParagraph.Inlines.Add(new LineBreak());
var separator = new BlockUIContainer(new Separator
{
Margin = new Thickness(0, 10, 0, 10)
});
_document.Blocks.Add(separator);
return this;
}
/// <summary>
/// 构建最终文档
/// </summary>
public FlowDocument Build()
{
_document.FontFamily = new FontFamily("Microsoft YaHei");
_document.PagePadding = new Thickness(60);
return _document;
}
}
// 使用示例
public FlowDocument CreateInspectionReport(IEnumerable<InspectionRecord> records)
{
return new FlowDocumentReportBuilder()
.AddTitle("设备巡检报告")
.AddSubtitle($"生成时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss}")
.AddSeparator()
.AddTable(
records,
new[] { "设备名称", "巡检状态", "巡检人", "巡检时间", "备注" },
r => new[] { r.DeviceName, r.Status, r.Inspector, r.Timestamp.ToString("yyyy-MM-dd HH:mm"), r.Remark ?? "-" })
.AddSeparator()
.AddParagraph($"共 {records.Count()} 条巡检记录。")
.Build();
}自定义报表生成
FixedDocument 精确布局报表
// ========== FixedDocument 报表生成 ==========
/// <summary>
/// 设备巡检报告 — FixedDocument 实现
/// 精确控制每页布局,适合复杂报表
/// </summary>
public class FixedDocumentReportGenerator
{
private const double A4Width = 96 * 8.27; // A4 宽度(像素,96 DPI)
private const double A4Height = 96 * 11.69; // A4 高度(像素,96 DPI)
private const double Margin = 48; // 页边距
/// <summary>
/// 生成设备巡检报告
/// </summary>
public FixedDocument GenerateInspectionReport(
IEnumerable<DeviceInspection> inspections,
string title,
string company = "")
{
var doc = new FixedDocument();
doc.DocumentPaginator.PageSize = new Size(A4Width, A4Height);
var page = CreateNewPage();
double y = Margin;
// === 页眉 ===
y = DrawHeader(page, title, company, ref y);
// === 表头 ===
y = DrawTableHeader(page, ref y);
// === 数据行(自动分页)===
var inspectionList = inspections.ToList();
for (int i = 0; i < inspectionList.Count; i++)
{
var item = inspectionList[i];
// 检查是否需要换页(预留页脚空间)
if (y > A4Height - Margin - 60)
{
// 绘制页脚
DrawPageFooter(page, doc.Pages.Count + 1, ref y);
doc.Pages.Add(new PageContent { Child = page });
page = CreateNewPage();
y = Margin;
y = DrawTableHeader(page, ref y);
}
y = DrawDataRow(page, item, i, ref y);
}
// === 最后一页页脚 ===
DrawPageFooter(page, doc.Pages.Count + 1, ref y);
doc.Pages.Add(new PageContent { Child = page });
return doc;
}
private FixedPage CreateNewPage()
{
return new FixedPage { Width = A4Width, Height = A4Height };
}
private double DrawHeader(FixedPage page, string title, string company, ref double y)
{
// 标题
var titleBlock = new TextBlock
{
Text = title,
FontSize = 22,
FontWeight = FontWeights.Bold,
Foreground = new SolidColorBrush(Colors.DarkBlue)
};
FixedPage.SetLeft(titleBlock, Margin);
FixedPage.SetTop(titleBlock, y);
page.Children.Add(titleBlock);
y += 36;
// 公司信息
if (!string.IsNullOrEmpty(company))
{
var companyBlock = new TextBlock
{
Text = company,
FontSize = 12,
Foreground = new SolidColorBrush(Colors.Gray)
};
FixedPage.SetLeft(companyBlock, Margin);
FixedPage.SetTop(companyBlock, y);
page.Children.Add(companyBlock);
y += 20;
}
// 生成时间
var dateBlock = new TextBlock
{
Text = $"生成时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss}",
FontSize = 11,
Foreground = new SolidColorBrush(Colors.Gray),
TextAlignment = TextAlignment.Right
};
FixedPage.SetLeft(dateBlock, A4Width - Margin - 200);
FixedPage.SetTop(dateBlock, y);
page.Children.Add(dateBlock);
y += 30;
// 分隔线
var line = new Rectangle
{
Fill = new SolidColorBrush(Colors.DarkBlue),
Width = A4Width - 2 * Margin,
Height = 2
};
FixedPage.SetLeft(line, Margin);
FixedPage.SetTop(line, y);
page.Children.Add(line);
y += 10;
return y;
}
private double DrawTableHeader(FixedPage page, ref double y)
{
string[] headers = { "序号", "设备名称", "设备状态", "巡检人", "巡检时间", "备注" };
double[] widths = { 40, 150, 80, 80, 130, 150 };
double x = Margin;
for (int i = 0; i < headers.Length; i++)
{
var headerBlock = new TextBlock
{
Text = headers[i],
FontSize = 11,
FontWeight = FontWeights.Bold,
Foreground = new SolidColorBrush(Colors.White)
};
var bg = new Rectangle
{
Fill = new SolidColorBrush(Colors.DarkBlue),
Width = widths[i],
Height = 24
};
FixedPage.SetLeft(bg, x);
FixedPage.SetTop(bg, y);
page.Children.Add(bg);
FixedPage.SetLeft(headerBlock, x + 4);
FixedPage.SetTop(headerBlock, y + 4);
page.Children.Add(headerBlock);
x += widths[i];
}
y += 26;
return y;
}
private double DrawDataRow(FixedPage page, DeviceInspection item, int index, ref double y)
{
double[] widths = { 40, 150, 80, 80, 130, 150 };
string[] values =
{
(index + 1).ToString(),
item.DeviceName,
item.Status,
item.Inspector,
item.Timestamp.ToString("yyyy-MM-dd HH:mm"),
item.Remark ?? "-"
};
double x = Margin;
for (int i = 0; i < values.Length; i++)
{
// 交替行背景色
var bgColor = index % 2 == 0
? Colors.Transparent
: Color.FromArgb(15, 0, 0, 0);
var bg = new Rectangle
{
Fill = new SolidColorBrush(bgColor),
Width = widths[i],
Height = 22
};
FixedPage.SetLeft(bg, x);
FixedPage.SetTop(bg, y);
page.Children.Add(bg);
var cell = new TextBlock
{
Text = values[i],
FontSize = 10,
Foreground = new SolidColorBrush(Colors.Black),
TextTrimming = TextTrimming.CharacterEllipsis
};
FixedPage.SetLeft(cell, x + 4);
FixedPage.SetTop(cell, y + 3);
page.Children.Add(cell);
x += widths[i];
}
// 底部边线
var line = new Rectangle
{
Fill = new SolidColorBrush(Colors.LightGray),
Width = A4Width - 2 * Margin,
Height = 0.5
};
FixedPage.SetLeft(line, Margin);
FixedPage.SetTop(line, y + 22);
page.Children.Add(line);
y += 23;
return y;
}
private void DrawPageFooter(FixedPage page, int pageNum, ref double y)
{
y = A4Height - Margin - 20;
var footer = new TextBlock
{
Text = $"第 {pageNum} 页",
FontSize = 10,
Foreground = new SolidColorBrush(Colors.Gray),
TextAlignment = TextAlignment.Center
};
FixedPage.SetLeft(footer, A4Width / 2 - 30);
FixedPage.SetTop(footer, y);
page.Children.Add(footer);
}
}
public record DeviceInspection(
string DeviceName, string Status, string Inspector,
DateTime Timestamp, string? Remark = null);自定义 DocumentPaginator
// ========== 自定义分页器 ==========
/// <summary>
/// 自定义 DocumentPaginator — 灵活控制分页逻辑
/// 适用于需要动态计算分页位置的场景
/// </summary>
public class DataPaginator : DocumentPaginator
{
private readonly IEnumerable<object> _data;
private readonly Size _pageSize;
private readonly Size _contentArea;
private readonly double _rowHeight = 24;
private readonly double _headerHeight = 50;
public DataPaginator(IEnumerable<object> data, Size pageSize)
{
_data = data;
_pageSize = pageSize;
_contentArea = new Size(pageSize.Width - 96, pageSize.Height - 120); // 减去边距
// 计算总页数
var rowsPerPage = (int)(_contentArea.Height / _rowHeight);
PageCount = (int)Math.Ceiling((double)_data.Count() / rowsPerPage);
}
public override bool IsPageCountValid => true;
public override int PageCount { get; }
public override Size PageSize { get => _pageSize; set { } }
public override IDocumentPaginatorSource? Source => null;
public override DocumentPage GetPage(int pageNumber)
{
var page = new FixedPage { Width = _pageSize.Width, Height = _pageSize.Height };
// 绘制页面内容
var visual = new DrawingVisual();
using (var dc = visual.RenderOpen())
{
double y = 48; // 上边距
// 绘制表头
DrawPageHeader(dc, ref y);
// 计算当前页的数据范围
var rowsPerPage = (int)(_contentArea.Height / _rowHeight);
var startIndex = pageNumber * rowsPerPage;
var pageData = _data.Skip(startIndex).Take(rowsPerPage);
// 绘制数据行
int row = 0;
foreach (var item in pageData)
{
DrawDataRow(dc, item, startIndex + row, y);
y += _rowHeight;
row++;
}
// 绘制页脚
DrawPageFooter(dc, pageNumber + 1, PageCount, _pageSize.Height);
}
var host = new VisualHost(visual);
page.Children.Add(host);
return new DocumentPage(page);
}
private void DrawPageHeader(DrawingContext dc, ref double y)
{
dc.DrawText(
new FormattedText("报表标题", CultureInfo.CurrentCulture,
FlowDirection.LeftToRight, new Typeface("Microsoft YaHei"),
18, Brushes.DarkBlue, VisualTreeHelper.GetDpi(new DependencyObject()).PixelsPerDip),
new Point(48, y));
y += _headerHeight;
}
private void DrawDataRow(DrawingContext dc, object item, int index, double y)
{
// 绘制单行数据
dc.DrawText(
new FormattedText(item.ToString() ?? "", CultureInfo.CurrentCulture,
FlowDirection.LeftToRight, new Typeface("Microsoft YaHei"),
11, Brushes.Black, VisualTreeHelper.GetDpi(new DependencyObject()).PixelsPerDip),
new Point(48, y));
}
private void DrawPageFooter(DrawingContext dc, int current, int total, double pageHeight)
{
var text = $"第 {current}/{total} 页";
dc.DrawText(
new FormattedText(text, CultureInfo.CurrentCulture,
FlowDirection.LeftToRight, new Typeface("Microsoft YaHei"),
10, Brushes.Gray, VisualTreeHelper.GetDpi(new DependencyObject()).PixelsPerDip),
new Point(_pageSize.Width / 2 - 30, pageHeight - 48));
}
}
/// <summary>
/// Visual 容器 — 将 DrawingVisual 放入可视化树
/// </summary>
public class VisualHost : FrameworkElement
{
private readonly Visual _visual;
public VisualHost(Visual visual) => _visual = visual;
protected override int VisualChildrenCount => 1;
protected override Visual GetVisualChild(int index) => _visual;
}导出为 XPS 并预览
// ========== 报表预览服务 ==========
/// <summary>
/// 报表预览服务 — 导出 XPS 并在 DocumentViewer 中预览
/// </summary>
public class ReportPreviewService
{
/// <summary>
/// 预览并打印 FixedDocument
/// </summary>
public void PreviewAndPrint(FixedDocument document)
{
var tempFile = Path.Combine(Path.GetTempPath(), $"report_{Guid.NewGuid():N}.xps");
try
{
// 导出为 XPS 文件
ExportToXps(document, tempFile);
// 打开预览窗口
ShowPreviewWindow(tempFile);
}
finally
{
// 清理临时文件
try { File.Delete(tempFile); } catch { }
}
}
/// <summary>
/// 导出文档为 XPS 文件
/// </summary>
public string ExportToXps(IDocumentPaginatorSource document, string? filePath = null)
{
filePath ??= Path.Combine(Path.GetTempPath(), $"report_{Guid.NewGuid():N}.xps");
var xpsDoc = new XpsDocument(filePath, FileAccess.ReadWrite);
var writer = XpsDocument.CreateXpsDocumentWriter(xpsDoc);
writer.Write(document.DocumentPaginator);
xpsDoc.Close();
return filePath;
}
/// <summary>
/// 异步导出(不阻塞 UI 线程)
/// </summary>
public async Task<string> ExportToXpsAsync(IDocumentPaginatorSource document, string filePath)
{
return await Task.Run(() => ExportToXps(document, filePath));
}
/// <summary>
/// 显示预览窗口
/// </summary>
private void ShowPreviewWindow(string xpsFilePath)
{
var previewWindow = new Window
{
Title = "打印预览",
Width = 900,
Height = 700,
WindowStartupLocation = WindowStartupLocation.CenterScreen
};
// 使用 DocumentViewer 提供缩放和翻页
var viewer = new DocumentViewer();
var xpsDoc = new XpsDocument(xpsFilePath, FileAccess.Read);
viewer.Document = xpsDoc.GetFixedDocumentSequence();
// 工具栏
var toolbar = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(8) };
var printBtn = new Button { Content = "打印", Padding = new Thickness(16, 6, 16, 6), Margin = new Thickness(0, 0, 8, 0) };
printBtn.Click += (_, _) =>
{
var dialog = new PrintDialog();
if (dialog.ShowDialog() == true)
dialog.PrintDocument(viewer.Document.DocumentPaginator, "报表打印");
};
toolbar.Children.Add(printBtn);
var grid = new Grid();
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
grid.RowDefinitions.Add(new RowDefinition());
Grid.SetRow(toolbar, 0);
Grid.SetRow(viewer, 1);
grid.Children.Add(toolbar);
grid.Children.Add(viewer);
previewWindow.Content = grid;
previewWindow.Closed += (_, _) => xpsDoc.Close();
previewWindow.ShowDialog();
}
}MVVM 打印服务
// ========== MVVM 打印服务接口 ==========
/// <summary>
/// 打印服务接口 — ViewModel 通过此接口发起打印
/// </summary>
public interface IPrintService
{
/// <summary>打印 FlowDocument</summary>
Task<bool> PrintFlowDocumentAsync(FlowDocument document, string description = "");
/// <summary>打印 FixedDocument</summary>
Task<bool> PrintFixedDocumentAsync(FixedDocument document, string description = "");
/// <summary>预览并打印</summary>
void Preview(FixedDocument document);
/// <summary>导出为 XPS</summary>
Task<string> ExportToXpsAsync(IDocumentPaginatorSource document, string filePath);
}
/// <summary>
/// ViewModel 中的打印调用
/// </summary>
public class InspectionViewModel : ObservableObject
{
private readonly IPrintService _printService;
private readonly IInspectionRepository _repository;
[ObservableProperty]
private DateTime _startDate = DateTime.Today.AddDays(-7);
[ObservableProperty]
private DateTime _endDate = DateTime.Today;
[RelayCommand]
private async Task PrintReportAsync()
{
var records = await _repository.GetByDateRangeAsync(StartDate, EndDate);
var document = new FixedDocumentReportGenerator()
.GenerateInspectionReport(records, "设备巡检报告", "XX 公司");
_printService.Preview(document);
}
[RelayCommand]
private async Task ExportReportAsync()
{
var records = await _repository.GetByDateRangeAsync(StartDate, EndDate);
var document = new FixedDocumentReportGenerator()
.GenerateInspectionReport(records, "设备巡检报告");
var filePath = $"巡检报告_{DateTime.Now:yyyyMMdd}.xps";
await _printService.ExportToXpsAsync(document, filePath);
}
}打印设置持久化
// ========== 打印设置管理 ==========
/// <summary>
/// 打印设置管理 — 保存用户打印偏好
/// </summary>
public class PrintSettingsManager
{
private const string SettingsKey = "PrintSettings";
/// <summary>
/// 保存打印设置
/// </summary>
public void SaveSettings(PrintDialog dialog, PrintQueue queue)
{
var settings = new PrintUserSettings
{
PrinterName = queue.Name,
PageMediaSize = queue.CurrentJobSettings.CurrentPrintTicket.PageMediaSize?.PageMediaSizeName?.ToString() ?? "A4",
Copies = dialog.PrintTicket.CopyCount,
IsCollated = dialog.PrintTicket.Collation == Collation.Collated
};
var json = JsonSerializer.Serialize(settings);
Properties.Settings.Default.PrintSettings = json;
Properties.Settings.Default.Save();
}
/// <summary>
/// 恢复打印设置
/// </summary>
public PrintUserSettings? LoadSettings()
{
var json = Properties.Settings.Default.PrintSettings;
if (string.IsNullOrEmpty(json)) return null;
try
{
return JsonSerializer.Deserialize<PrintUserSettings>(json);
}
catch
{
return null;
}
}
}
public class PrintUserSettings
{
public string PrinterName { get; set; } = "";
public string PageMediaSize { get; set; } = "A4";
public int Copies { get; set; } = 1;
public bool IsCollated { get; set; }
}优点
缺点
总结
WPF 打印通过 PrintDialog 和 DocumentPaginator 实现。FlowDocument 适合自动分页的流式内容(如长文本报告),FixedDocument 适合精确控制布局的报表(如表格、巡检记录)。建议先导出 XPS 提供预览,确认后再打印。生产环境中应将报表生成放在后台线程,避免阻塞 UI。
关键知识点
- FixedDocument 每页精确控制,FlowDocument 自动分页。
- A4 纸张尺寸约为 8.27 x 11.69 英寸,96 DPI 下为 793 x 1122 像素。
- XpsDocumentWriter 可将任何 DocumentPaginator 写入 XPS 文件。
- DocumentViewer 控件提供内置的缩放、翻页和打印功能。
- 自定义 DocumentPaginator 可以实现更灵活的分页逻辑。
- PrintTicket 控制打印份数、纸张方向、双面打印等参数。
项目落地视角
- 封装 ReportService 统一处理报表生成、预览和打印。
- 为常用报表(巡检、报警、配方)创建模板类,减少重复代码。
- 注意打印设置的用户偏好持久化(打印机选择、份数、纸张大小)。
- 报表生成放在后台线程(Task.Run),避免 UI 卡顿。
- 导出功能支持 XPS 和 PDF 格式。
常见误区
- 忽略打印机边距导致内容被裁切。
- 大报表没有分页导致内存溢出或内容丢失。
- 打印操作阻塞 UI 线程,导致界面无响应。
- 忘记冻结 DrawingVisual 中的资源导致内存泄漏。
- FixedPage 中使用绝对坐标,不同 DPI 屏幕下打印效果不一致。
进阶路线
- 学习使用第三方报表库(如 FastReport、Stimulsoft)提升报表能力。
- 实现 PDF 导出(通过 XPS 转 PDF 或使用 PdfSharp)。
- 研究批量打印和打印队列管理。
- 实现报表模板系统,支持用户自定义报表布局。
适用场景
- 工业巡检报告打印。
- 报警记录导出和打印。
- 标签和条码打印。
- 配方参数单据打印。
- 生产日报/月报生成。
落地建议
- 为报表定义标准模板和样式,保持品牌一致性。
- 提供打印预览功能减少误打印和纸张浪费。
- 打印操作放在后台线程,提供进度反馈。
- 保存用户最后一次的打印设置(打印机、份数等)。
排错清单
- 检查打印机边距设置是否合理(建议 40-60 像素)。
- 确认分页逻辑是否正确处理跨页内容(如表头重复)。
- 验证中文字体在打印输出中是否正确显示(可能需要嵌入字体)。
- 检查 FixedPage 坐标是否考虑了 DPI 缩放。
- 确认临时 XPS 文件是否正确清理。
复盘问题
- 报表数据量增长到什么程度需要考虑异步生成?
- 如何处理用户切换打印机后页面布局不一致的问题?
- 相比第三方报表库,WPF 原生打印方案的优势和局限是什么?
- 如何实现报表模板的动态加载和参数化?
