I have a Database in Access 2010 where the 1st field is a unique membership number. The field is set Indexed, no duplicates. If a duplicate is entered on the input form no error message is displayed and nothing else works until a unique number is entered. I need the code that would trap the error on the number field losing focus so that a message box would tell the user the problem and then set focus back to the number field or let the user cancel all input.
This is what I have and it causes Datatype errors
Private Sub Grumpy_No_BeforeUpdate(Cancel As Integer)
If DLookup(Str("[Grumpy_No]"), "Grumpy", Str("[Grumpy_No]") = Me!Str(Grumpy_No)) Then
MsgBox "Number has already been entered in the database."
Cancel = True
Me!Grumpy_No.Undo
End If
End Sub
Any clues as to where I am going wrong would be very much appreciated
Looks to me like you're heading in the right direction but you need to fix your DLookup expression. Its third argument must be a single string. I don't see why Str() is useful --- just build a string. Actually I would use DCount instead of DLookup, but then the same issues still apply.
Private Sub Grumpy_No_BeforeUpdate(Cancel As Integer)
If DCount("[Grumpy_No]", "Grumpy", "[Grumpy_No] = " & Me!Grumpy_No.Value) > 0 Then
MsgBox "Number has already been entered in the database."
Cancel = True
'Me!Grumpy_No.Undo '<- consider allowing the user to see the value which failed
End If
End Sub
Note, if you can make Grumpy_No an autonumber primary key, Access will supply unique values automagically, so you wouldn't need your own code to check those values.
Related
I have a data entry form where two of the fields are contained in a subform within the main form. We are using barcode scanners to enter data. These barcodes essentially press enter after every scan, however sometimes our barcodes are damaged and, when scanned, can sometimes add random special characters. When ever someone scans a barcode in the "Part Number" field that contains any of the following characters (+, #, %, =) i have a msgbox that pops up alerting the person scanning that the barcode didn't read correctly. After the message pops up I want to refocus on the "Part Number" field so that they can rescan the barcode or just type the data manually (if the barcode keeps being buggy) rather than having to click back in to that field, deleting the wrong data, then re entering the data. My code is below:
Private Sub Part_Number_LostFocus()
Dim RegEx As Object, MyString As String, Answer As Integer
Set RegEx = CreateObject("VBScript.RegExp")
MyString = [Forms]![frmInventoryCountEntry]![tblInventoryCount subform].Form![Part Number]
With RegEx
.Pattern = "[+#%=]+"
End With
If RegEx.Test(MyString) = True Then
MsgBox "Part Number Contains Invalid Character", vbOKOnly
DoCmd.GoToControl "tblInventoryCount subform"
DoCmd.GoToControl "Part Number"
I've tried set focus as well and no matter what the cursor always goes to the next field rather than refocusing on the "Part Number" field. I'm sure the solution is simple but I've tried a bunch of different things and I'm starting to get frustrated :( can anyone help me out? Btw, I'm using access 2003
Have you tried .SetFocus?
You might also consider using a Before Update event and then cancelling if the string contains those symbols. That way the focus never leaves the Part Number control.
Many of my database forms use a listbox with 4 columns and several textbox/combobox controls which are mapped to their respective fields using the ‘control source’ in the property sheet.
I validate these using ‘If Trim(control.value & vbnullstring) = vbnullstring then’ and then ask the user to enter their ID number in a textbox (also mapped using control source) if the controls are not null.
An issue was raised with my database last night where a user managed to enter a record into the database with just their user number (my time field also contained an entry as its set to default = Now()) but every other field was blank.
The code below is what I’ve been using to prevent accidental blank records:
Private Sub Form_BeforeUpdate(Cancel As Integer)
If Not blnSave Then
Cancel = True
Me.Undo
End If
End Sub
Then my Submit_Click() button event contains this to save the record…
blnSave = True
DoCmd.GoToRecord , , acNewRec
blnSave = False
I can’t recreate the problem myself as my validation won’t allow you to even enable the ‘User ID’ control until something is selected in the listbox and all textboxes contain a value. What could have caused this? Would it be better to add records to the table using a query rather than relying on the Control Source property or am I focusing on the wrong thing?
Maybe the user circumvented your form by accessing the database table directly? Could that be possible?
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.
Is there a technique I'm overlooking when coding my form?
I have a pair of cascading combos.
ComboSource filters the options available for choice in ComboInformation
ComboInformation sets the contents of a mandatory field in a table
The rowsource for ComboSource is:
SELECT tblSource.SourceID, tblSource.Source
FROM tblSource
ORDER BY tblSource.Source;
The rowsource for ComboInformation is:
SELECT tblInformation.InformationID, tblInformation.SourceID, tblInformation.InformationSelector
FROM tblInformation
WHERE (((tblInformation.SourceID)=[ComboSource])) OR ((([ComboSource]) Is Null))
ORDER BY tblInformation.InformationSelector;
It is valid to select a value in ComboSource which results in the .Listcount for ComboInformation being zero. The user can choose to type a new value in ComboInformation and be prompted to create the relevant item in the Information table linked to the ComboSource entry in the Source table, or they can choose to navigate back to ComboSource to select a different Source.
The problem arises when a user edits an existing record and changes ComboSource to a value for which there is no associated Information records. In the After_Update event for ComboSource I have the following code, intended to update ComboInformation to reflect the new source and force the user to select a new value for ComboInformation (otherwise they could save the record with the old value of Information without realising it).
Me.ComboInformation.Requery 'Reflect the current source
'Set a default value for ComboInformation
If Me.ComboInformation.ListCount > 0 Then
Me.ComboInformation.DefaultValue = Me.InformationTitle.ItemData(0)
Else
On Error Resume Next 'Ignore inevitable error
Me.ComboInformation.DefaultValue = Null
On Error GoTo PROC_ERR 'restore normal error handling
End If
'Force the user to update Information by setting content to "" -- if this isn't done, the
'record can be saved with the 'previous value of Information and the user may not realise
'they haven't made any change
ComboInformation.SetFocus
If Not Me.NewRecord Then
If Me.ComboInformation.ListCount = 0 Then
'Clear info to force it to be updated
On Error Resume Next 'Ignore inevitable error
ComboInformation.Text = "" 'Set it to an invalid value
ComboInformation= Null
On Error GoTo PROC_ERR
Else
ComboInformation= ComboInformation.DefaultValue 'set it to a valid value
End If
End If
End If
If the user selects a source for which there are no valid information choices, and then chooses to create a new value for Information, no problem. But if they decide to navigate back to ComboSource instead, they get an error message insisting they complete ComboInformation, which they can't do. They can undo the change they made to get back to the previous state, but that isn't an intuitive response to an error message telling them to complete ComboInformation.
Is there either a way to allow them to navigate back to ComboSource when ComboInformation is invalid or another way to force them to update ComboInformation after updating ComboSource before saving the record that doesn't have this issue?
Update about the error message: I can trap it in Form_Error although the error message isn't generated there -- no other procedures in the stack. The error message is the message associated with the Information field in the Information table (Validation rule: Is Not Null; Validation text; Every piece of evidence must derive from a piece of information)
Further update: I could handle the error (3316) in the Form_OnError but there will be occasions when isn't a 'valid error' that I don't want to ignore...
Other things tried so far:
Refreshing the page doesn't help.
Demonstration database
There's a database to demo the problem at https://s3-eu-west-1.amazonaws.com/genquiry/test.accdb
Open the only form in the database, set the Source to S3 and attempt to navigate back to Source to select a different value.
I think my last message kind of hit it. Instead of having the Information combo be bound, set it's Control Source dynamically in the AfterUpdate event of the Source field. You're trying to forcefully fill a bound field with a NULL when there's a "Is Not Null" validation set up in the table design. If you unbind it, you should be able to get it to work.
Do you have to clear the second combo box on the after update? I wrote a quick test one and it seems to clear it automatically.
I do not think that the issues is whether or not the combo box is bound, but that you are setting focus to the combobox before you might have items in it or knowing if the user wants to add items to it which could be done before they enter.
Private Sub cboSource.AfterUpdate()
cboInformation.Requery
' if the combobox doesn't refresh to be empty or have a default after the
' requery check the row source for information.
' This way you don't have to focus the second combo box and run into problems
' when you leave it
Dim db as DAO.database
Dim rs as DAO.recordset
Set db = CurrentDB()
Set rs = db.OpenRecordset("SELECT tblInformation.InformationID,
tblInformation.SourceID, tblInformation.InformationSelector
FROM tblInformation
WHERE (((tblInformation.SourceID)= " & ComboSource.value & "))
OR (((" & ComboSource.value & " ) Is Null))
ORDER BY tblInformation.InformationSelector")
If rs.recordcount <> 0 then
cboInformation.SetFocus
Else
' Set and empty value for the combobox so they can't accidentally save the
' record with an old value.
' Prompt them to either select a new value that has records or create a
' new one here
End If
rs.close
db.close
Set rs = nothing
Set db = nothing
end Sub
One possibility:
If I set ComboInformation and ComboInformation.DefaultValue to -1 rather than Null when ComboInformation.Listcount = 0, this avoids triggering the table validation rule (Is Not Null) when the user navigates back to ComboSource. However, it is still an invalid value (as it breaks data integrity between tables) and the user is prevented from exiting the form or saving the record until a valid value is entered (by choosing a different value for Source or creating a value in the Information list).
It works, but it isn't an elegant solution and depends on -1 always being an invalid value for the Automatically-generated InformationID field in the linked Information table, which I don't believe is a safe assumption...
However, if I modify the method slightly to find the lowest existing InformationID field (call this LOWID) and then use LOWID-1 to set ComboInformation, that also works, and always produces an invalid value for the period it is in use.
I am very new to MS Access. I have created a database My_database.mdb with a form that displays some fields. The primary key is a field named Ticket.
When I hit the "Search" button, I want to take the numerical value from the Ticket's text field and search for that number in database under 'Ticket' field.
If the number exists, I want to execute some set of instructions, if it does not, I want to execute other set of instructions.
What code should be in the Command_Click function?
At first blush, seems this would do the trick:
(I'm assumming "Ticket" is a numeric data type)
If Not IsNull(Dlookup("[Ticket]", "[My Table]", "[Ticket] = " & [Ticket].Value)) Then
'It found the record, so go do something...
Msgbox "Woo Hoo...I found it!"
Else
'Didn't find anything, so do something else...
Msgbox "Nope. Nada. Nothing."
End if
Replace the above with your table and field reference as applicable.