Pass arguments to Constructor in VBA - vba

How can you construct objects passing arguments directly to your own classes?
Something like this:
Dim this_employee as Employee
Set this_employee = new Employee(name:="Johnny", age:=69)
Not being able to do this is very annoying, and you end up with dirty solutions to work this around.

Here's a little trick I'm using lately and brings good results. I would like to share with those who have to fight often with VBA.
1.- Implement a public initiation subroutine in each of your custom classes. I call it InitiateProperties throughout all my classes. This method has to accept the arguments you would like to send to the constructor.
2.- Create a module called factory, and create a public function with the word "Create" plus the same name as the class, and the same incoming arguments as the constructor needs. This function has to instantiate your class, and call the initiation subroutine explained in point (1), passing the received arguments. Finally returned the instantiated and initiated method.
Example:
Let's say we have the custom class Employee. As the previous example, is has to be instantiated with name and age.
This is the InitiateProperties method. m_name and m_age are our private properties to be set.
Public Sub InitiateProperties(name as String, age as Integer)
m_name = name
m_age = age
End Sub
And now in the factory module:
Public Function CreateEmployee(name as String, age as Integer) as Employee
Dim employee_obj As Employee
Set employee_obj = new Employee
employee_obj.InitiateProperties name:=name, age:=age
set CreateEmployee = employee_obj
End Function
And finally when you want to instantiate an employee
Dim this_employee as Employee
Set this_employee = factory.CreateEmployee(name:="Johnny", age:=89)
Especially useful when you have several classes. Just place a function for each in the module factory and instantiate just by calling factory.CreateClassA(arguments), factory.CreateClassB(other_arguments), etc.
EDIT
As stenci pointed out, you can do the same thing with a terser syntax by avoiding to create a local variable in the constructor functions. For instance the CreateEmployee function could be written like this:
Public Function CreateEmployee(name as String, age as Integer) as Employee
Set CreateEmployee = new Employee
CreateEmployee.InitiateProperties name:=name, age:=age
End Function
Which is nicer.

