How to get an object out of a collection in VBa - vba

I used to have the following working code
Set result = DecodeJson(MyRequest.responseText)
Dim keys() As String
keys = GetKeys(result.issues)
Now, my approach has changed and instead of having result being the object, I receive a Collection based upon Array of class objects as class member in VBA
Dim resultsFromQueries As Collection
Set resultsFromQueries = GetAllJSonObjects(searchString) ' this calls DecodeJson(MyRequest.responseText) as per the code snippet above
Dim i As Integer
For i = 0 To resultsFromQueries.Count
Dim keys() As String
Dim item As Object
Set item = resultsFromQueries.item(i + 1) ' I guess it's not 0 based?
keys = GetKeys(item.result.issues) 'KABOOM
The issue I have now is I keep getting the following exception
Run time error '424':
Object required
Checking in the watch window, item shows as type Variant/String and has the value of "[object Object]"
Do I need to cast it?

I had that problem just a few weeks ago, the problem was the following:
The collection was collected with the follwing code:Collection.Add(OBJECT)
This however leads to the addition of the value of the Object into the Collection and can be solved as simple as leaving the parathesis.Collection.add OBJECT
So my advice is to go into the GetAllJSonObjects(searchString) function and
search for the addition process and make sure that it is without parethesis.
Like this the Collection will contain Objects and not variants and your code with item(i+1) should work properly.
Good luck!

Related

How to access a hashtable's first value without knowing the key?

After reading a config file during installation, I am saving the web services url into a Hashtable to test the connectivity to those services.
Before going through all the values I saved, I want to test just the first value. The keys I am using are the whole xml node containing the service url so it's unknown to me.
I didn't know much about Hashtable first, so I tried accessing it using an index. Assuming ht is a populated Hashtable, I tried this:
Dim serviceUrl as String = ht(0).Value
Which obviously failed since there's no key equal to 0, and the serviceUrl is just a Nothing.
Then I tried to access first element using:
Dim firstEntry as DictionaryEntry = ht(ht.Keys(0).ToString())
' Also tried this:
' Dim firstEntry as DictionaryEntry = ht(ht.Keys(0))
In both cases I got an error:
System.InvalidCastException: Specified cast is not valid.
I ended up using a For Each and exiting the loop directly after first iteration.
For Each entry As DictionaryEntry In ht
Dim serviceUrl as String = entry.Value
'Use it and exit for.
Exit For
Next
Well, this looks really awful.
After some time debugging and looking around, I used an array to hold the keys values:
Dim arr as Object() = new Object(100){}
'Copy the keys to that array.
ht.Keys.CopyTo(arr,0)
'Now I can directly access first item from the Hashtable:
Dim serviceUrl as String = ht(arr(0))
I am not sure if this is the right approach.
Is there any direct/clean way to access first item from a Hashtable?
The Keys property is an ICollection, not an IList, so it can't be indexed. ICollection is basically just IEnumerable with a Count property, so you should treat is the same way as an IEnumerable. That means enumerating it to get the first item. You can use LINQ:
Dim firstKey = myHashtable.Keys.Cast(Of Object)().FirstOrDefault()
or you can go old-school:
Dim firstKey As Object
For Each key In myHashtable.Keys
firstKey = key
Exit For
Next
If the collection may be empty, you can use the Count property to test first.

A null reference could result in runtime

Dim policy_key() As RenewalClaim.PolicyKeyType
policy_key(0).policyEffectiveDt = date_format_string(ld_EffectiveDate)
Getting error at Line2.
An Error occured - Object reference not set to an instance of an object.
Each element of object arrays also needs to be declared as a new object too.
Dim policy_key() As RenewalClaim.PolicyKeyType
Redim policy_key(0)
policy_Key(0) = new RenewalClaim.PolicyKeyType
policy_key(0).policyEffectiveDt = date_format_string(ld_EffectiveDate)
QUICK TIP: When declaring classes structures etc, it is useful to name them so you can see what type they are....
e.g.
cls_Policy_Key for a class
str_Policy_Key for a structure etc.
When you come back to your code after a year.. you will thank yourself for doing so.
Dim policy_key() As RenewalClaim.PolicyKeyType
is part of your problem. When you are declaring policy_key() you are actually declaring it as an array with no elements. If you don't particularly need to use an array, for example, if you don't need to add objects to a particular element number, you might be better using a list and declaring it like this
Dim policy_key As New List(Of RenewalClaim.PolicyKeyType)
This way, you can add items easily without having to resize your array each time - The code is a little longer than Trevor's answer, but less prone to errors when you extend your code -
dim newPolicy_Key as RenewalClaim.PolicyKeyType
newPolicy_Key.policyEffectiveDt = date_format_string(ld_EffectiveDate)
policy_Key.add(newPolicyKey)

