Closing Excel after inactivity, even in edit mode - vba

I'm not sure this is possible I didn't really find anything when I was searching for an answer online. I have a macro in place where it will close after 5 minutes of inactivity. Works like a charm except when the user is editing a cell the timer does not start, therefore it will not close because of this. Is there a way for excel to have a timer for how long the user is in edit mode then it will take them out of it. Once out of edit mode the macro will start the 5 minutes of inactivity. Any help is greatly appreciated!
HERE IS THE WORKBOOK MODULE
Private Sub Workbook_BeforeClose(Cancel As Boolean)
Me.Saved = True
End Sub
Private Sub Workbook_Open()
start_Countdown
End Sub
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
stop_Countdown
start_Countdown
End Sub
HERE IS THE STANDARD MODULE
Option Explicit
Public Close_Time As Date
Sub start_Countdown()
Close_Time = Now() + TimeValue("00:05:00")
Application.OnTime Close_Time, "close_WB"
End Sub
Sub stop_Countdown()
Application.OnTime Close_Time, "close_WB", , False
End Sub
Sub close_wb()
ThisWorkbook.Close True
End Sub

You will need some kind of process monitor/task terminator outside of VBA to do this because VBA is "locked out" while the user is in input mode.

You can try this:
Public Declare Function SetTimer Lib "user32" ( _
ByVal HWnd As Long, _
ByVal nIDEvent As Long, _
ByVal uElapse As Long, _
ByVal lpTimerFunc As Long) As Long
Public Declare Function KillTimer Lib "user32" ( _
ByVal HWnd As Long, _
ByVal nIDEvent As Long) As Long
Public TimerID As Long
Public TimerSeconds As Single
Sub StartTimer()
TimerSeconds = 5*60 ' how often to "pop" the timer (5 minutes in the example).
TimerID = SetTimer(0&, 0&, TimerSeconds * 1000&, AddressOf TimerProc)
End Sub
Sub EndTimer()
On Error Resume Next
KillTimer 0&, TimerID
End Sub
Sub TimerProc(ByVal HWnd As Long, ByVal uMsg As Long, _
ByVal nIDEvent As Long, ByVal dwTimer As Long)
' call here whatever you want to call
End Sub
This always work even in edit mode, dont forget to call StartTimer to start and EndTimer before quitting...

Related

Continuous Press on Command Button

