在Word VBA中,为什么InlineShape对象的Delete方法不可靠?

huangapple go评论75阅读模式
英文:

In Word VBA, why does the InlineShape object's Delete method not work reliably?

问题

多年来,我遇到过几次的一个问题是,在开发删除Word文档的InlineShapes集合中的InlineShape对象的代码时,当在VBE窗口测试调用每个InlineShape对象的Delete方法的代码时,这些调用不会删除对象,而是删除文档当前选定的文本,或者如果选择已折叠,则删除插入点右侧的字符。

如果我逐步执行代码,它不会删除任何InlinShapes,而是删除文本。如果我运行整个删除循环(从VBE窗口内),它将删除一些对象,然后删除插入点文本,而不是最后一个要删除的对象。但通常情况下(在某些文档内容情况下),当文档是活动窗口(而不是VBE窗口)并且代码是通过用户交互方式执行(例如从宏菜单运行)时,它会正确执行,删除所有InlineShape对象并保留所有文本不变。但在其他文档内容情况下,有时会保留一个InlineShape对象不变(我尚未确定导致最后一种情况的特定情况)。

以下是演示问题的一些代码。

设置:

  1. 打开一个新文档。
  2. 向文档添加文本“这是一个测试”。
  3. 在文档下方添加三个ActiveX CommandButton对象。
  4. 双击文档中的单词“This”,它将选择该单词及其后的空格字符。
  5. 保存文档。
  6. 在文件资源管理器中,右键单击文档文件,选择属性,并设置为只读(以防止意外覆盖其原始状态)。
  7. 打开VBE窗口,在标准模块中添加以下代码:
Sub Demo()
    Dim i As Integer
    
    With ThisDocument
        Debug.Print "Before:"
        For i = .InlineShapes.Count To 1 Step -1
            Debug.Print "    " & .InlineShapes(i).OLEFormat.Object.Name 'To the Immediate window
        Next i
        
        For i = .InlineShapes.Count To 1 Step -1
            Debug.Print "Deleting " & .InlineShapes(i).OLEFormat.Object.Name
            .InlineShapes(i).Delete
        Next i
        
        Debug.Print "After:"
        For i = .InlineShapes.Count To 1 Step -1
            Debug.Print "    " & .InlineShapes(i).OLEFormat.Object.Name
        Next i
    End With
End Sub

测试1:在VBE窗口中,逐步执行演示代码。我的结果是,没有InlineShape对象(ActiveX控件)实际被删除,而是单词“This”及其后的空格字符在第一个对InlineShape对象的Delete方法的调用中被删除,第二和第三个Delete方法调用中删除了接下来的“i”和“s”字符。(还请注意在Immediate窗口打印的跟踪信息)。

测试2:不保存文档,然后重新打开它并再次双击单词“This”。在VBE窗口中,不逐步执行,而是直接运行Demo程序。我的结果是,前两个InlineShape对象被删除,但单词“This”及其后的空格字符被删除,而不是第三个对象。

测试3:不保存文档,然后重新打开它并再次双击单词“This”。不要打开VBE,在Word的查看选项卡中选择宏>查看宏。从那里运行Demo程序。我的结果是,所有三个InlineShape对象被正确删除,单词“This”保持完好。然而,在生产代码中,我曾经看到在正常运行时,有时不会删除任何InlineShape对象,或者除了一个之外的所有对象都会被删除。我不知道是什么情况会导致这些不同的结果。

显然,这是一个bug,但是否有人知道是否有一些明显的技巧可以在所有情况下可靠地执行InlineShapes Delete方法?

英文:

A problem that I've run across a few times over the years is that, when developing code that deletes InlineShape objects from a Word document's InlineShapes collection, while in the VBE window testing code that calls each InlineShape object's Delete method, those calls don’t delete the object, they instead delete the document's currently selected text or, if the selection is collapsed, they delete the character to the right of the insertion point.

