Wait until user has stopped typing in ComboBox to run macro (VBA) - vba

I'm programming a sort of cross-reference database. An ID is generated based on the document name chosen or created.
The ComboBox I've referred to in the title acts on change (after 3 letters), checks the database for similar entries to what was typed, and displays the drop downof options that match. Once an entry is picked from the match list or a new name created - the appropriate number is generated.
Since the DropDown list is generated after every letter is typed, it takes a while to type what you want. I want to wait a few seconds after the last change to run the macro.
Any Ideas on how I can accomplish this?

An alternative using Application.OnTime again:
In Userform:
Private Sub ComboBox1_KeyPress(ByVal KeyAscii As MSForms.ReturnInteger)
StartTimer
End Sub
In Module:
Public RunTime As Double
Public Sub StartTimer()
On Error Resume Next
Application.OnTime EarliestTime:=RunTime, Procedure:="YourCode", Schedule:=False
RunTime = Now() + TimeValue("00:00:03")
Application.OnTime RunTime, "YourCode"
End Sub
Public Sub YourCode()
MsgBox "It's working!"
End Sub

This is a bit tricky as VBA doesn't support multi-threading. But we can use the Application.OnTime event to trigger a test in the future to test if the last key event is at least 3 seconds ago.
In a Module insert:
Option Explicit
Public LastKeyEvent As Date
Public Const WaitTimeValue As String = "00:00:03" 'test for key event in 3 seconds
Public Sub TestKeyEvent()
'test if last key event is at least 3 seconds ago.
'If so: run your search or message box
'If not: do nothing
If LastKeyEvent <> 0 And LastKeyEvent + TimeValue(WaitTimeValue) <= Now Then
LastKeyEvent = 0 'Ensure this is only triggered once:
'If we don't do this and multiple keys are pressed within 1 second
'then it would run multiple times.
MsgBox "3 seconds without keypress, we can start search"
'start your search here (instead of message box) …
End If
End Sub
Now you can use for your textbox change event eg TextBox1:
Private Sub TextBox1_Change()
Dim alertTime As Date
LastKeyEvent = Now 'remember when the last key event was
alertTime = LastKeyEvent + TimeValue(WaitTimeValue)
Application.OnTime alertTime, "TestKeyEvent" 'run TestKeyEvent in 3 seconds
End Sub
Note:
This is a workaround that works for 2 or more seconds. But does not for less then 2 seconds.

Related

VBA : Disabling Listbox while macro is running

