Excel VBA, Custom Class: Function call raises "method not supported" error - class

I am writing my first classes.
One is cCRElist which is essentially a collection of cCRE instances (some specialized events).
I want there to be a sub or function inside cCRElist that will load all the CRE's from the worksheet into one big collection I can work with. I created the function and it worked OK when I called it from a normal code module, but then I tried to move the code into the class. Now I am having trouble calling the function LoadFromWorksheet(myWS as Worksheet).
The error is "object does not support this property or method". I have tried making it a sub, a function, making it public, not public, I have tried turning into a Property Let instead of a sub. Obviously I have a flimsy grasp on what that does. I have tried
Call CREList.LoadFromWorksheet(myWS)
and
CREList.LoadfromWorksheet myWS
Same error every time.
Here is the test code that uses the class and calls the function:
Sub TestClassObj()
Dim CRElist As cCRElist
Set CRElist = New cCRElist
Dim myWS As Worksheet
Set myWS = ThisWorkbook.ActiveSheet
CRElist.LoadFromWorksheet (myWS)
End Sub
Here is a snippet of the class cCRElist:
' **** CLASS cCRElist
Option Explicit
' This is a collection of CRE objects
Private pCRElist As Collection
Private Sub Class_Initialize()
Set pCRElist = New Collection
End Sub
Public Property Get CREs() As Collection
Set CREs = pCRElist
End Property
Public Property Set Add_CRE(aCRE As cCRE)
pCRElist.Add aCRE
End Property
Function LoadFromWorksheet(myWS As Worksheet)
Dim CRE As cCRE
Dim iFirst As Long
Dim iLast As Long
Dim i As Long
Set CRE = New cCRE
iFirst = gHeader_Row + 1
iLast = FindNewRow(myWS) - 1
' update data in CRE then add
For i = iFirst To iLast
If myWS.Cells(i, gCRE_Col) <> "" Then ' This is a CRE row
Set CRE = New cCRE
With CRE
.CRE_ID = myWS.Cells(i, gCRE_Col)
If Not IsDate(myWS.Cells(i, gCRE_ETA_Col)) Then
.ETA = "1/1/1900"
Else
.ETA = Trim(myWS.Cells(i, gCRE_ETA_Col))
End If
<... snipped ...>
End With
pCRElist.Add_CRE CRE
End If
Next
End Sub
' **** END OF CLASS cCRElist
Thanks for your expertise.

Here is what worked based on help I got in the comments. First, I did the "break in class module". In the test code, I changed the function call from:
CRElist.LoadFromWorksheet(myWS)
to
CRElist.LoadFromWorksheet myWS
Inside the class, i had to change
Set pCRElist.Add_CRE CRE
to
pCRElist.Add CRE
Then I was able to get rid of extraneous CLASS functions Add_CRE and Count.
Thanks for everyone's input. I couldn't figure out how to mark comments as accepted answers so I did this. Let me know if I need to do something differently.
Now it works!

Related

Why does my form disappear in a split second? VBA

