IndexOutOfRangeException on Datatable - vb.net

I have an application where a DAQ system samples data at about 1 kHz which is then written to a DataTable.
The code block below shows part of the code which adds data to the datatable.
Public Sub AddTimeLoadData(DateTime As DateTime, DataPointNo As Integer, ClampForceN As Double, _
TransverseForceN As Double, TransverseDispMm As Double, NumLoadCycles As Integer)
Try
_tblTimeLoadData.AddtblTimeLoadDataRow(NumLoadCycles, TransverseDispMm, TransverseForceN, ClampForceN, DataPointNo, DateTime, _TimeLoadTestRow)
_timRaiseDataAdded.Start()
Catch ex As Exception
Throw New Exception("Time/load data could not be added: " & ex.Message, ex)
End Try
End Sub
Updating the GUI each time AddTimeLoadData is called would be unnecessary, so instead I start a System.Timers.Timer. At the Elapsed event of the timer, I raise the TimeLoadDataUpdated event.
In the GUI another class is listening to this event, and updates the chart. This works good most of the times, but occasionally I get an IndexOutOfRangeException when reading from the data table.
It is pretty clear that this has something to do with synchronization, but I have not yet figured out exactly what might be the issue.
Looking at the code, the index 'i' would not really be able to go out of range.
One idea that came up is that if the for loop reaches last row at the same time as it is being created I might have a problem so I could just try updating the for loop to
For i As Integer = s.Points.Count To sender.tblTimeLoadData.Count - 2
Or maybe there is something else that I have foreseen!?

VB keep in memory the "To" part of the for loop.
Dim c As Integer
c = 3
' This will print 0, 1, 2, 3
For i As Integer = 0 To c
c = 10
Console.WriteLine(i)
Next
You might want to consider using a while loop instead.
Dim c As Integer
c = 3
Dim i As Integer = 0
' Will print 0 to 9
While i < c
c = 10
Console.WriteLine(i)
i += 1
End While
Or properly synchlock everything because even between the While/For line and the Rows(i) line, there is a chance the item will be deleted.
Also, if your timer is there to update the UI. You should look into remove all business rules out of it. Have your ds already populated with the proper data point before doing anything on the UI.

Related

inquiry Data from data table to datagridview with filters

