Iterate over COM collection without foreach or .Item() - vb.net

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.

Related

How to create simple set in vb.net

What I mean by simple set is set in a sense of simple collections of stuffs
https://en.wikipedia.org/wiki/Set_(mathematics)
So not set in a sense of setting some variable into something.
I can use list(of string). However, say I don't care about order. I care that every item is there exactly once. Well, list is not appropriate right? Unless we have a different code.
I look for "set" in vb.net but I got results for different meaning of set.
as far as I know, hashtable(of string) is not available.
What would be the appropriate container?
The collection should only check whether an object exist or not in the set. That's it. O(1) for both operation
You want the HashSet(Of T) class. It works basically the same way as the Keys of a Dictionary, where items are first compared by the result of their GetHashCode methods and, if those are the same, their Equals method.
It's worth noting that HashSet(Of T) implements ICollection(Of T), and therefore IEnumerable(Of T), but not IList(Of T). That means that you can enumerate the collection using a For Each loop and you can get the Count, but items have no index.
As mentioned in the comment below, the Add method will return a Boolean to indicate whether the item was added or not, based on whether the item was already contained in the set (False) or not (True).

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.

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.

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.