Using PreviousControl property with nested subforms and controls - vba

I am creating a custom floating number pad so that tablet users can more easily tap numerical data into a form. I'm using toggle buttons (the idea is to eventually highlight them briefly). The following code for the number pad works on my main form's controls. When I click on the 1 button, then a 1 is put into the active control of the main form
Private Sub Form_Activate()
FocusForm = Application.Screen.PreviousControl.Parent.Name
FocusControl = Application.Screen.PreviousControl.Name
End Sub
Private Sub Toggle1_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
'Add a 1 to the currently selected control
Dim refControl As Control
If FocusForm = "" Then End
Set refControl = Forms(FocusForm).Controls(FocusControl)
If (IsNull(refControl)) Then
refControl = "1"
Else
refControl.Text = refControl.Text & "1"
End If
refControl.SetFocus
End Sub
However, if I navigate into the subform then the Screen.PreviousControl properties return the main form's name and the subform's name, but I can't then seem to be able to refer to the subform control's name as well. Bascially I'm looking for a line that does the following
FocusForm = Application.Screen.PreviousControl.Parent.Name
FocusSubform = Application.Screen.PreviousControl.Name
FocusSubformControl = ?????
Similarly, my subform has its own nested subform and I would like to do the same with this
FocusForm = Application.Screen.PreviousControl.Parent.Name
FocusSubform = Application.Screen.PreviousControl.Name
FocusSubform2 = ?????
FocusSubform2Control = ?????
The in-built floating number pad on the tablets is very fiddly to use, hence my coding of this custom one, however if someone knows where I might find code for a customisable number pad that writes directly into whatever window is active, this would similarly be much appreciated!

