Trying to understand Control.BeginInvoke code over at MSDN - vb.net

I am referring to this document on MSDN. I understand what ".BeginInvoke" does, however looking at the example code on the document
Delegate Sub MyDelegate(myControl As Label, myArg2 As String)
Private Sub Button_Click(sender As Object, e As EventArgs)
Dim myArray(1) As Object
myArray(0) = New Label()
myArray(1) = "Enter a Value"
myTextBox.BeginInvoke(New MyDelegate(AddressOf DelegateMethod), myArray)
End Sub 'Button_Click
Public Sub DelegateMethod(myControl As Label, myCaption As String)
myControl.Location = New Point(16, 16)
myControl.Size = New Size(80, 25)
myControl.Text = myCaption
Me.Controls.Add(myControl)
End Sub 'DelegateMethod
The delegate myDelegate (and the DelegateMethod) accepts a control and a string, but, at the .BeginInvoke, a Label control is passed and an array...
myTextBox.BeginInvoke(New MyDelegate(AddressOf DelegateMethod), myArray)
and in the "DelegateMethod" there is
myControl.Text = myCaption
Shouldn't a string be passed instead of the array? Am I missing something?

BeginInvoke can accept two parameters. One is a delegate, in this case AddressOf DelegateMethod.
The other parameter is an array of parameters. DelegateMethod accepts two parameters: a label and a string. In order to pass these using begininvoke, an array of objects with two members is passed in to beinginvoke to match the parameters of the method: a label and a string.
So both the label and the string are passed in using this array

Your code is correct. The framework casts the parameters appropriately from the object array on your behalf.

Related

What does it mean by variable has been used before it has been assigned a value?

