Comparing arrays in VB.NET - sql

Let me provide a little detail to explain what I'm trying to accomplish before I get into the nuts and bolts of the question.
I've got two data sources - one is a SQL Server containing part numbers, descriptions, etc. The other is a CAD system that does not have a database in a traditional sense. What I'm trying to do is read out the bill of materials from the SQL Server and compare it to the CAD assembly drawing to ensure that the CAD system contains the same information as the SQL Server.
Getting the data from the SQL Server is fairly straight forward. I query the database and populate a datagrid. Done. Quick. Easy.
Getting the data from the CAD system is a little more involved. I have to load the assembly drawing to get a listing of all the component parts and then load those individual drawings to pull the "Part Number" property from the drawing. This is a somewhat time consuming and slow process (unfortunately) since each of the files must actually be accessed. I load those properties into an array (I guess a list might be more efficient).
So now I have a datagrid and array with part numbers. I need to compare them and colorize the grid accordingly. The grid should remain transparent if the part exists in both, color the row yellow if it only exists in the grid, and add a row colored red if only in the array.
As best I can tell, this means looping through the array on each line of the grid. The thought process is this:
Default the grid to yellow rows.
Loop through the grid and loop through the array to compare. If a match is found, make the row transparent and delete the element from the array.
After step 2 is completed, the array should only contain elements that are not found in the grid. Resize the array to remove the empty elements.
Add the elements of the array to the grid and color those new rows red.
The problems with this logic is that it seems expensive from a performance standpoint. Surely there is a better method? Also, if I modify the grid in some manner (like a resort) I have to go through the process again. I'd really appreciate some advice on this.
Thanks!
Note: written in Visual Studio 2005.

You could load the data from the CAD system in a dictionary (indexed by part number). Then you could go through the grid and check if it exists in the dictionary, which is a fast operation ( O(1) ). You could do exactly as you say, remove the found elements in the dictionary and add the remaining elements in the datagrid.
Here's some code for creating and using a dictionary (used C# style comments to preserve formatting):
//First argument is your key type, second is your item type
Dim cadParts As New Dictionary(Of Integer, Part)
//Add items to the parts dictionary
For Each part As Part In cadPartsArray
cadParts.Add(part.PartNumber,part)
Next
//Check if a part exists
Dim partNumber As Integer = 12345
If cadParts.ContainsKey(partNumber) ...
//Remove a part
cadParts.Remove(partNumber)
//Go through the remaining values
For Each part As Part In cadParts.Values ...
Edit:
1) Yes, if your key (here, part number) is a string, then a Dictionary(Of String,...) would be used.
2) I assumed you had a class named Part, which contained some information about a part. If you just have a part number, and no other info, then you could create a Hashset instead. It is basically the same as a dictionary, but with this structure the value is also your key. You would create a hashset like this:
Dim cadParts As New Hashset(Of String)
I won't go through code examples because it is very close to the Dictionary. ContainsKey becomes Contains, and Add accepts only one argument (which would be your part number here).
3) Yes, loop through them and add them to the hashset.

If part number is unique (it is not repeated in the values you want to search for) then you can use sorted dictionary. Then remove the duplicates and use remaining items.
To compare you can use part number as follows:
if(dictionary.ContainsKey(partNumber))
dictionary.Remove(partNumber)
end if

Given that there will only be a certain number of rows of the data grid visible at one time, it might be quicker to implement some code in the OnPaint method (I'm a little rusty here so apologies if that isn't exactly right) for each of the rows which checks against the array of part information and sets the background color of each row as it becomes visible, perhaps even marking each row as having been processed so the operation doesn't need to be repeated. There might be an initial performance gain here over processing all of the rows at once.
Of no real relevance; but from previous experience this sounds like you are interfacing with AutoDESK Inventor files?

