Waiting from input from form (VBA\MS Access) - forms

I have the following (pseudo-)code:
public publVar
Sub
publVar = ""
OpenForm Form1
Do until publVar <> ""
Sleep 100
DoEvents
Loop
'Do Something with publVar
End Sub
It is supposed to stop the execution of the code until a value is assigned to publVar on Form1. There is the option to assign the attribute "Popup" to a form in Access to prevent code from being executed but that didnt work for me.
My Question is: Is there a better way to do this in Access?

The OpenForm Method includes a WindowMode argument. Use acDialog to halt execution until the form has closed.
This should free you of nearly all the code above.

Related

How to prevent an Microsoft Access error message from populating when trying to close a form?

I have this form in Access with a bunch of drop down menu boxes. If I choose a value from the List and then change my mind and leave it blank again, click close, I always get this message:
the data has been changed
Another User edited this record and saved the changes
before you attempted to save your changes
Re-edit the record.
I have 3 different macros in the background running for
Deal type
Loan Exception Comments
Update Flags
I don't think those macros would affect anything to give that error. But can I write something so that it catches this error message and ignores it and proceeds to close when I click close?
The main form VBA has this code:
Private Sub Close_Click()
Me.FrmJobDetails.Form.Requery
Me.subform1.Form.Requery
If Me.FrmJobDetails.Form.Dirty = True Then 'what I tried adding
 me.FrmJobDetails.Form.Dirty = False 'what I tried adding
End If
If Me.subform1.Form.Dirty = True Then 'what I tried adding
 me.subform1.Form.Dirty = False 'what I tried adding
End If
DoCmd.SetWarnings (WarningsOff)
DoCmd.Save
entry
DoCmd.OpenQuery ("aqrySBORequestSiteDetail"), , acReadOnly
DoCmd.Close
DoCmd.SetWarnings (WarningsOn)
End Sub
In that code, with the comment "what I tried adding" is what I thought could prevent that error message from occurring.
Any suggestions?
I use this to handle the Write Conflict error (7787). When there's a write conflict, the first change goes through and the second one gets discarded.
Private Sub Form_Error(DataErr As Integer, Response As Integer)
If DataErr = 7787 Then
Response = acDataErrContinue
On Error Resume Next
With Me.Recordset
.MovePrevious
.MoveNext
End With
On Error GoTo 0
End If
End Sub
Well... to skip it:
On Error Resume Next
At the top of the Macro will skip any commands with errors.
This won't fix the cause, but it will stop popping up the errors.
Thanks! I found this to work on my end. Same script, but DataErr 7878 instead.
Private Sub Form_Error(DataErr As Integer, Response As Integer)
If DataErr = 7878 Then
Response = acDataErrContinue
On Error Resume Next
With Me.Recordset
.MovePrevious
.MoveNext
End With
On Error GoTo 0
End If
End Sub

Access 2007 form: event AFTER undo

