I have a textbox with the .Multiline property set to true. At regular intervals, I am adding new lines of text to it. I would like the textbox to automatically scroll to the bottom-most entry (the newest one) whenever a new line is added. How do I accomplish this?
12 Answers
At regular intervals, I am adding new lines of text to it. I would like the textbox to automatically scroll to the bottom-most entry (the newest one) whenever a new line is added.
If you use TextBox.AppendText(string text)
, it will automatically scroll to the end of the newly appended text. It avoids the flickering scrollbar if you're calling it in a loop.
It also happens to be an order of magnitude faster than concatenating onto the .Text
property. Though that might depend on how often you're calling it; I was testing with a tight loop.
This will not scroll if it is called before the textbox is shown, or if the textbox is otherwise not visible (e.g. in a different tab of a TabPanel). See TextBox.AppendText() not autoscrolling. This may or may not be important, depending on if you require autoscroll when the user can't see the textbox.
It seems that the alternative method from the other answers also don't work in this case. One way around it is to perform additional scrolling on the VisibleChanged
event:
textBox.VisibleChanged += (sender, e) =>
{
if (textBox.Visible)
{
textBox.SelectionStart = textBox.TextLength;
textBox.ScrollToCaret();
}
};
Internally, AppendText
does something like this:
textBox.Select(textBox.TextLength + 1, 0);
textBox.SelectedText = textToAppend;
But there should be no reason to do it manually.
(If you decompile it yourself, you'll see that it uses some possibly more efficient internal methods, and has what seems to be a minor special case.)
textBox1.Focus()
textBox1.SelectionStart = textBox1.Text.Length;
textBox1.ScrollToCaret();
didn't work for me (Windows 8.1, whatever the reason).
And since I'm still on .NET 2.0, I can't use ScrollToEnd.
But this works:
public class Utils
{
[System.Runtime.InteropServices.DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
private static extern int SendMessage(System.IntPtr hWnd, int wMsg, System.IntPtr wParam, System.IntPtr lParam);
private const int WM_VSCROLL = 0x115;
private const int SB_BOTTOM = 7;
/// <summary>
/// Scrolls the vertical scroll bar of a multi-line text box to the bottom.
/// </summary>
/// <param name="tb">The text box to scroll</param>
public static void ScrollToBottom(System.Windows.Forms.TextBox tb)
{
if(System.Environment.OSVersion.Platform != System.PlatformID.Unix)
SendMessage(tb.Handle, WM_VSCROLL, new System.IntPtr(SB_BOTTOM), System.IntPtr.Zero);
}
}
VB.NET:
Public Class Utils
<System.Runtime.InteropServices.DllImport("user32.dll", CharSet := System.Runtime.InteropServices.CharSet.Auto)> _
Private Shared Function SendMessage(hWnd As System.IntPtr, wMsg As Integer, wParam As System.IntPtr, lParam As System.IntPtr) As Integer
End Function
Private Const WM_VSCROLL As Integer = &H115
Private Const SB_BOTTOM As Integer = 7
''' <summary>
''' Scrolls the vertical scroll bar of a multi-line text box to the bottom.
''' </summary>
''' <param name="tb">The text box to scroll</param>
Public Shared Sub ScrollToBottom(tb As System.Windows.Forms.TextBox)
If System.Environment.OSVersion.Platform <> System.PlatformID.Unix Then
SendMessage(tb.Handle, WM_VSCROLL, New System.IntPtr(SB_BOTTOM), System.IntPtr.Zero)
End If
End Sub
End Class
I found a simple difference that hasn't been addressed in this thread.
If you're doing all the ScrollToCarat()
calls as part of your form's Load()
event, it doesn't work. I just added my ScrollToCarat()
call to my form's Activated()
event, and it works fine.
Edit
It's important to only do this scrolling the first time form's Activated
event is fired (not on subsequent activations), or it will scroll every time your form is activated, which is something you probably don't want.
So if you're only trapping the Activated()
event to scroll your text when your program loads, then you can just unsubscribe to the event inside the event handler itself, thusly:
Activated -= new System.EventHandler(this.Form1_Activated);
If you have other things you need to do each time your form is activated, you can set a bool
to true the first time your Activated()
event is fired, so you don't scroll on subsequent activations, but can still do the other things you need to do.
Also, if your TextBox
is on a tab that isn't the SelectedTab
, ScrollToCarat()
will have no effect. So you need at least make it the selected tab while you're scrolling. You can wrap the code in a YourTab.SuspendLayout();
and YourTab.ResumeLayout(false);
pair if your form flickers when you do this.
End of edit
Hope this helps!
For anyone else landing here expecting to see a webforms implementation, you want to use the Page Request Manager's endRequest event handler (https://stackoverflow.com/a/1388170/1830512). Here's what I did for my TextBox in a Content Page from a Master Page, please ignore the fact that I didn't use a variable for the control:
var prm = Sys.WebForms.PageRequestManager.getInstance();
function EndRequestHandler() {
if ($get('<%= ((TextBox)StatusWindow.FindControl("StatusTxtBox")).ClientID %>') != null) {
$get('<%= ((TextBox)StatusWindow.FindControl("StatusTxtBox")).ClientID %>').scrollTop =
$get('<%= ((TextBox)StatusWindow.FindControl("StatusTxtBox")).ClientID %>').scrollHeight;
}
}
prm.add_endRequest(EndRequestHandler);
This only worked for me...
txtSerialLogging->Text = "";
txtSerialLogging->AppendText(s);
I tried all the cases above, but the problem is in my case text s can decrease, increase and can also remain static for a long time. static means , static length(lines) but content is different.
So, I was facing one line jumping situation at the end when the length(lines) remains same for some times...