Код Word VBA для извлечения данных раскрывающегося списка

Я пытаюсь написать код в Word, который позволяет мне извлекать данные из раскрывающихся списков управления содержимым. Эти данные извлекаются из ранее сохраненного файла Word, на который я ссылаюсь в начале скрипта (но не показывать здесь, так как это не проблема).

У меня это работает для других типов управления контентом (пример ниже), но я не могу понять, как это будет работать для раскрывающихся списков.

Вот мой неэффективный код:

For l = 1 To 28
Windows(ReportWindowName).Activate
TagName = "Rating" & l
Set doc = ActiveDocument
Set ccs = doc.SelectContentControlsByTag(TagName)
Set cc = ccs(1)
cc.Range.Select
ccc = Selection.Text
OriginalDocument.Activate
TagName = "Rating" & l
Set doc = ActiveDocument
Set ccs = doc.SelectContentControlsByTag(TagName)
Set cc = ccs(1)
cc.Range.Select
Selection.Text = ccc
Next l

Код падает в Selection.Text. Мне нужно что-то изменить, чтобы код мог извлекать записи в раскрывающихся списках.

Ниже приведен еще один очень похожий код той же команды, которая работает, но возвращает данные из текстовых полей, а не из выпадающих списков, сохраненных в файле dame:

For j = 1 To 6
Windows(ReportWindowName).Activate
TagName = "Mandatory" & j
Set doc = ActiveDocument
Set ccs = doc.SelectContentControlsByTag(TagName)
Set cc = ccs(1)
cc.Range.Select
ccc = Selection.Text
OriginalDocument.Activate
TagName = "Mandatory" & j
Set doc = ActiveDocument
Set ccs = doc.SelectContentControlsByTag(TagName)
Set cc = ccs(1)
cc.Range.Select
Selection.Text = ccc
Next j

Буду признателен за любую помощь в изменении моего кода цикла для получения результатов раскрывающегося списка.

Большое спасибо!


person frond    schedule 08.07.2020    source источник


Ответы (1)


Если вы пытаетесь получить текст из элемента управления содержимым, вам нужно самое большее

Set ccs = doc.SelectContentControlsByTag(TagName)
Set cc = ccs(1)
' Let's just show the "display name"
Debug.Print cc.Range.Text

Вы можете сократить это до

Set ccs = doc.SelectContentControlsByTag(TagName)
' Let's just show the "display name"
Debug.Print ccs(1).Range.Text

или даже дальше, если хотите.

Причина того, что код, который у вас есть на данный момент, не работает, заключается в том, что он на самом деле пытается поместить текст в элемент управления содержимым. Вы можете сделать это с помощью элемента управления Text, но не с раскрывающимся списком.

(В продолжение вашего комментария) Если вы хотите установить для раскрывающегося списка определенное значение, вам в основном нужно определить, какой элемент в коллекции DropDownListEntries является правильным, а затем выбрать его. Каждый DropDownListEntry в ContentControl имеет уникальный индекс, уникальный текст (отображаемый текст) и значение (скрытое значение).

Вы можете получить текст из раскрывающегося списка, просмотрев .Range.Text исходного ContentControl, но вы не можете использовать его в качестве индекса в записях списка целевого ContentControl, поэтому вам нужно повторить :

Итак, если ccc содержит текст, который вы хотите отобразить, вам понадобится что-то вроде

Set ccs = doc.SelectContentControlsByTag(TagName)
Set cc = ccs(1)
' This asumes you know this is a dropdown list cc
Dim ddle as Word.ContentControlListEntry
For Each ddle in cc.DropdownListEntries
  If ddle.Text = ccc Then
    ddle.Select
    Exit For
  End If
Next

Или вы можете получить Index из системы управления версиями (для этого вам придется повторять попытки прослушивания системы управления версиями). Допустим, это переменная idx. Тогда все, что вам нужно, это

Set ccs = doc.SelectContentControlsByTag(TagName)
Set cc = ccs(1)
cc.DropdownListEntries(idx).Select

(На самом деле вы можете сделать все это в одном

doc.SelectContentControlsByTag(TagName)(1).DropDownlistEntries(idx).Select

но я обычно считаю, что использование нескольких операторов упрощает отладку).

Поэтому, используя этот подход, вам нужно либо перебрать один набор записей списка, либо другой (или оба, если вы хотите использовать значение).

Другой метод заключается в сопоставлении элемента управления с элементом в CustomXMLPart и простом обновлении значения элемента. Затем Word распространяет значение на все элементы управления ContentControl, сопоставленные с этим элементом. Здесь нужно многому научиться, и это может показаться усложнением, которое вам не нужно, но когда вы дойдете до конца, я надеюсь, вы поймете, почему это на самом деле довольно изящный подход.