i have datatable "dataitems" contain 98000 Rows And Datagridview have 11000 row
want to add the quantity form the datatable to the datagridview every item in it's row in the datagrid view
i am using this code but its take too too too much time to run and sometimes stop responding i need ideas to make it faster to run
Dim dt As New DataTable = najrndataset.dataitems
Dim Total As Integer
for x = 0 to datagridview1.rows.count -1
Dim b = datagridview1.rows(x).cells(1)
Dim c = 3
Total = (From r As DataRow In dt.AsEnumerable
Where r.Field(Of String)("Item_Number") = b And r.Field(Of Integer)("SOP_Type") = c
Select r.Field(Of Integer)("Quantity")).Sum
datagridview1.Rows(x).Cells(0).Value = Total
next
Hopefully you appreciate that if you have 11000 rows in a datagridview (terrible idea, by the way) and 98000 rows in a datatable, and you're running a loop that searches the table for each of the 11K items, and it does this by starting at number 1, and searching 98000 items for it, then going to 2 and searching 98000 items for it til you reach 11000 and searching 98000 items for it... At the end of the operation you're going to have performed 11000 x 98000 operations.. i.e. you're going to have performed 1.078 BILLION operations. This is why "its take too too too much time to run and sometimes stop responding" :)
You can speed this up by using a dictionary to track the sums, and it'll probably be fastest to index the 98000 items then set the dictionary from them:
Dim d as New Dictionary(Of String, Integer)
For Each di In najrndataset.dataitems.Where(Function(r) r.SOP_Type = 3)
If Not d.ContainsKey(di.Item_Number) Then
d(di.Item_Number) = di.Quantity
Else
d(di.Item_Number) += di.Quantity
End If
Next di
Then edit your grid (ugh; this isn't how datagridview are supposed to be used)
datagridview1.SuspendDrawing()
For x = 0 to datagridview1.rows.count - 1
Dim b = datagridview1.rows(x).cells(1)
Dim s As Integer
If d.TryGetValue(b, s) Then
datagridview1.rows(x).cells(0) = s
End If
Next x
datagridview1.ResumeDrawing()
You could also have a logic of "loop over the datagrid, putting 11000 items in the dictionary, loop over the datatable accumulating sums into the dictionary if the keys are present in the dictionary, loop over the datagriview putting the sums into the datagridview".
Not an answer, but too long for a traditional comment. I'm working to provide an answer separately.
I can't tell you how many times I've seen code like this:
Dim someVariable As New SomeObject
someVariable = someMethodToReturnSomeObject()
This pattern is bad code!
The New operator in the first line tells the compiler you want to allocate memory and run the constructor for your type. However, the next line assigns a new object... it throws away and forgets the memory and constructor work from the previous line. That's wasteful.
Instead, you want this:
Dim someVariable As SomeObject = someMethodToReturnSomeObject()
Worse, this speaks to a profound lack of understanding of the difference between a reference and an object, and what the computer is doing with your code in memory. This is a core principle of how many programming environments work. Code like the first example gives me big doubts of the capability of the programmer who writes it.

InvalidArgument=Value of ' ' is not valid for 'index' (' ' inside number)

This is my first question. I can't solve this error for 2 weeks.
In order to solve the problem signed up.
This is my vb code.
Try
For i As Integer = 0 To ListBox1.Items.Count - 1 Step 1
For j As Integer = 0 To ListBox2.Items.Count - 1 Step 1
If ListBox1.Items(i).ToString().Equals(ListBox2.Items(j).ToString()) = True Then
ListBox1.Items.RemoveAt(i)
End If
Next
Next
Catch ex As Exception
MsgBox("LOAD ERROR: " + ex.Message, vbCritical, "ERROR")
End Try
error :
InvalidArgument=Value of '20' is not valid for 'index'(' ' is varient.)
Project has no problems except for this error
Try this:
Dim items = ListBox1.Items.Where(Function(item) ListBox2.Items.Contains(item)).ToList()
For Each item in items
ListBox1.Remove(item)
Next
When I run your code, I receive a different exception, argument out of range...and that is caused by deleting items from an indexed collection while you're iterating through it. For example, let's say listbox1 has 10 items in it. If you find item number 1 in listbox2 and delete it, now you only have 9 items left in listbox1. The problem is, when you entered your loop, you told it to loop 10 items, and it will still try to do that. At some point, if any items are deleted, this loop will throw an exception...so you will need to change that sooner or later. To mitigate this, step through the collection that you'll be deleting items from backward like this:
For i As Integer = ListBox1.Items.Count - 1 to 0 Step -1
When I run the code with the change shown above, it works as intended and removes the duplicate items from listbox1. Unfortunately, I was unable to reproduce your invalid argument exception. It's odd to see that because usually that exception likes to pop up when using listviews, not listboxes. Perhaps you can edit your post and add a screenshot of the data in your listboxes so it's easier for other people to troubleshoot.
As you remove items from ListBox1 the total item count will decrease (obviously), however the For loop does not respect that. A For loop will only have the right side of To set once, which is done prior to the first iteration.
What you're currently doing is actually equal to this:
Dim a As Integer = ListBox1.Items.Count - 1
For i As Integer = 0 To a Step 1
Dim b As Integer = ListBox2.Items.Count - 1
For j As Integer = 0 To b Step 1
...
Next
Next
The fix for this is simple; create a variable that holds how many items you have removed, then, in an If-statement, check if i is more or equal to the current item count subtracted with how many item's you've removed. If so, exit the loop.
Dim ItemsRemoved As Integer = 0
For i As Integer = 0 To ListBox1.Items.Count - 1 Step 1
If i >= ListBox1.Items.Count - ItemsRemoved Then Exit For
For j As Integer = 0 To ListBox2.Items.Count - 1 Step 1
If ListBox1.Items(i).ToString().Equals(ListBox2.Items(j).ToString()) = True Then
ListBox1.Items.RemoveAt(i)
End If
Next
Next
For future reference you should also always remove/comment out the Try/Catch-statement so you can see where the error occurs and get more detail about it.
The point of my answer is that when you iterating any collection, you should NOT try to modify this collection. In for-loops you run into such trouble. But you can iterate using while-loop with no issues
Try
Dim index As Integer = 0
While index < ListBox1.Items.Count '!! this code based on fact that ListBox1 item Count changes
For j As Integer = 0 To ListBox2.Items.Count - 1 ' <- this is ok because ListBox2 doesn't chage
If string.Equals(ListBox1.Items(index).ToString(), ListBox2.Items(j).ToString()) Then
ListBox1.Items.RemoveAt(index)
Continue While ' no index increase here because if you remove item N, next item become item N
End If
Next
index += 1
End While
Catch ex As Exception
MsgBox("LOAD ERROR: " + ex.Message, vbCritical, "ERROR")
End Try
This is good example of how things actually work. And it shows few techniques
I just selected Build-->Clean solution and it cleaned out the bad elements. This occurred as a result of adding and deleting menu items, without deleting the subroutines of the deleted menu items. As soon as I cleaned the solution, and then ran the project, the error was gone.

Nested loop with same variable

Is that possible for me to do nested loop in VB with same counter
The code is somehow like this
For a As Integer = 1 to Console.ReadLine
For a = 1 to a
Console.WriteLine("*")
Next
Console.WriteLine()
Next
The program is designed for drawing a triangle of * with just a single variable at all
VB just disallow me to use a in nested loop again
Error: ...Variable 'a' is alreay used by a independent loop.
I have my own usage, can only use 1 variable.
What changing the second FOR loop to a WHILE loop?
For a As Integer = 1 to Console.ReadLine
Do While a <=5
Console.WriteLine("Line: " & a)
Exit Do
Loop
Next
Here's a different idea. You may consider splitting your integer variable into 2 parts 16-bit parts, keep user's input in the upper 16-bits, and current iteration value in the lower 16-bits (you'll need to use WHILE instead of FOR).
In fact, what you need is to start your inner counter by the value of a, if I've understand. And what you are doing is create another loop inside starting by 1.
For a As Integer = 1 to Console.ReadLine
For b As Integer = a to 5
Console.WriteLine("Line: " & a)
Next
Next
You cannot declare another variable with the same name in the same scope. But the inner loop is in the same scope as the outer loop. That's why you get that compiler error.
You can use a different name:
Dim i As Int32
If Int32.TryParse(Console.ReadLine, i) AndAlso i > 0 Then
For a As Integer = 1 To i
For aa = 1 To i
Console.WriteLine("Line: {0} {1}", a, aa)
Next
Next
End If
To draw a triangle, as you describe, you need two variables, like this:
For a As Integer = 1 to Console.ReadLine
For b As Integer = 1 to a
Console.Write("*")
Next
Console.WriteLine()
Next
If you used the same variable in the inner loop, the inner loop would change the value of the variable, which in most cases would not be what you want, and in all cases would be incredibly confusing. For that reason, VB forces you to use a different iterator in each nested For loop.

How to delete empty datagridview cells on import

Currently I have my program hiding blank or empty datagridview cells. I want to find a way to delete these cells entirely. The reason being, after the blank cells were hidden they would reappear after going through some of my other validations. These validations checked to see if the cells contained any invalid input such as negative numbers,non-numeric input and blank cells. If they contained any of the above they would be populated with default values, thus making my hidden cells reappear. Hopefully if there is a way to delete these cells they won't have a change of getting filled with default data. I've found the below code on MSDN but it doens't seem to work properly for whatever reason. Also I'm using the DATABINDINGCOMPLETE event. I'm not sure if there is another event that would work better for this situation. I greatly appreciate any help you may give!
Private Sub DataGridView1_DataBindingComplete(sender As Object, e As DataGridViewBindingCompleteEventArgs) Handles DataGridView1.DataBindingComplete
Dim i As Integer = 0
Dim mtCell As Integer = 0
For Each row As DataGridViewRow In DataGridView1.Rows
For j = 1 To row.Cells.Count -2
If row.Cells(j).Value Is DBNull.Value Then
mtCell += 1
End If
Next
If mtCell = row.Cells.Count Then
DataGridView1.Rows.RemoveAt(i)
End If
i += 1
mtCell = 0
Next
end sub
There are various problems with your code. Here you have an improved version which should work without any problem:
Dim mtCell As Integer = 0
Dim row As DataGridViewRow = New DataGridViewRow()
For rowNo As Integer = DataGridView1.Rows.Count - 2 To 0 Step -1
row = DataGridView1.Rows(rowNo)
Try
For j = 0 To row.Cells.Count - 2
If row.Cells(j).Value Is Nothing OrElse row.Cells(j).Value Is DBNull.Value Then
mtCell += 1
End If
Next
If mtCell = row.Cells.Count - 1 Then 'I understand that you want to delete the row ONLY if all its cells are null
DataGridView1.Rows.RemoveAt(rowNo)
End If
mtCell = 0
Catch ex As Exception
Exit For
End Try
Next rowNo
First thing, it is better to iterate the Collection "backwards" when deleting in order to avoid problems (example: 3 rows; you delete the first position and the loop goes to the second one; but after the deletion all the rows "move up" and thus the second position is now occupied by the third row -> you would skip the second row and, eventually, iterate beyond the limits of the Collection). DBNull.Value is quite restrictive; not sure if it is working fine under your specific conditions, but better complementing it with Nothing. You cannot affect the item being iterated in a For Each loop (unlikely in a normal For one); in this case you are affecting it indirectly but just to make sure, better relying on a normal For loop. You are iterating through rows but you are not deleting these rows, but the ones defined by a counter (i) which is not necessarily related to the current row number, better getting rid of it. Lastly I have included a try...catch to make sure (that you don't access an inexistent position).

Inspecting a Word mail merge data source programmatically

I want to iterate over all rows of a MS-Word mail merge data source and extract the relevant data into an XML.
I'm currently using this code:
Imports Microsoft.Office.Interop
Do
objXW.WriteStartElement("Recipient")
Dim objDataFields As Word.MailMergeDataFields = DataSource.DataFields
For Each FieldIndex As Integer In mdictMergeFields.Keys
strValue = objDataFields.Item(FieldIndex).Value
If Not String.IsNullOrEmpty(strValue) Then
strName = mdictMergeFields(FieldIndex)
objXW.WriteElementString(strName, strValue)
End If
Next
objXW.WriteEndElement()
If DataSource.ActiveRecord = LastRecord Then
Exit Do
Else
DataSource.ActiveRecord = Word.WdMailMergeActiveRecord.wdNextDataSourceRecord
End If
Loop
And it turns out to be a little sluggish (About 1 second for each row). Is there any way to do it faster?
My fantasy is finding a function like MailMergeDataSource.ToDatatable and then inspecting the datatable.
Any time you're iterating through something row by row, and then doing some kind of processing on each row, is going to get a little slow.
I would be inclined to approach this problem by having a step before this which prepared the mdictMergeFields collection so that it only contained elements that were not 'null or empty', this will mean you won't have to check for that on each iteration. You could do this in process, or 'sneakily' in the background while the user is doing something else.
The other thing to try (might help!) is to change the "Do... Loop" block so that you're not checking at the end of each imported row whether or the record is the 'last record'. Instead, get a count of the records, and then compare the current index to the knowm maximum (which might be quicker)
I.E.:
Dim i, x as Integer
i = ActiveDocument.MailMerge.DataSource.RecordCount
Do While x < i
objXW.WriteStartElement("Recipient")
Dim objDataFields As Word.MailMergeDataFields = DataSource.DataFields
For Each FieldIndex As Integer In mdictMergeFields.Keys
strValue = objDataFields.Item(FieldIndex).Value
If Not String.IsNullOrEmpty(strValue) Then
strName = mdictMergeFields(FieldIndex)
objXW.WriteElementString(strName, strValue)
End If
Next
objXW.WriteEndElement()
x += 1
Loop
I don't really work with the Office Interop much, but hopefully this might offer some assistance! Post back, let me know how it goes.
/Richard.