Dirty event not firing on Access 2007 form - ms-access

I have a bound pop up form with a Clear data button. It runs the Undo command.
The Form_Current event sets this button's enable property to False.
The Form_Dirty event sets this button's enable property to True.
The button's property is always set to False even after I enter data into the form. I think this is because my Form_Load event is populating two fields. One is passed from the main form as an OpenArgs, the other is the unique ID which is calculated based on the OpenArgs value.
Any suggestions as to how to get the Dirty event to activate under these circumstances? If not, then an alternative approach perhaps?
Thanks in advance.
Code below:
Private Sub cmdUndoChanges_Click()
CustID_temp = Me!CustID
FacNo_temp = Me!Unique_ID
DoCmd.RunCommand acCmdUndo
Me!CustID = CustID_temp
Me!Unique_ID = FacNo_temp
End Sub
Private Sub Form_Current()
Me!cmdUndoChanges.Enabled = False
End Sub
Private Sub Form_Dirty(Cancel As Integer)
Me!cmdUndoChanges.Enabled = True
End Sub
Private Sub Form_Load()
Dim test As Integer
Me!CustID = OpenArgs
test = DCount("Unique_ID", "tbl_Table2", "CustID = '" & Me!CustID & "'")
If IsNull(Me!UNIQUE_No) Then
test = test + 1
Me!Unique_ID = CustID & "-" & test
MsgBox "No Previous Data"
Else
' these will be turned back on when the user selects the
' modify data button or add new data button.
For Each ctl In Me.Controls
Select Case ctl.ControlType
Case acTextBox, acComboBox, acOptionGroup, acCheckBox
ctl.Locked = True
ctl.BackColor = 15066597
Box40.BackColor = 15066597
End Select
Next ctl
End If
MsgBox Me!Unique_ID
End Sub

Have you tried Form_BeforeInsert() instead of Form_Dirty()?

I found another case where the Form_Dirty event never fires, the hard way. I thought I'd add it here since this is the top search result for the "not firing" question.
If your Form_Load function does anything to change the value of an existing record, the form will be set to dirty immediately, apparently before the logic that fires the event begins to run. Because of that there will never be an event if other data on the form is changed.
We had a "smart" Save button that was disabled until Form_Dirty fired. After I added a fixup to Form_Load for old records with a junk field, editing those records no longer ever enabled the Save button.

Related

Alternative to SendKeys "{Esc}" for unbound text box Undo