I use one Factory module that contains one (or more) constructor per class which calls the Init member of each class.
For example a Point class:
Class Point
Private X, Y
Sub Init(X, Y)
Me.X = X
Me.Y = Y
End Sub
A Line class
Class Line
Private P1, P2
Sub Init(Optional P1, Optional P2, Optional X1, Optional X2, Optional Y1, Optional Y2)
If P1 Is Nothing Then
Set Me.P1 = NewPoint(X1, Y1)
Set Me.P2 = NewPoint(X2, Y2)
Else
Set Me.P1 = P1
Set Me.P2 = P2
End If
End Sub
And a Factory module:
Module Factory
Function NewPoint(X, Y)
Set NewPoint = New Point
NewPoint.Init X, Y
End Function
Function NewLine(Optional P1, Optional P2, Optional X1, Optional X2, Optional Y1, Optional Y2)
Set NewLine = New Line
NewLine.Init P1, P2, X1, Y1, X2, Y2
End Function
Function NewLinePt(P1, P2)
Set NewLinePt = New Line
NewLinePt.Init P1:=P1, P2:=P2
End Function
Function NewLineXY(X1, Y1, X2, Y2)
Set NewLineXY = New Line
NewLineXY.Init X1:=X1, Y1:=Y1, X2:=X2, Y2:=Y2
End Function
One nice aspect of this approach is that makes it easy to use the factory functions inside expressions. For example it is possible to do something like:
D = Distance(NewPoint(10, 10), NewPoint(20, 20)
or:
D = NewPoint(10, 10).Distance(NewPoint(20, 20))
It's clean: the factory does very little and it does it consistently across all objects, just the creation and one Init call on each creator.
And it's fairly object oriented: the Init functions are defined inside the objects.
EDIT
I forgot to add that this allows me to create static methods. For example I can do something like (after making the parameters optional):
NewLine.DeleteAllLinesShorterThan 10
Unfortunately a new instance of the object is created every time, so any static variable will be lost after the execution. The collection of lines and any other static variable used in this pseudo-static method must be defined in a module.

When you export a class module and open the file in Notepad, you'll notice, near the top, a bunch of hidden attributes (the VBE doesn't display them, and doesn't expose functionality to tweak most of them either). One of them is VB_PredeclaredId:
Attribute VB_PredeclaredId = False
Set it to True, save, and re-import the module into your VBA project.
Classes with a PredeclaredId have a "global instance" that you get for free - exactly like UserForm modules (export a user form, you'll see its predeclaredId attribute is set to true).
A lot of people just happily use the predeclared instance to store state. That's wrong - it's like storing instance state in a static class!
Instead, you leverage that default instance to implement your factory method:
[Employee class]
'#PredeclaredId
Option Explicit
Private Type TEmployee
Name As String
Age As Integer
End Type
Private this As TEmployee
Public Function Create(ByVal emplName As String, ByVal emplAge As Integer) As Employee
With New Employee
.Name = emplName
.Age = emplAge
Set Create = .Self 'returns the newly created instance
End With
End Function
Public Property Get Self() As Employee
Set Self = Me
End Property
Public Property Get Name() As String
Name = this.Name
End Property
Public Property Let Name(ByVal value As String)
this.Name = value
End Property
Public Property Get Age() As String
Age = this.Age
End Property
Public Property Let Age(ByVal value As String)
this.Age = value
End Property
With that, you can do this:
Dim empl As Employee
Set empl = Employee.Create("Johnny", 69)
Employee.Create is working off the default instance, i.e. it's considered a member of the type, and invoked from the default instance only.
Problem is, this is also perfectly legal:
Dim emplFactory As New Employee
Dim empl As Employee
Set empl = emplFactory.Create("Johnny", 69)
And that sucks, because now you have a confusing API. You could use '#Description annotations / VB_Description attributes to document usage, but without Rubberduck there's nothing in the editor that shows you that information at the call sites.
Besides, the Property Let members are accessible, so your Employee instance is mutable:
empl.Name = "Jane" ' Johnny no more!
The trick is to make your class implement an interface that only exposes what needs to be exposed:
[IEmployee class]
Option Explicit
Public Property Get Name() As String : End Property
Public Property Get Age() As Integer : End Property
And now you make Employee implement IEmployee - the final class might look like this:
[Employee class]
'#PredeclaredId
Option Explicit
Implements IEmployee
Private Type TEmployee
Name As String
Age As Integer
End Type
Private this As TEmployee
Public Function Create(ByVal emplName As String, ByVal emplAge As Integer) As IEmployee
With New Employee
.Name = emplName
.Age = emplAge
Set Create = .Self 'returns the newly created instance
End With
End Function
Public Property Get Self() As IEmployee
Set Self = Me
End Property
Public Property Get Name() As String
Name = this.Name
End Property
Public Property Let Name(ByVal value As String)
this.Name = value
End Property
Public Property Get Age() As String
Age = this.Age
End Property
Public Property Let Age(ByVal value As String)
this.Age = value
End Property
Private Property Get IEmployee_Name() As String
IEmployee_Name = Name
End Property
Private Property Get IEmployee_Age() As Integer
IEmployee_Age = Age
End Property
Notice the Create method now returns the interface, and the interface doesn't expose the Property Let members? Now calling code can look like this:
Dim empl As IEmployee
Set empl = Employee.Create("Immutable", 42)
And since the client code is written against the interface, the only members empl exposes are the members defined by the IEmployee interface, which means it doesn't see the Create method, nor the Self getter, nor any of the Property Let mutators: so instead of working with the "concrete" Employee class, the rest of the code can work with the "abstract" IEmployee interface, and enjoy an immutable, polymorphic object.

Using the trick
Attribute VB_PredeclaredId = True
I found another more compact way:
Option Explicit
Option Base 0
Option Compare Binary
Private v_cBox As ComboBox
'
' Class creaor
Public Function New_(ByRef cBox As ComboBox) As ComboBoxExt_c
If Me Is ComboBoxExt_c Then
Set New_ = New ComboBoxExt_c
Call New_.New_(cBox)
Else
Set v_cBox = cBox
End If
End Function
As you can see the New_ constructor is called to both create and set the private members of the class (like init) only problem is, if called on the non-static instance it will re-initialize the private member. but that can be avoided by setting a flag.

First, here is a very quick summary/comparison of the baseline approach and the top three answers.
The baseline approach: These are the basic ways to construct new instances of a class:
Dim newEmployee as Employee
Dim newLunch as Lunch
'==Very basic==
Set newEmployee = new Employee
newEmployee.Name = "Cam"
newEmployee.Age = 42
'==Use a method==
Set newLunch = new Lunch
newLunch.Construct employeeName:= "Cam" food:="Salad", drink:="Tea"
Above, Construct would be a sub in the Lunch class that assigns the parameter values to an object.
The issue is that even with a method, it took two lines, first to set the new object, and second to fill the parameters. It would be nice to do both in one line.
1) The Factory class (bgusach): Make a separate class ("Factory"), with methods to create instances of any other desired classes including set-up parameters.
Possible use:
Dim f as Factory 'a general Factory object
Dim newEmployee as Employee
Dim newLunch as Lunch
Set f = new Factory
Set newEmployee = f.CreateEmployee("Bob", 25)
Set newLunch = f.CreateLunch("Bob", "Sandwich", "Soda")
When you type "f." in the code window, after you Dim f as Factory, you see a menu of what it can create via Intellisense.
2) The Factory module (stenci): Same, but instead of a class, Factory can be a standard module.
Possible use:
Dim newEmployee as Employee
Dim newLunch as Lunch
Set newEmployee = CreateEmployee("Jan", 31) 'a function
Set newLunch = CreateLunch("Jan", "Pizza", "JuiceBox")
In other words, we just make a function outside the class to create new objects with parameters. This way, you don't have to create or refer to a Factory object. You als don't get the as-you-type intellisense from the general factory class.
3) The Global Instance (Mathieu Guindon): Here we return to using objects to create classes, but sticking to the class-to-be-made. If you modify the class module in an external text editor you can call class methods before creating an object.
Possible use:
Dim newEmployee as Employee
Dim newLunch as Lunch
Set newEmployee = newEmployee.MakeNew("Ace" 50)
Set newLunch = newLunch.MakeNew("Ace", "Burrito", "Water")
Here MakeNew is a function like CreateEmployee or CreateLunch in the general factory class, except that here it is in the class-to-be-made, and so we don't have to specify what class it will make.
This third approach has a fascinating "created from itself" appearance to it, permitted by the global instance.
Other ideas: Auto-instancing, a Clone method, or a Parent Collection Class
With auto instancing (Dim NewEmployee as new Employee, note the word "new"), you can achieve something similar to the global instance without the setup process:
Dim NewEmployee as new Employee
NewEmployee.Construct("Sam", 21)
With "new" in the Dim statement, the NewEmployee object is created as an implied pre-step to calling its method. Construct is a Sub in the Employee class, just like in the baseline approach.[1]
There are some issues with auto instancing; some hate it, a few defend it.2 To restrict auto-instancing to one proto-object, you could add a MakeNew function to the class as I used with the Global Instance approach, or revise it slightly as Clone:
Dim protoEmployee as new Employee 'with "new", if you like
'Add some new employees to a collection
Dim someNames() as Variant, someAges() as Variant
Dim someEmployees as Collection
someNames = array("Cam", "Bob", "Jan", "Ace")
someAges = array(23, 45, 30, 38)
set someEmployees = new Collection
for i = 0 to 3
someEmployees.Add protoEmployee.Clone(someNames(i), someAges(i))
next
Here, the Clone method could be set up with optional parameters Function Clone(optional employeeName, optional employeeAge)and use the properties of the calling object if none are supplied.
Even without auto-instancing, a MakeNew or Clone method within the class itself can create new objects in one line, once you create the proto-object. You could use auto-instancing for a general factory object in the same way, to save a line, or not.
Finally, you might want a parent class. A parent class could have methods to create new children with parameters (e.g., with Employees as a custom collection, set newEmployee = Employees.AddNew(Tom, 38)). For a lot of objects in Excel this is standard: you can't create a Worksheet or a Workbook except from its parent collection.
[1]One other adjustment relates to whether the Construct method is a Sub or a Function. If Construct is called from an object to fill in its own properties, it can be a Sub with no return value. However, if Construct returns Me after filling in the parameters, then the Factory methods/functions in the top 2 answers could leave the parameters to Construct. For example, using a factory class with this adjustment could go: Set Sue = Factory.NewEmployee.Construct("Sue", "50"), where NewEmployee is a method of Factory that returns a blank new Employee, but Construct is a method of Employee that assigns the parameters internally and returns Me.

