I am trying to update data in ms word from excel using macro. To simplify the process, I am using bookmark feature. The issue is when I ran the code, the data in bookmark gets duplicated and I see the same data points from excel in ms word multiple times. I tried to find out a solution on how to clear the data stored in bookmarks before pasting new data but the problem seems to be unresolved.
------------------CODE----------------------------
Option Explicit
Const FilePath As String = "C:\Users\Prablee.Singh\Desktop\"
Dim wd As New Word.Application
Sub ExporttoWord()
Dim doc As Word.Document
wd.Visible = True
Dim Model_Name As String
Dim Model_Description As String
Dim Model_Status As String
Model_Name = ThisWorkbook.Sheets(2).Range("A2").Value 'value from sheet1
Model_Description = ThisWorkbook.Sheets(2).Range("B2").Value
Model_Status = ThisWorkbook.Sheets(2).Range("C2").Value
Set doc = wd.Documents.Open(FilePath & "VBA Code Doc.docx")
Copy2word "Project1", Model_Name
Copy2word "Project1Description", Model_Description
Copy2word "Project1Status", Model_Status
doc.Close
wd.Quit
End Sub
Sub Copy2word(BookMarkName As String, Text2Type As String)
wd.Selection.GoTo What:=wdGoToBookmark, Name:=BookMarkName
wd.Selection.TypeText Text2Type
End Sub
Correct me if I'm wrong. You want to insert text to replace old bookmark? Is your bookmarks marked as range or it's only location? You can replace whole bookmark text using this sample:
Dim BookmarkRange As Range
Set BookmarkRange = ActiveDocument.Bookmarks("myBookmarkRange").Range
BookmarkRange.Text = "this text will replace the old one."
This problem is likely caused by the bookmark simply marking a location in the document, as shown in the top example of the screenshot below, rather than enclosing text.
To see the bookmarks go to File | Options | Advanced | Show Document Content and tick the "Show bookmarks" option.
When inserting text into an enclosing bookmark the bookmark gets deleted and needs to be replaced. This can be done by using a routine such as the one below which also checks that the bookmark exists to avoid errors.
Public Sub InsertIntoBookmark(bookmarkName As String, text As String, _
Optional targetDoc As Document = Nothing)
Dim bookmarkRange As Range
If targetDoc Is Nothing Then Set targetDoc = ActiveDocument
If targetDoc.Bookmarks.Exists(bookmarkName) Then
Set bookmarkRange = targetDoc.Bookmarks(bookmarkName).Range
bookmarkRange.text = text
targetDoc.Bookmarks.Add bookmarkName, bookmarkRange
End If
End Sub
Since the text, as you've said is duplicating that indicates your bookmark is a single point in the document and not a spanned range. The resultant behavor is then to add the text behind the bookmark and leave the bookmark in place. If your bookmark was a spanned range of existing text then Word would replace the text but it would also remove the bookmark.
Either way, your code must reestablish a bookmark ... that is unless you want to remove the bookmark after you have placed the new data into the document.
Try this code ...
Dim rng As Word.Range
Set rng = doc.Bookmarks("BookMarkName").Range
rng.Text = "Project Name"
doc.Bookmarks.Add "BookMarkName", rng
As I've tried to explain above, if your Word document had in the BookMarkName a placeholder of "Temp Name" the code above would replace it with "Project Name" and then reestablish the BookMareName surrounding "Project Name". If you didn't what it to reestablish BookMarkName, then just omit the last line.
If in your existing document BookMarkName is a single address point and not a span of text, the code above will still add the new text, but it will also relocate the BookMarkName range to the new span of text. And the difference here is if you don't want the BookMarkName to remain after inserting the new text, you will have to replace the last code line with ...
doc.Bookmarks(BookMarkName).Delete
Related
Is there any method of adding images or unicode characters from (say) Wingdings by in a table generated by lotusscript.
As the options for formatting of tables in lotusscript are limited I store a formatted table in a profile document and append it to the rich text field.
In the Queryopen event of the document I add the rtf table and add rows and populate. The one below is finding emails sent from a document and displaying them in that document in an RTF
Any help is greatly appreciated.
Sub Queryopen(Source As Notesuidocument, Mode As Integer, Isnewdoc As Variant, Continue As Variant)
Dim session As New NotesSession
Dim ws As New NotesUIWorkspace
Dim rtItem As NotesRichTextItem
Dim rtnavBody As NotesRichTextNavigator
Dim rtt As NotesRichTextTable
Dim rc As Integer
Dim cc As Integer
Dim rcc As Integer
Dim cl As Integer
Dim richStyle As NotesRichTextStyle
Dim tablelayout As NotesRichTextItem
Dim db As NotesDatabase
Dim pdoc As NotesDocument
On Error Goto errorsub
Set uidoc = source
Set db =session.CurrentDatabase
Set doc = uidoc.Document
Set view = db.Getview("MailByParentID")
Set col = view.Getalldocumentsbykey(doc.DocID,True)
If col.count=0 Then Exit Sub 'No items exist so no point in carrying on.
End If
Set rtItem = New NotesRichTextItem(doc,"rtfCustMail") 'field in the current document
Set pdoc=db.Getprofiledocument("Profile Doc")
Set tablelayout = pdoc.GetFirstItem("rtfMailLog") 'Get a ready made table from the Profile Doc.
Call rtitem.AppendRTItem(tablelayout)
Set rtnavBody = rtItem.CreateNavigator
Set richStyle = session.CreateRichTextStyle
Set idoc = col.Getfirstdocument()
'Add a row to the table to hold the data for the first item in the order
Call rtnavBody.FindFirstElement(RTELEM_TYPE_TABLE)
Set rtt = rtnavBody.GetElement
Do Until idoc Is Nothing
Call rtt.AddRow()
'Write the item data into the tablecells --
rc%= rtt.RowCount 'Find the number of rows in the table
cc% =rtt.ColumnCount
rcc% =rc%*cc% 'Calculate total number of table cells
cl% =rcc%-5 'Calculate cell number of the first cell in the new (last) row
Call rtnavBody.FindNthElement(RTELEM_TYPE_TABLECELL,cl%) 'Move to the first cell in the last row
Call rtitem.BeginInsert(rtnavBody)
Call rtitem.Appenddoclink(idoc,"")
Call rtitem.EndInsert
Call rtnavBody.FindNextElement(RTELEM_TYPE_TABLECELL)
Call rtitem.BeginInsert(rtnavBody)
Call rtitem.AppendText(******need to add characters in here or better still images.)
Call rtitem.EndInsert
'To
Call rtnavBody.FindNextElement(RTELEM_TYPE_TABLECELL)
Call rtitem.BeginInsert(rtnavBody)
Call rtitem.AppendText(idoc.SendTo(0))
Call rtitem.EndInsert
etc etc.
Set idoc = col.Getnextdocument(idoc)
Loop
errorsub: Print " Line " Erl & " Reason - "& Error$
End Sub
Unicode characters can simply be appended as text. There is some complication because Notes is using LMBCS, not Unicode. Still, if you can paste the character you want into a text string in your LotusScript code, the conversions will be done behind the scenes. If there are any issues with it, I uploaded an NSF containing a full listing of all Unicode characters with their LMBCs equivalents on the OpenNTF website somewhere between 10 and 20 years ago. I still have a copy if it can no longer be found after the various changes to OpenNTF.
BTW, a very useful trick that I've found for getting content into a NotesRichText item that is being constructed on the fly from parts that you have already built advance is the AppendToRTItem method. I.e., you are building rtitem, as above. You have a config document containing a rich text field containing the content that you want (e.g., an image, a hypertext link, something with a hide-when formula, etc.) so you open that config document, get the NotesRichText item from that document into rtitem2, and call rtitem2.AppendToRTItem(rtitem).
I have an Excel VBA program that will find a Lotus Notes email with specific text in the subject and import the body into the Excel spreadsheet.
I can find the email, and import the data to Excel. Unfortunately, it seems to be creating a new line where there isn't a new line character.
I noticed that the breaks in the Excel sheet match up with the line wrapping in Word when I cut and paste the Body into a Word doc and the doc is in portrait mode.
If I change Word to landscape, it looks like the e-mail.
Changing the Excel sheet to landscape had no effect.
I also tried changing the split to use vbCR and vbLf independently of each other without positive results.
Is there a line length option I can set? How can I keep all the data associated with one line in the e-mail with one line in Excel?
Sub LNExtract()
Dim NSession As Object 'NotesSession
Dim NMailDb As Object 'NotesDatabase
Dim NDocs As Object 'NotesDocumentCollection
Dim NDoc As Object 'NotesDocument
Dim NNextDoc As Object 'NotesDocument
Dim NItem As Object 'NotesItem
Dim view As String
Dim filterText As String
view = "Folder\SubFolder" 'Name of view or folder to retrieve documents from
filterText = "SubjectLineText" 'Optional text string to filter the view
Set NSession = CreateObject("Notes.NotesSession")
Set NMailDb = NSession.GETDATABASE("", "") 'Default server and database
If Not NMailDb.IsOpen Then
NMailDb.OPENMAIL
End If
Set NDocs = NMailDb.GETVIEW(view)
NDocs.Clear
'Apply optional filter
If filterText <> "" Then
NDocs.FTSEARCH filterText, 0
End If
Set NDoc = NDocs.GETFIRSTDOCUMENT
Do Until NDoc Is Nothing
Set NNextDoc = NDocs.GETNEXTDOCUMENT(NDoc)
Set NItem = NDoc.GETFIRSTITEM("Body")
If Not NItem Is Nothing Then
Lines = Split(NItem.Text, vbCrLf)
Range("A1").Resize(UBound(Lines) + 1, 1).Value = Application.WorksheetFunction.Transpose(Lines)
End If
Set NDoc = NNextDoc
Loop
End Sub
Where you have NItem.Text in your code, I'm fairly sure that gives the same result as the GetFormattedText method of the NotesRichTextItem class, which limits the line length. Relevant documentation is here:
https://help.hcltechsw.com/dom_designer/9.0.1/appdev/H_GETFORMATTEDTEXT_METHOD.html
In contrast, the GetUnformattedText method doesn't limit the line length, so you should get a better result if you replace NItem.Text with NItem.GetUnformattedText().
My code pastes a picture of the cells.
It should then paste the other cells which don't fit on the first page.
The picture of the 'second page' does not paste.
Dim wdApp As Object
Dim wd As Object
Dim sFil As String
On Error Resume Next
Set wdApp = GetObject(, "Word.Application")
If Err.Number <> 0 Then Set wdApp = CreateObject("Word.Application")
On Error GoTo 0
Set wd = wdApp.Documents.Add ' create a new document
wdApp.Visible = True
'change sheet and range below
ActiveSheet.Range("a1:z43").CopyPicture xlPrinter
wd.Range.Paste
wdApp.ActiveDocument.Range(0, 0).InsertBreak Type:=wdSectionBreakNextPage
wdApp.ActiveDocument.Sections (2)
ActiveSheet.Range("a43:z76").CopyPicture xlPicture
wd.Range.Paste
'wd.SaveAs Filename:="I:\'Files\RKG-COMMS\" & URNa & " - " & URNb & ".doc"
'wd.Close
'wd.Quit
Here is the result - only the first page
This is the second page which should be pasted
It's difficult for me to test what you're doing, but logically I believe the following should work, although there may be one problem (keep reading)...
Notice how this code uses a Word Range object as the target for new content. After pasting it's uncertain whether docRange will be before the pasted content, or contain the pasted content. If it contains the pasted content, the code should work. If it's before the pasted content, then next picture will come in before the first one. In that case, repeat the line Set docRange = wd.Content immediately after docRange.Paste.
Set wd = wdApp.Documents.Add ' create a new document
Dim docRange as Object 'Word.Range
Set docRange = wd.Content
wdApp.Visible = True
'change sheet and range below
ActiveSheet.Range("a1:z43").CopyPicture xlPrinter
docRange.Paste
'Go to the end
docRange.Collapse 0 'wdCollapseEnd
docRange.InsertBreak Type:=wdSectionBreakNextPage
ActiveSheet.Range("a43:z76").CopyPicture xlPicture
docRange.Paste
In response to an inquiry in comments about how the code works in detail:
Dim docRange as Object 'Word.Range
Declares an object variable to hold the part of the document the code works with. Because the code in the question, running from Excel, apparently uses late-binding (no reference to the Word object library) it's declared as an Object.
If early-binding were being used (the code project has a reference to the Word libraray) then declaring it As Word.Range would be preferable. I include the Word object data type (Word.Range) for the sake of completeness and to make it possible to research the object and its properties in the Help.
Set docRange = wd.Content
This instantiates the Word.Range object to contain the entire main body of the document (wd being instantiated in the code in the question to the target Word document).
After pasting, docRange still refers to the entire content of the document, including what was pasted. If the section break and second paste action were to be executed immediately, this would delete the content of the range. (Think of it like selecting text in Word, then typing: what was selected is replaced by what is typed. In order to avoid this, one presses the right arrow key before typing so that the new content follows what was selected.)
For this reason, the range is "collapsed" (like pressing the arrow key). Then the section break is inserted and the next paste action is executed.
Goal for my project:
I want to be able to copy the contents of one document and append that selection to the end of another document.
What it does... (this is just background info so you understand why I am trying to do this):
I am trying to dynamically produce a document which quotes a variety of information regarding different parts and materials involved for a product.
The document itself has a consistent format which I have broken down and separated into two documents. The first contains a bunch of data that needs to be entered manually, and is where I want to append all additional content. The second contains roughly a dozen custom fields which are updated from an excel spreadsheet in VBA. For a single part and as a single doc this works as I want it (my base case). However my issue is when there are multiple parts for a project.
The Problem:
For multiple parts I have to store information in an array which changes in size dynamically as each additional part is added. When someone has added all the necessary parts they can select a button called "Create Quote".
Create quote runs a procedure which creates/opens separate copies of the two template documents mentioned above (saved on my computer). It then iterates through the array of parts and updates all the custom field in the 2nd document (no problems). Now I just need the contents of the 2nd document appended to the end of the first which is my problem.
What I want:
Ideally, my procedure will continue to iterate through every part in the array - updating custom fields, copy then paste the updated text, repeat... Until every part is included in the newly generated quote.
What I Tried - this code can be found in my generate quote procedure
I have tried many of the examples and suggestions provided by people who had similar question, but I don't know if its because I am operating from an excel doc, but many of their solution have not worked for me.
This is my most recent attempt and occurs after each iteration of the for loop
wrdDoc2.Fields.Update 'Update all the fields in the format document
wrdDoc2.Activate
Selection.WholeStory ' I want to select the entire document
Selection.Copy ' Copy the doc
wrdDoc1.Activate ' Set focus to the target document
Selection.EndKey wdStory ' I want the selection to be pasted to the end of the document
Selection.PasteAndFormat wdPasteDefault
QUOTE PROCEDURE - I am only including a handful of the fields I am updating because its not necessary to show them all
Private Sub quote_button_Click()
On Error GoTo RunError
Dim wrdApp1, wrdApp2 As Word.Application
Dim wrdDoc1, wrdDoc2 As Word.Document
Set wrdApp1 = CreateObject("Word.Application")
Set wrdApp2 = CreateObject("Word.Application")
wrdApp1.Visible = True
wrdApp2.Visible = True
Set wrdDoc1 = wrdApp1.Documents.Add(Template:="C:\MWES\AQT_v1.1(start).docm", NewTemplate:=False, DocumentType:=0)
Set wrdDoc2 = wrdApp2.Documents.Add(Template:="C:\MWES\AQT_v2.1(format).docm", NewTemplate:=False, DocumentType:=0)
Dim propName As String
For i = LBound(part_array, 1) To UBound(part_array, 1)
For Each prop In wrdDoc2.CustomDocumentProperties
propName = prop.name
' Looks for and sets the property name to custom values of select properties
With wrdDoc2.CustomDocumentProperties(propName)
Select Case propName
Case "EST_Quantity"
.value = part_array(i, 0) ' "QTY" ' Sheet1.Cells(1, 3) 'NA
Case "EST_Metal_Number"
.value = part_array(i, 1) ' "METAL_#" ' Sheet1.Cells(2, 3) 'NA"
Case "EST_Metal_Name"
.value = part_array(i, 2) ' "METAL_N" ' Sheet1.Cells(5, 2)
End Select
End With
Next prop ' Iterates until all the custom properties are set
wrdDoc2.Fields.Update 'Update all the fields in the format document
wrdDoc2.Activate
Selection.WholeStory ' I want to select the entire document
Selection.Copy ' Copy the doc
wrdDoc1.Activate ' Set focus to the target document
Selection.EndKey wdStory ' I want the selection to be pasted to the end of the document
Selection.PasteAndFormat wdPasteDefault
Next i ' update the document for the next part
RunError: ' Reportd any errors that might occur in the system
If Err.Number = 0 Then
Debug.Print "IGNORE ERROR 0!"
Else
Dim strError As String
strError = "ERROR: " & Err.Number & vbCrLf & Err.Description & vbCrLf & Erl
MsgBox strError
Debug.Print strError & " LINE: " & Erl
End If
End Sub
I apologize this was so long winded. Let me know if there is anything confusing or you may want clarified. I think I included everything though.
I think you're close, so here are a couple of comments and an example.
First of all, you're opening two separate MS Word Application objects. You only need one. In fact, it's possible that the copy/paste is failing because you're trying to copy from one Word app to a document opened in the other. (Trust me, I've seen weird things like this.) My example below shows how to do this by only opening a single application instance.
Dim mswApp As Word.Application
Set mswApp = AttachToMSWordApplication() 'more on this function below...
Dim doc1 As Word.Document
Dim doc2 As Word.Document
Set doc1 = mswApp.Documents.Open("C:\Temp\combined.docx")
Set doc2 = mswApp.Documents.Open("C:\Temp\control.docx")
While I don't often write code for Word, I've found that there are so many different ways to get at the same content using different objects or properties. This is always a source of confusion.
Based on this answer, which has worked well for me in the past, I then set up the source and destination ranges to perform the "copy":
Dim destination As Word.Range
Dim source As Word.Range
Set source = doc1.Content
Set destination = doc2.Content
destination.Collapse Direction:=Word.wdCollapseEnd
destination.FormattedText = source
Here is the whole module for reference:
Option Explicit
Sub AddDocs()
Dim wordWasRunning As Boolean
wordWasRunning = IsMSWordRunning()
Dim mswApp As Word.Application
Set mswApp = AttachToMSWordApplication()
Dim doc1 As Word.Document
Dim doc2 As Word.Document
Set doc1 = mswApp.Documents.Open("C:\Temp\combined.docx")
Set doc2 = mswApp.Documents.Open("C:\Temp\control.docx")
Dim destination As Word.Range
Dim source As Word.Range
Set source = doc1.Content
Set destination = doc2.Content
destination.Collapse Direction:=Word.wdCollapseEnd
destination.FormattedText = source
doc2.Close SaveChanges:=True
doc1.Close
If Not wordWasRunning Then
mswApp.Quit
End If
End Sub
Here's the promised note on a couple functions I use in the sample. I've built up a set of library functions, several of which help me access other Office applications. I save these modules as .bas files (by using the Export function in the VBA Editor) and import them as needed. So if you'd like to use it, just save the code below in using a plain text editor (NOT in the VBA Editor!), then import that file into your project.
Suggested filename is Lib_MSWordSupport.bas:
Attribute VB_Name = "Lib_MSWordSupport"
Attribute VB_Description = "Variety of support functions operating on MS Word"
Option Explicit
Public Function IsMSWordRunning() As Boolean
Attribute IsMSWordRunning.VB_Description = "quick check to see if an instance of MS Word is running"
'--- quick check to see if an instance of MS Word is running
Dim msApp As Object
On Error Resume Next
Set msApp = GetObject(, "Word.Application")
If Err > 0 Then
'--- not running
IsMSWordRunning = False
Else
'--- running
IsMSWordRunning = True
End If
End Function
Public Function AttachToMSWordApplication() As Word.Application
Attribute AttachToMSWordApplication.VB_Description = "finds an existing and running instance of MS Word, or starts the application if one is not already running"
'--- finds an existing and running instance of MS Word, or starts
' the application if one is not already running
Dim msApp As Word.Application
On Error Resume Next
Set msApp = GetObject(, "Word.Application")
If Err > 0 Then
'--- we have to start one
' an exception will be raised if the application is not installed
Set msApp = CreateObject("Word.Application")
End If
Set AttachToMSWordApplication = msApp
End Function
I tried since more days to create a Word Document with Excel-VBA
Step by Step:
first: create Word-Document and add a Table (Mailing-Label)
second: fill sometext into some cells. Works great!
Now my Problem:
at last, i want to append an Picture in the cell.
My Problem is, the Image RANGE clear the old text.
And i don't know, how to set the Image and the text at the end of the Loop.
My code
oDoc.Tables(1).Cell(zeile, spalte).Range.Text = "some string"
oDoc.Tables(1).Cell(zeile, spalte).Range.InlineShapes.AddPicture path_to_image
The way to understand what's happening is to think about how this would work if you were doing this manually, working with a selection. When you assign text to a Range that's like typing it in, as you'd expect. The second line of code, inserting the image, is like selecting the entire cell (in this case) then inserting the image: it replaces what's in the Range. When working manually, if you had selected the entire cell, you'd press Right Arrow or click at the end to put the focus after what had been typed.
The same principle applies when using a Range object: it needs to collapse in order to add something to it.
The following code example demonstrates this. It also highlights how the code can be made more efficient by assigning the table and the target range to objects.
Dim tbl As Word.Table 'or As Object if using late-binding
Dim rng As Word.Range 'or As Object if using late-binding
Dim chrCount As Long
Set tbl = oDoc.Tables(1)
Set rng = tbl.Cell(zeile, spalte).Range
rng.Text = "test"
chrCount = rng.Characters.Count
'Get the end of the cell content
Set rng = rng.Characters(chrCount - 1)
rng.Collapse wdCollapseEnd
rng.InlineShapes.AddPicture path_to_image
May be something like
Sub Test()
Dim Wrd As Word.Application
Dim oDoc As Word.Document
Set Wrd = CreateObject("Word.Application")
Wrd.Visible = True
Set oDoc = Wrd.Documents.Add
oDoc.Tables.Add oDoc.Range, 3, 3
zeile = 2
spalte = 2
path_to_image = "C:\Users\user\Desktop\Pull2.jpg"
oDoc.Tables(1).Cell(zeile, spalte).Range.Select
With Wrd.Selection
.TypeText Text:="some string"
.InlineShapes.AddPicture path_to_image
End With
End Sub