在PowerPoint中通过占位符名称处理形状

10

我正在尝试创建一个函数,根据CustomLayout.Shapes.Placeholder对象分配的已知名称属性返回特定形状。但是,我无法使用.Name属性,因为这在预先不知道时无法使用,即使从模板/布局创建幻灯片。

挑战似乎在于自定义布局与实际幻灯片的关系。例如,当我迭代幻灯片的.CustomLayout.Shapes.Placeholders时,我可以通过它的.Name属性轻松地识别特定的占位符。

然而,如果我返回this形状,它将成为自定义布局占位符,这会影响此布局上的所有幻灯片(例如,如果我向这个占位符添加文本,它会更新使用此布局的所有幻灯片!)。显然,这是不可取的!

相反,如果我从幻灯片的.Shapes.Placeholders中索引集合,并尝试返回该索引位置处的形状,则似乎它们未维护相同的索引,即 .Shapes.Placeholders(i) <> .CustomLayout.Shapes.Placholders(i)

尝试的解决方法:

我认为我可以操纵自定义布局以向形状添加Tag。我尝试了一下,但由于相同的原因失败了(即,CustomLayout.Shape与Slide.Shape不是“同一个”形状...)。无论如何,如果存在这样的事情,我希望避免“解决方法”,而更愿意采用更适当的方法。

到目前为止,这就是我拥有的函数:

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

1
如果您认为幻灯片上的占位符形状副本不同,您可能需要检查幻灯片和占位符中的两个形状的“名称”、“ID”、“类型”和“AutoShapeType”是否相同。我进行了初步测试,如果幻灯片上的形状确实来自占位符,则所有4个都匹配。不确定您的情况是否相同。 - PatricK
@PatrickK 我不担心副本问题,我正在使用包含多个CustomLayouts的模板构建幻灯片。任何幻灯片上唯一的形状都在SlideMaster.CustomLayout中定义。似乎无法通过名称(因为PPT分配任何它想要的名称)或占位符名称(因为修改这个会影响使用该布局的所有幻灯片)来识别形状,除非我漏掉了什么。 - David Zemens
3
我对PPT的对象模型不是很熟悉,但是对于这个问题提得好和回答得好,我点赞。 - user2140173
3个回答

12

我有一个可能的解决方案。

问题出在幻灯片母板上的页脚、页码和日期占位符。它们包含在幻灯片母板的占位符集合中,但当创建单个幻灯片时,它们成为幻灯片的自己属性(在 .HeaderFooter 属性下)。这导致了在幻灯片母板和幻灯片上占位符数量不同,并且由于这些占位符可以在集合中间位置,索引不对齐。

所以,一种可能的解决方法是从您的母板中删除这三个占位符,方法是打开幻灯片母板并取消页脚复选框。如果这样做,您会发现幻灯片母板和幻灯片上的占位符数量相同,并且所有索引号都对齐。但是,仍然不能使用 SlideMaster.CustomLayouts(n).Shapes.Placeholders(m).Name 属性来访问幻灯片上的正确占位符。然而,一旦您知道占位符的索引(在我的例子中是 "m"),则应该能够通过 SlideObj.Shapes.PlaceHolders(m) 访问幻灯片上的正确占位符。您可以先迭代 SlideMaster.Shapes.PlaceHolders,并存储索引以备后用。

如果您想要页脚字段,只需将新的文本占位符添加到幻灯片母板中,将它们放在幻灯片底部,然后插入页码、日期或固定文本。

