Pass instance of class module in sub procedure - excel

I have a class module called game which calls a sub from another class module called gameUi. This sub receives an instance of a class module called board.
game class module:
Private board As New board
Private gameUi As New gameUi
Sub Init()
gameUi.Init board
End Sub
gameUi class module:
Private board As board
Sub Init(gameBoard As board)
board = gameBoard
End Sub
I keep getting this error and I can't understand why:
I have other sub / functions in other class modules that receive parameters such as Integers and Strings but I don't get this error there. It seems it just happens when I pass an instance of a class module.
I've tried passing another class module to the sub and it happens the same. Also tried adding the ByVal or ByRef options but the result is the error shown.
If anybody could help I would be very appreciated. Thank you for your time!

Related

Can't see created objects

I've created some simple classes in excel and I'm trying to create new objects from these classes. It works fine and let's me create them and I can also access the variables given to the object. I can't see the object in the local window though and I don't really understand why. Is it not created correctly because you are supposed to see your objects there I understand?
Here is the code for the class
Option Explicit
'Teams
Public Name As String
Public Group As String
Public GF As Integer
Public GA As Integer
Public Points As Integer
'Public Players(25) As String
Private Sub class_initialize()
Points = 5
End Sub
and here is the code where I try to create an object
Sub TestTeams()
Dim Madagaskar As Object
Set Madagaskar = New ETeam
MsgBox (Madagaskar.Points)
End Sub
If you put Stop on the line after the MsgBox call and run TestTeams, you will see the object in the locals window.
It will only be there while Madagaskar is in scope and you're in break mode.

Blank values in user form