В простейшем случае это работает так. Предположим, в вашем документе есть один элемент управления содержимым DropDown.

Затем вы можете (повторно) создать XML-часть и сопоставить с ней элемент управления содержимым следующим образом. Вам нужно будет выполнить этот фрагмент кода только один раз для документа. Если ваши документы основаны на шаблонах или сделаны из копий других документов, это один раз для шаблона/оригинала.

Option Explicit
' A namespace URI can just be  a piece of text, but its better if you can use
' something that you "own" such as a domain name.
' There is nothing special about this name.
Const myNameSpace As String = "myns0"

Sub recreateCXPandMapCCs()
Dim ccs As Word.ContentControls
Dim cxp As Office.CustomXMLPart
Dim i As Integer
Dim r As Word.Range
Dim s As String
' There is nothing special about these element names.
' You can use your own
s = ""
s = s & "<?xml version=""1.0"" encoding=""UTF-8""?>" & vbCrLf
s = s & "<ccvalues1 xmlns='" & myNameSpace & "'>" & vbCrLf
s = s & "  <dropdown1/>" & vbCrLf
s = s & "</ccvalues1>"

With ActiveDocument
  ' select and delete any existing CXPs with this namespace
 For Each cxp In .CustomXMLParts.SelectByNamespace(myNameSpace)
    cxp.Delete
  Next
  
  ' Create a new CXP
  Set cxp = .CustomXMLParts.Add(s)

  ' Connect your dropdown. Instead, you can do this manually in the XML Mapping
  ' Pane in the Developer tab

  ' For an XML Part that only has one namespace the prefix mapping should always be "ns0". 
  .ContentControls(1).XMLMapping.SetMapping "/ns0:ccvalues[1]/ns0:dropdown1[1]", , cxp
  Set cxp = Nothing
End With
End Sub

Затем, чтобы установить значение вашего раскрывающегося списка (и это должно быть скрытое значение, а не индекс или текст, вы можете сделать что-то подобное в том же модуле Итак, у вас настроена константа myNameSpace. Допустим, вы хотите установить постоянное значение xyzvalue.

Sub populateDropdown1Element()
With ActiveDocument.CustomXMLParts.SelectByNamespace(myNameSpace)(1)
  .SelectSingleNode("/ns0:ccvalues1[1]/ns0:dropdown1[1]").Text = "xyzvalue"
End With
End Sub

Конечно, если исходный документ имеет те же сопоставления, вы можете получить значение раскрывающегося списка исходного документа из того же элемента в XML исходного документа. Дело в том, что если у вас есть тот же XML, те же сопоставления и т. д., в идеале вы должны иметь возможность заменить всю CustomXMLPart в целевом документе на часть из исходного документа. Одна из причин, по которой были изобретены CustomXMLParts, заключалась в том, чтобы позволить людям, использующим Office Open XML SDK, делать именно это. К сожалению, это не работает в VBA с открытым документом, потому что Word имеет тенденцию отключать элементы управления содержимым от части.

Но что вы можете сделать, так это перебрать все узлы Element и Attribute (например) и заменить текст в цели текстом из источника. Как это:

' You would need to pass in a reference to the document you want to get your data *from*
Sub replaceXML(sourceDocument As Word.Document)
Dim s As String
Dim cxn As Office.CustomXMLNode
Dim sourcePart As Office.CustomXMLPart

' You still need that definition of "myNameSpace"
Set sourcePart = sourceDocument.CustomXMLParts.SelectByNamespace(myNameSpace)(1)

With ActiveDocument
  For Each cxn In .CustomXMLParts.SelectByNamespace(myNameSpace).Item(1).SelectNodes("//*[not(*)] | //@*")
    cxn.Text = sourcePart.SelectSingleNode(cxn.XPath).Text
  Next
End With
End Sub

Что выбирает "//*[not(*)] | //@*"? Ну, "//*[not(*)]" выбирает конечные элементы (включая элементы, у которых есть атрибуты), "//@*" выбирает все атрибуты (которые всегда являются конечными узлами), а | в основном является Or или объединением.

Большинство пользовательских xml, которые я видел в Word, хранят данные только в Elements, и в этом случае вам понадобится только "//*[not(*)]"

person Community    schedule 08.07.2020
comment
Привет, спасибо за ответ. Я пытаюсь заполнить шаблон ранее сохраненным отчетом. Я пытаюсь вызвать данные из последней сохраненной версии, а затем заполнить раскрывающиеся списки предыдущими ответами. Вы хотите сказать, что сделать это невозможно? Большое спасибо, - person frond; 09.07.2020
comment
Привет @slightly snarky, спасибо, я проработал ваш пример, и он работает. Цените помощь! - person frond; 13.07.2020