I have a combobox in my form with a table-filled list as rowsource. When the user enters the field, changes something and delete it so that the combobox is empty I get the error "You tried to assign a null value to a variable that is not a variant data type".
The problem is that my combobox does not allow an empty string but i also cannot find an event in the combobox that fires before the error. So I cannot use Len or IsNull to catch the error. Currently I use this "work around" but it is non-specific as it uses the error event of the form not of the combobox:
Private Sub Form_Error(DataErr As Integer, Response As Integer)
Me.myField.Undo
Response = acDataErrContinue
End Sub
Is there a better way to handle this issue?
I found this. But that did not solve the problem.
As Remou indicated in the comments, the error is in response to what you are doing after a new value is entered into the combobox. Check the combobox events. If you don't mind about the type and want to use null values, you can change your variable to a Variant type. If you are using an actual data type such as string or integer then use the NZ() function to screen out nulls.
For example, if you want to assign an empty string to a string variable if the combobox is null then you can do:
dim s as string
s = nz(me.combobox,"")
As for firing before the error - somewhere in your code you already have the offending code that is causing the error. You need to locate that. More information will be required from you in order to provide a more detailed and specific answer.
It's an undocumented Microsoft "Feature" designed to drive you crazy. And the people who tell you "look in your code", "somewhere in your code" don't know what they are talking about.
Two workarounds. Change the underlying field in the table to allow nulls, OR, and I think this is the best way, change your combo to an unbound control and set the underlying field in the AfterUpdate event.
Related
I've created a function that attempts to return a SubForm data type. This function is used by various parent Forms.
The function looks like this:
Public Function mySubFrm(name As String, subformName As String) As SubForm
Dim frm As Form
Dim subFrm As SubForm
Set frm = Forms(name)
Set subFrm = frm.Controls(subformName)
mySubFrm = subFrm
End Function
I've attempted to use it by the following:
Dim testSubForm As SubForm
testSubForm = mySubFrm("testForm", "testSubForm")
Immediately, it follows with compile error:
Invalid use of property
What I've attempted to do was add a watch at frm.Controls(subformName) and I see its return type is SubForm/SubForm, so I feel as though I am declaring and setting the right data type, but then again I'm not sure?
Can someone assist me with what I'm not doing properly?
Thanks
I don't know much Access, but I know VBA pretty well.
Your function is returning an object reference:
Public Function mySubFrm(name As String, subformName As String) As SubForm
As such, its return value must be assigned using the Set keyword:
Set mySubFrm = subFrm
The reason why you're getting this confusing error, is because of a lovely (not!) thing in VBA called default properties.
See, a form has a default property, most likely its Controls collection - and that property is only exposing a Public Property Get accessor, which makes it read-only.
So when you omit the Set keyword:
mySubFrm = subFrm
VBA assumes the code is legal, and so the only thing you could possibly be wanting to do, is to assign that default property - in other words, it's behaving exactly as if you would have written:
mySubFrm.Controls = subFrm
But the Controls class' own default property is its Item member, which is also read-only: there's no way the default Controls property can appear on the left-hand side of an assignment.
Hence, invalid use of property.
My open-source project Rubberduck will soon have an inspection that will issue a result whenever you're implicitly referring to an object's default property, offering to make the call explicit. Writing code that says what it does, and does what it says is hard when you don't even know you're referring to a default property.
You receive the error because you're trying to set an object, but are not using the Set keyword.
Perhaps it should be the following:
Set mySubFrm = subFrm
Or
Set mySubFrm = frmDatasheet
I would like to suggest a different approach. Typically when you create a form with a subform, it's quite rare that you actually need the subform to be dynamic. There is almost always a relationship between the parent form and the child form, and I would consider a parent form without its child a broken object in general.
Therefore, when I need to access things that's on the subform, I prefer to use the explicit version:
Dim mySubForm As Access.SubForm
Set mySubForm = Me.Controls("mySubForm").Form
mySubForm.SomeControlOnMySubForm.Value = 123
Yes it's 3 lines now but the advantage is that the reference to a specific form is now made explicit in your parent form. You now can see from the code that the parent form depends on that subform. More importantly, if you delete or rename the control SomeControlOnMySubform, the above form will allow the VBA compiler to warn you that there is no such object on the subform, enabling you to verify your code.
In other words, try your best to convert any potential runtime errors into compile-time errors because compile-time errors are much easier to validate and prevent than runtime errors.
The same principle works in the reverse; when you want to describe your subform as explicitly depending on a parent form, you can do:
Dim myParent As Form_frmParent
Set myParent = Me.Parent
NOTE: The code all assumes that there are code-behinds for all forms involved. If a form has no code-behind, it won't have a VBA class for you to reference. In such case, I set the form's property HasModule (located in the Others tab) to Yes even if there's ultimately no code-behind. (The reason is that it used to be that you could create "lightweight" form by creating it with no code-behind but with modern hardware, there is hardly any difference)
I have an Access 2000 form that contains a combobox. The combobox is bound to a field in a table. When the value in the table is null, I want to set a default value on combobox without making the record dirty. Setting defaultValue does not work unless it is a new record. When I try to set the value, I get an error "You cannot assign a value to this object".
Any thoughts?
Me.cboName.Value = Me!cboName.Value ' This causes the error mentioned above
Me.cboName.DefaultValue = Me!cboName.Value 'This does nothing on an existing record.
The DefaultValue is entered when a NEW RECORD is created. To have the value display for an existing record... the easiest way I can think to accomplish this is to usean unbound control. For example, if the field you are using is theName in the Current event you'd use code like this:
Private Sub Form_Current()
me.cboName.value = Nz(me.theName.value,defaultValue)
End Sub
Where defaultValue is your previously determined default value. This will effectively require you to have two controls for the name... One with the bound value and one with the displayed value. If you do this, you'll have to also add code to update theName, when you change cboName.
As Remou suggested, you should ask yourself if this is really what you want to do, as it is definitely at least a little messy.
I have a database with information about visitors to our department. Here is my setup: A user selects a visitor name from a listbox. Access assigns the values from that visitor's record to temporary variables and opens an unbound form where those variables are the default values in the text field. When a user changes a value in a textbox, an After_Update event uses SQL to update the corresponding field in that visitor's record and assigns the new value to the temporary variable.
After_Update example:
Dim strRUN As String
strRUN = updateVisitorOnDirty(Me.txtVisitorTravelMethod, "VisitorTravelMethod", 1)
HideWarnings (strRUN)
TempVars!strVisitorTravelMethod = Nz(Me.txtVisitorTravelMethod.Value, "")
"updateVisitorOnDirty" is a function that returns SQL to update a field (specified in second argument) in the table with visitor information with the first argument. (The number at the end is says it's a string.) "HideWarnings" turns warnings off, executes the SQL, and then turns warnings back on.
My problem:
When a user clears a textbox, the After_Update event makes the textbox revert to it's previous value.
I thought at first that maybe the unbound textbox was reverting to its default value when it was cleared, but I put a breakpoint in the sub that fires during After_Update:
If I change a value in mytextbox from a to b, debug.print shows the value of mytextbox as b. If I change a value in mytextbox from a to (blank, or empty string), debug.print shows the value of mytextbox to still equal a.
My workaround has been to include a little "x" near the textbox that the user can click to 1)clear the textbox, 2)update the temporary variable, and 3)update the table. It works fine, but it seems like there should be a better way. Help?
Please let me know if you need any other information or if I should otherwise re-word this. I come here all the time for answers, but this is the first time I've actually submitted my own question. Thank you!
(I edited this to make the code show up correctly...didn't leave a line before.)
I figured out that the unbound textboxes were reverting to their default value when a user tried to make them blank. To stop them from doing this, I put this in the AfterUpdate of the textboxes:
Call ResetDefault(Me.textbox1.Text, Me.textbox1)
And defined the function:
Public Sub ResetDefault(ByVal strChange As String, ByRef txtCurrent As TextBox)
If Nz(strChange, "") = "" Then txtCurrent.DefaultValue = ""
End Sub
That is, if a user cleared a textbox, the default value of that textbox was set to "", allowing the textbox to actually go blank.
I think the best approach is to scrap the after_update event on your text boxes and instead either put a save button on your unbound form so that all updates are written in one pass, or capture the forms closing event and update your table then.
This is the advantage of using unbound forms, you do not have to comit data until you are absolutely ready to, but with using an after_update event you are almost mimic-ing the behaviour of a bound form.
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.
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