Check if object exists at list index in Visual Basic - vb.net

I need to check if an object exists at a specified index in a List in Visual Basic.
What I have is
Dim theList As New List(Of Integer)({1,2,3})
If theList.Item(3) = Nothing Then
'some code to execute if there is nothing at position 3 in the list
But when I run the program I get System.ArgumentOutOfRangeException, saying the index is out of range. Of course it is, the entire point was to see if it does or doesn't exist. If "= Nothing" is not the way to check if something exists at that index, what is?
I am in my Application Development class and we are working on Windows Forms Applications using Visual Studio 2017, if any of that matters.

If you are checking a list of objects, you need to change two things.
The way to check for nothing is to use Is Nothing.
You can't even attempt to check items beyond the end of the list, so you must first check that the index you want to use is less than the number of items in the list'
You should change your code to
If theList.Items.Count > 3 AndAlso theList.Item(3) Is Nothing Then
'some code to execute if there is nothing at position 3 in the list
End If
Note the use of AndAlso rather than And in the If statement. This is required because it ensures that the check on item 3 happens only if there are at least 4 items in the list.
Also note that, in the code you posted, the list is a List(Of Integer). An Integer can never be Nothing, so the second part of you check is either not needed or you should be checking for = 0 instead of Is Nothing.

Any time you have a list you can only access members of the list, accessing an imaginary item outside of the bounds will result in the error you specified.. The integer datatype comparison to Nothing is a comparison to 0. IMO this use of nothing is not ideal. If you want nothing you might look at Nullable. Here is some code to take a look at.
Dim theList As New List(Of Integer)({0, 1, 2, 3})
For idx As Integer = 0 To theList.Count - 1 'look at each item
If theList.Item(idx) = Nothing Then
Stop ' item is 0
End If
Next
Dim i As Integer = Nothing ' i = 0
'Dim theListN As New List(Of Integer?)({1, 2, 3, Nothing, 5})
Dim theListN As New List(Of Nullable(Of Integer))({1, 2, 3, Nothing, 5, Nothing})
For idx As Integer = 0 To theListN.Count - 1 'look at each item
If theListN.Item(idx) Is Nothing Then
'item is nothing, not 0
Dim nullI As Nullable(Of Integer) = theListN.Item(idx)
If Not nullI.HasValue Then Stop
End If
Next

Related

Compare two datatables, if anything is different show MessageBox

I have two datatables, one of them is populated when application starts and the other one is populated on button click. How can i check (fastest way) if anything changed in second datatable?
I have tried this but it does not work:
For Each row1 As DataRow In dtt.Rows
For Each row2 As DataRow In dtt1.Rows
Dim array1 = row1.ItemArray
Dim array2 = row2.ItemArray
If array1.SequenceEqual(array2) Then
Else
End If
Next
Next
The problem is that your loops are nested. This means that the inner For Each loops through each row of dtt1 for each single row of dtt. This is not what you want. You want to loop the two tables in parallel. You can do so by using the enumerators that the For Each statements use internally
Dim tablesAreDifferent As Boolean = False
If dtt.Rows.Count = dtt1.Rows.Count Then
Dim enumerator1 = dtt.Rows.GetEnumerator()
Dim enumerator2 = dtt1.Rows.GetEnumerator()
Do While enumerator1.MoveNext() AndAlso enumerator2.MoveNext()
Dim array1 = enumerator1.Current.ItemArray
Dim array2 = enumerator2.Current.ItemArray
If Not array1.SequenceEqual(array2) Then
tablesAreDifferent = True
Exit Do
End If
Loop
Else
tablesAreDifferent = True
End If
If tablesAreDifferent Then
'Display message
Else
'...
End If
The enumerators work like this: They have an internal cursor that is initially placed before the first row. Before accessing a row through the Current property, you must move to it with the MoveNext function. This function returns the Boolean True if it succeeds, i.e. as long as there are rows available.
Since now we have a single loop statement and advance the cursors of enumerator1 and enumerator2 at each loop, we can compare corresponding rows.
Note that the Rows collection implements IEnumerable and thus the enumerators returned by GetEnumerator are not strongly typed. I.e. Current is typed as Object. If instead you write
Dim enumerator1 = dtt.Rows.Cast(Of DataRow).GetEnumerator()
Dim enumerator2 = dtt1.Rows.Cast(Of DataRow).GetEnumerator()
Then you get enumerators of type IEnumerator(Of DataRow) returning strongly typed DataRows.

SSRSS trying to see if the results of a LookUpSet are in a string

I'm trying to list the results of a LookUpSet only if they are in another string.
Initially I used the following to get an array of strings seperated by a semi-colon.
Join(LookUpSet(...),";")
The next stage was to test if the strings were in the main string. I tried nesting the return value inside an IIF statement;
Join(LookUpSet(...,IIF(InStr(Fields!BigText.Value,Fields!Text.Value) > 0, Fields!Text.Value, Nothing),...),";")
I understand this doesn't work because the big string that I am comparing against is in my first dataset and the other is in my second dataset
Next I tried to use InStr on the results of the LookUpSet;
IIF(InStr(Fields!BigText.Value, LookUpSet()) > 0, LookUpSet(), Nothing)
This won't work either so I've tried to create custom code and am nearly there;
Code.CheckSetInString(Fields!BigText.Value, LookUpSet())
The code is below, when debugging I receive "Object reference not set to an instance of an object", but can't see anything that I've failed to declare.
How should I troubleshoot which object isn't declared?
The custom code is below for reference.
Function CheckSetInString(ByVal str As String, ByVal setofstrings As Object())
If setofstrings Is Nothing OrElse Not setofstrings.length > 0 Then
Return Nothing
End If
Dim returnedstrings AS [String]()
Dim i As Integer = 0
For Each item As String In setofstrings
If InStr(1, str, item, 1) > 0 Then
returnedstrings(i) = item
i += 1
End If
Next
Return returnedstrings
End Function
Any advice is appreciated,
Dan

