2

I'm in the process of automating the production of a PowerPoint report from and Excel spreadsheet. I've got the process working up until I paste a table.

I'm pasting the table to PowerPoint using PPApp.CommandBars.ExecuteMso ("PasteSourceFormatting") and the table appears as a shape on my slide (the third shape).

To refer to the new shape I was using Set pShape = Slide2.Shapes(Slide2.Shapes.Count) but now now when I paste, the pShape is assigned "Shape 2" (not "Shape 3"). Is there something that needs to be done between the pasting and the assignment of the object?

Code below, commented where the issue occurs. (Full code removed; viewable here)

'Copy tables from Excel
Set rng = ws.Range("A:A")
rng.ColumnWidth = 22.75
Set rng = ws.Range("A4:C27")

'Copy the table range
Application.CutCopyMode = False
rng.Copy
Application.Wait (Now + TimeValue("0:00:02"))

'The issue occurs here!!! '-------------------------------------
'Paste the table in to the slide
Slide2.Select
PPApp.CommandBars.ExecuteMso ("PasteSourceFormatting")

'Name the new shape object
Set pShape = Slide2.Shapes(Slide2.Shapes.Count)
pShape.Name = "Slide_2_Table_1"
pShape.LockAspectRatio = False
ashleedawg
  • 20,365
  • 9
  • 72
  • 105
