Conditional Validation Rule in MS Access - ms-access

I have a table in MS-Access - let's call it tComputers. Two of the fields in that table are titled Status - with the options of Active, Storage, Deactivated - and DeactivationDate.
If I wanted to make DeactivationDate mandatory if and only if the value of Status is Deactivated, how can I do that?

If I wanted to make DeactivationDate mandatory if and only if the value of status is Deactivated how can I do that?
Conversely, if a Deactivated record later changes Status ... say becomes Active ... should the DeactivationDate be discarded?
I think you can accomplish this with a table level Validation Rule. With tComputers in Design View, open the property sheet and use this as the Validation Rule property:
IsNull([DeactivationDate])=IIf([Status]="Deactivated",False,True)
Figure out what message you want the users to see when that validation rule is violated and put it in as the Validation Text property. It's unlikely the default message would mean anything to them.
Although it's possible, I doubt know how useful this is. The users should be editing data via a form, and that gives you the opportunity to enforce your validation requirements before the database
engine even attempts to save the data ... use the Form_BeforeUpdate event as #mwolfe02 described.
Edit: Here is an outline for Form_BeforeUpdate. It assumes you have a combo named cboStatus bound to the Status field, and a text box named txtRetireDate bound to the RetireDate field.
Private Sub Form_BeforeUpdate(Cancel As Integer)
Dim strMsg As String
Select Case Me.cboStatus
Case "Deactivated"
If IsNull(Me.txtRetireDate) Then
strMsg = "RetireDate is required for Status = Deactivated"
MsgBox strMsg
Me.txtRetireDate.SetFocus
Cancel = True
End If
Case "Active", "Storage"
'* what should happen here? *'
Case Else
'* what should happen here? *'
End Select
End Sub

If you want to enforce this at the table level I believe you are out of luck. You might be able to do something with Data Macros if you are using Access 2010. EDIT: I stand corrected. Though I personally don't ever use this functionality (preferring to handle the validation at the form level where more complex validation is practical) it is most definitely possible: Validation Rules
If your users will only be updating the data through a bound form, you can perform the validation in the Form_BeforeUpdate event. Then if Status = 'Deactivated' and DeactivationDate is Null, you would set Cancel = True which will prevent the changes from being saved. Obviously you'd want to show a message box or indicate in some other way why the form could not be saved.

If you are entering the data with a form, you could use VBA to validate the form entries. On the button press to save the data you can check to see if the value of status is Deactivated, and if it is, you can require DeactivationDate to have a value. Otherwise, the data won't be saved.
Doing what you want in the table design window, I'm not sure how that could be done.

Related

Adding records to table without using inline query in VBA/Access

