Avi Cohen
Avi Cohen

Reputation: 21

WinForm RichTextbox auto scroll only when scrollbar caret is in the bottom

I am using a RichTextBox in Winforms to display live stream of text. When new data comes in, the RichTextBox scrolls down to display the latest added text.

If the user wants to view previous data by moving the scrollbar caret up, the RichTextBox will scroll up momentarily, but new text coming in will scroll it back down.

The behavior I'm expecting is similar to the one used in terminals:

I am using AppendText() and ScrollToCaret() in my code.

I tried several answers to similar problems but they did not work.

Upvotes: 2

Views: 54

Answers (1)

György Kőszeg
György Kőszeg

Reputation: 18013

In one of my projects I needed a similar solution where I used a RichTextBox to display logs. The problem is easier to solve if we update the requirements a tiny little bit:

  • If the scrollbar cursor caret is in the bottom: display the latest text in the bottom.
  • If the scrollbar cursor caret is not in the bottom: the text displayed is frozen, allowing the user to read this text. If he moves the scrollbar cursor caret to the bottom it continues displaying the latest text.

Meaning that you have to click or navigate by the keyboard to the position that you want to fix, rather than just scrolling up/down. And if you navigate back to the bottom (either by clicking or by pressing Ctrl+End), then you can see the latest updates again.

If you can live with these requirements, then here is a possible solution:

private void AppendText(string text, Color color)
{
    int len = _richTextBox.TextLength;

    // empty text box: trivial case
    if (len == 0)
    {
        _richTextBox.ForeColor = color;
        _richTextBox.Text = message;
        _richTextBox.SelectionStart = message.Length;
        return;
    }

    // Saving the original position of the cursor.
    // If it's not at the very end, then it must be reset after appending the text.
    var selStart = _richTextBox.SelectionStart;
    var selLength = _richTextBox.SelectionLength;
    var resetSelection = selStart != len;

    // Appending the text with the specified color at the bottom
    _richTextBox.SelectionStart = len;
    _richTextBox.SelectionColor = color;
    _richTextBox.AppendText(message);

    // Optional extra: removing lines from the top to prevent growing indefinitely
    // (RichTextBox can be really slow with more then 10K lines)
    int lines = _richTextBox.GetLineFromCharIndex(len = _richTextBox.TextLength);
    if (lines > MaxLines)
    {
        int removeLen = _richTextBox.GetFirstCharIndexFromLine(lines - MaxLines);
        _richTextBox.Select(0, removeLen);
        selStart -= removeLen;
        len -= removeLen;
        _richTextBox.ReadOnly = false;
        _richTextBox.SelectedText = String.Empty;
        _richTextBox.ReadOnly = true;
        if (selStart < 0)
            resetSelection = false;
    }

    // The key part: resetting the original position
    if (resetSelection)
    {
        _richTextBox.SelectionStart = selStart;
        _richTextBox.SelectionLength = selLength;
    }
    else
        _richTextBox.SelectionStart = len;
}

Upvotes: 1

Related Questions