MS Access: Replace Many On Click Events with Generic On Click Event - vba

I have a form in MS Access with 12 buttons running along the top serving as column headers. For each button there is an On Click event which calls the same public function. This function, (shown below), opens the Filter Menu for the field corresponding to the button that was clicked.
Public Function HeaderClick(HeaderName As String)
DoCmd.GoToControl "[" & HeaderName & "]"
DoCmd.RunCommand acCmdFilterMenu
End Function
For example, I click on the First Name button and it pulls up the filter menu for First Name:
I'm wondering if there is a way to call HeaderClick whenever ANY of these buttons are clicked. In other words, I'd like an alternative to creating 12 separate On Click Events. Reason being that this is a technique I use on many forms in many databases. It just doesn't seem very efficient.

When you click on the OnClick textbox to add a new function, you typically see the text [Event Procedure]. Just overwrite it with the name of the function (make sure the function is in a global module). So instead of [Event Procedure], you would type =HeaderClick("Name"). You would still need to specify the argument for the function.

You would create a class, i've called mine clsCustomButton like so
Option Explicit
Private WithEvents cmdCustom As CommandButton
Public Sub Initialise(cmdIn As CommandButton)
Set cmdCustom = cmdIn
cmdCustom.OnClick = "[Event Procedure]"
End Sub
Private Sub cmdCustom_Click()
MsgBox "Hello"
End Sub
Private Sub Class_Terminate()
Set cmdCustom = Nothing
End Sub
In a standard code module, you'll need to have a collection/array to hold this "new" custom buttons, i've called mine colButtons
Option Explicit
Public colButtons As Collection
Then you need to add to collection by passing in the buttons you want to change, i've just done one, but you could loop for all or specify use tag/name to do this. Like so, from within the form, usually on opening
Dim clsCommandButton As clsCustomButton
Set colButtons = New Collection
Set clsCommandButton = New clsCustomButton
clsCommandButton.Initialise Me.Command0
colButtons.Add clsCommandButton, CStr(colButtons.Count)

Another example is my Windows Phone Theme Colour selector where clicks on textboxes are caught:
Form:
Option Explicit
' Form to display the Windows Phone 7.5/8.0 colour theme.
' Also works as a basic example of implementing WithEvents for a form.
' 2017-04-19. Gustav Brock, Cactus Data ApS, CPH.
' Version 1.0.0
' License: MIT.
' *
Private ControlCollection As Collection
Private Sub Form_Load()
' Load events for all colour value textboxes.
Dim EventProcedure As ClassTextboxSelect
Dim Control As Access.Control
Set ControlCollection = New Collection
For Each Control In Me.Controls
If Control.ControlType = acTextBox Then
Set EventProcedure = New ClassTextboxSelect
EventProcedure.Initialize Control
ControlCollection.Add EventProcedure, Control.Name
End If
Next
Set EventProcedure = Nothing
Set Control = Nothing
End Sub
Private Sub Form_Unload(Cancel As Integer)
' Unload events for all colour value textboxes.
Dim EventProcedure As ClassTextboxSelect
For Each EventProcedure In ControlCollection
EventProcedure.Terminate
Next
Set EventProcedure = Nothing
Set ControlCollection = Nothing
End Sub
Class:
Option Explicit
' Helper class for form Palette for event handling of textboxes.
' 2017-04-19. Gustav Brock, Cactus Data ApS, CPH.
' Version 1.0.0
' License: MIT.
' *
Private Const EventProcedure As String = "[Event Procedure]"
Private WithEvents ClassTextBox As Access.TextBox
Attribute ClassTextBox.VB_VarHelpID = -1
Public Sub Initialize(ByRef TextBox As Access.TextBox)
Set ClassTextBox = TextBox
ClassTextBox.OnClick = EventProcedure
End Sub
Public Sub Terminate()
Set ClassTextBox = Nothing
End Sub
Private Sub ClassTextBox_Click()
' Select full content.
ClassTextBox.SelStart = 0
ClassTextBox.SelLength = Len(ClassTextBox.Value)
' Display the clicked value.
ClassTextBox.Parent!CopyClicked.Value = ClassTextBox.Value
' Copy the clicked value to the clipboard.
DoCmd.RunCommand acCmdCopy
End Sub
Full code, the Access application for download, and documentation at VBA.ModernTheme.

Related

Retrieve active control name from an excel user form after it's moused over

