Allow user to kill process during wait in VBA - vba

I have written a program to print multiple .pdfs with varying file extensions off of an Excel spreadsheet list.
The problem is that it takes anywhere from 30 seconds to a minute for the printer to receive the pdf after the line: Application.SendKeys "^p~", False is called.
To get by this I used Application.Wait (Now + TimeValue("0:01:03")) to wait for a minute (plus 3 seconds just to be safe) before closing the file.
To me there seems like there should be a better way than just causing the program to wait, so I looked around a little and found a question about this lovely gem known as Application.OnTime.
I tried a sample of one of the answers:
Sub test2()
ActiveSheet.Cells(1, 1).Value = ActiveSheet.Cells(1, 1).Value + 1
Application.OnTime Now + TimeValue("00:00:5"), "test2"
End Sub
However when I tried to stop the above code it kept going on an infinite loop and I was unable to stop it until I killed excel using the windows task manager.
I would like to be able to add in a little message box or something of the sort so that the user can click in between the wait time.
So that while the program is waiting for a minute, the user can either manually click and start the program on the next pdf, or click another button to exit if they need to stop printing early. Something like this:
Sub pdfPrinter()
'...
'Insert all the other code here
'...
Application.SendKeys "^p~", False
Application.OnTime Now + TimeValue("00:01:02"), "pdfPrinter"
continue= MsgBox("Click Retry to print again, or cancel to stop printer.", vbRetryCancel)
If continue = vbRetry Then
Call pdfPrinter
ElseIf continue = vbCancel Then
Exit Sub
End If
End Sub

Application.OnTime(unlike Application.Wait) is Asynchronous
so code after
Application.OnTime Now + TimeValue("00:01:02"), "pdfPrinter"
Runs Immediately.
If you want to print another PDF you need to call Application.OnTime with a different schedule of the 1st one.
Now, canceling Application.OnTime, when it is already started is a different story:
To do that you need to store that time that the respective function is scheduled to run and then Cancel it using the following code:
Application.OnTime Now + TimeValue("00:00:50"), "test", schedule:=False

Related

Excel 2016 crash on API timer initiated Workbook.close

I have a workbook that crashes Excel on close when the close is triggered by an API timer.
Edit: The workbook in link below has nothing but the timer and exit modules and it has the same behavior so it's not an issue with the content.
https://www.dropbox.com/s/x0xdwgj5h34ctdk/Book1.xlsm?dl=0
A few seconds after the workbook closes Excel crashes. I have tried turning the timer off, unhiding all sheets, unloading forms...all the objects are set to Nothing.
All I am doing is Workbooks(ThisWorkbook.Name).Close SaveChanges:=True??!!
The same sub is called from a UserForm with no problem. The workbook closes without Excel crashing.
How to fix this?
Sub ApplicationExit()
' Call UnloadAllForms
' DoEvents
' Sleep 1000
' Call StopCloseTimer
'DoEvents
'If Application.Workbooks.Count = 1 Then
' Workbooks(ThisWorkbook.Name).Save
' Application.Quit
'Else
DoEvents
Workbooks(ThisWorkbook.Name).Close SaveChanges:=True
'End If
End Sub
The code triggered by the timer is;
Sub TimerCalled()
If CloseTimerValue = "" Then Call Reset_CloseTimerValue
DoEvents
If basTimers.CloseTimerValue <= Now() And Not Unlocked Then Call ApplicationExit
On Error Resume Next 'In case sheet is protected
ThisWorkbook.Sheets("JobIndex").Range("CloseCount").Value = Format(Now() - CloseTimerValue, "hh:m:s")
End Sub
The userform says;
CloseUp:
i = MsgBox("Close Project Register?", vbYesNo, MsgBoxTitle)
Select Case i
Case vbYes
Call ApplicationExit
Case vbNo
Workbooks(ThisWorkbook.Name).Save
End Select
Timer in excel vba is done by using the Application.OnTime command or windows timer, as you are using.
Notice that, to schedule the call for a function (using the upper-mentioned command), it's used APPLICATION, which means it tells EXCEL to execute the call, not your workbook. So, in your case, the workbook is closed, BUT the to-be-called procedure is still scheduled to go off at some time in the future. The error that you get is that the excel doesn't find the procedure and then throws an error.
The reason it crashes in your case is that you are using windows timer to do it. When you close your workbook, your instance of the lib32 is lost and when time comes, Windows can't reach in the memory and then crashes the whole application.
In both cases, it seems that your scheduled procedure is still running.
I suggest that you look into that and consider using application.ontime.

Cannot run the macro