Another approach
Say you create a class clsBitcoinPublicKey
In the class module create an ADDITIONAL subroutine, that acts as you would want the real constructor to behave. Below I have named it ConstructorAdjunct.
Public Sub ConstructorAdjunct(ByVal ...)
...
End Sub
From the calling module, you use an additional statement
Dim loPublicKey AS clsBitcoinPublicKey
Set loPublicKey = New clsBitcoinPublicKey
Call loPublicKey.ConstructorAdjunct(...)
The only penalty is the extra call, but the advantage is that you can keep everything in the class module, and debugging becomes easier.

Why not this way:
In a class module »myClass« use Public Sub Init(myArguments) instead of Private Sub Class_Initialize()
Instancing:
Dim myInstance As New myClass: myInstance.Init myArguments

Related

How do I turn this code into a code using parameterized new sub?

I like to use Objective-c style of createObject instead of the normal new.
Basically it make it easier for me to just create a basic object and then initialize the variable. Sometimes when I want to create an object of derived class, I simply call a class that initiate the base class.
Also sometimes, often in fact, I want to do some things first before initializing say, the base class, or most of the basic variables.
Something like this
Public Shared Function createOrders(amount As String, base As String, quote As String, exchange As ExchangesClass, time As String, price As String, id As String, sellinginsteadofbuying As String, identifyingText As String) As OrderAlreadyPlacedAtanExchange
Debug.Assert(base <> "")
Debug.Assert(quote <> "")
Debug.Assert(price <> "")
Dim a = New OrderAlreadyPlacedAtanExchange
a.InitializeForBasicSimpleOrder(amount, price, base, quote, exchange, sellinginsteadofbuying, identifyingText)
time = IIf(time = "", "1-1-2000", time).ToString
a._timeOrderPlaced = Date.Parse(time)
a._id = id
If quote = "IFLT" Then
Dim b = 1
End If
Return a
End Function
Basically what happened is that the class OrderAlreadyPlacedAtanExchange inherits from class BasicSimpleOrder.
Class OrderAlreadyPlacedAtanExchange
Inherits BasicSimpleOrder
So if I am doing it my way I create an object of type OrderAlreadyPlacedAtanExchange but then I can initialize the base class of the object with
a.InitializeForBasicSimpleOrder(amount, price, base, quote, exchange, sellinginsteadofbuying, identifyingText)
Since using json.net I am interested in doing it the normal way.
But how can a constructor (or new sub) of OrderAlreadyPlacedAtanExchange initialize their parents?
Should a constructor of OrderAlreadyPlacedAtanExchange called a constructor of BasicSimpleOrder? But then I'll be getting an object of type BasicSimpleOrder
If that's the case then I should keep function InitializeForBasicSimpleOrder and get a new sub to call it then?