I am trying to dynamically create an application that will use the name of a button (created at runtime) on the form, to extract a number from the end of it's name:
FundNo = Right(Me.ActiveControl.Name, 1)
It's not working and I suspect it's because my button isn't the active control as I am using the mouse over event to trigger it which I'm guessing doesn't give it the focus.
It's nothing to do with the data type it's just not returning the name of the control.
Does anyone have any ideas as to how I could do this as it will be really cool if I can get it to work?
Thanks and regards, Mark
You are right, the CommandButton is not the active Control when the mouse moves over it. It is possible to do what you want, but not in a single line of code. You need a WithEvents variable in a Class module to return the events from the CommandButtons ... the following is the process, assuming you already have your UserForm set up and it is correctly adding your CommandButtons ... the following uses "MyUserFormName" as the assumed name of your UserForm, change this in the following to the actual name of your UserForm:
Add the following into the code-behind of your UserForm (the Debug.Print is just to test the code works and you can remove it when it is, if you want to):
Sub CommandButtonMovement(cb As MSForms.CommandButton)
FundNo = Right(cb.Name, 1)
Debug.Print Now, FundNo
End Sub
Add a Class module (must be a Class module ... not standard module), name it CCommandButtonEvents, add this code
Option Explicit
Private WithEvents mCommandButton As MSForms.CommandButton
Private mUserForm As MyUserFormName
Sub SetUpCommandButton(cb As MSForms.CommandButton, uf As MyUserFormName)
Set mCommandButton = cb
Set mUserForm = uf
End Sub
Private Sub mCommandButton_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
mUserForm.CommandButtonMovement mCommandButton
End Sub
... remember to change "MyUserFormName" to the actual name of your UserForm.
Back in your UserForm code-behind, for each CommandButton you create, you need to add the following at the module level:
Dim cbe1 As CCommandButtonEvents
Dim cbe2 As CCommandButtonEvents
... as many as you are adding CommandButtons
Finally, assuming you are dynamically creating your CommandButtons in UserForm_Activate(), then add (where cb1, cb2 is assumed to be the names of the CommandButton ... change this as required) in that method:
Set cbe1 = New CCommandButtonEvents
cbe1.SetUpCommandButton cb1, Me
Set cbe2 = New CCommandButtonEvents
cbe2.SetUpCommandButton cb2, Me
... again, as many as you are adding CommandButtons
Run your code. That's a lot of changes ... I thought it might help to post a full code example:
Class CCommandButtonEvents - as above
UserForm MyUserFormName
Option Explicit
Dim FundNo As Variant
Dim cbe1 As CCommandButtonEvents
Dim cbe2 As CCommandButtonEvents
Private Sub UserForm_Activate()
Dim cb1 As MSForms.CommandButton
Set cb1 = Me.Controls.Add("Forms.CommandButton.1", "CommandButton1", True)
cb1.Caption = "First button"
cb1.Top = 20
cb1.Left = 20
Set cbe1 = New CCommandButtonEvents
cbe1.SetUpCommandButton cb1, Me
Dim cb2 As MSForms.CommandButton
Set cb2 = Me.Controls.Add("Forms.CommandButton.1", "CommandButton2", True)
cb2.Caption = "Second button"
cb2.Top = 20
cb2.Left = 100
Set cbe2 = New CCommandButtonEvents
cbe2.SetUpCommandButton cb2, Me
End Sub
Sub CommandButtonMovement(cb As MSForms.CommandButton)
FundNo = Right(cb.Name, 1)
Debug.Print Now, FundNo
End Sub
If you are adding your CommandButtons in a loop then the example code will need adjusting for that eg to use an Array instead of individual CCommandButtonEvents variables at point 3 etc
EDIT: updated code per the questioners comments, to handle adding CommandButtons in an array and to handle multiple events (MouseMove and Click)
The UserForm code-behind needs some fairly serious re-working though the core principles are the same ... this is the new code that includes an array to hold the CommandButtons and a loop to add them all (change the value of mBtnCount to the number of CommandButtons you want added ... ensure the UserForm is large enough that you can see them all!)
Option Explicit
Const mBtnCount As Long = 5
Dim FundNo As Variant
Dim mCmdBtns() As CCommandButtonEvents
Private Sub UserForm_Activate()
Dim i As Long
ReDim mCmdBtns(1 To mBtnCount)
For i = 1 To mBtnCount
Dim cmdBtn As MSForms.CommandButton
Set cmdBtn = Me.Controls.Add("Forms.CommandButton.1", "CommandButton" & CStr(i), True)
cmdBtn.Caption = "Button " & CStr(i)
cmdBtn.Top = 4 + 26 * (i - 1)
cmdBtn.Left = 4
Set mCmdBtns(i) = New CCommandButtonEvents
mCmdBtns(i).SetUpCommandButton cmdBtn, Me
Next i
End Sub
Sub CommandButtonMovement(cb As MSForms.CommandButton)
FundNo = Right(cb.Name, 1)
Debug.Print Now, "Movement " & FundNo
End Sub
Sub CommandButtonClick(cb As MSForms.CommandButton)
FundNo = Right(cb.Name, 1)
Debug.Print Now, "Click " & FundNo
End Sub
... note there is a new Sub 'CommandButtonClick' that the code in CCommandButtonEvents will call.
Finally, to handle the Click event, in the CCommandButtonEvents Class module (you should normally use the drop-downs at the top of the code window to add the new event handlers to ensure the event 'signature' is correct), add this Sub which calls the new Sub in the UserForm:
Private Sub mCommandButton_Click()
mUserForm.CommandButtonClick mCommandButton
End Sub

