I have been struggling with this issue for a while. This is my first question to stackoverflow. I am developing an Access report that uses a Dialog Form for the user to enter the specific client to be displayed on report. I have been able to print out report but am unable to handle if user enters "Cancel" on Dialog Form. I have copied code from an existing report in another Access database.
This my Report_Open Sub:
Private Sub Report_Open(Cancel As Integer)
' Set public variable to true to indicate that the report
' is in the Open even
bInReportOpenEvent = True
' Open Appt Query Dialog
DoCmd.OpenForm "craid CMM Client Report Dialog", , , , , acDialog
' Cancel Report if User Clicked the Cancel Button
If IsLoaded("craid CMM Client Report Dialog") = False Then Cancel = True
' Set public variable to false to indicate that the
' Open event is completed
bInReportOpenEvent = False
End Sub
When I run this, I am getting "Compile error: Sub or Function not defined" highlighting the word "IsLoaded". I am able to comment out the IsLoaded line of code, but then I get an error if I click "Cancel" on Dialog Form.
I have seen this error in previous questions, but have been unable to determine a solution for my needs. Any help appreciated.
Laura
As noted by Thomas in the comments, IsLoaded is not a standard VBA function and so the compiler will search within the scope of evaluation and all public modules for a Sub or Function named IsLoaded, and will report an error if no such Sub or Function is found.
Assuming that your function merely returns a boolean value to determine whether or not a form with the supplied name is open, the function definition may look something like the following:
Function IsLoaded(strNme As String) As Boolean
IsLoaded = CurrentProject.AllForms(strNme).IsLoaded
End Function
Copying the above definition either to your Report module, or to a public module should resolve the error that you are facing. However, you should ideally source the code for the original definition of your function.
Related
Can an Access 2007 macro determine its own object name..?
My database relies heavily on Navigation Pane custom groups, some with hundreds or objects. I would like to create a generic macro that I can copy, rename, and drop into any group, which would perform actions best summarized as "do this task with every other object in this group".
To do so, the macro would need to be aware of its own name, so the MSysNavPane* groups could be queried for that name, and then the work could proceed from there.
I seem to recall that it's difficult if not impossible for VBA procedures to know their own names, but I couldn't find any information on this topic about Access macros.
As far as I know, no. Macros can only execute simple commands.
Things you can do:
List all open macros (open as in currently being edited, not running)
Public Function AllMacros()
Dim obj As AccessObject, dbs As Object
Set dbs = Application.CurrentProject
' Search for open AccessObject objects in AllMacros collection.
For Each obj In dbs.AllMacros
If obj.IsLoaded = True Then
' Print name of obj.
Debug.Print obj.Name
End If
Next obj
End Function
Use a form for exactly this purpose
Create a blank form with a timer set to 1, and add the following sub (it runs MyFunction and passes the form name as a parameter)
Private Sub Form_Timer()
Me.Visible = False
Run "MyFunction", Me.Name
DoCmd.Close acForm, Me.Name
End Sub
Should behave exactly as your desired macro, only it's a form not a macro
I found this question Why does Excel VBA prompt me for a Macro name when I press Run Sub but the answers don't help me because my sub doesn't have arguments and is not triggered by an event.
I have two subs. One in a Module and one in a Form.
Sub ModuleSub()
MsgBox ("Hello World")
End Sub
Sub FormSub()
MsgBox ("Hello World")
End Sub
The Module Sub DOES RUN when I press F5, but the Form Sub doesn't. It brings up the Macro dialog. What is the problem here?
Macros stored in modules are independent and can be ran just like that.
However, all the functions/subs stored in Form modules or in Class modules are in fact the methods of this form/class and they cannot be ran without instance of this form/class.
If you open Immediate window (CTRL + G) you can run this code:
Call ModuleSub
without any issues.
This code:
Call FormSub
will return Compile error: Sub or function not defined.
However, if you add the name of form before the name of the function (like below):
call Form_FormName.FormSub 'replace FormName with the name of your form.
this sub will be also invoked without any issues, since you dynamically create an instance of form and VBA compiler can use its methods.
We have an Access 2010 database that acts as a front-end to a MS SQL database. When we edit data in the form there is a procedure that needs to run in order to properly save certain data back to SQL.
Our DB programmer added a "Save Button" to do this. But that causes another problem - there are multiple ways in Access by which to save a form -
Navigate to the next record
Click on the Confirmation bar on the left
Create a new record
Search for a new record
Use commands in the ribbon
Is there any way to attach a procedure the actual save action so that no matter how a person moves to a next form that the procedure gets run?
[update]
Here is the code behind the scenes: the first sub is attached to the "Save" Button. Of course, the second is attached to the form BeforeUpdate.
Private Sub SaveRecord_Click()
'From NAME form
Form_BeforeUpdate False
End Sub
Private Sub Form_BeforeUpdate(Cancel As Integer)
'used by NAME form
[Last_Update] = Now
'*********************
Save_Record
'*********************
MName_ID = Me.Name_ID
Me.Undo
Cancel = True
If Not IsNull(MName_ID) Then
Jump_to_Name_ID MName_ID, True
Else
End If
End Sub
I guess I just don't understand what the button is for.
So I installed an MS Access 2010 trial and finally managed to figure out a way to solve your problem. It includes data macros and a hidden gem that took me quite a while to find.
Here's how you run VBA when a table changes:
Create an ordinary module (haven't tried class modules) with public functions:
Module name: EventHandlers
Public Function InsertEvent(ByVal id As Integer)
MsgBox "inserted: " + CStr(id)
End Function
Open the table that, when modified, should run VBA and go to "Table" in the ribbon.
Click on "After Insert"
In the "Add New Action"-select box, choose SetLocalVar (or select it from the Action Catalog).
In the Name-field, insert the name of the module (in this case, EventHandlers, as we created earlier)
In the Expression-field, write the name of the function: InsertEvent([id]) (where [id] is an actual column in the table you're adding a data macro for)
Save and close
Whenever something is inserted to the table, a messagebox will be shown with the id.
You could do the same with the update event. The function could be something like this:
Public Function UpdateEvent(ByVal oldValue As String, ByVal newValue As String)
MsgBox oldValue + " changed to: " + newValue
End Function
and the data macro would be
Action: SetLocalVar
Name: EventHandlers
Expression: UpdateEvent([Old].[your_column_name];[your_column_name])
Note: Executing DoCmd.RunSQL with update, insert or delete will execute data macros and THEN ask the user if he or she actually WANTS to update/insert/delete the row. If the user clicks cancel, nothing is changed but your data macro executed anyway. If you haven't already, you should probably disable this check before implementing data macros.
Well, I was not able to use Mr. Sommer's solution because it was not possible to add an event handler to a linked table on account of their being read-only. But, I did work out a simple procedure that seems to work well enough.
So, I was actually already using the BeforeUpdate event, so I'm catching the right event here - this is the event that traps the save, whether it be on change of navigation or the save-record bar on the left. However, there were a few issues that resulted from using Application.Echo False to keep Access from posting back the old data to the control whilst the Me.Undo takes place.
So we use cancel=true to prevent the BeforeUpdate event from doing its normal processing, and we use Me.Undo to prevent Access from trying to save data to the linked tables.
Private Sub Form_BeforeUpdate(Cancel As Integer)
Cancel = True
[Last_Update] = Now
'*********************
Save_Record '-->This will save the data back to SQL
'*********************
MName_ID = Me.Name_ID
Application.Echo False 'don't show the undo in the controls
Me.Undo
If Not IsNull(MName_ID) Then 'used for the navigation buttons
Jump_to_Name_ID MName_ID, True
Else
End If
Application.Echo True 'turn the control refresh back on
Me.Repaint
End Sub
I've been modifying my Access DB to clean it up and make it more user friendly. As such, I've been changing the names on form labels and controls to include _lbl and _Ctrl instead of the generic names Access assigned them. Previously, I had three separate forms (ListingsForm, ListOffersForm, ListDetailsForm) that I could access by using buttons to call the form. Since the forms were directly related to the primary form (ListingsForm), I changed two of the forms into subforms. Now I keep getting errors when I try to add a record.
On the ListingsForm form, there is a control field for Property Address (Address_Ctrl). This control uses a combo box that is linked to a separate table called Properties. If the property address is not listed in the drop down box, it used to bring up the PropertyForm form to input new properties. After, it requeried to get the new list with the new record. After making the subforms and changing control names, I'm getting an error whenever I try to add a new property address.
Run-time error '2473':
The expression On Not in List you entered as the event property setting produced the following error. Out of stack space.
'Add Address Form Script
Private Sub Address_Ctrl_NotInList(NewData As String, Response As Integer)
DoCmd.OpenForm "PropertyForm", , , , acFormAdd
Call Address_Ctrl_AfterUpdate
End Sub
'Requery Address List Script
Private Sub Address_Ctrl_AfterUpdate()
Forms!MLSListForm.Dirty = False
Me!Address_Ctrl.Requery
End Sub
It highlights the Forms!MLSListForm.Dirty = False line. I've tried Me.Dirty = False but I get the same error. If I remove the line entirely, I get 'Run-time error '2118': You must save the current field before you run the Requery action.'
Having the forms as subforms helps with user-interface so I really do not want to go back to having three separate forms. Any help would be appreciated.
Normally, your comment Address_Ctrl_AfterUpdate() event handler,
And you can try this event handler:
Private Sub Address_Ctrl_NotInList(NewData As String, Response As Integer)
'
' add in Properties tables for NewData:
' By calling SQL Server via ADO:
' INSERT INTO Properties (City) (NewData)...
' Or
'
'
' Now NewData Exists, do this:
'....
' and then set new value and status:
'
Response = acDataErrAdded
'
' here you can then modify new city:
'
DoCmd.OpenForm "PropertyForm", , , "City='" & NewData & "'", acFormPropertySettings
'
End Sub
I've re-debugged in our Access Database VBA, with Response = acDataErrAdded,
Access will Requery automatically Address_Ctrl.Requery() After the Address_Ctrl_NotInList() returns.
So I finally got it working correctly. Turns out I had to set some default values for the controls in the form design. After that, it worked out just fine.
I have a custom build print dialog that is used for many reports. Its arguments are the report name, filter string, open args for the report etc. What I would like to do is to display the caption of the report specified for printing on the form. For performance reasons, I would rather not open the report, get the caption and close it again. I would rather get it from the database somehow without actually opening the report itself.
One thing that DOES work is to call the report using it's class name report_some_report.caption but there is no way to do this without with a report name stored in a variable. I would have expected Reports("some_report").caption to work also but it only works for open reports.
Is there a better way to do this or am I going to have to do something like the following? (Which works)
docmd.OpenReport "schedule_simple",acViewDesign,,,acHidden
strCaption = Reports("schedule_simple").Caption
docmd.Close acReport,"schedule_simple"
There's no way to get a report caption from a report without first opening the report or using the report class object (as you know). It's also worth noting that "lightweight" reports (ie, ones whose HasModule property = False) do not have class objects.
You have a couple of options.
You could create a local table with a RptName and RptCaption field and query that. Of course, then you need to keep it updated somehow.
You could write a function that "memoizes" the results, so that you only have to open a given report once each time the program runs. For example:
.
Function GetReportCaption(RptName As String) As String
Static RptCaptions As Collection
If RptCaptions Is Nothing Then Set RptCaptions = New Collection
On Error Resume Next
GetReportCaption = RptCaptions(RptName)
If Err.Number = 0 Then Exit Function
On Error GoTo 0
DoCmd.OpenReport RptName, acViewDesign, , , acHidden
RptCaptions.Add Reports(RptName).Caption, RptName
DoCmd.Close acReport, RptName
GetReportCaption = RptCaptions(RptName)
End Function