#vba #loops #ms-word #copy #paste
#vba #циклы #ms-word #Копировать #вставить
Вопрос:
У меня есть приведенный ниже код, который пока проходит через простую электронную таблицу и вставляет организацию, одну таблицу с одним значением ячейки и диаграмму в шаблон word. Код выполняется и выдает как word, так и PDF-версию правильно. Но я вижу, что таблица иногда оказывается там, где должна быть организация, и диаграмма повторяется в документе. Кажется, что каждый вывод цикла меняется, и я не могу понять, не очищается ли информация из буфера обмена перед ее вставкой. Нужно ли мне разбивать разделы на подразделы или что-то в этом роде?
Ценю помощь.
Sub CreateBasicWordReport()
Dim WdApp As Word.Application
Dim wdDoc As Word.Document
Dim SaveName As String
Dim FileExt As String
Dim LstObj1 As ListObject
Dim MaxValue As Integer
Dim FilterValue As Integer
Dim Organisation As String
Dim Rng As Range
Dim WS As Worksheet
Set LstObj1 = Worksheets("Sheet1").ListObjects("Table1")
MaxValue = WorksheetFunction.Max(LstObj1.ListColumns(1).Range)
FilterValue = MaxValue
Set WdApp = CreateObject("Word.Application")
Do Until FilterValue = 0
Application.DisplayAlerts = False
Sheets.Add(After:=Sheets("Sheet1")).Name = "Static"
Sheets("Sheet1").Select
'moved outside of loop
' Set WdApp = CreateObject("Word.Application")
With WdApp
.Visible = True
.Activate
'create new document and assign to object variable
Set wdDoc = .Documents.Add("C:UsersdavidDocumentsCustom Office TemplatesTemplate2.dotx")
'now mostly finished with WdApp as from here wdDoc is used
End With
ActiveSheet.ListObjects("Table1").Range.AutoFilter Field:=1, Criteria1:=FilterValue
Range("F11").Select
Range("A1", Range("A1").End(xlDown).End(xlToRight)).Copy
' .Selection.GoTo what:=-1, Name:="TableLocation"
' .Selection.Paste
wdDoc.Bookmarks("TableLocation").Range.Paste
For Each Row In Range("Table1[#All]").Rows
If Row.EntireRow.Hidden = False Then
If Rng Is Nothing Then Set Rng = Row
Set Rng = Union(Row, Rng)
End If
Next Row
Set WS = Sheets("Static")
Rng.Copy Destination:=WS.Range("A1")
' Sheets("Static").Select
' Sheets("Static").Activate
Organisation = WS.Range("D2").Value
' Sheets("Static").Select
' Range("D2").Copy
WS.Range("D2").Copy
' .Selection.GoTo what:=-1, Name:="Organisation"
' .Selection.PasteAndFormat wdFormatPlainText
wdDoc.Bookmarks("Organisation").Range.PasteAndFormat wdFormatPlainText
Application.CutCopyMode = False
' Sheets("Static").Select
' Range("F2").Copy
WS.Range("F2").Copy
' .Selection.GoTo what:=-1, Name:="MalePatients"
' .Selection.PasteAndFormat wdFormatPlainText
wdDoc.Bookmarks("MalePatients").Range.PasteAndFormat wdFormatPlainText
Application.CutCopyMode = False
Chart2.ChartArea.Copy
' .Selection.GoTo what:=-1, Name:="ChartLocation"
' .Selection.Paste
wdDoc.Bookmarks("ChartLocation").Range.Paste
If WdApp.Version <= 11 Then
FileExt = ".doc"
Else
FileExt = ".docx"
End If
SaveName = Environ("UserProfile") amp; "DesktopReport for " amp; _
Organisation amp; " " amp; _
Format(Now, "yyyy-mm-dd hh-mm-ss") amp; FileExt
If WdApp.Version <= 12 Then
' .ActiveDocument.SaveAs SaveName
wdDoc.SaveAs SaveName
Else
' .ActiveDocument.SaveAs2 SaveName
wdDoc.SaveAs2 SaveName
End If
SaveNamePDF = Environ("UserProfile") amp; "DesktopReport " amp; _
Organisation amp; " " amp; _
Format(Now, "yyyy-mm-dd hh-mm-ss") amp; ".pdf"
wdDoc.ExportAsFixedFormat _
OutputFileName:=SaveNamePDF, _
ExportFormat:=wdExportFormatPDF _
wdDoc.Close
FilterValue = FilterValue - 1
Sheets("Static").Delete
Application.DisplayAlerts = True
Loop
WdApp.Quit
Set WdApp = Nothing
End Sub
Ответ №1:
Вы можете или не хотите воспринимать это как ответ на свой вопрос, но вот несколько способов улучшить свой код, чтобы получить лучший контроль над тем, как он работает. Это не «решение» в смысле завершенного, корректно работающего модуля кода, но если вы примете этот совет, он должен позволить вам решить проблему самостоятельно (наряду со многими другими проблемами, с которыми вы могли бы столкнуться в будущем).
(1) Избегайте использования Copy
и Paste
. Как вы правильно заметили, это ставит вас во власть буфера обмена Windows. Вместо этого присвоите исходный объект или значение переменной, а затем вставьте содержимое переменной в пункт назначения. Например:
Organisation = WS.Range("D2").Value
wdDoc.Bookmarks("Organisation").Range.Text = Organisation
Теперь вы можете управлять тем, что вставляется в точку назначения. Помимо прочего, вы можете сбросить переменную в конце каждого цикла, чтобы исключить риск повторной вставки объекта или значения, которые могут быть перенесены из одного цикла цикла в следующий.
(2) Используйте With ... End With
для явного указания родителей ваших объектов. Таким образом, вы не рискуете случайно ссылаться на другой объект, чем ожидали. Например, в этом отрывке из вашего кода …
With WdApp
Set wdDoc = .Documents.Add("C:UsersdavidDocumentsCustom Office TemplatesTemplate2.dotx")
End With
Range("A1", Range("A1").End(xlDown).End(xlToRight)).Copy
… диапазон, скопированный в последней строке, может быть не таким, как вы ожидаете. Если активным объектом в данный момент является ваш недавно созданный документ Word, Range
объект может быть интерпретирован как некоторый диапазон в документе, а не диапазон электронной таблицы, который вы хотели скопировать.
Чтобы оставаться под контролем, используйте With
последовательно:
With WdApp
Set wdDoc = .Documents.Add("C:UsersdavidDocumentsCustom Office TemplatesTemplate2.dotx")
End With
With MyWorkbook.Sheets("MySheet")
Set MyTableRange = .Range("A1", .Range("A1").End(xlDown).End(xlToRight))
End With
Это всего лишь пара указателей, но это основные хорошие методы кодирования, которые вы должны принять. Я думаю, что они помогут вам решить проблемы с вашим кодом.
Комментарии:
1. Вы ошибаетесь во втором пункте. Поскольку код размещен в Excel
Range
, он всегда будет интерпретироваться как диапазон Excel. Единственная временная путаница возникает, если вы объявляете переменную, напримерDim WdRange as Range
, вместоDim WdRange as Word.Range
2. … например, в объявлении
Dim Rng As Range
прямо здесь, в вопросе OP. Ваш комментарий сводится к идее, что OP будет в порядке, если продолжать полагаться на неявные ссылки, пока объявление переменной исправлено. На мой взгляд, это очень бесполезный совет (не для меня, а для нового программиста, который опубликовал этот вопрос).3. Все полагаются на неявные ссылки в своем коде, даже вы. Каждый раз, когда вы объявляете переменную
As String
илиAs Long
, или используете функцию, такую какLeft
,Split
, илиTrim
, вы полагаетесь на неявную ссылку на библиотеку VBA. Даже ваш пример кода включает две неявные ссылки. Чтобы избежать ихxlDown
иxlToRight
должны быть записаны какExcel.xlDown
иExcel.xlToRight
.4. Где вы ошибаетесь во втором пункте, это утверждение, которое
Range("A1", Range("A1").End(xlDown).End(xlToRight)).Copy
может быть истолковано как принадлежащее Word. Этого не могло произойти. Однако, если был активирован другой лист в рабочей книге, диапазон может быть взят не с того листа. Я согласен, что хорошей практикой является конкретизация рабочей книги / рабочего листа, на который ссылаются. Вы просто использовали неправильный вывод, чтобы проиллюстрировать свою точку зрения.
Ответ №2:
Проблемы, которые вы видите, вероятно, вызваны использованием буфера обмена. При копировании и вставке больших элементов, например таблиц данных, диаграмм, может возникнуть задержка.
Когда вы используете буфер обмена, вы передаете некоторый контроль ОС. VBA включает функцию, DoEvents
которая передает управление операционной системе. Затем управление возвращается после того, как операционная система завершит обработку событий в своей очереди. Добавляя это после каждого копирования / вставки, это должно позволить наверстать упущенное.
Вы также можете немного сократить использование буфера обмена, установив значения для «Организация» и «Пациенты мужского пола» напрямую.
Sub CreateBasicWordReport()
Dim WdApp As Word.Application
Dim wdDoc As Word.Document
Dim SaveName As String
Dim FileExt As String
Dim LstObj1 As ListObject
Dim MaxValue As Integer
Dim FilterValue As Integer
Dim Organisation As String
Dim Rng As Range
Dim WS As Worksheet
Application.DisplayAlerts = False
Set LstObj1 = Worksheets("Sheet1").ListObjects("Table1")
MaxValue = WorksheetFunction.Max(LstObj1.ListColumns(1).Range)
FilterValue = MaxValue
Set WdApp = CreateObject("Word.Application")
Do Until FilterValue = 0
Sheets.Add(After:=Sheets("Sheet1")).Name = "Static"
Sheets("Sheet1").Select
With WdApp
.Visible = True
.Activate
'create new document and assign to object variable
Set wdDoc = .Documents.Add("C:UsersdavidDocumentsCustom Office TemplatesIBD Registry Quarterly Report Template2.dotx")
'now mostly finished with WdApp as from here wdDoc is used
End With
ActiveSheet.ListObjects("Table1").Range.AutoFilter Field:=1, Criteria1:=FilterValue
Range("F11").Select
Range("A1", Range("A1").End(xlDown).End(xlToRight)).Copy
wdDoc.Bookmarks("TableLocation").Range.Paste
DoEvents
For Each Row In Range("Table1[#All]").Rows
If Row.EntireRow.Hidden = False Then
If Rng Is Nothing Then Set Rng = Row
Set Rng = Union(Row, Rng)
End If
Next Row
Set WS = Sheets("Static")
Rng.Copy Destination:=WS.Range("A1")
Application.CutCopyMode = False
DoEvents
Organisation = WS.Range("D2").Value
wdDoc.Bookmarks("Organisation").Range.Text = Organisation
wdDoc.Bookmarks("MalePatients").Range.Text = WS.Range("F2").Text
Chart2.ChartArea.Copy
wdDoc.Bookmarks("ChartLocation").Range.Paste
DoEvents
Application.CutCopyMode = False
If CLng(WdApp.Version) <= 11 Then
FileExt = ".doc"
Else
FileExt = ".docx"
End If
SaveName = Environ("UserProfile") amp; "DesktopIBD Registry Quarterly Report for " amp; _
Organisation amp; " " amp; _
Format(Now, "yyyy-mm-dd hh-mm-ss")
SaveNamePDF = SaveName amp; ".pdf"
SaveName = SaveName amp; FileExt
If CLng(WdApp.Version) <= 12 Then
wdDoc.SaveAs SaveName
Else
wdDoc.SaveAs2 SaveName
End If
wdDoc.ExportAsFixedFormat _
OutputFileName:=SaveNamePDF, _
ExportFormat:=wdExportFormatPDF _
wdDoc.Close
FilterValue = FilterValue - 1
Sheets("Static").Delete
Application.DisplayAlerts = True
Loop
WdApp.Quit
Set WdApp = Nothing
End Sub