Access VBA put a form element name as an argument

Let's see if anyone knows how to solve this problem:
I have a form with several elements: Some of them are textboxes called A1, A2, A3, A4...
Now, their AfterUpdate SubProcedure is extremely long but barely similar for each of them: A1_AfterUpdate, A2_AfterUpdate, A3_AfterUpdate...etc... are very similar but for the names of the textboxes they change.
My idea was to gather all that was equal in a subprocedure defined this way:
Private Sub Update(Box As String, Menu As Boolean)
If Menu=True{
Me!Box.Text = "This is the text that is going to change"
}
End Sub
So, the only thing I must do is to call it this way, for instance:
Update(A1, True)
But it doesn't seems to work. Any idea on how to reach this objective?
Add a class module - I've called it clsTextBoxEvents.
Add this code to the class:
Public WithEvents txt As Access.TextBox
Private Sub txt_AfterUpdate()
MsgBox txt.Name & " has been updated."
End Sub
In your form module add this code:
Public MyTextBoxes As New Collection
Private Sub Form_Open(Cancel As Integer)
Dim ctl As Control
Dim txtBoxEvent As clsTextBoxEvents
For Each ctl In Me.Controls
If TypeName(ctl) = "TextBox" Then
Set txtBoxEvent = New clsTextBoxEvents
Set txtBoxEvent.txt = ctl
txtBoxEvent.txt.AfterUpdate = "[Event Procedure]"
MyTextBoxes.Add txtBoxEvent
End If
Next ctl
End Sub
The MyTextBoxes declaration must be at the very top of the module.
This just adds the AfterUpdate event to all textboxes on the form. You'll probably want to refine that a bit to textboxes with specific text in the name, or controls that are in a specific frame on the form.
If you use a function instead of a sub:
Private Function UpdateCtl(Menu As Boolean)
If Menu Then
activecontrol = "This is the text that is going to change"
End If
End Sub
then you can call it directly from the control's AfterUpdate property: =UpdateCtl(True).
Simple and fast

VBA Input Value From Another UserFormB into TextBox From UserFormA

