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?
Related
If you open a Form with acDialog (using DoCmd.OpenForm), execution of the calling routine will halt execution until the form is either closed or made invisible. Using the invisible method is useful to let the calling routine harvest data off the form.
I had hoped that Reports would work the same way. That is, opening a report using acDialog with DoCmd.OpenReport. Unfortunately, when the report is made invisible, it disappears but does not return control to the calling routine.
Is this a bug in Access? Does anyone know of a way to make this work? My alternatives to pass data back to the calling routine are all ugly. For example, use global variables or have the report insert data back to its caller before closing (this requires having the report know its caller).
As pointed out, reports generally don’t do much interaction, and they don’t generally need to return data (what data going to change in a report?).
However, you can adopt a relative “clean” means to return values to the calling form, and this works well for forms, and would also work reasonable well for reports (but as noted, it seems rather rare that a report would return data since they tend to not change anything).
So what you do is in the forms on-load (or report) is grab a reference to the calling form like this:
Option Compare Database
Option Explicit
Public frmPrevious As Form
Private Sub Command67_Click()
' this is our report close button code
' we can set, call, or do ANYTHING to the calling
' form - including setting controls or running code
frmPrevious!Text18 = Now()
frmPrevious.Refresh
Call frmPrevious.MyCodeToRun
DoCmd.Close
End Sub
Private Sub Report_Open(Cancel As Integer)
Set frmPrevious = Screen.ActiveForm
End Sub
So the above is the code in the report. Note that we can set any value in the calling form, set any control, run any code, or even “refresh” the calling form.
So you are free to grab values from the previous form, you are free to return values, set controls, or run code, or use ANY property or method in that calling form.
In fact, as a coding standard, I very often create an frmPrevious value for “most” forms. And thus now in my code for a form (or report), you can always referance, or do anything to the previous calling form.
Eg:
frmPrevous.Requery ‘ requery data in calling form
‘ force write of data in previous form
if frmPrevious.Dirty = true then
frmPrevious.Dirty = false
End if
‘ get name of form that called the previous form.
frmPrevious.frmPrevious.Name
So this approach provides a nice clean way to do ANYTHING to the calling form, and the name of the calling form is not hard coded.
In addition to the above, this also means you do NOT have to launch the form as “dialog” which of course not only stops calling code, but prevents use of ribbon or custom menus etc.
So what this means you have to place the “additional” code you wanted to run in the calling form into a separate sub, and call that code from the report on the button you press to either close, or do whatever action is that you want.
I've created a function that attempts to return a SubForm data type. This function is used by various parent Forms.
The function looks like this:
Public Function mySubFrm(name As String, subformName As String) As SubForm
Dim frm As Form
Dim subFrm As SubForm
Set frm = Forms(name)
Set subFrm = frm.Controls(subformName)
mySubFrm = subFrm
End Function
I've attempted to use it by the following:
Dim testSubForm As SubForm
testSubForm = mySubFrm("testForm", "testSubForm")
Immediately, it follows with compile error:
Invalid use of property
What I've attempted to do was add a watch at frm.Controls(subformName) and I see its return type is SubForm/SubForm, so I feel as though I am declaring and setting the right data type, but then again I'm not sure?
Can someone assist me with what I'm not doing properly?
Thanks
I don't know much Access, but I know VBA pretty well.
Your function is returning an object reference:
Public Function mySubFrm(name As String, subformName As String) As SubForm
As such, its return value must be assigned using the Set keyword:
Set mySubFrm = subFrm
The reason why you're getting this confusing error, is because of a lovely (not!) thing in VBA called default properties.
See, a form has a default property, most likely its Controls collection - and that property is only exposing a Public Property Get accessor, which makes it read-only.
So when you omit the Set keyword:
mySubFrm = subFrm
VBA assumes the code is legal, and so the only thing you could possibly be wanting to do, is to assign that default property - in other words, it's behaving exactly as if you would have written:
mySubFrm.Controls = subFrm
But the Controls class' own default property is its Item member, which is also read-only: there's no way the default Controls property can appear on the left-hand side of an assignment.
Hence, invalid use of property.
My open-source project Rubberduck will soon have an inspection that will issue a result whenever you're implicitly referring to an object's default property, offering to make the call explicit. Writing code that says what it does, and does what it says is hard when you don't even know you're referring to a default property.
You receive the error because you're trying to set an object, but are not using the Set keyword.
Perhaps it should be the following:
Set mySubFrm = subFrm
Or
Set mySubFrm = frmDatasheet
I would like to suggest a different approach. Typically when you create a form with a subform, it's quite rare that you actually need the subform to be dynamic. There is almost always a relationship between the parent form and the child form, and I would consider a parent form without its child a broken object in general.
Therefore, when I need to access things that's on the subform, I prefer to use the explicit version:
Dim mySubForm As Access.SubForm
Set mySubForm = Me.Controls("mySubForm").Form
mySubForm.SomeControlOnMySubForm.Value = 123
Yes it's 3 lines now but the advantage is that the reference to a specific form is now made explicit in your parent form. You now can see from the code that the parent form depends on that subform. More importantly, if you delete or rename the control SomeControlOnMySubform, the above form will allow the VBA compiler to warn you that there is no such object on the subform, enabling you to verify your code.
In other words, try your best to convert any potential runtime errors into compile-time errors because compile-time errors are much easier to validate and prevent than runtime errors.
The same principle works in the reverse; when you want to describe your subform as explicitly depending on a parent form, you can do:
Dim myParent As Form_frmParent
Set myParent = Me.Parent
NOTE: The code all assumes that there are code-behinds for all forms involved. If a form has no code-behind, it won't have a VBA class for you to reference. In such case, I set the form's property HasModule (located in the Others tab) to Yes even if there's ultimately no code-behind. (The reason is that it used to be that you could create "lightweight" form by creating it with no code-behind but with modern hardware, there is hardly any difference)
I have a click event on a form in Access 2010 that looks like so:
Private Sub SaveRecord_Click()
checkDataIntegrity(Me)
End Sub
Where checkDataIntegrity is defined as follows:
Function checkDataIntegrity(ByVal fForm As form) As Boolean
This works fine and dandy. However, I have another click event:
Private Sub LFS_Flashed_Successfully_Fail_Click()
preventSimultaneousPassAndFail (Me)
End Sub
Where preventSimultaneousPassAndFail is defined as follows:
Function preventSimultaneousPassAndFail(ByVal fForm As form) As Boolean
When I invoke this click event I get the following error:
Run-time error '13':
Type mismatch
What am I doing wrong here? Both event calls are invoked in the same form and call functions in the same module.
You don't seem to be doing anything with the functions' return values, so don't enclose the arguments in parentheses.
'preventSimultaneousPassAndFail (Me)
preventSimultaneousPassAndFail Me
That is standard VBA practice. Unfortunately, I can't explain why it triggered an error in one case but not the other. So this issue may not contribute to the problem, but I still suggest you make that change to rule it out.
Although we don't know any details about those functions, I suggest you consider passing the form objects ByRef instead of ByVal.
The reason why you get the error is that in the case of
preventSimultaneousPassAndFail (Me)
the parenthesis expression tries to invoke the default member of the object. In case of an Access.Form this is the Name property being of type String. That's the reason for the type mismatch error.
In case of
preventSimultaneousPassAndFail Me
the reference to the form itself is passed to the method.
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 form called Form1. In Form1, I have the following code -
Dim details As clsDetails
Set details = getDetials(1) ' This fails. It doesn't assign a value.
The getDetails function is declared in a separate module as follows-
Public Function getDetials(detailNumber As Integer) As clsDetails
Dim details As clsDetails
Select Case detailNumber
Case "1"
Debug.Print "Inside case1"
Set details = getDetail1()
Debug.Print details.comment ' This prints correctly.
End Select
Set getDetails = details
End Function
However, when I execute the above code, somehow, the details variable in Form1 doesn't get set, even though the getDetails function is called and prints the details correctly inside it. How to rectify this?
Do you have the Option Explicit keyword defined?
It looks like you might have a typo. Your function is called getDetials, but the variable you're setting the result to is getDetails, so the return value is not getting set.
I fixed the typo and everything works as expected on my end. Using the Option Explicit keyword will catch these types of errors.
I think this may be a case of not having Option Explicit On
Check you have Option Explicit at the top of your form (and in fact everywhere)
Your type mistake declaring the function getDetials but then setting an object called getDetails to the newly created class is actually creating a new object and the function return is not being set at all.
Insert Option Explicit and you will see that it won't compile!
When you have added Option Explicit everywhere got to Tools>Options>Editor tab and tick the box that says Require variable declaration - that will make sure it is added every time you add a new code file to your project.