Select random person from text file and change corresponding values - vb.net

I have a form with a button and a label. I also have a text file with the following contents:
Bob:Available:None:0
Jack:Available:None:0
Harry:Available:None:0
Becky:Unavailable:Injured:8
Michael:Available:None:0
Steve:Available:None:0
Annie:Unavailable:Injured:12
Riley:Available:None:0
The values in the text file are:
person-name:available-or-unavailable:sick-or-injured:months-they-will-be-unavailable
What I would like to do is to have the user click the button and a random (available) person will be selected from the text file. The label's text will then say:
personname & " has gotten injured and will be unavailable for 10 months."
I would then like to overwrite the text file with the corresponding values for that particular person. For example that person's second value will now be "Unavailable", the third value will be "Injured" and the fourth value will be 10.
I hope this makes sense.
I don't have any code, as I literally have no idea how to do this. Any help would be much appreciated!

Explanations and code in line.
Private r As New Random
Private RandomIndex As Integer
Private dt As New DataTable
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
AddColumnsToDataTable()
FillDataTable()
End Sub
Private Sub AddColumnsToDataTable()
'Need to prepare the table to receive the data
dt.Columns.Add("Name")
dt.Columns.Add("Available")
dt.Columns.Add("Injury")
dt.Columns.Add("Months")
End Sub
Private Sub FillDataTable()
'ReadAllLines returns an array of lines in the text file
Dim lines = File.ReadAllLines("workers.txt")
'Loop through each line in the lines array
For Each line As String In lines
'.Split returns an array based on splitting the line
'by the colon. The c following ":" tells the compiler
'that this is a Char which the split function requires.
Dim items = line.Split(":"c)
'We can add a row to the data table all at once by
'passing in an array of objects.
'This consists of the elements of the items array
dt.Rows.Add(New Object() {items(0), items(1), items(2), items(3)})
Next
'Now we have an in memory DataTable that contains all the data from the text file.
End Sub
Private Function GetRandomWorker() As String
'A list of all the row indexes that are available
Dim AvailableList As New List(Of Integer)
For i As Integer = 0 To dt.Rows.Count - 1
'Loop through all the data in the date table row by row
If dt.Rows(i)("Available").ToString = "Available" Then
'Add only the row indexes that are Available
AvailableList.Add(i)
End If
Next
'Get a random index to use on the list of row indexes in IndexList
If AvailableList.Count = 0 Then
'No more workers left that are Available
Return ""
End If
'Get a random number to use as an index for the available list
Dim IndexForList = r.Next(AvailableList.Count)
'Selects a row index based on the random index in the list of Available
RandomIndex = AvailableList(IndexForList)
'Now use the index to get information from the row in the data table
Dim strName = dt.Rows(RandomIndex)("Name").ToString
Return strName
End Function
Private Sub SaveDataTable()
'Resave the whole file if this was a real app you would use a database
Dim sb As New StringBuilder
'A string builder keeps the code from creating lots of new strings
'Strings are immutable (can't be changed) so every time you think you are
'changing a string, you are actually creating a new one.
'The string builder is mutable (changable)
For Each row As DataRow In dt.Rows
'The ItemsArray returns an array of objects containing all the
'values in each column of the data table.
Dim rowValues = row.ItemArray
'This is a bit of Linq magic that turns the values into strings
Dim strRowValues = From s In rowValues
Select DirectCast(s, String)
'Now that we have strings we can use the String.Join with the colon
'to get the format of the text file
sb.AppendLine(String.Join(":", strRowValues))
Next
'Finally we change the StringBuilder to a real String
'The workers.txt is stored in the Bin\Debug directory so it is current directory
'no additional path required
File.WriteAllText("workers.txt", sb.ToString)
End Sub
Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles MyBase.FormClosing
SaveDataTable()
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
Dim WorkerName As String = GetRandomWorker()
If WorkerName = "" Then
MessageBox.Show("There are no available workers")
Return
End If
Label1.Text = $"{WorkerName} has gotten injured and will be unavailable for 10 months."
dt.Rows(RandomIndex)("Available") = "Unavailable"
dt.Rows(RandomIndex)("Injury") = "Injured"
dt.Rows(RandomIndex)("Months") = "10"
End Sub

