MS Access stopping Insert command on form close - ms-access

I've been working in Access on a project and the longer I work on it the more disdain I have for it. I've been googling and attempting workarounds all day but I can't quite find what I am trying to do.
I have a form that is linked to a table. I have one button that will set the record of the form to a new record. If you start typing into any one of the text boxes the record starts updating, it's not really doing an insert at this point. However if I navigate off the record the update is called. there isn't anything I can do to stop the update from happening if the user does not want to add a new record that I can find. I'm using
DoCmd.GoToRecord , , acNewRec
To get the form to have all the text boxes go to a new record. At this point as soon as you start typing into any of the text boxes you can't go back. The autonumber will incriment and I have sku builder that concatenates the autoNumber to a prefix to act as a serial number.
What I need to do, is figure out a way to get the update to cancel if the user clicks close. I've tried to disable close, but you can always right click the bar and select close from there. This, along with anything that prompts the form to close commits that in process record and subsequently fires the "afterUpdate" event. I read some Microsoft documentation and "form_unload" is called first in the workflow but insert always goes before that. I've also tried to use the "beforeUpdate" but that fires as soon as you attempt to write into one of the boxes and not right before "afterUpdate"
It seems there is a pretty huge hole in the space between beforeUpdate and afterUpdate. I don't think this is a terribly complicated operation I'm trying to attempt here but perhaps I've been working on it for so long I have missed a simpler solution. I just need to cancel the insert operation or prompt the user that they are closing the form with an open record. If they hit no insert will fire, and yes it will.
Also, I have no appetite to rewrite for ADO because of my timeline. I just need to quickly throw some forms together and get it done. There are also a ton of fields that I don't want to setup for an ADO insert. They are all built but I have this one issue that is giving me grief and I need to apply this solution to all the forms.
TLDR - Stopping a databound form from inserting if the user has entered text and tries to exit the form.
Solution Edit:
Change the form to modal/popup and disable close.
Add a button to handle close.
Add boolean flag
Private Sub Form_BeforeInsert(Cancel As Integer)
NR = True
End Sub
Then add the code for the close button.
Private Sub cmdClose_Click()
Dim msgRes As VbMsgBoxResult
If NR Then
msgRes = MsgBox("Do you want to save the current new record?", vbYesNoCancel, "Closing form...")
If msgRes = vbYes Then
DoCmd.RunCommand acCmdSaveRecord
DoCmd.Close
ElseIf msgRes = vbNo Then
Me.Undo
DoCmd.Close
End If
End If
DoCmd.Close
End Sub

You can always do either a Me.Undo or DoCmd.RunCommand acCmdUndo on FormClose
OR if you still can't get it to work (I've spent a lot of time in Access and I know it can be finicky) you can do a DoCmd.RunSql ("Delete * from TableName where Id=NewID")
You can also try this:
DoCmd.DoMenuItem acFormBar, acEditMenu, 8, , acMenuVer70
DoCmd.Close , , acSaveNo
You may also want to disable the close button and create a button to cancel the record so you can put all the code in there. If you set the form mode to a dialog you shouldn't be able to right click close it.

Related

Application.forms.Count is Sometimes Inaccurate

I've been using Application.forms.Count to measure the number of forms that are open, and most of the time, this function works correctly. However, every once in a while, the count is wrong.
When I view the amount in a MsgBox, Access thinks that there are 2 forms open when it is obvious that only 1 is open. I have this running on my Form_Unload method, as the form should only close if it is the last form still open. I have made sure that there are no other instances of Access running when this is performed and no pop-ups or modals are open.
Dim Form As Double
Form = Application.forms.Count
MsgBox Form
If Form = 1 Then
'Nothing, form closes
DoCmd.ShowToolbar "Ribbon", acToolbarYes
Else
MsgBox "You cannot close this form right now."
cancel = True
End If
End Sub
As mentioned, this code does work most of the time, but it is a major hindrance when the wrong count occurs, and I'd like to find out what is causing it.
Using (Debug.Print Forms(0).Name and Debug.Print Forms(1).Name), I was able to figure out that the form I had open had somehow duplicated itself (though only one version of the form is open on the screen). While I have no idea how this happened, a simple restart of MS Access will get rid of the duplication. I'll add this supplemental if statement to prevent this in the future:
If formCount = 1 Then
...
Else
If Forms(0).Name = Forms(1).Name Then
'The duplication error is happening again
DoCmd.Close "FormNameHere"
End If
End If
After all this time, I believe I've found the root of this error. I did not previously know that forms could be opened in the background without actually appearing, and this happens when you read or write a project-level variable. I had checked on a variable and then tried to check the number of forms up, and there was two open despite only one being visible. I think a simple DoCmd.Close "formNameHere" after checking on the variable would be the best way to go about this problem.

