Delete last section in Word VBA without the previous heading getting overwritten - vba

I have the following code that I found when googleing on the problem. The problem with this code is that it overwrites the next-to-last section header (and footer though I only need the header preserved) to that of the last section, which is the default (strange) behavior of Word.
Is there a workaround to this in VBA?
Here is the code that has the inherent fault:
Sub DeleteLastSection()
'Deletes last section of a document including
'the section break
Dim doc As Document
Dim rng As Range
Dim ctr As Integer
Set doc = ActiveDocument
ctr = doc.Sections.Count
Set rng = doc.Sections(ctr).Range
Dim myheader As HeaderFooter
If ctr > 1 Then
With rng
.Select
.MoveStart Unit:=wdCharacter, Count:=-1
.Delete
End With
End If
End Sub
Note: The entire range of the last section is being deleted by the code and that is the required behavior. The inherent problem in the default behavior of Word is what I needed a workaround for in VBA code. One can found complex manual procedures to avoid it, but I needed a simple approach in code.

The problem here lies in the fact that the section break carries the section information. If you delete it, the last section becomes part of the section before. The trick I use below is to create a continuous section break instead of a page break, and then do all the rest:
Sub DeleteLastSection()
'Deletes last section of a document including
'the section break
Dim doc As Document
Dim rng As Range
Dim NewEndOfDocument As Range
Dim ctr As Integer
Set doc = ActiveDocument
ctr = doc.Sections.Count
Set rng = doc.Sections(ctr).Range
If ctr > 1 Then
' Create a section break at the end of the second to last section
Set NewEndOfDocument = doc.Sections(ctr - 1).Range
NewEndOfDocument.EndOf wdSection, wdMove
doc.Sections.Add NewEndOfDocument, wdSectionContinuous
With rng
.Select
.MoveStart Unit:=wdCharacter, Count:=-1
.Delete
End With
End If
End Sub

Ordinarily, deleting a Section break causes the Section preceding the break to assume the page layout of the following Section. The following macro works the other way, across multiple (selected) Section breaks. All common page layout issues (margins, page orientation, text columns, headers & footers) are addressed. As you can see by studying the code, it's no trivial task to do all these things.
Sub MergeSections()
Application.ScreenUpdating = False
Dim sPageHght As Single, sPageWdth As Single
Dim sHeaderDist As Single, sFooterDist As Single
Dim sTMargin As Single, sBMargin As Single
Dim sLMargin As Single, sRMargin As Single
Dim sGutter As Single, sGutterPos As Single
Dim lPaperSize As Long, lGutterStyle As Long
Dim lMirrorMargins As Long, lVerticalAlignment As Long
Dim lScnStart As Long, lScnDir As Long
Dim lOddEvenHdFt As Long, lDiffFirstHdFt As Long
Dim bTwoPagesOnOne As Boolean, bBkFldPrnt As Boolean
Dim bBkFldPrnShts As Boolean, bBkFldRevPrnt As Boolean
Dim lOrientation As Long, oHdFt As HeaderFooter
Dim Sctn1 As Section, Sctn2 As Section
With Selection
If .Sections.Count = 1 Then
MsgBox "Selection does not span a Section break", vbExclamation
Exit Sub
End If
Set Sctn1 = .Sections.First: Set Sctn2 = .Sections.Last
With Sctn1.PageSetup
lPaperSize = .PaperSize
lGutterStyle = .GutterStyle
lOrientation = .Orientation
lMirrorMargins = .MirrorMargins
lScnStart = .SectionStart
lScnDir = .SectionDirection
lOddEvenHdFt = .OddAndEvenPagesHeaderFooter
lDiffFirstHdFt = .DifferentFirstPageHeaderFooter
lVerticalAlignment = .VerticalAlignment
sPageHght = .PageHeight
sPageWdth = .PageWidth
sTMargin = .TopMargin
sBMargin = .BottomMargin
sLMargin = .LeftMargin
sRMargin = .RightMargin
sGutter = .Gutter
sGutterPos = .GutterPos
sHeaderDist = .HeaderDistance
sFooterDist = .FooterDistance
bTwoPagesOnOne = .TwoPagesOnOne
bBkFldPrnt = .BookFoldPrinting
bBkFldPrnShts = .BookFoldPrintingSheets
bBkFldRevPrnt = .BookFoldRevPrinting
End With
With Sctn2.PageSetup
.GutterStyle = lGutterStyle
.MirrorMargins = lMirrorMargins
.SectionStart = lScnStart
.SectionDirection = lScnDir
.OddAndEvenPagesHeaderFooter = lOddEvenHdFt
.DifferentFirstPageHeaderFooter = lDiffFirstHdFt
.VerticalAlignment = lVerticalAlignment
.PageHeight = sPageHght
.PageWidth = sPageWdth
.TopMargin = sTMargin
.BottomMargin = sBMargin
.LeftMargin = sLMargin
.RightMargin = sRMargin
.Gutter = sGutter
.GutterPos = sGutterPos
.HeaderDistance = sHeaderDist
.FooterDistance = sFooterDist
.TwoPagesOnOne = bTwoPagesOnOne
.BookFoldPrinting = bBkFldPrnt
.BookFoldPrintingSheets = bBkFldPrnShts
.BookFoldRevPrinting = bBkFldRevPrnt
.PaperSize = lPaperSize
.Orientation = lOrientation
End With
With Sctn2
For Each oHdFt In .Footers
oHdFt.LinkToPrevious = Sctn1.Footers(oHdFt.Index).LinkToPrevious
If oHdFt.LinkToPrevious = False Then
Sctn1.Headers(oHdFt.Index).Range.Copy
oHdFt.Range.Paste
End If
Next
For Each oHdFt In .Headers
oHdFt.LinkToPrevious = Sctn1.Headers(oHdFt.Index).LinkToPrevious
If oHdFt.LinkToPrevious = False Then
Sctn1.Headers(oHdFt.Index).Range.Copy
oHdFt.Range.Paste
End If
Next
End With
While .Sections.Count > 1
.Sections.First.Range.Characters.Last.Delete
Wend
Set Sctn1 = Nothing: Set Sctn2 = Nothing
End With
Application.ScreenUpdating = True
End Sub

