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
I'm trying to import from a .csv to an Excel spreadsheet several (20) columns of data whilst cleaning up the data as it is imported.
I am new to using VBA to do anything in Excel. My coding experience is limited to a little VB from back in college so I have a grasp of the idea. I'm more than willing to invest time and effort, even buying a few books (any recommendations?).
The .csv file looks like this:
Job:JS_010815_HEASB,Version:2.40,Units:USSurveyFeet,,,,,,,,,,,,,,,,,
PS1457,17262086.61,711051.298,509.153,CONTROL POINT,,,,,,,,,,,,,,,
JS2924,17262069.42,711898.13,505.726,CKP,CKP:POINT ID,PS7431,CKP:NOTES,,,,,,,,,,,,
PS7431,17262069.36,711898.141,505.705,CP,CP:STYLE,PRIM. CONTROL,CP:TYPE,60D NAIL,CP:SIZE,,CP:CONDITION,UNDISTURBED,CP:PROTECTION,OTHER (SEE NOTES),CP:NOTES,,,,
CD7,17262018.81,711181.868,508,PI,,,,,,,,,,,,,,,
CD8,17262889.87,711158.429,510,PI,,,,,,,,,,,,,,,
PS2337,17258986.57,711490.088,506.345,PI,,,,,,,,,,,,,,,
CD5,17262001.04,711782.507,500,PI,,,,,,,,,,,,,,,
JS2925,17261586.74,711741.759,502.677,WELD,WELD:TYPE,MAIN LINE,WELD:XRAY#,BML-901,WELD:JOINT# AHEAD,1708,WELD:JNT HD HEAT#,M75460,WELD:JOINT # BEHIND,1709,WELD:JNT BK HEAT#,M75460,WELD:STATION,716+59,WELD:NOTES
JS2926,17261586.56,711746.613,507.221,NG,NG:REMARKS,4.5 COV,,,,,,,,,,,,,
JS2927,17261628.59,711745.877,502.167,WELD,WELD:TYPE,TIE IN,WELD:XRAY#,BTI-028,WELD:JOINT# AHEAD,1724,WELD:JNT HD HEAT#,M75455,WELD:JOINT # BEHIND,1708,WELD:JNT BK HEAT#,M75460,WELD:STATION,717+01,WELD:NOTES
JS2928,17261670.4,711749.899,501.692,WELD,WELD:TYPE,MAIN LINE,WELD:XRAY#,BML-926,WELD:JOINT# AHEAD,1725,WELD:JNT HD HEAT#,M75455,WELD:JOINT # BEHIND,1724,WELD:JNT BK HEAT#,M75455,WELD:STATION,717+43,WELD:NOTES
The two things I need to do are:
Remove the ":" and whatever precedes it in each cell.
(Minor) I would like to take the info which would be in cell A1 i.e. Job:JS_010815_HEASB, and insert it at the end of each row.
I'd suggest the easiest way to do this is to split it into the individual parts:
Import (not sure if you want to automate it or not, but if you do, I've included instructions.)
Filter the cells, removing the ":"
Copy Cell A1 to end (or move column A, not sure)
Import
Importing is pretty easy, and can be done with something like this:
Sub importCSV(file As String, wsName As String)
Dim connection As String
connection = "TEXT;" + file
With Worksheets(wsName).QueryTables.Add(Connection:=connection, Destination:=Worksheets(wsName).Range("A1"))
.Name = file
.FieldNames = True
.RowNumbers = False
.FillAdjacentFormulas = False
.PreserveFormatting = True
.RefreshOnFileOpen = False
.RefreshStyle = xlOverwriteCells
.SavePassword = False
.SaveData = True
.AdjustColumnWidth = True
.RefreshPeriod = 0
.TextFilePromptOnRefresh = False
.TextFilePlatform = 437
.TextFileStartRow = 1
.TextFileParseType = xlDelimited
.TextFileTextQualifier = xlTextQualifierDoubleQuote
.TextFileConsecutiveDelimiter = False
.TextFileTabDelimiter = False
.TextFileSemicolonDelimiter = False
' If you were using a file with some other type of delimeter, you'd set this to false and then that one to true
.TextFileCommaDelimiter = True
.TextFileSpaceDelimiter = False
.Refresh BackgroundQuery:=False
End With
End Sub
Where file is the file (including directory) and wsName is the name of the worksheet you'll be importing to.
Filter
To filter the cells, try this:
Sub filter(wsName As String)
Dim c As Range
For Each c In Worksheets(wsName).usedRange.Cells
c.value = Right(c.value, Len(c.value) - InStr(c.value, ":"))
Next
End Sub
What this does is it loops through each cell in the usedRange for the given worksheet (where wsName is the name of the worksheet) and sets the object c to it. Then we can simply set c.value to whatever new value we want, in our case, everything to the right of any :.
To do this, we use the Right() function which takes a string and an integer for the length and returns that many characters from the right hand side. To work out how many characters we want, we get the length of the entire string with Len(), and subtract from it the number of characters up to and including the : with InStr().
(InStr() returns the position of the character if it's in there, or 0, which means if the character isn't there, we'll be calling Right() with the length of the string, so it'll just return the full input.)
Copy
Not entirely sure what you were going for here. If you were trying to move the entire column, use:
Sub moveColumnToEnd(wsName As String, colNum As Integer)
Dim columnCount As Integer
With Worksheets(wsName)
columnCount = .UsedRange.Columns.Count
.Columns(colNum).Cut
.Columns(columnCount + 1).Insert
End With
End Sub
Where wsName is the name of the work sheet and colNum is the number of the column you want to move (in your case 1). Hopefully the code itself is pretty self explanatory. If not, just ask.
If you were trying to just copy Cell A1, try this:
Sub copyA1ToEnd(wsName As String)
Dim columnCount As Integer
Dim rowCount As Integer
Dim copyRange As Range
Dim c As Range
With Worksheets(wsName)
columnCount = .UsedRange.Columns.Count
rowCount = .UsedRange.Rows.Count
' What we're doing here is getting the range from the first cell of the last column+1
' all the way to the bottom most cell of that column. The Cells() command takes it's
' arguments (row, col) not (X, Y) like you'd probably expect.
Set copyRange = .Range(.Cells(1, columnCount + 1), .Cells(rowCount, columnCount + 1))
' Again, like in the filter function, we loop through each cell in the range and set
' it to what we want it to be
For Each c In copyRange.Cells
c.Value = .Range("A1").Value
Next
End With
End Sub
Where wsName is the name of the worksheet. Again, hopefully it's pretty self explanatory.
I'm trying to write an Excel macro using VBA to automate importing CSV text into a spreadsheet but I've never done it before. I need to make sure that the Text Import Wizard that comes up is run through the same way each time. The steps I need to take are:
Open a file, using an open file dialog
Set type to Delimited
Set Delimiter to comma
Set all columns to be imported as Text
Auto fit all columns
I can't seem to wade through the documentation that shows how to do these things like open files. Even being able to start there would be helpful.
The code below will allow a user to browse for a csv file.
It will then :
Open the selected file, treating the data as text
Resize the columns
Move the data into the workbook from which the code is run.
The .opentext code needs to be updated depending on the number of columns in the source data.
Sub ImportCSV()
Dim vPath As Variant
Dim wb As Excel.Workbook
Dim ws As Excel.Worksheet
Set wb = Excel.ActiveWorkbook
Set ws = Excel.ActiveSheet
vPath = Application.GetOpenFilename("CSV (Comma Delimited) (*.csv),*.csv" _
, 1, "Select a file", , False)
''//Show the file open dialog to allow user to select a CSV file
If vPath = False Then Exit Sub
''//Exit macro if no file selected
Workbooks.OpenText Filename:=vPath, Origin:=xlMSDOS, StartRow:=1 _
, DataType:=xlDelimited, TextQualifier:=xlDoubleQuote, Comma:=True _
, FieldInfo:=Array(Array(1, xlTextFormat), Array(2, xlTextFormat), _
Array(3, xlTextFormat))
''//The fieldinfo array needs to be extended to match your number of columns
Columns.EntireColumn.AutoFit
''//Resize the columns
Sheets(1).Move Before:=wb.Sheets(1)
''//Move the data into the Workbook
End Sub
Public Sub Example()
Const csPath As String = "C:\Test\Example.csv"
Dim ws As Excel.Worksheet
Set ws = Excel.ActiveSheet
With ws.QueryTables.Add("TEXT;" & csPath, ws.Cells(1, 1))
.FieldNames = True
.AdjustColumnWidth = True
.TextFileParseType = xlDelimited
.TextFileTextQualifier = xlTextQualifierDoubleQuote
.TextFileConsecutiveDelimiter = False
.TextFileCommaDelimiter = True
''// This array will need as many entries as there will be columns:
.TextFileColumnDataTypes = Array(xlTextFormat, xlTextFormat)
.Refresh
End With
End Sub
I ended up making some tweaks to the function before putting it into use.
Public Sub OpenCsv()
' I don't expect any more columns than 256 in my environment, so I can
' just fill this array and call it done.
Dim columnFormats(0 To 255) As Integer
For i = 0 To 255
columnFormats(i) = xlTextFormat
Next i
Dim filename As Variant
filename = Application.GetOpenFilename("All Files (*.*),*.*", 1, "Open", "", False)
' If user clicks Cancel, stop.
If (filename = False) Then
Exit Sub
End If
Dim ws As Excel.Worksheet
Application.Workbooks.Add
Set ws = Excel.ActiveSheet
Application.DisplayAlerts = False
Sheets("Sheet2").Delete
Sheets("Sheet3").Delete
Application.DisplayAlerts = True
With ws.QueryTables.Add("TEXT;" & filename, ws.Cells(1, 1))
.FieldNames = True
.AdjustColumnWidth = True
.TextFileParseType = xlDelimited
.TextFileTextQualifier = xlTextQualifierDoubleQuote
.TextFileConsecutiveDelimiter = False
.TextFileCommaDelimiter = True
''// This array will need as many entries as there will be columns:
.TextFileColumnDataTypes = columnFormats
.Refresh
End With
End Sub
Thanks to the above guys for getting me going.