Implementing a custom column sorting - wpf

I'm having issues implementing a custom sort on a datagrid column. Could someone explain what I'm doing wrong?
I'm trying to create a custom sort for column so that the strings are sorted by length first, then by their values. It reaches my custom made function, hits the "Throws not implemented line", and then gives me an InvalidOperationException error.
Private Sub customSorting(sender As Object, e As DataGridSortingEventArgs) Handles grid.Sorting
If e.Column.Header = "Test" Then
Dim comp As IComparer = New RowComparer(e.Column.SortDirection)
Dim lcv As ListCollectionView = CollectionViewSource.GetDefaultView(grid.ItemsSource)
lcv.CustomSort = comp
e.Handled = True
End If
End Sub
Private Class RowComparer
Implements System.Collections.IComparer
Private o As Integer = 0
Public Sub New(order As Integer)
o = order
End Sub
Public Function Compare(x As Object, y As Object) As Integer Implements IComparer.Compare
Throw New NotImplementedException()
If x.ToString.Length > y.ToString.Length Then
If o = 0 Then
Return 1
Else
Return -1
End If
ElseIf x.ToString.Length < y.ToString.Length Then
If o = 0 Then
Return -1
Else
Return 1
End If
Else
Return String.Compare(x, y)
End If
End Function
End Class

Visual studios automatically generated the 'Throw New NotImplementedException()' which was stopping my program.

Related

(VB.NET) display the lower half of a textfile to a listbox

