Using For each loop without try-catch in Visual basic 2012 - vb.net

I have this simple code:
Public Class Form1
Dim strFriends(4) As String
Private Sub ArrayElement_Click(sender As Object, e As EventArgs) Handles ArrayElement.Click
ClearList()
'Try
For Each item As String In strFriends
lstFriends.Items.Add(item)
Next
'Catch
'End Try
End Sub
Private Sub ClearList()
lstFriends.Items.Clear()
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
strFriends(0) = "tom"
strFriends(1) = "kate"
strFriends(2) = "bill"
strFriends(3) = "harry"
End Sub
End Class
If the try-catch is removed, i get System.ArgumentNullException is it compulsory to use try catch block to use For Each?

You are declaring a 5 element array: Dim strFriends(4) As String. In Vb.NET the number represents the max index on the array, not the number of elements.
But you're declaring only 4 elements. So in the foreach block, the last element is the default value for strings, which is Nothing, being unable to add it to the listview (or whatever).
You can check for every item on the array to be valid, like others suggested, or correct your code.
Try this, for example:
strFriends = New String() {"tom", "kate", "bill", "harry"}
You can use a List, too:
Dim strFriends As New List(Of String)()
strFriends.Add("tom")
strFriends.Add("kate")
strFriends.Add("bill")
strFriends.Add("harry")

Or you can check each item before adding. You are also not filling the last element and that is the reason for the exception.
If item IsNot Nothing Then
'add item
End If

Try this:
If Not String.IsNullOrEmpty(item) Then
' Add item
End If
UPDATE:
You can check to see if the array has anything in it, like this:
If strFriends.Length > 0 Then
' Do something with array
End If

No, a for each loop does not require a try block. Using try-catch for flow control is a bug. Instead, test to ensure that elements are not Nothing before adding them.

Related

How can I use a variable to reference a textbox?

I'm new to visual basic and programming in general, but I'm trying to make a statistic counter sort of program. I'm trying to use a variable to reference a textbox, for example, k_kills(i) = txtKills(i).Text. This doesn't work, however, so I then tried the following:
For i = 0 To 8
Dim tempBox As TextBox
Dim tempName As String = "txtKills" & i.ToString
tempBox = Me.Controls.Item(tempName)
k_kills(i) = tempBox.Text
Next
This also doesn't work and spits out an error each time saying that 'tempBox was Nothing'.
Can anyone tell me if I can make this work?
Thanks.
You will need to find the control in some collection. By default the control would exist in its parent's Controls property and since you're trying to get the control by its name then you could use ControlCollection's Find method. If you can guarantee that the control's parent is the Form then you'd call:
Dim tempBox As TextBox = DirectCast(Me.Controls.Find(tempName, False), TextBox)
But if there is the possibility that the control's parent is something other than the Form then you'd call:
Dim tempBox As TextBox = DirectCast(Me.Controls.Find(tempName, True), TextBox)
The first would execute slightly quicker because it only iterates over the current ControlCollection whereas the second could take longer because if it cannot find the control in the current ControlCollection then it starts to iterate over the child controls as well.
Assuming the controls are all in Form as parent and they all start with txtKills...
If you are going to use these text boxes as a group for several actions you may want to build an array or list of TextBox.
Dim Kills(7) As TextBox
Private Sub CreateTextBoxArray()
Dim index As Integer
For Each ctrl As Control In Controls
If ctrl.Name.StartsWith("txtKills") Then
Kills(index) = DirectCast(ctrl, TextBox)
index += 1
End If
Next
End Sub
Private Sub ClearKillTextBoxes()
For Each t In Kills
t.Clear()
Next
End Sub
Private Function GetTextFromKillBoxes() As List(Of String)
Dim lst As New List(Of String)
For Each t In Kills
lst.Add(t.Text)
Next
Return lst
End Function
After Mary's comment I edit my answer to add this line --> My code does not work if Option Strict is On and 'For' starting in 0 or 1 or any number and txtKills[X] exists.
This was my previous answer and I don't know if I have to delete or not:
Your code works fine but I think you have an error because your For starts in 0 and you don't have any "txtKills0". I've tested it now:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim k_kills(10) As String '<< Ignore the length
For i = 1 To 7
Dim tempBox As TextBox
Dim tempName As String = "txtKills" & i.ToString
tempBox = Me.Controls.Item(tempName)
k_kills(i) = tempBox.Text
MsgBox(k_kills(i))
Next
End Sub