Looking more into this on my own (I had to solve the issue in short order and could not wait), I came to the same conclusion as was noted in the comment by #CindyMeister that when deleting the last "section break" in actual fact the next-to-last section is being deleted, and what data and formatting heretofore belonged to the last section is apparently inherited by the new last section (i.e. the earlier next-to-last section). But in reality the last section remained and only the section break was deleted, so what was deleted was the next-to-last section (and the actual pages from the last section).
I found that the LinkToPrevious property of the HeaderFooter object allows a simplistic approach to "inherit" the settings from the previous section.
So by adding a few lines to set this property to true in each instance and then change it back to false, I can get the required behavior of the next-to-last section remaining the same as before.
(Please note that it worked for me because I simply had different text in the primary header, and did not have special formatting and else. But I suspect that based on the workings of the LinkToPrevious property this is a panacea. Please comment if otherwise.)
These are the lines to set the property:
for each hf in .Sections(1).Headers
hf.LinkToPrevious = True
hf.LinkToPrevious = False
next
for each hf in .Sections(1).Footers
hf.LinkToPrevious = True
hf.LinkToPrevious = False
next
The full working code for progeny:
Sub DeleteLastSection()
'Deletes last section of a document including
'the section break
Dim doc As Document
Dim rng As Range
Dim ctr As Integer
Set doc = ActiveDocument
ctr = doc.Sections.Count
Set rng = doc.Sections(ctr).Range
Dim myheader As HeaderFooter
If ctr > 1 Then
With rng
'Added lines to "inherit" the settings from the next-to-last section
for each hf in .Sections(1).Headers
hf.LinkToPrevious = True
hf.LinkToPrevious = False
next
for each hf in .Sections(1).Footers
hf.LinkToPrevious = True
hf.LinkToPrevious = False
next
.Select
.MoveStart Unit:=wdCharacter, Count:=-1
.Delete
End With
End If
End Sub

Deleting the last section of a word document is not a trivial task.
Things you might have to do if items are different between the 'next to last' and 'last' section of a document.
Ensure that in the last section any 'linktoprevious' in a header or footer is set to to false
Copy all headers and footers from the next to last section to the last section
Copy the relevant page format items of the next to last section to the last section (paper size, orientation, margins etc)
Get the range for the last section in the document. Move the end of the range backward until the ascii value is >=32.
Then you can safely delete the adjusted range from your document without any nasty side effects

This is the code I just created that works well:
Sub DeleteLastPage()
Dim pgSetUp As PageSetup
Dim iSect As Integer
iSect = ActiveDocument.Sections.Count - 1
Set pgSetUp = ActiveDocument.Sections(iSect).PageSetup
With ActiveDocument.Sections.Last.PageSetup
.LineNumbering.Active = pgSetUp.LineNumbering.Active
.Orientation = pgSetUp.Orientation
.TopMargin = pgSetUp.TopMargin
.BottomMargin = pgSetUp.BottomMargin
.LeftMargin = pgSetUp.LeftMargin
.RightMargin = pgSetUp.RightMargin
.Gutter = pgSetUp.Gutter
.HeaderDistance = pgSetUp.HeaderDistance
.FooterDistance = pgSetUp.FooterDistance
.PageWidth = pgSetUp.PageWidth
.PageHeight = pgSetUp.PageHeight
.SectionStart = wdSectionNewPage
.OddAndEvenPagesHeaderFooter = pgSetUp.OddAndEvenPagesHeaderFooter
.DifferentFirstPageHeaderFooter = pgSetUp.DifferentFirstPageHeaderFooter
.VerticalAlignment = wdAlignVerticalTop
End With
With ActiveDocument.Sections.Last.Headers(wdHeaderFooterPrimary)
.LinkToPrevious = true
End With
With ActiveDocument.Sections.Last.Footers(wdHeaderFooterPrimary)
.LinkToPrevious = true
End With
ActiveDocument.Sections.Last.Range.Characters.Delete
Selection.EndKey Unit:=wdStory
Selection.MoveLeft Unit:=wdCharacter, Count:=1, Extend:=wdExtend
Selection.Delete Unit:=wdCharacter,Count:=1
End Sub

