Automating PowerPoint.
Automating PowerPoint
This article is an introduction to the basics of the PowerPoint object model and how to automate PowerPoint. If you are not familiar (yet) with the automation of an MS Office application, I strongly recommend giving this article a read first. That article will give you a more thorough explanation on automating MS Office programs in general, early and late binding, and how to refer to, use, and work with MS Office application objects.
In this article I take a closer look to the most important PowerPoint objects, how to work with them, and explain some essential things you should know about them, before automating PowerPoint.
These objects are in order of importance:
Available objects, methods, properties and events might differ in each of the PowerPoint versions.
An enumeration of the version specific innovations can be found here:
Note that this article only applies to the "full" version of PowerPoint which is part of the MS "Office" package, or which can be bought as a stand-alone application, and not to the free PowerPoint "viewer" distributed by MS, as these viewers don't support running macros.
References to the PowerPoint viewer download pages and articles about their limitations:
- The 97 version and its limitations (KB 170443).
- The 2003 version and short description of its limitations.
Although running macros is not supported in any of the PowerPoint viewers, the 97/2000 PowerPoint viewers support a limited Automation model that we can use to programmatically start a slide show. For those who wish to learn more about the limited automation of the 97/2000 PowerPoint viewers, following articles might be worth reading:
- Known issues (KB 173127).
- Available methods and properties (KB 170796).
- Description and example of the automation model (KB 265385).
The application:
Within the Office environment, the PowerPoint object is a somewhat special application. For a start, unlike for instance Word or Excel, PowerPoint is a single-session program.
In short this means that code like this:
'The early binding way. Dim PP As PowerPoint.Application Set PP = New PowerPoint.Application 'Or the late binding way. Dim PP As Object Set PP = CreateObject("PowerPoint.Application")
will only create a new PowerPoint instance if PowerPoint is not running already, but will never create a new PowerPoint instance if PowerPoint is already running. If you want to read MS's "official" article about this issue, look here (KB 222783).
Depending on circumstances, this "single-session" property of the PowerPoint program can be very handy, but on the other hand, it can also make some of our automated tasks more dangerous, as we could easily mess up an already running PowerPoint instance or open presentation(s). For this reason, if we wish to automate PowerPoint from another (MS Office) application, at least an additional test is necessary to check whether or not PowerPoint is already running.
That test could look like this:
Sub EditPPtFile() Dim PP As PowerPoint.Application, PPRunning As Boolean Dim Pres As PowerPoint.Presentation, PresMsg As Boolean 'Note: error handling Is directed To different points, "AppErr" In Case an error occurs While creating Or accessing the 'PowerPoint application Or on opening of the presentation, "PresErr" In Case an error occurs While editing the presentation. 'Depending on where an error occurs, an error specific message will be shown after the cleanup process. On Error Resume Next Set PP = GetObject(Class:="PowerPoint.Application") On Error Goto AppErr 'If PowerPoint Is already running, make a note of it, because we shouldn't quit the application when done. 'If PowerPoint Is Not running yet, create a New instance of it. If Not PP Is Nothing Then PPRunning = TRUE Else Set PP = New PowerPoint.Application End If 'Open a presentation. Set Pres = PP.Presentations.Open("C:MyPresentation.ppt", WithWindow:=FALSE) On Error Goto PresErr 'Do stuff, add a blank slide at the End of the presentation For instance. Pres.Slides.Add Index:=Pres.Slides.Count + 1, Layout:=ppLayoutBlank 'Save the changes made. Pres.Save PresErr: '(the "cleanup" process) 'Close the presentation. Pres.Close If Err.Number <> 0 Then PresMsg = TRUE AppErr: 'If a New instance of PowerPoint was created, quit it. If (Not PP Is Nothing) And (PPRunning = FALSE) Then PP.Quit 'Clear Object variables. Set Pres = Nothing Set PP = Nothing 'If no error occurred, exit the procedure, Else, show the proper error message. If Err.Number = 0 Then Exit Sub If PresMsg Then MsgBox "A problem occurred while editing the presentation. " & vbCrLf & _ "Due to this problem, the presentation is closed. ", vbExclamation, "May I have your attention please..." Else MsgBox "PowerPoint is possibly not installed on your system, " & vbCrLf & _ "or the presentation you wish to open could not be found. ", vbExclamation, "May I have your attention please..." End If End Sub
The "WithWindow" argument of the "Open" method (as shown in the above example), determines whether or not the presentation will be made visible in a new window and the taskbar. If WithWindow is set to True (which is the default value), the visibility of the application in which we want to open the presentation needs to be set to True (in case we create a new PowerPoint instance), or be True already (in case we use an existing PowerPoint instance), otherwise the code will crash!
Presentations:
Once we have access to the PowerPoint application, we are ready to access the presentation(s). Here again the PowerPoint program is a little different from some other MS Office programs. Whereas Word (.dot) and Excel (.xls) have only one type of work document, PowerPoint has two kinds of work documents, namely presentations (.ppt) and slide shows (.pps) (I don't reckon with templates, add-ins etc...). And to make it even more complicated, each slide show is a presentation, but not every presentation is a slide show. In short, each presentation could be running as a slide show or in "edit" mode, a slide show is a "running" presentation.
With two presentations open, but only one of them running as a slide show, the next little piece of code:
MsgBox "Presentations " & Presentations.Count & vbCrLf & "Slide shows " & SlideShowWindows.Count
would return: Presentations 2, Slide shows 1.
Knowing this, we can expand this code, to determine which of the open presentations are in "edit" mode, and which are running as a slide show:
'Executed from a running slide show. Dim Pres As Presentation, i As Integer Dim PPsList As String, PPtList As String 'Since each slide show Is a presentation but Not every presentation a slide show, 'two loops are used, an outer Loop which loops through the presentations collection, 'and a inner Loop which loops only through the slide show windows collection. For Each Pres In Presentations For i = 1 To SlideShowWindows.Count 'Compare the slide show name To presentation name(s), 'and If a match Is found, skip To the Next presentation. If SlideShowWindows(i).Presentation.Name = Pres.Name Then 'Create the collection of slide shows. PPsList = PPsList & Pres.Name & vbCrLf: Goto SkipPres End If Next i 'Create the collection of presentations. PPtList = PPtList & Pres.Name & vbCrLf SkipPres: Next Pres 'Display the result. MsgBox "Running Slide show(s):" & vbCrLf & PPsList & vbCrLf & _ "Presentation(s) in edit mode:" & vbCrLf & PPtList
Opening presentations and running slide shows:
When we want to open a ppt file, a single and simple line of code will be sufficient:
Presentations.Open ("C:MyPresentation.ppt")
Whereas the Presentations.Open method will open a ppt file in "edit" mode, when we apply the same method to open a pps file, the file will always be opened as a slide show. This is the default behaviour of the "Open" method, but sometimes we might want/need to open a pps file in edit mode too.
The following workaround does the trick:
Dim EditPres As Presentation 'Open a pps file, but prevent it from running As a slide show by setting the WithWindow argument To False. Set EditPres = Presentations.Open("C:MyPresentation.pps", WithWindow:=FALSE) 'And once the file Is opened, create a New "edit" window For it. EditPres.NewWindow 'Do stuff...rest of your code.
Once a presentation is open in "edit" mode, it can easily be launched as a slide show. That's when the "SlideShowSettings" property of the presentation object comes into play. The most important method of the SlideShowSettings object necessary to start a slide show is "Run".
Presentations("MyPresentation").SlideShowSettings.Run
Though "Run" is the only method really necessary to launch a slide show, some of the SlideShowSettings properties might be worth taking a closer look at as well:
- ShowType: sets the show type for the specified slide show...(kiosk, speaker, window).
- StartingSlide: sets the first slide to be displayed in the specified slide show.
- EndingSlide: sets the last slide to be displayed in the specified slide show.
Custom slide shows:
We can also create custom slide shows based upon an existing presentation and specify which of the presentation's slides needs to be part of the custom slide show (in the PowerPoint menu > "Slide Show" > "Custom Shows"). Assuming "MyPresentation" is open in edit mode and does hold at least 5 slides, next code will create and launch a custom slide show based upon only 3 of the slides of the original presentation (slides 1 and 4 are excluded):
Dim SlArr(3) As Long With Presentations("MyPresentation") 'Add the slide ID's To an array. SlArr(1) = .Slides(2).SlideID SlArr(2) = .Slides(3).SlideID SlArr(3) = .Slides(5).SlideID 'Add, create And name the custom show, And Set the type of show that needs To be shown. With .SlideShowSettings .NamedSlideShows.Add "MyCustomShow", SlArr .RangeType = ppShowNamedSlideShow .SlideShowName = "MyCustomShow" .Run End With End With
Note that after running the above piece of code we will be prompted to save changes when closing "MyPresentation" since a custom show has been added to the presentation's "NamedSlideShows" collection.
Presentations("MyPresentation").NamedSlideShows("MyCustomShow").Delete
Although you can refer to a custom slide show by its index number, referring to it by its name is a much safer way.
Closing presentations:
Being prompted to save changes on closing of a presentation can be avoided by fooling PowerPoint and making the application think that our presentation is saved already.
With Presentations("MyPresentation") .Saved = TRUE .Close End With
When applied on a running slide show, the "Close" method will not only stop the slide show, but also close both the presentation's "slide show" and "edit" window, in other words close the entire presentation. The "Exit" method on the other hand will stop a running slide show but not close the presentation's edit window. One remark though, when applied on a pps file opened by double clicking or the "Presentations.Open" method, both "Close" and "Exit" will have the same effect, i.e. stop and close the running slide show.
The following example will show the difference between the two methods:
With Presentations("MyPresentation") 'First run. .SlideShowSettings.Run MsgBox "Exit show" 'Force the show back In "edit" mode. .SlideShowWindow.View.Exit 'Second run. .SlideShowSettings.Run MsgBox "Close show" 'Close both the show And the presentation's edit window. .Close End With
Detecting when a slide show has finished:
In case we would create and/or launch a slide show from another application, it sometimes might be useful to know when the slide show we started has ended or been closed manually. The following procedure which invokes some API, will detect when a launched slide show has come to an end and give the focus back to the application which has called it.
A procedure which can be called like this:
ResumeWhenDone "C:MyPresentation.ppt", 5
'The timeGetTime Function retrieves the system time, In milliseconds. 'The system time Is the time elapsed since Windows was started. Private Declare Function timeGetTime Lib "winmm.dll" () As Long 'The Sleep Function suspends the execution of code For the specified interval (In milliseconds). Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long) 'Note that these API functions needs To be declared In a module. Sub ResumeWhenDone(PresFullName As String, MaxMinutes As Integer) '"MaxMinutes" Is the maximum time (In minutes) the show Is allowed To run. 'If Not closed by the user within this time, the show will be closed code wise. Dim StrT As Long, RunT As Double, i As Integer Dim PP As PowerPoint.Application, PPRunning As Boolean Dim Pres As PowerPoint.Presentation, PresMsg As Boolean On Error Resume Next Set PP = GetObject(Class:="PowerPoint.Application") On Error Goto AppErr If Not PP Is Nothing Then PPRunning = TRUE Else Set PP = New PowerPoint.Application End If Set Pres = PP.Presentations.Open(FileName:=PresFullName, WithWindow:=FALSE) StrT = timeGetTime On Error Goto PresErr 'Make the application visible And start the slide show. 'Making the application visible after the presentation Is opened, will prevent us from 'seeing the application window first In Case opening of the presentation would take some time. PP.Visible = TRUE Pres.SlideShowSettings.Run Do On Error Resume Next 'Check one of the presentation's properties, And As Long this check does 'not result In an error we know the show Is still running. i = Pres.SlideShowSettings.ShowType 'Calculate the time elapsed since the show Is running, In minutes. RunT = ((timeGetTime - StrT) / 1000) / 60 'If the presentaion Is closed by the user. If Err.Number <> 0 Then Err.Clear: Goto AppErr 'If "MaxMinutes" Is exceeded. If (Int(RunT) >= Int(MaxMinutes)) Then Goto PresErr DoEvents Sleep 10 Loop PresErr: Pres.Close If Err.Number <> 0 Then PresMsg = TRUE AppErr: If (Not PP Is Nothing) And (PPRunning = FALSE) Then PP.Quit Set Pres = Nothing Set PP = Nothing If Err.Number = 0 Then Exit Sub If PresMsg Then MsgBox "A problem occurred while running the slide show. " & vbCrLf & _ "Due to this problem, the slide show is closed. ", vbExclamation, "May I have your attention please..." Else MsgBox "PowerPoint is possibly not installed on your system, " & vbCrLf & _ "or the presentation you wish to open could not be opened. ", vbExclamation, "May I have your attention please..." End If End Sub
Slides:
Presentations are made up of slides, but whereas a Word document needs to have at least one page to have a saveable document, and an Excel workbook needs at least one worksheet to be saveable, a PowerPoint presentation can be saved as presentation without any slides.
MsgBox ActivePresentation.Slides.Count
Will pretty obviously return the number of slides of the active presentation.
And looping through the presentation's or slide show's slides collection can easily be done like this:
Dim i As Integer, Sl As Slide With ActivePresentation 'If there Is at least one slide. If .Slides.Count >= 1 Then 'One way To Loop For i = 1 To .Slides.Count 'Do stuff, For instance : MsgBox .Slides(i).SlideID Next i 'And an other one... For Each Sl In .Slides 'Do stuff, For instance : MsgBox Sl.SlideID Next Sl End If End With
Slide ranges:
If we would like to manipulate some or all slides of a presentation in one go, a loop would not be the most efficient way, the "Range" method would be a much better one.
Manipulating specific slides in one go (change their "Background" colour and effect):
'Create a range With slides 2, 3 And 4. With ActivePresentation.Slides.Range(Array(2, 3, 4)) 'Ignore the default background settings. .FollowMasterBackground = FALSE 'And add a New background color And effect. .Background.Fill.PresetGradient msoGradientHorizontal, 1, msoGradientDaybreak End With
Manipulating all slides in one go (change their "SlideShowTransition" settings):
With ActivePresentation.Slides.Range.SlideShowTransition 'make sure the Next slide will be shown automatically. .AdvanceOnTime = TRUE 'Set "advance to next slide" time (In seconds). .AdvanceTime = 5 End With
Note that not all properties can be changed and not all methods can be applied on an entire presentation or a multiple sliderange!
Identifying slides: names, numbers and indices...
Each slide has a "SlideID", "SlideIndex" and "SlideNumber".
The slide ID is a unique number added by default to each slide you add to a presentation. This number will never change during the lifetime of the slide, nor by moving the slide within the presentation, as long as the slide stays in the presentation in which it was created.
The slide index is the number which represents the slide's current position within the presentation, and will change as soon the slide is moved within the presentation. Needless to say the movement of a slide within a presentation will also affect the index number of some or all other slides in the same presentation, dependent on the position the slide had in the slides hierarchy before it was moved. For instance, moving the first slide in a presentation after the 5th, will also change the index number of 4 other slides.
The slide number is added by default, like the slide ID, but its use is mostly for printing purposes only, and it can be hidden or made visible in a slide's header or footer. By default the slide number will correspond with the slide index number, unless we change the "FirstSlideNumber", code wise or manually (File > Page Setup > Number slides from). The slide number will always be equal to the starting slide number + the slide index number - 1.
We can determine which slide we are looking at (no matter whether the presentation is running as a slide show or in edit mode), with a piece of code which looks like this:
Dim i As Integer, SlIndex As Integer With ActivePresentation 'Check If any slide shows are running. If SlideShowWindows.Count > 0 Then 'Loop through the slide shows collection, 'and If the slide show's name Is equal To the active presentation's name, 'then get the index number of the active slide. For i = 1 To SlideShowWindows.Count If SlideShowWindows(i).Presentation.Name = .Name Then SlIndex = SlideShowWindows(i).View.Slide.SlideIndex End If Next i 'If there are no slide shows running the presentation will be In "edit" mode. Else 'Loop through the active presentation's windows collection, 'and f the caption of the window Is equal To the active one, 'then get the index number of the active slide. For i = 1 To ActivePresentation.Windows.Count If .Windows(i).Caption = ActiveWindow.Caption Then SlIndex = .Windows(i).View.Slide.SlideIndex End If Next i End If End With MsgBox "The current active slide's index number = " & SlIndex
Instead of referring to a slide by its ID or index number, we could equally well use the slide's name. It is a good habit to add a custom name to each slide you programmatically add, and to store that name in for instance the slide title (which visibility can be set to False), or in the "NotesPage" for later reference.
Shapes:
Without shapes, no presentations or slide shows. It would be pretty boring to watch a 30 minutes slide show, if that show would consist of blank slides only. Every object (autoshape, commandbutton, picture, sound, graph etc...) you add to a slide will become part of that slide's shapes collection. And as is the case with a presentation and its slides, we can loop through a slide's shapes collection as well, or count the number of items the shapes collection has:
MsgBox ActivePresentation.Slides(1).Shapes.Count
Although the above piece of code will work error free, it might give us a misleading result, as it will not reckon with grouped shapes. Once shapes are grouped, this group will be treated as if it was an individual (single) shape. And a group could consist of individual shapes, but might also have sub-groups, sub-sub-groups and sub-sub-sub-groups etc...too.
If we really would like to count (or list) all the individual shapes in a slide, the following routine will do the trick:
Dim Sl As SlideRange, Sh As Shape, GotAll As Boolean 'The routine will create a duplicate of the involved slide first 'to avoid messing up the original slide Or presentation, And delete it afterwards. 'Once a duplicate has been created, the routine will Loop through the duplicate's shapes 'collection And ungroup each group of shapes until no more grouped shapes are found. 'When all grouped shapes are ungrouped, all individual shapes are counted. With ActivePresentation.Slides(1) 'Create a duplicate of the slide. Set Sl = .Duplicate Do 'Set Boolean In Case none of the shapes found would be a group. GotAll = TRUE 'Loop through the slide's shapes collection, 'and If the shape Is a group, ungroup it. For Each Sh In Sl.Shapes If Sh.Type = msoGroup Then Sh.Ungroup 'Set Boolean To keep the Loop going. GotAll = FALSE End If Next Sh Loop Until GotAll End With 'Display result. MsgBox Sl.Shapes.Count 'And delete the duplicate. Sl.Delete 'Clear Object variables. Set Sl = Nothing
Changing grouped shapes:
We can access individual shapes which are part of a group, edit, format, and even move them separately, but we can not add to, or delete shapes from a group.
'Change the text of the second individual shape, part of a group named "MyGroup" With ActivePresentation.Slides(1).Shapes("MyGroup") .GroupItems(2).TextFrame.TextRange.Text = "A new piece of text!" End With
If we wish to delete an individual shape from a group, we'll have to "Ungroup" that group first. Note that from the moment we "Ungroup" a shape group, not only all of the index numbers of the shapes that come after that group in the index hierarchy will change, but once grouped again, both the name and ID of that group will be changed. Note that the Shape- and ShapeRange ID were introduced in version 10.0 (PowerPoint 2002/XP).
Add some shapes (at least 3) to the first slide of a presentation, and group them, if you wish to run following example:
Dim DelSh As String, Sh As Shape Dim OldInfo As String, NewInfo As String 'Refer To a slide In which the first And only shape Is a shape group. With ActivePresentation.Slides(1).Shapes(1) 'Create a message text, And get the group name. OldInfo = "The original group name was: " & .Name & vbCrLf 'Get the name of the second sub-shape And ungroup its parent shape. DelSh = .GroupItems(2).Name .Ungroup 'Delete the shape And group the remaining shapes again. ActivePresentation.Slides(1).Shapes(DelSh).Delete Set Sh = ActivePresentation.Slides(1).Shapes.Range.Group 'Create the second piece of message text, And get the group name again. NewInfo = "The new group name is: " & Sh.Name & vbCrLf 'Display the message. MsgBox OldInfo & vbCrLf & NewInfo End With 'Clear Object variables. Set Sh = Nothing
You'll notice that the name of the group will be changed. As is the case with slides, a shape's ID will never change during the lifetime of the shape as long as the shape is not moved from the slide in which it was created. We have seen before that a group of shapes is treated as it were a single shape, ungrouping a group will destroy that shape object, its name and ID, and create a new name and ID, once shapes are (re-) grouped. Due to this behaviour, I would strongly recommend giving each object you programmatically add or deal with, a custom name, and to use custom names in general as much as possible.
Grouped shapes, continued:
We can also do the opposite, and access the entire group of which an individual shape is part of like this:
Dim Sh As Shape 'Refer To a slide In which the first shape Is a shape group. With ActivePresentation.Slides(1).Shapes(1) 'Set Object (the entire group of which the item Is part of). Set Sh = .GroupItems(2).ParentGroup 'Grab the forecolor used In the second item, And apply it on the entire group. Sh.Fill.ForeColor = .GroupItems(2).Fill.ForeColor End With 'Clear Object variables. Set Sh = Nothing
And as was the case with slides, the "Range" method can be applied on the shapes collection too.
Manipulating specific shapes in a slide in one go (change their "ForeColor" and effect):
With ActivePresentation.Slides(2).Shapes.Range(Array("SillyShape", "SpongeBob SquarePants")) .Fill.ForeColor.RGB = RGB(150, 255, 150) .Fill.TwoColorGradient msoGradientHorizontal, 2 End With
Manipulating all shapes in a slide in one go (change their "Transparency"):
With ActivePresentation.Slides(2).Shapes.Range .Fill.Transparency = 0.5 '(0 = solid, 1.0 = transparent). End With