I have an unbound textbox control. The user enters some valid text and leaves the control. The user then returns to the control and enters some invalid text. I want to show the user a message then rollback the value of the control to its previous state and keep the focus in that control.
I've tried the following approaches, none of which gets me exactly what I am looking for:
OPTION A: SendKeys
Private Sub MyTextBox_BeforeUpdate(Cancel As Integer)
Cancel = DataIsInvalid(Me.MyTextBox.Value)
If Cancel Then SendKeys "{Esc}"
End Sub
This does exactly what I want, but I really want to avoid using SendKeys. There are many problems that come with using SendKeys: Vista+ compatibility, keys being sent to a different application, etc.
OPTION B: Undo method of control
Private Sub MyTextBox_BeforeUpdate(Cancel As Integer)
Cancel = DataIsInvalid(Me.MyTextBox.Value)
If Cancel Then Me.MyTextBox.Undo
End Sub
This is simply broken for unbound controls (at least as of MS Access 2002/XP). This method does not restore the value of MyTextBox to the valid input. However, it does allow the user to change the focus to a new control while leaving the invalid input in place in Me.MyTextBox! Unbelievable!!!
OPTION C: Undo method of form
Private Sub MyTextBox_BeforeUpdate(Cancel As Integer)
Cancel = DataIsInvalid(Me.MyTextBox.Value)
If Cancel Then Me.Form.Undo
End Sub
The Undo here does absolutely nothing. But at least it doesn't break the BeforeUpdate Cancel=True code and allow the invalid data to stand.
OPTION D: Explicitly restore old value in BeforeUpdate event
Private mPrevMyTextBoxValue As Variant
Private Sub MyTextBox_AfterUpdate()
mPrevMyTextBoxValue = Me.MyTextBox.Value
End Sub
Private Sub MyTextBox_BeforeUpdate(Cancel As Integer)
Cancel = DataIsInvalid(Me.MyTextBox.Value)
If Cancel Then Me.MyTextBox.Value = mPrevMyTextBoxValue
'If Cancel Then Me.MyTextBox.Text = mPrevMyTextBoxValue
End Sub
Attempts to assign the previous value to either the .Value or .Text property of the textbox result in the same error message:
The macro or function set to the BeforeUpdate or ValidationRule property for this field is preventing {Program Name} from saving the data in the field.
OPTION E: Explicitly restore old value in AfterUpdate event
Private mPrevMyTextBoxValue As Variant
Private Sub MyTextBox_AfterUpdate()
If DataIsInvalid(Me.MyTextBox.Value) Then
Me.MyTextBox.Value = mPrevMyTextBoxValue
Me.MyTextBox.SetFocus
Else
mPrevMyTextBoxValue = Me.MyTextBox.Value
End If
End Sub
This is really close to the desired behavior. Unfortunately, it's impossible to keep the focus on the control because the AfterUpdate event runs before the Tab/Enter keypress or mouse-click events are processed. So even if we try to force the focus to the proper control (via the .SetFocus statement above), the program will immediately shift the focus to the control the user selected.
It seems to me that the "right" way to do this is to use the .Undo method of the control. That does not work for unbound controls, though. This is an inexplicable shortcoming, especially given the fact that an Escape key-press performs this functionality for an unbound field.
Is there a better way to do this or should I just stick to using SendKeys?
After getting to page 3 on google I decided to just muck around and see what sort of ordering happens when trying your Option E. Below is the best workaround method I was able to use to get focus to "Stay" on the textbox in question.
Private mPrevMyTextBoxValue As Variant
Private Sub Text0_AfterUpdate()
If Me.Text0.Value = 0 Then
Me.Text0.Value = mPrevMyTextBoxValue
Me.Text12.SetFocus
Me.Text0.SetFocus
Else
mPrevMyTextBoxValue = Me.Text0.Value
End If
End Sub
0 simulates that it failed the validation. It seems like if you set focus to something else before setting it back to the textbox you are working with it will stay there. I don't have an answer as to why unfortunately.
Edit: I have decided to try to program my theory as to how this works.
Private blnGoToNextControl as Boolean
Private Function SetFocus(ctrl As control)
blnGoToNextControl = True
If ctrl.HasFocus = True Then
'Do nothing
Else
ctrl.HasFocus = True
blnGoToNextControl = False
End If
End Function
This is my idea of how the SetFocus function works. So then after the AfterUpdate event runs it would check to see if the flag to go to next control is and see that it is set to false and not go to the next control.
Rough coding obviously but hopefully this gets my theory across?
If it is unbound, why not use the AfterUpdate event:
Private Sub MyTextBox_AfterUpdate()
If DataIsInvalid(Me!MyTextBox.Value) Then
Me!MyTextBox.Value = Null
Me!MyTextBox.SetFocus
End If
End Sub
or modify your other solution:
Private Sub MyTextBox_AfterUpdate()
Static mPrevMyTextBoxValue As Variant
If IsEmpty(mPrevMyTextBoxValue) Then
mPrevMyTextBoxValue = Null
End If
If DataIsInvalid(Me!MyTextBox.Value) Then
Me!MyTextBox.Value = mPrevMyTextBoxValue
Me!MyTextBox.SetFocus
Else
mPrevMyTextBoxValue = Me!MyTextBox.Value
End If
End Sub
Try moving the focus off the textbox, then back:
Me!MyTextBox.Value = mPrevMyTextBoxValue
Me!SomeOtherControl.SetFocus
Me!MyTextBox.SetFocus
That someothercontrol can be a tiny nearly hidden textbox.

Making a cell required based on combobox selection

Nw to the forum and may as well be new to programming.
I would like to make 'Invoice No.' text box required, ONLY when 'INVOICED' is selected in my combobox.
If your form is bound, you could add the validation in the before update event like this:
Private Sub Form_BeforeUpdate(Cancel As Integer)
If Me.cboType = "INVOICED" And Nz(Me.InvoiceNo) = vbNullString Then
MsgBox "Invoice No. is required!", vbExclamation, Me.Caption
Me.InvoiceNo.SetFocus
Cancel = True
End If
End Sub
If unbound form, add the above statements (except Cancel = True) to your button click event.

