Home » excel » vba – check whether a shape is a group (GroupItems raises an error)

vba – check whether a shape is a group (GroupItems raises an error)

Posted by: admin May 14, 2020 Leave a comment

Questions:

I was trying the following code in Word:

Sub MyMacro()

Dim sh1 As Shape

For Each sh1 In ActiveDocument.Shapes
 If sh1.GroupItems.Count > 0 Then
  Debug.Print sh1.Name + " is a group!"
  Else: Debug.Print sh1.Name + " is not a group!"
 End If
Next

End Sub

For the actual grouped shape it works, but when the shape is not a group, I get an error:

Run-time error ‘-2147024891 (80070005)’: This member can only be
accessed for a group

How can I check if the object is a group, besides using On Error?

How to&Answers:

There are a number of ways you can go about this in Word. The first two would proabably also work for Excel, but the third is only available in Word.

  1. Use the code you have with On Error Resume Next and check for Err.Number. If it’s 0 you have a group, if not then you don’t.

    Sub CheckIfGroup()
    Dim shp As word.Shape
    Dim bIsGroup As Boolean

    Set shp = Selection.ShapeRange(1)
    
    On Error Resume Next
    Debug.Print shp.GroupItems.Count
    Select Case Err.Number
        Case 0
            bIsGroup = True
        Case -2147024891
            bIsGroup = False
        Case Else
            bIsGroup = False
    End Select
    On Error GoTo 0
    
    Debug.Print bIsGroup
    

    End Sub

Possibly, there might be other errors, although none occur to me at the moment.

  1. Another possibility would be to check the Name property, assuming it hasn’t been changed by any code. By default, it will be something like “Group 3”, so

    Sub CheckIfGroup()
    Dim shp As word.Shape
    Dim bIsGroup As Boolean

    Set shp = Selection.ShapeRange(1)
    
    If InStr(shp.NAME, "Group") <> 0 Then
        bIsGroup = True
    Else
        bIsGroup = False
    End If
    Debug.Print bIsGroup
    

    End Sub

  2. Check the WordOpenXML whether it contains the element tag <wpg:wgp> (stands for wordProcessingGroup, see the Open XML SDK documentation). You can’t get the WordOpenXML for a Shape, you need to query it for the Shape.Anchor.Paragraphs(1).Range – the Range in the Word document with which the Shape is associated.

    Sub CheckIfGroup()
    Dim shp As word.Shape
    Dim bIsGroup As Boolean
    Dim rng As word.Range

    Set shp = Selection.ShapeRange(1)
    Set rng = shp.anchor.Paragraphs(1).Range
    If InStr(rng.WordOpenXML, "<wpg:wgp>") <> 0 Then
        bIsGroup = True
    Else
        bIsGroup = False
    End If
    
    Debug.Print bIsGroup
    

    End SUb

Note that this simple approach can only work if the grouped Shape is the only one anchored to the paragraph. If there are more than one you can still use the WordOpenXML but you’d need to analyze it with XML tools to make sure that the Shape in question is the one that’s a Group.

Answer:

So far I got two very comprehensive answers here, and I’m thankful to their authors. However I would like to avoid On Error catching, did not want to rely on shape’s name and also did not want to make complicated things like building my own dll. Cindy’s solution with InStr(rng.WordOpenXML, "<wpg:wgp>") looked like the most closest, but for some reason that piece of code did not work in my document: <wpg:wgp> was found for any shape, grouped or not.
Therefore I’ve decided to post my own solution which is very simple and seems to work correctly for all cases. We just need to use .AutoShapeType property (what @SMeaden pointed in the comment):

Sub MyMacro()

Dim sh1 As Shape

For Each sh1 In ActiveDocument.Shapes
  If sh1.AutoShapeType = msoShapeMixed Then
  Debug.Print sh1.Name + " is a group!"
  Else: Debug.Print sh1.Name + " is not a group!"
 End If
Next

End Sub

Answer:

Interface Design Error

IMHO, it looks like an interface design error. What the designers should have done is also export a GroupItemsCount method which client code could inspect before attempting to call GroupItems.

Burying Errors

I also dislike using On Error Resume Next (hereafeter OERN) and on every development team try to persuade colleagues to ship a DLL written in .NET (either C# or VB.NET) which buries all these errors thrown by these methods.

Excel’s missing Worksheet Exists method

So in Excel, we have a Worksheets collection for each workbook but the designers forget to export an Exists method. One must call [Worksheets]Item but if the sheet does not exist then Item throws an error. Writing one’s own [Worksheet]Exists method in a .NET DLL means one can bury the error and avoid VBA OERN.

SupportsGroupItems

In your situation, I’d recommend inventing your own SupportsGroupItems function which itself has an error handler but it would be hidden from VBA view (so errors get buried). It would be housed in a .NET DLL.

Management

I realise many managers prefer a pure VBA solution and management can be scared of DLL hell but if you them that it is a one-off they should persuadable. It will only be a very few lines of code.

Deployment

Next problem for DLL solution is deployment, not all deployment is done in one complete roll-out and so you’d need to write code to take advantage of the DLL if installed and fallback on VBA if not installed. You can look in the registry for code to detect the DLL’s installation.

COM Interop Code

If you have read this far and are convinced that you want your own dll to bury any errors then you need some sample code. This blog post has some sample code. Key to making a .NET DLL callable from VBA is marking ComVisible(true) in Assembly.cs and ion the build tab of project properties ensure the following checkbox is checked ‘Register for COM interop’.

Then ensure a separation of interface and class. Do you know what? Let me write the code here. So this is sample C# that avoids the Word Primary Interop Assembly by using the dynamic keyword.

using System;
using System.Runtime.InteropServices;  // No Word PIA required


namespace SupportsGroupItemsByDynamicLib
{

    public interface ISupportsGroupItemsByDynamic
    {
        bool SupportsGroupItemsByDynamic(object shp);
    }

    [ClassInterface(ClassInterfaceType.None)]
    [ComDefaultInterface(typeof(ISupportsGroupItemsByDynamic))]
    public class CSupportsGroupItemsByDynamic : ISupportsGroupItemsByDynamic
    {
        bool ISupportsGroupItemsByDynamic.SupportsGroupItemsByDynamic(object shp)
        {
            bool bSupportsGroupItems = false;
            try
            {
                dynamic dynaShape = shp;
                dynamic grpShps = dynaShape.GroupItems;
                bSupportsGroupItems = true; // it worked
            }
            catch (Exception)
            {
                // bury the error
            }
            return bSupportsGroupItems;
        }
    }
}

So here is some sample VBA code calling the .NET Dll

Sub TestByDynamic()
    Dim oGroupItemsByDynamic  As SupportsGroupItemsByDynamic.CSupportsGroupItemsByDynamic
    Set oGroupItemsByDynamic = New SupportsGroupItemsByDynamic.CSupportsGroupItemsByDynamic

    Dim shp As Shape
    For Each shp In ActiveDocument.Shapes

        If oGroupItemsByDynamic.SupportsGroupItemsByDynamic(shp) Then
            Debug.Print shp.Name + " is a group! and has " & shp.GroupItems.Count & " items"
        Else
            Debug.Print shp.Name + " is not a group!"
        End If
    Next

End Sub

All the above makes the OERN in VBA look palatable.