Related

VBA | Word <--> Table Column Alignment

got a problem with the right alignment from columns in a word document.
I'm creating a word document with a table from one of our systems. The first time creating the table and selecting the columns for alignment will work without problems. If the user now creates a new document, overwriting the old one, it will crash. If the new word document is created, without overwriting an old one, no errors occur.
So the combination out of overwriting an existing document, there aren't any word processes running, and selecting columns for right alignment will crash. This is how I try to align the columns.
objTable.Columns(4).Select
Selection.ParagraphFormat.Alignment = wdAlignParagraphRight
objTable.Columns(5).Select
Selection.ParagraphFormat.Alignment = wdAlignParagraphRight
objTable.Columns(6).Select
Selection.ParagraphFormat.Alignment = wdAlignParagraphRight
Does anyone have an idea how to fix this?
Thanks
€dit:
We have software, where a user can create a Word document. The word document loads a Word Template, in which a bookmark marks the location for creating the table. Before the table is created, the new document from the template will be saved on a network path. If there is already a document from that template, it should be overwritten. After saving the document for the first time and giving the right name, my method creates the table and fills it with content. The creating part will crash as soon as my method tries to align the columns(alignment-part in the code above) if there was a document created before. I took a look at the task manager there were no running word processes left after the first run. If a new word document is created, without overwriting an existing one, there are no problems with the alignment. So I guess the combination of overwriting an existing document and the alignment is responsible for the error.
€dit2 - My Code (I removed unneccessary lines of code like variable declaration):
'That is kind of strange, because even though it should be nothing it skipped that part - But if it tries to use the existing word instance - it crashes with the 462 - remote-server-computer is not available.
If app is Nothing Then
Set app = New Word.Application
Exit Function
End If
Set document = app.Documents.Add(Template:=Template, NewTemplate:=False, DocumentType:=0)
Dim settings As settings
settings = exportWord (document,...)
Private Function exportWord (oDoc As Word.Document, ...) As settings
On Error GoTo Err_WordExport
Dim sets As settings
With sets
.export = False
End With
exportWord = sets
Dim objRange As Word.Range
Dim objTable As Word.Table
With oDoc
Set objRange = .Bookmarks("tbl").Range
.Tables.Add objRange, positionen.Count + 1, 6
Set objTable = .Bookmarks("tbl").Range.Tables(1)
End With
With objTable
With .Rows(1)
.Cells(1).Range.Text = ""
.Cells(2).Range.Text = ""
.Cells(3).Range.Text = ""
.Cells(4).Range.Text = ""
.Cells(5).Range.Text = ""
.Cells(6).Range.Text = ""
.Cells(1).Range.Font.Bold = True
.Cells(2).Range.Font.Bold = True
.Cells(3).Range.Font.Bold = True
.Cells(4).Range.Font.Bold = True
.Cells(5).Range.Font.Bold = True
.Cells(6).Range.Font.Bold = True
End With
End With
Dim i As Long
i = 2
For Each ItemPos In Positionen
'fill the content
Next ItemPos
With objTable.Rows(1).Borders(wdBorderBottom)
.Visible = True
.LineStyle = wdLineStyleDouble
End With
objTable.Columns(4).Select
Selection.ParagraphFormat.Alignment = wdAlignParagraphRight
objTable.Columns(5).Select
Selection.ParagraphFormat.Alignment = wdAlignParagraphRight
objTable.Columns(6).Select
Selection.ParagraphFormat.Alignment = wdAlignParagraphRight
objTable.Columns.AutoFit
oDoc.SaveAs2 pathToSave
With sets
.export = True
.PathToFile = pathToSave
End With
exportWord = sets
Set objTable = Nothing
End Function
You can avoid selecting the columns by looping through the cells. You can also simplify your code as below:
Set objTable = oDoc.Tables.Add(oDoc.Bookmarks("tbl").Range, Positionen.Count + 1, 6)
With objTable
Dim wdCell As Word.Cell
With .Rows(1).Borders(wdBorderBottom)
.Visible = True
.LineStyle = wdLineStyleDouble
End With
For Each wdCell In .Rows(1).Cells
With wdCell.Range
.Text = ""
.Font.Bold = True
End With
Next wdCell
Dim colIndex As Long
For colIndex = 4 To 6
For Each wdCell In .Columns(colIndex).Cells
wdCell.Range.ParagraphFormat.Alignment = wdAlignParagraphRight
Next wdCell
Next colIndex
End With
You can refine this even further simply by adding a 2 row table into the template with the formatting already applied. Then all you need is:
Set objTable = oDoc.Bookmarks("tbl").Range.Tables(1)
Dim i As Long
For i = 1 To positionen - 1
objTable.Rows.Add
Next i

