Passing alternative OpenArgs to Access Forms created with 'New' - ms-access

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"

Related

Linking form controls to class

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.

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

MS Access Pass Form Through Function

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.

MS Access VBA - Variable not defined error when variables have been declared

I have created a form with some fields that I want to transfer to a new form with the use of variables. The settings for the form's code are 'Option Compare Database' and 'Option Explicit.' I have declared four public variables just below this as follows:
Option Compare Database
Option Explicit
Public LnNum As Long
Public InvLnNum As String
Public Inv As String
Public DefID As Long
Further down is the code to open the form. I have a button set-up on the first form, and when it opens I want a few fields to copy over to the new form (these fields have the same names). This is the code for that specific event:
Private Sub cmdNewDefect_Click()
LnNum = Me.LoanNumber.Value
InvLnNum = Me.InvestorLoanNumber.Value
Inv = Me.Investor.Value
DefID = Me.ID.Value
DoCmd.OpenForm "frmInvestorDefects"
DoCmd.GoToRecord acDataForm, "frmInvestorDefects", acNewRec
Form_frmInvestorDefects.LoanNumber.Value = LnNum
Form_frmInvestorDefects.InvestorLoanNumber.Value = InvLnNum
Form_frmInvestorDefects.Investor.Value = Inv
Form_frmInvestorDefects.ID.Value = DefID
End Sub
When I click the button I get the error message, 'Compile Error: Variable Not Defined.' I have another database with multiple forms that do the exact thing that I am wanting this form to do without issue. The code is exactly the same, except that the event occurs on an After Update event instead of On Click, so I am stumped. I have tried moving the variable declarations into the actual sub procedure with Dim statements instead of Public, but that doesn't work either. I am thinking there is a setting or something that I am forgetting. Thanks in advance!
If the form you are opening is: "frmInvestorDefects"
Then try:
frmInvestorDefects.LoanNumber.Value = LnNum
Not sure why the 'Form_' prefix is required...
Not sure why the .Value part is needed either
frmInvestorDefects.LoanNumber = LnNum
or
[frmInvestorDefects].[LoanNumber] = LnNum

Referencing global variable - MS Access

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.