I have to make a application that organizes a list of runners and their teams. In the following text file, I have to remove the top half of the text file (the top half being the listed teams) and display only the bottom half (the runners)in a listbox item.
The Text file:
# School [School Code|School Name|Coach F-Name|Coach L-Name|AD F-Name|AD L Name]
WSHS|Worcester South High School|Glenn|Clauss|Bret|Zane
WDHS|Worcester Dorehty High School|Ellsworth|Quackenbush|Bert|Coco
WBCHS|Worcester Burncoat High School|Gail|Cain|Kevin|Kane
QRHS|Quabbin Regional High School|Bob|Desilets|Seth|Desilets
GHS|Gardner High School|Jack|Smith|George|Fanning
NBHS|North Brookfield High School|Hughe|Fitch|Richard|Carey
WHS|Winchendon High School|Bill|Nice|Sam|Adams
AUBHS|Auburn High School|Katie|Right|Alice|Wonderland
OXHS|Oxford High School|Mary|Cousin|Frank|Daughter
# Roster [Bib #|School Code|Runner's F-Name|Runner's L-Name]
101|WSHS|Sanora|Hibshman
102|WSHS|Bridgette|Moffitt
103|WSHS|Karine|Chunn
104|WSHS|Shanita|Wind
105|WSHS|Fernanda|Parsell
106|WSHS|Albertha|Baringer
107|WSHS|Carlee|Sowards
108|WDHS|Maisha|Kleis
109|WDHS|Lezlie|Berson
110|WDHS|Deane|Rocheleau
111|WDHS|Hang|Hodapp
112|WDHS|Zola|Dorrough
113|WDHS|Shalon|Mcmonigle
I have some code that reads each row from the text file as an array and uses boolean variables to determine where to end the text file. This worked with displaying only the teams, which I've managed to do. But I now need to do the opposite and display only the players, and I'm a bit stumped.
My Code:
Private Sub btnLoadTeams_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnLoadTeam.Click
' This routine loads the lstTeam box from an ASCII .txt file
' # School [School Code | Name | Coach F-Name| Coach L-Name | AD F-Name | AD L-Name]
Dim strRow As String
Dim bolFoundCode As Boolean = False
Dim bolEndCode As Boolean = False
Dim bolFoundDup As Boolean = False
Dim intPosition As Integer
Dim intPosition2 As Integer
Dim strTeamCodeIn As String
Dim textIn As New StreamReader( _
New FileStream(txtFilePath.Text, FileMode.OpenOrCreate, FileAccess.Read))
' Clear Team listbox
lstTeam.Items.Clear()
btnDeleteRunner.Enabled = True
Do While textIn.Peek <> -1 And Not bolEndCode
Me.Refresh()
strRow = textIn.ReadLine.Trim
If Not bolFoundCode Then
If "# SCHOOL " = UCase(Mid(strRow, 1, 9)) Then
bolFoundCode = True
End If
Else
If Mid(strRow, 1, 2) <> "# " Then
For Each item As String In lstTeam.Items
intPosition = InStr(1, strRow, "|")
strTeamCodeIn = Mid(strRow, 1, intPosition - 1)
intPosition2 = InStr(1, item, strTeamCodeIn)
If intPosition2 > 0 Then
bolFoundDup = True
MsgBox("Found Duplicate School Code: " & strTeamCodeIn)
End If
Else
bolEndCode = True
Next
If Not bolFoundDup Then
lstTeam.Items.Add(strRow)
Else
lstTeam.Items.Add("DUPLICATE School Code: " & strRow)
lstTeam.Items.Add("Please correct input file and reload teams")
bolEndCode = True
End If
End If
End If
Loop
End Sub
Ive put bolEndCode = True in between the part that reads the mid section of the text file, but all Ive managed to display is the following in the listbox:
# Roster [Bib #|School Code|Runner's F-Name|Runner's L-Name]
Any help or hints on how I would display just the runners to my "lstPlayers" listbox would be greatly appreciated. I'm a beginner programmer and We've only just started learning about reading and writing arrays in my .NET class.
First I made 2 classes, one Runner and one School. These have the properties available in the text file. As part of the class I added a function that overrides .ToString. This is for he list boxes that call .ToString for display.
Next I made a function that reads all the data in the file. This is very simple with the File.ReadLines method.
Then I created 2 variables List(Of T) T stands for Type. Ours Types are Runner and School. I used List(Of T) instead of arrays because I don't have to worry about what the size of the list is. No ReDim Preserve, just keep adding items. The FillList method adds the data to the lists. First I had to find where the schools ended and the runners started. I used the Array.FindIndex method which is a bit different because the second parameter is a predicate. Check it out a bit. Now we know the indexes of the lines we want to use for each list and use a For...Next loop. In each loop an instance of the class is created and the properties set. Finally the new object is added to the the list.
Finally we fill the list boxes with a simple .AddRange and the lists.ToArray. Note that we are adding the entire object, properties and all. The neat thing is we can access the properties from the listbox items. Check out the SelectedIndexChanged event. You can do the same thing with the Runner list box.
Sorry, I couldn't just work with your code. I have all but forgotten the old vb6 methods. InStr, Mid etc. It is better when you can to use .net methods. It makes your code more portable when the boss says "Rewrite the whole application in C#"
Public Class Runner
Public Property BibNum As Integer
Public Property SchoolCode As String
Public Property FirstName As String
Public Property LastName As String
Public Overrides Function ToString() As String
'The listbox will call .ToString when we add a Runner object to determin what to display
Return $"{FirstName} {LastName}" 'or $"{LastName}, {FirstName}"
End Function
End Class
Public Class School
Public Property Code As String
Public Property Name As String
Public Property CoachFName As String
Public Property CoachLName As String
Public Property ADFName As String
Public Property ADLName As String
'The listbox will call .ToString when we add a School object to determin what to display
Public Overrides Function ToString() As String
Return Name
End Function
End Class
Private Runners As New List(Of Runner)
Private Schools As New List(Of School)
Private Function ReadData(path As String) As String()
Dim lines = File.ReadLines(path).ToArray
Return lines
End Function
Private Sub FillLists(data As String())
Dim location = Array.FindIndex(data, AddressOf FindRosterLine)
'The first line is the title so we don't start at zero
For index = 1 To location - 1
Dim SplitData = data(index).Split("|"c)
Dim Schl As New School
Schl.Code = SplitData(0)
Schl.Name = SplitData(1)
Schl.CoachFName = SplitData(2)
Schl.CoachLName = SplitData(3)
Schl.ADFName = SplitData(4)
Schl.ADLName = SplitData(5)
Schools.Add(Schl)
Next
For index = location + 1 To data.GetUpperBound(0)
Dim SplitData = data(index).Split("|"c)
Dim Run As New Runner
Run.BibNum = CInt(SplitData(0))
Run.SchoolCode = SplitData(1)
Run.FirstName = SplitData(2)
Run.LastName = SplitData(3)
Runners.Add(Run)
Next
End Sub
Private Function FindRosterLine(s As String) As Boolean
If s.Trim.StartsWith("# Roster") Then
Return True
Else
Return False
End If
End Function
Private Sub FillListBoxes()
Dim arrRunners As Runner() = Runners.ToArray
Dim arrSchools As School() = Schools.ToArray
ListBox1.Items.AddRange(arrSchools)
ListBox2.Items.AddRange(arrRunners)
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim arrRunner = ReadData("Runners.txt")
FillLists(arrRunner)
FillListBoxes()
End Sub
Private Sub ListBox1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles ListBox1.SelectedIndexChanged
Dim Schl = DirectCast(ListBox1.SelectedItem, School)
TextBox1.Text = Schl.CoachLName
TextBox2.Text = Schl.Code
End Sub