Trying to get values from userform in module but it always shows blank.
EmailIdForm:
Private Sub CLRBTN_Click()
Call UserForm_Initialize
End Sub
Private Sub OKBTN_Click()
ToInput = EmailIdForm.ToInput.Value
CCInput = EmailIdForm.CCInput.Value
Unload EmailIdForm
End Sub
Private Sub UserForm_Initialize()
ToInput.Value = ""
CCInput.Value = ""
ToInput.SetFocus
End Sub
Module:
Public ToInput As String
Public CCInput As String
Sub EmailInput()
EmailIdForm.Show
MsgBox ToInput
MsgBox CCInput
End Sub
I think the problem is the visibility of the modul level variables ToInput and CCInput. If you name the modul MyModul just add the scope to the variables variables. See the modified code here:
Private Sub OKBTN_Click()
MyModul.ToInput = EmailIdForm.ToInput.Value
MyModul.CCInput = EmailIdForm.CCInput.Value
Unload EmailIdForm
End Sub
To elaborate on the comments;
10000% read UserForm1.Show. It might take some time to understand it (and a lot of googling things) but if you use UserForms it's worth the read to better understand how they work and how to work with them.
If you have a control on your userform with the same name as your public variable, the control will take precedence over the public variable.
So it is assumed that your TextBox names are ToInput and CCInput respecitvely.
When writing code in the UserForm code module, you could access these controls in 3 different ways (let's use the ToInput textbox in our example):
EmailIdForm.ToInput 'Explicitly declare which UserForm we are accessing the control on.
Me.ToInput 'The 'Me' keyword refers to that instance of the class (in the case you could have more than 1 instance of the class loaded).
ToInput 'The UserForm object is implied by it's own class.
The Me Keyword
Avoiding implicit qualifiers
To fix your problem, you will need to remove your naming conflicts. Either rename your variables or rename your textbox controls - *or both!
Consider doing a bit of research about good practices when it comes to naming variables, controls, objects, data types etc. I like to use descriptive names so someone who doesn't already know what the codes is doing can read it and understand - but this can come at the cost of making the code quite verbose - or - dense.
Consider chaning the names like this: (Note: It is entirely up to you how you name things in your code, this is just a suggestion.)
Module:
----------------------------------------
Public PublicToInputString As String
Public PublicCCInputString AS String
Sub EmailInput()
EmailIdForm.Show
MsgBox PublicToInputString
MsgBox PublicCCInputString
End Sub
-----------------------------------
Userform:
-----------------------------------
Private Sub OKBTN_Click()
PublicToInputString = Me.ToInputTextBox.Value
PublicCCInputString = Me.CCInputTextBox.Value
Unload Me 'Consider chaning to Me.Hide
End Sub
Now the code is clear and telling us what is what - Anyone could understand you are assigning a textbox value to a public variable string and following a naming convention like this also means you are far less likely to have naming conflicts - Again - Note: this is not a naming convention you must follow, merely a suggestion. Everyone has their own way that works for them.

Excel VBA - Return Control to Calling Method when the called method has already called another method

I have a model simluation coded in Excel VBA. It is built inside of a class module named "ChemicalRelease". There is another Class module named "UniversalSolver" which works to optimize parameters of the ChemicalRelease.
While running different simulations, universalSolver will sometimes use a combination of parameters that goes outside of the modeling bounds of the application. It is difficult to determine the true modeling boundaries as it is based on multiple combinations of parameters.
An instance of UniversalSolver will create a set of input parameters and instantiate ChemicalRelease to run a model as specified. Inside of ChemicalRelease, the flow works within several methods such as "setden" which may call other methods to perform their calculation. For example, "setden" may call "tprop" to determine thermodynamic properties, and "tprop" may in turn call a function to iteratively solve for a value.
At any point within any of these methods, the model may determine that the combination of input parameters cannot be solved. The current configuration notifies me of the issue thru a msgbox and stops the program, bringing it into debug mode.
I would like to make use of an event handler that will set a value of an instance of a handler that will stop calculations within "ChemicalRelease", set the instance to "Nothing" and return control to "UniversalSolver", directly after the line where "ChemicalRelease" was instantiated and called for modeling.
serveral google searches, and none point to a way to return control to "UniversalSolver".
'event handler code: credit to Change in variable triggers an event
"ClassWithEvent" class
Public Event VariableChange(value As Integer)
Private p_int As Integer
Public Property Get value() As Integer
value = p_int
End Property
Public Property Let value(value As Integer)
If p_int <> value Then RaiseEvent VariableChange(value) 'Only raise on
actual change.
p_int = value
End Property
"ClassHandlesEvent" class
Private WithEvents SomeVar As ClassWithEvent
Private Sub SomeVar_VariableChange(value As Integer) 'This is the event
handler.
'line here to return control to "UniversalSolver" instance, out of
"ChemicalRelease" instance, regardless of how many methods have to be
returned out of within ChemicalRelease.
End Sub
Public Property Get EventVariable() As ClassWithEvent
Set EventVariable = SomeVar
End Property
Public Property Let EventVariable(value As ClassWithEvent)
Set SomeVar = value
End Property
"Globals" Module
'Globally set instances for ClassHandlesEvent and ClassWithEvent
Global VAR As ClassHandlesEvent
Global TST As ClassWithEvent
"UniversalSolver" class
Public Sub initialize()
Set VAR = New ClassHandlesEvent
Set TST = New ClassWithEvent
VAR.EventVariable = TST
End Sub
Public Sub solve()
Do 'iterate through potential input parameters
Set m_chemRelease = New ChemicalRelease
m_chemRelease.initialize 'initializes and launches modeling
Loop until satisfied
End Sub
"ChemicalRelease" class
Public Sub initialize(modelParamsSheet As Worksheet)
Set m_modelParamsSheet = modelParamsSheet
Call readModelInputsAndSetProperties(0)
End Sub
Private Sub readModelInputsAndSetProperties(inNum As Integer)
'set all properties and launch modeling
Call setjet(0)
End Sub
Private Sub setjet(inInt As Integer)
'lots of math.
call tprop(tpropsInputDict)
'lots more math.
End Sub
Private Sub tprop(inDict as Scripting.Dictionary)
'more math.
'check for convergence
If check > 0.00001 Then
'failed convergence
'trigger event to exit ChemicalRelease Instance and return control
to UniversalSolver instance
TST.value = 2
End If
'more math.
Call limit()
End Sub
Private Sub limit()
'more math.
'check for sign
If fa * fb > 1 Then
'failed convergence
'trigger event to exit ChemicalRelease Instance and return control
to UniversalSolver instance
TST.value = 2
End If
'more math.
End Sub
Expected results are to have an event which can be triggered at any location within the project that will return control to UniversalSolver as if I was stating "exit sub" from within ChemicalRelease.initialize. However, I cannot find a valid method for this.
Error handling in the calling function works for all called functions. However, the "resume" command is required to take VBA out of error-handling mode. Per the code below, flow is returned to normal mode at the "endoffor" label in the calling function.
errcatch:
Err.Clear
On Error GoTo errcatch
Resume endoffor '

Interface Problems

I tried to implement a new class that is implementing two interfaces in Excel VBA but I have compilation problems as the members of the interfaces do not seem to be properly implemented in the implementing class (they are not callable).
The Interfaces are the following:
ICrawlable:
Option Explicit
Public Function GetCrawler() As ICrawler
End Function
IEquatable:
Option Explicit
Public Function Equals(CompareObject As Variant) As Boolean
End Function
Whereas ICrawlable also holds a function returning the interface ICrawler:
Option Explicit
Public Property Get CurrentItem() As Variant
End Property
Public Sub MoveNext()
End Sub
Public Function GetNext() As Variant
End Function
Public Function ItemsLeft() As Boolean
End Function
I created a sample class InterfaceTester using those two first interfaces:
Option Explicit
Implements ICrawlable
Implements IEquatable
Private Function ICrawlable_GetCrawler() As Variant
End Function
Private Function IEquatable_Equals(CompareObject As Variant) As Boolean
End Function
When trying to use that class in a Module or wherever else I do not have Intellisense support. Furthermore, the code does not compile and I get a "Method or Data member not found" in this module code highlighting the .Equals:
Sub TestInterfacing()
Dim TestInstance As InterfaceTester
Set TestInstance = New InterfaceTester
Dim VerificationInstance As InterfaceTester
Set VerificationInstance = New InterfaceTester
Dim Result As Boolean
Result = TestInstance.Equals(VerificationInstance)
End Sub
Is this a bug in VBA? Have I declared things in the interfaces that are not allowed (I have tried changing all return data types to Variant already and have tried disabling each of the functions of the interfaces)? Do I use reserved keywords (in the object explorer I do not see duplicates of my interface names)?
Does it compile on your machines?
If you want to use methods from the interface, your variable must be declared as this interface type:
Sub TestInterfacing()
Dim TestInstance As IEquatable 'InterfaceTester
Set TestInstance = New InterfaceTester
Dim VerificationInstance As InterfaceTester
Set VerificationInstance = New InterfaceTester
Dim Result As Boolean
Result = TestInstance.Equals(VerificationInstance)
End Sub
However, in this case you cannot use methods from the second interface implemented by this class: ICrawlable.
The reason for this is that in VBA implementing method looks like that:
Private Function ICrawlable_GetCrawler() As ICrawler
while using the rules from other languages it should look like below:
Public Function GetCrawler() As ICrawler
VBA would not understand that this is implementation of interface's method GetCrawler.
In order to overcome this issue you should add another two public methods to InterfaceTester class - Equals and GetCrawler. Implementing methods should only direct to those methods:
InterfaceTester class:
Implements ICrawlable
Implements IEquatable
Public Function Equals(CompareObject As Variant) As Boolean
'implementation
End Function
Public Function GetCrawler() As ICrawler
'implementation
End Function
Private Function ICrawlable_GetCrawler() As ICrawler
Set IEquatable_Equals = GetCrawler
End Function
Private Function IEquatable_Equals(CompareObject As Variant) As Boolean
IEquatable_Equals = Equals(CompareObject)
End Function
Now you can declare your variable TestInstance as InterfaceTester class and use methods from both interfaces.

Build Object Hierarchy in Excel-VBA

I'm losing quite some time copy-pasting identical properties and methods in various vba custom object I'm building. How do I create an custom-object hierarchy in VBA so one object and inherit properties and methods from others.
In python I would prob write something like:
Class Car(Object)
whatever
Class SlowCar(Car)
inherit whatever
Class FastCar(Car)
inherit whatever
tks in advance.
If i understand what you're saying, this can be done via the Class Module.
From the VBA Editor, select Insert > Class Module
Change the name of the class Module to whatever you want (Car for
example) via the Properties Window (press F4 to make it appear if it
does not already)
Now that you've created your class module you can define its variables and/or properties. The example below would go into your Car Class Module creates a object that holds a car name and a speed
Private carName As String
Private carSpeed As Integer
' Car Name
Public Property Get Name() As String
Name = carName
End Property
Public Property Let Name(result As String)
carName = result
End Property
' Car Speed
Public Property Get Speed() As Integer
Speed = carSpeed
End Property
Public Property Let Speed(result As Integer)
carSpeed = result
End Property
Then from your Module, you can do the following
Sub CreateMyCars()
Dim slowCar as Car
Dim fastCar as Car
Set slowCar = New Car
Set fastCar = New Car
slowCar.Name = "GoKart"
slowCar.Speed = 35
fastCar.Name = "Ferarri"
fastCar.Speed = 185
End Sub
VBA supports inheritance through the use of Interfaces, but they only "inherit" the signature of methods, not the implementation.
A way to reuse an object implementation would be through composition.
Class Car(Object)
whatever
Class SlowCar(Car)
Implements Car
private mCar as Car
Private Sub Class_Initialize
set mCar = new Car
End Sub
Private Sub Car_whatever
Call mCar.whatever
End Sub
And same for FastCar.

Resources