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.
Related
Sub getTrailInfo()
Dim attr As Variant
Dim attrTB As String
attrNames = Split("trailName,trailType,aSideSite,zSideSite,status", ",")
Dim trailForm As New formTrailInfo
For Each attr In attrNames
attrTB = attr + "TB"
trailForm.attrTB = attr
Next attr
trailForm.Show
End Sub
When I run the above code it gives a compilor error: Method or Data not found at line trailForm.attrTB = attr
I have required variables in attrNames String array. I need to put values of these variables in corosponding textboxes in a userForm. The name of Text Box in this userForm is attrNameTB. For example Text box for trailName is trailNameTB.
You cannot use VBA like that.
When you start your code, the compiler will first compile the code and check that you want to access a property named attrTB of your form. This doesn't exist and you will get the error you mentioned.
The compiler cannot wait until your variable attrTB has an actual value and then guess that you don't want a property with that name, but use the content of that variable as property name.
However, every form has a collection of all it's controls (button, edit boxes, combo boxes, labels...), and you can access the members of a collection either by index or by name. As you have the name of the control in attrTB, you could simply write
trailForm.Controls(attrTB).Text = attr
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.
I create a Class type Employee
Private emplId As String
Private name As String
Private rank As String
Private post As String
I create a module with a function Inside who return an object type Employee.
Public Function FuncNewPerson(Emplid As String) As Employee
Dim newPerson As New Employee
With newPerson
newPerson.SetEmplid = Emplid
newPerson.SetName = mytab(25, 1)
newPerson.SetRank = mytab(23, 1)
newPerson.SetPost = mytab(27, 1)
Set FuncNewPerson = newPerson
I use two UserForms, in the first one after selection of an emplId via a ComboBox it initialize an object type Employee, then I fill all the TextBox of the first UserForm with the get property of the object :
RequestForm.TxtBoxPerson = person.GetName
RequestForm.txtBoxRank = person.GetRank
On this UserForm i have a Button who calls the second one :
Public Sub BtnVerify_Click()
Me.Hide
ValidationForm.Show
End Sub
In the second UserForm I have some other TextBox to fill :
ValidationForm.TxtBoxEmployee = person.GetEmplid
ValidationForm.txtBoxRank = person.GetRank
ValidationForm.txtBoxPost = person.GetPost
I would like to use the object who is already in memory so my first idea is to pass it as an argument from the first form to the second one.
Of course i already searched the web but i'm now more confused about it, that's why i ask some help here in order to :
get some clues
get a feedback about my way of doing this
Thank you
In the second form, create a suitable variable
Private Person as Employee
Then create a property
Property Set CurrentPerson(p as Employee)
set person = p
End Property
Then set this property in the code from your first form
Public Sub BtnVerify_Click()
Me.Hide
ValidationForm.Show
Set validationform.currentperson = person
End Sub
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
I have code like this:
Dim MyACL As Variant
Dim Person As List
Redim MyACL(0)
Person("Detail1") = "Something1"
.
.
.
Person(Detailx") = "Somethingx"
ForAll name in names
ReDim Preserve MyAcl(Ubound(MyACL)+1)
Person("Name") = name
MyACL = ArrayAppend(MyACL,Person)
End ForAll
It throws error "Type Mismatch". Do you know, how to create an array of lists? Thank you.
This is a typical example of when you want to use a class instead, and create an array of that class. That class, in turn can contain a list (as well as other things). Can be very powerful!
Updated:
The benefit of using a class is that you can add business logic in the class, and it is very easy to extend it with more functionality later. Below is an example, based on the question above, but with additional functionality.
Class PersonObject
Public PersonAttribute List As String
Public NABdoc As NotesDocument
Public PersonName As String
Public Sub New(personname As String)
Dim nab as New NotesDatabase("Server/Domain","names.nsf")
Dim view as NotesView
'*** Get person document from Domino directory
Set view = nab.GetView("PeopleByFirstName")
Set me.NABdoc = view.GetDocumentByKey(personname)
'*** Set person name in object
me.PersonName = personname
'*** Set some values from person doc
me.PersonAttribute("Email") = GetValue("InternetAddress")
me.PersonAttribute("Phone") = GetValue("OfficePhone")
End Sub
Public Function GetValue(fieldname as String) as String
GetValue = me.NABdoc.GetItemValue(fieldname)(0)
End Function
Public Sub AddAttribute(attributename as String, value as string)
me.PersonAttribute(attributename) = value
End Sub
End Class
You can now very easily build you a list, using this class (and assuming that names is a list of unique names):
Dim person List As PersonObject
Dim personname As String
ForAll n in names
'*** Create person object based on name
person(n) = New PersonObject(n)
'*** Store additional info about this person
person.AddAttribute("Age","35")
End ForAll
Hopefully this gives you an idea of what you can do with classes.
You can also take a look at the following two blog entries about the basics of object oriented Lotusscript:
http://blog.texasswede.com/object-oriented-lotusscript-for-beginners-part-1/
http://blog.texasswede.com/object-oriented-lotusscript-for-beginners-part-2/
If you explicitely declare a variable as Array (as you do in your Redim Statement), then it can not be "reassigned" using arrayappend.
And it is NOT necessary to do it that way. just replace the line MyACL = ArrayAppend(MyACL,Person) with MyACL(Ubound(MyACL)) = Person
Take care: With that example code you will never fill MyACL(0) as the first Element filled is MyACL(1)
To begin filling the array with element 0 the code needs to be changed like this:
Dim max As Integer
max = 0
ForAll thisName In names
ReDim Preserve MyAcl(max)
Person("Name") = thisName
MyACL(max) = Person
max = max + 1
End ForAll
BUT: I don't know, if this is a good idea, as you can not access the "Detail1- Property" of Person directly.
Something like
detail = MyACL(1)("Detail1")
is not possible. You always have to have a temporary variable like this:
person = MyACL(1)
detail = person("Detail1")