I have a form in Access 2007, which has an "update" routine, that enables or disables certain textboxes based on values in other fields (textboxes, checkboxes, comboboxes). The regular operation of that routine works well.
Now I found that pressing ESC calls the undo function, that restores the original values in all fields. But this undo does not call the events on those fields, so the form is in a wrong state, where textboxes are disabled/enabled although they shouldn't.
I also found that there is an undo-event, but that is useless for me because it is called before undo. I need an event after undo. What can I do here to update the fields when ESC is pressed?
I like this solution more, because it works not only on the "ESC"-Key:
private Sub form_Undo(cancel as integer)
afterUndo = true
TimerInterval = 1
end Sub
private Sub Form_Timer()
if afterUndo then
'do something after the Undo-Event
end if
TimerInterval = 0
end Sub
Well, like many times before I have an idea for a solution after postion the question.
The solution here is enabling KeyPreview on the form and using the KeyUp event. The undo is called on KeyDown, so when KeyUp is raised, the form already has the restored values again and the update routine works.
Another solution to this problem is to use each control's OldValue property. Through experimentation, I've found that the three different value properties of controls come into play in different situations:
Control.Value or simply Control
The control's current value most of the time
When the control has focus, it is the value the control had before gaining focus
During a Form.Undo event, it is the value the control prior to the Undo
Relevant during the Control.AfterUpdate and Control.Undo events
Control.Text
The control's value while it has focus
Relevant during events that occur as the user types, such as Control.Change, Control.KeyUp, Control.KeyDown
Control.OldValue
The value the control had when the current record was first opened
Also the value that a form-level Undo will reset the control to
Relevant during the Form.Undo event
The timer-based answer to this question is my go-to solution, but if you're already handling events as the user types (e.g., for live validation), then code like this can be sensible:
Private Sub LastName_Change()
ValidateLastName SourceProperty:="Text"
End Sub
Private Sub LastName_Undo(Cancel As Integer)
ValidateLastName SourceProperty:="Value"
End Sub
Private Sub Form_Undo(Cancel As Integer)
ValidateLastName SourceProperty:="OldValue"
End Sub
Private Sub ValidateLastName(SourceProperty As Variant)
Dim LastName As String
Select Case SourceProperty
Case "LastName"
LastName = Nz(Me.LastName.Text, "")
Case "Value"
LastName = Nz(Me.LastName.Value, "")
Case "OldValue"
LastName = Nz(Me.LastName.OldValue, "")
Case Else
Debug.Print "Invalid case in ValidateLastName"
Exit Sub
End Select
' <Do something to validate LastName>
End Sub
Note that this method does not get you access to the post-Form-Undo value of Control.Column(x) of combo/list boxes. You can get it by using DLOOKUP on Control.OldValue, but you're probably better off just using the timer-based solution instead.

VBA controls reference (form) global variables?

OK, I've been out of Access programming for a couple of versions, but I could swear I used to be able to point controls at form global variables. Sample code as follows:
Option Compare Database
Option Explicit
Dim Testvar As String
Private Sub Form_Load()
Testvar = "Load"
End Sub
Private Sub Form_Open(Cancel As Integer)
Testvar = "open"
End Sub
Private Sub Text0_Click()
Testvar = "settest"
End Sub
I should be able to put a text box on the control that can see the TestVar variable, but the controls don't do it. Also, I used to be able to do that with the form's record source.
So, the questions -
Am I crazy - that was never possible?
Or have I forgotten how to address the form?
And then the most important question - what is the best way to get around this?
The most common way this is used is to pass in OpenArgs (record keys in this case) which is then parsed in to global vars and then a couple of controls display the open args and/or look up values to display from the keys.
I really hate to have to build routines that rebuild and load the record sources for the controls. Hope someone knows a better approach
In addition to your existing event procedures, you can add a function in the form module which retrieves the value of the Testvar module variable.
Function GetTestvar() As String
GetTestvar = Testvar
End Function
Then use =GetTestvar() as the Control Source for Text0.
You have to actually set the value of the text box. There's no way (to the best of my knowledge) to bind a text box to a variable.
Option Compare Database
Option Explicit
Private Sub Form_Load()
Text0.Value = "Load"
End Sub
Private Sub Form_Open(Cancel As Integer)
Text0.Value = "open"
End Sub
Private Sub Text0_Click()
Text0.Value = "settest"
End Sub
Of course, you could store the value in a variable and use it to set the value instead, but it makes little sense to do so in this simple example.
The TempVars collection is a feature introduced in Access 2007. So, if your Access version is >= 2007, you could use a TempVar to hold the string value. Then you can use the TempVar as the control source for your text box.
With =[TempVars]![Testvar] as the Control Source for Text0, the following event procedures do what you requested.
Private Sub Form_Open(Cancel As Integer)
TempVars.Add "Testvar", "Open"
End Sub
Private Sub Form_Load()
TempVars("Testvar") = "Load"
End Sub
Private Sub Text0_Click()
TempVars("Testvar") = "settest"
Me.Text0.Requery
End Sub
Note: [TempVars]![Testvar] will then be available throughout the application for the remainder of the session. If that is a problem in your situation, you could remove the TempVar at Form Close: TempVars.Remove "Testvar"
Requirement was: To show the login Id of the application user on all forms in the application.
Here is how I implemented this:
Create a module: module_init_globals
with the following code:
Option Compare Database
'Define global variables
Global GBL_LogonID as string
Option Explicit
Public Sub init_globals ()
'Initialize the global variables
'Get_Logon_Name is a function defined in another VBA module that determines the logon ID of the user
GBL_LogonID = Get_Logon_Name()
End Sub
On the main/first form - we need to call the module that will initialize the global variables:
In the code for "on open" event I have:
Private Sub Form_Open (Cancel as Integer)
call init_globals
End Sub
then on each of the forms in the app, I have a text control - txtUserID to display the logon id of the user
and I can set it's value in the "on open" event of the form.
txtUserID.value = GBL_LogonID

