VB store list of records in a text file and read back - vb.net

I have a VB program in VS2017. It reads many excel files and store relevant data in
Dim total_sessions As New List(Of Session_CSV_file)
The list is big (~10k), and includes about 20 fields each.
Is there a way in VB to store it in a file in one command and read it later on easily., without reading and writing each field?
Often I need to debug the later part. I prefer doing it without rerunning the whole first part.
Or is there a way in Visual Studio, to stop the run at a certain point, and store this point in a dump file, like simulators often do? (so they can run the simulation from this point later on)

Perhaps if you load your csv data into a DataTable instead:
Dim dt as New DataTable
dt.Columns.Add("Name")
dt.Columns.Add("Age", GetType(Int32))
For Each filepath in tenKFilePaths
For Each line in File.ReadAllLines(file)
Dim bits = line.Split(","c)
dt.Rows.Add(bits(0), Convert.ToInt32(bit(1))
Next line
Next filepath
Then you can save the whole lot to xml:
dt.WriteXml("c:\temp\my.xml")
Have a coffee, reboot etc the read them again and carry on where you left off:
Dim dt2 as New DataTable
dt2.ReadXml("c:\temp\my.xml")
Raw datatables are a bit difficult to work with because you end up accessing their rows and column data by string indexing and having to cast it a lot - fairly lame. There is a better way to use them, as visual studio can create custom classes that inherit from the base datatables and offer lots of functionality that does away with all the awkwardness of dealing with datatables as a very generic thing. Just like the forms designer creates a UI form that inherits from Form, the dataset designer creates custom datatables that inherit from the base
If you add a new DataSet type object to your project, double click it it opens something like a database designer. Right click that surface and choose Add DataTable, Add your Columns with their respective datatypes etc - this is the equivalent of your Session_Csv_File class, with its properties and fields. Double click on it and you can add code etc - if your session_csv_file has custom methods you can transfer their code to the custom data row
Imagine I created a new datatable in a DataSet and called it SessionCsvFiles. Imagine I added a Name and Age column, and I added a custom method called BlahBlah
I'd then be able to say in code things like:
Dim dt as New MyCustomDataSet.SessionCsvFilesDataTable
'it's like making a new SessionCsvFile
Dim r = dt.NewSessionCsvFilesRow()
'It's like setting your properties
r.Name = csvBits(0)
r.Age = Convert.ToInt32(csvBits(1))
'It's like adding your file to the List
dt.Add(r)
You now have virtually the same thing as you had before - a collection of objects that represents things about your csv files. The difference is with this route it has an already-built-in way of saving all the properties and the collection to xml and reading it back again

Add the Serializable attribute to the Class. I added a parameterized constructor for ease in building my list but you must provide a constructor without parameters for this to work. Thus, the empty Sub New.
<Serializable()>
Public Class Session_CSV_file
Public Property ID As Integer
Public Property Name As String
Public Property Type As String
Public Sub New()
End Sub
Public Sub New(SessID As Integer, SessName As String, SessType As String)
ID = SessID
Name = SessName
Type = SessType
End Sub
End Class
The add Imports System.Xml.Serialization to the top of the file.
Private Sub SaveList()
Dim serializer = New XmlSerializer(GetType(List(Of Session_CSV_file)))
Using writer As New StreamWriter("C:\Users\xxx\Documents\XMLtest.xml")
serializer.Serialize(writer, lst)
End Using
End Sub
I filled my list as follows but you can fill you list any way you choose.
Private lst As New List(Of Session_CSV_file)
Private Sub FillList()
Using cn As New SqlConnection(My.Settings.CoffeeConnection),
cmd As New SqlCommand("Select Top 10 * From Coffees;", cn)
cn.Open()
Dim reader = cmd.ExecuteReader
While reader.Read
Dim sess As New Session_CSV_file(reader.GetInt32(0), reader.GetString(1), reader.GetString(3))
lst.Add(sess)
End While
End Using
End Sub
To recreate the list... (OOps! forgot this)
Private RehydratedList As New List(Of Session_CSV_file)
Private Sub CreateListFromXML()
Dim serial As New XmlSerializer(GetType(List(Of Session_CSV_file)))
Using fs As New FileStream("C:\Users\maryo\Documents\XMLtest.xml", FileMode.Open)
RehydratedList = DirectCast(serial.Deserialize(fs), List(Of Session_CSV_file))
End Using
For Each item In RehydratedList
Debug.Print(item.ToString)
Next
End Sub

Related

Dictionary comes back from cache as Null/Nothing

I'm writing some vb.net code to retrieve some data from middleware, do some stuff with it, and then save a dictionary of name/value pairs to cache for quick retrieval when needed.
It all seems to go well until I retrieve the object from cache and it is always Null/Nothing.
Yet in the watch window I can see my cached object and it has data in it.
Thinking maybe it's a serialization issue I made a new class that inherits Dictionary and is marked as serializable but I still get the same results.
So right now I have this class:
<Serializable()> Public Class SerialDict
Inherits Dictionary(Of String, String)
Public Sub New()
End Sub
End Class
I populate it and put it into cache like this:
Dim Licenses As New SerialDict
For Each r As DataRow In dtLicenses.Rows
Dim prikey As String = r("SettingID").ToString.Trim
Dim decryptionKey As String = GetHash((xx))
Dim licData As String = DecryptData(r("SettingVal"), decryptionKey)
Licenses.Add(r("SettingKey"), licData)
Next
If IsNothing(HttpContext.Current.Cache("Licenses")) Then
HttpContext.Current.Cache.Add("Licences", Licenses, Nothing, Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.Default, Nothing)
End If
Then elsewhere we need to check that data so I try to retrieve it like this:
Dim Licences As SerialDict = CType(HttpContext.Current.Cache("Licenses"), SerialDict)
At this point Licenses is always Nothing, but the watch window shows data in HttpContext.Current.Cache("Licenses").
Any suggestions? Thanks!

Saving user's DataGridViewColumn widths for multiple forms/controls

Typically, when designing a DataGridView, I try to size the columns so that nothing will need to be resized by the user. This practice works most of the time, but I recently had a user change her Windows settings so that text would display larger than usual.
That single act broke all of the tedious sizing that I worked so hard on. I have looked into saving column width per user and allowing them to be saved to the registry. The issue I run into is having to create a field in the application settings for each and every value that I want to save to the registry.
When saving settings for a single form, that is not a problem, and I do use the application settings for this purpose to save the main window size/location so that users can determine the optimal view of the application.
My question is:
Is there a way to save an array to the registry, or perhaps otherwise save dynamic values into the registry, without adding these values to the application settings in advance? Ideally, I will just have a "ColumnWidths" application setting or something along those lines, and dynamically add column name/width for any column that is resized by the user.
I have the following code, which is fine if I add a new setting for each individual form that I want to save sizing for, but I'm hoping to achieve this with a single setting, that will save column sizing for multiple DataGridView/Forms.
Private Sub SaveColumnSettings()
If My.Settings.CustomColumnWidths Is Nothing Then
My.Settings.CustomColumnWidths = New StringCollection
End If
For Each dc As DataGridViewColumn In grdBackOrderedItems.Columns
My.Settings.CustomColumnWidths.Add(dc.Width.ToString())
Next
End Sub
Private Sub LoadColumnSettings()
For i As Integer = 0 To My.Settings.CustomColumnWidths.Count - 1
grdBackOrderedItems.Columns(i).Width = CInt(My.Settings.CustomColumnWidths(i))
Next
End Sub
Settings allows for a Collection type, however it is a string collection (initial post seemed not to know this).
The current edit is dicey since it assumes one collection will fit all DGVs on all forms (that is, the same number of columns for all of them) and you are only adding to the Collection every time you save - making it larger. Since you want to save the users optimal view you should have one collection per DGV since they might well make col4 on DGVFoo wider than elsewhere.
Rather than adding a collection Setting for each DGV (ie "DGVFooColumns", "DGVBarColumns" etc), which requires each one be hard coded to map to the correct entry, I would use a custom class and serialize it.
Most everything regarding them can be internalized to the class. This is not a finished class, but a rough out of making one class service any and all DGVs (it should be very close):
' for the collection
Imports System.Collections.ObjectModel
Imports System.Runtime.Serialization.Formatters.Binary
<Serializable>
Public Class GridLayouts
<Serializable>
Friend Class GridLayout
Public Property GridName As String
Public Widths As New List(Of Integer)
' Some Serializers will require a simple ctor
Public Sub New()
Widths = New List(Of Integer)
End Sub
Public Sub SaveLayout(name As String, dgv As DataGridView)
' something the code uses to map them
' other than "DataGridView1" of which there may be several
GridName = name
' ToDo: loop thru DGV save widths to Widths
End Sub
End Class
Private mCol As Collection(Of GridLayout)
Private myFile As String
Public Sub New(filename As String)
myFile = filename
mCol = New Collection(Of GridLayout)
End Sub
Private Function IndexOf(grdName As String) As Integer
For n As Integer = 0 To mCol.Count - 1
If grdName = mCol(n).GridName Then
Return n
End If
Next
Return -1 ' not found
' alternatively find by name:
'Dim item = mCol.FirstOrDefault(Function(i) i.GridName = grdName)
'Return item
End Function
Public Sub StoreLayout(name As String, dgv As DataGridView)
Dim ndx As Integer = IndexOf(name)
If ndx <> -1 Then
mCol.RemoveAt(ndx) ' throw away old one
End If
Dim grd As New GridLayout
grd.SaveLayout(name, dgv)
mCol.Add(grd)
End Sub
Public Sub RestoreLayout(name As String, dgv As DataGridView)
Dim ndx As Integer = IndexOf(name)
If ndx = -1 Then
Exit Sub
End If
For n As Integer = 0 To mCol(ndx).Widths.Count - 1
' loop thru grid and set the columns
' maybe check that the col sizes are equal
dgv.Columns(n).Width = mCol(n).Widths(n)
Next n
End Sub
Public Sub Save()
' ToDo: Make a backup (?)
' Add a Try/Catch and convert to function
Dim bf As New BinaryFormatter
Using fs As New FileStream(myFile, FileMode.OpenOrCreate)
bf.Serialize(fs, mCol)
End Using
End Sub
Public Sub Load()
Dim bf As New BinaryFormatter
Using fs As New FileStream(myFile, FileMode.Open)
mCol = CType(bf.Deserialize(fs), Collection(Of GridLayout))
End Using
End Sub
End Class
Notes:
1) The Example has SaveLayout at the Class-Item level and RestoreLayout at the Collection Level. I would pick one way or the other not split it. This is was for illustrative purposes. I would generally favor the ClassItem Level for both.
2) Rather than the BinaryFormatter, you can use the XML Serializer. I personally loathe it.
3) Note that the collection class saves/loads its own data.
4) System.Collections.ObjectModel is required for Collection(of T) and prevent VB from wanting the use the vile VisualBasic Collection (which stores only Object).
5) Code elsewhere could be optimized so that you only store a layout if they actually change the widths and click something (or have an AutoSaveChanges option).
6) A fair amount is riding on the names being unique so that the layouts can be found.
Usage
Dim SaveFile As String = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
CompName,
ProgramName,
filename)
' e.g C:\Users\Ziggy\MyCompany\ThisProd\GridSettngs.bin
' application-wide collection of grid layouts for this user
Private myGrids As New GridLayouts(SaveFile)
...
myGrids.StoreLayout("Customers", datagridView12)
myGrids.RestoreLayout("Orders", datagridView34)

