VBA - Nested Class - ms-access

[Edited the code]
Sorry for newbie question. I was trying to test nested classes in VBA for Excel but got error message. Can anyone help me understand why? Thank you!
Inside class:
' CLASS MODULE - cInside
' Member variables
Private m_Value As Integer
' Properties
Property Get Value() As Integer
Value = m_Value
End Property
Property Let Value(i As Integer)
m_Value = i
End Property
' Methods
Sub init()
m_Value = 0
End Sub
Sub Inc()
m_Value = m_Value + 1
End Sub
Outside class:
' CLASS MODULE - cOutside
' Member variables
Private m_Num1 As New cInside
Private m_Num2 As New cInside
' Properties
Property Get Num1() As cInside
Num1 = m_Num1.Value
End Property
Property Get Num2() As cInside
Num2 = m_Num2.Value
End Property
Property Set Num1(i As cInside)
Set m_Num1 = i
End Property
Property Set Num2(i As cInside)
Set m_Num2 = i
End Property
Main program:
Sub Main()
Dim o As New cOutside
Dim i As New cInside
i.Value = 9
i.Inc
Debug.Print i.Value '<-- this works
Set o.Num1 = i
o.Num1.Inc '<-- object variable or with block variable not set
Debug.Print (o.Num1.Value)
End Sub
Thank you again!

n.Num1 returns an Integer, which isn't an instance of cInside, and therefore doesn't have an Inc member, which makes it an invalid qualifier for the compiler to complain about ;-)
Your cOutside class needs to expose it somehow:
Property Get ComposedObject1() As cInside
Set ComposedObject1 = m_Num1
End Property
Then the calling code could do this:
n.ComposedObject1.Inc
I called that "composed", because what you're doing isn't "nesting", but "composition". VBA doesn't supported nested classes, which would be a class module defined inside another class module, like so:
Class Outside
'... members...
Class Inside
'...members...
End Class
End Class
VB.NET can do that, but in VBA 1 class module can only define 1 class.

Related

VBA Class Module: Runtime error 438 Object doesn't support this property or method