Object Property Being Changed When It's Not Supposed To? (Vb.net)

I'll try to keep it simple, but this is making me almost rip my hair out as I do not understand why a certain property is being changed on one of my objects when I am not assigning it a change.
Sample class:
Public Class Person
Public Name As String
Public Age as UInteger
End Class
OK cool.
In my project:
Dim Me as New Person, You as New Person
Me.Name = "John"
You.Name = "Terry"
Me = You
You.Age = 32
So I assigned the Name properties to 'Me' and 'You' respectively as John & Terry. I then coped the properties of 'You' To 'Me'.
When I go change the Age Property of 'You' to 32. The property age of 'Me' ALSO gets changed to 32 even though I never assigned it a change.
A total head scratcher as I am not quite catching the error here?
If you want to create a new object with the same property values then you have no option but to create a new object and copy the property values. There are a number of ways that you can implement the details though.
The most obvious and most basic is to simply create a new object and copy the property values:
Public Class Person
Public Property Name As String
Public Property Age As UInteger
End Class
Dim firstPerson As New Person
firstPerson.Name = "John"
firstPerson.Age = 32
Dim secondPerson As New Person
secondPerson.Name = firstPerson.Name
secondPerson.Age = firstPerson.Age
The next option to consider is to build the functionality into the type itself. If you do this, the first option is to implement the ICloneable interface and use the MemberwiseClone method:
Public Class Person
Implements ICloneable
Public Property Name As String
Public Property Age As UInteger
Public Function Clone() As Object Implements ICloneable.Clone
Return MemberwiseClone()
End Function
End Class
Dim firstPerson As New Person
firstPerson.Name = "John"
firstPerson.Age = 32
Dim secondPerson = DirectCast(firstPerson.Clone(), Person)
The ICloneable.Clone method returns an Object reference, because it must work for any type, so it's not ideal. You might choose not to implement the interface and change the return type, or you could add your own Copy method and cast the result:
Public Class Person
Implements ICloneable
Public Property Name As String
Public Property Age As UInteger
Public Function Clone() As Object Implements ICloneable.Clone
Return MemberwiseClone()
End Function
Public Function Copy() As Person
Return DirectCast(Clone(), Person)
End Function
End Class
Dim firstPerson As New Person
firstPerson.Name = "John"
firstPerson.Age = 32
Dim secondPerson = firstPerson.Copy()
That MemberwiseClone method creates a shallow copy of the current object, which means a direct copy of property value. That's fine for simple types like String and numeric types but may not be for more complex types. For instance, if you had a property that referred to a DataSet then the same property in the new object would refer to that same DataSet object, not a new DataSet containing the same data. The same goes for arrays and collections. If you don't want a shallow copy then you need to write your own code to explicitly create a deep copy in the specific way that you want, e.g.
Public Class Person
Public Property Name As String
Public Property Age As UInteger
Public ReadOnly Property Children As New List(Of Person)
Public Function Copy() As Person
Dim newPerson As New Person With {.Name = Name,
.Age = Age}
For Each child As Person In Children
newPerson.Children.Add(child.Copy())
Next
Return newPerson
End Function
End Class
Dim firstPerson As New Person
firstPerson.Name = "John"
firstPerson.Age = 32
firstPerson.Children.Add(New Person With {.Name = "Jim", .Age = 10})
firstPerson.Children.Add(New Person With {.Name = "Jane", .Age = 12})
Dim secondPerson = firstPerson.Copy()
The reason that's not done by default is that you could end up with a stack overflow. In this case, creating a copy of a Person object will also create a copy of the Person objects in its Children collection. If there is a circular reference there somewhere, i.e. two Person objects were in each other's Children collection then you would just keep creating copies until the system ran out of resources. You need to be very careful when creating deep copies in order to avoid such situations. You need to be very sure that you only go to the depth you need and that you catch any circular references and stop copying when you get back to the start of the chain.
Notice that this last example does require you to specify each property explicitly. It is possible to avoid this if you use Reflection but that is less efficient and, unless you have a ridiculous number of properties, a bit silly as the few minutes it takes to type out those properties should be a very minor annoyance.