If called from a subform,Screen.PreviousControlreturns the name of the mainforms subfom-control (as mainform needs to be active to get the subform control (nested in mainform) active), not the subform (but they can have same name, just change controls name and you won't get subforms name returned, but subforms control name!).
Now how to get previous control of the subform? By its.ActiveControlproperty, as form was active before, its active control must be the previous control. (if you want to get previous control, when you stay on the same subform, you need to store it in a module variable)
As there may be multiple subforms inside of subforms, we just loop while the control is a subform and set it to the next subform.ActiveControltill finished.
Why bother with names, if you can use references? This enables using multiple instances of a form.
Just store the reference to the control in Form_Activate and use it inMouseUp:
'On top of forms code module
Option Explicit
Private PreviousFormControl As Access.Control
Private Sub Form_Activate()
On Error Resume Next ' ignore error if no previous control availible
Set PreviousFormControl = Application.Screen.PreviousControl ' this stores the reference not the name!
If Err.Number <> 0 Then
'Handle error here, e.g. disable controls, show msg, ...
End If
On Error GoTo 0 'Reset error handler (not needed only to remind you not just to ignore all errors!
Do While TypeOf PreviousFormControl Is SubForm ' Loop till control is not subform
Set PreviousFormControl = PreviousFormControl.Form.ActiveControl ' if subform, previous control of it is its current ActiveControl
Loop
End Sub
Private Sub Toggle1_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
'Add a 1 to the currently selected control
PreviousFormControl.Text = PreviousFormControl.Text & "1" ' concat "1" to controls text, no need to handle empty text as it can only be an vbNullString(""), even if Null, then Null & "1" => "" & "1" => "1"
PreviousFormControl.SetFocus
End Sub
however if someone knows where I might find code for a customisable number pad that writes directly into whatever window is active
Of course you can use any Virtual Keyboard running on windows that fit your needs. But then you may have to handle things like bring keyboard to front on wanted position.
numpad emulator was the first open source app I found on quick search, but looks goods on short test. Of course there may be better solutions out there, just search and test.

Related

Detect if combobox has been modified in the last Xs [duplicate]

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

Split Form creates a separate collections for each of its parts

I have a split Form in MS Access where users can select records in the datasheet and add it to a listbox in the Form thereby creating custom selection of records.
The procedure is based on a collection. A selected record gets transformed into a custom Object which gets added to the collection which in turn is used to populate the listbox. I have buttons to Add a Record, Remove a Record or Clear All which work fine.
However I thought, that pressing all these buttons is a bit tedious if you create a Selection of more then a dozen records, so i reckoned it should be simple to bind the actions of the Add and Remove buttons to the doubleclick event of a datasheet field and the listbox respectively.
Unfortunately i was mistaken as this broke the form. While a doubleclick on a field in the datasheet part of the form added the record to the listbox it was now unable to remove an item from the underlying collection giving me Run-time error 5: "Invalid procedure call or argument". Furthermore the clear all Button doesn't properly reset the collection anymore. When trying to Clear and adding a record of the previous selection the code returns my custom error that the record is already part of the selection and the listbox gets populated with the whole previous selection, which should be deleted. This leaves me to believe, that for some Reason the collection gets duplicated or something along these lines. Any hints into the underlying problem is apprecciated. Code as Follows:
Option Compare Database
Dim PersColl As New Collection
Private Sub AddPerson_Click()
AddPersToColl Me!ID
FillListbox
End Sub
Private Sub btnClear_Click()
Set PersColl = Nothing
lBoxSelection.RowSource = vbaNullString
End Sub
Private Sub btnRemovePers_Click()
PersColl.Remove CStr(lBoxSelection.Value)
FillListbox
End Sub
Private Sub FillListbox()
Dim Pers As Person
lBoxSelection.RowSource = vbaNullString
For Each Pers In PersColl
lBoxSelection.AddItem Pers.ID & ";" & Pers.FullName
Next Pers
lBoxSelection.Requery
End Sub
Private Function HasKey(coll As Collection, strKey As String) As Boolean
Dim var As Variant
On Error Resume Next
var = IsObject(coll(strKey))
HasKey = Not IsEmpty(var)
Err.Clear
End Function
Private Sub AddPersToColl(PersonId As Long)
Dim Pers As Person
Set Pers = New Person
Pers.ID = PersonId
If HasKey(PersColl, CStr(PersonId)) = False Then
PersColl.Add Item:=Pers, Key:=CStr(PersonId)
Else: MsgBox "Person Bereits ausgewählt"
End If
End Sub
This works alone, but Simply Adding this breaks it as described above.
Private Sub Nachname_DblClick(Cancel As Integer)
AddPersToColl Me!ID
FillListbox
End Sub
Further testing showed that its not working if i simply remove the Private Sub AddPerson_Click()
Edit1:
Clarification: I suspected that having 2 different events calling the same subs would somehow duplicate the collection in memory, therefore removing one event should work. This is however not the case. Having the subs called by a button_Click event works fine but having the same subs called by a double_click event prompts the behaviour described above. The issue seems therefore not in having the subs bound to more than one event, but rather by having them bound to the Double_Click event.
Edit2: I located the issue but I Haven't found a solution yet. Looks like Split-Forms are not really connected when it comes to the underlying vba code. DoubleClicking on the record in the datasheet view creates a Collection while using the buttons on the form part creates another one. When trying to remove a collection item by clicking a button on the form, it prompts an error because this collection is empty. However clicking the clear all button on the form part doesn't clear the collection associated with the datasheet part.
Putting the collection outside into a separate module might be a workaround but i would appreciate any suggestions which would let me keep the Code in the form module.
The behavior is caused by the split form which creates two separate collections for each one of its parts. Depending from where the event which manipulates the collection gets fired one or the other is affected. I suspect that the split form is in essence not a single form, but rather 2 instances of the same form-class.
A Solution is to Declare a collection in a separate module "Coll":
Option Compare Database
Dim mColl as new Collection
Public Function GetColl() as Collection
Set GetColl= mColl
End Function
And then remove the Declaration of the Collection in the SplitFormclass and Declare the Collection in every Function or Sub by referencing the collection in the separate Module like in the following example:
Option Compare Database
Private Sub AddPersToColl(PersonId As Long)
Dim Pers As Person
Dim PersColl as Collection
Set PersColl = Coll.GetColl
Set Pers = New Person
Pers.ID = PersonId
If HasKey(PersColl, CStr(PersonId)) = False Then
PersColl.Add Item:=Pers, Key:=CStr(PersonId)
Else: MsgBox "Person Bereits ausgewählt"
End If
End Sub
This forces the form to use the same Collection regardless if the event is fired from the form or datasheet part of the split-form. Any Further information is appreciated but the matter is solved for now.
Thanks everyone for their time

How can i make or not make particular Tabs visible by loading a form in Access with VBA?

Question
How can i make or not make particular Tabs visible by loading a form in Access with VBA?
Explanation:
I have a Acess Database with 4 forms containing buttons. Today i started to create ribbons to get rid of those in the forms so everything is sorted and easy to overview. I want the Tabs in my Access Database to be unvisible until i open a form from my main form.
Main Form (no tabs showed) --> Switching to another form by clicking a button in my main Form (now i want to show a particular tab after opening the form)
I created my Ribbons with "Ribbon Creator 2019" for Office 2019.
Problem:
I cant solve it ... i tried so many things until i found a excel worksheet having a function by swithing sheets to display particular tabs. Its exactly what i want but i can't get it to work for my access.
By getting the name of the active form i cant get it to work and in my opinion this would be tha fastest way.
My approach:
I set "StartFromScratch" in my XML to 'true' and gave my tabs names like this: "CustomTagValue1:=xstart".
Code for my tabs (Module):
Sub GetVisible(control As IRibbonControl, ByRef visible)
' Callbackname in XML File "getVisible"
' To set the property "visible" to a Ribbon Control
' For further information see: http://www.accessribbon.de/en/index.php?Downloads:12
' Setzen der Visible Eigenschaft eines Ribbon Controls
' Weitere Informationen: http://www.accessribbon.de/index.php?Downloads:12
Select Case control.ID
Case "tab_3"
' Tab: tab_3
visible = False
Case "tab0"
' Tab: tab0
visible = False
Case "tab1"
' Tab: tab1
visible = False
Case Else
visible = True
End Select
End Sub
Code from another Module to declare my tabs:
Option Compare Database
Option Explicit
'**************************************************************************
' About this Code:
'
' This Code checks if a Formular is in active use by his 'Name'. Simple.
'**************************************************************************
Dim MyTag As String
'Callback for customUI.onLoad
Sub RibbonOnLoad(ribbon As IRibbonUI)
Set Rib = ribbon
End Sub
Sub GetVisible(control As IRibbonControl, ByRef visible)
If control.Tag Like MyTag Then
visible = True
Else
visible = False
End If
End Sub
Sub RefreshRibbon(Tag As String)
MyTag = Tag
If Rib Is Nothing Then
MsgBox "Fehler RBC1018, bitte starten Sie das Programm neu."
Else
Rib.Invalidate
End If
End Sub
Code in the Form onLoad:
Private Sub Form_Load(ByVal Sh As Object)
Select Case Screen.ActiveForm
Case "frmVerteiler": Call RefreshRibbon(Tag:="xverteiler")
Case Else: Call RefreshRibbon(Tag:="")
End Select
End Sub
Help:
I want it to work as in this excel Dokument: https://www.rondebruin.nl/win/s2/win012.htm
It is far less work and significant more easy to just create a custom ribbon for each form, and that way you don't have to write all kinds of code to hide show tabs on the ribbion. So just specify the correct ribbon for the given form, and no code is required.
I also suggest that you do NOT use call backs in the ribbon. If you adopt this approach, then the ribbon can directly call your EXISTING button code. So the code, buttons you now have can be transferred to the ribbon for that form, and you don't have to setup ribbon call backs, and all of your existing button code can remain "as is" and be called directly from the ribbon - and the ribbon will run the button code that is and remains in the form.
All you need to do is to declare any function you want called as a public function.
You then set the on action as follows:
=MyPublicFunctionName()
Note CAREFULL how we have = and () (you must have these), and they must be under the quotes.
Eg for the xml we have:
onAction="=MyDelete()"
Note how the above is DIFFERENT then a call back for a ribbon (and callbacks use sub, where this is a function). Even better is the above means you do NOT have to place the code in a standard code module, but can place the function it in the current form. So, no macro needed, no callback, and the code can go in the current form (just like it does for a command button). And bottoms to dollar, in 9 out of 10 cases, the code you need for particular form and button belongs in that particular forms code module anyway. In fact it's a very bad programming practice to start taking code that belongs in a form and placing that code in a standard public code module. The reason is for many, but all kinds of issues can crop up if you have multiple instances of that same form which is allowed in access. Furthermore when copying forms between applications, or even making a copy of the form within the same application means that you have outside code dependencies that we normally as access developers do not expect (we assume for the most part is that the code we're using for the form belongs in the forms code module and I 100% agree). I am open to the idea that this does go against the well known concept of moving UI and code apart but this is "HOW" access works. So the access way does go against trends in our industry.
Keep in mind the above function call idea is the same format we can and would have been using since near day one with menu bars in previous editions of access. So, if you are wanting to change menu bar code to ribbon, use the above idea. Also, if you have several buttons that runs code in a given form, then again the above syntax allows one to KEEP the code in the current form and simply declare the button code as a public function (you can thus real easy move buttons from a form to a ribbon if you do this).
If the function name you specified in the menu or ribbon was named as public in the form's code module, then the CURRENT FORM with the CURRENT FOCUS is where the function will be first looked for to execute. This is SIGNIFICANTLY important because it means you can use one custom menu bar for five different forms, but each of the five different forms will run a custom delete routine for example (no messing with screen.Activeform). And, you don't have to use a bunch of messy case statements as you do with a call back. In fact, all of the code stays in the form where it likely was or belongs in the first place, and that is in the forms code module.
So, if you have specific and specialized delete code that might be required for the given form that has the focus, then that's forms function code in the forms module will be run when it is called from the on action in the menu bar, or now ribbon.
This means if you set the on action to =MyDelete()
Then in each form you have, you simply declare a public functions such as
CODE
Public function MyDelete()
Code here to delete the record
End function
However it turns out for probably more then half or even close to 90% of your forms, it's entirely possible that you want a general catchall delete routine that works for all forms that don't need specialized custom deleting code. In this case you simply place the function in a standard code module (and again as public). If the current form does not have that function, then it is run from a standard code module (again, ideal behavior, and again this is how the onAction worked before ribbon).
Also, note that you also pass values directly from the ribbon.
So, ribbon xml might be:
CODE
<button id="MyDelete" label="Delete Record"
imageMso="Delete" size="large"
onAction="=MyDelete('Staff','tblStaff')"
supertip="Delete this record"
/>
In the above, I passed the table name, and a prompt text of Staff.
And the public catch all function in a standard code module will be:
CODE
Public Function MyDelete(strPrompt As String, strTable As String)
Dim strSql As String
Dim f As Form
Set f = Screen.ActiveForm
If MsgBox("Delete this " & strPrompt & " record?", _
vbQuestion + vbYesNoCancel, "Delete?") = vbYes Then
etc....
Note again how I passed two parameters to the delete routine (the text must be under single quotes). The prompt part so the msgbox command will say "Delete this staff ?". And, then I also pass the table name. What the above means is that for ten forms, if you don't specify a public function inside of the form, when the menu button is clicked on is selected, the catchall general routine in an standard code module (non forms code module) will run.
And for the few forms that have specialize deleting code, the function inside of the forms code module will run. That code might look like:
CODE
Public Function MyDelete(s1 as string, s2 as string)
' check for history
....
lngHistory = Nz(DLookup("ContactId", "NHistory", "ContactID = " & Me.ContactID), 0)
If lngHistory > 0 Then
Beep
MsgBox "You cannot delete a person with past history bookings!" & vbCrLf & vbCrLf & _
"You should simply check the 'Do NOT INCLUDE in mailings' to remove from" & vbCrLf & _
" future mailings.", vbExclamation, "Rides"
Exit Sub
End If
...code here with sql to delete record....
So a few things:
I would just create a ribbon for the given form (take your xml for the given tab, and create a whole new ribbon). Now, if you have 2 or 5 forms open, then the ribbon will automatic flip and change for you. If you try and use tabs, then a simple change of focus to another form will require you to hide+show given tabs - it becomes a real mess rather quick.

