Does it degrade performance to use subforms in MS Access? - 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

Related

Run time error -2147417848 (80010108) when disabling FilterOn property of Report

I am working on creating filtering options of myReport contained in subform container.
I have used solution described in this question:
Filtering Report
from HansUp. Using described code filter is succesfully created, stored and applied with OnClick event of apllyFilterButton.
Then I create disableFilterButton and in OnClick event I put code suggested in before mentioned thread. While testing, I get this error:
"Run-time error '-2147417848 (80010108)':
Method 'FilterOn' of object '_Report_myReport' failed"
If I enable/disable filter using Access Ribbon (switch-filter button on/off) everything works fine with no error. So my mistake might be simple, even in syntax, but I cant find the source.
Code:
Private Sub disableFilterButton_Click()
Forms![myForm]![myReport].Report.FilterOn = False
Forms![myForm]![myReport].Report.Filter = ""
Forms![myForm]![myReport].Requery
End Sub
After crash, using "Debug" option, the first row of code above is highlighted. My research here and around net suggested adding the "blank filter" row, and also registry problem or an existing loop in code, which creates the crash. But since using Ribbon buttons everything works, I think its something simple that I am missing. I am still an beginner.
Code used in myFilterButton to create filter is not fully completed, but should be working:
Private Sub applyFilterButton_Click()
Dim ctl As Control
Dim varVyber As Variant
Dim filtrVolba As String
Dim filtrUplny As String
'Criteria creation for filter SQL
Set ctl = Forms![myForm]![filterOptionOne]
If ctl.ItemsSelected.Count <> 0 Then
For Each varVyber In ctl.ItemsSelected
filtrVolba = filtrVolba & ctl.Column(0, varVyber) & """ OR (sourceQuery.sourceColumn) = """
Next varVyber
filtrVolba = Left$(filtrVolba, Len(filtrVolba) - Len(" OR (qry_sourceQuery.sourceColumn) = "))
Forms![myForm]![myReport].Report.Filter = "(((sourceQuery.fieldBoundToMyReport)=[Forms]![myForm]![TextBoxBoundToMyReport]) AND ((sourceQuery.filterOptionOneSourceField) = """ & filtrVolba & "))"
Forms![myForm]![myReport].Report.FilterOn = True
Else
MsgBox "Not yet"
End If
End Sub
Help will be much appreciated! Thomas
Tomáš,
Since all of the code in your example is in the myForm class, let me explain some Access coding basics.
The [Forms]![Form]![Control] syntax is really intended for Queries, Form & Report events & properties, and Macros. While valid in VBA, there is no compile-time verification of this syntax! So, a VBA line like:
varValue = [Forms]![Blah]![BlahBlah] ' doesn't exist
will compile OK, but fail at runtime.
Controls in a form with a class module become public members of the form class. Take your form myForm: you can refer to its controls in code by using:
varValue = Form_myForm.filterOptionOne.Value
Form_myForm is the name of the form class. This line of code will throw errors if filterOptionOne gets renamed or removed.
In any VBA class, Me refers to the public interface of that class. "Public interface" simply means all of the public functions, subroutines and properties defined in the class. So, if you were using the line of code above within myForm, the best style would look like this:
varValue = Me.filterOptionOne.Value
Me in this context constrains filterOptionOne to be actually defined in the class, providing further compile-time checks.
So, in light of these three points, I would refactor your code like so:
Private Sub disableFilterButton_Click()
Me.myReport.Report.FilterOn = False
Me.myReport.Report.Filter = ""
Me.myReport.Requery
End Sub
Likewise:
Private Sub applyFilterButton_Click()
Dim ctl As Control
Dim varVyber As Variant
Dim filtrVolba As String
Dim filtrUplny As String
'Criteria creation for filter SQL
Set ctl = Me.filterOptionOne
If ctl.ItemsSelected.Count <> 0 Then
For Each varVyber In ctl.ItemsSelected
filtrVolba = filtrVolba & ctl.Column(0, varVyber) & """ OR (sourceQuery.sourceColumn) = """
Next varVyber
filtrVolba = Left$(filtrVolba, Len(filtrVolba) - Len(" OR (qry_sourceQuery.sourceColumn) = "))
Me.myReport.Report.Filter = "(((sourceQuery.fieldBoundToMyReport)=" & Me.TextBoxBoundToMyReport.Value & ") AND ((sourceQuery.filterOptionOneSourceField) = """ & filtrVolba & "))"
Me.myReport.Report.FilterOn = True
Else
MsgBox "Not yet"
End If
End Sub
This may not solve all your problems, but this should put you on a much sounder footing for understanding how to code Access forms.

