C# WinForm中如何实现RichTextBox内容(含格式、图片)的打印预览?
问题根源
你现有的代码仅调用DrawString绘制RichTextBox的纯文本内容,会丢失所有富文本格式(自定义字体、颜色、行间距、段落样式)和嵌入图片,也没有实现分页逻辑,完全无法满足需求。
实现思路
Windows系统原生的RichEdit控件(WinForm RichTextBox的底层控件)提供了EM_FORMATRANGE消息,支持将富文本内容完整渲染到指定的设备上下文(打印用的Graphics对象),可以1:1保留所有格式、图片,还能自动计算分页位置。我们只需要调用这个消息即可实现符合预期的打印/预览效果。
完整实现步骤
首先确保你的代码顶部已经引入了必要的命名空间:
using System.Drawing.Printing; using System.Runtime.InteropServices;
- 在窗体类中添加需要的Win32互操作定义、全局打印状态变量
// 定义EM_FORMATRANGE消息常量 private const int WM_USER = 0x0400; private const int EM_FORMATRANGE = WM_USER + 57; // 定义相关结构体 [StructLayout(LayoutKind.Sequential)] private struct RECT { public int Left; public int Top; public int Right; public int Bottom; } [StructLayout(LayoutKind.Sequential)] private struct CHARRANGE { public int cpMin; public int cpMax; } [StructLayout(LayoutKind.Sequential)] private struct FORMATRANGE { public IntPtr hdc; public IntPtr hdcTarget; public RECT rc; public RECT rcPage; public CHARRANGE chrg; } // 导入SendMessage方法 [DllImport("user32.dll")] private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, ref FORMATRANGE lParam); // 记录当前打印到的字符位置,用于分页 private int _printPosition;
- 处理
PrintDocument的BeginPrint事件,初始化打印起始位置
private void printDocument1_BeginPrint(object sender, System.Drawing.Printing.PrintEventArgs e) { // 打印开始时从第一个字符开始打印 _printPosition = 0; }
- 重写
PrintPage事件的逻辑,用EM_FORMATRANGE渲染内容
private void printDocument1_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e) { FORMATRANGE fmtRange = new FORMATRANGE(); RECT rect = new RECT(); RECT pageRect = new RECT(); // 转换打印边距为RichEdit要求的百分之一英寸单位 rect.Left = (int)e.MarginBounds.Left * 1440 / e.Graphics.DpiX; rect.Top = (int)e.MarginBounds.Top * 1440 / e.Graphics.DpiY; rect.Right = (int)e.MarginBounds.Right * 1440 / e.Graphics.DpiX; rect.Bottom = (int)e.MarginBounds.Bottom * 1440 / e.Graphics.DpiY; pageRect.Left = (int)e.PageBounds.Left * 1440 / e.Graphics.DpiX; pageRect.Top = (int)e.PageBounds.Top * 1440 / e.Graphics.DpiY; pageRect.Right = (int)e.PageBounds.Right * 1440 / e.Graphics.DpiX; pageRect.Bottom = (int)e.PageBounds.Bottom * 1440 / e.Graphics.DpiY; fmtRange.chrg.cpMin = _printPosition; fmtRange.chrg.cpMax = -1; // 打印到内容末尾 fmtRange.hdc = e.Graphics.GetHdc(); fmtRange.hdcTarget = e.Graphics.GetHdc(); fmtRange.rc = rect; fmtRange.rcPage = pageRect; // 发送消息渲染内容 IntPtr res = SendMessage(richTextBox1.Handle, EM_FORMATRANGE, (IntPtr)1, ref fmtRange); // 释放设备上下文 e.Graphics.ReleaseHdc(fmtRange.hdc); // 记录下一页的起始字符位置 _printPosition = res.ToInt32(); // 判断是否还有剩余内容需要打印 e.HasMorePages = _printPosition < richTextBox1.TextLength; // 释放缓存 SendMessage(richTextBox1.Handle, EM_FORMATRANGE, IntPtr.Zero, IntPtr.Zero); }
- 调用打印预览
private void btnPrintPreview_Click(object sender, EventArgs e) { PrintPreviewDialog previewDialog = new PrintPreviewDialog(); previewDialog.Document = printDocument1; previewDialog.ShowDialog(); }
注意事项
- 该实现会完整保留RichTextBox内所有的富文本格式、嵌入图片,分页逻辑和Office Word的打印预览逻辑一致
- 不需要硬编码坐标位置,会自动适配打印设置里的页边距、纸张大小
- 如果你需要修改打印的字体大小、样式,直接修改RichTextBox内的内容格式即可,预览会同步更新
内容的提问来源于stack exchange,提问作者Iranga Fernando
相关产品推荐
相关产品推荐