I am completely new to VBA. I have been told to add records to a table by using a form and a Save button and given some very basic instructions. While I have achieved this with inline query, I have been told to follow some strict methods like usage of QueryDef/QueryDefs and .Parameters.
So far I am trying a very basic project, just to grasp the concepts, but I am unable to add any record to an empty table. In case the table is not empty(I manually enter a record), whenever I click the Save button for saving newer records, the number of records added are somehow doubling with each instance. For example, when I Save for the 1st time, 1 record is added, 2nd time 2 records of the same type is added, 3rd time 4 are added and so on.
The table(tbl_test) has 2 fields --> ID(primary key), Source(Long Text) and Reg No (Number).
The query(qry_test) is made with the Append feature and I have been told to add expressions which makes the code like this -
INSERT INTO tbl_test ( Source, [Reg No] )
SELECT [strSource] AS Expr1, [lngRegNo] AS Expr2
FROM tbl_test;
The form has 2 fields for Source(txt_Source) and Reg No(txt_RegNo) which have blank Record Sources (Unbound). The Save button has the following Event Procedure -
Private Sub btn_save_Click()
Dim qdf As QueryDef
Set qdf = CurrentDb.QueryDefs("qry_test")
qdf.Parameters("strSource") = Me.txt_Source
qdf.Parameters("lngRegNo") = Me.txt_RegNo
qdf.Execute
End Sub
I have zero knowledge about VBA and would gladly accept ANY help. It would be great if I get any sort of source code that will explain to all the details about saving records from forms and editing them using these querydef, parameter and recordset stuff.
Welcome to StackOverflow!
If you are using a form to collect data for records that are stored in a table, running a saved append query by QueryDefs to do this is, in my opinion, not the best method.
Whilst an append query does add new records to an existing table, I would tend to use an append query where I've got multiple established records that I want to add to an existing table.
If I'm setting up a data entry form that is designed to collect new data for new records 1-at-a-time, an append query doesn't really suit this purpose.
Instead, there are a number of features built in to the design of MS-Access forms to help collect data and save it to a table. This is because forms are very much intended to be set up so that users can interact with records from a table in a controlled, user-friendly way rather than interact directly with the table object itself.
The first and most important feature of a form in this context is probably the form's record source property. When you create a new form, go in to design view for the form and open the form's property sheet (F4 key). In the "Data" tab of the form's property sheet you'll find the record source property:
The record source essentially connects your form with a set of records, whether those be records in a table object, a query object or an sql query string.
In your case it would be better, in my opinion, to bind your tbl_Test table to your form by referring to it in your form's record source:
It will seem like nothing has happened to your form, but if you now open the "Add Existing Fields" panel (alt + F8), you'll notice that the fields associated with your tbl_Test table are available to you:
Drag them to the detail section of your form...
Then put your form in to Form View:
Essentially what you and your users are seeing is the first blank record in your tbl_Test, but displayed on a form instead.
Entering data in these fields on the form...
...will put that data in to the table we specified in the form's record source...
So hopefully you can see that setting the form's record source property to that of your table, is much cleaner than trying to get an append query to collect data from your form and deliver it your table.
At this point you're probably asking a few questions:
When I have filled in the fields for a record on my form, how do I save that record?
More can be said about this, but for brevity, I'd recommend using a command button for running some vba to save the record; similar to what you've done, but utilising the form's Dirty property instead using an append query:
Here's the click event VBA for my save button example:
Private Sub cmdSave_Click()
If Me.Dirty Then
Me.Dirty = False
End If
End Sub
Me.Dirty is a Boolean (True or False) setting for the form; essentially it is automatically set to True when a user changes something on the form. To save those changes, the Me.Dirty setting will have to be set to False.
Me.Dirty is a bit like the swing gate on a sheep pen. When a shepherd puts a sheep (data) in the pen (form) they will open the gate to the pen. The open gate is like the form's Me.Dirty being set to True. To lock the sheep (data) in, the gate needs to be closed, or in the case of forms, the Me.Dirty property needs to be set to False. The VBA above essentially checks to see if the gate was opened and if it was to close it.
How do I move to a new record in the form once I have saved the current one?
Again, I'd give the user a command button to do this and run some VBA on its click event:
Here's the VBA for moving to a new record:
Private Sub cmdNew_Click()
DoCmd.GoToRecord , , acNewRec
End Sub
Summary
There is a lot more to consider than what I've outlined here, such as:
validating data before it is saved
checking other form events (such as close) to ensure data entry is not lost
navigating to an existing record
But hopefully what I've given you here is at least pointing you in a better direction. Best of luck!
Access is optimized for bound forms. Unbound forms are for special cases only.
Should you prefer unbound forms (could be for many reasons), Access isn't worth the trouble, and you should turn to Visual Studio and WinForms.
In either case, browse for a beginner's tutorial.
If you want to use an unbound for or unbound controls you can create the SQL string in the procedure:
Dim mysql as String
mysql = "INSERT INTO tbl_test ( [source], [reg_No]) VALUES (Me.txt_Source, Me.txt_RegNo)
DoCmd.RunSQL mysql

How to force the update of an Access combobox?

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.

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

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

access to oldValue for control in continous form generates error 3251 in beforeUpdate when a checkbox on form is updated

