Read Text File Line by Line and Take Input from what ever proceeds '=' [closed] - vb.net

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
Closed 8 years ago.
This question appears to be off-topic because it lacks sufficient information to diagnose the problem. Describe your problem in more detail or include a minimal example in the question itself.
Questions asking for code must demonstrate a minimal understanding of the problem being solved. Include attempted solutions, why they didn't work, and the expected results. See also: Stack Overflow question checklist
Improve this question
newbie question but I can't seem to find a definitive way to do this. I want the program to be able to take information from particular lines of text files and use that information for a variety of tasks. I'd really appreciate it if you could also teach me how to write to a particular line of the text file.
Here's an example of the text file:
Line1 Name = Garry
Line2 Set = 0
Line3 Other = Other
Plus many more lines
The tricky part is to grab the information that comes after the '=' on line 2 for example. And also if the user wanted to change one of the information on these lines, how I'd go around changing it... I would appreciate any help what so ever.
*Using Visual Basic Studio 2012 on .net 4.5

And if you want to do it simply:
First read all lines of the file
Dim Lines() as String = IO.File.ReadAllLines("C:\myfile.txt")
The array then contains elements for each line in the file.
Then iterate through these lines:
Dim MyKeyValues As New Dictionary(Of String, String) 'See below
For each line as String in Lines
Dim LineParts() as String = Strings.Split(line, "=", 2) 'Split the current line into two chunks at the first =
If LineParts.Count < 2 Then
Continue For 'No = in the line, so skip it
Else
Dim Key as String = LineParts(0)
Dim Value as String = LineParts(1) 'This contains the part after the =
'Do whatever you want with the value here. e.g.
MyKeyValues.Add(Key, Value) 'See below
Endif
Next
You can for example save the line parts into a Dictionary(Of String, String) and make the changes to the values in this dictionary. Afterwards write the dictionary back to the file:
Dim FileLines as New List(Of String)
For each k as String in MyKeyValues.Keys
FileLines.Add(k & "=" & MyKeyValues(k))
Next
IO.File.WriteAllLines("C:\myfile.txt", FileLines)
The ReadAllLines() and WriteAllLines() methods should be reasonably fast if the textfile is not extremely large. As others have pointed out an INIFile class should be most suitable for your needs since this makes it easy to read and change keys.

