Passing a form and list box - ms-access

I'm trying to develop a sub that I can pass different variables to save me some time when creating quite a few different types of listbox in VBA Access. I've come up with the following:
Public Sub openCASEFORM(f As Form, list As ListBox)
DoCmd.OpenForm f, , , "[FileID]=" & f.list
End Sub
And I call it as follows:
Private Sub listPreAn_Click()
Call openCASEFORM(Me, listPreAn)
End Sub
However when trying it I get a Run-time error '2465' Application-defined or object-defined error. I'm struggling to see where it's going wrong. Any thoughts?

What happens if you use
Call openCaseFORM(Me, Me!listPreAn
?
Well of if you do not use list as Listbox as second parameter, You are not using it anyway.

Your code is really badly written, as you're passing objects but you need to use strings to do the job. I'd rewrite it thus:
Public Sub openCASEFORM(f As String, list As String)
DoCmd.OpenForm f, , , "[FileID]=" & f(list)
End Sub
Either that, or:
Public Sub openCASEFORM(f As String, list As ListBOx)
DoCmd.OpenForm f, , , "[FileID]=" & list.Value
End Sub
Both of these assume the listbox is a simple one (i.e., not multiselect).
I would suggest that it would make more sense to rewrite thus:
Public Sub openCASEFORM(ByVal strFormName As String, ByVal strCriteria As String)
DoCmd.OpenForm strFormName, , , strCriteria
End Sub
That way, you could pass any arbitrary WHERE clause you liked.

Related

Is it possible to incorporate a private sub into a function in VBA access?

Basically I would like to use a function or sub to "paste" code so that I don't have to paste the same thing over and over again. I'm kinda new with VBA so I'm not sure if there's an easier way to handle this. I have many rows of combo boxes that will be determined by their corresponding text box in front of them. I would like to just place a function/subroutine with a number to designate the combo box.
Function cboAfterUpdate (x as Variant)
Private Sub cboOperation &x& _AfterUpdate()
'some other function that uses x'
End Sub
End Funcion
I'm hoping it'll end like this...
cboAfterUpdate(5)
Will show..
Private Sub cboOperation5_AfterUpdate()
'some other function that uses 5'
End Sub
In other words I'd like to do this
Private Sub cboOperation1_AfterUpdate()
Call SqlDes(1)
End Sub
Private Sub cboOperation2_AfterUpdate()
Call SqlDes(2)
End Sub
Private Sub cboOperation3_AfterUpdate()
Call SqlDes(3)
End Sub
up to 20 times since I have cboOperation(1-20) combo boxes. How can I do that in one function without having to have to copy/paste/type 20 private sub events?
I'd probably suffix my comboboxes with a number and use use their Caller property to provide the parameter you want via their string name. E.G. create 3 comboboxes and call them cbo_1, cbo_2, and cbo_3. Then assign a single sub to them:
Sub CboHasChanged()
Dim cboNumber As Integer
Msgbox Application.Caller & " is about to call the sub SqlDes!", vbinformation
cboNumber = CInt(Right(Application.Caller, 1))
SqlDes cboNumber
End Sub
You can make the string manipulation more complicated of course, to account for numbers with double digits and so on but I hope you get the idea - use the name of the combobox to provide the required value.
By the way, you don't need to Call your sub like that, just put the sub name then any parameters without brackets, as I've done in the example.
If you really want to write code with code it is possible in VBA but is an advanced topic.

MS Access 2016 VBA Code Reuse in Buttons

I have an Access database frontend that houses 16 different forms. All of them have three buttons in common namely Show All, Clear and Refresh, that perform that exact same function using their respective subforms. For instance, for viewing data from a table named tbl_Students the 'On Click' event of these buttons on the Students Form have the following code:
Option Explicit
'Show all records button
Private Sub cmdShowAll_Click()
Dim task As String
task = "SELECT * FROM tbl_Students"
Me.frm_Students_subform.Form.RecordSource = task
Me.frm_Students_subform.Form.Requery
End Sub
'Clear displayed records button
Private Sub cmdClear_Click()
Dim task As String
task = "SELECT * FROM tbl_Students WHERE (StudentID) is null"
Me.frm_Students_subform.Form.RecordSource = task
Me.frm_Students_subform.Form.Requery
End Sub
'Refresh records button
Private Sub cmdRefresh_Click()
Me.frm_Students_subform.Form.Requery
End Sub
Currently, I'm using the exact same code, but with different respective subform names, in all my 16 forms. Is there a better, more efficient way to do it, with code reuse? Thanks.
Consider creating one generalized subroutine in a standard module that all 16 forms call passing needed parameters. Specifically use CurrentProject and Controls to reference objects dynamically by string.
Module save in a named standard module (not behind any form)
Option Explicit
Public Sub ProcessForms(task As String, mainform As String, subform As String)
On Error GoTo ErrHandle
CurrentProject.AllForms(mainform).Controls(subform).Form.RecordSource = task
CurrentProject.AllForms(mainform).Controls(subform).Requery
Exit Sub
ErrHandle:
Msgbox Err.Number & " - " & Err.Description, vbCritical, "RUNTIME ERROR"
Exit Sub
End Sub
Example Form single line calls
Option Explicit
'Show all records button
Private Sub cmdShowAll_Click()
Call ProcessForms("SELECT * FROM tbl_Students", _
"frm_students", "frm_Students_subform")
End Sub
'Clear displayed records button
Private Sub cmdClear_Click()
Call ProcessForms("SELECT * FROM tbl_Students WHERE (StudentID) IS NULL", _
"frm_students", "frm_Students_subform")
End Sub
'Refresh records button
Private Sub cmdRefresh_Click()
' Re-assigns same recordsource in line with others
Call ProcessForms(Me.Controls("frm_Students_subform").Form.RecordSource, _
"frm_students", "frm_Students_subform")
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.

How do I use a variable from a different function in another function?

I have one method
Public CurrentFileNameNoExtension As String
Public Sub importexcelfile()
CurrentFileNameNoExtension ="Filename"
'do something
End Sub
I want to use CurrentFileNameNoExtension value in onEnter event of the dropdown list(cmvalues) event. That Value use in sql query. My code is
Private Sub cmvalues_Enter()
Dim qstng As String
qstng = CurrentFileNameNoExtension
Me.cmvalues.RowSourceType = "Table/Query"
Me.cmvalues.RowSource = "Select F1 from " & qstng & " WHERE F1 <> 'Control Model';"
End Sub
But qstng value is empty. it is not giving the value in the importexcelfile() function.
EDIT: As I've just noticed, thanks to #simoco, that this is indeed for a userform, there are actually a couple of things to pull this off. One is using globals, which is quite tricky, and another is to use a function to get the string you want.
Function CurrentFileNameNoExtension() As String
'Do some FSO or GetOpenFileName here.
CurrentFileNameNoExtension = "Filename"
End Sub
Private Sub cmvalues_Enter()
qstng = CurrentFileNameNoExtension
Me.cmvalues.RowSourceType = "Table/Query"
Me.cmvalues.RowSource = "Select F1 from " & strFileName & " WHERE F1 <> 'Control Model';"
End Sub
There is not much of an issue using the code you have, really. You just have to make sure that the first sub is called before the second one so that cmvalues_Enter has a valid string to process.
Place this function under Microsoft Access Class Objects Form control,Where cmvalues dropdown exists
Public CurrentFileNameNoExtension As String
Public Sub importexcelfile()
CurrentFileNameNoExtension ="Filename"
'do something
End Sub

Does it degrade performance to use subforms in MS Access?

I am considering the use of a tab control on a parent form for which I would like to have around 20 tabs. Each tab I am considering the use of one or two separate sub forms. Each sub form will have varied complexity in coded logic. By taking this approach will I severally reduce the performance of my application? I am currently using this in MS Access 2003. I will expect an average of 15 users at any given time on the various forms.
Thoughts?
Yes, performance will be degraded slightly for each subform. One or three isn't too bad but twenty is definitely going to cause you performance issues.
Once you have the subform working to your satisfaction either save the Record Source as a query and give it a name or save the query SQL string. Then either paste the query name or the query SQL string in the VBA code in the tab control change event.
Private Sub TabCtl_Change()
On Error GoTo TabCtl_Change_Error
Select Case Me.TabCtl.Value
Case Me.pagPartsConsumed.PageIndex
If Me.PartsConsumedsbf.Form.RecordSource <> "Equipment - Parts Consumed sbf" Then _
Me.PartsConsumedsbf.Form.RecordSource = "Equipment - Parts Consumed sbf"
....
Now just to ensure that I don't accidentally leave some subform recordsources filled in slowing down the app on startup I check to see if the file the code is running is an MDB (instead of an MDE. The function is below) then display a message telling me I have to remove the recordsource.
If Not tt_IsThisAnMDE Then
If Me.PartsConsumedsbf.Form.RecordSource <> "" Then _
MsgBox "Record source of Equipment - Parts Consumed sbf not empty"
...
End If
Public Function tt_IsThisAnMDE()
On Error GoTo tagError
Dim dbs As Database
Set dbs = CurrentDb
Dim strMDE As String
On Error Resume Next
strMDE = dbs.Properties("MDE")
If Err = 0 And strMDE = "T" Then
' This is an MDE database.
tt_IsThisAnMDE = True
Else
tt_IsThisAnMDE = False
End If
Exit Function
tagError:
Call LogError(Application.CurrentObjectName, "")
Exit Function
End Function
Also in the form unload event I clear the Recourd Source as well.
Private Sub Form_Unload(Cancel As Integer)
On Error GoTo Form_Unload_Error
Me.PartsConsumedsbf.Form.RecordSource = ""
....
BTW I almost always would put each subform on a seperate tab. Also that many tab entries gets visusally unwieldy. When I had a similar question my fellow Access MVPs suggested using a listbox along the left hand side to control which subform is viewable.
Also each combo box and list box will also slightly degrade the performance. So if you have those on a subform then consider similar logic.
In addition to adding recordsets at runtime, I would generally only use one or two tabs and a number of controls to load various subforms into a subform control.
The text for the On Click event of the control might be:
=WhichPage([Form],"lblLocations")
Where WhichPage is a function with the following lines, amongst others:
Function WhichPage(frm, Optional LabelName = "")
<..>
Select Case LabelName
Case "lblLocations"
frm("sfrmAll").SourceObject = "sfrmLocations"
<...>
If necessary, the link child and link master fields can be changed at runtime. The link master field is best set to the name of a control, rather than a field, to avoid errors.
Me.sfrmAll.LinkChildFields = "LocationKey"
Me.sfrmAll.LinkMasterFields = "txtLocationKey"
To expand on Remou's answer...here is a sub I wrote that dynamically loads a form into a subform control. You pass in the name of the form in the call and it will load it into the subform of the Main form. The arguments map to the arguments of Docmd.OpenForm method of Access. If the main form that is hosting the subform control is not open...it just does a regular open of the form. Otherwise it loads it into the subform control. If a where clause was passed in it is used to filter the subform.
Public Sub MyOpenForm(FormName As String, _
Optional View As AcFormView = acNormal, _
Optional FilterName As String = vbNullString, _
Optional WhereCondition As String = vbNullString, _
Optional DataMode As AcFormOpenDataMode, _
Optional WindowMode As AcWindowMode, _
Optional OpenArgs As String)
On Error GoTo PROC_ERR
Dim frm As Form
Dim strNewForm As String
Dim strCurrentForm As String
Dim strNewTable As String
Dim fDoNotFilter As Boolean
Dim strActionText As String
Dim strID As String
If Not IsLoaded("frmMain") Then
DoCmd.OpenForm FormName:=FormName, View:=View, FilterName:=FilterName, WhereCondition:=WhereCondition, DataMode:=DataMode, WindowMode:=WindowMode, OpenArgs:=OpenArgs
Else
strCurrentForm = Forms![frmMain]![sfrMyForm].SourceObject
If strCurrentForm <> FormName Then
Forms![frmMain]![sfrMyForm].SourceObject = vbNullString
Forms![frmMain]![sfrMyForm].SourceObject = FormName
End If
If WhereCondition <> vbNullString Then
Forms![frmMain]![sfrMyForm].Form.Filter = WhereCondition
Forms![frmMain]![sfrMyForm].Form.FilterOn = True
End If
End If
PROC_EXIT:
Exit Sub
PROC_ERR:
MsgBox Err.Description
Resume PROC_EXIT
End Sub