I am trying to create a function that will allow me to use various command buttons without having to recreate the code every time.
To do this I have to pass the form name through a function
Function:
public Function NewRecord(controlForm, focusForm)
focusForm.SetFocus
DoCmd.GoToRecord , , acNewRecord
controlForm.SetFocus
controlForm - This is the main form akin to the Me function
focusForm - This is for not only the main form but when I create subforms I have to have the focus on the subform to have the command work.
To call the function I did the following:
public sub Command19_Click()
Dim controlForm
Dim focusForm
Set controlForm = Forms![frm_Sales_CustomerProfile]
Set focusForm = Forms![frm_Sales_CustomerProfile]
Call NewRecord(controlForm, focusForm)
End Sub
I get this error that States: Compile Error: Invalid Use Of Property.
You got trapped using an already in this context (the form) used identifier and not using a strong name (NameOfLibrary.NameOfFunction).NewRecordis a forms property, soInvalid Use Of Propertymakes sense. If you useMyModulName.NewRecord(frm1,frm2)everything is fine. If you useNewRecordin Module òr Class it works too as there is no property with same name (I assume;-)).
To be honest, I don't use strong names either (except on database or recordset objects, as I got trapped there too, assuming DAO, using ADODB), but the Pros suggest that and now we know why!
Your function should have just one argument as it is sufficent to pass only the subforms reference if you need that form NewRecord(frm as Access.Form) (note the strong name!). You can easy refer to the mainform with Set mfrm = frm.Parent
Your code;
Public Function FrmNewRecord(frm As Access.Form)
frm.Recordset.AddNew
End Function
Public Sub Command19_Click()
FrmNewRecord(Forms![frm_Sales_CustomerProfile]) ' mainform
FrmNewRecord(Forms![frm_Sales_CustomerProfile]!sfrmControl.Form) ' subform
End Sub
You are passing the same form two times in your code, any reason? If Forms[frm_Sales_CustomerProfile] contains Command19 use Me.
I dropped the .SetFocuspart as not necessary or any reason to for setting focus? Why is NewRecord a function? Doesn't return anything.
btw: I am working on aSubForms(frm)function , that returns a collection of all subforms.
Code:
'SubForms(frm As Access.Form) returns a collection of all subform references in frm
Public Function SubForms(frm As Access.Form) As VBA.Collection
Dim ctr As Access.Control
Dim sfrm As Access.Form
Dim col As New VBA.Collection
For Each ctr In frm.Controls
If ctr.ControlType = acSubform Then
On Error Resume Next
Set sfrm = ctr.Form
If Err.Number = 0 Then
col.Add sfrm, sfrm.Name
End If
On Error GoTo 0
End If
Next ctr
Set SubForms = col
End Function
As a general rule to build say custom menu bars, or ribbons, you can write code that is “form” neutral like this:
Public Function MyDelete(strPrompt As String, strTable As String)
Dim strSql As String
Dim f As Form
Set f = Screen.ActiveForm
If MsgBox("Delete this " & strPrompt & " record?", _
vbQuestion + vbYesNoCancel, "Delete?") = vbYes Then
So note how we don’t need to pass the form name at all – the above code simply picks up the active screen as variable “f”.
At that point you can do anything as if the code was inside the form.
So
Me.Refresh (code inside the form)
Becomes
f.Refresh
So the key concept here is that you don’t need to pass the current active form since screenActive form will enable you to get the current form object anyway.
However, for sub forms and “common” type of code the above falls apart because screen.ActiveForm will return the main form, and not the sub form instance.
So as a second recommended approach, simply always pass the current context form object “me” like this:
Call MySub(me)
And you define your sub like:
Sub MySub(f as form)
Now in this code we can reference "anything" by using "f" in place of "me"
f.Refresh
So just pass “me” if you ever use sub forms. Given the above information, then your code becomes:
public sub Command19_Click()
Call NewRecord(me)
End Sub
And NewReocrd becomes:
Sub NewRecord(f as form)
Now in your newreocrd code, you can use “anything” form the object such as:
f.Name ' get name of the form.
or
City = f.City ' get value of city control
So pass the “whole” form context.
And you could say make a routine to display the City value for any form like:
Call ShowCity(me, "City")
And then
Sub ShowCity(f as form, strControlToShow as string)
Msgbox "City value = " & f(strControlToShow)
So OFTEN one will write code that works for any form by simply picking up the current active form as:
Dim f As Form
Set f = Screen.ActiveForm
And note how the above code picks up right away the active form – this is a good idea since then if focus changes, the “f” reference will remain intact for the code that follows in that “general” routine that is called + used from many forms.
However due to the sub form issue, then often it simply better to always pass the “whole” forms instance/object with:
Call MyNewRecord(me)
And then define the sub as:
Sub MyNewReocord(f as form)
DoCmd.GoToRecord acDataForm, f.Name, acNewRec
End Sub
And you could optional add “focus” to above with
f.SetFocus
So for a lot of menu or ribbon code, you don't pass the form object, but simply use screen.ActiveForm, and you can also use Screen.ActiveControl (again great for menu bar or ribbon code to grab what control has focus). However due to sub form limitations, then often passing "me" to the routine will achieve similar results if not better in some cases.
Related
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
I'm a newcomer to Access trying to cobble things together from helpful information I've found here.
I have a form that needs to populate the fields based on a combo box selection in the form header. The form is based on an underlying query with the following criteria for field "StudID" [Forms]![frmStudConsentUpdate]![cmbStud] where cmbStud is my combo box. The combo box pulls in StudID, StudFN, StudLN with StudID as the bound columnn. The after update event requeries the form (Me.Requery). This works beautifully, but only if I first open the form in design view, open the Record Source, and save it. I don't make any changes at all, but once I've done this the form works. Otherwise, nothing happens when I select a student in the combo box. Any thoughts on what I need to do to make this work without having to re-save the underlying query?
This is old bug in MS Access, I have no idea why they still didn't fix it:
If underlying form's query has in criteria form's control and the form was filtered once (at start or manually/using VBA), it doesn't accept new values from form's control and uses old value.
Workaround: create public function, which returns control's value and use it in criteria instead of [Forms]![frmStudConsentUpdate]![cmbStud]. You will need to create function for each control or use this function:
Public Function GetControlValue(strFormName As String, strControlName As String, Optional strSubFormControlName As Variant, Optional varDefault As Variant) As Variant
' Returns : Variant, value of control in form/subform
' Comments:
' Params :
' strFormName
' strControlName
' strSubFormControlName
' varDefault - value returned if control is not accessible
'----------------------------------------------------------------------------
On Error GoTo ErrorHandler
If IsMissing(strSubFormControlName) Or Nz(strSubFormControlName, "") = "" Then
GetControlValue = Forms(strFormName).Controls(strControlName).Value
Else
GetControlValue = Forms(strFormName).Controls(strSubFormControlName).Form.Controls(strControlName).Value
End If
ExitHere:
On Error Resume Next
Exit Function
ErrorHandler:
If Not IsMissing(varDefault) Then
GetControlValue = varDefault
End If
Resume ExitHere
End Function
In criteria use function call GetControlValue("frmStudConsentUpdate","cmbStud") instead of [Forms]![frmStudConsentUpdate]![cmbStud]
In your afterupdate for your cmbStud combobox, create code that refreshes the recordsource to
me.recordsource = "SELECT * FROM {yourQueryName} WHERE StudID = '" & me!cmbStud & "'"
I have a form MyForm1 with a module having the method.
Public Sub CreateSQL(ProductID as variant)
Me.Recordsource = " SELECT * FROM ProductOrders " _
" WHERE ProductID = " & Nz(ProductID ,0)
Me.Requery
End Sub
I use this as a subform to a form named MyMainForm.
When I change the value of a control on MyMainForm it successfully executes the following line:
Me.Subform1.Form.CreateSQL ProductID:=Me.cboProductID
My Questions:
Why are the members not listed on the intellisense list that appears after I type Me.Subform1.Form.?
Is there a way of getting them to be listed?
Is there a property that will let me access the "Form_MyForm1" class of the form referenced in the subform control "Source object property" (ie the Me.Subform1.form ) ?
It's like I need to be able to write:
Me.Subform1.Form_MyForm1.CreateSQL ProductID:=Me.cboProductID
Does such a property already exist? If so how do I access it? Is it in the properties collection?
PS: If you need more information please see the same questions posted in a long Stack overflow question here
Harvey
Don't know.
Not that I know of.
Make the subfunction Public.
But it looks like you could save yourself a lot of trouble by using the Master/Child link option of a form and its subform control.
Instead to the mainform calling the method Me.Subform1.Form.CreateSQL
You should create an object variable in the subform that points to the main form and responds to events eg:
Dim WithEvents cvMyParentForm As Form
Property Set MyParentForm(MyParentForm As Form)
Set cvMyParentForm = MyParentForm
End Property
Property Get MyParentForm() As Form
Set MyParentForm = cvMyParentForm
End Property
When the main form opens use the Form_Open event to "initialise" the subforms
Private Sub Form_Open(Cancel As Integer)
If Me.Subform1.Form.MyParentForm Is Nothing Then
Set Me.Subform1.Form.MyParentForm = Me
End If
End Sub
then you can get the subform to respond to the FORM events that are raised by the mainform.
If you need to have the subform respond to any events that you declare in the main form you will need to chnaeg the above code to use the Form_MyMainFormname type
Dim WithEvents cvMyParentForm As Form_MyMainFormName
Property Set MyParentForm(MyParentForm As Form_MyMainFormName)
Set cvMyParentForm = MyParentForm
End Property
Property Get MyParentForm() As Form_MyMainFormName
Set MyParentForm = cvMyParentForm
End Property
Private Sub cvMyParentForm_Current()
'MsgBox "Sub form current event - does syncing"
Me.Form.Recordset.FindFirst "ID = " & Nz(cvMyParentForm.ID, 0)
If Me.Form.Recordset.NoMatch Then
MsgBox "Humph"
Else
End If
End Sub
Private Sub cvMyParentForm_MyEvent()
MsgBox "A user define event 'MyEvent' was fired on the main form"
End Sub
Im trying to pass the name of a global variable to a sub routine and would like to know how to reference it. For example I could do the below with a control:
Private Sub passCtrlName(ctlName as String)
Me.Controls(ctlName) = "Whatever"
End Sub
Edit:
For Example
Public imGlobVar As String
Public Sub passGlobVar(frm as Form, ctlName as String, globVar as String)
frm.Controls(ctlName) = globVar
End sub
And call it as
Private Sub imaButton_Click()
imGlobVar = "something"
Call passGlobVar(Me , txtBox1, imGlobVar)
End sub
2nd Edit:
It seems that I could most definitely be barking up the wrong tree here, so I will explain what I'm trying to achieve.
I have a form that has textboxes for the users (risk) address, with a checkbox at the top that lets the user select that this address is the same as the 'contact' details already on the system, and the textboxes are locked.
Populating the textboxes is fine and works. What I use the global variables for is to improve usability (albeit slightly).
The user can add new details, and if they hit the checkbox 'make same as contact' the details that they have entered are stored in the global variables, one for each control.
If the user has made a mistake by hitting the checkbox, they haven't lost these value, and by unchecking the box the entered values are returned.
I hoped to create a sub routine where I could pass the name of the global variable and control and calling this routine, as opposed to writing it out for each control.
I have a feeling that I could be using the wrong technique to achieve my goals. But in answer to my original question, it appears that you can not pass global variables to sub routines in the manner that I wished.
You do not need to pass global variables, you can simply refer to them by name. Note that global variables are reset if an unhandled error occurs.
In http://msdn.microsoft.com/en-us/library/dd897495(office.12).aspx you will find a section on Scope and Lifetime of Variables and Constants.
In a module:
Option Explicit
Public glbVarName As String
Const MyConstant=123
Sub InitVar
glbVarName="Something"
End Sub
Any other module, includeing a form's class module:
Sub SomeSub
MsgBox glbVarName
SomeVar=2+MyConstant
End Sub
If you're asking if you can dynamically reference global variables using a string containing the variable name the answer is no. You could use a single global array and pass the index, which would allow you to dynamically reference an element of the array.
[Edit]
In response to the clarification in the question: You could just save the value of each control to its Tag property when the user checks the checkbox. Then, if the user unchecks the checkbox, you can just loop over your controls and assign the value from the Tag back to the Value of the control.
You could store the values from your controls in a Dictionary object, using the control names as the dictionary keys. Then you can retrieve the value for each control based on the control's name.
Option Compare Database
Option Explicit
Const cstrMyControls As String = "Text0,Text2,Text4,Text6"
Dim objDict As Object
Private Sub chkToggle_Click()
If Me.chkToggle = True Then
Call SaveValues
Else
Call RestoreValues
End If
End Sub
Private Sub SaveValues()
Dim varControls As Variant
Dim i As Long
Set objDict = Nothing 'discard previous saved values '
Set objDict = CreateObject("Scripting.Dictionary")
varControls = Split(cstrMyControls, ",")
For i = 0 To UBound(varControls)
objDict.Add varControls(i), Me.Controls(varControls(i)).Value
Next i
End Sub
Private Sub RestoreValues()
Dim varControls As Variant
Dim i As Long
If objDict Is Nothing Then
'MsgBox "No values to restore." '
Else
varControls = objDict.keys()
For i = 0 To UBound(varControls)
Me.Controls(varControls(i)).Value = objDict(varControls(i))
Next i
End If
End Sub
I use additional field in table - name cancel - of course boolean - when i'm not sure if contents of fields will be valid I set it true. If this field will be true by the end - then I clean up (it may be all record or some fileds of course). Very easy.
I have a Microsoft Access 2003 file with various tables of data. Each table also has a duplicate of it, with the name '[original table name]_working'. Depending on the user's choices in the switchboard, the form the user choose to view must switch its recordsource table to the working table. I refactored the relevant code to do such a thing into the following function today:
Public Sub SetFormToWorking(ByRef frm As Form)
With frm
.RecordSource = rst![Argument] & "_working"
.Requery
Dim itm As Variant
For Each itm In .Controls
If TypeOf itm Is subForm Then
With Item
Dim childFields As Variant, masterFields As Variant
childFields = .LinkChildFields
masterFields = .LinkMasterFields
.Form.RecordSource = .Form.RecordSource & "_working"
.LinkChildFields = childFields
.LinkMasterFields = masterFields
.Form.Requery
End With
End If
Next
End With
End Sub
The lines of code that call the function look like this:
SetFormToWorking Forms(rst![Argument])
and
SetFormToWorking Forms(cmbTblList)
For some reason, the above function doesn't change the recordsource for the form. I added the 'ByRef' keyword to the parameter just to be certain that it was passing by reference, but no dice. Hopefully someone here can tell me what I've done wrong?
Try removing the parenthesis from your function calls.
SetFormToWorking Forms(rst![Argument])
SetFormToWorking Forms(cmbTblList)
more information
I found the problem. The variable on the third line
rst![Argument]
doesn't exist in the function's scope. I replaced it with
.RecordSource
Unfortunately I'm having another problem with the code but it's unrelated to this question.