I wrote a file streamer that reads live data being written in a txt file and displays that data in a richtextbox.
The problem is I want it to scroll down automatically as new text is added, and also at the end of the stream to show a msgbox that the stream ended.
This is the code:
Private Sub btnRadOnly_Click(sender As Object, e As EventArgs) Handles btnRadOnly.Click
RichTextBox1.Invoke(Sub()
RichTextBox1.Text = ""
End Sub)
t = New Thread(Sub()
bStop = False
While (Not bStop)
Thread.Sleep(500)
RichTextBox1.Invoke(Sub()
Using fs = New FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
Using sr = New StreamReader(fs, Encoding.Default)
RichTextBox1.Text = sr.ReadToEnd()
End Using
End Using
End Sub)
End While
End Sub)
t.Start()
End Sub
How can I do this?
CodePudding user response:
I think I gave you enough to answer your questions in comments, but here is an alternate way to do it, using Async / Await
Private Async Sub btnRadOnly_Click(sender As Object, e As EventArgs) Handles btnRadOnly.Click
Await fooAsync()
End Sub
Private Function fooAsync() As Task
Return Task.Run(
Sub()
RichTextBox1.Invoke(Sub() RichTextBox1.Text = "")
bStop = False
While (Not bStop)
Thread.Sleep(500)
Dim s As String
Using fs = New FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
Using sr = New StreamReader(fs, Encoding.Default)
s = sr.ReadToEnd()
End Using
End Using
RichTextBox1.Invoke(
Sub()
RichTextBox1.Text = s
RichTextBox1.Select(RichTextBox1.Text.Length, 0)
End Sub)
End While
Me.Invoke(Sub() MessageBox.Show("Done"))
End Sub)
End Function
CodePudding user response:
It might be tempting to add code like this after t.Start()
t.Join() 'Wait for thread to finish
MessageBox.Show("Finished")
But that would be a mistake. It would eliminate much of the benefit of using a separate thread for this, and cause the application to become unresponsive until it finishes. I think you understand that or you wouldn't be using threads in the first place, but this is useful to mention.
Once upon a time I would have solved this instead with a timer component to poll the t.ThreadState
or t.IsAlive
properties, and show the MessageBox when those change. This is still a reasonable approach; the downside is it's not so good at re-entry. You need to make sure someone doesn't click the button again and start the process a second time before the first finishes. It also spreads this feature across code in multiple locations. The good news is it gives you a nice place to check whether to scroll the richtextbox.
Another option is using a BackgroundWorker
control. It lets you do this in a way that will avoid needing to call Invoke()
or manually create any threads. You can pass the text back to the RichTextbox on the main form via the ProgressChanged
or WorkCompleted
events.
Modern code recommends an Aysnc/Await approach. This has better handling of the re-entry issue partially built in, such that you should be able to just put the MessageBox at the end of the method, with no separate timer needed. There's another answer already covering how to use it.
In either case, I suggest switching from sr.ReadToEnd()
to using sr.ReadLine()
in a loop. This will allow you to update (and scroll) the RichTextBox in a more granular way, as data arrives on the stream. Otherwise, there's no good way to update the RichText on the screen until the entire process is finished.