You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#窗体中PrintDocument打印时文本溢出无法自动换行如何解决

C# PrintDocument打印RichTextBox溢出文本自动换行实现方案

RichTextBox编辑状态下的自动换行是控件根据自身显示宽度做的内置适配,直接调用PrintDocument打印时,默认不会按打印页的可打印区域重新计算折行位置,就会出现超出区域的文本被截断、无法自动换行的问题。

推荐实现方案(支持保留富文本格式)

借助Win32 API的EM_FORMATRANGE消息,让RichTextBox直接根据指定的打印区域宽度自动完成折行计算、渲染输出,同时可以自动处理分页,兼容富文本的字体、颜色、内嵌元素格式。

  • 首先引入所需的Win32 API和结构体定义
using System.Runtime.InteropServices;

[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;
}

private const int WM_USER = 0x0400;
private const int EM_FORMATRANGE = WM_USER + 57;

[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
  • 在PrintDocument的对应事件中编写核心打印逻辑,代码中可直接指定打印边距,控件会自动按边距宽度折行
// 定义全局变量记录多页打印时的起始字符位置
private int _printStartChar = 0;

private void printDocument1_BeginPrint(object sender, System.Drawing.Printing.PrintEventArgs e)
{
    // 打印开始时重置起始位置
    _printStartChar = 0;
}

private void printDocument1_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)
{
    RichTextBox targetRtb = richTextBox1;
    // 将打印边距单位转换为RichTextBox渲染使用的缇单位
    RECT printRect = new RECT()
    {
        Top = (int)Math.Round(e.MarginBounds.Top * 100 / e.Graphics.DpiY),
        Bottom = (int)Math.Round(e.MarginBounds.Bottom * 100 / e.Graphics.DpiY),
        Left = (int)Math.Round(e.MarginBounds.Left * 100 / e.Graphics.DpiX),
        Right = (int)Math.Round(e.MarginBounds.Right * 100 / e.Graphics.DpiX)
    };

    IntPtr hdc = e.Graphics.GetHdc();
    FORMATRANGE fmtParam = new FORMATRANGE()
    {
        hdc = hdc,
        hdcTarget = hdc,
        rc = printRect,
        rcPage = printRect,
        chrg = new CHARRANGE()
        {
            cpMin = _printStartChar,
            cpMax = -1 // -1代表处理到文本末尾
        }
    };

    IntPtr paramPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(fmtParam));
    Marshal.StructureToPtr(fmtParam, paramPtr, false);
    // 发送渲染消息,返回值为当前页渲染完成后最后一个字符的索引
    IntPtr result = SendMessage(targetRtb.Handle, EM_FORMATRANGE, (IntPtr)1, paramPtr);
    int lastCharOnPage = result.ToInt32();

    // 释放非托管资源
    Marshal.FreeCoTaskMem(paramPtr);
    e.Graphics.ReleaseHdc(hdc);
    SendMessage(targetRtb.Handle, EM_FORMATRANGE, IntPtr.Zero, IntPtr.Zero);

    // 判断是否需要分页
    if (lastCharOnPage < targetRtb.TextLength)
    {
        e.HasMorePages = true;
        _printStartChar = lastCharOnPage;
    }
    else
    {
        e.HasMorePages = false;
    }
}

简易纯文本方案

如果不需要保留富文本格式,也可以直接用GDI+的测量方法手动折行:

  • 调用e.Graphics.MeasureString逐段测量文本在打印区域宽度下的可容纳字符数
  • 拆分文本为逐行内容,调用e.Graphics.DrawString逐行输出,直到填满当前页后触发分页
  • 注意:该方案会丢失RichTextBox内的字体样式、颜色、图片等富文本格式,仅适合纯文本打印场景。

问题示例截图

内容的提问来源于stack exchange,提问作者Manly

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 08:57:38