Another solution could be to implement the IComparable(Of T) interface
That would require for you to build a class that you would use for both situation.
Public Class Item
Implements IComparable(Of Item)
Private _Description As String
Public Property Description() As String
Get
Return _Description
End Get
Set(ByVal value As String)
_Description = value
End Set
End Property
Private _PartNo As Integer
Public Property PartNo() As Integer
Get
Return _PartNo
End Get
Set(ByVal value As Integer)
_PartNo = value
End Set
End Property
Public Function CompareTo(ByVal other As Item) As Integer Implements System.IComparable(Of Item).CompareTo
' Your rules for comparing content for objects
If other.PartNo <> Me.PartNo Then Return 1
If other.Description <> Me.Description Then Return 1
' Return 0 if the object are the same
Return 0
End Function
End Class
Here is a small test that works with upper implementation.
Dim item As New Item
item.Description = "Desc"
item.PartNo = 34
Dim item2 As New Item
item2.Description = "Desc"
item2.PartNo = 35
Dim item3 As New Item
item3.Description = "Desc"
item3.PartNo = 36
Dim listFromDatabase As New Generic.List(Of Item)
listFromDatabase.Add(item)
listFromDatabase.Add(item2)
If listFromDatabase.Contains(item2) Then
MessageBox.Show("item2 was found in list")
End If
If Not listFromDatabase.Contains(item3) Then
MessageBox.Show("item3 was NOT found in list")
End If
Hope it helps a bit,
- Dan

Related

Getting the Index of an Added ListView.Item

So all I am trying to do is get the index of an Item Programmatically added to a ListView from a Database.
Example:
Index = lvwNames.Items.Add(Player)
Player is a Class that uses Get/Set methods for ID, FirstName, and, LastName.
I have googled my heart out to no avail.
EDIT
So I had a GetInformation() procedure that populated my Player Class. So I was helped by a personal friend, I just needed to pass in my ListViewItem ByRef into there and I can grab the data I needed (SUCH A DUMMY!). Still having an issue with the Index. I would like to have the newly added item selected after it is added.
Getting the Index of an Added ListView.Item as per the title.
Method A: Look at the count
lvwNames.Items.Add(Player.ToString())
Dim ndx = lvwNames.Items.Count-1
Method B: Use an object
Dim lvi As New ListViewItem(Player.ToString())
lvwNames.Items.Add(lvi)
Dim ndx = lvi.Index
However, Player is an Object and there is no matching Add overload, with Option Strict (which all good code should use). The best you can do is pass it as ToString() as shown.
The result will be something like WindowsApplication1.Player unless your class overrides ToString(). In no case will the ListView parse it to put the properties ("Get/Set methods") as sub items which sort of sounds like what you expect.
For that, a DatagridView would be a better choice using a List(Of Player). Each property could be mapped to its own column.

Excel Indirect() type function For VB.net?

