unable to add items to a list in VB.NET - vb.net

Good morning,
so i have received this homework for the summer where i have to create a program to store a list of movies and display them, but the problem is that there isn't a defined number of movie so i can't use the constant method i've always used, so i tried doing that with variables instead, but whenever i press the input button twice the app crashes and i get the error "Index over the matrix limits"
Here's the code in the module
Module Module1
Public Structure Film
Public Titolo As String
Public Autore As String
Public Incasso As Integer
Public Nazionalita As String
End Structure
Public i As Integer = 0
Public Flm(i) As Film
End Module
And here's the input part
Public Class frmInput
Private Sub btnInserisci_Click(sender As Object, e As EventArgs) Handles btnInserisci.Click
If IsNumeric(txtIncasso.Text) = False Then
MsgBox("L'incasso deve essere un valore numerico", MsgBoxStyle.Exclamation, "Attenzione")
ElseIf txtTitolo.Text = "" Or txtAutore.Text = "" Or txtNazionalita.Text = "" Then
MsgBox("Uno o piĆ¹ valori sono vuoti", MsgBoxStyle.Exclamation, "Attenzione")
Else
Flm(i).Titolo = txtTitolo.Text
Flm(i).Autore = txtAutore.Text
Flm(i).Incasso = txtIncasso.Text
Flm(i).Nazionalita = txtNazionalita.Text
i += 1
End If
End Sub
End Class

You should use a List(Of Film) to store the inputs received.
A generic List like that has no practical limits and can grow while you add elements to it
Public Flm As List(Of Film) = new List(Of Film)
....
Else
Dim f as Film = new Film()
f.Titolo = txtTitolo.Text
f.Autore = txtAutore.Text
f.Incasso = txtIncasso.Text
f.Nazionalita = txtNazionalita.Text
Flm.Add(f)
End If
A List(Of Film) could be used like it was an array
For x As Integer = 0 To Flm.Count -1 Step 1
Console.WriteLine("Film #" & x+1)
Console.WriteLine("Titolo = " & Flm(x).Titolo)
.....
Next
And of course you can iterate over it using a simpler foreach
For Each Film f in Flm
Console.WriteLine("Film #" & x+1)
Console.WriteLine("Titolo = " & f.Titolo)
.....
Next

Although others have mentioned using List, which would probably be appropriate, you also mentioned it was a homework task, so maybe you need, or have to, use the more traditional arrays as you have shown. Bearing this in mind, and also to let you know what your problem is. You are incrementing i but not the array.
Public i As Integer = 0
Public Flm(i) As Film
Thus, Flm is 0 to 0, one element.
You add to this, all is OK.
You increment i, good, i += 1
However, you don't then increment the array, Flm(). Incrementing i doesn't automatically increment the array, Flm().
You need to use: ReDim Preserve
Thus... Change:
Else
Flm(i).Titolo = txtTitolo.Text
to:
Else
ReDim Preserve Flm(i)
Flm(i).Titolo = txtTitolo.Text
Lastly, IsNumeric and MsgBox are remnants of the VB6 days, there are vb.net equivalents. Also, using i as a global/public variable is really not good. It's very common, standard, to use i in all local subroutines and functions for little loops etc, it gets used and lost. If you look at almost all examples of code, you'll see i being used as the integer counter.

Related

VB.Net | Is there a way to reference a dynamic amount of variables as arguments to function/sub?