MS Access: Action "onchange" event after a delay?

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

Using For Loop to Check for duplicates - Error The error is: Object reference not set to an instance of an object

I have a problem that I and my limited experience cannot figure out.
I have a multi-display (form) program that is for a game show system which uses a (5) display system. As I am wrapping this project up, I have been doing some house keeping and handling some potential user errors up front.
In this instance: There is a form called GameSetup which has drop down menus that are filled via a database selection of "Teams". When a Team is selected, (10) labels are populated with registered team members. The user drags and drops a team member (ie. player) to a group of (6) labels, which will make up the active team for the game.
The problem: I wanted to have a check when a name is added to the active team roster to make sure there are no duplicates. The cleanest way to do this, I thought, would be on the .TextChanged action of the active player label and use a For loop to check the other active team player names.
Private Sub lblT1_P1_TextChanged(sender As Object, e As EventArgs) Handles lblT1_P1.TextChanged
cmdReady.BackColor = Color.Yellow
For i = 1 To 6
If i = 1 Then
' Do nothing
ElseIf Me.Controls("lblT1_P" & i.ToString).Text = lblT1_P1.Text Then 'This is the line triggering the NullException
MsgBox("This Player is already selected. Please choose another Player", MsgBoxStyle.OkOnly)
Me.Controls("lblT1_P" & i.ToString).Text = "DRAG PLAYER HERE" 'This is the other line triggering the NullException
End If
Next i
End Sub
i = 1 Do Nothing is so that it wont compare against itself (in this instance)
When I run the program (de-bug), before the form GameSetup loads, I am getting a Null Exception, which makes sense as it is looking at a form or an object in a form that is not yet initialized. Clearly, the Me.Controls is the problem, but I do not know how to handle using the integer (i) in the For Loop otherwise.
The label names are lblT1_P1 - lblT1_P6 (3 instance of each group (T1/T2/T3). I'm only dealing with T1_P1 at the moment.
Any suggestion would be greatly appreciated.
Use string.Format() to generate your control names, because it is more readable.
Instead of Me.controls("lblName").Text
use DirectCast(Me.FindControl("lblName"), Label).Text