This is one of the stranger issues I have seen in MS Access. I have the following code in a continuous form:
Private Sub thisForm_BeforeUpdate(Cancel As Integer)
If Not Cancel Then
Debug.Print "pre-logging data changes..."
' here we need to doublecheck to see if any values changed.
' we simply iterate through the whole list, re-setting oldValue
' and newValue.
For Each control In thisForm.Section(acDetail).controls
If control.ControlType = acTextBox Or _
control.ControlType = acComboBox Or _
control.ControlType = acListBox Or _
control.ControlType = acOptionGroup Or _
control.ControlType = acCheckBox Then
Debug.Print control.Name
oldValues(control.Name) = control.oldValue
newValues(control.Name) = control.value
End If
Next
End If
End Sub
oldValues and newValues are Dictionary objects (although likely not related to the issue).
My form has 3 textbox controls, and a checkbox control. One of the text box controls is disabled, and is populated via the results of a simple inner join (to get the human readable name associated with a foreign key). The data source comes from the form's recordsource (no DLookup or anything is used).
If I edit one of the other two textbox controls, this code runs absolutely fine. HOWEVER, if I toggle the checkbox on the form, i get a runtime error 3251. In the watches window, I get the error again when i try to view the properties of "control". It shows the value of oldValue for the disabled control to be "Reserved Error".
If it did this consistently, I would think it was due to the control being disabled; but since it works without a problem when the other textboxes receive edits, and only breaks when the checkbox is toggled; I am stumped. I'm almost inclined to believe I found a bug in access, but I could use some extra input.
Anyone else every encounter an issue like this?
EDIT: Upon digging further, I found that in actuality only one of the 3 editable fields will not trigger this error. It holds string data. The other two controls hold a date value, and a yes/no value. Now I am even more confused.
i've got two ideas to that issue.
First one: If the RecordSource of your Form is an ODBC-Table thats linked to a SQL-Server then you should set a standard value for the CheckBox-Column. Otherwise it will try to set NULL to False and throw an error saying that somebody else edited the current record.
Second idea: Sometimes Access just has a little "hiccup" when it compiles the code. You could make a backup of your database and then try to decompile it using "C:\Program Files\Microsoft Office 2007\Office12\MSACCESS.EXE" "C:\yourFolder\yourDatabase.accdb" /decompile in the Run... Window (of course you have to insert the Path as it is on your machine). That often helps solving strange Problems.

How can I make a combo box readonly in Access 2000?

This seem so simple but I can't figure it out. It seems like I'd want to set the Locked property to Yes, but that actually prevents them from selecting anything in the drop down!
When I say readonly, I mean having the combo box preloaded with values and forcing the user to only select one of those values without being able to type in their own.
Any ideas?
The standard way to achieve this is to set the Limit To List property to True.
The user will be able to type in invalid text but if he tabs away or presses Enter a popup will say
The text you entered isn't an item in
the list
And he will be forced back to choose something from the list.
It's not great, compared to the true dropdown list control you get elsewhere (VB6, Winforms, .NET, etc)
There is a Limit To List property you should set to Yes
There are two parts to what you've requested:
a. I mean having the combo box preloaded with values...
For this, there's nothing you need to do -- just set the rowsource appropriately. To add values to the list, you'd have to do some work, but in order to prevent, you need do nothing at all.
b. and forcing the user to only select one of those values without being able
to type in their own.
As explained in other answers, you need to set the LimitToList property to TRUE.
Now, the error messages you'll get from this are not all that great, so you'll likely want to define the NotInList event to be a little more friendly to the user. Something like this could be appropriate:
Private Sub MyCombo_NotInList(NewData As String, Response As Integer)
MsgBox Chr(34) & NewData & Chr(34) & _
" is not one of the choices in the list.", _
vbInformation, "Select from the list"
Me!MyCombo.Undo
Response = acDataErrorContinue
End Sub
If you wanted to add to the list, this is where you'd handle that, too, but since you don't, you need only concern yourself with informing the user of the problem (i.e., protecting them from the cryptic default error message).
you can also add this code into presskey event of combobox
KeyAscil = 0
msgbox " please select from list"
this will prevent users from write into combobox and show message error