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
Related
[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.
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
The Scenario: I tried to use this code to create an alternative OpenArgs property according to the suggestion of Rubberduck, but I cannot get this to work. I guess I'm missing something here, but have no idea what.
The Code:
Public varAltOpenArgs As Variant
Dim FormX as form
Public Property Let AltOpenArgs(value As Variant)
varAltOpenArgs = value
End Property
Public Property Get AltOpenArgs() As Variant
AltOpenArgs = varAltOpenArgs
End Property
A new instance of the form is opened from this form (form1) using:
Set frmX = New
frmX.AltOpenArgs = "abcde"
frmX.SetFocus
The problem: The property AltOpenArgs contains an empty string ("") when called in Form_Open.
Can anybody point me in the right direction? I'm using Access 2010 (32).
The problem is, all the events are firing as soon you create the new instance (Set frmX = New Form_MyExampleForm) before your next line of code runs where you set the value of AltOpenArgs.
This will never work because Access forms are made to be Instantiated by the Application and you don't really have control over the events. If you absolutely can not use the built in OpenArgs, then you could do something like this where you are running a routine from the Property Let:
Public Property Let AltOpenArgs(value As Variant)
varAltOpenArgs = value
FancyRoutine
End Property
Private Sub FancyRoutine()
'Do whatever fancy stuff you want to do
MsgBox AltOpenArgs
End Sub
I do something along the lines of what you are trying (though not using the Form_Open event) for subforms. It might look like this:
Dim frm As Form_MyExampleForm
Me.SubformControl.SourceObject = "MyExampleForm"
Set frm = MainSubform.Form
frm.AltOpenArgs = "whatever you want"
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!
Perhaps someone can give me an advice how i could solve the following problem.
Is there simple solution to "bind" an instance attribut of an object to an element of my form. Of course it could be also solved by triggering the checkbox_Click() callback, but at the moment i'm not very happy to with this solution.
For example:
Form Load - Object Init:
Dim handlecontact As ClsHandleContact
Private Sub Form_Load()
''' init new model handler '''
Set handlecontact = New ClsHandleContact
''' bind attribute of instance to element of form '''
Me!CheckBox.Bind(handlecontact.boolean_attribut)
End Sub
Class ClsHandleContact:
Public boolean_attribut As Boolean
Private Sub Class_Initialize()
''' False by init '''
boolean_attribut = False
End Sub
If an user checks in, i would expect an update of my underlying object instance. Is there an official and supported way to realize this kind of binding ?
Thanks for any advice!
I would suggest using WithEvents to set this up. Here's a quick tutorial to get you started:
Create a form with a checkbox named Check0. Set Check0's After Update property to [Event Procedure]. In the form's code module:
Dim handlecontact As clsHandleContact
Private Sub Form_Load()
Set handlecontact = New clsHandleContact
Set handlecontact.MyCheckBox = Me.Check0
End Sub
And in the clsHandleContact class module:
Public WithEvents MyCheckBox As CheckBox
Private Sub MyCheckBox_AfterUpdate()
MsgBox "The value of the checkbox is now: " & MyCheckBox.Value
End Sub