I have a userForm (mappingGuide) that allows user to pick a smartyTag from a list of more user-friendly names.
I have a second user-form (conditionalBuilder) that I would like to call this userForm upon double-clicking a text field so that a user can lookup which smartyTag to apply (in case they don't know).
So logic, is:
open conditionalBuilder
double-click Field text box
mappingGuide opens
pick a smartytag from listbox
fill smartytag value into field text-box in conditionalBuilder
unload mappingGuide
The issue I think I having with completing the requirement is that when I load the forms themselves I cannot find a way to set the text of the fieldName textbox of the loaded instance of conditionalBuilder (see last code block below). I've been searching around, but cannot figure it out.
Here is relevant code:
conditionalBuilder loads from Custom UI ribbon
Sub RunCode(ByVal Control As IRibbonControl)
Select Case Control.ID
Case Is = "mapper": LoadMappingGuide
Case Is = "conditional": LoadConditionalBuilder
End Select
End Sub
Sub LoadConditionalBuilder()
Dim conditionalForm As New conditionalBuilder
conditionalForm.Show False
End Sub
double-click event of fieldName then loads mappingGuide
Private Sub fieldName_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
Me.hide
Dim pickField As New mappingGuide
pickField.Show False
End Sub
smartTag listbox click event then attempts to place selection into fieldName (or selection if form not loaded)
Private Sub smartTagList_Click()
If smartTagList.ListIndex > -1 And smartTagList.Selected(smartTagList.ListIndex) Then
Dim smartyTag As String
smartyTag = smartTagList.List(smartTagList.ListIndex, 2)
If isUserFormLoaded(conditionalBuilder.Name) Then
'*** ---> below is my issue how to reference instance of form
conditionalBuilder.fieldName.Text = smartyTag
conditionalBuilder.Show
Else
Selection.Range.Text = smartyTag
End If
End If
Unload Me
End Sub
If there is a better set-up that would be great to know too. I have the forms separate because there's a couple of levels a user can create tags with.
This is how I would do it, a bit of overkill but in case of multiple forms it will be beneficial.
Module 1:
Option Explicit
Sub test()
frmMaster.Show False
End Sub
Form 1 : frmMaster:
Option Explicit
'/ Declare with events
Dim WithEvents frmCh As frmChild
Private Sub TextBox1_DblClick(ByVal cancel As MSForms.ReturnBoolean)
handleDoubleClick
End Sub
Sub handleDoubleClick()
If frmCh Is Nothing Then
Set frmCh = New frmChild
End If
frmCh.Show False
End Sub
'/ Handle the event
Private Sub frmCh_cClicked(cancel As Boolean)
Me.TextBox1.Text = frmCh.bChecked
End Sub
Form 2: frmChild:
Option Explicit
Event cClicked(cancel As Boolean)
Private m_bbChecked As Boolean
Public Property Get bChecked() As Boolean
bChecked = m_bbChecked
End Property
Public Property Let bChecked(ByVal bNewValue As Boolean)
m_bbChecked = bNewValue
End Property
Private Sub CheckBox1_Click()
Me.bChecked = Me.CheckBox1.Value
'/ Raise an event when something happens.
'/ Caller will handle it.
RaiseEvent cClicked(False)
End Sub
You can do this with a presenter class which controls userform instances and pass values between them. I mocked up something similar to give you an idea.
Presenter. This is a class module which creates the userforms, controls their scope, and catches the event thrown by the
ConditionalBuilder. It makes it super easy to pass values between
userforms.
Private WithEvents CB As ConditionalBuilder
Private MG As MappingGuide
Public Sub ShowCB()
Set CB = New ConditionalBuilder
CB.Show vbModal
End Sub
Private Sub CB_ShowMappingGuide()
Set MG = New MappingGuide
MG.Show vbModal
CB.UpdateTB1 Value:=MG.SmartTag
End Sub
ConditionalBuilder.
This has a simple function to update your textbox and also an event which raises actions in the presenter.
Public Event ShowMappingGuide()
Public Function UpdateTB1(Value As String)
TextBox1.Value = Value
End Function
Private Sub TextBox1_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
RaiseEvent ShowMappingGuide
End Sub
MappingGuide.
The Type and Property could be overkill since we just want one value from the mapping guide but it's still good practice.
Private Type TView
Tag As String
End Type
Private this As TView
Public Property Get SmartTag() As String
SmartTag = this.Tag
End Property
Private Sub UserForm_Initialize()
Tags.List = Array("a", "b", "c")
End Sub
Private Sub Tags_Click()
this.Tag = Tags.List(Tags.ListIndex, 0)
Me.Hide
End Sub
I have one final Standard Module which creates the Presenter. This is what you'd hook up to your ribbon.
Public Sub ShowProject()
With New Presenter
.ShowCB
End With
End Sub
Step 1 (double click text field)
Step 2 (selecting "b")
Step 3 (result)
I actually solved it by placing the below block inside the IF where I check for the form being loaded and I will leave open for better answers, if there are any.
Dim uForm As Object
For Each uForm In VBA.UserForms
If uForm.Name = conditionalBuilder.Name Then
uForm.fieldName.Text = smartyTag
uForm.Show
End If
Next

Unable to modify object by another - runtime Error 91

Let's say I have a user form with two combo boxes created at runtime. First combo sources items from A1:A2 (Fruits, Vegetables). I want the second combo to source either from B1:B2 (Apple,Orange) or from C1:C2 (Carrot,Tomato) deppending what user chose in first combo. Below my class named "FoodClass"
Option Explicit
Public WithEvents MyCombo1 As MSForms.ComboBox
Public WithEvents MyCombo2 As MSForms.ComboBox
Public Sub DrawCombo1(oParent)
Set MyCombo1 = oParent.Controls.Add("Forms.ComboBox.1", "Combo1", True)
With MyCombo1
.Top = 20
'(...) setting position
.RowSource = "A1:A2" 'Fruits;Vegetables
End With
End Sub
Public Sub DrawCombo2(oParent)
Set MyCombo2 = oParent.Controls.Add("Forms.ComboBox.1", "Combo2", True)
With MyCombo2
'(seting position)
End With
End Sub
Public Sub MyCombo1_Change()'When user chose from combo 1...
If MyCombo1.Value = "Fruits" Then MyCombo2.RowSource = "B1:B2" 'Apple;Orange
If MyCombo1.Value = "Vegetables" Then MyCombo2.RowSource = "C1:C2"'Carrot;Tomato
End Sub
Then there is my code in user form:
Option Explicit
Public ComboBox1 As FoodClass
Public Combobox2 As FoodClass
Private Sub UserForm_Initialize()
Set ComboBox1 = New FoodClass
Call ComboBox1.DrawCombo1(UserForm)
Set Combobox2 = New FoodClass
Call Combobox2.DrawCombo2(UserForm)
End Sub
Each time user changes the first combobox Error 91 appears. How can I modify one object by another?
In UserForm_Initialize create just one instance of FoodClass so this instance will have both combo boxes initialized:
dim food as FoodClass
Set food = New FoodClass
call food.DrawCombo1(UserForm)
call food.DrawCombo2(UserForm)
Note: Instead of UserForm the keyword Me can be used inside of the UserForm class. Me refers to current instance of the UserForm.
call food.DrawCombo1(Me)

VBA for Access 2010: Dynamically creating a control and linking event handlers

In Microsoft Access 2010, I am trying to dynamically create a form and then add a commandbutton to it. However, I can't figure out how to assign an event handler to that button's click (or onclick) event?
From reading extracts on the internet, I have created the following vba module code:
Option Compare Database
Option Explicit
Public Sub ProduceForm()
Dim aForm As Form
Dim aButton As CustomButton
Set aForm = CreateAForm("Table1")
Set aButton = CreateAButton(aForm, "Click me!")
DoCmd.Restore
End Sub
Private Function CreateAForm(table As String) As Form
Set CreateAForm = CreateForm(, table)
With CreateAForm
.Caption = CreateAForm.Name
End With
End Function
Private Function CreateAButton(aForm As Form, text As String) As CustomButton
Set CreateAButton = New CustomButton
Set CreateAButton.btn = CreateControl(aForm.Name, acCommandButton)
CreateAButton.SetupButton text
End Function
In accordance with the internet advice, I have then added the following class module (that I've called "CustomButton" and is referred to above):
Option Compare Database
Public WithEvents btn As CommandButton
Public Sub SetupButton(text As String)
If IsNull(btn) = False Then
With btn
.Caption = text
.OnClick = "[Event Procedure]"
End With
End If
End Sub
Public Sub btn_OnClick() ' or should this method just be called btn_Click()?
MsgBox "Happy days"
End Sub
However, when I run this code and then click the button (when in form view) nothing happens?
I notice that explanations given for similar problems, but for excel 2010, give an alternative solution by writing code as a string in a "CodeModule", which I think is linked to the "vbComponents" object. This solution should work for me but I can't find this functionality in Access 2010?
Anyway, any help on this issue would be greatly appreciated.
Thanks
EDIT - my original answer had assumed that Access behaved something like excel, which doesn't seem to be the case...
You don't need a custom class to handle events - you just pass a string to the OnClick property of your button.
This works for me - maybe needs tidying up a bit though.
Public Sub ProduceForm()
Dim aForm As Form, strName As String
Dim btn As CommandButton
Set aForm = CreateAForm("Table1")
Set btn = CreateAButton(aForm, "Click me!", "=SayHello()")
btn.Top = 100
btn.Left = 100
Set btn = CreateAButton(aForm, "Click me too!", "=SayHello('World')")
btn.Top = 1000
btn.Left = 100
DoCmd.OpenForm aForm.Name, , , , , acDialog
DoCmd.Restore
End Sub
Private Function CreateAForm(table As String) As Form
Set CreateAForm = CreateForm(, table)
With CreateAForm
.Caption = CreateAForm.Name
End With
End Function
Private Function CreateAButton(aForm As Form, txt As String, proc As String) As CommandButton
Dim btn As CommandButton
Set btn = CreateControl(aForm.Name, acCommandButton)
btn.OnClick = proc
btn.Caption = txt
Set CreateAButton = btn
End Function
'has to be a Function not a Sub
Public Function SayHello(Optional arg As String = "")
MsgBox "Hello " & arg
End Function