Object variable or With Block variable not set error when accessing members of BuildingBlockEntries collection

I am attemping to access members of a template's BuildingBlockEntries collection through a macro in Microsoft Word 2007. As it is a collection, I first thought a For Each loop would be the best way to to this:
For Each bBlock In NormalTemplate.BuildingBlockEntries
MessageBox.Show (bBlock.Name)
Next bBlock
However this attempt through the error: Object doesn't support property or method.
So I tried this method which was suggested here:
Templates.LoadBuildingBlocks
Dim iBB As Integer
iBB = NormalTemplate.BuildingBlockEntries.Count()
Dim bb As Word.BuildingBlock
Dim i As Integer
Dim objCounter As Object
If iBB > 0 Then
For i = 1 To iBB
objCounter = i
bb = NormalTemplate.BuildingBlockEntries.Item(objCounter)
MessageBox.Show (bb.Name)
Next
End If
However, this is resulting in the error shown in the title: Object variable or With Block variable not set.
I have tried just using an integer variable for the index, i specifically, but with now avail. How can I acheive the desired effect? What is wrong with my attempt?
Thank you for the help.
With your 2nd question the answer is that you need to use Set, as bb is an object:
Set bb = NormalTemplate.BuildingBlockEntries.Item(objCounter)
For more info on Set take a look at this SO question.
With your For/Next loop, it's not clear how you've declared bBlock. I guess it should be something like:
Dim bBlock as BuildingBlock
And perhaps the For line should reference BuildingBlocks instead of BuildingBlockEntries:
For Each bBlock In NormalTemplate.BuildingBlocks
I don't know for sure though, as I'm just looking at what pops up in Intellisense.

Setting the Item property of a Collection in VBA

I'm surprised at how hard this has been to do but I imagine it's a quick fix so I will ask here (searched google and documentation but neither helped). I have some code that adds items to a collection using keys. When I come across a key that already exists in the collection, I simply want to set it by adding a number to the current value.
Here is the code:
If CollectionItemExists(aKey, aColl) Then 'If key already has a value
'add value to existing item
aColl(aKey).Item = aColl(aKey) + someValue
Else
'add a new item to the collection (aka a new key/value pair)
mwTable_ISO_DA.Add someValue, aKey
End If
The first time I add the key/value pair into the collection, I am adding an integer as the value. When I come across the key again, I try to add another integer to the value, but this doesn't work. I don't think the problem lies in any kind of object mis-match or something similar. The error message I currently get is
Runtime Error 424: Object Required
You can't edit values once they've been added to a collection. So this is not possible:
aColl.Item(aKey) = aColl.Item(aKey) + someValue
Instead, you can take the object out of the collection, edit its value, and add it back.
temp = aColl.Item(aKey)
aColl.Remove aKey
aColl.Add temp + someValue, aKey
This is a bit tedious, but place these three lines in a Sub and you're all set.
Collections are more friendly when they are used as containers for objects (as opposed to containers for "primitive" variables like integer, double, etc.). You can't change the object reference contained in the collection, but you can manipulate the object attached to that reference.
On a side note, I think you've misunderstood the syntax related to Item. You can't say: aColl(aKey).Item. The right syntax is aColl.Item(aKey), or, for short, aColl(aKey) since Item is the default method of the Collection object. However, I prefer to use the full, explicit form...
Dictionaries are more versatile and more time efficient than Collections. If you went this route you could run an simple Exists test on the Dictionary directly below, and then update the key value
Patrick Matthews has written an excellent article on dictionaries v collections
Sub Test()
Dim MyDict
Set MyDict = CreateObject("scripting.dictionary")
MyDict.Add "apples", 10
If MyDict.exists("apples") Then MyDict.Item("apples") = MyDict.Item("apples") + 20
MsgBox MyDict.Item("apples")
End Sub
I think you need to remove the existing key-value pair and then add the key to the collection again but with the new value

