How to write VBA code that works in multiple Office apps - vba

I want to write a single VBA code module that works on the three main Office Apps (Excel, PowerPoint, Word).
Because the object models are different in each app, if I write code that's specific for PowerPoint while in the Excel VBE, the project won't compile. The way to go first appears to be to use conditional compiler constants. But this still causes the VBE to spit out errors depending on which MSO app the VBE is currently being hosted in.
In the simplified example below, I want to add a picture to a sheet, slide or document, depending on which app the VBA code is being run from. If I try to compile it in Excel, the PowerPoint code doesn't compile (even though it's within a conditional compiler If...Then statement!) and vice-versa. How does one get round this without adding references to the other MSO apps (as this causes compatibility issues when distributing to different MSO versions)?
The way the compiler continues to look at code that should be effectively "commented out" by the conditional compiler constants is very odd/annoying behaviour!
' Set the compiler constant depending on which MSO app is hosting the VBE
' before saving as the respective .ppam/.xlam/.dotm add-in
#Const APP = "EXL"
Option Explicit
Dim curSlide As Integer
Dim curSheet As Integer
Public Sub InsertPicture()
Dim oShp as Shape
#If APP = "PPT" Then
' Do PowerPoint stuff
' The next 2 lines will throw "Invalid qualifier" and
' "Variable not defined" errors respectively when compiling in Excel.
curSlide = ActiveWindow.View.Slide.SlideIndex
Set oShp = ActivePresentation.Slides(curSlide).Shapes.AddPicture & _
(filename, msoFalse, msoTrue, 0, 0)
#ElseIf APP = "EXL" Then
' Do Excel stuff
curSheet = ActiveWindow.ActiveSheet
Set oShp = ActiveSheet.AddPicture(filename, msoFalse, msoTrue, 0, 0)
#ElseIf APP = "WRD" Then
' Do Word stuff
#End If
End Sub
Since I'm unable to answer my own question:
Expanding on your idea KazJaw, I think something like this may work, replacing the CreateObject function with GetObject (because the instance will already exist since the procedure is being called from within an add-in):
' CONDITIONAL COMPILER CONSTANTS
' Set this value before saving to .ppam, .xlam or .dotm
#Const APP = "EXL" ' Allowed Values : PPT, EXL or WRD
Sub One_Sub_For_Word_Excel_PP(filename As String, Optional SlideIndex as Integer)
#If APP = "PPT" Then
Dim appPPP As Object
Set appPPT = GetObject(, "PowerPoint.Application")
appPPT.ActivePresentation.Slides(SlideIndex).Shapes.AddPicture & _
(filename,msoFalse,msoTrue,0,0)
#ElseIf APP = "EXL" Then
Dim appEXL As Object
Set appEXL = GetObject(, "Excel.Application")
appEXL.ActiveSheet.AddPicture(filename, msoFalse, msoTrue, 0, 0)
#ElseIf APP = "WRD" Then
Dim appWRD As Object
Set appWRD = GetObject(, "Word.Application")
appWRD.ActiveDocument.AddPicture(filename, msoFalse, msoTrue, 0, 0)
#End If
End Sub

You could try:
Public AppName as String
Public App as Object
Sub One_Sub_For_Word_Excel_PP(filename As String, Optional SlideIndex as Integer)
AppName = Application.Name
Set App = Application
Select Case AppName
Case "Microsoft PowerPoint"
App.ActivePresentation.Slides(SlideIndex).Shapes.AddPicture & _
(filename,msoFalse,msoTrue,0,0)
Case "Microsoft Excel"
App.ActiveSheet.AddPicture(filename, msoFalse, msoTrue, 0, 0)
Case "Microsoft Word"
App.ActiveDocument.AddPicture(filename, msoFalse, msoTrue, 0, 0)
End Select
End Sub
Alternatively, write a COM Add-in.

