Run VBA Code Before Next Slide Becomes Visible - vba

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

Related

Creating shapes in similar fashion as with autoshape dropdown

I would like to make a macro in Powerpoint that enables me to create shapes in a similar fashion as when you select the autoshapes in the autoshape overview (i.e. once you call the macro you have a possibility to click to set the coordinates and subsequently you drag and click to set the width&height). Also, I would like to give it pre-set cosmetic characteristics (e.g. certain inner margins, fill color, border style and transparancy), which will be defined in the vba code.
I am aware of .addshapes(), however, this requires coordinates and height/width as input. Moreover, I have not find any posts / documents on vba to create shapes without defined coordinates and height/width.
Anyone some ideas on how to tackle this challenge?
Many thanks in advance!
Sofar
Building on what John Korchok suggested, here's code that retrieves the just-drawn shape so that your code can resume and manipulate it...
Sub testAppComBars()
Dim SHP As Shape
Application.CommandBars.ExecuteMso ("ShapeFreeform")
Stop
Set SHP = Selection.ShapeRange(1)
With SHP.Fill
.ForeColor.RGB = RGB(192, 0, 0)
.Transparency = 0.75
End With
End Sub
I would hope there's a more elegant solution than using Stop to pause code execution while the user picks the shape's location (or in this case, draws a freeform polyline/polygon), but that's all I could come up with off the top of my head.
I was fascinated by this problem and think this might help you.
Consider that when you draw a new autoshape, you have changed the window selection, and created a new selection ShapeRange with exactly 1 item (the new shape).
So by setting a WindowSelectionChange event, you're able to apply any formatting you wish at the time of creation.
First create a class module called cPptEvents with the following:
Public WithEvents PPTEvent As Application
Private Sub PPTEvent_WindowSelectionChange(ByVal sel As Selection)
On Error GoTo Errhandler
Debug.Print "IN_PPTEvent_WindowSelectionChange"
Dim oShp As Shape
If (ActiveWindow.ViewType = ppViewNormal) Then
With sel
If .Type = ppSelectionShapes Then
If .ShapeRange.Count = 1 Then
Set oShp = .ShapeRange(1)
If oShp.Type = msoAutoShape Then
If oShp.AutoShapeType = msoShapeOval Then
If oShp.Tags("new_oval") = "" Then
oShp.Fill.ForeColor.RGB = RGB(255, 0, 0)
oShp.Tags.Add "new_oval", "true"
End If
End If
End If
End If
End If
End With
End If
Exit Sub
Errhandler:
Debug.Print "Error: " & Err.Description
End Sub
This checks the selection every time it changes. If there's an oval selected, it looks for the "new_oval" tag, which will not exist for a newly created shape. In that case, it applies a red fill, although of course once you get to this point you can call an entirely different sub, pass along the shape, and do whatever you want formatting-wise to it.
By adding that "new_oval" tag, you ensure that the formatting will not be applied to an oval that hasn't been newly created. This allows the user to make manual changes to the formatting as needed -- otherwise you're just resetting the formatting every time the user selects an oval.
Note that for the _WindowSelectionChange event to be running in the background, you have to call this at some point:
Public MyEventClassModule As New cPptEvents
'
Public Sub StartMeUp()
Set MyEventClassModule.PPTEvent = Application
End Sub
You can include that one line from StartMeUp above in whatever Ribbon_Onload sub is triggered by your addin, if you're making a new addin ribbon.
With this solution, you don't even have to give the end user a special button or set of tools to create the shapes that are being formatted. It happens invisibly whenever the user draws a new shape from the native PPT tools.
This will put your cursor in drawing mode to draw an oval. After running, you may have to click on the slide once, then the cursor will change shape and you can draw an oval:
Sub DrawOval()
Application.CommandBars.ExecuteMso ("ShapeOval")
End Sub
Other commands to substitute for ShapeOval:
ShapeRectangle
ShapeElbowConnectorArrow
ShapeStraightConnectorArrow
Get the full list in Excel spreadsheets from Microsoft Office 2016 Help Files: Office Fluent User Interface Control Identifiers
Look for the powerpointcontrols.xlsx file and search the first column with "shape"
There are 173 shapes in the menu, so you have a lot of macros to write.

