How to get Lambda in LINQ to actually filter for dynamic linq - vb.net

Example-I have a person class
Public Class Person
Private _fname As String
Public Property Fname() As String
Get
Return _fname
End Get
Set(ByVal value As String)
_fname = value
End Set
End Property
Private _lname As String
Public Property Lname() As String
Get
Return _lname
End Get
Set(ByVal value As String)
_lname = value
End Set
End Property
Private _age As Integer
Public Property Age() As Integer
Get
Return _age
End Get
Set(ByVal value As Integer)
_age = value
End Set
End Property
End Class
Dim people As New List(Of Person)
people.Add(New Person With {.Fname = "Alice", .Lname = "Apples", .Age = 1})
people.Add(New Person With {.Fname = "Bob", .Lname = "Banana", .Age = 2})
people.Add(New Person With {.Fname = "Charlie", .Lname = "Cherry", .Age = 3})
people.Add(New Person With {.Fname = "Dave", .Lname = "Durian", .Age = 4})
people.Add(New Person With {.Fname = "Eric", .Lname = "EggPlant", .Age = 10})
Dim filteredPerson = From person In people
filteredPerson.Where(Function(fp) fp.Fname = "Bob")
Dim finalList = filteredPerson.ToList
For Each p In finalList
Debug.Print("FNAME: " + p.Fname)
Next
This still returns all 5 people, like the where is not being applied, what am I doing wrong?
I would also like to be able to pass a list of names and return only those
Dim searchList As New List(Of String)
searchList.Add("Bob")
searchList.Add("Dave")
Dim filteredPerson = From person In people
For Each s In searchList
Dim innerName As String = s
filteredPerson.Where(Function(fp) fp.Fname = innerName)
Next
Dim finalList = filteredPerson.ToList
For Each p In finalList
Debug.Print("FNAME: " + p.Fname)
Next