If my mouse is pressed continuously on the Spin Button, the increment keeps happening. However, the Command Button requires me to click again and again. How can I have the Command Button behave in a similar fashion to that of a Spin Button?
Private Sub CommandButton2_Click()
Label1.Caption = Int(Label1.Caption) + 10
End Sub
Private Sub spbSpinButton_Change()
spbSpinButton.Min = 100
spbSpinButton.Max = 200
spbSpinButton.SmallChange = 10
Label1.Caption = spbSpinButton.Value
End Sub
You can't do it with the Click event, but if you keep track of MouseUp and MouseDown you can trigger a loop. Something like this:
Add a module
Put this code in the module. Give the module any name, but it is referred to as Module1 below.
#If VBA7 Then
Public Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As LongPtr) 'For 64 Bit Systems
#Else
Public Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
#End If
In your Form
Put this in your Module
Dim ButtonDown As Boolean
Private Sub UserForm_Activate()
ButtonDown = False
End Sub
Private Sub CommandButton1_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
ButtonDown = True
IncrementCounter
End Sub
Private Sub CommandButton1_MouseUp(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
ButtonDown = False
End Sub
Sub IncrementCounter()
If ButtonDown Then
Label1.Caption = Int(Label1.Caption) + 10
DoEvents
Module1.Sleep 100
IncrementCounter
End If
End Sub
The Module1.Sleep 100 says wait 100 milliseconds. Adjust to your need.

How to delay a macro that runs after sending email?

I have the below code for Outlook 365 which will run a macro after sending an email.
How do I modify this to delay the macro 10 seconds after clicking send, and how do I limit this code to my exchange account email which is the default email account?
Option Explicit
Private Sub Application_ItemSend(ByVal Item As Object, Cancel As Boolean)
With Item
Call BatchResendEmailsMacro
End With
End Sub
Outlook doesn't have a timer function but you can use Appointment or Task Reminders to trigger macros. Set up an Application_Reminder macro that will do something when a reminder fires. To limit it to running when specific reminders fire, use an If statement to look for words in the subject or a specific category.
If you want the macro to fire a specified time after you restart Outlook, use an Application_Startup macro to create the appointment. Read more about that in the Running Outlook Macros on a Schedule article.
Also you may consider using Windows API functions such as SetTimer and KillTimer. Outlook VBA - Run a code every half an hour page provides a sample code (for example, that is for Windows x64):
Declare PtrSafe Function SetTimer Lib "user32" (ByVal hwnd As LongLong, ByVal nIDEvent As LongLong, ByVal uElapse As LongLong, ByVal lpTimerfunc As LongLong) As LongLong
Declare PtrSafe Function KillTimer Lib "user32" (ByVal hwnd As LongLong, ByVal nIDEvent As LongLong) As LongLong
Public TimerID As LongLong 'Need a timer ID to eventually turn off the timer. If the timer ID <> 0 then the timer is running
Public Sub TriggerTimer(ByVal hwnd As Long, ByVal uMsg As Long, ByVal idevent As Long, ByVal Systime As Long)
MsgBox "The TriggerTimer function has been automatically called!"
End Sub
Public Sub DeactivateTimer()
Dim lSuccess As LongLong
lSuccess = KillTimer(0, TimerID)
If lSuccess = 0 Then
MsgBox "The timer failed to deactivate."
Else
TimerID = 0
End If
End Sub
Public Sub ActivateTimer(ByVal nMinutes As Long)
nMinutes = nMinutes * 1000 * 60
'The SetTimer call accepts milliseconds, so convert to minutes
If TimerID <> 0 Then Call DeactivateTimer
'Check to see if timer is running before call to SetTimer
TimerID = SetTimer(0, 0, nMinutes, AddressOf TriggerTimer)
If TimerID = 0 Then
MsgBox "The timer failed to activate."
End If
End Sub
You could trigger the code with the ItemAdd event on the Sent Items folder.
Option Explicit
' In the ThisOutlookSession module
Private WithEvents sentItems As Items
Private Sub Application_Startup()
Dim sentItemsFolder As Folder
' default Sent Items folder
Set sentItemsFolder = Session.GetDefaultFolder(olFolderSentMail)
Set sentItems = sentItemsFolder.Items
End Sub
Private Sub sentItems_ItemAdd(ByVal item As Object)
Dim waitTime As Long
Dim waitDiff As Long
Dim delay As Date
Dim waitStart As Date
waitTime = 10 ' in seconds
Debug.Print vbCr & "Wait start: " & Now
waitStart = Now
delay = DateAdd("s", waitTime, waitStart)
Debug.Print "Wait until: " & delay
Do Until Now >= delay
DoEvents
Loop
Debug.Print "Wait end..: " & Now
waitDiff = DateDiff("s", waitStart, Now)
Debug.Print waitDiff & " seconds delay."
Debug.Print "Call BatchResendEmailsMacro"
'Call BatchResendEmailsMacro
Debug.Print "Done."
End Sub
Private Sub test()
sentItems_ItemAdd ActiveInspector.currentItem
End Sub

How can I throttle functions in VBA?

I'm making a drop down inside of Excel using a ComboBox and VBA. I have been able to get it to make requests to a remote api, add the returned data to a hidden worksheet, and update the drop down options based on the result of the api.
What I'm looking to do is throttle the api requests. At the moment, it seems that Excel will not fire off the sub if it is already processing an api request. This is not ideal, because often people will type more than one character in rapid succession. I'd like to add a timer to each sub call, and if there hasn't been a new call to the sub function within ~250ms, send the api request. If another call is made during the 250ms, I want to cancel the execution of that sub.
Initially I tried creating a global "process_id" where the sub would add 1 to the current global, set its local id to that value, wait for x time, check if its local id === the global id, and if not exit the sub. However it now seems that the second sub never runs while the timer is waiting for x time, so the first sub still runs, just x seconds later (and the second sub never runs at all).
How do I go about throttling sub functions in Excel VBA so that the same sub function can be run while the first is waiting?
As stated; you require an asynchronous timer with millisecond precision
Hopefully the following should work for you:
While the timer is set, no further timer should be set and when the timer fires the event, the timer stops itself so multiple calls during the 'DelayTimeSeconds' period should only result in one call to the API
' Adapted from https://groups.google.com/forum/?hl=en#!topic/microsoft.public.excel.programming/Ce--7sympZM
' These procedures require that you are using Office 2000 or later, because of the AddressOf function
Public Declare Function SetTimer Lib "user32" ( _
ByVal HWnd As Long, ByVal nIDEvent As Long, ByVal uElapse As Long, ByVal lpTimerFunc As Long) As Long
Public Declare Function KillTimer Lib "user32" ( _
ByVal HWnd As Long, ByVal nIDEvent As Long) As Long
Private AllowFireTime As Single
Private TimerID As Long
Private TimerSeconds As Single
Private TimerSet As Boolean
Const DelayTimeSeconds As Single = 0.75
Sub TestFireDelay()
CallAPIProc
End Sub
Private Function CallAPIProc()
Dim NowTime As Single: NowTime = Timer
If AllowFireTime > NowTime Then
If TimerSet = False Then StartTimer AllowFireTime - NowTime
Else
AllowFireTime = NowTime + DelayTimeSeconds
Call TestCall
' Code for API Call
End If
End Function
Function StartTimer(Optional TimerSeconds = 1) ' how often to "pop" the timer.
TimerID = SetTimer(0&, 0&, TimerSeconds * 1000&, AddressOf TimerProc)
TimerSet = True
End Function
Function EndTimer()
On Error Resume Next
KillTimer 0&, TimerID
TimerSet = False
End Function
Function TimerProc(ByVal HWnd As Long, ByVal uMsg As Long, ByVal nIDEvent As Long, ByVal dwTimer As Long)
' The procedure is called by Windows. Put your timer-related code here.
'
Call EndTimer
Call CallAPIProc
End Function
Function TestCall()
Debug.Print Format(Now, "hh:mm:ss") & "." & Strings.Right(Strings.Format(Timer, "#0.00"), 2)
End Function
I think what you're really looking for is "don't call the API until I've stopped typing" (i.e. when no further key press has occurred within x msec of the previous one).
If that's what you want then this (with hat tip to #Tragamor's answer) should do it. This is much closer to what you might do in js using window.setTimeout for example.
In a regular code module:
Option Explicit
Public Declare Function SetTimer Lib "user32" ( _
ByVal HWnd As Long, ByVal nIDEvent As Long, _
ByVal uElapse As Long, ByVal lpTimerFunc As Long) As Long
Public Declare Function KillTimer Lib "user32" ( _
ByVal HWnd As Long, ByVal nIDEvent As Long) As Long
Private TimerID As Long
'this function called from the control's Change event
Function CallApi()
Const DelayMsec As Long = 500
If TimerID > 0 Then KillTimer 0&, TimerID 'kill any existing timer
TimerID = SetTimer(0&, 0&, DelayMsec, AddressOf CallApi2) 'set a timer
End Function
'this function called from the timer
Sub CallApi2()
If TimerID > 0 Then KillTimer 0&, TimerID
Debug.Print "Calling API with '" & Sheet1.TextBox1.Text & "'"
End Sub
Sticking with your method of using global variables, keep one to track the Timer function. Unfortunately you did not provide your code, so I can only make assumptions on how you should implement this.
Private myTimer As Single '// Global Variable, outside of any routine
Sub foo()
If Timer - myTimer < 0.25 Then Exit Sub
callMyAPI
myTimer = Timer
End Sub
If you prefer not to exit the sub, wrap the API call in an If statement.
Private myTimer As Single
Sub foo()
' Non-API related code
If Timer - myTimer > 0.25 Then
callMyAPI
myTimer = Timer
End If
'Non-API related code
End Sub
With each API call, you will reset the myTimer variable.

