Making Excel Worksheet a Class Module member - class

I'm trying to create a class module to build some basic db functionality in a workbook. The problem I'm running into is attempting to add a worksheet as a class member. I keep getting "Invalid use of property" as an error.
My class declaration:
Option Explicit
Private pboolLock As Boolean
Private pintColCount, pintRowCount As Integer
Private pWorksheet As Excel.Worksheet
'Lock bit properties:
Property Get boolLock() As Boolean
boolLock = pboolLock
End Property
Property Let boolLock(boollockval As Boolean)
pboolLock = boollockval
End Property
'Utility properties- no sets
Property Get ColCount() As Integer
ColCount = pintColCount
End Property
Property Get RowCount() As Integer
RowCount = pintRowCount
End Property
'Worksheet specific props
Property Set dpDefine(ByRef wks As Worksheet)
Set pWorksheet = wks
End Property
Property Get dpDefine() As Worksheet
dpDefine = pWorksheet
End Property
Different module: Class instantiation:
Sub tryClass()
Dim thisdp As New Cdatapage
Dim iansTest As String
iansTest = Sheets("typical datapage").Name
'this works, so reference is being passed:
MsgBox ("The name is " & iansTest)
'this doesn't work:
thisdp.dpDefine (Sheets("typical datapage"))
End Sub
Any suggestions? Thanks.

Its a Set property so you need to:
Set thisdp.dpDefine = Sheets("typical datapage")
Or if you change dpDefine to a Let you can;
thisdp.dpDefine = Sheets("xxx")

Related

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.

using class module properties in UDF's excel vba