Scripting.Dictionary adding items without using Dictionary.Add - BUG?

Scripting.Dictionary likes to add values for no reason, when you look a value up! Demonstrated with a 30 second example:
Create a new worksheet, and fill in A1:A4 = {1,2,3,4}
Insert a new vba module and add this code
Public Sub test()
Dim rowIndex As Integer
'
Dim dict As Scripting.Dictionary
Set dict = New Scripting.Dictionary
For rowIndex = 1 To 4
dict.Add Trim(Sheet1.Cells(rowIndex, 1).Value), rowIndex
Dim notEvenAddingSoWhyAreYouAdding As Variant
notEvenAddingSoWhyAreYouAdding = dict(Sheet1.Cells(rowIndex, 1).Value)
Next rowIndex
End Sub
Put a breakpoint on Next rowIndex
Run the sub and inspect the value of dict. It now has two values, "1" and 1, as you can see in the image below:
What. The. Hell?!
I realise I have the Trim(...) function in the dict.Add() line, which means there are two distinct keys in use, but why is it adding an extra value when it does a lookup?! That makes no sense whatsoever - now dict.Count would not give the value I would expect.
As you pointed out, you have two different keys, 1 and "1". If you try to access a key that doesn't already exist in the dictionary, it will add it automatically. It is strange behaviour.
I'd use something like this:
If dict.Exists(something) Then
myVariable = dict.Item(something)
End If
You creating one key as a string representing 1 (e.g. "1") and the key's item as the number 1 using the conventional dict.Add <key>,<item>.
Immediately afterwards, you shortcut add another with the number 1 as the key.personally, I shortcut the add/overwrite with dict.item(1) = "abc" but your method works as well.
tbh, I'm not even sure if .CompareMode = vbTextCompare would resolve a strin 1 to equal a numeric 1. In any event, you are currently on a vbBinaryCompare so there is no way it will match.

Make ListView FindItemWithText search anywhere in text

Dim query as String = "Card" ' Just an example, I would call this with a function
Dim itemF As ListViewItem = ListView1.FindItemWithText(query, False, 0, True)
If (itemF IsNot Nothing) Then
itemF.Selected = True
Else
Alert("Nothing was found")
End If
So I am trying to use this code to add a search functionality. The problem is, this works if I'm trying to go from the beginning to the end. But if I wanted to search Card and find W_Card_Brt_Better, I won't get expected results. Though if I search, W_Card, the item will be selected.
ListView.FindItemWithText always searches using BeginsWith pattern - this is by design, as specified in the docs:
ListView.FindItemWithText Method (String) # MSDN
Finds the first ListViewItem that begins with the specified text value.
If you are trying to do anything more custom than that, I suggest you search against the underlying data source instead. You should generally avoid performing any kind of business logic/validation from the UI control directly.
In the business object, you can do a simple for loop, or build a dictionary, to make it search faster, and encapsulate this search functionality within a method or property, which you will call from the UI layer.
FindItemWithText finds the first ListViewItem ... that begins with the specified text value. If you are looking for Items/SubItems which simply CONTAINS the text you have to iterate and search yourself.
To make it like FindItemWithText, specify a start point and Bool for the SubItem search:
Function FindItemContainingText(lv As ListView, searchSubs As Boolean,
StartIndex As Integer,
searchTarget As String) As ListViewItem
If (StartIndex >= lv.Items.Count) Or (StartIndex < 0) Then
Return Nothing
End If
Dim FindMe As String = searchTarget.ToLowerInvariant
Dim subStopper As Integer = 0
For i As Integer = StartIndex To lv.Items.Count - 1
If searchSubs Then
subStopper = lv.Items(i).SubItems.Count - 1
Else
' Item.Text/Label can be read from SubItem(0)
' just look at it if not searching subI's
subStopper = 0
End If
For n As Integer = 0 To subStopper
If lv.Items(i).SubItems(n).Text.ToLowerInvariant.Contains(FindMe) Then
' return the LVI that contains the text
Return lv.Items(i)
End If
Next n
Next
' note!
Return Nothing
End Function

DataRow.SetColumnError(Int32, String) ignores Int32 value and always uses zero

Okay, this is doing my head in. I'm calling SetColumnError() on a DataRow object that has 20 columns, but no matter which ColumnIndex I use it sets the error text on column 0. The MSDN documentation makes it plainly clear that the error text is supposed to be set on the column that the ColumnIndex provides.
I'm trying to set error text on columns 1 and 2 (I normally use constants when doing this, I have just used the integers in this example code for simplicity). Why does the error text appear on column 0 and what should I be doing to get the text to show on columns 1 and 2? I'm not receiving IndexOutOfRangeException.
Here is the code I'm having trouble with.
Public Sub ValidateRows()
For Each dgvRow As DataGridViewRow In Me.DataGridView1.Rows
If dgvRow.DataBoundItem IsNot Nothing Then
Dim rowView As DataRowView = dgvRow.DataBoundItem
Dim rowData As MyDataSet.DocumentRow = rowView.Row
rowData.ClearErrors()
If rowData.Revision = rowData.Revision_old Then
rowData.SetColumnError(1, "You must change the revision")
End If
If rowData.InternalRevision = rowData.InternalRevision_old Then
rowData.SetColumnError(2, "You must change the internal revision")
End If
End If
Next
End Sub
I'm not sure, but I think the number of the column must be of the type "DataColumn".