In excel I could say =INDIRECT("A" & G3) where G3 had a value of 4 and my cell would then refer to A4. What I am looking for is a similar kind of function for VB.net.
Is there a way to refer to a different variable based on a variable. EG. first pass I was to refer to txtJobNum1, txtBatNum1, and lblBat1. on pass two txtJobNum2, txtBatNum2, and lblBat2. If it were only a few, 3-4 maybe, it wouldnt be bothersome, but it's 50. The best I have come up with now to work around is build a class that holds references to those objects and make an array of that class. Below is an example table showing What I want to make with a given input number.
You can see how if I could make use of an "INDIRECT" function It could potentially shrink down to a 5-6 line loop instead of 200 lines of just variable assignments.
my concept of how it would work
BatchGroups(<NUMBER>).Label = lblBatNum<NUMBER+1>
0 BatchGroups(0).Label = lblBatNum1
0 BatchGroups(0).Number = txtBatNum1
0 BatchGroups(0).Quantity = txtQtybat1
0 BatchGroups(0).JobNumber = txtJobNum1
1 BatchGroups(1).Label = lblBatNum2
1 BatchGroups(1).Number = txtBatNum2
1 BatchGroups(1).Quantity = txtQtybat2
1 BatchGroups(1).JobNumber = txtJobNum2
2 BatchGroups(2).Label = lblBatNum3
2 BatchGroups(2).Number = txtBatNum3
All of the controls are stored in the Controls collection of their parent, and the controls in the Controls collection are addressable by name, like this:
Dim theControl As Control = Me.Controls("txtJobNum" & theNumber)
(Where Me is the Form) If the controls are in some other container control, such as a panel, you can get to them through that container control's Controls property, for instance:
Dim theControl As Control = MyPanel.Controls("txtJobNum" & theNumber)
However, having 50 some-odd controls like that sounds like it may be a bad design anyway. It may be better to consider having a grid, or an editable list of some sort. If you must have all of the separate controls like that, it would probably be better to dynamically load them in a loop, and then store references to them in a list of BatchGroup objects as you were thinking. It would be much easier to to that in a loop because you'd only write the code once rather than separately for each batch group.
More generally, the term in .NET for what you are asking is called "Reflection". However, using reflection can cause your code to be more brittle and it is also not very efficient, so, when there are other alternatives, is is the case here, it is usually best to take one of them, rather than resorting to reflection.
Create a dictionary of your objects by Name. Then use TryGetValue to retrieve. In this case it would expect a String value as a Key, so you can have a custom naming scheme, which maps 1-to-1 onto your controls list.
Read more about a Dictionary class on MSDN:
Dictionary(Of TKey, TValue) Class
You could use .Controls of the parent container, but then your controls could be nested in each other, so you'd have to use recursion or linearize into flat list, either adds complexity and maintainbility effort, and reduces performance. Dictionary is the fastest, especially if your Key is a string.

Get Values from Listbox for if functions

Hey guys very new here.
Have a listbox that gets account names from a specific game server using this command line
Dim apikeyinfo As APIKeyInfo = api.getApiKeyInfo()
lstbxCharacters.DataSource = apikeyinfo.Characters
this code gets all the characters in a single account by displaying it in a listbox.
Now i would like to reference a character from the lisbox but not sure how
Any method such as Listbox.Get to get the value and compare it with something else?
Thanks
you can try something like
lstbxCharacters.SelectedItem
Update
To read the data from the listbox I think there are multiple ways (Assuming that it is readable).
-> Usually listbox display strings, so it should work to read to a string variable
Dim a_string as Strin = lstbxCharacters.SelectedItem
also you may like to add a small check before, assuring that an Item is currently selected:
If lstbxCharacters.SelectedIndex < 0 then return
This jumps out of current sub if no item is selected
And finally, to read the first entry, you can also do it this way:
a_string = lstbxCharacters.Items(0)
if it returns objects, then instead of accessing the object directly, it may work to do
a_string = lstbxCharacters.Items(0).ToString
(most objects allow a .ToString() Function )
Here two ideas for different solutions:
As a user commented, you could access the DataSource directly + the information which listIndex was selected. But if you do so, then maybe it is more easy (if you need to access it anyways, to go with solution 2)
Create a variable of type list(Of some_object) and fill it with the data from the datasource. It will take some time to do this, but if you define for the class some_object a function ToString, then you can fill all objects directly to the lstbxCharacters, and access them without any worries, by doing CType(lstbxCharacters.SelectedItem, some_object)
Update 2
If with reference you mean to access further information from the datasource, then you need to build some kind of query, or set the content of the listbox in relation to another control that shows the database content (in that way the listbox lstbxCharacters would act like a filter)

Cannot access property value on listbox item