Related

reading from txt file and writing in textbox

Can somebody help me with this, im stuck no idea what to do next
give a text file at any location in the computer it contains 15 different integers that can be repeated
make the program so that multiple repetitions of one number are not printed separately, but each number is printed exactly once
and next to it are written in which places he appeared
Imports System.IO
Public Class form1
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim povratnaVrijednost As DialogResult
Dim nazivDatoteke As String
Try
OpenFileDialog1.AddExtension = True
OpenFileDialog1.Multiselect = False
OpenFileDialog1.Filter = "Tekst datoteke (*.txt)|*.txt;"
povratnaVrijednost = OpenFileDialog1.ShowDialog()
If povratnaVrijednost = Windows.Forms.DialogResult.OK Then
If OpenFileDialog1.CheckFileExists = True And
OpenFileDialog1.CheckPathExists = True Then
nazivDatoteke = OpenFileDialog1.FileName
TextBox1.Text = nazivDatoteke
Dim citac As New StreamReader(nazivDatoteke)
Dim redTeksta As String = ""
Do
redTeksta = citac.ReadLine()
If Not redTeksta Is Nothing Then
RichTextBox1.Text = RichTextBox1.Text + redTeksta
End If
Loop Until redTeksta Is Nothing
citac.Close()
End If
End If
Catch ex As Exception
MsgBox("Greska prilikom otvaranja" + ex.StackTrace.ToString)
End Try
End Sub
End Class
123
Requirement #1:
give a text file at any location in the computer it contains 15 different integers that can be repeated
This requirement implies a couple of other requirements. First, you're expected to read the text file. Second, you're expected to parse the values into numbers (presumably integers).
You can use an OpenFileDialog (documentation) and specify that it can only accept text files:
Using browseFileDialog = New OpenFileDialog()
With browseFileDialog
.Filter = "*.txt|*.txt"
If (.ShowDialog() = DialogResult.Ok) Then
'.FileName will be the text file that the user picked
End If
End With
End Using
To read the text file, assuming you want each line, use the File.ReadAllLines method (documentation):
Using browseFileDialog = New OpenFileDialog()
With browseFileDialog
.Filter = "*.txt|*.txt"
If (.ShowDialog() = DialogResult.Ok) Then
Dim lines = IO.File.ReadAllLines(.FileName)
End If
End With
End Using
To parse the values, use the Array.ConvertAll method (documentation) and inside of the predicate use the Integer.Parse method (documentation):
Using browseFileDialog = New OpenFileDialog()
With browseFileDialog
.Filter = "*.txt|*.txt"
If (.ShowDialog() = DialogResult.Ok) Then
Dim lines = IO.File.ReadAllLines(.FileName)
Dim values = Array.ConvertAll(lines, Function(line) Integer.Parse(line))
End If
End With
End Using
Keep in mind that if you wanted to validate that the lines are all valid numbers, instead of assuming that they are, this step would be different. However, you didn't specify that requirement in your original post so I'm not including how to do that.
Requirement #2:
make the program so that multiple repetitions of one number are not printed separately, but each number is printed exactly once
You can use the Random.Next method (documentation) to generate random values. Be sure to declare the new instance of the random object once so that the seed is only set once. To randomly order the values, use the OrderBy method (documentation) passing the random value in the predicate:
Private ReadOnly _random As New Random()
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Using browseFileDialog = New OpenFileDialog()
With browseFileDialog
.Filter = "*.txt|*.txt"
If (.ShowDialog() = DialogResult.Ok) Then
Dim lines = IO.File.ReadAllLines(.FileName)
Dim values = Array.ConvertAll(lines, Function(line) Integer.Parse(line))
Dim randomlyOrderedValues = values.OrderBy(Function(value) _random.Next())
RichTextBox1.Text = String.Join(", ", randomlyOrderedValues.ToArray())
End If
End With
End Using
End Sub
Default for .AddExtension is True. Default for .Multiselect is False. I have simplified the If statement by comparing the return value of .Showdialog directly to the desired DialogResult. If the path of file don't exist the dialog will show a waring.
Always scope variables as narrowly as possible. I move the Dim nazivDatoteke to inside the If since it is not used anywhere else. StreamReaders require a Using block since they need to be disposed.
Why a RichTextBox for a text file?
As you can see getting the distince items in the array is easy, a single line of code. What is more difficult is finding the indexes in the original array, lines.
I looped through all the items in the distinct array (actually it is an IEnumerable(Of String) but that is not important to this code, it is just easier to type array in my explanation). I created a List(Of Integer) to hold the indexes of each item in the original array, lines. The startIndex where the FindIndex method of an Array is the location of the first occurrence of the String.
On each iteration of the Do loop I look for the matches to the item from the lines array. The FindIndex takes parameters (original array, index to start at, what we are looking for). It stops as soon as it finds a match and returns -1 if no matches are found. You can see the operation of this method be setting a break point and checking the variable values.
If a match is found I added that value to the list and change the startIndex to the position following the found index.
Next I used a StringBuilder to create the string to display. I chose the immediate window for testing purposes but you could show sb.ToString in a text box or add it to a ListBox.
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
OpenFileDialog1.Filter = "Tekst datoteke (*.txt)|*.txt;"
If DialogResult.OK = OpenFileDialog1.ShowDialog Then
Dim nazivDatoteke = OpenFileDialog1.FileName
TextBox1.Text = nazivDatoteke
Dim lines = File.ReadAllLines(nazivDatoteke)
Dim distinct = lines.Distinct
For Each item In distinct
Dim lst As New List(Of Integer)
Dim startIndex = Array.IndexOf(lines, item)
Do
Dim FoundIndex = Array.FindIndex(lines, startIndex, Function(line) line = item)
If FoundIndex = -1 Then
Exit Do
Else
lst.Add(FoundIndex)
startIndex = FoundIndex + 1
End If
Loop
Dim sb As New StringBuilder
sb.Append(item)
For Each i In lst
sb.Append($" ({i})")
Next
Debug.Print(sb.ToString)
Next
End If
End Sub
This code displays the distinct items in the list and follow it with the positions (indexes) in the original list.

