Autoscroll to the bottom of a multiline textbox

13,273

Solution 1

If you're using AppendText, you can completely get rid of that console_TextChanged method because AppendText already does this for you.

For some reason (may be a bug?) when the TextBox is not exposed to the screen, AppendText doesn't seem to scroll to end. I have no good explanation now, need to look into the .Net framework source.

As a workaround just move all your code to MyBase.Shown event, not Load event. That works as expected, difference is Shown event will be raised as soon as the Form is first rendered in the screen as opposed to Load which gets fired before the form is rendered.

Solution 2

One more useful solution:

textBox1.SelectionStart = textBox1.Text.Length
textBox1.ScrollToCaret()

It just put cursor at the end of text in textbox and scroll to current cursor position. Worked for me perfectly!

Solution 3

You can do it in load event but it is more complicated:

<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Private Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal Msg As UInteger, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr
End Function

After setting the text in textbox:

If (console.IsHandleCreated) Then
    'set focus
    SendMessage(console.Handle, &H7, IntPtr.Zero, IntPtr.Zero) 'WM_SETFOCUS
    'move caret to the end
    SendMessage(console.Handle, &HB1, CType(-1, IntPtr), CType(-1, IntPtr)) 'EM_SETSEL
    'scroll to the end
    SendMessage(console.Handle, &HB6, IntPtr.Zero, CType(console.Lines.Length, IntPtr)) 'EM_LINESCROLL
Else
    MsgBox("console window is not created")
End If

Solution 4

Using a RichTextBox instead of a TextBox you can use this code.

'SENDMESSAGE constants
'move to the last row in a RichTextBox
'you can obtain the same effect using ScrollToCaret but it works only if Focus is on RichTextBox
Private Const WM_VSCROLL As Int32 = &H115
Private Const SB_BOTTOM As Int32 = 7

Private Sub WriteLog(ByVal strLineLog As String)

    If Me.rtbLog.Text = "" Then
        Me.rtbLog.Text = strLineLog
    Else
        Me.rtbLog.AppendText(System.Environment.NewLine & strLineLog)
    End If

    SendMessage(Me.rtbLog.Handle, WM_VSCROLL, SB_BOTTOM, 0)

End Sub
Share:
13,273
AStopher
Author by

AStopher

A network engineer went to the doctor. He told the doctor: "It hurts when IP!"

Updated on June 16, 2022

Comments

  • AStopher
    AStopher almost 2 years

    I have started to make a master-server program for a small project involving rebuilding a game on a newer engine. The master server program currently looks like this:

    enter image description here

    The large textbox with the 'Found 4 installed processor(s)' is a 'console' which outputs raw event messages sent to and from clients/gameservers using the master-server. It cannot be inputted into and the administrator (the only person who has access to this interface of the master-server program) can only copy from the textbox; they cannot delete/add anything.

    The issue is that because it's supposed to be a 'console', it should automatically scroll down to the last line of the multiline textbox.

    There are many questions on Stack Overflow which cover this (this one for example), but I haven't been able to get it working (the textbox doesn't scroll down) when placing the code within the console_TextChanged subroutine. I have tried this:

    Private Sub console_TextChanged(sender As Object, e As EventArgs) Handles console.TextChanged
        console.AppendText(Text)
        console.Select(console.TextLength, 0)
        console.ScrollToCaret()
    End Sub
    

    It doesn't work, but it does however cause a bug in the program where each line is appended with the program's title quite a few times:

    [net 11:32:22.243] System Started.Server Network | Crysis Wars 1.5Server Network | Crysis Wars 1.5Server Network | Crysis Wars 1.5Server Network | Crysis Wars 1.5Server Network | Crysis Wars 1.5Server Network | Crysis Wars 1.5Server Network | Crysis Wars 1.5Server Network | Crysis Wars 1.5Server Network | Crysis Wars 1.5Server Network | Crysis Wars 1.5
    

    Some C# solutions have also worked for me in Visual Basic .Net in the past, and so I have tried some of the ones on Stack Overflow but I haven't been able to get these working either.

    Is this really the correct way to autoscroll a multilined textbox, and if so, why is it not working for me?

    The complete (relevant) code:

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        console.Text = GetNetTime() + "System Started."
        WriteToConsole("Working Area: " + CStr(My.Computer.Screen.WorkingArea().Width) + "*" + CStr(My.Computer.Screen.WorkingArea().Height))
        WriteToConsole("Found " + CStr(Environment.ProcessorCount) + " installed processor(s)")
        Dim i As Integer = 0
        While (i < Environment.ProcessorCount)
            WriteToConsole("Processor " + CStr(i) + ": " + My.Computer.Registry.LocalMachine.OpenSubKey("Hardware\Description\System\CentralProcessor\" + CStr(i)).GetValue("ProcessorNameString"))
            WriteToConsole("            Family: " + My.Computer.Registry.LocalMachine.OpenSubKey("Hardware\Description\System\CentralProcessor\" + CStr(i)).GetValue("Identifier"))
            WriteToConsole("            Manufacturer: " + My.Computer.Registry.LocalMachine.OpenSubKey("Hardware\Description\System\CentralProcessor\" + CStr(i)).GetValue("VendorIdentifier"))
            i += 1
        End While
        WriteToConsole("Starting networking services")
    End Sub
    Private Sub console_TextChanged(sender As Object, e As EventArgs) Handles console.TextChanged
        console.AppendText(Text)
        console.Select(console.TextLength, 0)
        console.ScrollToCaret()
    End Sub
    Function GetNetTime()
        Return "[net " + CStr(DateTime.UtcNow.Hour) + ":" + CStr(DateTime.UtcNow.Minute) + ":" + CStr(DateTime.UtcNow.Second) + "." + CStr(DateTime.UtcNow.Millisecond) + "] "
    End Function
    Function WriteToConsole(ByVal input As String)
        console.AppendText(Environment.NewLine & GetNetTime() + input)
        Return -1
    End Function