WinForms中如何在RichTextBox上方绘制覆盖式大X?
问题解决:在RichTextBox上实现覆盖式X(X-Out功能)
问题背景
需要给嵌套在Panel中的RichTextBox添加“X-Out”功能:勾选复选框时,在控件显示的5个字符上方绘制覆盖整个控件的大X;取消勾选则隐藏X。尝试多种方案均遇到问题:
- 直接在Panel的Paint事件绘制:内容被上层的RichTextBox遮挡,仅角落可见线条。
- 基于NativeWindow的自定义RichTextBox:未设置文本/颜色时功能正常,但设置后勾选复选框,WndProc中的WM_PAINT(消息ID=15)不再触发。
- 补充尝试:在底层Panel上叠加透明Panel并绘制,无内容显示,怀疑位置计算错误。
问题根源分析
- Panel绘制被遮挡:RichTextBox作为Panel的子控件,Z轴层级高于Panel,因此Panel上的绘制内容会被完全覆盖。
- NativeWindow实现缺陷:
- 存在拼写错误:
parentTextBox应为ParentTextBox,ParentTex应为ParentTextBox,导致无法正确触发重绘。 - 提前缓存的
textBoxGraphics易失效:控件的Graphics对象会随窗口状态变化(如最小化、重设大小)失效,不能提前创建并长期持有。
- 存在拼写错误:
- 透明Panel位置计算错误:误用了
panel1.Location(Panel相对于其父控件的坐标),但Panel的Paint事件坐标系是相对于自身的,应使用RichTextBox在Panel内的位置。
解决方案
方案一:修正NativeWindow自定义控件(推荐)
直接拦截RichTextBox的WM_PAINT消息,在控件自身绘制完成后叠加X,确保内容显示在最上层。
修正后的CustomRichTextBox代码
public class CustomRichTextBox : NativeWindow { public RichTextBox ParentTextBox { get; private set; } public bool IsChecked { get; set; } public CustomRichTextBox(RichTextBox tb) { ParentTextBox = tb; AssignHandle(ParentTextBox.Handle); // 监听控件销毁事件,释放句柄避免内存泄漏 ParentTextBox.Disposed += (s, e) => ReleaseHandle(); } protected override void WndProc(ref Message m) { const int WM_PAINT = 15; switch (m.Msg) { case WM_PAINT: // 先让RichTextBox完成自身文本绘制 base.WndProc(ref m); if (IsChecked) { // 使用临时Graphics对象绘制,避免缓存失效 using (Graphics g = ParentTextBox.CreateGraphics()) using (Pen pen = new Pen(ParentTextBox.ForeColor, 3)) { Rectangle clientRect = ParentTextBox.ClientRectangle; // 绘制覆盖整个控件的X g.DrawLine(pen, clientRect.TopLeft, clientRect.BottomRight); g.DrawLine(pen, clientRect.TopRight, clientRect.BottomLeft); } } break; default: base.WndProc(ref m); break; } } }
修正后的Form代码
public partial class ButtonDataForm : Form { CustomRichTextBox cstmRichTextBox; public ButtonDataForm() { InitializeComponent(); } void OnFormLoad(object sender, EventArgs e) { cstmRichTextBox = new CustomRichTextBox(richTextBox1); } void OnXOutChecked(object sender, EventArgs e) { cstmRichTextBox.IsChecked = xoutCheckbox.Checked; // 触发RichTextBox重绘,强制发送WM_PAINT消息 richTextBox1.Invalidate(); } }
方案二:透明Panel叠加绘制
创建与RichTextBox尺寸、位置完全一致的透明Panel,置于RichTextBox上方,在其Paint事件中绘制X。
实现步骤
- 在设计器中添加Panel(命名为
overlayPanel),设置BackColor = Color.Transparent,Size和Location与richTextBox1完全匹配,确保其Z轴层级在richTextBox1之上。 - 绑定事件代码:
void OnXOutChecked(object sender, EventArgs e) { overlayPanel.Visible = xoutCheckbox.Checked; overlayPanel.Refresh(); } void OnOverlayPanelPaint(object sender, PaintEventArgs e) { using (Pen xpen = new Pen(Color.Red, 3)) { Rectangle rect = overlayPanel.ClientRectangle; e.Graphics.DrawLine(xpen, rect.TopLeft, rect.BottomRight); e.Graphics.DrawLine(xpen, rect.TopRight, rect.BottomLeft); } }
方案三:修正Panel Paint事件位置计算
如果坚持在Panel的Paint事件中绘制,需修正坐标计算逻辑:
void OnXOutChecked(object sender, EventArgs e) { panel1.Refresh(); } void OnPanel1Paint(object sender, PaintEventArgs e) { if (!xoutCheckbox.Checked) return; using (Pen xpen = new Pen(Color.Red, 3)) { // 获取RichTextBox在Panel内的位置和尺寸 Rectangle rtBoxArea = new Rectangle(richTextBox1.Location, richTextBox1.Size); e.Graphics.DrawLine(xpen, rtBoxArea.TopLeft, rtBoxArea.BottomRight); e.Graphics.DrawLine(xpen, rtBoxArea.TopRight, rtBoxArea.BottomLeft); } }
关键注意事项
- 始终用
using语句包裹Graphics、Pen等GDI+对象,避免资源泄漏。 - NativeWindow需在控件销毁时调用
ReleaseHandle(),防止内存泄漏。 - 触发重绘时,
Invalidate()比Refresh()更高效:前者将重绘请求加入消息队列等待处理,后者强制立即重绘。
内容的提问来源于stack exchange,提问作者zynovij
相关产品推荐
相关产品推荐