Most efficient way to refer to multiple controls. IE: the multi-WITH

I have had a little look around and cannot seem to find an easy way to refer to multiple controls in stuff like an IF statement, or to set a property to multiple controls etc. (IF exists = true!)
The 'shortcut'if you like; would best be described in illegal code such as:
Public Sub BreakCompiler()
if string.IsNullOrEmpty(Textbox1.text, textbox2.text, textbox3.text) Then .....
'As opposed to
If String.IsNullOrEmpty(PartNumTextBox.Text) Or _
String.IsNullOrEmpty(PartNameTextBox.Text) Or _
String.IsNullOrEmpty(PartGRNTextBox.Text) Or_
String.IsNullOrEmpty(SerialNumTextBox.Text) Then
'Warn user
Else
'do nofin.
End If
Or even more outlandish:
WITH Textbox1.text, textbox2.text, textbox3.text
.ReadOnly = true
END WITH
End sub
The idea is to prevent having to run 3 if statements, or whatever, that basicaly do the same thing to 3 different objects...etc.. etc. Similar to a handler for multiple events that can be separated by a ','.
Im aware of looping through controls (IE for each control in groupbox.controls for eg) but that wouldn't quite achieve what I'm after here. (Say you wanted to skip a couple?)
Just thought id check the collective wisdom.
As far as I know, there isn't anything similar to a handler for control properties in if statements.
You either have to do them separately (or add them all into an array), or, as you mentioned, go through a subset within another control.
However, one thing you can do (say you only wanted to change a certain type of control), is this:
For Each tb As TextBox In Me.Controls.OfType(Of TextBox)
'do stuff here
Next
Another option is to create a Sub that will make the changes for you, and then pass each into the Sub.
Private Sub changeTextBox(tBox as TextBox)
'make changes here
End Sub
You want to process a variable length collection of some type of object. One way to do this is to write helper methods that have an argument decorated with ParamArray keyword.
Public Shared Function AnyIsNullOrEmpty(ParamArray controls As Control()) As Boolean
Dim ret As Boolean
For i As Int32 = 0 to controls.GetUpperBound(0)
ret = String.IsNullOrEmpty(controls(i).Text)
If ret then Exit for
Next
Return ret
End Function
Public Shared Sub SetTBReadOnlyProperty(value As Boolean,ParamArray textboxes As TextBox())
For i As Int32 = 0 to textboxes.GetUpperBound(0)
textboxes(i).ReadOnly=value
Next
End Sub
Possible usage:
Private Sub DemoUsage
If AnyIsNullOrEmpty(TextBox1,TextBox3,TextBox4) then
' at least one is empty
Else
' all have value
End If
SetTBReadOnlyProperty(True,TextBox1,TextBox3,TextBox4)
End Sub
The newer versions of .NET allow you to use the .ForEach Linq extension on a List object. If you build a e.g. a List(Of TextBox) you can use .ForEach with an anonymous method to quickly iterate the controls in the List and manipulate properties and so forth. It's still a loop - but in a much more compact form. You can be selective about which controls are in a List, and have multiple Lists etc.
Here's an example:
Public Class Form1
Private _BoxList As New List(Of TextBox)
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
_BoxList.ForEach(Sub(tb As TextBox) tb.Enabled = Not (tb.Text = String.Empty))
End Sub
Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles Me.Shown
_BoxList.Add(Me.TextBox1)
_BoxList.Add(Me.TextBox2)
_BoxList.Add(Me.TextBox3)
End Sub
End Class
That's just 3 TextBoxs and a Button on a vanilla form:
For the particular example I was working on when I thought "There must be a better way" Something like this works perfectly.
It might be easier if trying to do multiple non type specific operations on different things. (Thanks #Sastreen and #the_lotus)
Private Sub Validate_PartDetails() Handles PartNumTextBox.TextChanged, PartNameTextBox.TextChanged, PartGRNTextBox.TextChanged, SerialNumTextBox.TextChanged
For Each tb As TextBox In BasicDetailsCustomGroupBox.Controls.OfType(Of TextBox)
If tb.Tag = "notnull" Then
If String.IsNullOrEmpty(tb.Text) Then
tb.BackColor = Color.MistyRose
Else
tb.BackColor = Control.DefaultBackColor
End If
End If
If tb.Tag = "notnumeric" Then
If not(isnumeric(tb.Text)) Then
tb.BackColor = Color.MistyRose
Else
tb.BackColor = Control.DefaultBackColor
End If
End If
Next
End Sub
Thanks guys.

Find and Replace using another form

I have my frmMainwhich has RichTextBox1 and I have a button btnfind&Replacewhich whose click event pops out another minute form frmFindandReplace which has two textboxes: TextBoxSearch and TextBoxReplace with two buttons: replaceButton and findButton. I cannot seem to get my code for instances of finding a word in textbox and an instance of replacing it. Here is my code:
Public Class frmFindandReplace
Dim txtClientArea As RichTextBox
Private Sub TextBoxSearch_TextChanged(sender As Object, e As EventArgs) Handles TextBoxSearch.TextChanged
End Sub
Private Sub frmFindandReplace_Load(sender As Object, e As EventArgs) Handles MyBase.Load
End Sub
Private Sub replaceButton_Click(sender As Object, e As EventArgs) Handles replaceButton.Click
End Sub
Protected Friend Sub findButton_Click(sender As Object, e As EventArgs) Handles findButton.Click
Dim a As String
Dim b As String
a = TextBoxSearch.Text
b = InStr(StartPosition, a, txtClientArea)
If b Then txtClientArea.Focus()
txtClientArea.SelectionStart = b - 1
txtClientArea.SelectionLength = Len(a)
txtClientArea.ScrollToCaret()
End Sub
The findButton code doesnot even work. Throws an error!
Error 3: Overload resolution failed because no accessible 'InStr' can be called with these arguments:
'Public Function InStr(Start As Integer, String1 As String, String2 As String, [Compare As Microsoft.VisualBasic.CompareMethod = Microsoft.VisualBasic.CompareMethod.Binary]) As Integer': Value of type 'System.Windows.Forms.TextBox' cannot be converted to 'String'.
'Public Function InStr(String1 As String, String2 As String, [Compare As Microsoft.VisualBasic.CompareMethod = Microsoft.VisualBasic.CompareMethod.Binary]) As Integer': Value of type 'System.Windows.Forms.RichTextBox' cannot be converted to 'Microsoft.VisualBasic.CompareMethod'. C:\Users\Joseph GodwinKE\Documents\Visual Studio 2013\Projects\simpleapp\frmFindandReplace.VB 25 13 Simple app
I know I have not done much but am new and all my efforts of searching a solution over the internet have failed! Thank you I hope someone will help me pls.
A few pointers:
InStr returns an integer.
Check the documentation as it'll show you have the search values the wrong way around.
Turn Option Explicit on to help find your issues.
This should work better.
Private Sub findButton_Click(sender As Object, e As EventArgs) Handles findButton.Click
Dim searchString As String
Dim findPos As Integer
Try
searchString = TextBoxSearch.Text
findPos = InStr(txtClientArea.Text, searchString)
If findPos > 0 Then txtClientArea.Focus()
txtClientArea.SelectionStart = findPos - 1
txtClientArea.SelectionLength = searchString.Length
txtClientArea.ScrollToCaret()
Catch ex As Exception
MessageBox.Show(String.Concat("An error occurred: ", ex.Message))
End Try
End Sub
If you want your code work you need to pass the reference to the RichTextBox present in the first form to the findandReplace form.
Otherwise you will not be able to work with that instance of the RichTextBox.
Usually, this means that when you create and open an instance of the findandReplace form you pass the reference to the RichTextBox to work with in the call to the constructor. Something like this
Dim fReplace As frmFindandReplace = New frmFindandReplace(Me.txtClientArea)
fReplace.Show()
Here the New call reaches the constructor of frmfindandReplace. This call is usually hidden by VB.NET but you could add it writing explicit code for it
Public Class frmFindandReplace
Dim txtClientArea As RichTextBox
Public Sub New (ByVal txt as RichTextBox)
txtClientArea = txt
End Sub
Now the global variable txtClientArea inside the findandReplace class is assigned to the existing reference of the RichTextBox present in the first form and you could happily work with it
Protected Friend Sub findButton_Click(sender As Object, e As EventArgs) Handles findButton.Click
Dim a As String
a = TextBoxSearch.Text
Dim position = txtClientArea.Find(a, 0, RichTextBoxFinds.MatchCase)
.....
End Sub
And please make yourself a favor and start using the more complete methods available from the NET Framework library and stop using the old fashioned VBA methods.
For example the RichTextBox has a method that does exactly what you are trying to do in code. Find, search the content of the textbox and if it founds a match it highlight the text and return the starting position of the text.
There is no replace builtin method but having the position and the length is really simple to implement your own replacing code.
You Have defined b as a string. Change it to an integer. Also Instr doesn't allow you to set a start position, just a string to search and the string to search for and optionally the type of search - Binary or Text.
Finally rather than type If b then, use If b>0 then rather than turning off Option Strict. It's always better to write code with Option Strict on as it makes you write better code and in the long run is easier to chase down errors

What's wrong with this iterating over a collection code?

I want to pass the items from ListBox1 to ListBox2, and delete them from LisBox1. It throws a null exception at "lb2.Items.Add(item)" but can't find out why. It works fine with just one item though
I tried doing a "for each item in lb1.items... lb2.items.add(item) + lb1.items.remove(item)" but it wouldn't work for you can't modify a list while iterating over it or an exception will be thrown. Also tried other different approaches but couldn't make it work
Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
If lb1.Items.Count > 0 Then
Dim itemsAPasar((lb1.Items.Count - 1)) As Object
For Each item In lb1.Items
itemsAPasar(UBound(itemsAPasar)) = item
Next
For Each item In itemsAPasar
lb2.Items.Add(item)
Next
For Each item In itemsAPasar
lb1.Items.Remove(item)
Next
End If
End Sub
It looks like you could simplify your approach a bit...
Dim itemsToMove = lb1.Items.ToList()
For Each item in itemsToMove
lb1.Items.Remove(item)
lb2.Items.Add(item)
Next

Dynamically add Items to Combobox VB.NET

I have 2 combobox in windows application. I want to programmatically add Items to second combobox based on what is selected in the first combobox.
This is what I do in the selected index change of the combobox:
Private Sub ComboBox1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles ComboBox1.SelectedIndexChanged
Try
If ComboBox1.SelectedIndex = 0 Then
ComboBox2.Items.Add("MM")
ComboBox2.Items.Add("PP")
ElseIf ComboBox1.SelectedIndex = 1 Then
ComboBox2.Items.Add("SMS")
ElseIf ComboBox1.SelectedIndex = 2 Then
ComboBox2.Items.Add("MMS")
ComboBox2.Items.Add("SSSS")
End If
Catch ex As Exception
End Try
End Sub
It works fine, however, if I keep selecting different items it's keep adding the value over and over. I would like to add those values only once.
Also, when I add an item I would prefer to add an ID with the item description. I tried:
ComboBox2.Items.Add("SSSS", "1")
It seems that it's not working.
Any suggestions?
try this
Private Sub ComboBox1_SelectedIndexChanged(ByVal sender As Object, ByVal e As EventArgs) Handles ComboBox1.SelectedIndexChanged
Try
If ComboBox1.SelectedIndex = 0 Then
If Not (ComboBox2.Items.Contains("MM")) And Not (ComboBox2.Items.Contains("PP")) Then
ComboBox2.Items.Add("MM")
ComboBox2.Items.Add("PP")
End If
ElseIf ComboBox1.SelectedIndex = 1 Then
If Not (ComboBox2.Items.Contains("SMS")) Then
ComboBox2.Items.Add("SMS")
End If
ElseIf ComboBox1.SelectedIndex = 2 Then
If Not (ComboBox2.Items.Contains("MMS")) And Not (ComboBox2.Items.Contains("SSSS")) Then
ComboBox2.Items.Add("MMS")
ComboBox2.Items.Add("SSSS")
End If
End If
Catch ex As Exception
End Try
Regarding:
"Also, when I add an item I would prefer to add an ID with the item
description"
You can use the AddRange function of the ComboBox or make a list as I show here and use it as a datasource - as #Plutonix mentioned in his comment.
With it you can add an array of objects of a type that holds a value and a description, given that you override the ToString base function (of Object class).
Here is such a class:
Public Class CodeAndDescription
Public code As String
Public description As String
Public Overrides Function ToString() As String
Return Me.code + " - " + description
End Function
End Class
Now make a list of that upper class and add it to the combobox. Something like:
Dim lstItems As List(Of CodeAndDescription) = GetList()
yourComboBox.Items.Clear()
yourComboBox.Items.AddRange(lstItems.ToArray)
Don't forget to Cast the retrieved object when you take it out of the combo:
Dim codeDesc As CodeAndDescription = TryCast(yourComboBox.Items(0), CodeAndDescription)
I've done this on a check list, but I think the principle is the same for a ComboBox.