Linking form controls to class - ms-access

I am trying to link controls on a form that I have created in a Class Module and am having trouble getting it to work properly.
'Class Name is CustForm
Option Explicit
Private WithEvents btnTest as CommandButton
Public Function showForm()
Dim tempForm as Form
Dim formName as String
Set tempForm = CreateForm
formName = tempForm.Name
Set btnTest = CreateControl(formName,
acCommandButton,acDetail,,,300,300,1000,500)
Dim btnName As String
btnName = btnTest.Name
Docmd.RunCommand acCmdFormView
End Function
Private Sub btnTest_Click()
MsgBox "Test"
End Sub
In a separate form I create the object and call the showForm in a click event
Private Sub Command0_Click()
Dim tstForm as CustForm
set tstForm= New CustForm
tstForm.showForm
End Sub
But nothing happens when I click the button created in the CustForm
I have tried using a temporary CommandButton in the showForm and after the docmd.runcommand acCmdFormView
set btnTest = Forms(formName).Controls(btnName)
under the assumption that maybe the instance of the command button changes when the form goes to Form View. Again no joy.
However if I add this to the CustForm Class
Public Function init(lclBtn as CommandButton)
set btnTest = lclBtn
btnTest.OnClick = "[Event procedure]"
End Function
And then I delete the OnClick and add this code to the form's module
Option Explicit
Dim tester as CustForm
Private Sub Form_Open(Cancel as Integer)
Set tester = new CustForm
tester.init Me.Command0
End Sub
Then it fires the MsgBox when I click the button.
But I need to be able to eventually build a form factory class that allows me to build forms for class objects dynamically and handle the events in the object class. I would rather not make a bunch of purpose built forms for each class and have the form instantiate the class. I want to do it the other way around. Class builds the form.
Can this be done?