How to cancel a form close in Close Event?

I'm sure this is very simple, but I can't find it. In the close event of an Access Form, how can I cancel closing the form? I have a test that counts the records in a table. If that table has records, I want to ask the user if they want to close or go back and work with them. So how do I cancel the close event?
You can use the Unload event:
GlobalVar ButtonClicked
Private Sub Form_Open(Cancel As Integer)
ButtonClicked = False
End Sub
Private ClickMe_Click(Cancel As Integer)
ButtonClicked = True
End Sub
Private Sub Form_Unload(Cancel As Integer)
If Not ButtonClicked Then
Cancel = True
End if
End Sub
Order of events for database objects
Study and try this code, it worked for me. Replace necessary variable names with your chosen names. Paste the code in the form_unload Event of your form.
WARNING!!!: After you perform this operation you will find it difficult to access your form in design and layout view
Private Sub Form_Unload(Cancel As Integer)
userresponse = MsgBox("Are you sure you want close? All your work wouldn't be saved", vbYesNo, "Database Information")
Select Case userresponse
Case 6
Cancel = False
'this line opens another form in my own case
DoCmd.OpenForm "EngMenu"
Case 7
Cancel = True
'this line keeps my own form open in my own case
DoCmd.OpenForm "UpdateForm"
Case Else:
MsgBox "You are not allowed to perform this operation", vbInformation, "Database Information"
End Select
End Subenter code here
Use the "Form_BeforeUpdate(cancel as integer)" event and set cancel to True.
Notice that you simply will not be able to close at all unless you add some logic to actually allow updating the database.

Access: How can I display a new record in one form after creating in a second form

Access 2007: I have one form with 100s of records displayed. I have a second form for editing or creating new records. When I return to the first form after adding a new record, I do On Activate: Me.Requery so the new record is added to the list, but I would like it to display on the screen with the record selector on the new record. Is there a way to do this? I am assuming that I save the ID in a global variable, but not sure what to do next. Thanks.
RESPONSE:
Thanks //t. Your answer got me going in the right direction. I will post my solution, which I think is more of a work-around. There has to be a better solution, but this seems to work.
Form 1 (list) -> Form 2 (edit/new) and create new record.
Private Sub Form_Current ()
glngID = Me.ID.Value
End Sub
Private Sub Form_Close
gstrLastForm = "Form2"
End Sub
When I close Form 2, Form 1 is active.
Private lngSelectedRecord as Long
Private Sub Form_Activate()
Me.Requery
FindSelectedRecord
If gstrLastForm = "Form2" Then
DoCmd.GoToRecord acDataForm, "Form1", acGoTo, lngSelectedRecord
End If
End Sub
Private Sub FindSelectedRecord()
...
Open recordset, move through records, increment counter, exit when ID found
...
lngSelectedRecord = intCounter
...
End Sub
This is usually done with a form opened in dialog mode. In most cases, you'd have a command button on your main form ADD NEW RECORD, that when clicked would run code like this:
DoCmd.OpenForm "MyAddForm", , , , acFormAdd, acDialog
This opens the form you use for adding a new record to a new, empty record, and pauses the code.
However, you need to know the PK of the record that was added, so you can't just close the form and let the code continue. So, the usual practice is to set the dialog form's Visible property to False, get the data out of it that you need, then close it and do what you want:
Dim lngPK As Long
DoCmd.OpenForm "MyAddForm", , , , acFormAdd, acDialog
If Forms!MyAddForm.Tag <> "Cancel" Then
lngPK = Forms!MyAddForm!PK
Application.Echo False
Me.Requery
With Me.RecordsetClone
.FindFirst "[PK]=" & lngPK
If Not .NoMatch Then
If Me.Dirty Then
Me.Dirty = False
End If
Me.Bookmark = .Bookmark
End If
End With
Application.Echo True
End If
DoCmd.Close acForm, "MyAddForm"
In the dialog form, you have to hide the default window controls so the user can't close it. Instead, have two command buttons SAVE and CANCEL. The SAVE button does this:
If Me.Dirty Then
Me.Dirty = False
End If.
Me.Visible = False
...and the CANCEL button does this:
Me.Undo
Me.Tag = "Cancel"
Me.Visible = False
The result is that you know that a record was not saved so you don't want to do anything to the calling form.
This is all standard Access user interface design, and it's by far the easiest and most bulletproof method for doing this kind of thing.
Use the on_close-method in your popup window to go to the inserted record,
something like:
private sub on_close()
'maby first check we're not undoing..
docmd.gotoRecord yourform,yournewid
me.close
(on osx currently, but I hope you get the concept...)

