9

I need to append text to RichTextBox, and need to perform it without making text box scroll or lose current text selection, is it possible?

user626528
  • 13,999
  • 30
  • 78
  • 146

5 Answers5

15

The RichTextBox in WinForms is quite flicker happy when you play around with the text and select-text methods.

I have a standard replacement to turn off the painting and scrolling with the following code:

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();
    }
  }
}

and then from my form, I can happily have a flicker-free richtextbox control:

richTextBoxEx1.SuspendPainting();
richTextBoxEx1.AppendText("Hey!");
richTextBoxEx1.ResumePainting();
LarsTech
  • 80,625
  • 14
  • 153
  • 225
  • Interesting. Have you ever tried .DoubleBuffered property on the form? – Robert Beaubien Jul 01 '11 at 16:10
  • @Robert DoubleBuffering the form won't automatically DoubleBuffer the child controls. Although there have been lots of [creative ways to try it](http://stackoverflow.com/q/76993/719186). – LarsTech Jul 01 '11 at 16:15
  • Text coloring does not work with this. Any idea how to make text coloring work when using SuspendPainting? – Adam Bruss Sep 12 '18 at 18:19
  • 1
    @AdamBruss It should work. Why it doesn't work for you??? I don't see your code. Post a well documented question if you need help. – LarsTech Sep 12 '18 at 18:35
  • Hi Lars. Thanks for responding. Our app has an autoscroll checkbox to disable autoscroll so the user can go back up in the rtb and read/copy text. I've found the only time I need to suspend painting is when autoscroll is disabled AND the rtb has focus. Some of our log content is red to signify errors. The red text shows up black when suspending painting. I'll post the code which suspends painting, sets selection color to red, appends text and sets things back. – Adam Bruss Sep 14 '18 at 15:04
  • richTextBox.SuspendPainting(); richTextBox.SelectionColor = System.Drawing.Color.Red; richTextBox.AppendText(DateTime.Now.ToString() + " : should be red should be red : focused : " + richTextBox.Focused + "\n"); richTextBox.SelectionColor = System.Drawing.Color.Black; richTextBox.ResumePainting(); – Adam Bruss Sep 14 '18 at 15:04
  • 1
    @AdamBruss OK, I reproduced it. If I enter enough text to produce a scrollbar, then scroll to the top and put the cursor on the first line, your appended output is now black instead of red. When you set the SelectionColor, your selection is not at the end, so you are coloring the wrong selection. I'll have to play around with a work around. – LarsTech Sep 14 '18 at 15:50
  • @LarsTech Okay thanks. I'll try to think of a workaround as well. – Adam Bruss Sep 14 '18 at 18:48
  • @LarsTech Lars, I don't want to bother you too much. But did you find any workarounds for the text coloring issue? I'd rather not have to replace our richtextbox with something different but this may be a showstopper. – Adam Bruss Sep 17 '18 at 14:30
  • 1
    @AdamBruss The "hack" would be to supply an rtf string: `rtb.Select(rtb.TextLength, 0); rtb.SelectedRtf = @"{\rtf\ansi{\colortbl;\red255\green0\blue0;}\cf1 Red String\par}";rtb.Select(rtb.TextLength, 0);rtb.SelectionColor = Color.Black;` – LarsTech Sep 17 '18 at 17:22
  • @LarsTech That works in the test app. Thanks. I wasn't aware of this RTF support. – Adam Bruss Sep 20 '18 at 21:04
0

based on LarsTech article here something nice:

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();
            }
        }
    }
}

you can use it like this:

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

textbox.AppendText("Hi");
0

This solution is almost spot on, except that it does not correctly handle reverse selections (where the caret is at the start of the selection, not the end - e.g. SHIFT+LEFT or an upwards mouse drag is used to select text).

Here's an improved version, with the following added features:

  • If the caret is at the end of the text, it remains there, scrolling if required.
  • If the original selection started or ended on the last character, any appended text is included in the new selection.

This means you can put the caret at the end of the text and monitor text being added (think log file monitoring). It also means you can do CTRL+A to select all, and have any appended text automatically included in your selection.

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();
        }
    }
}
pcbbc
  • 81
  • 1
  • 3
0

Modified LarsTech code to automatically stop auto scrolling if caret is not at the last position in the RichTextBox. Also solved the problem with entering colored text. To resume scrolling put caret at the last position (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();
            }
        }
    }
Ray V
  • 1
  • 1
-3

This should do what you want:

        Dim tempStart As Int32
    Dim tempLength As Int32

    tempStart = RichTextBox1.SelectionStart
    tempLength = RichTextBox1.SelectionLength

    RichTextBox1.Text += "dsfkljwerhsdlf"

    RichTextBox1.SelectionStart = tempStart
    RichTextBox1.SelectionLength = tempLength
Robert Beaubien
  • 3,126
  • 4
  • 22
  • 29