Select random element from array and display in textbox

I have a form with a button and a textbox. I also have a text file with the following contents..
Bob:Available:None:0
Jack:Available:None:0
Harry:Available:None:0
Becky:Unavailable:Injured:8
Michael:Available:None:0
Steve:Available:None:0
Annie:Unavailable:Injured:8
Riley:Available:None:0
When the user loads the form each value of the text file gets stored into an array. This works fine. What i would like to happen is when the button is pressed a random person (name) who has the value 'Available' will be retrieved from the array and displayed in the textbox.
The code i have so far (which stores each item in text file into arrays):
Public Class Form1
'define profile of person
Public Structure PersonInfo
Public name As String
Public status As String
Public status_type As String
Public monthsunavailable As Integer
End Structure
'Profile List of persons
Public Shared personInfos As New List(Of PersonInfo)() 'roster data
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'read all infomations of person from file. lines is profile array
Dim lines() As String = IO.File.ReadAllLines(filelocation)
For Each line In lines
'Parses the line string, make Person Info and add it to Person List
'split string with ":"
If line.Trim.Equals("") Then Continue For
Dim strArr() = line.Split(":")
'make Person Info
Dim pi As New PersonInfo()
pi.name = strArr(0)
pi.status = strArr(1)
pi.status_type = strArr(2)
pi.monthsunavailable = strArr(3)
'add Person Info to Person List
personInfos.Add(pi)
Next
How do i select a random name from the array and display it in a textbox?
You can use something like this. Try reading the docs!
Private Sub Button1_click (sender As Object, e As EventArgs) Handles Button1.Click
Dim r As New Random ()
Textbox1.Text = personinfos(r.Next (0,personinfos.count)).name
End Sub
Update
To select only names with status "Available"
Private Sub Button1_click (sender As Object, e As EventArgs) Handles Button1.Click
'Instantiate a new random variable
Dim r As New Random ()
'This is a LINQ query to select all items from the list where a property of the item
'(in this case , status) is equal to something.
'Try changing Available to something else and see what you get
Dim qr = From pi in personinfos
Where pi.status = "Available"
Select pi
'The following line can be simplified as follows
'Dim i As Integer = r.Next (0,qr.count)
'Dim s As String = qr (i).name
'Textbox1.Text = s
Textbox1.Text = qr(r.Next (0,qr.count)).name
End Sub