As I stated in my comment- I can't imagine the situation I would like to use the solution you are trying to prepare. However, there is one solution even you set lot's of limitations (including not setting references to other application libraries). Please keep in mind that such attempt will be not efficient and I would never recommend anything like this.
The following test subroutine works for all three applications: MS Word, MS PowerPoint, MS Excel. Additional information in comments inside the code.
Sub One_Sub_For_Word_Excel_PP()
Dim XLS As Object
Dim PP As Object
Dim WRD As Object
'this will open instances of all application- to avoid any errors
Set XLS = CreateObject("Excel.Application")
Set PP = CreateObject("PowerPoint.Application")
Set WRD = CreateObject("Word.Application")
'your code here
'remember- do not use vba constants like msoFalse but use _
their numeric values instead
'simple test
If Application.Name = "Microsoft Excel" Then
'do things only for excel
Debug.Print XLS.Name
ElseIf Application.Name = "Microsoft PowerPoint" Then
'do things only for PP
Debug.Print PP.Name
Else
'do things only for Word
Debug.Print WRD.Name
End If
Set XLS = Nothing
Set PP = Nothing
Set WRD = Nothing
End Sub

Isn't it
#Const APP = "EXL"
#If APP = "PPT" Then
etc.?

I'm assuming that you want the same code to be able to run from within any VBA-enabled app (but not necessarily to invoke other apps). So ...
Sub One_Sub_To_Rule_Them_All()
' Modified version of KazJaw's previous post
Dim oApp As Object
Set oApp = Application
Select Case oApp.Name
Case Is = "Microsoft Excel"
'do things only for excel
Case Is = "Microsoft PowerPoint"
'do things only for PP, eg
MsgBox oApp.ActivePresentation.Fullname
Case Is = "Microsoft Word"
' do wordthings
Case Is = "Visio or CorelDraw or Whatever"
' do whatever things
Case Else
MsgBox "Jumping up and down and waving hands and running around like headless chicken"
End Select
Set oApp = Nothing
End Sub
All the same, I wouldn't do it this way. Apart from the other objections, you need to treat the apps as objects in order for the code to compile, and when you do that, you toss out intellisense. Not a trivial loss. Sure, you can get around that by developing the Word part in Word, the PPT part in PPT ... but in that case, why not just make separate code modules?

Related

Selection.Goto from Excel to Word

I am using a spreadsheet to capture test cases, and in the process of automating the generation of a Word document for presentation to the business. I can't get the GoTo function to work across the files, however.
All of the subs are written in the Excel VBA instance.
Here are the two pertinent subs:
Sub 1
Sub CreateTestDocument()
Set wordapp = CreateObject("word.Application")
Set Wordfile = wordapp.Documents.Open("S:\myPath\myFilename.dotm")
wordapp.Visible = True
AddNextCase ("FeatureCases")
End Sub
'Sub 2 (called from Sub 1)
Sub AddNextCase(Bmark As String)
Wordfile.Activate
Wordfile.SelectAllEditableRanges
ActiveWindow.Selection.Goto What:=wdGoToBookmark, Name:=Bmark 'ERROR HERE
Selection.TypeText "TEST1"
End Sub
on the Selection.Goto line, I get the error below:
Run-time error 438:
Object doesn't support this property or method
I've tried various different approaches to this, but i always hit a blocker on setting my entry point to start putting this text block in, what's the obvious issue i'm missing?
You need to make sure that a couple things are addressed:
Make sure that the wordapp and wordfile objects are defined at the module level. I have shown this below using object type references from the Microsoft Word XX.X Object Library. This will also make sure that any word constants you use are defined.
Then you need to use the wordapp context for the activewindow call. This is done with wordapp.activewindow
Private wordapp As Word.Application, _
wordfile As Word.Document
Sub CreateTestDocument()
Set wordapp = CreateObject("word.Application")
Set wordfile = wordapp.Documents.Open("FILE NAME")
wordapp.Visible = True
AddNextCase ("FeatureCases")
End Sub
Sub AddNextCase(Bmark As String)
wordfile.Activate
wordfile.SelectAllEditableRanges
wordapp.ActiveWindow.Selection.Goto What:=wdGoToBookmark, Name:=Bmark
wordapp.Selection.TypeText "TEST1"
End Sub
As per MDN (paraphrased):
.ActiveWindow is an Application property that returns the
active window (Window Object).
You can't however apply a method to a property, you need to apply it to a Window Object instead. Easiest way of achieving this is to store the window object into a variable.
Additionally, your variables such as wordapp or Wordfile are presumably objects, since you have Set them, but there are nowhere declared in your code.
So your code should look something like this:
Private Wordfile as Word.Document
Set Wordfile = CreateObject("word.Application")
Private word as Word.Application
Set word = wordapp.Documents.Open("S:\myPath\myFilename.dotm")
' ... rest of the code
wordapp.ActiveWindow.Selection.GoTo '... etc
In general I would recommend doing a bit of studying into objects, because it looks like you have some learning gaps (with all due to respect)