Why is is my MS Access control not accepting my "on not in list" event code

I've been using the Access "On Not In List" event for a long time. It allows you to limit the items in your combo box to a particular list, but allows the user to add an item to combo's record source on the fly if they want to enter something that isn't already there. You set the "Limit To List" property of the combo box to Yes, but then you put some code behind the "On Not In List" event. Intermittently, I get a situation in Access 2016 where this doesn't seem to work. I get the standard "The item is not in the list." error when trying to enter a new item, without my code-behind logic being seen and called. What's up?
After banging my head against the wall for a long time, I believe this is bug in Access 2016 and I think I stumbled on a fix. I set the form's RecordSetType property to Snapshot, closed and saved form, reopened the form in design view, set the RecordSetType property back to Dynaset. This seems to have cleared up the problem. I have no idea why.
But since I'm here. . . some additional details: In the code-behind for each control I use code like this:
Private Sub VendorID_NotInList(NewData As String, Response As Integer)
Response = RU_NotInList("Lookup Vendor", "Description", NewData, gc_strMsgCap)
End Sub
This type of subroutine gets created automatically behind the "On Not In List" event, when you click on the 'code builder' option. I have mine call a utility function that I wrote a long time ago. ("RU" refers a code library.)
The function returns an intrinsic Access integer constant that gets passed straight back to Access to handle.
The inside of that routine looks like this:
Function RU_NotInList(TableName As String, FieldName As String, newdata As String, Optional pstrTile As String) As Integer
Dim rs As DAO.Recordset, db As DAO.Database, n1 As Integer
RU_NotInList = DATA_ERRCONTINUE
On Error GoTo RU_NotInList_Error
If Len(Trim(newdata)) = 0 Then Exit Function
n1 = MsgBox(newdata & " is not in the list. Do you wish to add it?", MB_ICONQUESTION + MB_YESNO, pstrTile)
If n1 = IDNO Then Exit Function
Dim strSQL As String
strSQL = "INSERT INTO [" & TableName & "] ([" & FieldName & "]) VALUES (""" & newdata & """)"
WarningsHour True 'Turns hourglass cursor on, warning messages off.
DoCmd.RunSQL strSQL
WarningsHour False 'Undoes the above.
RU_NotInList = DATA_ERRADDED
Exit Function
RU_NotInList_Error:
RUError "RU_NotInList", Err 'generic error-handling routine in the RU library
Exit Function
End Function
All the all-caps items in the code above are Access intrinsic constants.

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.

Get Form Recordsource without opening the form

Does MS Access allow to get the recordsource value of the form without opening the form itself? I'm trying to optimize my code as of now, what I did is I just hide the form then get the Recordsource form query but it takes time to load since some of the forms trigger a code upon onload.
I'm late to the game here - I sometimes post answers months or years after the original question was posted, as I post my own solutions when a quick search of the 'Stack finds questions relevant to my own problem of the day, but no answers that I can actually use.
[UPDATE, 06 June 2016]
The 'NameMap' property is not available in document objects from Access 2010 onwards. However, 'Stacker Thunderframe has pointed out that this is now available in the 'MsysNameMap' table.
I have amended the code, and this works in Access 2010 and 2013.
[/UPDATE]
Most of a form's properties are only available when the form is open, but some are available in the form's entry in the DAO Documents collection.
The DAO 'document' is a horrible object: it won't persist in memory and you have to refer to it explicitly every time you use it:
FormName = "MyForm"
For i = 0 To Application.CodeDb.Containers("Forms").Documents(FormName).Properties.Count - 1
Debug.Print i & vbTab & Application.CodeDb.Containers("Forms").Documents(FormName).Properties(i).Name & vbTab & vbTab & Application.CodeDb.Containers("Forms").Documents(FormName).Properties(i).Value
Next
Run that snippet for your form, and you'll see a 'NameMap' property that contains a list of the form's controls, and some of the form's properties.
...In a truly horrible format which needs a binary parser. You might want to stop reading and take an aspirin, right now, before continuing.
Health Warnings:
The NameMap Property is undocumented. It is therefore unsupported and there is no guarantee that this solution will work in future versions of Microsoft Access.
The solution in my code below will stop working if the NameMap's two-byte binary label for a Record Source ever changes, or if it's locale-specific.
This is a horrible hack: I accept no liability for any effects on your sanity.
OK, here's the code:
A VBA function to return the Record Source from a closed MS-Access form:
Private Function FormRecordSource_FromNameMap(FormName As String) As String
' Reads the Record Source from the NameMap Property of the Document object for the form.
' WARNING: there is a potential error here: if the form's RecordSource property is blank
' and it has one or more list controls with a .RecordSource property populating
' the list, this function will return the first list control's Record Source.
' This won't work if you're using non-ASCII characters (Char > 255) in your form name.
Dim i As Integer
Dim j As Integer
Dim k As Integer
Dim arrByte() As Byte
Dim strOut As String
If Application.Version &LT; 12 Then
arrByte = Application.CodeDb.Containers("Forms").Documents(FormName).Properties("NameMap").Value
For i = 1 To UBound(arrByte) - 2 Step 2
' 2-byte marker for a querydef in the NameMap:
If (arrByte(i) = 228 And arrByte(i + 1) = 64) Then
j = i + 2
Do While arrByte(j) = 0 And arrByte(j + 1) = 0 And j &LT; UBound(arrByte)
' loop through the null chars between the marker and the start of the string
j = j + 2
Loop
strOut = ""
Do Until (arrByte(j) = 0 And arrByte(j + 1) = 0) Or j &GT;= UBound(arrByte) - 2
If arrByte(j) = 0 Then j = j + 1
' loop until we reach the null char which terminates this string
' appending the Bchars (not unicode Wchars!) of the table or query
strOut = strOut & Chr(arrByte(j))
j = j + 2
Loop
Exit For ' we only want the first datasource
End If
Next i
Else
arrByte = Nz(DLookup("[NameMap]", "[MSYSNameMap]", "[Name] = '" & FormName & "'"), vbNullChar)
If UBound(arrByte) &LT; 4 Then Exit Function
strOut = ""
For j = 60 To UBound(arrByte) - 2 Step 2
If arrByte(j) = 0 And arrByte(j + 1) = 0 Then Exit For
strOut = strOut & Chr(arrByte(j))
Next j
End If
frmRecordSource_FromNameMap = strOut
Erase arrByte
End Function
If you use the RecordSource in (say) OpenRecordset or a DCOUNT function, I would advise you to encapsulate it in square brackets: you might get the name of a hidden query object saved from a 'SELECT' statement in the RecordSource, and that name will contain '~' tilde characters which need special handling.
And now, something extra that you didn't ask for, but other people will be looking for if they Googled their way here for 'MS Access RecordSource for a closed form':
Getting an MS-Access form's RecordSource, whether it's open or not
Most times, your form will be open. Problem is, you don't know that... And if it's a subform, it might not be visible in the Forms() collection. Worse, a form that's hosted as a subform might exist as multiple instances in several open forms.
Good luck with that, if you're looking to extract dynamic properties... Like filters, or the Record Source if it's set 'on the fly' by VBA.
Public Function GetForm(FormName As String, Optional ParentName As String = "") As Form
' Returns a form object, if a form with a name like FormName is open
' FormName can include wildcards.
' Returns Nothing if no matching form is open.
' Enumerates subforms in open forms, and returns the subform .form object if
' it has a matching name. Note that a form may be open as multiple instances
' if more than one subform hosts it; the function returns the first matching
' instance. Specify the named parent form (or the subform control's name) if
' you need to avoid an error arising from multiple instances of the form.
Dim objForm As Access.Form
If ParentName = "" Then
For Each objForm In Forms
If objForm.Name Like FormName Then
Set GetForm = objForm
Exit Function
End If
Next
End If
If GetForm Is Nothing Then
For Each objForm In Forms
Set GetForm = SearchSubForms(objForm, FormName, ParentName)
If Not GetForm Is Nothing Then
Exit For
End If
Next
End If
End Function
Private Function SearchSubForms(objForm As Access.Form, SubFormName As String, Optional ParentName As String = "") As Form
' Returns a Form object with a name like SubFormName, if the named object SubFormName is subform
' of an open form , or can be recursively enumerated as the subform of an open subform.
' This function returns the first matching Form: note that a form can be instantiated in multiple
' instances if it is used by more than one subform control.
Dim objCtrl As Control
For Each objCtrl In objForm
If TypeName(objCtrl) = "SubForm" Then
If objCtrl.Form.Name Like SubFormName Then
If ParentName = "" Or objForm.Name Like ParentName Or objCtrl.Name Like ParentName Then
Set SearchSubForms = objCtrl.Form
Exit For
End If
Else
Set SearchSubForms = SearchSubForms(objCtrl.Form, SubFormName, ParentName)
If Not SearchSubForms Is Nothing Then
Exit For
End If
End If
End If
Next objCtrl
End Function
Public Function FormRecordSource(FormName As String, Optional ParentName As String = "") As String
' Returns the Recordsource for a form, even if it isn't open in the Forms() collection
' This will look for open forms first. If you're looking for a subform, you may need a
' parent name for the form which hosts the subform: your named form might be open as a
' subform instance in more than one parent form.
' WARNING: there is a potential error here: if the form isn't open, and it has a blank
' RecordSource property, and it has one or more controls with a .RecordSource
' property populating a list, a list control's RecordSource could be returned
Dim objForm As Form
If FormName = "" Then
Exit Function
End If
Set objForm = GetForm(FormName, ParentName)
If objForm Is Nothing Then
FormRecordSource = FormRecordSource_FromNameMap(FormName)
Else
FormRecordSource = objForm.RecordSource
Set objForm = Nothing
End If
End Function
Share and enjoy: and please accept my apologies for any unwanted line breaks in the code sample.
One option would be to save the Record Source of the form as a Query. Say you have a form named [AgentForm] whose Record Source is
SELECT ID, AgentName FROM Agents
In your development .accdb copy of the database, open the form in Design View and open the Record Source in the Query Builder. Click the "Save As" button ...
and save the query as "AgentForm_RecordSource". Now the Record Source property of the form is just a reference to the saved query, and the query itself can be accessed directly through a QueryDef object. So, you could retrieve the SQL statement for the form's Record Source with
Dim cdb As DAO.Database, qdf As DAO.QueryDef, sql As String
Set cdb = CurrentDb
Set qdf = cdb.QueryDefs("AgentForm_RecordSource")
sql = qdf.SQL
or you could go ahead and open a Recordset with
Dim cdb As DAO.Database, qdf As DAO.QueryDef, rst As DAO.Recordset
Set cdb = CurrentDb
Set qdf = cdb.QueryDefs("AgentForm_RecordSource")
Set rst = qdf.OpenRecordset
If the form's Record Source is a SELECT statement rather than the name of a table or saved query, you can check the QueryDefs collection for the hidden QueryDef which Access created for that Record Source statement.
If it exists, you can check its .SQL property.
strFormName = "Form15"
? CurrentDb.QueryDefs("~sq_f" & strFormName).SQL
SELECT DISTINCTROW *
FROM [DB Audits];
You can trap error #3265, "Item not found in this collection", which will be thrown if that QueryDef does not exist.
Since you can't open your form in design view and opening your form regularly is causing performance issues, there are but a few more workarounds:
Depending on how you want to check for the closed form's recordsource, you can set a global variable in the following way, in a separate module:
Public glb_getrecordsource As String
Afterwards, depending on how you call the code, you can do the following:
Private Sub Command1_Click()
glb_getrecordsource = "Yes"
DoCmd.OpenForm "Form1"
'... Do something
End Sub
Then, as the final step, put the following at the beginning of your form's OnLoad event:
Private Sub Form_Load()
If glb_getrecordsource = "Yes" Then
glb_getrecordsource = Me.Form.RecordSource
DoCmd.Close acForm, "Form1", acSaveYes
Exit Sub
End If
'... Usual OnLoad events
End Sub
This will at least solve the performance issues, since you will not trigger any of the time consuming events, in the form's load event.
Another workaround:
You can export your form to a .txt file and then search the text file for the recordsource. The following code will export your forms to .txt files in a specified folder:
Dim db As Database
Dim d As Document
Dim c As Container
Dim sExportLocation As String
Set db = CurrentDb()
sExportLocation = "C:\AD\" 'Do not forget the closing back slash! ie: C:\Temp\
Set c = db.Containers("Forms")
For Each d In c.Documents
Application.SaveAsText acForm, d.Name, sExportLocation & "Form_" & d.Name & ".txt"
Next d
Code partly borrowed from this forum. Afterwards, you only have to open the file and search for the recordsource. If the recordsource is empty it will not be exported, so keep that in mind. Also, I doubt this will improve perfomance, but who knows!