I have the following class module BlSecurity in my Access database:
Option Compare Database
Option Explicit
Private tiendaRepo As ITiendaRepository
Public Sub Init(ptiendaRepo As ITiendaRepository)
tiendaRepo = ptiendaRepo
End Sub
Public Sub Login(code As String)
If tiendaRepo.CheckIfCodeExists(code) = "" Then
Err.Raise Number:=CustomErrors.CenterCodeNotExisting
End If
Exit Sub
End Sub
Private Sub Class_Terminate()
Set tiendaRepo = Nothing
End Sub
This is TiendaRepository code:
Option Compare Database
Option Explicit
Implements ITiendaRepository
Public Function ITiendaRepository_CheckIfCodeExists(ByVal code As String) As String
Err.Raise vbObjectError, "CheckCode", "Not implemented"
Exit Function
End Function
And this is the "interface" ITiendaRepository I'm implementing:
Option Compare Database
Option Explicit
Public Function CheckIfCodeExists(ByVal code As String) As String
End Function
Then, inside a button handler:
Private Sub btnLogin_Click()
Dim bl As blSecurity
Set bl = New blSecurity
bl.Init (New TiendaRepository)
bl.Login (txtUsuario.Value)
End Sub
But when I click the button, I receive message:
Object doesn't support this property or method
in line bl.Init (New TiendaRepository)
. What's wrong with it?
It runs (i.e., raises the "Not implemented" message) on my test system with these two changes:
In your button-click module, remove the parentheses around New TiendaRepository.
Private Sub btnLogin_Click()
Dim bl As BlSecurity
Set bl = New BlSecurity
bl.Init New TiendaRepository ' <=== here
bl.Login txtUsuario.Value
End Sub
This is because VBA doesn't use parentheses when calling subroutines and not collecting a return value. If you add the parentheses, they actually cause evaluation of the default property. Therefore, instead of passing the New TiendaRepository object to bl.Init, you are passing whatever VBA thinks the default value is.
Note that the VBA editor put a space before the opening parenthesis in your code. That's a visual clue that it's not doing what you might expect coming from languages that always use parens on calls.
In BlSecurity.Init, add a Set:
Public Sub Init(ptiendaRepo As ITiendaRepository)
Set tiendaRepo = ptiendaRepo
End Sub
That is because you always (as far as I know) need Set when you are assigning objects (internally, references to objects).
If you want to use parentheses around method calls (not function calls) in VBA, you use the Call keyword. That is,
Call bl.Init(New TiendaRepository)
is the same as
bl.Init New TiendaRepository
This is true regardless of the number of parameters — Call foo(a,b,c) is the same as foo a, b, c.
Maybe
1) Note of set keyword
2) Removal of () in call
3) BlSecurity must also implement ITiendaRepository_CheckIfCodeExists
BlSecurity
Option Compare Database
Option Explicit
Implements iTiendaRepository
Private tiendaRepo As iTiendaRepository
Public Sub Init(ptiendaRepo As iTiendaRepository)
Set tiendaRepo = ptiendaRepo '*1
End Sub
Public Sub Login(code As String)
If tiendaRepo.CheckIfCodeExists(code) = "" Then
Err.Raise Number:=CustomErrors.CenterCodeNotExisting
End If
Exit Sub
End Sub
Private Sub Class_Terminate()
Set tiendaRepo = Nothing
End Sub
Public Function ITiendaRepository_CheckIfCodeExists(ByVal code As String) As String '*3
Err.Raise vbObjectError, "CheckCode", "Not implemented"
Exit Function
End Function
Module
Option Compare Database
Option Explicit
Private Sub btnLogin_Click()
Dim bl As BlSecurity
Set bl = New BlSecurity
bl.Init New TiendaRepository '*2
bl.Login txtUsuario.Value '<=== Not sure where declare and should remove ()
End Sub
Though I am unsure where you have declared txtUsuario

New to classes VBA Access

