Sum the counts with Lambda expressions - vb.net

I have a List(of T) where T has a property that is a list of checkboxes, what I need is a Lambda expression that will count all the checked checkboxes in the list.
I tried with:
Me.list.Sum(Function(objT) objT.CheckBoxes.Where(Function(chk) chk.Checked).Count)
But it didn't do the trick. Any suggestions?

Dim count = (From box In objtCheckBoxes Where box.Checked Select box).Count()

Somehow you must tell VB that T has a property CheckBoxes. Therefore define an interface that the types you add to the list must implement
Public Interface ICheckBoxes
ReadOnly Property CheckBoxes() As List(Of CheckBox)
End Interface
Public Class ClassWithCkeckBoxes
Implements ICheckBoxes
Private m_CheckBoxes As List(Of CheckBox) = New List(Of CheckBox)
Public ReadOnly Property CheckBoxes() As List(Of CheckBox) Implements ICheckBoxes.CheckBoxes
Get
Return m_CheckBoxes
End Get
End Property
End Class
Specify that T must implement ICheckBoxes with the type parameter (Of T As ICheckBoxes)
Class CountCheckBoxes(Of T As ICheckBoxes)
Public Sub Test(ByVal list As List(Of T))
Dim count As Integer = list _
.SelectMany(Function(t) t.CheckBoxes) _
.Count(Function(checkBox) checkBox.Checked)
End Sub
End Class
SelectMany flattens the list. I.e., it converts the list of list of checkboxes into a simple list of checkboxes.

You'll need to use SelectMany. Something like this may work.
Me.list.SelectMany(Function(objT) objT.Checkboxes)
.Count(Function(chk) chk.Checked)

At the end there was a problem populating the list which translated in an always 0 answer. Fixing the method that populated the list gave me the correct answer, which means the lambda expression per se was right all along.
P.S. sorry for all those other answers, I did read them all so it wasn't time wasted guys ;)

Dim b As New List(Of Boolean)
b.AddRange({True, False, True})
Dim n = b.Sum(Function(x)
Return If(x, 1, 0)
End Function)
n now contains 2.

Related

How to create a Boolean Function to list objects in a Generic Collection?

After creating a List(Of T), I want to create aBoolean function. First, we will ask data to add an object in the list. However, in case this new object has the same "DNI" (String attribute from the class Aspirante), then we cannot include this new Object in the list. Therefore, it should be True when we have an Object with the same attribute and False when we don´t, so we can add the new object.
Below is the code I did:
Public Class Oposicion
Private datos As New List(Of Aspirante)()
Public Function Alta(ByRef objAspirante As Aspirante) As Boolean
If datos.Contains(objAspirante.DNI) Then
Return True
Else
datos.Add(objAspirante)
Return False
End If
End Function
End Class
However it doesn´t work. I have no clue on how to do it. Sorry if I was not clear enough.
This doesn't answer your question directly but it involves a significant amount of code, so it won't work in a comment.
You probably shouldn't be using a List(Of T) in the first place. The HashSet(Of T) already includes functionality to prevent adding duplicate items, so that may be a better option. If you want to compare objects on a specific property value then you need to first create a comparer based on that:
Public Class Thing
Public Property Stuff As String
End Class
Public Class ThingComparer
Implements IEqualityComparer(Of Thing)
Public Overloads Function Equals(x As Thing, y As Thing) As Boolean Implements IEqualityComparer(Of Thing).Equals
Return x.Stuff.Equals(y.Stuff)
End Function
Public Overloads Function GetHashCode(obj As Thing) As Integer Implements IEqualityComparer(Of Thing).GetHashCode
Return obj.GetHashCode()
End Function
End Class
You then create a HashSet(Of T) that uses that comparer to determine equality:
Dim things As New HashSet(Of Thing)(New ThingComparer)
You can then just add items as you please by calling Add. That will either add the new item and return True or it will not not add the duplicate item and return False:
Dim variousStuff = {"One", "Two", "One"}
For Each stuff In variousStuff
Dim something As New Thing With {.Stuff = stuff}
If things.Add(something) Then
Console.WriteLine($"'{stuff}' was added successfully.")
Else
Console.WriteLine($"'{stuff}' is a duplicate and was not added.")
End If
Next
The potential drawback is that HasSet(Of T) does not implement IList(Of T), so you cannot access items by index. It does implement ICollection(OF T) though, so it does have a Count property and you can enumerate it with a For Each loop.
You can create a List(Of String) containing the just the DNI property of each object in datos. Then see if the DNI of objAspirante is contained in lst.
Public Class Oposicion
Private datos As New List(Of Aspirante)()
Public Function Alta(objAspirante As Aspirante) As Boolean
Dim lst As New List(Of String)
For Each a As Aspirante In datos
lst.Add(a.DNI)
Next
If lst.Contains(objAspirante.DNI) Then
Return True
Else
datos.Add(objAspirante)
Return False
End If
End Function
End Class
If you can change the type of datos, this might be easier.
Public Class Oposicion
Private datos As New Dictionary(Of String, Aspirante)()
Public Function Alta(objAspirante As Aspirante) As Boolean
If datos.ContainsKey(objAspirante.DNI) Then
Return True
Else
datos.Add(objAspirante.DNI, objAspirante)
Return False
End If
End Function
End Class
If you want to stick with your existing List(Of Aspirante), then simply use .Any() and pass it a Lambda to determine if one already exists. It'd look something like this:
Public Function Alta(ByVal objAspirante As Aspirante) As Boolean
If Not datos.Any(Function(x) x.DNI = objAspirante.DNI) Then
datos.Add(objAspirante)
Return True ' your description and code did not match for these!
End If
Return False ' your description and code did not match for these!
End Function
Note my comment on the Return lines...your code and description did not match here.

