Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handling a Shape by its Placeholder Name in PowerPoint

Tags:

powerpoint

vba

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
like image 595
David Zemens Avatar asked Mar 09 '15 18:03

David Zemens


2 Answers

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:

  1. Uncheck the Footers checkbox on all slide masters that you care about. Not sure if this can be done programatically.

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

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

like image 72
hpf Avatar answered Oct 27 '22 21:10

hpf


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
like image 34
David Zemens Avatar answered Oct 27 '22 21:10

David Zemens