How to monitor array's and reset when limit is reached. VB.net

I have a VB.Net program that loops through array's to try to figure out where bottles are on a "conveyor". The point of this program is to visually show staff, how the conveyor works using VB.net and Labels. It's extremely difficult to explain, so I’ll do my best.
Bottle_Number(10) Bottle_Position(128)
There are 10 bottles that I want to track at all 128 stops on the conveyor.
We have a conveyor that can only fit 10 bottles. I need to track the position of each of the 10 bottles. Once bottle 11 comes on - That means bottle 1 is completed and off the conveyor. So, bottle 11 becomes bottle 1, so I need to reset the position of bottle1 to 0, and continue tracking bottles 2-9 while also tracking bottle 11(Not bottle 1). Once bottle 12 comes on it becomes bottle 2, and I need to reset the position of bottle 2 to '0' and continue tracking all bottles.
Any help would be appreciated.
Here is my code:
Public Class frmMain
Dim Product_Position(10) As Integer
Dim Inches_Per_Pulse As Integer
Dim PulseNumber As Integer
Dim Product_Counter As Integer
Dim Product_Location(10) As Integer
Dim Function1 As Integer
Dim Function2 As Integer
Dim Function3 As Integer
Dim Function4 As Integer
Dim Function5 As Integer
Dim Function6 As Integer
Dim Function7 As Integer
Dim Function8 As Integer
Dim Function9 As Integer
Dim Function10 As Integer
Dim Product_in_Tunel As Integer
Dim test As Integer
Dim Roll_OVer As Boolean
Dim Product_Counter_Test As Integer
Private Sub btnStart_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnStart.Click
lblStatus.BackColor = Color.Green
lblStatus.Text = "Conveyor Status: Running"
End Sub
Private Sub btnStop_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnStop.Click
lblStatus.BackColor = Color.Red
lblStatus.Text = "Conveyor Status: Off"
End Sub
Private replace_next As Integer
Private Sub btnSend_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSend.Click
If Product_Counter = 10 Then
replace_next += 1
If replace_next > 10 Then
replace_next = 1 ' replace them in turn 1..10, then loop back to 1
Product_Position(replace_next) = 0 ' put initial position here
End If
End If
Product_Counter = Product_Counter + 1
If Product_Counter > 10 Then
Product_Counter = 1
Roll_over = True
End If
'MsgBox(Product_Counter)
'MsgBox(replace_next)
End Sub
Private Sub btnPulse_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnPulse.Click
Get_Location()
End Sub
Private Sub frmMain_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
PulseNumber = "0"
Inches_Per_Pulse = "1"
Roll_OVer = False
'MsgBox("Test")
End Sub
Public Sub Get_Location()
'MsgBox(Product_Counter)
If Roll_OVer = True Then
Product_Counter_Test = 10
'MsgBox("i'm stuck here")
End If
If Roll_OVer = False Then
Product_Counter_Test = Product_Counter
End If
'MsgBox(Product_Counter_Test)
'MsgBox("am I here - Yes")
For test = 1 To Product_Counter_Test
'MsgBox("This works")
Product_Position(test) = Product_Position(test) + Inches_Per_Pulse
Next
PulseNumber = PulseNumber + 1
ClearLabels()
lblProduct1Position.Text = Product_Position(1)
lblProduct2Position.Text = Product_Position(2)
lblProduct3Position.Text = Product_Position(3)
lblProduct4Position.Text = Product_Position(4)
lblProduct5Position.Text = Product_Position(5)
lblProduct6Position.Text = Product_Position(6)
lblProduct7Position.Text = Product_Position(7)
lblProduct8Position.Text = Product_Position(8)
lblProduct9Position.Text = Product_Position(9)
lblProduct10Position.Text = Product_Position(10)
End Sub
Public Sub ClearLabels()
lblProduct1Position.Text = ""
lblProduct2Position.Text = ""
lblProduct3Position.Text = ""
lblProduct4Position.Text = ""
lblProduct5Position.Text = ""
lblProduct6Position.Text = ""
lblProduct7Position.Text = ""
lblProduct8Position.Text = ""
lblProduct9Position.Text = ""
lblProduct10Position.Text = ""
End Sub
The Pulse button is what is actually driving the conveyor, each pulse (click of the button) means the conveyor is moving forward.
Right now once the program gets to bottle 11, it resets and only moves forward the "new" bottle (bottle1). It should continue incrementing the remaining bottles until they reach the end and do the same for them - Reset the position to 0 and begin counting again.
As far as I understand it, once you have 11 bottles, you don't want to reset to only one bottle, but instead still have 10 bottles, and replace one of them. You'll need a second variable to keep track of which is to be replaced.
So instead of :
Private Sub btnSend_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSend.Click
Product_Counter = Product_Counter + 1
If Product_Counter > 10 Then Product_Counter = 1
End Sub
It would be something like:
Private Replace_Next as Integer = 0
Private Sub btnSend_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSend.Click
If Product_Counter = 10 Then
Replace_Next += 1
If Replace_Next > 10 Then Replace_Next = 1 ' replace them in turn 1..10, then loop back to 1
Product_Position(Replace_Next) = .... ' put initial position here
Else
Product_Counter = Product_Counter + 1
End If
End Sub
Your conveyor is FIFO (first-in, first-out), so rather than constantly shifting, reindexing and/or rebuilding (=reset?) an array to make it seem like it is FIFO, Net includes the Queue(Of T) collection which is FIFO.
A LinkedList(Of T) could also be used. A plain List(Of T) would also work, but if the add/remove frequency is high, that will result in the same inefficient shifting taking place under the hood that you have with an array.
The only issue is enforcement of the size limit, which is easily handled with a small class wrapper. I assume there is something interesting or identifiable about the bottles other than their position. The test code uses a sequence ID and the contents.
Friend Class Bottle
Public Property Contents As String
Public Property SequenceID As Int32
' etc
Public Overrides Function ToString() As String
Return String.Format("{0}: ({1})", SequenceID.ToString("00"), Contents)
End Function
End Class
You likely have more relevant information to show. The, the collection class:
Friend Class BottleQueue
Private mcol As Queue(Of Bottle)
Private lbls As Label()
Private MaxSize As Int32 = 10 ' default
Public Sub New(size As Int32)
MaxSize = size
mcol = New Queue(Of Bottle)
End Sub
Public Sub New(size As Int32, l As Label())
Me.New(size)
lbls = l
End Sub
Public Sub Add(b As Bottle)
mcol.Enqueue(b)
Do Until mcol.Count <= MaxSize
mcol.Dequeue()
Loop
UpdateDisplay()
End Sub
Public Function Peek() As Bottle
Return mcol.ElementAtOrDefault(0)
End Function
Public ReadOnly Property Count() As Int32
Get
Return mcol.Count
End Get
End Property
Public Function Remove() As Bottle
Dim b As Bottle = Nothing
If mcol.Count > 0 Then
b = mcol.Dequeue
UpdateDisplay()
End If
Return b
End Function
Private Sub UpdateDisplay()
Dim n As Int32
If lbls Is Nothing OrElse lbls.Count = 0 Then
Return
End If
For n = 0 To mcol.Count - 1
lbls(n).Text = mcol.ElementAtOrDefault(n).ToString
Next
For n = n To lbls.Count - 1
lbls(n).Text = "(empty)"
Next
End Sub
Public ReadOnly Property GetQueue As Bottle()
Get
Return mcol.ToArray()
End Get
End Property
End Class
The class has 2 display means built in. One updates a set of labels. Since it is a collection, it also provides a way to get the current collection in order for a collection type control such as a Listbox. An even better way would be if the collection itself was "observable", so it could be used as a datasource.
It also provides a way to Removethe next bottle manually. Removing from a specific index (e.g. Remove(3)) is antithetical to a Queue, so it isnt implemented.
test code:
' form level vars:
Private BottleQ As BottleQueue
Private BottleID As Int32 = 7
' form load, passing the labels to use
' using a queue size of FIVE for test
BottleQ = New BottleQueue(5, New Label() {Label1, Label2, Label3, Label4, Label5})
Adding an item:
Dim material = {"Napalm", "Beer", "Perfume", "Pepsi", "Cyanide", "Wine"}
' add new bottle with something in it
BottleQ.Add(New Bottle With {.Contents = material(RNG.Next(0, material.Count)),
.SequenceID = BottleID})
BottleID += 1
' clear and show the contents in a listbox:
lbQueView.Items.Clear()
lbQueView.Items.AddRange(BottleQ.GetQueue)
The BottleId arbitrarily starts at 7, the contents are random. BTW, material shows just about the only way I ever use an array: when the contents are fixed and known ahead of time. In almost all other cases, a NET collection of one sort or another, is probably a better choice.
Because it is not an observable collection (and that is a little at odds with the FIFO nature), the listbox needs to be cleared each time. That could be internal to the class like the label display is. Results:
On the right, the first 5 are shown in order; 3 clicks later, the result is on the left: everything moved up 3 and 3 new items have been added.
Note: If the code using this needs to know when/which Bottle is removed from the conveyor, the class could include a ItemRemoved event which provides the item/Bottle just removed when adding forces one out. That is probably the case, but the question doesnt mention it.