How to call Word macros from Excel

I have two macros, one in Excel, and one in Word. The Excel Macro calls the Word macro. My code is as follows:
Excel:
Public wb1 As Workbook
Public dt1 As Document
Sub openword()
Dim wpath, epath As String 'where the word document will be opened and where the excel sheet will be saved
Dim wordapp As Object 'preparing to open word
Set wb1 = ThisWorkbook
While wb1.Sheets.Count <> 1
wb1.Sheets(2).Delete
Wend
wpath = "C:\users\GPerry\Desktop\Projects and Work\document.docm"
Set wordapp = CreateObject("Word.Application")
'Set wordapp = CreateObject(Shell("C:\Program Files (x86)\Microsoft Office\Office14\WINWORD", vbNormalFocus)) this is one I tried to make work because while word.application seems to work, I don't *understand* it, so if anyone can help, that'd be awesome
wordapp.Visible = True
Set dt1 = wordapp.Documents.Open(wpath)
wordapp.Run "divider", wb1, dt1
dt1.Close
wordapp.Quit
End Sub
And word:
Sub divider(wb1, dt1)
Set dt1 = ThisDocument
If dt1.Paragraphs.Count > 65000 Then
Set cutrange = dt1.Range(dt1.Paragraphs(1).Range.Start, dt1.Paragraphs(65000).Range.End)
If wb1.Sheets(Sheets.Count).Cells(1, 1) <> "" Then
wb1.Sheets.Add After:=Sheets.Count
End If
Else
Set cutrange = dt1.Content
If wb1.Sheets(Sheets.Count).Cells(1, 1) <> "" Then
wb1.Sheets.Add After:=Sheets.Count
End If
End If
cutrange.Cut Destination:=wb1.Sheets(wb1.Sheets(Sheets.Count)).Cells(1, 1)
wb1.Sheets(Sheets.Count).Cells(1, 1).TextToColumns Destination:=wb1.Sheets(1).Cells(1, 1)
End Sub
My problem is that the variable wb1 isn't getting passed between them. Even though I put wb1 in the list of variables to send to the macro, when it arrives at the document, wb1 has no value inside of it. I would re-initialize it, but I don't know how to refer to an already existing document - only how to set it equal to one as you open it.
So either how do I pass the value through into the Word macro, or how do I re-initialize this variable? Preferably without having to set something equal to the excel application, because every time I try that it sets it equal to Excel 2003, not 2010 (though any solutions to that are also, of course, welcome).
Thanks!
You can't use the Excel global objects from inside of Word without explicitly qualifying them (they simply don't exist there). In particular, that means you can't use Sheets. You should also explicitly declare the variable types of your parameters - otherwise they'll be treated as Variant. This is important with reference types because in that it helps prevent run-time errors because the compiler knows that the Set keyword is required.
Sub divider(wb1 As Object, dt1 As Document)
Set dt1 = ThisDocument
If dt1.Paragraphs.Count > 65000 Then
Set cutrange = dt1.Range(dt1.Paragraphs(1).Range.Start, dt1.Paragraphs(65000).Range.End)
If wb1.Sheets(wb1.Sheets.Count).Cells(1, 1) <> "" Then
wb1.Sheets.Add After:=wb1.Sheets.Count
End If
Else
Set cutrange = dt1.Content
If wb1.Sheets(wb1.Sheets.Count).Cells(1, 1) <> "" Then
wb1.Sheets.Add After:=wb1.Sheets.Count
End If
End If
cutrange.Cut Destination:=wb1.Sheets(wb1.Sheets(wb1.Sheets.Count)).Cells(1, 1)
wb1.Sheets(wb1.Sheets.Count).Cells(1, 1).TextToColumns Destination:=wb1.Sheets(1).Cells(1, 1)
End Sub
Note - you also don't need to pass dt1 at all. You never use the value in the parameter and actually set it to something else. This could be a source of errors if you're using internal calls, because dt1 is implicitly passed ByRef (it gets boxed when you call it through Application.Run). That means whenever you call divider, whatever you pass to dt1 in the calling code will change to ThisDocument. You should either remove the parameter or specify that it is ByVal.
Borrowed from another SO link.
Sub Sample()
Dim wdApp As Object, newDoc As Object
Dim strFile As String
strFile = "C:\Some\Folder\MyWordDoc.dotm"
'~~> Establish an Word application object
On Error Resume Next
Set wdApp = GetObject(, "Word.Application")
If Err.Number <> 0 Then
Set wdApp = CreateObject("Word.Application")
End If
Err.Clear
On Error GoTo 0
wdApp.Visible = True
Set newDoc = wdApp.Documents.Add(strFile)
Call wdApp.Run("YHelloThar", "Hello")
'
'~~> Rest of the code
'
End Sub