I'm trying to pass a dynamic amount of variables to a Sub by using ByRef;
Essentially I'm trying to create a module that I can easily import into my projects and make handling the file saving/loading process automated.
The Sub/Function would take a number of variables as references and then loop through them changing each one's value.
I realize I'm missing a crucial point in how visual basic's syntax works but I haven't been able to figure out what I need to do.
The code I've written for this is:
Public Sub LoadSaveToVars(ByRef KeyNamesAndVars() As Object, ByVal FileLoc As String = "")
If isEven(KeyNamesAndVars.Length) Then
Dim Contents As String = My.Computer.FileSystem.ReadAllText(FileLoc)
Dim isOnName As Boolean = True
Dim CurrentVal As String = ""
For i = 0 To KeyNamesAndVars.Length - 1
If isOnName Then
CurrentVal = GetStringValue(KeyNamesAndVars(i), Contents) 'Get the value of the key with the key name in the array
isOnName = False
Else
KeyNamesAndVars(i) = CurrentVal 'Set the variable referenced in the array to the value
isOnName = True
End If
Next
Else
Throw New ArgumentOutOfRangeException("The key names and variables supplied are not even.", "Error loading to variables!")
End If
End Sub
And here's how I try to use this function:
Dim TestVar1 As String = ""
Dim TestVar2 As String = ""
LoadSaveToVars({"key1", TestVar1, "key2", TestVar2})
To keep this question clean I did not include the other functions, but I did make a poor attempt at drawing what I want to happen: https://gyazo.com/eee34b8dff766401f73772bb0fef981a
In the end, I want TestVar1 to be equal to "val1" and TestVar2 to be equal to "val2" and to be able to extend this to a dynamic number of variables. Is this possible?

Visual Basic - ReDim Preserve - Object reference not set to an instance of an object [duplicate]

This question already has answers here:
What is a NullReferenceException, and how do I fix it?
(27 answers)
Closed 6 years ago.
I'm very new to programming and trying to write a program where it reads data about real estate properties from a txt file and has the option of adding another property on a new line under the rest of the properties in the txt file.
This is the sub handling the ReDim of the array at the moment:
Private Sub ExitSetTexts()
Dim propertyId As String = arrListings(UBound(arrListings)).propertyId
ReDim Preserve arrListings(UBound(arrListings) + 1)
arrListings(UBound(arrListings)).address = txtAddress.Text
arrListings(UBound(arrListings)).city = txtCity.Text
arrListings(UBound(arrListings)).state = txtState.Text
arrListings(UBound(arrListings)).postcode = txtPostcode.Text
If rbHouse.Checked = True Then
arrListings(UBound(arrListings)).type = "H"
ElseIf rbUnit.Checked = True Then
arrListings(UBound(arrListings)).type = "U"
ElseIf rbAcreage.Checked = True Then
arrListings(UBound(arrListings)).type = "A"
End If
arrListings(UBound(arrListings)).bedrooms = txtBedrooms.Text
arrListings(UBound(arrListings)).salePrice = txtSalePrice.Text
propertyId = CInt(propertyId.Trim.Remove(0, 1))
propertyId = CInt(propertyId) + 1
propertyId = CInt(propertyId).ToString("D4")
propertyId = " P" + propertyId
arrListings(UBound(arrListings)).propertyId = propertyId
End Sub
When I run the program, enter all the details and then click on the button that runs this code, it throws a NullReferenceException saying "Additional information: Object reference not set to an instance of an object" and highlights this line:
arrListings(UBound(arrListings)).address = txtAddress.Text
I presume the ReDim is not working as intended because if I change it to (UBound(arrListings) - 1) then it will rewrite the new information ontop of the last line perfectly fine, but I cannot get it to write it on a new line.
Any guidance would be greatly appreciated,
Thanks.
Edit:
Forgot to add the part where I declare the array. I feel the way I've done this is rather dodgy but it was the first way I thought of and it seems to work.
In modMain is this;
Private listings() As Listing
Further in the Module is this function;
Public Function getListings() As Listing()
Return listings
End Function
At the top of frmListings I've declared a second array;
Private arrListings() As Listing
frmListings then calls this function on frmListings_Load like this;
arrListings = getListings()
Then at the end when you close the form is calls a second sub which replaces the data in the original array with the altered data in the second array.
Like this;
setListings(arrListings)
Public Sub setListings(ByVal arrListings())
listings = arrListings
End Sub
It's very messy but I couldn't work out how to use the array in the module from the form so I just did it like that.
When you resize the array, the new elements are Nothing by default so you need to set them to something before you use them:
ReDim Preserve arrListings(arrListings.Length)
arrListings(UBound(arrListings)) = New Listing
arrListings(UBound(arrListings)).address = txtAddress.Text
...
or
Private Sub ExitSetTexts()
Dim list = New Listing
list.address = txtAddress.Text
...
list.propertyId = propertyId
ReDim Preserve arrListings(arrListings.Length)
arrListings(UBound(arrListings)) = list
End Sub
A better answer would be to use Private arrListings As New List(Of Listing), but that would need a lot of changes in your code, so easier to leave it as array.

