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

RichTextBox控件调整大小时滚动异常并出现多余空白问题

RichTextBox 控件滚动重绘异常的解决办法

问题描述

RichTextBox控件存在一个异常:当控件包含足够多文本以显示垂直滚动条,且滚动至底部时,增大其高度(仍保留滚动条),控件仅显示原小尺寸下可见的文本量,下方出现多余空白;仅需轻微滚动即可触发正确重绘。该问题在.NET Framework 4.7.2和.NET 7.0中均可复现,而TextBox(Multiline=true、Scrollbars=Vertical)无此异常。

复现代码

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();

        var rtb = new RichTextBox();
        rtb.Location = new Point(12, 12);
        rtb.Size = new Size(150, 100);
        rtb.Lines = Enumerable.Range(1, 20).Select(i => $"Line {i:00}").ToArray();
        rtb.SelectionStart = rtb.TextLength;
        rtb.ScrollToCaret();
        this.Controls.Add(rtb);

        var btn = new Button { Text = "Resize", AutoSize = true };
        btn.Location = new Point(rtb.Right + rtb.Margin.Right + btn.Margin.Left, 3);
        this.Controls.Add(btn);
        btn.Click += (o, e) => rtb.Height = rtb.Height == 100 ? 200 : 100;
    }
}

已尝试无效的方案

  • 调用rtb.Refresh()、rtb.Invalidate()、rtb.PerformLayout()均无法触发正确重绘
  • 在SizeChanged事件中调用rtb.ScrollToCaret()可解决该问题,但会随机引发其他异常

可行解决办法

方案1:通过发送WM_VSCROLL消息强制滚动刷新

利用Windows API发送滚动消息,模拟轻微滚动后恢复位置,避免ScrollToCaret()的异常问题:

using System.Runtime.InteropServices;

// 定义Windows API常量
private const int WM_VSCROLL = 0x0115;
private const int SB_LINEUP = 0;
private const int SB_LINEDOWN = 1;
private const int SB_ENDSCROLL = 8;

[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int SendMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam);

// 在SizeChanged事件中调用
private void Rtb_SizeChanged(object sender, EventArgs e)
{
    var rtb = sender as RichTextBox;
    if (rtb == null) return;

    // 先向上滚动一行
    SendMessage(rtb.Handle, WM_VSCROLL, (IntPtr)SB_LINEUP, IntPtr.Zero);
    // 再向下滚动一行回到原位置
    SendMessage(rtb.Handle, WM_VSCROLL, (IntPtr)SB_LINEDOWN, IntPtr.Zero);
    // 发送结束滚动消息
    SendMessage(rtb.Handle, WM_VSCROLL, (IntPtr)SB_ENDSCROLL, IntPtr.Zero);
}

使用时给RichTextBox绑定SizeChanged事件:

rtb.SizeChanged += Rtb_SizeChanged;

方案2:临时修改滚动位置再恢复

通过获取当前滚动位置,临时调整后再恢复,触发控件重绘:

private void Rtb_SizeChanged(object sender, EventArgs e)
{
    var rtb = sender as RichTextBox;
    if (rtb == null) return;

    // 记录当前滚动位置
    int originalScrollPos = rtb.GetScrollPos(rtb.Handle, SB_VERT);
    // 滚动到上一行
    rtb.SetScrollPos(rtb.Handle, SB_VERT, originalScrollPos - 1, true);
    // 恢复原位置
    rtb.SetScrollPos(rtb.Handle, SB_VERT, originalScrollPos, true);
}

// 补充API声明
private const int SB_VERT = 1;

[DllImport("user32.dll")]
private static extern int GetScrollPos(IntPtr hWnd, int nBar);

[DllImport("user32.dll")]
private static extern int SetScrollPos(IntPtr hWnd, int nBar, int nPos, bool bRedraw);

以上两种方案均可触发控件正确重绘,且不会引发ScrollToCaret()带来的随机异常。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 07:43:12