VBA Function - Argument Not Optional - excel

Public Function RETURN_Equipment(Optional category As String) As Collection
Dim config As classConfiguration
Set config = New classConfiguration
Dim item As classItem
Set item = New classItem
Dim myCollection As Collection
Set myCollection = New Collection
For Each config In Configurations
For Each item In config.colItems
If IsMissing(category) Then
myCollection.add item
ElseIf InStr(category, "mainframe") <> 0 And item.category = "mainframe" Then
myCollection.add item
MsgBox "Fired!"
ElseIf category = "accessory" And item.category = "accessory" Then
Else
End If
Next
Next
RETURN_Equipment = myCollection
End Function
I keep getting
Compile error:
Argument not optional
I get the error on the last line
RETURN_Equipment = myCollection
I understand the error message, its telling me I did not fill out a parameter. But I only have one parameter, and I've declared it optional. It looks like the code thinks I'm trying to call the function from the function?
What gives?

Anytime you assign an object you need to use the set keyword.
set RETURN_Equipment = myCollection

I was getting this error because I was using the wrong function name when trying to return a result from a function. I was doing this:
Function MyFuncA(arg as String)
MyFuncB = arg 'The problem is I'm using MyFuncB instead of MyFuncA
End Function
This happened because I copied a function from somewhere else and changed the name, but not the return statement. This is not the OP's problem, but I was getting the same error message.

Because you've specified the Optional Parameter as a string it will default to an empty string if you've not specified a value.
This means it can't be missing
If you'd specified it as
Public Function RETURN_Equipment(Optional category) As Collection
It would be a variant and that could be missing, although you'd also be able to mess things up by passing non string variants as the category parameter
The best course of action is probably to replace
If IsMissing(category) Then
with
If category = "" Then
And as Brad has pointed out you'll need to use Set
Set RETURN_Equipment = myCollection
For full details check this
http://msdn.microsoft.com/en-us/library/office/gg251721%28v=office.15%29.aspx

Related

Class constructor confusion - wrong number of arguments or invalid property assignment

