Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to prevent TextBox auto scrolls when append text?

I have a multi-line TextBox with a vertical scrollbar that logs data from a realtime process. 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 decides when the TextBox is allowed to auto scrolls. Is there anyway to do this?

Note:

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

If we have a solution to do that, then one more question is how to prevent that 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 issue 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 problem. Thanks for cgyDeveloper's source code.

Does anyone have a simpler way?

like image 783
Huy Nguyen Avatar asked Sep 13 '13 19:09

Huy Nguyen


1 Answers

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));
    }
like image 190
Sorceri Avatar answered Sep 22 '22 18:09

Sorceri