For a combobox with item = a datarow, how to point ValueMember to one of it's columns

I add items to a combobox like this:
For each R as DataRow in MyDataTable.Rows
If R("ID") > 10 then MyCombo.Items.Add(R)
Next
And now I need to set the DisplayMember and ValueMember to a column of the datarow:
MyCombo.ValueMember = R("ID")
MyCombo.DisplayMember = R("Name")
I know it doesn't make sence to to use "R" as it doesn't reference to anything at this point but it's just to make an indication of what I mean ;-)
The documentation for ValueMember says:
"A String representing a single property name of the DataSource property value, or a hierarchy of period-delimited property names that resolves to a property name of the final data-bound object"
I know I can add the rows to a new datatable and set it to the DataSource, but as you can add any object to the combobox items, it would be nice to use the rows directly, just can't figures out how to make a reference the particular column as a string.?
Maybe you cannot use a row object directly. I guess to use Valuemember you need your item objects to be wrapped in a collection which implement an ilist interface.
In the old MS-Access days combobox items had natively Display- and ValueMember properties, I've always missed that in the .Net combobox control.
My work-around is to use this class, which then can be used for all your ComboBoxes:
Class oComboItems
Public items As New List(Of oDVpairs)
Class oDVpairs
Implements IComparable(Of oDVpairs)
Private myDM As String
Private myVM As Object
Sub New(DM As String, VM As Object)
myDM = DM
myVM = VM
End Sub
Public ReadOnly Property DM() As String
Get
Return myDM
End Get
End Property
Public ReadOnly Property VM() As Object
Get
Return myVM
End Get
End Property
Public Function CompareTo(other As oDVpairs) As Integer Implements IComparable(Of oDVpairs).CompareTo
Return Me.myDM.CompareTo(other.myDM)
End Function
End Class
Public Sub AddItems(DisplayMember As String, ValueMemeber As Object)
items.Add(New oDVpairs(DisplayMember, ValueMemeber))
End Sub
Public ReadOnly Property DisplayMember() As String
Get
Return "DM"
End Get
End Property
Public ReadOnly Property ValueMember() As Object
Get
Return "VM"
End Get
End Property
End Class
And now add my datarows(or any other objects) to the ComboBox:
Dim CI As New oComboItems
For Each R As DataRow In DT_U.Rows
If R("medlnr") > 10 Then
CI.AddItems(R("name"), R("ID"))
end if
Next
CI.items.Sort()
MyCombo.DataSource = CI.Items
MyCombo.DisplayMember = CI.DisplayMember
MyCombo.ValueMember = CI.ValueMember

Windows forms CheckedListBox issue

