I am reading a temperature value from a transceiver into the serial port and I want this value to change the value of a label in my Visual Basic Form. This value changes every few seconds. I am using the code below:
Me.dataReceived.Text &= [text]
where dataReceived is the label I am using and [text] is the data I am reading from the serial port. This results in the data being displayed but instead of overwriting the label, it writes the value after each other. (The data is appended). I tried to remove the & before the = but this did not work as nothing showed up. Any ideas on what I can do?
The code I am using is the following:
'To receive data into the text field
Private Sub SerialPort1_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
While (SerialPort1.IsOpen)
ReceivedText(SerialPort1.ReadExisting()) 'This is called automatically every time data is received at the Serial Port
End While
End Sub
Private Sub ReceivedText(ByVal [text] As String)
Dim temperature As String
'This function compares the creating Thread's ID with the calling Thread's ID
If Me.dataReceived.InvokeRequired Then
Dim x As New SetTextCallback(AddressOf ReceivedText)
Me.Invoke(x, New Object() {(text)})
Else
'To output to the text box:
Me.dataReceived.Text = text
'To output to the label
temperature = text
Me.temp.Text = text
hmd.Text = temperature
End If
End Sub
If you want to overwrite the old value, you should not use &= but just = to assign the new value:
Me.dataReceived.Text = newText
&= is the same as Me.dataReceived.Text = Me.dataReceived.Text & newText
From MSDN:
The &= operator concatenates the String expression on its right to
the String variable or property on its left, and assigns the result to
the variable or property on its left.
Try this:
If (text IsNot Nothing) AndAlso (text.Trim().Length <> 0) Then
Me.dataReceived.Text = text
End If
I'm not that familiar with the SerialPort class, but I'll do my best to explain what's going on. The serial port raises the data received event when new data comes in through the serial port. Your code then reads all the existing data that has been received. When you call the ReadExisting method, it only returns what has been received up to that point. It does not wait for all the data to come in before it returns. It then calls the ReceivedText method which sets the value of some controls to the text that was received. The InvokeRequired and Invoke calls are only there to get you back on the UI thread. Apparently the SerialPort's DataReceived event may be raised on a separate thread, so you need to get back to the UI thread before you can do anything to any of the controls on the form. The change I suggested simply checked to make sure that the text recieved was not null or empty before you changed the value of the text box. The odd thing about the code is that it continues to read from the serial port until it is no longer open. I wouldn't think you'd want to do that. Rather, I would think that in each DataReceived event, you would just call ReadExisting once and then just expect it to be raised again the next time more data is received. By continuously calling ReadExisting in a loop, it must either return a null string or an empty string if there is no more data to read, which was why the text box was being blanked out.
&= is concatinating your text. use = to over write lable.
Me.dataReceived.Text = text
I'm not sure what is happening, but its likely that text does not have a value when setting the dataReceived.Text. Try this,
Private Sub ReceivedText(ByVal datatext As String)
Me.Invoke(Sub()
'To output to the text box:
Me.dataReceived.Text = datatext
Me.temp.Text = datatext
hmd.Text = datatext
End Sub)
End If
End Sub
I changed text to datatext since text could be confused with the forms local text property. I also moved the setting of the text properties of the labels/textboxes to an invoke using a lambda (note this syntax is new and only will work in VB 2010). I feel like somehow your invoke statement might not have been passing the string value properly. Also I removed the check for InvokeRequired, since the doing an invoke will work in both situations and it looks like you might just be doing threaded calls each time anyways.
If that compiles for you, it should work. If not, its likely that RecievedText is never being called. Set some breakpoints and step through the code to see that datatext has a value and that ReceivedText actually gets called.
Related
I'm running the following loop successfully when the number of items is low. However, when run against a larger list on the ListView, it seems to be taking way too long. I tested it with a list of 8,700 files and it took about two hours to complete. Is there something I can do to speed this up? I guess that removing the check for the Cancel button would help but I would like to keep that there for usability. As I've mentioned in earlier posts, I'm pretty new to Visual Basic so please provide lots of explanation with your suggestions. Thanks. Here's the code:
For i As Integer = 0 To m_CountTo
' Has the background worker be told to stop?
If BackgroundWorker1.CancellationPending Then
' Set Cancel to True
e.Cancel = True
Exit For
End If
'Select the row from the LVFiles ListView, then move the first column (0) into strSourceFilePath and the last
' column (3) into strDestFilePath. Execute the CopyFile method to copy the file.
LVFiles.Items(i).Selected = True
strSourceFilePath = LVFiles.SelectedItems(i).SubItems(0).Text
strDestFilePath = LVFiles.SelectedItems(i).SubItems(3).Text
My.Computer.FileSystem.CopyFile(strSourceFilePath, strDestFilePath, overwrite:=False)
' Report The progress of the Background Worker.
BackgroundWorker1.ReportProgress(CInt((i / m_CountTo) * 100))
' Me.LabelStatus.Text = FormatPercent((i + 1) / (intLVIndex + 1), 2) ' Show Percentage in Label
SetLabelText_ThreadSafe(Me.LabelStatus, FormatPercent(i / m_CountTo, 2))
Next
The Backgroundworker encapsulates a new thread. You cannot directly access controls that are created in another thread. If you do you will get an InvalidOperationException because of a cross-thread operation. The Backgroundworker however offers some functionality to share data (or access to controls) between threads. You should use them.
Private Sub StartBGW_Click(sender As Object, e As EventArgs) Handles StartBGW.Click
Dim dict As New Dictionary(Of String, String)
For i As Integer = 0 To m_CountTo
dict.Add(Me.LVFiles.Items(i).SubItems(0).Text,
Me.LVFiles.Items(i).SubItems(3).Text)
Next
Me.BackgroundWorker1.RunWorkerAsync(dict)
End Sub
First we prepare a dictionary that contains the source as Key and the target as Value. This object is given to the BackgroundWorker as a parameter.
Now comes the essential part:
Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Dim counter As Integer = -1
Dim dict = DirectCast(e.Argument, Dictionary(Of String, String))
For Each kvp In dict
counter += 1
' Has the background worker be told to stop?
If Me.BackgroundWorker1.CancellationPending Then
' Set Cancel to True
e.Cancel = True
Exit For
End If
'Select the row from the LVFiles ListView, then move the first column (0) into strSourceFilePath and the last
' column (3) into strDestFilePath. Execute the CopyFile method to copy the file.
My.Computer.FileSystem.CopyFile(kvp.Key, kvp.Value, overwrite:=False)
' Report The progress of the Background Worker.
Me.BackgroundWorker1.ReportProgress(CInt((counter / m_CountTo) * 100), counter)
Next
End Sub
We don't access the ListView anymore. Instead we use the dictionary that is given to us as a parameter through e.Argument. Theres also a slight difference in the BackgroundWorker1.ReportsProgress line. There's a second parameter I have used to pass the current index to the ProgressChanged event which can be obtained via e.UserState.
Private Sub BackgroundWorker1_ProgressChanged(sender As Object, e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
Me.LVFiles.Items(Convert.ToInt32(e.UserState)).Selected = True
Me.LabelStatus.Text = e.ProgressPercentage.ToString
End Sub
This event is designed to be raised with a SynchronizationContext of the calling thread, in this case the UI thread. Here we can safely access any control and update them. The index is passed as e.UserState, so we can access the relevant item and set their Selected property to true.
The biggest improvement comes from the change of Me.LVFiles.SelectedItems(i).SubItems(0).Text to Me.LVFiles.Items(i).SubItems(0).Text. I'm not a professional, but it seems that SelectedItems isn't a real list. Instead it iterates through every item using the SendMessage API until the desired index is reached. This is why it takes longer the higher your index is. Everytime it starts with the first item and iterates through them. Lot of operations.
The second improvement is the separation of code that access UI controls. It's all done in one method now. More clear and readable.
Update: #Enigmativity mentioned that SelectedListViewItemCollection implements IList and therefore is a real list. Even though it has no underlying list containing all selected items like you have in ListViewItemCollection. My point was to say, that accessing a single element is more complicated.
In my WebBrowser I got text like this: Remaining balance: 10$
I would like to convert it to another currency, I want just to read number from my browser, then after that I will send it to a Label or TextBox with the new converted currency. I am stuck here.
A screenshot of it
Private Sub LinkLabel2_LinkClicked(sender As Object, e As LinkLabelLinkClickedEventArgs) Handles LinkLabel2.LinkClicked
WebBrowser2.Navigate(TextBox9.Text + TextBox2.Text)
End Sub
Private Sub WebBrowser2_DocumentCompleted(sender As Object, e As WebBrowserDocumentCompletedEventArgs) Handles WebBrowser2.DocumentCompleted
Label1.Text = (WebBrowser2.Document.Body.InnerText)
End Sub
I have this suggestion (I know this is probably not the perfect solution):
First, try loading that same webpage in a normal web browser such as Google Chrome or Firefox or any browser which has the feature to "inspect elements", meaning to view their HTML code.
Find out the element which displays the price you want.
Note down the ID of the element (usually written something like id="someID")
Back to your program's code, include the following Function, which will get the text displayed and convert it to another currency:
Public Function ConvertDisplayedCurrency() As Decimal
Dim MyCurrencyElement As HtmlElement = WebBrowser2.Document.GetElementById("theIdYouGotInStep3") 'This will refer to the element you want.
Dim TheTextDisplayed As String = MyCurrencyElement.InnerText 'This will refer to the text that is displayed.
'Assuming that the text begins like "Remaining balance: ###", you need to strip off that first part, which is "Remaining balance: ".
Dim TheNumberDisplayed As String = TheTextDisplayed.Substring(19)
'The final variable TheNumberDisplayed will be resulting into a String like only the number.
Dim ParsedNumber As Decimal = 0 'A variable which will be used below.
Dim ParseSucceeded As Boolean = Decimal.TryParse(TheNumberDisplayed, ParsedNumber)
'The statement above will TRY converting the String TheNumberDisplayed to a Decimal.
'If it succeeds, the number will be set to the variable ParsedNumber and the variable
'ParseSucceeded will be True. If the conversion fails, the ParseSucceeded will be set
'to False.
If Not ParseSucceeded = True Then Return 0 : Exit Function 'This will return 0 and quit the Function if the parse was a failure.
'Now here comes your turn. Write your own statements to convert the number "ParsedNumber"
'to your new currency and finally write "Return MyFinalVariableName" in the end.
End Function
Call that Function probably when the document of the WebBrowser2 loads, that is, in the WebBrowser2_DocumentCompleted sub.
I hope it helps!
I have a background worker control that is set to perform a task, and update a multiline text box on my main UI using a delegate procedure. this is all working perfectly, however once the updating scrolls off the bottom of the text box, the scroll bars appear, but the continuous refreshing causes the text box to stay locked at the top. Ideally, I would like the text box to auto-scroll itself to the bottom to show the latest entry in real-time. What would be the best way to implement this?
I have tried using the scrolltocaret() method, with and without a SelectionStart = txtlog.Text.Length command preceding it. perhaps I'm putting it in the wrong place?
some code samples below:
Delegate code:
Delegate Sub updateresults_delegate(ByVal textbox As TextBox, ByVal text As String)
Private Sub updatelog_threadsafe(ByVal textbox As TextBox, ByVal text As String)
If textbox.InvokeRequired Then
Dim mydelegate As New updateresults_delegate(AddressOf updatelog_threadsafe)
Me.Invoke(mydelegate, New Object() {textbox, text})
'Me.txtlog.SelectionStart = txtlog.Text.Length
'Me.txtlog.ScrollToCaret()
Else
textbox.Text = text
End If
End Sub
main backgroundworker activity:
For i As Integer = val1 To val2
'generate an IP address from split host parts and current value of i
host = s1(0) & "." & s1(1) & "." & s1(2) & "." & i
Try 'attempt to ping the IP
Dim reply As PingReply = pingsender.Send(host, timeoutval, buffer, options)
If reply.Status = IPStatus.Success Then
name = System.Net.Dns.GetHostEntry(host)'get DNS entry
resulttext += String.Format("{1} - {2}: reply: Bytes={3} time{4} TTL={5}{0}", vbCrLf, name.HostName, reply.Address.ToString, reply.Buffer.Length, getms(reply.RoundtripTime), reply.Options.Ttl) 'print out success text
Else
resulttext += String.Format(" {1}: Ping failed. {2}{0}", vbCrLf, host, reply.Status.ToString) 'print out fail text
End If
updatelog_threadsafe(txtlog, resulttext) 'send text to textbox
System.Threading.Thread.Sleep(1000)
Catch ex As Exception
End Try
Next
I guess my main question is: I'm pretty certain that the textbox.scrolltocaret() is the correct method to use for what I want, but where is the best place for me to put it? I've tried it in the delegate, the main backgroundworker, as well as before & after the runworkerasync() method. none of these worked, and now I'm stumped!
Try it this way:
'textbox.Text = text
textbox.AppendText(text)
The code you commented out wasn't running on the GUI thread, and as M Granja pointed out, AppendText will automatically scroll to the appended text in the box, so no need to call ScrollToCaret.
xxx.SetFocus ' xxx = the name of the textbox
SendKeys "^{END}" ' pop to last line
I have a Serial connection to a device and am having trouble handling the response from the device.
If I use Hyperterminal and send the command TIME to the device, I get a response along the lines of;
TIME:13:30:30
which will keep updating on the same line. When I try and do this with my app, depending on whether I'm using a RTB or a TB to display the response, I get either;
RTB;
TIME:13:30:30
TIME:13:30:31
TIME:13:30:32
TIME:13:30:33
TIME:13:30:34
or TB;
TIME:13:30:30TIME:13:30:31TIME:13:30:32TIME:13:30:33TIME:13:30:34
Code is;
Private Sub SerialPort1_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
ReceivedText(SerialPort1.ReadExisting())
End Sub
Private Sub ReceivedText(ByVal [text] As String)
If Me.RichTextBox1.InvokeRequired Then
Dim x As New SetTextCallback(AddressOf ReceivedText)
Me.Invoke(x, New Object() {(text)})
Else
RichTextBox1.AppendText([text])
TextBox1.AppendText([text])
End If
End Sub
Any way I can get the same response I get when using Hyperterminal?
My approach would be to accumulate data into a StringBuilder until you receive an event that contains the field separator. Then cut the StringBuilder in two at the field separator, set all of the data before the separator into the Text property of the message box, and then continue processing. Something like this should do (pseudocode follows)
StringBuilder builder;
SerialPort1_DataReceived()
receivedData = SerialPort1.ReadExisting()
builder.Append(receivedData)
if receivedData contains delimiter
timeData = builder.ToString().Split(delimiter)
ReceivedText(timedata[0])
builder = new StringBuilder(timedata[1])
end
end
So we read each dispatch from the serial port into our buffer. If we notice that it contains the indicator that a new time value is starting, we split on the indicator, set the text box to contain the beginning of the buffer, and clear the buffer and re-insert the rest.
And in ReceivedText, you would change
RichTextBox1.AppendText([text])
to
RichTextBox1.Text = [text]
Apologies for the lack of VB.NET code, I'm quite rusty on the language so this will probably be easier to understand. Let me know if you have any questions, or if this works at all.
Insure your textBox (TB) has
textBox1.Multiline = True
textBox1.AcceptsReturn = True
As a TB is often used for input, a return may instead of being displayed, is used to signal user input completion.
Less likely avenue of pursuit.
The incoming data TIME:13:30:30 has an EOLine, either a CR, LF or CR+LF. ReadExisting() appears to not have any trouble with the incoming data, hence this is not likely a serial data issue, other than an extra/unexpectented EOL character may be lurking in text (in the CR + LF) case) and TB/RTB is configured to handle them differently. Simple noting the expected length of text will (in)validate this concern.
Notes:
TB "the NewLine value is removed from the input buffer."
The value of NewLine in changeable. The default is a line feed, (NewLine).
I have an exceptionhandler function that basically just writes a line to a textbox on Form1. This works fine when being run normally but the second I use a thread to start a process it cannot access the property. No exception is thrown but no text is written to the textbox:
Public Sub ExceptionHandler(ByVal Description As String, Optional ByVal Message As String = Nothing)
' Add Error To Textbox
If Message = Nothing Then
Form1.txtErrLog.Text += Description & vbCrLf
Log_Error(Description)
Else
Form1.txtErrLog.Text += Description & " - " & Message & vbCrLf
Log_Error(Description, Message)
End If
MessageBox.Show("caught")
End Sub
Is it possible to access a form's properties from a thread this way or would it be easier to write to a text file or similar and refresh the textbox properties every 10 seconds or so (Don't see this as a good option but if it's the only way it will have to do!).
Also, still new to VB so if I have done anything that isn't good practice please let me know!
No, you shouldn't access any GUI component properties from the "wrong" thread (i.e. any thread other than the one running that component's event pump). You can use Control.Invoke/BeginInvoke to execute a delegate on the right thread though.
There are lots of tutorials around this on the web - many will be written with examples in C#, but the underlying information is language-agnostic. See Joe Albahari's threading tutorial for example.
You have to use delegates. Search for delegates in VB.
Here a peace of code that does the job.
Delegate Sub SetTextCallback(ByVal text As String)
Public Sub display_message(ByVal tx As String)
'prüfen ob invoke nötig ist
If Me.RichTextBox1.InvokeRequired Then
Dim d As New SetTextCallback(AddressOf display_message)
Me.Invoke(d, tx)
Else
tx.Trim()
Me.RichTextBox1.Text = tx
End If
End Sub