Readonly in keyvaluepair - vb.net

Well I have created a program that takes some files (Mp3) and change their tags
recently I wanted to add some new Subs (like: Take the songs name and make every letter in it upercase). The problem is that i use a list with its items to be keyvaluepairs
Public MP3List As New List(Of KeyValuePair(Of String, String))
When i tried to edit the key or value of any Item in that list i get an error (That this is READONLY)
Example:
For Each Song In MP3List
Song.Key = "Something"
Next
I add items like this :
Private Function OpenAFile()
Dim MP3List1 = MP3List
Dim oFileDialog As New OpenFileDialog
oFileDialog.Title = "Επέλεξε ένα MP3 Άρχειο"
oFileDialog.Filter = "MP3 Files|*.mp3|All Files|*.*"
oFileDialog.Multiselect = True
Dim Path As String = ""
Dim Name As String = ""
Dim NewPair As New KeyValuePair(Of String, String)
If oFileDialog.ShowDialog = Windows.Forms.DialogResult.OK Then
For Each sPath In oFileDialog.FileNames
Path = New String(sPath)
Name = New String(Strings.Split(Path, "\").ToList(Strings.Split(Path, "\").ToList.Count - 1))
NewPair = New KeyValuePair(Of String, String)(Name, Path)
If Not MP3List1.Contains(NewPair) Then MP3List1.Add(NewPair)
Next
End If
Return MP3List1
End Function
So the idea is this: Each time i press A button to add a song it will run the function OpenAFile() and it was working fine then . Now that i want to change a key or value i get this error
Thanks for the Help and sorry for bad english

The Keys in a KeyValuePair are readonly because they are often used as the key in a hash table. Changing the key would cause issues where you would lose your item in the hash.
If you want to do something like this, you could always create your own data type that stores a key and value. An overly simplified example would be as follows.
Public Structure PathNamePair
Public Property Path As String
Public Property Name As String
Public Sub New(path As String, name As String)
Me.Path = path
Me.Name = name
End Sub
End Structure
I will note that in order to get better performance with your Contains method, you should also implement IEquatable(Of T), but that's probably beyond the scope of this question. I will also note that it is not best practice to have a ValueType (Structure) that is mutable.

Related

Convert an unknown structure to an untyped Object in VB.NET

I'd like to convert an unknown basic structure to an Object (no type here).
I'm building a library that will be used by many users to extract data from my system but don't want to do a new function for everyone of them. They have to know what will be the result.
In vb, it is possible to create an Object with some properties and use it as it is a regular Class like so:
Dim myObj as New With { .name = "Matt", .age = "28" }
MsgBox( myObj.name & " is now " & myObj.age & " years old.")
So far, so good.
Next step : my user will give me some instructions that I need to extract data from various DBs, and I've no idea of what the result will be.
What I know after the execution is a list of String containing the columns of the result set and, of course a (set of) rows.
And here is the problem of course
My function (for a single row) so far:
Public Function GetData(ByVal instructions as String) as Object ' User is supposed to know what will be inside, instructions is as XML describing DB, table, query, ...
' Do what is needed to retrieve data
' Here I have a variable cols As List(Of String) ' e.g. ("BP", "NAME", "VAT")
Dim o As New With ???
Return o
End Function
What I've tried: build a fake JSon on the fly, and try to Deserialize to Object.
But even if it seems to work, I (and the user) can't access the property as in my top piece of code like:
MsgBox(o.BP)
I know that I could do
Public Function GetData(Of T As {New})(ByVal instructions as String) As T
Dim o As T
' Use some Reflexion to TryInvokeMember of T
Return o
End Function
But I wanted to remove the hassle to create a class to use my code.
Plus, My librairy will be use in a webservice and the class of the user is then unknown.
One approach could be - to use Dictionary(Of String, Object)
Public Function GetData(instructions as String) As Dictionary(Of String, Object)
Dim data = ' Load data
Dim columns As String() = { "BP", "NAME", "VAT" }
Return columns.ToDictionary(
Function(column) column,
Function(column) data.GetByColumnName(column)
)
End Function
` Usage
Dim result = GetDate("instructions: ['BP', 'NAME']")
' Because user knows it is Integer
Dim bpValue = DirectCast(result.Item("BP"), Integer)
Thanks to #GSerg, #Fabio and a few other searches about ExpandoObject, I did it !
Imports System.Dynamic
Dim o As Object = New ExpandoObject()
For Each col In cols
DirectCast(o, IDictionary(Of String, Object)).Add(col, row.GetString(col))
Next

Properties, Arrays and ArrayLists using vb.net

In my code behind in my Web project I have a Property
Public Shared UserAttributes(2) As String
Public Property _UserAttributes(ByVal Index As Integer) As String
Get
Return UserAttributes(Index)
End Get
Set(value As String)
UserAttributes(Index) = value
End Set
End Property
And I also have an ArrayList declared as Friend
Friend UserParameters As New ArrayList
I call my property like that:
_UserAttributes(0) = "parameter1"
_UserAttributes(1) = "parameter2"
_UserAttributes(2) = "parameter3"
UserParameters.Add(UserAttributes)
_UserAttributes(0) = "parameter1,1"
_UserAttributes(1) = "parameter2,1"
_UserAttributes(2) = "parameter3,1"
UserParameters.Add(UserAttributes)
From the above code we may see the two pairs of Attributes having one text each one.
What I need now is:
After I add the three Attributes from my Property to my ArrayList
The second three Attributes of my property NOT to spoil the first one.
Which by now that they are doing
And finally I have two(2) _items in my ArrayList which they have the same text on each _item (which is the last one).
What I need is to write the second (or more) set of Attributes without spoiling the previous _items from ArrayList.
Finally I've made it to solve this puzzle as follows
One Property as ArrayList
Public Property _UserParameters As ArrayList
Get
Return UserParameters
End Get
Set(value As ArrayList)
UserParameters = value
End Set
End Property
Second Property as Array
Public Property _UserAttributes(ByVal Index As Integer) As String
Get
Return UserAttributes(Index)
End Get
Set(value As String)
UserAttributes(Index) = value
End Set
End Property
And from code behind I use this code:
Dim UserAttributes As New Hashtable
Dim key As Object = Nothing
Dim Param As Object = Nothing
Dim myList As New ArrayList
Dim item As Object = UserAttributes
UserAttributes.Add("UserId", "Parametr1")
UserAttributes.Add("UserName", "Parametr2")
UserAttributes.Add("UserMail", "Parametr3")
For Each item In UserAttributes
key = item.Key
Param = item.value
logHandler._UserParameters.Add(key & "^" & Param)
Next
myList.Add(logHandler.UserParameters.ToArray)
UserAttributes.Clear()
logHandler.UserParameters.Clear()
UserAttributes.Add("UserId", "Parametr1-1")
UserAttributes.Add("UserName", "Parametr2-1")
UserAttributes.Add("UserMail", "Parametr3-1")
For Each item In UserAttributes
key = item.Key
Param = item.value
logHandler._UserParameters.Add(key & "^" & Param)
Next
myList.Add(logHandler.UserParameters.ToArray)
The use of HashTable solves my issue, Along with the conversion from HashTable parameters to String
Which add them first to the ArrayList Property
And after that add them to the second ArrayList
And the result of these ArrayList I add it to a Pull Down menu control.
And Why I'm doing all that?
That is because I have many users with the same attributes as keys but deferent values
Good day to all, with many thanks.

Looping through a list gives me repeation of items, xml serializer

I have two lists. Both are made of structures I have defined, and this loop is meant to convert the two. I.e., convert and then add to the second list of the other type. Here is what I have:
Dim tempList As New List(Of CameraTemplateProduct)
tempList.Clear()
For j As Integer = 0 To EditCamerasNEW.templateList.Count - 1
'Set up product object.
Dim temp As New CameraTemplateProduct()
'equal properties
temp.Name = EditCamerasNEW.templateList.Item(j).mac
temp.Bitrate = EditCamerasNEW.templateList.Item(j).bitrate
temp.CamDate = EditCamerasNEW.templateList.Item(j).camdate
temp.CamTime = EditCamerasNEW.templateList.Item(j).camtime
temp.Encoder = EditCamerasNEW.templateList.Item(j).encoder
temp.FPS = EditCamerasNEW.templateList.Item(j).fps
temp.Hostname = EditCamerasNEW.templateList.Item(j).hostname
temp.MD = CBool(EditCamerasNEW.templateList.Item(j).MDen)
temp.OSD = CBool(EditCamerasNEW.templateList.Item(j).OSD)
temp.Resolution = EditCamerasNEW.templateList.Item(j).res
tempList.Add(temp)
Next
'Serialize object to a text file.
Dim x As New XmlSerializer((tempList.GetType))
x.Serialize(objStreamWriter, tempList)
Very straight forward. Copy over each property, then add it to the list. When I'm in the loop, temp's values copy over well. The values are exactly the same as the item in TemplateList. When I step through the loop, tempList is exactly what I expect it to be. Three distinct structures. But after wards I get the exact same number of items, but copies of one of them in my list.
However, the line right after the next loop, the tempList instead is the exact same count as templateList, but each value is exactly the same. So every item has the same name, MD, encoder, etc value.
What I've tried: I've tried changing the line after the next to
Dim x As New XmlSerializer((GetType(List(Of CameraTemplateProduct))))
but it gives the same result.
What am I doing wrong? Is there anything the "templist.gettype" is doing to cause this?
EDIT:
I have found that the temp is not changing property values when it loops, so it stays stuck at the first loop values. Is there a better way to clear or set it? I tried setting it to Nothing, but it gave me NULL assignment error.
EDIT2: So following the comments,
I checked to see if the templatelist items were changing. I added a
Dim test = EditCamerasNEW.templateList(j).mac
for each loop to see that it changed. The value did change. I set the rest of the "templatelist.item(j).x" to just "templatelist(j)" as above, but it didn't stop it from creating a list of repeated values.
EDIT3 Tried the below method to no avail. I'm thinking it is possibly when I create the templist of my class. It may not know how to create a list of the product? I will take any help on that.
tempList.Add(New CameraTemplateProduct With {.Name = EditCamerasNEW.templateList(j).mac, _
.Bitrate = EditCamerasNEW.templateList(j).bitrate, _
.CamDate = EditCamerasNEW.templateList(j).camdate, _
.CamTime = EditCamerasNEW.templateList(j).camtime, _
.Encoder = EditCamerasNEW.templateList(j).encoder, _
.FPS = EditCamerasNEW.templateList(j).fps, _
.Hostname = EditCamerasNEW.templateList(j).hostname, _
.MD = EditCamerasNEW.templateList(j).MDen, _
.OSD = EditCamerasNEW.templateList(j).OSD, _
.Resolution = EditCamerasNEW.templateList(j).res})
Here's a portion of the CameraTemplateProduct definition. It's pretty normal:
Public Class CameraTemplateProduct
Public Shared strhostname As String
Public Shared bOSD As Boolean
Public Shared strbitrate As String
Public Shared strencoder As String
Public Shared bMDen As Boolean 'motion detection enabled
Public Shared strres As String
Public Shared intfps As Integer
Public Shared strcamtime As String
Public Shared strcamdate As String
Public Shared strTemplateName As String
'grab properties
Public Property Name() As String
Get
Name = strTemplateName
End Get
Set(ByVal Value As String)
strTemplateName = Value
End Set
End Property
Public Property Hostname() As String
Get
Hostname = strhostname
End Get
Set(ByVal Value As String)
strhostname = Value
End Set
End Property
Public Property OSD() As Boolean
Get
OSD = bOSD
End Get
Set(ByVal Value As Boolean)
bOSD = Value
End Set
End Property
' code continues

Writing Entries in a VB Dictionary into a Text File

I'm working on VB in college and am running into a snag with one of my assignments. Can someone help? I'm aiming to try to take the following dictionary code:
Public Class Inventory
Public ItemInventory As New Dictionary(Of String, Item)
Public Function iItem(ByVal key As String) As Item
Return ItemInventory(key)
End Function
Public Sub addItem(ByVal item As String, ByVal Desc As String, ByVal DRate As Double, ByVal WRate As Double, _
ByVal MRate As Double, ByVal Quantity As Integer)
With ItemInventory
.Add(item, New Item(item, Desc, DRate, WRate, MRate, Quantity))
End With
End Sub
Public Sub removeItem(ByVal item As String)
With ItemInventory
.Remove(item)
End With
End Sub
Public Function returnKeys() As String()
Dim Keys() As String
With ItemInventory
Keys = .Keys.ToList.ToArray
End With
Return Keys
End Function
End Class
Not pretty, I know, but it gets the job done, that's all I aim to do. Now a bit of this also has to do with displaying a dictionary item in the program, which I'm also having issues with, however, I'd like to take this one step at a time, so we'll get to that later, if possible.
As per writing, this is my current code for reading and writing:
Imports System.IO
Public Class InventoryFile
Public Sub RFile(ByVal FPath As String, ByRef dInventory As Inventory)
Dim infile As StreamReader = File.OpenText(FPath)
Dim entireLine As String = infile.ReadLine()
Dim fields() As String = entireLine.Split(","c)
While infile.EndOfStream
Dim dItem As New Item
dItem.ID = fields(0)
dItem.Description = fields(1)
dItem.Daily = fields(2)
dItem.Weekly = fields(3)
dItem.Monthly = fields(4)
dItem.Quantity = fields(5)
'AddItem
dInventory.addItem(dItem.ID, dItem.Description, dItem.Daily, dItem.Weekly, _
dItem.Monthly, dItem.Quantity)
End While
End Sub
Public Sub WFile(ByVal FPath As String, ByRef dInventory As Inventory)
Dim outfile As StreamWriter = File.CreateText(FPath)
For Each Item As KeyValuePair(Of String, Item) In dInventory.ItemInventory
Next
End Sub
End Class
I hope that posted right. Now, reading in, as far as I understand, works just fine, in terms of a file going into a dictionary, however 'WFile', my StreamWriter, is what's got me stumped. Can someone help me with that? Likewise, its supposed to close and write to the file upon closing, and my only code for the close button is the Me.Close() command. How would I write a trigger to make the program write to the file? Know that the main form code, and my 'InventoryFile' are both separate classes, so this has to be done by referencing the other classes in question
Try this to write each dictionary key/value pair on a single line in the file:
Dim fs As FileStream
' Open the stream and write to it.
fs = File.OpenWrite(FPath)
For Each Item As KeyValuePair(Of String, Item) In dInventory.ItemInventory
fs.Write("{0}:{1}", Item.Key, Item.Value)
Next
UPDATE:
Since Item is both the variable used in the loop and the name of a class, change it to another name like singleItem and then pull out the other pieces of information from the Value portion of the key/value pair, because the Value is actually an Item class object, like this:
Dim fs As FileStream
' Open the stream and write to it.
fs = File.OpenWrite(FPath)
For Each singleItem As KeyValuePair(Of String, Item) In dInventory.ItemInventory
fs.Write("{0}:{1}:{2}:{3}:{4}:{5}:{6}", singleItem.Key, singleItem.Value.ID, singleItem.Value.Description, singleItem.Value.Daily, singleItem.Value.Weekly, singleItem.Value.Monthly, singleItem.Value.Quantity)
Next
In order to create the format your RFile procedure can read you need something like this:
For Each kvp As KeyValuePair(Of String, Item) In dInventory.ItemInventory
' using Karl's compact approach, but add commas
' since your Read expects them
outfile.Write("{0},{1},{2},{3},{4}...", kvp.Key, kvp.Value.ID, _
kvp.Value.Description, ... kvp.Value.Quantity.Tostring)
' the Value part of the fvp is another class, right? `Value.XXX` should drill
' into it to get to the members
Next
outfile.flush
outfile.close ' look into 'Using...'
Thats NOT how I would do it, look into serialization for a less fragile way to read/write the data. It is basically meant for just this sort of thing: save class data for later, and it is not that hard to use.
as for hooking it up, the button click would just call Inventory.WFile

How to write the contents of a dictionary to a MessageBox

In VB.NET I want to write the contents of a dictionary to a message box.
The dictionary is rather basic
Dim x As New Dictionary(Of Integer, Users)
x.Add("1", New Users("1", "Simon"))
The user class contains 2 attributes, user ID (Integer) and Username (String).
I am struggling to write the dictionary contents. I would like to write each dictionary entry to a string but i am having no success as I keep getting the error message:
Argument 'Prompt' cannot be converted to type 'String'.
You are passing a string where you specified an integer:
Fix:
Dim x As New Dictionary(Of Integer, Users)
x.Add(1, New Users(1, "Simon"))
Then to show the contents:
Dim sb As New StringBuilder
For Each item As KeyValuePair(Of Integer, Users) In x
sb.AppendLine(item.Key & ") " & item.Value.ToString)
Next
MessageBox.Show(sb.ToString())
Your Users class would need to override the ToString function or change the ToString call to the property in Users that shows the user's name.
Update to Users class:
Public Class Users
Private _p1 As Integer
Private _p2 As String
Sub New(ByVal p1 As Integer, ByVal p2 As String)
_p1 = p1
_p2 = p2
End Sub
Public Overrides Function ToString() As String
Return _p2
End Function
End Class
Here you go.
Dim sbMessage As New System.Text.StringBuilder(500)
For Each wKey As Integer In x.Keys
sbMessage.Append("Key = ").Append(wKey).Append(", Value = ").Append(x.Item(wKey).ToString()).AppendLine()
Next
MessageBox.Show(sbMessage.ToString)
To make this useful, you will need to override the ToString method in the Users class. For example, assuming that there is an ID and a name in this class:
Public Overrides Function ToString() As String
Dim sbText As New System.Text.StringBuilder(500)
sbText.Append("ID = ").Append(Me.Id).Append(", Name = ").Append(Me.Name)
Return sbText.ToString
End Function
For Each kvp As KeyValuePair(Of Integer, Users) In x
Console.WriteLine("Key = {0}, Value = {1}", _
kvp.Key, kvp.Value)
Next kvp
Something like that, bearing in mind that your Value will be a Users object, and that as #LarsTech said, you should pass in an integer instead of a string into the Dictionary