More effective loop

I'm making a player-match-up program in Visual Basic. The program is supposed to pick random registered players and and pair them. I'm currently working on the odd-number-of-players-part.
The solution I have is working but is perhaps not that effective. Is there a better way for me to write this code?
The code is supposed to pick the random players and make sure they are not picked again. As you see, for the code to work i must make it loop thousands of times. If I don't some of the players won't show up in the listbox. Is there a better solution???
In case it's confusing "spiller" is norwegian for "player"
For i As Integer = 0 To 100000
Dim spiller1 As Integer
Dim spiller2 As Integer
Do
spiller1 = CInt(Math.Floor(Rnd() * spillerListe.Count))
spiller2 = CInt(Math.Floor(Rnd() * spillerListe.Count))
Loop Until CBool(spiller1 <> spiller2)
If brukteSpillere(spiller1) = False And brukteSpillere(spiller2) = False Then
brukteSpillere(spiller1) = True
brukteSpillere(spiller2) = True
lstSpillere.Items.Add(spillerListe(spiller1).ToString + " VS. " + spillerListe(spiller2).ToString())
End If
Next i
This is a mess... Have a List(Of Integer) with all the available index.
Loop while availableIndex.Count > 1
Pick a random index from availableIndex and remove it from that list
Pick a random index from availableIndex and remove it from that list
Add these two index to the list of pairs
End Loop
that way you don't need to check if the random values are the same or if they were already picked.
Now, if you don't want to create a list. Then threat the random number not as an index, but as the number of items to check.
Delta = RandomNumber
x = 0
For i As Integer = 0 To itemList.Count-1
If Not itemList(i).IsChoosen Then
x += 1
If x = Delta Then
' i is now your item to pick
itemList(i).IsChoosen = True
Exit For
End If
End If
Next
There are two efficient ways in approaching this problem:
Sort your player list by random number, then match up 1 with 2, 3 with 4 and so on.
Dim r As New Random
Dim randomListe = spillerListe.OrderBy(Function() r.Next).ToList
Generate two random numbers from your range, match up those players into a separate List, remove players from the original list. General two more random numbers from a smaller range (original minus 2), match up, etc.
EDIT: Having looked at MSDN, List has O(n) performance for RemoveAt, so it's not quite efficient, better be using a dictionary, which is O(1) at removing items, so instead of spillerListe have some spillerDicte, where you would add entries in a form (key = index, value = item).
Instead of working with integers, what if you keep your players name in a list and, after picking a player you remove it from the list. Probably this will not be the best performant solution, but it is clear what are you trying to do
Dim lstSpillere = new List(Of String)() ' Just for the example below
Dim spillerListe = new List(Of String)() from {"Marc", "John", "Steve", "George", "David", "Jeremy", "Andrew" }
Dim rnd = new Random()
While spillerListe.Count > 1
Dim firstPlayer = spillerListe(rnd.Next(0, spillerListe.Count))
spillerListe.Remove(firstPlayer)
Dim secondPlayer = spillerListe(rnd.Next(0, spillerListe.Count))
spillerListe.Remove(secondPlayer)
lstSpillere.Add(firstPlayer + " VS. " + secondPlayer)
' for debug purpose....
Console.WriteLine(firstPlayer & " VS. " & secondPlayer)
End While
if spillerListe.Count > 0 Then
Console.WriteLine("Excluded from play list is:" & spillerListe(0))
End if
The important key here is the generation of Random instance that should be outside the loop to avoid to generate the same number in the short time period required by the loop to execute.
Try this:
Module Module1
Dim rnd As New Random
Sub Main()
Dim RegisteredPlayers As New List(Of Player)
' Fill List (example 100 players)
For index As Integer = 1 To 100
RegisteredPlayers.Add(New Player(String.Format("Player{0}", index)))
Next
'Sort Players using a random number
Dim SortedPlayersArray = RandomSortItems(RegisteredPlayers.ToArray())
'Pair players by selecting 2 consequative ones from randomly sorted array
Dim Matches As New List(Of Match)
For index As Integer = 1 To SortedPlayersArray.Length Step 2
Dim m As Match = New Match(SortedPlayersArray(index - 1), SortedPlayersArray(index))
Matches.Add(m)
Debug.WriteLine(m.ToString())
Next
' Match Player48 vs. Player79
' Match Player3 vs. Player53
' Match Player18 vs. Player43
' Match Player85 vs. Player1
' Match Player47 vs. Player56
' Match Player23 vs. Player66
' etc..
End Sub
Public Function RandomSortItems(Of T)(ByVal items As T()) As T()
Dim sorted As T() = New T(items.Length-1) {}
Array.Copy(items, sorted, sorted.Length)
Dim keys As Double() = New Double(items.Length-1) {}
For i As Integer = 1 To items.Length
keys(i - 1) = rnd.NextDouble()
Next
Array.Sort(keys, sorted)
Return sorted
End Function
End Module1
Public Class Player
Dim m_name As String
Public Sub New(ByVal player_name As String)
m_name = player_name
End Sub
Public ReadOnly Property Name() As String
Get
Return m_name
End Get
End Property
Public Overrides Function ToString() As String
Return m_name
End Function
End Class
Public Class Match
Dim m_player_1 As Player, m_player_2 As Player
Public Sub New(ByVal player_1 As Player, ByVal player_2 As Player)
m_player_1 = player_1
m_player_2 = player_2
End Sub
Public ReadOnly Property Player1() As Player
Get
Return m_player_1
End Get
End Property
Public ReadOnly Property Player2() As Player
Get
Return m_player_2
End Get
End Property
Public Overrides Function ToString() As String
Return String.Format("Match {0} vs. {1}", Player1, Player2)
End Function
End Class
Edit 1:
An alternate random sorter (which should be faster) is
Public Function RandomSortItems(Of T)(ByVal items As T()) As T()
Dim slist As New SortedList(Of Double, T)
For i As Integer = 1 to items.Length
slist.Add(rnd.NextDouble(), items(i-1) )
Next i
return slist.Values.ToArray()
End Function