Word VBA Move Images and Text into a Table

I'm trying to check every section of a document for images or grouped images and, if they're found, create a table with 1 row and 2 columns at the beginning of that section, where the first column will contain the text (with original formatting) and the second column will contain the images. I have converted all the images in the document to inline shapes.
Edit: In the document, there's random amounts of text (and/or other characters) before, after and in between a random amount of images. Sometimes a section has no text and only images. For each section, I would like all of the text (with original formatting and in the order in which it occurs) to be contained in the first column, and all images and grouped images (also in their same order) to be contained in the second folder. Ideally, if the only things on the page are a heading and an image, they would be put into a 1x1 table (with the heading above the image).
I've tried a few variations of this with no success. Generally stuff starts getting pretty messy because I have no idea what I'm doing. I've left out the text in this code because it was only complicating things, but I would like to move the text as well.
Sub ToTables()
Dim iShp As InlineShape
Dim oRng As Range
Dim oTbl As Table
Dim i As Integer
Dim a As Integer
Dim b As Integer
a = ActiveDocument.BuiltInDocumentProperties("Number of Sections")
For i = 1 To a
Set oRng = ActiveDocument.GoTo(What:=wdGoToSection, Name:=i)
Set oRng = oRng.GoTo(What:=wdGoToBookmark, Name:="\section")
If Right(oRng, 1) = vbCr Then _
oRng = Left(oRng, Len(oRng) - 1)
b = oRng.InlineShapes.Count
If b >= 1 Then
oRng.Collapse Direction:=wdCollapseStart
Set oTbl = oRng.Tables.Add(oRng, 1, 2, AutoFitBehavior:=wdAutoFitContent)
For Each iShp In oRng.InlineShapes
iShp.Select
Selection.Cut
oTbl.Cell(1, 2).Range.Paste
Next iShp
End If
Next i
End Sub
Thanks
Try the revised code:
Sub Demo()
Application.ScreenUpdating = False
Dim Sctn As Section, Rng As Range, Tbl As Table, s As Long, w As Single
For Each Sctn In ActiveDocument.Sections
Set Rng = Sctn.Range: w = 0
Rng.End = Rng.End - 1
Set Tbl = Rng.ConvertToTable(, NumRows:=1, NumColumns:=1, InitialColumnWidth:=50, AutoFit:=True)
With Tbl
.Columns.Add
For s = .Range.InlineShapes.Count To 1 Step -1
With .Range.InlineShapes(s)
If .Width > w Then w = .Width
.Range.Rows(1).Cells(2).Range.FormattedText = .Range.FormattedText
.Delete
End With
Next
.Columns(1).Cells.Merge
.Columns(2).Cells.Merge
.PreferredWidthType = wdPreferredWidthPercent
.PreferredWidth = 100
If w > 0 Then .Columns(2).Width = w + .LeftPadding + .RightPadding
.Rows.HeightRule = wdRowHeightAuto
End With
Next
Application.ScreenUpdating = True
End Sub
Assuming the text precedes the inlineshapes:
Sub Demo()
Application.ScreenUpdating = False
Dim iShp As InlineShape
For Each iShp In ActiveDocument.InlineShapes
With iShp.Range
.Characters.First.Previous = vbTab
.Start = .Paragraphs.First.Range.Start
.ConvertToTable vbTab, 1, 2
End With
Next
Application.ScreenUpdating = True
End Sub
The above code assumes there is a single character between the text & inlineshape. That character could be a space, paragraph break, line break, anything at all.

I want to copy all highlighted and shaded text from Word file to Excel along with the colors through VBA