I created this program with the intention of bubble sorting "numbers.txt" textfile. But the line below that is bolded is giving me an error of variable has been used before it has been assigned a value. Can someone help me at this? I seem to cant figure out this line numbers2(z) = numbers(z). Cause every time I load this program, it crashes on me. I would love someone to give me some assistance
Public Class Form1
Dim currentLine As String = ""
Dim data As New System.IO.StreamReader("numbers.txt")
Dim counter As Integer = 0
Dim currentValue As Integer
Dim previousValue As Integer
Dim nextValue As Integer
Dim isLoaded As Boolean = False
Dim numbers As String()
Dim numbers2 As String()
Public Sub btnSort_Click(sender As Object, e As EventArgs) Handles btnSort.Click
If (Not isLoaded) Then
MessageBox.Show("You have not loaded")
Else
For j = 0 To counter
If (j = 0) Then
currentValue = CInt(numbers2(j))
nextValue = CInt(numbers2(j + 1))
If (currentValue > nextValue) Then
numbers2(j + 1) = numbers2(j)
numbers2(j) = numbers(j + 1)
End If
ElseIf (j = counter) Then
Continue For
ElseIf (j = counter - 1) Then
currentValue = CInt(numbers2(j))
previousValue = CInt(numbers2(j - 1))
If (currentValue > previousValue) Then
'Good
ElseIf (currentValue < previousValue) Then
numbers2(j - 1) = numbers2(j)
numbers2(j) = numbers(j - 1)
End If
Else
currentValue = CInt(numbers2(j))
previousValue = CInt(numbers2(j - 1))
nextValue = CInt(numbers(j + 1))
If (currentValue < nextValue) Then
'Good
ElseIf (currentValue > nextValue) Then
numbers2(j + 1) = numbers2(j)
numbers2(j) = numbers(j + 1)
End If
If (currentValue > previousValue) Then
'Good
ElseIf (currentValue < previousValue) Then
numbers2(j - 1) = numbers2(j)
numbers2(j) = numbers(j - 1)
End If
End If
Next
For k = 0 To counter
tbOutput.Text += numbers2(k) & vbCrLf
Next
For z = 0 To counter
numbers(z) = numbers2(z)
Next
End If
End Sub
Public Sub btnLoad_Click(sender As Object, e As EventArgs) Handles btnLoad.Click
Dim numbers As String() = currentLine.Split(New String() {Environment.NewLine}, StringSplitOptions.None)
Dim numbers2 As String()
Do While data.Peek() <> -1
'Appends currentline with results from data.readline and a new line
currentLine = currentLine & data.ReadLine() & vbNewLine
'shows the amount of lines in the file
counter += 1
Loop
'displays content from the file
tbInput.Text = currentLine
'sets
For z = 0 To counter
**numbers2(z) = numbers(z)**
Next
isLoaded = True
End Sub
Private Sub tbOutput_TextChanged(sender As Object, e As EventArgs) Handles tbOutput.TextChanged
End Sub
End Class
Fundamentally, this question speaks to understanding of the differences between variables, object references, object instances, and types. Until you really understand those differences, you will continue to struggle as a coder. Don't feel bad; you're far from alone here, and once you do get this suddenly everything is a whole lot easier.
numbers2 is a variable. Like all variables in .Net, numbers2 has a specific type: in this case, string array, which is a reference type. But that is not the same thing as saying numbers2 is itself an array. An actual array is an instance of an Object, and like all object instances exists off in memory somewhere separate from any variable. You must then give the numbers2 variable a reference to the array object. References connect variables with objects.
The reference is not the object, because there may be many references all pointing to the same object. But without a reference, an object is useless and the memory will be reclaimed by the garbage collector. The reference is not the variable, because one variable may have several different references assigned to it over its lifetime. But for the span of time the reference is assigned, the value of the variable is that reference. And a variable most certainly is not (by itself) an object. Even the Form1 variable is simply an object reference variable, and you could assign a whole new Form1 object instance to it (that VB.Net allows you to have variables and types with the same name can be frustrating, and I believe doing this by default with Windows forms is the source of much confusion in this whole "types vs reference vs object" area for many new programmers).
One way to assign a reference is while creating a new object. This is what happens when you see a Dim statement with the New keyword on the same line. This is also what happens when you have a Dim statement for array that includes a subscript (size), such as Dim items(4) As String or Dim items As String(4). The compiler here will automatically create the array object. But if you don't include a size for an array (Dim numbers2() As String), the compiler does not yet have enough information to create the array, because it doesn't know how big the object needs to be. So now you have an object reference variable, but there's no object instance assigned to it yet.
Now we know enough to begin debugging this program.
We'll start in the Load method. The beginning of this method re-declares the numbers and numbers2 arrays:
Dim numbers As String() = currentLine.Split(New String() {Environment.NewLine}, StringSplitOptions.None)
Dim numbers2 As String()
This means the method is working with completely different variables from the arrays defined at the top of the class, and so later on, in the Sort method, there's no data; the arrays are still null/Nothing.
Additionally, the declaration for numbers2 doesn't assign anything. It just creates an empty array reference. This is different than having an array object with 0 elements. There is no array object here at all yet. This is what causes the exception on the line indicated. You are trying to use a numbers2 reference variable before any object has been assigned to it.
To fix things, what I would do first of all is separate the event handlers from the code that does work; limit event code as much as possible to the statements the deal with reading and writing control properties. We'll make a new LoadData() function which accepts an argument and returns a value. The new method is not concerned with any textboxes, global or classs variables, or controls. It merely knows how to read the data in the format most appropriate for the data:
Public Function LoadData(fileName As String) As IEnumerable(Of Integer)
Return File.ReadLines(fileName).Select(Function(line) Integer.Parse(line.Trim()))
End Function
Now the existing Load method can call this function. Even better if this method handles the Form_Load, Form_Shown, Form_Activated, or similar event depending on what is most appropriate for your application, rather than a button click. Also, let's choose a better type for our numbers object:
Private numbers As List(Of Integer)
Public Sub Form1_Load(sender As Object, e As EventArgs) Handles Form1.Load
numbers = LoadData("numbers.txt").ToList()
tbInput = String.Join("\n", numbers)
End Sub
We could make this more efficient by removing a conversion back to string, but that version requires a lot more code, and it's won't make a meaningful difference until you have very large collections.
Now let's look at the sort method:
Public Sub btnSort_Click(sender As Object, e As EventArgs) Handles btnSort.Click
If numbers Is Nothing OrElse numbers.Count = 0 Then
MessageBox.Show("You have not loaded")
Return
End If
numbers = numbers.OrderBy(Function(n) n).ToList()
tbOutput.Text = String.Join("\n", numbers)
End Sub
Put it all together:
Public Class Form1
Private numbers As List(Of Integer)
Public Sub btnSort_Click(sender As Object, e As EventArgs) Handles btnSort.Click
If numbers Is Nothing OrElse numbers.Count = 0 Then
MessageBox.Show("You have not loaded")
Return
End If
numbers = numbers.OrderBy(Function(n) n).ToList()
tbOutput.Text = String.Join("\n", numbers)
End Sub
Public Sub Form1_Load(sender As Object, e As EventArgs) Handles Form1.Load
numbers = LoadData("numbers.txt").ToList()
tbInput = String.Join("\n", numbers)
End Sub
Public Function LoadData(fileName As String) As IEnumerable(Of Integer)
Return File.ReadLines(fileName).Select(Function(line) Integer.Parse(line.Trim()))
End Function
End Class
Now I suspect this is course work, where your instructor does not want you to use OrderBy() or Array.Sort(). But even in that case, follow the example from the LoadData() function: create a method separate from the button event handler which accepts an IEnumerable(Of Integer) and returns a sorted IOrderedEnumerable(Of Integer), and use the button click event handler to call this method. The result will be code that retains this same basic structure, where you still only need the one class-level variable.

