I'm trying to create my own toolbar button (commandbarbutton) in Access VBA Editor. Its a button to run a procedure in a public module called "RunTests". I made the button like so:
Public Sub CreateToolbar()
Dim cmdBar As CommandBar
Dim cmdButton As CommandBarButton
Set cmdBar = Application.VBE.CommandBars.Add("Run Tests")
Set cmdButton = cmdBar.Controls.Add(msoControlButton)
cmdButton.FaceId = 558
cmdButton.DescriptionText = "Run the tests"
cmdButton.Caption = "Run Tests"
cmdButton.OnAction = "RunTests"
cmdButton.Enabled = True
cmdButton.Style = msoButtonIconAndCaption
cmdBar.Visible = True
End Sub
I ran the procedure as a test. And it created the button just fine. But the "click" action doesn't respond. The button doesn't seem to be clickable. Does anybody know why?
Edit: Found the solution: http://support.microsoft.com/default.aspx?scid=kb;en-us;Q280607 - I will answer my own question as soon as I can to close it.
The key for me was to make "RunTests" a public subroutine. When it was private it didn't work.
cmdButton.OnAction = "=RunTests()"
public sub RunTests()
private sub RunTests() does not work.
Found the solution - To make it work you have to implement the workaround described here: http://support.microsoft.com/default.aspx?scid=kb;en-us;Q280607
Unfortunately I tried to be fancy and define my button and menus in a class. The OnAction subroutine was never executed while it was defined locally as part of the class. It would only execute when defined outside of the class definition.
When I redefined my class to be just a module containing many subs and functions, then the OnAction subroutine was executed. Might have to do with scope and visibility or that I didn't have the OnAction semantics correct for a call to a class method. Bottom line is to define the button/menu/widget in a module and it will be much easier. (I was using Excel VBA but I think Access and Excel VBA are the same.)
Related
I'm trying to call a Sub from another Form, but I get:
Error 2465: Application defined error or object defined error
I already know that to call a procedure from another form I can use the syntax:
Form(Name).Procedure
But, in this case I want to refer to this form from a variable (Type Form).
Code from form Parametro_Cadastro:
Dim formCaller As Form
Set formCaller = Application.Forms("Cadastro_Aluno").Form
If (Not formCaller Is Nothing) Then
'
formCaller.SetDadosParametroCadastro Me.txtKey.Value, dadoAlterado
Set formCaller = Nothing
End If
Code from form Cadastro_Aluno:
Sub SetDadosParametroCadastro(ByVal KeyValue As String, ByVal DataValue As String)
'
'...
End Sub
The code in Parametro_Cadastro gives Error 2465 on the line:
formCaller.SetDadosParametroCadastro Me.txtKey.Value, dadoAlterado`
...even before the Sub SetDadosParametroCadastroin the form Cadastro_Alunos to be called.
In the Immediate Window, the variable formCaller is already signed as Form\Form_Cadastro_Alunos as expected.
What can I do in this case?
EDIT
The only way it worked was to assign the variable formCaller to Access.Forms(formName) and not through Application.Forms(formName) or simply Forms(formName), so the code now is like below:
Code from form Parametro_Cadastro:
Dim formCaller As Form
Set formCaller = Access.Forms("Cadastro_Aluno")
If (Not formCaller Is Nothing) Then
'
formCaller.SetDadosParametroCadastro Me.txtKey.Value, dadoAlterado
Set formCaller = Nothing
End If
When I check the variable formCaller in the Imediate Window, I also get (as before) Forms\Form_Cadastro_Alunos.
The interesting thing is this only change make the code work as expected. Maybe the Forms member to be called is from Access Object instead of Applications?
Although you can call a procedure from a form, it's generally avoided and is considered bad practice.
Any Sub or Function that need to be shared should be placed in a separate, shared module.
Some excellent advice from Stewart # Bytes.com:
Form code modules are essentially 'private' in scope. Functions and subroutines defined in them are normally unavailable outside of the form concerned - although it is actually possible to call the functions and subroutines defined as Public in scope within a form code module from outside of that module.
Use of the keyword Public is not just a matter of suddenly adding one as a change of style. It reflects that you wish the subroutine, function or variable to be available outside of the scope of the code module concerned...
The code modules which can be created or opened from the Modules tab are the usual locations of public subroutines and functions. Named modules provide a means of grouping tasks by logical function, and if used consistently can be an aid to maintainability.
I use many several named modules in my applications, separating custom linked table maintenance from username parsing, e-mail handling and so on. I also include a "General" module which contains general bespoke functions that I find useful and re-use across multiple databases (for example, to return the current financial year or the current quarter within it).
All subroutines and functions defined using the Public keyword in the publicly-accessible code modules are available outside of the module concerned, whereas those defined as Private cannot be used outside of the scope of the code module in which they reside.
"If you must..."
If, for some reason, you must call a public sub from a form, the syntax is:
Call Forms!FormName.SubName
Note that the sub being called must have the Public keyword specified.
More Information:
MSDN : Understanding Scope and Visibility
Bytes.com : How and where to use Private or Public sub or function?
In an Access application, I'd like to programmatically set up a dynamic key handler to execute a VBA function when this key is pressed.
I was able to manually create a macro that calls the function and another AutoKeys macro that "calls" the first macro when a specific key is pressed.
Now, I'd like to be able to create these macros with VBA only. Is this somehow possible?
Of course, if there are other ways to achieve what I want, I am open to suggestions.
You could do something like this, create a class called clsCustomForm, containing this code
Option Compare Database
Option Explicit
Private WithEvents frmCustomForm As Form
Public Function INIT(frmFormToMakeCustom As Form)
Set frmCustomForm = frmFormToMakeCustom
frmCustomForm.OnKeyDown = "[Event Procedure]"
End Function
Private Sub frmCustomForm_KeyDown(KeyCode As Integer, Shift As Integer)
MsgBox "Hello"
End Sub
Then in a module, you will need to have a public collection, which you add a class for each form opened and removed when each form closed, I'd suggest a dictionary, with the key as the form name and the item as the class, for example, just testing (limited time) I've used the following code in 1 form, hope this helps, i'll have a bit more time later to show the dictionary if needed.
Option Compare Database
Option Explicit
Public CUSTOM_FORM As clsCustomForm
Private Sub Form_Load()
Set CUSTOM_FORM = New clsCustomForm
CUSTOM_FORM.INIT Me
End Sub
I've made a graphical navigation system so the user can easily find the record he/she wants to. I'm using this on multiple forms now, and since I'm going to use it on another form, I want to standarize the code into a class instead of copy-pasting the VBA code. This way I can improve the code in one place, instead of doing the same change on all the forms.
This is how it works right now:
Dim v As New clsNav
Set v.button1 = Me.button1
Set v.button2 = Me.button2
v.init
And in v.init, I want to set up all the events like click. So when the user clicks button1, it should run a specified method.
How can I do that?
The events for the button clicks will look like this
Private Sub Button1_Click()
' Your code
End Sub
And they are located within the code found behind the respective forms. To be able to reuse code you simply write a sub in a separate module and then call it in the event.
Private Sub Button1_Click()
call MySub()
End Sub
' This is in a module
Private Sub MySub()
' Your code
End Sub
Now this works as long as the code you have to run doesn't use controls specific to that form. If you do need to write code like that, its simply a matter of passing a control to the sub, instead of calling it by its Name
Example. Lets say when we click our buttons it updates a TextBox with today's date. The textboxes have different names on each form. txtDate1 on Form1 and txtDate2 on Form2. So how that will look is
'Form 1 Button
Private Sub Button1_Click()
call MySub(txtDate1)
End Sub
'Form 2 Button
Private Sub Button2_Click()
call MySub(txtDate2)
End Sub
' This is in a module
Private Sub MySub(t as TextBox)
t.Text = Date()
End Sub
If you're trying to do this during run time
How to add events to Controls created at runtime in Excel with VBA
seems like a good place to start. I can't imagine a situation where this would be worth the effort.
I have a generic class I use called frmCtrl, which has WithEvents pointers set up for different control types, with the corresponding events (click, dbl-click, etc...). I then have a function on that class to "set up" the object, so I pass the control in, and the function assigns it to the object pointer of the correct type. This routine also has a parameter for what function to call on which object, so it knows how to respond to the event.
So when I build a form that uses dynamically-added controls, I just create one of these objects for each of the controls, and set it up to call code within the form. The code in the form takes an argument of just the frmCtrl object, so it can easily see which control fired the code, and go from there. I work in Visio, so I'm able to also call a function by module and function name, so the class can handle that too. You'd have to work in how to make such a call using Access, though.
This approach can be used when your controls have been added via the VBIDE, but it may not be worth the effort to set this class up for that, when you can just use the method in the other answer here.
I have a navigation form with two subforms that looks something like this:
When the button in Subform A is clicked, I would like to call a method from Subform B. The method is defined like this:
Public Sub MyMethod()
Debug.Print "MyMethod called"
End Sub
I tried using Forms!SubformB.MyMethod but I get the error Database cannot find the referenced form 'SubformB'. Referring to this page, I also tried Forms!NavigationForm!SubformB.MyMethod but then I get Database cannot find the referenced form 'NavigationForm'. Does anyone know how to do this properly?
Thank you.
Function and Sub procedures defined in the class module of a Form are considered to be private to that form. If you want a Sub that can be called from several different forms then move that Sub to a "regular" VBA Module (i.e., one that is created when you choose Insert > Module from the menu bar in the VBA editor) and be sure to declare it as Public.
Here is a snippet of code I have that toggest the AllowEdits method of a subform when clicking a button on the mainform. Pretty sure this is what you are after.
Function ToggleEditStatus_Child(WhichForm, WhichChild As String)
'Changes the AllowEdits setting of the SubForm parameter
Dim Cform As Object
Set Cform = Forms(WhichForm).Controls(WhichChild).Form
Cform.AllowEdits = Not Cform.AllowEdits
Forms(WhichForm).Refresh
Cform.Refresh
Forms(WhichForm).Controls(WhichChild).SetFocus
Set Cform = Nothing
End Function
I did figure this out but there's a catch - the other form has to be open. This worked for me though so I'm posting the solution here.
First, navigate to the form:
DoCmd.BrowseTo ObjectType:=acBrowseToForm, _
ObjectName:="SubformB", _
PathToSubformControl:="NavigationForm.NavigationSubform", _
DataMode:=acFormEdit
Then make a pointer to the subform:
Dim f As Form_SubformB
Set f = Forms("NavigationForm").NavigationSubform.Form
And you are free to call the method:
f.MyMethod()
Note: you will most likely have to make the method Public for this to work.
So I have a sub form that creates a record (and record it), and then another sub form opens up for data entry...however can I still use an open arg to pass the value (ProjectID) between these sub forms.
I know exactly how to do this with forms, but trying this with subforms is a little different.
docmd.openform "FormExample",,,,,,Passingvalue
but can't do it like this:
me.MyChild.SourceObject = "SecondSubForm",,,,,,PassingValue
so yeah the above might look ridiculous to those who know the right way, but I thought that this would illustrate exactly what I am trying to do.
So how can I pass the value? Do I even need to with subforms instead of forms? or would this variable hold my value even though it was created in the first sub form?
thanks
justin
I don't think there's any way to pass an OpenArg with SourceObject =
Can it work to assign the value to a control after you set the SourceObject?
Me.MyChild.SourceObject = "SecondSubForm"
Me.MyChild!SomeControl = PassingValue
Rather than using a Hidden control as an alternative you can add a public method or public property to the Child form and then call it.
Personally I prefer to use a method because typically mutating a property shouldn't have side-effects.
Here's an example.
The child form Called SomeChildForm will have this method
Public Sub SetSomeValue(ByVal somestring As String)
Me.Text1.Text = somestring 'or probably something to do with the record source
End Sub
From the parent you do this
Dim frm As Form_SomeChildForm
Set frm = Me.SubFormControlName.Form
frm.SetSomeValue "x"