Update the fill colour of each shape immediately after it is changed in a loop

I'm trying to get Visio to update the fill colour of each shape immediately after I change it.
I've tried using various methods - screenupdate, showchanges, sendkeys "%^g" but nothing works with the colour. Only changing the screen size by 0.01% forces the app to change the text, which is at least something.
I can step through the code and it works but when I run it none of the colours change until the end.
I'm changing the colour of each object using:
Application.ActiveWindow.Page.Shapes.ItemFromID(servshape(y - 1)).CellsU("Fillforegnd").FormulaU = "RGB(253, 190, 0)"
The code runs through a list of dates and changes the colour of the objects when required, problem is it only shows the changes at the end.
The loop through each item in the list is approximately one second, long enough to see any change. I was hoping there was a simple refresh command but that only seems to work with datarecordsets.
Is there any way of refreshing an object fill colour immediately after changing it?
Should work with DoEvents:
Option Explicit
Sub reColorAll()
Dim pg As Visio.Page
'Set pg = Application.ActiveWindow.Page
Set pg = ActivePage ' Probably what you want
Dim shp As Visio.Shape
For Each shp In pg.Shapes
If True Then 'test if shape is one of the ones you want, replace true with test
If shp.CellExistsU("Fillforegnd", False) Then 'test if cell even exists
shp.CellsU("Fillforegnd").FormulaU = "RGB(253, 190, 0)"
DoEvents' force Application to update
End If
'Timer to simulate delay, can be removed for your case
Dim pauseTime As Long
Dim start As Long
pauseTime = 1 ' Set duration in seconds
start = Timer ' Set start time.
Do While Timer < start + pauseTime
Loop
'End Timer Code
End If
Next shp
End Sub
Timer Source:

Animated slides conversion to static PDF

