This might be impossible or a bit wrong headed, however in WinForms I've got combo boxes that need populating with specific options. The project uses about 10 different forms, all with similar but slightly different functionality: hence why I didn't use just one form and hide/show controls as appropriate.
Now I made a simple dictionary of options and fed the values with an Enum. Now I realise I've got duplicate code and would like to consolidate it. The option sets are date order and name order, but I've got one or two more to list.
This is what I've tried but cannot pass the dictionary into:
Public Sub BuildOrderOptions(nameorder As ComboBox, Options As Dictionary(Of String, [Enum]))
nameorder.Items.Clear()
For Each item In Options
nameorder.Items.Add(item)
Next
nameorder.DisplayMember = "Key"
nameorder.ValueMember = "Value"
End Sub
Property NameOrderOptions As New Dictionary(Of String, PersonList.orderOption) From {{"First Name", PersonList.orderOption.FirstName},
{"Last Name", PersonList.orderOption.LastName},
{"Room Number", PersonList.orderOption.RoomNumber}}
Property DateOrderOptions As New Dictionary(Of String, OrderDate) From {{"Newest First", OrderDate.NewestFirst}, {"Oldest First", OrderDate.OldestFirst}}
I've tried a few variations with Type and [enum].getnames etc but I can't pass the differing dictionary types in at all - I think I've overcomplicated the whole business by now but feel I'm missing an elegant solution. Shortly I'll either convert back to string matching alone, or just have functions per box type - evil duplication but I can move on.
Am I right in thinking there is a nicer way to do this? Unless I just define some kind of global resource for the options maybe-but globals are bad right?
Edit: Fixed thanks to Steven. In case anyone finds it useful OR better yet, anyone can critique and make nicer, here's the module code that all the forms can use to generate their options.
Public Sub BuildOrderOptions(nameorder As ComboBox, Options As IDictionary)
nameorder.Items.Clear()
For Each item In Options
nameorder.Items.Add(item)
Next
nameorder.DisplayMember = "Key"
nameorder.ValueMember = "Value"
End Sub
Property NameOrderOptions As New Dictionary(Of String, orderOption) From {{"First Name", orderOption.FirstName},
{"Last Name", orderOption.LastName},
{"Room Number", orderOption.RoomNumber}}
Property DateOrderOptions As New Dictionary(Of String, OrderDate) From {{"Newest First", OrderDate.NewestFirst}, {"Oldest First", OrderDate.OldestFirst}}
Property personStatusOptions As New Dictionary(Of String, personStatus) From {{"Active", personStatus.Active},
{"InActive", personStatus.InActive},
{"All", personStatus.All}}
Public Sub BuildComboBoxes(ListBoxes As Dictionary(Of ComboBox, IDictionary))
For Each pair In ListBoxes
BuildOrderOptions(pair.Key, pair.Value)
Next
End Sub
Public Enum OrderDate
OldestFirst
NewestFirst
End Enum
Public Enum personStatus
Active
InActive
All
End Enum
Public Enum orderOption
None
FirstName
LastName
RoomNumber
End Enum
And here's the way I've got one form using it - yes, I could have had a bunch of parameters or multiple function calls: I just like having a single object giving me a single parameter to pass on.
BuildComboBoxes( New Dictionary ( Of ComboBox , IDictionary ) From {{NameOrder, NameOrderOptions},
{DateOrder, DateOrderOptions},
{personStatus, PersonStatusOptions}})
You just need to change your method to accept any IDictionary object rather than a specific type of dictionary:
Public Sub BuildOrderOptions(nameorder As ComboBox, Options As IDictionary)
When you are using generics, such as Dictionary(Of TKey, TValue), the generic type is not really a type at all. You can think of it like a template for any number of specific types. So each time you use a generic type using different type parameters, they are entirely different and incompatible types. For instance:
' This works fine because both d1 and d2 are exactly the same type
Dim d1 As New Dictionary(Of String, String)()
Dim d2 As Dictionary(Of String, String) = d1
' This will not compile because d1 and d2 are completely different types
Dim d1 As New Dictionary(Of String, Integer)()
Dim d2 As Dictionary(Of String, Boolean) = d1
As you have found out, even if you try to use a base class as the generic type parameter, the two are still incompatible. So, even though Stream is the base class for MemoryStream, you still cannot do this:
' This will not compile because d1 and d2 are completely different types
Dim d1 As New Dictionary(Of String, MemoryStream)()
Dim d2 As Dictionary(Of String, Stream) = d1
Related
i have a list(of custom class)
and i want to extract a list of all 'name' String, from it, through linq
I know how to do with a loop, but i need to get it with a linear, brief linq instruction.
i've checked this help
C# Extract list of fields from list of class
but i have problem in linq correct syntax
in particular because i would like to extract a New List(Of String)
Class Student
Sub New(ByVal NewName As String, ByVal NewAge As Integer)
Name = NewName
Age = NewAge
End Sub
Public Name As String
Public Age As Integer
End Class
Public Sub Main
Dim ClassRoom as New List(Of Student) From {New Student("Foo",33), New Student("Foo2",33), New Student("Foo3",22)}
Dim OneStudent as Student = ClassRoom(0)
Dim AllStudentsNames As New List(Of String) From {ClassRoom.Select(Function(x) x.Name <> OneStudent.Name).ToList}
End Sub
But something wrong...
Any help?
P.S. Since c# it's close to vb.Net, also c# helps are well welcome.
First, you don't need to create a new list From the one returned by the LINQ method. It's already in a new list at that point, so you can just set AllStudentsNames equal directly to what the ToList method returns.
Second, you are not selecting the name. You are selecting the result of the equality test to see if the names are different. In other words, when you say Select(Function(x) x.Name <> OneStudent.Name), that returns a list of booleans, where they true if the names are different and false if the names are the same. That's not what you want. You want the list of names, so you need to select the name.
Third, if you need to filter the list so that it only returns ones where the name is different, then you need to add a call to the Where method.
Dim AllStudentsNames As List(Of String) = ClassRoom.
Where(Function(x) x.Name <> OneStudent.Name).
Select(Function(x) x.Name).
ToList()
I have an infragistics grid control that features two columns: one of strings showing the names of certain settings and the other with a drop-down menu containing the values available for the name it's associated with. All the values are the same. I added the drop-down in the designer after instantiating it and adding it to the control like so:
Me.settingLevelDrpDown.DataSource = MyDict.ToList()
Me.settingLevelDrpDown.ValueMember = "Key"
Me.settingLevelDrpDown.DisplayMember = "Value"
In this case, MyDict is a Dictionary(Of MyEnum, String) where MyEnum is just an enum. The code that displays these is:
settingLevelDrpDown.Visible = True
settingLevels.DisplayLayout.Bands(0).Columns(1).ValueList = settingLevelDrpDown
I'm having two issues so far:
The first is that, when I display a drop-down, I get a table with one row for Key and a list of the enum keys and a row for Value with a list of the strings I actually want to display. How can I ensure that the enum-keys are bound to the drop-down selection while ensuring that the string values are displayed?
The second is performance. I've read section three of this and, as far as I can tell, I've not stumbled on any of the points listed, yet load times are really slow and the application lags super-hard even after the forms load.
Any help with these two problems would be greatly appreciated.
I would work to avoid the UltraDropDown in your code.
I would just use the ValueList property of the column.
Suppose that your MyDict is an instance of this class
Dim myDict As Dictionary(Of Int32, String) = New Dictionary(Of Int32, String)
I would transform it in a ValueList with a method like this
Public Function ToValueList(settings As Dictionary(Of Int32, String)) As ValueList
Dim result As ValueList = New ValueList()
For Each kvp As KeyValuePair(Of Int32, String) In settings
result.ValueListItems.Add(kvp.Key, kvp.Value)
Next
Return result
End Function
Now in your InitializeLayout event of your grid you could write
Dim b as UltraGridBand = settingLevels.DisplayLayout.Bands(0)
' Just to avoid the user typing something not expected
' Default is an editable DropDown
b.Columns(1).Style = ColumnStyle.DropDownList
b.Columns(1).ValueList = ToValueList(MyDict)
I want to make a collection to have data available
Example:
Dim b As New Collection
colb = New Collection
b.Add("system", "1001", "SYSTEM")
b.Add("network", "1002", "NETWORKA")
b.Add("networksecond", "1010", "NETWORKB")
colb.Add(b, "list")
im looking for a function to get data from this collection:
I want to, based on the ID (Second number) get the first and third value
So if I search for 1010, I need to have the value Network and NETWORKA
VB6 called, they want their Collection back.
No, seriously, please consider using a Dictionary instead of the old, legacy Collection class. Behold the beauty of generics and strong typing:
Dim dic As New Dictionary(Of Integer, Tuple(Of String, String))
dic.Add(1001, Tuple.Create("system", "SYSTEM"))
dic.Add(1002, Tuple.Create("network", "NETWORKA"))
dic.Add(1010, Tuple.Create("networksecond", "NETWORKB"))
' Search
Dim t As Tuple(Of String, String) = Nothing
If dic.TryGetValue(1002, t) Then
Console.WriteLine(t.Item1) ' prints "network"
Console.WriteLine(t.Item2) ' prints "NETWORKA"
End If
As soon as you have more than two values, I suggest that you use a specialized class instead of a Tuple to increase readability.
Also, you can simply use List(Of T). In most cases this is enough. Dictionary is good for fast search out long list by a single key.
'declare model
Public Class NetworkModel
Public Property Id As Integer
Public Property Name1 As String
Public Property Name2 As String
End Class
' load list of models
Private _modelList As New List(Of NetworkModel)()
.......
' search using LINQ
Dim model As NetworkModel = _modelList.FirstOrDefault(Function(m) m.Id = 1001)
If model IsNot Nothing Then . . . . .
I have read here that a dictionary (of string, object) can hold multiple variable types. However, in the function below, endTime does not appear to get assigned. The line Console.Write(lobjWaveOutList(0)("endTime")) in the code below gives me the error 'the given key was not present in the dictionary'.
Private lobjWaveOutList As New List(Of Dictionary(Of String, Object))()
Public Sub addIndex(waveOut As Object, endTime As DateTime)
Console.WriteLine("endTime:")
Console.WriteLine(endTime)
lobjWaveOutList.Add(New Dictionary(Of String, Object)() From {{"waveOut", waveOut}})
lobjWaveOutList.Add(New Dictionary(Of String, Object)() From {{"endTime", endTime}})
Console.Write(lobjWaveOutList(0)("endTime"))
End Sub
I called the addIndex function with the following parameters:
waveouts.addIndex(New WaveOut(), DateTime.Now.AddSeconds(10))
lobjWaveOutList(0)("endTime") will not work because it is accessing the first dictionary in the list, which only contains a "waveOut" item. The "endTime" item is in the second dictionary in the list. To get that one, you'd need to do this:
Console.Write(lobjWaveOutList(1)("endTime"))
As Neolisk pointed out, it would seem more appropriate, based on your example, to simply have a single dictionary, containing multiple items, rather than a list of dictionaries, each only containing a single item.
As you have a list of Dictonary "endtime" is here: lobjWaveOutList(1)("endTime")
How do you get the key value from a vb.net collection when iterating through it?
Dim sta As New Collection
sta.Add("New York", "NY")
sta.Add("Michigan", "MI")
sta.Add("New Jersey", "NJ")
sta.Add("Massachusetts", "MA")
For i As Integer = 1 To sta.Count
Debug.Print(sta(i)) 'Get value
Debug.Print(sta(i).key) 'Get key ?
Next
Pretty sure you can't from a straight Microsoft.VisualBasic.Collection.
For your example code above, consider using a System.Collections.Specialized.StringDictionary. If you do, be aware that the Add method has the parameters reversed from the VB collection - key first, then value.
Dim sta As New System.Collections.Specialized.StringDictionary
sta.Add("NY", "New York")
'...
For Each itemKey in sta.Keys
Debug.Print(sta.Item(itemKey)) 'value
Debug.Print(itemKey) 'key
Next
I don't recommend using the Collection class, as that is in the VB compatibility library to make migrating VB6 programs easier. Replace it with one of the many classes in the System.Collections or System.Collections.Generic namespace.
It is possible to get a key with using Reflection.
Private Function GetKey(Col As Collection, Index As Integer)
Dim flg As BindingFlags = BindingFlags.Instance Or BindingFlags.NonPublic
Dim InternalList As Object = Col.GetType.GetMethod("InternalItemsList", flg).Invoke(Col, Nothing)
Dim Item As Object = InternalList.GetType.GetProperty("Item", flg).GetValue(InternalList, {Index - 1})
Dim Key As String = Item.GetType.GetField("m_Key", flg).GetValue(Item)
Return Key
End Function
Not using VB.Collection is recommended but sometimes we are dealing with code when it was used in past. Be aware that using undocumented private methods is not safe but where is no other solution it is justifiable.
More deailed information can be found in SO: How to use reflection to get keys from Microsoft.VisualBasic.Collection
Yes, it may well, but I want recomend that you use another Collection.
How to do you do with Reflection, the type Microsoft.VisualBasic.Collection contains some private fields, the field one should use in this case is "m_KeyedNodesHash" the field, and the field type is System.Collections.Generic.Dictionary(Of String, Microsoft.VisualBasic.Collection.Node), and it contains a property called "Keys", where the return type is System.Collections.Generic.Dictionary(Of String, Microsoft.VisualBasic.Collection.Node).KeyCollection, and the only way to get a certain key is to convert it to type IEnumerable(Of String), and the call ElementAt the function.
Private Function GetKey(ByVal col As Collection, ByVal index As Integer)
Dim listfield As FieldInfo = GetType(Collection).GetField("m_KeyedNodesHash", BindingFlags.NonPublic Or BindingFlags.Instance)
Dim list As Object = listfield.GetValue(col)
Dim keylist As IEnumerable(Of String) = list.Keys
Dim key As String = keylist.ElementAt(index)
Return key
End Function