Reputation: 53663
I am attempting to create a function that will return a specific shape, based on the known Name property assigned to the CustomLayout.Shapes.Placeholder
object. I can't use the shape .Name
because this is not known in advance, even when creating slides from template/layout.
The challenge seems to be how the custom layout is related to the actual slide. For instance, when I iterate the slide's .CustomLayout.Shapes.Placeholders
, I can easily identify the particular placeholder by it's .Name
property.
HOWEVER if I return this shape, it will be the custom layout placeholder, which affects ALL slides on this layout (e.g., if I add text to this placeholder, it updates all slides using this layout!). Obviously this is undesirable!
If instead, I index the collection, and attempt to return the shape at that index position, from the slide's .Shapes.Placeholders
, it appears that they are not maintaining the same index, i.e., .Shapes.Placeholders(i) <> .CustomLayout.Shapes.Placholders(i)
Attempted workaround:
Thought I might be able to manipulate the custom layout to add a Tag
to the shapes. I tried, and it fails for the same reasons (i.e., the CustomLayout.Shape is somehow not the "same" shape as the Slide.Shape...). In any case, I'm hoping to avoid a "workaround" in favor of a more proper way to do this, if such a thing exists.
This is the function I have so far:
Function GetShapeByPlaceholderName(sName As String, sld As Slide) As Object
Dim plchldrs As Placeholders
Dim shp As Shape
Dim ret As Shape
Dim i As Long
For Each shp In sld.CustomLayout.Shapes.Placeholders
i = i + 1
If shp.Name = sName Then
'####
' This can easily identify the CustomLayout.Shapes.PLACEHOLDER
'
' But I need to return the SHAPE in the Slide.Shapes collection
'####
'###
Set ret = shp 'This will return the CustomLayout.Placeholder, which affects ALL slides
'###
'Set ret = sld.Shapes.Placeholders(i) 'the index of the Shapes.Placeholders is NOT the same
'###
'Set ret = sld.Shapes.Placeholders.FindByName(sName) 'This returns an error/specified shape name does not exist
'###
'Set ret = sld.Shapes.Placeholders.FindByName(i) 'This observes same failure that the index of the collections is not the same
Exit For
End If
Next
Set GetShapeByPlaceholderName = ret
End Function
Upvotes: 10
Views: 7091
Reputation: 31
I have another workaround. I iterate trough all the shapes in the slide and compare them with some shape properties of the shape in the Custom Layout. I took width, height and autoshapetype. If they are exactly the same I have found the corresponding shape in the slide.
For Each sh In sl.Shapes
With sl.CustomLayout.Shapes("Name of shape in layout")
If sh.Width = .Width And _
sh.Height = .Height And _
sh.AutoShapeType = .AutoShapeType Then
bFound = True
Exit For
End If
End With
Next sh
If bFound Then
'sh is the shape you are looking for
End If
Upvotes: 2
Reputation: 428
I have a potential solution for you.
The problem is the footer, page number, and date placeholders on the slide master. They are included in the placeholder collection on the slide master, but when an individual slide is created they become their own properties of the slide (under the .HeaderFooter
property). This results in a different count of placeholders on the Master and on the slides, and because these placeholders can be in the middle of the collection, the indexes don't align.
So, one possible solution is to remove these three placeholders from your Master, which is done by opening the Slide Master and unchecking the footers checkbox. If you do this, you'll find that the number of placeholders on the Master and on the Slides are the same, and all of the index numbers line up. You still can't use the SlideMaster.CustomLayouts(n).Shapes.Placeholders(m).Name
property to access the correct placeholder on the actual slide. However, once you know the index of the placeholder ("m" in my example in the last sentence), you should be able to access the correct placeholder on the slide via SlideObj.Shapes.PlaceHolders(m)
. You could iterate through your SlideMaster.Shapes.PlaceHolders first and store the index for later use.
If you want footer fields, simply add new Text placeholders to your slide master, put them at the bottom of the slide, and then insert the page number, date, or fixed text into them.
Summary:
Uncheck the Footers checkbox on all slide masters that you care about. Not sure if this can be done programatically.
Iterate through ActivePresentation.SlideMaster.CustomLayout(n).Shapes.Placeholders
for each of your Slide Masters (Custom Layouts) looking at the .Name property to find the placeholder(s) you are interested in. Store that in an array (would use name of the placeholder as the array name, so if the placeholder name was "datatable" I would use datatable[n])=index # of the placeholder on the CustomLayout/Master. Do this once and store it in a global variable.
When you want to access the placeholder on a slide, get the SlideMaster index for the slide with SM_index=SlideObj.CustomFormat.Index. Then access the placeholder "datatable" using SlideObj.Shapes.Placeholders(datatable[SM_index])
If you only have a single SlideMaster for all of your slides then you don't need an array and can use a simple variable instead.
If you need actual code, let me know -- but I expect you don't. Let me know if this works in your real world project.
Upvotes: 12
Reputation: 53663
My current workaround is to do the following:
Delcare a module-level Dictionary
object, which creates a sort of hash table based on the slide's CustomLayout
and the known index of each placeholder within the Slide.Shapes
collection. (This I obtain through a simple FOr/Next iteration in a throwaway subroutine).
Since I am building slides from template, I think this is relatively safe and reliable, but not flexible (the whole point of working with POTX template files should be ease of use and flexibility...).
Dim dictShapes As Object 'Dictionary
Then a procedure to establish it based on CustomLayout
Sub SetShapeDict(cLayout as Object)
Set dictShapes = CreateObject("Scripting.Dictionary")
Select Case cLayout.Name
Case "layout_one"
dictShapes("chart RIGHT") = 1
dictShapes("chart RIGHT title") = 2
dictShapes("chart LEFT") = 5
dictShapes("chart LEFT title") = 6
Case "layout_two"
dictShapes("chart RIGHT") = 1
dictShapes("chart RIGHT title") = 2
dictShapes("q text") = 4
dictShapes("source text") = 5
End Select
End Sub
I call this function like:
Dim shp as Object 'PowerPoint.Shape
Set shp = GetShapeByIndex(shp.Parent, dictShapes("chart RIGHT"))
The dictionary is initialized in such a manner that I can pass a string argument and it will return the index of the shape, which all should work.
Function GetShapeByIndex(chartSlide As Object, i As Long) As Object
Dim ret
Dim s As Long
'if slide #1, there is no "Slide Number Placeholder"
' this placeholder appears in the shapes' 3rd index for
' both Vertical Master no Background AND Horizontal Master
If chartSlide.SlideNumber = 1 Then
If i > 2 Then
s = i - 1
Else
s = i
End If
Else
s = i
End If
On Error Resume Next
Set ret = chartSlide.Shapes(s)
If Err.Number <> 0 Then Set ret = Nothing
On Error GoTo 0
Set GetShapeByIndex = ret
End Function
Upvotes: 3