For all of you, people who make ppt slides with animations like:
Showing bullet points one by one
Showing images one by one or zooming a plot
Showing a border on an active element
Internal navigation / menu / link to another slide
Transitions between slides
Is there a tool that can convert the ppt to PDF and keep each animation in a separate slide, for example?
I know you can create animated slides with LaTeX Beamer that convert nicely to PDF, I have made some of those, but I also have some ppt files that I want to convert to PDF.
This is what I have tried so far:
Slideshare, however not only it doesn't support animations, but internal navigation doesn't work, and the fonts are all messed up.
PDFcreator, the quality is quite superior in comparison, but it doesn't support the animations neither. As Slideshare, it will just put one image over the other. Also, it doesn't support transparency (for example, a text box with a semitransparent bg over an image)
LaTeX Beamer, already mentioned, but I would prefer to avoid typing these ppts content and animations into LaTeX just so that the animations are displayed correctly in PDF.
I have searched SO and didn't find a satisfactory answer to deal with animations. What do you use?
I found a small plugin that splits your powerpoint slides whenever they have animations. So if you have 3 animations on 1 slide he will generate 3 slides with each animation step by step. Then export it in PDF :-)
It worked for me on powerpoint 2010. I would recommend you do a backup file of presentation before splitting. And don't forget to uncheck the "Split on click-triggered animations".
http://www.dia.uniroma3.it/~rimondin/downloads.php
I also found this (but the first solution was free and worked so :-))
http://www.verypdf.com/wordpress/201306/how-to-create-a-pdf-from-powerpoint-with-animations-36850.html
This blog post provides a VBA macro script that will split every slide that has animations (e.g. images or bullet points that appear one by one) into multiple slides, and then you can save as PDF and voila!
Importantly, since it's a VBA script it should work both for Windows and Mac. I've only tried it on OSX (yosemite) with powerpoint 2011, and it worked pretty well. The only issue I had was that slides with animated bullet points (that appear one by one) were split into multiple slides but every slide contained all the bullet points, so I had to delete some manually. Still, for everything else it worked perfectly and it's a small price to pay compared to doing it all manually, especially image animations. Of course you may/may not encounter the same issue on Windows or other versions of PP. In any case, for OSX it's the only working solution I've found so far.
Instructions for adding VBA macros to powerpoint can be found here.
Hope it works for you too!
This blog post provides a VBA macro script that will split every slide that has animations into multiple slides, without keeping the original slides in front of the expanded slides (as is the case in this answer).
The problem that remains with this macro and the other macro, is that the content of a text block with multiple animations is always shown as a whole (e.g. if each sentence of the same text block has a separate animation, all sentences will always be shown together).
VBA Code:
Private AnimVisibilityTag As String
Sub ExpandAnimations()
AnimVisibilityTag = "AnimationExpandVisibility"
Dim pres As Presentation
Dim Slidenum As Integer
Set pres = ActivePresentation
Slidenum = 1
Do While Slidenum <= pres.Slides.Count
Dim s As Slide
Dim animationCount As Integer
Set s = pres.Slides.Item(Slidenum)
If s.TimeLine.MainSequence.Count > 0 Then
Set s = pres.Slides.Item(Slidenum)
PrepareSlideForAnimationExpansion s
animationCount = expandAnimationsForSlide(pres, s)
Else
animationCount = 1
End If
Slidenum = Slidenum + animationCount
Loop
End Sub
Private Sub PrepareSlideForAnimationExpansion(s As Slide)
' Set visibility tags on all shapes
For Each oShape In s.Shapes
oShape.Tags.Add AnimVisibilityTag, "true"
Next oShape
' Find initial visibility of each shape
For animIdx = s.TimeLine.MainSequence.Count To 1 Step -1
Dim seq As Effect
Set seq = s.TimeLine.MainSequence.Item(animIdx)
On Error GoTo UnknownEffect
For behaviourIdx = seq.Behaviors.Count To 1 Step -1
Dim behavior As AnimationBehavior
Set behavior = seq.Behaviors.Item(behaviourIdx)
If behavior.Type = msoAnimTypeSet Then
If behavior.SetEffect.Property = msoAnimVisibility Then
If behavior.SetEffect.To <> 0 Then
seq.Shape.Tags.Delete AnimVisibilityTag
seq.Shape.Tags.Add AnimVisibilityTag, "false"
Else
seq.Shape.Tags.Delete AnimVisibilityTag
seq.Shape.Tags.Add AnimVisibilityTag, "true"
End If
End If
End If
Next behaviourIdx
NextSequence:
On Error GoTo 0
Next animIdx
Exit Sub
UnknownEffect:
MsgBox ("Encountered an error while calculating object visibility: " + Err.Description)
Resume NextSequence
End Sub
Private Function expandAnimationsForSlide(pres As Presentation, s As Slide) As Integer
Dim numSlides As Integer
numSlides = 1
' Play the animation back to determine visibility
Do While True
' Stop when animation is over or we hit a click trigger
If s.TimeLine.MainSequence.Count <= 0 Then Exit Do
Dim fx As Effect
Set fx = s.TimeLine.MainSequence.Item(1)
If fx.Timing.TriggerType = msoAnimTriggerOnPageClick Then Exit Do
' Play the animation
PlayAnimationEffect fx
fx.Delete
Loop
' Make a copy of the slide and recurse
If s.TimeLine.MainSequence.Count > 0 Then
s.TimeLine.MainSequence.Item(1).Timing.TriggerType = msoAnimTriggerWithPrevious
Dim nextSlide As Slide
Set nextSlide = s.Duplicate.Item(1)
numSlides = 1 + expandAnimationsForSlide(pres, nextSlide)
End If
' Apply visibility
rescan = True
While rescan
rescan = False
For n = 1 To s.Shapes.Count
If s.Shapes.Item(n).Tags.Item(AnimVisibilityTag) = "false" Then
s.Shapes.Item(n).Delete
rescan = True
Exit For
End If
Next n
Wend
' Clear all tags
For Each oShape In s.Shapes
oShape.Tags.Delete AnimVisibilityTag
Next oShape
' Remove animation (since they've been expanded now)
While s.TimeLine.MainSequence.Count > 0
s.TimeLine.MainSequence.Item(1).Delete
Wend
expandAnimationsForSlide = numSlides
End Function
Private Sub assignColor(ByRef varColor As ColorFormat, valueColor As ColorFormat)
If valueColor.Type = msoColorTypeScheme Then
varColor.SchemeColor = valueColor.SchemeColor
Else
varColor.RGB = valueColor.RGB
End If
End Sub
Private Sub PlayAnimationEffect(fx As Effect)
On Error GoTo UnknownEffect
For n = 1 To fx.Behaviors.Count
Dim behavior As AnimationBehavior
Set behavior = fx.Behaviors.Item(n)
Select Case behavior.Type
Case msoAnimTypeSet
' Appear or disappear
If behavior.SetEffect.Property = msoAnimVisibility Then
If behavior.SetEffect.To <> 0 Then
fx.Shape.Tags.Delete AnimVisibilityTag
fx.Shape.Tags.Add AnimVisibilityTag, "true"
Else
fx.Shape.Tags.Delete AnimVisibilityTag
fx.Shape.Tags.Add AnimVisibilityTag, "false"
End If
Else
' Log the problem
End If
Case msoAnimTypeColor
' Change color
If fx.Shape.HasTextFrame Then
Dim range As TextRange
Set range = fx.Shape.TextFrame.TextRange
assignColor range.Paragraphs(fx.Paragraph).Font.Color, behavior.ColorEffect.To
End If
Case Else
' Log the problem
End Select
Next n
Exit Sub
UnknownEffect:
MsgBox ("Encountered an error expanding animations: " + Err.Description)
Exit Sub
End Sub
For those of you using LibreOffice or OpenOffice,
there is a plugin available on github that does this very well :
ExpandAnimations
In my experience, all of the standard appear/disappear animations are nicely split. Object movement animations also work (you get a slide with start position and one with end position of the object). I haven't had the chance to test other animation types, but that should cover about all standard needs :-)