This is very similar to an INI file except without headers.
Here is an example of an INI file:
[CONFIG] Header
Option1=true Key=Value
Option2=true Key=Value
Option3=false Key=Value
You can read and write to INI files easily by using this class in your project:
Imports System.IO
Imports System.Text.RegularExpressions
Imports System.Collections
Imports System.Diagnostics
Public Class IniFile
' List of IniSection objects keeps track of all the sections in the INI file
Private m_sections As Hashtable
' Public constructor
Public Sub New()
m_sections = New Hashtable(StringComparer.InvariantCultureIgnoreCase)
End Sub
' Loads the Reads the data in the ini file into the IniFile object
Public Sub Load(ByVal sFileName As String, Optional ByVal bMerge As Boolean = False)
If Not bMerge Then
RemoveAllSections()
End If
' Clear the object...
Dim tempsection As IniSection = Nothing
Dim oReader As New StreamReader(sFileName)
Dim regexcomment As New Regex("^([\s]*#.*)", (RegexOptions.Singleline Or RegexOptions.IgnoreCase))
' Broken but left for history
'Dim regexsection As New Regex("\[[\s]*([^\[\s].*[^\s\]])[\s]*\]", (RegexOptions.Singleline Or RegexOptions.IgnoreCase))
Dim regexsection As New Regex("^[\s]*\[[\s]*([^\[\s].*[^\s\]])[\s]*\][\s]*$", (RegexOptions.Singleline Or RegexOptions.IgnoreCase))
Dim regexkey As New Regex("^\s*([^=\s]*)[^=]*=(.*)", (RegexOptions.Singleline Or RegexOptions.IgnoreCase))
While Not oReader.EndOfStream
Dim line As String = oReader.ReadLine()
If line <> String.Empty Then
Dim m As Match = Nothing
If regexcomment.Match(line).Success Then
m = regexcomment.Match(line)
Trace.WriteLine(String.Format("Skipping Comment: {0}", m.Groups(0).Value))
ElseIf regexsection.Match(line).Success Then
m = regexsection.Match(line)
Trace.WriteLine(String.Format("Adding section [{0}]", m.Groups(1).Value))
tempsection = AddSection(m.Groups(1).Value)
ElseIf regexkey.Match(line).Success AndAlso tempsection IsNot Nothing Then
m = regexkey.Match(line)
Trace.WriteLine(String.Format("Adding Key [{0}]=[{1}]", m.Groups(1).Value, m.Groups(2).Value))
tempsection.AddKey(m.Groups(1).Value).Value = m.Groups(2).Value
ElseIf tempsection IsNot Nothing Then
' Handle Key without value
Trace.WriteLine(String.Format("Adding Key [{0}]", line))
tempsection.AddKey(line)
Else
' This should not occur unless the tempsection is not created yet...
Trace.WriteLine(String.Format("Skipping unknown type of data: {0}", line))
End If
End If
End While
oReader.Close()
End Sub
' Used to save the data back to the file or your choice
Public Sub Save(ByVal sFileName As String)
Dim oWriter As New StreamWriter(sFileName, False)
For Each s As IniSection In Sections
Trace.WriteLine(String.Format("Writing Section: [{0}]", s.Name))
oWriter.WriteLine(String.Format("[{0}]", s.Name))
For Each k As IniSection.IniKey In s.Keys
If k.Value <> String.Empty Then
Trace.WriteLine(String.Format("Writing Key: {0}={1}", k.Name, k.Value))
oWriter.WriteLine(String.Format("{0}={1}", k.Name, k.Value))
Else
Trace.WriteLine(String.Format("Writing Key: {0}", k.Name))
oWriter.WriteLine(String.Format("{0}", k.Name))
End If
Next
Next
oWriter.Close()
End Sub
' Gets all the sections
Public ReadOnly Property Sections() As System.Collections.ICollection
Get
Return m_sections.Values
End Get
End Property
' Adds a section to the IniFile object, returns a IniSection object to the new or existing object
Public Function AddSection(ByVal sSection As String) As IniSection
Dim s As IniSection = Nothing
sSection = sSection.Trim()
' Trim spaces
If m_sections.ContainsKey(sSection) Then
s = DirectCast(m_sections(sSection), IniSection)
Else
s = New IniSection(Me, sSection)
m_sections(sSection) = s
End If
Return s
End Function
' Removes a section by its name sSection, returns trus on success
Public Function RemoveSection(ByVal sSection As String) As Boolean
sSection = sSection.Trim()
Return RemoveSection(GetSection(sSection))
End Function
' Removes section by object, returns trus on success
Public Function RemoveSection(ByVal Section As IniSection) As Boolean
If Section IsNot Nothing Then
Try
m_sections.Remove(Section.Name)
Return True
Catch ex As Exception
Trace.WriteLine(ex.Message)
End Try
End If
Return False
End Function
' Removes all existing sections, returns trus on success
Public Function RemoveAllSections() As Boolean
m_sections.Clear()
Return (m_sections.Count = 0)
End Function
' Returns an IniSection to the section by name, NULL if it was not found
Public Function GetSection(ByVal sSection As String) As IniSection
sSection = sSection.Trim()
' Trim spaces
If m_sections.ContainsKey(sSection) Then
Return DirectCast(m_sections(sSection), IniSection)
End If
Return Nothing
End Function
' Returns a KeyValue in a certain section
Public Function GetKeyValue(ByVal sSection As String, ByVal sKey As String) As String
Dim s As IniSection = GetSection(sSection)
If s IsNot Nothing Then
Dim k As IniSection.IniKey = s.GetKey(sKey)
If k IsNot Nothing Then
Return k.Value
End If
End If
Return String.Empty
End Function
' Sets a KeyValuePair in a certain section
Public Function SetKeyValue(ByVal sSection As String, ByVal sKey As String, ByVal sValue As String) As Boolean
Dim s As IniSection = AddSection(sSection)
If s IsNot Nothing Then
Dim k As IniSection.IniKey = s.AddKey(sKey)
If k IsNot Nothing Then
k.Value = sValue
Return True
End If
End If
Return False
End Function
' Renames an existing section returns true on success, false if the section didn't exist or there was another section with the same sNewSection
Public Function RenameSection(ByVal sSection As String, ByVal sNewSection As String) As Boolean
' Note string trims are done in lower calls.
Dim bRval As Boolean = False
Dim s As IniSection = GetSection(sSection)
If s IsNot Nothing Then
bRval = s.SetName(sNewSection)
End If
Return bRval
End Function
' Renames an existing key returns true on success, false if the key didn't exist or there was another section with the same sNewKey
Public Function RenameKey(ByVal sSection As String, ByVal sKey As String, ByVal sNewKey As String) As Boolean
' Note string trims are done in lower calls.
Dim s As IniSection = GetSection(sSection)
If s IsNot Nothing Then
Dim k As IniSection.IniKey = s.GetKey(sKey)
If k IsNot Nothing Then
Return k.SetName(sNewKey)
End If
End If
Return False
End Function
' Remove a key by section name and key name
Public Function RemoveKey(ByVal sSection As String, ByVal sKey As String) As Boolean
Dim s As IniSection = GetSection(sSection)
If s IsNot Nothing Then
Return s.RemoveKey(sKey)
End If
Return False
End Function
' IniSection class
Public Class IniSection
' IniFile IniFile object instance
Private m_pIniFile As IniFile
' Name of the section
Private m_sSection As String
' List of IniKeys in the section
Private m_keys As Hashtable
' Constuctor so objects are internally managed
Protected Friend Sub New(ByVal parent As IniFile, ByVal sSection As String)
m_pIniFile = parent
m_sSection = sSection
m_keys = New Hashtable(StringComparer.InvariantCultureIgnoreCase)
End Sub
' Returns all the keys in a section
Public ReadOnly Property Keys() As System.Collections.ICollection
Get
Return m_keys.Values
End Get
End Property
' Returns the section name
Public ReadOnly Property Name() As String
Get
Return m_sSection
End Get
End Property
' Adds a key to the IniSection object, returns a IniKey object to the new or existing object
Public Function AddKey(ByVal sKey As String) As IniKey
sKey = sKey.Trim()
Dim k As IniSection.IniKey = Nothing
If sKey.Length <> 0 Then
If m_keys.ContainsKey(sKey) Then
k = DirectCast(m_keys(sKey), IniKey)
Else
k = New IniSection.IniKey(Me, sKey)
m_keys(sKey) = k
End If
End If
Return k
End Function
' Removes a single key by string
Public Function RemoveKey(ByVal sKey As String) As Boolean
Return RemoveKey(GetKey(sKey))
End Function
' Removes a single key by IniKey object
Public Function RemoveKey(ByVal Key As IniKey) As Boolean
If Key IsNot Nothing Then
Try
m_keys.Remove(Key.Name)
Return True
Catch ex As Exception
Trace.WriteLine(ex.Message)
End Try
End If
Return False
End Function
' Removes all the keys in the section
Public Function RemoveAllKeys() As Boolean
m_keys.Clear()
Return (m_keys.Count = 0)
End Function
' Returns a IniKey object to the key by name, NULL if it was not found
Public Function GetKey(ByVal sKey As String) As IniKey
sKey = sKey.Trim()
If m_keys.ContainsKey(sKey) Then
Return DirectCast(m_keys(sKey), IniKey)
End If
Return Nothing
End Function
' Sets the section name, returns true on success, fails if the section
' name sSection already exists
Public Function SetName(ByVal sSection As String) As Boolean
sSection = sSection.Trim()
If sSection.Length <> 0 Then
' Get existing section if it even exists...
Dim s As IniSection = m_pIniFile.GetSection(sSection)
If s IsNot Me AndAlso s IsNot Nothing Then
Return False
End If
Try
' Remove the current section
m_pIniFile.m_sections.Remove(m_sSection)
' Set the new section name to this object
m_pIniFile.m_sections(sSection) = Me
' Set the new section name
m_sSection = sSection
Return True
Catch ex As Exception
Trace.WriteLine(ex.Message)
End Try
End If
Return False
End Function
' Returns the section name
Public Function GetName() As String
Return m_sSection
End Function
' IniKey class
Public Class IniKey
' Name of the Key
Private m_sKey As String
' Value associated
Private m_sValue As String
' Pointer to the parent CIniSection
Private m_section As IniSection
' Constuctor so objects are internally managed
Protected Friend Sub New(ByVal parent As IniSection, ByVal sKey As String)
m_section = parent
m_sKey = sKey
End Sub
' Returns the name of the Key
Public ReadOnly Property Name() As String
Get
Return m_sKey
End Get
End Property
' Sets or Gets the value of the key
Public Property Value() As String
Get
Return m_sValue
End Get
Set(ByVal value As String)
m_sValue = value
End Set
End Property
' Sets the value of the key
Public Sub SetValue(ByVal sValue As String)
m_sValue = sValue
End Sub
' Returns the value of the Key
Public Function GetValue() As String
Return m_sValue
End Function
' Sets the key name
' Returns true on success, fails if the section name sKey already exists
Public Function SetName(ByVal sKey As String) As Boolean
sKey = sKey.Trim()
If sKey.Length <> 0 Then
Dim k As IniKey = m_section.GetKey(sKey)
If k IsNot Me AndAlso k IsNot Nothing Then
Return False
End If
Try
' Remove the current key
m_section.m_keys.Remove(m_sKey)
' Set the new key name to this object
m_section.m_keys(sKey) = Me
' Set the new key name
m_sKey = sKey
Return True
Catch ex As Exception
Trace.WriteLine(ex.Message)
End Try
End If
Return False
End Function
' Returns the name of the Key
Public Function GetName() As String
Return m_sKey
End Function
End Class
' End of IniKey class
End Class
' End of IniSection class
End Class
Here is an example of how to use this class:
Private Sub WriteValuesToIniFile()
Dim IniFileConfig As New IniFile
IniFileConfig.Load("C:\User\SumGuy\Desktop\Config.ini")
IniFileConfig.SetKeyValue("CONFIG", "Key1", "Value")
IniFileConfig.Save("C:\User\SumGuy\Desktop\Config.ini")
End Sub
This will write this to an ini file:
[CONFIG]
Key1=Value

