You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

向TableLayoutPanel指定单元格添加内容后生成图片时RichTextBox消失问题

解决TableLayoutPanel保存为图片时RichTextBox消失的问题

哦,这个坑我踩过好多次!RichTextBox这类基于Win32原生控件的组件,和.NET的WinForms控件绘制逻辑不一样——它底层调用的是系统的richedit组件,而DrawToBitmap方法对这类原生控件的支持很差,直接调用TableLayoutPanel的DrawToBitmap会完全跳过RichTextBox的内容。

下面给你两种可行的解决方案,根据你的需求选就行:

方案一:用系统消息渲染RichTextBox内容(支持带格式文本)

这个方法能完美保留RichTextBox里的字体、颜色、换行等格式,是最靠谱的方案。

首先,你需要添加几个辅助的结构体、常量和Win32 API调用,用来触发RichTextBox的内容渲染:

[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
    public int Left;
    public int Top;
    public int Right;
    public int Bottom;
}

[StructLayout(LayoutKind.Sequential)]
private struct FORMATRANGE
{
    public IntPtr hdc;
    public IntPtr hdcTarget;
    public RECT rc;
    public RECT rcPage;
    public CHARRANGE chrg;
}

[StructLayout(LayoutKind.Sequential)]
private struct CHARRANGE
{
    public int cpMin;
    public int cpMax;
}

private const int EM_FORMATRANGE = 0x00CF;

[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);

然后写一个方法,专门用来把RichTextBox的内容绘制到指定的Graphics对象上:

private void DrawRichTextBoxContent(RichTextBox rtb, Graphics g)
{
    using (MemoryStream ms = new MemoryStream())
    {
        // 先把RichTextBox的内容保存为RTF格式,避免丢失格式
        rtb.SaveFile(ms, RichTextBoxStreamType.Rtf);
        ms.Seek(0, SeekOrigin.Begin);
        
        // 用临时RichTextBox加载内容,避免影响原控件
        using (RichTextBox tempRtb = new RichTextBox())
        {
            tempRtb.LoadFile(ms, RichTextBoxStreamType.Rtf);
            tempRtb.Size = rtb.Size;

            // 准备渲染参数,用缇(Twip)作为单位,1像素=100缇
            RECT rect = new RECT();
            rect.Top = 0;
            rect.Left = 0;
            rect.Right = rtb.Width * 100;
            rect.Bottom = rtb.Height * 100;

            FORMATRANGE fmtRange = new FORMATRANGE();
            fmtRange.hdc = g.GetHdc();
            fmtRange.hdcTarget = g.GetHdc();
            fmtRange.rc = rect;
            fmtRange.rcPage = rect;
            fmtRange.chrg.cpMin = 0;
            fmtRange.chrg.cpMax = -1; // 渲染全部内容

            // 调用系统消息触发渲染
            IntPtr wparam = new IntPtr(1);
            IntPtr lparam = Marshal.AllocCoTaskMem(Marshal.SizeOf(fmtRange));
            Marshal.StructureToPtr(fmtRange, lparam, false);

            SendMessage(tempRtb.Handle, EM_FORMATRANGE, wparam, lparam);

            // 释放资源,避免内存泄漏
            Marshal.FreeCoTaskMem(lparam);
            g.ReleaseHdc(fmtRange.hdc);
            g.ReleaseHdc(fmtRange.hdcTarget);
        }
    }
}

最后修改你的保存代码,先绘制TableLayoutPanel的基础内容,再单独绘制每个RichTextBox的内容:

// 创建和TableLayoutPanel一样大的Bitmap
Bitmap bm = new Bitmap(tableLayoutPanel1.Width, tableLayoutPanel1.Height);
using (Graphics g = Graphics.FromImage(bm))
{
    // 先绘制TableLayoutPanel的其他控件(比如PictureBox、按钮等)
    tableLayoutPanel1.DrawToBitmap(bm, new Rectangle(0, 0, tableLayoutPanel1.Width, tableLayoutPanel1.Height));
    
    // 遍历所有控件,找到RichTextBox并绘制它们的内容
    foreach (Control ctrl in tableLayoutPanel1.Controls)
    {
        if (ctrl is RichTextBox rtb && rtb.Visible)
        {
            // 获取RichTextBox在TableLayoutPanel中的位置
            Point location = rtb.Location;
            using (Graphics rtbG = Graphics.FromImage(bm))
            {
                // 把绘图原点移到RichTextBox的位置
                rtbG.TranslateTransform(location.X, location.Y);
                // 绘制内容
                DrawRichTextBoxContent(rtb, rtbG);
            }
        }
    }
}
// 保存图片
bm.Save(path);
bm.Dispose();

方案二:直接绘制纯文本(适合无格式的情况)

如果你的RichTextBox里只有纯文本,不需要保留格式,那可以直接用Graphics.DrawString来绘制,代码更简单:

Bitmap bm = new Bitmap(tableLayoutPanel1.Width, tableLayoutPanel1.Height);
using (Graphics g = Graphics.FromImage(bm))
{
    tableLayoutPanel1.DrawToBitmap(bm, new Rectangle(0, 0, tableLayoutPanel1.Width, tableLayoutPanel1.Height));
    
    foreach (Control ctrl in tableLayoutPanel1.Controls)
    {
        if (ctrl is RichTextBox rtb && rtb.Visible)
        {
            // 用RichTextBox的字体、颜色绘制文本
            using (Brush brush = new SolidBrush(rtb.ForeColor))
            {
                g.DrawString(rtb.Text, rtb.Font, brush, rtb.Location);
            }
        }
    }
}
bm.Save(path);
bm.Dispose();

注意事项

  • 确保RichTextBox的Visible属性为true,否则无法正确获取内容
  • 如果TableLayoutPanel有滚动条,需要调整坐标,确保绘制的是当前可见区域的内容
  • 所有GDI+资源(比如Bitmap、Graphics、Brush)都要记得释放,用using包裹是最安全的方式

内容的提问来源于stack exchange,提问作者Pârvănescu Stefan

火山引擎 最新活动