I have a sub which instantiates a new form, makes it visible. However the new form lasts only for a very brief moment. This is my code:
Private Sub txtProject_ID Click()
Dim frmReq as Form
Set frmReq = New Form_Request
frmReq.Visible = True
End Sub
I tried to set break points on this and found out the form becomes visible when I set its visibility to true. However it disappears as soon as the Sub ends.
Edit:
How to create multiple instances of the pop up form?
In Access VBA you use
DoCmd.OpenForm "Request"
If you need to open it invisible you do
DoCmd.OpenForm "Request", WindowMode:=acHidden
' Initialize stuff while invisible
Forms!Request!foo = "bar"
' show form
Forms!Request.Visible = True
To open multiple instances (would have helped to mention that in the question) and pass a parameter, create a public initialization function in the form:
Public Sub InitForm(ReqId As Long)
' e.g.
Me.Filter = "Request_ID = " & ReqId
Me.FilterOn = True
'show me
Me.Visible = True
End Sub
and call it like this (see bottom of https://msdn.microsoft.com/en-us/library/office/ff845819.aspx)
Option Compare Database
Option Explicit
' This variable must be on *module* level!
Dim frmReq as Form
Private Sub txtProject_ID Click()
Set frmReq = New Form_Request
Call frmReq.InitForm(Me!txtProject_ID)
End Sub
The variable/object of scope is limited to sub procedure so it needs to be form level.

Class Object update using a form

I am creating a tool in VBA to create labels with information about transactions.
I would like the user to use a form to give all the information and the labels will be automatically created.
To do so I have created a transaction class called clsTransaction.
Public PartNum As String
Private Sub Class_Initialize()
MsgBox "initialized!"
End Sub
At the initilalisation of the form a new object is created
Private Sub UserForm_Initialize()
(...)
Dim trans As New clsTransaction
End Sub
Then when a textbox is updated, I want to update the object property
Private Sub txtPartNum_AfterUpdate()
Dim strPartNum As String
strPartNum = txtPartNum.Value
MsgBox (strPartNum)
trans.PartNum = strPartNum
End Sub
I get the following error message
Run-time error '424':
Object required
My understanding is I need to declare the obect. I have tried to add:
dim trans as object
but I get an other error message:
Run time error '91':
object variable or With block variable not set
Could someone help me to update the object through the form?
The problem seems to be in the scope of your declaration. By doing this:
Private Sub UserForm_Initialize()
(...)
Dim trans As New clsTransaction '<-- declaration + assignment
End Sub
you are declaring trans in the scope of UserForm_Initialize(). Hence, when you then try to do something with it into another scope:
Private Sub txtPartNum_AfterUpdate()
(...)
trans.PartNum = strPartNum
End Sub
the object trans doesn't exist anymore because it's out of scope (so it has already been collected by the garbage collector) and you get an "object required error". You need to declare trans at global level, this is the idea:
Dim trans as yourClass '<-- global declaration: the object trans will keep on existing into all scopes
Private Sub UserForm_Initialize()
Set trans = New yourClass '<-- new instance of the object
End Sub
Private Sub txtPartNum_AfterUpdate()
'use the instance here
trans.PartNum = strPartNum
End Sub

What is the proper syntax for using an object as a class property in excel vba?

I'm trying to use the worksheet object as a property in a vba class module. Hers' what I have in a a class module called clsAgent:
Public Property Get AgentSheet() As Worksheet
Set AgentSheet = pAgentSheet
End Property
' error thrown on next two lines
Public Property Set AgentSheet(AgentSheet As Worksheet)
Set pAgentSheet = AgentSheet
End Property
When I use the the following code I get a `compile error: Variable not defined" thrown in the class module:
Sub test_agent_class()
Dim agent1 As clsAgent
Set agent1 = New clsAgent
agent1.AgentSheetName = "agentsFullOutput.csv"
Set agent1.AgentSheet = Worksheets(agent1.AgentSheetName)
Debug.Print agent1.AgentSheet.Name
End Sub
Please try to add in your clsAgent module file the following line:
Dim pAgentSheet As Worksheet
since the properties you have created ought to reference an existing pAgentSheet field of the clsAgent module.
Please try also the modified test file:
Sub test_agent_class()
Dim agent1 As clsAgent
Set agent1 = New clsAgent
Set agent1.AgentSheet = Worksheets("agentsFullOutput.csv")
Debug.Print agent1.AgentSheet.Name
End Sub
I'm not 100% sure but it looked like you were referencing a variable/propertyAgentSheetName which didn't exist in the clsAgent class module.
I hope it helps at least slightly.

VBA Class Method Chaining

I'm looking for a way to 'chain class methods', for example the Range object can do things like "Range.Borders.Color", I guess that the Borders part is it's own class which is being accessed by the Range class but I have no idea how to implement something similar with my own classes - Nor do I even know what this is called and after hours of searching I think I might slowly be un-learning VBA.
Can anybody either a) Provide code which I could look at to replicate or b) Tell me what this is called and maybe even nudge me in a helpful direction?
As I know asking for code without providing any makes me look like a dick, consider the following pseudo-code. I know it's horrific but it might help me make any sense:
main ------------------------------------------------------------------------
Dim obj as class1
set obj = new class1
obj.Target = Range("A1:B5")
obj.Borders.Add
'A1:B5 put into modRange then given borders
class1 ------------------------------------------------------------------------
Private modRange as range
Public Property Let Target(newTarget as Range)
set modRange = newTarget
End Property
Public Property Borders()
Public Sub Add()
'Code to add borders to modRange
End Sub
Public Sub Remove()
'Code to remove borders from modRange
End Sub
End Property
I know this is not how the actual code would look. but as I don't know the syntax this is the closest thing I can imagine. I guess the real thing would have class1 linking to other class modules. Maybe.
As a side note. If I did have a class called "Borders" (I probably wont) as part of this class 1 object, would it conflict with the Borders portion of the Range object as well as it has a similar name? Or will the Private scope save the day?
(The .Borders.Add/Remove is a bit ridiculous to have as a class I know, I'm really only after the syntax - Honest)
To have complex properties of an object, you need to create a new class and then create an instance of that class in the parent class. So if you want to have something like Class1.Borders.Add(), you'd first have to create a new CBorders class (I used to prepend C to my class names in VB6 / VBA to avoid name collisions). Something like:
'- in class CBorder
Private m_lColor As Long
Public Property Get Color() As Long
Color = m_lColor
End Property
Public Property Let Color(ByVal lNewColor As Long)
m_lColor = lNewColor
End Property
Public Sub Reset()
m_lColor = 0
End Sub
...
Then inside Class1, you'd have something like this:
Private m_oBorder As CBorder
Private Sub Class_Initialize()
...
Set m_oBorder = New CBorder
...
End Sub
Public Property Get Border() As CBorder
Set Border = m_oBorder
End Property
...
Then you can do this:
Dim obj As Class1
Set obj = New Class1
obj.Borders.Color = ...
...
Notice how the Borders property of Class1 is accessed as a member of the obj instance and then how the Color property of the CBorder class is used. Creating these values as properties is what lets you chain these calls together.
You'd need error checking and validation code as well - I left those out to keep the example short.
Another solution for this is to just return Me to make it chainable.
class module: CChaining
Using Functions for Target and Borders returning Me to enable chaining. Using Subs for Add and Remove to "finish" the chain.
Private modRange As Range, modRangeBorders As Object
Public Function Target(rng As Range)
Set modRange = rng
Set Target = Me
End Function
Public Function Borders()
Set modRangeBorders = modRange.Borders
Set Borders = Me
End Function
Public Sub Add()
modRangeBorders.LineStyle = xlContinuous
End Sub
Public Sub Remove()
modRangeBorders.LineStyle = xlNone
End Sub
Testing the class in a module
Sub testing()
Dim obj As New CChaining
obj.Target(Range("A1:B5")).Borders.Add
'now the target and property (Borders) is set and you could do this
'obj.Remove
End Sub
Pretty nice ... hmm, with this it would be possible to build a library like in other languages (javascript > jQuery) to make using Excel VBA much easier.

VBScript: Function visibility inside a Class with identical property name

Given that we have a script
Option Explicit
Class CClass
Private m_date
Private Sub Class_Initialize()
m_date = CDate("1970-01-01 00:00:00")
End Sub
Public Function Foo()
Dim d : d = Date()
WScript.Echo "d is " & FormatDateTime(d, vbGeneralDate)
End Function
Public Property Get Date()
Date = m_date
End Property
Public Property Let Date(p_date)
m_date = CDate(p_date)
End Property
End Class
Dim obj : Set obj = NEW CClass
Call obj.Foo()
How can class function CClass.Foo() call built-in VBScript function Date() without the property CClass.Date interfering?
My current solution is to introduce a dummy Date_() function which can be called instead. But that just seems wrong. I'm thinking there should be some way to specify that we want to call something outside the class scope.
I am almost positive that there is no way to do what you're asking in VBScript.
But even if you could figure out a way to do this, you really shouldn't. You need to choose names for your own functions that don't conflict with the names of built-in functions. Anything else is completely unmaintainable for a dynamic scripting language like VBScript.
Pick a different name for your Date property. Preferably something more descriptive: what kind of date does that property return? What does the date refer to? How is it likely to be used? Whatever you do, don't rename it to Date_—that's not any better.
You can call it from inside the class like:
Dim d : d = me.Date()
Me in VBScript is the same as you use This in Javascript for example
Too late to the party, but there is a solution using eval function.
see eval function docs for details
Class CClass
Private m_date
Private Sub Class_Initialize()
m_date = CDate("1970-01-01 00:00:00")
End Sub
Public Function Foo()
Dim d : d = eval("Date()")
WScript.Echo "d is " & FormatDateTime(d, vbGeneralDate)
End Function
Public Property Get Date()
Date = m_date
End Property
Public Property Let Date(p_date)
m_date = CDate(p_date)
End Property
End Class
Dim obj : Set obj = NEW CClass
Call obj.Foo()