I have set the row source to the same table for two different unbound comboboxes, cbo_MaxCost and cbo_MinCost, on a form. These comboboxes are meant to specify the minimum and maximum of a range of values for each record in another table. To ensure they do, I've set up code in each combobox's AfterUpdate event to check that
cbo_MaxCost >= cbo_MinCost
or warn the user with an error.
The problem is, this check performs erratically. For example, sometimes Access VBA will evaluate
21 > 11
as False and other times as True. Usage of the dot and exclamation point notations makes no difference, nor does using "Me." or not, or ".Value" or not. Stepping through it shows that Access registers the proper values from the comboboxes during the comparison. This behavior seems to happen more often when I select a much higher or lower value than was previously in one of the boxes. I can't figure out what causes it.
This is the code:
Private Sub cbo_MaxCost_AfterUpdate()
If cbo_MaxCost >= cbo_MinCost Then
MsgBox ("Access is calculating correctly.")
Else
MsgBox ("The maximum cost should be higher than the minimum.")
End If
End Sub
Private Sub cbo_MinCost_AfterUpdate()
If cbo_MaxCost >= cbo_MinCost Then
MsgBox ("Access is calculating correctly.")
Else
MsgBox ("The maximum cost should be higher than the minimum.")
End If
End Sub
Have in mind, that a combobox/listbox always returns text, so convert to numeric before the comparison:
CCur(Me!cbo_MaxCost.Value) >= CCur(Me!cbo_MinCost.Value)
Related
Good morning!
I have a "fancy" search function in Microsoft Access where the list of possible options shrinks as you type in the search field. Unfortunately the computer and server can't keep up with these rapid requeries of the data.
Currently the command to requery with the field in the 'onchange' function of the search box. I'd like to add a delay so it only runs the requery when the search box has not changed for a second. Thus if someone types in a 8 letter word, it isn't running 8 requeries.
The current idea I have for it, which I know there must be something better, is..
"On change, set search box value to X and wait 1 second. After 1 second, if X = search box value, run the requery. An issue is that it would be rapidly rewriting the X value and have a 'wait' command floating for each letter.
Hopefully there's a way to write an event trigger of "When field X has changed, but not changed for the past second."
Thank you!
As requested, here is my current code
'Create a string (text) variable
Dim vSearchString As String
'Populate the string variable with the text entered in the Text Box SearchFor
vSearchString = SearchFor.Text
'Pass the value contained in the string variable to the hidden text box SrchText,
'that is used as the sear4ch criteria for the Query QRY_SearchAll
SrchText = vSearchString
'Requery the List Box to show the latest results for the text entered in Text Box SearchFor
Me.SearchResults.Requery
Me.SearchResults2.Requery
'Tests for a trailing space and exits the sub routine at this point
'so as to preserve the trailing space, which would be lost if focus was shifted from Text Box SearchFor
If Len(Me.SrchText) <> 0 And InStr(Len(SrchText), SrchText, " ", vbTextCompare) Then
'Set the focus on the first item in the list box
Me.SearchResults = Me.SearchResults.ItemData(1)
Me.SearchResults.SetFocus
'Requery the form to refresh the content of any unbound text box that might be feeding off the record source of the List Box
DoCmd.Requery
'Returns the cursor to the the end of the text in Text Box SearchFor,
'and restores trailing space lost when focus is shifted to the list box
Me.SearchFor = vSearchString
Me.SearchFor.SetFocus
Me.SearchFor.SelStart = Me.SearchFor.SelLength
Exit Sub
End If
'Set the focus on the first item in the list box
' Me.SearchResults = Me.SearchResults.ItemData(1)
Me.SearchResults.SetFocus
'Requery the form to refresh the content of any unbound text box that might be feeding off the record source of the List Box
DoCmd.Requery
'Returns the cursor to the the end of the text in Text Box SearchFor
Me.SearchFor.SetFocus
If Not IsNull(Len(Me.SearchFor)) Then
Me.SearchFor.SelStart = Len(Me.SearchFor)
End If
Obviously this is not MY code, it's from somewhere on the interweb. It works fantastic for databases stored locally, but everything is moving to our Sharepoint server which is running on a 386 in a moldy basement powered by a narcoleptic gerbil.
You can simply use the Timer of the current form. No need for a separate form or anything.
Private Sub DoSearch()
' Your current code
' but you should look into removing as many "Requery" from there as possible!
End Sub
Private Sub SearchFor_Change()
' Wait for x Milliseconds until the search is started.
' Each new change restarts the timer interval.
' Use 1000 (1 s) for slow typists or a really slow server
' 200 ms feels right for a normal typist
Me.TimerInterval = 200
End Sub
Private Sub Form_Timer()
' Disable timer (will be enabled by the next SearchFor_Change)
Me.TimerInterval = 0
' Now run the search
DoSearch
End Sub
Note: you may need to move some of the cursor-handling code from DoSearch() to SearchFor_Change(), specifically:
Me.SearchFor.SelStart = Len(Me.SearchFor)
Assign a shortcut key like (Ctrl+ J) to the logic in on change event and call it on demand once you have finished typing search keyword.
Remove on change event.
Create other procedure which has the logic of on change event and assign a shortcut key
Press shortcut to get search suggestion
Other approach
Add below validation to Change event which will check for length of string and will trigger only if length of string is >=8
Private Sub txtSearch_Change()
If Len(Nz(txtSearch.Text, 0)) >= 8 Then
End If
End Sub
I'm going a little outside my comfort area, since I hardly use MS Access forms, but why are you bothering the Server/Database so much? In my experience, each query costs the same amount of time, whether it returns 1 record or 100,000 records.
So even before the user types anything, why don't you just do a single query to return a sorted list. After that, it takes almost no time to use VBA to process the results and find everything in the list that starts with whatever the user types in (it's sorted after all).
Except for the initial load, users who are local to the database or on the other side of the world will experience the same snappy response from your interface.
----------
Like I said, I haven't messed with Access Forms a lot, so this is more of a strict VBA solution. Maybe there is a better way to do it without going outside the Access Forms box that someone could enlighten us with.
You should basically just call LoadItemList when you load the form, or whenever you need to.
Public dbConn As ADODB.Connection
Private ItemList As Variant
Private RecordCount As Long
Sub LoadItemList()
Dim SQL As String
Dim RS As New ADODB.Recordset
SQL = "SELECT T.Name FROM Table T"
Set RS = dbConn.Execute(SQL)
If Not RS.EOF Then
ItemList = RS.GetRows
RecordCount = UBound(ItemList, 2) - LBound(ItemList, 2) + 1
End If
End Sub
Then replace DoCmd.Requery with AddItemtoCombobox SearchResults, SearchFor.Text
Sub AddItemtoCombobox(Control As ComboBox, Filter As String)
Dim Index As Long
Control.Clear
If Not IsEmpty(ItemList) Then
For Index = 0 To RecordCount - 1
If ItemList(Index) Like Filter Then Control.AddItem ItemList(Index)
Next
End If
End Sub
Again, maybe there is a better way that is built into Access...
The technical term that you're looking for is debounce.
What you can do is on your on change event, keep track of the current search string
in terms of pseudocode.
sub onChange()
Form.timerinterval = 0
setSearchString
form.timerinterval = delay
So in terms of the explanation, if your on change is called, disable the timer. Update your search string, then reset the timer to fire after a certain amount of time. The form should be a hidden form that contains the code that you want to execute
I am creating a database and I have a table where I am collecting if a patient has been or is currently on specific medications.
I have a list of 17 medications that we care about and have a yes/no checkbox for each one. If a patient is or has ever been on one of these medications we want to collect 9 additional fields.
I want to create a form that lists only the medications and checkboxes. When the user checks a checkbox I then want the additional fields to appear for them to fill out.
Most patients will have only been on 2-5 of these medications, so I don’t want to clutter the form with unnecessary blank fields.
Is there a way to do this without VBA? If no, will someone give me an example of what the VBA code should look like?
Thanks so much! We collect this data as part of an ongoing registry monitoring the long term safety of a specific research medication. We usually collect this data differently when the patient is in clinic however, in lite of the pandemic we need to do this via telephone and this database will be crucial in ensuring the continued collection of this vital data!!
Thanks,
Allen
What you can do is to rely on the fact that Yes/No fields are stored as 0 (False) and non-0 (True). You can then have a small piece of VBA code behind the form that adds all of the Yes/No fields to determine whether to display the additional text boxes or not:
Sub sCheckMedication()
On Error GoTo E_Handle
If (Me!Medication1 + Me!Medication2 + Me!Medication3) <> 0 Then
Me!txtMedicationNotes.Visible = True
Else
Me!txtMedicationNotes.Visible = False
End If
sExit:
On Error Resume Next
Exit Sub
E_Handle:
MsgBox Err.Description & vbCrLf & vbclf & "sCheckMedication", vbOKOnly + vbCritical
Resume sExit
End Sub
You would then call this procedure in the Form's Current event (to cover when the form is first loaded, and also when navigating between records), and also on the CheckBox's Click event:
Private Sub chkMedication1_Click()
Call sCheckMedication
End Sub
Regards,
Currently I'm creating a program in excel that runs solver. I have set a maximum time limit on the solver command. If the program exceeds the time limit, a solver dialogue box comes up that asks whether to continue or stop. I was wondering if there was a way to code into VBA to automatically select stop rather than having to have a user click the option.
Thanks in advance!
Yes, you can. You need to set some options on the Solver object. You can read more about it In the MSDN documentation
SolverSolve(UserFinish, ShowRef)
UserFinish Optional Variant. True to return the results without displaying the Solver Results dialog box. False or omitted to return the results and display the Solver Results dialog box.
ShowRef Optional Variant. You can pass the name of a macro (as a string) as the ShowRef argument. This macro is then called, in lieu of displaying the Show Trial Solution dialog box, whenever Solver pauses for any of the reasons listed
You need to define a macro that runs, which must take an integer argument. Here is a code example, straight from the linked page (posted here in case the link is broken in the future)
You call SolverSolve, passing it the arguments above:
SolverSolve UserFinish:=True, ShowRef:= "ShowTrial"
You then need to define the ShowTrail macro that runs, and it must have the correct signature:
Function ShowTrial(Reason As Integer)
'Msgbox Reason <= commented out, as you just want it to end, with no user input
ShowTrial = 0 'See comment below on the return value.
End Function
The return value is important. Return 0 if you want solver to just carry on regardless, or 1 if you want it to stop.
You could then get it to have different behavior on different reasons it finishes. Eg, if it reaches maximum time limit (your case), then stop. Otherwise, carry on:
Function ShowTrial(Reason As Integer)
Select Case Reason
Case 1 '//Show iterations option set
ShowTrial = 0 '//Carry on
Exit Function
Case 2 '//Max time limit reached
ShowTrial = 1 '//Stop
Exit Function
Case 3 '//Max Iterations limit reached
ShowTrial = 0 '//Keep going
Exit Function
Case 4 '//Max subproblems limit reached
ShowTrial = 0 '//Keep Going
Exit Function
Case 5 '//Max feasible solutions limit reached
ShowTrial = 0 '//Keep going
Exit Function
End Select
End Function
How do I return a value from a VBA Function variable to a Defined Name in Excel 2010?
In the example below, I want i to be returned to Excel as a Defined Name Value_Count that can be reused in other formulas, such that if MsgBox shows 7, then typing =Value_Count in a cell would also return 7.
Everything else below is about what I've tried, and why I'd like to do it. If it's inadvisable, I'd be happy to know why, and if there's a better method.
Function process_control_F(raw_data As Variant)
Dim i As Integer
i = 0
For Each cell In raw_data
i = i + 1
Next cell
MsgBox i
End Function
My goal is to have the value returned by the MsgBox be returned instead to a Defined Name that can be reused in other forumulas. However, I cannot get the value to show. I have tried a variety of forms (too numerous to recall, let alone type here) similar to
Names.Add Name:="Value_Count", RefersTo:=i
I am trying to accomplish this without returning a ton of extra info to cells, just to recall it, hence the desire to return straight to a Defined Name.
I'm using a Function rather than Sub to streamline my use, but if that's the problem, I can definitely change types.
I am creating a version of a Statistical Control Chart. My desired end result is to capture a data range (generally about 336 values) and apply a series of control rules to them (via VBA), then return any values that fall outside of the control parameters to Defined Names that can then be charted or otherwise manipulated.
I've seen (and have versions of) spreadsheets that accomplish this with digital acres of helper columns leading to a chart and summary statistics. I'm trying to accomplish it mostly in the background of VBA, to be called via Defined Names to Charts — I just can't get the values from VBA to the Charts.
The interest in using a Function rather than a Sub was to streamline access to it. I'd rather not design a user interface (or use one), if I can just keystroke the function into a cell and access the results directly. However, as pointed out by Jean-François Corbett, this is quickly turning into a circuitous route to my goal. However, I still think it is worthwhile, because in the long-term I have a lot of iterations of this analysis to perform, so some setup time is worth it for future time savings.
With minor changes to your function, you can use its return value to accomplish what you want:
Function process_control_F(raw_data As Variant) As Integer ' <~~ explicit return type
Dim i As Integer
Dim cell As Variant ' <~~~~ declare variable "cell"
i = 0
For Each cell In raw_data
i = i + 1
Next cell
process_control_F = i ' <~~~~ returns the value i
End Function
You can then use that function in formulas. For example:
I have a combo box (Status) which includes the following:
Shortage
Allocated
Actioned
Acknowledged
Complete
I also have 5 other date fields which are as follows:
Shortage_date
Allocated_date
Actioned_date
Acknowledged_date
Complete_date
However I want this status to be populated automatically based on what data has been entered in my previous fields.
For example, once shortage_date has been populated with a valid date (00/00/0000) I want the "status" to change to "shortage".
Once allocated_date has been populated with a valid date (00/00/0000) I want the "status" to change to "allocated".
I saw this bit of code online but I'm totally confused:
Private Sub Textbox1_AfterUpdate()
If Textbox1.Value = "1" Then
Textbox2.Value = "10"
End If
End Sub
I believe mine should look something like this but I dont know what I need to make sure it validates the date.
Private Sub shortage_date_AfterUpdate()
If shortage_date.Value = "(I want to valididate the date here)" Then
Status.Value = "Status"
End If
End Sub
Hope I make sense!
Firstly, I would set up an Input Mask on the field itself. You can do that by putting the form into design view, then select the field, then go the Property Sheet which isAlt+Enter if it isn't open, then select the Data tab and set up a Input Mask. That will handle your validation part so you don't have to in code.
Then you should be able to just use the code:
Private Sub shortage_date_AfterUpdate()
If Nz(shortage_date.Value, "") <> "" Then
Status.Value = "Status"
End If
End Sub
The If statement is just to make sure that it doesn't reset the value back to the original every time the date is changed. Also here is link where you can read about Input Masks: https://msdn.microsoft.com/en-us/library/office/ff821336.aspx
Update: Changed to Input Mask instead of Validation Rule