PowerPoint 2013 Macro Runs Slow (redrawing issue?)

I have a macro that is supposed to make every shape on a page visible (I have other macros that make them invisible). Here is the code:
Dim Slide As Integer
Slide = SSW.View.CurrentShowPosition
If Slide = 1 Then
For Each shp In ActivePresentation.Slides(2).Shapes
shp.Visible = True
Next shp
End if
This macro takes forever to run. I suspect this is because it is redrawing the screen every time a shape is made visible.
This is not necessary, in fact the slide isn't even shown on the screen when this macro is run (it runs on Slide 1 but makes the shapes on Slide 2 visible). Is there any way to make this run faster? Disable the screen refresh or something?
I tried Shyam's solution from http://www.vbaexpress.com/forum/showthread.php?33671-Solved-PP2010-ScreenUpdating-False but it doesn't work. His only goes up to 2010 and I'm using 2013.
Your code doesn't work as shown. I changed it to this, which works pretty much instantly on a slide with 175 shapes:
' Put this at the top of every module; builds character, keeps you out of trouble
Option Explicit
Sub ThisWorks()
' Always dim ALL variables
Dim Slide As Long ' SlideIndex is a Long, not an Integer
Dim oSh As Shape
' Replaced your SSW with this:
Slide = SlideShowWindows(1).View.CurrentShowPosition
If Slide = 1 Then
For Each oSh In ActivePresentation.Slides(2).Shapes
' I was toggling them back and forth as a test
' oSh.Visible = Not oSh.Visible
oSh.Visible = True
Next
End If
' Delete this when it's no longer needed
MsgBox "Done"
End Sub

Powerpoint macro - basic problem with counter

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).