I am working on a desktop application developed in vb.net. I am trying to select the items in a checkedlistbox depending on the values I get from database. Below is the code to populate the checkedlistboxes
Private Sub LoadDisapprovalList()
cblFedralReasons.Items.Clear()
cblStateReasons.Items.Clear()
cblFedralReasons.DataSource = Main.DataClient.DisapprovalReasonList_Get(FedralReason)
cblFedralReasons.DisplayMember = "DisapprovalReasonTypeDesc"
cblFedralReasons.ValueMember = "DisapprovalReasonTypeGenId"
cblStateReasons.DataSource = Main.DataClient.DisapprovalReasonList_Get(StateReason)
cblStateReasons.DisplayMember = "DisapprovalReasonTypeDesc"
cblStateReasons.ValueMember = "DisapprovalReasonTypeGenId"
End Sub
After that I am trying to select the items based on the values from database. Here is the code
Private Sub LoadApplicationDisapprovalReasons()
Dim lstApplicationDisapprovalReasons As New List(Of DataService.usp_ApplicationDisapprovalReason_Get_Result)
lstApplicationDisapprovalReasons = Main.DataClient.ApplicationDisapprovalReason_Get(_SeqID)
If lstApplicationDisapprovalReasons.Count > 0 Then
For Each item In lstApplicationDisapprovalReasons
Dim selectedDisapprovalId As Integer = item.DisapprovalReasonTypeGenId
Select Case item.DisapprovalReasonType
Case FedralReason
Dim selectedIndex = cblFedralReasons.Items.IndexOf(selectedDisapprovalId)
cblFedralReasons.SetItemCheckState(selectedIndex, CheckState.Checked)
Case StateReason
Dim selectedIndex = cblStateReasons.Items.IndexOf(selectedDisapprovalId)
cblStateReasons.SetItemCheckState(selectedIndex, CheckState.Checked)
End Select
Next
End If
End Sub
But the problem is cblFedralReasons.Items.IndexOf always returns -1. All the data from database is coming correctly but something weird happening with checkedlistbox which I couldn't understand.
EDIT:
Also when I try to get the text of an item by using the following code it returns me name of my collections instead of the text.
cblFedralReasons.items(1).tostring
It returns
DisapprovalReasonList
and not the text of that item!
I'll try to explain what I think about this:
If cblFedralReasons has as Datasource a List(Of DataService.usp_DisapprovalReasonList), if you search a selectedDisapprovalId vía IndexOf passing an Integer on the list.... that -1 value returned, its coherent.
IndexOf, on a collection, are internally doing a Equals comparison. So you are comparing different types: an Integer vs a DataService.usp_DisapprovalReasonList.
There are many ways to get the correct object from the collection.
One idea could be do an override of object.equals in your class:
Public Overrides Function Equals(ByVal p_oAnotherObject As Object) As Boolean
If TypeOf p_oAnotherObject Is DataService.usp_DisapprovalReasonList AndAlso Me.GetType.Equals(p_oAnotherObject.GetType) Then
Return Me.DisapprovalReasonTypeGenId.Equals(DirectCast(p_oAnotherObject, DataService.usp_DisapprovalReasonList).DisapprovalReasonTypeGenId)
Else
Return False
End If
End Function
Assuming you have a constructor accepting an ID, you now can do this:
cblFedralReasons.Items.IndexOf(New DataService.usp_DisapprovalReasonList(selectedDisapprovalId))
and then, you will get it.
Finally, cblFedralReasons.items(1).tostring, you are getting the default GetType.Name. Do this in your class, then:
Public Overrides Function ToString() As String
Return DisapprovalReasonTypeDesc
End Function
Hope I have explained.

How Do I loop through this class once I have added items