If I single-step through the code, it deletes none of the InlinShapes and, instead, deletes text. If I run the entire deletion-loop (from within the VBE window), it will delete some of the objects but then delete the insertion-point text instead of the last object to be deleted. But, usually (under some document-content circumstances), when the document is the active window (not the VBE window) and the code is executed by way of a user interaction (such as run from the Macros menu) then it executes correctly, deleting all InlineShape objects and leaving all text intact. However, in other document-content circumstances, it sometimes leaves one of the InlineShape objects intact. (I have yet to determine the particular circumstances that result in the last case.)

For reference, here's some code that demonstrates the problem.

Setup:

  1. Open a new document.

  2. Add the text "This is a test" to the document.

  3. Below that, add three ActiveX CommandButton objects to the document.

  4. Double-click the word "This" in the document's text, which will select it and its trailing space character.

  5. Save the document

  6. In File Explorer, right-click the document file, select Properties, and set it to Read-only (to prevent accidentally overwriting its original state).

  7. Open the VBE window and, in a standard module, add the following code:

    Sub Demo()
        Dim i As Integer
    
        With ThisDocument
            Debug.Print "Before:"
            For i = .InlineShapes.Count To 1 Step -1
                Debug.Print "    " & .InlineShapes(i).OLEFormat.Object.Name 'To the Immediate window
            Next i
    
            For i = .InlineShapes.Count To 1 Step -1
                Debug.Print "Deleting " & .InlineShapes(i).OLEFormat.Object.Name
                .InlineShapes(i).Delete
            Next i
    
            Debug.Print "After:"
            For i = .InlineShapes.Count To 1 Step -1
                Debug.Print "    " & .InlineShapes(i).OLEFormat.Object.Name
            Next i
        End With
    End Sub
    

Test-1: In the VBE window, single-step through the demo code. My result is that no InlineShape objects (the ActiveX controls) actually get deleted and, instead, the word "This" and its trailing space character get deleted by the first call to the InlineShape object's Delete method, and the following "i" and "s" characters get deleted by the second and third Delete method calls. (Also note the trace information printed to the Immediate window.)

Test-2: Close the document without saving it, then re-open it and double-click the word "This" again. In the VBE window, run the Demo routine straight through without single-stepping. My result is that the first two InlineShape objects get deleted but the word "This" and its trailing space character get deleted instead of the third object, which remains.

Test-3: Close the document without saving it, then re-open it and double-click the word "This" again. Without opening VBE, in the Word ribbon’s View tab, select Macros=>View Macros. Run the Demo routine from there. My result is that all three InlineShape objects get correctly deleted and the word "This" remains intact, correctly. However, in production code, I have seen cases where no InlineShape objects are deleted or all but one are deleted when run in the normal manner. I have no clue what circumstances produce those varying results.

Obviously this is a bug, but does anyone know whether there is some obvious technique that results in reliable execution of the InlineShapes Delete method under all circumstances?

答案1

得分: 1

以下是您要翻译的内容:

在任何情况下,从历史上看,我一直采用以下可行的解决方法,无论是正常运行还是单步运行代码时都有效,我在这里提供给其他人以防有人觉得有用。

有三种已知的解决方法(我知道的):

  1. 首先,将每个ActiveX InlineShape对象转换为Shape对象(通过调用其ConvertToShape方法),然后通过Shapes集合删除它们。 这个解决方法不是首选的,因为需要进行第二个集合遍历循环,存在额外的复杂性和开销。(请注意,Shapes循环必须检查Shape.Type属性是否为msoOLEControlObject而不是wdInlineShapeOLEControlObject)。

  2. 选择InlineShape对象的范围,并通过Selection对象的Delete方法删除它。 由于需要保存当前选择的额外开销,选择每个InlineShape进行删除,然后在完成后恢复原始选择,因此此解决方法不是首选。

  3. 简单地将InLineShape对象的范围内容替换为空字符串。 这样做会自动从InlineShapes集合中删除对象。 由于其简单性,此解决方法首选。

代码:

Sub DeleteAllActiveXControls(wdDoc As Word.Document)
'
'删除指定文档中的所有ActiveX控件。
'
'*************************************************************************************************************

Dim i As Integer