Action on Form Save

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

"Operation not supported in transactions" While Copy/Paste Records in Access

When copy / pasting cells from Excel into an Access Subform, when no parent record has been created, will result in an error - as expected.
The problem, is that after this error occurs access gets locked in a state where all subsequent data that is entered results in an Operation not supported in transactions error. When you open the table, you can see that the newly added data is not yet in the table - so it appears as if Access is in fact engaged in some sort of transaction.
I have tried hitting 'Save' .. 'Refresh' .. and even adding an AfterInsert event to force a commitTrans() but have had no luck - and Access claims there is no transaction underway
There is no error if the records are entered manually. The problem only seems to occur when pasting records. I'm guessing that Access is creating a transaction to handle the multiple record operations and is not properly recovering from the error.
I can remove the 'Required' flag and it will work - but then you have orphan records. I was thinking that perhaps an After Insert Data Macro could be used to add a new Batch with a default batch Name, and auto-fill the new BatchID into the Items table. I am not certain how that would be done however.
I did try to remove the 'Required' flag and trap for the error using a Before Change Data Macro - but while it cut down on the errors - it still produced the same Operation not supported in transactions error.
I have looked up the error on the Microsoft Knowledgebase, but did not find anything specific to my circumstances. I searched stackoverflow for the error message and found nothing.
I created a new database and was able to replicate the issue.
Steps to Replicate
Set up the Database
Create a new ACCDB database in Access 2010
Create a Table called 'Batches', with the following fields:
BatchID (AutoNumber) (Primary Key)
BatchName (Text)
Create a Table called 'Items', with the following fields:
RecordID (AutoNumber) (Primary Key)
BatchID (Long Integer)
Set Required attribute to True
Data - Text
Create a Relationship, linking Batches.BatchID to Items.BatchID
Include all Records from Batches, and matching records from Items
Enforce Referential Integrity
Cascade Updates / Deletes
Create a Form called 'Form'
Set the Recordsource to Batches
Add in the BatchID and Batch name Textboxes
Add in a SubForm/SubReport Control
Set Source Object to "Table.Items"
Set Link Master Fields to "BatchID"
Set Link Child Fields to "BatchID"
Set "Filter On Empty Master" = Yes
Create sample data (Using the Form)
Create a New Record in Batches.
Set BatchName = "Test"
Create a New Record in Items.
Reference the Batch Record.
Set Data = "Test"
As you can see, by hand this works fine.
Copy and Paste Data From Excel
In Excel
From A1-A10 enter one letter per cell running down: A,B,C,D,E,F,G,H,I,J
Highlight the cells A1-A10
Copy (Control+C)
In Access, using the Form:
Add a New Batch Record. It should say "(New)" in BatchID TextBox
Do NOT enter a Batch Name
In the Sub-Form, click the record selector (*) for the new record to select the entire row. Type Control+V to paste.
Click OK for "You must enter a value in the 'Data.BatchID' field. error
This error may repeat. Keep Clicking OK
If it asks "Do you want to suppress further error messages..." answer Yes
Click OK for the "Records that Microsoft Access was unable to paste have been inserted into a new table called 'Paste Errors.' notification
Fill in the Batch Name Textbox with "TestName"
Try to gracefully recover. Hit Escape. Change the Record.
At this point - you should see the BatchID entered, the Batch Name, and the New Data. Everything appears to be working as expected. If you try to refresh or navigate to another batch record - you will get the error Operation not supported in transactions. Access will continue to display this error message until we close and reopen the form. The data you pasted will not have made it into the database.
Normally someone with a bit of tech savvy will realize something isn't going well and close out of the database and re-open ... but unfortunately I have users that play "whack-a-mole" with any popup boxes and then try to continue on - so I'd like to make things as bulletproof as possible.
Desired Solution
I'd like a workaround to the problem, that won't eventually lead to other quirks with access, duplicate values, etc.
In my own experience, using VBA to 'fix-up' keys isn't reliable. Data macros seem to be a lot more reliable - but they can be tricky to set up - they aren't very mainstream yet (I'd say there should be a ms-access-data-macros tag on stackoverflow but there isn't yet)
Suggested workaround:
In the [Batches] table, set the Required property of the [BatchName] field to Yes.
Change the Tab Stop property of the [BatchID] text box to "No". That will give the [BatchName] text box the default focus when the form opens.
Have the On Current event of the form give the [BatchName] text box the focus for new records (IsNull(Me.BatchID) = True).
Make the form dirty when the [BatchName] text box loses focus.
Option Compare Database
Option Explicit
Dim DeletePending As Boolean
Private Sub Form_Load()
DeletePending = False
Me.ItemSubForm.Enabled = False ' Disable Subform by default
End Sub
Private Sub Form_Current()
If IsNull(Me.BatchID) Then
Me.BatchName.SetFocus
' Disable Subform if BatchID is NULL
Me.ItemSubForm.Enabled = False
Else
' Enable SubForm Otherwise
Me.ItemSubForm.Enabled = False
End If
End Sub
Private Sub Form_BeforeDelConfirm(Cancel As Integer, Response As Integer)
DeletePending = True
End Sub
Private Sub Form_AfterDelConfirm(Status As Integer)
DeletePending = False
End Sub
Private Sub BatchName_LostFocus()
If IsNull(Me.BatchID) And Not DeletePending Then
Me.Dirty = True
End If
End Sub
When the user clicks on the subform (and off the [BatchName] text box) they make the form dirty and BatchID gets a value. Then they can paste and they don't get the "You must enter a value..." message for [BatchID]. If they haven't entered a [BatchName] value they now get prompted for it (because it is now Required), but at least they can gracefully recover from that.
Updated 2013-11-09 18:40 UTC:
In playing around with this a bit more I discovered two quirks:
If you deleted the last parent record Access would do it and then immediately create another new one in its place.
If you navigated into the "new" parent record and then immediately backed out (e.g., via the PageDown and PageUp keys) Access would create a new record and then leave you on that record with the form dirty. Hitting Esc and then moving off the "new" record worked, and it didn't cause any errors, but it certainly could be confusing to the user.
I have updated the answer to try and address these issues. I added a bit more VBA code to track "DeletePending" status. I also added the requirement that [BatchName] be set to Required = Yes in the [Batches] table. The latter makes it slightly more intuitive (albeit slightly more annoying) if the user move into the "new" record and then immediately moves back out again.
I struggled with this for a long until I finally understood what is happening to cause this error. It would take an article of considerable length to go into the details rather than a blog response to explain it all. If anyone is interested they can leave a contact method and I will contact them and explain it in detail.
However, for those who want to figure this out, I can save you a lot of time by giving you the idea behind the issue:
When you are performing a data transaction in a bound sub-form, you cannot reference other objects. The internal code of the transaction process does not allow this. For example, if you have code in the Form_BeforeUpdate event that attempts to open another form while in the midst of a sub-form data transaction, you will get error 3246. You can have code that creates variables, set values, references the sub-form controls, etc. but you cannot go out to another object.
This makes sense if you think about it. Who knows what the user or code might do once it gets itself into another form or object. It may never come back or get involved in other errors that leave the transaction hanging. That's why the transaction must complete first.
This means that you must trap and dismiss error 2115 that is caused when a user tries to click on an area outside the sub-form while in the midst of a transaction. This usually occurs during large copy and paste where the user becomes inpatient or starts to proceed to another record while still in the midst of the sub-form transaction.
I know that this is an old storry, I was also strugling with this.
My solution was to re-desing the process so the user closes the form receiving the data in order to save the records inserted. Is nor elegant but efficient and saved me from guessing each and every event which could occure.
to avoid the undesired internal transaction is enough to
code the sub-form Error event with:
Private Sub Form_Error(DataErr As Integer, Response As Integer)
Response = acDataErrContinue
End Sub
A general approach for intercepting sub-forms events is
' parent form code
' ---------------------------------------------------
Private WithEvents subFormObj As [Form_Sottomaschera items]
Private Sub Form_Open(Cancel As Integer)
Set subFormObj = Me.Sottomaschera_items.Form
End Subcode here
' asynchronous way
Private Sub subFormObj_isInserting()
Me.batchName = vbNullString ' this resolves the new ID issue
End Sub
' synchronous way
Public Sub subFormInserting()
Me.batchName = vbNullString
End Sub
' sub-form code
' ---------------------------------------------------
Public Event isInserting() ' for asynchronous way
Dim parentFormObj As Form_Maschera1 ' for synchronous way
Private Sub Form_BeforeInsert(Cancel As Integer)
parentFormObj.subFormInserting
RaiseEvent isInserting
' Cancel = True
End Sub
Private Sub Form_Error(DataErr As Integer, Response As Integer)
Response = acDataErrContinue
End Sub
Private Sub Form_Open(Cancel As Integer)
Set parentFormObj = Me.Parent
End Sub
where [Maschera1] is the main form and [Sottomaschera items] the sub-form.
Unfortunately it doesn't resolve the paste issue.
To definitely resolve issue you need to save parent record + a SetFocus trick, either synchronous or asynchronous:
Private Sub subFormObj_isInserting()
Me.batchName = vbNullString
DoCmd.RunCommand acCmdSaveRecord
' DoEvents
Me.batchName.SetFocus
End Sub
Public Sub subFormInserting()
Me.batchName = vbNullString
DoCmd.RunCommand acCmdSaveRecord
' DoEvents
Me.batchName.SetFocus
End Sub
I don't understand what exactly do you want to achive, so this answer may be inadequate.
You can
set your subform property .Visible = False when Me.NewRecord = True to prevent entering data into it
force saving record of the main form to the table after adding Batch name by setting .Dirty = False in After Update event triggered by pressing Enter. It allows also to avoid not saving records of a subsform to a table after adding few records to a main form in some databases, at least with dynamical subform .Recordsource.
set your subform property .Visible = True
The code below works for Form View and perhaps should be extended (develop) somehow for other Views.
Set .Tag of the subform Child and all other controls you want to hide / show to "a".
Private Sub Form_Current()
If Me.CurrentView = 1 Then
If Me.NewRecord = True Then
ShowControls False
ElseIf Me![Items subform Child].Visible = False Then
ShowControls True
End If
End If
End Sub
Private Sub BatchName_Text_AfterUpdate()
Dim NewRecordx As Boolean
If Me![Items subform Child].Visible = False And Me.CurrentView = 1 Then ShowControls True
NewRecordx = Me.NewRecord
If Me.Dirty Then Me.Dirty = False 'save the new record to the table
If Me.CurrentView = 1 And NewRecordx Then Me![Items subform Child].Form.Requery
End Sub
Private Sub ShowControls(bVisible As Boolean)
Dim ctl As Control
For Each ctl In Me.Controls
If ctl.Tag = "a" Then ctl.Visible = bVisible
Next ctl
End Sub
I reported this as a bug through Microsoft Premier Support a number of years ago with a concise standalone repro case. It appears this was finally resolved in October 2021 with KB5001978.

