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

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?

Related

How to create a list of multiple object types and preserve methods/properties?

I have a program that de-serializes stuff from an xml file and does all kinds of fancy things with it. I have 2 arrays in the XML file, one called variables and one called lookupTables. I also have 2 classes, variable and lookupTable. Both of those classes inherit from a class called definition. definition is inherit only and has one method that must be inherited, evaluate. Here is the code:
Definition
Public MustInherit Class Definition
Public Sub New()
End Sub
<XmlAttribute("name")> _
Public Property name As String
Public MustOverride Function evaluate(variables As Dictionary(Of String, Double)) As Double
End Class
Variable
<XmlRoot("variable")> _
Public Class Variable
Inherits Definition
<XmlAttribute("value")> _
Public Property value As String
Public Overrides Function evaluate(variables As Dictionary(Of String, Double)) As Double
Dim calculator As CalculationEngine = New CalculationEngine()
Return calculator.Calculate(value, variables)
End Function
End Class
LookupTable
<XmlRoot("lookupTable")> _
Public Class LookupTable
Inherits Definition
Public Sub New()
End Sub
<XmlElement("data")> _
Public Property data As Integer()()
Public Overrides Function evaluate(variables As Dictionary(Of String, Double)) As Double
Return True
End Function
End Class
My question is (hopefully) very simple. How can I create a list of Defintions (so a list containing both Variables and LookupTables) without loosing their individual methods and properties. All I will need to use this list for is calling evaluate.
I thought I could just create a List(Of Definition) since both Variable and LookupTable are guaranteed to implement evaluate() but as I read, it seems that typecasting both of the lists would strip them of their own innards and keep onluy what is common with Definition. What can I do here?
Since both your objects inherit from definition, you could create a list of Definition items then when you need to access specific methods, you cast them to their proper type using directCast to their specific type. To determine the type, you can use
If you had multiple variables types not inheriting from the same base, you could create a list of objects and apply the same idea.
'List of definition item
Private _List As New List(Of Definition)
'When you want to use specific method, you can cast items back to their types.
For Each Item As Definition In _List
Select Case Item.GetType
Case GetType(LookupTables)
Dim Table As LookupTables = DirectCast(Item, LookupTables)
Table.msg() 'Method in my LookupTables class only.
Case GetType(Variables)
Dim Variable As Variables = DirectCast(Item, Variables)
Variable.WriteToConsole() 'Method found in Variables class only.
End Select
Next
As for casting,
you can cast your LookupType to definition and vice-versa to use their respective methods as needed.
The simple answer was to use an ArrayList.

So a VB interface can't have shared functions. Is there an alternative to creating dummy objects?

