PDA

View Full Version : [SOLVED:] Grouping of shapes in PowerPoint VBA



CuriosityBug
08-15-2019, 07:17 AM
Hello,


I am trying to group multiple shapes in a slide without using selection. Following is snippet I am working on but the problem is, each time I have to select shapes that are to be grouped and then run macro which is similar to manually grouping by using builtin feature.

My idea is to 1. select all ( ctr+ A) and run macro or
2. To detect shapes that needs to be grouped and collect them into range or array and then utilize grouping method.
To detect shapes - A condition that checks for overlapping or touching of shapes and consider them as shapes that are to be grouped.
I am not sure how can this be done. Any thoughts on this is really helpful. : pray2:

code:

Sub Grouping() ActiveWindow.Selection.ShapeRange.Group
' A code which can avoid task of selecting shapes
End Sub

John Wilson
08-16-2019, 02:29 AM
That will be a tricky project!

Here is how to group shapes on a slide that are Blue (RGB(68, 114, 196) which might give you some pointers


Sub Grouper()
Dim rayBlue() As Long
Dim osld As Slide
Dim L As Long
ReDim rayBlue(1 To 1)
Set osld = ActivePresentation.Slides(1)
For L = 1 To osld.Shapes.Count
If osld.Shapes(L).Fill.ForeColor.RGB = RGB(68, 114, 196) Then
rayBlue(UBound(rayBlue)) = L
ReDim Preserve rayBlue(1 To UBound(rayBlue) + 1)
End If
Next L
'Remove last unwanted blank
ReDim Preserve rayBlue(1 To UBound(rayBlue) - 1)
ActivePresentation.Slides(1).Shapes.Range(rayBlue).Group
End Sub

CuriosityBug
08-16-2019, 04:07 AM
That will be a tricky project!

Here is how to group shapes on a slide that are Blue (RGB(68, 114, 196) which might give you some pointers


Sub Grouper()
Dim rayBlue() As Long
Dim osld As Slide
Dim L As Long
ReDim rayBlue(1 To 1)
Set osld = ActivePresentation.Slides(1)
For L = 1 To osld.Shapes.Count
If osld.Shapes(L).Fill.ForeColor.RGB = RGB(68, 114, 196) Then
rayBlue(UBound(rayBlue)) = L
ReDim Preserve rayBlue(1 To UBound(rayBlue) + 1)
End If
Next L
'Remove last unwanted blank
ReDim Preserve rayBlue(1 To UBound(rayBlue) - 1)
ActivePresentation.Slides(1).Shapes.Range(rayBlue).Group
End Sub


Thanks John! This input is so valuable. Yes this is so tricky and a good challenge for me. I will try to replace IF condition to check to boundary condition of shapes (left , top, bottom , right - few calculations) that are touching or overlapping each other and if Boolean is true the shape(some iterations) is inserted into array. Later perform group method on that array. Is this practical and achievable as per your knowledge?

John Wilson
08-16-2019, 04:46 AM
I would say it is possible but tricky. You would have to look at the first shape, find others that fill the criteria and add to the array. Group and look again for another ungrouped shape and repeat until no more suitable matches are found.

After each "run" you would need to clear the array or use a new array.

You clear the array by ReDim raywhatever(1 to 1)

Interesting challenge!

CuriosityBug
08-23-2019, 03:43 AM
Hello,

I found few online resources that can help one approach. I am trying to use overlap shape function instead of intersect function (as PowerPoint doesn't support this function). Please help!
https://www.thespreadsheetguru.com/the-code-vault/group-all-shapes-within-a-selection-of-cells - main sub procedure
https://stackoverflow.com/questions/9003696/how-to-find-out-if-two-textboxes-or-shapes-overlap-using-vba-in-powerpoint-2007 - shape overlap function

CuriosityBug
08-23-2019, 06:49 AM
This is the code I tried to solve this problem. But, not sure how to polish this so that it works perfectly. Could anyone please suggest modifications so that my code works fine. I used overlap function to check boundary conditions form the source :
https://stackoverflow.com/questions/...owerpoint-2007 (https://stackoverflow.com/questions/9003696/how-to-find-out-if-two-textboxes-or-shapes-overlap-using-vba-in-powerpoint-2007) - shape overlap function

Sub Grouping()
Dim V AsLong
Dim oSh1 As Shape
Dim oSh2 As Shape
Dim Shapesarray()As Shape
OnErrorResumeNext
If ActiveWindow.Selection.ShapeRange.Count <2Then
MsgBox "Select at least 2 shapes"
ExitSub
EndIf
ReDim Shapesarray(1To ActiveWindow.Selection.ShapeRange.Count)' maximum
array size = no.of shapes selected, dynamic array
For V =1To ActiveWindow.Selection.ShapeRange.Count
' A condition to check boundary conditions and add shape into array if it is true.

Set oSh1 = ActiveWindow.Selection.ShapeRange(V)
Set oSh2 = ActiveWindow.Selection.ShapeRange(V +1)

If ShapesOverlap(oSh1, oSh2)=TrueThen

' boundary conditions AND shape type is not a connector
' the next shape it is going to add should be atleast nearby the present
shape,if so add into array or group current array anderase contents in
that array
Set Shapesarray(V)= oSh1
Set Shapesarray(V +1)= oSh2
'else move to next shape in selction range and check
EndIf
' group items in array

Range(Shapesarray).Group ' Grouping all the elements of the array
V = V +1
Next V
' at last remaining shapes in shape collection are grouped all together

End Sub

Paul_Hossler
09-16-2019, 06:36 PM
OK, this is the best I could come up with

All I can say is that it works on my test case:

1 stand alone shape
2 overlapping
3 overlapping





Option Explicit


Sub GroupOverlappingShapes()
Dim oPres As Presentation
Dim oSlide As Slide
Dim oShape1 As Shape, oShape2 As Shape
Dim iShape1 As Long, iShape2 As Long
Dim bDone As Boolean
Dim vGroupedShapes() As String
Dim i As Long

Set oPres = ActivePresentation

For Each oSlide In oPres.Slides
With oSlide

bDone = False

Do While Not bDone

bDone = True

For iShape1 = 1 To .Shapes.Count - 1
For iShape2 = iShape1 + 1 To .Shapes.Count

Set oShape1 = .Shapes(iShape1)
Set oShape2 = .Shapes(iShape2)

' Debug.Print "Shape1 = " & oShape1.Name & " -- Shape2 = " & oShape2.Name
' Stop

If Not ShapesOverlap(oShape1, oShape2) Then GoTo NextShape2

If oShape1.Type = msoGroup Then
Erase vGroupedShapes
ReDim vGroupedShapes(1 To oShape1.GroupItems.Count + 1)

For i = 1 To oShape1.GroupItems.Count
vGroupedShapes(i) = oShape1.GroupItems(i).Name
Next i
vGroupedShapes(oShape1.GroupItems.Count + 1) = oShape2.Name

oShape1.Ungroup

oSlide.Shapes.Range(vGroupedShapes).Group

bDone = False
GoTo LoopAgain

Else
oSlide.Shapes.Range(Array(oShape1.Name, oShape2.Name)).Group
bDone = False
GoTo LoopAgain
End If

NextShape2:
Next iShape2
Next iShape1

LoopAgain:
Loop
End With
Next


End Sub


'https://stackoverflow.com/questions/9003696/how-to-find-out-if-two-textboxes-or-shapes-overlap-using-vba-in-powerpoint-2007
Function ShapesOverlap(oSh1 As Shape, oSh2 As Shape) As Boolean
Dim Shp1Left As Single
Dim Shp1Right As Single
Dim Shp1Top As Single
Dim Shp1Bottom As Single


Dim Shp2Left As Single
Dim Shp2Right As Single
Dim Shp2Top As Single
Dim Shp2Bottom As Single


Dim bHorizontalOverlap As Boolean
Dim bVerticalOverlap As Boolean


With oSh1
Shp1Left = .Left
Shp1Right = .Left + .Width
Shp1Top = .Top
Shp1Bottom = .Top + .Height
End With


With oSh2
Shp2Left = .Left
Shp2Right = .Left + .Width
Shp2Top = .Top
Shp2Bottom = .Top + .Height
End With


' do they overlap horizontally?
If Shp1Left > Shp2Left Then
If Shp1Left < Shp2Right Then
bHorizontalOverlap = True
End If
End If
If Shp1Left < Shp2Left Then
If Shp1Right > Shp2Left Then
bHorizontalOverlap = True
End If
End If


' do they overlap vertically?
If Shp1Top > Shp2Top Then
If Shp1Top < Shp2Bottom Then
bVerticalOverlap = True
End If
End If
' do they overlap vertically?
If Shp1Top < Shp2Top Then
If Shp1Bottom > Shp2Top Then
bVerticalOverlap = True
End If
End If


ShapesOverlap = bHorizontalOverlap And bVerticalOverlap


End Function

Paul_Hossler
09-17-2019, 09:23 AM
Added some refinement to first version




Option Explicit


Sub GroupOverlappingShapes()
Dim oPres As Presentation
Dim oSlide As Slide
Dim oShape1 As Shape, oShape2 As Shape
Dim iShape1 As Long, iShape2 As Long
Dim bDone As Boolean
Dim vGroupedShapes() As String
Dim i As Long

Set oPres = ActivePresentation

For Each oSlide In oPres.Slides
With oSlide

'Ungroup any shapes
bDone = False

Do While Not bDone

bDone = True

For iShape1 = 1 To .Shapes.Count
Set oShape1 = .Shapes(iShape1)

If oShape1.Type = msoGroup Then
oShape1.Ungroup
bDone = False
GoTo Loopagain2
End If
Next iShape1
Loopagain2:
Loop


'now group overlapping shapes
bDone = False

Do While Not bDone

bDone = True

For iShape1 = 1 To .Shapes.Count - 1
For iShape2 = iShape1 + 1 To .Shapes.Count

Set oShape1 = .Shapes(iShape1)
Set oShape2 = .Shapes(iShape2)

If Not ShapesOverlap(oShape1, oShape2) Then GoTo NextShape2

If oShape1.Type = msoGroup Then
Erase vGroupedShapes
ReDim vGroupedShapes(1 To oShape1.GroupItems.Count + 1)

For i = 1 To oShape1.GroupItems.Count
vGroupedShapes(i) = oShape1.GroupItems(i).Name
Next i
vGroupedShapes(oShape1.GroupItems.Count + 1) = oShape2.Name

oShape1.Ungroup

oSlide.Shapes.Range(vGroupedShapes).Group

bDone = False
GoTo LoopAgain

Else
Erase vGroupedShapes
ReDim vGroupedShapes(1 To 2)

vGroupedShapes(1) = oShape1.Name
vGroupedShapes(2) = oShape2.Name

oSlide.Shapes.Range(vGroupedShapes).Group
bDone = False
GoTo LoopAgain
End If

NextShape2:
Next iShape2
Next iShape1

LoopAgain:
Loop
End With
Next


End Sub





and




Option Explicit


Type Dimensions
Left As Single
Right As Single
Top As Single
Bottom As Single
Height As Single
Width As Single
End Type


'https://stackoverflow.com/questions/9003696/how-to-find-out-if-two-textboxes-or-shapes-overlap-using-vba-in-powerpoint-2007
Function ShapesOverlap(oSh1 As Shape, oSh2 As Shape) As Boolean
Dim S1 As Dimensions, S2 As Dimensions
Dim bHorizontalOverlap As Boolean, bVerticalOverlap As Boolean


S1 = GetDimensions(oSh1)
S2 = GetDimensions(oSh2)

' do they overlap horizontally?
If S1.Left > S2.Left Then
If S1.Left < S2.Right Then bHorizontalOverlap = True
ElseIf S1.Left < S2.Left Then
If S1.Right > S2.Left Then bHorizontalOverlap = True
End If


' do they overlap vertically?
If S1.Top > S2.Top Then
If S1.Top < S2.Bottom Then bVerticalOverlap = True
ElseIf S1.Top < S2.Top Then
If S1.Bottom > S2.Top Then bVerticalOverlap = True
End If


ShapesOverlap = (bHorizontalOverlap And bVerticalOverlap)


End Function


Private Function GetDimensions(oShape As Shape) As Dimensions
With GetDimensions
.Left = oShape.Left
.Right = oShape.Left + oShape.Width
.Top = oShape.Top
.Bottom = oShape.Top + oShape.Height
.Height = oShape.Height
.Width = oShape.Width
End With
End Function





I'm still trying to decide how to handle the overlap test if (for ex) .Top1 = .Top2

I think I want to do something like


If S1.Left => S2.Left Then

CuriosityBug
09-17-2019, 12:02 PM
Attaching the sample presentation I am trying and error I am getting.

Paul_Hossler
09-17-2019, 12:18 PM
Actually, I thought you'll only get that message if there are PlaceHolders, and you didn't seem to have any

However, PP does allow shapes to have the same names, and I think that's what's going on here

25100

Let me play a bit

I think I'll add a serial number suffix to each shape and then group

CuriosityBug
09-17-2019, 12:29 PM
Sure, thanks Paul. I guess rename function might help us to set unique values to shapes.

Paul_Hossler
09-17-2019, 01:08 PM
Try this version

The weak spot is the Connectors, since they sort of are in multiple groups

CuriosityBug
09-17-2019, 02:35 PM
Try this version

The weak spot is the Connectors, since they sort of are in multiple groups Thanks so much Paul. Can this be applied for text boxes or pictures that are piled up on shapes or is there any restrictions (like, what pp doesn't support regarding connectors).

Paul_Hossler
09-17-2019, 04:32 PM
John Wilson is the PP expert here

He might know, but try different types of shapes and report back

Paul_Hossler
09-17-2019, 07:01 PM
I moved some logic into a separate function, and specified the types of shapes that I considered Group-able

It includes Textboxes and Pictures, but not Connectors. Those you can do manually






Private Function pvtGroupAble(shp As Shape) As Boolean
pvtGroupAble = False

Select Case shp.Type
Case msoAutoShape
If shp.AutoShapeType <> msoShapeMixed Then pvtGroupAble = True
Case msoGroup, msoTextBox, msoPicture
pvtGroupAble = True
End Select
End Function

CuriosityBug
09-18-2019, 07:47 AM
You are genius Paul! Thanks for your patience and effort. This solved my problem 99%. I am attaching ppt with two issues(slide 3 and 4), I am facing (not being skeptical):). Just if you can input any thoughts if possible. thanks again Paul.

I moved some logic into a separate function, and specified the types of shapes that I considered Group-able

It includes Textboxes and Pictures, but not Connectors. Those you can do manually






Private Function pvtGroupAble(shp As Shape) As Boolean
pvtGroupAble = False

Select Case shp.Type
Case msoAutoShape
If shp.AutoShapeType <> msoShapeMixed Then pvtGroupAble = True
Case msoGroup, msoTextBox, msoPicture
pvtGroupAble = True
End Select
End Function

Paul_Hossler
09-18-2019, 11:01 AM
The 2 boxes don't overlap - they just touch.

I added something for that



'do they touch
If S1.Bottom = S2.Top Or S1.Top = S2.Bottom Or S1.Left = S2.Right Or S1.Right = S2.Left Then
ShapesOverlap = True
Exit Function
End If





I can't make the connectors hidden

If the background rectangle is there before running the macro, everything is grouped under it

25108

If you run the macro first without it, the overlapping/touching shapes are grouped. Adding the large rectangle and then "Send to Back" still shows the connectors

25109

Paul_Hossler
09-18-2019, 05:34 PM
Try changing the line to




'do they touch
If Abs(S1.Bottom - S2.Top) <= 0.5 Or Abs(S1.Top - S2.Bottom) <= 0.5 Or Abs(S1.Left - S2.Right) <= 0.5 Or Abs(S1.Right - S2.Left) <= 0.5 Then
ShapesOverlap = True
Exit Function
End If



to define 'touching' as 'close enough' :)

CuriosityBug
09-19-2019, 06:45 AM
Try changing the line to




'do they touch
If Abs(S1.Bottom - S2.Top) <= 0.5 Or Abs(S1.Top - S2.Bottom) <= 0.5 Or Abs(S1.Left - S2.Right) <= 0.5 Or Abs(S1.Right - S2.Left) <= 0.5 Then
ShapesOverlap = True
Exit Function
End If



to define 'touching' as 'close enough' :) It is working differently. Following is happening:
For example if group 1, group 2 are sets that already exist, these are grouped as group 3 but shapes in group 1, group 2 are regrouped now.

CuriosityBug
09-19-2019, 07:05 AM
If shapes are this close (as in picture) they are not grouped, where as if either of dimensions touch they are grouped.

Paul_Hossler
09-19-2019, 07:27 AM
Hello Paul, I am including something like:

If S1.bottom falls anywhere between (S2.Top and S2.Top + 0.5). Because the shapes seems like touching but they are little away but close enough (creating an illusion). is there any effective way to check for this, can using of multiple operators cause problem?



I used .5 as a tolerance from your PM, but you might have to adjust it to establish your own 'close enough'

What is shape1.top + shape1.height?

what is shape2.top?

What is the difference?

Attach a sample PP if you want


Edit

ver 3 has close shapes and a msgbox macro to show measurements

ver 4 has different tolerances to define close (i.e 2 instead of .5)

I don't see the behavior in your #19, so attach an example and I'll look

CuriosityBug
09-19-2019, 08:25 AM
I used .5 as a tolerance from your PM, but you might have to adjust it to establish your own 'close enough'

What is shape1.top + shape1.height?

what is shape2.top?

What is the difference?

Attach a sample PP if you want


Edit

ver 3 has close shapes and a msgbox macro to show measurements

ver 4 has different tolerances to define close (i.e 2 instead of .5)

I don't see the behavior in your #19, so attach an example and I'll look

Attaching the sample Paul. Thank you.

Paul_Hossler
09-19-2019, 10:10 AM
I had to change my definition of 'close' to determine if 2 shapes overlapped. I assumed that each shape was +2 up, left, down and right and then checked against these slightly larger shapes

Seem to work better in your case

Tolerance =2 but you can change that

CuriosityBug
09-19-2019, 10:56 AM
Ingenious Paul!! I thought this is Impossible but you people made it easier to solve. Thanks a lot:bow:. This is new and I will learn it. I am just curious what is 'test' for in pp you attached?



I had to change my definition of 'close' to determine if 2 shapes overlapped. I assumed that each shape was +2 up, left, down and right and then checked against these slightly larger shapes

Seem to work better in your case

Tolerance =2 but you can change that

Paul_Hossler
09-19-2019, 12:21 PM
'Test' is a throw-away macro I wrote when I was investigating why some shapes would group when I didn't expect them to

You can delete the entire standard module



Option Explicit


Sub test()
Dim s As String
Dim S1 As Dimensions, S2 As Dimensions


With ActivePresentation.Slides(1)
S1 = GetDimensions(.Shapes("myShape1"))
S2 = GetDimensions(.Shapes("myShape2"))

s = "S1 Top = " & S1.Top & vbCrLf & "s1.Height = " & S1.Height & vbCrLf & "S2 Top = " & S2.Top & vbCrLf
s = s & "Gap = " & (S1.Top + S1.Height - S2.Top)


MsgBox s
End With


End Sub

CuriosityBug
09-19-2019, 12:33 PM
Perfect! Thank you.

'Test' is a throw-away macro I wrote when I was investigating why some shapes would group when I didn't expect them to

You can delete the entire standard module



Option Explicit


Sub test()
Dim s As String
Dim S1 As Dimensions, S2 As Dimensions


With ActivePresentation.Slides(1)
S1 = GetDimensions(.Shapes("myShape1"))
S2 = GetDimensions(.Shapes("myShape2"))

s = "S1 Top = " & S1.Top & vbCrLf & "s1.Height = " & S1.Height & vbCrLf & "S2 Top = " & S2.Top & vbCrLf
s = s & "Gap = " & (S1.Top + S1.Height - S2.Top)


MsgBox s
End With


End Sub

Paul_Hossler
09-19-2019, 01:03 PM
Here's a more cleaned up 'production' ready version

This was a fun little project

CuriosityBug
09-19-2019, 01:11 PM
Sure! Thank Paul. you are a Gem!! A light in dark tunnel.
Here's a more cleaned up 'production' ready version

This was a fun little project

CuriosityBug
09-19-2019, 01:13 PM
Thanks John and other contributors for helping people for no reason. Thanks VBA express team!