英文:
Code for resetting the counter for textboxes inserted in the word document
问题
我有一段代码,它将粗体文本从文本框复制到所选表格的第二列。但是,该代码无法按顺序识别文本框,并从文档中首先插入的文本框复制文本,依此类推。当文本框不按顺序插入时,会出现问题。例如,如果文档中插入了文本框2在文本框1之上,那么表格中将首先复制文本框2中的文本。
如何重置所有文本框的顺序,以便始终从第一个文本框复制文本到最后一个?
'此代码将粗体文本从文本框复制并插入到所选表格的第二列
Sub Copy_text_from_textbox_into_table()
Dim nNumber As Integer
Dim strText As String
Dim i As Long
Dim doc As Document
Dim tbl As Table
Dim rng As Range
Dim shp As Shape
Set doc = ActiveDocument
Selection.Collapse Direction:=wdCollapseStart
Set tbl = Selection.Tables(1)
i = 0
With doc
For Each shp In .Shapes
If shp.Type = msoTextBox Then
Set rng = shp.TextFrame.TextRange
With rng.Find
.Font.Bold = True
.Wrap = wdFindStop
.Execute
strText = rng.Text
End With
i = i + 1
With tbl.Cell(Row:=i + 1, Column:=2).Range
.Delete
.InsertAfter Text:=strText
End With
Else
MsgBox ("There is no textbox.")
End If
Next
End With
End Sub
英文:
I have a code that copies the bold text from textbox to the column 2 of the selected table. However
this code is not identifying the textboxes in a sequential manner and is copying the text from the textbox that was inserted first in the document and so on. This creates problem when the textboxes are not inserted sequentially. For example, if textbox 2 was inserted above textbox 1 in the document then the text from textbox 2 will be copied first in the table.
How can I reset the sequence of all textboxes so that the text is always copied from the first textbox to the last?
'This code copies bold text from the textboxes and insert into the column 2 of the selected table
Sub Copy_text_from_textbox_into_table()
Dim nNumber As Integer
Dim strText As String
Dim i As Long
Dim doc As Document
Dim tbl As Table
Dim rng As Range
Dim shp As Shape
Set doc = ActiveDocument
Selection.Collapse Direction:=wdCollapseStart
Set tbl = Selection.Tables(1)
i = 0
With doc
For Each shp In .Shapes
If shp.Type = msoTextBox Then
Set rng = shp.TextFrame.TextRange
With rng.Find
.Font.Bold = True
.Wrap = wdFindStop
.Execute
strText = rng.Text
End With
i = i + 1
With tbl.Cell(Row:=i + 1, Column:=2).Range
.Delete
.InsertAfter Text:=strText
End With
Else
MsgBox ("There is no textbox.")
End If
Next
End With
End Sub
答案1
得分: 1
你正在处理的问题涉及文本框的锚定位置。这是文档文本流中Shape被管理的位置。如果你查看底层的XML,你可以看到它是如何工作的(但这并不是理解发生的事情的必要条件)。要查看这些锚点,请转到文件/选项/显示,然后在“始终在屏幕上显示这些格式标记”部分激活“对象锚点”。(注意:这些不会打印出来;它们的另一个术语是“非打印字符”)。
一般来说,当用户插入文本框时,它将锚定到所选位置所在的段落。如果随后拖动文本框,锚点将移动,除非它已经明确“锁定”在位置上。当代码插入文本框时,它将锚定到由Anchor
参数指定的Range
;如果未设置,那么就有点像抽奖。
当Word遍历Shapes
集合时,它会按锚点的顺序依次拾取文档中的连续文本,而不管对象可能出现在页面的哪个位置。
解决这个非常复杂的需求的完整解决方案超出了Stack Overflow的范围。以下说明了涉及和如何处理这个问题的基本原理。
一个简单的方法
解决这个问题的一种方法是循环遍历Shapes
,将每个对象添加到数组或集合中。检查数组(或集合)中每个对象的垂直/水平位置,相对于页面边距。然后根据这些信息对数组/集合进行排序。最后,遍历已排序的数组/集合,并将内容分配给表格。
这样做的复杂性进一步增加了一个事实,即Shape
的位置可以相对于锚点、边距或页面。以下代码展示了按照它们在页面上出现的顺序(从上到下)获取文本框的可能方法。
为了清晰起见,代码省略了将内容写入表格的步骤,但在此步骤插入了注释。
代码
该代码执行了三个For
循环。第一个循环遍历文档中的所有Shapes
并测试每个是否是文本框。如果是文本框,则将所需的属性写入用户定义的Type
,然后将Type
分配给数组。出于效率考虑,这样做比在以后的循环中再次引用每个Shape
对象要快。
还要注意,在每次迭代之前,Shape
明确设置为相对于页面定位,而不是其他位置。这意味着文本框不会随文本在页面上移动。如果需要这样做,需要添加另一层复杂性来确定每个文本框的相对位置,并根据这个位置计算相对于页面的位置。 (或者,可能可以将设置更改回去,但需要测试以确保文本框不会移动。无论如何,这种复杂程度超出了此问题的范围。)
由于我们需要Shape
对象(或标识该对象的方法)和其位置信息,因此需要一个多维数组。在代码启动时,元素(文本框)的数量是未知的,因此数组需要在运行时维度。但是,Redim Preserve
只能更改最后一个维度,因此不适合此目的。因此,信息不能直接分配给多维数组,这就是为什么首先将其分配给用户定义的Type
数组的原因,该数组携带了所有信息。
在为数组分配维度之后,将位置信息与Type
数组中的信息一起分配给它,同时填充一个包含索引值和Shape
名称的第三个数组。
第三个数组的原因是使用WordBasic.SortArray
对数组按页面上Shapes
的Top
位置进行排序。这将所有元素强制转换为相同的数据类型,这意味着不保留Shape.Name
的字符串值。
最后,代码循环遍历已排序的数组,该数组现在按照页面上每个文本框的升序顺序排列。
Public Type DocShapes
shpName As String
top As Double
left As Double
End Type
Sub GetTextBoxPositionalOrder()
Dim doc As Word.Document
Dim shp As Word.Shape
Dim aShapes() As Variant
Dim counter As Long, i As Long
Dim shpType As DocShapes
Dim shpTypes() As DocShapes
Dim shpIndex() As Variant
counter = 0
Set doc = ActiveDocument
For Each shp In doc.Shapes
'Count the shapes to dimension the array and
'assign to user-defined Type
If shp.Type = msoTextBox Then
shp.RelativeVerticalPosition = wdRelativeVerticalPositionPage
shp.RelativeHorizontalPosition = wdRelativeHorizontalPositionPage
shpType.shpName = shp.Name
shpType.left = shp.left
shpType.top = shp.top
ReDim Preserve shpTypes(counter)
shpTypes(counter) = shpType
counter = counter + 1
End If
Next
ReDim Preserve aShapes(counter - 1, 2)
ReDim Preserve shpIndex(counter - 1, 1)
For i = LBound(shpTypes) To UBound(shpTypes)
shpIndex(i, 0) = i + 1
shpIndex(i, 1) = shpTypes(i).shpName
aShapes(i, 2) = i 'corresponds to the index
aShapes(i, 0) = shpTypes(i).top
aShapes(i, 1) =
<details>
<summary>英文:</summary>
The issue you're dealing with is the position in which the textboxes are *anchored*. This is the place in the document's text flow where the Shape is managed. If you were to look at the underlying XML you could see how this works (but that's not necessary for understanding what's happening). In order to see these anchors, go to File/Options/Display and activate "Object anchors" in the section "Always show these formatting marks on the screen". (Note: these do not print out; another term for them is "non-printing characters".)
[![enter image description here][1]][1]
Generally, when the user inserts a text box, it will anchor to the paragraph in which the selection is located. If the text box is then dragged, the anchor will move, unless it's been explicitly "locked" in position. When code inserts a text box, it will anchor to the `Range` specified by the `Anchor` parameter; if that's not set, it's a bit of a lottery.
When Word runs through the `Shapes` collection it follows the contiguous text in the document, picking up the `Shapes` in the order of the anchors, no matter where the object might appear on the page.
A complete solution to this very complex requirment goes beyond the scope of Stack Overflow. The following illustrates the basics about what's involved and how it can be approached.
**A simple approach**
An approach to solving this would be to loop the `Shapes`, adding each object to an array or collection. Check the vertical / horizontal positions of each object in the array (or collection), relative to the page margins. Then sort the array/collection according to this information. Finally, go through the sorted array/collection and assign the content to the table.
Doing this is further complicated by the fact that `Shape` positions can be relative to the anchor point, to a margin or to a page.
The following code shows a possible approach to getting the text boxes in the correct order (top-to-bottom) as they appear on a page.
For the sake of clarity, the step of writing the content to a table has been left out, but a comment is inserted at the point this would take place.
**The code**
The code performs three `For`loops. The first loops all `Shapes` in the document and tests whether each is a text box. If it is, the required properties are written to a user-defined `Type`, then the `Type` is assigned to an array. This is done for reasons of efficiency: looping an array of a `Type` is faster than addressing each `Shape` object again, in a later loop.
Note also, before each iteration, the `Shape` is explicitly set to be positioned relative to the page, rather than anything else. This means that the text boxes will not move on the page with the text. If this is required, another level of complexity needs to be added to ascertain how each text box is positioned, relatively, and calculate the position relative to the page based on that. (Or, it might be possible to change the setting back, but that would need to be tested to make sure the text boxes do not move. In any case, such a level of complexity goes beyond the scope of this question.)
Since we need both the `Shape` object (or a way to identify that object) and its positional information, a multi-dimensional array is needed. The number of elements (TextBoxes) is unknown when the code starts, so the array needs to be dimensioned during run-time. But `Redim Preserve` can only change the last dimension, so is not suited to this purpose. Therefore, the information cannot be assigned directly to the multi-dimensional array, which is why it's first assigned to an array of the user-defined `Type`, which carries all the information.
After dimensioning the array, the positional information is assigned to it from the array of the `Type`, along with an index value. At the same time, a third array, with the index value and the name of the `Shape` is populated.
The reason for the third array is that `WordBasic.SortArray` is used to sort the array by the `Top` position of the `Shapes` on the page. This coerces all elements into the same data type, meaning the string value of the `Shape.Name` is not retained.
Finally, the code loops the sorted array, which is now in ascending order of each text box on the page.§
Public Type DocShapes
shpName As String
top As Double
left As Double
End Type
Sub GetTextBoxPositionalOrder()
Dim doc As Word.Document
Dim shp As Word.Shape
Dim aShapes() As Variant
Dim counter As Long, i As Long
Dim shpType As DocShapes
Dim shpTypes() As DocShapes
Dim shpIndex() As Variant
counter = 0
Set doc = ActiveDocument
For Each shp In doc.Shapes
'Count the shapes to dimension the array and
'assign to user-defined Type
If shp.Type = msoTextBox Then
shp.RelativeVerticalPosition = wdRelativeVerticalPositionPage
shp.RelativeHorizontalPosition = wdRelativeHorizontalPositionPage
shpType.shpName = shp.Name
shpType.left = shp.left
shpType.top = shp.top
ReDim Preserve shpTypes(counter)
shpTypes(counter) = shpType
counter = counter + 1
End If
Next
ReDim Preserve aShapes(counter - 1, 2)
ReDim Preserve shpIndex(counter - 1, 1)
For i = LBound(shpTypes) To UBound(shpTypes)
shpIndex(i, 0) = i + 1
shpIndex(i, 1) = shpTypes(i).shpName
aShapes(i, 2) = i 'corresponds to the index
aShapes(i, 0) = shpTypes(i).top
aShapes(i, 1) = shpTypes(i).left
Next
WordBasic.SortArray aShapes, 0, 0, UBound(aShapes), 0, 0
For i = LBound(aShapes) To UBound(aShapes)
'''Write the text box content to the table at this point
Debug.Print shpIndex(aShapes(i, 2), 1), aShapes(i, 0), aShapes(i, 1)
Next
End Sub
§ Note that this code works for a one-page document. If you need to handle text boxes on multiple pages, then an added dimension is required: on which page each `Shape` is located. Then the text box information would first need to be sorted by page, and then by position on each page. Or set it up to work with one page's `Shapes` at a time.
It would also be possible to use a different sort algorithm - there are a lot out there. I used `WordBasic.SortArray` because 1) it's built-in and 2) I couldn't take the time to research various sort algorithms.
[1]: https://i.stack.imgur.com/Shw41.png
</details>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论