I'm working on a program to parse a file name based on a drag drop, and process and remove a list of words that may be found in the file. I had the program working, but in a not so elegant manner, as well as a manner that would introduce issues later on in the program.
I'm trying create a list of a custom class or structure.
Here's what I have:
Public Class moviePath
Public currentPath As String
Public currentNameExt As String
Public currentName As String
Public currentExt As String
Public correctedName As String
Public correctYear As String
End Class
The issue is then when I attempt to create a usable variable based on this:
Dim workingList as New List(Of moviePath)
I'm left with no good way to correctly add data to the list using subs such as this:
Sub scanParent(ByVal sDir As String)
Dim f As String
Dim i As Integer = 0
Try
For Each f In Directory.GetFiles(sDir)
workingList(i).currentName = (Path.GetFileNameWithoutExtension(f))
workingList(i).currentNameExt = (Path.GetFileName(f))
workingList(i).currentPath = (Path.GetFullPath(f))
i += 1
Next
Catch excpt As System.Exception
MessageBox.Show("It didn't work " & excpt.ToString)
End Try
End Sub
Hopefully those tidbits make sense. scanParent sub is called passing the path to a folder as the argument (ie C\somefolder) and I intend to populate an array of sorts with information about files and folders. My reasoning is that I would like to be able to remove words from movie titles, in order use a opensource library (not sure if correct language) that parses IMDB to pull in movie info.
The main thing that I need is for each item in the class moviePath to be addressable and tied to each other item at that position in the list.
EDIT: ie. moviePath(0).currentPath would be in reference to the same file moviePath(0).currentName
For Each f In Directory.GetFiles(sDir)
dim info as new moviePath
info.currentName = (Path.GetFileNameWithoutExtension(f))
info.currentNameExt = (Path.GetFileName(f))
info.currentPath = (Path.GetFullPath(f))
workingList.add(info)
Next
You should be using the List(T).Add method for this.
Sub scanParent(ByVal sDir As String)
Dim f As String
Try
For Each f In Directory.GetFiles(sDir)
Dim newPath As New moviePath()
newPath.currentName = (Path.GetFileNameWithoutExtension(f))
newPath.currentNameExt = (Path.GetFileName(f))
newPath.currentPath = (Path.GetFullPath(f))
workingList.Add(newPath)
Next
Catch excpt As System.Exception
MessageBox.Show("It didn't work " & excpt.ToString)
End Try
End Sub
I agree with Plutonix. In addition to the fix by Bradley, also pass the FileName to your movePath class like this:
Public Class moviePath
Public Sub New(ByVal FullFilePath As String)
Me.currentPath = FullFilePath
Me.currentName = Path.GetFileNameWithoutExtension(FullFilePath)
Me.currentNameExt = Path.GetFileName(FullFilePath)
End Sub
Public currentPath As String
Public currentNameExt As String
Public currentName As String
Public currentExt As String
Public correctedName As String
Public correctYear As String
End Class
Now your loop becomes simply:
For Each f In Directory.GetFiles(sDir)
workingList.add(New moviePath(f))
Next
Related
I have a line of code saving a value to a database. The database class handles the saving of the data and is sound, the code I'm having an issue with is:
With New xdTableName
.Name = "Name"
.DayOfYear = Format(DayOfYear, "000")
.FullString = "Name" & Format(DayOfYear, "000") & Format(Number, "0000")
.Save
End With
All values for the table are saved as string, DayOfYear is an integer at first. I've tried DayOfYear.ToString.PadLeft(3, "0"c), DayOfYear.ToString("D3"), and maybe some others. Number is an integer as well and saves correctly as the left padded value of the integer it represents but for some reason DayOfYear will not save correctly. It should be saving as 012 but it only saves as 12 in both the FullString and the DayOfYear columns. Why? Even when using the same function as the Number value it still doesn't save correctly. The database column is a varchar(3) and shouldn't have any issue holding 012 as the saved data.
Your class only needs 2 properties for this example. Keep numbers as numbers, you can format them as you like when the need to be displayed.
Override ToString for the FullString and just call ToString on the xdTableName. This way, you are not storing redundant data.
Provide a parameterized constructor to easily flesh out a stable xdTableName.
Private Sub Button1_Click() Handles Button1.Click
Dim TN As New xdTableName("Name", Now.DayOfYear)
Debug.Print(TN.ToString)
TN.Save()
End Sub
Public Class xdTableName
Public Property Name As String
Public Property DayOfYear As Integer
Public Sub New(TName As String, TDay As Integer)
Name = TName
DayOfYear = TDay
End Sub
Public Overrides Function ToString() As String
Return "{Name} {DayOfYear:000}"
End Function
Public Sub Save()
'code to save
End Sub
End Class
I have to make a application that organizes a list of runners and their teams. In the following text file, I have to remove the top half of the text file (the top half being the listed teams) and display only the bottom half (the runners)in a listbox item.
The Text file:
# School [School Code|School Name|Coach F-Name|Coach L-Name|AD F-Name|AD L Name]
WSHS|Worcester South High School|Glenn|Clauss|Bret|Zane
WDHS|Worcester Dorehty High School|Ellsworth|Quackenbush|Bert|Coco
WBCHS|Worcester Burncoat High School|Gail|Cain|Kevin|Kane
QRHS|Quabbin Regional High School|Bob|Desilets|Seth|Desilets
GHS|Gardner High School|Jack|Smith|George|Fanning
NBHS|North Brookfield High School|Hughe|Fitch|Richard|Carey
WHS|Winchendon High School|Bill|Nice|Sam|Adams
AUBHS|Auburn High School|Katie|Right|Alice|Wonderland
OXHS|Oxford High School|Mary|Cousin|Frank|Daughter
# Roster [Bib #|School Code|Runner's F-Name|Runner's L-Name]
101|WSHS|Sanora|Hibshman
102|WSHS|Bridgette|Moffitt
103|WSHS|Karine|Chunn
104|WSHS|Shanita|Wind
105|WSHS|Fernanda|Parsell
106|WSHS|Albertha|Baringer
107|WSHS|Carlee|Sowards
108|WDHS|Maisha|Kleis
109|WDHS|Lezlie|Berson
110|WDHS|Deane|Rocheleau
111|WDHS|Hang|Hodapp
112|WDHS|Zola|Dorrough
113|WDHS|Shalon|Mcmonigle
I have some code that reads each row from the text file as an array and uses boolean variables to determine where to end the text file. This worked with displaying only the teams, which I've managed to do. But I now need to do the opposite and display only the players, and I'm a bit stumped.
My Code:
Private Sub btnLoadTeams_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnLoadTeam.Click
' This routine loads the lstTeam box from an ASCII .txt file
' # School [School Code | Name | Coach F-Name| Coach L-Name | AD F-Name | AD L-Name]
Dim strRow As String
Dim bolFoundCode As Boolean = False
Dim bolEndCode As Boolean = False
Dim bolFoundDup As Boolean = False
Dim intPosition As Integer
Dim intPosition2 As Integer
Dim strTeamCodeIn As String
Dim textIn As New StreamReader( _
New FileStream(txtFilePath.Text, FileMode.OpenOrCreate, FileAccess.Read))
' Clear Team listbox
lstTeam.Items.Clear()
btnDeleteRunner.Enabled = True
Do While textIn.Peek <> -1 And Not bolEndCode
Me.Refresh()
strRow = textIn.ReadLine.Trim
If Not bolFoundCode Then
If "# SCHOOL " = UCase(Mid(strRow, 1, 9)) Then
bolFoundCode = True
End If
Else
If Mid(strRow, 1, 2) <> "# " Then
For Each item As String In lstTeam.Items
intPosition = InStr(1, strRow, "|")
strTeamCodeIn = Mid(strRow, 1, intPosition - 1)
intPosition2 = InStr(1, item, strTeamCodeIn)
If intPosition2 > 0 Then
bolFoundDup = True
MsgBox("Found Duplicate School Code: " & strTeamCodeIn)
End If
Else
bolEndCode = True
Next
If Not bolFoundDup Then
lstTeam.Items.Add(strRow)
Else
lstTeam.Items.Add("DUPLICATE School Code: " & strRow)
lstTeam.Items.Add("Please correct input file and reload teams")
bolEndCode = True
End If
End If
End If
Loop
End Sub
Ive put bolEndCode = True in between the part that reads the mid section of the text file, but all Ive managed to display is the following in the listbox:
# Roster [Bib #|School Code|Runner's F-Name|Runner's L-Name]
Any help or hints on how I would display just the runners to my "lstPlayers" listbox would be greatly appreciated. I'm a beginner programmer and We've only just started learning about reading and writing arrays in my .NET class.
First I made 2 classes, one Runner and one School. These have the properties available in the text file. As part of the class I added a function that overrides .ToString. This is for he list boxes that call .ToString for display.
Next I made a function that reads all the data in the file. This is very simple with the File.ReadLines method.
Then I created 2 variables List(Of T) T stands for Type. Ours Types are Runner and School. I used List(Of T) instead of arrays because I don't have to worry about what the size of the list is. No ReDim Preserve, just keep adding items. The FillList method adds the data to the lists. First I had to find where the schools ended and the runners started. I used the Array.FindIndex method which is a bit different because the second parameter is a predicate. Check it out a bit. Now we know the indexes of the lines we want to use for each list and use a For...Next loop. In each loop an instance of the class is created and the properties set. Finally the new object is added to the the list.
Finally we fill the list boxes with a simple .AddRange and the lists.ToArray. Note that we are adding the entire object, properties and all. The neat thing is we can access the properties from the listbox items. Check out the SelectedIndexChanged event. You can do the same thing with the Runner list box.
Sorry, I couldn't just work with your code. I have all but forgotten the old vb6 methods. InStr, Mid etc. It is better when you can to use .net methods. It makes your code more portable when the boss says "Rewrite the whole application in C#"
Public Class Runner
Public Property BibNum As Integer
Public Property SchoolCode As String
Public Property FirstName As String
Public Property LastName As String
Public Overrides Function ToString() As String
'The listbox will call .ToString when we add a Runner object to determin what to display
Return $"{FirstName} {LastName}" 'or $"{LastName}, {FirstName}"
End Function
End Class
Public Class School
Public Property Code As String
Public Property Name As String
Public Property CoachFName As String
Public Property CoachLName As String
Public Property ADFName As String
Public Property ADLName As String
'The listbox will call .ToString when we add a School object to determin what to display
Public Overrides Function ToString() As String
Return Name
End Function
End Class
Private Runners As New List(Of Runner)
Private Schools As New List(Of School)
Private Function ReadData(path As String) As String()
Dim lines = File.ReadLines(path).ToArray
Return lines
End Function
Private Sub FillLists(data As String())
Dim location = Array.FindIndex(data, AddressOf FindRosterLine)
'The first line is the title so we don't start at zero
For index = 1 To location - 1
Dim SplitData = data(index).Split("|"c)
Dim Schl As New School
Schl.Code = SplitData(0)
Schl.Name = SplitData(1)
Schl.CoachFName = SplitData(2)
Schl.CoachLName = SplitData(3)
Schl.ADFName = SplitData(4)
Schl.ADLName = SplitData(5)
Schools.Add(Schl)
Next
For index = location + 1 To data.GetUpperBound(0)
Dim SplitData = data(index).Split("|"c)
Dim Run As New Runner
Run.BibNum = CInt(SplitData(0))
Run.SchoolCode = SplitData(1)
Run.FirstName = SplitData(2)
Run.LastName = SplitData(3)
Runners.Add(Run)
Next
End Sub
Private Function FindRosterLine(s As String) As Boolean
If s.Trim.StartsWith("# Roster") Then
Return True
Else
Return False
End If
End Function
Private Sub FillListBoxes()
Dim arrRunners As Runner() = Runners.ToArray
Dim arrSchools As School() = Schools.ToArray
ListBox1.Items.AddRange(arrSchools)
ListBox2.Items.AddRange(arrRunners)
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim arrRunner = ReadData("Runners.txt")
FillLists(arrRunner)
FillListBoxes()
End Sub
Private Sub ListBox1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles ListBox1.SelectedIndexChanged
Dim Schl = DirectCast(ListBox1.SelectedItem, School)
TextBox1.Text = Schl.CoachLName
TextBox2.Text = Schl.Code
End Sub
I'm creating a Windows application in VB.NET in which needs to store a decent amount of associative information about different applications. I'm trying to find out what the best way to store this data.
After doing some research the best solution that I have found is to use arrays of objects, or dictionaries of objects.
Here is my data structure that needs to be stored:
var Info[AppID]['AppName']-> String (returns the app name as String)
['Exes'] -> Array of Strings
['RegKeys'] -> Array of Strings
['Versions'][VersionID]['PCID'] -> String
['Date'] -> DateTime
['Size'] -> Integer
The keys without quotes are dynamic and represent the actual AppID/VersionID, the keys in quotes are static (so the 2nd key will always have 'AppName' 'Exes' etc.
So for example:
Info[123ab]['Name'] = 'Internet Exploder'
['Exes'] = {'iexplore.exe', 'whatever.exe'}
['RegKeys'] = {'hkey/local machine .....'}
['Versions'][1]['PCID'] = 'My Desktop'
['Date'] = Jan 1 1960
['Size'] = 9001
would be declared and set up as
Dim appinfo As New Dictionary(Of String, Object)
Dim Lv2 As New Dictionary(Of String, Object)
Dim Exes As New List(Of String)(New String() {"iexplorer.exe", "whatever.exe"})
Dim RegKeys As New List(Of String)(New String() {"blah"})
Dim Directories As New List(Of String)(New String() {"c:\program files\internet explorer"})
Dim Name As String = "Internet Exploder"
Dim Versions As New Dictionary(Of String, Object)
Dim VersionsLv2 As New Dictionary(Of String, Object)
Dim VersionID As String = "1"
Dim PCID As String = "My Desktop"
Dim TheDate As Date = Now
Dim Size As Integer = 9001
VersionsLv2.Add("PCID", PCID)
VersionsLv2.Add("Date", TheDate)
VersionsLv2.Add("Size", Size)
Versions.Add("VersionID", VersionsLv2)
Lv2.Add("Name", Name)
Lv2.Add("RegKeys", RegKeys)
Lv2.Add("Directories", Directories)
Lv2.Add("Versions", Versions)
appinfo.Add("abc12", Lv2)
I'm just wondering if anyone knows any better way to do this? I kind of hate having to work from the top of the tree down in order to initialize the variable, but this seems to work ok.
Thank you very much for your input!
It looks like you should be defining two types to begin with, e.g.
Class App
Private ReadOnly _exes As New List(Of String)
Private ReadOnly _regKeys As New List(Of String)
Private ReadOnly _versions As New List(Of Version)
Public Property AppId() As String
Public Property AppName() As String
Public ReadOnly Property Exes() As List(Of String)
Get
Return _exes
End Get
End Property
Public ReadOnly Property RegKeys() As List(Of String)
Get
Return _regKeys
End Get
End Property
Public ReadOnly Property Versions() As List(Of Version)
Get
Return _versions
End Get
End Property
End Class
Class Version
Public Property VersionId() As String
Public Property PcId() As String
Public Property [Date]() As Date
Public Property Size() As Integer
End Class
In that example, the Version objects are stored in an App object in a simple collection and you would get them by index and use LINQ to get one by ID. If you wanted, you could make the Versions property a Dictionary instead. You could then create an array, List or Dictionary to store your App objects.
I have been developing a quiz application that uses a textfile to store questions.
The questions are formatted in this format "QUESTION##CHOICE_A##CHOICE_B##CHOICE_C##CHOICE_D##ANSWER"
I want it to read each line splits it into 6 different parts using "##" as the split string and store it into arrays such as Questions, CHOICE_A,CHOICE_B,CHOICE_C,CHOICE_D
My code does not loop. it just stores the first line
A graphical illustration is shown below of the questions
Here is my code
Dim sr As StringReader = New StringReader(My.Resources.ResourceManager.GetObject(globalVariables.currSubject))
Dim questions As String
Dim splitquestions(6) As String
Dim Unsplitquestions(6) As String
Dim i As Integer = 0
Do Until sr.Peek = -1
questions = sr.ReadLine
Unsplitquestions(i) = questions
splitquestions = Unsplitquestions(i).Split(New String() {"##"}, StringSplitOptions.RemoveEmptyEntries)
' Splits and Stores Into Various
'
'
globalVariables.ArrayQuestions.Add(splitquestions(0))
globalVariables.optionA.Add(splitquestions(1))
globalVariables.optionB.Add(splitquestions(2))
globalVariables.optionC.Add(splitquestions(3))
globalVariables.optionD.Add(splitquestions(4))
globalVariables.Answer.Add(splitquestions(5))
Loop
No, do not use an ArrayList for manipulating a set of objects like this. You should try to think in an Object Oriented way. A QuestionEntry is an entity that contains a QuestionText, 4 possible question answers and one QuestionAnswer.
So let's try this code
Public Class QuestionEntry
Public QuestionText as String
Public ChoiceA as String
Public ChoiceB as String
Public ChoiceC as String
Public ChoiceD as String
Public QuestionAnswer as String
End Class
Dim questions = new List(Of QuestionEntry)()
Dim line As String
Do While sr.Peek() >= 0
line = sr.ReadLine
Console.WriteLine(line)
Dim parts = line.Split(New String() {"##"}, StringSplitOptions.RemoveEmptyEntries)
Dim q = new QuestionEntry()
With q
.QuestionText = parts(0)
.ChoiceA = parts(1)
.ChoiceB = parts(2)
.ChoiceC = parts(3)
.ChoiceD = parts(4)
.QuestionAnswer = parts(5)
End With
questions.Add(q)
Loop
Of course this is just an example and a bit of error checking will be required to make the code more safe. For example before creating the new question entry you should check if the array returned by the split has effectively 6 elements. (parts.Length = 6)
Now all of your text is handled by an instance of a List(Of QuestionEntry) and you could use it like a normal array
Dim qe = questions(0)
Console.WriteLine("Question: " & qe.QuestionText)
Console.WriteLine("Choice A: " & qe.ChoiceA)
Console.WriteLine("Choice B: " & qe.ChoiceB)
Console.WriteLine("Choice C: " & qe.ChoiceC)
Console.WriteLine("Choice D: " & qe.ChoiceD)
Console.ReadLine("Enter your answer:")
The best way to do this is to rely on an existing delimited data parser. The .Split() method is very often horrible for this: performance is sub-par, and there are all kings of edge cases (more than you'd think) where it just doesn't work well. There is even a parser already built into .Net: Microsoft.VisualBasic.FileIO.TextFieldParser.
Additionally, ArrayLists really only exist for pre-.Net 2.0 compatibility. There's is no good reason to ever use one any more. At very least, use a generic List(Of String). In this case, though, your best option is to build a quick class:
Public Class Question
Public Property QuestionText As String
Public Property OptionA As String
Public Property OptionB As String
Public Property OptionC As String
Public Property OptionD As String
Public Property Answer As String
End Class
Now you read your file like this:
Dim results As New List(Of Question)()
Using rdr As New TextFieldParser(My.Resources.ResourceManager.GetObject(globalVariables.currSubject))
rdr.Delimiters = new String() {"##"}
Dim row() As String
While Not rdr.EndOfData
row = rdr.ReadFields()
results.Add(New Question() {
QuestionText = row(0),
OptionA = row(1),
OptionB = row(2),
OptionC = row(3),
OptionD = row(4),
Answer = row(5)
})
End While
End Using
Even with the class definition, that's a whole let less code than the original, and it's much easier to maintain, as well.
I'd also be tempted to write this as an Iterator:
Public Iterator Function ReadQuestions(ByVal FileName As String) As IEnumerable(Of Question)
Using rdr As New TextFieldParser(FileName)
rdr.Delimiters = new String() {"##"}
Dim row() As String
While Not rdr.EndOfData
row = rdr.ReadFields()
Yield New Question() {
QuestionText = row(0),
OptionA = row(1),
OptionB = row(2),
OptionC = row(3),
OptionD = row(4),
Answer = row(5)
}
End While
End Using
End Function
I have two final changes to suggest. The first to add a constructor to the Question type that accepts a string array. This would remove one bit of advanced syntax (the object initializer) from the code, and simplify reading through the portion of the code that actually reads the data. The second isto make the ReadQuestions() method a shared member of the Question type. The final result is this:
Public Class Question
Public Property QuestionText As String
Public Property OptionA As String
Public Property OptionB As String
Public Property OptionC As String
Public Property OptionD As String
Public Property Answer As String
Public Sub New(ByVal data() As String)
'Add error handling here
QuestionText = data(0),
OptionA = data(1),
OptionB = data(2),
OptionC = data(3),
OptionD = data(4),
Answer = data(5)
End Sub
Public Shared Iterator Function ReadFromFile(ByVal FileName As String) As IEnumerable(Of Question)
Using rdr As New TextFieldParser(FileName)
rdr.Delimiters = new String() {"##"}
While Not rdr.EndOfData
Yield New Question(rdr.ReadFields())
End While
End Using
End Function
End Class
And you call all this from your existing code like so:
Dim Questions = Question.ReadFromFile(My.Resources.ResourceManager.GetObject(globalVariables.currSubject))
For Each q As Question in Questions
'...
Next
I need to declare an array in VBA that will be used by every function. However, I cannot declare it as a global as I would do in C++.
My code is as follows:
Option Explicit
Dim test(0 to 10) as String
test(0) = "avds"
test(1) = "fdsafs"
....
The following conceptualizes what I am trying to do.
public function store() as boolean
Worksheets("test").cells(1,1) = test(0)
End Function
How can I achieve this functionality?
For global declaration, change Dim to Public like so:
Public test(0 to 10) as String
You can call this like (assuming it is in Module1, else change Module1 to whatever you've named it):
Module1.test(0) = "something"
Or simply:
test(0) = "something"
Why wouldn't you create everything in a class? That's the reason why classes where invented after all.
Consider the Class1 definition
Option Explicit
Private m_data() As String
Private Sub Class_Initialize()
ReDim m_data(0 To 10)
End Sub
Private Sub Class_Terminate()
Erase m_data
End Sub
Public Property Get Count() As Integer
Count = UBound(m_data) - LBound(m_data) + 1
End Property
Public Property Get Data(index As Integer) As String
Data = m_data(index)
End Property
Public Property Let Data(index As Integer, value As String)
m_data(index) = value
End Property
Public Function Store(rng As Range) As Boolean
Store = (rng.value = m_data(0))
End Function
You can add all the functions you want that can access your array just like Store().
with the test code in a worksheet of
Public Sub Test()
Dim c As New Class1
c.Data(0) = "January"
Debug.Print c.Store(Cells(1, 1))
End Sub
You can also cache the location of the cell where it is referencing, or used an assumed named argument and only supply a reference to the worksheet once after class initialization.
You can use the Public keyword to declare a variable that you need to access in any module.
Remember that in vba you cannot declare variables or code outside of procedures.
See here for more information
I have a recommendation that is a bit lighter than a class (although class is a great recommendation)
Option 1
Define your desired constant array as a delimited string constant:
Public Const cstrTest = "String 1;String 2; String 3; String 4; String 5; String 6"
Next, whenever you need it just use Split to create an array with minimal code:
Dim arrStrings
arrStrings = Split (cstrTest, ";")
Option 2
You might replace (or combine with Option 1) a simple public function
Public Function constStringArray() As String()
constStringArray = Split (cstrTest, ";")
End Function
So then, in use...
Dim arrStrings
'Option 1 example
arrStrings = Split (cstrTest, ";")
'Option 2 example
arrStrings = constStringArray()
one can do it (with global initialization) via Static Property quite straight-forward without creating a class or string parsing - as described in detail and with examples here