VB.net - Overwriting random access file - vb.net

This part of my program is designed to add user details to a random access file. The sub routine below is designed to do this:
'This allows a user to be added to the User File
Dim UserToAdd As User
Dim UserFile As Integer
Dim RecordNumber As Long
'Read the data off the form and populate corresponding
'UserToAdd values
With UserToAdd
.UserID = Val(txt_UserID.Text)
.UserBarcode = txt_UserBarcode.Text
.Forename = txt_Forename.Text
.Surname = txt_Surname.Text
.AccessRights = cmb_AccessRights.Text
End With
'Find the next open space in the User File
UserFile = FreeFile()
'Now open the file used to store User records
FileOpen(UserFile, UserFileName, OpenMode.Random, OpenAccess.Write, OpenShare.Shared, Len(UserToAdd))
RecordNumber = Len(UserFile) + 1
'Add the new user to the file
FilePut(UserFile, UserToAdd, RecordNumber)
FileClose(UserFile)
There are no problems in actually saving the details, however, the file is overwritten every time another record is added. How could I stop this and what have I done wrong above?

It appears a couple things are happening, the first is since you are writing a RandomAccess file the Records need to be of the same length, therefore your Structure needs to be setup something like this (Since you didn't post your structure/class I am guessing that this could be a problem, if not the second issue is probably what is causing your issue).
Public Structure User
Public UserId As Integer
<VBFixedString(50)> Public UserBarCode As String
<VBFixedString(20)> Public Forename As String
<VBFixedString(20)> Public Surname As String
<VBFixedString(20)> Public AccessRights As String
End Structure
The second is that your RecordNumber is not valid you are just getting the length of an Integer(your UserFile Variable). By giving your structure a fixed size you can use the LOF Method to get the length of your open file then divide that by your record size to determine the amount of records something like this.
RecordNumber = (LOF(UserFile) \ Len(UserToAdd)) + 1
As I said in my comment these functions are left over from VB6, but I can see why you would want to use them, there appears to be a lack of information on any other way.

Related

list.add for a list (of ClassType) not adding to list - no errors reported

;tldr ...Orders.Add(order class variable) does not add a record the the Orders list, but also does not generate any errors.
I'll try to make a long story short and I apologize for the book you are about to read - I'm coming back to VB after spending a while in Python and C#. There is a bit of an explanation before any of the code...
I have a command-line exe program that at its base is working fine. We get 4 differently formatted CSV files that the program reads, and based on the file name, runs them through a parser that pulls the text information out of the file and populates a Public Class that in turn calls a function to populate a database table. The class is a single instance class named DataRecord. This program works absolutely fine (and has for well over 18 months now). It reads one line, if a batch# doesn't exist it creates one, and post the data class to the DB, and loops thru the file one line at a time doing so.
Also, their are 2 files to this program - the OrderAutomation main program file and the DBAccess file (which handle the DataRecord Class and all the various DB calls to verify information and to push the record into the database tables.
It used to be for all 4 file types, each file was a single batch all from a single region (data delimiters) - but now 1 of the file types will start sending multiple regions in a single file (and not necessarily in order) - so I need to modify the program that if its this 1 file type, it goes through the entire file, grouping each region together (each region will get its own Batch#).
I figured this would be fairly simple. I'd create an additional class (VTLData) that has some basic region separation data, and then a list of the DataRecord class. That looks like this:
Public Class VTLData
Public Property Locality As String
Public Property ARProvider As String
Public Property CORegion As String
Public Property BatchID As String
Public Property ProviderZip As String
Public Property IsValidRegion As Boolean
Public Property Orders As List(Of RecordData)
Public Sub New(s As String)
Locality = s
ARProvider = "00000000"
CORegion = "XX"
BatchID = "VXX-00000"
ProviderZip = "00000"
IsValidRegion = True
Orders = New List(Of RecordData)
End Sub
End Class
Like I said earlier - the RecordData class has no changes and is working fine.
There is a point in the program where I know this is the new file type (basically the region information has gone from a zip code to a set of specific text values) so when I test for that, if it's new, I set a boolean NewVTL to true. Because the file can contain more than one region, I have a VTLList property that is a List (of VTLData). I make sure I don't already have a Region in this list already - and if so, reference that index in the list.
When I get to the point where the program normally processes the record, I added the following (VTLIndex is the index variable for the list - at this time its value is 0):
If NewVTL Then
DB.data.CanProcess = CanProcessLine
DB.VTLList(VTLIndex).Orders.Add(DB.data)
Else
. - the old processing method
.
.
End If
I have a watch set for the VTLList(VTLIndex).Orders.Count value - that is at 0 when that line is reached, and stays at 0 when that line is processed.
I've tried creating a Push procedure in the VTLData class where you pass in the db.data class values, create a new instance of the record data and try to assign it that way
Public Sub PushOrder(item As RecordData)
Dim order As New RecordData
With order
.ARProviderNumber = ARProvider
.OrderID = item.OrderID
.AcctSuffix = item.AcctSuffix
.BatchID = BatchID
. - the 70 some other items in the recorddata class
.
.
End With
Orders.Add(order)
End Sub
This would also run without error, but not increase the count of .Orders.Count value.
I'm assuming its something stupid that I'm missing and I'm hoping someone can filter through all this and be like "You forgot to do blah..."
The only other thing I can think of is the do that Push function, but start it with Orders.Add(new RecordData) - and then modify the individual values through an index (.Orders(0).ARProviderNumber = data.ARProviderNumber... etc)
But based on other things I've done in VB - this should be working - I'm sure I'm just missing something...
So thanks for reaching this point - and thank you for letting me know what mistake I've made!!!
So the only way I got this to work for me was to create a Function in the VTLData class that did the following:
Public Function PushOrder(item As RecordData) As Boolean
Dim i As Integer = Orders.Count
Orders.Add(New RecordData)
With Orders(i)
.ARProviderNumber = ARProvider
.OrderID = item.OrderID
.AcctSuffix = item.AcctSuffix
.
. - more fields being populated
.
End With
If Orders.Count <= i Then Return False
Return True
End Function

Retrieve specific data in a text file

I have a text file called "Games.txt" that looks like this:
Call of Duty: 50
Assasins Creed: 23
Watch Dogs: 140
If i want to know the number of "Assasins Creed" How would I get that?
What i have tried so far:
I have tried finding it by knowing the line, length of the string. Then reading that and removing the first 15 charachters ("Assasins Creed:") That would leave me with: 23. But this is a pretty bad way of doing it, and it needs me to know the exact line.
What would a better solution be?
If you only want to get the values for "Assasins Creed", with Regex, you can do something like this:
Dim contents As String = IO.File.ReadAllText(filePath)
Dim re As New Regex("(Assasins Creed):\s*(\d+)")
Dim m As Match = re.Match(contents)
If m.Success Then
Console.WriteLine($"The number of '{m.Groups(1).Value}' games is {m.Groups(2).Value}.")
End If
Output:
The number of 'Assasins Creed' games is 23.
If you need to retrieve the values for all games, you can adjust the above code to something like this:
Dim contents As String = IO.File.ReadAllText(filePath)
Dim re As New Regex("(.+):\s*(\d+)")
Dim matches As MatchCollection = re.Matches(contents)
For Each m As Match In matches
Console.WriteLine($"The number of '{m.Groups(1).Value}' games is {m.Groups(2).Value}.")
Next
Output:
The number of 'Call of Duty' games is 50.
The number of 'Assasins Creed' games is 23.
The number of 'Watch Dogs' games is 140.
Notes:
Remember to replace filePath with the actual path of your text file.
You need to add Imports System.Text.RegularExpressions at the top of your class in order to be able to use the Regex class.
Getting started in this game can be tricky because lots of people expect beginners to do their own research and understand it before offering a simple solution and explanation as to why it works. Giving an absolute beginner a regex statement looks more like someone flexing their ego than offering assistance, so if you're looking for a solution that a beginner can not only use, but also have a hope of understanding (and possibly utilise again elsewhere in their code), then the split command could be used like this:
If you Import System.IO it be more useful than regular expressions at this stage I believe
'Place this at the top of the code block above the Class statement
Imports System.IO 'This provides access to File.ReadAllLines (Input/Output)
Then you could create a function which retrieves the information you need and returns the result like this:
''' <summary>
''' Returns the game value plus the index in the list
''' </summary>
''' <param name="FilePath">Full path of the file</param>
''' <param name="Game">Name of the game to return</param>
''' <param name="index">(Optional) Index of the position in the list (-1 if not found)</param>
''' <returns></returns>
Function GameValue(ByVal FilePath As String, ByVal Game As String, Optional ByRef Index As Integer = -1) As Integer
'This returns the file as an array of lines
Dim Lines() As String = File.ReadAllLines(FilePath)
'This loop will iterate through each item in the array
For i As Integer = 0 To Lines.Length - 1
'This turns each line into an array of name and value (either side of the colon + space)
Dim Segments() As String = Lines(i).Split({": "}, StringSplitOptions.None)
'This will bypass any blank lines or lines without ": "
If Segments.Length > 1 Then
'This tries to match the first segment against the name of the game
If Segments(0) = Game Then
'This handles a successful match
'Store the index of the position of the game in the list
Index = i
'Convert final value into Integer and return result
Return Convert.ToInt32(Segments(1))
End If
End If
Next i
'If no correct match for Game is found, 0 is returned
Return 0
End Function
When you want to call the function, you can do this from within a button click for example:
Private Sub cmdApply_Click(sender As Object, e As EventArgs) Handles cmdApply.Click
'Specify the filename however you like
Dim f As String = "C:\ProgramData\game.txt"
'Specify the name of game however you like
Dim g As String = "Watch Dogs"
'Start an index at -1 (This will get modified by the function if the result is found)
Dim i As Integer = -1
'Use the function to return process a result (value)
Dim v As Integer = GameValue(f, g, i)
Console.WriteLine("Game: " & g)
Console.WriteLine("Value: " & v)
Console.WriteLine("Index: " & i)
End Sub
Sometimes, simple things can actually be pretty complicated once you really look into how to do it. And like most things, there's always another way to go about it.
If you try this code you should be able to take out a few things:
How to make and use a function
How to add xml tags to functions to make calling them easier to understand
One method of utilising ByRef to affect the value of a variable declared elsewhere (index)
How to read a text file and process its contents
How to split a string into an array for independent processing
How to convert a string into an integer
Things to note:
An error will occur if file path or name does not exist
An error might occur if Game: xys value is not a number
An error will not occur if the line does not contain ": " because we tested for that
In this case, the game name is case sensitive. You could use Game.ToUpper and Segments(0).ToUpper to convert both values to uppercase when checking if they match if you want to remove case sensitivity

VB.Net Read multi column text file and load into ListBox

First, I am not a programmer, I mainly just do simple scripts however there are somethings that are just easier to do in VB, I am pretty much self taught so forgive me if this sounds basic or if I can't explain it to well.
I have run into an issue trying to load a multi-column text file into a list box. There are two separate issues.
First issue is to read the text file and only grab the first column to use in the listbox, I am currently using ReadAllLines to copy the text file to a string first.
Dim RDPItems() As String = IO.File.ReadAllLines(MyDocsDir & "\RDPservers.txt")
However I am having a difficult time finding the correct code to only grab the first Column of this string to put in the listbox, if I use the split option I get an error that "Value of type '1-dimensional array of String' cannot be converted to 'String'"
The code looked like
frmRDP.lstRDP.Items.Add() = Split(RDPItems, ";", CompareMethod.Text)
This is the first hurdle, the second issue is what I want to do is if an item is selected from the List box, the value of the second column gets pulled into a variable to use.
This part I'm not even sure where to begin.
Example data of the text file
Server1 ; 10.1.1.1:3389
Server2 ; 192.168.1.1:8080
Server3 ; 172.16.0.1:9833
.....
When it's working the application will read a text file with a list of servers and their IPs and put the servers in a listbox, when you select the server from the listbox it and click a connect button it will then launch
c:\windows\system32\mstsc.exe /v:serverip
Any help would be appreciated, as I can hard code a large list of this into the VB application it would be easier to just have a text file with a list of servers and IPs to load instead.
The best practise for this would probably be to store your "columns" in a Dictionary. Declare this at class level (that is, outside any Sub or Function):
Dim Servers As New Dictionary(Of String, String)
When you load your items you read the file line-by-line, adding the items to the Dictionary and the ListBox at the same time:
Using Reader As New IO.StreamReader(IO.Path.Combine(MyDocsDir, "RDPservers.txt")) 'Open the file.
While Reader.EndOfStream = False 'Loop until the StreamReader has read the whole file.
Dim Line As String = Reader.ReadLine() 'Read a line.
Dim LineParts() As String = Line.Split(New String() {" ; "}, StringSplitOptions.None) 'Split the line into two parts.
Servers.Add(LineParts(0), LineParts(1)) 'Add them to the Dictionary. LineParts(0) is the name, LineParts(1) is the IP-address.
lstRDP.Items.Add(LineParts(0)) 'Add the name to the ListBox.
End While
End Using 'Dispose the StreamReader.
(Note that I used IO.Path.Combine() instead of simply concatenating the strings. I recommend using that instead for joining paths together)
Now, whenever you want to get the IP-address from the selected item you can just do for example:
Dim IP As String = Servers(lstRDP.SelectedItem.ToString())
Hope this helps!
EDIT:
Missed that you wanted to start a process with it... But it's like charliefox2 wrote:
Process.Start("c:\windows\system32\mstsc.exe", "/v:" & Servers(lstRDP.SelectedItem.ToString()))
Edit: #Visual Vincent's answer is way cleaner. I'll leave mine, but I recommend using his solution instead. That said, scroll down a little for how to open the server. He's got that too! Upvote his answer, and mark it as correct!
It looks like you're trying to split an array. Also, ListBox.Items.Add() works a bit differently than the way you've written your code. Let's take a look.
ListBox.Items.Add() requires that you provide it with a string inside the parameters. So you would do it like this:
frmRDP.lstRDP.Items.Add(Split(RDPItems, ";", CompareMethod.Text))
But don't do that!
When you call Split(), you must supply it with a string, not an array. In this case, RDPItems is an array, so we can't split the entire thing at once. This is the source of the error you were getting. Instead, we'll have to do it one item at a time. For this, we can use a For Each loop. See here for more info if you're not familiar with the concept.
A For Each loop will execute a block of code for each item in a collection. Using this, we get:
For Each item In RDPItems
Dim splitline() As String = Split(item, ";") 'splits the item by semicolon, and puts each portion into the array
frmRDP.lstRDP.Items.Add(splitline(0)) 'adds the first item in the array
Next
OK, so that gets us our server list put in our ListBox. But now, we want to open the server that our user has selected. To do that, we'll need an event handler (to know when the user has double clicked something), we'll have to find out which server they selected, and then we'll have to open that server.
We'll start by handling the double click by creating a sub to deal with it:
Private Sub lstRDP_MouseDoubleClick(sender As Object, e As MouseEventArgs) Handles lstRDP.MouseDoubleClick
Next, we'll get what the user has selected. Here, we're setting selection equal to the index that the user has selected (in this case, the first item is 0, the second is 1, and so on).
Dim selection As Integer = lstRDP.SelectedIndex
Lastly, we need to open the server. I'm assuming you want to do that in windows explorer, but if I'm mistaken please let me know.
Dim splitline() As String = Split(RDPItems(selection), ";")
Dim location As String = Trim(splitline(1))
We'll need to split the string again, but you'll notice this time I'm choosing the item whose location in the array is the same as the index of the list box the user has selected. Since we added our items to our listbox in the order they were added to our array, the first item in our listbox will be the first in the array, and so on. The location of the server will be the second part of the split function, or splitline(1). I've also included the Trim() function, which will remove any leading or trailing spaces.
Finally, we need to connect to our server. We'll use Process.Start() to launch the process.
Process.Start("c:\windows\system32\mstsc.exe", "/v:" & location)
For future reference, to first argument for Process.Start() is the location of the process, and the second argument is any argument the process might take (in this case, what to connect to).
Our final double click event handler looks something like this:
Private Sub lstRDP_MouseDoubleClick(sender As Object, e As MouseEventArgs) Handles lstRDP.MouseDoubleClick
Dim selection As Integer = lstRDP.SelectedIndex
Dim splitline() As String = Split(RDPItems(selection), ";")
Dim location As String = Trim(splitline(1))
Process.Start("c:\windows\system32\mstsc.exe", "/v:" & location)
End Sub
A final note: You may need to put
Dim RDPItems() As String = IO.File.ReadAllLines(MyDocsDir & "\RDPservers.txt")
outside of a sub, and instead just inside your class. This will ensure that both the click handler and your other sub where you populate the list box can both read from it.

Converting textfile to a two dimensional array (with a comma deliminator) VB.NET

I'm trying like crazy to figure out how to do this. I need to look at a textfile that I've designed (for a quiz program)- line by line (the line having two parts separated by a comma) for the question (First part) and the boolean answer (second part). Ie. A line from the text file will look like:
You have 10 fingers,true
you have 10 toes,true
you have 2 thumbs,true
I just need to be able to convert this to an array whereby I can access the elements on command, so for instance (0,0) would display the first question in a textbox, and I would reference the users answer (in the form of true or false) against (0,1) where I would use a counter to count the number of correct answers. After the user answers the first question I could loop to the second question to be displayed in the array and so forth. Although there may be more advanced ways of doing this I will need to use a stream reader in this context.
I see I can read ONE line into an array with :
dim line() as string = io.file.readalllines("C:\data.txt")
dim value(2) as integer
value = line.split(","c)
but I need to access each line, with their answers, one at a time. If I could get the textfile into an two dimensional array I could simply access each element on command. Help :)
Any help would be GREATLY appreciated!
I suggest to use a proper class to handle your data. For example you could write a class named QA that stores the Question text and the boolean value of the answer
Public Class QA
Public Question as String
Public Answer as Boolean
End Class
You could read your file with File.ReadLines or use the StreamReader class.
Sub Main
' A list of QA objects loaded from file
Dim qaList as New List(Of QA)()
Using sw = new StreamReader("C:\data.txt")
Dim line as String
Do
line = sw.ReadLine()
If line Is Nothing Then Exit Do
Dim parts() = line.Split(","c)
' Initialize a new QA object and add it to the list
qaList.Add(new QA() _
With
{
.Question = parts(0),
.Answer = Convert.ToBoolean(parts(1))
})
Loop
End Using
Now you could use the List(Of QA) as an array referencing the elements using an indexer
Console.WriteLine(qaList(0).Question)
Console.WriteLine(qaList(0).Answer)
End Sub

How can I read individual lines of a CSV file into a string array, to then be selectively displayed via combobox input?

I need your help, guys! :|
I've got myself a CSV file with the following contents:
1,The Compact,1.8GHz,1024MB,160GB,440
2,The Medium,2.4GHz,1024MB,180GB,500
3,The Workhorse,2.4GHz,2048MB,220GB,650
It's a list of computer systems, basically, that the user can purchase.
I need to read this file, line-by-line, into an array. Let's call this array csvline().
The first line of the text file would stored in csvline(0). Line two would be stored in csvline(1). And so on. (I've started with zero because that's where VB starts its arrays). A drop-down list would then enable the user to select 1, 2 or 3 (or however many lines/systems are stored in the file). Upon selecting a number - say, 1 - csvline(0) would be displayed inside a textbox (textbox1, let's say). If 2 was selected, csvline(1) would be displayed, and so on.
It's not the formatting I need help with, though; that's the easy part. I just need someone to help teach me how to read a CSV file line-by-line, putting each line into a string array - csvlines(count) - then increment count by one so that the next line is read into another slot.
So far, I've been able to paste the numbers of each system into an combobox:
Using csvfileparser As New Microsoft.VisualBasic.FileIO.TextFieldParser _
("F:\folder\programname\programname\bin\Debug\systems.csv")
Dim csvalue As String()
csvfileparser.TextFieldType = Microsoft.VisualBasic.FileIO.FieldType.Delimited
csvfileparser.Delimiters = New String() {","}
While Not csvfileparser.EndOfData
csvalue = csvfileparser.ReadFields()
combobox1.Items.Add(String.Format("{1}{0}", _
Environment.NewLine, _
csvalue(0)))
End While
End Using
But this only selects individual values. I need to figure out how selecting one of these numbers in the combobox can trigger textbox1 to be appended with just that line (I can handle the formatting, using the string.format stuff). If I try to do this using csvalue = csvtranslator.ReadLine , I get the following error message:
"Error 1 Value of type 'String' cannot be converted to '1-dimensional array of String'."
If I then put it as an array, ie: csvalue() = csvtranslator.ReadLine , I then get a different error message:
"Error 1 Number of indices is less than the number of dimensions of the indexed array."
What's the knack, guys? I've spent hours trying to figure this out.
Please go easy on me - and keep any responses ultra-simple for my newbie brain - I'm very new to all this programming malarkey and just starting out! :)
Structure systemstructure
Dim number As Byte
Dim name As String
Dim procspeed As String
Dim ram As String
Dim harddrive As String
Dim price As Integer
End Structure
Private Sub csvmanagement()
Dim systemspecs As New systemstructure
Using csvparser As New FileIO.TextFieldParser _
("F:\folder\programname\programname\bin\Debug\systems.csv")
Dim csvalue As String()
csvparser.TextFieldType = Microsoft.VisualBasic.FileIO.FieldType.Delimited
csvparser.Delimiters = New String() {","}
csvalue = csvparser.ReadFields()
systemspecs.number = csvalue(0)
systemspecs.name = csvalue(1)
systemspecs.procspeed = csvalue(2)
systemspecs.ram = csvalue(3)
systemspecs.harddrive = csvalue(4)
systemspecs.optical = csvalue(5)
systemspecs.graphics = csvalue(6)
systemspecs.audio = csvalue(7)
systemspecs.monitor = csvalue(8)
systemspecs.software = csvalue(9)
systemspecs.price = csvalue(10)
While Not csvparser.EndOfData
csvalue = csvparser.ReadFields()
systemlist.Items.Add(systemspecs)
End While
End Using
End Sub
Edit:
Thanks for your help guys, I've managed to solve the problem now.
It was merely a matter calling loops at the right point in time.
I would recommend using FileHelpers to do the reading.
The binding shouldn't be an issue after that.
Here is the Quickstart for Delimited Records:
Dim engine As New FileHelperEngine(GetType( Customer))
// To Read Use:
Dim res As Customer() = DirectCast(engine.ReadFile("FileIn.txt"), Customer())
// To Write Use:
engine.WriteFile("FileOut.txt", res)
When you get the file read, put it into a normal class and just bind to the class or use the list of items you have to do custom stuff with the combobox. Basically, get it out of the file and into a real class asap, then things will be easier.
At least take a look at the library. After using it, we use a lot more simple flat files since it is so easy, and we haven't written a file access routine since (for that kinda stuff).
http://msdn.microsoft.com/en-us/library/microsoft.visualbasic.fileio.textfieldparser.aspx
I think your main problem is understanding how arrays work (hence the error message).
You can use split and join functions to convert strings into and out of arrays
dim s() as string = split("1,2,3",",") gives and array of strings with 3 elements
dim ss as string = join(s,",") gives you the string back
Firstly, it's actually really good that you are using the TextFieldParser for reading CSV files - most don't but you won't have to worry about extra commas and quoted text etc...
The Readline method only gives you the raw string, hence the "Error 1 Value of type 'String' cannot be converted to '1-dimensional array of String'."
What you may find easier with combo boxes etc is to use an object (e.g. 'systemspecs') rather than strings. Assign the CSV data to the objects and override the "ToString" method of the 'systemspecs' class to display in the combo box how you want with formatting etc. That way when you handle the SelectedIndexChanged event (or similar) you get the "SelectedItem" from the combo box (which can be Nothing so check) and cast it as the 'systemspecs' to use it. The advantage is that you are not restricted to display the exact data in the combo etc.
' in "systemspecs"...
Public Overrides Function ToString() As String
Return Name ' or whatever...
End Function ' ToString
e.g.
dim item as new systemspecs
item.ID = csvalue(1)
item.Name = csvalue(2)
' etc...
combobox1.Items.Add(item)
Let me know if that makes sense!
PK :-)