Writing Data from Excel to Word

I want to use Excel to store "tag names" in column A and their associated "replacement text" in Column B. When the code runs, it needs to collect each tag, one at a time (row by row), search an entire Word document for those words, and replace them with their corresponding replacements.
I noticed the special tags in the headers and footers weren't being replaced. I turned to this article (http://word.mvps.org/faqs/customization/ReplaceAnywhere.htm) and found that working with a range of ranges (or cycling through all available Story Ranges in the document) I was able to do this.
I improved my code, as recommended in the link above and it worked, so long as my code was embedded in my "Normal" Word file, thereby using my VBA code from Word to operate on another Word document. However, the goal is to use VBA Excel to operate the replacements while reading an Excel file.
When I moved the code to Excel, I'm getting hung up on an Automation error which reads,
"Run-time error '-2147319779 (8002801d)': Automation error Library not registered.".
I've looked for answers from reviewing the Registry to using "Word.Application.12" in place of "Word.Application".
I have a Windows 7, 64-Bit machine, with Microsoft Office 2007. I have the following libraries selected:
Excel:
Visual Basic For Applications
Microsoft Excel 12.0 Object Library
OLE Automation
Microsoft Access 12.0 Object Library
Microsoft Outlook 12.0 Object Library
Microsoft Word 12.0 Object Library
Microsoft Forms 2.0 Object Library
Microsoft Office 14.0 Object Library
Word:
Visual Basic For Applications
Microsoft Word 12.0 Object Library
OLE Automation
Microsoft Office 12.0 Object Library
I have no issues with operating inside of Excel with regard to VBA. Normally, I will be passing a set of strings to this function, but for now, I have embedded the strings inside of the function, as if I am only planning on swapping one string (for any number of instances), with another predetermined string.
Function Story_Test()
Dim File As String
Dim Tag As String
Dim ReplacementString As String
Dim a As Integer
Dim WordObj As Object
Dim WordDoc As Object
Dim StoryRange As Word.Range
Dim Junk As Long
Dim BaseFile As String
'Normally, these lines would be strings which get passed in
File = "Z:\File.docx"
Tag = "{{Prepared_By}}"
ReplacementString = "Joe Somebody"
'Review currently open documents, and Set WordDoc to the correct one
'Don't worry, I already have error handling in place for the more complex code
Set WordObj = GetObject(, "Word.Application")
BaseFile = Basename(File)
For a = 1 To WordObj.Documents.Count
If WordObj.Documents(a).Name = BaseFile Then
Set WordDoc = WordObj.Documents(a)
Exit For
End If
Next a
'This is a fix provided to fix the skipped blank Header/Footer problem
Junk = WordDoc.Sections(1).Headers(1).Range.StoryType
'Okay, this is the line where we can see the error.
'When this code is run from Excel VBA, problem. From Word VBA, no problem.
'Anyone known why this is???
'***********************************************************************
For Each StoryRange In WordObj.Documents(a).StoryRanges
'***********************************************************************
Do
'All you need to know about the following function call is
' that I have a function that works to replace strings.
'It works fine provided it has valid strings and a valid StoryRange.
Call SearchAndReplaceInStory_ForVariants(StoryRange, Tag, _
ReplacementString, PreAdditive, FinalAdditive)
Set StoryRange = StoryRange.NextStoryRange
Loop Until StoryRange Is Nothing
Next StoryRange
Set WordObj = Nothing
Set WordDoc = Nothing
End Function
For Each StoryRange In WordObj.Documents(a).StoryRanges
should probably be
For Each StoryRange In WordDoc.StoryRanges
since you just assigned that in the loop above.
For now, I will have to conclude, as I don't have the possibility of testing the contrary, that there is a difference between using Microsoft Office 12 Object Library in one VBA environment, and Microsoft Office 14 Object Library in another. I don't have the means/authorizations to change either, so I must conclude, for now that is, that the difference between the two is the culprit. So, if I was to go forward and expect different results, I would assume Microsoft Office 12 Object Library to be the correct library, where 14 has a few differences that I am not aware of.
Thank you to all who provided input. If you have any other suggestions, we can discuss and forward. Thanks!
This is to update a bunch of links spread over body & Headers footers.
I didn't write this only from memory made a bunch of fixes, inclusions and tweaks.
It shows you how to cover all the different sections and can easily be modified to work within your parameters.
Please post your final code once done.
Public Sub UpdateAllFields()
Dim doc As Document
Dim wnd As Window
Dim lngMain As Long
Dim lngSplit As Long
Dim lngActPane As Long
Dim rngStory As Range
Dim TOC As TableOfContents
Dim TOA As TableOfAuthorities
Dim TOF As TableOfFigures
Dim shp As Shape
Dim sctn As Section
Dim Hdr As HeaderFooter
Dim Ftr As HeaderFooter
' Set Objects
Set doc = ActiveDocument
Set wnd = ActiveDocument.ActiveWindow
' get Active Pane Number
lngActPane = wnd.ActivePane.Index
' Hold View Type of Main pane
lngMain = wnd.Panes(1).View.Type
' Hold SplitSpecial
lngSplit = wnd.View.SplitSpecial
' Get Rid of any split
wnd.View.SplitSpecial = wdPaneNone
' Set View to Normal
wnd.View.Type = wdNormalView
' Loop through each story in doc to update
For Each rngStory In doc.StoryRanges
If rngStory.StoryType = wdCommentsStory Then
Application.DisplayAlerts = wdAlertsNone
' Update fields
rngStory.Fields.Update
Application.DisplayAlerts = wdAlertsAll
Else
' Update fields
rngStory.Fields.Update
End If
Next
'Loop through text boxes and update
For Each shp In doc.Shapes
With shp.TextFrame
If .HasText Then
shp.TextFrame.TextRange.Fields.Update
End If
End With
Next
' Loop through TOC and update
For Each TOC In doc.TablesOfContents
TOC.Update
Next
' Loop through TOA and update
For Each TOA In doc.TablesOfAuthorities
TOA.Update
Next
' Loop through TOF and update
For Each TOF In doc.TablesOfFigures
TOF.Update
Next
For Each sctn In doc.Sections
For Each Hdr In sctn.Headers
Hdr.Range.Fields.Update
For Each shp In Hdr.Shapes
With shp.TextFrame
If .HasText Then
shp.TextFrame.TextRange.Fields.Update
End If
End With
Next shp
Next Hdr
For Each Ftr In sctn.Footers
Ftr.Range.Fields.Update
For Each shp In Ftr.Shapes
With shp.TextFrame
If .HasText Then
shp.TextFrame.TextRange.Fields.Update
End If
End With
Next shp
Next Ftr
Next sctn
' Return Split to original state
wnd.View.SplitSpecial = lngSplit
' Return main pane to original state
wnd.Panes(1).View.Type = lngMain
' Active proper pane
wnd.Panes(lngActPane).Activate
' Close and release all pointers
Set wnd = Nothing
Set doc = Nothing
End Sub

