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
相关产品推荐
相关产品推荐