Value of type string() cannot be converted to string

I keep getting this error, I tried all I could but it still says "Value type of String() cannot be converted to string."
Here is the code:
Private Sub Label1_Click(sender As Object, e As EventArgs) Handles Label1.Click
End Sub
Sub New()
InitializeComponent()
RAN = New Random
WB = New WebClient
End Sub
Private Const IDNum As String = "https://example.com/Data.php"
Private WB As WebClient
Private RAN As Random
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim Account As String() = WB.DownloadString(IDNum).Split(Environment.NewLine)
AccSplit(Account(RAN.Next(1, Account.Length)))
End Sub
Private Sub AccSplit(ByVal Account As String)
TextBox2.Text = Account.Split()
End Sub
When you call Split here:
TextBox2.Text = Account.Split()
You are getting a String array back. Calling Split with no arguments will split the String on whitespace characters. For instance, this:
Dim arr = "Hello World".Split()
is equivalent to this:
Dim arr = {"Hello", "World"}
The Text property of a TextBox is type String, so you can't assign a String array to it. That doesn't make sense. If you want to fry an egg, do you put am egg carton in the pan? The correct course of action depends on what you're actually trying to achieve. If you just want that String displayed in the TextBox then do this:
TextBox2.Text = Account
You could also do this:
TextBox2.Lines = Account.Split()
to display the array with the elements on separate lines in the TexTbox, which assumes that you have set its Multiline property to True.
TextBox2.Text is a string. The string.Split() function returns an array of strings (shown by Visual Studio as a string()). Those types don't match up. You can't just assign an array to a string. I might ask if you wanted this:
TextBox2.Text = String.Join(",", Account.Split())
That will at least compile. But it makes no sense... why split a string, just to join it back again?

Get Label through Function

all! I'm developing a BlackJack game but I've run into a little bit of a problem. When calculating score, I have to type YourCard1.Text, YourCard2.Text, YourCard3.Text, etc.
Can I make a function that gets the right label each time it's called? I want to do this so I don't have to type so much...
For example, instead of typing out "YourCard1.Text", I want to be able to type "card(1)" Is this possible? I've tried multiple ways of doing this, but to no avail. I'm having trouble figuring out how to make it work.
Assuming you have those labels on your form, YourCard1.Text, YourCard2.Text, YourCard3.Text, etc., This function should work for you. It returns the Label itself, not the Text property.
Private Function card(index As Integer) As Label
Try
Return Me.Controls.
OfType(Of Label).
Where(Function(l) l.Name = "YourCard" & index.ToString()).
Single()
Catch
Return Nothing
End Try
End Function
Note: Me.Controls returns the controls directly inside the form, but doesn't return controls inside containers in the form. If your cards are inside a panel, Panel1 for example, you would do Return Panel1.Controls.OfType(Of Label)...
Usage:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
card(1).Text = "Hello"
card(2).Text = "World"
End Sub
Edit to address comment.
You are pidgeonholed into only those semantics. So there is another way I could think of. But I wouldn't personally do this.
Public Class Form1
Private Class cardClass
Private myContainer As Control
Sub New(container As Control)
myContainer = container
End Sub
Default Public WriteOnly Property Item(ByVal index As Long) As String
Set(value As String)
card(index).Text = value
End Set
End Property
Private Function card(index As Integer) As Label
Try
Return myContainer.Controls.
OfType(Of Label).
Where(Function(l) l.Name = "YourCard" & index.ToString()).
Single()
Catch
Return Nothing
End Try
End Function
End Class
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim card As New cardClass(Me)
card(1) = "Hello"
card(2) = "World"
End Sub
End Class
The reason it's so complex is that though String is a reference type, it uses value type semantics. So when returning a string from a function, it can't refer back to the original memory location: it actually creates a copy of the string. So using function semantics won't work. Same would go for an array. It would be difficult (impossible?) to modify a string from either a function or array and have it modify the Label's Text property.