As can be read in the documentation of the Form.HasModule property, there are two kinds of forms and reports: lightweight forms and reports that don't have a class module and don't support events, and full forms and reports that do have a class module and support events.
This means you still have to toggle this property for it to support events:
Private WithEvents btnTest as CommandButton
Public Function showForm()
Dim tempForm as Form
Dim formName as String
Set tempForm = CreateForm
tempForm.HasModule = True
formName = tempForm.Name
Set btnTest = CreateControl(formName, acCommandButton,acDetail,,,300,300,1000,500)
btnTest.OnClick = "[Event Procedure]"
Dim btnName As String
btnName = btnTest.Name
DoCmd.RunCommand acCmdFormView
End Function
Private Sub btnTest_Click()
MsgBox "Test"
End Sub
Do note that setting Form.HasModule modifies the VB Project part of the Access database (it adds a class module), and thus every time you do this, your database will need to be recompiled. Generally, you want to avoid that, as it might lead to problems.
Instead, I recommend having a single form with a module and all the controls you might want. Then, you can move around the controls, change their caption, and resize them, bind them to table fields, and set up event handlers, all without modifying the VB Project behind the database (note that you can't add controls to a full form or change the names of controls without modifying the VB Project).
Note that a second issue is the persistence of your class object. Currently, there are no references to the class so it's destroyed. You can easily make your class persist indefinitely by using code like this:
Private WithEvents btnTest as CommandButton
Private Myself As Object
Public Function showForm()
Set Myself = Me 'Circular reference, object won't get destroyed until myself is set to nothing
For more information about gracefully handling classes with references to forms, you can see this answer by me. You should probably listen to the Form_Unload event and clean up when that occurs.

Related

How do i reference a Form with a global variable within Subforms and do stuff to it?

I never used an English Access version, so forgive me if I explain the question poorly.
I'm working on an old access vba application. It has a main form named Form_GUI. Form_GUI has multiple tabs, each with it's own 'main form' and a couple different sub forms. These build up the FrontEnd. All of them are connected with various BackEnd tables from a different database.
How can I declare the variable p_FrmZuordnung globally and in a way that I can reference it when I want to make some changes to Form1 or Form2 within another form like Form3?
I want subforms to react differently, based on the value of this variable.
Examples of things I want to do with p_FrmZuordnung are below.
Things i tried:
I tried declaring it as String and simple adding it to the code.
Public p_FrmZuordnung As String
I tried delcaring it as Form like this:
Public p_FrmZuordnung As Form
And then setting it up like this:
Set p_FrmZuordnung = Forms!Form1
And using it like this within another form like Form3:
p_FrmZuordnung!somecontrolelement.requery
The code I wrote to identify the page within the main form works.
The code I wrote to reference the variable to a form doesn't.
Option Compare Database
Option Explicit
Public p_FrmZuordnung As Form
Public Sub p_ErmittleFrmZuordnung()
p_FrmZuordnung = "keine Zuordnung"
Select Case Form_Form_GUI.RegisterStr1.Pages(Form_Form_GUI.RegisterStr1.Value).Name
Case "pgeVerbMassnahmen"
Set p_FrmZuordnung = Forms("Form1")
Case "pgeKVPMassnahmen"
Set p_FrmZuordnung = Forms("Form2")
End Select
End Sub
My goal is to use p_frmZuordnung in other subforms like that within Form3:
Private Sub btnCancel_Click()
Me.Undo
DoCmd.Close acForm, "Form3", acSaveYes
p_FrmZuordung.somecontrolelement.Requery
Exit Sub
End Sub
Private Sub btnSaveAndClose_Click()
Me.txt_Kontrolle.Value = 1
If Me.Form.Dirty And Me.txt_Text.Locked = False Then
p_FrmZuordnung.txtHilfstextFokus.SetFocus
p_FrmZuordnung.Form.Dirty = True
Debug.Print p_FrmZuordnung.Form.Dirty
Me.Form.Dirty = False
End If
Me.txt_Kontrolle.Value = 0
DoCmd.Close acForm, "Form3", acSaveNo
Exit Sub
End Sub
Global variables must be defined in a module, not in a form. It doesn't really matter which module you define it in. Once you declare a global variable, it is static and is accessible from any form.
The reason that you are having problems using the controls on the form, us because z_FrmZuordnung is just a generic form and the compiler cannot resolve the controls by name. You need to get the controls from the Controls collection.
Set z_FrmZuordnung = Forms!Form1
MsgBox z_FrmZuordnung.Name
'MsgBox z_FrmZuordnung.txtHilfstextFocus.Name '<--gives an error
MsgBox z_FrmZuordnung.Controls("txtHilfstextFokus").Name '<--This works
z_FrmZuordnung.Controls("txtHilfstextFokus").SetFocus
As you told us now, the forms Form1 and Form2 are in subform controls of register control pages.
So you can't reference them by Forms("Form2"), because they are not 'stand alone' forms.
Instead you have to know the name of the each subform control which holds such a subform. The name of the form which is stored in each of this controls doesn't matter at all then.
So my suggestion is that you name those subform controls with the same name as the containing page, but adding a static suffix like SubFormControl (or any other suffix you like).
So for example the subform control which is on the page pgeVerbMassnahmen should be named pgeVerbMassnahmenSubFormControl.
Then you can reference the subform controls by the name of the page and the static suffix.
So I think this is what you need:
Declare a form variable in the Form_GUI (as long as you just reference the variable from code inside Form_GUI, it can be private):
Private p_FrmZuordnung As Form
I expect that p_ErmittleFrmZuordnung is located in the form Form_GUI too, so you can use Me to reference Form_GUI:
Private Sub p_ErmittleFrmZuordnung()
Const SUFFIX As String = "SubFormControl"
Dim currentPagename As String
currentPagename = Me("RegisterStr1").Pages(Me("RegisterStr1").Value).Name
Select Case currentPagename
Case "pgeVerbMassnahmen"
Set p_FrmZuordnung = Me(currentPagename & SUFFIX).Form
Case "pgeKVPMassnahmen"
Set p_FrmZuordnung = Me(currentPagename & SUFFIX).Form
Case Else
p_FrmZuordnung = Nothing
End Select
End Sub
Edit:
If all pages contain a subform control you can shorten it:
Private Sub p_ErmittleFrmZuordnung()
Const SUFFIX As String = "SubFormControl"
Set p_FrmZuordnung = Me(Me("RegisterStr1").Pages(Me("RegisterStr1").Value).Name & SUFFIX).Form
End Sub

Passing alternative OpenArgs to Access Forms created with 'New'

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"

Refer to Form Control Without Specific Naming

I have a form with numerous images, each of which performs a series of actions when clicked. I can create a Private Sub with all of the actions for each button, however I think this is inefficient. Rather, I'd like to record all the actions in one Macro and then call this Macro when each image is clicked. To do so, I'd need the single Macro to refer to the current image selected and not refer to any image by name. Is this possible?
My current code includes the following:
Me.Image001.BorderColor = RGB(1, 1, 1)
Me.Image001.BorderWidth = 2
Me.Image001.BorderStyle = 1
I'd need to amend this so that it amends the border colour/width/style etc of whichever image is selected, and not a specific named image ('Image001').
Thanks!
You should use event sinking.
With event sinking you could bind to an event your own procedures.
You can see an example here http://p2p.wrox.com/access-vba/37472-event-triggered-when-any-control-changed.html
In simple words you create a module where you bind the event to your specific implementation . Then on the form you are interested you create a collection where you register the controls you want to "follow" the event sinking...
My sub sinking for checkboxes (i have alot)
1st a class module name SpecialEventHandler
Option Compare Database
Private WithEvents chkbx As CheckBox
Private m_Form As Form
Private Const mstrEventProcedure = "[Event Procedure]"
Public Function init(chkbox As CheckBox, frm As Form)
Set chkbx = chkbox
Set m_Form = frm
'Debug.Print frm.Name
chkbx.AfterUpdate = mstrEventProcedure
End Function
Private Sub chkbx_AfterUpdate()
'your Code here
End Sub
Private Sub Class_Terminate()
Set chkbx = Nothing
Set m_Form = Nothing
End Sub
Then on the form you want to use event sinking
Option Compare Database
Dim spEventHandler As SpecialEventHandler
Private colcheckBoxes As New Collection
Private Sub Form_Open(Cancel As Integer)
Dim ctl As Control
For Each ctl In Me.Detail.Controls
Select Case ctl.ControlType
Case acCheckBox
Set spEventHandler = New SpecialEventHandler
spEventHandler.init Controls(ctl.NAME), Me
colcheckBoxes.Add spEventHandler
End Select
Next ctl
End Sub
You could always create a Standard Sub, then call it on the click of the button. Something like
Public Sub changeColor(frm As Form, ctrl As Control)
frm.Controls(ctrl.Name).BorderColor = RGB(1, 1, 1)
frm.Controls(ctrl.Name).BorderWidth = 2
frm.Controls(ctrl.Name).BorderStyle = 1
End Sub
So when you click an image all you have to do is,
Private Sub Image001_Click()
changeColor Me, Image001
End Sub

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!

MS Access: Possible to bind checkbox value to an attribute of object instance?

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