Getting only records not in second table LINQ - vb.net

i need help figuring out how to do this, i have a DataSet that contains records by Date and currencyCode, what i want is to compare two datasets and know if one (left table) have records that don´t exists on the second one (right table), i have the following code for joining the table based on date and code:
vLINQ = (From ContentExchangeRates In myDataset.Tables(0).AsEnumerable() _
Join PartnerExchangeRates In PartnerDataSet.Tables(0).AsEnumerable() _
On ContentExchangeRates.Field(Of Date)("EffectiveDate") Equals PartnerExchangeRates.Field(Of Date)("RateDate") _
And ContentExchangeRates.Field(Of String)("CurrencyCode") Equals PartnerExchangeRates.Field(Of Date)("CurrencyCode") _
Select New With { .PartnerRateDate = PartnerExchangeRates.Field(Of Date)("RateDate"), _ etc}).ToList
Using Jeff answer the code looks like this:
vLINQ = (From ContentExchangeRates In myDataSet.Tables(0).AsEnumerable() _
Group Join PartnerExchangeRates In PartnerDataSet.Tables(0).AsEnumerable() _
On PartnerExchangeRates.Field(Of Date)("RateDate") Equals ContentExchangeRates.Field(Of Date)("EffectiveDate") _
And PartnerExchangeRates.Field(Of String)("CurrencyCode") Equals ContentExchangeRates.Field(Of String)("ConvertCurrencyCode") _
Into result = Group From m In result.DefaultIfEmpty _
Select New With _
{ _
.PartnerRateDate = m.Field(Of Date)("RateDate") _
}).ToList
But i get the following error: Value cannot be null.
Parameter name: row
I want to be able to make a left outer join, that will only retrieve records that do not exists on PartnerExchangeRates table, but i don´t know what to do any more :(
Any help is appreciated!

To perform a left outer join, you need to do a group join and go through the results of the group join and use DefaultIfEmpty. It will translate to a left outer join.
Dim query =
From cer In myDataset.Tables(0).AsEnumerable
Group Join per In PartnerDataSet.Tables(0).AsEnumerable
On New With
{
.Date = cer.Field(Of Date)("EffectiveDate"),
.Currency = cer.Field(Of String)("CurrencyCode")
}
Equals New With
{
.Date = per.Field(Of Date)("RateDate"),
.Currency = per.Field(Of String)("CurrencyCode")
}
Into pers = Group
From per in pers.DefaultIfEmpty
Where per Is Nothing
Select PartnerRateDate = per.Field(Of Date)("RateDate"), ...

You can use the Enumerable.Except(IEnumerable, IEnumerable, IEqualityComparer) method:
Option Infer On
Module Module1
Class RowComparer : Implements IEqualityComparer(Of DataRow)
Public Function Equals1(x As DataRow, y As DataRow) As Boolean Implements IEqualityComparer(Of DataRow).Equals
' use .Equals for the DateTime and Is for the String
Return x(0).Equals(y(0)) AndAlso x(1) Is y(1)
End Function
Public Function GetHashCode1(obj As DataRow) As Integer Implements IEqualityComparer(Of DataRow).GetHashCode
Return obj(0).GetHashCode Xor obj(1).GetHashCode
End Function
End Class
Sub ShowDt(dt As DataTable)
For Each r As DataRow In dt.Rows
Console.WriteLine(CDate(r(0)).ToString("yyyy-MM-dd") & " " & CStr(r(1)))
Next
Console.WriteLine("-------------")
End Sub
Sub Main()
Dim dt(1) As DataTable
For i = 0 To 1
dt(i) = New DataTable
dt(i).Columns.Add(New DataColumn With {.ColumnName = "EffectiveDate", .DataType = Type.GetType("System.DateTime")})
dt(i).Columns.Add(New DataColumn With {.ColumnName = "CurrencyCode", .DataType = Type.GetType("System.String")})
Next
' test data
For i = 0 To 1
For j = 1 To 10
Dim nr = dt(i).NewRow
nr("EffectiveDate") = New DateTime(2015, 1, j)
nr("CurrencyCode") = "USD"
dt(i).Rows.Add(nr)
Next
Next
' remove a couple of rows from the second table
dt(1).Rows.RemoveAt(9)
dt(1).Rows.RemoveAt(2)
ShowDt(dt(0))
ShowDt(dt(1))
' find the rows in dt(0) which are not in dt(1)...
Dim missings = (dt(0).AsEnumerable.Except(dt(1).AsEnumerable, New RowComparer())).CopyToDataTable()
ShowDt(missings)
Console.ReadLine()
End Sub
End Module
But if it happens that your data is amenable to being stored in lists, your code could be cleaner and more readable:
Option Infer On
Module Module1
Public Class DateAndCurrency
Public Property EffectiveDate As DateTime
Public Property CurrencyCode As String
Public Overrides Function ToString() As String
Return Me.EffectiveDate.ToString("yyyy-MM-dd") & " " & Me.CurrencyCode
End Function
End Class
Public Class DateAndCurrencyComparer : Implements IEqualityComparer(Of DateAndCurrency)
Public Function Equals1(x As DateAndCurrency, y As DateAndCurrency) As Boolean Implements IEqualityComparer(Of DateAndCurrency).Equals
Return x.CurrencyCode = y.CurrencyCode AndAlso x.EffectiveDate = y.EffectiveDate
End Function
Public Function GetHashCode1(obj As DateAndCurrency) As Integer Implements IEqualityComparer(Of DateAndCurrency).GetHashCode
Return obj.CurrencyCode.GetHashCode Xor obj.EffectiveDate.GetHashCode
End Function
End Class
Sub Main()
' test data
Dim daccs As New List(Of List(Of DateAndCurrency))
For i = 0 To 1
Dim dacc As New List(Of DateAndCurrency)
For j = 1 To 10
dacc.Add(New DateAndCurrency With {.EffectiveDate = New DateTime(2015, 1, j), .CurrencyCode = "USD"})
Next
daccs.Add(dacc)
Next
' remove a couple of rows from the second list
daccs(1).RemoveAt(9)
daccs(1).RemoveAt(2)
For i = 0 To 1
daccs(i).ForEach(Sub(x) Console.WriteLine(x.ToString()))
Console.WriteLine("-------------")
Next
' find the items in daccs(0) which are not in daccs(1)...
Dim missings = daccs(0).Except(daccs(1), New DateAndCurrencyComparer())
missings.ToList().ForEach(Sub(x) Console.WriteLine(x.ToString()))
Console.WriteLine("-------------")
Console.ReadLine()
End Sub
End Module

Related

Removing duplicates in Text Box and adding the corresponding values

I have a VB form with three TextBoxes. Here's an example of what I'd like the program to achieve:
So, that's the form ... the program sorts a text file and gets names, goals, and positions. E.g.
Jordan 26 Center
James 10 Mid
Jordan 4 Center
Jack 6 Forward
James 10 Mid
When the update button is clicked, the program should realize that James and Jordan are written twice, remove one of them and add their goals, so it should output:
Jordan 30 Center
James 20 Mid
Jack 6 Forward
To do this I've had the data transferred into ListBoxes which makes it easier to remove duplicates, the data is then transferred back into a multi-line TextBox so it is editable. Here's my code so far. It either gives the wrong results or an index out of range error.
Dim Count1 As Integer
Dim Count2 As Integer
Dim Count3 As Integer
Dim NewInt As Integer
Dim ValOne As Integer
Dim ValTwo As Integer
ListBox1.Items.Clear()
ListBox2.Items.Clear()
ListBox3.Items.Clear()
NewInt = 0
ValOne = 0
ValTwo = 0
ListBox1.Items.AddRange(Players.Text.Split(vbNewLine))
ListBox2.Items.AddRange(Goals.Text.Split(vbNewLine))
ListBox3.Items.AddRange(Positions.Text.Split(vbNewLine))
Count1 = ListBox1.Items.Count
Count2 = ListBox2.Items.Count
Count3 = ListBox3.Items.Count
If Count1 = Count2 And Count1 = Count3 And Count2 = Count3 Then
'Set two counters to compare all words with each other
For iFirstCounter As Integer = 0 To ListBox1.Items.Count - 1
For iSecondCounter As Integer = 0 To ListBox1.Items.Count - 1
'Make sure there will not be an 'out of range' error,
'because you are removing items from the listbox.
iSecondCounter = Convert.ToInt64(iSecondCounter)
iFirstCounter = Convert.ToInt64(iFirstCounter)
ListBox2.Items.RemoveAt(iSecondCounter)
ListBox2.Items.RemoveAt(iFirstCounter)
If iFirstCounter < iSecondCounter Then
ListBox2.Items.Insert(iFirstCounter, NewInt.ToString)
Else
ListBox2.Items.Insert(iSecondCounter, NewInt.ToString)
End If
Next
Next
Players.Text = ""
Goals.Text = ""
Positions.Text = ""
Dim i As Integer
For i = 0 To ListBox1.Items.Count - 1
If Players.Text = "" Then
Players.Text = ListBox1.Items(i)
Else
Players.Text = Players.Text & vbNewLine & ListBox1.Items(i)
End If
Next
Dim a As Integer
For a = 0 To ListBox2.Items.Count - 1
If Goals.Text = "" Then
Goals.Text = ListBox2.Items(a)
Else
Goals.Text = Goals.Text & vbNewLine & ListBox2.Items(a)
End If
Next
Dim b As Integer
For b = 0 To ListBox3.Items.Count - 1
If Positions.Text = "" Then
Positions.Text = ListBox3.Items(b)
Else
Positions.Text = Positions.Text & vbNewLine & ListBox3.Items(b)
End If
Next
Else
MessageBox.Show("The Text Boxes don't contain an equal number of values ... please add more/remove some values")
End If
Could be done in multiple ways, for example:
If TextBox2.Lines.Count > 1 Then
Dim LineList As List(Of String) = TextBox2.Lines.ToList 'textbox lines
Dim NewLines As List(Of String) = TextBox2.Lines.ToList 'can't edit list we're looping over, a copy of lines
Dim NamesList As New List(Of String)
For x = 0 To LineList.Count - 1
Dim linesplit As String() = LineList(x).Split({" "}, StringSplitOptions.RemoveEmptyEntries)
If NamesList.Contains(linesplit(0)) Then
NewLines.Remove(LineList(x))
Else
NamesList.Add(linesplit(0))
End If
Next
TextBox2.Lines = NewLines.ToArray
End If
Here's an example of code that does this via LINQ and Lambdas.
Module Module1
Sub Main()
Dim ungroupedPlayers(1) As String
ungroupedPlayers(0) = "Jordan 26 Center"
ungroupedPlayers(1) = "Jordan 4 Center"
Dim players = ungroupedPlayers.ToList().ConvertAll(Of Player)(Function(x As String) As Player
Dim split() As String = x.Split(" "c)
Dim p As New Player
p.PlayerName = split(0)
p.Count = split(1)
p.Position = split(2)
Return p
End Function)
Dim playersGrouped = From p In players
Group By PlayerName = p.PlayerName Into g = Group
Select PlayerName, Count = g.Sum(Function(ip As Player) ip.Count), Position = g.Min(Function(ip As Player) ip.Position.ToString())
Dim groupedPlayers() As String = playersGrouped.ToList().ConvertAll(Of String)(Function(ip)
Return ip.PlayerName.ToString() & " " & ip.Count.ToString() & " " & ip.Position.ToString()
End Function).ToArray()
For Each groupedPlayer as String in groupedPlayers
Console.WriteLine(groupedPlayer)
Next
Console.Read()
End Sub
Public Class Player
Public PlayerName As String
Public Count As Integer
Public Position As String
End Class
End Module
You don't need heavy ListBox control for working with players data.
Use List(Of T) and create class Player for better readability.
You can remove duplicates before you will display values in your form.
And instead of multiline textbox you can use DataGridView as "right tool for the editing data".
Public Class Player
Public Property Name As String
Public Property Position As String
Public Property Goals As Integer
End
Public Class PlayersForm : Form
Private Sub Form_Load(sender As Object, e As System.EventArgs) Handles MyBase.Load
Dim data As List(Of Player) = LoadPlayersData()
Dim players As List(Of Player) = NormalizeData(data)
' Use DataGridView
Me.DataGridView1.DataSource = players
End Sub
Private Function LoadPlayersData() As List(Of Player)
Dim rawData As String() = File.ReadAllLines("pathToTextFile")
Return rawData.Select(Function(line) LineToPlayer(line)).ToList()
End Function
Private Function NormalizeData(players As List(Of Player)) As List(Of Player)
Return players.Group(Function(player) player.Name)
.Select(Function(group)
Return New Player With
{
.Name = group.Key,
.Position = group.First().Position,
.Goals = group.Sum(Function(player) player.Goals)
}
End Function)
.ToList()
End Function
Private Function LineToPlayer(line As String) As Player
Dim values = line.Split(" "c)
Return New Player With
{
.Name = values(0),
.Position = values(2),
.Goals = Integer.Parse(values(1))
}
End Function
End Class
DataGridView control will automatically update your List(Of Players) when you make any change. which give you possibility to have some other controls which automatically display best scorers for example, without extra converting data from string to integer and back.

VB.NET Filtering with a Linq query

I've been tasked with adding a filtering mechanism to a result set. I understand the basic concepts of filtering using the Where clause in Linq, however there HAS to be a better way to do this, right?
Scenario:
I have 5 filterable columns on my result set. I have to take into account all variations of these 5 filters at any given time. This means that I have to manually type out an If/ElseIf statement with 120 different variations!
Here's the general way of how things are going and I just simply need to ask: Is there a better, quicker way to do this?!
The Filters
ByVal SearchMxMID As Integer,
ByVal SearchProfile As Integer,
ByVal SearchCZ As String,
ByVal SearchTerm As Integer,
ByVal SearchFwMth As Integer
The course of the If statements
If SearchMxMID = 0
And SearchProfile = 0
And SearchCZ = "All"
And SearchTerm = 0
And SearchFwMth = 0 Then
Dim queryMM = (From a In DBPricingContext.tbl_Matrix_Margins
Where a.CompanyID = CInt(sCompanyID)
Order By a.Profile
Select a)
Return New StoreResult(queryMM)
ElseIf SearchMxMID > 0
And SearchProfile = 0
And SearchCZ = "All"
And SearchTerm = 0
And SearchFwMth = 0 Then
Dim queryMM = (From a In DBPricingContext.tbl_Matrix_Margins
Where a.CompanyID = CInt(sCompanyID) And a.MarGroupID = SearchMxMID
Order By a.Profile
Select a)
Return New StoreResult(queryMM)
ETC
ETC
ETC
120 total times for each combination of these 5 filters (whether they are blank or have values). Is there a quicker way i can do this, something maybe in 1 single Linq query?
If SearchMxMID > 0 Then a.MarGroupID = SearchMxMID Else DO NOT APPLY WHERE CLAUSE
???
You can chain the Where clauses and construct your query dynamically like this:
Dim query = DBPricingContext.tbl_Matrix_Margins.AsQueryable()
If compID > 0 Then
query = query.Where(Function(a) a.CompanyID = compID)
End If
If SearchMxMID > 0 Then
query = query.Where(Function(a) a.MarGroupID = SearchMxMID)
End If
...
query = query.OrderBy(Function(a) a.Profile)
This scales well with many columns, as there will be only one If-statement per column.
Note that I am using the extension method syntax and lambda expressions instead of the specialized LINQ query syntax. A call to Select is not necessary, if you are just selecting the parameter a itself.
If you are using LINQ-to-Objects instead of LINQ-to-some_database, then replace the AsQueryable() by AsEnumerable().
Linq queries are chainable. You could simply have a single if per your case and if it is true then add another where. ie (in C# but should be easy to follow for any .Net coder - or you can use Telerik's code converter and revise the code a bit):
string country = "Germany";
string city = "Berlin";
int? productId = 3;
var data = db.Customers;
var result = data.AsQueryable();
if (!string.IsNullOrEmpty(country))
{
result = result.Where(r => r.Country == country);
}
if (!string.IsNullOrEmpty(city))
{
result = result.Where(r => r.City == city);
}
if (productId.HasValue)
{
result = result
.Where(r =>
r.Orders.Any(o =>
o.OrderDetails
.Any(od => od.ProductID == productId.Value)));
}
Another way is to is to simply use a NULL or ... approach. ie:
Private Sub Main()
Dim country As String = "" '"Brazil"
Dim city As String = "" '"Sao Paulo"
Dim orderDate As System.Nullable(Of DateTime) = Nothing 'new DateTime(1996,8,28);
Dim data = Orders.Where(Function(c) _
String.IsNullOrEmpty(country) OrElse c.ShipCountry.StartsWith(country)) _
.Where(Function(c) String.IsNullOrEmpty(city) OrElse c.ShipCity.StartsWith(city)) _
.Where(Function(c) orderDate Is Nothing OrElse c.OrderDate = orderDate) _
.Select(Function(c) New Order() With { _
.OrderId = c.OrderID, _
.CustomerId = c.CustomerID, _
.OrderDate = c.OrderDate, _
.ShipCountry = c.ShipCountry, _
.ShipCity = c.ShipCity _
})
Dim f As New Form() With { .Text = "Query Results" }
Dim dgv As New DataGridView() With { .Dock = DockStyle.Fill }
f.Controls.Add(dgv)
dgv.DataSource = data.ToList()
f.ShowDialog()
End Sub
Public Class Order
Public Property OrderId() As Integer
Get
Return m_OrderId
End Get
Set
m_OrderId = Value
End Set
End Property
Private m_OrderId As Integer
Public Property OrderDate() As System.Nullable(Of DateTime)
Get
Return m_OrderDate
End Get
Set
m_OrderDate = Value
End Set
End Property
Private m_OrderDate As System.Nullable(Of DateTime)
Public Property CustomerId() As String
Get
Return m_CustomerId
End Get
Set
m_CustomerId = Value
End Set
End Property
Private m_CustomerId As String
Public Property ShipCountry() As String
Get
Return m_ShipCountry
End Get
Set
m_ShipCountry = Value
End Set
End Property
Private m_ShipCountry As String
Public Property ShipCity() As String
Get
Return m_ShipCity
End Get
Set
m_ShipCity = Value
End Set
End Property
Private m_ShipCity As String
End Class
Note: Code is Linq To SQL code. With EF this wouldn't be supported and you need to use the former approach.
You can also compose the queries in series using the LINQ syntax.
Dim TempQuery = (From a In DbPricingContext.tbl_Matrix_Margins
Where a.CompanyID = CInt(sCompanyID)
Select a)
If SearchMxMID > 0 Then
TempQuery = (From a In TempQuery
Where a.MarGroupID = SearchMxMID
Select a)
End If
'... Add your other conditions in here...
Return New StoreResult(From a In TempQuery Order By a.Profile Select a)
Use logic like SearchValue = DefaultValue OrElse SearchValue = PropertyValue
Dim result = DBPricingContext.tbl_Matrix_Margins.
Where(Function(a) a.CompanyID = CInt(sCompanyID)).
Where(Function(a) SearchMxMID = 0 OrElse a.MarGroupID = SearchMxMID)
And add other search conditions in the same way.
It will return all rows if SearchMxMID = 0 and return only matched rows if SearchMxMID <> 0
For accuracy result I will be used Nullable type
Where(Function(a) SearchMxMID.HasValue = False OrElse a.MarGroupID = SearchMxMID.Value)
You can implement a specification pattern. Something like this:
Sub Main()
Dim someList As New List(Of SomeClass)
Dim now As DateTime = DateTime.Now
someList.Add(New SomeClass() With {.Id = 1, .Title = "001", .EntryDate = now})
someList.Add(New SomeClass() With {.Id = 2, .Title = "002", .EntryDate = now.AddSeconds(10)})
someList.Add(New SomeClass() With {.Id = 3, .Title = "003", .EntryDate = now.AddSeconds(20)})
Dim idParam As Integer = 1
Dim titleParam As String = "" '"001"
Dim dateParam As DateTime = now
' first approach, one selector
Dim selector As Func(Of SomeClass, Boolean) = Function(item)
With item
Return ((idParam <= 0) OrElse (.Id = idParam)) AndAlso
((String.IsNullOrEmpty(titleParam)) OrElse (.Title = titleParam)) AndAlso
((dateParam.CompareTo(DateTime.MinValue) = 0) OrElse (.EntryDate = dateParam))
End With
End Function
Dim list = From o In someList Where selector(o) Select o
For Each o In list
Console.WriteLine(o)
Next
' second approach, one selector per parameter
Dim selectorId As Func(Of SomeClass, Boolean) = Function(item)
Return ((idParam <= 0) OrElse (item.Id = idParam))
End Function
Dim selectorTitle As Func(Of SomeClass, Boolean) = Function(item)
Return ((String.IsNullOrEmpty(titleParam)) OrElse (item.Title = titleParam))
End Function
Dim selectorEntryDate As Func(Of SomeClass, Boolean) = Function(item)
Return ((dateParam.CompareTo(DateTime.MinValue) = 0) OrElse (item.EntryDate = dateParam))
End Function
Dim list2 = From o In someList
Where
selectorId(o) AndAlso
selectorTitle(o) AndAlso
selectorEntryDate(o)
Select o
For Each o In list2
Console.WriteLine(o)
Next
Console.ReadLine()
End Sub
Public Class SomeClass
Public Property Id As Integer
Public Property Title As String
Public Property EntryDate As DateTime
Public Overrides Function ToString() As String
Return String.Format("Id:{0} Title:{1} EntryDate:{2}", Id, Title, EntryDate)
End Function
End Class
Hope this example helps you a bit.

Storing Objects into a Class & Retrieving upon Request

I seriously need help with my project.
I am trying to store specific jobs into a Class, which then displays in a List Box.
When selecting the List Box, I want the rest of the information to be displayed into a Text Box.
I can add Jobs into the List Box, and the Report button sorts the Job by Earliest to Latest.
I just CANNOT seem to code the Display Button to retrieve the rest of the information.
http://i.stack.imgur.com/0eV5j.png
What am I doing wrong?
My Code:
Public Class Form1
Dim jobList As List(Of UserInformation) = New List(Of UserInformation)
Dim j As UserInformation = New UserInformation()
Private Sub btnReport_Click(sender As Object, e As EventArgs) Handles btnReport.Click
Dim p As UserInformation = New UserInformation()
Dim qty As Integer = jobList.Count - 1
Dim name(qty) As String
Dim deadline(qty) As Date
Dim i As Integer = 0
'fill the array
For i = 0 To qty
p = jobList(i)
name(i) = p.Name
deadline(i) = p.Deadline
Next
'sort the array
Dim done As Boolean = False
While done = False
done = True
For i = 0 To qty - 1
Dim tempName As String
Dim tempDate As Date
If deadline(i) > deadline(i + 1) Then
tempName = name(i)
tempDate = deadline(i)
name(i) = name(i + 1)
deadline(i) = deadline(i + 1)
name(i + 1) = tempName
deadline(i + 1) = tempDate
done = False
End If
Next
End While
lsbReport.Items.Clear()
lblListbox.Text = "List in date order"
For i = 0 To name.Length - 1
Dim str As String
str = name(i) + ", "
str += deadline(i).ToString + "."
lsbReport.Items.Add(str)
Next
End Sub
Private Sub updateListBox()
lsbReport.Items.Clear()
lblListbox.Text = "All people in the List"
For Each person As UserInformation In jobList
Dim str As String
str = person.Name + ", "
str += person.Deadline.ToString + "."
lsbReport.Items.Add(str)
Next
End Sub
Private Sub btnAdd_Click(sender As Object, e As EventArgs) Handles btnAdd.Click
Dim p As UserInformation = New UserInformation()
p.Name = firstNameText.Text
p.Deadline = lastNameText.Value
jobList.Add(p)
updateListBox()
End Sub
Private Sub btnDisplay_Click(sender As Object, e As EventArgs) Handles btnDisplay.Click
Dim job_info As UserInformation = CType(lsbReport.SelectedItem(), UserInformation)
txtReport.Text = "Job Title: " & job_info.Name() & Environment.NewLine
txtReport.Text &="Job DeadLine: " & job_info.Deadline & Environment.NewLine
txtReport.Text &="Job Description" & job_info.Description
End Sub
End Class
Public Class UserInformation
Public job_deadline As Date
Public job_name As String
Public job_description As String
Public Property Name() As String
Get
Return job_name
End Get
Set(ByVal value As String)
job_name = value
End Set
End Property
Public Property Deadline() As String
Get
Return job_deadline
End Get
Set(ByVal value As String)
job_deadline = value
End Set
End Property
Public Property Description() As String
Get
Return job_description
End Get
Set(ByVal value As String)
job_description = value
End Set
End Property
End Class
The bottom line is that you stored string values to the LBs:
str = person.Name & ", " & person.Deadline.ToString
lsbReport.Items.Add(str)
So, that is what you get back making it difficult to connect the string created with the object it represents.
Listboxes and comboboxes can store objects as well as strings. Simple Demo:
Public Class Employee
Public Property ID As Integer ' a db ID maybe
Public Property Name As String
Public Property Department As String
Public Property HireDate As Date
Public Overrides Function ToString As String
Return Name ' text to show in controls
' in a more realistic class it might be:
' Return String.Format("{0}, {1} ({2})", LastName,
' FirstName, BadgeNumber)
' e.g. "Whitman, Ziggy (123450)"
End Function
End Class
Friend EmpList As New List(of Employee) ' if needed
storing an object to a listbox is simple:
Dim newEmp As Employee
newEmp.Name = "Ziggy"
' set props as needed
myLB.Items.Add(newEmp) ' add to a ListBox directly.
Once you have a class for these things, you have many options. You can store them to a List(Of T) which can be used with a List or ComboBox:
Private EmpList As New List(Of Employee)
...
EmpList.Add(newEmp) ' adding to a list is same as a ListBox
' add from List to a control:
myLB.Items.AddRange(Emps.ToArray)
myLB.SelectedItem will now be an Employee object, which should make displaying details elsewhere simple.
To make it even more efficent, you can use the list as a DataSource so you dont have to add references to the listbox:
myLB.DataSource = EmpList
myLB.DisplayMember = "Name" ' the property to display
myLB.ValueMember = "Id" ' what to use for SelectedValue

VB2010 List(Of String) to multiple labels

I have 64 panels, each containing two strings of data. For testing purposes I used a random number generator to provide my data.
I created a list of strings (64 strings total, containing "," separating the two pieces of data per panel) and now need to write them to each label.
I need to find a way to split the string data (which I know how) and write to each label.
For example: the first string will be split and added to the first panel called Label1a and Label1b, 2nd string split to Label2a and Label2b, etc..
Dim LotData As New List(Of String)
Dim randomnumber1 As Integer, randomnumber2 As Integer, randomchance As Integer
Dim slotnumber As String
Dim sbailes As String
Dim stemp As String
Randomize()
Dim n As Integer
For n = 1 To 64
randomnumber1 = CInt(Rnd() * 1000000000)
randomnumber2 = CInt(Rnd() * 300)
randomchance = CInt(Rnd() * 1000)
slotnumber = Convert.ToString(randomnumber1)
'approximately 50% of the lots will be empty in this test
If randomchance >= 500 Then
sbailes = CStr(randomnumber2)
Else
sbailes = "0"
End If
LotData.Add(slotnumber & "," & sbailes)
Next
My only solution is to write 128 lines of code, manually adding each string in but I know there must be a better solution than that...
You would want to get a control by string name. I have some methods that help to do this here (put them in a module)
<Extension()> _
Public Function ChildControls(ByVal parent As Control) As ArrayList
Return ChildControls(Of Control)(parent)
End Function
<Extension()> _
Public Function ChildControls(Of T)(ByVal parent As Control) As ArrayList
Dim result As New ArrayList()
For Each ctrl As Control In parent.Controls
If TypeOf ctrl Is T Then result.Add(ctrl)
result.AddRange(ChildControls(Of T)(ctrl))
Next
Return result
End Function
Public Function GetControlByName(ByRef parent As Control, ByVal name As String) As Control
For Each c As Control In parent.ChildControls
If c.Name = name Then
Return c
End If
Next
Return Nothing
End Function
Then in your method, you can get the control by name, depending on an integer counting from 1 to 64 like so:
For i As Integer = 1 To 64
CType(GetControlByName(Me, "Label" & i.ToString() & "A"), Label).Text = _
LotData(i).Split(",").FirstOrDefault()
CType(GetControlByName(Me, "Label" & i.ToString() & "B"), Label).Text = _
LotData(i).Split(",").LastOrDefault()
Next
Oh! You can also save yourself the trouble of adding all the controls to the form at design time by doing something like this:
For i As Integer = 1 To 64
Dim lblA As New Label()
Dim lblB As New Label()
lblA.Name = "Label" & i.ToString() & "A"
lblB.Name = "Label" & i.ToString() & "B"
' do something about Locations here
Me.Controls.Add(lblA)
Me.Controls.Add(lblB)
' you could even add them to your panel.controls
Next
(don't forget exception handling)

Sort an array of structures in .NET

This is one of those times when only the hive mind can help - no amount of Google-fu can!
I have an array of structures:
Structure stCar
Dim Name As String
Dim MPH As Integer
Sub New(ByVal _Name As String, ByVal _MPH As Integer)
Name = _Name
MPH = _MPH
End Sub
End Structure
How do I sort the array on one variable / property of the structure?
Dim cars() as stCar = {new stCar("ford",10), new stCar("honda",50)}
cars.sort("MPH") // how do I do this?
Assuming that the structure has a property called MPH:
cars = cars.OrderBy(Function(c) c.MPH)
Note: the above code was auto-converted from the following c# code (in case it contains errors):
cars = cars.OrderBy(c => c.MPH);
The easiest way to perform the sort would be to use LINQ to Objects.
Dim q = From c In cars Order By c.MPH Select c
I don't know VB.NET, but you should be able to do this my implimenting IComparer. Take a look at this example
http://www.java2s.com/Code/VB/Data-Structure/UseIComparertosortbydifferentproperties.htm
Alternativly you can also use Linq
Another possibility, that doesn't use Linq but instead uses the .Net Array class' Sort method:
Module Module1
Structure stCar
Dim Name As String
Dim MPH As String
Sub New(ByVal _Name As String, ByVal _MPH As Integer)
Name = _Name
MPH = _MPH
End Sub
End Structure
Class CarCompareMph : Implements IComparer
Public Function Compare(ByVal x As Object, ByVal y As Object) As Integer Implements System.Collections.IComparer.Compare
Dim xCar As stCar = DirectCast(x, stCar)
Dim yCar As stCar = DirectCast(y, stCar)
Return New CaseInsensitiveComparer().Compare(xCar.MPH, yCar.MPH)
End Function
End Class
Sub Main()
Dim cars() As stCar = {New stCar("honda", 50), New stCar("ford", 10)}
Array.Sort(cars, New CarCompareMph)
For Each c As stCar In cars
Console.WriteLine("{0} - {1} MPH", c.Name, c.MPH)
Next
End Sub
End Module
I'm not sure if that's what you're looking for, but it's another approach.
A simple way that seems to be working for me in vb.net 2013 is as follows:
cars.Sort(Function(c1,c2) c1.MPH.CompareTo(c2.MPH))
In VB.Net of VS2015 there is another method of sorting:
cars.Sort(New Comparison(Of stCar)(Function(x, y) 'x and y are of type of stCar
If x.MPH > y.MPH Then
Return 1 ' Return Value>0 means x>y
End If
If x.MPH < y.MPH Then
Return -1 ' Return Value<0 means x<y
End If
If x.MPH = y.MPH Then
Return 0 ' Return Value=0 means x=y
End If
End Function))
Public Class Form1
Public Structure EstruturaPessoa
Dim nome As String
Dim idade As Integer
End Structure
Private Sub Form1_Load(sender As Object, e As System.EventArgs) Handles Me.Load
Dim nome(,) As String = {{"Milton Inácio Pozza", 30}, _
{"Araci Moraes", 34}, _
{"Marli Lipi Jesus", 21}, _
{"Gerson Guebur", 45}, _
{"Marli Ulths", 72}, _
{"Mauro Jesus Nadolni", 56}, _
{"Cristiano Kobashikawa Ferreira", 44}, _
{"Débora Nadolni", 90}, _
{"Samanta Gomes Guebur", 66}, _
{"Miguel Barbosa", 42}, _
{"Luis Jesus", 24} _
}
Dim Pessoa As EstruturaPessoa
Dim ListaPessoa = New List(Of EstruturaPessoa)
Dim i As Integer
'Load ListaPessoa
For i = 0 To (nome.Length / 2) - 1
Pessoa.nome = nome(i, 0)
Pessoa.idade = nome(i, 1)
ListaPessoa.Add(Pessoa)
Next i
'Fill the ListView1 with the ListaPessoa before sorting
For Each item_pessoa In ListaPessoa
With Me.ListView1
.Items.Add(item_pessoa.nome)
With .Items(.Items.Count - 1).SubItems
.Add(item_pessoa.idade)
End With
End With
Next
'Sort ListaPessoa
ListaPessoa.Sort(Function(c1, c2) c1.nome.CompareTo(c2.nome))
'Modifiy the 6th item of ListaPessoa
Pessoa = ListaPessoa(5)
Pessoa.nome += " ***" 'Acrescenta asteriscos ao nome
Pessoa.idade = 99 'Modifica a idade para 99
ListaPessoa(5) = Pessoa 'Atualiza o item na ListaPessoa
'Fill ListView2 with the ListaPessoa after sorting
For Each item_pessoa In ListaPessoa
With Me.ListView2
.Items.Add(item_pessoa.nome)
With .Items(.Items.Count - 1).SubItems
.Add(item_pessoa.idade)
End With
End With
Next
End Sub
End Class