Compare the values of two listboxes and add the non-equivalent values to a third listbox

My knowledge of programming is not too extensive but I have just finished my second year of higher education in computer engineering and have taken a few low level programming courses.
In Visual Basic, I am having trouble comparing the values of two ListBox controls and putting the values that aren't the same into another ListBox.
I need to compare the items of ListBox2 to the items of ListBox1 and if there are any items in ListBox2 that are not in ListBox1, add them to ListBox3. I do not need to find the items in ListBox1 that are not in ListBox2. I cannot use a loop to compare their values based on index because these lists are of names which will be constantly added to and removed from. I also cannot sort these ListBoxes.
There was an example for C# that I found here that used LINQ (I don't really know what that is) to compare the lists and then add the result to a TextBox control. However, I need to know how to add them to a ListBox and not a TextBox.
[EDIT] The example that I have tried is this:
Dim result As List(Of String) = (From s1 As String In Me.ListBox1.Items Where Not Me.ListBox2.Items.Contains(s1) Select s1).ToList()
Me.TextBox1.Text = String.Join(Environment.NewLine, result)
Dim one As ListBox = New ListBox()
Dim two As ListBox = New ListBox()
Dim three As ListBox = New ListBox()
Dim unique As Boolean = True
For i As Integer = 0 To one.Items.Count
For j As Integer = 0 To two.Items.Count
If (one.Text = two.Text) Then
unique = False
Else
two.SelectedIndex = j
End If
Next
If (unique) Then
three.Items.Add(one.Text)
Else
one.SelectedIndex = i
unique = True
End If
Next
I believe this may be what you're looking for. What it does is compares each value in Listbox one to all values in Listbox two and if a value is seen to be a duplicate the Boolean flag unique is switched to false and the item is not added to Listbox three.
I think i found the solution:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
ListBox3.Items.Clear()
Dim result As List(Of String) = (From s1 As String In Me.ListBox2.Items Where Not Me.ListBox1.Items.Contains(s1) Select s1).ToList()
For Each l In result
ListBox3.Items.Add(l)
Next
End Sub
or
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim result As List(Of String) = (From s1 As String In Me.ListBox2.Items Where Not Me.ListBox1.Items.Contains(s1) Select s1).ToList()
For Each l In result
If Not ListBox3.Items.Contains(l) Then
ListBox3.Items.Add(l)
End If
Next
End Sub
Try which works best for you :)

How do you calculate a result from a listbox selection in Visual Basic? (The data in the listbox is being read off a text file)

