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.
Related
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).
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
I have a MS Access form with a button that calls a function that starts by checking a date and asks the user if he needs to manually change the date. If this is the case, another form is opened where the date is entered and validated. Is there a possible way to load the selected date into a variable and return to the first form into the function that is running to use it further down the procedure?
The following way is working for me although I assume there is a better one.
First of all I inserted a module and declared a global variable:
Global globalText As String
Then you need to make sure the other form (here frmEntry) is called "modal", so that your code will wait until the form is closed again. You can achieve this by:
DoCmd.OpenForm "frmEntry", WindowMode:=acDialog
In frmEntry you need to write whatever value was selected then to the global variable and then close the form, which would look something like this:
Private Sub btnClose_Click()
globalText = Me.txtEntry.Value
DoCmd.Close acForm, "frmEntry"
End Sub
And then it is basically done and your code in your basic form will go on running and you can use the value from globalText anywhere or can write it to some hidden textBox to use it later in other functions.
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.
I've created a form with 3 subforms in it to display an user's details and the inventory the user has. The form enables user to update the details displayed. Thus each subform has a "save" and "undo" button. I'm trying to create a "Clear All" button on the parent form which undo all changes there are in all the 3 subforms.
I don't really want to retype the same codes used in the 3 "undo" buttons, so is there a way to make use of the Onclick function of the 3 buttons?
I've tried the following with one subform first:
Private Sub ClearAllParentForm_Click()
Me.Subform1.Form.clearButton_Click
End Sub
However, the form invokes the subform's beforeupdate event instead (a messagebox that prompts user to save the updated record). I've also tried to change the codes to Me.Subform1.Form.Undo which produces the same issue. Is there somewhere which I did wrongly or is my concept wrong?
Sorry, just started using Microsoft Access 2007 only recently so quite confused with some stuff.
You need three sub routines that are separate from the button's click event. Have each button call their respective sub routine or function. Then the one single button can call all three.
#JeffO is right, but I thought I'd expand on what he said with a little more guidance.
If you have the following in a Worksheet module:
Private Sub ClearButton_Click()
'ClearButton code here.
End Sub
You need to move the ClearButton code into a Sub in a general module. So in a new module, you need the following:
Sub clearbtn()
'ClearButton code here.
End Sub
Now, back in the Worksheet module you call this code on button click with one line:
Private Sub ClearButton_Click()
Call clearbtn
End Sub
Making these changes to your event triggers will allow you to use them elsewhere in your code. The implicit lesson here is that you can't call an event trigger sub from elsewhere in VBA, but other subs can be called from within an event trigger.