I am writing a VBA function to import data from one table to another in Access. The table I'm importing into has more strict data constraints (i.e. types, size etc.), so I'm expecting a lot of errors.
Rather than sift through every VBA error that comes up, I want my recordset loop to skip the entire current record and make a note of it in a separate table whenever it runs into an error. So every other line I've inserted On Error GoTo RecordError. But for some reason it's not handling every error. My code just breaks and tells me what the error is. I have the "Break on Unhandled Exceptions" option checked already.
Here's a screenshot that should explain it.
Why would it be breaking on the line immediately following an Error handler?
I think you're not understanding how VB(A) error handling works. Follow these principles:
An On Error... statement only applies to the routine (Sub or Function) in which it appears (though it will also catch errors that "bubble up" from routines that are called from within the routine in which you use it).
On Error sets a state. That is, Once you issue an On Error... it remains in force for the rest of the routine, unless superceded by a new On Error....
There are four forms of On Error...:
On Error GoTo <label>: <label> must be defined in the same routine, by writing the label name immediately followed by a colon (:) on a line by itself.
On Error Resume: immediately retries the error-throwing statement. Hardly ever used, since it's potentially infinite.
On Error Resume Next: ignores the error & continues. Sometimes useful at the end of routines for cleanup (for instance, if you want to Close a Recordset that may or may not be open). Alternatively, this form can also be used if you check the Err object immediately after any potentially error-throwing line (if Err.Number is zero (0), the statement succeeded without throwing an error). This is way too much work for most situations.
On Error GoTo 0: turns off error handling.
Given this, it's usual to place the On Error... statement immediately followng the routine's declaration (the Sub or Function statement), though some people put their Dim statements in between. If you want to temporarily change the manner of error handling within a routine, put the "new" one right before the code to which it is to apply, and (if used), the "revert" (reissuing the original), right after.
Even given all that, I have no idea why it would break on the error-throwing line when "Break on Unhandled Errors" is selected, unless you've managed to confuse it so much that it thinks there's no active error handling (and I'd be surprised if it compiled if that were the case).
Note that David Heffernan gave you the essential part of this in his answer, and it was here before mine....
The reason it is not working is because you cannot use On Error Goto ... within an error handler.
see http://www.cpearson.com/excel/errorhandling.htm
you cannot use On Error to skip a few lines, instead on error should go to a error handler which then resume's to the desired next line (in your example you could probably get away with one error handler which contains a resume next which will take you back to the next field).
thanks to Tim Williams on this question: The second of 2 'On Error goto ' statements gets ignored
and BTW ParseInt on a ZIP will destroy zip codes that begin with a 0, zipcodes should probably be treated as text.
You need to place the On Error line before the code whose errors you wish to handle.
What's more you only need to have one On Error line. The error handler then stays active until the subroutine exits or you execute another On Error statement.
Error handling with VBA is a real PITA. I'd propose you to have a look at this answer to the 'MS-Access, VBA and error handling' question, and have it adapted to your own situation. You can easily write down some code that will store all your error messages in a table, building a de facto error reporting system.
Setting the debug mode to 'break on all errors' will make the program execution stop at the line that causes an error even when the error handler has been correctly written. This can be confusing as it appears that error handling is not working.
Nobody has really answered your question.
Say your code is something like this (a skeletal framework):
Public Sub MySub()
On Error GoTo errHandler
Dim rs As DAO.Recordset
Set rs = CurrentDB.OpenRecords([SQL SELECT])
If rs.RecordCount >0 Then
rs.MoveFirst
Do Until rs.EOF
[do whatever that produces the error]
errSkipToNext:
rs.MoveNext
Loop
End If
exitRoutine:
If Not (rs Is Nothing) Then
rs.Close
Set rs = Nothing
Exit Sub
errHandler:
Select Case Err.Number
Case X, Y, Z ' where these are error numbers you want to ignore
Err.Clear
' do whatever it is you need to do in order to record the offending row
Call RecordError(rs!PK, Err.Number) ' PK is a field that identifies the bad record
GoTo errSkipToNext
Case Else
MsgBox Err.Number & ": " & Err.Description, vbExclamation, _
"Error!"
Resume exitRoutine
End Select
End Sub
In this code, you use a SELECT CASE in your error handler to decide which errors you want to ignore. In my code framework above, I listed the error numbers as X, Y, Z, but you'd replace that with the real error numbers you want to ignore, instead.
You don't want to ignore every single error because you might end up ignoring important errors elsewhere in your subroutine. If you don't want to figure out what the limited number of errors you want to ignore happen to be, I would suggest that you set a flag at the beginning of the code block that produces the errors you want to ignore, then use an `If bolErrorInCodeBlockToIgnore Then to decide if you're ignoring all errors or not. Something like this:
Public Sub MySub()
On Error GoTo errHandler
Dim rs As DAO.Recordset
Dim bolErrorInCodeBlockToIgnore As Boolean
Set rs = CurrentDB.OpenRecords([SQL SELECT])
If rs.RecordCount >0 Then
rs.MoveFirst
Do Until rs.EOF
bolErrorInCodeBlockToIgnore = True
[do whatever that produces the error]
errSkipToNext:
rs.MoveNext
Loop
End If
exitRoutine:
If Not (rs Is Nothing) Then
rs.Close
Set rs = Nothing
Exit Sub
errHandler:
If bolErrorInCodeBlockToIgnore Then
Err.Clear
' do whatever it is you need to do in order to record the offending row
Call RecordError(rs!PK, Err.Number) ' PK is a field that identifies the bad record
bolErrorInCodeBlockToIgnore = False
GoTo errSkipToNext
Else
MsgBox Err.Number & ": " & Err.Description, vbExclamation, _
"Error!"
Resume exitRoutine
End If
End Sub
I would much prefer the first, as I'm a firm believer in only ignoring known errors, not any old error that happens. But it might be quite difficult to come up with tests that will produce all the possible errors you want to ignore.
I have seen error handling fail too. Here is one example.
Public Function Have(ByVal item As Variant) As Boolean
'Have = Have data. Simplifies handling nulls and empty strings in validation code
On Error GoTo Procerr
If IsNull(item) Then
Have = False
**ElseIf Len(Trim(item)) = 0 Then 'Faster than Item <> ""**
Have = False
ElseIf item = 0 Then
Have = False
Else
Have = True
End If
exitproc:
Exit Function
Procerr:
'Errors sometimes occur if an unbound control is referenced
Have = False
End Function
The code sometimes fails on the line flagged with **. Here is the error message.
Note that the error handler has failed. In this case, the form that called the code returned had its recordsource set on the fly to an empty recordset, hence the fields on the screen are not visible. The form is a continuous form, so records and fields are not visible when the form is loaded with an empty recordset. The have() function is not directly called by my code, but somehow seems to be triggered by the me.requery method. The have() has been called hundreds of millions of times in my code but this is the only instance that causes it to fail and the error handler is not involked.
To Lance Roberts re original question. utf-8 unicode can sometimes play havoc with ms-access as it seems to be allow data to be confused for instruction codes (my guess). utf-8 can get into your data if data was originally loaded from a text file. utf-8 with a byte order mark (BoM) is particularly nasty. When you run some procedure that works with the data, strange errors can occur and it may look like your file has become corrupt. In other cases, text handling functions give wrong answers, e.g. Mid() will see the BOM and if you specify a starting point will start at the BOM, but Len() ignores the BOM. I am speculating that if you have this issue, then ms-access may not handle errors correctly. I have had similar issues importing data and importing utf-8 as ANSI was the cause. Note that utf-8 and ANSI are identical most of the time for plain English data so your errors may not be on every line. My errors were mostly with time-date fields. Try exporting the data first and then forcing it to be ANSI and remove any BoM and and reimporting it.
Related
Let me start by saying I am not at all familiar with Access or VBA. However, I am the IT guy and at some point someone created an MS Access database that does a thing and now I have to support it.
We have a database that upon opening deletes any old data and re-runs the external query that makes this application work. Occasionally whatever state the program exited out in that table already does not exist. This causes MS Access to hang on the delete line and I have to run the debugger, comment out the DoCmd.DeleteObject line, re run, and then un-comment the line to let the user continue with their day.
I want to add in some sort of conditional statement, but anything I've been able to google in terms of If...Then statements or 'TableExist' type functions always causes an error to be thrown that I haven't defined the function. My best guess is I'm nesting this incorrectly or I'm not calling some sort of external function correctly, but as I said my VBA knowledge is extremely limited.
This code executes on startup:
Public Function Startup() As Integer
DoCmd.Close acForm, "soLookup"
DoCmd.DeleteObject acTable, "sales_order"
DoCmd.RunSavedImportExport "Import-sales_order"
DoCmd.OpenForm "soLookup"
End Function
Its the
DoCmd.DeleteObject acTable, "sales_order"
Line that causes things to fail.
I've attempted to restructure this several times based on several examples I had found, but I'll only bother with one below
Public Function Startup() As Integer
DoCmd.Close acForm, "soLookup"
If TableExists("sales_orders") Then
DoCmd.DeleteObject acTable, "sales_orders"
Else
'Do nothing
End If
DoCmd.RunSavedImportExport "Import-sales_order"
DoCmd.OpenForm "soLookup"
End Function
Nothing I seem to try seems to give me any result other than an error of some sort. All I want to do is add a conditional statement to this 'Startup' bit that checks if the "sales_order" table even if exists, and if it doesn't, then to just move on to the next comment and forget the DoCmd.DeleteObject. How can I achieve this functionality?! Thanks!
The TableExists function is not a standard function in Access. You need to define it yourself.
There are two main ways to define such a function, by trying and error trapping, or by iterating through all tables and checking names.
Way 1 (error trapping):
Public Function TableExists(TableName As String) As Boolean
On Error Resume Next
TableExists = CurrentDb.TableDefs(TableName).Name = TableName
End Function
Way 2 (iterating collection):
Public Function TableExists(TableName As String) As Boolean
Dim td As DAO.TableDef
For Each td In CurrentDb.TableDefs
If td.Name = TableName Then
TableExists = True
Exit Function
End If
Next
End Function
Define either of these functions in a public module, and your last approach should work
I will instead modify the query to import the source data into a new table (sales_orders), which will overwrite existing data when the query runs and so I don't have to delete the table and have additional check for TableExists
I'm using the form_error event in MS Access to catch errors, which gives only a dataerr integer and not a description like a vba error, however the MS Website gives the following description of the event:
You can use the DataErr argument with the Error function to map the
number to the corresponding error message
But doesn't actually give an example of this and I'm struggling to figure it out.
The specific situation is that the user will be updating records in a subform, some fields may break referential integrity rules, the number will only tell me that a referential integrity rule has been broken, whereas the description will give me the table name, which allows me to give a prescriptive error message relating to the field where the error occurs
Is there an easy way to do this?
Create a separate function to perform all of your validation and message the user with anything missing. Run that in the form's beforeupdate event and cancel the event if needed. That would probably eliminate many of your form_error's. In the form_error event, you could call the same function. It is has found an issue, suppress the default message. Any other error that pops up you'll can handle separately if needed.
Normally it is just to pass the number to Error to obtain the description:
MsgBox Error(DataErr)
In short
As far as I know #AVG's solution is probably the best available workaround, with one exception. That is, #AVG probably gets it right that the best workaround is to: try to catch the error at the Form_BeforeUpdate event; and (probably, depending on your needs) cancel the event in Form_BeforeUpdate.
However, it is a good idea not to suppress the default error at the Form_Error level. If you catch and cancel the error at Form_BeforeUpdate then the error won't arise at Form_Error. So there's no need to suppress the error at Form_Error in that case. But if there's another kind of referential integrity violation laying in wait (e.g. an Category/Organisation violation rather than a Category/Subcategory violation), that you haven't thought to handle as a developer, then having the default error message arise in Form_Error will alert you to it. You wouldn't want to suppress a message for an error you haven't caught.
Below I provide example code for this workaround.
I also discuss how AccessError(DataErr) looks like a possible solution, but isn't quite. That is, it can be used to map DataErr numbers to messages. This is partially helpful. But, unfortunately, the messages are the variable unsubstituted versions. So, for example, we can't use AccessError(DataErr) in code to interrogate specific referential integrity violation messages.
Making the problem sharper
A Form_Error event will fire when a user, through a form: violates referential integrity (that you've set precisely to prevent such attempts at invalid data states); violates a validation rule set at the table level; or otherwise (?) produces a data error.
However, the default message returned is likely to be user-unfriendly. E.g., in the case of a referential integrity violation ...
You cannot add or change a record because a related record is required in table 'CategorySubcategory'.
In this example the table 'CategorySubcategory' expresses a many-to-many relationship between tables 'Category' and 'CategorySubcategory', defining the particular Subcategories that belong to particular Categories. (In my example) it is also expressed, in CategorySubcategory, that some of the Categories have no Subcategories.
What's needed is a means to have a user friendly message in place of the user-unfriendly message. And to do this it would be convenient to interrogate the default error message as it arises, suppress it, and substitute your own.
(As far as I know) the closest we can come to interrogating the default message, in Form_Error, is AccessError(DataErr). Unlike Error(DataErr), AccessError promises to
Microsoft > Docs > Office VBA Reference > Access > Object model > Application object > Methods > AccessError
... return the descriptive string associated with a Microsoft Access or DAO error [Emphasis added]
[And the promise is that you] can use the AccessError method to return a descriptive string from within a form's Error event.
So you might be tempted to do something like, and it would be convenient if you could do something like ...
Private Sub Form_Error(DataErr As Integer, Response As Integer)
If AccessError(DataErr) Like "*CategorySubcategory*" Then
MsgBox "[My user friendly message] Choose the right Subcategory given the Category; or change the Category.", vbOKOnly Or vbExclamation
Response = acDataErrContinue ' Suppress default message
End If
End Sub
Might AccessError work?
However, it turns out AccessError only returns the variable unsubstituted version of the error message, not the variable substituted message the user sees at run-time. For example AccessError returns something like ...
You cannot add or change a record because a related record is required in table '|'.
... rather than ...
You cannot add or change a record because a related record is required in table 'CategorySubcategory'.
To prove this trigger a data error (E.g. by violating referential integrity) with the following.
Private Sub Form_Error(DataErr As Integer, Response As Integer)
' For some reason you'll need to call AccessError(DataErr) before Error(DataErr)
Debug.Print "AccessError(DataErr): ", AccessError(DataErr)
Debug.Print "Error(DataErr): ", Error(DataErr)
End Sub
Observe both: AccessError returns a specific message where Error returns the uselessly generic 'Application-defined or object-defined error'; and AccessError returns the variable unsubstituted version of the error message.
#Hello World correctly quotes from MS Docs > ... > Form.Error event (Access)
You can use the DataErr argument with the Error function to map the number to the corresponding error message
Even with AccessError substituted for Error #Hello World's lament remains fair ...
[The MS docs don't] actually give an example of this and I'm struggling to figure it out.
Mapping messages to the DataErr number
What the MS doc author probably has in mind is code like the following
Function AccessAndJetErrorsTable() As Boolean
' Source: Access 2000 Help "Determine the Error Codes Reserved by Microsoft Access
' and the Microsoft Jet Database Engine"
Const strTable As String = "zttblAccessAndJetErrors"
Dim cat As New ADOX.Catalog
Dim tbl As New ADOX.Table
Dim cnn As ADODB.Connection
Dim rst As New ADODB.Recordset, lngCode As Long
Dim strAccessErr As String
Const conAppObjectError = "Application-defined or object-defined error"
On Error GoTo Error_AccessAndJetErrorsTable
Set cnn = CurrentProject.Connection
' Create Errors table with ErrorNumber and ErrorDescription fields.
tbl.Name = strTable
tbl.Columns.Append "ErrorCode", adInteger
tbl.Columns.Append "ErrorString", adLongVarWChar
Set cat.ActiveConnection = cnn
cat.Tables.Append tbl
' Open recordset on Errors table.
rst.Open strTable, cnn, adOpenStatic, adLockOptimistic
' Loop through error codes.
For lngCode = 0 To 35000
On Error Resume Next
' Raise each error.
strAccessErr = AccessError(lngCode)
DoCmd.Hourglass True
' Skip error numbers without associated strings.
If strAccessErr <> "" Then
' Skip codes that generate application or object-defined errors.
If strAccessErr <> conAppObjectError Then
' Add each error code and string to Errors table.
rst.AddNew
rst!ErrorCode = lngCode
' Append string to memo field.
rst!ErrorString = strAccessErr
rst.Update
End If
End If
Next lngCode
' Close recordset.
rst.Close
DoCmd.Hourglass False
RefreshDatabaseWindow
MsgBox strTable & " errors table created."
AccessAndJetErrorsTable = True
Exit_AccessAndJetErrorsTable:
Exit Function
Error_AccessAndJetErrorsTable:
MsgBox Err & ": " & Err.Description
AccessAndJetErrorsTable = False
Resume Exit_AccessAndJetErrorsTable
End Function
To use that code, create a new Database, put the code in a module, your cursor in the middle of the code, then hit F5 (to run it). A table zttblAccessAndJetErrors will be created where you can look up all the variable unsubstituted versions of the Access and Jet error messages; and correspond these to a DataErr number.
That code will give you the most up to date list of error messages. However, as an alternative you could just lookup an online source where this is published. E.g. FMS > Microsoft Access 2010 Error Numbers and Descriptions
So either with zttblAccessAndJetErrors or a lookup online you can now determine the DataErr number that corresponds most closely to the error message you are seeing during testing. So that gives us the ability to do something like ...
Private Sub Form_Error(DataErr As Integer, Response As Integer)
Select Case DataErr
Case 3201
' "You cannot add or change a record because a related record is required in table '|'."
MsgBox "[My user friendly message] Choose the right Subcategory given the Category; or change the Category.", vbOKOnly Or vbExclamation
Response = acDataErrContinue ' Suppress default message
End Select
End Sub
That is, we don't use AccessError directly in Form_Error to return a message. Rather we've used AccessError (or relied on someone else to have used) to produce the DataErr/Message mapping. In Form_Error we continue to match against the DataErr number only. But now we can copy the relevant message as a comment for our DataErr number.
However, at least in this referential integrity (DataErr 3201) case our problem remains. If we suppressed the default message for a specific referential integrity violation (like a Category/Subcategory mismatch), as with the code above, then that risks burying other specific referential integrity violations. E.g. If another referential integrity violation occurred with respect to, say a Category and Organisation chosen by the user, then the above code would wrongly express (beguilingly in a user friendly manner) that a Category/Subcategory violation had occurred.
The probable best available workaround
So, as far as I know #AVG's solution is probably the best available workaround, with one exception. That is, #AVG probably gets it right that the best workaround is to: try to catch the error at the Form_BeforeUpdate event; and (probably, depending on your needs) cancel the event in Form_BeforeUpdate.
However, it is a good idea not to suppress the default error at the Form_Error level. If you catch and cancel the error at Form_BeforeUpdate then the error won't arise at Form_Error. So there's no need to suppress the error at Form_Error in that case. But if there's another kind of referential integrity violation laying in wait (e.g. an Category/Organisation violation rather than a Category/Subcategory violation), that you haven't thought to handle as a developer, then having the default error message arise in Form_Error will alert you to it. You wouldn't want to suppress a message for an error you haven't caught.
So, in my case, the workaround code looks like (having to "go down the DLookup route" as #Hello World suggests).
Private Sub Form_BeforeUpdate(Cancel As Integer)
' Head off a Form_Error referential integrity error 3201:
' "You cannot add or change a record because a related record is required in table 'CategorySubcategory'."
Dim subCategoryIDGivenCategoryIDAtTableLevel As Variant
If IsNull(Me.cboSubcategory) Then
subCategoryIDGivenCategoryIDAtTableLevel = DLookup("[SubcategoryID]", "[CategorySubcategory]", "[CategoryID] = " & Me.cboCategory)
If Not IsNull(subCategoryIDGivenCategoryIDAtTableLevel) Then
' The user has not chosen a value for cboSubcategory, when they should have chosen some value
message = "For this Category you need to choose a Subcategory. Either choose a Subcategory or change the Category."
Me.cboSubcategory.SetFocus
Cancel = True
End If
End If
If Cancel Then
MsgBox message, vbOKOnly Or vbExclamation, GetApplicationTitle() & " (Record Level Validation)"
End If
End if
Private Sub Form_Error(DataErr As Integer, Response As Integer)
Select Case DataErr
Case 3201
' A referential integrity error ...
' "You cannot add or change a record because a related record is required in table '|'."
' We display the default error message in case we haven't caught a referential integrity
' condition in Form_BeforeUpdate
Response = acDataErrDisplay
End Select
End Sub
I always use the below code pattern so that users can get a proper error message, which they can then convey to me to understand the reason.
Private Sub cmdSearch_Click()
On Error GoTo Sub_Err
--Your code here---
'errors catching stuff
Sub_Exit:
Exit Sub
Sub_Err:
MsgBox Error$
Resume Sub_Exit
End Sub
Use the same thing for every sub you use in code.
Without setting a Label, is it possible for a VBA Error Handler to resume at the beginning of a With statement?
For example:
Dim rst As DAO.Recordset
Set rst = Nothing
On Error GoTo ErrHand
With rst
.AddNew
!MyValue = 1
.Update
.Bookmark = .LastModified
End With
ErrHand:
If Err.Number <> 0 Then
Call SetRST 'Assume this procedure opens the recordset appropriately
Resume
End If
End Sub()
The code will cause an error on the ".AddNew" line, and then when it goes through the error handler will set the recordset, but then it will Resume on the ".AddNew" line. The problem is that it is still within the "With" statement where CommentRST Is Nothing. Is there a way to tell the error handler to resume at the "With RST" line instead or the ".AddNew" line without creating a label before the "With" statement or checking for a blank recordset first?
I know there are ways around this (as I've just suggested 2 of them), but am curious as to whether this is possible.
Just add a byRef argument to SetRST.
e.g:
Sub SetRST(byref myrs as recordset)
'do stuff
set myrs = ...
By the way, your error handling sample sucks: just add an Exit Sub before ErrHand:,
so you won't need to test if err.number<>0, because you know it will always be.
In your error handling, use:
call SetRST rst
Edit:
I would prefer something like:
If rst Is Nothing Then
Set rst = something
End if
With rst
'continue here
In this case, how would the compiler know to resume at the beginning of the With block (and not at some other point)? It would not, and although it may be logically connected (i.e., it's within the With block) that's still no reason to assume by rule that execution should resume at the start of that block, without explicit reference to resume at that point.
What you're asking essentially assumes the source of the error, and then expects that VBA has this assumption built in, but it would certainly not be applicable for all or even most cases, consider below, assume the rst is open/etc., the actual error raises at the Bookmark property, your error handler doesn't account for that, and so resuming at the beginning of the block will cause infinite failure loop!
On Error GoTo ErrHand
With rst
.AddNew
!MyValue = 1
.Update
.Bookmark = "George Washington"
End With
See the documentation on the Resume statement:
https://msdn.microsoft.com/en-us/library/office/gg251630.aspx
There are your options to Resume
If the error occurred in the same procedure as the error handler, execution resumes with the statement that caused the error. If the error occurred in a called procedure, execution resumes at the statement that last called out of the procedure containing the error-handling routine.
Or to Resume Next:
If the error occurred in the same procedure as the error handler, execution resumes with the statement immediately following the statement that caused the error. If the error occurred in a called procedure, execution resumes with the statement immediately following the statement that last called out of the procedure containing the error-handling routine (or On Error Resume Next statement).
Or to Resume <line>
Execution resumes at line specified in the required line argument. The line argument is a line label or line number and must be in the same procedure as the error handler.
A With block holds an instance of an object, and releases it at End With. If you jumped outside the With block, the object is gone.
So the answer is no, you can't Resume into the middle of a With block. well you actually can, but things get ugly and confusing.
This is a common misuse of the With statement you have here - you're merely using it because you're being lazy (no offense intended), and don't want to type rst. in front of every line that uses that object.
Proper usage would have the With block itself hold the reference, like this:
With GetRecordset 'a function that returns a recordset
.AddNew
!MyValue = 1
.Update
.Bookmark = .LastModified
.Close
End With
..not that I'd recommend working with recordsets like this, but you get the idea ;)
I have a weird situation in Access. Normally, the Invalid use of Null error is quite a simple thing - assigning a null to a string variable or some such. But I'm getting the error in a place where it seems to me it should not be happening. Here is the code snippet:
bch = Form_Akces.txtMaxCisla.BackColor
If Err <> 0 Then Stop
Form_Akces.txtMaxCisla.BackColor = vbYellow
If Err <> 0 Then Stop
DoEvents
If Err <> 0 Then Stop ' This is where I get the error
With qdf_GPsp
What's been happening is that I get this error only sometimes, usually only on the first time I run the code in a while. If I close the database and immediately re-open it, usually I do not get the error. It's been driving me nuts for quite a while, so I put in all these "If Err <> 0 Then Stop" statements, trying to track down where it happens. It's a live system, and users know to simply restart the app, but it's a massive PIA, and kind of embarrassing to boot.
Can anybody think of something to try or examine? I'm not exactly an amateur in Access, but this is far outside anything I have ever run across. Why a DoEvents statement should generate such an error is beyond me, especially since I am not doing anything even in the preceeding statements that should generate such an error, that it might be somehow 'held' until the processor gets an opportunity to throw the error. If I take out the DoEvents, I simply get the same error somewhat further down the line. txtMaxCisla is an unbound text field on the form Form_Akces, from which the routine containing this code is called. It is only on start-up - once everything is loaded and running, this never happens again. And it only happens once in a while - no pattern to it that I have been able to detect.
It's been going on for a couple of months, through numerous compile, decompile, recompile, compress and repair cycles, with no discernible change except that sometimes it happens other places, again with no reason for it that I can see.
Update *
No luck - it still crashes, and for absolutely NO reason that I can see. Here's the code now:
Public Sub ReloadMaxNumbers(tmc As TextBox)
Dim rst As DAO.Recordset, x$, xb$, xe$, bch&
On Error GoTo 0
If Err <> 0 Then Stop
DoEvents
If Err <> 0 Then Stop
...
The code stops on the SECOND test, after the DoEvents, with the same error, "Invalid use of Null". I realize this code is completely retarded, but it's the result of tracking back, trying to find the root of the error. Without this, it crashes further down the road somewhere, with this same error. At this point I can't think of anything else to even try.
I'm puzzled by Form_Akces in your code. If I create a form named Akces, the form's code module is named Form_Akces. But you said "on the form Form_Akces". So I am confused whether Form_Akces is the name of the form or the form's code module. Perhaps Access is also confused.
Either way, since you said the code is in the form's code module, I suggest you substitute Me for Form_Akces
Edit: I misunderstood your situation. That code you showed us is actually from a procedure in another code module, not the form's code module. In that case I would do something like this for the external procedure DoSomething:
Public Sub DoSomething(ByRef frm As Form)
bch = frm.txtMaxCisla.BackColor
frm.txtMaxCisla.BackColor = vbYellow
DoEvents
' whatever else you need
End Sub
Then in the form's code module where you call DoSomething:
DoSomething Me
And if DoSomething only operates on a single control each time, you could just pass a reference to that control instead.
Public Sub DoSomething(ByRef ctl As Control)
This approach will allow DoSomething to be re-used for other forms with no changes required because the target form name is not "hard-wired" into the procedure. Also, it won't break if you rename your Akces form. In the second variation, it would also accommodate changes to the control name.
Is there any method to avoid the annoying write conflict messages by automating and hiding the process so that it doesn't appear as if the program is defective? There doesn't seem to be any point to these messages anyway as there is only one real choice which is to drop the changes.
The only way I know to avoid that message is to requery your screen after running a process or changing data on the backend database (or sql server)
You should be able to handle these errors in a combination of two places. The first, and most important is on the Form_Error event. Your code will look something like this:
Private Sub Form_Error(DataErr As Integer, Response As Integer)
If DataErr = 7787 Then
MsgBox "Oops, this record was edited by someone else or " & _
"in another screen while you were making edits." & _
"Your edits cannot be saved."
Response = acDataErrContinue
End If
End Sub
You will also need to handle error 3021 anywhere you run a Save command in VBA, like this:
Private Sub cmdSave_Click()
On Error GoTo ErrHandler
DoCmd.RunCommand acCmdSaveRecord
Exit Sub
ErrHandler:
If Err.Number = 3021 Then
'Do Nothing
Resume Next
Else
'Handle other errors here
Resume Next
End If
End Sub
Now, I readily agree with one of the comments that it's more important that you try to resolve whatever is causing these errors rather than coding around them. In my case I am using the above solution to handle write conflicts that occur when a user opens two instances of the same form to the same record and makes edits in both instances. It would be better if I would prevent the user from opening the same record twice or prevent edits by only allowing the user to make edits in one of the open form instances but neither of these are exactly easy to implement, especially when you are using your own forms collection so I guess you could say I'm waiting for a "rainy day".