Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Autoscroll to the bottom of a multiline textbox

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
like image 660
AStopher Avatar asked Oct 27 '14 11:10

AStopher


Video Answer


4 Answers

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!

like image 139
Mikhail_Sam Avatar answered Oct 08 '22 13:10

Mikhail_Sam


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.

like image 36
Sriram Sakthivel Avatar answered Oct 08 '22 11:10

Sriram Sakthivel


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

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
like image 40
tezzo Avatar answered Oct 08 '22 11:10

tezzo