Update linked fields in Word document from Excel VBA - vba

I am trying to automatically update certain information (such as names, dates and numbers) across 3 different Word documents by putting the data into a spreadsheet and linking to the respective cells from Word. The spreadsheet has some Macros in it which auto-update parts of the spreadsheet internally.
Everything is working fine, except for updating the links in the Word documents.
When trying to update a link in Word by right-clicking on it and selecting the "update link" option it brings up the Macro warning dialog for the spreadsheet, asking whether I want to activate Macros or not. It doesn't do this just once but constantly during the 20s or so the update process takes (which seems unusually long to me). So updating the link works, but only if you're willing to click the "activate Macros" button of a few dozen times.
I tried to automate updating all fields in a document from Word with VBA, but that has the same problem, it also brings up the Macros dialog constantly for half a minute.
Here's my code for that:
Sub UpdateFields()
ActiveDocument.Fields.Update
End Sub
I also tried to update the Word documents directly from the spreadsheet, but that does not work either, because when Excel tries to open a Word document via VBA the program stops executing and trows this error:
"Excel is waiting for another application to complete an OLE action."
Clicking ok and waiting does not help because the error message reappears after a few seconds, and the only way to stop it is to manually kill the Excel process.
Here's my Excel Macro code:
Sub LoopThroughFiles()
Path = Application.ActiveWorkbook.Path
Dim WordFile As String
WordFile = Dir(Path & "\*.doc")
Do While Len(WordFile) > 0
Run Update(Path & "\" & WordFile)
WordFile = Dir
Loop
End Sub
Function Update(Filepath As String)
Dim WordDoc As Word.Document
Set WordApplication = CreateObject("Word.Application")
Set WordDoc = WordApplication.Documents.Open(Filepath) 'This produces the error
ActiveDocument.Fields.Update
End Function
Note that the only files in the folder are the 3 documents and the spreadsheet, and the program does find the files without any problems.
I have searched for solutions online but I did not really find anything, which I found odd, since it seems like a pretty common thing that someone would do with VBA.
Then again, I have very little experience with VBA, so I might be completely missing the point and there is a super simple solution I am just not aware of.

I think I see the error, which is a silent failure, becuase the document contains links, there is an open dialog waiting for you to say "yes" or "no" to update the links.
We can suppress this dialog by disabling the automatic link updates (WordApplication.Options.UpdateLinksAtOpen = False).
Function Update(Filepath As String)
Dim WordApplication As Word.Application
Dim WordDoc As Word.Document
Dim updateLinks As Boolean
Set WordApplication = CreateObject("Word.Application")
updateLinks = WordApplication.Options.UpdateLinksAtOpen 'capture the original value
WordApplication.Options.UpdateLinksAtOpen = False 'temporarily disable
Set WordDoc = WordApplication.Documents.Open(Filepath)
WordDoc.Fields.Update
'MsgBox "Links updated in " & WordDoc.Name
'## Save and Close the Document
WordDoc.Save
WordDoc.Close
'## reset the previous value and Quit the Word Application
WordApplication.Options.UpdateLinksAtOpen = updateLinks '
WordApplication.Quit
End Function
Also, remember to Save and Close the document, and Quit the word application inside the function.
I made this other modification:
In your function, ActiveDocument is not an object in Excel, so you would need to qualify it, otherwise that line will also throw an error. Rather than refer to WordApplication.ActiveDocument, I just simply refer to the WordDoc which you have already assigned.

Related

How to make the new document visible after creating from a template?

I use the macro Word letter template to generate customized letters. After I run the macro, the new document is not in focus, and is behind other documents that are opened.
The new document hasn't been saved, so the name of the document is "Document XX"
I tried ActiveDocument.Activate, and the code below. The letter template is a macro userform.
Dim odoc As Document
Set odoc = Documents.Add("\\XXXX\LetterTemplate.dotx", Visible:=True)
How do I bring the active document into view?
The Active Document is already active.
Try:
oDoc.Activate
It should have become the active document when created and at the front. Do you have other code involving screenupdating?
A radical approach is to minimize all open application windows. Then when your code opens a document, it will be the only window displayed.
Sub MinimizeAll()
Dim shell As Object
Set shell = CreateObject("shell.application")
shell.MinimizeAll
Set shell = Nothing
End Sub
You and your end-users will have to decide if that is an acceptable approach because this can be an annoying solution on two-monitor systems.

Word VBA Runtime Error 5460 on second Documents.Add in global template

For a customer, I have created a Global template with a ’New Document” button that shows a userform letting the user create a new document from a choice of templates, e.g. Letter, Invoice, etc.
Recently, we have added the choice Report.
The code executed from this userform is very simple, using e.g.
Documents.Add Template:="Letter.dotm", NewTemplate:=False
to create the new documents.
The Report template is – by my standards – quite complex, with AutoNew and AutoClose macros, an EventClassModule, it writes to and reads from the CustomDocumentProperties, opens several specific Word documents from where it copies text and pastes it into the Report document, etc.
The first time a new Report is created it works as planned; but the second time (in the same Word session) the Report option is used, a ‘Runtime Error 5460’ occurs. And after that, any of the other document options returns the same error.
Quitting Word and starting a new Word session sets everything back to normal, until the Report template again is called the second time.
Strangely enough, the Report template works with no errors when new documents based on it are created directly from Explorer, as many times in the same Word session as needed.
The problem occurs in Word 2016 (365) with Windows 7 Pro, but not in Word 2013 with Windows 10.
Anybody that has ever experienced anything like this?? Help is really appreciated.
Runtime Error 5460: 'A file error has occured.'
Debug is not possible.
The Report template has thousands of lines of code and I have not been able to find out if it is in fact code in the Report template that causes the error, or code in the Global template.
As said, the Report template works fine when used from Explorer, and when called via the Global template in 2013 everything works there too.
Problem solved!
I followed the advice from #macropod and added the path also.
In stead of just using
'…
If OptionButton3.Value = True Then
Documents.Add Template:="Report.dot", NewTemplate:=False
End If
'…
I changed the code to:
Private Sub CommandButton1_Click()
Dim strPath As String
strPath = Options.DefaultFilePath(wdWorkgroupTemplatesPath)
Dim wdApp As Word.Application
Set wdApp = GetObject(, "Word.Application")
'…
If OptionButton3.Value = True Then
wdApp.Documents.Add Template:=strPath & "\Report.dot", NewTemplate:=False
End If
'…
End Sub
Thanks!!

VBA - Internet Data Scrape & Download File & Copy Paste Data

Summary: We need to go to a website, download an Excel file, and copy&paste the data into an Excel template file all automatically.
Current Problem: My current VBA code opens IE, does the data scrape and downloads the file automatically without any problems. However, ONLY in Break Mode that it opens the downloaded workbook and copy&paste the data into the template.
What Didn't Work: I tried multiple ideas like Sleep, Application.Wait, and DoEvents. Unfortunately, it keeps getting stuck on the same line. The code does not recognize a Workbook has been open and can't find the data to copy&paste. Only after I enter into the Break Mode that the file will eventually open.
Observation: Security Settings on Excel removed so I do not receive a warning message when opening a file.
This snippet is part of a loop which will make the same procedure you will find below for 5 different files. After it downloads each file, it copies&paste the data into my template.
Any idea & solution is totally welcomed!
Please find my code below:
sub test()
'-----------------------------------
'Code to do the Data Scrape in here. It works fine.
'-----------------------------------
Dim o As IUIAutomation
Dim e As IUIAutomationElement
Dim h As Long
Dim wkbook As Workbook
Dim Cwkbook As String, ThisWkbook As String
'Code to Automate IE Download
Set o = New CUIAutomation
h = ieApp.hWnd
h = FindWindowEx(h, 0, "Frame Notification Bar", vbNullString)
If h = 0 Then Exit Sub
Set e = o.ElementFromHandle(ByVal h)
Dim iCnd As IUIAutomationCondition
Set iCnd = o.CreatePropertyCondition(UIA_NamePropertyId, "Open")
Dim Button As IUIAutomationElement
Set Button = e.FindFirst(TreeScope_Subtree, iCnd)
Dim InvokePattern As IUIAutomationInvokePattern
Set InvokePattern = Button.GetCurrentPattern(UIA_InvokePatternId)
InvokePattern.Invoke
'Download Completed and File Should be opened.
DoEvents
Application.Wait Now + #12:00:10 AM#
'Even after using DoEvents & Application.Wait, the code won't open the workbook until it goes into Break Mode.
'Code to find Downloaded Workbook which will come named either as X or Y.
ThisWkbook = ThisWorkbook.Name
For Each wkbook In Application.Workbooks
If Left(wkbook.Name, 9) = "XXXXXXXXX" Or Left(wkbook.Name, 9) = "YYYYYYYYY" Then
Cwkbook = wkbook.Name
'-----------------------------------
'Code to Copy&Paste data in here. It works fine.
'-----------------------------------
End If
Next
End Sub
You will need the UIAutomationClient reference to make the code work. If you get an error when including it (like I did), add "Microsoft Visual Basic for Applications Extensibility" reference and run the code below:
Sub MyUIAutomation()
'----------------------------------------------------
'Add UIAutomation as VBA Reference
'----------------------------------------------------
Dim VBProj As VBIDE.VBProject
Set VBProj = ThisWorkbook.VBProject
VBProj.References.AddFromFile Environ("systemroot") & "/system32\uiautomationcore.dll"
Set VBProj = Nothing
End Sub
UPDATE 8.28.17: I tried a work around to copy/paste the data only when a Workbook is opened. The idea would be to finish my main "data extract" code. After the code is finished the Workbooks should open as it was behaving before, and the second code would trigger, copying/pasting the data.->
Run VBA macro whenever any workbook is opened
Outcome: It is still not working. It would only open the last workbook of the whole loop and copy&paste the data of it. The other 4 previous files wouldn't open.
UPDATE 8.31.17 Another work around failed. I tried to create an User Form, where the user would have to click on 5 different buttons. The idea is to check if my problem was the looping. Each button would extract the file and after that my Copy&Paste Macro would trigger.
Outcome: It did not work as well. I figure that while I have my "User Form" open excel recognizes that a macro is running. If I run it under Break Mode it will still open the file and copy the data.
I believe it would be important to other people know that I found a solution to my own problem.
The main problem was that my original code was structured this way:
'Open IE -> Search for a specific criteria -> Download File -> Open File -> Copy&Paste File.
It would repeat the above procedure 5 times for each different file I need.
The code would run perfectly under break mode. However, when you actually ran it, it wouldn't bring over the data from the downloaded file.
FIX: It is a work around. I separated my code into pieces. The first piece is going to search, download, and open the file I want. The second piece will only trigger when a file with a specific name is opened. It will then perform all the remaining steps I need.
I used the Workbook_Open event to do such thing:
Private Sub Workbook_Open()
The third and last piece, is a form created using shapes like circles, squares, and text box. It actually looks way nicer than an userform. Each circle performs like a button and will trigger the download of a specific file when clicked.
Now the code is structured like:
'1. Form using Shapes, each circle activates a specific script
'2. 5 Scripts -> Open IE, Search for a specific criteria, Download File, Open File
'3. 1 Script. Only Triggers when Excel Workbooks are opened. It will then only copy the data over if it meets certain criteria (such as filename).

Activate Word window from Excel with VBA

I am trying access window of MS Word from Excel. I found methods to access a new Word document or a specific one, like the one at
Copy Text from Range in Excel into Word Document,
But in my case I do not know the name of the document, it should be the last active one. I was hoping to use something like
Word.ActiveDocument
but no success. I also tried to simulat Alt+Tab keystroke to activate the window with
Application.SendKeys("%{TAB}")
but it does not work too. Any hint?
I am trying to create a macro that will just copy charts and some text around to Word and do some formating of the text along with it. So basically I can use any approach to this task.
Thanks a lot.
You can access an open instance of Word by using late binding (http://support.microsoft.com/kb/245115) and GetObject. If you have multiple instances of Word open you are not guaranteed of getting any one of them in particular though.
Getting an instance of Word will allow you to access the ActiveDocument or the Application's current Selection. I'd still suggest doing some error checking to make sure you've got what you want.
Sub GetWordDocument()
Dim wdApp As Object
'Turn off error handling since if the Application is not found we'll get an error
'Use Late Binding and the GetObject method to find any open instances of Word
On Error Resume Next
Set wdApp = GetObject(, "Word.Application")
On Error GoTo 0
'Check to see if we found an instance. If not you can create one if you desire
If wdApp Is Nothing Then
MsgBox "No instances of Word found"
Exit Sub
End If
'Check if there are documents in the found instance of Word
If wdApp.Documents.Count > 0 Then
wdApp.Selection.TypeText "Cool, we got it" & vbCr
'You can now access any of the active document properties too
wdApp.ActiveDocument.Range.InsertAfter "We did indeed"
End If
'Clean up the Object when Finished
Set wdApp = Nothing
End Sub

Cancel External Query in Excel VBA

I have created an Excel Spreadsheet which helps with data analysis from an Oracle database.
The user enters then clicks the "Refresh Query" button which generates a query for Oracle to execute. The query takes a minute or so to complete. Although the VBA code does not hang on ".Refresh", all Excel windows remain frozen until the query completes.
Sub refreshQuery_click()
Dim queryStr as String
' Validate parameters and generate query
' ** Code not included **
'
' Refresh Query
With ActiveWorkbook.Connections("Connection").OLEDBConnection
.CommandText = queryStr
.Refresh
End With
End Sub
Is there a way for the user to manually cancel the query (calling .CancelRefresh) while the Excel user-interface is frozen?
EDIT I don't know if the following is worth noting or regular behavior. While the query is executing, all open Excel windows (including the VBA Editor) become "Not Responding" in Task Manager. Neither pressing Esc nor Ctrl+Break will cancel the script. Also, calling DoEvents (either before or after .Refresh) does not change this behavior.
Here's a method that I know will work. However, there are some complications.
Here's how it's done:
Put the spreadsheet with the data in a separate workbook. This worksheet should execute the refresh query when it's opened and then close once the data is updated.
Create a batch file to call the "Data" Excel file.
Within a different workbook, create a procedure (macro) for the user to call. This procedure will call the batch file, which subsequently calls the Excel file. Since you are calling a batch file and not Excel directly, the Excel procedure will continue because the command shell is released so quickly and opens the other Excel file in a different thread. This allows you to continue working within the main Excel file.
Here are some complications:
I included a method to alert the user that the data has been udpated. There are timing issues where it's possible to try to check if the data has been update when the workbook is not accessible, which forces the user to try to update values. I included a method called my time which pauses the execution of the code so it only checks every so many seconds.
The updated worksheet will pop up in a new window, so the user will need to click on their original worksheet and keep working. You could learn to hide this if you're comfortable with Windows scripting (I haven't learned that yet).
Here are some files and code. Be sure to read the comments in the code for why some things are there.
FILE: C:\DataUpdate.xls
We'll make a workbook called "DataUpdate.xls" and put it in our C:\ folder. In cell A1 of Sheet1, we'll add our QueryTable which grabs external data.
Option Explicit
Sub UpdateTable()
Dim ws As Worksheet
Dim qt As QueryTable
Set ws = Worksheets("Sheet1")
Set qt = ws.Range("A1").QueryTable
qt.Refresh BackgroundQuery:=False
End Sub
Sub OnWorkbookOpen()
Dim wb As Workbook
Set wb = ActiveWorkbook
'I put this If statement in so I can change the file's
'name and then edit the file without code
'running. You may find a better way to do this.
If ActiveWorkbook.Name = "DataUpdate.xls" Then
UpdateTable
'I update a cell in a different sheet once the update is completed.
'I'll check this cell from the "user workbook" to see when the data's been updated.
Sheets("Sheet2").Range("A1").Value = "Update Table Completed " & Now()
wb.Save
Application.Quit
End If
End Sub
In the ThisWorkbook object in Excel, there's a procedure called Workbook_Open(). It should look like the following so it executes the update code when it is opened.
Private Sub Workbook_Open()
OnWorkbookOpen
End Sub
NOTE: I found a bug when this file closed if 1) you accessed the file from the command line or shell and 2) you have the Office Live Add-in installed. If you have the Office Live Add-in installed, it will throw an exception on exit.
FILE: C:\RunExcel.bat
Next, we're going to create a batch file that will open the Excel file we just made. The reason that call the Excel file from within the batch file and not directly from the other Excel file using Shell is because Shell will not continue until the other application closes (at least when using Excel.exe "c:\File.xls"). The batch file, however, runs its code and then immediately closes, thus allowing the original code that called it to continue. This is what will let your uses continue working in Excel.
All this file needs is:
cd "C:\Program Files\Microsoft Office\Office10\"
Excel.exe "C:\DataUpdate.xls"
If you're handy with Windows Scripting, you do fancy things like open the window in a hidden mode or pass a parameter of the file name or Excel location. I kept it simple with a batch file.
FILE: C:\UserWorkbook.xls
This is the file that the user will open to "do their work in." They'll call the code to update the other workbook from within this workbook and they'll still be able to work in this workbook while this one is updating.
You need a cell in this workbook where you'll check the "Update Table Completed" cell from the DataUpdate workbook. I chose cell G1 in Sheet1 for my example.
Add the following code to a VBA module in this workbook:
Option Explicit
Sub UpdateOtherWorkbook()
Dim strFilePath As String
Dim intOpenMode As Integer
Dim strCallPath As String
Dim strCellValue As String
Dim strCellFormula As String
Dim ws As Worksheet
Dim rng As Range
Set ws = Worksheets("Sheet1")
Set rng = ws.Range("G1")
strCellFormula = "='C:\[DataUpdate.xls]Sheet2'!A1"
'This makes sure the formula has the most recent "Updated" value
'from the data file.
rng.Formula = strCellFormula
strFilePath = "C:\RunExcel.bat"
intOpenMode = vbHide
'This will call the batch file that calls the Excel file.
'Since the batch file executes it's code and then closes,
'the Excel file will be able to keep running.
Shell strFilePath, intOpenMode
'This method, defined below, will alert the user with a
'message box once the update is complete. We know that
'the update is complete because the "Updated" value will
'have changed in the Data workbook.
AlertWhenChanged
End Sub
'
Sub AlertWhenChanged()
Dim strCellValue As String
Dim strUpdatedCellValue As String
Dim strCellFormula As String
Dim ws As Worksheet
Dim rng As Range
Set ws = Worksheets("Sheet1")
Set rng = ws.Range("G1")
strCellFormula = "='C:\[DataUpdate.xls]Sheet2'!A1"
strCellValue = rng.Value
strUpdatedCellValue = strCellValue
'This will check every 4 seconds to see if the Update value of the
'Data workbook has been changed. MyWait is included to make sure
'we don't try to access the Data file while it is inaccessible.
'During this entire process, the user is still able to work.
Do While strCellValue = strUpdatedCellValue
MyWait 2
rng.Formula = strCellFormula
MyWait 2
strUpdatedCellValue = rng.Value
DoEvents
Loop
MsgBox "Data Has Been Updated!"
End Sub
'
Sub MyWait(lngSeconds As Long)
Dim dtmNewTime As Date
dtmNewTime = DateAdd("s", lngSeconds, Now)
Do While Now < dtmNewTime
DoEvents
Loop
End Sub
As you can see, I constantly updated the formula in the "Listening Cell" to see when the other cell was updated. Once the data workbook has been updated, I'm not sure how you'd force an update in code without rewriting all the cells. Closing the workbook and reopening it should refresh the values, but I'm not sure of the best way to do it in code.
This whole process works because you're using a batch file to call Excel into a different thread from the original file. This allows you to work in the original file and still be alerted when the other file has been updated.
Good luck!
EDIT: Rather than include a more complete answer in this same answer, I've created a separate answer dedicated entirely to that solution. Check it out below (or above if it gets voted up)
Your users can break the VBA function by pressing Ctrl+Break on the keyboard. However, I've found that this can cause your functions to randomly break until each time any function is run. It goes away when the computer is restarted.
If you open this file in a new instance of Excel (meaning, go to Start > Programs and open Excel from there), I think that the only workbook that will be frozen will be the one executing the code. Other intances of Excel shouldn't be affected.
Lastly, you might research the DoEvents functions, which yields execution back to the Operating System so that it can process other events. I'm not sure if it would work in your case, but you could look into it. That way you can do other things while the process is being completed (It's kind of dangerous because the user can then change the state of your application while the process is working).
I believe I know a way that actually will work, but it's complicated and I don't have the code in front of me. It involves creating a separate instance of the Excel application in code and attaching a handler to the execution of that instance. You include the DoEvents part of the code in a loop that releases once the application closes. The other instantiated Excel application has the sole purpose of opening a file to execute a script and then close itself. I've done something like this before so I know that it works. I'll see if I can find the code tomorrow and add it.
Well, you could consider the old-fashion way -- split the query into smaller batches and use Do Events in between batches.
You could try XLLoop. This lets you write excel functions (UDfs) on an external server. It includes server implementations in many languages (eg. Java, Ruby, Python, PHP).
You could then connect to your oracle database (and potentially add a caching layer) and serve up the data to your spreadsheet that way.
The XLL also has a feature to popup a "busy" GUI that lets the user cancel the function call (which disconnects from the server).
BTW, I work on the project so let me know if you have any questions.