Outlook Redemption : GetNamesFromIDs

I'm trying to get all property names / values from an Outlook item. I have custom properties in addition to the default outlook item properties. I'm using redemption to get around the Outlook warnings but I'm having some problems with the GetNamesFromIDs method on a Redemption.RDOMail Item....
I'm using my redemption session to get the message and trying to use the message to get the names of all the properties.
Dim rMessage as Redemption.RDOMail = _RDOSession.GetMessageFromID(EntryID, getPublicStoreID())
Dim propertyList As Redemption.PropList = someMessage.GetPropList(Nothing)
For i As Integer = 1 To propertyList.Count + 1
Console.WriteLine(propertyList(i).ToString())
Console.WriteLine(someMessage.GetNamesFromIDs(________, propertyList(i)))
Next
I'm not totally sure what to pass in as the first parameter to getNamesFromIDs. The definition of GetNamesFromIDs is as follows:
GetNamesFromIDs(MAPIProp as Object, PropTag as Integer) As Redemption.NamedProperty
I'm not totally sure what should be passed in as the MAPIProp object. I don't see this property referenced in the documentation. http://www.dimastr.com/redemption/rdo/MAPIProp.htm#properties
Any help or insight would be greatly appreciated.
I think I figured it out. I have used VBA only, so you need to "think around" it's limitations, it will follow the same scheme in VB.NET.
The function signature is this:
Function GetNamesFromIDs(MAPIProp As Unknown, PropTag As Long) As NamedProperty
As the first parameter it requires an object which supports the IUnknown interface. Looking at the Redemption docs it became clear that there is an interface named _MAPIProp, from which many other RDO objects are derived (IRDOMail is among them). So this must be the very RDOMail you are trying to get data out of.
Knowing that, it needed only one other subtle hint from the docs to get it working:
Given a prop tag (>= 0x80000000),
returns the GUID and id of the named
property.
So property tag must be >= 0x80000000, this means it wont work for all properties, but just for the custom ones (I guess that is the distinction in this case, correct me if I'm wrong.) Passing in property tags not fulfilling this condition raises an error message (0x8000ffff "unexpected results").
Here is my code. It is VBA, so forgive me the Hex() blunder, as VBAs long integer overflows for numbers that big. I am sure you will get the picture.
Sub GetNamesFromIds()
Dim rSession As New Redemption.RDOSession
Dim rMessage As Redemption.RDOMail
Dim PropertyList As Redemption.PropList
Dim PropTag As Long
Dim EntryId As String
Dim i As Integer
rSession.MAPIOBJECT = Application.Session.MAPIOBJECT
' retrieve first random mail for this example '
EntryId = ActiveExplorer.CurrentFolder.Items(1).EntryId
Set rMessage = rSession.GetMessageFromID(EntryId)
Set PropertyList = rMessage.GetPropList(0)
For i = 1 To PropertyList.Count
PropTag = PropertyList(i)
If "0x" & Right("00000000" & Hex(PropTag), 8) > "0x80000000" Then
Debug.Print
If IsArray(rMessage.Fields(PropTag)) Then
Debug.Print Hex(PropTag), "(Array:", UBound(rMessage.Fields(PropTag)), "items)"
Else
Debug.Print Hex(PropTag), "(", rMessage.Fields(PropTag), ")"
End If
Debug.Print " GUID:", rMessage.GetNamesFromIds(rMessage, PropTag).GUID
Debug.Print " ID:", rMessage.GetNamesFromIds(rMessage, PropTag).ID
End If
Next
End Sub
First snippet from the output:
8041001E ( urn:content-classes:message )
GUID: {00020386-0000-0000-C000-000000000046}
ID: content-class
Well, for background info, the author suggests using something like OutlookSpy to see how Outlook stores the properties.
Looking at this exchange (make sure to read through all the follow-up responses), there isn't much more (in fact, I think at one point the Outlook MVP types GetNamesFromIDs when he means GetIDsFromNames).
What you might try is using GetIDsFromNames to see what that returns, and then use that to pass to GetNamesFromIDs.
I've used Redemption before, but not in this particular manner, so that's all I've got for you ...