How to select several slides using For Loop? - vba

I have a presentation to split into several smaller ones depending on the text in the header.
I've managed to find and compile a code that finds the last slide that has the text), then it selects several previous slides, copies them to a new presentation, saves it, and repeats that for the next value.
This would be fine if number of slides with every text was the same (and all the slides with the same text were in the same order), but it's not always the case in my presentation. I need to modify the function.
Basically, it should return not just a slide, but a slide range, and it should be resized with every loop where if function is true. I know how to get all the slide indexes as a string, but I don't know how to use that string to select those slides.
Function FindSlideByTitle(sTextToFind As String) As slide
For Each oSl In ActivePresentation.slides
With oSl.Shapes.Title.TextFrame
If .HasText Then
If InStr(1, .TextRange.Text, sTextToFind, vbTextCompare) > 0 Then
Set FindSlideByTitle = oSl
End If
End If
End With
Next

I don't see an advantage to creating a slide range in this instance. A function for this isn't going to work, since it can only return one slide at a time, as you've discovered.
Instead, create a Sub using your loop, then replace
Set FindSlideByTitle = oSl
with a routine to add the found slide to an array:
Option Base 1
Dim FoundSlide() As Integer
Dim oSl As Slide
Sub FindSlideByTitle()
ReDim Preserve FoundSlide(1)
For Each oSl In ActivePresentation.Slides
With oSl.Shapes.Title.TextFrame
If .HasText Then
If InStr(1, .TextRange.Text, "Ham", vbTextCompare) > 0 Then
Count% = UBound(FoundSlide)
ReDim Preserve FoundSlide(Count% + 1)
FoundSlide(Count% + 1) = oSl.SlideNumber
End If
End If
End With
Next
End Sub
Then process that subset of slides one at a time:
Sub DoSomethingWithSlide()
For X = 1 To UBound(FoundSlide)
With ActivePresentation.Slides(FoundSlide(X))
'Process each slide here
End With
Next X
End Sub

Anyway, this is what I came up with in the end, and it (almost) seems to be working.
Dim sSlides() As String
sSlides() = Split(FindSlide(sCountry), ";|;")
Dim n As Long
Dim iSlides() As Integer
ReDim iSlides(LBound(sSlides) To UBound(sSlides))
For n = LBound(sSlides) To UBound(sSlides)
iSlides(n) = CInt(sSlides(n))
Next n
Dim rCountrySlides As SlideRange
Set rCountrySlides = ActivePresentation.slides.range(iSlides)
rCountrySlides.Select

Related

VBA PowerPoint Slides set custom layout to refresh the layout

I have created a script processing many slides and at the end, some slides seem to have glitches in their layout. For example, slide numbers have moved on some slides but not on others. It can be fixed manually by re-assigned the custom layout to the slide.
How can I do this automatically?
I could just loop over all slides, find out it's custom layout and re-assign it. But how? This code seems to loop infinitely:
Dim sld As Slide
Dim layoutName As String
Dim layoutIndex As Integer
Set sld = Application.ActiveWindow.View.Slide
layoutName = sld.CustomLayout.Name
layoutIndex = getLayoutIndexByName(layoutName)
ActivePresentation.Slides(y).CustomLayout = ActivePresentation.Designs(y).SlideMaster.CustomLayouts(layoutIndex)
Function getLayoutIndexByName(xName As String) As Integer
ActivePresentation.Designs(1).SlideMaster.CustomLayouts.Item (1)
With ActivePresentation.Designs(1).SlideMaster.CustomLayouts
For i = 1 To .Count
Debug.Print ("inLoop Name: " + .Item(i).Name)
If .Item(i).Name = xName Then
getLayoutIndexByName = i
Exit Function
End If
Next
End With
End Function
To simply reapply the layout already assigned, you only need this:
ActivePresentation.Slides(y).CustomLayout = ActivePresentation.Slides(y).CustomLayout
Occasionally, that command doesn't work, then this workaround is worth a try:
DoEvents
Application.CommandBars.ExecuteMso ("SlideReset")
DoEvents
To apply a new layout, then you need to use something like this code, which is pretty similar to yours:
ActivePresentation.Slides(y).CustomLayout = ActivePresentation.Designs(1).SlideMaster.CustomLayouts(GetLayoutIndexFromName("Text Page", ActivePresentation.Designs(1)))
My version of GetLayoutIndexFromName:
Function GetLayoutIndexFromName(sLayoutName As String, oDes As Design) As Long
Dim x As Long
For x = 1 To oDes.SlideMaster.CustomLayouts.Count
If oDes.SlideMaster.CustomLayouts(x).Name = sLayoutName Then
GetLayoutIndexFromName = x
Exit Function
End If
Next
End Function

