In my word "sheet", I have a CommandButton of which, when clicked, triggers a certain part of code which basically is about opening a second word document and inserting some informations from the current one (Document1) into the second one (Document2) TextBox.
I have String variables containing text in one word document (e.i. Document1). I am opening a second document (e.i. Document2). Then, I need to reach a specific TextBox from the Document2 and insert into it the value of one of the String variables I already have.
That being said, I can't access that second document (Document2) since I always gets the "4160 error" which result of the file name being incorrect.
Therefore, how can I access my second document (Document2) TextBox and insert into it a specific value I already have?
My code as follow (simplified to one variable since it'll be the same for every other):
Private Sub btn1_Click()
Dim strFile As String
Dim WordApp As New Word.Application
Dim WordDoc As Word.Document
Dim name As String
strFile = "C:\Users\WhateverUser\Desktop\WhateverFolder\Document2.docx"
name= txtBoxName.Text
'This comes from the first document (Document1) which is correct.
' Opening another Word document (Document2)
Set WordDoc = WordApp.Documents.Open(strFile)
WordApp.Visible = True
'Here is the problem
'Trying to access the Document2 TextBox (txtBoxNameDoc2) with various ways but I always get the Incorrect File Name Error
'First I tried
Documents("Document2.docx").Bookmarks("txtBoxNameDoc2").Range.Text = name
'Then I tried
Documents("Document2.docx").txtBoxNameDoc2.Text = name
'And after those, I went looking on internet and tried what I could find but none did work.
End Sub
I can speculate at some errors in the coding you have provided above, but if this line works without returning an error:
Set WordDoc = WordApp.Documents.Open(strFile)
WordApp.Visible = True
THen you should be able to do:
WordDoc.Bookmarks(txtBoxNameDoc2).Range.Text = name
This is because you have already opened "Document2.docx" and furthermore you have specifically assigned it to the WordDoc object variable. Because you have done this, you do not need to explicitly reference it from the Documents collection, as you are doing in your original code.
NB: This assumes that txtBoxNameDoc2 is valid string that identifies a bookmark in the WordDoc document. If it should be interpreted as a literal string (i.e., it is the actual name of the bookmark, then you need to qualify it with quotation marks, like:
WordDoc.Bookmarks("txtBoxNameDoc2").Range.Text = name
If this continues to raise an error, then the named bookmark doesn't exist.
It is possible to assign a bookmark to a TextBox object. Bookmarks do not "automatically" exist in a document, so first you have to ensure such a bookmark exists. You can review these and assign them (if they do not exist) through the ribbon).
Bookmarks don't exist unless you create them. You've assumed that the object's name can also refer to a Bookmark, and while it can, first you need to create the bookmark and assign it the name by which you want to refer it.
Related
I'm trying to export (button click) data from Access recordset (data from 2 columns - "Question" & "Answer") to Word template which has FormFields "Question" & "Answer".
I managed to do it but only until Word document runs out of FormFields (which I added - 100), if recordset has more than 100 rows, it will result with an error "error 5941 the requested member of the collection does not exist" - because it run out of available FormFields (at this time max number of questions is 1100, and it will grow). I can, btw, add several thousand FormFields, but then user will have to delete the empty ones when he uses this option.
I feel like there is an easy way to implement some other kind of loop which will take a document which has only these two FormFields and copy or duplicate first FormField - "Question" and insert data in second, but I still didn't find any way of doing that.
Exporting data from 2 columns in Access recordset, one is "Question" and other "Answer". Code on ButtonClick:
Private Sub cmdToWordMultiple_Click()
Dim db As DAO.Database
Dim rst As DAO.Recordset
Set db = CurrentDb()
Set rst = Me.RecordsetClone
Set Wd = New Word.Application
Set myDoc = Wd.Documents.Add("C:\Users\User\Desktop\ReportQuestions.docx")
Wd.Visible = True
rst.MoveFirst
Do Until rst.EOF
myDoc.FormFields("Question").Range.Text = Nz(rst!Question, "")
myDoc.FormFields("Answer").Range.Text = Nz(rst!Answer, "")
rst.MoveNext
Loop
rst.Close
Set rst = Nothing
objWord.Application.Quit
End Sub
I would like to make this button export data from Access recordset to Word Document, without leaving excess FormFields.
Most efficient is to save the form fields as Building Blocks which can be inserted as often as required. Building Blocks can only be saved in a template, so open ReportQuestions.docx then use File/Save As to save it as a dotx.
Select the pair of form fields, then use Insert->Quick Parts->Save selection to quick part gallery. Fill out the fields in the dialog box being sure to select the template ReportQuestions.dotx for "Save in".
To insert the form fields, the code would look something like below. Note, also, how to get to the end of the document on each iteration, before inserting the next pair of form fields. (See also note below the code!)
Dim tmpl as Word.Template
Dim sTmplPath as String
sTmplPath = "C:\Users\User\Desktop\ReportQuestions.dotx"
Set myDoc = Wd.Documents.Add(sTmplPath)
Set tmpl = myDoc.AttachedTemplate
Dim rngEndOfDoc as Word.Range
Set rngEndOfDoc = myDoc.Content
rngEndOfDoc.Collapse wdWollapseEnd
tmpl.BuildingBlockEntries("Name of Entry").Insert Where:=rngEndOfDoc, RichText:=True
This builds on the code shown in the question. Based on my experience with form fields, I suspect this code, as shown, does not work for more than one pair of form fields because form field names are also Word bookmarks. And bookmark names must be unique (can only appear once) in a document. So Question and Answer can only be used once. I don't know what you're actually naming the form fields, but you'll probably need to add code to rename the form fields once they've been inserted.
Added from OP's comment: The suggestion was successfully implemented like this
Do Until rst.EOF
tmpl.BuildingBlockEntries("Answer").Insert Where:=rngEndOfDoc, RichText:=True myDoc.FormFields("Answer").Range.Text = "ANSWER: " & Nz(rst!Answer, "")
rst.MoveNext
Loop
I have a set of functions in an Access database where I am generating Word documents based on a fixed template in a folder.
I am doing this with the function shown below. For easier maintenance, I would like to be able to define the Word-template paths as public constants in the begining of my module. And therefore, I have been trying to create the adjusted function below.
Original function:
Function MyFunc(rs as DAO.Recordset)
Dim objWord As Object
Dim objDoc As Object
...
Set objWord = CreateObject("Word.Application")
Set objDoc = objWord.Documents.Open("C:\test_template.docx")
...
End Function
Adjusted function:
Public Const ReminderOneTemplate As Variant = "C:\test_template.docx"
...
Function MyFunc(rs as DAO.Recordset)
Dim objWord As Object
Dim objDoc As Object
...
Set objWord = CreateObject("Word.Application")
for the Word.Documents.Open method in the next, subsequent line of code I have tried this:
Set objDoc = objWord.Documents.Open(ReminderOneTemplate)
...
End Function
and
Set objDoc = objWord.Documents.Open(Chr(34) & ReminderOneTemplate & Chr(34))
...
End Function
But the function keeps returning Run-time error '424' Object required when I use a constant as input to the Word.Documents.Open method.
Can anyone explain why this is the case and what I am doing wrong. Is it not possible to pass a Constant to the Word.Open method?
Thanks.
I refactored your function, using the following sub procedure to test the basic code of creating a Word instance, and then opening an existing document using a constant for the document name.
I used Office 2007 to test the code, and everything worked fine. I don't see anything wrong with your code, what version of Access and Word are you using? Notice that I added some code to test whether the objWord variable is actually assigned a value by the call to CreateObject. I would suggest 2 things to help try to resolve the problem you are having:
1. use a String for the filename. I know the documentation for the Open method of the Documents collection says that the filename argument is a Variant, but the code does seem to work better if it is a String.
2. Make sure you set the instance of Word to be visible, otherwise you clutter up your system with invisible instances of Word (which will not be listed in Task Manager) and the only way to get rid of them is to restart the computer. If the Word instance is visible, you can switch to it and see if Word is displaying any error messages.
You may notice that the Word document my code opens is a macro-enabled .docm file. I did this because I tested whether a document with an Open event-handler that caused a runtime error would show the error message in the code in Access, but it does not.
When I first ran the code with the constant declared as a Variant, I did get an error, but not the Object required error that is giving you a problem. I then noticed that when I re-opened the Word document in Word, that I got an error message from Word that "the last time this document was opened it caused a serious error, are you sure you want to continue to open the document?" I would suggest you make sure that you can open your Word document in Word without errors or problems. I also suggest you add code similar to what I have in the example below to ensure that the objWord variable is indeed being initialized by the CreateObject method -- if CreatObject is failing to create an instance of Word, then objWord will still be Nothing, and might then produce an object required runtime error. (problems in the Registry can make CreateObject fail.)
I'm really sorry, but I have no idea why you are getting the error you are getting. I think if you redeclare the constant as a String, and ensure that you make the Word instance visible, that your code will work! The only problem I had when testing was that the document failed to open until I added the code to make the Word instance visible. But I did not get the same error that you are trying to overcome.
I have develop a library of Access VBA code for exactly this type of task -- using code in Access to create instances of Word, Excel, and to open documents and worksheets. If you think looking at a code library designed to provide easily called procedures and functions for inter-operability among MS Office applications, you can download it from here: [http://www.didjiman.com/business/vbademo/libMSOffice.htm] The code in the library has been tested to work in all versions of MS Office from 2003 though 2016, and is released to the public under the Gnu public license. The code is in a zip archive that contains an Access .accdb file with all the code, and a PDF document discussing the functions and procedures, and how to use them, along with complete code listings.
Public Const ReminderOneTemplate As String = "C:\users\matthew\documents\temp\test document1.docm"
Sub testWord_DocOpen()
Dim objWord As Object
Dim objDoc As Object
Set objWord = CreateObject("Word.Application")
objWord.Visible = True 'make the Word application window visible
objWord.Application.WindowState = 1 'maximize the Word application window
If (objWord Is Nothing) Then
Debug.Print "Word object NOT initialized."
End If
Set objDoc = objWord.Documents.Open(ReminderOneTemplate)
End Sub
How do I copy the entire content (approx 2 pages) of a Word document in VBA and store in a variable?
I keep trying several things, none of which works:
Dim mainData As String
ThisDocument.Activate
ActiveDocument.WholeStory 'error on this line
mainData = Selection.Text
With 'record macro' I can simulate selecting a piece or the entire text, but I can't simulate storing that into a variable.
The above code throws
'This command is not available because no document is open',
but hadn't I first activated this (the current) document, and then selected it (ActiveDocument.WholeStory)?
Why doesn't this work?
Later edit: I managed to do the selection like this:
Dim sText As String
Application.Selection.ClearFormatting
Application.Selection.WholeStory
sText = Application.Selection.Text
MsgBox sText
but the problem is I can't store the entire text (2 pages) in a variable. Part of it is truncated. Would you know how to store word by word (I only need a word at a time anyway)?
Later edit. I applied strReverse on the text to find out the text is actually stored entirely in the variable, just not fully displayed in the message box.
Don't use ThisDocument in code, unless you specifically want to address the file in which the code is stored and running. ThisDocument is the "code name" of that file.
Instead, use ActiveDocument to mean the document currently active in the Word window.
An addition, if you want the Selection in the currently active document, there's no reason to activate it - it's already active.
So to get the entire document content in a string
Dim mainData As String
mainData = ActiveDocument.Content.Text
where Content returns the entire main body's text as a Range object.
Note: The MsgBox has an upper character limit. If you're working with long text strings and want to see what they hold the following has more (but not "infinite") capacity:
Debug.Print mainData
All you need is:
Dim mainData As String
mainData = ActiveDocument.Range.Text
I need to find a way to automatically update some procedure documents (word docs) with fields from a spreadsheet:
There are 20 documents in total, so really I would like a single method where I can apply updates to all documents in one go.
All the documents are based on the same template; however, each contians an table (which logs the version history for that document). The content of this table is unique and the number of rows varies from document to document.
The first thing I though of was using a mailmerge; however, this would mean I woudl loose the unique tables from each document and would have to re add them, which would defeate the point of automating the process.
I have made the fields that I want to update in the word doc as Legacy Text Form Fields, and have found some VBA code which enables me to specify, that specific form fields should update from specific cells in my spreadsheet; however, I have to specify these for each docment individually.
This is the code I am using
"FormFields("Field1").Result =ActiveWorkbook.Sheets("Sheet4").Range("A2").Value"
Ideally I need a method that will work for every word doc in a given folder, identifing the related row in the spreadsheet and updating the doc accordingly. The word docs are named as the Doc ID (first column in spreadsheet).
Add DocVariables in Word. If you don't know how to do this, Google it. Then, run the script below, from Excel.
Sub PushToWord()
Dim objWord As New Word.Application
Dim doc As Word.Document
Dim bkmk As Word.Bookmark
sWdFileName = Application.GetOpenFilename(, , , , False)
Set doc = objWord.Documents.Open(sWdFileName)
objWord.activedocument.variables("BrokerFirstName").Value = Range("BrokerFirstName").Value
objWord.activedocument.variables("BrokerLastName").Value = Range("BrokerLastName").Value
ActiveDocument.Fields.Update
objWord.Visible = True
End Sub
Finally, loop through all Word docs in a single folder.
I'm writing a Word/VBA macro for a document template. Every time a user saves/creates a new document from the template, the document needs an ID embedded in the text. How can I (as simple as possible) implement auto-increment for this ID? The ID is numeric.
The system has to have some kind of mechanism to avoid different documents getting the same IDs, but the load is very low. About 20 people will use this template (on our intranet), creating something like 20 new documents a week altogether.
I've toyed with the idea of having a text file that I lock and unlock from the macro, or call a PHP page with an SQLite database, but is there other, smarter solutions?
Note that I can't use UUID or GUID, since the IDs need to be usable by humans as well as machines. Our customers must be able to say over the phone: "... and about this, then, with ID 436 ...?"
Gave some further thought to this, and here is another approach you may want to consider. If you're not interested in a catalog of previous IDs, then you could simply use a custom document property to store the last ID that was used.
In Word 97-2003, you can add a custom property by going to "File / Properties", choosing the custom tab and assigning a name and value there. Adding a custom document property in Word 2007 is a bit more buried and off the top of my head, I think it's "Office Button / Prepare / Document Properties", choose the little drop down box for advanced properties and you'll get the same ol' pre-2007 dialog.
In the example below, I called mine simply "DocumentID" and assigned it an initial value of zero.
The relevant bit of code to update a Custom document property is:
ThisDocument.CustomDocumentProperties("DocumentID").Value = NewValue
As a proof of concept, I created a .dot file and used the following code in the Document_New() event:
Sub UpdateTemplate()
Dim Template As Word.Document
Dim NewDoc As Word.Document
Dim DocumentID As DocumentProperty
Dim LastID As Integer
Dim NewID As Integer
'Get a reference to the newly created document
Set NewDoc = ActiveDocument
'Open the template file
Set Template = Application.Documents.Open("C:\Doc1.dot")
'Get the custom document property
Set DocumentID = Template.CustomDocumentProperties("DocumentID")
'Get the current ID
LastID = DocumentID.Value
'Use any method you need for determining a new value
NewID = LastID + 1
'Update and close the template
Application.DisplayAlerts = wdAlertsNone
DocumentID.Value = NewID
Template.Saved = False
Template.Save
Template.Close
'Remove references to the template
NewDoc.AttachedTemplate = NormalTemplate
'Add your ID to the document somewhere
NewDoc.Range.InsertAfter ("The documentID for this document is " & NewID)
NewDoc.CustomDocumentProperties("DocumentID").Value = NewID
End Sub
Good luck!
You could handle this entirely through VBA using Word and Excel (or Access I suppose, but I have an unnatural aversion towards using Access).
First, create a new Excel workbook and store it in a location that you can access through your word document (mine is C:\Desktop\Book1.xls). You may even want to seed the values by entering a numeric value into cell A1.
In your word document, you would enter this into your Document_Open() subroutine:
Private Sub Document_Open()
Dim xlApp As Excel.Application
Dim xlWorkbook As Excel.Workbook
Dim xlRange As Excel.Range
Dim sFile As String
Dim LastID As Integer
Dim NewID As Integer
'Set to the location of the Excel "database"
sFile = "C:\Desktop\Book1.xls"
'Set all the variables for the necessary XL objects
Set xlApp = New Excel.Application
Set xlWorkbook = xlApp.Workbooks.Open(sFile)
'The used range assumes just one column in the first worksheet
Set xlRange = xlWorkbook.Worksheets(1).UsedRange
'Use a built-in Excel function to get the max ID from the used range
LastID = xlApp.WorksheetFunction.Max(xlRange)
'You may want to come up with some crazy algorithm for
'this, but I opted for the intense + 1
NewID = LastID + 1
'This will prevent the save dialog from prompting the user
xlApp.DisplayAlerts = False
'Add your ID somewhere in the document
ThisDocument.Range.InsertAfter (NewID)
'Add the new value to the Excel "database"
xlRange.Cells(xlRange.Count + 1, 1).Value = NewID
'Save and close
Call xlWorkbook.Save
Call xlWorkbook.Close
'Clean Up
xlApp.DisplayAlerts = True
Call xlApp.Quit
Set xlWorkbook = Nothing
Set xlApp = Nothing
Set xlRange = Nothing
End Sub
I realize this is a tall procedure, so by all means re-factor it to your heart's content. This was just a quick test I whipped up. Also, you'll need to add a reference to the Excel Object Library through References in VBA. Let me know if you have any questions about how that works.
Hope that helps!
Well you have to store the next ID number somewhere. The text file idea is as good as any. You just have to handle the possibility of it being locked or unaccessible for some reason.
Using a database for one number is overkill.
Off the top of my head:
Use Excel as your external DB with Automation.
Explore the several SQLite COM wrappers (Litex comes to mind).
"text file that I lock and unlock from the macro" would be the safest approach.
The DOCID file would only have one number: the last ACTUALLY used ID.
A) You read the file (not in write/append mode) and store on a variable on your document DOC_ID =FILE_ID+1 and save the doc. Tentatively you kill the DOCID file, open/create for read-write sotring your DOC_ID. Close the file. If all went well including Close, you're safe, otherwise, back to A).
You might want to consider: if no file is found create it with this document ID +100, as a measure of recovering from no-UPS disasters whilst in A)
I'm too tired to check if it might create a deadlock under concurrency scenario... it might.
If you feel its worth it, I can put code here.
It seems I found a way to open and update a text file with exclusive rights, which means that there will be no concurrency problems:
Private Function GetNextID(sFile As String) As Integer
Dim nFile As Integer
nFile = FreeFile
On Error Resume Next
Open sFile For Binary Access Read Write Lock Read Write As #nFile
If Err.Number <> 0 Then
' Return -1 if the file couldn't be opened exclusively
GetNextID = -1
Err.Clear
Exit Function
End If
On Error GoTo 0
GetNextID = 1 + Val(Input(LOF(nFile), #nFile))
Put #nFile, 1, CStr(GetNextID)
Close #nFile
End Function
Simply call this function until it doesn't return -1 anymore. Neat.