如何在不滚动和失去选择的情况下将文本附加到RichTextBox?

9

我需要在RichTextBox中追加文本,而且需要在不使文本框滚动或失去当前文本选择的情况下执行,这是否可能?

5个回答

15

在WinForms中,RichTextBox在使用文本和选取文本方法时会出现很多闪烁。

我有一个标准的替代方案,可以通过以下代码关闭绘制和滚动:

class RichTextBoxEx: RichTextBox
{
  [DllImport("user32.dll")]
  static extern IntPtr SendMessage(IntPtr hWnd, Int32 wMsg, Int32 wParam, ref Point lParam);

  [DllImport("user32.dll")]
  static extern IntPtr SendMessage(IntPtr hWnd, Int32 wMsg, Int32 wParam, IntPtr lParam);

  const int WM_USER = 0x400;
  const int WM_SETREDRAW = 0x000B;
  const int EM_GETEVENTMASK = WM_USER + 59;
  const int EM_SETEVENTMASK = WM_USER + 69;
  const int EM_GETSCROLLPOS = WM_USER + 221;
  const int EM_SETSCROLLPOS = WM_USER + 222;

  Point _ScrollPoint;
  bool _Painting = true;
  IntPtr _EventMask;
  int _SuspendIndex = 0;
  int _SuspendLength = 0;

  public void SuspendPainting()
  {
    if (_Painting)
    {
      _SuspendIndex = this.SelectionStart;
      _SuspendLength = this.SelectionLength;
      SendMessage(this.Handle, EM_GETSCROLLPOS, 0, ref _ScrollPoint);
      SendMessage(this.Handle, WM_SETREDRAW, 0, IntPtr.Zero);
      _EventMask = SendMessage(this.Handle, EM_GETEVENTMASK, 0, IntPtr.Zero);
      _Painting = false;
    }
  }

  public void ResumePainting()
  {
    if (!_Painting)
    {
      this.Select(_SuspendIndex, _SuspendLength);
      SendMessage(this.Handle, EM_SETSCROLLPOS, 0, ref _ScrollPoint);
      SendMessage(this.Handle, EM_SETEVENTMASK, 0, _EventMask);
      SendMessage(this.Handle, WM_SETREDRAW, 1, IntPtr.Zero);
      _Painting = true;
      this.Invalidate();
    }
  }
}

然后,通过我的表单,我可以愉快地拥有一个无闪烁的富文本框控件:

richTextBoxEx1.SuspendPainting();
richTextBoxEx1.AppendText("Hey!");
richTextBoxEx1.ResumePainting();

有趣。你有没有尝试过在窗体上使用.DoubleBuffered属性? - Robert Beaubien
@Robert DoubleBuffering窗体不会自动DoubleBuffer子控件。虽然有很多创造性的尝试 - LarsTech
1
@AdamBruss 它应该可以工作。为什么对你不起作用?我没有看到你的代码。如果需要帮助,请发布一个文档完备的问题。 - LarsTech
1
@AdamBruss 好的,我已经复现了。如果我输入足够的文本以产生滚动条,然后滚动到顶部并将光标放在第一行上,你追加的输出现在是黑色而不是红色。当你设置SelectionColor时,你的选择不在结尾,所以你正在给错误的选择着色。我需要尝试一些解决方法。 - LarsTech
1
@AdamBruss "Hack"的方法是提供一个rtf字符串:rtb.Select(rtb.TextLength, 0); rtb.SelectedRtf = @"{\rtf\ansi{\colortbl;\red255\green0\blue0;}\cf1 红色字符\par}";rtb.Select(rtb.TextLength, 0);rtb.SelectionColor = Color.Black; - LarsTech
显示剩余6条评论

0

这个解决方案几乎完美,但是它没有正确处理反向选择(即插入符号在选择的开头而不是结尾 - 例如使用SHIFT+LEFT或向上拖动鼠标选择文本)。

这里是一个改进版本,增加了以下功能:

  • 如果插入符号在文本末尾,则保持在那里,并在需要时滚动。
  • 如果原始选择从最后一个字符开始或结束,则任何附加的文本都包含在新选择中。

这意味着您可以将插入符号放在文本末尾并监视添加的文本(考虑日志文件监视)。 这也意味着您可以使用CTRL+A选择所有内容,并自动将任何附加的文本包含在您的选择中。

class RichTextBoxEx : RichTextBox
{
    [DllImport("user32.dll")]
    private static extern IntPtr SendMessage(IntPtr hWnd, Int32 wMsg, Int32 wParam, ref Point lParam);

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

    [DllImport("user32")]
    private static extern int GetCaretPos(out Point p);

