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
Related
I am new on access and what I am trying to do is a select with a criteria so I created a query with the wizard and seted the criteria with a text from a form ([Forms]![Form1]![Transacao]) and created a button to run the query at the first time works great but when I type something else and click the button the datas do not refresh. What I have to do to refresh? I've tryed to add refresh on the event click of the button and did not work.
Thanks in advance for your help.
In Access, a query is usually opened in a default Datasheet view. This default datasheet is contained in a window (or tab) that is only accessible using Macros or DoCmd in VBA.
Once a query window is open, its data will not necessarily update automatically when new records are added to the underlying table(s). The datasheet needs to be "requeried". (Incidentally, the term "refresh" is usually reserved to mean "redrawing" a window on the screen and has nothing to do with the data. This is especially the case in programming and development environments which deal with data and drawing/painting windows and controls on the screen.)
Here is one way to force a query to update its data (when open in its default datasheet view):
DoCmd.OpenQuery "QueryName"
DoCmd.Requery
Calling OpenQuery should also activate the query window if it is already open. If you find that the windows does not activate, you can also call DoCmd.SelectObject acQuery, "QueryName" before DoCmd.Requery.
The DoCmd methods correspond to Macro actions, so if the query is activated by a Macro, just add the Requery action to the macro after the OpenQuery or SelectObject actions. Leave the Control Name parameter of the Requery action blank to force the entire query to updated.
I know this question is a bit stale at this point, but since I couldn't find a suitable answer to this question and the above answer didn't work for me (and still hasn't been accepted), I thought I'd offer my solution for those few poor saps still stuck developing applications in Access. My use case was slightly different (changing the underlying SQL of a query, then opening/refreshing it), but the same principle could be applied. The gist is to first check to see if the query is open and close it if it is. Then open it up again.
To do this, paste this code into a VBA module:
Public Function open_or_refresh_query(query_name As String, Optional sql_str As String = "")
' Refresh or open an existing query
' query_name: Name of the query
' sql_str: optional new SQL string if changing the underlying SQL. If not given,
' the query will open with its existing SQL string
On Error GoTo err_handler
Dim qdf As QueryDef
' Loop through each query in the DB and find the one of interest by name
For Each qdf In CurrentDb.QueryDefs
If qdf.Name = query_name Then
' If the query is open, close it
If SysCmd(acSysCmdGetObjectState, acQuery, query_name) <> 0 Then
DoCmd.Close acQuery, query_name, acSaveNo
End If
Exit For
End If
Next qdf
Set qdf = CurrentDb.QueryDefs(query_name)
' Reset the SQL if new SQL string was given
If Len(sql_str) > 0 Then qdf.sql = sql_str
' Close the QueryDef object to release resources
qdf.Close
' Open the query in default datasheet view
DoCmd.OpenQuery query_name
exit_function:
Exit Function
err_handler:
MsgBox "Error " & Err.Number & ": " & Err.Description, vbCritical, "Error"
Resume exit_function
End Function
At this point you could call this function from any other VBA code in your project. To open/refresh a query from a macro as the OP wanted to do, create a new macro and add a RunCode action with open_or_refresh_query("my_query") in the Function Name field, changing my_query to the name of your query.
I have a main form named Calling. It has a button named Travel. That Travel button does:
Private Sub btnTravel_Click()
On Error GoTo btnTravel_Click_Err
DoCmd.OpenForm "Travel", acNormal, "", "", acFormEdit
'If you can find a cleaner way to open a form I will be thankful.
btnTravel_Click_Exit:
Exit Sub
btnTravel_Click_Err:
MsgBox Error$
Resume btnTravel_Click_Exit
End Sub
The Travel info form performs correctly. That Travel form has a Close button with the code:
Private Sub bntClose_Click()
Unload Me
End Sub
When pressed, the Close code generates "Run-time error '361': Can't load or unload this object.
Your help is much appreciated.
You do not need the commas with the empty strings, nor do you need the acFormEdit as when you open the form you will be able to edit and add new records anyway.
If you leave this argument blank the form will open in the data mode set by the forms AllowEdits, AllowDeletions, AllowAdditions, and DataEntry permissions (in the form properties).
DoCmd.OpenForm "Travel", acNormal
As for the next sub routine, I would use docmd.close instead of unload.
Private Sub bntClose_Click()
Me.Undo
DoCmd.Close acForm, "Travel", acSaveNo
End Sub
The me.undo is optional, if you don't want to save, and if you want to save the form change the acSaveNo to acSaveYes.
EDIT:
I have just re-read your question and noticed in the title you want to do this without docmd.
I have had a think about this and docmd is the standard way of closing forms in access using VBA. I am not sure if you have inherited the unload from using VB, but I would stick to docmd.close when using access.
"Unolad me" just doesn't work in Access Form objects.
Access wants you to use DoCmd.Open/DoCmd.Close but you are clever than that and can use Access Form Object as an object. Access names the Form Classes prefixed the name you give them with "Form_". You create a Form named "YourForm", Access create the Class "Form_YourForm". Use this class as a real object:
'Declare a typed variable
Dim f As Form_YourForm
'Create the object
Set f = New Form_YourForm 'This triggers the Open event
'Use the object
f.SetFocus
f.Resize
'... And eventually, dispose the object
Set f = Nothing
'Remember <<Unload f>> won't work, neither you can use DoCmd.Close
Also, you can use Form_YourForm directly as an object variable because VBA Access just create "implicitly" this object out of the class Form_YourForm when you first use it or when you use DoCmd.Open. (Yes, it's a little bit confusing, but Access was create for users that didn't have necessarily programmer skills).
However, you'll get a different instance of the Form_YourForm Class each time you use a variable object typed as any of the Form Class that exist in your project. This means you can open as many instances of a form as you want ...or while it fits your computer's memory. You can't acomplish it using DoCmd.Open.
The main "disadvantages" are that you have to handle the form object from another module. You can still use the "Forms" collection but since you don't know the given key you can't reach your form instance easily. Also, you can't close the form from its own code, but only disposing the typed variable (Set f=nothing).
I'm trying to figure out if/how I can generate a new query window in the Design View either via a macro or VBA code.
Specifically, I want to open a new, blank query in SQL View so I can quickly test SQL code. This is a common activity for me.
I usually create a new query manually via the Ribbon: Create tab > Query Design button > Close (Show Table window) button > SQL View button. This results in a new window called Query1 (or Query2, etc.).
I'd like to condense all of those multiple clicks into a keyboard shortcut or a single macro button on the Quick Access Toolbar. Note that I'm using Access 2010.
Possible?
Add this to a module:
Function NewQueryInSqlView()
' Send the ESC key, without waiting for processing,
' to cancel the select table dialog that occurs when designing new query
SendKeys "{ESC}", False
DoCmd.RunCommand acCmdNewObjectDesignQuery ' create new query
DoCmd.RunCommand acCmdSQLView ' switch to SQL view
End Function
To execute the code by way of a keyboard shortcut, create a new macro, and ensure "Macro Names" column is visible.
Add the following row to macro with the following values in the specified columns:
Macro Name: +{F3}
Action: RunCode
with Function Name argument: NewQueryInSqlView()
Save this macro as "AutoKeys". This macro will map SHIFTF3 to the execution of NewQueryInSqlView(). Note that choosing a key mapping starting with CTRL (the ^ character) will be problematic due to the SendKeys statement in NewQueryInSqlView().
Write a function and call it from a macro:
Public Function foo()
Dim qdf As QueryDef
Set qdf = CurrentDb.CreateQueryDef("qNew", "SELECT 'd' as foo")
DoCmd.OpenQuery qdf.Name
End Function
This will create the query and open it
The macro is a RunCode command that runs the above function.
Try This code:
Sub EmptyQuery()
Dim Qname As String
Qname = "mQname"
On Error Resume Next
CurrentDb.CreateQueryDef Qname
On Error GoTo 0
DoCmd.OpenQuery Qname, acViewDesign
End Sub
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 have an embarrassing question regarding Access. I can build relational databases in my sleep but I have always used PHP, mySQL and HTML to build my applications.
I've hated Access with a passion for various reasons ever since I can remember but now I'm stuck using it for a particular project.
I have a number of tables, one of which is customer, which among other things has a username and password field.
When I start the database, I want a login form to appear, which I can do using the AutoExec macro, I've made the form with a username and password field. After that, I get stuck with the logic of querying for the username/password and then showing a new form if correct or an error if not.
Could anyone help me out with making the macro and query work together?
Clarification: I am trying to do this without coding whole Visual Basic macros, if at all possible, I want to be able to do it using the macro builder thingumy.
Thanks
Given a form frmLogin, with 2 text boxes, txtUserName and txtPassword, and a command button, you can try the following
Private Sub Command0_Click()
Dim rec As Recordset2
Set rec = CurrentDb.OpenRecordset("SELECT * FROM Customer WHERE username = """ & txtUserName.Value & """ AND password = """ & txtPassword.Value & """")
If (rec.RecordCount > 0) Then
DoCmd.OpenForm "frmMain"
DoCmd.Close acForm, "frmLogin"
End If
End Sub
Malphas -
It is actually possible to do this without using VBA, but I am wondering whether the reason why you don't want to use VBA is because of the Trust issue. In which case, this won't be possible, because the macro actions Close and Quit are disallowed if the database is not trusted.
Whilst you can to run actions in the AutoExec macro beyond the point where you use the OpenForm command, I think it is neater to continue the next actions on the form itself. First because you can't really do branching in a macro; secondly because it is more modular to keep actions to do with the form actually on the form.
In the example below, my sample login form is called LoginForm, and the username text box is txtUserName, and the password text box is called txtPassword.
The first thing to do is to protect the dialogue from the simple act of letting the user close the dialogue and escape into the database design screen. The best way to do this is to set a flag called ValidLogin when the form loads. You will set this flag during the login process. When the form is closed, check whether the flag is true. If ValidLogin is false, then close the database.
On the OnLoad event of the Login form, click on the ellipsis button, and choose Macros Builder. In the Macro screen, use the following actions (note that the Condition column is hidden by default - but you'll only need for the next two macros):
Line Condition Action/Arguments
1 SetTempVar, Name = ValidLogin, Expression = False
On the OnUnload event of the Login form, do the same as above, and add:
Line Condition Action/Arguments
1 Not [TempVars]![ValidLogin]
Quit, Options = Exit.
If you run this now, as soon as you close the form, the database will close. To be useful, you need to add the following macro actions to the OnClick event of your Login button:
Line Condition Action/Arguments
1 SetTempVar, Name = Valid Login, Expression = DCount("*","Customer","[Username]=[Forms]![LoginForm]![txtUserName] And [Password]=[Forms]![LoginForm]![txtPassword]")>0
2 Not [TempVars]![ValidLogin]
MsgBox, Message = Invalid Login
3 ... StopMacro
4 OpenForm, Form Name = MainForm
5 Close, Object Type = Form, Object Name = LoginForm, Save = No
Note that in all these examples, I have used embedded macros, not named macros, so you can keep them together with the form. Also note the ellipsis (...) in the last macro, which represents the value of the last condition.
A slight tweak to the above as the code above would be open to SQL injection attacks (yes I know it is only access but it never hurts)
Public Function CheckUserPassword(strUserName As String, strPassword As String) As Boolean
Dim rst As DAO.Recordset
Set rst = DBEngine(0)(0).OpenRecordset("tblUsers", dbOpenTable)
With rst
.Index = "UserName"
.Seek "=", strUserName
If .NoMatch = False Then
If !Password = strPassword Then
CheckUserPassword = True
Else
CheckUserPassword = False
End If
Else
CheckUserPassword = False
End If
End With
rst.Close
Set rst = Nothing
End Function