Putting a Copy of an Object into A Collection - vba

I have a loop which runs over 100,000 times where I read a text file which gives my information to create Service objects.
For searching later on, I need to put these Service objects into a collection (with defined keys).
The problem I'm facing is that I have a this_service variable (Service object) which I add at the end of the loop to the all_services collection - but since the add method is by reference, I end up having a collection with 100,000 duplicates of the final object I create.
Is there no way that I can add the custom object to the collection ByVal?

Related

Showing the same image repeatedly in a DataGridView without memory over-use

I think I'm doing something very stupid, but can't find an alternative!
I've got a list of files that my form is going to process, and am using a datagridview to show them all and my progress through the list. I've got a "waiting" "processing" and "done" image resource, and throw them all into a DataGridViewImageColumn.
For Each file As String In folderListing
DataGridView1.Rows.Add(file, My.Resources.WaitingImg)
Next
Problem is, it seems to use a little tiny chunk of RAM for each of those images, and when I have 5,000 of them in a test run it used 4Gb...
How do I do this so it's only loading the resource once but displaying it in hundreds or thousands of places?
Don't use any property of My.Resources repeatedly. Every time you use such a property, it extracts the data from the resource and creates a new object. In any particular context, get the property once, assign to a variable and then use that variable multiple times, e.g.
Dim waitingImg = My.Resources.WaitingImg
For Each file As String In folderListing
DataGridView1.Rows.Add(file, waitingImg)
Next
If you need to change the images, assign the resource properties to fields first and then use those fields to set the cell values as and when required.

Iterate over COM collection without foreach or .Item()

I need to iterate over a COM collection within Matlab. In VB I could use For Each item In Collection; in Python I could use for item in Collection. But unfortunately such constructs are not available in Matlab, which uses a simple loop, something like this:
for index = 1 : Collection.Count
item = Collection.Item(index);
% now do something useful with the item
end
This works well in general. But in my particular Collection, .Item() only accepts a string (the item's name), not a numeric index. So the code snippet above fails, because I do not know the item names in advance, before iterating over the loop.
In VB I could do item = Collection(index), but this fails in Matlab, since Matlab understands this as "item is Collection number index". In other words, Collection(2) tries to access the 2nd collection, not the 2nd item within Collection. This is why we typically use .Item(index) in Matlab, but as I said above, this fails in my particular collection where Item only accepts strings.
So my question is: can I iterate over a COM collection without a foreach construct or an Item(index) method?
I can tell you how to do it in COM, but I have no knowledge if Matlab can do this.
There is a special property DISPID_NEWENUM, with the name _NewEnum. Retrieving this property gets a IUnknown interface. You can get an interface to an IEnumVARIANT via QueryInterface.
So simply ask for this interface. Then call the Next method and retrieve VARIANT by VARIANT from your enumeration until the return value is no longer S_OK. Usually S_FALSE is returned when the end of the collection is reached. I always use a count of 1 when I call Next.
So if MatLab can use other interfaces than IDispatch, it should be possible.

StringList in VBA

A function I'm using in VBA returns a variable ErrorTags which has to be defined as type StringList. In Debug watch if I watch ErrorTags I can see string variables returned as Item 1, Item 2 etc. However I can't find any way in the code to access these variables. Eg I've tried
Test = ErrorTags.Item 1
and
Test = ErrorTags.Item_1
without any success
What is the correct format?
Assuming that ErrorTags is indeed a collection, you have to think of it like this:
ErrorTags is an object that holds all the values associated with it (the Items you see in the watch list). That object has a default property, Item. That property can return all the values stored in the object, but only one at a time. Which will be returned depends on the parameter you pass to the property:
ErrorTags.Items(1)
This will return the first item stored in the collection. The parameter serves as an index by which you can address all items in the collection. Some collections in VBA also accept strings as an index, but that depends on the type of collection. In Excel, for example, the Sheets property of a workbook returns a collection (containing all the worksheets in an Excel file). That collection does not only take numbers as an index but also works with the sheet name as an index.
As said above, the Item property of a collection is the default property. In VBA, an object can have a default property, which means, it can be omitted. To access the first item from your collection can therefore also be written like this:
ErrorTags(1)
Now, you probably want sometimes to access not just one item but all of them in a loop. You could do this with a For loop using the counter as an index for the collection.
But there is a better way:
Dim et As Variant
For Each et In ErrorTags
' now you have one item stored in et, do with it as you like
Next et
Using a For Each loop you can iterate over all items of a collection (it also works for arrays). For each pass the current item will be written to the variable et. Use that variable instead of ErrorTags.Items() as long as you are in the loop. Assuming the items in the ErrorTags collection are simple strings you could output all of them to the immediate window if you put Debug.Print et in the body of the loop.
If the items in your collection are objects themselves, then you should change the declaration of the variable et to that object type:
Dim et As ErrorTag ' assuming that is the object type
More information about the For Each loop can be obtained from the documentation.

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)

VBA object's Collection property contains data but won't let me access it (get EOF/BOF error)

I have a class module in an Excel project that has a property called Marks, this is a VB Collection and has a public get property (but no set or let).
I can assign values to this without any problem:
myObject.Marks.Add 3.14159
However, when I try to do something with this object (e.g., iterating through it), I get an error:
3021: Either BOF or EOF is true or the current record has been deleted
However, if I try myObject.Marks.Count, it shows that the collection contains the amount of data that I was expecting... I just can't access it!
I don't really understand why. I am using the same process with other collection properties within the object -- even collections of collections -- and they're working fine.
Any ideas?
myObject.myCollection.Add recordset!field adds the recordset field object to the myCollection object, rather than its value. As such, simply casting the field to its appropriate type solves the problem.