Related

VB.NET Group by two columns and write results to an array

I need to group csv data to new csv by column values. I can do it by only one column, but unfortunately it is not enough, because I got duplicates and not achieve my goal. Here is my csv example, there is about 50 columns and last here is column(29) in my input csv:
603;10453;2.12.2020;88,69
603;10453;2.12.2020;88,69
603;10453;4.12.2020;72,69
605;10441;3.12.2020;39,51
605;10441;8.12.2020;25,85
605;10441;9.12.2020;52,91
605;10441;10.12.2020;66,31
605;10441;10.12.2020;66,31
606;10453;11.12.2020;72,69
606;10453;11.12.2020;72,69
607;11202;1.12.2020;250,98
607;11202;1.12.2020;250,98
607;11202;1.12.2020;250,98
607;11202;1.12.2020;250,98
607;11202;1.12.2020;250,98
607;11202;2.12.2020;274,02
607;11202;2.12.2020;274,02
607;11202;2.12.2020;274,02
607;11202;2.12.2020;274,02
607;11202;2.12.2020;274,02
607;11202;2.12.2020;274,02
607;11202;3.12.2020;165,29
607;11202;3.12.2020;165,29
607;11202;3.12.2020;165,29
607;11202;3.12.2020;165,29
607;11202;4.12.2020;75,87
607;11202;5.12.2020;123,24
607;11202;5.12.2020;123,24
607;11202;5.12.2020;123,24
607;11202;7.12.2020;88,69
607;11202;7.12.2020;88,69
And here is my code, where I group values by last column:
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
Dim inputFile = "input.csv"
Dim outputFile = "output.csv"
IO.File.WriteAllLines(outputFile, IO.File.ReadLines(inputFile).
Select(Function(x) x.Split(";"c)).
GroupBy(Function(x) {x(0), x(3)}).
Select(Function(x)
Return String.Format(
"{0};{1};{2};{3}",
x.Select(Function(y) y(0)).First,
x.Select(Function(y) y(1)).First,
x.Select(Function(y) y(2)).First,
x.Select(Function(y) y(3)).First)
End Function).ToArray)
End Sub
As you can see in the last column duplicate values and I need group this file by two keys, one of them column(0) or column(1) values and second one is column(3). But I can't figure out how I can I do it with my code.
Desiret outout file have to looks like this:
603;10453;2.12.2020;88,69
603;10453;4.12.2020;72,69
605;10441;3.12.2020;39,51
605;10441;8.12.2020;25,85
605;10441;9.12.2020;52,91
605;10441;10.12.2020;66,31
606;10453;11.12.2020;72,69
607;11202;1.12.2020;250,98
607;11202;2.12.2020;274,02
607;11202;3.12.2020;165,29
607;11202;4.12.2020;75,87
607;11202;5.12.2020;123,24
607;11202;7.12.2020;88,69
Usualy I have to remove duplicates if column(0) and column(2) if they has match.
Thanks for help!
I would use an object oriented approach. Make a wrapper object around each line that does the parsing and provides properties for each value, and then group the result as desired (I choose again the object oriented approach with an equality comparer and distinct).
As I don't know the meaning of the columns I simply assumed something: OrderNo, CustomerNo, OrderDate and Value.
Here's the code for the wrapper class:
Private Class Record
'Constructors
Public Sub New(lineNo As Int32, line As String)
Const expectedColumnCount As Int32 = 4
Const delimiter As String = ";"
If (lineNo < 1) Then Throw New ArgumentOutOfRangeException(NameOf(lineNo), lineNo, "The line number must be positive!")
If (line Is Nothing) Then Throw New ArgumentNullException(NameOf(line))
Dim tokens As String() = Split(line, delimiter, expectedColumnCount + 1, CompareMethod.Binary)
If (tokens.Length <> expectedColumnCount) Then Throw New ArgumentException($"Line {lineNo}: Invalid data row! {expectedColumnCount} '{delimiter}'-delimitered columns expected.")
Me.Tokens = tokens
End Sub
'Public Properties
Public ReadOnly Property OrderNo As String
Get
Return Tokens(0)
End Get
End Property
Public ReadOnly Property CustomerNo As String
Get
Return Tokens(1)
End Get
End Property
Public ReadOnly Property OrderDate As String
Get
Return Tokens(2)
End Get
End Property
Public ReadOnly Property Value As String
Get
Return Tokens(3)
End Get
End Property
'Private Properties
Private ReadOnly Property Tokens As String()
End Class
And this is the comparer that does the grouping:
Private Class RecordComparer
Implements IEqualityComparer(Of Record)
Private Sub New()
End Sub
Public Shared ReadOnly Property Singleton As New RecordComparer()
Public Function Equals(x As Record, y As Record) As Boolean Implements IEqualityComparer(Of Record).Equals
If (Object.ReferenceEquals(x, y)) Then Return True
If (x Is Nothing) OrElse (y Is Nothing) Then Return False
Return Comparer.Equals(x.OrderNo, y.OrderNo) AndAlso Comparer.Equals(x.CustomerNo, y.CustomerNo) AndAlso Comparer.Equals(x.Value, y.Value)
End Function
Public Function GetHashCode(obj As Record) As Integer Implements IEqualityComparer(Of Record).GetHashCode
If (obj Is Nothing) Then Return 42
Return Comparer.GetHashCode(obj.OrderNo) Xor Comparer.GetHashCode(obj.CustomerNo) Xor Comparer.GetHashCode(obj.Value)
End Function
Private Shared ReadOnly Comparer As IEqualityComparer(Of String) = StringComparer.Ordinal
End Class
and finally the usage:
'Convert input lines to simple objects
Dim i As Int32 = 1
Dim dataRows As New List(Of Record)()
For Each line As String In File.ReadLines(inputFile)
Dim data As New Record(i, line)
dataRows.Add(data)
i += 1
Next
'Group by the 3 columns (the DateTime is kind of random, no guarantee which object wins)
Dim consolidatedRows As IEnumerable(Of Record) = dataRows.Distinct(SimpleInputDataComparer.Singleton)
'Convert and export lines
Dim outputLines As IEnumerable(Of String) = consolidatedRows.Select(Function(e) $"{e.OrderNo};{e.CustomerNo};{e.OrderDate};{e.Value}")
File.WriteAllLines(outputFile, outputLines)
I got it work. For my goal I used Christoph example. Finaly my code looks like this:
Public Class TempClass
Public Property ID As String
Public Property day As String
Public Property OriginalStr As String
End Class
Public Class TempIDComparer
Implements IEqualityComparer(Of TempClass)
Private Function IEqualityComparer_Equals(x As TempClass, y As TempClass) As Boolean Implements IEqualityComparer(Of TempClass).Equals
If ReferenceEquals(x, y) Then
Return True
End If
If ReferenceEquals(x, Nothing) OrElse ReferenceEquals(y, Nothing) Then
Return False
End If
Return x.ID = y.ID AndAlso x.day = y.day
End Function
Private Function IEqualityComparer_GetHashCode(obj As TempClass) As Integer Implements IEqualityComparer(Of TempClass).GetHashCode
If obj Is Nothing Then Return 0
Return obj.ID.GetHashCode()
End Function
End Class
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
Dim inputFile = "input.csv"
Dim outputFile = "output.csv"
Dim list As List(Of TempClass) = New List(Of TempClass)()
Dim ls As List(Of String()) = New List(Of String())()
Dim fileReader As StreamReader = New StreamReader(inputFile)
Dim strLine As String = ""
While strLine IsNot Nothing
strLine = fileReader.ReadLine()
If strLine IsNot Nothing AndAlso strLine.Length > 0 Then
Dim t As TempClass = New TempClass() With {
.ID = strLine.Split(";"c)(0),
.day = strLine.Split(";"c)(3),
.OriginalStr = strLine
}
list.Add(t)
End If
End While
fileReader.Close()
Dim tempList = list.Distinct(New TempIDComparer())
Dim fileWriter As StreamWriter = New StreamWriter(outputFile, False, System.Text.Encoding.Default)
For Each item In tempList.ToList()
fileWriter.WriteLine(item.OriginalStr)
Next
fileWriter.Flush()
fileWriter.Close()
End Sub