So I am asked in school to create a Railway Ticketing System program that will allow you to buy either a single or return ticket and based off that the cost will be displayed. The information of all destinations is to be read via a text file. The text file is given below:
Knapford , 1
Crosby , 8
Weltworth , 15
Maron , 23
Cronk , 28
Kildane , 31
Keltthorpe Road , 46
Crovan's Gate , 56
Vicarstown , 76
Barrow , 77
I have managed to separate the integers from the destination names via the code below which then is displayed in a listbox:
Public Class PurchaseScreen
Dim Filename As String
Private Sub Form2_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Filename = "StationFile.txt"
Dim sr As New StreamReader("StationFile.txt")
Dim Word As String = ""
Dim Words(11) As String
Dim i As Integer = 0
Do Until sr.Peek = -1
'grab one word at a time from the text file
Word = sr.ReadLine()
'place word into array
Words(i) = Word
'increment array counter
i = i + 1
Loop
Return
End Sub
The problem I am facing is that I am trying to access the numbers that were omitted by the code, since only the destination name can be displayed in the listbox. How do I go about using those numbers to perform my calculations?
I've edited your post so that the format of the text file is clearer to users reading your question
So to make it easier to access your data, you would be better having a Station structure and storing each station in list of these structures.
To populate your list, iterate through the list of stations and add the names to the list.
When an item is selected, use the selected item's index to look up the data in the stations list and there you go.
'obviously you need to change this path to match where your "stations.txt"file is stored
'and also possibly the name of the listbox you're populating
Dim Filename As String = "D:\Visual Studio 2015\Projects\blank\blank\stations.text"
Dim selectedStation As Station
'a structure of station that can be used to store the data in the stations.txt file
'in a clearer more maintainable way
Structure Station
Dim Name As String
Dim Data As Single
End Structure
'list to store the data from stations.txt
Dim stations As New List(Of Station)
'your form's load event
Private Sub Form2_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
stations.Clear()
ReadStationData()
PopulateListBox()
End Sub
Private Sub ReadStationData()
Dim tempDataLine As String = ""
'read all the stations.txt data into a string
Using sr As New StreamReader(Filename)
While Not sr.EndOfStream
tempDataLine = sr.ReadLine
Dim newStation As Station
Dim tempSplit() As String = tempDataLine.Split(New String() {" , "}, StringSplitOptions.RemoveEmptyEntries)
newStation.Name = tempSplit(0)
newStation.Data = CSng(tempSplit(1))
stations.Add(newStation)
End While
End Using
End Sub
'clear the ListBox and populate it from the Stations list
Private Sub PopulateListBox()
ListBox1.Items.Clear()
For Each stationItem As Station In stations
ListBox1.Items.Add(stationItem.Name)
Next
End Sub
Private Sub ListBox1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles ListBox1.SelectedIndexChanged
'dummy code to show usage
selectedStation = stations(ListBox1.SelectedIndex())
MessageBox.Show("Selected station = " & selectedStation.Name & vbCrLf & "Station price = " & selectedStation.Data)
End Sub

How to filter unbound datagridview with textbox vb.net?

