总之
据我所知@AVG 的解决方案可能是最好的解决方法,但有一个例外。也就是说,@AVG 可能正确地认为最好的解决方法是:尝试在 Form_BeforeUpdate 事件中捕获错误;并且(可能取决于您的需要)取消Form_BeforeUpdate 中的事件。
但是,最好不要抑制Form_Error 级别的默认错误。如果您在Form_BeforeUpdate 捕获并取消错误,那么Form_Error 将不会出现错误。因此,在这种情况下,无需抑制 Form_Error 处的错误。但是,如果有另一种类型的参照完整性违规正在等待中(例如,类别/组织违规而不是类别/子类别违规),您没有想过作为开发人员处理,那么在@987654330 中会出现默认错误消息@会提醒你。您不希望因未捕获的错误而隐藏消息。
下面我提供了此解决方法的示例代码。
我还讨论了AccessError(DataErr) 看起来是一个可能的解决方案,但并不完全是。也就是说,它可以用于将DataErr 数字映射到消息。这部分有帮助。但是,不幸的是,这些消息是可变的 unsubmitted 版本。因此,例如,我们不能在代码中使用AccessError(DataErr) 来询问特定引用完整性违规消息。
使问题更尖锐
当用户通过以下表单触发Form_Error 事件: 违反参照完整性(您已精确设置以防止此类尝试无效数据状态);违反了在表级别设置的验证规则;否则 (?) 会产生数据错误。
但是,返回的默认消息可能对用户不友好。例如,在违反参照完整性的情况下......
您无法添加或更改记录,因为表“CategorySubcategory”中需要相关记录。
在此示例中,表“CategorySubcategory”表示表“Category”和“CategorySubcategory”之间的多对多关系,定义了属于特定类别的特定子类别。 (在我的示例中)在 CategorySubcategory 中还表示某些类别没有子类别。
我们需要一种方法来使用对用户友好的消息来代替对用户不友好的消息。为此,可以方便地在出现默认错误消息时对其进行询问、抑制它并替换您自己的错误消息。
(据我所知)最接近Form_Error 中的默认消息是AccessError(DataErr)。与Error(DataErr) 不同,AccessError 承诺
Microsoft > Docs > Office VBA Reference > Access > Object model > Application object > Methods > AccessError
...返回与 Microsoft Access 或 DAO 错误相关的描述性字符串[强调]
[承诺是您] 可以使用 AccessError 方法从表单的错误事件中返回描述性字符串。
所以你可能会想做类似的事情,如果你能做类似的事情会很方便......
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
AccessError 可以工作吗?
然而,事实证明AccessError 只返回变量 unsubmitted 版本的错误消息,而不是用户在运行时看到的变量 submitted 消息。例如 AccessError 返回类似 ...
您无法添加或更改记录,因为表“|”中需要相关记录。
...而不是...
您无法添加或更改记录,因为表“CategorySubcategory”中需要相关记录。
为了证明这会触发 data 错误(例如,通过违反参照完整性),请使用以下内容。
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
同时观察:AccessError 返回一条特定消息,其中 Error 返回无用的通用“应用程序定义或对象定义错误”;并且AccessError 返回变量未替换 版本的错误消息。
@Hello World 正确引用 MS Docs > ... > Form.Error event (Access)
您可以将 DataErr 参数与 Error 函数一起使用,以将数字映射到相应的错误消息
即使用AccessError 代替Error @Hello World 的哀叹仍然公平......
[MS 文档没有] 实际上给出了这个例子,我正在努力弄清楚。
将消息映射到DataErr 号码
MS 文档作者可能想到的是如下代码
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
要使用该代码,请创建一个新数据库,将代码放入模块中,将光标放在代码中间,然后按 F5(运行它)。将创建一个表 zttblAccessAndJetErrors,您可以在其中查找 Access 和 Jet 错误消息的所有变量未替换版本;并将这些与DataErr 号码相对应。
该代码将为您提供最新的错误消息列表。但是,作为替代方案,您可以只查找发布此内容的在线资源。例如。 FMS > Microsoft Access 2010 Error Numbers and Descriptions
因此,无论是使用zttblAccessAndJetErrors 还是在线查找,您现在都可以确定与您在测试期间看到的错误消息最接近的DataErr 数字。所以这让我们有能力做类似的事情......
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
也就是说,我们不直接在Form_Error中使用AccessError来返回消息。相反,我们使用AccessError(或依赖其他人使用)来生成 DataErr/Message 映射。在Form_Error 中,我们继续仅匹配DataErr 号码。但是现在我们可以将相关消息复制为我们DataErr 号码的评论。
但是,至少在这种参照完整性 (DataErr 3201) 的情况下,我们的问题仍然存在。如果我们抑制特定参照完整性违规的默认消息(如类别/子类别不匹配),就像上面的代码一样,那么就有隐藏其他特定参照完整性违规的风险。例如。如果针对用户选择的类别和组织发生了另一个参照完整性违规,那么上述代码将错误地表达(以用户友好的方式)错误地表示发生了类别/子类别违规。
可能的最佳解决方法
所以,据我所知@AVG 的解决方案可能是最好的解决方法,但有一个例外。也就是说,@AVG 可能正确地认为最好的解决方法是:尝试在 Form_BeforeUpdate 事件中捕获错误;并且(可能取决于您的需要)取消Form_BeforeUpdate 中的事件。
不过,最好不要抑制Form_Error 级别的默认错误。如果您在Form_BeforeUpdate 捕获并取消错误,那么Form_Error 将不会出现错误。因此,在这种情况下,无需抑制 Form_Error 处的错误。但是,如果有另一种类型的参照完整性违规正在等待中(例如,类别/组织违规而不是类别/子类别违规),您没有想过作为开发人员处理,那么在@987654368 中会出现默认错误消息@会提醒你。您不希望因未捕获的错误而隐藏消息。
因此,就我而言,解决方法代码看起来像(必须按照@Hello World 的建议“走 DLookup 路线”)。
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