    const int WM_USER = 0x400;
    const int WM_SETREDRAW = 0x000B;
    const int EM_GETEVENTMASK = WM_USER + 59;
    const int EM_SETEVENTMASK = WM_USER + 69;
    const int EM_GETSCROLLPOS = WM_USER + 221;
    const int EM_SETSCROLLPOS = WM_USER + 222;

    private Point oScrollPoint;
    private bool bPainting = true;
    private IntPtr oEventMask;
    private int iSuspendCaret;
    private int iSuspendIndex;
    private int iSuspendLength;
    private bool bWasAtEnd;

    public int CaretIndex
    {
        get
        {
            Point oCaret;
            GetCaretPos(out oCaret);
            return this.GetCharIndexFromPosition(oCaret);
        }
    }

    public void AppendTextWithoutScroll(string text)
    {
        this.SuspendPainting();
        this.AppendText(text);
        this.ResumePainting();
    }

    private void SuspendPainting()
    {
        if (this.bPainting)
        {
            this.iSuspendCaret = this.CaretIndex;
            this.iSuspendIndex = this.SelectionStart;
            this.iSuspendLength = this.SelectionLength;
            this.bWasAtEnd = this.iSuspendIndex + this.iSuspendLength == this.TextLength;

            SendMessage(this.Handle, EM_GETSCROLLPOS, 0, ref this.oScrollPoint);
            SendMessage(this.Handle, WM_SETREDRAW, 0, IntPtr.Zero);
            this.oEventMask = SendMessage(this.Handle, EM_GETEVENTMASK, 0, IntPtr.Zero);
            this.bPainting = false;
        }
    }

    private void ResumePainting()
    {
        if (!this.bPainting)
        {
            if (this.iSuspendLength == 0)
            {
                if (!bWasAtEnd)
                {
                    this.Select(this.iSuspendIndex, 0);
                }
            }
            else
            {
                // Original selection was to end of text
                if (bWasAtEnd)
                {
                    // Maintain end of selection at end of new text
                    this.iSuspendLength = this.TextLength - this.iSuspendIndex;
                }

                if (this.iSuspendCaret > this.iSuspendIndex)
                {
                    // Forward select (caret is at end)
                    this.Select(this.iSuspendIndex, this.iSuspendLength);
                }
                else
                {
                    // Reverse select (caret is at start)
                    this.Select(this.iSuspendIndex + this.iSuspendLength, -this.iSuspendLength);
                }
            }
            SendMessage(this.Handle, EM_SETSCROLLPOS, 0, ref this.oScrollPoint);
            SendMessage(this.Handle, EM_SETEVENTMASK, 0, this.oEventMask);
            SendMessage(this.Handle, WM_SETREDRAW, 1, IntPtr.Zero);
            this.bPainting = true;
            this.Invalidate();
        }
    }
}

0

基于LarsTech的文章,这里有一些不错的东西:

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Drawing;

namespace yournamespace
{
    class RichTextBoxEx : RichTextBox
    {
        [DllImport("user32.dll")]
        static extern IntPtr SendMessage(IntPtr hWnd, Int32 wMsg, Int32 wParam, ref Point lParam);

        [DllImport("user32.dll")]
        static extern IntPtr SendMessage(IntPtr hWnd, Int32 wMsg, Int32 wParam, IntPtr lParam);

        const int WM_USER = 0x400;
        const int WM_SETREDRAW = 0x000B;
        const int EM_GETEVENTMASK = WM_USER + 59;
        const int EM_SETEVENTMASK = WM_USER + 69;
        const int EM_GETSCROLLPOS = WM_USER + 221;
        const int EM_SETSCROLLPOS = WM_USER + 222;

        Point _ScrollPoint;
        bool _Painting = true;
        IntPtr _EventMask;
        int _SuspendIndex = 0;
        int _SuspendLength = 0;

        public bool Autoscroll = true;

        public void SuspendPainting()
        {
            if (_Painting)
            {
                _SuspendIndex = this.SelectionStart;
                _SuspendLength = this.SelectionLength;
                SendMessage(this.Handle, EM_GETSCROLLPOS, 0, ref _ScrollPoint);
                SendMessage(this.Handle, WM_SETREDRAW, 0, IntPtr.Zero);
                _EventMask = SendMessage(this.Handle, EM_GETEVENTMASK, 0, IntPtr.Zero);
                _Painting = false;
            }
        }

        public void ResumePainting()
        {
            if (!_Painting)
            {
                this.Select(_SuspendIndex, _SuspendLength);
                SendMessage(this.Handle, EM_SETSCROLLPOS, 0, ref _ScrollPoint);
                SendMessage(this.Handle, EM_SETEVENTMASK, 0, _EventMask);
                SendMessage(this.Handle, WM_SETREDRAW, 1, IntPtr.Zero);
                _Painting = true;
                this.Invalidate();
            }
        }