How can I print from an object?

I am having a problem getting my program to print an array. I have created a class with code and I want to be able to use the class to print the array. I have submitted my code below. hopefully Y'all can help me out thanks.
Option Strict On
Imports System.IO
Imports FinalLIB
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim fsrFile As StreamReader = New StreamReader("Cars.csv")
Dim line, splitLine(1) As String
Dim bestCars(14) As Cars
Dim counter As Integer
Do Until fsrFile.EndOfStream
line = fsrFile.ReadLine
splitLine = Split(line, ",")
bestCars(counter) = New Cars(splitLine(0), (splitLine(1)), (splitLine(2)), (splitLine(3)))
counter += 1
Loop
Dim strCarMake, strCarModel, intyear, strColorc As Cars
Console.WriteLine(bestCars(3))
End Sub
This is the code from my library created.
Option Strict On
Public Class Cars
Private strCarMake As String
Private strCarModel As String
Private intYear As String
Private strColor As String
Public Sub New(ByVal bvstrCarMake As String, ByVal bvstrCarModel As String, ByVal bvintYear As String, ByVal bvstrColor As String)
prpCarMake = bvstrCarMake
prpYear = CInt(bvintYear)
prpCarModel = bvstrCarModel
prpColor = bvstrColor
End Sub
Public Property prpCarMake() As String
Get
Return strCarMake
End Get
Set(bvstrCarMake As String)
strCarMake = bvstrCarMake
End Set
End Property
Public Property prpCarModel() As String
Get
Return strCarModel
End Get
Set(bvstrCarModel As String)
strCarModel = bvstrCarModel
End Set
End Property
Public Property prpYear() As Integer
Get
Return CInt(intYear)
End Get
Set(bvintYear As Integer)
intYear = CType(bvintYear, String)
End Set
End Property
Public Property prpColor() As String
Get
Return strColor
End Get
Set(bvstrColor As String)
strColor = bvstrColor
End Set
End Property
Public ReadOnly Property prpIsOld() As Boolean
Get
If prpYear > 2010 Then
Return True
Else
Return False
End If
End Get
End Property
'Public ReadOnly Property prpSSN() As String
'Get
'Return strSSN
'End Get
'End Property
Public Function ReturnFullInfo() As String
Return "Make: " & prpCarMake & " Model: " & prpCarModel & "Year: " & prpYear & "Color: " & prpColor
End Function
End Class