Adding a variable to a listBox in VB.net Windows Form

I am making a dvd database system in windows form and trying to display the dvd's entered by a user. Then display the Title, Director and Genre in 3 separate listBoxes.
When the user enters the information through 3 separate text boxes, the information is stored in a structure I made called TDvd. This means I can call for example dvd.Title or dvd.Director. I also use the variable index to add this information to an array I made called Dvd(100) (just a random number I used to test).
Here is the code I currently have for adding the items to the ListBox:
For i = 1 To noOfAddedDvds
lstTitle.Items.Add(dvd(i).Title)
lstDirector.Items.Add(dvd(i).Director)
lstGenre.Items.Add(dvd(i).Genre)
Next
The variable NoOfDvdsAdded is just a way of keeping track of the number of dvd's the user has already entered.
I run this and enter the Title, Director and Genre, but when I try and display this information across the 3 listboxes, I get the error:
An unhandled exception of type 'System.ArgumentNullException' occurred in System.Windows.Forms.dll
Public Class Form1
Structure TDvd
Dim Title As String
Dim Director As String
Dim Genre As String
End Structure
Dim dvd(100) As TDvd
Dim index As Integer = 0
Dim noOfAddedDvds As Integer
Private Sub btnAddToDatabase_Click(sender As Object, e As EventArgs) Handles btnAddToDatabase.Click
If txtDirector.Text <> "" Or txtGenre.Text <> "" Or txtTitle.Text <> "" Then
txtTitle.Text = dvd(index).Title
txtDirector.Text = dvd(index).Director
txtGenre.Text = dvd(index).Genre
index += 1
noOfAddedDvds += 1
End If
End Sub
Private Sub btnDisplayDatabase_Click(sender As Object, e As EventArgs) Handles btnDisplayDatabase.Click
Dim i As Integer
For i = 0 To noOfAddedDvds
MessageBox.Show(index & ", " & i)
lstTitle.Items.Add(dvd(i).Title)
lstDirector.Items.Add(dvd(i).Director)
lstGenre.Items.Add(dvd(i).Genre)
MessageBox.Show(index & ", " & i)
Next
End Sub
End Class
According to the documentation, an ArgumentNullException is thrown by the Add() method if the argument passed to it is null. (Or Nothing in VB.) So one of these is Nothing at runtime:
dvd(i).Title
dvd(i).Director
dvd(i).Genre
You'll have to debug to determine which. It would seem that the error is because you're starting your iteration at 1 instead of 0, I would think it should be:
For i = 0 To noOfAddedDvds - 1
So when you get to the index of noOfAddedDvds in your collection, that element will be an uninitialized struct with Nothing strings.
You'll definitely want to fix the iteration (indexes start at 0). Additionally, you may also benefit from initializing the String properties in your struct to String.Empty internally. Depends on whether you want similar errors to manifest as an exception or as an empty record. Sometimes the latter makes the problem more obvious since at runtime you'd see that your output started on the second record.
Just a few pointers...
The Items collection on the ListBox is actually 0 indexed, by which I mean that instead of going "1,2,3", it actually goes (0,1,2).
That's what your problem is.
Hint - think about perhaps using a List instead of an array as well... (for dvd)
Your thing cries out for being rewritten in OO form:
Friend DVDGenres
Undefined
Comedy
Action
Adventure
Sci-Fi
End Enum
Friend Class DVD
Public Property Title As String
Public Property Director As String
Public Property Genre As DVDGenres
Public Sub New
Title = ""
Director = ""
Genre = DVDGenres.Undefined
' other stuff too
End Sub
Public Overrides Function ToString As String
Return Title
End Sub
End Class
Now something to store them in. Arrays went out with Rubik's Cubes, so a List:
Private myDVDs As New List(of DVD)
A list and a class can do what arrays and structures can without the headaches. Add a DVD:
Dim d As New DVD
d.Name = TextBoxName.Text
d.Director = TextBoxDir.Text
d.Genre = comboboxGenre.SelectedItem
' add to the container:
myDVDs.Add(d)
Display all the DVDs in a ListBox to pick from:
AllDVDsLB.DataSource = myDVDs
AllDVDsLB.DisplayMember = "Title"
This will set your list as the datasource for the listbox. Whatever is in the List is automatically displayed without copying data into the Items collection. Then, say from selectedindex changed event, display the selected item details to some labels:
Label1.Text = Ctype(AllDVDsLB.SelectedItem, DVD).Title
Label2.Text = Ctype(AllDVDsLB.SelectedItem, DVD).Director
Label3.Text = Ctype(AllDVDsLB.SelectedItem, DVD).Genre.ToString
Iterate to do something like what is in the Question:
For Each d As DVD in myDVDs ' CANT run out of data
lstTitle.Items.Add(d.Title)
lstDirector.Items.Add(d.Director)
lstGenre.Items.Add(d.Genre.ToString)
Next
Or iterate and reference with an Int32:
For n As Integer = 0 To myDVDs.Count - 1
lstTitle.Items.Add(myDVDs(n).Title)
' etc
Next n
HTH

