Powerpoint macro - basic problem with counter - vba

I am making my first Powerpoint 2007 macro and I am having a bit of trouble with it hanging, and not letting me move on to the next slide. I can press ESCAPE to quit the slideshow, but pressing space bar or anything else won't progress to the next slide. After a while, it just crashes. I come from a C++/Java background so I think its just something basic that I'm missing.
Basically I am trying to do a counter slide that counts the days/minutes/seconds from a particular date. When the slide loads I want it to show, in real time, how long its been since that date. I've put it through an infinite loop, which works fine to update the time, but then doesnt let me move on to the next slide.
Here's my code:
Sub OnSlideShowPageChange(ByVal SSW As SlideShowWindow)
'If SSW.View.CurrentShowPosition = 3 Then
Do While SSW.View.CurrentShowPosition = 3 ' infinite loop
Dim currentSlide As Integer
currentSlide = SSW.View.CurrentShowPosition
Dim startDate As Date
Dim currentDate As Date
Dim sngDiff As Single
Dim lngDays As Long
Dim lngHours As Long
Dim lngMinutes As Long
Dim lngSeconds As Long
startDate = #7/22/2011 2:00:00 PM#
currentDate = Now
sngDiff = currentDate - startDate
lngDays = CLng(sngDiff)
sngDiff = sngDiff - lngDays
lngHours = Hour(sngDiff)
lngMinutes = Minute(sngDiff)
lngSeconds = Second(sngDiff)
With ActivePresentation.Slides(currentSlide)
With .Shapes(2)
.TextFrame.TextRange.Text = "It has been:" & lngDays & " Days " & lngHours & " hours " & lngMinutes & " minutes " & lngSeconds & " Seconds"
End With
End With
DoEvents
Loop
End Sub
Do I need to listen for some sort of button click to stop this infinite loop, or how do I do this?
Thanks.

A user form is something you add in the VBA editor; it's what you'd normally think of as a dialog box, though forms can be used for other things and needn't even become visible; that's what we're going to do here:
Option Explicit
Public bFormCodeRunning As Boolean
Sub FormDemo()
' Set a flag to let us know the code in the form
' is running
bFormCodeRunning = True
' "show" the form
UserForm1.Show vbModeless
End Sub
Sub KillForm()
' call this at some other point in the presentation
' when you're sure you're done running the form code
If Not bFormCodeRunning Then
Unload UserForm1
End If
' You could actually call this from your slide change event handler
End Sub
Then Insert, User Form from the menu to add a new form; doubleclick it to view its code and add this:
Private Sub UserForm_Activate()
' Don't show my face
Me.hide
DoEvents
' prove that the form's loaded
MsgBox "I'm well-formed"
DoEvents
' and put your other code here
' and when the code's done, flag it
bFormCodeRunning = False
End Sub

For doing a time delay in a VBA context it is usually better to use a form_timer object so in your code have:
If SSW.View.CurrentShowPosition = 3 Then
Me.TimerInterval = 1000
Else
Me.TimerInterval = 0
End If
Or something similar. Then in the form timer code have your clock update code
Private Sub Form_Timer()
// Your clock update code here
End Sub
It's been years since I've done any VBA so I'm a bit rusty but I hope this helps. In general use timers instead of loops for threading tasks, VBA doesn't cope well with them.

The problem is that your routine "owns" the app; until it exits, you won't be able to do anything manually (ie, advance to the next slide).
Whether or not you use a timer on a form (and fwiw, the Timer control isn't shipped with VBA as it is with VB), I think a form may be your solution.
Have your event handler load a form modelessly then exit.
The code in the form can then do any mods to slides or whatever else you want.
Include DoEvents often enough that you don't slow down the main app, but the code in the form will run independently of what the main app is doing.
You don't need to make the form visible (and probably don't want to).

Related

Run VBA Code Before Next Slide Becomes Visible