I have encountered a problem in the macro below
Sub RefreshAction()
Range("b7").Select
Application.Run "RefreshCurrentSelection"
Application.OnTime (Now() + TimeValue("00:00:05")), "thisworkbook.Action"
End Sub
The cell refreshes when I run the macro the first time but I get the error message immediately after
Message: Cannot run the macro "C\Desktop\XYZ.xlsm'!thisworkbook.Action'. The macro may not be available in this workbook or all macros may be disabled.
I have already gone through "Trust Center->Trust Center Settings->Macro Settings->Enable all macros and it didn't work.
The "Trust access to VBA project object model" box is also clicked.
First of all, here is a snapshot of the error you get when you attempt to run OnTime from a worksheet (not a module) as I will explain. I was getting this error too and trying to figure out why.
It looks like a security error, but in this case it isn't exactly a normal security error.
To run code on a timer you have to add it to a VBA module.
Go to the VisualBasic editor and right click the VBAProject (book).
In Excel it looks like the following:
Once the module is added you add your timer code there.
Since you want to call RefreshAction every 5 seconds you would do something like the following:
Sub StartProcess()
Debug.Print Now()
Application.OnTime Now() + TimeValue("00:00:05"), "RefreshAction", Schedule = True
End Sub
Sub RefreshAction()
Application.EnableEvents = True
Debug.Print Now() + TimeValue("00:00:05")
Application.OnTime Now() + TimeValue("00:00:05"), "RefreshAction", Schedule = True
End Sub
I'll let you add the code that you want it to do each time in the RefreshAction subroutine.
Here's what it will look like in the Module. Make sure yours shows that it is in a module as it does in the image:
Also, I found it to be quite flaky. If you have anything even slightly wrong in the OnTime call it will fail silently. Copy my code (I tested it) and try it first. Once it runs, just add your code to the RefreshAction sub.
StartProcess()
Run the StartProcess to start the thing going.
Additionally Odd Thing
After I added that Module, I still had my code in the Worksheet and I went back and attempted to run it to see the error again and the odd thing is that once the code is in the Module you won't get the error from the Worksheet any more. It's probably referencing the code in the Module now.
See the absolute reference for more details : CPearson OnTime
First issue, you need to store the time that you'll input in your OnTime method to be able to stop it. (Here I declared a Public TimeToRun As Date)
Second Point To use the OnTime method continuously, you need to reset the timer at the end of your timed procedure (here RefreshAllStaticData).
So your whole code should look like this :
Public TimeToRun As Date 'so that TimeToRun can be used in both the functions
Sub RefreshAction()
Range("b7").Select
Application.Run "RefreshCurrentSelection"
DoEvents
'Store the next date of execution in TimeToRun
TimeToRun = Now() + TimeValue("00:00:05")
'Launch the next OnTime
Application.OnTime TimeToRun, "RefreshAllStaticData"
End Sub
Sub RefreshAllStaticData()
'--++-- Place your code here, as it is now --++--
'----Call RefreshAction to reset the OnTime method
'---------to another 5 seconds and keep "looping"
RefreshAction
End Sub
Sub Kill_OnTime()
'Launch this to stop the OnTime method
Application.OnTime _
earliesttime:=TimeToRun, _
procedure:="RefreshAllStaticData", _
schedule:=False
End Sub
A different but related cause of this error can be the string-length limitation of the OnTime method's Procedure parameter's argument. See my post at: Getting around the Max String size in a vba function?

How to get VBA Macro to run continuously in the background?

I want to monitor a value and get an email notifications when certain conditions are met. I have a macro like so:
Do While True
Worksheet.Calculate
If Value > 10 Then
SendEmail
End If
Sleep 60*CLng(1000)
Loop
However, when I run this, it clogs up the entire program and will turn unresponsive if I try to do anything.
Is there anyway to accomplish this but have it run in the background or at least not crash the program?
What I was doing before was using VBScript to open a not-visible spreadsheet and the VBScript ran continuously in the background monitoring the condition and worked fine, but my client really wants a GUI and for it to be in the program itself.
Any thoughts?
Use the Application.OnTime method to schedule code that will run in one minute.
Your code will look something like this (Untested):
Sub CreateNewSchedule()
Application.OnTime EarliestTime:=DateAdd("n", 1, Now), Procedure:="macro_name", Schedule:=True
End Sub
Sub macro_name()
If Value > 10 Then
SendEmail
Else
CreateNewSchedule
End If
End Sub
You might want to store the time of the next schedule in a global variable so that the Workbook_BeforeClose event can cancel the next schedule. Otherwise Excel will re-open the workbook.
Public nextScheduledTime As Date
Sub CreateNewSchedule()
nextScheduledTime = DateAdd("n", 1, Now)
Application.OnTime EarliestTime:=nextScheduledTime , Procedure:="macro_name", Schedule:=True
End Sub
Sub macro_name()
If Value > 10 Then
SendEmail
Else
CreateNewSchedule
End If
End Sub
Private Sub Workbook_BeforeClose(Cancel As Boolean)
On Error Resume Next
Application.OnTime EarliestTime:=nextScheduledTime, Procedure:="macro_name", Schedule:=False
End Sub
You can then continue to use Excel between the scheduled times.
I think you need to specifically process the application event stack with a DoEvents call. This allows user interactions with the spreadsheet to occur, where normally the macro would take precedence. You code would look something like:
Do While True
If Value > 10 Then
SendEmail
End If
Sleep 60*CLng(1000)
DoEvents
Loop
You could also construct a GUI with HTA if you wanted to remain with VBScript.