I have the following function that works fine:
Function GetAgentEmailWorksheet(AgentObjectId As String)
Dim specific_agent As clsAgent
Set specific_agent = New clsAgent
specific_agent.AgentSheetName = "agentsFullOutput.csv"
Dim id_array() As Variant
id_array = specific_agent.AgentIDArray
Dim email_array() As Variant
email_array = specific_agent.AgentEmailArray
GetAgentEmailWorksheet = vlook_using_array(AgentObjectId, id_array, email_array)
End Function
When, however, I change the last line to:
GetAgentEmailWorksheet = vlook_using_array(AgentObjectId, specific_agent.AgentIDArray, specific_agent.AgentEmailArray)
I get the following error:
Compile error:
Type mismatch:array or user-defined type expected
and it hi-lights AgentIDArray (or AgentEmailArray if I sub out the first parameter.
Why?
EDIT
here's the fuction vlook_using_array:
Function vlook_using_array(target_string As String, _
input_array() As Variant, _
output_array() As Variant)
Dim rows_dim As Long
Dim cols_dim As Integer
For rows_dim = 1 To UBound(input_array, 1)
For cols_dim = 1 To UBound(input_array, 2)
If input_array(rows_dim, cols_dim) = target_string Then
vlook_using_array = output_array(rows_dim, cols_dim)
End If
Next cols_dim
Next rows_dim
End Function
Here are the clsAgent properties:
Public Property Get AgentClientsArray() As Variant
AgentClientsArray = get_column_array(AgentClientsCol)
End Property
Public Property Get AgentIDArray() As Variant
AgentIDArray = get_column_array(1)
End Property
Public Property Get AgentEmailArray() As Variant
AgentEmailArray = get_column_array(AgentEmailCol)
End Property
Here's the function that's in the class module:
Private Function get_column_array(col_num As Integer) As Variant
' create a range out of the used range (of the sheet) in the column specified
' used to create array properties in the class
Dim total_rows As Long
total_rows = Worksheets(Me.AgentSheetName).UsedRange.rows.Count
Dim target_range As Range
With Worksheets(Me.AgentSheetName)
Set target_range = .Range(.Cells(1, col_num), .Cells(total_rows, col_num))
End With
Dim target_arr() As Variant
target_arr = target_range
get_column_array = target_arr
End Function
There's a difference between this:
Public Property Get AgentIDArray() As Variant
AgentIDArray = get_column_array(1)
End Property
and
Public Property Get AgentIDArray() As Variant()
AgentIDArray = get_column_array(1)
End Property
The first (which you have in your class) returns a Variant which happens to be an array, the second an array of variants.
So, the return value of your AgentIDArray is not consistent with the parameter types expected by vlook_using_array

VBScript class property is empty after setting a value

I made a class in VBScript and used it in asp classic to instatiate an object:
This is my class:
<%
Class RBAC
Public dateTimeValue
Public userIdValue
Public fileIdValue
Public actionValue
Public Property Get DateTime()
'Gets the propery value
DateTime = dateTimeValue
End Property
Public Property Set DateTime(value)
'Sets the property value
dateTimeValue = value
End Property
Public Property Get UserId()
'Gets the propery value
UserId = userIdValue
End Property
Public Property Set UserId(value)
'Sets the property value
userIdValue = value
End Property
Public Property Get FileId()
'Gets the propery value
FileId = fileIdValue
End Property
Public Property Set FileId(value)
'Sets the property value
fileIdValue = value
End Property
Public Property Get Action()
'Gets the propery value
Action = actionValue
End Property
Public Property Set Action(value)
'Sets the property value
actionValue = value
End Property
Public Sub Insert()
sqlMethods = "INSERT INTO RBAC ([DateTime],[UserId],[FileId],[Action]) VALUES ("+dateTimeValue+","+userIdValue+","+fileIdValue+","+actionValue+",)"
Conn.Execute(sqlMethods)
End Sub
End Class
%>
And here I instantiate an object and set it's properties:
Dim RbacObject
Set RbacObject = New RBAC
Set RbacObject.DateTime = Now
Set RbacObject.UserId = Cstr(Session("cgsid"))
sqlFileId = "SELECT int_fileid FROM tbl_SecFiles where str_filename = '"&split(Request.ServerVariables("SCRIPT_NAME"),"/cgs/")(1)&"'"
Set RS = Conn.Execute(sqlFileId)
Set RbacObject.FileId = RS("int_fileid")
Set RbacObject.Action = "<"&stringMethods&"><old>"&enabled_profiles_old&"</old><new>"&enabled_profiles_old&siteid&",</new></"&stringMethods&">"
RbacObject.Insert
The problem is that only FileId gets a value rest of the fields are empty, even if I set them an value. What am I doing wrong?
Set is used for assigning objects to variables. So
Set RbacObject = New RBAC
is correct, but all other statements like
Set RbacObject.DateTime = Now
are not. Use
RbacObject.DateTime = Now
instead.
Set RbacObject.FileId = RS("int_fileid")
is a borderline case: fileIdValue will contain a Field object, but evaluate to its .Value when used in a 'non-object context' (like IO or computations).
You shouldn't run dubious code with/under On Error Resume Next.
Demo:
copy con 10.vbs
Class C
Public V
End Class
Set O = New C
Set O.V = "don't do this at home."
^Z
cscript 10.vbs
... 10.vbs(5, 1) Microsoft VBScript runtime error: Object required: 'O.V'
Demo II (to prove that 'it works' if you don't use Set for assignment of non-objects, and indicate that there must be other error hidden by the evil OERN if it 'still doesn't work'):
Class C
Public V
End Class
Set O = New C
On Error Resume Next
Set O.V = "don't do this at home."
WScript.Echo Err.Description
On Error GoTo 0
WScript.Echo "Set O.V => '" & O.V & "'"
O.V = "don't do this at home."
WScript.Echo "O.V => '" & O.V & "'"
output:
cscript 10.vbs
Object required
Set O.V => ''
O.V => 'don't do this at home.'
In addition to what Ekkehard.Horner said you need to define the setters for properties that don't take objects as
Public Property Let name(value)
not as
Public Property Set name(value)

VBA giving method or data member not found for a method I have defined

I have been trying to implement a very simple tree structure in VBA for some basic text parsing needs. The relevant code follows.
Private pLeaves() As CTree
Private numLeaves As Integer
Private leavesLen As Integer
Private iterate As Integer
Private pParent As CTree
Public pValue As Object
Public Sub class_initialize()
ReDim pLeaves(10)
leavesLen = 10
numLeaves = 0
Set pParent = Nothing
iterate = 0
Set pValue = Nothing
End Sub
Public Sub Class_terminate()
'We'll leave it empty for now
'if it looks like it's not working right we might change it
End Sub
Public Property Get Parent() As CTree
Parent = pParent
End Property
Public Property Get Leaves() As CTree
Leaves = pLeaves
End Property
Private Property Set Parent(ByRef p As CTree)
Set pParent = p
End Property
Private Property Set value(ByRef value As Object)
Set pValue = value
End Property
Public Sub Add_Leaf(ByRef value As Object)
Dim n As Integer
n = numLeaves
If numLeaves >= leavesLen Then
ReDim Preserve pLeaves(leavesLen + 10)
leavesLen = leavesLen + 10
End If
Set pLeaves(n) = New CTree
Set pLeaves(n).Parent = Me
Set pLeaves(n).value = value
End Sub
Public Function DFFind_Leaf(value As Object) As CTree
Dim i As Integer
If pValue = value Then
Set DFFind_Leaf = Me
Return
End If
If numLeaves = 0 Then
Set DFFind_Leaf = Nothing
Return
End If
For i = 0 To numLeaves
Set DFFind_Leaf = pLeaves(i).DFFind_Leaf(value)
If DFFind_Leaf <> Nothing Then
Return
End If
Next i
Set DFFind_Leaf = Nothing
Return
End Function
When I try to call the Add_Leaf function with an object though I end up getting a
method or data member not found error from VBA on the line where I'm trying to
set pLeaves(n).Parent= me
Any idea what the reason for that might be?
Edit: ok I figured out how to fix this. Changing the set property Parent(...) from private
to public fixed the problem. So apparently I don't quite understand what private exactly does in vba. If someone wants to explain how to do this without essentially exposing the variables for anyone to set as they want I would be grateful.
Private tells the VBA interpreter that the scope of the variable is restricted to the class/module only. You can therefore access it only inside of the original class, but not from the outside. Publicon the other hand allows you to access the variable from the outside.
However, note that when working with classes, it is strongly discouraged to publicly expose any variables. Instead it is best practice to expose so called properties, using the SET PROPERTYand GET PROPERTY keywords.
I.e. instead of
Public Amount as Double
It is often better to use this approach:
Private mAmount as Double
Property Set Amount(amt as Double) 'Note that by default, properties are public
If amt > 0 Then
mAmount = amt
Else
mAmount = 0
End If
End Property
Property Get Amount as Double
Amount = mAmount
End Property
As you can see from this example, the advantage is that you can actually add additional verifications/modifications and therefore ensure the consistency.
In your example, you could consider to simply provide a method, i.e. a public sub, that adds a leave and takes care of all the settings, e.g.
Public Sub AddLeave(strName as String)
Dim objLeave as New CTree
objLeave.Init strName, Me
mLeaves.Add objLeave, strName
End Sub
Public Sub Init(strName as String, objParent as CTree)
Me.Name = strName
Set Me.Parent = objParent
End Sub

Dynamic property names in VBA

I have a custom class module in VBA (Access) that is supposed to handle a large amount of external data. Currently I have two functions Read(name) and Write(name, value) that allows to read and set dynamic properties.
Is there a way to define a more syntactic way to read and write those data? I know that some objects in VBA have a special way of accessing data, for example the RecordSet, which allows to read and set data using myRS!property_name. Is there a way to do exactly the same for custom class modules?
The exclamation mark syntax is used to access members of a Scripting.Dictionary instance(you'll need to add a reference to Microsoft Scripting Runtime through Tools > References first). To use this syntaxyou'll need to be storing the information internally in a dictionary.
The quickest way to use it in a class is to give your class an object variable of type Scripting.Dictionary and set it up as follows:
Option Explicit
Dim d As Scripting.Dictionary
Private Sub Class_Initialize()
Set d = New Scripting.Dictionary
End Sub
Private Sub Class_Terminate()
Set d = Nothing
End Sub
Public Property Get IntData() As Scripting.Dictionary
Set IntData = d
End Property
Now you can access properties using myinstance.IntData!MyProperty = 1... but to get to where you want to be you need to use Charlie Pearson's technique for making IntData the default member for your class.
Once that's done, you can use the following syntax:
Dim m As MyClass
Set m = New MyClass
Debug.Print "Age = " & m!Age ' prints: Age =
m!Age = 27
Debug.Print "Age = " & m!Age ' prints: Age = 27
Set m = Nothing
Okay, thanks to Alain and KyleNZ I have now found a working way to do this, without having a collection or enumerable object below.
Basically, thanks to the name of the ! operator, I found out, that access via the bang/pling operator is equivalent to accessing the default member of an object. If the property Value is the default member of my class module, then there are three equivalent statements to access that property:
obj.Value("param")
obj("param")
obj!param
So to make a short syntax working for a custom class module, all one has to do is to define a default member. For example, I now used the following Value property:
Property Get Value(name As String) As String
Value = SomeLookupInMyXMLDocument(name)
End Property
Property Let Value(name As String, val As String) As String
SetSomeNodeValueInMyXMLDocument(name, val)
End Property
Normally, you could now access that like this:
obj.Value("foo") = "New value"
MsgBox obj.Value("foo")
Now to make that property the default member, you have to add a line to the Property definition:
Attribute Value.VB_UserMemId = 0
So, I end up with this:
Property Get Value(name As String) As String
Attribute Value.VB_UserMemId = 0
Value = SomeLookupInMyXMLDocument(name)
End Property
Property Let Value(name As String, val As String) As String
Attribute Value.VB_UserMemId = 0
SetSomeNodeValueInMyXMLDocument(name, val)
End Property
And after that, this works and equivalent to the code shown above:
obj("foo") = "New value"
MsgBox obj("foo")
' As well as
obj!foo = "New value"
MsgBox obj!foo
' Or for more complex `name` entries (i.e. with invalid identifier symbols)
obj![foo] = "New value"
MsgBox obj![foo]
Note that you have to add the Attribute Value.VB_UserMemId = 0 in some other editor than the VBA editor that ships with Microsoft Office, as that one hides Attribute directives for some reason.. You can easily export the module, open it in notepad, add the directives, and import it back in the VBA editor. As long as you don't change too much with the default member, the directive should not be removed (just make sure you check from time to time in an external editor).
See this other question: Bang Notation and Dot Notation in VBA and MS-Access
The bang operator (!) is shorthand for
accessing members of a Collection or
other enumerable object
If you make your class extend the Collection class in VBA then you should be able to take advantage of those operators. In the following question is an example of a user who extended the collection class:
Extend Collections Class VBA