Adding a Slide at the beginning of a section

I am trying to code an automatic agenda/table of contents generator in VBA for PowerPoint, which generates agenda bullet points based on the titles of the sections inside the PowerPoint presentation. Since I also want the agenda to appear at the beginning of every section, I am struggling with the fact that the method
.AddSlide(Index (I am inserting the ID of a section´s first slide here), pCustomLayout )
adds the slide(s) just before the section (so actually at the end of the previous section) because it's just based on an ID and does not say "insert the slide at a section's beginning".
Is there an easy solution (without deleting and recreating the section for instance) to achieve that the slides are created just at the beginning of a section and not at the end of the previous section?
Solution
Sub moveSlidesToSectionStart(pSectionIndex, pFirst, pLast)
Dim objPresentation As Presentation
Set objPresentation = Application.ActivePresentation
totalSlides = pLast - pFirst + 1
Dim arr()
ReDim arr(totalSlides - 1)
For i = 0 To totalSlides - 1 'fill array with all slides (slide numbers) that need to be moved
arr(i) = pFirst + i
Next i
objPresentation.Slides.Range(arr).MoveToSectionStart(pSectionIndex)
End Sub
You could use the MoveToSectionStart method available on the slide. Pass in the section index as the argument and it will place the slide right at the start of that section.
Function MoveSlideToSectionStart(Sld As Slide, SectionIndex As Long) As Boolean
If Sld.Parent.SectionProperties.Count < SectionIndex Then
MoveToSection = False
Exit Function
End If
Call Sld.MoveToSectionStart(SectionIndex)
MoveToSection = True
End Function
Sub Test()
Debug.Print MoveToSection(ActivePresentation.Slides(6), 1)
End Sub

Delete Shape Macro needs to be run twice to work

I have this PPT vba script to delete all shapes in a region at the bottom left corner of each slide. It works but sometimes it leaves one shape in the area. If I run it again it will get rid of it, but I don't want to have to run it twice! How can I fix this?
Sub GoAwayDumbText()
Dim oPres As Presentation
Dim oSlides As Slides
Dim oSld As Slide
Dim oShp As Shape
Dim PathSep As String
Dim sTempString As String
#If Mac Then
PathSep = ":"
#Else
PathSep = "\"
#End If
Set oPres = ActivePresentation
Set oSlides = oPres.Slides
For Each oSld In oSlides
For Each oShp In oSld.Shapes
If oShp.Left <= 135 And oShp.Top >= 260 Then
oShp.Delete
Else
End If
Next oShp
Next oSld
End Sub
When you're iterating through a collection (shapes, slides, whatever) and deleting items along the way, For/Each loops won't behave as expected.
Instead, use (in this case)
For x = oSld.Shapes.Count to 1 Step -1
Delete from the end of the collection to the beginning rather than from the beginning to the end.
Why?
Suppose you have three items in the collection:
VBA starts with its internal For/Each counter set to 1
First item meets your criteria, you delete it
VBA increments its internal For/Each counter to 2
Since you deleted one item, there are only two items left in the collection, so VBA looks at the second item (which USED TO BE the third item) and deletes it.
VBA increments its internal counter to 3, but you've deleted the first two of three original items, so there's only 1 item in the collection; there's no item three so what used to be item 3 never gets looked at.
[If anyone can offer a better/clearer explanation of this, PLEASE have at it.]

Create animated random image display tool in VBA