Can't set field value in Access form with new record

When I open my access form ActivityTracker to a new record, I want it to auto-populate the field *start_time* with the current time now().
Private Sub Form_Open(Cancel As Integer)
If IsNull(Form_ActivityEntry.Start_time) And IsNull(Form_ActivityEntry.id) Then
Form_ActivityEntry.Start_time = Now()
End If
End Sub
This throws an error "You can't assign a value to this object" and stops execution.
I can fix the error by going explicitly to a new record
Private Sub Form_Open(Cancel As Integer)
If IsNull(Form_ActivityEntry.Start_time) And IsNull(Form_ActivityEntry.id) Then
DoCmd.RunCommand acCmdRecordsGoToNew
Form_ActivityEntry.Start_time = Now()
End If
End Sub
or by
Private Sub Form_Open(Cancel As Integer)
If IsNull(Form_ActivityEntry.Start_time) And IsNull(Form_ActivityEntry.id) Then
Me.Recordset.AddNew
Form_ActivityEntry.Start_time = Now()
End If
End Sub
but either of these causes a popup warning, "You can't go to the specified record."
I've tried to suppress the warning with this
DoCmd.SetWarnings False
DoCmd.RunCommand acCmdRecordsGoToNew
DoCmd.SetWarnings True
or by setting up error handling,
On Error GoTo Err_SomeName
but I still get the warning.
AllowAdditions is set to True. Recordset type is Dynaset.
Otherwise, everything works fine with this form.
What am I doing wrong? Is there an event called "opening new record" as opposed to "open form"?
Thanks for your help!
The problem here is still no one correctly answered why the code as posted does not work.
The REASON why is that you are using the on-open event.
Unlike .net and most systems access has a GREAT design in which you have two events occur when you an open form event (which can be canceled) and an on-load event.
This great design of Access means that code to test conditions and prevent the form load can be placed in the on-open event. If you look CLOSE you will see that the on-open even has a cancel. If you set cancel = true, then the FORM WILL NOT LOAD, AND WILL NOT DISPLAY.
So, you can test for no data, or for user rights or whatever, and huts cancel the form loading. If you can cancel the form loading, then it would make little sense to allow modifying the value of bound controls – as such all bound controls are READ ONLY.
You are NOT ALLOWED to change the values of BOUND controls in the on-open event. This is TOO soon and is by DESIGN and intention of the product.
So, testing of conditions to prevent the form load goes in on-open.
This thus suggests that setup of variables,, setup of controls, setting values of controls and your basic form startup code belongs in the ON-LOAD event. On-open is too soon.
And of Couse if your code is going to cancel a form load, then it is logical that all of your forms startup and setup code SHOULD NOT and does not need to run.
In summary
On-open event:
Can set cancel = true
Code goes here to test if you wish to prevent the form from loading and being seen by the user.
On-LOAD event:
All your startup code, setting of variables, setting of controls etc. is now recommended and possible.
And this simple design in Access ALSO means that as a developer you now know where to look for the code that will prevent and cancel the form load. Of course without this basic understanding of the difference and WHY two events exist in Access then the confusing of the poster and answers given becomes obvious.
I suggest you set the default value of the control to Now() or Date(). The default value only applies to new records and once you complete any other field, the value will be assigned.
In this particular case, there is even an argument for setting the default value of the field in the table.
Please do not use Set Warnings: What's the difference between DoCmd.SetWarnings and CurrentDB.Execute
I am not a fan of moving to a new record on a form. There are too many data holes that can occur and you run into issues like yours.
I reccomend a blank, unbound form, with a textbox, calander conrol, number up down... etc for each of the fields that you want to add. This way you can type check each of the fields or do other checks against what you want. Then, when the user is happy, then add the record with an insert query.
However, for the question that you asked. It looks like you are trying to assign a value to the bound field. Try assigning the value to the object the field is bound to.

How do I not commit a record on MS-Access?

I have a bound form in microsoft access that allows a user to add data to a table. I want to place a "cancel" button on the form, that if clicked, will stop the record commitment.
What is the command I need to cancel the record commitment?
It's been a few years since I've had to work with Access, but you should be able to undo changes to the current record using the command:
DoCmd.RunCommand acCmdUndo
or
Me.Undo
?
I have a similar situation where I want the user to confirm whether to save changes, and have used the form's BeforeUpdate event to ask the user if they want to save changes. It seems to work pretty well, at least in my case.
Private Sub Form_BeforeUpdate(Cancel As Integer)
If MsgBox("Do you want to save changes?", vbYesNo, "Confirm change") = vbNo Then
Cancel = True
Else
'Do Nothing
End If
End Sub