Macro to start at a particular time

I have found sources that say to use
Application.OnTime TimeValue("18:00:00"), "MyMacro"
But I can't seem to get it to work. This is what I have entered
Sub TimeStamp()
'
' TimeStamp Macro
'
' Keyboard Shortcut: Ctrl+Shift+T
'
Application.OnTime TimeValue("13:25:00"), "TimeStamp"
' Following refreshes the data
Application.CalculateFullRebuild
Then the rest of the code followed by End Sub
For whatever unknown reason, when I start the Macro, it does not wait until that given time. What syntax rules am I breaking here?
The macro is triggered when the macro is triggered. OnTime schedules it to be run automatically, but why should that prevent you from running it manually? Think of it like a virus scan. Many people have their computers configured to do automatic scans at certain set times, but are perfectly able to run nonscheduled scans at any time. If you want to make sure that your macro doesn't have any effect before a certain time, use an if - then statement involving Now (if it is too early exit sub) -- although it makes more sense to not run it at all when you don't want to.
Another method that I've used for years successfully is by just creating a small vb script that is scheduled to call the macro. My other answer to a very similar question show this example script
Dim xlApp
Dim xlWkb
Set xlApp = CreateObject("excel.application")
Set xlWkb = xlApp.Workbooks.Open("PATH TO YOUR FILE")
xlApp.Visible = True
xlWkb.RunAutoMacros 1 'enables macros to be run on open
xlApp.Run ("YOUR PROCEDURE")
xlApp.Workbooks("YOUR WORKBOOK NAME").Save 'Save the workbook
xlApp.Quit 'quits excel
Schedule this through Tasks Scheduler. My other answer can be found here
This is what I ended up with thanks to the help of several of you. I used:
Private Sub Workbook_Open()
Application.Wait "6:45:00"
Call TimeStamp
End Sub
and placed this in "ThisWorkbook". This made it so that when I open up my workbook, the macro is automatically started but it waits until 3:03 pm to perform the rest of the task.
Next, I needed it to refresh every 15 minutes so I used Chip's solution suggested by User: Findwindow. The code goes as follows
Public RunWhen As Double
Public Const cRunIntervalSeconds = 900 ' 15 minutes
Public Const cRunWhat = "TimeStamp" ' the name of the procedure to run
Sub StartTimer()
If Time < TimeSerial(13, 15, 0) Then
RunWhen = Now + TimeSerial(0, 0, cRunIntervalSeconds)
Application.OnTime EarliestTime:=RunWhen, Procedure:=cRunWhat, _
Schedule:=True
End If
End Sub
at the end of the TimeStamp macro, I put Call StartTimer so that every time the macro is ran, the timer will schedule a new run time for 15 minutes from the current time. The If Time < TimeSerial(13, 15, 0) Then allows for the macro to stop running at 1:15pm, the time that I wanted it to stop.

Can I display something while a macro is running?

I have a macro that takes roughly five seconds to execute and I'm using the code Application.ScreenUpdating = False so it looks like Word has locked up when it's running.
I use a message box at the end to tell them it's complete, but I'd like to display an image during its execution that tells the user the macro is running (primarily so they don't get worried, but also because it's nicer to look at).
There might be an easier way to go about this, but I decided to create an image, create a very basic user form, and simply set my image as its background. Then, I have that user form pop up as soon as the code starts to run.
The problem I'm having is that unless I close it, the rest of the code won't execute. It just hangs until I do something. Is there a way I can get the user form to be displayed without stopping the rest of the process? If not, is there another approach I can take to display an image while the macro is running?
You can show the userform as non-modal:
Userform1.Show False
'do more stuff (doesn't wait for form to close)
Userform1.Hide
I too have found that if you .show a userform at the beginning of the process, images contained within userform take a while to display and the result is less than smooth.
I have solved it by inserting a 1 second "wait" immediately after the userform.show instruction. In that split second, the image loads and displays successfully and the result is seamless.
Here the code. I have 2 macros: 1 to display and 1 to hide. I call the first macro at the beginning of the process just before freezing the screen and the second at the end of the process just before unfreezing the screen
Userform name is "myuserform"
Sub message_show()
userform.Show False
Application.Wait (Now + TimeValue("0:00:01"))
End Sub
Sub message_hide()
Application.Wait (Now + TimeValue("0:00:02"))
userform.Hide
End Sub