Serializing and Searching Object Collections

I would appreciate if somebody could answer some questions regarding storing and searching object collections please. If you could give a basic example of any suggestions I would be very grateful.
Storing:
I'm writing an application which needs to search Active Directory for computer objects, and I'm storing them in a collection of objects. I want to save the computer objects collection along with additional information which is not stored in Active Directory (e.g. the computer's MAC address) for the next time the application is run. I also want to save a list of OU's.
Here is my object collection so far (It will have more properties). What is the best method of saving the collection? Preferably not using a database or will saving to a file have a drastic performance impact?
Or is there a better way to do what I have below?
Public Class Computer
Public Property Name As String
Public Property FQDN As String
Public Property Path As String
End Class
Public Class ComputerCollection
Inherits Collections.CollectionBase
Public Sub Add(ByVal computer As Computer) 'Adds a new computer object to the collection
List.Add(computer)
End Sub
End Class
Searching:
I have a layout similar to ADUC with a tree view of OU's and a list view displaying computer objects in the selected OU. The following code loops through the computer object collection checking if the path of the computer object matches the selected OU path and then displays them in the list view.
Is this the best method in terms of performance? or is there a faster way?
Private Sub tvOU_AfterSelect(sender As Object, e As TreeViewEventArgs) Handles tvOU.AfterSelect
lvComputers.Items.Clear()
If tvOU.SelectedNode.Name > "" Then
For Each computerObj In computers
If computerObj.Path.ToString.Replace("CN=" + computerObj.Name + ",", "") = tvOU.SelectedNode.Name Then
Dim lvItem As New ListViewItem
lvItem.Text = computerObj.Name
lvComputers.Items.Add(lvItem)
End If
Next
Else
Exit Sub
End If
End Sub
A simple List(Of Computer) is indeed all you need unless there is some other unknown requirement. Saving is very simple using serialization for this type of thing, and would work the same for a List or Collection<T>.
Imports System.Collections.ObjectModel
Public Class ComputersCollection
Inherits Collection(Of Computer)
' NOT the crude thing in the VB NameSpace
' there is almost nothing to add: item, add and remove
' are in the base class
' ... perhaps saving and retrieving them by a key (name)
' which may do away with the search procedure in the posted code
End Class
Private Computers As New Collection(Of Computer)
' or
Private Computers As New List(Of Computer)
The other part, saving your collection, can be simple with serialization. First, you will have to add the Serializable attribute to your Computer class:
<Serializable>
Public Class Computer
If you forget, you get the error Class foobar is not marked as serializable. Save to disk:
Imports System.Runtime.Serialization
Private myComputerFile As String = ...
Dim bf As New BinaryFormatter
Using fs As New FileStream(myComputerFile , FileMode.OpenOrCreate)
bf.Serialize(fs, Computers)
End Using
Then you can recreate the List or Collection just as easily; this time the List:
' ToDo: add a check to skip if file is not there
' as in the first time run
Dim bf As New BinaryFormatter
Using fs As New FileStream(myComputerFile , FileMode.Open)
Computers = CType(bf.Deserialize(fs), List(Of Computers))
End Using
Alternatively you can use the XML serializer to create an XML File or use the faster, smarter binary serializer is ProtoBuf-Net

How to serialize a List in VB?

I am writing a program to take several fields of a "patient" and serialize the data into XML. I am grabbing all the information as follows:
Dim pacients As New List(Of Patients)
Dim p As New Patients
p.mFirstName = txtFirstName.ToString
p.mlastName = txtLastName.ToString
p.mInsurance = txtInsurance.ToString
p.mDOB = txtDateOfBirth.ToString
p.mEmail = txtEmail.ToString
p.mPhone = txtPhone.ToString
p.mPlanID = txtPhone.ToString
p.mSubID = txtSubID.ToString
And this is the part with the big error (this is triggered by a different 'save all' button, where as the code before is part of a 'add to list' button):
Dim writer As New StreamWriter(PatientFileName.PatientFileName)
Dim serial As New XmlSerializer(GetType(Patients))
serial.Serialize(writer, pacients) 'in this line it just says there was an error generating the xml document
writer.Close()
If the patients class is required I will post it.
I found the problem. This:
Dim serial As New XmlSerializer(GetType(Patients))
Is expecting one object of patient, this needs to be changed to expect a list as follows:
Dim serial As New XmlSerializer(GetType(List(Of Patient)))
Thanks for all the suggestions.
This should do it... you can loop through your list and call this method each time, also make sure you have the serialize attribute at the top of your patient class, its required. Also your issue is because your gettype is for your patient class, not a list of patients which your passing in to serialize. Also from the looks of code you posted your don't need a list, just use your class object and save it...
Public Sub SavePatient(Byval patients As Patients)
Dim writer As New System.Xml.Serialization.XmlSerializer(GetType(patients))
Dim file As New System.IO.StreamWriter(
"YOURPATH.xml")
writer.Serialize(file, patients)
file.Close()
End Sub

How to get all forms in a VB.net (VS08) project in an array?

Alright, so I need a method that traverses all the forms inside a VB.net project under Visual Studio 2008, and create an array of type form with references to all the forms inside it, so that the array looks like this (pseudocode)
FormsArray() = [Form1, Form2, Form3, Form4]
However, I don't have a clue as to how to begin.
You have to adjust the function to put the result of msgbox in a array
Public Sub getallforms(ByVal sender As Object)
Dim Forms As New List(Of Form)()
Dim formType As Type = Type.GetType("System.Windows.Forms.Form")
For Each t As Type In sender.GetType().Assembly.GetTypes()
If UCase(t.BaseType.ToString) = "SYSTEM.WINDOWS.FORMS.FORM" Then
MsgBox(t.Name)
End If
Next
End Sub
You must call the function from any form in the application like this (getallforms(me))
Here is how you would do this using Reflection, assuming that the class where you placed this code was in the same assembly that you wanted to iterate over. If not, then you'll need to change the Me.GetType().Assembly in the For Each loop into something else to account for loading the assembly in a different manner.
Dim Forms As New List(Of Form)()
Dim formType As Type = Type.GetType("System.Windows.Forms.Form")
For Each t As Type In Me.GetType().Assembly.GetTypes()
If t.IsSubclassOf(formType) = True Then
Forms.Add(CType(Activator.CreateInstance(t), Form))
End If
Next
Hey this is what I did to get the list of forms in my vb project, how ever this in not in code but you could write system.io code fragment to do just that.
open cmd prompt
go to project folder
run a dir /s/b *.designer.vb >> list.txt
use notepad or sublimetext and edit it to get the list ordered as you like it.
:) hope this helped!
I could not get this version to work:
Dim Forms As New List(Of Form)()
Dim formType As Type = Type.GetType("System.Windows.Forms.Form")
For Each t As Type In Me.GetType().Assembly.GetTypes()
If t.IsSubclassOf(formType) = True Then
Forms.Add(CType(Activator.CreateInstance(t), Form))
End If
Next
In VB2010 formType is always Nothing
So I dumped the formType line and simply modified your 'IF' statement to check the BaseType instead. Here is the New Version
Dim Forms As New List(Of Form)()
For Each t As Type In Me.GetType().Assembly.GetTypes()
If t.BaseType.Name = "Form" Then
Forms.Add(CType(Activator.CreateInstance(t), Form))
End If
Next
You need to either write a VS macro or an Addin.
In it, from a DTE or DTE2 instance, you can write:
Public Sub GetForms(ByVal host As DTE2)
Dim project As Project = host.ActiveDocument.ProjectItem.ContainingProject
For Each ce As CodeElement In project.CodeModel.CodeElements
If ce.Kind = vsCMElement.vsCMElementClass Then
Dim cl As CodeClass = CType(ce, CodeClass)
If cl.IsDerivedFrom("System.Windows.Forms) Then
'do something
End If
End If
Next
End Sub
2 options
I would load the actual project file into a XML reader. Then iterate all the nodes looking for all Form SubTypes and store the linked files in an array. If the name of file matches the name of the form class, you can create your FormsArray from that list. Otherwise you have to load each file and look for the public class definition of the file to get the list.
Using Reflection, examine the project using Assembly.GetTypes. Find all the System.Windows.Forms.Form Types and store them in a list. Then write out the Type.Name.