3rd Post: Access Validation

  • Thread starter Thread starter kiln
  • Start date Start date
K

kiln

Access 20000/2002 (this is the third request for help on this issue)

Data validation error trapping can be strange. I have some fields that
are set to required and unique on the table level, for instance
ProjName. I'd like to trap for required and duplicate values.

Minimally, I usually add error trapping code to the Save button's On
Click and the form's On Error to test for Err = 3314 (required) and Err
= 3022 (dupe). This approach deals with a user tabbing off of the form
into a subform (for instance) and explicitly clicking the save button.

I don't think is copes well with the user clicking a button that moves
the focus to a subform in code, which forces a save, or if save by
shift+enter. The form On Error routine is not invoked. A "Previous
action was canceled" type message appears in the calling routine. I
could error code for that with a resume next but it seems "wrong" to
have to code for the same errors in so many places.

The only event that would seem to cover all the bases is the form before
update, which has a cancel event. But to use it I have to "know" which
fields are required and should not be dupes, and test in code to see if
the rec is safe to update. I would like to use the db engine errors if I
can, as they are fast and built in (this is Jet).

I could write a class that incorporates this sort of validation stuff
and gets involved in the form before update. But isn't there a way to
gracefully handle this with the built in stuff Access offers?
 
In the past I've experienced similar problems, you feel like you're putting
out fires all over the place! My solution is to avoid the problem occuring
in the first place by just not allowing duplicate data (or null data) to
ever be entered. Usually this can be done by generating your own unique data
and hiding it from the user completely. In some cases this is not possible
(or desirable), in which case I do checks using dcount or dlookup in the
before update event for the actual control that displays the data. In this
way the problem never occurs when the record is saved,

Don't know if this is much help, but at least it's a reply!!!

Sam
 
Well, you can use some of the built in functions and events.

The problem here is that you have some theory, or idea that using the built
in JET validation is going to save you some code, and it is not. You have to
get rid of
this imaginary idea that you have.

The reason is as follows::

While you can have many fields required, and the JET engine can thus trigger
an error, that error code will simply tell you that you a have a required
field missing. What good is that? Your code will still have to check which
field is missing, and give the user a intelligent message. I mean trapping
the error that field is missing, and the giving the user a message such as:

A require field is missing.

The above is useless, and really NO BETTER then the error that you get from
JET.

To make a an intelligent message, YOU WILL need to check which field it was
that triggered the error message. Really, the message you need is:

The field "LastName" is required

Further, if you are not trying to torture the hell out of your users, after
they click ok to the above nice message, your cursor should be placed in
that last name field (assuming that this field in actually displayed on the
input form). So, really, you still need to code/write a nice custom message.
Further, you should take different action for some fields that are required.

Minimally, I usually add error trapping code to the Save button's On
Click

Well, using a save button is really the problem here also. I don't know why
people insist on having save buttons!, but they are really dumb, and not
necessary!. Lets leave this issue of the save button for another day, but
when a user moves to another record, do you think they want to save? Thank
goodness thinks like cars were made before software developers came along. I
can just see it now: you turn the key in the car, and it asks you do you
want to start the car? Or, how about a file cabinet, you open it, place a
letter inside, and then when close the file cabinet, it will ask you do you
want to save your letter? (how dumb can we get!). Ms-access has NOT have a
save button for 10 YEARS NOW! More and more of the software industry is
finally realizing that save buttons are really a bad idea. I always been
tortured when exiting Word, and it asks me do you really want to save? (how
I wish it was like ms-access!). You MOST certainly should offer a user a
un-do button. But to bother millions of users around the world daily is not
needed.

The above is especially more so when using bound forms. The reason is that
you need to save the parent record to disk before it is possible or even
feasible to start adding child records. In fact, this rule applies to ANY
major database. You can't solve, or approach this problem any other way. In
other words, if you are adding child records to a data base, you MUST first
add the parent record, and have the correspond key generated for the child
records to use. I would not fight this fact.
I don't think is copes well with the user clicking a button that moves
the focus to a subform in code, which forces a save