Satkin2
  • 201
  • 4
  • 11
  • The *short answer* is to see the section `Retrieve "Last Shape Created"` in the [answer below](https://stackoverflow.com/a/51170453/8112776) and instead of using `.Count`, use the `idxLastShape`function to retrieve the number of the most recently created shape. I would also suggested you use the proper VBA copy and [`PasteSpecial`](https://msdn.microsoft.com/vba/powerpoint-vba/articles/shapes-pastespecial-method-powerpoint) methods rather than the `CommandBars` methods. See the links at bottom of post.) – ashleedawg Jul 04 '18 at 12:07
  • were you able to get it figured out? – ashleedawg Jul 06 '18 at 01:46
  • Hi. I've been out of the office for the past couple of days, so haven't had a chance to look in to this until now. Reading through it's making sense, just need to try and implement it. – Satkin2 Jul 06 '18 at 09:16

1 Answers1

5

'Shapes.Count' ≠ Shape Index# !

The .Count is not the same as the upper limit of current shape .Index numbers.

The numbering system is easier understood by listing all the shapes within the document:

Sub ListShapes()
    'hit CTRL+G to view output in Immediate Window
    Dim sh As Shape, sld As Slide, idx As Long
    Set sld = ActivePresentation.Slides(1) '<-- change to your slide number
    For Each sh In sld.Shapes
        idx = idx + 1
        Debug.Print "Shape ID#" & sh.Id, "Index #" & idx, "Name: " & sh.Name
    Next sh
    Debug.Print "Count of shapes: " & sld.Shapes.Count
End Sub

NOTE: There is alternative code for Excel at the bottom of this post!

To demonstrate, we can add shapes to a new document:

  • First, add one rectangle manually by clicking Insert (on the ribbon)
  • [If using Excel, click Illustrations], then Shapes, and the rectangle symbol. <code>▯</code>
  • Draw the shape, then hit Ctrl+C to copy it, and hit Ctrl+C four times to paste 4 copies.
  • Run the above procedure, and the output will be:

    Shape ID#2 Index #1 Name: Rectangle 1
    Shape ID#3 Index #2 Name: Rectangle 2
    Shape ID#4 Index #3 Name: Rectangle 3
    Shape ID#5 Index #4 Name: Rectangle 4
    Shape ID#6 Index #5 Name: Rectangle 5
    Count of shapes: 5         

Note that the Index is not a property of this object, but it counted in order that Excel's storing the shapes in memory (same as the order returned by the For Each..Next statement.

  • You can prove this by running:

    Debug.Print ActivePresentation.Slides(1).Shapes(5).Name  
    

    ...which in this case return Rectangle 5.

Another way to understand how Excel is storing the shapes is with the Watch Window. Add a breakline or Stop in the middle of the loop, then highlight ws.Shapes, right-click it, choose Add Watch... and click OK. Browse through the tree to discover the varies properties/attributes of the shapes within the document.


  • Next, if we delete the "middle rectangle" and run the above procedure again, we will get:

    Shape ID#2 Index #1 Name: Rectangle 1
    Shape ID#3 Index #2 Name: Rectangle 2
    Shape ID#5 Index #3 Name: Rectangle 4
    Shape ID#6 Index #4 Name: Rectangle 5
    Count of shapes: 4         

The ID and Name of remaining shapes do not change, but the Index is renumbered to reflect the new "order".

...thus to return the name Rectangle 5 we now need to use:

Debug.Print ActivePresentation.Slides(1).Shapes(4).Name  

Referring to shapes (including controls)

When you refer to a shape by number, like .Shapes(), you're referring to the shape Index Number , not the ID number. Index numbers are dynamically assigned as needed as therefore are not a stable method to refer to a shape.

  • Therefore, .Count is irrelevant to the shape Index number.

Ideally, you should refer to the shape by the .Name or .ID number. If generating shapes dynamically, you'd ideally store a list of shapes in an array or collection, so you can look at the list as required.


Retrieve "Last Shape Created"

If the only reason for using the Index Number is to retrieve the "last shape created", then you could use a function like this to get the index number:

Function idxLastShape(slideNum As Long) As Long
    Dim sh As Shape
    For Each sh In ActivePresentation.Slides(slideNum).Shapes
        idxLastShape = idxLastShape + 1
    Next sh
End Function

Example Usage:

Debug.Print idxLastShape(1) 'Returns index of last shape on slide#1

NOTE: There is alternate code for Excel at the bottom of this post!


Alternatively, you could have the function return a reference to the actual shape object, rather than the number, like this:

Function LastShape(slideNum As Long) As Shape
    Dim sh As Shape
    For Each sh In ActivePresentation.Slides(slideNum).Shapes
        Set LastShape = sh
    Next sh
End Function

...so you could get the name of the "last shape" with:

Debug.Print LastShape(1).Name

Delete the most recently created shape

Using the function above, you can use any methods you would normally use with shapes. For example, you can delete the "last shape" that was created on Slide #1:

LastShape(1).Delete

Caution CAUTION!

The examples in the post (including the deletion example!) are indiscriminate of what type of shape they're returning/editing/deleting!

There are dozens of types of shapes, from graphics to sound/video and controls. You can filter the shapes being enumerated by these procedures using the .Type property of the Shape object, as well as other methods. There is a partial list here, and more information in the links below.


Alternative code for Excel:

List all shapes on worksheet (Excel)

Sub ListShapes()
    'hit CTRL+G to view output in Immediate Window
    Dim sh As Shape, ws As Worksheet, idx As Long
    Set ws = Sheets("Sheet1") '<-- change to your worksheet name
    For Each sh In ws.Shapes
        idx = idx + 1
        Debug.Print "Shape ID#" & sh.ID, "Index #" & idx, "Name: " & sh.Name
    Next sh
    Debug.Print "Count of shapes: " & Sheets("Sheet1").Shapes.Count
End Sub

Return index number of "last shape" (Excel)

Function idxLastShape(shtName As String) As Long
    Dim sh As Shape
    For Each sh In Sheets(shtName).Shapes
        idxLastShape = idxLastShape + 1
    Next sh
End Function

Example Usage: Debug.Print idxLastShape("Sheet1")


Return reference to "last shape" object (Excel)

Function LastShape(shtName As String) As Shape
    Dim sh As Shape
    For Each sh In Sheets(shtName).Shapes
        Set LastShape = sh
    Next sh
End Function

Example Usage: Debug.Print LastShape("Sheet1").Name


More Information:

Other ways to copy from Excel to Powerpoint:

ashleedawg
  • 20,365
  • 9
  • 72
  • 105
  • Fantastic, that's a really thorough and useful answer, thank you. – Satkin2 Jul 06 '18 at 09:39
  • I'm now just struggling to get my Excel VBA to talk to the PowerPoint correctly in the function. Where it's `ActivePresentation.Slides(slideNum).shapes` it returns _italic_ Run-time error '429': ActiveX component can't be created object. _italic_ . I thought this would be my PPPres variable instead of ActivePresentation, but that in turn returned _italic_ Run-time error '13': Type mismatch _italic_ So I've now got to work out that issue to get it to work. I'm basically learning as I go along with this project. Thanks for your help! – Satkin2 Jul 06 '18 at 09:46
  • I've worked it out. Instead of `Dim shp as Shape` I should have used `Dim shp as PowerPoint.shape` – Satkin2 Jul 06 '18 at 11:15
  • are you doing this from an application other than PowerPoint? – ashleedawg Jul 06 '18 at 15:18
  • Sorry, yes, I'm running from Excel. All working now, thanks for your help. – Satkin2 Jul 10 '18 at 08:32
  • I do not agree with your findings and solutions. I found the reality is that the ExecuteMso is not blocking, so when Count, the ExecuteMso has not been finished thus returns the old number. If you add some additional commands that either somewhat implicitly force or explicitly delay CPU logics, such as the above answes's additional small function for counting, then it will be normal. – mendel Apr 07 '22 at 05:25
  • According to CommandBars.ExecuteMso Method: The Microsoft Office Assistant has been deprecated in the 2007 release of the Microsoft Office system. This method is useful in cases where there is no object model for a particular command.Works on controls that are built-in buttons, toggleButtons and splitButtons. On failure it returns E_InvalidArg for an invalid IdMso, and E_Fail for controls that are not enabled or not visible. This is perhaps why it is not blocking, because it cannot predict anything the command it is. – mendel Apr 07 '22 at 05:31
  • So the final culprit is that Office does not provide equivalent of PasteSourceFormatting in Shapes.PasteSpecial arguments. – mendel Apr 07 '22 at 05:33
  • A simple delay solves, although not elegant. You can find another case in https://www.mrexcel.com/board/threads/excel-to-powerpoint-with-source-format.1027196/ – mendel Apr 07 '22 at 05:34
  • while aSlide.Shapes.Count~=nShapesOld;pause(0.1);continue;end – mendel Apr 07 '22 at 05:41