VBA List of Custom Datastructures

One of the main problems in VBA are custom data structures and lists.
I have a loop which generates with each iteration multiple values.
So as an example:
Each loop iteration generates a string "name" an integer "price" and an integer "value".
In C# for example I'd create a class which can hold these three values and with each loop iteration I add the class object to a list.
How can I do the same thing in VBA if I want to store multiple sets of data when not knowing how many iterations the loop will have (I cant create an array with a fixed size)
Any ideas?
The approach I use very frequently is to use a class and a collection. I also tend to use an interface model to make things more flexible. An example would look something like this:
Class Module IFoo
Option Explicit
Public Sub Create(ByVal Name as String, ByVal ID as String)
End Property
Public Property Get Name() as String
End Property
Public Property Get ID() as String
End Property
This enforces the pattern I want for my Foo class.
Class Module Foo
Option Explicit
Private Type TFoo
Name as String
ID as String
End Type
Private this as TFoo
Implements IFoo
Private Sub IFoo_Create(ByVal Name as String, ByVal ID as String)
this.Name = Name
this.ID = Name
End Sub
Private Property Get IFoo_Name() as String
IFoo_Name = this.Name
End Property
Private Property Get IFoo_ID() as String
IFoo_ID = this.ID
End Property
We get intellisense from the Private Type TFoo : Private this as TFoo where the former defines the properties of our container, the latter exposes them privately. The Implements IFoo allows us to selectively expose properties. This also allows you to iterate a Collection using an IFoo instead of a Foo. Sounds pointless until you have an Employee and a Manager where IFoo_BaseRate changes depending on employee type.
Then in practice, we have something like this:
Code Module Bar
Public Sub CollectFoo()
Dim AllTheFoos as Collection
Set AllTheFoos = New Collection
While SomeCondition
Dim Foo as IFoo
Set Foo = New Foo
Foo.Create(Name, ID)
AllTheFoos.Add Foo
Loop
For each Foo in AllTheFoos
Debug.Print Foo.Name, Foo.ID
Next
End Sub
While the pattern is super simple once you learn it, you'll find that it is incredibly powerful and scalable if implemented properly. It also can dramatically reduce the amount of copypasta that exists within your code (and thus reduce debug time).
You can use classes in VBA as well as in C#: Class Module Step by Step or A Quick Guide to the VBA Class Module
And to to the problem with the array: you can create an array with dynamic size like this
'Method 1 : Using Dim
Dim arr1() 'Without Size
'somewhere later -> increase a size to 1
redim arr1(UBound(arr1) + 1)
You could create a class - but if all you want to do is hold three bits of data together, I would define a Type structure. It needs to be defines at the top of an ordinary module, after option explicit and before any subs
Type MyType
Name As String
Price As Integer
Value As Integer
End Type
And then to use it
Sub test()
Dim t As MyType
t.Name = "fred"
t.Price = 12
t.Value = 3
End Sub

