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:
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
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!
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With