I have a PowerPoint slide with different images. I need to create VBA code in PowerPoint that recognises all these images and fades them out one by one - except for one randomly chosen image. This last image should remain until the end, then fade out and display in the middle of the slide.
I have an idea of how to do it and have experience with object oriented languages (R) but I have never used VBA before. Therefore I would be grateful for pointers on how to do any of the following in VBA:
Determine number of images on active slide
Select each image one after another and assign a counter variable as selection label (that part should work as described here)
Create "Range A" of all assigned counter variables
Select random number "x" in "Range A"
Create "Range B" of all counter variables in "Range A" EXCEPT for the random number "x"
Randomise the order of variables in "Range B"
Loop through "Range B" and fade out images whose label corresponds to the respective "Range B" variable that comes up
Fade out the image whose label corresponds to "x"
Insert the image whose label corresponds to "x" in the centre of the slide
If it is very difficult to recognise images or assign labels to those images I can also do so manually. However, it would be nicer if that could happen automatically. I would be grateful for any pointers, also in the form of links if you think that part of the above process is already described somewhere else (I'm afraid since I'm inexperienced in VBA I am not using very effective search terms).
EDIT:
Please find the solution (steps 8 and 9 are still missing)
Sub SelectionMacro()
Dim oSl As Slide
Dim oSh As Shape
Dim aArrayOfShapes() As Variant
Dim ShapeX As Shape
Dim N As Long
Dim Temp As Variant
Dim J As Long
Dim FadeEffect As Effect
Set oSl = ActivePresentation.SlideS(1)
'This section creates an array of all pictures on Slide1 called
'"aArrayOfShapes"
For Each oSh In oSl.Shapes
If oSh.Type = msoPicture Then
On Error Resume Next
Debug.Print UBound(aArrayOfShapes)
If Err.Number = 0 Then
ReDim Preserve aArrayOfShapes(1 To UBound(aArrayOfShapes) + 1)
Else
ReDim Preserve aArrayOfShapes(1 To 1)
End If
Set aArrayOfShapes(UBound(aArrayOfShapes)) = oSh
End If
Next
'This section creates a random index number within the bounds of the
'length of aArrayOfShapes and assigns the shape with that index number
'to the Shape object ShapeX
Randomize
NumberX = Int((UBound(aArrayOfShapes) - (LBound(aArrayOfShapes) - 1)) * Rnd) + LBound(aArrayOfShapes)
Set ShapeX = aArrayOfShapes(NumberX)
'This section shuffles aArrayOfShapes
For N = LBound(aArrayOfShapes) To UBound(aArrayOfShapes)
J = CLng(((UBound(aArrayOfShapes) - N) * Rnd) + N)
If N <> J Then
Set Temp = aArrayOfShapes(N)
Set aArrayOfShapes(N) = aArrayOfShapes(J)
Set aArrayOfShapes(J) = Temp
End If
Next N
'This section loops through all Shapes in aArrayOfShapes and
'fades them out one by one EXCEPT for ShapeX
For Each Shape In aArrayOfShapes
If ShapeX.Name <> Shape.Name Then
Set FadeEffect = oSl.TimeLine.MainSequence.AddEffect _
(Shape:=Shape, effectid:=msoAnimEffectFade, trigger:=msoAnimTriggerAfterPrevious)
With FadeEffect
.Timing.Duration = 0.5
.Exit = msoTrue
End With
End If
Next Shape
End Sub
In order to reset the slide to the state before running the macro (so as to be able to run it again and display another random image) the following macro needs to be run:
Sub ResetSelection()
For i = ActivePresentation.SlideS(1).TimeLine.MainSequence.Count To 1 Step -1
ActivePresentation.SlideS(1).TimeLine.MainSequence(i).Delete
Next i
End Sub
Working out the range of images shouldn't be too hard. This'll get you started.
Assigning animation to shapes can be tricky. You might be better off duplicating the slide with all the images then deleting all but a randomly chosen image.
Dim oSl As Slide
Dim oSh As Shape
' Dynamic array of shapes to hold shape references
Dim aArrayOfShapes() As Shape
Set oSl = ActiveWindow.Selection.SlideRange(1)
For Each oSh In oSl.Shapes
If oSh.Type = msoPicture Then
On Error Resume Next
Debug.Print UBound(aArrayOfShapes)
If Err.Number = 0 Then
ReDim Preserve aArrayOfShapes(1 To UBound(aArrayOfShapes))
Else
ReDim Preserve aArrayOfShapes(1 To 1)
End If
Set aArrayOfShapes(UBound(aArrayOfShapes)) = oSh
End If
Next`enter code here`
' Now you have an array containing references to all the pictures
' on the slide. You can use a random number function to return
' an index into the array to choose a picture at random.
With aArrayOfShapes(RandomNumberFunction(LBound(aArrayOfShapes), UBound(aArrayOfShapes)))
' google to find an appropriate function; they're out there
' do whatever you need to do with your shapes here
End With