Pass user input to new instance of class

Alright, I have looked through 20 pages on here and can't find what I'm looking for... I've seen it in C# and other languages.. but not Visual Basic..
Say I have a class:
Public Class Cars
Private doors as integer
Private Liters as double
Private otherStuff as string
' more code'
end class
Say I also have a Form.. an inputForm we'll call it that has numerous textboxes for users to input these characteristics. The first textbox is labeled nameTextBox. Is there any way to assign the string value of that textbox as a new car?
something to the likes of..
dim nameTextBox.value as new car
??
The fields in your class are private, so they arent of much use - no other code will be able to see those values.
Public Class Car
Public Property Make As String
Public Property Model As String
Public Property Year As Integer
' something the class may use/need but doesnt expose
Private DealerCost As Decimal
' the constructor - called when you create a NEW car
Public Sub New(mk As String, md As String)
Make = mk
Model = md
End Sub
...
End Class
By specifying only a constructor which takes params, I am saying that you cannot create a new car without specifying those properties. If the constructor takes no params, then you can create an "empty" car object and set each property individually. You can do both - called overloading - so you can create a car with or without the Make and Model at the outset.
As Public Properties, other code can examine them to see what kind of car this is.
Dim myCar = New Car("Toyata", "Corolla")
myCar.Year = 2013
myCar.Color = Color.Blue
The text used of course can come from user input:
Dim myCar = New Car(tbMake.Text, tbModel.Text)
Dim yr As Int32
If Integer.TryParse(tbYear.Text, yr) Then
myCar.Year = yr
Else
' ToDo: scold user
End If