To avoid getting into the weeds on my particular program, let me just create a simplified case.
I have a generic class that should work on a variety of objects. Each of those objects must implement a certain interface.
What I WANT to say is something like:
Public Interface GenThing
Shared Function thing_name() As String ' This doesn't work! Can't be shared!
Sub FillOne(row As DataRow)
End Interface
public class Thing1
implements GenThing
public shared function thing_name() as string implements GenThing.thing_name
return "thing number one"
end function
public sub FillOne(row as DataRow) implements GenThing.MakeOne
... bunch of work ...
end sub
end class
public class ThingUtil(of T as {GenThing,New})
public function GetList(id as integer) as List(of T)
dim name=T.thing_name() ' This doesn't work!
dim ds as DataSet=GetData(name,id) ' bunch of work here that's the whole point of the class but not relevant to the question
dim my_list = new List(of T)
for each row as DataRow in ds.tables(0).rows
dim my_t = new T()
my_t.FillOne(row)
my_list.add(my_t)
next
return my_list
end function
end class
Do you get my problem? I need every class that implements the interface to have a function that returns a "name" that is used to get the data that is needed to create an instance of the object. But I need to know this name BEFORE I create the instance, because I need it to be able to create the instance. But VB doesn't allow an interface to have a shared function, so what I want to write doesn't work.
So what I've done is this:
I make thing_name not shared.
Then instead of simply "dim name=T.thing_name()", I write
dim dummy = new T()
dim name = dummy.thing_name()
Okay, it works, but it seems really ugly. I create an instance of the object, with all the overhead that that involves, just to get a piece of constant text.
Is there a better way? Or am I making a big deal out of nothing?
Update
I see that two people voted to close this question on the grounds that it is the same as "Why can't we have shared functions in an interface?"
I am not asking why I can't have a shared. I am saying, GIVEN that I can't, how do I solve this particular problem?
There's no really simple way of fixing this, no.
Depending on what thing_name does, however, you might approach things in a different way. If each implementation just returns a constant value, then it's effectively metadata about the class - and could be described in an attribute instead, which can be fetched at execution time. (See Type.GetCustomAttributes.) Unfortunately you can't then enforce all types implementing the interface to be decorated with the attribute - but you could write a unit test to check this pretty easily.
If thing_name needs to really do work at execution time, that's tougher. You could potentially look for a well-known shared method name instead and execute that via reflection (and again have unit tests to check that it's implemented properly).
I realize this is from a few years ago, but running into a similar problem, I wanted to offer a different solution. Pass a delegate as parameter to the ThingUtil constructor. You avoid having to put a shared method in an interface, and the constructor will force you to include the parameter at compile time.
You can add more delegates if needed, or to make it even simpler in this case, just pass name as a string instead of get_name as a delegate.
Define the delegate in the interface:
Public Interface GenThing
Delegate Function ThingNameDelegate() As String
Sub FillOne(row As DataRow)
End Interface
Public Class Thing1
Implements GenThing
Public Shared Function thing_name() As String 'name this whatever you want
Return "thing number one"
End Function
Public Sub FillOne(row As DataRow) Implements GenThing.FillOne
'do stuff
End Sub
End Class
In ThingUtil, add a member to store the delegate, a constructor parameter to to accept, and call it with .Invoke():
Public Class ThingUtil(Of T As {GenThing, New})
Private m_thing_name As GenThing.ThingNameDelegate
Public Sub New(thing_name As GenThing.ThingNameDelegate)
m_thing_name = thing_name
End Sub
Public Function GetList(id As Integer) As List(Of T)
Dim name = m_thing_name.Invoke()
Dim ds As DataSet = GetData(name, id) ' bunch of work here that's the whole point of the class but not relevant to the question
Dim my_list = New List(Of T)
For Each row As DataRow In ds.Tables(0).Rows
Dim my_t = New T()
my_t.FillOne(row)
my_list.Add(my_t)
Next
Return my_list
End Function
End Class
Finally, use it like this:
Dim tu as new ThingUtil(Of Thing1)(AddressOf Thing1.get_name)
tu.GetList(1)

Pass arguments to Constructor in 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

How can I get a property name for a type without the need to instantiate an object of that type?

I have a requirement where I need to have a "type safe" way of accessing property names, without actually instantiating an object to get to the property. To give an example, consider a method that takes as arguments a list of IMyObject and a string that represents a property name (a property that exists in IMyObject).
The methods implementation will take the list and access all the objects in the list using the property name passed... for some reason or another, we won't dwell on that!!
Now, I know that you can do this using an instantiated object, something like ...
Dim x as MyObject = nothing
Dim prop As PropertyInfo = PropHelper.GetProperty(Of MyObject)(Function() x.MyProperty)
Where my helper method uses reflection to get the name of the property as a string - there are numerous examples of this flying around on the web!
But I don't want to have to create this pointless object, I just want to do something like MyObject.MyProperty! Reflection allows you to iterate through a types properties and methods without declaring an object of that type... but I want to access a specific property and retrieve the string version of its name without iteration and without declaring an object of that type!
The main point here is that although I am trying to get the property name as a string... this is done at run time... at compile time, I want this to be type safe so if someone changes the property name, the compilation will break.
Can anyone help in this quest!?!
So here is a quick code-listing to demonstrate the answer that I was looking for:
Imports System.Linq.Expressions
Public Class A
Public Prop1 As String
Public Prop2 As Integer
End Class
Public Class Form1
Public Function GetPropertyNameB(Of TModel, TProperty)(ByVal [property] As Expression(Of Func(Of TModel, TProperty))) As String
Dim memberExpression As MemberExpression = DirectCast([property].Body, MemberExpression)
Return memberExpression.Member.Name
End Function
Public Sub New()
InitializeComponent()
Dim propertyName As String = GetPropertyNameB(Function(myObj As A) myObj.Prop1)
Dim propertyName2 As String = GetPropertyNameB(Function(myObj As A) myObj.Prop2)
MsgBox(propertyName & " | " & propertyName2)
End
End Sub
End Class
You may be able to pass the property in as a simple lamdba expression, and take it in the method as an expression tree. You should be able to analyze the expression tree to get the string name of the property, but it the lambda expression will fail to compile if the property name changes. Check out this page for more details:
http://msdn.microsoft.com/en-us/library/bb397951.aspx
You can make use of the NameOf function:
Dim fieldName = nameOf(MyClass.MyField)

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.