I have been working on a project and have multiple tick boxes (25) and multiple labels in a form that are names SC1, SC2...SCN and Lbl1, Lbl2...LblN depending on a recordset. When I click the tickbox I want the label beside it to display some information, see below -
Private Sub SC1_Click()
If (Me!SC1) = True Then
Form.Controls("Lbl1").Caption = ("Completed by " & (Environ$("Username")))
Form.Controls("Lbl1").ForeColor = vbGreen
Else
Form.Controls("Lbl1").Caption = ("Please tick if Complete")
Form.Controls("Lbl1").ForeColor = vbBlack
End If
End Sub
My issue is I can't change the number in the Sub name so I would have to create multiple sub procedures. I think if I created a class for the tick box this would change but I am not sure how I can set up the class. I have tried the below class template but am not sure where I can change the property values in order to reach my goal. I am not sure why you would have both get and set properties in one class. Any help on this is greatly appreciated.
Option Compare Database
Option Explicit
Private pName As String
Private pCaption As String
Private pVisiblity As Boolean
Private pValue As Boolean
Public Property Get Name() As String
Name = pName
End Property
Public Property Let Name(Value As String)
pName = Value
End Property
Public Property Get Caption() As String
Caption = pCaption
End Property
Public Property Let Caption(Value As String)
pCaption = "Please Tick Box if complete"
End Property
Public Property Get Visibility() As Boolean
Visibility = pVisibility
End Property
Public Property Let Visibility(Value As Boolean)
pVisibility = True
End Property
Public Property Get Value() As Boolean
Value = pValue
End Property
Public Property Let Value(Value As Boolean)
pValue = True
End Property
There are two parts to creating and linking form controls to custom support objects (classes). In your case
Class Module: clsMyCheckbox
Option Explicit
Option Compare Database
Public WithEvents chkBox As CheckBox
Public chkLabel As Label
Private currentUser As String
Private Sub chkBox_Click()
If chkBox.Value = True Then
chkLabel.Caption = "Completed by " & currentUser
chkLabel.ForeColor = vbGreen
Else
chkLabel.Caption = "Please tick if Complete"
chkLabel.ForeColor = vbBlack
End If
End Sub
Private Sub Class_Initialize()
currentUser = Environ$("Username")
End Sub
And in your form module:
Option Explicit
Option Compare Database
Private localCheckboxes As New Collection
Private Sub Form_Load()
'--- find all the checkboxes on the form and create a
' handler object for each one
Dim ctl As Control
Dim chknum As String
Dim cbObj As clsMyCheckbox
Dim chkLbl As Label
For Each ctl In Me.Controls
If ctl.ControlType = acCheckBox Then
'--- you can filter by name if needed
If ctl.Name Like "SC*" Then
chknum = Right(ctl.Name, Len(ctl.Name) - 2)
Set chkLbl = Me.Controls.Item("Lbl" & chknum)
chkLbl.Caption = "initialized" 'optional during form load
Set cbObj = New clsMyCheckbox 'class object for this checkbox
Set cbObj.chkBox = ctl 'link the control to the object
Set cbObj.chkLabel = chkLbl 'link the label too
'--- add it to a local store so the object persists
' as long as the form is open
localCheckboxes.Add cbObj
End If
End If
Next ctl
End Sub
Private Sub Form_Unload(Cancel As Integer)
'--- guarantee the objects are destroyed with the form
Set localCheckboxes = Nothing
End Sub
I think you are going the wrong way. In Access you can't really derive your own classes for GUI control and use them on the form. For your problem, you basically have three options:
Use the default event handlers and call one custom function from each. This will improve your situation a little.
Use one custom event handler for all checkboxes, instead of the default event-handlers.
Use a class and attach an instance to each of the checkboxes you use. The class can then recieve any event from the checkbox. This is powerful but you will still need to register your class with each control and hold all you instances somewhere for this to work.
For your problem, I'd go with solution 2:
First, write a custom event handler like this in your Form-module:
Private Function chkClick(sName As String)
Debug.Print "Clicked: " & sName
Me.Controls(sName).Controls(0).Caption = "x"
End Function
Next, enter design mode of you form and go to all checkboxes. In Checkbox "SC1", you go to the "OnClick" event and enter =chkClick("SC1") as event handler instead of [Eventprocedure]. Make sure you use the correct name of the control as the parameter of the function.
Congratulations! From now on, all your checkboxes will call the same event-handler and pass their name. Since the label of a checkbox is its associated control, you get to that label from the checkbox via .Controls(0), meaning the first "sub"-control of the checkbox. This way, you don't need to know the name of the associated label at all!

VBA call property from with class

In VBA I would like to call a Property Let() from within my class constructor rather than just setting the variable in the constructor. Is there anyway to do this?
Unless I've misunderstood your question, you're looking for the Me keyword.
Option Explicit
Dim l_ As Long
Private Sub Class_Initialize()
Me.l = 5
End Sub
Public Property Let l(newl As Long)
l_ = newl
End Property

what are the DB.Properties(??) variables? Specifically setting the default ribbon