Function returning a class containing a function returning a class

I'm working on an object-oriented Excel add-in to retrieve information from our ERP system's database. Here is an example of a function call:
itemDescription = Macola.Item("12345").Description
Macola is an instance of a class which takes care of database access. Item() is a function of the Macola class which returns an instance of an ItemMaster class. Description() is a function of the ItemMaster class. This is all working correctly.
Items can be be stored in more than one location, so my next step is to do this:
quantityOnHand = Macola.Item("12345").Location("A1").QuantityOnHand
Location() is a function of the ItemMaster class which returns an instance of the ItemLocation class (well, in theory anyway). QuantityOnHand() is a function of the ItemLocation class. But for some reason, the ItemLocation class is not even being intialized.
Public Function Location(inventoryLocation As String) As ItemLocation
Set Location = New ItemLocation
Location.Item = item_no
Location.Code = inventoryLocation
End Function
In the above sample, the variable item_no is a member variable of the ItemMaster class.
Oddly enough, I can successfully instantiate the ItemLocation class outside of the ItemMaster class in a non-class module.
Dim test As New ItemLocation
test.Item = "12345"
test.Code = "A1"
quantityOnHand = test.QuantityOnHand
Is there some way to make this work the way I want? I'm trying to keep the API as simple as possible. So that it only takes one line of code to retrieve a value.
Every time your function refers to Location, it creates a New ItemLocation (because it recalls the function, recursive like), or so it seems. Maybe you need to isolate the ItemMaster inside the function, like this
Public Property Get Location(inventoryLocation As String) As ItemLocation
Dim clsReturn As ItemLocation
Set clsReturn = New ItemLocation
clsReturn.Item = "item_no"
clsReturn.Code = inventoryLocation
Set Location = clsReturn
End Property
I'm not sure why you use a function instead of a property, but if you have a good reason, I'm sure you can adapt this. I also couldn't figure out where item_no came from, so I made it a string.
You might try separating out the declaration and instantiation of objects in your VBA code. I would also create an object variable local to the function and return it at the end. Try this:
Public Function Location(inventoryLocation As String) As ItemLocation
Dim il As ItemLocation 'Declare the local object '
Set il = New ItemLocation 'Instantiate the object on a separate line '
il.Item = item_no
il.Code = inventoryLocation
Set Location = il 'Return the local object at the end '
End Function
I'm not sure if this is what caused the problem, but I remember reading that VB6/VBA has a problem with declaring and instantiating an object on the same line of code. I always separate out my Dim from my Set in VBA into two lines.
I can't seem to reproduce this, but let me report what I did and maybe that will help you find your problem.
Here is the code for Class1:
Public Function f() As Class2
Set f = New Class2
f.p = 42
End Function
and here is the code for Class2:
Private p_
Public Property Let p(value)
p_ = value
End Property
Public Property Get p()
p = p_
End Property
Private Sub Class_Initialize()
Debug.Print "Class 2 init"
End Sub
Private Sub Class_Terminate()
Debug.Print "Class 2 term"
End Sub
If I go to the immediate window and enter:
set c1=new Class1
and then
?c1.f().p
I get back:
Class 2 init
42
Class 2 term
So an instance of Class2 gets created, it's property 'p' gets written and read, but then VBA kills it after that line executes because no variable has a reference to that instance.
Like I said, this doesn't match up with your problem as described. I am probably missing some point in the details, but I hope this helps.
EDIT:
To clarify, I mean for my simpler example of calling 'c1.f().p' to correspond to your
quantityOnHand = Macola.Item("12345").Location("A1").QuantityOnHand
but my simpler example works just fine. So you now have three answers that amount to "need more info", but it's an interesting little puzzle.
If you're not seeing an instance of 'ItemLocation' get created at all, does that mean you're also not seeing a call to your 'Location' method of class 'ItemMaster'? So possibly the problem is upstream from the 'Location' code posted.