I need to create a dynamic (on the fly) report, because the number and names of fields will change frequently. I have it all worked out except I cannot figure out how to create a group header based upon a field name.
Here was my first try, which returns "The number you used to refer to the form or report section is invalid."
Dim rpt as Report
dim txtNew as Access.Textbox
set rpt = CreateReport
With rpt
.Width = 8500
.RecordSource = "IS_Subscales_GB_Final"
End With
Set txtNew = CreateReportControl(rpt.Name, acTextBox, acGroupLevel1Header, , "FriendlyName01", 0, 0)
txtNew.FontBold = True
txtNew.FontSize = 16
txtNew.SizeToFit
DoCmd.OpenReport rpt.Name, acViewPreview
So I next tried to use CreateGroupLevel, but this returns the error "You can't call this function when the Group, Sort, and Total Pane is open."
Dim rpt as Report
dim txtnew as Access.Textbox
Dim vargrplvl As Variant
set rpt = CreateReport
With rpt
.Width = 8500
.RecordSource = "IS_Subscales_GB_Final"
End With
vargrplvl = CreateGroupLevel(rpt.Name, "FriendlyName", True, False)
rpt.Section(acGroupLevel1Header).Height = 400
Set txtNew = CreateReportControl(rpt.Name, acTextBox, acGroupLevel1Header, , "FriendlyName01", 0, 0)
txtNew.FontBold = True
txtNew.FontSize = 16
txtNew.SizeToFit
DoCmd.OpenReport rpt.Name, acViewPreview
Any guidance would be appreciated.
I made multiple mistakes but also, Access has a quirk where it unexpectedly throws the 2451 error ("You can't call this function when the Group, Sort, and Total Pane is open.")---this happens only if the last time you were in Design view for a report, you had the Group, Sort, and Total Pane open. Access saves that setting so you either need to go back into a report in Design view and turn the panel off, or use an error handler to work around it. So here is a learning lesson coming from the whole ordeal:
Dim rpt as Report
Dim vargrplevel As Variant 'holds grouping level of report
Dim txtNew as Access.Textbox ' textbox control
Dim lblNew As Access.Label ' label control
set rpt = CreateReport 'creates a report object
with rpt
.width = 8500 'sets width of report
.RecordSource = "IS_Subscales_GB_Final" ' your table or query
end with
'start setting your controls on the report using CreateReportControl, i.e.:
Set lblNew = CreateReportControl(rpt.Name, acLabel, acDetail, , , 2700, 0, 270, 315)
lblNew.Caption = "O"
lblNew.FontSize = 12
lblNew.FontBold = True
'To add a group level to the report, it MUST be in acViewDesign!
DoCmd.OpenReport rpt.Name, acViewDesign
On Error GoTo ErrorHandler
'if the Group, Sort, and Total pane was left "on" then this next line will throw error 2451
vargrplevel = CreateGroupLevel(rpt.Name, "FriendlyName01", True, False) 'Creates a group header, named "FriendlyName01"
rpt.Section(acGroupLevel1Header).Height = 400 'optional; sets the header height
Set txtNew = CreateReportControl(rpt.Name, acTextBox, acGroupLevel1Header, , "FriendlyName01", 0, 0) 'This actually inserts the field into the header as textbox control
txtNew.FontBold = True
txtNew.FontSize = 16
DoCmd.Save acReport, rpt.Name
Exit Sub
ErrorHandler:
If Err.Number = 2154 Then
RunCommand acCmdSortingAndGrouping 'turns off the Sorting and Grouping pane
Resume
Else
Debug.Print "Error in creating report header (EH01- " & Err.Number & ")"
Exit Sub
End If
Cheers.
Related
In my office of 65 people, I want to create a "portal" for all the employees out of a single .accdb file. It will allow each employee to navigate to a new "screen" from a dropdown menu.
Should I use a single form with plug-and-play subform controls in order to centralize the VBA code, or should I just use different forms?
I'm thinking it would be nice to have one form with plug-and-play subform controls. When the employee selects a new "screen", the VBA just sets the SourceObject property of each subform control and then re-arranges the subforms based on the layout of the selected "screen".
For instance, we currently use a couple of Access database forms to enter and review errors that we find in our workflow system. So in this scenario, to review the errors I would just say
SubForm1.SourceObject = "Form.ErrorCriteria"
SubForm2.SourceObject = "Form.ErrorResults"
And then I would just move them into place (these values would be pulled dynamically based upon the "screen" selected):
SubForm1.Move WindowWidth * 0.05, WindowHeight * 0.05, WindowWidth * 0.9, WindowHeight * 0.2
SubForm2.Move WindowWidth * 0.05, WindowHeight * 0.25, WindowWidth * 0.9, WindowHeight * 0.65
So this creates a small header section (SubForm1) on the form where I can select the criteria for the errors I want to see (data range, which team committed the error, etc) and then I can view the errors in the much larger section below the header (SubForm2) that holds the datasheet with the results.
I can propogate events up to the main form from the ErrorCriteria and ErrorResults forms that are now bound to the subform controls. That will help me to use the basic MVC design pattern for VBA described here. I can treat the main form as the view, even though parts of that view are buried in subform controls. The controller only has to know about that one view.
My problem comes when the user selects a new "screen" from the dropdown menu. I think it would be nice to just re-purpose the subform controls, like so:
SubForm1.SourceObject = "Form.WarehouseCriteria"
SubForm2.SourceObject = "Form.InventoryResults"
And then just move/resize those subforms to the appropriate layout for the "Inventory" screen.
This approach seems to make the user interface design cleaner in my mind because you basically only ever have to deal with one main form that acts as a template and then you plug in the values (the SourceObject properties) into that template.
But each time we change the "screen", we have a totally different "Model" behind the scenes and a new "View" too according to the MVC design pattern. I wonder if that would clutter up the MVC VBA code behind the scenes, or if the VBA code itself could be modularized too (possibly using Interfaces) to make it just as adaptable as the user interface.
What is the cleanest way to do this from both a User Interface perspective, and from a VBA perspective. Use one main form as template where other forms could be swapped in and out as subforms, or just close the current form and open a new form when the user selects a new "screen" from the dropdown menu.
Below is a brief description of one way to 'repurpose' or reformat a form for several uses. Re your question of changing the VBA code, a simple solution would be to check a label value or some value you set in the control, then call the appropriate VBA subroutine.
We had over 100 reports available, each with their own selection criteria/options and we did not want to create a unique filter form for every report. The solution was to identify the selection options available by report, identify the logical order of those options, then create a table that would present the options to the user.
First, we created the table: ctlReportOptions (PK = ID, ReportName, OptionOrder)
Fields: ID (Int), ReportName (text), OptionOrder (Int), ControlName (text), ControlTop (Int), ControlLeft (Int), SkipLabel (Y/N), ControlRecordsourc(text)
Note 1: ID is not an AutoNumber.
Next we populated with records that would define the view the user would see.
Note 2: Using an ID of zero, we created records for EVERY field on the report so we could always redraw for the developers.
Then we created the form and placed controls for every possible filter.
We set the 'Default Value' property to be used as our default.
Some of the controls:
ComboBox to select the report name. Add code for Change event as follows:
Private Sub cboChooseReport_Change()
Dim strSQL As String
Dim rs As ADODB.recordSet
Dim i As Integer
Dim iTop As Integer
Dim iLeft As Integer
Dim iLblTop As Integer
Dim iLblLeft As Integer
Dim iLblWidth As Integer
Dim iTab As Integer
Dim strLabel As String
On Error GoTo Error_Trap
' Select only optional controls (ID <> 0); skip cotrols always present.
strSQL = "SELECT ctlRptOpt.ControlName, 'lbl' & Mid([ControlName],4,99) AS LabelName, SkipLabel " & _
"From ctlRptOpt WHERE (((ctlRptOpt.ID)<>0)) " & _
"GROUP BY ctlRptOpt.ControlName, 'lbl' & Mid([ControlName],4,99), SkipLabel;"
Set rs = New ADODB.recordSet
rs.Open strSQL, CurrentProject.Connection, adOpenDynamic
Do While Not rs.EOF
Me(rs!ControlName).Visible = False ' Hide control
If rs!skiplabel = False Then ' Hide Label if necessary
Me(rs!LabelName).Visible = False
End If
rs.MoveNext
Loop
rs.Close
iTop = 0
iTab = 0
' Get list of controls used by this report; order by desired sequence.
strSQL = "select * from ctlRptOpt " & _
"where [ID] = " & Me.cboChooseReport.Column(3) & _
" order by OptionOrder;"
Set rs = New ADODB.recordSet
rs.Open strSQL, CurrentProject.Connection, adOpenDynamic
If rs.EOF Then ' No options needed
Me.cmdShowQuery.Visible = True
Me.lblReportCriteria.Visible = False
Me.cmdShowQuery.left = 2000
Me.cmdShowQuery.top = 1500
Me.cmdShowQuery.TabIndex = 1
Me.cmdReset.Visible = False
rs.Close
Set rs = Nothing
GoTo Proc_Exit ' Exit
End If
' Setup the display of controls.
Me.lblReportCriteria.Visible = True
Do While Not rs.EOF
If rs!skiplabel = False Then
strLabel = "lbl" & Mid(rs!ControlName, 4)
iLblWidth = Me.Controls(strLabel).Width
Me(strLabel).top = rs!ControlTop
Me(strLabel).left = rs!ControlLeft - (Me(strLabel).Width + 50)
Me(strLabel).Visible = True
End If
iTab = iTab + 1 ' Set new Tab Order for the controls
Me(rs!ControlName).top = rs!ControlTop
Me(rs!ControlName).left = rs!ControlLeft
Me(rs!ControlName).Visible = True
If left(rs!ControlName, 3) <> "lbl" Then
Me(rs!ControlName).TabIndex = iTab
End If
If Me(rs!ControlName).top >= iTop Then
iTop = rs!ControlTop + Me(rs!ControlName).Height ' Save last one
End If
' If not a label and not a 'cmd', it's a filter! Set a default.
If left(rs!ControlName, 3) <> "lbl" And left(rs!ControlName, 3) <> "cmd" Then
If Me(rs!ControlName).DefaultValue = "=""*""" Then
' Me(rs!ControlName) = "*"
ElseIf left(Me(rs!ControlName).DefaultValue, 2) = "=#" And right(Me(rs!ControlName).DefaultValue, 1) = "#" Then
i = Len(Me(rs!ControlName).DefaultValue)
' Me(rs!ControlName) = Mid(Me(rs!ControlName).DefaultValue, 3, i - 3)
ElseIf Me(rs!ControlName).DefaultValue = "True" Then
' Me(rs!ControlName) = True
ElseIf Me(rs!ControlName).DefaultValue = "False" Then
' Me(rs!ControlName) = False
End If
Else
If Me(rs!ControlName).top + Me(rs!ControlName).Height >= iTop Then
iTop = rs!ControlTop + Me(rs!ControlName).Height ' Save last one
End If
End If
rs.MoveNext
Loop
rs.Close
Set rs = Nothing
If Me.cboChooseReport.Column(1) <> "rptInventoryByDate" Then ' It's special
Me.cmdShowQuery.Visible = True
Me.cmdShowQuery.left = 2000
Me.cmdShowQuery.top = iTop + 300
iTab = iTab + 1
Me.cmdShowQuery.TabIndex = iTab
Else
Me.cmdShowQuery.Visible = False
End If
Me.cmdReset.Visible = True
Me.cmdReset.left = 5000
Me.cmdReset.top = iTop + 300
Me.cmdReset.TabIndex = iTab + 1
Proc_Exit:
Exit Sub
Error_Trap:
Err.Source = "Form_frmReportChooser: cboChooseReport_Change at Line: " & Erl
DocAndShowError ' Save error to database for analysis, then display to user.
Resume Proc_Exit ' Exit code.
Resume Next ' All resumption if debugging.
Resume
End Sub
lblReportCriteria: We displayed the final set of filters so when users complained of nothing showing on the report, we asked them to send us a screen print. We also passed this text to the report and it was printed as a footer on the last page.
cmdReset: Reset all controls back to their default values.
cmdShowQuery: Executes the running of the report
Private Sub cmdShowQuery_Click()
Dim qdfDelReport101 As ADODB.Command
Dim qdfAppReport101 As ADODB.Command
Dim qdfDelReport102 As ADODB.Command
Dim qdfAppReport102 As ADODB.Command
Dim qryBase As ADODB.Command
Dim strQueryName As String
Dim strAny_Open_Reports As String
Dim strOpen_Report As String
Dim qdfVendorsInfo As ADODB.Command
Dim rsVendorName As ADODB.recordSet
Dim strVendorName As String
Dim rsrpqFormVendorsInfo As ADODB.recordSet
On Error GoTo Error_Trap
If Not IsNull(Me.cboChooseReport.value) And Me.cboChooseReport.value <> " " Then
strAny_Open_Reports = Any_Open_Reports()
If Len(strAny_Open_Reports) = 0 Then
If Me.cboChooseReport.value = "rptAAA" Then
BuildReportCriteria '
If Me.chkBankBal = True Then
DoCmd.OpenReport "rptAAA_Opt1", acViewPreview
Else
DoCmd.OpenReport "rptAAA_Opt2", acViewPreview
End If
ElseIf Me.cboChooseReport.value = "rptBBB" Then
If IsNull(Me.txtFromDate) Or Not IsDate(Me.txtFromDate) Then
MsgBox "You must enter a valid From Date", vbOKOnly, "Invalid Date"
Exit Sub
End If
If IsNull(Me.txtToDate) Or Not IsDate(Me.txtToDate) Then
MsgBox "You must enter a valid To Date", vbOKOnly, "Invalid Date"
Exit Sub
End If
Me.txtStartDate = Me.txtFromDate
Me.txtEndDate = Me.txtToDate
DoCmd.OpenReport Me.cboChooseReport.value, acViewPreview
ElseIf Me.cboChooseReport.value = "rptCCC" Then
If Me.txtVendorName = "*" Then
gvstr_VendorName = "*"
Else
Set rsVendorName = New ADODB.recordSet
rsVendorName.Open "selVendorName", gv_DBS_Local, adOpenDynamic
Set qdfVendorsInfo = New ADODB.Command
qdfVendorsInfo.ActiveConnection = gv_DBS_SQLServer
qdfVendorsInfo.CommandText = ("qryVendorsInfo")
qdfVendorsInfo.CommandType = adCmdStoredProc
strVendorName = rsVendorName("VendorName")
gvstr_VendorName = strVendorName
End If
DoCmd.OpenReport "rptFormVendorReport", acViewPreview
Else
BuildReportCriteria
If Me.cboChooseReport.value = "rptXXXXXX" Then
ElseIf Me.cboChooseReport.value = "rptyyyy" Then
On Error Resume Next ' All resumption if debugging.
DoCmd.DeleteObject acTable, "temp_xxxx"
On Error GoTo Error_Trap
Set qryBase = New ADODB.Command
qryBase.ActiveConnection = gv_DBS_Local
qryBase.CommandText = ("mtseldata...")
qryBase.CommandType = adCmdStoredProc
qryBase.Execute
End If
DoCmd.Hourglass False
DoCmd.OpenReport Me.cboChooseReport.value, acViewPreview
End If
Else
MsgBox "You cannot open this form/report because you already have a form/report(s) open: " & _
vbCrLf & strAny_Open_Reports & _
vbCrLf & "Please close the open form/report(s) before continuing."
strOpen_Report = Open_Report
DoCmd.SelectObject acReport, strOpen_Report
DoCmd.ShowToolbar "tbForPost"
End If
Else
MsgBox "Please Choose Report", vbExclamation, "Choose Report"
End If
Exit Sub
Error_Trap:
Err.Source = "Form_frmReportChooser: cmdShowQuery_Click - Report: " & Nz(Me.cboChooseReport.value) & " at Line: " & Erl
If Err.Number = 2501 Then ' MsgBox "You chose not to open this report.", vbOKOnly, "Report cancelled"
Exit Sub
ElseIf Err.Number = 0 Or Err.Number = 7874 Then
Resume Next ' All resumption if debugging.
ElseIf Err.Number = 3146 Then ' ODBC -- call failed -- can have multiple errors
Dim errLoop As Error
Dim strError As String
Dim Errs1 As Errors
' Enumerate Errors collection and display properties of each Error object.
i = 1
Set Errs1 = gv_DBS_SQLServer.Errors
Err.Description = Err.Description & "; Err.Count = " & gv_DBS_SQLServer.Errors.Count & "; "
For Each errLoop In Errs1
With errLoop
Err.Description = Err.Description & "Error #" & i & ":" & " ADO Error#" & .Number & _
" Description= " & .Description
i = i + 1
End With
Next
End If
DocAndShowError ' Save error to database for analysis, then display to user.
Exit Sub
Resume Next ' All resumption if debugging.
Resume
End Sub
Function to build a string showing all of the selection criteria:
Function BuildReportCriteria()
Dim frmMe As Form
Dim ctlEach As Control
Dim strCriteria As String
Dim prp As Property
Dim strSQL As String
Dim rs As ADODB.recordSet
On Error GoTo Error_Trap
strSQL = "select * from ctlRptOpt " & _
"where ID = " & Me.cboChooseReport.Column(3) & _
" order by OptionOrder;"
Set rs = New ADODB.recordSet
rs.Open strSQL, CurrentProject.Connection, adOpenDynamic
If rs.EOF Then
strCriteria = " Report Criteria: None"
Else
strCriteria = " Report Criteria: "
End If
Do While Not rs.EOF
Set ctlEach = Me.Controls(rs!ControlName)
If ctlEach.ControlType = acTextBox Or ctlEach.ControlType = acComboBox Then
If ctlEach.value <> "*" And ctlEach.Name <> "cboChooseReport" And ctlEach.Name <> "cboLocCountry" Then
strCriteria = strCriteria & ctlEach.Tag & " = " & ctlEach.value & " , "
End If
End If
rs.MoveNext
Loop
rs.Close
Set rs = Nothing
If Me.chkOblBal = -1 Then
strCriteria = strCriteria & "Non-zero balances only = Yes"
Else
'return string with all choosen criteria and remove last " , " from the end of string
strCriteria = left$(strCriteria, Len(strCriteria) - 3)
End If
fvstr_ReportCriteria = strCriteria
Set ctlEach = Nothing
Exit Function
Error_Trap:
If Err.Number = 2447 Then
Resume Next ' All resumption if debugging.
End If
Err.Source = "Form_frmReportChooser: BuildReportCriteria at Line: " & Erl
DocAndShowError ' Save error to database for analysis, then display to user.
Exit Function
Resume Next ' All resumption if debugging.
End Function
Finally, each report had it's own query that would filter based on the values in the controls on this form.
Hope this helps. If you are curious about any of the weird things you see, let me know. (i.e. we always used line numbers in the code (I deleted before posting) that allowed us to identify exact line where code fails)
I'm trying to make a button in an Access form which will run a couple queries and then take the resultant recordsets and put them into reports. I have gotten to the point where the button will call the module, it creates the proper recordsets, and then it creates the reports. However, the reports are blank, they don't have the data in them from the recordsets. I think my problem is that I haven't properly assigned the data source of the reports but I can't figure out how to if that is the issue.
Private Function showReport(sectionHeading As String, SQL As String, recordset As ADODB.Recordset)
Dim textBox As Access.textBox ' textbox control
Dim label As Access.label ' label control
Dim report As report ' hold report object
Dim controlTop As Long ' holds top value of control position
Dim controlLeft As Long ' holds left value of control position
Dim title As String 'holds title of report
Dim i As Integer 'iterator
i = 0
title = sectionHeading
controlLeft = 0
controlTop = 0
Set report = CreateReport
report.Width = 8500
report.Caption = title
Set label = CreateReportControl(report.Name, acLabel, _
acPageHeader, , "Title", 0, 0)
label.FontBold = True
label.FontSize = 12
label.SizeToFit
For Each fld In recordset.fields
Set textBox = CreateReportControl(report.Name, acTextBox, _
acDetail, , fld.Name, controlLeft + 1500, controlTop)
textBox.SizeToFit
Set label = CreateReportControl(report.Name, acLabel, acDetail, _
textBox.Name, fld.Name, controlLeft, controlTop, 1400, textBox.Height)
label.SizeToFit
controlTop = controlTop + textBox.Height + 25
i = i + 1
Next
Set label = CreateReportControl(report.Name, acLabel, _
acPageFooter, , Now(), 0, 0)
Set textBox = CreateReportControl(report.Name, acTextBox, _
acPageFooter, , "='Page ' & [Page] & ' of ' & [Pages]", report.Width - 1000, 0)
textBox.SizeToFit
report.RecordSource = SQL
DoCmd.OpenReport report.Name, acViewPreview
recordset.Close
Set recordset = Nothing
Set report = Nothing
End Function
I'd say you are missing a line like
report.RecordSource = "the query that fills <recordset>"
But I'm pretty sure that there must be a better way to achieve your goal than creating a new report from scratch.
And it's error-prone to name your variables like their data types (label, report, ...)
Edit
Are you sure your SQL is valid? Alternatively, you can try saving it as query and pass the query name.
I did a little test, it should work principally. r_tbProduct has an empty recordsource when I run this:
Dim rep As Report
DoCmd.OpenReport "r_tbProduct", acViewDesign
Set rep = Reports!r_tbProduct
rep.RecordSource = "SELECT * FROM tbProduct WHERE ID >= 6"
DoCmd.OpenReport "r_tbProduct", acViewPreview
It shows the correct data.
Again, I strongly suggest you rename your variables.
Dim report As report
is just asking for problems.
How can you create controls at run time with VB code in Microsoft Access? after some digging I found that this is possible with the CreateControl function. The problem is every random forum I find online has some code similar to this:
Private Sub Button_Click()
Call AddLabel
End Sub
Function AddLabel()
Set ctl = CreateControl("MyForm", acLabel, acDetail, "", "", 0, 0, 100, 100)
With ctl
.name = "myTextBox"
.Caption = "Hello World!"
.Height = 50
.Width = 100
.FontSize = 11
.Visible = True
End With
End Function
but this code appears to not create a visible label.
In my case I'm just trying to learn how to get this to work. So I created a blank form with a button that when clicked will create a label that says "Hello world!". What I'm expecting to happen is a textbox will display in the top left of the form when the button is clicked. I'm curious if anyone could help show me a simple example of some code that actually works.
Before anyone says I can create a label and hide it then change its visibilty property, I know. But I'd like to know how to create controls dynamically and getting this simple example to actually work will greatly help my understanding.
The documentation you need is here (these are specifically for Access VBA):
Application.CreateControl Method (Office 2007)
Application.CreateControl Method (Office 2003)
According to the documentatin, there are some big limitations to this feature:
Limited to 754 controls over the lifetime of the form (this is not reset by deleting them, so you are likely to run into this limit quickly)
Must be done in Design view (so it can't be done in mde/accde)
It is questionable how it will perform in a multi-user environment.
Because of these limitations, it is inadvisable, unless you are using to design forms initially.
Duplicate Question: How do you dynamically create controls on a MS Access form?
In response to the OP's suggestion, here is my test code which was able to add 40 controls and repeat the process 50 times without exceeding the 754 limit (I reused 40 names in my test).
Caveat 1 This is inadvisable because it can only be done in design view which will not work in an mde/accde.
Caveat 2: It is questionable how it will perform in a multi-user environment.
This code is from a form with two buttons. It opens a second form named "Form2"
Option Compare Database
Option Explicit
Private Const FORM_NAME As String = "Form2"
Private m_nCounter As Long
Private Sub cmdCreate_Click()
runDynamicForm
End Sub
Private Sub cmdRepeat_Click()
Dim n As Long
m_nCounter = 0
For n = 0 To 50
runDynamicForm
DoEvents
DoCmd.Close acForm, FORM_NAME, acSaveNo
DoEvents
Next 'n
MsgBox m_nCounter
End Sub
Private Sub runDynamicForm()
Const DYNAMIC_TAG As String = "dynamic"
Dim n As Long
Dim frm As Form
Dim ctl As Access.Control
On Error GoTo EH
Application.Echo False
DoCmd.OpenForm FORM_NAME, acDesign
Set frm = Application.Forms(FORM_NAME)
For n = frm.Controls.Count - 1 To 0 Step -1
Set ctl = frm.Controls(n)
If ctl.Tag = DYNAMIC_TAG Then
Application.DeleteControl FORM_NAME, ctl.Name
End If
Next 'n
For n = 1 To 20
With Application.CreateControl(FORM_NAME, acLabel, acDetail, , , 400, n * 300, 1500, 300)
.Name = "lbl" & n
.Caption = "Question " & n
.Visible = True
.Tag = DYNAMIC_TAG
End With
With Application.CreateControl(FORM_NAME, acTextBox, acDetail, , , 2000, n * 300, 3000, 300)
.Name = "txt" & n
.Visible = True
.TabIndex = n - 1
.Tag = DYNAMIC_TAG
End With
m_nCounter = m_nCounter + 2
Next 'n
DoCmd.Close acForm, FORM_NAME, acSaveYes
DoCmd.OpenForm FORM_NAME, acNormal
GoTo FINISH
EH:
With Err
MsgBox "Error:" & vbTab & .Number & vbCrLf _
& "Source" & vbTab & .Source & vbCrLf _
& .Description
End With
FINISH:
Application.Echo True
End Sub
I took that upove code and simplified it as it was long winded, and turned it into a a sample code for my own future use. Hope it helps someone in the future.
Public Sub runDynamicCreateControls()
Dim FormName As String
Dim NumControls As Integer
Dim n As Long
Dim ctl As Access.Control
Dim ctlname As String
On Error GoTo EH
Application.Echo False
FormName = "createcontrolF" 'Input Name of Form
NumControls = 20 'input number of controls
ctlname = "txt" 'input control name
DoCmd.OpenForm FormName, acDesign
For n = 1 To NumControls
With Application.CreateControl(FormName, acTextBox, acDetail, , , 1000,1000, 1100, 600)
.Name = ctlname & "_" & n
.Visible = True
.Tag = n
End With
Next 'n
DoCmd.Close acForm, FormName, acSaveYes
DoCmd.OpenForm FormName, acNormal
GoTo FINISH
EH:
With Err
MsgBox "Error:" & vbTab & .Number & vbCrLf _
& "Source" & vbTab & .Source & vbCrLf _
& .Description
End With
FINISH:
Application.Echo True
End Sub
Whenever I attempt to run your code I get the runtime error of:
Run-time error '6062':
You must be in Design or Layout View to create or delete controls.
Based off of that information it seems like dynamically creating controls in runtime isn't going to be possible.
You might be only missing DoCmd.Restore, here is an example on how to dynamically create form, data bind it, and create controls, all in runtime.
Sub NewControls()
Dim frm As Form
Dim ctlLabel As Control, ctlText As Control
Dim intDataX As Integer, intDataY As Integer
Dim intLabelX As Integer, intLabelY As Integer
' Create new form with Orders table as its record source.
Set frm = CreateForm
frm.RecordSource = "Orders"
' Set positioning values for new controls.
intLabelX = 100
intLabelY = 100
intDataX = 1000
intDataY = 100
' Create unbound default-size text box in detail section.
Set ctlText = CreateControl(frm.Name, acTextBox, , "", "", _
intDataX, intDataY)
' Create child label control for text box.
Set ctlLabel = CreateControl(frm.Name, acLabel, , _
ctlText.Name, "NewLabel", intLabelX, intLabelY)
' Restore form.
DoCmd.Restore
End Sub
I have a table (tblForms) in which one of the fields is a lookup to another table (tblClients). How can I find if a certain Client has data or does not have data in tblForms? DCount only works if the Client does appear in tblForms.
I have a form (frmDisclosure) with a command button - onClick:
Private Sub Command245_Click()
On Error GoTo Command245_Click_Err
DoCmd.OpenForm "frmClient", acNormal, "", "[ClientID]= " & Me.Client, , acNormal
DoCmd.Close acForm, "frmDisclosure"
Command245_Click_Exit:
Exit Sub
Command245_Click_Err:
MsgBox Error$
Resume Command245_Click_Exit
End Sub
When I click this I get the error (N.B. I f I open frmClient directly form Switchboard I don't get the error). frmClient has a subform (continuous) frmFormsList which derives its data from:
SELECT tblForms.ClientLookup, tblForms.Issued, First(tblForms.RefNo) AS FirstOfRefNo, Last(tblForms.RefNo) AS LastOfRefNo, Count(tblForms.RefNo) AS CountOfRefNo, tblClient.KnownAs, tblClient.EMail
FROM tblForms INNER JOIN tblClient ON tblForms.ClientLookup = tblClient.ClientID
GROUP BY tblForms.ClientLookup, tblForms.Issued, tblClient.KnownAs, tblClient.EMail
HAVING (((tblForms.Issued) Is Not Null));
This function resides in frmFormsList:
Public Function NumRecs() As Integer
NumRecs = DCount("*", "tblForms", "ClientLookup = " & Me.ClientLookup)
End Function
My query shows data where I have issued forms to a client. Therefore if I have not issued forms to a Client tne the query shows nothing for that client so does not give a result 0. I get RunTime Error 2427 "You entered ans expression that has no value". NumRecs = DCount("*", "tblForms", "ClientLookup = " & Me.ClientLookup) is highlighted in debug.
In frm Disclosure, if I Rem out ", acNormal, "", "[ClientID]= " & Me.Client, , acNormal" the problem does not occur, but I don't get straight to the Client I am interested in. So the problem occurs when I try to open a form using the Rem'd out bit where the client has not been issued with any forms. When I opne the frm Client directly the rocord presented does not have forms issued but the problem does not occur.
Here's the solution:
Public Function NumRecs() As Integer
Dim dbs As DAO.Database
Dim rs As Object
Set dbs = CurrentDb
Set rs = dbs.OpenRecordset("qryDisclosure", dbOpenDynaset)
If Me.Recordset.RecordCount = 0 Then
NumRecs = 0
Else
NumRecs = Nz(DCount("*", "qryDisclosure", "ClientLookup = " & Me.ClientLookup), 0)
End If
End Function
I have a Subform/Subreport control displayed on a Form in an Access 2010 database, and I use it to display both Forms and Reports. I have a few event handlers in which I need to know whether a Report is currently loaded into the Subform/Subreport control, or if it's a Form that's loaded. I have tried all of the following to no avail.
Any of the following conditions
If IsEmpty(NavigationSubform.Form) Then '...
If IsNull(NavigationSubform.Form) Then '...
If IsOject(NavigationSubform.Form) Then '...
If NavigationSubform.Form Is Nothing Then '...
If NavigationSubform.Form Is Null Then '...
If Nz(NavigationSubform.Form) Then '...
If (Not NavigationSubform.Form) = -1 Then '... This is a trick I use to check for uninitialized arrays
Results in
Run-time error '2467':
The expression you entered refers to an object that is closed or doesn't exist.
Is there some way that I can check whether a Subform/Subreport control currently has a Form or Report loaded without intentionally causing an error?
I don't believe that there is a way to reliably perform the check without error trapping, so you may want to wrap the code in a Public Function and put it into a regular VBA Module:
Public Function CheckSubformControlContents(ctl As SubForm) As String
Dim obj As Object, rtn As String
rtn = "None"
On Error Resume Next
Set obj = ctl.Form
If Err.Number = 0 Then
rtn = "Form"
Else
On Error Resume Next
Set obj = ctl.Report
If Err.Number = 0 Then
rtn = "Report"
End If
End If
Set obj = Nothing
On Error GoTo 0
CheckSubformControlContents = rtn
End Function
Then your form code can simply call CheckSubformControlContents(Me.NavigationSubform).
Here are two functions that work in Access 2013 for determining if a name is a Report or a Form.
Once that is determined the IsLoaded function of AllForms or AllReports can be used. Note that dbs is an object and rpt or frm are AccessObjects not forms or reports
Public Function IsForm(FormName As String) As Boolean
Dim dbs As Object
Dim frm As AccessObject
Set dbs = Application.CurrentProject
IsForm = False
For Each frm In Application.CurrentProject.AllForms
If frm.Name = FormName Then
IsForm = True
Exit For
End If
Next frm
Set frm = Nothing
Set dbs = Nothing
End Function
Public Function IsReport(ReportName As String) As Boolean
Dim dbs As Object
Dim rpt As AccessObject
Set dbs = Application.CurrentProject
IsReport = False
For Each rpt In Application.CurrentProject.AllReports
If rpt.Name = ReportName Then
IsReport = True
Exit For
End If
Next rpt
Set rpt = Nothing
Set dbs = Nothing
End Function
Here is a program that uses the above functions:
Public Sub EnumerateTaggedControls(ReportName As String, MyTag As String)
Dim dbs As Object
Dim rpt As Report
Dim frm As Form
Dim col As Controls
Dim ctl As Control
Dim left As Integer
Dim top As Integer
Dim width As Integer
Dim height As Integer
Dim tag As String
Dim i As Integer
Const format1 As String = "0000 "
Set dbs = Application.CurrentProject
If IsForm(ReportName) Then
If dbs.AllForms(ReportName).IsLoaded Then
DoCmd.OpenForm ReportName, acViewDesign
Set frm = Forms(ReportName)
Set col = frm.Controls
End If
Else
If dbs.AllReports(ReportName).IsLoaded Then
DoCmd.OpenReport ReportName, acViewDesign
Set rpt = Reports(ReportName)
Set col = rpt.Controls
Else
Debug.Print ReportName & " is not a loaded form or report."
Exit Sub
End If
End If
Set dbs = Nothing
Debug.Print Tab(53); "Left Top Width Height"
For Each ctl In col
With ctl
left = .Properties("Left")
top = .Properties("Top")
width = .Properties("Width")
height = .Properties("Height")
tag = Nz(.Properties("Tag"), vbNullString)
If MyTag = "" Then
i = 1
Else
i = InStr(1, tag, MyTag)
End If
If i > 0 Then
Debug.Print .Name & ">"; Tab(33); tag; Tab(53); Format(left, format1) & Format(top, format1) & Format(width, format1) & Format(height, format1)
End If
End With
Next ctl
Debug.Print "====================================================="
Set ctl = Nothing
Set rpt = Nothing
Set col = Nothing
Set frm = Nothing
End Sub
I hope this meets your requirements.