【问题标题】:check whether a shape is a group (GroupItems raises an error)检查一个形状是否是一个组(GroupItems 引发错误)
【发布时间】:2021-03-14 21:01:22
【问题描述】:

我在 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

对于实际的分组形状,它可以工作,但是当形状不是一个组时,我得到一个错误:

运行时错误“-2147024891 (80070005)”:该成员只能是 群组访问

除了使用On Error之外,我如何检查对象是否是一个组?

【问题讨论】:

  • 组实际上不是“组”类型吗?我会检查属性以进行测试。
  • 是的,我一直在寻找这样的东西,但在属性中找不到像“组”这样的东西。到目前为止,我发现的最接近的是属性 AutoShapeType,它的值是 msoShapeMixed - 不确定这是否是一种检查组的犹太方式,但也许我应该使用它。
  • @SMeaden,也许您可​​以将其添加为答案,以便我接受?到目前为止,我认为没有更好的解决方案。
  • 好的,我已经确定了答案。

标签: excel vba ms-word


【解决方案1】:

到目前为止,我在这里得到了两个非常全面的答案,我感谢他们的作者。但是我想避免On Error 捕获,不想依赖形状的名称,也不想做复杂的事情,比如构建我自己的 dll。 Cindy 的InStr(rng.WordOpenXML, "<wpg:wgp>") 解决方案看起来最接近,但由于某种原因,这段代码在我的文档中不起作用:<wpg:wgp> 被发现适用于任何形状,无论是否分组。 因此,我决定发布我自己的解决方案,该解决方案非常简单,似乎适用于所有情况。我们只需要使用.AutoShapeType 属性(@SMeaden 在评论中指出的):

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

【讨论】:

    【解决方案2】:

    您可以通过多种方式在 Word 中解决此问题。前两个可能也适用于 Excel,但第三个仅适用于 Word。

    1. 使用On Error Resume Next 的代码并检查Err.Number。如果是 0,你有一个组,如果不是,那么你没有。

      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
      

      可能还有其他错误,尽管我目前没有发生任何错误。

    2. 另一种可能性是检查Name 属性,假设它没有被任何代码更改。默认情况下,它将类似于“Group 3”,所以

      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
      
    3. 检查WordOpenXML是否包含元素标签&lt;wpg:wgp&gt;(代表wordProcessingGroup,见Open XML SDK documentation)。您无法获取 Shape 的 WordOpenXML,您需要查询 Shape.Anchor.Paragraphs(1).Range - Word 文档中与 Shape 关联的 Range。

      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
      

    请注意,这种简单的方法只有在分组的形状是唯一锚定到段落的形状时才有效。如果有多个形状,您仍然可以使用 WordOpenXML,但您需要使用 XML 工具对其进行分析,以确保所讨论的形状是一个组。

    【讨论】:

    • 谢谢@Cindy,提供不同的选择!我知道On Error 解决方案,但正如我所说,我想知道是否有一种不同的方法。检查名称对我来说似乎不是 100% 可靠 - 正如您所指出的,它确实可以更改(尽管对于 99% 的情况,我认为这会起作用)。
    【解决方案3】:

    我遇到了同样的挑战,发现以前的解决方案要么过于复杂(错误处理)要么不起作用(AutoShapeType)。但是它表明,通过检查 Shape.Type 属性,组本身将识别为 值 6(或 msoGroup)。

    所以之前的代码可能会调整为如下:

    Sub MyMacro()
    
        Dim SHP As SHAPE
        
        For Each SHP In ActiveDocument.SHAPES
            If SHP.Type = 6 Then
                Debug.Print SHP.Name & " is a group!"
            Else
                Debug.Print SHP.Name & " is not a group!"
            End If
        Next
    
    End Sub
    

    【讨论】:

    • 这是正确答案。在 PowerPoint VBA 上测试过,效果很好。
    【解决方案4】:

    界面设计错误

    恕我直言,这看起来像是一个界面设计错误。设计人员应该做的也是导出一个GroupItemsCount 方法,客户端代码可以在尝试调用GroupItems 之前对其进行检查。

    隐藏错误

    我也不喜欢使用On Error Resume Next(以下简称 OERN),并且每个开发团队都试图说服同事发布一个用 .NET(C# 或 VB.NET)编写的DLL,它会掩盖这些方法引发的所有这些错误.

    Excel 缺少 Worksheet Exists 方法

    所以在 Excel 中,我们为每个工作簿都有一个 Worksheets 集合,但设计人员忘记导出 Exists 方法。必须调用[Worksheets]Item,但如果工作表不存在,则 Item 会引发错误。在 .NET DLL 中编写自己的 [Worksheet]Exists 方法意味着可以隐藏错误并避免 VBA OERN。

    SupportsGroupItems

    在您的情况下,我建议您创建自己的 SupportsGroupItems 函数,该函数本身具有错误处理程序,但它会从 VBA 视图中隐藏(因此错误会被隐藏)。它将位于 .NET DLL 中。

    管理

    我意识到许多经理更喜欢纯 VBA 解决方案,并且管理层可能会害怕 DLL 地狱,但如果您认为这是一次性的,他们应该可以说服。只需几行代码。

    部署

    DLL 解决方案的下一个问题是部署,并非所有部署都在一次完整的部署中完成,因此如果已安装,您需要编写代码以利用 DLL,如果未安装则回退到 VBA。您可以在注册表中查找代码以检测 DLL 的安装。

    COM 互操作代码

    如果您已阅读本文并确信您希望自己的 dll 隐藏任何错误,那么您需要一些示例代码。这个blog post 有一些示例代码。使 .NET DLL 可从 VBA 调用的关键是在 Assembly.cs 中标记 ComVisible(true) 并在项目属性的构建选项卡中确保选中以下复选框“注册 COM 互操作”。

    然后确保接口和类的分离。你知道吗?让我在这里写代码。因此,这是使用 dynamic 关键字避免 Word 主互操作程序集的示例 C#。

    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;
            }
        }
    }
    

    这里是一些调用 .NET Dll 的示例 VBA 代码

    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
    

    以上所有使 VBA 中的 OERN 看起来很可口。

    【讨论】:

    • 哦哇...我对如此认真的方法印象深刻...说实话,我真的不想为这项任务制作自己的 dll,我只想用标准的简单 VBA 来管理事物。但我相信您的解决方案会对某些人有所帮助,感谢您花时间写这篇文章!
    猜你喜欢
    • 2021-10-27
    • 1970-01-01
    • 2015-11-06
    • 1970-01-01
    • 1970-01-01
    • 2010-09-24
    • 1970-01-01
    相关资源
    最近更新 更多