I have a listbox with multiple selection activated and i am trying to read the different selected values
I have tried many snippets, here are the latest two :
For i = 0 To ListBox1.SelectedIndices.Count
MsgBox(ListBox1.Items((ListBox1.SelectedIndices(i))).value)
Next
For i = 0 To ListBox1.SelectedItems.Count
MsgBox(ListBox1.SelectedItems(i).value)
Next
For some reason with any approach i choose i can't read any item's value
My listbox is data bound, so i found out on a forum that making it public might fix the issue but it did not
I am hesitating as even Intellisense doesn't show much info, all i get is :
Equals
GetHashCode
GeType
ReferenceEquals
ToString
Any ideas where i went wrong?
Thanks in advance
Edit:
I'm not a big fan of using the Data GUI tools in VB. That said, I think what you are looking for is this:
For Each dvRow As DataRowView In Me.Listbox1.SelectedItems
MessageBox.Show(dvRow("Id").ToString)
Next
If you were interested in the Last Name field, change "Id" to "Last Name".
Also, I used MessageBox.Show instead of MsgBox. MsgBox is a leftover from VB6.
When you use a databound ListBox, the items aren't ListBoxItem objects (which you would expect to have Text and Value properties). Rather they are the type from the data source. The Items collection (and its variations, such as SelectedItems) is defined as a collection of objects, and the runtime type is obtained from the data source. Have you tried something like this?
For i = 0 To ListBox1.SelectedItems.Count
MsgBox(ListBox1.SelectedItems(i).ToString())
Next
In the comments, you indicated that the data source is a DB object containing String objects. If you have a collection of classes, such as Person, you can get it this way:
For i = 0 To ListBox1.SelectedItems.Count
MsgBox(DirectCast(ListBox1.SelectedItems(i), Person).FirstName)
Next
You should also get the same results with a For Each, as long as the selected items collection isn't changed while you are looping.
For Each p As Person in ListBox1.SelectedItems
MsgBox(p.FirstName)
Next
NOTE: This is untested code, as I'm not in front of Visual Studio at the moment.
EDIT: I see from the screenshot that the Value Member is set to a property named Id. If that is a uniqueidentifier column from the database, then the runtime type in the ListBox should be Guid.

VB.Net Copy a list to store original values to be used later

I have a WPF form that takes a list of objects that have locations and sizes and plots them on the canvas. I'm currently trying to implement an undo button that will throw out all the changes that have been made to the positions of the objects and revert back to the original collection that was retrieved when the form loaded.
As it stands now I go out to the database on the load of the form and get all the objects that will need to be displayed then assign the list that is returned to two seperate collections. The problem that comes up is that the two collections are actually pointers to the original collection and whenever one is changed the changes are reflected in the second collection.
Is it possible to copy a list of objects so that changes made to one collection won't affect the secondary collection?
So far I've tried simply using the assignment operator, passing the source collection into a function byval and scrolling through each element of the list manually adding it to the second collection and using linq to get all the objects from the original list and pushing the results to a separate temporary list and assigning the second collection to the temporary list.
I feel like I'm overcomplicating the issue but almost all the places I've come across while googling say that this behavior is by design, which I understand but it seems like this would be a fairly common idea.
Here's a function I have used before to make "Deep" copies of objects:
Public Function DeepCopy(ByVal ObjectToCopy As Object) As Object
Using mem as New MemoryStream
Dim bf As New BinaryFormatter
bf.Serialize(mem, ObjectToCopy)
mem.Seek(0, SeekOrigin.Begin)
Return bf.Deserialize(mem)
End Using
End Function
This is kind of a low level approach compared to some of the other answers, but allows you to deep copy any object. I've used it successfully in a situation similar to yours where I needed a deep copy of an array.
Simply assign all items of the listA to listB using this code
For Each elm In ListA
ListB.Add(elm)
Next
There was another answer that was since deleted that said to use var copy = list.ToList(); to get a copy of the list. This will work with the following caveat: Both lists will still reference the same objects, so any changes to those objects will reflect in both lists. As long as you're only changing the order of the objects in the list, this solution is perfectly viable.
You would have to create a new list and add copies of the items in list1 to it. You could do this using object initialization, e.g.
Dim list2 = (From item in list1
Select New ItemType With {.Property1 = item.Property1, .Property2 = item.Property2}.ToList()
An alternative would be to add a Copy constructor to ItemType
Public Sub New(item as ItemType)
Me.Property1 = item.Property1
Me.Property2 = item.Property2
End Sub
And your list copy could be simplified to
Dim list2 = (From item in list1
Select New ItemType(item)}.ToList()
Just beware that if any of the properties of your ItemType are references, you would need to make copies of these objects also. (This is known as a Deep Copy)