Extracting property values from a dictionary

I am attempting to write a subroutine that will deserialize a dictionary from a .ser file (this bit works fine) and then repopulate several lists from this dictionary (this is the bit I cannot do).
The dictionary contains objects (I think) of a custom class I wrote called "Photo Job" which has properties such as ETA, notes, medium etc. (Declared as such)
Dim photoJobs As New Dictionary(Of String, PhotoJob)
In short, I want to be able to extract every entry of each specific property into an separate arrays (one for each property) and I can go from there.
Any help would be appreciated, I may be going about this completely the wrong way, I'm new to VB. The relevant code is below:
Photo Job Class:
<Serializable()> _Public Class PhotoJob
Private intStage As Integer 'Declare all local private variables
Private ID As String
Private timeLeft As Integer
Private material As String '
Private note As String
Private path As String
Private finished As Boolean = False
'Declare and define properties and methods of the class
Public Property productionStage() As Integer
Get
Return intStage
End Get
Set(ByVal Value As Integer)
intStage = Value
End Set
End Property
Public Property photoID() As String
Get
Return ID
End Get
Set(ByVal Value As String)
ID = Value
End Set
End Property
Public Property ETA() As Integer
Get
Return timeLeft
End Get
Set(ByVal Value As Integer)
timeLeft = Value
End Set
End Property
Public Property medium() As String
Get
Return material
End Get
Set(ByVal Value As String)
material = Value
End Set
End Property
Public Property notes() As String
Get
Return note
End Get
Set(ByVal Value As String)
note = Value
End Set
End Property
Public Property imagePath() As String
Get
Return path
End Get
Set(ByVal Value As String)
path = Value
End Set
End Property
Public Property complete() As Boolean
Get
Return finished
End Get
Set(value As Boolean)
finished = value
End Set
End Property
Public Sub nextStage()
If intStage < 4 Then
intStage += 1
ElseIf intStage = 4 Then
intStage += 1
finished = True
End If
End Sub
End Class
Subroutines involved in de/serialisation:
Private Sub BackupAllToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles BackupAllToolStripMenuItem.Click
Dim formatter As New BinaryFormatter
Dim backupFile As New FileStream(Strings.Replace(Strings.Replace(Now, ":", "_"), "/", ".") & ".ser", FileMode.Create, FileAccess.Write, FileShare.None)
formatter.Serialize(backupFile, photoJobs)
backupFile.Close()
MsgBox("Collection saved to file")
End Sub
Private Sub RestoreFromFileToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles RestoreFromFileToolStripMenuItem.Click
With OpenFileDialog 'Executes the following sets/gets/methods of the OpenFileDialog
.FileName = ""
.Title = "Open Image File"
.InitialDirectory = "c:\"
.Filter = "Serial Files(*.ser)|*ser"
.ShowDialog()
End With
Dim backupPathStr As String = OpenFileDialog.FileName
Dim deSerializer As New BinaryFormatter
Dim backupFile As New FileStream(backupPathStr, FileMode.Open)
photoJobs = deSerializer.Deserialize(backupFile)
backupFile.Close()
End Sub
From what I can see using the autos menu, the saving/restoring of the dictionary works just fine.
First, if you are using VS2010+, you can greatly reduce boilerplate code using autoimplemented properties:
<Serializable()>
Public Class PhotoJob
Public Property productionStage() As Integer
Public Property photoID() As String
Public Property ETA() As Integer
etc
End Class
That is all that is needed, all the boilerplate code is handled for you. Second, with this line:
photoJobs = deSerializer.Deserialize(backupFile)
Your deserialized photojobs will be a generic Object, not a Dictionary. You should turn on Option Strict so VS will enforce these kinds of errors. This is how to deserialize to Type:
Using fs As New FileStream(myFileName, FileMode.Open)
Dim bf As New BinaryFormatter
PhotoJobs= CType(bf.Deserialize(fs), Dictionary(Of String, PhotoJob))
End Using
Using closes and disposes of the stream, CType converts the Object returned by BF to an actual dictionary
To work with the Dictionary (this has nothing to do with Serialization) you need to iterate the collection to get at the data:
For Each kvp As KeyValuePair(Of String, PhotoJob) In PhotoJobs
listbox1.items.Add(kvp.value.productionStage)
listbox2.items.Add(kvp.value.ETA)
etc
Next
The collection is a made of (String, PhotoJob) pairs as in your declaration, and when you add them to the collection. They comeback the same way. kvp.Key will be the string key used to identify this job in the Dictionary, kvp.Value will be a reference to a PhotoJobs object.
As long as VS/VB knows it is a Dictionary(of String, PhotoJob), kvp.Value will act like an instance of PhotoJob (which it is).

