My project is in Visual Basic. I am trying to create a custom & savable "filter" for a DataGridView using several TextBoxes. Right now, any List(Of String) that is added to the Combo Box is displayed in the box as (Collection). I want my users to be able to select the one they created, so I would like the Lists to have a display name that can be selected in the Combo Box. Here is some of the code.
Dim savedFilter As New List(Of String)
savedFilter.Add(NameTextBox.Text)
savedFilter.Add(AgeTextBox.Text)
savedFilter.Add(NotesTextBox.Text)
ComboBoxSavedFilters.Items.Add(savedFilter)
Is it possible to add a display name for a List?
Or if you are lazy use buid-in generic class Tuple From MSDN.
Create collection of Tuple(Of String, List(Of String)) and use approach suggested by #Plutonix for binding collection to ComboBox
Dim savedFilter As New List(Of Tuple(Of String, List(Of String)))()
savedFilter.Add(
Tuple.Create("default",
New List From {"filter1", "filter2", "filter3"}))
savedFilter.Add(
Tuple.Create("Blue ones",
New List From {"filter4", "filter5"}))
savedFilter.Add(
Tuple.Create("Old ones",
New List From {NameTextBox.Text, AgeTextBox.Text, NotesTextBox.Text}))
With ComboBoxSavedFilters
.DisplayMember = "Item1" 'Name of first property in Tuple type
.ValueMember = "Item2" 'Name of second property in Tuple type -List
.DataSource = savedFilter
End With
Then SelectedValue will contain currently selected filter's collection,
which can be accessed like that
Dim filter As List(Of String) =
DirectCast(Me.ComboBoxSavedFilters.SelectedValue, List(Of String))
You could setup under My.Settings a StriingCollection
Initializing (you can omit the items added if so desired)
If My.Settings.Filters Is Nothing Then
My.Settings.Filters = New StringCollection() From {"One", "Two"}
End If
Setup items in a ComboBox
ComboBox1.Items.AddRange(My.Settings.Filters.Cast(Of String).ToArray)
Adding an item
My.Settings.Filters.Add(Now.ToShortDateString)
You can remove and clear items too.
Provide a Display Member for List(Of String)
Apparently, these are less a collection of filters than a collection of criteria or clauses for one Filter:
I condensed the code in the question, but there are 14 fields that can be filtered and there are multiple filters that can be applied on one field.
For the multiples per field, I am not sure I would want to store those individually, but keep the field criteria together. So, if you want to apply a name to these, a class would not only do that but could help manage the filter elements:
Public Class SuperFilter
Public Property Name As String
Public Property Elements As SortedList
Public ReadOnly Property FilterText As String
Get
Return GetFilterText()
End Get
End Property
Public Sub New(n As String)
Name = n
Elements = New SortedList
End Sub
Public Sub AddItem(filter As String)
Elements.Add(Elements.Count, filter)
End Sub
Public Sub InsetAt(index As Int32, filter As String)
Elements.Add(index, filter)
End Sub
Private Function GetFilterText() As String
Dim els(Elements.Count - 1) As String
Elements.Values.CopyTo(els, 0)
Return String.Join(" ", els)
End Function
Public Overrides Function ToString() As String
Return String.Format("{0} ({1})", Name, Elements.Count.ToString)
End Function
End Class
You would need to add methods and properties like Remove and Count but this should be enough to demonstrate. I am not sure about the SortedList, a Dictionary using the field name might be better, but something to control the order seems worthwhile. I am also unsure I would expose the Elements collection - managing it might be better left to the class.
Hopefully, the Combo displaying a set of these (as opposed to the filter elements/clauses) is the goal.
Private filters As New List(Of SuperFilter)
Add filter items to the list:
Dim item As New SuperFilter("Default")
item.AddItem("Id = 7")
filters.Add(item)
item = New SuperFilter("Blue Ones")
item.AddItem("Color = Blue")
filters.Add(item)
item = New SuperFilter("Complex")
item.AddItem("[Name] like %Bob% OR [Name] like %Alice%")
item.AddItem("AND Color = 'Blue'")
item.AddItem("AND Active=True")
item.AddItem("AND AccessRequired < 3")
item.AddItem("AND DateAdded > #2/11/2010#")
item.AddItem("AND CreatedBy = 'ziggy'")
filters.Add(item)
cbo1.DataSource = filters
cbo1.DisplayMember = "Name"
cbo1.ValueMember = "FilterText"
The value member could be the Elements - the collection of filter clauses, or it could be the query text. The GetFilterText method joins them together for you as part of what a filter manager class could/should:
For n As Int32 = 0 To filters.Count - 1
Console.WriteLine("Name: {0} Count: {1}{2}Text:{3}", filters(n).Name,
filters(n).Elements.Count,
Environment.NewLine, filters(n).FilterText)
Next
Result:
Name: Default Count: 1
Text:Id = 7
Name: Blue Ones Count: 1
Text:Color = Blue
Name: Complex Count: 6
Text:[Name] like %Bob% OR [Name] like %Alice% AND Color = 'Blue' AND Active=True AND AccessRequired < 3 AND DateAdded > #2/11/2010# AND CreatedBy = 'ziggy'
If you use "Elements" as the ValueMember you will get back the collection.
The combo displays the Name for the user. On the right, a label displays the ValueMember in this case, it is the FilterText or joined Elements. As I said, you could get back the actual collection as the SelectedValue instead, but that is available as part of SelectedItem.
If savable means beyond the life of the application instance, that is another question, but these are very easily serialized.
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()
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.
I have a TableAdapter for a table like:
ID_BRAND NAME_BRAND ... (Other columns)
(...) (...) (...)
I have a ComboBox where I need to add all NAME_BRANDs, but without losing the reference to their ID_BRAND, because there are some NAME_BRANDs identical, but with different ID_BRAND. Thhen when the user selects a name in the ComboBox, the correspondent ID_BRAND must be extracted.
Plus, I need to make a query in a method but problem is I am not sure what kind of data to return.
Function returnBrands() As ??
brands.Fill(db.brands) 'my brandsTableAdapter
Dim q = From pc In db.brands
Select pc.NAME_BRAND, pc.ID_BRAND
Order By NAME_BRAND
Return q
End Function
Visual studio says that q is a kind of
OrderedEnumerableRowCollection(Of <anonymous type: Key NAME_BRAND As String, Key ID_BRAND As String>)
But when I try to return this by the method, it returns an error.
I'm also worried that when the ComboBox correctly loads the NAME_BRANDs, how will I extract the corresponding ID_BRAND after the user will selects a NAME_BRAND?
Thanks for your attention.
Create a type that represents the table you're querying from. Something like this:
Public Class Brand
Public Property ID_BRAND As String
Public Property NAME_BRAND As String
End Class
In the select portion of your query, specify the Brand type and copy the appropriate values:
Dim q = From pc In db.brands
Select New Brand WITH { .ID_BRAND = pc.ID_BRAND, .NAME_BRAND = pc.NAME_BRAND }
Order By NAME_BRAND
Now your function signature looks something like this:
Public Function returnBrands() As IQueryable<Brand>
You can specify which value to show using DisplayMember. You can also keep a value to associate with what you display using ValueMember
.NET has a KeyValuePair(Of TKey,TValue) that is handy for keep a related key and value.
You can use a Dictionary(Of TKey,TValue) to store your ID_BRAND and NAME_BRAND pairs. You can have duplicate NAME_BRANDs but a Dictionary requires the key to be Unique.
For your needs, you can return a Dictionary(Of String, String) from your method and bind it to your ComboBox:
Function returnBrands() As Dictionary(Of String, String)
brands.Fill(db.brands) 'my brandsTableAdapter
Dim q = From pc In db.brands
Select pc.NAME_BRAND, pc.ID_BRAND
Order By NAME_BRAND
Return q.ToDictionary(Of String, String)(Function(x) x.ID_BRAND, Function(x) x.NAME_BRAND)
End Function
Now that you have your dictionary, you can set it as the DataSource of your ComboBox and set the ValueMember and DisplayMember accordingly:
ComboBox1.DataSource = returnBrands()
ComboBox1.DataSource.DisplayMember = "Key"
ComboBox1.DataSource.ValueMember = "Value"
You could also return an object from bind directly to your anonymous type.
Function returnBrands() As Object
brands.Fill(db.brands) 'my brandsTableAdapter
Dim q = From pc In db.brands
Select pc.NAME_BRAND, pc.ID_BRAND
Order By NAME_BRAND
Return q.ToArray()
End Function
ComboBox1.DataSource = returnBrands()
ComboBox1.DisplayMember = "NAME_BRAND"
ComboBox1.ValueMember = "ID_BRAND"
However, the latter approach isn't considered best practice.
I have a ComboBox that is populated with objects of type ProfileName
Private Class ProfileName
Public Property Name As String
Public Property File As String
Public Property ProductVersion As String
End Class
These items are created added to the combo box after a deserialising a bunch of files and copying some of the values from the resulting objects:
pcb.DisplayMember = "Name"
For Each F As FileInfo In ProfileFiles
Dim Reader As StreamReader = F.OpenText()
Dim Serialize As Serializer = New Serializer()
Dim SerializedData As String = Reader.ReadToEnd()
Dim P As Profile = Serialize.DesearializeObject(Of Profile)(SerializedData)
If P.Type = Profile.ProfileType.Product Then
Dim PN As ProfileName = New ProfileName()
PN.File = F.Name
PN.ProductVersion = P.ProductVersion
PN.Name = P.ProductName & " - " & P.ProductVersion
pcb.Items.Add(PN)
End If
Reader.Close()
Next
Then if a user opens one of these files, the file will be again deserialised resulting in a Profile object with a 'ProductName' property that should match one of the items already on the ComboBox items list, so I'd like for the ComboBox to show that as the selected item.
i.e.
-On form load the ComboBox is populated with all possible product names.
-When a profile file is opened the product that the profile uses is automatically selected in the ComboBox.
I've been playing with
ProductComboBox.DataBindings.Add("SelectedValue", CurrentProfile, "ProductName")
and permutations thereof, but can't seem to get it right.
You cant mix and match - put objects into the items collection and use the data binding methods/elements. Databinding basics:
Public Class Profile
Public Property Name As String
Public Property File As String
Public Property ProductVersion As String
Public Overrides Function ToString() As String
Return String.Format("{0} ({1})", Name, ProductVersion)
End Function
End Class
The ToString() controls what will be displayed when you cant specify the property to display. Note, these should be properties becaus mere Fields will be treated differently.
Then a container for them. This will be the DataSource for the cbo.
Private Profiles As List(Of Profile)
...
' create instance of list and populate from where ever:
Profiles = New List(Of Profile)
Profiles.Add(New Profile With {.Name = "Default", .File = "foo",
.ProductVersion = "1.0"})
Profiles.Add(New Profile With {.Name = "Ziggy", .File = "bat",
.ProductVersion = "1.9.8"})
Profiles.Add(New Profile With {.Name = "Zoey", .File = "bar",
.ProductVersion = "1.4.1"})
Rather than putting the Profile objects into the Items collection, bind the control to the List:
cboP.DataSource = Profiles
cboP.DisplayMember = "Name"
If you omit the property to display, ToString() will be shown (or WindowsApp1.Profile if you did not override it). Note: When using a DataSource you no longer add or delete from the control's Items collection - it will yell at you. Instead manage the underlying source, your List(Of Profile) in this case.
To change the selection, for example to the one for "Ziggy":
Dim n As Int32 = Profiles.FindIndex(Function(f) f.Name = "Ziggy")
If n > -1 Then
cboP.SelectedIndex = n
End If
You can also set SelectedItem after you find the Profile instead, but I tend to use index. Even though the list is a new actor, serializing the entire thing is easy:
' serializing the List acts on all the profiles in it
Dim json = JsonConvert.SerializeObject(Profiles)
File.WriteAllText("C:\Temp\Profiles.json", json)
Read it back:
json = File.ReadAllText("C:\Temp\Profiles.json")
Dim newPs = JsonConvert.DeserializeObject(Of List(Of Profile))(json)
Its a bit simpler than looping thru a set of files. List(of T) has a full set of methods and extensions to remove, sort, find etc so you should gain functionality over the items collection or array.
Alternatively, you could keep the one file per structure, but add the deserialized Profile objects to a List(of Profile) rather than the Items collection.
I have a LongListSelector in an .xaml and I am able to fill it by binding to a an ItemSource when the source is filled by a DataContext using a single table from my SQL Server CE database like this:
Dim row = (From rows In db.Hub
Order By rows.HubID Descending
Select rows).ToList()
Me.MainLongListSelector.ItemsSource = row
I am thus able to get the ID of the selected item as follows:
HubID = CType(MainLongListSelector.SelectedItem, Hub).HubID
I am also able to bind to a 'query' DataSource as follows:
Dim row = (From ac In db.Activity
Join at In db.ActivityType On ac.ActivityTypeID Equals at.ActivityTypeID
Select New With {.ID = ac.ActivityID,
.Title = ac.Activity1}).ToList()
Me.MainLongListSelector.ItemsSource = row
however, since this is not referring to a specific table in the DataContext, I cannot get the ID using the above code, ie:
Dim ActID = CType(MainLongListSelector.SelectedItem, Activity).ActivityID '- returns nothing
How should I get the value(s) of selectedItem in this case?
NB: I have created the anonymous fields (.ID and .Title) because those are the names I have bound in the xaml, so the LongListSelected gets populated without writing extra code.
Thanks
Phew!!
I discovered that two things:
this HubID = CType(MainLongListSelector.SelectedItem, Hub).HubID is calling a List (Of DataContext), while in the second scenario above I am using a List (Of Anonymous). So I searched for List (Of Anonymous) and this came up!
I now know I can create a class for List (Of Anonymous) and properly name its properties, thus make it available outside its methods, like in my 'query' question above.
So the answer is I created the class for my anonymous list, declared its properties
Public Class AnonList
Private _id As Integer
Public Property ID() As Integer
Get
Return _id
End Get
Set(ByVal value As Integer)
_id = value
End Set
End Property
Private _title As String
Public Property Title() As String
Get
Return _title
End Get
Set(ByVal value As String)
_title = value
End Set
End Property
Private _desc As String
Public Property Desc() As String
Get
Return _desc
End Get
Set(ByVal value As String)
_desc = value
End Set
End Property
End Class
and therefore assigned them to the ItemSource values,
Select New AnonList With {.ID = ac.ActivityID,
thus being able to get the SelectedItem values as required:
ActivityID = CType(MainLongListSelector.SelectedItem, AnonList).ID
Took a bit of determination to figure that out!