总结:

  1. 取消所有您关心的幻灯片母板上的页脚复选框。不确定是否可以通过程序自动完成此操作。

  2. 针对每个幻灯片母板(自定义布局),迭代 ActivePresentation.SlideMaster.CustomLayout(n).Shapes.Placeholders 并查看 .Name 属性以找到您感兴趣的占位符。将其存储在一个数组中 (将占位符名称用作数组名称,因此如果占位符名称是 "datatable",则应使用 datatable[n]) = 占位符在 CustomLayout/Master 上的索引号。只需执行一次并将其存储在全局变量中。

  3. 如果您想访问幻灯片上的占位符,请使用SM_index = SlideObj.CustomFormat.Index获取该幻灯片的SlideMaster索引。然后,使用SlideObj.Shapes.Placeholders(datatable[SM_index])来访问占位符"datatable"。

如果您所有幻灯片都只有一个SlideMaster,则不需要数组,可以使用简单变量代替。

如果您需要实际的代码,请告诉我--但我认为您不需要。如果这在您的实际项目中有效,请告诉我。


这听起来很有前途...我认为可以使用这些概念来找到一个更简单的解决方案,我需要动手尝试一下,但暂时给你点赞。谢谢! - David Zemens
我做了一些尝试,但最终我不确定这是否真的能解决我的问题;当然,我可以操纵“CustomLayouts”以避免使用“HeadersFooters”,但我认为这比使用形状的一次哈希(我的当前方法)和自定义函数并没有更好。也许它稍微更多才华横溢,但当然这不能防止任何人更改SlideMaster,这会使一切重新变得混乱。令人震惊的是,微软竟然没有提供通过其占位符“链接”或以其他方式标识形状的方法。 - David Zemens
1
我的另一个想法是循环遍历母版上的占位符,并保存您感兴趣的每个形状的左侧和顶部设置。然后,在幻灯片上,您可以比较左/顶部以找到正确的形状。但是,如果有人移动任何内容,这种方法就会失败。所以相当脆弱。最后一个“疯狂”的解决方法是在母版上循环遍历您的形状,并将形状的名称插入到文本框中。然后插入一个新幻灯片,并循环遍历形状,查看文本框的内容。然后删除幻灯片,并循环回来清除文本。它能够工作...但是完全是一种hack方法。 - hpf
那个最后的评论虽然有点糟糕,但说不定能起作用。 - David Zemens
2
我感觉很蠢。我写了“它可以工作”,但实际上没有检查它。因为我能看到那里的文本,所以我无法想象微软会愚蠢到没有在textrange对象中放置文本。显然,微软又一次比我更蠢! - hpf
显示剩余5条评论

3
我的当前解决方法是执行以下操作:
声明一个模块级别的Dictionary对象,它基于幻灯片的CustomLayout和已知索引中每个占位符在Slide.Shapes集合中创建了一种哈希表。 (这是通过一个简单的For / Next迭代在一个丢弃的子例程中获得的)。
由于我正在从模板构建幻灯片,因此我认为这是相对安全和可靠的,但不具有灵活性(与使用POTX模板文件的“易用性”和灵活性的整个要点相反...)。
Dim dictShapes As Object 'Dictionary

那么,基于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

我会这样调用该函数:

Dim shp as Object 'PowerPoint.Shape

Set shp = GetShapeByIndex(shp.Parent, dictShapes("chart RIGHT"))

字典被初始化为这样的方式,我可以传递一个字符串参数,它会返回形状的索引,所有的应该都能正确工作。
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

2

我有另一个解决方法。我遍历幻灯片中的所有形状,并将它们与自定义版式中的某些形状属性进行比较。我采用了宽度、高度和自动形状类型。如果它们完全相同,我就找到了幻灯片中对应的形状。

     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

似乎你至少应该检查sh.Leftsh.Top,因为很容易想象一个幻灯片包含多个相似或相同的形状,在幻灯片上处于不同的位置(箭头、标注、文本框等)。 - David Zemens
我同意。你可以添加许多其他属性来进行比较。但它仍然不完美! - martinitram
没错,@martinitram,它绝对不是完美的,到目前为止,所有的方法都似乎有点“hack”-y。我无法相信PPT没有任何通过其占位符或反之识别形状的方法 ¯_(ツ)_/¯。 - David Zemens

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接