How declare non standard array belong to class?

I have a VBA class:
Option Explicit
Public Re As Double 'Real
Public Im As Double 'Imaginary
Public Function CZ_Sqt(Z As Complex, Exp As Integer) As Variant
Dim Table() As Complex
Dim i As Integer
If Exp > 0 Then
ReDim Preserve Table(0 To Exp - 1)
Set Table(UBound(Table)) = New Complex
Else: Exit Function
End If
For i = 0 To UBound(Table)
Table(i).Re = 1
Table(i).Im = 1
Next i
set CZ_Sqt = Table
End Function
In module:
Sub asd()
Dim K As Complex
Dim A As Variant
Set K = New Complex
K.Re = 1
K.Im = 3
Set A = K.CZ_Sqt(Z, 5)
end sub
How "set" all variable "Table" in on step ?
In solution which is in example is set only element Table(4) but other elements are omited.
How to return this variable "Table" to the function name "CZ_Sqt" ?
This what I proposed doesn't work.
How pass variable "CZ_Sqt" which consider Array Complex type to the variable "A"?
You are using the same class as an object and object collection.
I would separate the functionalities into 2 Classes:
Complex
ComplexCollection - Contains a collection of complex class
EDIT: There is no duplicate check in ComplexCollection.Add and exists check in ComplexCollection.Retrieve.
Complex
Option Explicit
Public Re As Double
Public Im As Double
ComplexCollection
Option Explicit
Dim oCol As Collection
Public Function Create(pRe As Double, pIm As Double) As Complex
Dim oResult As Complex
Set oResult = New Complex
oResult.Re = pRe
oResult.Im = pIm
Set Create = oResult
End Function
Public Sub Add(pComplex As Complex, pKey As String)
oCol.Add pComplex, pKey
End Sub
Public Function Retrieve(pKey As String) As Complex
Set Retrieve = oCol(pKey)
End Function
Private Sub Class_Initialize()
Set oCol = New Collection
End Sub
Private Sub Class_Terminate()
Set oCol = Nothing
End Sub
Test.bas
Public Sub TestCollection()
Dim oCL As ComplexCollection
Dim oC As Complex
Set oCL = New ComplexCollection
Set oC = oCL.Create(1, 2)
Debug.Print oC.Im, oC.Re
oCL.Add oC, "1"
Set oC = Nothing
Set oC = oCL.Retrieve("1")
Debug.Print oC.Im, oC.Re
End Sub