Occasional VBA Method 'Activate' of object 'ChartData' failed

Before I begin, here is some history:
Created VBA in Excel to open and read three (3) Excel files (includes itself) and input data into charts/tables/graphs into a PowerPoint presentation. This version runs beautifully. VBA kicked off by a User Form
Modified code to fit a requirement passed down to me. This one causes the error of VBA Method 'Activate' of object 'ChartData' when loading a graph in one particular slide. This data is transferred from the sheet that kicks off the VBA.
I was unable to recreate this error steadily until I started saving the Excel file that kicks off the script when it asked. Now I can.
NO VBA resides in the powerpoint presentation.
Users testing this experience the error first time around. I do not. However, I do in the further iterations I do after saving the Excel book after either a successful or unsuccessful run.
Screen behaviors I've noticed when error occurs:
Only happens after I save the Excel that kicked off the procedure and I test the procedure again when trying to re-create error.
PowerPoint presentation becomes the 'activated' application while VBA runs in background
Happens on the same slide and chart (yes, using object labels in PowerPoint).
When error occurs and I break code, I can NOT close PowerPoint or Excel using the File menu. I HAVE to use the 'Red X' in the upper right hand corner to close. The ribbons and tabs are also unusable (do not react to a clicking event). Microsoft does ask the Save option.
What I've tried:
Walking through code and explicitly closing objects after they've been opened and are not required.
Varying the placement of the ScreenUpdating, etc. Application processes
Here is the function where it trips. It trips up at trpChartData.Activate for a particular graph (which is shapeName):
Function insGraphInfo(ByVal numOfSlide As Integer, ByVal shapeName As String, ByVal cellToMod As String, ByVal valToIns As Variant) As Variant
'Inserts data into a CHART TYPE graph
On Error GoTo ERR_INS_GRAPH
Dim trpChart As PowerPoint.Chart
Dim trpChartData As ChartData
Dim trpWkBk As Excel.Workbook
Dim trpChartSheet As Excel.Worksheet
Dim errString As String
Set oPPTSlide = oPPTFile.Slides(numOfSlide)
With oPPTSlide
.Select
End With
Set oPPTShape = oPPTFile.Slides(numOfSlide).Shapes(shapeName)
Set trpChart = oPPTShape.Chart
Set trpChartData = trpChart.ChartData
Debug.Print "Activating: " & shapeName & " in slide number: " & numOfSlide
errString = "Activating: " & shapeName & " in slide number: " & numOfSlide
trpChartData.Activate
Debug.Print shapeName & " activated."
errString = shapeName & " activated."
errString = "Setting Workbook and Worksheet Objects"
Set trpWkBk = trpChartData.Workbook
Set trpChartSheet = trpWkBk.Worksheets(1)
errString = "Inserting Value into appropriate cell)"
With trpChartSheet
.Range(cellToMod).Value = valToIns
End With
insGraphInfo = valToIns
errString = "Refreshing Chart."
With oPPTShape 'Refreshes
.Chart.ChartData.Activate
.Chart.ChartData.Workbook.Close
.Chart.Refresh
End With
Set trpWkBk = Nothing
Set oPPTSlide = Nothing
Set oPPTShape = Nothing
Exit Function
ERR_INS_GRAPH:
MsgBox "An error occurred while: " & errString
Resume Next
End Function
Excel and PowerPoint are created by two different teams of developers.
PowerPoint.Chart is not the same as Excel.Chart
Yes, they look the same and you would think that you have the same level of access to their properties, but that is where you would be wrong. The PowerPoint version is very limited.
Anyway, as far I can tell, you went wrong when you declared
Dim trpChartData As ChartData
Instead of
Dim trpChartData As PowerPoint.ChartData
As Rachel pointed out,
trpChartData is declared without a library qualifier and thus defaults to Excel.ChartData
In addition to that you never cleared trpChartData with
Set trpChartData = Nothing
I also don't see where you .Quit the Excel application for the Chart.Workbook that must have been created. This could explain why there were versions of Excel open in the Task Manager afterwards. Try adding this...
Dim xlApp as Excel.Application
'
'
Set xlApp = .Chart.ChartData.Workbook.Application
'
'
xlApp.Quit
Set xlApp = Nothing