How To Read From Text File & Store Data So To Modify At A Later Time

What I am trying to do may be better for use with SQL Server but I have seen many applications in the past that simply work on text files and I am wanting to try to imitate the same behaviour that those applications follow.
I have a list of URL's in a text file. This is simple enough to open and read line by line, but how can I store additional data from the file and query the data?
E.g.
Text File:
http://link1.com/ - 0
http://link2.com/ - 0
http://link3.com/ - 1
http://link4.com/ - 0
http://link5.com/ - 1
Then I will read the data with:
Private Sub ButtonX2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ButtonX2.Click
OpenFileDialog1.Filter = "*txt Text Files|*.txt"
If OpenFileDialog1.ShowDialog() = DialogResult.OK Then
Dim AllText As String = My.Computer.FileSystem.ReadAllText(OpenFileDialog1.FileName)
Dim Lines() = Split(AllText, vbCrLf)
Dim list = New List(Of Test)
Dim URLsLoaded As Integer = 0
For i = 0 To UBound(Lines)
If Lines(i) = "" Then Continue For
Dim URLInfo As String() = Split(Lines(i), " - ")
If URLInfo.Count < 6 Then Continue For
list.Add(New Test(URLInfo(0), URLInfo(1)))
URLsLoaded += 1
Next
DataGridViewX1.DataSource = list
LabelX5.Text = URLsLoaded.ToString()
End If
End Sub
So as you can see, above I am prompting the user to open a text file, afterwards it is displayed back to the user in a datagridview.
Now here is my issue, I want to be able to query the data, E.g. Select * From URLs WHERE active='1' (Too used to PHP + MySQL!)
Where the 1 is the corresponding 1 or 0 after the URL in the text file.
In the above example the data is being stored in a simple class as per below:
Public Class Test
Public Sub New(ByVal URL As String, ByVal Active As Integer)
_URL = URL
_Active = Active
End Sub
Private _URL As String
Public Property URL() As String
Get
Return _URL
End Get
Set(ByVal value As String)
_URL = value
End Set
End Property
Private _Active As String
Public Property Active As String
Get
Return _Active
End Get
Set(ByVal value As String)
_Active = value
End Set
End Property
End Class
Am I going completely the wrong way about storing the data after importing from a text file?
I am new to VB.NET and still learning the basics but I find it much easier to learn by playing around before hitting the massive books!
Working example:
Dim myurls As New List(Of Test)
myurls.Add(New Test("http://link1.com/", 1))
myurls.Add(New Test("http://link2.com/", 0))
myurls.Add(New Test("http://link3.com/", 0))
Dim result = From t In myurls Where t.Active = 1
For Each testitem As Test In result
MsgBox(testitem.URL)
Next
By the way, LINQ is magic. You can shorten your loading/parse code to 3 rows of code:
Dim Lines() = IO.File.ReadAllLines("myfile.txt")
Dim myurls As List(Of Test) = (From t In lines Select New Test(Split(t, " - ")(0), Split(t, " - ")(1))).ToList
DataGridViewX1.DataSource = myurls
The first line reads all lines in the file to an array of strings.
The second line splits each line in the array, and creates a test-item and then converts all those result items to an list ( of Test).
Of course this could be misused to sillyness by making it to a one-row:er:
DataGridViewX1.DataSource = (From t In IO.File.ReadAllLines("myfile.txt") Select New Test(Split(t, " - ")(0), Split(t, " - ")(1))).ToList
Wich would render your load function to contain only following 4 rows:
If OpenFileDialog1.ShowDialog() = DialogResult.OK Then
DataGridViewX1.DataSource = (From t In IO.File.ReadAllLines("myfile.txt") Select New Test(Split(t, " - ")(0), Split(t, " - ")(1))).ToList
LabelX5.Text = ctype(datagridviewx1.datasource,List(Of Test)).Count
End If
You can query your class using LINQ, as long as it is in an appropriate collection type, like List(of Test) . I am not familiar completely with the VB syntax for LINQ but it would be something like below.
list.Where(Function(x) x.Active == "1").Select(Function(x) x.Url)
However, this isnt actually storing anything into a database, which i think your question might be asking?
I think you are reinventing the wheel, which is not generally a good thing. If you want SQL like functionality just store the data in a SQL DB and query it.
There are a lot of reasons you should just use an existing DB:
Your code will be less tested and thus more likely to have bugs.
Your code will be less optimized and probably perform worse. (You were planning on implementing a query optimizer and indexing engine for performance, right?)
Your code won't have as many features (locking, constraints, triggers, backup/recovery, a query language, etc.)
There are lots of free RDBMS options out there so it might even be cheaper to use an existing system than spending your time writing an inferior one.
That said, if this is just an academic exercise, go for it. However, I wouldn't do this for a real-world system.