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
Related
I'm seeking advice for sorting an Access (2013) form's data source by accepting a single letter typed by the user. This question is more about a design strategy more than actual code, although code samples may be helpful as well.
The system I developed for a client has a form with a dataset that is sorted by a set of parameters I defined. All works and I have provided some real-time resorting methods for the users which can dynamically re-sort the bound dataset as needed. Again, all is working fine.
However, the system they used prior to this (a green screen terminal application believe it or not) had one feature they still miss very much and which should be easy to implement. That feature is to see the data on the screen of customers, for example, (which is a very long list) and to be able to either (1) type the first letter of the last name to automatically "scroll down" to that part of the list or (2) to filter the dataset for that first letter ALL BY TYPING JUST THAT LETTER. I proposed creating an unbound field they could mouse to, type a letter and hit enter but the feedback was that it takes time to mouse to, click, type and press enter. I proposed making buttons of each letter they could click to accomplish the same goal but again the same feedback.
This feature does make sense. If you have a list of 8,000 customers and you want to get quickly to the people with a last name starting with the letter "R" for instance, mousing to a scroll bar and dragging can become tedious for users doing this all day long. Instead, it would be a good idea if they could simply press the R key and go straight there.
I suspect the answer is simple but I just haven't found it. Is it setting a keydown event somewhere?
Set the form's KeyPreview property to True to give it a first shot at any keystrokes, then add a KeyDown event handler to the form like this (untested):
Private Sub Form_KeyDown(ByVal KeyCode As Integer, ByVal Shift As Integer)
Dim NoTextBoxFocused As Boolean
' Make sure we're not stealing text from focused control
NoTextBoxFocused = TypeName(ActiveControl) <> "TextBox" And _
TypeName(ActiveControl) <> "ComboBox"
If KeyCode >= vbKeyA And KeyCode <= vbKeyZ And NoTextBoxFocused Then
' PSEUDOCODE: Sort here, presumably case-insensitive;
' otherwise, check for capitals with this:
' If (Shift And acShiftMask) <> 0 Then
KeyCode = 0 ' Turns off further handling
End If
' Otherwise, fall through without doing anything else
End Sub
You might also want to expand this to a full incremental search, complete with a label or textbox shown only when searching which filters progressively as the users type in the first few letters of whatever they want. If you show a textbox, make sure it's easy to start a new incremental search (with Esc or perhaps just a timeout). That should help wean them off missing the old program.
Simply use any control (combobox, listbox, textbox, button) and in an AfterUpdate Or OnClick Event run an ApplyFilter embedded macro or vba RunCommand using an SQL LIKE clause:
ComboBox: Letter Filter
Row Source: "A";"B";"C"; ... "Z"
Row Source Type: Value List
ApplyFilter
Filter Name: <blank>
Where Condition: ="[LastName] LIKE '" & [Forms]![Customers]![LetterFilter] & "*'"
Control Name: <blank>
Alternatively, with same controls and trigger events, update the form's recordsource:
Recordsource: SELECT * FROM [Customers]
WHERE [LastName] LIKE [Forms]![Customers]![LetterFilter] & '*'
OR synonymous query using Left()
Recordsource: SELECT * FROM [Customers]
WHERE Left([LastName], 1) = [Forms]![Customers]![LetterFilter]
OR SQL query in VBA:
Forms!Customers.Form.RecordSource = "SELECT * FROM [Customers]
WHERE [LastName] LIKE '" & [Forms]![Customers]![LetterFilter] & "*'"
Remember setting RecordSource is not in VBA but the query window
In Access Properties for a subform, I have "LinkChildFields = '' " and ""LinkMasterFields = '' " (i.e. blank for both, this is what I want: no link)
When I change the Recordsource of the subform, and requery the subform, (in VBA) they are both automatically being set to a field, in my case "CaseID" (resulting in no records being displayed)
This is my recordsource:
stSQL = "SELECT qryCasesAndCards1.CaseID, qryCasesAndCards1.CaseStatusID, qryCasesAndCards1.Status, qryCasesAndCards1.CompanyName, qryCasesAndCards1.NumberOfCards, qryCasesAndCards1.FullName, qryCasesAndCards1.CaseCreatedDate, qryCasesAndCards1.CaseClosedDate, qryCasesAndCards1.CreatedBy" & _
" FROM qryCasesAndCards1 where not caseID = " & Me.CaseID & " and cardnumber in (select qryCasesAndCards1.cardnumber from qrycasesandcards1 where qryCasesAndCards1.caseID = " & Me.CaseID & ")"
I've tried substituting a simpler query "select * from qryCases1" and the problem still occurs.
In VBA, right after the requery, I am debug.print'ing ".linkchildfields" and ".linkmasterfields" and this is how I can see they are both being automatically set to "CaseID"
I have tried changing both values in Access form properties to nonsense value, resaving, changing again, resaving etc, and still no joy.
I can workaround problem by setting both those values right after the recordsource runs, but there is an unacceptable delay when doing this (about 5 seconds for each value)
One thing I'm just wondering now, is wether my form filter is being propagated to the subform? (am opening it via "docmd.open ... caseid = x" )
CaseID might have at ONE stage some long time ago been entered in to those link fields... but it's definitely not now. It's like they're locked in an Access vault somewhere and it's thinking "golly gee i'm pretty sure he wants CaseID in there gee whiz I'll get right on that!"
I've found MSAccess: Change in subform recordsource causes loss of LinkChildFields binding and I've found Linking SubReports Without LinkChild/LinkMaster but I can't get any help from them.
Thanks for any help offered: I'm tearing my hair out :)
To keep Access from automatically updating the LinkChildFields and LinkMasterFields on a subform...
Ensure that the RecordSource queries do not have matching key fieldnames.
Ensure that the Link Child Fields and Link Master Fields are blank on the SubForm object's Property Sheet.
DO NOT set the LinkChildFields and LinkMasterFields properties in code (i.e. remove any previous code like MySubForm.LinkChildFields = ""), otherwise the negative side effects may not be resolved.
If the form and/or subform are bound directly to tables, simply update one of them to query the table and add an alias for one of the key fields. For existing queries, it is possible to add an alias for one of the key field names without negative consequences in probably most cases. In my case, I only had to add "AS Acct" to a single field in the SELECT part of the subform's RecordSource SQL--no other change was necessary in the FROM, WHERE or ORDER BY clauses even though the original [Account] fieldname is referenced in each of those clauses.
SELECT Actions.[Account] As Acct, ...
Of course, this required an update to the ControlSource property for the relevant control on the form.
These changes eliminated the extra few-second lag when changing both the parent record and when switching records on the subform! Likewise, it eliminated redundant events during record navigation!
I found no other setting that keeps Access from automatically re-binding the subform via the LinkChildFields and LinkMasterFields properties. As already pointed out in the comments, it also doesn't help by resetting the link fields (e.g. LinkChildFields = "") after setting RecordSource, primarily because Access takes time to reset the fields, much of that time spent in events fired both upon the automatic assignment of the link fields and again when they are reset to blank. (e.g., Form_Current is called at least twice for each new record navigation.)
Years passed... as even on Microsoft Access 2019, changing subform .RecordSource always alters original .LinkChildFields and .LinkMasterFields, reassigning good fields will cost us 5s time for each property, we have no way to avoid this.
As a workarround, we can use always the same query, for example, qrySelTmp, as .RecordSource for the subform, at the design time
Mainform.subform.Form.RecordSource = "qrySelTmp"
When we must change .RecordSource content, we change only the definition of the Query, like this
CurrentDb.QueryDefs("qrySelTmp").SQL = "SELECT qryCasesAndCards1.CaseID, qryCasesAndCards1.CaseStatusID, qryCasesAndCards1.Status, qryCasesAndCards1.CompanyName, qryCasesAndCards1.NumberOfCards, qryCasesAndCards1.FullName, qryCasesAndCards1.CaseCreatedDate, qryCasesAndCards1.CaseClosedDate, qryCasesAndCards1.CreatedBy" & _
" FROM qryCasesAndCards1 where not caseID = " & Me.CaseID & " and cardnumber in (select qryCasesAndCards1.cardnumber from qrycasesandcards1 where qryCasesAndCards1.caseID = " & Me.CaseID & ")"
If(Mainform.subform.Form.RecordSource = "qrySelTmp") then
Mainform.subform.Form.Requery
else
Mainform.subform.Form.RecordSource = "qrySelTmp"
End if
As shown in the code, we do only a subform .Requery instead of assigning a new .RecordSource.
As Subform.Form.Requery does not alter subform properties .LinkChildFields and .LinkMasterFields, we gain about 8s for each real change of .RecordSource.
In my case with 1 Customer - Many Orders diagram, I've a gain of time at the proportion of 2s over 20s.
What I done in the past is simply REMOVE the link child/master settings.
Then in code I SET the child critera. So in the following, the link master/child SHOULD filter by tour_id, but I simply INCLUDED the criteria in the SQL and then HAMMER OUT the settings.
Dim strSql As String
' load sub-form to this tour....
strSql = "select * from qryGuidesForTour2 where Tour_id = " & Me!ID
Me.subGuides.Form.RecordSource = strSql
Me.subGuides.LinkChildFields = ""
Me.subGuides.LinkMasterFields = ""
The above was a fix and fixed any performance issues. I suppose it really depends on "how" the main form record is set, but if you are setting up the "main" form record via code, then as above simply stuff in the child forms recordset directly with the critera you need. And while in above I did blank out the master/child settings, if you ARE going to allow adding of reocrds in the sub form, then likely in above you should set the master/child fields as opposed blanking them out.
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.
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.
According to the Access (2003) documentation, when you delete a record, it's placed in a buffer, then the BeforeDeleteConfirm event is raised, then the AfterConfirmDelete ad Delete events. In my table if a record is deleted, I need to add one of its field values (a quantity value) to a field in another table.
I tried setting the quantity to a private variable in the Current event, but as soon as I delete click delete, it looks like the Current event fires again and resets my variable.
How can I access the value of a field in a deleted record?
The values in bound controls are still available at the form delete event. In this sample, txtid is a bound text box.
Private Sub Form_Delete(Cancel As Integer)
Dim strActivity As String
Dim strSql As String
strActivity = Me.txtid & " deleted."
strSql = "INSERT INTO tblAudit (activity) " & _
"VALUES (""" & strActivity & """);"
CurrentDb.Execute strSql, dbFailOnError
End Sub
This solution may require some redesigning, but it's what I use for a form of my own:
Add a boolean field to your record table called "Visible".
On your main form, set your form.filter="[Visible]=True"
On your main form, set AllowFilters=Yes.
On your main form, set your Form.Allow Deletions=False (we don't want them actually deleting the record)
Make a custom Delete command button that sets Visible=False.
If you feel the need later, you could add some maketable queries and delete queries to clean up your main table and move the deleted values off to another table.
This way, the user doesn't actually 'delete' the record. Rather, they just make that record invisible. And later, when the form is unloaded, you could add in some clean-up queries.
Generally, if I want to do something in the OnDelete events other than the default actions, I will tend to turn AllowDeletes OFF and create a DELETE RECORD command button that does what I want.
Thus, I don't depend on the built-in events. If I need to store a value, I do it, the pop up the confirm message, and then do what's needed, by whichever method I consider easiest.
I just find this a lot easier, particularly because user settings can cause things to behave differently. For example, the two Confirm events don't happen when the users has these settings (from the A2003 help file, but it's always been this way):
Note The AfterDelConfirm event does
not occur and the Delete Confirm
dialog box isn't displayed if you
clear the Record Changes check box
under Confirm on the Edit/Find tab of
the Options dialog box, available by
clicking Options on the Tools menu.
So I just avoid the whole thing by rolling my own deletion code and avoiding the default behaviors.
(and yes, if you've followed my posts over the years, you know that it's pretty unusual for me to advise against simply using default Access behaviors, but this is one case where I feel more control is appropriate in almost all cases)