vb.net class and array

I am having an issue with trying to take info from class and putting them into an array with the class data type. I am getting a null error. I can see its not adding the variable info into the array at all. I am unsure what it is I am missing. Can anyone point out to me what it is?
Here is the code:
Option Explicit On
Option Strict On
Option Infer Off
Public Class Form1
Public Class Chandelier
Private _strFinish As String
Private _intLights As Integer
Private _blnCrystal As Boolean
Private _dblTotal As Double
Public Property Finish As String
Get
Return _strFinish
End Get
Set(value As String)
_strFinish = value
End Set
End Property
Public Property Lights As Integer
Get
Return _intLights
End Get
Set(value As Integer)
If value > 0 Then
_intLights = value
Else
_intLights = 0
End If
End Set
End Property
Public Property Crystal As Boolean
Get
Return _blnCrystal
End Get
Set(value As Boolean)
_blnCrystal = value
End Set
End Property
Public Property Total As Double
Get
Return _dblTotal
End Get
Set(value As Double)
If value > 0 Then
_dblTotal = value
Else
_dblTotal = 0
End If
End Set
End Property
Public Sub New()
_strFinish = Nothing
_intLights = 0
_blnCrystal = False
_dblTotal = 0
End Sub
Public Function getCrystal() As Boolean
Return _blnCrystal
End Function
Public Function getPrice() As Double
Dim crystalPrice As Double
Dim price As Double
If _strFinish.Contains("Silver") Then
price = 39.99
If getCrystal() = True Then
crystalPrice = _intLights * 25.5
Else
crystalPrice = 0
End If
price = price + crystalPrice
End If
If _strFinish.Contains("Brass") Then
price = 49.99
If getCrystal() = True Then
crystalPrice = _intLights * 25.5
Else
crystalPrice = 0
End If
price = price + crystalPrice
End If
Return price
End Function
End Class
Public chandelierStyle(49) As Chandelier
Dim count As Integer = 0
Private Sub btnSubmit_Click(sender As Object, e As EventArgs) Handles btnSubmit.Click
Dim ceilingOrnament As New Chandelier
ceilingOrnament.Finish = cboFinish.SelectedItem.ToString
Integer.TryParse(cboLights.SelectedItem.ToString, ceilingOrnament.Lights)
If chkTrimmings.Checked Then
ceilingOrnament.Crystal = True
End If
Dim dblTotal As Double = ceilingOrnament.getPrice()
ceilingOrnament.Total = dblTotal
If count <= 49 Then
'here is where the error starts
chandelierStyle(count).Finish = ceilingOrnament.Finish
chandelierStyle(count).Lights = ceilingOrnament.Lights
chandelierStyle(count).Crystal = ceilingOrnament.Crystal
chandelierStyle(count).Total = ceilingOrnament.Total
count += 1
End If
End Sub
Just place your dynamic instance into the array, instead of trying to copy each individual field.
Change this:
If count <= 49 Then
'here is where the error starts
chandelierStyle(count).Finish = ceilingOrnament.Finish
chandelierStyle(count).Lights = ceilingOrnament.Lights
chandelierStyle(count).Crystal = ceilingOrnament.Crystal
chandelierStyle(count).Total = ceilingOrnament.Total
count += 1
End If
To:
If count <= 49 Then
'here is where the error starts
chandelierStyle(count) = ceilingOrnament
count += 1
End If
Looks like you create your array of 49 elements with this line:
Public chandelierStyle(49) As Chandelier
but you never actually initialize its contents. What you have is 49 empty slots, not 49 Chandelier instances. You need to first set each array slot to a new instance of Chandelier before you can update their properties in your button Click event.
You can use a For loop to initialize each array slot before you use it; something like:
For i As Integer = 0 To 49
chandelierStyle(i) = New Chandelier()
Next i
You'd have this loop in the constructor of the class (looks like it's a Form) that contains your chandelierStyle array.
Note: Don't quote me on that. :) I haven't used VB.NET for ages and there may be an easier way to do this.
Edit: Or, you can do what Idle_Mind suggested in his answer and just stash the ceilingOrnament instance in the appropriate array slot.