How do i loop through this class once I add items via this method. Just I am quite new to generic lists so was wonding if someone could point me in right direction in datatables im used to doing the following:
For Each thisentry In dt.rows
Next
What do I use in collections
Calling Code
Calling this in my delciarations of main class
Dim infoNoProductAvail As List(Of infoProductsNotFound) = New List(Of infoProductsNotFound)()
this is how i am adding the files but I have checked in the routine and the count for the list is at 2 products
If medProductInfo.SKU.SKUID = 0 Then
infoNoProductAvail.Add(New infoProductsNotFound(thisenty2.Item("EAN13").ToString(), True))
End If
this is the class itselfs
Public Class infoProductsNotFound
Public Sub New(tbcode As String, notfound As Boolean)
Me.tagbarcode = tbcode
Me.notfound = notfound
End Sub
Private tagbarcode As String = String.Empty
Private notfound As Boolean
Public Property tbcode() As String
Get
Return tagbarcode
End Get
Set(ByVal value As String)
tagbarcode = value
End Set
End Property
Public Property isNotFound() As Boolean
Get
Return notfound
End Get
Set(ByVal value As Boolean)
notfound = value
End Set
End Property
End Class
Tried
I tried using the following
Function BuildExceptionsForEmail()
Dim retval As String = ""
Dim cnt As Int32 = 0
retval = "The following products are not avialable" & vbCrLf
For Each info As infoProductsNotFound In infoNoProductAvail
retval &= info.tbcode
cnt &= 1
Next
Return retval
but for some reason at this point my info noproductAvail is blank even though in the routine above its sitting at count of 2 what gives?
First I'd shrink that declaration a bit:
Dim infoNoProductAvail As New List(Of infoProductsNotFound)
Next, to iterate there are several options. First (and what you're likely most used to):
For Each info as infoProductsNotFound in infoNoProductAvail
If info.tbCode = "xyz" Then
DoSomething(info)
End If
Next
Or you might want to use lambda expressions (if you're using .Net 3.5 and above I think - might be .Net 4):
infoNoProductAvail.ForEach (Function(item) DoSomething(item))
Remember that generics are strongly typed (unlike the old VB collections) so no need to cast whatever comes out: you can access properties and methods directly.
If infoNoProductAvail(3).isNotFound Then
'Do something
End If
(Not that that is a great example, but you get the idea).
The For Each syntax is the same. It works the same way for all IEnumerable objects. The only "trick" to it is to make sure that your iterator variable is of the correct type, and also to make sure that you are iterating through the correct object.
In the case of the DataTable, you are iterating over it's Rows property. That property is an IEnumerable object containing a list of DataRow objects. Therefore, to iterate through it with For Each, you must use an iterator variable of type DataRow (or one of its base classes, such as Object).
To iterate through a generic List(Of T), the IEnumerable object is the List object itself. You don't need to go to one of it's properties. The type of the iterator needs to match the type of the items in the list:
For Each i As infoProductsNotFound In infoNoProductAvail
' ...
Next
Or:
Dim i As infoProductsNotFound
For Each i In infoNoProductAvail
' ...
Next
Or:
For Each i As Object In infoNoProductAvail
' ...
Next
Etc.

Search for Object in Generic List

Is it possible to search for an object by one of its properties in a Generic List?
Public Class Customer
Private _id As Integer
Private _name As String
Public Property ID() As Integer
Get
Return _id
End Get
Set
_id = value
End Set
End Property
Public Property Name() As String
Get
Return _name
End Get
Set
_name = value
End Set
End Property
Public Sub New(id As Integer, name As String)
_id = id
_name = name
End Sub
End Class
Then loading and searching
Dim list as new list(Of Customer)
list.Add(New Customer(1,"A")
list.Add(New Customer(2,"B")
How can I return customer object with id =1? Does this have to do with the "Predicate" in Generics?
Note: I am doing this in VB.NET.
Yes, this has everything to do with predicates :)
You want the Find(Of T) method. You need to pass in a predicate (which is a type of delegate in this case). How you construct that delegate depends on which version of VB you're using. If you're using VB9, you could use a lambda expression. (If you're using VB9 you might want to use LINQ instead of Find(Of T) in the first place, mind you.) The lambda expression form would be something like:
list.Find(function(c) c.ID = 1)
I'm not sure if VB8 supports anonymous methods in the same way that C# 2 does though. If you need to call this from VB8, I'll see what I can come up with. (I'm more of a C# person really :)
Generally you need to use predicates:
list.Add(New Customer(1, "A"))
list.Add(New Customer(2, "B"))
Private Function HasID1(ByVal c As Customer) As Boolean
Return (c.ID = 1)
End Function
Dim customerWithID1 As Customer = list.Find(AddressOf HasID1)
Or with inline methods:
Dim customerWithID1 As Customer = list.Find(Function(p) p.ID = 1)
You could also overload the equals method and then do a contains. like this
Dim list as new list(Of Customer)
list.Add(New Customer(1,"A")
list.Add(New Customer(2,"B")
list.contains(new customer(1,"A"))
the equals method then would look like this
public overrides function Equals(Obj as object) as Boolean
return Me.Id.Equals(ctype(Obj,Customer).Id
end Function
Not tested but it should be close enough.
If you are using .NET 3.5 this can be done with LINQ to Objects:
How to: Query an ArrayList with LINQ
If not, in .NET 2.0 you can use the Find method of the list.
The idea is that you will need to provide an method that return true if a property of your object satisfies a certain condition.