'How to prevent TextBox auto scrolls when append text?

I have a multi-line TextBox with a vertical scrollbar that logs data from real-time processing. Currently, whenever a new line is added by textBox.AppendText(), the TextBox scrolls to the bottom so you can see the last entry, this great. But I have a checkbox to indicate whether TextBox is allowed to auto-scroll. Is there any way to do this?

Note:

  • I want to use the TextBox because the added text has multi-lines and alignment by whitespace, so it's not simple to use with a ListBox or a ListView.
  • I tried to add a new line by textBox.Text += text, but the TextBox constantly scrolls to the top.

If we have a solution to do that, then one more question is how to prevent the TextBox auto scrolls when the user uses the scrollbar to view somewhere else in the TextBox while the TextBox appends text?

private void OnTextLog(string text)
{
    if (chkAutoScroll.Checked)
    {
        // This always auto scrolls to the bottom.
        txtLog.AppendText(Environment.NewLine);
        txtLog.AppendText(text);

        // This always auto scrolls to the top.
        //txtLog.Text += Environment.NewLine + text;
    }
    else
    {
        // I want to append the text without scrolls right here.
    }
}

Update 1: As saggio suggests, I also think the solution to this problem is to determine the position of the first character in the current text that is displayed in the TextBox before appending text and restoring it after that. But how to do this? I tried to record the current cursor position like this, but it did not help:

int selpoint = txtLog.SelectionStart;
txtLog.AppendText(Environment.NewLine);
txtLog.AppendText(text);
txtLog.SelectionStart = selpoint;

Update 2 (the issue was resolved): I found a solution that can solve my issue here on Stack Overflow. I have optimized their code to suit my case as follows:

// Constants for extern calls to various scrollbar functions
private const int SB_VERT = 0x1;
private const int WM_VSCROLL = 0x115;
private const int SB_THUMBPOSITION = 0x4;

[DllImport("user32.dll", CharSet = CharSet.Auto)]
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);
[DllImport("user32.dll")]
private static extern bool PostMessageA(IntPtr hWnd, int nBar, int wParam, int lParam);
[DllImport("user32.dll")]
private static extern bool GetScrollRange(IntPtr hWnd, int nBar, out int lpMinPos, out int lpMaxPos);

private void AppendTextToTextBox(TextBox textbox, string text, bool autoscroll)
{
    int savedVpos = GetScrollPos(textbox.Handle, SB_VERT);
    textbox.AppendText(text + Environment.NewLine);
    if (autoscroll)
    {
        int VSmin, VSmax;
        GetScrollRange(textbox.Handle, SB_VERT, out VSmin, out VSmax);
        int sbOffset = (int)((textbox.ClientSize.Height - SystemInformation.HorizontalScrollBarHeight) / (textbox.Font.Height));
        savedVpos = VSmax - sbOffset;
    }
    SetScrollPos(textbox.Handle, SB_VERT, savedVpos, true);
    PostMessageA(textbox.Handle, WM_VSCROLL, SB_THUMBPOSITION + 0x10000 * savedVpos, 0);
}

private void OnTextLog(string text)
{
    AppendTextToTextBox(txtLog.Text, Environment.NewLine + text, chkAutoScroll.Checked);
}

Another way:

private const int SB_VERT = 0x1;
private const int WM_VSCROLL = 0x115;
private const int SB_THUMBPOSITION = 0x4;
private const int SB_BOTTOM = 0x7;

[DllImport("user32.dll", CharSet = CharSet.Auto)]
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);
[DllImport("user32.dll")]
private static extern bool PostMessageA(IntPtr hWnd, int nBar, int wParam, int lParam);

private void AppendTextToTextBox(TextBox textbox, string text, bool autoscroll)
{
    int savedVpos = GetScrollPos(textbox.Handle, SB_VERT);
    textbox.AppendText(text + Environment.NewLine);
    if (autoscroll)
    {
        PostMessageA(textbox.Handle, WM_VSCROLL, SB_BOTTOM, 0);
    }
    else
    {
        SetScrollPos(textbox.Handle, SB_VERT, savedVpos, true);
        PostMessageA(textbox.Handle, WM_VSCROLL, SB_THUMBPOSITION + 0x10000 * savedVpos, 0);
    }
}

I post these solutions for those who have a similar issue. Thanks for cgyDeveloper's source code.

Does anyone have a more straightforward way?



Solution 1:[1]

This seems pretty straight forward but I may be missing something. Use append text to scroll to the position if Autochecked is true and just add the text if you do not wish to scroll.

Update...I was missing something. You want to set the selection point and then scroll to the caret. See below.

    if (chkAutoScroll.Checked)
    {
        // This always auto scrolls to the bottom.
        txtLog.AppendText(Environment.NewLine);
        txtLog.AppendText(text);

        // This always auto scrolls to the top.
        //txtLog.Text += Environment.NewLine + text;
    }
    else
    {
        int caretPos = txtLog.Text.Length;
        txtLog.Text += Environment.NewLine + text;
        txtLog.Select(caretPos, 0);            
        txtLog.ScrollToLine(txtLog.GetLineIndexFromCharacterIndex(caretPos));
    }

Solution 2:[2]

You have to do it something like this,

textBox1.AppendText("Your text here");
// this selects the index zero as the location of your caret
textBox1.Select(0, 0);
// Scrolls to the caret :)
textBox1.ScrollToCaret();

Tested and working on VS2010 c# Winforms, i dont know about WPF but google probably has the answer for you.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2