        new public void AppendText(string text)  // overwrites RichTextBox.AppendText
        {
            if (Autoscroll)
                base.AppendText(text);
            else
            {
                SuspendPainting();
                base.AppendText(text);
                ResumePainting();
            }
        }
    }
}

你可以这样使用它:

var textbox = new RichTextBoxEx();
textbox.Autoscroll = false;

textbox.AppendText("Hi");

0
修改了LarsTech的代码,使其自动停止自动滚动,如果插入符号不在RichTextBox中的最后位置。同时解决了输入彩色文本的问题。如果要恢复滚动,请将插入符号放置在最后位置(Ctrl-END)。
    class RichTextBoxEx : RichTextBox
    {
        [DllImport("user32.dll")]
        private static extern IntPtr SendMessage(IntPtr hWnd, Int32 wMsg, Int32 wParam, ref Point lParam);

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

        [DllImport("user32")]
        private static extern int GetCaretPos(out Point p);

        const int WM_USER = 0x400;
        const int WM_SETREDRAW = 0x000B;
        const int EM_GETEVENTMASK = WM_USER + 59;
        const int EM_SETEVENTMASK = WM_USER + 69;
        const int EM_GETSCROLLPOS = WM_USER + 221;
        const int EM_SETSCROLLPOS = WM_USER + 222;

        private Point oScrollPoint;
        private bool bPainting = true;
        private IntPtr oEventMask;
        private int iSuspendCaret;
        private int iSuspendIndex;
        private int iSuspendLength;
        private bool bWasAtEnd;
        private Color _selColor = Color.Black;

        public int CaretIndex
        {
            get
            {
                Point oCaret;
                GetCaretPos(out oCaret);
                return this.GetCharIndexFromPosition(oCaret);
            }
        }

        new public Color SelectionColor { get { return _selColor; } set { _selColor = value; } }
        new public void AppendText(string text)  // overwrites RichTextBox.AppendText
        {
            if (this.SelectionStart >= this.TextLength)
            {
                base.SelectionColor = _selColor;
                base.AppendText(text);
            }
            else
            {
                var selStart = this.SelectionStart;
                var selLength = this.SelectionLength;
                SuspendPainting();
                this.Select(this.TextLength, 0);
                base.SelectionColor = _selColor;
                base.AppendText(text);
                this.Select(selStart, selLength);
                ResumePainting();
            }
        }
        private void SuspendPainting()
        {
            if (this.bPainting)
            {
                this.iSuspendCaret = this.CaretIndex;
                this.iSuspendIndex = this.SelectionStart;
                this.iSuspendLength = this.SelectionLength;
                this.bWasAtEnd = this.iSuspendIndex + this.iSuspendLength == this.TextLength;

                SendMessage(this.Handle, EM_GETSCROLLPOS, 0, ref this.oScrollPoint);
                SendMessage(this.Handle, WM_SETREDRAW, 0, IntPtr.Zero);
                this.oEventMask = SendMessage(this.Handle, EM_GETEVENTMASK, 0, IntPtr.Zero);
                this.bPainting = false;
            }
        }

        private void ResumePainting()
        {
            if (!this.bPainting)
            {
                if (this.iSuspendLength == 0)
                {
                    if (!bWasAtEnd)
                    {
                        this.Select(this.iSuspendIndex, 0);
                    }
                }
                else
                {
                    // Original selection was to end of text
                    if (bWasAtEnd)
                    {
                        // Maintain end of selection at end of new text
                        this.iSuspendLength = this.TextLength - this.iSuspendIndex;
                    }

                    if (this.iSuspendCaret > this.iSuspendIndex)
                    {
                        // Forward select (caret is at end)
                        this.Select(this.iSuspendIndex, this.iSuspendLength);
                    }
                    else
                    {
                        // Reverse select (caret is at start)
                        this.Select(this.iSuspendIndex + this.iSuspendLength, -this.iSuspendLength);
                    }
                }
                SendMessage(this.Handle, EM_SETSCROLLPOS, 0, ref this.oScrollPoint);
                SendMessage(this.Handle, EM_SETEVENTMASK, 0, this.oEventMask);
                SendMessage(this.Handle, WM_SETREDRAW, 1, IntPtr.Zero);
                this.bPainting = true;
                this.Invalidate();
            }
        }
    }

-3
这应该可以满足你的需求:
        Dim tempStart As Int32
    Dim tempLength As Int32

    tempStart = RichTextBox1.SelectionStart
    tempLength = RichTextBox1.SelectionLength

    RichTextBox1.Text += "dsfkljwerhsdlf"

    RichTextBox1.SelectionStart = tempStart
    RichTextBox1.SelectionLength = tempLength

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接