I'm having a class module with some data:
Private sharedFolders() As String
Public Property Let SetSharedFolders(val As String)
Dim i As Integer
sharedFolders = Array("folder one", "folder two")
i = UBound(sharedFolders)
i = UBound(sharedFolders)
ReDim Preserve sharedFolders(i)
sharedFolders(i) = CStr(val)
End Property
Property Get GetSharedFolders()
GetSharedFolders = sharedFolders()
End Property
And I want to add something to this property from other module like this:
Sub PrepareData()
Dim e
Dim s
Dim a(2) As String
Set e = New Entry
a(0) = "add one"
a(1) = "add two"
For Each s In a
e.SetSharedFolders (s) 'Here comes exception
Next
For Each s In e.GetSharedFolders
Debug.Print s
Next
End Sub
But I receive an "wrong number of arguments or invalid property assignment vba" exception... Can anyone assist?
Addendum
Thanks to #AJD and #Freeflow to pointing out a mistake and idea to make it easier. Decided to make as like below.
Class Module:
Private sharedFolders As New Collection
Public Property Let SetSharedFolders(val As String)
If sharedFolders.Count = 0 Then ' if empty fill with some preset data and add new item
sharedFolders.Add "folder 1"
sharedFolders.Add "folder 2"
sharedFolders.Add CStr(val)
Else
sharedFolders.Add CStr(val)
End If
End Property
Property Get GetSharedFolders() As Collection
Set GetSharedFolders = sharedFolders
End Property
and regular module:
Sub AddData()
Dim e As New Entry ' creating an instance of a class
Dim s As Variant ' variable to loop through collection
Dim a(1) As String 'some array with data to insert
a(0) = "add one"
a(1) = "add two"
For Each s In a
e.SetSharedFolders = s
Next
For Each s In e.GetSharedFolders
Debug.Print s
Next
End Sub
Initially I thought the problem lies in this code:
i = UBound(sharedFolders)
i = UBound(sharedFolders)
ReDim Preserve sharedFolders(i)
sharedFolders(i) = CStr(val)
i is set twice to the same value, and then the sharedFolders is reDimmed to the same value it was before! Also, there is some trickery happening with the use of ix within a 0-based array.
But the problem is most likely how you have declared your variables.
For Each s In a
e.SetSharedFolders (s) 'Here comes exception
Next
s is a Variant, and a is a Variant. At this point VBA is trying to guess how to handle a For Each loop with two Variants. And then the improper call is made. The correct syntax is:
e.SetSharedFolders s '<-- no parenthesis
There are plenty of posts on StackOverflow explaining how to call routines and what the impact of the evaluating parenthesis are!
However, at this point we are only assuming it is passing in a single element of the array - it could be passing the full array itself (albeit unlikely).
And the third factor -
Public Property Let SetSharedFolders(val As String)
The parameter val is being passed ByRef and should be passed ByVal. This also has unintended side effects as I found out (Type mismatch trying to set data in an object in a collection).
Public Property Let SetSharedFolders(ByVal val As String)
All in all you have the perfect storm of ambiguity driving to an unknown result.
The answer here is to strongly type your variables. This removes about two layers of ambiguity and areas where errors can happen. In addition, this will slightly improve code execution.
Another aspect is to understand when you should pass something ByVal and when to use the default (preferably explicitly) ByRef.
And a final gratuitous hint: Use a Collection instead of an Array. Your code you have implies a Collection will be more efficient and easier to manage.
Addendum
(thanks to #FreeFlow):
If the OP changes the definition of sharedfolders to Variant rather than String() then the array statement will work as expected.
The line e.SetSharedFolders (s) will work fine if it is changed to e.SetSharedFolders = s because the method SetSharedFolders is a Let Property not a Sub. There are other errors but these two changes will make the code run.

VBA - Dictionary search - type mismatch

So I'm learning how to use Dictionaries but I have encountered a type problem. In the code below I get an error.
Type mismatch
Each time I try to call If Not target.SearchItem(sKey) Is Nothing Then. I want it to return a object then if it isn't Nothing I can convert it back to long.
For Each target In dList.getArray
For Each key In aList.getArray(1).getKeys
sKey = CStr(key)
'Error occurs here - as Type mismatch
If Not target.SearchItem(sKey) Is Nothing Then
cData.setData(sKey) = CLng(target.SearchItem(sKey))
target.removeData = sKey
Else
'if value doesn't exists it will just be 0
cData.setData(sKey) = 0
End If
Next key
Next target
data is my dictionary that is in a separate class:
Property Get SearchItem(name As String) As Variant
If data.exists(name) Then
'becomes a Variant/Integer - data(name) is a Variant/long
Set SearchItem = CVar(data(name))
Else
'Should return Nothing if item doesnt exist
Set SearchItem = Nothing
End If
End Property
Update: To explain the problem I bit more. Even though I return it as a variant it will still be partly Integer and therefor If Not target.SearchItem(sKey) Is Nothing Then will return mismatch as it needs an object and VBA doesn't read is as a variant or something. Is there anything like Nothing, null or equally that works for a long? That would solve the problem.
This code below returns a long as I want it to, but I can't use -99 as it would corrupt the data analyse. It needs to be something that isn't a value
Property Get SearchItem(name As String)
If data.exists(name) Then
SearchItem = data(name)
Else
'SearchItem = Nothing
SearchItem = -99
End If
End Property
If Not target.SearchItem(sKey) Is Nothing Then
This implies that the stored item is an Object
cData.setData(sKey) = CLng(target.SearchItem(sKey))
The only way that this line will work on an Object is that the Object has a default value that can be converted to long. If the Object's default value is returning a value that can be converted to long.
Does Clng(Object) work on the actual Object that is being stored in the Dictionary?
If you are storing mixed data type then SearchItem check if the return data is an Object.
Property Get SearchItem(name As String) As Variant
If isObject(data(name)) Then
Set SearchItem = data(name)
Else
SearchItem = data(name)
End If
End Property
I would not convert the datatype in this method. I would instead create one or more separate methods or convert the data type at the point of use.
Function getTypedItem(Key as Variant, DataType as VbVarType) as Variant
IsNumeric(Obj)
Since you are using actual Keys of the Dictionary we know that it will return something and there is not a need for .Exists in this context. What you will need to do is test if it is returning an Object before you test whether the Object Is Nothing.
If IsObject(Target.SearchItem(sKey)) Then
If IsNumeric(Target.SearchItem(sKey)) Then
cData.setData(sKey) = CLng(Target.SearchItem(sKey))
End If
ElseIf IsNumeric(Target.SearchItem(sKey)) Then
cData.setData(sKey) = CLng(Target.SearchItem(sKey))
End If
I don't think you can use Set to assign an integer, so the property will fail when the dictionary item is an Integer. Perhaps use:
Property Get SearchItem(name As String) As Variant
If data.exists(name) Then
'becomes a Variant/Integer - data(name) is a Variant/long
SearchItem = data(name)
...
Unfortunately, VBA does not have nullable primitive types such as a nullable Long. A usual workaround is to use a Variant, which can be assigned the value of Empty and which can also hold a Long natively.
I would rewrite your property as follows:
Property Get SearchItem(name As String) As Variant
If Data.exists(name) Then
SearchItem = Data(name)
Else
SearchItem = Empty
End If
End Property
And the test for empty would be:
If Not IsEmpty(target.SearchItem(sKey)) Then ...

VBA Customize collection object

I've been trying to learn how to create customized collections in Excel VBA and I found this piece of code on MSDN. While I understand most of it, can anyone tell me what the last code Set Add = empNew is doing? I don't understand it's comment. Thank you!
' Methods of the Employees collection class.
Public Function Add(ByVal Name As String, _
ByVal Salary As Double) As Employee
Dim empNew As New Employee
Static intEmpNum As Integer
' Using With makes your code faster and more
' concise (.ID vs. empNew.ID).
With empNew
' Generate a unique ID for the new employee.
intEmpNum = intEmpNum + 1
.ID = "E" & Format$(intEmpNum, "00000")
.Name = Name
.Salary = Salary
' Add the Employee object reference to the
' collection, using the ID property as the key.
mcolEmployees.Add empNew, .ID
End With
' Return a reference to the new Employee.
Set Add = empNew
End Function
You will notice that Add is the name of the Function. By issuing Set Add = newEmp your code is declaring that the return value (or object, in this case) of the function, is the newly created employee object newEmp. This means that the function will pass the variable newEmp back to its caller.
Say that you had some procedure calling your function, you would be able to do this:
Sub listEmployees
Dim e As Employee
' Create a new employee, and assign the variable e to point to this object
Set e = Add("John", 1000) ' Notice that the only reason we use "add" here is because it is the name of the function you provided
' e is now an Employee object, after being created in the line above, meaning we can access whatever properties is defined for it. The function Add lists some properties, so we can use those as examples.
Debug.Print e.Name
Debug.Print e.Salary
Debug.Print e.ID
End Sub
First, you need to define the new Type you have created, so put the following code on top of your module:
Public Type Employee
id As String
Name As String
Salary As Long
End Type
Then, inside your Public Function Add , change to Dim empNew As Employee.
Not sure why you need the following line : mcolEmployees.Add empNew, .id ??
and the last line modify to Add = empNew.
Then, when I test this Function from the following Sub:
Sub testEmp()
Dim s As Employee
s = Add("Shai", 50000)
End Sub
I get for s in the immediate window the following values:
s.id = E00001
s.Name = "Shai"
s.Salary = 50000
I hope this is what you intended in your post.

Excel VBA: Adding objects to a collection within a class

I am trying to create a class (named ClassSection) that contains a collection (named DefectCollection). It needs a function to add items to that collection but I'm having trouble making it work. I get Error 91 "Object variable or with block variable not set."
I have looked at the other answers on here, which is what got me this far, but I don't understand what I'm missing.
Here is the class module code:
Public DefectCollection As Collection
Private Sub Class_Initialise()
Set DefectCollection = New Collection
End Sub
Public Function AddDefect(ByRef defect As CDefect)
DefectCollection.Add defect [<---- error 91]
End Function
And here is the code that calls the function: ('defect' is another class, which works fine - I want each 'ClassSection' to be able to hold an unlimited number of 'defects')
Dim SC As Collection
Dim section As ClassSection
Set SC = New Collection
Dim SurveyLength As Double
For Each defect In DC
SurveyLength = WorksheetFunction.Max(SurveyLength, defect.Pos, defect.EndPos)
Next defect
SurveyLength = Int(SurveyLength)
For i = 0 To numSurveys
For j = 0 To SurveyLength
Set section = New ClassSection
section.ID = CStr(j & "-" & dates(i))
SC.Add Item:=section, Key:=section.ID
Next j
Next i
Dim meterage As Double
For Each defect In DC
meterage = Int(defect.Pos)
Set section = SC.Item(meterage & "-" & defect.SurveyDate)
section.AddDefect defect
Next defect
Thanks!
You get the error because the DefectCollection is Nothing. This is due to the fact that you mispelled the initalization method:
Private Sub Class_Initialise() '<-- it's with "Z", not "S"
Hence, the initialization of the class is never called, the object remain Nothing by default and the method fails when trying to add an object to Nothing

Excel-VBA get filtered collection from Outlook AddressList

Problem:
As it seems to me that AddressList does not have a built-in filter functionality such as, say a C# DataTable (DatTableObject.Select(filter criteria), i am looking for a way to do this.
The Global Address List I am accessing has around a million entries, and I need to search through it up to 1000 times.
I am using the exchange-user name to find the e-mails of people, using the following code:
Set olApp = CreateObject("Outlook.Application")
Set myNamespace = olApp.GetNamespace("MAPI")
Set aList = myNamespace.AddressLists.Item("Global Address List")
Set aEntry = aList.AddressEntries("" + ExchangeName + "")
Set exUser = aEntry.GetExchangeUser
But it only retrieves me a single AddressEntry, which is a problem when I have several people of the same Exchange name - happens often enough.
Question: When I search Global Address List in Outlook, I have everything sorted alphabetically and with good speed, I am presented with all matches starting with the string I type in. How can I get a similar collection in VBA?
The AddressEntries object is a collection of AddressEntry objects.
When you index directly into the AddressEntries collection as you are, you return a single AddressEntry object based on the Index parameter provided. The Index parameter can either by an index number or it can be the default property of the item.
Since the default property of an AddressEntry item is the .Name property, the return is whatever the first item in the collection is which matches on the .Name property.
If you want to return all AddressEntry objects in the collection which match on the .Name property, you will need to loop through the collection.
Now, in .Net you can iterate over a collection using For...Next, and I believe you can do this in VBA as well, but I just can't recall off hand.
e.g.:
Set olApp = CreateObject("Outlook.Application")
Set myNamespace = olApp.GetNamespace("MAPI")
Set aList = myNamespace.AddressLists.Item("Global Address List")
Set aEntries = aList.AddressEntries
For each aEntry in aEntries
if aEntry.Name="" + ExchangeName + "" Then
'Do something with aEntry object
End If
Next
If for some reason that doesn't work, you can iterate over the collection using the GetFirst and GetNext methods.
e.g.:
Set olApp = CreateObject("Outlook.Application")
Set myNamespace = olApp.GetNamespace("MAPI")
Set aList = myNamespace.AddressLists.Item("Global Address List")
Set aEntries = aList.AddressEntries
Set aEntry = aEntries.GetFirst
Do While Not aEntry is Nothing
if aEntry.Name="" + ExchangeName + "" Then
'Do something with aEntry object
End If
Set aEntry = aEntries.GetNext
Loop
As for sorting alphabetically, have a look at the Sort Method.
And as far as building functionality where
...I am presented with all matches starting with the string I type in.
I'm not sure how you want to "present" the matches, or where you want to type in a string. But the general idea would be to a create function that takes an input parameter (e.g "MatchName" As String) and perform a loop like above to find all matches on that string for whatever property you want to look at, and then you would return an array, or something which you could use to "present" the information.
If you wanted to make it dynamic, so that the list updated "as you typed" you could run the updating procedure from the KeyPress event. In order to not run through the whole collection as you typed in a word, you would probably want to store the array and then with each additional letter typed you could just iterate through that array and remove non-matches (narrow the results). Before that happened you would probably then need some sort of check to see if a letter was removed, (e.g. checking the length of the string in the textbox) which would tell your program to re-run the check on the AddressEntries collection (widen the results).
Anyways, that's kind of a general idea on one way you could do it.

Resources