I'm a complete rookie. I learned so much from here but this one I can't find the answer to. I'm using Visual Studio Pro 2015.
I have a windows form application that has a single column datagridview that is populated by reading a textfile, line by line at runtime. Each time the contents of the textfile will be different.
I want the user to be able to filter the list in the datagridview by entering characters in a textbox. The data is not "bound" to the datagridview, because at this point I don't know if that is necessary, and I don't completely understand it.
This is the code that I have for loading the datagridview, and the textbox is called txtFilter.
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'read all lines from the file into a string array (one line per string)
Dim lines() As String = My.Computer.FileSystem.ReadAllText("c:\list_in.txt").Replace(vbLf, "").Split(vbCr)
Dim dgrow As DataGridViewRow
Dim dgcell As DataGridViewCell
'insert each line of input into a row in the datagrid
For Each line As String In lines
dgrow = New DataGridViewRow
dgcell = New DataGridViewTextBoxCell
If line <> "" Then
dgcell.Value = line
dgrow.Cells.Add(dgcell)
DataGridView1.Rows.Add(dgrow)
End If
Next
DataGridView1.Columns("ObjectName").ReadOnly = True
DataGridView1.ClearSelection()
End Sub
Edit:
Looking at your solution, I would advise you execute Dim lines() As String = My.Computer.FileSystem.ReadAllText("c:\list_in.txt").Replace(vbLf, "").Split(vbCr) outside of the txtFilter_TextChanged sub, as otherwise you are importing the entire list every time the user enters a key, which is unnecessary. If the list may change while the user is using the program, I'd instead recommend adding a 'refresh' button, especially if your text file could be a long one.
You also added a couple lines of code to remove the sound when the user presses the Enter key. If the user is expected to press the enter key to search, then you don't need to update the DataGridView every time the user enters a new character in the textbox. This would be easier on memory, and again very beneficial if you have a large text file.
I'm sure there's an easier way, but here's my approach.
Private Sub txtFilter_TextChanged(sender As Object, e As EventArgs) Handles txtFilter.TextChanged
Dim searchedlines(-1) As String 'create an array to fill with terms that match our search
If txtFilter.Text = Nothing Then
datapopulate(lines) 'just populate the datagridview with our text file array
Else
For Each line In lines
If line Like "*" & txtFilter.Text & "*" Then 'check if anything in our search matches any of our terms
ReDim Preserve searchedlines(UBound(searchedlines) + 1) 'resize the array to fit our needs
searchedlines(UBound(searchedlines)) = line 'add the matched line to our array
End If
Next
datapopulate(searchedlines) 'populate the datagrid with our matched terms array
End If
End Sub
Private Sub datapopulate(ByVal mylist)
Dim dgrow As DataGridViewRow
Dim dgcell As DataGridViewCell
DataGridView1.Rows.Clear() 'clear the grid
For Each line As String In mylist 'do the same thing here that we did on form load
dgrow = New DataGridViewRow
dgcell = New DataGridViewTextBoxCell
dgcell.Value = line
dgrow.Cells.Add(dgcell)
DataGridView1.Rows.Add(dgrow)
Next
End Sub
What I did is created a sub that handles whenever the text in txtFilter is changed. Alternatively, you could run that code in a sub that handles a button click. Given that, from what I know, ReDim can be a costly item in terms of memory usage, if your text document was hundreds of lines long, you might want it on a button click instead. You could probably use a list, but I haven't played around enough to know how to go about doing that.
An important note: in order for the sub txtFilter_TextChanged to be able to see lines(), I defined it outside of a sub but inside of your main class, so that all subs could access it, like so:
Public Class Form1
Dim lines() As String = My.Computer.FileSystem.ReadAllText("c:\list_in.txt").Replace(vbLf, "").Split(vbCr)
'...subs here...
End Class
I hope this helps you get started!
I have a solution working. Thank you very much.
Private Sub txtFilter_TextChanged(sender As Object, e As EventArgs) Handles txtFilter.TextChanged
'read all lines from the file into a string array (one line per string)
Dim lines() As String = My.Computer.FileSystem.ReadAllText("c:\list_in.txt").Replace(vbLf, "").Split(vbCr)
Dim dgrow As DataGridViewRow
Dim dgcell As DataGridViewCell
DataGridView1.Rows.Clear()
'insert each line of input into a row in the datagrid
For Each line As String In lines
dgrow = New DataGridViewRow
dgcell = New DataGridViewTextBoxCell
If line.Contains(txtFilter.Text) Then
dgcell.Value = line
dgrow.Cells.Add(dgcell)
DataGridView1.Rows.Add(dgrow)
End If
Next
DataGridView1.Columns("ObjectName").ReadOnly = True
DataGridView1.ClearSelection()
End Sub
And I also found this to eliminate the bell from ringing when the user pressed the enter key when entering the filter text.
Private Sub txtFilter_KeyPress(sender As Object, e As KeyPressEventArgs) Handles txtFilter.KeyPress
' this keeps the bell from ringing when the user presses the 'enter' key
If Asc(e.KeyChar) = 13 Then
e.Handled = True
End If
End Sub