Target parameter count exception on delegate Sub

I have the following code to write some text from different functions and subs but that has been working ok but now I'm getting the target parameter count exception when I call the delegate from SerialPort DataReceived event.
I can't figure out what I'm doing wrong, Any ideas?
Delegate Sub PrintSmsLogDelegate(ByVal NewText As String, ByVal NewLine As Boolean)
Protected Friend Sub PrintSmsLog(ByVal NewText As String, Optional ByVal NewLine As Boolean = True)
If Me.InvokeRequired Then
Dim Txt As New PrintSmsLogDelegate(AddressOf PrintSmsLog)
'Me.Invoke(Txt, NewText)'This fail too
Me.Invoke(Txt, New Object() {NewText}) '<--- TargetParameterCountException
Else
'...
End If
End Sub
Private Sub SmsSerialPort_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SmsSerialPort.DataReceived
'... code to receive data and save it in "Lines" variable
Dim Lines as String
Me.PrintSmsLog(Lines, False)
End Sub
The problem is that your PrintSmsLogDelegate delegate declaration contains 2 required parameter.
So you have to provide the 2nd parameter as well.
The method signature for Invoke method is this:
Function Control.Invoke(method As [Delegate], ParamArray args As Object()) As Object
So you should call your PrintSmsLogDelegate delegate instance (which is Txt) with two parameters even if the PrintSmsLog method does not require the 2nd parameter.
Me.Invoke(Txt, NewText, True)
You cannot call the Invoke method with a single array parameter. Due to ParamArray keyword an array will be automatically created of the multiple parameter you specify.

Backgroundworker progress: how can it report a double number instead of integer

I am developing a window application in VB.net and i as using backgroundworker.
Maybe it is a very simple question, but is it possible report the progress as a double number and not as the integer part of the progress percentage?
I need the full number in order to display some more info, and i can only do this when i know the exact iteration the algorithm is in.
Is there any easy way doing so?
Thanks in advance!
The ReportProgress method has two overloads. The first one takes only a percentProgress As Integer parameter, but the second one takes an additional userState As Object parameter. With that second overload, you can pass any type of data that you want. In your case, you could pass a Double value as your user-state, like this
BackgroundWorker1.ReportProgress(0, myDouble)
Then, in the ProgressChanged event handler, you can convert the value back to a Double, like this:
Private Sub BackgroundWorker1_ProgressChanged(sender As Object, e As ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
Dim myDouble As Double = CDbl(e.UserState)
' ...
End Sub
As in the above example, if you don't need the percentProgress parameter, you can just pass a value of 0 for that parameter. You are not limited to passing just one or two values either. If you need to pass additional information, such as a status string, you could do so by creating your own class to encapsulate all of the status-related data and then pass one of those objects as your userState parameter. For instance:
Public Class MyUserState
Public Property MyDouble As Double
Public Property StatusDescription As String
End Class
Then you could call the ReportProgress method like this:
Dim myState As New MyUserState()
myState.MyDouble = 1.1
myState.StatusDescription = "Test"
BackgroundWorker1.ReportProgress(0, myState)
Then, you can read the values like this:
Private Sub BackgroundWorker1_ProgressChanged(sender As Object, e As ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
Dim myState As MyUserState = DirectCast(e.UserState, MyUserState)
' ...
End Sub