After clicking Next, I want to run some code before the next slide becomes visible on screen. The code will control some third-party software. This will take between 1 and 3 seconds to complete. In an ideal world, the previous slide's content would remain visible while the code is running and then PowerPoint would advance to the next slide.
I have used PowerPoint's Application.SlideShowNextSlide event using the 'Application Object Events' method described here: http://youpresent.co.uk/powerpoint-application-events-in-vba/ This works, the event triggers as expected.
Despite the documentation saying:
Occurs immediately before the transition to the next slide
...my tests suggest that the code runs after the slide is displayed. Using the test code below, this is what I see on my screen:
The next slide is displayed together with all its content
After approximately 1 second, all content is hidden
After about 5 seconds, all content becomes visible
Private Sub App_SlideShowNextSlide(ByVal Wn As SlideShowWindow)
' Checking this works before spending time on the desired functionality
' Will Select/Case (.SlideID) to ensure code runs on the correct slide
Dim oSld As Slide
Dim oShp As Shape
Dim stoptime As Date
Set oSld = Wn.View.Slide
' Test #6: See if hiding the slide's content, running the code, and
' then revealing it is a solution. Unfortunately, all the content is
' already visible before this loop is executed.
'
For Each oShp In oSld.Shapes
oShp.Visible = False
Next
' Waste time (code would go here)
'
stoptime = Now() + #12:00:05 AM#
Do Until Now() > stoptime
DoEvents
Loop
' Reveal content of the slide
'
For Each oShp In oSld.Shapes
oShp.Visible = True
Next
End Sub
I've tried the Sleep API instead of a loop. I've tried making a WScript.Shell call with bWaitOnReturn = true. Neither prevents the content of the slide being rendered before/while the code is being executed.
It is possible to stop the old slide being replaced by the new one until some code has run? Or, stop the content of the next slide being seen while a code block does its job?
Okay. I've made something that works well enough for my needs. It isn't perfect but I'm putting it here in case it acts as a good starting point for others:
The important thing for me is that code must run immediately before the slide is displayed. It's not good enough to run code some seconds prior. This would be apparent to the audience.
So, the key to this is that in the App_SlideShowNextClick event, the passed variable nEffect is Nothing when there are no animations left to complete on that slide. Testing for that alerts you to the point where PowerPoint is about to move to the next slide. Running code at this point, and finishing it with
ActivePresentation.SlideShowWindow.View.Next to trigger the move, works for me.
This is what I did:
Set-up the 'Application Object Events' method of triggering events (see original question for more on this)
Add something unnecessary (e.g., text) within the slide before the one you need code to have been run before it's displayed
[Colour|Color] or bury it such that it won't bother your audience
Add an animation effect to it e.g., 'Disappear'
Ensure this animation is the final animation on this slide
Design some way of knowing when you want the code to run (e.g., using SlideID)
' Module variable
Private mGoingForward As Boolean
Private mPreviousSlideNumber as Integer
Private Sub App_SlideShowNextSlide(ByVal Wn As SlideShowWindow)
' In which direction is the user moving through the slideshow?
' This is used in App_SlideShowNextClick
'
mGoingForward = Wn.View.Slide.SlideNumber > mPreviousSlideNumber
End Sub
Private Sub App_SlideShowNextClick(ByVal Wn As SlideShowWindow, ByVal nEffect As Effect)
' nEffect is nothing when there are no further effects to
' perform on the current slide i.e., it's about to move to
' the next slide
'
If nEffect Is Nothing Then
' This is the last time we are going to be on this slide, so update this
'
mPreviousSlideNumber = Wn.View.Slide.SlideNumber
' We are now at the very end of the current slide. The next click will
' move you to the next slide
'
If [ condition when you want the code to run e.g., Wn.View.Slide.SlideID ] Then
' Run your code here
' If necessary, use something like the following to delay execution
' until everything's done
'Dim oShell As Object
'Dim waitOnReturn As Boolean: waitOnReturn = True
'Dim windowStyle As Integer: windowStyle = 0
'oShell.Run cmd, windowStyle, waitOnReturn
' In my situation, I don't have to consider when a user
' moves backwards in the presentation. However, if you don't
' do something like this, you'll never be able to move back. It may
' be possible to possible to detect this without relying on a
' module variable
'
If mGoingForward Then ActivePresentation.SlideShowWindow.View.Next
End If
End If
End Sub

Set a timer between two button clicks