global click event handler (WithEvents)

I am trying to create a class module that will act as a global handler for when ever someone clicks one of the sixty textboxes I have in my form. Th textboxes represent a timecard for the week displaying information as clock in, clock out, lunch start,end,duration, total daily hours under each fo the seven days of the week. When someone clicks anyone of the boxes under a day all the boxes will unlock and enable so that the user can edit the information in them.
After scouring the web for a solutio of a global click event I found that I could create a class module that would handle the event without create a click event for every single text box that calls a seperate function to handle the event. The problem I am having is that my class module doesn't seem to be handling my event and was wondering if someone could suggest a solution to my problem. FYI, All my textboxes and locked and disabled to prevent data corruption. Below is my code:
''# Class module
Option Compare Database
Option Explicit
Public WithEvents TC_txtbox As TextBox
''# Set the textbox so that its events will be handled
Public Property Set TextBox(ByVal m_tcTxtBox As TextBox)
TC_txtbox = m_tcTxtBox
End Property
''# Handle and onClick event of the
Private Sub TC_txtbox_Click()
''# Find out the controls that where clikck
Debug.Print Form_TimeCard.ActiveControl.Name
Dim ctl As Control
For Each ctl In access.Forms.Controls
Debug.Print ctl.Name
Next ctl
End Sub
Form Code
Option Compare Database
Option Explicit
''# Global Variables
Public clk_inout As Boolean
Public settings
Public weekDict
Public weekOf As Variant
Public curDay As Variant
Public txtBxCollection As Collection
''# Event Handler for when the form opens
Private Sub Form_Open(Cancel As Integer)
''# Configure varaibles
Me.TimerInterval = 60000 ''# 10 sec Interval
weekOf = getFirstDayofWeek(Date)
curDay = Date
Set weekDict = CreateObject("Scripting.Dictionary")
Set settings = CreateObject("Scripting.Dictionary")
Set txtBxCollection = New Collection
''# Load Time Card Data
Call initSettings
''# Debug.Print "Work Day Goal " & settings.Item("Work_day_goal_hrs")
Call initDict
Call initTextBoxEventHandler
Debug.Print "Collection count " & txtBxCollection.Count
Call loadDates(Date)
Call clearDay
Call selectDay(Date)
Call loadWeeksData(weekOf)
Dim ctl As Control
Set ctl = weekDict.Item(Weekday(curDay)).Item("In")
If IsDate(ctl.Value) And (Not ctl.Value = "") Then
Me.but_clk_inout.Caption = "Clock Out"
Me.but_lunch.Visible = True
clk_inout = False
Else
Me.but_clk_inout.Caption = "Clock In"
Me.but_lunch.Visible = False
clk_inout = True
End If
''# Debug.Print "Work Day Goal " & settings.Item("Salary")
End Sub
Public Sub initTextBoxEventHandler()
Dim eventHandler As TextBoxEventHandler
Set eventHandler = New TextBoxEventHandler
Debug.Print "Collection count " & txtBxCollection.Count
Set eventHandler.TextBox = Me.txt_F_in
txtBxCollection.Add eventHandler
Debug.Print "Collection count " & txtBxCollection.Count
End Sub
Are you missing a Set? The public property set should be
Public Property Set TextBox(ByVal m_tcTxtBox As TextBox)
Set TC_txtbox = m_tcTxtBox ' dont forget the Set! '
End Property
I figure out My problem in the class module where I am setting the text box I forgot to add "TC_txtbox.OnClick = "[Event Procedure]"" VBA will not fire the custom even handler in my extended textbox if [Event Procedure] is not declared in the property of the event you would like to handle