ActiveWindow VBA commands to access active slide does not work on PowerPoint 2013 running on virtual machine

I just started using VBA a few days ago. I noticed that some few commands do not seem to work on my computer and I was wondering whether this is due to my computer setup.
I am using VBA in PowerPoint 2013 on Windows 7 run via VMware Fusion (virtual machine) on MacOSX. I need to create a dynamic reference to the active slide, but several way of doing so broke my code:
Set oSl = Application.ActiveWindow.View.Slide
(as suggested here)
Set oSl = ActivePresentation.Slides(ActiveWindow.View.Slide.SlideNumber)
(as suggested here)
Set oSl = ActiveWindow.Selection.SlideRange.SlideIndex
(as suggested here)
None of these worked for me. Since I just started using VBA I simply inserted Message Boxes after different parts of the code, and looked at when the boxes where no longer triggered - in this case always after the "oSl =" line that I replaced with the various other approaches delineated above. Additionally,
Set oSl = ActiveWindow.Selection.SlideRange(1)
also broke my code (as discussed here)
What DID work so far was
Set oSl = ActivePresentation.SlideS(1)
All the methods above that didn't work (but should) contain "ActiveWindow". It would be great if you could advise on whether there are errors in my approach to selecting the active slide or whether the issue might be that VBA can't access the "ActiveWindow" properly because my PowerPoint runs on a virtual machine. Should that be the case, is there another way to select the currently active slide without using ActiveWindow?
EDIT: I am trying to apply this to the following code in PowerPoint. Basically what I want to do is replace the line "oSl = ActivePresentation.SlideS(1)" with a line of code that won't always target slide 1 but whichever slide is currently active. My question is not so much HOW to do this - there are plenty of instructions on how to do it online. My question is WHY these approaches are not working for me.
Sub SelectionMacro()
Dim oSl As Slide
Dim oSh As Shape
Dim aArrayOfShapes() As Variant
Dim ShapeX As Shape
Dim N As Long
Dim Temp As Variant
Dim J As Long
Dim FadeEffect As Effect
Set oSl = ActivePresentation.SlideS(1)
'This section creates an array of all pictures on Slide1 called
'"aArrayOfShapes"
For Each oSh In oSl.Shapes
If oSh.Type = msoPicture Then
On Error Resume Next
Debug.Print UBound(aArrayOfShapes)
If Err.Number = 0 Then
ReDim Preserve aArrayOfShapes(1 To UBound(aArrayOfShapes) + 1)
Else
ReDim Preserve aArrayOfShapes(1 To 1)
End If
Set aArrayOfShapes(UBound(aArrayOfShapes)) = oSh
End If
Next
'This section creates a random index number within the bounds of the
'length of aArrayOfShapes and assigns the shape with that index number
'to the Shape object ShapeX
Randomize
NumberX = Int((UBound(aArrayOfShapes) - (LBound(aArrayOfShapes) - 1)) * Rnd) + LBound(aArrayOfShapes)
Set ShapeX = aArrayOfShapes(NumberX)
'This section shuffles aArrayOfShapes
For N = LBound(aArrayOfShapes) To UBound(aArrayOfShapes)
J = CLng(((UBound(aArrayOfShapes) - N) * Rnd) + N)
If N <> J Then
Set Temp = aArrayOfShapes(N)
Set aArrayOfShapes(N) = aArrayOfShapes(J)
Set aArrayOfShapes(J) = Temp
End If
Next N
'This section loops through all Shapes in aArrayOfShapes and
'fades them out one by one EXCEPT for ShapeX
For Each Shape In aArrayOfShapes
If ShapeX.Name <> Shape.Name Then
Set FadeEffect = oSl.TimeLine.MainSequence.AddEffect _
(Shape:=Shape, effectid:=msoAnimEffectFade, trigger:=msoAnimTriggerAfterPrevious)
With FadeEffect
.Timing.Duration = 0.5
.Exit = msoTrue
End With
End If
Next Shape
End Sub
I had similar problem.
Try to replace:
ActiveWindow.View.Slide.SlideNumber
with:
ActivePresentation.SlideShowWindow.View.Slide.SlideIndex
It was what I needed in my project, maybe it helps you.