VB.NET DataGridView "An item with the same key has already been added." while using a unique index

VB.NET 2010, .NET 4
Hello,
I've looked around and can't seem to find a solution to my problem. I have an EventLog object which inherits DataGridView and has a public variable EventList which is a List(Of EventLogItem). EventLogItem has seven properties which describe the event, including Index which is set to EventList.Count each time an entry is added (so, it should be unique). Everything has worked just fine until I tried to add an entry from a serial port DataReceived event handler upon which I receive the following exception:
An error occurred creating the form. See Exception.InnerException for details. The error is: An item with the same key has already been added.
Clicking 'View Details' and expanding the InnerException yields no more information. Here is some relevant code:
The EventLog class with its EventList:
Public Class EventLog
Inherits DataGridView
Public EventList As New List(Of EventLogItem)
..Column creation code, etc..
End Class
The EventLogItem class:
Public Class EventLogItem
Public Property Index As Integer
Public Property Timestamp As String
Public Property User As String = String.Empty
Public Property [Step] As String = String.Empty
Public Property Type As Types
Public Property Message As String
Public Property TypeIcon As Image
Public Enum Types
SeriousError = -2
NormalError = -1
Warning = 0
NormalEvent = 1
ImportantEvent = 2
ManualEntry = 3
End Enum
Private Sub New()
Me.Timestamp = DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss.f")
Me.Type = Types.NormalEvent
SetTypeIcon()
End Sub
Public Sub New(ByVal Message As String)
Me.New()
Me.Message = Message
End Sub
Public Sub New(ByVal Message As String, ByVal User As String)
Me.New()
Me.Message = Message
Me.User = User
End Sub
Public Sub New(ByVal Message As String, ByVal User As String, ByVal Type As Types)
Me.New(Message, User)
Me.Type = Type
SetTypeIcon()
End Sub
Public Sub New(ByVal Message As String, ByVal User As String, ByVal Type As Types, ByVal [Step] As Integer)
Me.New(Message, User, Type)
Me.Step = ([Step] + 1).ToString
End Sub
Private Sub SetTypeIcon()
Select Case Me.Type
Case Types.NormalError
Me.TypeIcon = My.Resources.ErrorIcon
Case Types.SeriousError
Me.TypeIcon = My.Resources.ErrorIcon
Case Types.Warning
Me.TypeIcon = My.Resources.WarningIcon
Case Types.ManualEntry
Me.TypeIcon = My.Resources.ManualIcon
Case Else
Me.TypeIcon = My.Resources.OkayIcon
End Select
End Sub
End Class
Code for inserting an item into the event log:
Inside my main-form class:
Public Sub NewEventLogEntry(ByVal Message As String, ByVal ex As Exception, ByVal Type As EventLogItem.Types, ByVal IncludeProcessStepNumber As Boolean)
If IncludeProcessStepNumber Then
SafeInvokeControl(EventLog, Sub(x)
Dim FirstRow As Integer = x.FirstDisplayedScrollingRowIndex
Dim [Event] As New EventLogItem(Message, My.Settings.SelectedUser, Type, Device.CurrentProcessStep)
[Event].Index = x.RowCount + 1
x.EventList.Insert(0, [Event])
x.DataSource = GetType(List(Of EventLogItem))
x.DataSource = x.EventList
x.SetRowStyles()
If FirstRow >= 0 And FirstRow < x.RowCount Then x.FirstDisplayedScrollingRowIndex = FirstRow
End Sub)
Else
SafeInvokeControl(EventLog, Sub(x)
Dim FirstRow As Integer = x.FirstDisplayedScrollingRowIndex
Dim [Event] As New EventLogItem(Message, My.Settings.SelectedUser, Type)
[Event].Index = x.RowCount + 1
x.EventList.Insert(0, [Event])
x.DataSource = GetType(List(Of EventLogItem))
x.DataSource = x.EventList
x.SetRowStyles()
If FirstRow >= 0 And FirstRow < x.RowCount Then x.FirstDisplayedScrollingRowIndex = FirstRow
End Sub)
End If
If Type < EventLogItem.Types.Warning Then
Dim ErrorBox As New ErrorBox
Dim ErrorBoxThread As New Threading.Thread(AddressOf ErrorBox.ShowDialog)
ErrorBoxThread.IsBackground = True
ErrorBox.Exception = ex
If Type = EventLogItem.Types.NormalError Then
ErrorBox.Type = ErrorBox.Types.Error
ErrorBox.Message = Message
ElseIf Type = EventLogItem.Types.SeriousError Then
ErrorBox.Type = ErrorBox.Types.SeriousError
ErrorBox.Message = Message & vbNewLine & vbNewLine & "This is a serious error and indicates that the program is " & _
"unstable. The source of this error should be corrected before this program is used for anything important."
End If
StopMasterTimer()
ErrorBoxThread.Start()
End If
End Sub
end main-form class snippet
The code that's causing the problem (ch4cp is my namespace. this code resides in a class other than my main-form class):
Inside a serial port device class:
<Runtime.CompilerServices.MethodImplAttribute(Runtime.CompilerServices.MethodImplOptions.Synchronized)> _
Private Shared Sub Port_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles Port.DataReceived
..some code..
ch4cp.NewEventLogEntry("testing")
..some more code..
End Sub
End serial port device class snippet
Any ideas?
Thanks a lot in advance.
It sounds like you are getting a thread exception error in your code somewhere. The threading problem will cause other issues and not appear until you step through the code and look at each line being executed.
Try the following at the beginning of your code block to try to identify the problem.
Control.CheckForIllegalCrossThreadCalls = false

Resources