Problem
I have a macro (I'll call it launch_macro) which is launched by double-clicking in an Userform ListBox (ListBox1_DblClick).
My problem is that if the user double-click again while the macro is still running, the macro will be launched again as soon as the first execution is finished, regardless of the fact that I'm disabling ListBox while the macro is running.
Code and tests
Private sub ListBox1_DblClick(Byval Cancel as MSForms.ReturnBoolean)
(....Logging...)
If Not Cancel Then
Me.ListBox1.Enabled = False
(...DisplayStatusBar / ScreenUpdating / ListBox1.BackColor...)
launch_macro
(...DisplayStatusBar / ScreenUpdating / ListBox1.BackColor...)
Me.ListBox1.Enabled = True
End If
End sub
It seems like Excel records/queues the ListBox1_DblClick events (for future execution) while the associated ListBox is disabled. Why that ? How can I prevent this ?
I also tried with no success :
Locked : Me.ListBox1.Locked = True
Doevents : Adding DoEvents after Me.ListBox1.Enabled = False
EnableEvents :Application.EnableEvents = False
macroLaunched variable :Using a variable to check if the macro is already launched (macroLaunched = True at the beginning of the ListBox1_DblClick event and macroLaunched = False at the end). This doesn't work since the second execution is launched after the end of the first event (thus the variable is set back toFalse). (And setting the variable back to False outside the scope of the Dbl_Click event is not acceptable since the user need to be able to launch the macro immediately again (but just not while the first execution is still running)).
Adding delay (for test purpose only) : I added a 10s delay (Application.Wait) right back after the launch_macro. I then double-clicked twice within 1s. The second execution still launched. I checked by logging : the 2nd ListBox1.Dbl_Click event is 'recorded' by Excel 12s after the first event.
Note : I'm using Office Standard 2013
Current 'solution'
This trick is adapted (to reduce delay) from A.S.H answer :
Private sub ListBox1_DblClick(Byval Cancel as MSForms.ReturnBoolean)
Static nextTime As Single
If Timer < nextTime then
Log_macro "Event canceled because Timer < nextTime : " & Timer
Exit Sub
End if
(....Logging...)
If Not Cancel Then
(...DisplayStatusBar / ScreenUpdating / ListBox1.BackColor...)
launch_macro
(...DisplayStatusBar / ScreenUpdating / ListBox1.BackColor...)
End If
nextTime = Timer + 0.5
Log_macro "nextTime = " & nextTime
End sub
It 'does the trick' but but I still don't like that ListBox1 is still enabled and Excel is still queueing events, thus I need to estimate how many time the user might Dbl_Click (depending on how long the macro takes) to estimate how much a delay I need (currently 0.5s to be able to handle (and log) at least 10 canceled events). Also, it seems like Excel doesn't really like (in regards to performance) queuing events while the macro is running.
Well I will post my suggestion, I hope you try it because may be it was misunderstood. The idea is that once the macro is finished, we set a delay of n seconds (say 2 seconds) before handling again the double-click event. This way, the dbl-clicks that were queued during the macro's execution are handled with no effect during these two seconds.
Private Sub ListBox1_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
Static NextTime As Variant ' Will set a barrier for launching again the macro
If Not IsEmpty(NextTime) Then If Now < NextTime Then Exit Sub
ListBox1.Enabled = False
' Any event code
launch_macro
' ...
ListBox1.Enabled = True
NextTime = Now + TimeSerial(0, 0, 2) ' dbl-click events will have no effects during next 2 seconds
End Sub
You could use a variable to lock the critical section of code for a set amount of time.
The example below locks the critical part of Test() function in Sheet2 of an Excel workbook.
Option Explicit
Private booIsRunning As Boolean
Private Sub Test()
If Not booIsRunning Then
booIsRunning = True
Debug.Print "Hello."
Application.OnTime Now + TimeValue("00:00:02"), "Sheet2.UnlockTest"
End If
End Sub
Public Sub UnlockTest()
booIsRunning = False
End Sub

Application.OnTime stops firing after changing the active document

I am trying to implement an autosave function for all documents created from a certain template. In this template I have created the following for tests:
Dim doc As Document
Dim count As Integer
Private Sub Document_Open()
count = 1
Set doc = ActiveDocument
SaveTime
End Sub
Sub SaveTime()
Application.OnTime When:=Now + TimeValue("00:00:15"), _
name:="DoSave"
End Sub
Sub DoSave()
doc.SaveAs2 "c:\test\testsave" & count & ".docx"
count = count + 1
SaveTime
End Sub
Now if I open a document thats created by this template the autosaving works every 15 seconds as intended (15sec + the counter with different name is just for testing).
BUT as soon as I create a new document in Word or open another file the autosaving in the first document stops working and also doesnt come back if I continue to work in the document.
How can I make the autosave work no matter of which document is active? Like this the feature would only work if only one Document is open at a time, which I can not garantuee of course.
Shot in the dark here, but you are dimming doc within the scope of the module (from what I can tell). It is possible that opening the new document causes this to go out of scope. It would be best to just pass the document between routines.
Something like this:
' This will declare count within the scope of the module
Private count As Integer
Private Sub Document_Open()
count = 1
SaveTime ActiveDocument
End Sub
Sub SaveTime(doc as Document)
Application.OnTime When:=Now + TimeValue("00:00:15"), "'DoSave doc'"
End Sub
Sub DoSave(doc as Document)
doc.SaveAs2 "c:\test\testsave" & count & ".docx"
count = count + 1
SaveTime
End Sub
I haven't used OnTime much, so if the syntax above doesnt allow arguments to be passed, here is a version using a Private doc variable.
' This will declare count and doc within the scope of the module
Private count As Integer
Private doc as Document
Private Sub Document_Open()
count = 1
SaveTime
End Sub
Sub SaveTime()
Application.OnTime When:=Now + TimeValue("00:00:15"), "DoSave"
End Sub
Sub DoSave()
doc.SaveAs2 "c:\test\testsave" & count & ".docx"
count = count + 1
SaveTime
End Sub
I hope this helps. I mostly work within Excel for VBA so I apologize if I am way off the mark here.
EDIT:
I figured it out. What is happening is every time your save event fires it is saving the active document as a new file, but it is not creating a new instance. For example, if test is your first document then:
Test Opens > Event Fires > Test becomes Test2.docx ( Event Fires > Test2 becomes Test3 etc.
Since the code is still stored in memory somehow (this is the part I dont fully understand, but it is, from what I can tell, is what's happening) the event still fires. The problem is, opening a new document somehow refreshes this and the event cancels.
There is a simple workaround, see below:
Option Explicit
Private doc As Document
Private count As Integer
Private Sub Document_Open()
count = 1
Set doc = ActiveDocument
SaveTime
End Sub
Sub SaveTime()
Application.OnTime Now + TimeValue("00:00:15"), "DoSave"
End Sub
Sub DoSave()
doc.Save
Application.Documents.Add doc.FullName
ActiveDocument.SaveAs2 "c:\test\testsave" & count & ".docx"
ActiveDocument.Close
count = count + 1
SaveTime
End Sub
This creates a new instance of the document, saves this new instance, and then closes it. This leaves the old instance in-tact and running.
I hope that all makes sense. Admittedly, I am not clear on why the code remains in memory even when the document holding the code no longer exists.

Excel: Recalculating every x seconds

One of my spreadsheets deals with various calculations involving, among other things, the current date and time and it would be nice to have it automatically refresh itself once in a while instead of manually having to either press F9 or altering one of the cells.
Is there some way in Excel to set a spreadsheet to automatically recalculate itself every x seconds?
I haven't been able to find a setting in Excel itself, perhaps indicating that no such feature exists. If not, can this be achieved with VBA? (The latter may or may not sound like a silly question, but I have no prior experience with writing Excel macros and as such have no idea what its capabilities are in terms of manipulating spreadsheets.)
This code will create a clock, updated every 10 seconds.
Note that it only refreshes specific cells, and not the entire workbook - this means that you can leave the calculation options at whatever you are happy with:
Dim SchedRecalc As Date
Sub Recalc()
'Change specific cells
Range("A1").Value = Format(Now, "dd-mmm-yy")
Range("A2").Value = Format(Time, "hh:mm:ss AM/PM")
'or use the following line if you have a cell you wish to update
Range("A3").Calculate
Call StartTime ' need to keep calling the timer, as the ontime only runs once
End Sub
Sub StartTime()
SchedRecalc = Now + TimeValue("00:00:10")
Application.OnTime SchedRecalc, "Recalc"
End Sub
Sub EndTime()
On Error Resume Next
Application.OnTime EarliestTime:=SchedRecalc, _
Procedure:="Recalc", Schedule:=False
End Sub
and, to make sure it stops, in the This Workbook module:
Private Sub Workbook_BeforeClose(Cancel As Boolean)
EndTime
End Sub
Goto Developer Visual basic Editor - Right Click workbook - insert module (make sure you have manual calculation
in the module
Sub run_over
Timetorun = Now + timevalue("00:00:10")
application.ontime timetorun,"Refresh_all"
End Sub
Sub Refresh_all
Activeworkbook.Refreshall
End Sub
Sub auto_close()
Application.OnTime timetorun, Refresh_all, , False
End Sub
Change the timing in "00:00:00" format as required

Excel macro doesn't update correctly

i have created macro for excel but it seems somewhere i have done something wrong,
i want to fetch an image from a URL and then update it up to 1 second (more or less)
Sub GetPicture()
PictureURL = "This is where i put the URLi want"
Set MyPict = ActiveSheet.Pictures.Insert(PictureURL)
Cells(1).Value = Now
nextTime = Now + TimeValue("00:00:01")
End Sub
when i run the macro doesn't do anything,only when i press f5 the it updates as fast as i press f5,also what is the value to update less than 1 second ("00:00:01"),when i try ("00:00:0.5") it comes up with "run time error 13" "type mismatch"
Any help is very much apreciated.
In Excel, you can use VBA to trigger code that updates a Worksheet on specific intervals. The code below shows how you would activate a Timer each time the Worksheet is activated by a user. Whenever the Timer fires (on 1 second intervals here) this code updates Cell A1 in the ActiveSheet with the current Time.
To further customize, you would add code to the OnTimerMacro in order to update a Picture or whatever else you recurring task might be.
(Props to Hartmut Gierke for his post on the topic.)
Option Explicit
Dim Execute_TimerDrivenMacro As Boolean
Sub Start_OnTimerMacro()
Execute_TimerDrivenMacro = True
Application.OnTime Time + TimeValue("00:00:01"), ActiveSheet.Name & ".OnTimerMacro"
End Sub
Sub Stop_OnTimerMacro()
Execute_TimerDrivenMacro = False
End Sub
Public Sub OnTimerMacro()
If Execute_TimerDrivenMacro Then
' Do something e.g. put the actual time into cell A1 of the active sheet
ActiveSheet.Cells(1, 1).Value = Time
' At the end restart timer
Application.OnTime Time + TimeValue("00:00:01"), ActiveSheet.Name & ".OnTimerMacro"
End If
End Sub
Private Sub Worksheet_Activate()
'Start the timer driven method when opening the sheet
Start_OnTimerMacro
End Sub
Private Sub Worksheet_Deactivate()
'Stop the timer driven method when opening the sheet
Stop_OnTimerMacro
End Sub
if you would like the macro to repeat you have to put it in a do...until loop. The only problem, is that you can't really have the macro run all the time. There has to be a way to stop it. The do...until loop will help with this, but you have to come up with a reasonable exit from the loop. Can you give a little more background as to what you ultimately want this to do?
Also it sounds like you want the running of the macro to be triggered by something other than the pressing of F5. Can you explain when you would like to see it start?

Need VB to make Excel calculate a sheet or range in realtime and in the background

How can I make excel continuously calculate a sheet/range in realtime (not 1 calc/sec) and do it in the background?
I want this metric clock to run like a stopwatch....
=IF(LEN(ROUND((HOUR(NOW())*(100/24)),0))=1,"0"&ROUND((HOUR(NOW())*(100/24)),0),ROUND((HOUR(NOW())*(100/24)),0))&":"&IF(LEN(ROUND((MINUTE(NOW())*(100/60)),0))=1,"0"&ROUND((MINUTE(NOW())*(100/60)),0),ROUND((MINUTE(NOW())*(100/60)),0))&":"&IF(LEN(ROUND((SECOND(NOW())*(100/60)),0))=1,"0"&ROUND((SECOND(NOW())*(100/60)),0),ROUND((SECOND(NOW())*(100/60)),0))
I've used the following to produce the effect you are looking for:
Option Explicit
Public TimerRunning As Boolean
Dim CalculationDelay As Integer
Public Sub StartStop_Click()
If (TimerRunning) Then
TimerRunning = False
Else
TimerRunning = True
TimerLoop
End If
End Sub
Private Sub TimerLoop()
Do While TimerRunning
'// tweak this value to change how often the calculation is performed '
If (CalculationDelay > 500) Then
CalculationDelay = 0
Application.Calculate
Else
CalculationDelay = CalculationDelay + 1
End If
DoEvents
Loop
End Sub
StartStop_Click is the macro that I tie to the Start/Stop button for the stopwatch. You can get fancy, and change its name to "Start" or "Stop" depending on the value of TimerRunning, but I kept things simple to illustrate the concept.
The two key things here are:
Application.Calculate
Which forces Excel to calculate the worksheet, and:
DoEvents
Which allows VBA to run in the background (i.e. Excel does not stop responding to user input). This is what allows you to still press the "Stop" Button even though the timer is running.
I think this might fail your "(not 1 calc/sec)" criteria, but I achieved something similar as follows. Assumes your formula is in cell A1 of a worksheet named Sheet1.
In the ThisWorkbook code module:
Private Sub Workbook_Open()
Application.OnTime Now + TimeValue("00:00:01"), "RecalculateRange"
End Sub
... and in a regular code module:
Public Sub RecalculateRange()
Sheet1.Range("A1").Calculate
Application.OnTime Now + TimeValue("00:00:01"), "RecalculateRange"
End Sub