'*************************************************************************************************************
'Word InLineShape Delete方法的BUG解决方法---解决方法---解决方法---解决方法---解决方法
'
'Word存在一个奇怪的bug,调用InLineShape对象的Delete方法不能可靠地删除形状。测试表明,问题总是在通过删除代码单步运行时表现出来。当通过与文档的某些用户交互的方式正常执行代码时,某些InLineShape对象确实被删除,但并非所有对象都能可靠删除(在我尚未确定的情况下)。
'
'这显然是一个错误,因为Delete方法并不能像Microsoft的参考文档中所宣传的那样可靠地按照它所说的工作:https://learn.microsoft.com/en-us/office/vba/api/word.inlineshape.delete
'
'我找到的最简单的解决方法就是将InLineShape对象的范围内容替换为空字符串。
'
With wdDoc
For i = .InlineShapes.Count To 1 Step -1
If .InlineShapes(i).Type = wdInlineShapeOLEControlObject Then '(ActiveX控件)
.InlineShapes(i).Range = "" '通过替换删除InlineShape对象的当前范围内容,
End If '这会自动从InlineShapes集合中删除对象。
Next i
End With
'
'END WORKAROUND --- END WORKAROUND --- END WORKAROUND --- END WORKAROUND --- END WORKAROUND --- END WORKAROUND
'*************************************************************************************************************
End Sub

英文:

In any case, historically, I’ve resorted to the following workaround that works when run normally and also works while single-stepping through the code, which I provide here in case anyone else finds it useful.

There are three workarounds (known to me):

  1. First convert each ActiveX InlineShape object to a Shape object (by calling its ConvertToShape method) and then delete them via the Shapes collection. This workaround is not preferred because of the extra complexity and overhead of the second collection-traverse loop. (Note that the Shapes loop must check for a Shape.Type property of msoOLEControlObject instead of wdInlineShapeOLEControlObject).

  2. Select the InlineShape object's range and delete it via the Selection object's Delete method. This workaround is not preferred due to the extra overhead of saving the current selection, selecting each InlineShape for deletion, then restoring the original selection when done.

  3. Simply replace the InLineShape object's range content with a null string. Doing so automatically deletes the object from the InlineShapes collection. This workaround is preferred due to its simplicity.

Code:

Sub DeleteAllActiveXControls(wdDoc As Word.Document)
    '
    'Deletes all ActiveX controls in the specified document.
    '
    '*************************************************************************************************************

    Dim i As Integer

    '*************************************************************************************************************
    'Word InLineShape Delete method BUG WORKAROUND --- WORKAROUND --- WORKAROUND --- WORKAROUND --- WORKAROUND
    '
    'Word has a bizarre bug in which a call to an InLineShape object's Delete method doesn't reliably delete the
    'shape.  Testing has demonstrated that the problem always manifests while single-stepping through the deletion
    'code.  When the code is executed normally by way of some user interaction with the document, some of the
    'InLineShape objects do get deleted, but not all of them get reliably deleted (under circumstances that I have
    'yet to determine).
    '
    'This is clearly a bug because the Delete method does not *reliably* function as advertised by Microsoft's
    'reference documentation: https://learn.microsoft.com/en-us/office/vba/api/word.inlineshape.delete
    '
    'The simplest workaround that I've found is to just replace the InLineShape object's range content with a null
    'string.
    '
    With wdDoc
    For i = .InlineShapes.Count To 1 Step -1
        If .InlineShapes(i).Type = wdInlineShapeOLEControlObject Then '(ActiveX control)
            .InlineShapes(i).Range = "" 'Delete the InlineShape object's current range content by replacement,
        End If                          'which automatically deletes the object from the InlineShapes collection.
    Next i
    End With
    '
    'END WORKAROUND --- END WORKAROUND --- END WORKAROUND --- END WORKAROUND --- END WORKAROUND --- END WORKAROUND
    '*************************************************************************************************************
End Sub

huangapple
  • 本文由 发表于 2023年6月8日 10:52:23
  • 转载请务必保留本文链接:https://go.coder-hub.com/76428304.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定