Let's say that I have an object...
Class test
Public a
Public b
End class
And in my code I would like to instantiate it without knowing a predetermined variable name to store the new instance.
Is this possible? How would I then dim the random variable?
I want to be able to do this because I don't want my user input to be stored or saved in the same variable with other data conflict. Like say for example I am storing stats for an athlete and I ask the athlete's name. If every time the user enters a different name, I don't want to have this information in the same object instance. Could I create an object for the athlete and reference this object inside another object?
I have a snippet of code:
Function addStats
dim pAtt, pComp, pInt, pTds, pYds, endNum, pName
Wscript.StdOut.WriteLine "What is your quarterback's name"
pName = Wscript.StdIn.ReadLine
Wscript.StdOut.WriteLine "How many attempts: "
pAtt = Wscript.StdIn.ReadLine
'chkNum(pAtt)
Wscript.StdOut.WriteLine "How many completions: "
pComp = Wscript.StdIn.ReadLine
'chkNum(pComp)
Wscript.StdOut.WriteLine "How many yards: "
pYds = Wscript.StdIn.ReadLine
'chkNum = pYds
Wscript.StdOut.WriteLine "How many touchdowns: "
pTds = Wscript.StdIn.ReadLine
'chkNum = pTds
Wscript.StdOut.WriteLine "How many interceptions: "
pInt = Wscript.StdIn.ReadLine
'chkNum = pInt
endNum = UBound(newStats) + 1
redim preserve newStats(endNum)
'---- vvvv ----
set newStats(endNum) = new QB
'---- ^^^^ ----
newStats(endNum).att = pAtt
newStats(endNum).comp = pComp
newStats(endNum).yds = pYds
newStats(endNum).tds = pTds
newStats(endNum).ints = pInt
newStats(endNum).qbname = pName
Wscript.StdOut.WriteLine "Stats Added"
writeBuffer()
end Function
The object is:
class QB
dim att, comp, yds, tds, ints, qbname
public property let qbAtt(n)
att = n
end property
public property let qbComp(n)
comp = n
end property
public property let qbYds(n)
yds = n
end property
public property let qbTds(n)
tds = n
end property
public property let qbInt(n)
ints = n
end property
public property let qName(n)
qbname = n
end property
public property get qbAtt
qbAtt = att
end property
public property get qbComp
qbComp = comp
end property
public property get qbYds
qbYds = yds
end property
public property get qbTds
qbTds = tds
end property
public property get qbInt
qbInt = ints
end property
public property get qName
qName = qbname
end property
end class
The highlighted statement is me instantiating the object with a global variable - newStat(). My belief is that I would have to either create a class for just the quarterback and somehow reference this into a variable array that is determined on the quarterback's name or create a function that takes the user prompt variable that asks the quarterback's name and set it as an array that instantiates the QB class.
I think you're confusing classes, objects, and variables. Classes are basically templates that describe which properties and behavior entities of a particular category have. Objects are instances of a class. Variables are identifiers that refer to objects (or data of primitive types).
Take lockers for example. Lockers usually have a color, they can be opened and closed, and they may contain a number of items. Thus a general description of lockers (a class "Locker") might look like this:
Class Locker
Public color
Public closed
Public content
Sub Class_Initialize
closed = True
content = Array()
End Sub
Sub Open
closed = False
End Sub
Sub Close
closed = True
End Sub
End Class
The constructor (Class_Initialize) is a special method that is only called when an object (an instance of a class) is created. It sets the initial state of the object. Kind of like when a locker is built in the factory.
A particular locker (object) might be green and contain a hat and a magazine, while another locker (object) might be blue and contain a book and a jacket.
Set locker_A = New Locker
locker_A.color = "green"
locker_A.content = Array("hat", "magazine")
Set locker_B = New Locker
locker_B.color = "blue"
locker_B.content = Array("book", "jacket")
To be able to actually work with objects (or other data) in a program you need variables. These identifiers (locker_A and locker_B in the example above) allow you to refer to particular objects in your program code in order to access their properties and methods.
When you run a statement
Set newStats(2) = New QB
it creates a new instance of the class QB (a new object) and places a reference to that object in the third slot of the array newStats. Afterwards you can use newStats(2) to refer to that object in your program or script. For instance:
newStats(2).yds = 42 'yards
If you have a number of QB object and store each of them in a different array slot there shouldn't be any conflict between them, as long as you don't replace a reference in one slot with another one.
Set newStats(2) = newStats(5) '<-- don't do this
If you wanted to access objects from the list by a particular property (for instance the name) instead of by index you'd use a Dictionary instead of an array:
Set newStats = CreateObject("Scripting.Dictionary")
newStats.CompareMode = vbTextCompare 'make lookups case-insensitive
...
Set player = New QB
player.att = pAtt
...
player.qbname = pName
newStats.Add pName, player
Then you could access a particular player like this:
name = "Charley Johnson"
WScript.Echo newStats(name).yds
Related
is there way how to make class working similar to Arrays?
Let's say, I have Class (e.g. Workers) where main property is array of the Workers, nothing else.
Then I'm filling the class as follows
Dim wks as new Workers
wks.add("Worker1")
wks.add("Worker2")
wks.add("Worker3")
Then in Workers Class module:
Private Workers as Variant
Public Function add(ByVal val As Variant) As Long
ReDim Preserve Workers(LBound(Workers) To UBound(Workers) + 1)
Workers(UBound(Workers)) = val
add = UBound(Workers) - LBound(Workers) +1
End Function
Workers representation -> {"Worker1", "Worker2", "Worker3"}
Then I want to access Worker by its index. I know, how to access it by e.g wks.getWorker(1) but what I want to do, is to access it directly by wks(1) which should return "Worker 1". Example above looks, that usual Array or Collection can be used, but I have many internal methods done, only what I'm missing is to access Workers property to read/write directly by its index number.
Is it possible?
Edit
After transfer to Collections, Class looks like:
Option Explicit
Private Workers As Collection
Private Sub Class_Initialize()
Set Workers = New Collection
End Sub
Public Function add(ByVal val As Variant) As Long
Workers.add val
End Function
Public Property Get Item(Index As Integer) As Variant
Item = Workers(Index)
End Property
Public Property Set Item(Index As Integer, Value As Variant)
Workers.Remove Index
Workers.add Value, Before:=Index
End Property
with hidden attributes Attribute Item.VB_UserMemId = 0 at Getter and Setter.
Getting works fine:
Dim wks As New Workers
wks.add "Worker1"
wks.add "Worker2"
wks.add "Worker3"
Debug.Print wks(2) ' <-- OK here
'wks(2) = "Second Worker" ' <-- By debugging this go to Getter not Setter and after Getter is done, it allerts with Runtime error '424': Object required
Set wks(2) = "Second Worker" ' <-- This alert immediately Compile error: Object required on "Second Worker" string
Debug.Print wks(2)
Prints "Worker2" into console, thanks for this, but still I'm not able to set a new value to the required Index of the Workers Collection.
You could use a default member in VBA. Though you can't make the default memeber directly through VBA editor, but you can use any text editor.
Export your class from VBA editor, i.e. File->Export File
Open your exported class in Notepad (or any text editor)
Add this attribute line on your method or property you want to make it default. Attribute Item.VB_UserMemId = 0
You can for example make getWorker default member as.
Public Function GetWorker(Index As Integer) As Worker
Attribute Item.VB_UserMemId = 0
GetWorker = Workers(Index)
End Function
you can then use it like.
Set wk = wks(1)
Here is some detail about default members
http://www.cpearson.com/excel/DefaultMember.aspx
Edits
An example to make Getter/Setter as default member
Public Property Get Item(Index as Integer) as Worker
Attribute Item.VB_UserMemId = 0
Set Item = Workers(Index)
End Property
Public Property Set Item(Index as Integer, Value as Worker)
Attribute Item.VB_UserMemId = 0
Set Workers(Index) = Value
End Property
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
In my code, in need to store two value in my key to be able to do the analysis i require. And since I didn't want to store everything in an array, i decided to create an object with 2 parameters. But when i run the Exist function of the dictionary with that object "TwoInputs" as the type of Key, I always get that they Key doesn't exist. Can anyone help please?
I added "Option Compare Text" just in case but the exist still return False.
When I run the code line by line and force it to go the If "true" condition, a new Key is still created, don't know why.
This is the class module i created:
Private acc As Double
Private act As Variant
'Account property
Public Property Get Account() As Double
Account = acc
End Property
Public Property Let Account(Value As Double)
acc = Value
End Property
'Activity property
Public Property Get Activity() As Variant
Activity = act
End Property
Public Property Let Activity(Value As Variant)
act = Value
End Property
In a normal module i wrote a function to create a TwoInputs object based on two entries:
Public Function cTwoInputs(Account As Double, Activity As Variant) As TwoInputs
Set cTwoInputs = New TwoInputs
cTwoInputs.Account = Account
cTwoInputs.Activity = Activity
End Function
Then I create a sub where I want to add the 2 informations in a Key if they exist:
While dataSheet.Range("dataAgent").Offset(j, 0).Value <> "Project ID:" And dataSheet.Range("dataAgent").Offset(j, 0).Row <= lRow
If dataSheet.Range("dataAgent").Offset( j, 0).Value = "Activity ID:" Then
actName = dataSheet.Range("dataAgent").Offset(j, 1).Value
End If
If (dataSheet.Range("dataAgent").Offset(j, 0).Value = "XXXXX" Or dataSheet.Range("dataAgent").Offset(j, 0).Value = "") Then
KeyExist.Account = dataSheet.Range("dataAccount").Offset(j , 0).Value
KeyExist.Activity = actName
If dicBudget.Exists(KeyExist) Then
dicBudget(KeyExist) = dicBudget(KeyExist) + dataSheet.Range("dataBudget").Offset(j , 0).Value
Else
dicBudget.Add cTwoInputs(dataSheet.Range("dataAccount").Offset(j, 0).Value, actName), dataSheet.Range("dataBudget").Offset( j, 0).Value
End If
End If
j = j + 1
Wend
.Exists() method compares objects by their instances not by values of their fields.
So it's better to use primitive types like String, Integer, Double etc. as keys, and not to use custom object as a key. If you really need to use object-key for some purpose than you must be sure that you call .Exists() method on the same object you put to dictionary (e.g. by storing that key in a global variable).
All,
I am setting-up a class module structure in VBA to add plans that have multiple milestones, but I'm quite new to it. I did the following:
A class module called 'Plan' that contains a 'name' property (string) and a 'Milestones' property (class Milestones).
This milestones class module is a collection of objects of a class module called 'Milestone'.
The 'Milestone' class has a 'name' property and a 'value' property.
So in my module I am now specifying the milestones for a specific plan:
Plan.Milestones.Add "MilestoneA", Cells(i, 5)
Plan.Milestones.Add "MilestoneB", Cells(i, 7)
...
Until now everything is fine. Now for MilestoneC I would like to know the value of MilestoneA. How do I get the value for the Milestone with name 'MilestoneA'.
I know the below code would give me the answer, but I don't want to hardcode 'item(1)' (I want to use the name):
Plan.Milestones.Item(1).Value
In the clsMilestones class:
Private prvt_Milestones As New Collection
Property Get Item(Index As Variant) As clsMilestone
Set Item = prvt_Milestones(Index)
End Property
Sub Add(param_Name As String, param_Value As String)
Dim new_milestone As clsMilestone
Set new_milestone = New clsMilestone
new_milestone.Name = param_Name
new_milestone.Value = param_Value
prvt_Milestones.Add new_milestone
End Sub
Your Milestones class is a collection class. By convention, collection classes have an Item property that is the class' default member. You can't easily specify a class' default member in VBA, but it's not impossible.
Export the code file, open it in Notepad. Locate your Public Property Get Item member and add a VB_UserMemId attribute - while you're there you can add a VB_Description attribute, too:
Public Property Get Item(ByVal Index As Variant) As Milestone
Attribute Item.VB_UserMemId = 0
Attribute Item.VB_Description = "Gets the item at the specified index, or with the specified name."
Set Item = prvt_Milestones(Index)
End Property
The UserMemId = 0 is what makes the property the class' default member - note that only one member in the class can have that value.
Don't save and close just yet.
You'll want to make your collection class work with a For Each loop too, and for that to work you'll need a NewEnum property that returns an IUnknown, with a number of attributes and flags:
Public Property Get NewEnum() As IUnknown
Attribute NewEnum.VB_Description = "Gets an enumerator that iterates through the collection."
Attribute NewEnum.VB_UserMemId = -4
Attribute NewEnum.VB_MemberFlags = "40"
Set NewEnum = prvt_Milestones.[_NewEnum]
End Property
Note that your internal encapsulated Collection has a hidden member with a name that begins with an underscore - that's illegal in VBA, so to invoke it you need to surround it with square brackets.
Now this code is legal:
Dim ms As Milestone
For Each ms In Plan.Milestones
Debug.Print ms.Name, ms.Value ', ms.DateDue, ...
Next
Save the file, close it, and re-import it into your project.
Since you're populating the collection using a string key (at least that's what your Add method seems to be doing), then the client code can use either the index or the key to retrieve an item.
And now that Item is the class' default member, this is now legal:
Set milestoneA = Plan.Milestones("Milestone A").Value
Note that your Add method needs to specify a value for the Key argument when adding to the internal collection - if you want the items keyed by Name, use the Name as a key:
Public Sub Add(ByVal Name As String, ByVal Value As Variant)
Dim new_milestone As Milestone
Set new_milestone = New Milestone
new_milestone.Name = Name
new_milestone.Value = Value
prvt_Milestones.Add new_milestone, Name
End Sub
Use a dictionary of Milestone classes in the plan class and set the key to be the "Milestone_x" and the item to be a milestone class
Then you can say Plan.Milestones("Milestone99")
Add a property to the Milestones class that returns the milestone based on the name:
Property Get SelectByName(strMilestoneName as string) as clsMilestone
Dim vIndex
'Add code here to find the index of the milestone in question
vIndex = ????????
Set SelectByName = prvt_Milestones(Index)
End Property
OR
Edit the Item Property to Allow selection by either Index or Name:
Property Get Item(Index As Variant) As clsMilestone
If isNumeric(Index) then
Set Item = prvt_Milestones(Index)
Else
'Find Item based on Name
Dim vIndex
vIndex = ?????
Set Item = prvt_Milestones(vIndex)
End If
End Property
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")