I am using an Excel file with an Userform to control how long it takes for some people to complete a set of tasks.
I have an Userform that opens on start up and as soon as you click the button on it, the time starts counting. However, I don't really understand how I can do that.
I need the timer to run until the task is complete and the user clicks "Exit" or "Complete" and then save the time to my spreadsheet.
I propose slightly different approach. In the userform, declare variables:
Private startTime As Double
Private endTime As Double
Private blTime As Boolean
blTime is double that tells the application that the clock goes on. Then set value for startTime when userform is launched:
Private Sub UserForm_Initialize()
startime = Time
blTime = True
End Sub
I have assumed thet your button complete control is named cbComplete. Change to your button's real name
Private Sub cbComplete_Click()
If blTime Then endTime = Time
blTime = False
End Sub
The clock is turn off (blTime = False) when user click the button. You can add analogous code for other buttons as well but only the first click matters.
What we need is a way to get time that lapsed from the form. Use property:
Property Get Timespan() As Double
If Not blTime Then
Timespan = endTime - startTime
Else
Timespan = 0
End Property
Then, in module you can use command UserForm1.Timespan. Or format it nicely:
Format(UserForm1.Timespan, "hh:mm:dd")
Important: UserForm cannot be .Unload before, just .Hide.
You could adapt something like this:
Sub CalculateRunTime_Minutes()
'PURPOSE: Determine how many minutes it took for code to completely run
'SOURCE: www.TheSpreadsheetGuru.com/the-code-vault
Dim StartTime As Double
Dim MinutesElapsed As String
'Remember time when macro starts
StartTime = Timer
'*****************************
'Insert Your Code Here...
'*****************************
'Determine how many seconds code took to run
MinutesElapsed = Format((Timer - StartTime) / 86400, "hh:mm:ss")
'Notify user in seconds
MsgBox "This code ran successfully in " & MinutesElapsed & " minutes", vbInformation
End Sub
To suit your needs. Source is here
You can learn more about VBA's Timer function here
A second option would be to use the Now function which will give you the current time. So for example:
t = Now
.... run your tasks....
tfinal = Now
time_elapsed = tfinal - t

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.

Pause VBA macro, allow user to make a selection, and restart where it left off

I want to allow the user to make a selection, run some code, pause for another selection, run more code?
I work with documents with large number of tables that eventually convert to HTML. Sometimes the formatting on two like tables doesn't convert the same. Knowing how the converter works, I'd like to copy all of the formatting data from one table and "paste" it onto another one.
I've the idea of a userform to have the user select something, hit a copy button, select something else and hit a paste button.
The timer function allows you to do this. It may not be the best way to code, but it is the answer to your problem:
'1st code here
Start = Timer
Do While Timer < Start + 10
DoEvents
Loop
'do 2nd code here
DoEvents allows the user to select text, etc. After 10 seconds, the code resumes at "2nd code."
You can use global a global variable:
Public myVar as Variant
Sub fillmyVar()
myVar = Selection
End Sub
Sub doSth()
' use myVar and the new selected text
End Sub
Using the answer from Aaron and incorporating it with a ToggleButton in the Userform you can successfully pause the code. With this you can then work in an additional selection to change the operation.
I originally did not use Global or Public Variables but soon learnt that its easier for passing data between Subs and Userforms
Userform:
Public Sub ToggleButton1_AfterUpdate()
'Program is Paused / Selected to Pause
If ProgBar.ToggleButton1.Value = True Then
'Changing the Text of the Toggle button once the program is selected to Pause
'If program paused then button will display continue
ProgBar.ToggleButton1.Caption = "Continue"
'For Sending to Loop Code
ProgramStatus = "0"
Call LoopCode.PrgStat(ProgramStatus)
End If
'Program is running / Selected to run
If ProgBar.ToggleButton1.Value = False Then
'Changing the Text of the Toggle button once the program is selected to continue
'If program running then button will display pause
ProgBar.ToggleButton1.Caption = "Pause"
'For Sending to Loop Code
ProgramStatus = "1"
Call LoopCode.PrgStat(ProgramStatus)
End If
End Sub
In your Module
Public Status As String
Public Sub PrgStat(ByVal ProgStatus As String)
Status = ProgStatus
End Sub
Sub SomeSub()
Do While
' Some Loop Code Running
If Status = "1" Then
'Toggle Not Pressed
End If
If Status = "0" Then
'Toggle Button Pressed
Do While Status = "0"
'Program will stop here until the togglebutton on the
'userform is pressed again which changes Status = 1
'This is where you can make another selection on the
'userform
DoEvents
Loop
End If
Loop
End Sub

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?