I want to copy all Highlighted and Shaded text from Word file to Excel with same color in Word file through VBA.
I was able to copy only highlighted text from word to word. But the actual task is to copy all highlighted and shaded text to Excel and sort all the data according to color in Excel.
I use this code and it works fine to just copy from word to word but there is no formatting this code copies only text no colors;
Sub ExtractHighlightedText()
Dim oDoc As Document
Dim s As String
With Selection
.HomeKey Unit:=wdStory
With .Find
.ClearFormatting
.Text = ""
.Highlight = True
Do While .Execute
s = s & Selection.Text & vbCrLf
Loop
End With
End With
Set oDoc = Documents.Add
oDoc.Range.InsertAfter s
End Sub
Code for converting from Shaded to Highlighted:
Sub ConvertTextsFromShadedToHighlighted()
Dim objParagraph As Paragraph
Dim objCharacterRange As Range
For Each objParagraph In ActiveDocument.Paragraphs
If objParagraph.Range.Information(wdWithInTable) = False Then
If objParagraph.Range.Shading.BackgroundPatternColor <> wdColorAutomatic
Then
objParagraph.Range.Shading.BackgroundPatternColor = wdColorAutomatic
objParagraph.Range.HighlightColorIndex = wdPink
End If
End If
Next objParagraph
For Each objCharacterRange In ActiveDocument.Characters
if objCharacterRange.Font.Shading.BackgroundPatternColor <>
wdColorAutomatic Then
objCharacterRange.Font.Shading.BackgroundPatternColor = wdColorAutomatic
objCharacterRange.HighlightColorIndex = wdPink
End If
Next objCharacterRange
End Sub
May try something like this
Edit: Tried to include Extraction of Shaded text (Any color) along with Highlighted text by using tow finds. Following workaround methods are adopted
For finding Shaded text (of any color) find is executed for .Font.Shading.BackgroundPatternColor = wdColorAutomatic and the range excluding that selection was picked up as shaded text and color. Method somehow crudely performing when selection contain pure text characters but still picking up wrong color value when selection contain non text characters (i.e. paragraph marks etc). Otherwise it is working up to expectation. Otherwise there is always another option open to iterate through all the characters in the documents. But that option was left out as it is very slow and impractical for large documents.
As no simple method (or property) found by me to convert HighlightColorIndex to RGB color value, The same was applied to one character's Font.ColorIndex and later extracted as Font.Color
So finally the solution become messy and somehow crude, I am not at all satisfied with and more answers are invited from experts for simple direct solutions in these regards.
Code:
Option Explicit
Sub ExtractHighShadeText()
Dim Exc As Excel.Application
Dim Wb As Excel.Workbook
Dim Ws As Excel.Worksheet
Dim s As String, Rw As Long
Set Exc = CreateObject("Excel.Application")
Exc.Visible = True
Set Wb = Exc.Workbooks.Add
Set Ws = Wb.Sheets(1)
Rw = 0
Dim Rng As Range, StartChr As Long, EndChr As Long, OldColor As Long, Clr As Long
''''''''''''''''''''HiLight''''''''''''''''''
Set Rng = ActiveDocument.Characters(1)
OldColor = Rng.Font.Color
Selection.HomeKey Unit:=wdStory
With Selection.Find
.ClearFormatting
.Text = ""
.Highlight = True
Do While .Execute
'These two line Converting HighlightColorIndex to RGB Color
Rng.Font.ColorIndex = Selection.Range.HighlightColorIndex
Clr = Rng.Font.Color
Rw = Rw + 1
Ws.Cells(Rw, 1).Value = Selection.Text
'Ws.Cells(Rw, 1).Interior.ColorIndex = Selection.Range.HighlightColorIndex
Ws.Cells(Rw, 1).Interior.Color = Clr
'For sorting on HighlightColorIndex
'Ws.Cells(Rw, 2).Value = Selection.Range.HighlightColorIndex
'For sorting on HighlightColorIndex RGB value
Ws.Cells(Rw, 2).Value = Clr
Loop
End With
Rng.Font.Color = OldColor
'''End Hilight''''''''''''''''''''''''''''''
'WorkAround used for converting highlightColorIndex to Color RGB value
StartChr = 1
EndChr = 0
Set Rng = ActiveDocument.Characters(1)
Selection.HomeKey Unit:=wdStory
With Selection.Find
.ClearFormatting
.Text = ""
'.Highlight = True
.Font.Shading.BackgroundPatternColor = wdColorAutomatic
Do While .Execute
EndChr = Selection.Start
Debug.Print Selection.Start, Selection.End, StartChr, EndChr, IIf(EndChr > StartChr, "-OK", "")
If EndChr > StartChr Then
Set Rng = ActiveDocument.Range(Start:=StartChr, End:=EndChr)
Clr = Rng.Font.Shading.BackgroundPatternColor
Rw = Rw + 1
Ws.Cells(Rw, 1).Value = Rng.Text
Ws.Cells(Rw, 1).Interior.Color = Clr
Ws.Cells(Rw, 2).Value = Clr
End If
StartChr = Selection.End
Loop
If EndChr > StartChr Then
Set Rng = ActiveDocument.Range(Start:=StartChr, End:=EndChr)
Clr = Rng.Font.Shading.BackgroundPatternColor
Rw = Rw + 1
Ws.Cells(Rw, 1).Value = Rng.Text
Ws.Cells(Rw, 1).Interior.Color = Clr
Ws.Cells(Rw, 2).Value = Clr
End If
End With
If Rw > 1 Then
Ws.Range("A1:B" & Rw).Sort Key1:=Ws.Range("B1"), Order1:=xlAscending, Header:=xlNo
Ws.Range("B1:B" & Rw).ClearContents
End If
End Sub

MSWord - Leading inverted comma doesn't take font via VBA

I've been tasked with writing some VBA to combine a number of MSWord documents into a single document and format it based on certain styles in the original. One of the formatting changes is a different font. The document owner can't use styles in the final document.
My code works other than for one issue: If the paragraph is dialogue, ie it starts with an inverted comma, the new font is adopted apart from the very first inverted comma which has the default document font. Even if I set the .Range.Font property on the character, parapgraph or entire document, the default font of that inverted comma won't change. And yet it does if I manually select the text and set the font from my dropdown.
Does anyone know what I'm missing here? How do I ensure that leading inverted commas in a paragraph adopt the assigned font? The relevant parts of the code are as follows:
Dim fd As FileDialog
Dim txDoc As Document, refDoc As Document
Dim rng As Range, chr As Range
Dim para As Paragraph
Dim pt(1) As Long
Dim i As Long, p As Long
'... some code to open the files and find the relevant parts.
'Transfer the text.
'-> refDoc is the new document to which the old documents are transferred.
'-> txDoc is one of the old documents.
'-> Both are opened from a FileDialog routine.
With refDoc
.Range(.Content.End - 1, .Content.End - 1).InsertBreak wdPageBreak
pt(0) = .Content.End - 1
.Range.InsertAfter txDoc.Content.Text
pt(1) = .Content.End - 1
End With
'Set the basic format.
With refDoc.Range(pt(0), pt(1))
With .Font
.Name = "Sabon LT"
.Size = 12
End With
With .ParagraphFormat
.LeftIndent = CentimetersToPoints(0)
.RightIndent = CentimetersToPoints(0)
.SpaceBefore = 0
.SpaceBeforeAuto = False
.SpaceAfter = 0
.SpaceAfterAuto = False
.LineSpacingRule = wdLineSpaceDouble
.Alignment = wdAlignParagraphLeft
.WidowControl = False
.KeepWithNext = False
.KeepTogether = False
.PageBreakBefore = False
.NoLineNumber = False
.Hyphenation = False
.FirstLineIndent = CentimetersToPoints(1)
.OutlineLevel = wdOutlineLevelBodyText
.CharacterUnitLeftIndent = 0
.CharacterUnitRightIndent = 0
.CharacterUnitFirstLineIndent = 0
.LineUnitBefore = 0
.LineUnitAfter = 0
.MirrorIndents = False
.TextboxTightWrap = wdTightNone
.CollapsedByDefault = False
End With
End With
'... some additional formatting code.

Run Time Error '1004': Paste Method Of worksheet Class Failed error

Copy pasting 1 line of text from word to excel using VBA.
When the code reaches the below line I am getting the below error.
ActiveSheet.Paste
Run Time Error '1004': Paste Method Of worksheet Class Failed error
But if I click Debug button and press F8 then it's pasting the data in excel without any error.
This error occurs each time the loop goes on and pressing debug and F8 pasting the data nicely.
I did several testing and unable to find the root cause of this issue.
Also used DoEvents before pasting the data code but nothing worked.
Any suggestions?
EDIT:-
I am posting the code since both of you are saying the same. Here is the code for your review.
Sub FindAndReplace()
Dim vFR As Variant, r As Range, i As Long, rSource As Range
Dim sCurrRep() As String, sGlobalRep As Variant, y As Long, x As Long
Dim NumCharsBefore As Long, NumCharsAfter As Long
Dim StrFind As String, StrReplace As String, CountNoOfReplaces As Variant
'------------------------------------------------
Dim oWord As Object
Const wdReplaceAll = 2
Set oWord = CreateObject("Word.Application")
'------------------------------------------------
Application.ScreenUpdating = False
vFR = ThisWorkbook.Sheets("Sheet1").Range("A1").CurrentRegion.Value
On Error Resume Next
Set rSource = Cells.SpecialCells(xlCellTypeConstants)
On Error GoTo 0
If Not rSource Is Nothing Then
For Each r In rSource.Cells
For i = 2 To UBound(vFR)
If Trim(vFR(i, 1)) <> "" Then
With oWord
.Documents.Add
DoEvents
r.Copy
.ActiveDocument.Content.Paste
NumCharsBefore = .ActiveDocument.Characters.Count
With .ActiveDocument.Content.Find
.ClearFormatting
.Font.Bold = False
.Replacement.ClearFormatting
.Execute FindText:=vFR(i, 1), ReplaceWith:=vFR(i, 2), Format:=True, Replace:=wdReplaceAll
End With
.Selection.Paragraphs(1).Range.Select
.Selection.Copy
r.Select
ActiveSheet.Paste'Error occurs in this line pressing debug and F8 is pasting the data
StrFind = vFR(i, 1): StrReplace = vFR(i, 2)
NumCharsAfter = .ActiveDocument.Characters.Count
CountNoOfReplaces = (NumCharsBefore - NumCharsAfter) / (Len(StrFind) - Len(StrReplace))
.ActiveDocument.UndoClear
.ActiveDocument.Close SaveChanges:=False
If CountNoOfReplaces Then
x = x + 1
ReDim Preserve sCurrRep(1 To 3, 1 To x)
sCurrRep(1, x) = vFR(i, 1)
sCurrRep(2, x) = vFR(i, 2)
sCurrRep(3, x) = CountNoOfReplaces
End If
CountNoOfReplaces = 0
End With
End If
Next i
Next r
End If
oWord.Quit
'Some more gode goes here... which is not needed since error occurs in the above loop
End Sub
If you want to know why I have chosen word for replacement then please go through the below link.
http://www.excelforum.com/excel-programming-vba-macros/1128898-vba-characters-function-fails-when-the-cell-content-exceeds-261-characters.html
Also used the code from the below link to get the number of replacements count.
http://word.mvps.org/faqs/macrosvba/GetNoOfReplacements.htm
Characters(start, length).Delete() method really seems not to work with longer strings in Excel :(. So a custom Delete() method could be written which will work with decoupled formating informations and texts. So the text of the cell can be modified without loosing the formating information. HTH.
Add new class named MyCharacter. It will contain information about text and
formating of one character:
Public Text As String
Public Index As Integer
Public Name As Variant
Public FontStyle As Variant
Public Size As Variant
Public Strikethrough As Variant
Public Superscript As Variant
Public Subscript As Variant
Public OutlineFont As Variant
Public Shadow As Variant
Public Underline As Variant
Public Color As Variant
Public TintAndShade As Variant
Public ThemeFont As Variant
Add next new class named MyCharcters and wrap the code of the new
Delete method in it. With Filter method a new collection of MyCharacter is created. This collection contains only the characters which should remain. Finally in method Rewrite the text is re-written from this collection back to target range along with formating info:
Private m_targetRange As Range
Private m_start As Integer
Private m_length As Integer
Private m_endPosition As Integer
Public Sub Delete(targetRange As Range, start As Integer, length As Integer)
Set m_targetRange = targetRange
m_start = start
m_length = length
m_endPosition = m_start + m_length - 1
Dim filterdChars As Collection
Set filterdChars = Filter
Rewrite filterdChars
End Sub
Private Function Filter() As Collection
Dim i As Integer
Dim newIndex As Integer
Dim newChar As MyCharacter
Set Filter = New Collection
newIndex = 1
For i = 1 To m_targetRange.Characters.Count
If i < m_start Or i > m_endPosition Then
Set newChar = New MyCharacter
With newChar
.Text = m_targetRange.Characters(i, 1).Text
.Index = newIndex
.Name = m_targetRange.Characters(i, 1).Font.Name
.FontStyle = m_targetRange.Characters(i, 1).Font.FontStyle
.Size = m_targetRange.Characters(i, 1).Font.Size
.Strikethrough = m_targetRange.Characters(i, 1).Font.Strikethrough
.Superscript = m_targetRange.Characters(i, 1).Font.Superscript
.Subscript = m_targetRange.Characters(i, 1).Font.Subscript
.OutlineFont = m_targetRange.Characters(i, 1).Font.OutlineFont
.Shadow = m_targetRange.Characters(i, 1).Font.Shadow
.Underline = m_targetRange.Characters(i, 1).Font.Underline
.Color = m_targetRange.Characters(i, 1).Font.Color
.TintAndShade = m_targetRange.Characters(i, 1).Font.TintAndShade
.ThemeFont = m_targetRange.Characters(i, 1).Font.ThemeFont
End With
Filter.Add newChar, CStr(newIndex)
newIndex = newIndex + 1
End If
Next i
End Function
Private Sub Rewrite(chars As Collection)
m_targetRange.Value = ""
Dim i As Integer
For i = 1 To chars.Count
If IsEmpty(m_targetRange.Value) Then
m_targetRange.Value = chars(i).Text
Else
m_targetRange.Value = m_targetRange.Value & chars(i).Text
End If
Next i
For i = 1 To chars.Count
With m_targetRange.Characters(i, 1).Font
.Name = chars(i).Name
.FontStyle = chars(i).FontStyle
.Size = chars(i).Size
.Strikethrough = chars(i).Strikethrough
.Superscript = chars(i).Superscript
.Subscript = chars(i).Subscript
.OutlineFont = chars(i).OutlineFont
.Shadow = chars(i).Shadow
.Underline = chars(i).Underline
.Color = chars(i).Color
.TintAndShade = chars(i).TintAndShade
.ThemeFont = chars(i).ThemeFont
End With
Next i
End Sub
How to use it:
Sub test()
Dim target As Range
Dim myChars As MyCharacters
Application.ScreenUpdating = False
Set target = Worksheets("Demo").Range("A1")
Set myChars = New MyCharacters
myChars.Delete targetRange:=target, start:=300, length:=27
Application.ScreenUpdating = True
End Sub
Before:
After:
To make it more stable, you should:
Disable all events while operating
Never call .Activate or .Select
Paste directly in the targeted cell with WorkSheet.Paste
Cancel the Copy operation with Application.CutCopyMode = False
Reuse the same document and not create one for each iteration
Do as less operations as possible in an iteration
Use early binding [New Word.Application] instead of late binding [CreateObject("Word.Application")]
Your example refactored :
Sub FindAndReplace()
Dim dictionary(), target As Range, ws As Worksheet, cell As Range, i As Long
Dim strFind As String, strReplace As String, diffCount As Long, replaceCount As Long
Dim appWord As Word.Application, content As Word.Range, find As Word.find
dictionary = [Sheet1!A1].CurrentRegion.Value
Set target = Cells.SpecialCells(xlCellTypeConstants)
' launch and setup word
Set appWord = New Word.Application
Set content = appWord.Documents.Add().content
Set find = content.find
find.ClearFormatting
find.Font.Bold = False
find.replacement.ClearFormatting
' disable events
Application.Calculation = xlManual
Application.ScreenUpdating = False
Application.EnableEvents = False
' iterate each cell
Set ws = target.Worksheet
For Each cell In target.Cells
' copy the cell to Word and disable the cut
cell.Copy
content.Delete
content.Paste
Application.CutCopyMode = False
' iterate each text to replace
For i = 2 To UBound(dictionary)
If Trim(dictionary(i, 1)) <> Empty Then
replaceCount = 0
strFind = dictionary(i, 1)
strReplace = dictionary(i, 2)
' replace in the document
diffCount = content.Characters.count
find.Execute FindText:=strFind, ReplaceWith:=strReplace, format:=True, Replace:=2
' count number of replacements
diffCount = diffCount - content.Characters.count
If diffCount Then
replaceCount = diffCount \ (Len(strFind) - Len(strReplace))
End If
Debug.Print replaceCount
End If
Next
' copy the text back to Excel
content.Copy
ws.Paste cell
Next
' terminate Word
appWord.Quit False
' restore events
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
Application.EnableEvents = True
End Sub
How about change it from: activesheet.paste
to:
activesheet.activate
activecell.pastespecial xlpasteAll
This post seems to explain the problem and provide two solutions:
http://www.excelforum.com/excel-programming-vba-macros/376722-runtime-error-1004-paste-method-of-worksheet-class-failed.html
Two items come to light in this post:
Try using Paste Special
Specify the range you wish to paste to.
Another solution would be to extract the targeted cells as XML, replace the text with a regular expression and then write the XML back to the sheet.
While it's much faster than working with Word, it might require some knowledge with regular expressions if the formats were to be handled. Moreover it only works with Excel 2007 and superior.
I've assemble an example that replaces all the occurences with the same style:
Sub FindAndReplace()
Dim area As Range, dictionary(), xml$, i&
Dim matchCount&, replaceCount&, strFind$, strReplace$
' create the regex object
Dim re As Object, match As Object
Set re = CreateObject("VBScript.RegExp")
re.Global = True
re.MultiLine = True
' copy the dictionary to an array with column1=search and column2=replacement
dictionary = [Sheet1!A1].CurrentRegion.Value
'iterate each area
For Each area In ActiveSheet.Cells.SpecialCells(xlCellTypeConstants)
' read the cells as XML
xml = area.Value(xlRangeValueXMLSpreadsheet)
' iterate each text to replace
For i = 2 To UBound(dictionary)
If Trim(dictionary(i, 1)) <> Empty Then
strFind = dictionary(i, 1)
strReplace = dictionary(i, 2)
' set the pattern
re.pattern = "(>[^<]*)" & strFind
' count the number of occurences
matchCount = re.Execute(xml).count
If matchCount Then
' replace each occurence
xml = re.Replace(xml, "$1" & strReplace)
replaceCount = replaceCount + matchCount
End If
End If
Next
' write the XML back to the sheet
area.Value(xlRangeValueXMLSpreadsheet) = xml
Next
' print the number of replacement
Debug.Print replaceCount
End Sub
DDuffy's answer is useful.
I found the code can run normally at slowly cpu PC .
add the bellow code before paste, the problem is sloved:
Application.Wait (Now + TimeValue("0:00:1"))'wait 1s or more
ActiveSheet.Paste