Run a function when any textbox changes in a form? MS Access

Is there a way to run a function in VBA the moment data in any control element changes? I've tried Form_AfterUpdate and Form_DataChange but they seem not to do anything
You do not have to code After Update/Change event of the controls, check out Key Preview
You can use the KeyPreview property to specify whether the form-level
keyboard event procedures are invoked before a control's keyboard
event procedures. Read/write Boolean.
Use it carefully.
For example, with KeyPreview on:
Private Sub Form_KeyPress(KeyAscii As Integer)
MsgBox "You pressed a key"
End Sub
Step 1: Create a function
Function DoStuff()
Call RunMySub
End Function
Step 2: Create a Macro (Named RunMyCode)
RunCode
Function Name DoStuff()
Step 3: Modify the Form_Load() sub
Private Sub Form_Load()
Dim cControl As Control
On Error Resume Next
For Each cControl In Me.Controls
if cControl.ControlType = 109 'this is for text boxes
'Depending on what your code does you can use all or some of these:
cControl.OnExit = "RunMyCode"
cControl.OnEnter = "RunMyCode"
cControl.OnLostFocus = "RunMyCode"
cControl.OnGotFocus = "RunMyCode"
If cControl.OnClick = "" Then cControl.OnClick = "RunMyCode"
end if
Next cControl
On Error GoTo 0
You can use any of the attributes from the control I find the pairs of 'OnExit/OnEnter' and 'OnLostFocus/OnGotFocus' to be the most effective. I also like 'OnClick' but I use the if statement to not overwrite actions (for buttons and stuff). There are a dozen other methods you can assign the control action to -- I'm sure you'll be able to find one/several that meet your goal.
Note -- I use the on error enclosure because I wrap this code around multiple different types of controls and not all have all of the methods.

MS Access OpenForm acDialog option does not seem to work

This
DoCmd.OpenForm fnew, , , , , acDialog
Doesn't seem to stop code execution for some reason. Is it because I'm calling the function that has this method from another function and that is messing it up?
E.g.
func1
<code>
Call func2
<func2 code>
DoCmd.OpenForm fnew, , , , , acDialog
<back to func1 code that executes even though i dont want it to until the form closes>
With acDialog as the OpenForm WindowMode parameter, your calling code should not continue until the form is closed.
It should not matter that OpenForm takes place in another procedure called from your code. In this example, when running func1, the MsgBox is not displayed until the form closes.
Public Function func1()
Call func2
MsgBox "form closed"
End Function
Public Function func2()
'DoCmd.OpenForm "Form3", , , , , acDialog
DoCmd.OpenForm "Form3", WindowMode:=acDialog
End Function
Note that code operates as described as long as the form is not already open. If it's open in Design View, calling OpenForm only switches it to Form View without applying acDialog. If open in Form View, nothing happens, which means acDialog is not applied then either.
If you want to guarantee that acDialog is applied, make sure the form is closed before calling OpenForm ...
Public Function func2()
Const cstrForm As String = "Form3"
If CurrentProject.AllForms(cstrForm).IsLoaded Then
DoCmd.Close acForm, cstrForm
End If
'DoCmd.OpenForm cstrForm, , , , , acDialog
DoCmd.OpenForm cstrForm, WindowMode:=acDialog
End Function
I struggled with this one for ages until I realised that the acDialog setting does nothing if the form is open in design view when the code is run. It does actually pause the code from the opening procedure when run in normal use (i.e. no one's trying to design anything). If you want to test this behaviour, create a form "zzTestForm", and run this from a module:
Public Sub DoThis()
MsgBox "ok, let's go"
DoCmd.OpenForm "zzTestForm", acNormal, , , acFormPropertySettings, acDialog
MsgBox "you done yet?"
End Sub