In an Access application, I'd like to programmatically set up a dynamic key handler to execute a VBA function when this key is pressed.
I was able to manually create a macro that calls the function and another AutoKeys macro that "calls" the first macro when a specific key is pressed.
Now, I'd like to be able to create these macros with VBA only. Is this somehow possible?
Of course, if there are other ways to achieve what I want, I am open to suggestions.
You could do something like this, create a class called clsCustomForm, containing this code
Option Compare Database
Option Explicit
Private WithEvents frmCustomForm As Form
Public Function INIT(frmFormToMakeCustom As Form)
Set frmCustomForm = frmFormToMakeCustom
frmCustomForm.OnKeyDown = "[Event Procedure]"
End Function
Private Sub frmCustomForm_KeyDown(KeyCode As Integer, Shift As Integer)
MsgBox "Hello"
End Sub
Then in a module, you will need to have a public collection, which you add a class for each form opened and removed when each form closed, I'd suggest a dictionary, with the key as the form name and the item as the class, for example, just testing (limited time) I've used the following code in 1 form, hope this helps, i'll have a bit more time later to show the dictionary if needed.
Option Compare Database
Option Explicit
Public CUSTOM_FORM As clsCustomForm
Private Sub Form_Load()
Set CUSTOM_FORM = New clsCustomForm
CUSTOM_FORM.INIT Me
End Sub
Related
I'm trying to trigger the button that will handle the extraction of the selected document once the user double click the document listed in the form like the screen shot below
So I went in the properties and configured:
Private Sub ParamName_DblClick(Cancel As Integer)
call Forms!FormName.Extract_Click
End Sub
With no success , I also tried :
Private Sub ParamName_DblClick(Cancel As Integer)
Forms!FormName.Extract_Click
End Sub
Inside the form , the sub is declared as below :
Private Sub Extract_Click()
Dim dbs As DAO.Database
//.... //do work
end sub
What I am doing wrong ?
Short answer: You can't, and even if you could (or in contexts where you can), you shouldn't.
Longer answer: don't implement any logic directly in event handlers. Invoke the logic from the handlers:
Private Sub SomeControl_SomeEvent()
DoTheThing
End Sub
Public Sub DoTheThing()
'do stuff
End Sub
That Public member can live in a standard module, and thus can be invoked by anything anywhere - from a button on some form, or from another button on another form, or whatever you need to invoke it from.
But don't invoke event handler procedures yourself. Event handlers handle events, they're invoked by the VBA runtime: leave it that way and live prosper.
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 searched and can't find any help with this behavior. (I'm working in Access 2010, but the database is in Access 2000 file format.) I'm working with a form in datasheet view. I wrote the code below to copy Inventory Location from the record above if the Down Arrow key is pressed. The code works fine if I use the Down Arrow key to move vertically down from one record to the next, but not if I use the Tab or Enter keys to move from one field to the next.
Private Sub InventoryLocation_KeyDown(KeyCode As Integer, Shift As Integer)
' Variables are defined as Public at head of module.
If KeyCode = vbKeyDown Then
If Me.CurrentRecord = intPreviousRecordNumber + 1 Then
If IsNull(Me.InventoryLocation.Value) Then
Me.InventoryLocation.Value = varPreviousInventoryLocation
DoCmd.CancelEvent
End If
End If
End If
End Sub
Variable are being populated from the previous record here:
Private Sub InventoryLocation_LostFocus()
' Variables are defined as Public at head of module.
varPreviousInventoryLocation = Me.InventoryLocation.Value
intPreviousRecordNumber = Me.CurrentRecord
End Sub
This is my first post, but I find most of my answers here at StackOverflow.com. Any help will be greatly appreciated! Thanks!
My mistake apparently is that I thought my variable intPreviousRecordNumber could be used for more than one field in the record. (I also have a field called OverstockLocation whose LostFocus event was setting the value of intPreviousRecordNumber. I assumed this was fine because the value would be the same for both fields in the same record.)
Apparently Access was objecting to both InventoryLocation and OverstockLocation trying to use the same variable. I created a new variable called intPreviousRecordNumberOverstock, for use by the OverstockLocation code, and now both work as I expected.
I've made a graphical navigation system so the user can easily find the record he/she wants to. I'm using this on multiple forms now, and since I'm going to use it on another form, I want to standarize the code into a class instead of copy-pasting the VBA code. This way I can improve the code in one place, instead of doing the same change on all the forms.
This is how it works right now:
Dim v As New clsNav
Set v.button1 = Me.button1
Set v.button2 = Me.button2
v.init
And in v.init, I want to set up all the events like click. So when the user clicks button1, it should run a specified method.
How can I do that?
The events for the button clicks will look like this
Private Sub Button1_Click()
' Your code
End Sub
And they are located within the code found behind the respective forms. To be able to reuse code you simply write a sub in a separate module and then call it in the event.
Private Sub Button1_Click()
call MySub()
End Sub
' This is in a module
Private Sub MySub()
' Your code
End Sub
Now this works as long as the code you have to run doesn't use controls specific to that form. If you do need to write code like that, its simply a matter of passing a control to the sub, instead of calling it by its Name
Example. Lets say when we click our buttons it updates a TextBox with today's date. The textboxes have different names on each form. txtDate1 on Form1 and txtDate2 on Form2. So how that will look is
'Form 1 Button
Private Sub Button1_Click()
call MySub(txtDate1)
End Sub
'Form 2 Button
Private Sub Button2_Click()
call MySub(txtDate2)
End Sub
' This is in a module
Private Sub MySub(t as TextBox)
t.Text = Date()
End Sub
If you're trying to do this during run time
How to add events to Controls created at runtime in Excel with VBA
seems like a good place to start. I can't imagine a situation where this would be worth the effort.
I have a generic class I use called frmCtrl, which has WithEvents pointers set up for different control types, with the corresponding events (click, dbl-click, etc...). I then have a function on that class to "set up" the object, so I pass the control in, and the function assigns it to the object pointer of the correct type. This routine also has a parameter for what function to call on which object, so it knows how to respond to the event.
So when I build a form that uses dynamically-added controls, I just create one of these objects for each of the controls, and set it up to call code within the form. The code in the form takes an argument of just the frmCtrl object, so it can easily see which control fired the code, and go from there. I work in Visio, so I'm able to also call a function by module and function name, so the class can handle that too. You'd have to work in how to make such a call using Access, though.
This approach can be used when your controls have been added via the VBIDE, but it may not be worth the effort to set this class up for that, when you can just use the method in the other answer here.
I have a navigation form with two subforms that looks something like this:
When the button in Subform A is clicked, I would like to call a method from Subform B. The method is defined like this:
Public Sub MyMethod()
Debug.Print "MyMethod called"
End Sub
I tried using Forms!SubformB.MyMethod but I get the error Database cannot find the referenced form 'SubformB'. Referring to this page, I also tried Forms!NavigationForm!SubformB.MyMethod but then I get Database cannot find the referenced form 'NavigationForm'. Does anyone know how to do this properly?
Thank you.
Function and Sub procedures defined in the class module of a Form are considered to be private to that form. If you want a Sub that can be called from several different forms then move that Sub to a "regular" VBA Module (i.e., one that is created when you choose Insert > Module from the menu bar in the VBA editor) and be sure to declare it as Public.
Here is a snippet of code I have that toggest the AllowEdits method of a subform when clicking a button on the mainform. Pretty sure this is what you are after.
Function ToggleEditStatus_Child(WhichForm, WhichChild As String)
'Changes the AllowEdits setting of the SubForm parameter
Dim Cform As Object
Set Cform = Forms(WhichForm).Controls(WhichChild).Form
Cform.AllowEdits = Not Cform.AllowEdits
Forms(WhichForm).Refresh
Cform.Refresh
Forms(WhichForm).Controls(WhichChild).SetFocus
Set Cform = Nothing
End Function
I did figure this out but there's a catch - the other form has to be open. This worked for me though so I'm posting the solution here.
First, navigate to the form:
DoCmd.BrowseTo ObjectType:=acBrowseToForm, _
ObjectName:="SubformB", _
PathToSubformControl:="NavigationForm.NavigationSubform", _
DataMode:=acFormEdit
Then make a pointer to the subform:
Dim f As Form_SubformB
Set f = Forms("NavigationForm").NavigationSubform.Form
And you are free to call the method:
f.MyMethod()
Note: you will most likely have to make the method Public for this to work.