I have an Access 2007 app that I'm updating to be able to run on both 2007 and 2010. In 2007 I use the form ribbon property, but with 2010 I've needed to make a default ribbon that turns off the backstage. I've done that but the app needs too set it as default when it detects that it is running on 2010 instead of 2007. The Load custom UI does not work. It loads it but it does not set a ribbon as default. I know I can set the default start up form and other properties with the database.properties function. But I need to know the property name for the application default ribbon. Anyone know the property names?
I think the name of the Database Property your looking for is: CustomRibbonId
Here's some code to output a list of Database Properties to the Debug window.
Private Sub EnumerateDatabaseProperties()
On Error Resume Next
Dim p1 As DAO.Property, s1 As String
For Each p1 In CurrentDb.Properties
s1 = p1.Name
s1 = s1 & "=" & p1.value
Debug.Print s1
Next p1
End Sub
Do realize that a database property might not show up in the output if it doesn't exist, rather than just showing up in the output with no value.
First we need a robust method for setting DB properties.
Public Sub SetCurrentDBProperty(ByVal propertyName As String, ByVal newValue As Variant, Optional ByVal prpType As Long = dbText)
Dim thisDBs As Database
Set thisDBs = CurrentDb
Dim wasFound As Boolean
' Look for property in collection
Dim thisProperty As Object ' DAO.Property
For Each thisProperty In thisDBs.Properties
If thisProperty.Name = propertyName Then
' Check for matching type
If thisProperty.Type <> prpType Then
' Remove so we can add it back in with the correct type.
thisDBs.Properties.Delete propertyName
Exit For
End If
wasFound = True
' Skip when no change is required
If thisProperty.Value = newValue Then
Exit For
Else
' Update value
thisProperty.Value = newValue
End If
End If
Next thisProperty
If Not wasFound Then
' Add new property
Set thisProperty = thisDBs.CreateProperty(propertyName, prpType, newValue)
thisDBs.Properties.Append thisProperty
End If
End Sub
Then given an example ribbon name of Runtime you could call the property setter like this:
Public Sub SetRuntimeRibbon()
SetCurrentDBProperty "CustomRibbonID", "Runtime"
End Sub

VBA MS Access 2010 How to call a Subroutine of an Object?

I've been trying to create a Subroutine in VBA for my Access application:
Public Sub addProduct(ByRef Product As Product, AsFoo As Integer, Optional Mutual As Boolean = True)
Products.Add (Product)
If (Mutual) Then
Select Case AsFoo
Case 0
Product.setProjectmanager = Me
Case 1
Product.setVIP1 = Me
Case 2
Product.setVIP2 = Me
Case 11
Product.setVIP1A = Me
Case 22
Product.setVIP2A = Me
End Select
End If
End Sub
That one should just add the given Product to a Collection of Products and set the reference to the User reference, if Mutual is true.
That one should work... the problem is that I don't know how to call that
my current try is:
User.addProduct(Product, 0, True)
But the IDE wants to have a = at the end so I thought that would work:
User.addProduct(Product, 0, True) = Product
But that causes a Compile Error:
Expected function or Variable
Try calling that line with:
User.addProduct Product, 0, True
or
Call User.addProduct(Product, 0, True)
Removing the () or using the Call keyword should work for you.
I'm not sure if this is what you want to do, but if it is adding class instances to a collection, I propose something like this:
Sub Main_sub()
dim colProduct as Collection
dim cProduct as clsProduct
dim asFoo as integer
dim Mutual as boolean
set colProduct = new collection
set cProduct = new clsproduct
asFoo = ? 'Define
Mutual = ? 'Define
'if the AddProduct function resides in the clsProduct class:
set cProduct = cproduct.AddProduct(cProduct, asFoo, Mutual)
colProduct.add cProduct
set cProduct = nothing
set colProduct = nothing
end sub
And for private Product class properties Projectmanager, VIP1, VIP2, VIP1A,VIP2A
Public function addProduct(cProduct as clsProduct, AsFoo As Integer, Optional Mutual As Boolean) as cProduct
If (Mutual) Then
Select Case AsFoo
Case 0
cProduct.Projectmanager = Me
Case 1
cProduct.VIP1 = Me
Case 2
cProduct.VIP2 = Me
Case 11
cProduct.VIP1A = Me
Case 22
cProduct.VIP2A = Me
End Select
End If
set addProduct = cProduct
End Sub
You declare properties in a class module this way:
Private pVIP1 as <Type>
And getters / setters:
Public Property Get VIP1() As <Type>
VIP1 = pVIP1
End Property
Public Property Let VIP1(tVIP1 As <Type>)
pVIP1 = tVIP1
End Property
If the type is an object, you need to use SET instead of LET.
Public Property SET VIP1(tVIP1 As <Type>)
SET pVIP1 = tVIP1
End Property
Maybe I've got your intentions wrong, because I don't see the purpose of adding Me to each of the cases. But this was the best I could come up with.