Yea, boy!....you got that one right! How can you possibly let the user add
child records without first saving the parent record? Further, are you going
to train your users to hit the save button BEFORE they move to the sub form.
(this is now starting to get really silly here!).

Even one of the original authors of VB has whole cheaper in a book devoted
as how dumb it is to have users to save. And, look at the success of
products like the palm pilot (again, just like ms-access, they used implied
saves). Anyway, thank goodness those ms-access developers for the last 10
years have had this concept correct. More and more software is now working
this way.
The only event that would seem to cover all the bases is the form before
update, which has a cancel event. But to use it I have to "know" which
fields are required and should not be dupes, and test in code to see if
the rec is safe to update.

That is correct. However, what kind of error messages, and user friendly
kind of code are you gong to use?

Answer: you will in fact have to deal with the required fields, and the dup
fields anyway!

Further, you will want to give the user a intelligent message, and deal with
it in a intelligent fashion. The amount of code to check for blank fields is
trivial, and in ADDITION THIS MEANS YOU can give the user a friendly
message. Writing the verifying code in the before update ALSO MEANS that you
can take appropriate action. Thus, you can do things like after the user
clicks ok, you can move the cursor to the bad field.

I mean, what do you want with the errors from the JET engine? So what if JET
spits out a error message. You still need to do something here! You going to
give the user a list of all the fields at once! You don't plan to generate a
list of the offending fields that jet found and then give the user a whole
big huge message?

I mean, lumping all that field information, and how you want to deal with it
simply means you will need SOME CODE for each of those fields. That code
will have user friendly messages, and even the possibility of moving the
cursor/focus to the offending field for the user.

What good is dealing with an error message from JET when you have to then
deal with the problem?

I would suggest you just integrate the checking code along with the nice
user friendly code that you WILL NEED write anyway! Since any good developer
is going to make user friendly messages, and take user friendly action, then
the addition of one line of code to "check" for the blank field is not a
development penalty at all. Your idea of saving a bunch of code by using the
engine level here is simply not applicable all. I see no real way to save
any code EVEN IF you could get the engine to tell you more about the error.


So, we get in the before update:

' check for last name

if isnull(lastName) = True then
Cancel = true
msgbox "LastName is a required field",vbExclamation,"Required Field"
me.LastName.SetFocus
exit sub
endif

As you can see, the above code gives the user a nice message (which you have
to give anyway). The above code also puts the cursor right into the field
that caused the error message (which you should do). So, how many lines of
code does getting a return error from the data engine save you in the above?
It saves you bunkus lines of code. I mean, if you go and add a few new
fields that are required, and don't write any code to deal with those new
fields, then your users will get that weird system error. Even if you could
trap that system error, you will still have to code, and give a nice error
message for each field. As it stands, I see nothing in your weird search for
a solution that is going to save you any coding at all.

You most certainly can, and should perhaps wire a few nice re-useable
routines. For example, if I have 5 controls on the form bound to 5 required
fields, then I certainly could using something like:

cancel = True
if BadCheck("LastName","Last Name Field is required") then exit sub
if BadCheck "Company","Company Name Field is required" then exit sub

et.c etc.
' if we get here...every thing ok...
cancel = False

The code in BadCheck could be:

Private Function MyCheck(strField as string,strErMsg as string) as boolean

BadCheck = False
if isnull me(strField) = True then
BadCheck = True
msgbox strErMsg,vbExclamation,"Required Field"
me(strField).SetFocus
endif
end Function

So, you can use some creative coding to make up a library of routines. So,
the above could be use for required, and you can cook up a few more for
duplicates etc. And, if you look closely at the above, note how the controls
on a form can be referenced by the value of a variable. This means you could
even put that list of fields, and the error messages in a table, and have a
generic code "loop" through that list.