VBA Power Point equivilant of xlveryhidden

Power Point 2016
I want to hide a shape, but I don't want the user to be able to unhide it except for programmatically essentially
shape.visible = xlVeryHidden
It is possible to do this programmatically using PowerPoint events to detect the selection of your 'marked' shape, hide it and then unselect it. I have used this mechanism in several of my PowerPoint add-in products before and it works very well. It requires the following elements:
VBA code in class module for PowerPoint app events
VBA code in standard module
Initialisation of app events via the ribbon onLoad callback
Identification mechanism for the shape(s) you want to hide. Tags work best but you could also use the simpler .Name property
Use of Win API timer to trigger a check for shapes unhidden using the Selection Pane
Solution code contained within either a macro-enabled PowerPoint file (.pptm, .potm, .ppsm) or a PowerPoint application add-in (.ppam)
Here is tested code: (not production quality, for example, doesn't handle non-slide views)
In a class module called "clsAppEvents":
' Source code provided by youpresent.co.uk
Option Explicit
Public WithEvents App As Application
Private Sub App_WindowSelectionChange(ByVal Sel As Selection)
Debug.Print "Event : App_WindowSelectionChange"
If Sel.Type = ppSelectionShapes Then CheckSelection
End Sub
Private Sub App_PresentationClose(ByVal Pres As Presentation)
StopTimer
End Sub
In a standard module called "Main":
' Source code provided by youpresent.co.uk
Option Explicit
'Create a new event handler object from the event class
Public oEH As New clsAppEvents
' Ribbon callback to initialise PowerPoint events
Public Sub OnLoadRibbon(ribbon As IRibbonUI)
Set oEH.App = Application
Debug.Print "PowerPoint App Events Initialised"
StartTimer
End Sub
' Timer initiated check to see if Very Hidden shapes have been unhidden using the Selection Pane
Public Sub CheckShapes()
Dim lCurSlide As Long
Dim oShp As Shape
Dim bFound As Boolean
lCurSlide = ActiveWindow.View.Slide.SlideIndex
For Each oShp In ActivePresentation.Slides(lCurSlide).Shapes
If oShp.Name = "VeryHidden" Then oShp.Visible = msoFalse
Next
End Sub
' Selection change event initialised check to see if selection is Very Hidden
Public Sub CheckSelection()
Dim oShp As Shape
Dim bFound As Boolean
StopTimer
For Each oShp In ActiveWindow.Selection.ShapeRange
If oShp.Name = "VeryHidden" Then
oShp.Visible = msoFalse
bFound = True
End If
Next
If bFound Then ActiveWindow.Selection.Unselect
StartTimer
End Sub
In a standard module called "WinTimer":
' Source code provided by youpresent.co.uk
Option Explicit
Public TimerID As Long
Public TimerCycles As Long
' Source : https://support.microsoft.com/kb/180736?wa=wsignin1.0
#If VBA7 Then
Private Declare PtrSafe Function SetTimer Lib "user32" _
(ByVal hwnd As LongPtr, _
ByVal nIDEvent As LongPtr, _
ByVal uElapse As LongPtr, _
ByVal lpTimerFunc As LongPtr) As LongPtr
Private Declare Function KillTimer Lib "user32" _
(ByVal hwnd As LongPtr, _
ByVal nIDEvent As LongPtr) As LongPtr
#Else
Private Declare Function SetTimer Lib "user32" _
(ByVal hwnd As Long, _
ByVal nIDEvent As Long, _
ByVal uElapse As Long, _
ByVal lpTimerFunc As Long) As Long
Private Declare Function KillTimer Lib "user32" _
(ByVal hwnd As Long, _
ByVal nIDEvent As Long) As Long
#End If
' Starts the time with uElapse time-out period in milliseconds
Public Function StartTimer()
TimerID = SetTimer(0, 0, 100, AddressOf TimerProc)
If TimerID = 0 Then Debug.Print "Timer not created.": Exit Function
Debug.Print "Timer " & TimerID & " started at : " & Now
End Function
Private Function TimerProc(ByVal hwnd As Long, _
ByVal uMsg As Long, _
ByVal idEvent As Long, _
ByVal dwTime As Long)
TimerCycles = TimerCycles + 1
If TimerCycles Mod 10 = 0 Then Debug.Print "Timer " & TimerID & " running : " & TimerCycles
CheckShapes
End Function
Public Function StopTimer()
Dim tmpTimerID As Long
tmpTimerID = TimerID
' If the KillTimer function succeeds, the return value is nonzero.
' If the KillTimer function fails, the return value is zero.
TimerID = KillTimer(0, TimerID)
If TimerID = 0 Then
Debug.Print "Couldn't kill the timer"
Else
Debug.Print "Timer " & tmpTimerID & " stopped at : " & Now & " with " & TimerCycles & " cycles"
End If
TimerCycles = 0
TimerID = 0
End Function
And finally, add this ribbon XML to the macro-enabled pptm/ppam/ppsm/potm file:
<customUI onLoad="OnLoadRibbon" xmlns="http://schemas.microsoft.com/office/2006/01/customui"/>
Now, if you open the macro-enabled file and add a shape to any slide with a name "VeryHidden" you shouldn't be able to unhide it via the PowerPoint UI. Of course, tags should be used instead of names but this is just to prove the concept.
There is no such equivalent in PowerPoint. Any hidden shape can be made visible from the selection pane.

Outlook VBA - Run a code every half an hour

I want run a specific code in outlook(VBA) every half an hour.
Also the outlook user should not get disturbed when the code runs. It should run in back-end only.
There is an event called Application_Reminder. It runs when a at each occurrence of reminder in outlook. But this still involves user interaction. I want a complete back end procedure.
http://www.outlookcode.com/threads.aspx?forumid=2&messageid=7964
Place the following code in the ThisOutlookSession module (Tools->Macros->VB Editor):
Private Sub Application_Quit()
If TimerID <> 0 Then Call DeactivateTimer 'Turn off timer upon quitting **VERY IMPORTANT**
End Sub
Private Sub Application_Startup()
MsgBox "Activating the Timer."
Call ActivateTimer(1) 'Set timer to go off every 1 minute
End Sub
Place the following code in an new VBA module
Declare Function SetTimer Lib "user32" (ByVal hwnd As Long, ByVal nIDEvent As Long, ByVal uElapse As Long, ByVal lpTimerfunc As Long) As Long
Declare Function KillTimer Lib "user32" (ByVal hwnd As Long, ByVal nIDEvent As Long) As Long
Public TimerID As Long 'Need a timer ID to eventually turn off the timer. If the timer ID <> 0 then the timer is running
Public Sub ActivateTimer(ByVal nMinutes As Long)
nMinutes = nMinutes * 1000 * 60 'The SetTimer call accepts milliseconds, so convert to minutes
If TimerID <> 0 Then Call DeactivateTimer 'Check to see if timer is running before call to SetTimer
TimerID = SetTimer(0, 0, nMinutes, AddressOf TriggerTimer)
If TimerID = 0 Then
MsgBox "The timer failed to activate."
End If
End Sub
Public Sub DeactivateTimer()
Dim lSuccess As Long
lSuccess = KillTimer(0, TimerID)
If lSuccess = 0 Then
MsgBox "The timer failed to deactivate."
Else
TimerID = 0
End If
End Sub
Public Sub TriggerTimer(ByVal hwnd As Long, ByVal uMsg As Long, ByVal idevent As Long, ByVal Systime As Long)
MsgBox "The TriggerTimer function has been automatically called!"
End Sub
Key points:
1) This timer function does not require that a particular window is open; it works in the background
2) If you don't deactivate the timer when the application closes it will likely crash
3) The example shows the timer being activated at startup, but it can just as easily be called by a different event
4) If you don't see the msgbox indicating that the timer was activated upon startup, your macro security is set too high
5) To have the timer deactivate after one iteration of the time interval add: If TimerID <> 0 Then Call DeactivateTimer after the msgbox statement in sub TriggerTimer
Someone else suggested
"one point to note, if you don't check if TimerID is the same as idevent in the TriggerTimer, you get every so often, and not the time you asked for."
Public Sub TriggerTimer(ByVal hwnd As Long, ByVal uMsg As Long, ByVal idevent As Long, ByVal Systime As Long)
'keeps calling every X Minutes unless deactivated
If idevent = TimerID Then
MsgBox "The TriggerTimer function has been automatically called!"
End If
End Sub
For Win64, I needed to change it to this:
Declare PtrSafe Function SetTimer Lib "user32" (ByVal hwnd As LongLong, ByVal nIDEvent As LongLong, ByVal uElapse As LongLong, ByVal lpTimerfunc As LongLong) As LongLong
Declare PtrSafe Function KillTimer Lib "user32" (ByVal hwnd As LongLong, ByVal nIDEvent As LongLong) As LongLong
Public TimerID As LongLong 'Need a timer ID to eventually turn off the timer. If the timer ID <> 0 then the timer is running
Public Sub TriggerTimer(ByVal hwnd As Long, ByVal uMsg As Long, ByVal idevent As Long, ByVal Systime As Long)
MsgBox "The TriggerTimer function has been automatically called!"
End Sub
Public Sub DeactivateTimer()
Dim lSuccess As LongLong
lSuccess = KillTimer(0, TimerID)
If lSuccess = 0 Then
MsgBox "The timer failed to deactivate."
Else
TimerID = 0
End If
End Sub
Public Sub ActivateTimer(ByVal nMinutes As Long)
nMinutes = nMinutes * 1000 * 60 'The SetTimer call accepts milliseconds, so convert to minutes
If TimerID <> 0 Then Call DeactivateTimer 'Check to see if timer is running before call to SetTimer
TimerID = SetTimer(0, 0, nMinutes, AddressOf TriggerTimer)
If TimerID = 0 Then
MsgBox "The timer failed to activate."
End If
End Sub
Correct for upper answer for 64-bit:
Declare PtrSafe Function SetTimer Lib "user32" (ByVal hwnd As LongLong, ByVal nIDEvent As LongLong, ByVal uElapse As LongLong, ByVal lpTimerfunc As LongLong) As LongLong
Declare PtrSafe Function KillTimer Lib "user32" (ByVal hwnd As LongLong, ByVal nIDEvent As LongLong) As LongLong
Public TimerID As LongLong 'Need a timer ID to eventually turn off the timer. If the timer ID <> 0 then the timer is running
Public Sub TriggerTimer(ByVal hwnd As Long, ByVal uMsg As Long, ByVal idevent As Long, ByVal Systime As Long)
MsgBox "The TriggerTimer function has been automatically called!"
End Sub
Public Sub DeactivateTimer()
Dim lSuccess As LongLong '<~ Corrected here
lSuccess = KillTimer(0, TimerID)
If lSuccess = 0 Then
MsgBox "The timer failed to deactivate."
Else
TimerID = 0
End If
End Sub
Public Sub ActivateTimer(ByVal nMinutes As Long)
nMinutes = nMinutes * 1000 * 60 'The SetTimer call accepts milliseconds, so convert to minutes
If TimerID <> 0 Then Call DeactivateTimer 'Check to see if timer is running before call to SetTimer
TimerID = SetTimer(0, 0, nMinutes, AddressOf TriggerTimer)
If TimerID = 0 Then
MsgBox "The timer failed to activate."
End If
End Sub