How to change RichTextBox line endings from Lf to CrLf

As opposed to the TextBox control which use CrLf line endings the RichTextBox control use Lf line endings. I don't want that. I need consistency. I need a RichTextBox control whom use CrLf line endings.
I opened the control in reflector and noticed that the getter of the Text property calls the following function:
Private Function StreamOut(ByVal flags As Integer) As String
'...
Me.StreamOut(data, flags, False)
'...
End Function
Which ends up calling:
Private Sub StreamOut(ByVal data As Stream, ByVal flags As Integer, ByVal includeCrLfs As Boolean)
'...
Dim es As New EDITSTREAM
'...
ElseIf includeCrLfs Then
num = (num Or &H20)
Else
'...
es.dwCookie = DirectCast(num, IntPtr)
'...
End Sub
And as you can see, the includeCrLfs parameter will always be False.
So I subclassed the control and intercepted the EM_STREAMOUT message. The LParam of this message contains the pointer to the EDITSTREAM structure. I appended the &H20 flag as seen in the function above, but this didn't work. The Text property started to return an empty string. I believe that I might have to remove/append other flags, but I have no clue as to which flags. Also, MSDN do not provide any hints other than application-defined value.
dwCookieSpecifies an application-defined value that the rich edit control passes to the EditStreamCallback callback function specified by the pfnCallback member.
Here's my subclassed control:
Public Class UIRichTextBox
Inherits System.Windows.Forms.RichTextBox
Private Sub EmStreamOut(ByRef m As Message)
Dim es As New EDITSTREAM
es = DirectCast(Marshal.PtrToStructure(m.LParam, GetType(EDITSTREAM)), EDITSTREAM)
If (IntPtr.Size = 4) Then
Dim cookie As Int32 = es.dwCookie.ToInt32()
'cookie = (cookie Or &H20I) '<- Didn't work
es.dwCookie = New IntPtr(cookie)
Else
Dim cookie As Int64 = es.dwCookie.ToInt64()
'cookie = (cookie Or &H20L) '<- Didn't work
es.dwCookie = New IntPtr(cookie)
End If
Marshal.StructureToPtr(es, m.LParam, True)
MyBase.WndProc(m)
End Sub
Protected Overrides Sub WndProc(ByRef m As Message)
Select Case m.Msg
Case EM_STREAMOUT
Me.EmStreamOut(m)
Exit Select
Case Else
MyBase.WndProc(m)
Exit Select
End Select
End Sub
Private Const EM_STREAMOUT As Integer = &H44A
Private Delegate Function EDITSTREAMCALLBACK(ByVal dwCookie As IntPtr, ByVal buf As IntPtr, ByVal cb As Integer, <Out()> ByRef transferred As Integer) As Integer
<StructLayout(LayoutKind.Sequential)> _
Private Class EDITSTREAM
Public dwCookie As IntPtr = IntPtr.Zero
Public dwError As Integer
Public pfnCallback As EDITSTREAMCALLBACK
End Class
End Class
Update
So it turns out that the flags are not undocumented at all. They are part of the EM_GETEDITSTYLE and EM_SETEDITSTYLE messages. But as you can see, the flag is obsolete.
SES_USECRLF Obsolete. Do not use.
So I guess I'm back at square one overriding the text property.
Public Overrides Property Text() As String
Get
Dim value As String = MyBase.Text
If (Not value Is Nothing) Then
value = value.Replace(ChrW(13), "")
value = value.Replace(ChrW(10), Environment.NewLine)
End If
Return value
End Get
Set(value As String)
MyBase.Text = value
End Set
End Property
So I managed to create a working solution using reflection. I'm sure there must be a good reason as to why SES_USECRLF is obsolete, so proceed with caution.
Public Class UIRichTextBox
Inherits System.Windows.Forms.RichTextBox
Shared Sub New()
UIRichTextBox.InternalEditStream = GetType(System.Windows.Forms.RichTextBox).GetField("editStream", (BindingFlags.NonPublic Or BindingFlags.Instance))
UIRichTextBox.InternalStreamIn = GetType(System.Windows.Forms.RichTextBox).GetMethod("StreamIn", (BindingFlags.NonPublic Or BindingFlags.Instance), Nothing, New Type() {GetType(System.IO.Stream), GetType(System.Int32)}, Nothing)
UIRichTextBox.InternalStreamOut = GetType(System.Windows.Forms.RichTextBox).GetMethod("StreamOut", (BindingFlags.NonPublic Or BindingFlags.Instance), Nothing, New Type() {GetType(System.IO.Stream), GetType(System.Int32), GetType(System.Boolean)}, Nothing)
End Sub
Public Sub New()
Me.m_includeCrLfs = True
End Sub
<DefaultValue(True), Category("Behavior")> _
Public Property IncludeCrLfs() As Boolean
Get
Return Me.m_includeCrLfs
End Get
Set(value As Boolean)
If (value <> Me.m_includeCrLfs) Then
Me.m_includeCrLfs = value
Me.RecreateHandle()
End If
End Set
End Property
Public Overrides Property [Text]() As String
Get
Dim value As String = Nothing
If (Me.StreamOut(&H11, value)) Then
Return value
End If
Return MyBase.[Text]
End Get
Set(ByVal value As String)
If (Not Me.StreamIn(value, &H11)) Then
MyBase.[Text] = value
End If
End Set
End Property
Private Function StreamIn(ByVal str As String, ByVal flags As Integer) As Boolean
If (((Me.IsHandleCreated AndAlso ((Not Me.IsDisposed) AndAlso (Not Me.Disposing))) AndAlso ((Not str Is Nothing) AndAlso (str.Length > 0))) AndAlso ((Not UIRichTextBox.InternalEditStream Is Nothing) AndAlso (Not UIRichTextBox.InternalStreamIn Is Nothing))) Then
Dim bytes As Byte()
Dim index As Integer = str.IndexOf(ChrW(0))
If (index <> -1) Then
str = str.Substring(0, index)
End If
If ((flags And &H10) <> 0) Then
bytes = Encoding.Unicode.GetBytes(str)
Else
bytes = Encoding.Default.GetBytes(str)
End If
Dim data As New System.IO.MemoryStream()
UIRichTextBox.InternalEditStream.SetValue(Me, data)
data.Write(bytes, 0, bytes.Length)
data.Position = 0
UIRichTextBox.InternalStreamIn.Invoke(Me, New Object() {data, flags})
Return True
End If
Return False
End Function
Private Function StreamOut(ByVal flags As Integer, ByRef result As String) As Boolean
If ((Me.IsHandleCreated AndAlso ((Not Me.IsDisposed) AndAlso (Not Me.Disposing))) AndAlso (Not UIRichTextBox.InternalStreamOut Is Nothing)) Then
Dim data As New System.IO.MemoryStream()
UIRichTextBox.InternalStreamOut.Invoke(Me, New Object() {data, flags, Me.m_includeCrLfs})
data.Position = 0
Dim length As Integer = CInt(data.Length)
Dim str As String = String.Empty
If (length > 0) Then
Dim buffer As Byte() = New Byte(length - 1) {}
data.Read(buffer, 0, length)
If ((flags And &H10) <> 0) Then
str = Encoding.Unicode.GetString(buffer, 0, buffer.Length)
Else
str = Encoding.Default.GetString(buffer, 0, buffer.Length)
End If
If ((Not String.IsNullOrEmpty(str)) AndAlso (str.Chars((str.Length - 1)) = ChrW(0))) Then
str = str.Substring(0, (str.Length - 1))
End If
End If
result = str
Return True
End If
Return False
End Function
Private Shared ReadOnly InternalEditStream As FieldInfo
Private Shared ReadOnly InternalStreamIn As MethodInfo
Private Shared ReadOnly InternalStreamOut As MethodInfo
Private m_includeCrLfs As Boolean
End Class