I don't know where, or why, or what your mental block is resenting the use
of the before update event, but it is a far superior event model then what
you have withy VB or c++ forms for example (well, in fact those systems
don't EVEN have the before update event, so you have to write EVEN MORE!. In
fact you are dealing with un-bound forms in those systems anyway. Of course,
using VB, and un-bound-und forms means a $12,000 project in ms-access will
cost AT LEAST $35,000 or more in VB. The 3 x productive you get with
ms-access forms is a conservative estimate by most experienced developers.
But isn't there a way to
gracefully handle this with the built in stuff Access offers?

If you mean by graceful using the way cool built in events like the before
update, and giving the user a nice friendly message, then the above
solutions beats the pants off of just about every development platform I
have seen, or used. And, I assure I have used a LOT of platforms....

And, yes, you could also cook up a class object to help with the above, but
that class object will still be fired, and controlled in the before update
event. You might even as I mentioned, make a table, and generic routine.

If you are going to deal with your users via the UI, then use the UI. The
data engine will not help much here at all. And, I don't know of a
development platform where the engine helps that much anyway.

Further, if you tie all your code too tight to that one data engine, what
happens when you want to move the data to sql server, or some other data
engine, and KEEP your front end in ms-access? Ms-access makes a great front
end to sql-server, or oracle, or many of the other systems out there.
Ms-access is not a database, but VB UI development system. You might use
the JET data engine, or perhaps you need 1000 users with ms-access? You then
would use sql server with ms-access. So, ms-access is the UI development
too. Placing your user friendly code in the UI makes sense. Save the engine
checking for your coding, and catching error in processing code/routines
that you may write (but that engine stuff is just not made, nor used by the
users).

All in all, I don't see any other viable, and reasoned out approach that
will be more efficient, and take less developer time then the above.

If have not yet used class objects in ms-access, you can read the following
article of mine:

http://www.attcanada.net/~kallal.msn/Articles/WhyClass.html
 
I agree with most of what you say Albert, however every Access developer is
constantly writing little bits of code just like the code in your post. This
is the issue as I see it!

Remember one can set validation rules and validation text within table
design. These settings are inherited by any textboxes (and other controls)
that are later bound to that field. As you set a field as required within
table design then it would be nice if you could also enter suitable text for
display should no data be entered. Surely this makes sense and would save
everyone remembering which fields are required and which are not.

The unique and relationship problems are different as they involve examining
other records. Hence such errors currently occur as an attempt to save the
record is made. Nevertheless if the developer wants unique field values or
the field has relationship restrictions, and furthermore the developer
creates a control for its data entry then the implication is that they want
these directives enforced. The logical time for enforcement being
immediately after the data is entered in the field, hence it would be
sensible and logical to add such functionality as a standard property of
microsoft's textbox control (and other controls) rather than having to code
it within the before update event.

Sam
 
Sam said:
Remember one can set validation rules and validation text within table
design. These settings are inherited by any textboxes (and other controls)
that are later bound to that field. As you set a field as required within
table design then it would be nice if you could also enter suitable text for
display should no data be entered. Surely this makes sense and would save
everyone remembering which fields are required and which are not.

I don't remember the last time I set the Required property of a field to
Yes. As you point out, the Validation Rule and Validation Text provide
a good interface, which I find much more useful than Required property,
i.e. set the Validation Rule to Is Not Null.
 
Sam said:
I agree with most of what you say Albert, however every Access developer is
constantly writing little bits of code just like the code in your post. This
is the issue as I see it!

Yes, I agree with you. My only point was that even IF we could get more
information from JET, it would not really help us get rid of much code. If
we are talking about making some "more general" code to deal with this, then
fine (and I gave a few examples, and even hinted at a table driven
approach).
Remember one can set validation rules and validation text within table
design. These settings are inherited by any textboxes (and other controls)
that are later bound to that field.

Excellent point. Often, we do forget that the controls do have a validation
rule. And, you can even use a function expression. So, there is some built
in features that I did fail to mention. (good point on your part!).
The logical time for enforcement being
immediately after the data is entered in the field, hence it would be
sensible and logical to add such functionality as a standard property of
microsoft's textbox control (and other controls) rather than having to code
it within the before update event.

I agree again with the above. The only exceptions to the above is often the
user MAY NOT enter, or even bother to fill in the field. That being the
case, I would isolate the code in the before update, and NOT have two sets
of code. I accept that this approach may most certainly be less user
friendly then IMMEDIATE informing the user when they tab across/through a
require field. However, this does give the user more flexibility in the
order they enter the form, and does not nag them until they try and exit. A
bit of a trade off...but not too bad.

In fact, the amount of coding to deal with this stuff is not that large
anyway. Further, using code does give the most flexible approach.
 
[snip]
You most certainly can, and should perhaps wire a few nice re-useable
routines. For example, if I have 5 controls on the form bound to 5
required fields, then I certainly could using something like:

cancel = True
if BadCheck("LastName","Last Name Field is required") then exit sub
if BadCheck "Company","Company Name Field is required" then exit
sub

et.c etc.
' if we get here...every thing ok...
cancel = False

The code in BadCheck could be:

Private Function MyCheck(strField as string,strErMsg as string) as
boolean

BadCheck = False
if isnull me(strField) = True then
BadCheck = True
msgbox strErMsg,vbExclamation,"Required Field"
me(strField).SetFocus
endif
end Function

Along these lines, I have a standard function I use to check for
required fields on a form, as indicated by having the controls' Tag
property set to "Required":

'----- start of function code -----
Function fncRequiredFieldsMissing(frm As Form) As Boolean

Dim ctl As Access.Control
Dim strErrCtlName As String
Dim strErrorMessage As String
Dim lngErrCtlTabIndex As Long
Dim blnNoValue As Boolean

lngErrCtlTabIndex = 99999999 'more than max #controls

For Each ctl In frm.Controls
With ctl
Select Case .ControlType
Case acTextBox, acComboBox, acListBox, acCheckBox
If .Tag = "Required" Then
blnNoValue = False
If IsNull(.Value) Then
blnNoValue = True
Else
If .ControlType = acTextBox Then
If Len(.Value) = 0 Then
blnNoValue = True
End If
End If
End If
If blnNoValue Then
strErrorMessage = strErrorMessage & vbCr & _
" " & .Name
If .TabIndex < lngErrCtlTabIndex Then
strErrCtlName = .Name
lngErrCtlTabIndex = .TabIndex
End If
End If
End If
Case Else
' Ignore this control
End Select
End With
Next ctl

If Len(strErrorMessage) > 0 Then
MsgBox "The following fields are required:" & vbCr & _
strErrorMessage, vbInformation, _
"Required Fields Are Missing"
frm.Controls(strErrCtlName).SetFocus
fncRequiredFieldsMissing = True
Else
fncRequiredFieldsMissing = False
End If

End Function
'----- end of function code -----

The function is called in the form's BeforeUpdate event like this:

'----- start of code for event procedure -----
Private Sub Form_BeforeUpdate(Cancel As Integer)

Cancel = fncRequiredFieldsMissing(Me)

End Sub
'----- end of code for event procedure -----

The way this works, a list of all required-but-empty controls is
displayed in the error message, and the focus placed in the whichever of
those controls is first in the tab order.
 
Brilliant! Can I use this Dirk?

I'm thinking I could easily adapt it to parse the Tag property for all sorts
of things (including relationship problems). Just need to create some rules
for the data in the tag property.

Sam
Dirk Goldgar said:
[snip]
You most certainly can, and should perhaps wire a few nice re-useable
routines. For example, if I have 5 controls on the form bound to 5
required fields, then I certainly could using something like:

cancel = True
if BadCheck("LastName","Last Name Field is required") then exit sub
if BadCheck "Company","Company Name Field is required" then exit
sub

et.c etc.
' if we get here...every thing ok...
cancel = False

The code in BadCheck could be:

Private Function MyCheck(strField as string,strErMsg as string) as
boolean

BadCheck = False
if isnull me(strField) = True then
BadCheck = True
msgbox strErMsg,vbExclamation,"Required Field"
me(strField).SetFocus
endif
end Function

Along these lines, I have a standard function I use to check for
required fields on a form, as indicated by having the controls' Tag
property set to "Required":

'----- start of function code -----
Function fncRequiredFieldsMissing(frm As Form) As Boolean

Dim ctl As Access.Control
Dim strErrCtlName As String
Dim strErrorMessage As String
Dim lngErrCtlTabIndex As Long
Dim blnNoValue As Boolean

lngErrCtlTabIndex = 99999999 'more than max #controls

For Each ctl In frm.Controls
With ctl
Select Case .ControlType
Case acTextBox, acComboBox, acListBox, acCheckBox
If .Tag = "Required" Then
blnNoValue = False
If IsNull(.Value) Then
blnNoValue = True
Else
If .ControlType = acTextBox Then
If Len(.Value) = 0 Then
blnNoValue = True
End If
End If
End If
If blnNoValue Then
strErrorMessage = strErrorMessage & vbCr & _
" " & .Name
If .TabIndex < lngErrCtlTabIndex Then
strErrCtlName = .Name
lngErrCtlTabIndex = .TabIndex
End If
End If
End If
Case Else
' Ignore this control
End Select
End With
Next ctl

If Len(strErrorMessage) > 0 Then
MsgBox "The following fields are required:" & vbCr & _
strErrorMessage, vbInformation, _
"Required Fields Are Missing"
frm.Controls(strErrCtlName).SetFocus
fncRequiredFieldsMissing = True
Else
fncRequiredFieldsMissing = False
End If

End Function
'----- end of function code -----

The function is called in the form's BeforeUpdate event like this:

'----- start of code for event procedure -----
Private Sub Form_BeforeUpdate(Cancel As Integer)

Cancel = fncRequiredFieldsMissing(Me)

End Sub
'----- end of code for event procedure -----

The way this works, a list of all required-but-empty controls is
displayed in the error message, and the focus placed in the whichever of
those controls is first in the tab order.

--
Dirk Goldgar, MS Access MVP
www.datagnostics.com

(please reply to the newsgroup)
 
Sam said:
Can I use this Dirk?

I'm thinking I could easily adapt it to parse the Tag property for
all sorts of things (including relationship problems). Just need to
create some rules for the data in the tag property.

Sure, help yourself. Yes, if you plan to put multiple keywords in the
Tag property you'll want to parse it. I think in the past I've done
this by separating keywords with ";" and using code like

If InStr(";" & .Tag & ";", ";Required;") > 0 Then

I probably should change my function to do that -- I just haven't yet
had cause to.
 
Zowie! Albert, you said a lot, thanks. A few items...

I don't like save buttons either but some of my users feel naked without
them so there they are.

And I wasn't intending for users to go to a child rec before a parent
rec was established. However if there is something that prevents the
parent rec from saving (dupe unique value or whatever) when they click
into the subform all the fireworks go off anyways. This is about
trapping for routine happenstances, and for non-linear user habits.

I don't have a before update event phobia - in fact I have been using
that most of the time. I have been ignoring the Jet required field
validation events prior to this because they seemed clunky. I thought
"hey I must be missing the boat here, probably I could skip a lot of
before update coding if I use the table level validation stuff!" It
seems I was wrong, that's all.

I think what I'll do is to write a class that inspects the table when
the form opens and puts all the required fields into a collection. Then
use the collection to validate the fields in the before update.
 
Hi Sam

The problem with the field validation that is inherited from table
validation is that sometimes forms get built and then later the
validation or req property for a table column is changed. It's not
inherited then. Could be added by hand of course but I was hoping to
have a more universal way to handle this stuff. I think I'll write a
class to do this stuff.

I was originally thinking that the table level validation and so forth
would be more efficient than quick stuff I put together by hand using
dlookup etc. I was hoping to reduce the time to inspect before updating.
 
But, don't you then assume that the user enters a value and then trys to
leave the field? At least in my experience, setting validation rules on
the fields only leaves a huge gap. The validation rule will not fire if
the user never enters the field. Nor if they enter the field but don't
edit. I can't imagine how you are getting database integrity like this.
 
Kiln,

Er, No. If you set a Validation Rule in the table, it fires, as you
said, on a form if the user accesses the control bound to the field, and
then tries to leave and the rule is not satisfied. But if the contro is
not accessed, the validation rule also fires when you try to leave the
record or close the form. Thus, if you use a Validation Rule of Is Not
Null, you won't be able to enter a record with this field empty, and
this is regardless of whether the field was accessed or not. And you
get your own message displayed.
 
Hmmm, I can't find a behavior like you are describing. I have been
testing as the conversation moves along. I have a table with a field
validation req set to not is null and a validation msg. The validation
don't get fired if I save a simple form that has that field null, unless
I edit the field. It behaves just like having the same settings on the
form control.

I tried removing and deleting the control too and still doesn't fire
under the conditions you describe. I don't doubt your experience but am
wondering what the difference it between our experiences. On my end this
is Access 2000, lastest sp, and Jet 4 to the last update (I think it's
8).
 
Kiln,

This is very weird. Prompted by your post, I have tried this out with a
number of versions and configurations, including the one you are using,
and my results are consistent. Here's what I did...
1. Make a table with two text fields
2. In the Validation Rule property of the second field, put Is Not Null
3. In the Validation Text property, put something
4. Make a continuous view form based on this table
5. Enter a record by typing text into both these fields via the form
6. Move to the next record, or close the form... all is well.
7. Enter another record by entering something in the first field, but
leave the second field blank, with or without having accessed it on the form
8. Move to another record, or close the form... the validation rule
prevents this, and the validation text message is displayed
9. Go to an existing valis record, i.e. with data in both fields, and
delete the data from the second field
10. Do anything to move the focus from this control... the validation
rule prevents this, and the validation text message is displayed
11. Enter a series of record such that the text in all records of the
second field begins with the letter A
12. In table design, change the Validation Rule to Is Not Null And Like "A*"
13. Return to the form
14. Enter a new record, and in the second field put something that
starts with a letter other than A
15. Do anything to move the focus from this control... the validation
rule prevents this, and the validation text message is displayed
16. Enter another new record, with data entered into the first field but
leave the second one blank, without having accessed it at all
17. Move to another record, or close the form... the validation rule
prevents this, and the validation text message is displayed

If you can confirm that you get different behaviour, please let us know,
and I'll get some more tests done.
 
Hi Steve

I didn't test via your carefully laid out steps but I'm sure that what
you wrote is true. And I think I figured out why we are seeing different
results. At least on my end, the way to see the non-validation is to
have some records in the table before you apply the table level
validation, and for those records to be missing the data in the field
you are testing on. The validation rule won't fire in for those records
if you view them on the form and update another field. I had this data
condition in the table I was testing on, thus what I reported. If I
start with a empty table and use the table level validation, I see the
behavior you describe, ie that a record with no value in the test field
can't be saved and the validation message will be presented. This means
that form validation behaves consistently, as long as one starts with
clean data, or no data.
 
Kiln,

Well, yes, but if you put a Validation Rule into a field in a table
which already contains data, some of which violates the Validation Rule,
Access gives you notice of such when you try to save the table... so the
situation you describe should never arise.
 
Kiln,

Well, yes, but if you put a Validation Rule into a field in a table
which already contains data, some of which violates the Validation Rule,
Access gives you notice of such when you try to save the table... so the
situation you describe should never arise.
It could arise if you ignore the warning at table change. I'm not
"against" your approach, esp now that I see it works more thoroughly
than I thought it did; I was just completing the conversation.
 
Hey, no problem, Kiln. I hadn't interpreted your comment as
argumentative, and I hope you didn't interpret mine as such.

Best wishes.
 
Back
Top