The problem is that Where doesn't change the collection. It returns the newly filtered collection.
Try this:
Dim filteredPerson = people.Where(Function(fp) fp.Fname = "Bob")
(By the way, I don't see anything dynamic in here... where are you using dynamic LINQ?)
To add multiple Where clauses, you'll want something like this:
Dim searchList As New List(Of String)
searchList.Add("Bob")
searchList.Add("Dave")
Dim filteredPerson As IEnumerable(Of String) = people
For Each s In searchList
Dim innerName As String = s
filteredPerson = filteredPerson.Where(Function(fp) fp.Fname = innerName)
Next
Dim finalList = filteredPerson.ToList
For Each p In finalList
Debug.Print("FNAME: " + p.Fname)
Next
However, I don't believe that's actually what you want to do. Each Where clause is going to insist that Fname is the specified name - and it's not going to be both Bob and Dave! I think you actually want something which can be expressed much more simply:
Dim searchList As New List(Of String)
searchList.Add("Bob")
searchList.Add("Dave")
Dim filteredPerson = people.Where(Function(fp) searchList.Contains(fp.Fname))
Dim finalList = filteredPerson.ToList
For Each p In finalList
Debug.Print("FNAME: " + p.Fname)
Next
All we want to know is whether Fname is in searchList, which is what Contains does.

Dim people As New List(Of Person)
people.Add(New Person With {.Fname = "Alice", .Lname = "Apples", .Age = 1})
people.Add(New Person With {.Fname = "Bob", .Lname = "Banana", .Age = 2})
people.Add(New Person With {.Fname = "Charlie", .Lname = "Cherry", .Age = 3})
people.Add(New Person With {.Fname = "Dave", .Lname = "Durian", .Age = 4})
people.Add(New Person With {.Fname = "Eric", .Lname = "EggPlant", .Age = 10})
Dim searchList As New List(Of String)
searchList.Add("Bob")
searchList.Add("Dave")
dim filteredItems = from p in people _
join i in searchList on p.FName equals i _
select p
dim personFound as Person
for each personFound in filteredItems
Console.WriteLine(personFound.Lname)
next

Related

vb,net my collection property in my class is throwing an error

I am learning about collections, I have a Person class
Imports System
Imports System.Collections.Generic
Imports System.Text
Public Class Person
Public Sub New()
End Sub
Public Sub New(ByVal id As Integer, ByVal first_name As String, ByVal mid_name As String, ByVal last_name As String, ByVal age As Short, ByVal sex As Char)
Me.p_id = id
Me.first_name = first_name
Me.mid_name = mid_name
Me.last_name = last_name
Me.p_age = age
Me.p_sex = sex
End Sub
Private p_id As Integer = -1
Private first_name As String = String.Empty
Private mid_name As String = String.Empty
Private last_name As String = String.Empty
Private p_age As Short = 0
Private p_sex As Nullable(Of Char) = Nothing
Public Property ID() As Integer
Get
Return p_id
End Get
Set(ByVal value As Integer)
p_id = value
End Set
End Property
Public Property FirstName() As String
Get
Return first_name
End Get
Set(ByVal value As String)
first_name = value
End Set
End Property
Public Property MiddleName() As String
Get
Return mid_name
End Get
Set(ByVal value As String)
mid_name = value
End Set
End Property
Public Property LastName() As String
Get
Return last_name
End Get
Set(ByVal value As String)
last_name = value
End Set
End Property
Public Property Age() As Short
Get
Return p_age
End Get
Set(ByVal value As Short)
p_age = value
End Set
End Property
Public Property Sex() As Nullable(Of Char)
Get
Return p_sex
End Get
Set(ByVal value As Nullable(Of Char))
p_sex = value
End Set
End Property
End Class
and an Employee Class where I define a Person property
Imports System
Imports System.Collections.Generic
Imports System.Text
Public Class Employee
Public Sub New()
End Sub
Public Sub New(ByVal id As Integer, ByVal companyName As String, ByVal office As String, colPerson As Person)
'Me._employeeid = id
'Me._companyName = companyName
'Me._office = office
End Sub
Private _employeeid As Integer = -1
Private _companyName As String = String.Empty
Private _office As String = String.Empty
Public Property Empoyee_ID() As Integer
Get
Return _employeeid
End Get
Set(ByVal value As Integer)
_employeeid = value
End Set
End Property
Public Property CompanyName() As String
Get
Return _companyName
End Get
Set(ByVal value As String)
_companyName = value
End Set
End Property
Public Property Office() As String
Get
Return _office
End Get
Set(ByVal value As String)
_office = value
End Set
End Property
Property colPerson As List(Of Person)
End Class
How can i populate the persons class as well
Sub Main()
Dim pList As List(Of Person) = New List(Of Person)()
Dim thePerson As New List(Of Person) From
{
New Person With {.Age = 29, .FirstName = "John", .LastName = "Shields", .MiddleName = "", .Sex = "M", .ID = 1},
New Person With {.Age = 34, .FirstName = "Mary", .LastName = "Matthew", .MiddleName = "L", .Sex = "F", .ID = 2},
New Person With {.Age = 55, .FirstName = "Amber", .LastName = "Carl", .MiddleName = "P", .Sex = "M", .ID = 3},
New Person With {.Age = 12, .FirstName = "Kathy", .LastName = "Berry", .MiddleName = "O", .Sex = "F", .ID = 4}
}
'pList.Add(New Person(1, "John", "", "Shields", 29, "M"c))
'pList.Add(New Person(2, "Mary", "Matthew", "Jacobs", 35, "F"c))
'pList.Add(New Person(3, "Amber", "Carl", "Agar", 25, "M"c))
'pList.Add(New Person(4, "Kathy", "", "Berry", 21, "F"c))
'pList.Add(New Person(5, "Lena", "Ashco", "Bilton", 33, "F"c))
'pList.Add(New Person(6, "Susanne", "", "Buck", 45, "F"c))
'pList.Add(New Person(7, "Jim", "", "Brown", 38, "M"c))
'pList.Add(New Person(8, "Jane", "G", "Hooks", 32, "F"c))
'pList.Add(New Person(9, "Robert", "", "", 31, "M"c))
'pList.Add(New Person(10, "Cindy", "Preston", "Fox", 25, "F"c))
'pList.Add(New Person(11, "Gina", "", "Austin", 27, "F"c))
'pList.Add(New Person(12, "Joel", "David", "Benson", 33, "M"c))
'pList.Add(New Person(13, "George", "R", "Douglas", 55, "M"c))
'pList.Add(New Person(14, "Richard", "", "Banks", 22, "M"c))
'pList.Add(New Person(15, "Mary", "C", "Shaw", 39, "F"c))
'
'loop through the list
' PrintOnConsole(pList, "1. --- Looping through all items in the List<T> ---")
'
'Filtering List(T) using a single condition - (Age > 35)
'Dim filterOne As List(Of Person) = pList.FindAll(Function(p As Person) p.Age > 35)
'PrintOnConsole(filterOne, "2. --- Filtering List<T> on single condition (Age > 35) ---")
''
'' Filtering List(T) on multiple conditions (Age > 35 and Sex is Female)
'Dim filterMultiple As List(Of Person) = pList.FindAll(Function(p As Person) p.Age > 35 AndAlso p.Sex = "F"c)
'PrintOnConsole(filterMultiple, "3. --- Filtering List<T> on multiple conditions (Age > 35 and Sex is Female) ---")
''
''Sorting List(T) (Sort on FirstName)
'Dim sortFName As List(Of Person) = pList
'sortFName.Sort(Function(p1 As Person, p2 As Person) p1.FirstName.CompareTo(p2.FirstName))
'PrintOnConsole(sortFName, "4. --- Sort List<T> (Sort on FirstName) ---")
'
'Sorting List(T) descending (Sort on LastName descending)
'Dim sortLNameDesc As List(Of Person) = pList
'sortLNameDesc.Sort(Function(p1 As Person, p2 As Person) p2.LastName.CompareTo(p1.LastName))
'PrintOnConsole(sortLNameDesc, "5. --- Sort List<T> descending (Sort on LastName descending) ---")
''Add new List(T) to existing List(T)
'Dim newList As List(Of Person) = New List(Of Person)()
'newList.Add(New Person(16, "Geoff", "", "Fisher", 29, "M"c))
'newList.Add(New Person(17, "Samantha", "Carl", "Baxer", 32, "F"c))
'pList.AddRange(newList)
'PrintOnConsole(pList, "6. --- Add new List<T> to existing List<> ---")
''Remove multiple items from List(T) based on condition (remove male employees)
'Dim removeList As List(Of Person) = pList
'removeList.RemoveAll(Function(p As Person) p.Sex = "M"c)
'PrintOnConsole(removeList, "7. --- Remove multiple items from List<> based on condition ---")
'' Create Read Only List(T)
'Console.WriteLine("Create Read Only List<>")
'Dim personReadOnly As IList(Of Person) = pList
'Console.WriteLine("Before - Is List Read Only? True or False : " & personReadOnly.IsReadOnly)
'personReadOnly = pList.AsReadOnly()
'Console.WriteLine("After - Is List Read Only? True or False : " & personReadOnly.IsReadOnly & "</br>")
'
'Dim pList1 As New Person
Dim emp As New List(Of Employee)
Dim r As New Employee
r.CompanyName = "zac"
r.Office = "home"
r.Empoyee_ID = 1
'Dim pList1 = New Person(1, "John", "", "Shields", 29, "M"c)
r.colPerson.Add(New Person(1, "John", "", "Shields", 29, "M"c))---> Gives error
emp.Add(r)
'
Dim i As New Employee
i.CompanyName = "zac1"
i.Office = "home1"
i.Empoyee_ID = 2
i.colPerson.Add(New Person(3, "Amber", "Carl", "Agar", 25, "M"c))
pList.Add(New Person(4, "Kathy", "", "Berry", 21, "F"c))
emp.Add(i)
'
Dim t As New Employee
t.CompanyName = "zac2"
t.Office = "home2"
t.Empoyee_ID = 2
pList.Add(New Person(5, "Lena", "Ashco", "Bilton", 33, "F"c))
pList.Add(New Person(6, "Susanne", "", "Buck", 45, "F"c))
emp.Add(t)
For Each item In emp
'item.CompanyName = "zac"
'item.Office = "home"
'item.colperson.Where(Function(x) x.ID = 17)
'Console.WriteLine("employee with person collection: " & item.CompanyName & " " & item.Office & " " & item.colperson.Where(Function(x) x.ID = 17).ToString & "</br>")
Console.WriteLine("employee with person collection: " & item.CompanyName & " " & item.Office & "</br>")
Next
End Sub
r.colPerson.Add(New Person(1, "John", "", "Shields", 29, "M"c))---> Gives error
It gives an error because even though colPerson is declared to be capable of holing a list of people, it hasn't actually been set to be a list of people, so it's currently Nothing, and you can't call methods on something that is Nothing
Property colPerson As New List(Of Person)
^^^
Add a New directive to ensure it's declared and initialized to an instance of a List
Also, please:
Don't put "col" in names
Use a plural name for List(Of Thing) - this is a list of person so it should at least be called People, but also perhaps state what kind of people they are. For example if this Employee was recommended by a few people, call it RcommendedByPeople
Only use Collection in a name if you're writing a class that is a collection, such as Microsoft did when they wrote MatchCollection - a collection of regular expression Matches
Be definite about whether the property is public, private etc
i.e.
Public Property RcommendedByPeople As New List(Of Person)

object Collection Containing a List of IDs Linq

I have a list box and the user is able to multi-select. I want to use Linq and bring back the records of the selected IDs that the user selects. I need to bring back the full object record for each selected ID
Here is the contact object along with collection object
Namespace MODEL
<System.Serializable()> _
Public Class ContactCollection
Inherits System.Collections.ObjectModel.Collection(Of Contact)
Implements IList(Of Contact)
End Class
End Namespace
Namespace MODEL
<System.Serializable()> _
Public Class Contact
Private mContactID As Int32 = 0
Private mFirstName As String
Private mLastName As String
Public Property ContactID As Int32
Get
Return mContactID
End Get
Set(value As Int32)
mContactID = value
End Set
End Property
Public Property FirstName As String
Get
Return mFirstName
End Get
Set(value As String)
mFirstName = value
End Set
End Property
Public Property LastName As String
Get
Return mLastName
End Get
Set(value As String)
mLastName = value
End Set
End Property
End Class
End Namespace
Adding 5 Records to the collection object
Dim objCollection As New MODEL.ContactCollection
Dim obj As New MODEL.Contact
objCollection.Add(New MODEL.Contact With {
.ContactID = 1, _
.FirstName = "John", _
.LastName = "Smtih" _
})
objCollection.Add(New MODEL.Contact With {
.ContactID = 2, _
.FirstName = "Mark", _
.LastName = "Davis" _
})
objCollection.Add(New MODEL.Contact With {
.ContactID = 3, _
.FirstName = "Tom", _
.LastName = "Howe" _
})
objCollection.Add(New MODEL.Contact With {
.ContactID = 4, _
.FirstName = "Jerry", _
.LastName = "Thomas" _
})
objCollection.Add(New MODEL.Contact With {
.ContactID = 5, _
.FirstName = "Jane", _
.LastName = "Marry" _
})
This is the selected contact List from the list box
Dim lstContacts As New List(Of Integer)
lstContacts.Add(2)
lstContacts.Add(4)
I am not sure what to do at this point with Linq to find the values. I think I have to use contains but I have tried may different ways but I was unable to get the values.
I have tried this Linq but does not work or bring any records back
Dim objSearch from SearchContacts in objCollection
Where (lstContacts.Contains(SearchContacts.ContactID))
To get the Ids, try that :
Dim ids As IEnumerable(Of Int32) = myListBox.SelectedItems _
.OfType(Of Contact)() _
.Select( Function(c) c.ContactID ) _
Edit
If you want the Contacts, you can just just :
Dim ids As IEnumerable(Of Contact) = myListBox.SelectedItems _
.OfType(Of Contact)()
And if you want the contacts in a separate copied collection, you can :
Dim ids As List(Of Contact) = myListBox.SelectedItems _
.OfType(Of Contact)() _
.ToList()
Last (if think this is your real question - just tell and I erase everything above)
Dim selectedContacts As IEnumerable(Of MODEL.Contact) = From contact In objCollection
Join id In lstContacts
On contact.ContactID Equals id
Select contact

VB.Net : How do you bind a dictionary collection to a Combobox?

I am trying to bind this dictionary collection to a combobox but the display
is not correct. The displayMember should be the ProvName and the ValueMember should be the key.
Private Sub Button8_Click(sender As Object, e As EventArgs) Handles Button8.Click
Dim Country1 As Dictionary(Of String, Province)
Country1 = Module1.CreateCountry
'Display results in combox
ComboBox3.DataSource = New BindingSource(Country1, Nothing)
ComboBox2.DisplayMember = "Value"
ComboBox2.ValueMember = "Key"
End Sub
Module Module1
Public provinces As CollectionBase
Function CreateCountry() As Dictionary(Of String, Province)
Dim Country As New Dictionary(Of String, Province)
Dim Prov As Province
Prov = New Province
With Prov
.Abbrv = "Qc"
.ProvName = "Quebec"
.Population = "7 500 000"
.Region = "East"
End With
Country.Add(Prov.Abbrv, Prov)
Prov = New Province
With Prov
.Abbrv = "BC"
.ProvName = "British Columbia"
.Population = "4 500 000"
.Region = "West"
End With
Country.Add(Prov.Abbrv, Prov)
Prov = New Province
With Prov
.Abbrv = "NS"
.ProvName = "Nova Scotia"
.Population = "2 000 000"
.Region = "Maritimes"
End With
Country.Add(Prov.Abbrv, Prov)
Prov = New Province
With Prov
.Abbrv = "AB"
.ProvName = "Alberta"
.Population = "5 500 000"
.Region = "Prairies"
End With
Country.Add(Prov.Abbrv, Prov)
Return Country
End Function
End Module
Public Class Province
Public Property Abbrv As String
Public Property ProvName As String
Public Property Population As String
Public Property Region As String
Public Overrides Function ToString() As String
Return ProvName
End Function
End Class
Here is the sample source code:
'Declare and Fill a generic Dictionary
Dim dictionary As New Dictionary(Of String, Integer)
dictionary.Add("one", 1)
dictionary.Add("two", 2)
dictionary.Add("three", 3)
dictionary.Add("four", 4)
dictionary.Add("five", 5)
dictionary.Add("six", 6)
dictionary.Add("seven", 7)
dictionary.Add("eight", 8)
'Initialize DisplayMember and ValueMember of an existing combobox to be filled with dictionary values
cboCombo.DisplayMember = "Key"
cboCombo.ValueMember = "Value"
'Bind the combobox to dictionary
cboCombo.DataSource = New BindingSource(dictionary, Nothing)
'Now I can assign the selected value of combobox with this simple command:
cboCombo.SelectedValue = 4
'I can also retrive the selected value with:
value = cboCombo.SelectedValue
If this help you mark as answer
This is just an example of using a dictionary to bind data to a combobox.
Declare dictionary object and initialize it with values as follows:
Dim dcItems As New Dictionary(Of String, Integer)
lstTPAType.Add("Select", -1)
lstTPAType.Add("Item 1", 0)
lstTPAType.Add("Item 2", 1)
lstTPAType.Add("Item ", 2)
cmbMyCombo.DataSource = New BindingSource(dcItems, Nothing)
cmbMyCombo.ValueMember = "Value"
cmbMyCombo.DisplayMember = "Key"
Hope this generic solution will help developers looking for it.

Dictionary (of integer,class) type

I would like to save variables in a simple method before adding to a datatable. Is it possible to have a dictionary (of integer,class) type like the code below? I can then loop through the keys to get the variables.Any ideas please?
Public Class CONTACTS
Dim FIRST_NAME As String
Dim SURNAME As String
Dim DOB As Date
Dim AGE As Integer
End Class
Sub TEST_DICT()
Dim dctCONTACTS As Dictionary(Of Integer, List OF Class(CONTACTS)) 'not sure how to declare this
dctCONTACTS.Add(1, "MARK").FIRST_NAME 'not sure how to savevariable
dctCONTACTS.Add(1, "SMITH").SURNAME
dctCONTACTS.Add(1, "5 May 1995").DOB
dctCONTACTS.Add(1, 31).AGE
dctCONTACTS.Add(2, "ANNE").FIRST_NAME
dctCONTACTS.Add(2, "MOORE").SURNAME
dctCONTACTS.Add(3, "5 April 1990").DOB
dctCONTACTS.Add(4, 26).AGE
Dim X = dctCONTACTS(1).AGE
End Sub
You really you go look at the many many "how to code VB.NET" resources online rather than ask basic syntax questions here.
Here is your code in valid VB.NET:
Public Class CONTACTS
Public FIRST_NAME As String
Public SURNAME As String
Public DOB As Date
Public AGE As Integer
End Class
Sub TEST_DICT()
Dim dctCONTACTS As New Dictionary(Of Integer, List(Of CONTACTS))
dctCONTACTS.Add(1, New List(Of CONTACTS) From
{
New CONTACTS() With { .FIRST_NAME = "MARK", .SURNAME = "SMITH", .DOB = New DateTime(1995, 5, 5), .AGE = 31 },
New CONTACTS() With { .FIRST_NAME = "JOE", .SURNAME = "SMITH", .DOB = New DateTime(1995, 5, 5), .AGE = 31 },
New CONTACTS() With { .FIRST_NAME = "SARAH", .SURNAME = "SMITH", .DOB = New DateTime(1995, 5, 5), .AGE = 31 }
})
dctCONTACTS.Add(2, New List(Of CONTACTS) From
{
New CONTACTS() With { .FIRST_NAME = "BOB", .SURNAME = "SMITH", .DOB = New DateTime(1995, 5, 5), .AGE = 31 },
New CONTACTS() With { .FIRST_NAME = "SUE", .SURNAME = "SMITH", .DOB = New DateTime(1995, 5, 5), .AGE = 31 },
New CONTACTS() With { .FIRST_NAME = "ABIGAIL", .SURNAME = "SMITH", .DOB = New DateTime(1995, 5, 5), .AGE = 31 }
})
Console.WriteLine(dctCONTACTS(2)(0).FIRST_NAME)
End Sub
Running TEST_DICT gives BOB in this case.

How do you assign different data sources for a DataGridViewComboBox for each record?

I have a winforms DataGridView that I wish to have a column containing comboboxes for each record. Each combobox will have completely different values for each row and need to be assigned a datasource during the databinding of the DataGridView. I can assign 1 datasource to all (that's easy), but having each combobox have different values looks impossible).
Here's what I'm working with - note: The datasource of the DataGridView is defined programmatically by setting a property from the calling form.
Public Class frmSendToQuickbooksPopup
Public Property CurrentOrder As OrderICT
Public Property lineitems As List(Of OrderLineItemICT)
Private Sub frmSendToQuickbooksPopup_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'define the combobox (datasource can't be assigned here as each will be different for each row)
Dim dgvcboMatch As New DataGridViewComboBoxColumn
dgvcboMatch.DisplayMember = "Name"
dgvcboMatch.ValueMember = "ListID"
dgvcboMatch.HeaderText = "Matches"
dgvcboMatch.Name = "Match"
dgvcboMatch.Width = 150
dgvLineItems.Columns.Add(dgvcboMatch)
Me.dgvLineItems.DataSource = lineitems
End Sub
Private Sub dgvLineItems_DataSourceChanged(sender As Object, e As EventArgs) Handles dgvLineItems.DataSourceChanged
Dim L As New QBI.OrderLineItemICT
With L
dgvLineItems.Columns(.col_LineItemBvin).Visible = False
dgvLineItems.Columns(.col_ProductId).Visible = False
dgvLineItems.Columns(.col_ProductShortDescription).Visible = False
dgvLineItems.Columns(.col_ShippingBoxCount).Visible = False
dgvLineItems.Columns(.col_CustomProperties).Visible = False
dgvLineItems.Columns(.col_ShippingLength).Visible = False
dgvLineItems.Columns(.col_ShippingWidth).Visible = False
dgvLineItems.Columns(.col_ShippingHeight).Visible = False
dgvLineItems.Columns(.col_ProductName).DisplayIndex = 2
dgvLineItems.Columns(.col_ProductName).HeaderText = "Product Name"
dgvLineItems.Columns(.col_ProductName).Width = 170
dgvLineItems.Columns(.col_ProductSku).HeaderText = "SKU"
dgvLineItems.Columns(.col_ProductSku).Width = 160
dgvLineItems.Columns(.col_Quantity).DefaultCellStyle.Format = "n0"
dgvLineItems.Columns(.col_Quantity).DisplayIndex = 7
dgvLineItems.Columns(.col_Quantity).DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter
dgvLineItems.Columns(.col_Quantity).Width = 65
dgvLineItems.Columns(.col_AdjustedPrice).DefaultCellStyle.Format = "c2"
dgvLineItems.Columns(.col_AdjustedPrice).HeaderText = "Adj Price"
dgvLineItems.Columns(.col_AdjustedPrice).DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight
dgvLineItems.Columns(.col_AdjustedPrice).Width = 80
dgvLineItems.Columns(.col_BasePrice).DefaultCellStyle.Format = "c2"
dgvLineItems.Columns(.col_BasePrice).HeaderText = "Base Price"
dgvLineItems.Columns(.col_BasePrice).DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight
dgvLineItems.Columns(.col_BasePrice).Width = 85
dgvLineItems.Columns(.col_Discounts).DefaultCellStyle.Format = "c2"
dgvLineItems.Columns(.col_Discounts).HeaderText = "Discounts"
dgvLineItems.Columns(.col_Discounts).DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight
dgvLineItems.Columns(.col_Discounts).Width = 80
dgvLineItems.Columns(.col_LineTotal).DefaultCellStyle.Format = "c2"
dgvLineItems.Columns(.col_LineTotal).HeaderText = "Line Total"
dgvLineItems.Columns(.col_LineTotal).DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight
dgvLineItems.Columns(.col_LineTotal).Width = 80
dgvLineItems.Columns(.col_UOM).DisplayIndex = 9
dgvLineItems.Columns(.col_UOM).DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter
dgvLineItems.Columns(.col_UOM).HeaderText = "Units"
dgvLineItems.Columns(.col_UOM).Width = 55
dgvLineItems.Columns(.col_ShippingWeight).DisplayIndex = 10
dgvLineItems.Columns(.col_ShippingWeight).HeaderText = "Unit Wt"
dgvLineItems.Columns(.col_ShippingWeight).DefaultCellStyle.Format = "N1"
dgvLineItems.Columns(.col_ShippingWeight).DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight
dgvLineItems.Columns(.col_ShippingWeight).Width = 70
End With
End Sub
Public Function GetComboboxData(ProductNameToMatch As String, ProductSKUToMatch As String) As List(Of WPM_Item)
'this function returns a list of returned matches for a given row's ProductName or SKU
'this function will be the datasource for a given combobox
Dim itms As New List(Of WPM_Item)
itms = WPM_Data.FindWPM_ItemMatch(ProductNameToMatch, ProductSKUToMatch)
Return itms
End Function
End Class
If there were some way to intercept the databinding of the DataGridView so I can get a value from another cell (to call another procedure), then use the return data to populate each row's combobox. This is easy w/webforms, but I don't see an event for the winforms version.
Another thing I've tried, but does not work (the combobox datasource just disappears!)
Private Sub dgvLineItems_DataBindingComplete(sender As Object, e As DataGridViewBindingCompleteEventArgs) Handles dgvLineItems.DataBindingComplete
For Each row As DataGridViewRow In Me.dgvLineItems.Rows
Dim dr = DirectCast(row.DataBoundItem, OrderLineItemICT)
If dr Is Nothing Then
Return
End If
Dim name As String = row.Cells(dr.col_ProductName).Value.ToString
Dim SKU As String = row.Cells(dr.col_ProductSku).Value.ToString
Dim cell As DataGridViewComboBoxCell = TryCast(row.Cells("Match"), DataGridViewComboBoxCell)
cell.DisplayStyle = DataGridViewComboBoxDisplayStyle.ComboBox
Dim wpm_items As List(Of WPM_Item) = GetComboboxData(name, SKU)
If cell.DataSource Is Nothing Then
cell.DataSource = wpm_items 'this forgets the datasource, why?
cell.DisplayMember = "Name"
cell.ValueMember = "ListID"
cell.Value = wpm_items.Item(0).Name 'this always throws an error "DataGridViewComboBoxCell value is not valid."
End If
Next
End Sub
Here's another version of that same assignment, this time the combobox is populated with the add method. (STILL does not remember the values)
I basically followed another forum's idea - maybe DotNet 4.5 DataGridView has a bug in it.
Private Sub dgvLineItems_DataBindingComplete(sender As Object, e As DataGridViewBindingCompleteEventArgs) Handles dgvLineItems.DataBindingComplete
For Each row As DataGridViewRow In Me.dgvLineItems.Rows
Dim dr = DirectCast(row.DataBoundItem, OrderLineItemICT)
If dr Is Nothing Then
Return
End If
Dim name As String = row.Cells(dr.col_ProductName).Value.ToString
Dim SKU As String = row.Cells(dr.col_ProductSku).Value.ToString
Dim cell As DataGridViewComboBoxCell = DirectCast(row.Cells("Match"), DataGridViewComboBoxCell)
cell.DisplayStyle = DataGridViewComboBoxDisplayStyle.ComboBox
Dim wpm_items As List(Of WPM_Item) = GetComboboxData(name, SKU)
'THIS DOES NOT WORK EITHER!!
cell.DisplayMember = "Name"
cell.ValueMember = "ListID"
For Each wItm As WPM_Item In wpm_items
Dim c As New comboboxitem
c.Text = wItm.Name
c.Value = wItm.ListID
cell.Items.Add(c)
Next
Next
End Sub
Public Class ComboboxItem
Public Property Text() As String
Public Property Value() As Object
Public Overrides Function ToString() As String
Return Text
End Function
End Class
Allrighty Then! Here's a working solution. What I discovered is that you must create your DataGridView programmatically (tedious, yes!) and turn off auto-generation of columns (absolute must) - this prevents multiple firings of dataGridView events, for one. It also prevents losing your datasource for your combobox. Using the DataBindingComplete event and parsing the rows within is the best way to do this (CellFormatting event is overkill and is called w/every mouse move, click, resize, etc).
For those of us working with objects instead of datatables, I hope this solution is usable.
The code:
Imports QBI
Imports QBI.QBI
Imports QBI.AppCore.Xutilities
Public Class TestDGV1
Public dgv1 As DataGridView
Private Sub TestDGV1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim ordlineitems As List(Of OrderLineItemICT)
ordlineitems = Order_DataICT.GetNewOrderLineItemsICT("26073bff-3a08-4bc2-8da9-79c75534bd6b")
dgv1 = Me.CreateDGV(ordlineitems) 'create the DataGridView and save it as "dgv1" (which must be publically accessible)
Me.SplitContainer1.Panel2.Controls.Add(dgv1) 'NOTE, the datagridview is inside a SplitContainer
End Sub
Public Function CreateDGV(dsItems As List(Of OrderLineItemICT)) As DataGridView
Dim dgv As New DataGridView()
dgv.Dock = DockStyle.Fill
dgv.DataSource = dsItems
dgv.EditMode = DataGridViewEditMode.EditOnEnter
dgv.AutoGenerateColumns = False
dgv.AllowUserToAddRows = False
Dim L As New QBI.OrderLineItemICT 'only using this reference for my column names defined elsewhere
Dim col0 As New DataGridViewComboBoxColumn With {.Name = "Match", .DataPropertyName = "NameMatch", .DisplayMember = "Name", .ValueMember = "ListID", .HeaderText = "Matches", .AutoComplete = True}
col0.DisplayIndex = 0
Dim col1 As New DataGridViewTextBoxColumn With {.Name = L.col_LineItemBvin, .DataPropertyName = L.col_LineItemBvin, .Visible = False, .ReadOnly = True, .HeaderText = L.col_LineItemBvin}
col1.DisplayIndex = 1
Dim col2 As New DataGridViewTextBoxColumn With {.Name = L.col_ProductId, .DataPropertyName = L.col_ProductId, .Visible = False, .ReadOnly = True, .HeaderText = L.col_ProductId}
col2.DisplayIndex = 2
Dim col3 As New DataGridViewTextBoxColumn With {.Name = L.col_ProductSku, .DataPropertyName = L.col_ProductSku, .Visible = True, .ReadOnly = True, .HeaderText = "SKU"}
col3.DisplayIndex = 3
col3.Width = 160
Dim col4 As New DataGridViewTextBoxColumn With {.Name = L.col_Quantity, .DataPropertyName = L.col_Quantity, .Visible = True, .ReadOnly = True, .HeaderText = "QTY"}
col4.DisplayIndex = 9
col4.Width = 65
col4.DefaultCellStyle.Format = "n0"
col4.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter
Dim col5 As New DataGridViewTextBoxColumn With {.Name = L.col_BasePrice, .DataPropertyName = L.col_BasePrice, .Visible = True, .ReadOnly = True, .HeaderText = "Base Price"}
col5.DisplayIndex = 6
col5.DefaultCellStyle.Format = "c2"
col5.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight
col5.Width = 85
Dim col6 As New DataGridViewTextBoxColumn With {.Name = L.col_Discounts, .DataPropertyName = L.col_Discounts, .Visible = True, .ReadOnly = True, .HeaderText = "Discounts"}
col6.DisplayIndex = 7
col6.DefaultCellStyle.Format = "c2"
col6.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight
col6.Width = 80
Dim col7 As New DataGridViewTextBoxColumn With {.Name = L.col_AdjustedPrice, .DataPropertyName = L.col_AdjustedPrice, .Visible = True, .ReadOnly = True, .HeaderText = "Adj Price"}
col7.DisplayIndex = 8
col7.DefaultCellStyle.Format = "c2"
col7.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight
col7.Width = 80
Dim col8 As New DataGridViewTextBoxColumn With {.Name = L.col_LineTotal, .DataPropertyName = L.col_LineTotal, .Visible = True, .ReadOnly = True, .HeaderText = "Line Total"}
col8.DisplayIndex = 11
col8.DefaultCellStyle.Format = "c2"
col8.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight
col8.Width = 80
Dim col9 As New DataGridViewTextBoxColumn With {.Name = L.col_ProductName, .DataPropertyName = L.col_ProductName, .Visible = True, .ReadOnly = True, .HeaderText = "Product Name"}
col9.DisplayIndex = 4
col9.Width = 170
Dim col10 As New DataGridViewTextBoxColumn With {.Name = L.col_ProductShortDescription, .DataPropertyName = L.col_ProductShortDescription, .Visible = False, .ReadOnly = True, .HeaderText = L.col_ProductShortDescription}
col10.DisplayIndex = 5
Dim col11 As New DataGridViewTextBoxColumn With {.Name = L.col_ShippingWeight, .DataPropertyName = L.col_ShippingWeight, .Visible = True, .ReadOnly = True, .HeaderText = "Unit Wt"}
col11.DisplayIndex = 12
col11.DefaultCellStyle.Format = "N1"
col11.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight
col11.Width = 70
Dim col12 As New DataGridViewTextBoxColumn With {.Name = L.col_ShippingWidth, .DataPropertyName = L.col_ShippingWidth, .Visible = False, .ReadOnly = True, .HeaderText = L.col_ShippingWidth}
col12.DisplayIndex = 13
Dim col13 As New DataGridViewTextBoxColumn With {.Name = L.col_ShippingHeight, .DataPropertyName = L.col_ShippingHeight, .Visible = False, .ReadOnly = True, .HeaderText = L.col_ShippingHeight}
col13.DisplayIndex = 14
Dim col14 As New DataGridViewTextBoxColumn With {.Name = L.col_ShippingBoxCount, .DataPropertyName = L.col_ShippingBoxCount, .Visible = False, .ReadOnly = True, .HeaderText = L.col_ShippingBoxCount}
col14.DisplayIndex = 15
Dim col15 As New DataGridViewTextBoxColumn With {.Name = L.col_CustomProperties, .DataPropertyName = L.col_CustomProperties, .Visible = False, .ReadOnly = True, .HeaderText = L.col_CustomProperties}
col15.DisplayIndex = 16
Dim col16 As New DataGridViewTextBoxColumn With {.Name = L.col_UOM, .DataPropertyName = L.col_UOM, .Visible = True, .ReadOnly = True, .HeaderText = "Units"}
col16.DisplayIndex = 10
col16.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter
col16.Width = 55
dgv.Columns.AddRange({col0, col1, col2, col3, col4, col5, col6, col7, col8, col9, col10, col11, col12, col13, col14, col15, col16})
AddHandler dgv.DataBindingComplete, AddressOf dgvLineItems_DataBindingComplete
Return dgv
End Function
Private Sub dgvLineItems_DataBindingComplete(sender As Object, e As DataGridViewBindingCompleteEventArgs)
For Each row As DataGridViewRow In Me.dgv1.Rows
Dim dr = DirectCast(row.DataBoundItem, OrderLineItemICT)
If dr Is Nothing Then
Return
End If
Dim name As String = row.Cells(dr.col_ProductName).Value.ToString
Dim SKU As String = row.Cells(dr.col_ProductSku).Value.ToString
Dim cell As DataGridViewComboBoxCell = DirectCast(row.Cells("Match"), DataGridViewComboBoxCell)
Dim wpm_items As List(Of WPM_Item) = GetComboboxData(name, SKU) 'function inside
If cell.DataSource Is Nothing Then
cell.DataSource = wpm_items
cell.DisplayMember = "Name"
cell.ValueMember = "ListID"
cell.Value = cell.Items(0).ListID
End If
Next
End Sub
Public Function GetComboboxData(ProductNameToMatch As String, ProductSKUToMatch As String) As List(Of WPM_Item)
'this function returns a list of returned matches for a given row's ProductName or SKU
'this function will be the datasource for a given combobox
Dim itms As New List(Of WPM_Item)
itms = WPM_Data.FindWPM_ItemMatch(ProductNameToMatch, ProductSKUToMatch)
Return itms
End Function
End Class
Here's an example in C# on how to have a DataGridViewComboBoxColumn in a DataGridView with different Types.
public static class ComboColumnDemo {
public static DataGridView CreateSampleDGV() {
DataTable table = new DataTable();
table.Columns.Add("VariableType", typeof(String));
table.Columns.Add("VariableId", typeof(int));
table.Columns.Add("Custom", typeof(Object));
table.Columns.Add("Default", typeof(Object));
table.Columns.Add("Date", typeof(DateTime));
table.Rows.Add(new Object[] { "int", 0, null, null, DateTime.MinValue });
table.Rows.Add(new Object[] { "decimal", 0, null, null, DateTime.MinValue });
table.Rows.Add(new Object[] { "enum", 0, null, null, DateTime.Now });
table.Rows.Add(new Object[] { "date", 0, null, null, DateTime.UtcNow });
table.Rows.Add(new Object[] { "person", 0, null, null, DateTime.Today });
table.Rows.Add(new Object[] { "String", 0, null, null, DateTime.MaxValue });
DataGridView dgv = new DataGridView();
dgv.Dock = DockStyle.Fill;
dgv.DataSource = table;
dgv.EditMode = DataGridViewEditMode.EditOnEnter;
dgv.AutoGenerateColumns = false;
dgv.AllowUserToAddRows = false;
DataGridViewComboBoxColumn colComboDefault = new DataGridViewComboBoxColumn { DataPropertyName = "Default", MaxDropDownItems = 20 };
//col2.DisplayStyle = DataGridViewComboBoxDisplayStyle.Nothing; //.DropDownButton;
colComboDefault.AutoComplete = true;
colComboDefault.ValueType = typeof(Object);
dgv.Columns.Add(new DataGridViewTextBoxColumn() { DataPropertyName = "VariableType" });
dgv.Columns.Add(new DataGridViewTextBoxColumn() { DataPropertyName = "VariableId" });
dgv.Columns.Add(colComboDefault);
dgv.Columns.Add(new CalendarColumn { DataPropertyName = "Date" });
foreach (DataGridViewColumn c in dgv.Columns)
c.HeaderText = c.DataPropertyName;
List<Object> strings = new List<Object>(new [] { "a", "b", "c" });
var ints = new List<Object>(new Object[] { DBNull.Value, 1, 2, 3 });
var decimals = new List<Object>(new Object[] { 1111111m, 2222222m, 3333333m });
List<Object> dates = new List<Object>(new Object[] { DBNull.Value, DateTime.Today });
List<Object> enums = new List<object>();
enums.Add(DBNull.Value);
enums.AddRange(Enum.GetValues(typeof(DayOfWeek)).Cast<Object>().ToArray());
DataTable enumsTable = Opulos.Core.Utils.EnumUtil<DayOfWeek>.ToDataTable();
List<Person> persons = new List<Person>();
dgv.CellFormatting += (sender, e) => {
var drv = (DataRowView) dgv.Rows[e.RowIndex].DataBoundItem;
if (drv == null)
return;
DataRow dr = drv.Row;
//String colName = dgv.Columns[col2].DataPropertyName;
String vt = (String) dr["VariableType"]; //table.Rows[row][0].ToString();
if (vt == "decimal" && e.ColumnIndex == 2) {
if (e.Value is decimal) {
e.Value = ((decimal) e.Value).ToString("n0");
e.FormattingApplied = true;
}
}
};
dgv.CellBeginEdit += (sender, e) => {
//dgv.EditingControlShowing += delegate {
int rowIndex = e.RowIndex;//dgv.CurrentCell.RowIndex;
int colIndex = e.ColumnIndex; //dgv.CurrentCell.ColumnIndex;
DataGridViewColumn col = dgv.Columns[colIndex];
try {
var drv = (DataRowView) dgv.Rows[rowIndex].DataBoundItem;
if (drv == null)
return;
DataRow dr = drv.Row;
//String colName = dgv.Columns[col2].DataPropertyName;
String vt = (String) dr["VariableType"]; //table.Rows[row][0].ToString();
//String vt = vm.VariableType;
Object items = null;
ComboBoxStyle? style = null;
Type valueType = null;
String formatString = null;
String displayMember = null;
String valueMember = null;
if (vt == "String") {
items = strings;
valueType = typeof(String);
}
else if (vt == "int") {
items = ints;
valueType = typeof(int);
}
else if (vt == "decimal") {
items = decimals;
formatString = "n0";
valueType = typeof(decimal);
}
else if (vt == "date") {
items = dates;
valueType = typeof(DateTime);
}
else if (vt == "enum") {
if (col == colComboDefault) {
items = enumsTable;
displayMember = "Display";
valueMember = "Value";
}
else
items = enums;
style = ComboBoxStyle.DropDownList;
valueType = typeof(DayOfWeek);
}
else if (vt == "person") {
items = persons;
valueType = typeof(Person);
}
if (col == colComboDefault) {
var cell2 = (DataGridViewComboBoxCell) dgv.Rows[rowIndex].Cells[colIndex];
cell2.DataSource = items;
cell2.ValueType = valueType;
cell2.DisplayMember = displayMember;
cell2.ValueMember = valueMember;
}
} catch (Exception ex) {
MessageBox.Show(dgv.FindForm(), ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
};
return dgv;
}
}
internal class Person {
public String name;
public override string ToString() {
return name;
}
public override bool Equals(object obj) {
if (obj is Person)
return String.Compare(name, ((Person) obj).name, false) == 0;
return false;
}
public override int GetHashCode() {
return name.GetHashCode();
}
}
Solution
Use the DataBindingComplete event handler to iterate through your rows and set the source for each DataGridViewComboBoxCell.
Example
Here's an example of how I did this. You'll need to add a DataGridView and a ComboBox (with two options, i.e. "option 1" and "option 2", for switching the DataGridView.DataSource) to your Form.
Public Class Form1
Public Property Groups As List(Of Group)
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Me.Groups = Me.InitializeGroups()
Dim cbc As New DataGridViewComboBoxColumn()
cbc.DisplayMember = "Name"
cbc.ValueMember = "ID"
cbc.HeaderText = "Matches"
cbc.Name = "Match"
cbc.Width = 150
Me.DataGridView1.Columns.Add(cbc)
Me.ComboBox1.SelectedIndex = 0
End Sub
Private Sub ComboBox1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles ComboBox1.SelectedIndexChanged
Dim examples As New List(Of Example)()
If Me.ComboBox1.SelectedIndex = 0 Then
examples.Add(New Example("Me", "blah"))
examples.Add(New Example("You", "blah"))
Else
examples.Add(New Example("Him", "blah"))
examples.Add(New Example("Her", "blah"))
examples.Add(New Example("It", "blah"))
End If
Me.DataGridView1.DataSource = examples
End Sub
Private Sub DataGridView1_DataBindingComplete(sender As Object, e As DataGridViewBindingCompleteEventArgs) Handles DataGridView1.DataBindingComplete
For Each row As DataGridViewRow In Me.DataGridView1.Rows
Dim cell As DataGridViewComboBoxCell = TryCast(row.Cells("Match"), DataGridViewComboBoxCell)
cell.DataSource = Me.Groups.Where(Function(g) g.Members.Contains(row.Cells("Foo").Value.ToString())).ToList()
cell.Value = cell.Items(0).ID ' Same property as combocell.ValueMember
Next
End Sub
Private Function InitializeGroups() As List(Of Group)
Dim groups = New List(Of Group)() From { _
New Group() With { _
.ID = 1, _
.Name = "All", _
.Members = New List(Of String)() From { _
"Me", _
"You", _
"Him", _
"Her", _
"It" _
} _
}, _
New Group() With { _
.ID = 2, _
.Name = "Them", _
.Members = New List(Of String)() From { _
"Him", _
"Her", _
"It" _
} _
}, _
New Group() With { _
.ID = 3, _
.Name = "Us", _
.Members = New List(Of String)() From { _
"Me", _
"You" _
} _
}, _
New Group() With { _
.ID = 4, _
.Name = "Me", _
.Members = New List(Of String)() From { _
"Me" _
} _
}, _
New Group() With { _
.ID = 5, _
.Name = "Human", _
.Members = New List(Of String)() From { _
"Me", _
"You", _
"Him", _
"Her" _
} _
}, _
New Group() With { _
.ID = 6, _
.Name = "Non-human", _
.Members = New List(Of String)() From { _
"It" _
} _
} _
}
Return groups
End Function
End Class
Public Class Example
Public Sub New(foo__1 As String, bar__2 As String)
Foo = foo__1
Bar = bar__2
End Sub
Public Property Foo() As String
Get
Return m_Foo
End Get
Set(value As String)
m_Foo = Value
End Set
End Property
Private m_Foo As String
Public Property Bar() As String
Get
Return m_Bar
End Get
Set(value As String)
m_Bar = Value
End Set
End Property
Private m_Bar As String
End Class
Public Class Group
Public Sub New()
Me.Members = New List(Of String)()
End Sub
Public Property ID() As Integer
Get
Return m_Id
End Get
Set(value As Integer)
m_Id = value
End Set
End Property
Private m_Id As String
Public Property Name() As String
Get
Return m_Name
End Get
Set(value As String)
m_Name = Value
End Set
End Property
Private m_Name As String
Public Property Members() As List(Of String)
Get
Return m_Members
End Get
Set(value As List(Of String))
m_Members = Value
End Set
End Property
Private m_Members As List(Of String)
End Class