Populating a combo box with a list of functions - Need Advice

I'm looking for some advice on the best way to handle this.
I have a list of about 200 "Functions" which are listed in a combo box. When the user selects a 'function' from the list, I need to return the functionID (integer).
I know this can be done easily by binding a dataset to the key and value of the combobox, I'm just not sure about the best way to populate the dataset.
I feel that the way I'm doing it currently is very convoluted:
I currently have a txt file as an embedded resource which I write to a temporary directory, then I use the following code to read in that text file and populate that box by setting the combobox's datasource and Display Member. It does this by way of a custom class which is implementing System.Collections.IList.
I have pasted the code below. The reason I want to simplify it is that I dislike writing the text file to the disk, because sometimes it fails.
I'm looking for a way to populate my combobox and return my ID, without writing anything to the user's temp folder.
I am open to changing the format of the embedded resource, and or the code.
The fnlist.txt is formatted currently as follows.
index, Function Name, ID
The index is only included for sorting (to keep NONE at the bottom, and unknown function at the top), and I suppose is not strictly required.
#Region "Function lookup"
Dim path As String = System.IO.Path.GetTempPath
Dim _objFnXtef As New clsFunctionXref(path & "fnList.txt")
Private Sub populate_list()
functionlist.DataSource = _objFnXtef
functionlist.DisplayMember = "StrFunction"
End Sub 'Populates the function list
Function get_index(ByVal fnid As Integer)
Dim iLookupNumber As Integer = fnid
Dim tmpFnInfo As New clsFunctionInfo
Dim iReturnIdx As Integer = -1
If iLookupNumber <> 0 Then
tmpFnInfo.IFunctionNumber = iLookupNumber
iReturnIdx = _objFnXtef.IndexOf(tmpFnInfo)
If iReturnIdx <> -1 Then
Return iReturnIdx - 1
Else
Return get_index(9999)
End If
End If
Return 0
End Function 'Returns index of specified function number
#End Region 'All function list functions
Here is the code when a user changes the drop down:
Private Sub functionlist_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles functionlist.SelectedIndexChanged
Dim iReturnFuctionID As Integer = 0
Dim tmpFnInfo As New clsFunctionInfo
tmpFnInfo = _objFnXtef(functionlist.SelectedIndex)
iReturnFuctionID = tmpFnInfo.IFunctionNumber
Func = (iReturnFuctionID)
End Sub
And here is the supporting class:
Imports Microsoft.VisualBasic.FileIO
Public Class clsFunctionInfo
Private _idxFunction As Integer
Public Property IdxFunction() As Integer
Get
Return _idxFunction
End Get
Set(ByVal value As Integer)
_idxFunction = value
End Set
End Property
Private _strFunction As String
Public Property StrFunction() As String
Get
Return _strFunction
End Get
Set(ByVal value As String)
_strFunction = value
End Set
End Property
Private _iFunctionNumber As Integer
Public Property IFunctionNumber() As Integer
Get
Return _iFunctionNumber
End Get
Set(ByVal value As Integer)
_iFunctionNumber = value
End Set
End Property
End Class
Public Class clsFunctionXref
Implements System.Collections.IList
Private _colFunctionInfo As New Collection
Private _filePath As String
Public Property FilePath() As String
Get
Return _filePath
End Get
Set(ByVal value As String)
_filePath = value
End Set
End Property
Public Sub New(ByVal filename As String)
_filePath = filename
Dim _idx As Integer = 1
Dim fields As String()
Dim delimiter As String = ","
Dim iFnx As Integer
Using parser As New TextFieldParser(filename)
parser.SetDelimiters(delimiter)
While Not parser.EndOfData
' Read in the fields for the current line
fields = parser.ReadFields()
Try
iFnx = Convert.ToInt16(fields(0).ToString)
Catch ex As Exception
MessageBox.Show("Error reading file. " & ex.ToString, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
Exit Sub
End Try
Dim objFunction As New clsFunctionInfo
objFunction.IdxFunction = _idx
objFunction.IFunctionNumber = iFnx
objFunction.StrFunction = fields(1).ToString
Me.Add(objFunction)
_idx += 1
End While
End Using
End Sub
Public Function Add(ByVal value As Object) As Integer Implements System.Collections.IList.Add
If _colFunctionInfo.Contains(value.IFunctionNumber.ToString) Then
SyncLock Me.SyncRoot
_colFunctionInfo.Remove(value.IFunctionNumber.ToString)
End SyncLock
ReIndex()
End If
SyncLock Me.SyncRoot
_colFunctionInfo.Add(value, value.IFunctionNumber.ToString)
End SyncLock
End Function
Public Sub Clear() Implements System.Collections.IList.Clear
SyncLock Me.SyncRoot
_colFunctionInfo.Clear()
End SyncLock
End Sub
Public Function Contains(ByVal value As Object) As Boolean Implements System.Collections.IList.Contains
If _colFunctionInfo.Contains(value.IFunctionNumber.ToString) Then
Return True
Else
Return False
End If
End Function
Public ReadOnly Property Count() As Integer Implements System.Collections.ICollection.Count
Get
Return _colFunctionInfo.Count
End Get
End Property
Public ReadOnly Property IsReadOnly() As Boolean Implements System.Collections.IList.IsReadOnly
Get
Return False
End Get
End Property
Public Sub Remove(ByVal value As Object) Implements System.Collections.IList.Remove
If _colFunctionInfo.Contains(value.IFunctionNumber.ToString) Then
SyncLock Me.SyncRoot
_colFunctionInfo.Remove(value.IFunctionNumber.ToString)
End SyncLock
ReIndex()
End If
End Sub
Public Function GetEnumerator() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator
Return _colFunctionInfo.GetEnumerator
End Function
Public Sub Insert(ByVal index As Integer, ByVal value As Object) Implements System.Collections.IList.Insert
SyncLock Me.SyncRoot
If _colFunctionInfo.Contains(value.IFunctionNumber.ToString) Then
_colFunctionInfo.Remove(value.IFunctionNumber.ToString)
End If
If index < _colFunctionInfo.Count Then
_colFunctionInfo.Add(value, value.IFunctionNumber.ToString, index - 1)
Else
_colFunctionInfo.Add(value, value.IFunctionNumber.ToString)
End If
End SyncLock
ReIndex()
End Sub
Public Sub RemoveAt(ByVal index As Integer) Implements System.Collections.IList.RemoveAt
SyncLock Me.SyncRoot
If _colFunctionInfo.Count <= index And index > 0 Then
_colFunctionInfo.Remove(index)
End If
End SyncLock
ReIndex()
End Sub
Private Sub ReIndex()
SyncLock Me.SyncRoot
Dim iReIndex As Integer = 1
Dim colTemp As New Collection
For Each obj As clsFunctionInfo In _colFunctionInfo
obj.IdxFunction = iReIndex
colTemp.Add(obj, obj.IFunctionNumber)
iReIndex += 1
Next
_colFunctionInfo.Clear()
For Each obj1 As clsFunctionInfo In colTemp
_colFunctionInfo.Add(obj1, obj1.IFunctionNumber.ToString)
Next
colTemp.Clear()
End SyncLock
End Sub
Public ReadOnly Property IsSynchronized() As Boolean Implements System.Collections.ICollection.IsSynchronized
Get
Return True
End Get
End Property
Public ReadOnly Property SyncRoot() As Object Implements System.Collections.ICollection.SyncRoot
Get
Dim _syncRoot As New Object
Return _syncRoot
End Get
End Property
Public ReadOnly Property IsFixedSize() As Boolean Implements System.Collections.IList.IsFixedSize
Get
Return False
End Get
End Property
Public Sub CopyTo(ByVal array As System.Array, ByVal index As Integer) Implements System.Collections.ICollection.CopyTo
For Each obj As clsFunctionInfo In _colFunctionInfo
array(index) = obj
index += 1
Next
End Sub
Public Function IndexOf(ByVal value As Object) As Integer Implements System.Collections.IList.IndexOf
SyncLock Me.SyncRoot
Dim tmpFnInfo As New clsFunctionInfo
Dim tmpFunctionNumber As Integer
Dim tmpidx As Integer = -1
tmpFnInfo = DirectCast(value, clsFunctionInfo)
tmpFunctionNumber = tmpFnInfo.IFunctionNumber
For Each obj In _colFunctionInfo
tmpFnInfo = DirectCast(obj, clsFunctionInfo)
If tmpFunctionNumber = tmpFnInfo.IFunctionNumber Then
tmpidx = tmpFnInfo.IdxFunction
Exit For
End If
Next
Return tmpidx
End SyncLock
End Function
Default Public Property Item(ByVal index As Integer) As Object Implements System.Collections.IList.Item
Get
index += 1
Return _colFunctionInfo(index)
End Get
Set(ByVal value As Object)
End Set
End Property
End Class
I'm sorry that this is so long, but I know that someone on here has some great suggestions on how to handle this because I'm having a little trouble wrapping my head around it. I think I've been starring at it too long.
since you have the text file as an embedded resource, you can open a stream to the file from there, without having to write it to disk. The ResourceReader class should help you.