Excel VBA to Open Multiple Word files in a loop

I apologize in advance for the newbie question -- most of my VBA experience is in Excel, or Word to Excel. In this case, I am going from Excel to Word. I am trying to capture some data off of some Word forms and store it in an Excel file.
Right now, my code works for the first document in the folder, but after that, it hoses up with an automation error "the server threw an exception" (goo!)
Here is my code:
Dim objWordApp As Object
strCurFileName = Dir(strFilePath)
Set objWordApp = CreateObject("word.application")
objWordApp.Visible = True
Do While strCurFileName <> ""
objWordApp.documents.Open strFilePath & strCurFileName
objWordApp.activedocument.Unprotect password:="testcode"
{EXCEL PROCESSING HERE}
strCurFileName = Dir
objWordApp.activedocument.Close 0
Loop
objWordApp.Quit
Set objWordApp = Nothing
I notice that the code works fine if I quit the app and set the object = nothing within the loop. But the way it is now, it bombs-out on the second file in the folder on the "objWordApp.documents.Open strFilePath & strCurFileName" line.
Can I open and close Word documents in a loop without having to create the object over and over? It's really slow when I do it that way.
Thanks for the help -- I like your way much better. Unfortunately, I get the same result. The program dies the second time through the loop on the line that reads:
Set objWordDoc = objWordApp.Documents.Open(objFile.Path)
The error that I get is:
Run-time Error -2147417851 (80010105)
Automation Error
The server threw an exception.
I tried your code on regular word docs (not the ones I'm processing) and it worked fine. The docs I'm running have form fields and macros -- not sure if that makes a difference. I have set the macro security in Word to both "low" and "very high" to make sure the other macros don't interfere.
I just can't figure it out why it works for the first doc and then not the next. I even cloned the first doc but it made no difference.
Still no luck, though. The only thing I can get to work is if I completely wipe the objects and re-create them every time I want to open a file.
Set objFolder = FSO.GetFolder(strFilePath)
For Each objFile In objFolder.Files
Set objWordApp = CreateObject("word.application")
objWordApp.Visible = True
If Right(objFile.Name, 4) = ".doc" Then
Set objWordDoc = objWordApp.documents.Open(Filename:=objFile.Path, ConfirmConversions:=False, _
ReadOnly:=True, AddToRecentFiles:=False, PasswordDocument:="", _
PasswordTemplate:="", Revert:=False, WritePasswordDocument:="", _
WritePasswordTemplate:="", Format:=wdOpenFormatAuto)
[Process DOC]
objWordDoc.Close 0, 1
End If
Set objWordDoc = Nothing
objWordApp.Quit
Set objWordApp = Nothing
Next
I'm not sure why that works and why it won't work the other way. If I have to go this route, I can -- it just seems really slow and inefficient. Is this a bad idea?
I changed the Dir to a FileSystemObject (go to Tools\References and add Microsoft Scripting Runtime) and I was able to successfully open multiple files. If you are having problems, please describe the error you see in the debugger. Also, if you need to recurse into subdirectories, you will need to refactor this.
Private mobjWordApp As Word.Application
Sub Test()
ProcessDirectory "PathName"
End Sub
Property Get WordApp() As Word.Application
If mobjWordApp Is Nothing Then
Set mobjWordApp = CreateObject("Word.Application")
mobjWordApp.Visible = True
End If
Set WordApp = mobjWordApp
End Property
Sub CloseWordApp()
If Not (mobjWordApp Is Nothing) Then
On Error Resume Next
mobjWordApp.Quit
Set mobjWordApp = Nothing
End If
End Sub
Function GetWordDocument(FileName As String) As Word.Document
On Error Resume Next
Set GetWordDocument = WordApp.Documents.Open(FileName)
If Err.Number = &H80010105 Then
CloseWordApp
On Error GoTo 0
Set GetWordDocument = WordApp.Documents.Open(FileName)
End If
End Function
Sub ProcessDirectory(PathName As String)
Dim fso As New FileSystemObject
Dim objFile As File
Dim objFolder As Folder
Dim objWordDoc As Object
On Error Goto Err_Handler
Set objFolder = fso.GetFolder(PathName)
For Each objFile In objFolder.Files
If StrComp(Right(objFile.Name, 4), ".doc", vbTextCompare) = 0 Then
Set objWordDoc = GetWordDocument(objFile.Path)
' objWordDoc.Unprotect Password:="testcode" ' Need to check if it has Password?
ProcessDocument objWordDoc
objWordDoc.Close 0, 1
Set objWordDoc = Nothing
End If
Next
Exit_Handler:
CloseWordApp
Exit Sub
Err_Handler:
MsgBox "Error " & Err.Number & ": " & Err.Description
Resume Exit_Handler
'Resume Next ' or as above
End Sub
Sub ProcessDocument(objWordDoc As Document)
'{EXCEL PROCESSING HERE}'
End Sub
EDIT: I've added some error handling and a little refactoring although there is quite a bit more refactoring that could be done.
There must be something special about the documents you are opening. You might try using different parameters for opening the documents, such as:
Set objWordDoc = objWordApp.Documents.Open( _
FileName:=objFile.Path, ReadOnly:=True)
You may need to add Microsoft Word as a Reference, and if you do that then start using the Word constants (wdDoNotSaveChanges, etc.). Check out the help on Documents.Open and test different parameters.
Also, use the "Set Next Statement" from the Context Menu during debugging and maybe skip the first document and open the second document directly and see if there are issues.
EDIT: I've changed the code to close and reopen Word if you get the automation error you described. You may have to adjust the error numbers, or simply close Word on any error (If Err.Number <> 0 Then ...).
Again, something must be special about your documents (macros, protection, etc.) because this code works on the test cases I have tried. Have you tried manually opening the documents in Word in the same order as the script, updating information similar to your process script, and then closing the documents to see if Word does anything strange?
Closing the Word.Application won't hurt anything, but it will obviously significantly slower.