Opening/Activating Word Documents in a VBA macro - vba

I'm hoping a VB/VBA expert can help me out. Consider the following:
The user opens a document in Word 2003, and within the Normal.dot AutoOpen macro, we look at current document, and if it has been opened by clicking on a link on a webpage, and meets certain other application specific criteria, close the streamed 'copy' and open the source document (found on a shared drive we can assume the user has access to):
Documents.Open origDoc
Documents(ActiveDocument.FullName).Close SaveChanges:=wdDoNotSaveChanges
Documents(origDoc).Activate
With ActiveDocument
''# Do work
End With
My thought was that I needed to call Activate to ensure that the original document was the ActiveDocument, but I'm getting a 4160 'Bad file name' error on the .Activate call. If I comment out the call to .Activate, it appears that ActiveDocument is set to the origDoc document, even if there were other documents already opened (I'm not really sure how the Documents Collection is managed, and how Word determines what next ActiveDocument would be if you programatically close the current ActiveDocument)
So, does calling .Open on a document explicitly set the Document to be the ActiveDocument? Also, does calling .Activate on the already active document cause an error?
I haven't really been able to find much documentation about this, so thanks in advance for any suggestions and insight!

The simple answer is yes. By opening the document with your code you make it the active document, which you then close in the next line and try to activate in the next, and this fails because the document is no longer open. VBA in general seems to work this way.
It is important to be careful with ActiveDocument, because it's not always self-evident what actions, in code or elsewhere, will make a document 'active' (i have no proof but even an autosave might do it). IF there's any doubt you're better off referring to a document through the Documents collection, though this can also cause errors if the document is no longer open, and you might have to resort to iterating through the collection to be sure the document is, in fact, open. I run into this a lot with excel VBA, and Word VBA seems to function identically in that regard.
Also, VBA is flaky about releasing application objects. If you're not careful you'll end up with multiple WINWORD processes,viewable in task manager, regardless of whether you Close or Quit them in your code. The code I've found to work around this amounts to simulating the process of selecting END PROCESS in task manager. It works, but there should be a better solution.

Beware that there are a variety of problems that can be encountered:
if you want to re-open the document after closing it
once....Word/Windows DOES NOT
'release' the filename and you get a 'file busy' message, or message about 'creating
a temporary copy'.
to deal with this problem, I've had to develop an elaborate system of creating/saving and
tidying up multiple versions of any other documents I open/manipulate in my Word applications
because of this design flaw in Office Open/Close/Save methods.
Use the ReadOnlyRecommended property set to False with the
.Open method
referring to the document object (named doc, above) can cause
serious errors if you do not assure
that the doc object still exists before you try and manipulate it. Remember always, that
Word is an 'open' application platform....and the user can be doing things you didn't count
on...in the last millisecond or so. This advice holds for any other object or property you may
wish to manipulate in Word.
if you manipulate the Documents collection (or any other) without
assuring that the document or other object is still there and
valid before deleting or moving it within the collection you may
get 'stack overflow' errors. Particularly if you try and
close/delete objects in a collection starting at .item(1). You
must delete items in a collection from the last one, and remember
that the collection idicies and pointers change whenever you
.add/.remove/.close items from them.

You have an error here:
Document(origDoc).Activate
Should be Documents.
Yes, you can activate the active document. Nothing happens then.
Yes, opened document becomes active.
If you are not sure, use Documents.Open(origDoc).Activate.

You shouldn't be using the ActiveDocument object in the first place unless absolutely necessary because it's very unreliable. The preferred approach would be this:
Documents(ActiveDocument.FullName).Close SaveChanges:=wdDoNotSaveChanges
Dim doc as Document
Set doc = Documents.Open(origDoc)
With doc
'Do work
End With

Related

MS Office - ActiveX Buttons switching places

There are several instances of this problem, but this one is predominant. This is in relation to updates (our most notable problem child being KB2726958). We have a Leave Spreadsheet that looks like this:
Leave Spreadsheet example
By pressing the grey Leave button, you end up here:
Leave Word doc
All the programming for these is written in VBA (i've never worked with VBA before, I can understand it to a degree).
Now, the issue is that using the ActiveX button in the 'Leave Spreadsheet example' causes the 2 buttons 'Send by Email' and 'Save' to switch functions; Send by email attempts to save and save opens up Outlook and creates the email message.
Both functions have completely retained functionality, just on the wrong buttons.
The thing I find weird is that a hyperlink to the very same file works; the buttons aren't switched and have full functionality. The only hint that I have towards resolution is that when using a hyperlink, it's directly opening the file. When using the ActiveX button, it seems to be creating a new file based off the file it's linking to. For example, the hyperlink directly opens C:\Report.dotm but the ActiveX button opens Document1.doc with a template based on Report.dotm.
I'm considering that maybe the activeX button is opening up Word with an incorrect extension? But i'm not sure how to figure this out (code below shows that the linked file on the activeX control is a .dotm).
What further throws a spanner into the mix is that it only affects some computers... Considering on-site we all use the same type of PC with the same image... :(
My question is, does anyone know why they may be swapping? They're located on the same network drive albeit different directories. They require the same permissions to access. The code for the buttons is as follows:
Excel Button:
Private Sub CommandButton1_Click()
' This button links the excel spreadsheet to the word doc
Dim wrdApp As Object
Dim wrdDoc As Object
Dim i As Integer
Set wrdApp = CreateObject("Word.Application")
wrdApp.Visible = True
Set wrdDoc = wrdApp.Documents.Add("\\networkdrive\directories\Request for Leave.dotm")
End Sub
Word buttons 1 and 2:
Private Sub cmdSend_Click()
' This is the code for the button 'Send by Email'
MsgBox "Send the following email to your Team Leader/Line Manager", vbInformation
SendDocumentAsAttachment "", "IPL Request for Leave"
End Sub
Private Sub cmdSave_Click()
' This is the code for 'Save'
modSend.SaveLeaveForm
End Sub
Please Note: The comments above are not in the code in VBA, i've written them in myself in this question to provide clarity.
Troubleshooting that i've done:
Removing all .exd files
Running the MS Hotfix (removes all .exd files in a GUI)
The next step would be to try running all 6 patches related to fixing ActiveX controls with the particular patches we've done to see if that fixes the problem. The reason I haven't done this yet is because of ITIL (Change management) although I may try testing this later today.
What is the outcome i'm after?
Ideally, I want to understand what is causing these buttons to, from what it looks like, swap their functions. I have different scenarios of button swaps, some of which are remedied by removing the .exd files, and some that aren't.
By understanding what is happening, I hope that I can apply the knowledge to the other scenarios (same problem, different coding).
Then, I'll be able to document my findings so that when we perform the next round of patching that is known to break ActiveX controls, my organization will know how to deal with it.
So the patch mentioned below has fixed this issue. There's still some other issues that I need to test this patch against, but I definitely should have started there. Lesson learnt.
From my work email:
I’ve just tried using the patch related to the ActiveX controls breaking, KB2920754. I’ve used it on two PC’s here in the training room; both had different issues:
- The first one had buttons that had switched around (save attempted to email, email attempted to save)
- The second one couldn’t use the buttons at all.
This patch cured both w/o requiring a restart or logging out and back in. I didn’t remove any .exd files, either.
It does state, however:
“Important For this fix to be fully effective, you also have to apply the other patches for Office 2013 that are listed in the "Resolution" section of the following Microsoft Knowledge Base article”
There are 6 in total.
Patches:
1. KB2920754 – (the one I’ve used successfully)
2. KB2956145
3. KB2956163
4. KB2965206
5. KB2956176
6. KB2956155

Prevent Word macro in Normal.dotm for some templates

I have a Normal.dotm file that contains an AutoNew macro.
This macro is automatically executed each time a new document is created using any other template.
Is there any way I can prevent this automatic behavior for a specific template?
I have a Word VSTO add-in running, so I can hook into Word's events, but so far I havn't found a way to prevent this.
I do know that I can prevent macro execution when using templates programmatically, for example like this:
' Disable auto-macros before opening document
wordApplication.WordBasic.DisableAutoMacros(1)
' Open document
newWordDocument = wordApplication.Documents.Open(template.FullName, ConfirmConversions:=False, [ReadOnly]:=True, AddToRecentFiles:=False, Revert:=True)
' Re-enable auto-macros
wordApplication.WordBasic.DisableAutoMacros(0)
But this solution doesn't work when the user uses a Word template from Windows explorer or the Open-dialog in Word, since in those cases I can't execute code before it's too late already.
Or can I?
I hope someone has a trick for me :-)
-
Edit: While trying different solutions, I discovered something that might help others in similar situations, though unfortunately it doesn't help me.
It seems that if a template contains a module containing an AutoNew (or AutoOpen for that matter), that local macro is executed instead of the one in Normal.dotm.
Example:
Normal.dotm contains the following macro:
Sub AutoNew()
MsgBox "Normal.dotm"
End Sub
Test.dotm contains the following macro:
Sub AutoNew()
MsgBox "Test.dotm"
End Sub
When executing Test.dotm the message "Test.dotm" is displayed, while the message "Normal.dotm" is not displayed.
If the AutoNew macro is removed from the Test.dotm template, the message "Normal.dotm" is indeed displayed.
So it is possible to easily override the auto-macros.
The local versions of AutoNew and AutoOpen can even be empty subs that do nothing. It still works.
This is not possible in my case though, since the template I use is generated by code, and cannot contain macros (because adding macros to templates programmatically requires the user to manually activate the option "Trust access to the VBA project object model", and that's something I cannot ask my customers to do for all users. It's also a security risk.)
Based on the workaround described in the "Edit" part of the question - providing a template with "empty" Auto-macros - it's possible to use the Open XML SDK to create a template and add the VBA project to it in order to provide this functionality. This approach avoids the user needing to allow access to the VBA project on his installation. The only "macro security" that could be triggered is that for not allowing macros to run. But since the client uses macros, anyway, this should not be a major obstacle.
The simplest method is to create as much of the basic template as possible in the Word UI and use this as a starting point.
Since you're unfamiliar with the Open XML SDK, the next step would be to create one (or more) templates in the Word UI using the basic template as the starting point, saving under a different file name.
You can then use Open XML SDK Productivity Tool to see the code required to generate any one of these files, as well as, using the Compare tool, the code for converting the basic template to the derived version. This should give you a decent start with the SDK and it's object model. Once you get a feel for how the Open XML SDK works, if you're familiar with Word's object model, using that provided by the SDK is relatively straight-forward as an effort was made to make it correspond as closely as possible to the "COM" object model.
The VBA project can be added later, but you can also include it in the basic template. That would be the simplest approach.
Include this "starting point" basic template as part of your solution, installing it as part of the solution.
Within the AutoNew macro you can check the AttachedTemplate property. Only if it is a template where you want to apply the cleaning you can execute the respective macros.
Sub AutoNew()
If ActiveDocument.AttachedTemplate <> "Normal.dotm" Then
Exit Sub
End If
' rest of the macro
End Sub
If you don't control the Normal.dotm you can put an empty AutoNew macro in your own templates. As Word only executes the auto macro in the closest context, the macro in the Normal.dotm file would not be executed.
If you don't control the other templates either, you can tell your users to hold down the SHIFT key while creating a document. This prevents the execution of the auto macro.
Probably it is best, however, if you ask the owner of the other system to find another solution that does not rely on polluting the Normal.dotm file.

Word opens for real when trying to fill-in the blanks dynamically?

I'm currently using Word documents as templates where blanks have to be filled dynamically/programmatically in PwoerBuilder.
This has always worked fine until the company moves on Windows 7.
In short, the Word application is opened and made invisible.
Word.Application.Visible = false
Except that sometimes, and I don't know why, once the template is accessed, Word opens itself just as if I had double-clicked the template byself through the Explorer - but I didn't.
So, it asks whether I want to open it in read-only mode, since the application already has a handle on the file. And even if I click [Cancel] not to open the file, Word opens with no document, then the application crashes.
It reports PowerBuilder System Error 35.
Error Number 35.
Error text = Error calling external object function open at line 24 in function of_fusion of object n_cst_9999.
The external object that the application is trying to call a function against is Word.
oleobject lole_word
lole_word = create oleobject
lole_word = ConnectToNewObject("Word.Application")
lole_word.Documents.Open("templatefile.docx")
It may work for a few documents, and after a few, the problem comes up. This is the first time ever I meet with this issue.
I'll be glad to answer anyone's question who's trying to help.
Will, you may try setting DisplayAlerts and FeatureInstall properties on Word Application object.
That hid most of word alerts for us. (The code is from C# project and may not be exactly what you need)
Word.Application.DisplayAlerts = Word.WdAlertLevel.wdAlertsNone;
Word.Application.FeatureInstall = 0;
You may also try making a copy of the file before opening it to avoid accessing same .docx from different threads - if that may be the case.

Word VBA and Multiple Word Instances

Good morning.
I am having a problem with getting my code to find other instances of word and have hit a brick wall after much google searching.
My Code below will find all open word documents and populate them into a combo box.
My problem is we have applications (I have no control over these) that will open word documents in a new instance and therefore my code will not find/control these documents.
Any ideas?
Dim objWordDocument As Word.Document
Dim objWordApplication As Word.Application
'//find all open word documents
Set objWordApplication = GetObject(, "Word.Application")
'//clear combobox
OpenDocs.Clear
'//add all open documents to combo box
For Each objWordDocument In objWordApplication.Documents
OpenDocs.AddItem objWordDocument.Name
Next objWordDocument
From what I have seen, and come to understand, the only sure fire way to do this is to iterate through the running instances of word and then kill each one in turn to be sure that you are getting the next instance.
Since word registers in the running object table the same exact way for every instance of itself, there is no way to get through them without first closing the one you were looking at.
One option to this approach, which is probably not desirable, is to get all the file names while you are killing the application instances and then load them all back in one instance that you create.
Alternately if you know the names of the open files, you can 'getObject' by open file name since Word will push its document names into the running object table, sadly this does not sound like the case for you.
Without writing an active x MFC service, you are not going to be able to do what you are looking to do.
I hope that is helpful.
EDIT:
there was an extensive discussion on subclassing and windows API's to get handles in order to change focus. http://www.xtremevbtalk.com/showthread.php?t=314637
if you dove into that head first and were able to enumerate the word instances by hwnd then you could potentially focus each one in turn and then list the file names. I do warn you though; that is some nasty subclassing which is dark magic that only some people who really want to accidentally break stuff play with.
In any event if you wanted to take the look at one instance, kill, repeat, reopen try this:
Adapted from this thread: http://www.xtremevbtalk.com/showthread.php?t=316776
Set objWordApplication = GetObject(, "Word.Application")
'//clear combobox
OpenDocs.Clear
'//add all open documents to combo box
Do While Not objWordDocument is nothing
For Each objWordDocument In objWordApplication.Documents
OpenDocs.AddItem objWordDocument.Name
Next objWordDocument
objWordApplication.Quit False
Set objWordApplication = Nothing
Set objWordApplication = GetObject(, "Word.Application")
loop
** use create object to open a new instance of word here and then go though
** your list of files until you have opened them all as documents in the new
** instance.
It's an old thread, but I too have the need to iterate over Word instances and bumped here.
Following the #Pow-Ian's advise, I tried to do the
if you dove into that head first and were able to enumerate the word
instances by hwnd then you could potentially focus each one in turn
and then list the file names.
Although I have managed to get all the handles, I have found an easier strategy with regard to office applications through AccessibleObjectFromWindow and our question is now solved.
Also, I believe the code showed #Pow-lan's has a mistype on
Do While Not objWordDocument is nothing
and should be:
Do While Not objWordApplication is nothing

Accessing Excel Object without using Shape.Activate() on a Word document using VBA

I have a tried reading an embedded excel document in a word document. I followed the code specified at this blog article:
http://vbadud.blogspot.com/2010/08/how-to-read-excel-sheet-embedded-in.html
Dim oWB As Excel.Workbook
Dim oIShape As InlineShape
For Each oIShape In ActiveDocument.InlineShapes
If InStr(1, oIShape.OLEFormat.ProgID, "Excel") Then
oIShape.OLEFormat.Activate
Set oWB = oIShape.OLEFormat.Object
oWB.Sheets(1).Range("A1").Value = "ProdID"
End If
Next oIShape
It works fine but the Activate line causes the document to flicker on each excel document I read. I tried to remove the oIShape.OLEFormat.Activate code but it causes the next line to throw a "Runtime error '430' (class does not support Automation or does not support expect).
The question is there any other way to access embedded excel without calling the Activate method?
This is tricky! The short answer is, no. Not with an embedded Excel.
I did some experimentation and some research. Since I could not find any sources that specifically explained the behavior. this is somewhat a guess on my part. It appears that when you embed the Excel spreadsheet into your word document essentially Word stores a link of spreadsheet, which displays only the appearance because it needs to be interpreted with the Excel program. Until you actually active the shape, you cannot interact with it because that cannot be done with Word directly. This article alludes to the behavior, but doesn't explain it. Here's a quote:
If you edit the object in Word, click anywhere outside the object to return
to the destination file.
If you edit the object in the source program in a separate window,
click Exit on the File menu of the source program to return to the
destination file.
You may have noticed that even if you use. Application.ScreenUpdating = false it still does the flickering you mention. This is because you are using a different application when you access the shapes! Every time you active the shape, the object specific menus etc load.
A possible work around:
If instead of embedding Excel Spreadsheets via the insert menu, you can instead add a control. On my machine using Office 2003 the comparible one is: Microsoft Office Spreadsheet 11.0 This is technically a web control, but the methods and behavior are very comparable to an Excel workbook.
Using the control instead of the handy inserted object, with a slight variation of your code I was able to comment out your activate command and the code ran as expected. Specifically, I had to change these lines:
Dim oWB As Spreadsheet instead of Excel.Workbook.
If InStr(1, oIShape.OLEFormat.ProgID, "OWC11.Spreadsheet.11") Then instead of "Excel"
Basically you can decide... Activate your embedded object that requires Excel to interpret, or use a different control that doesn't require activation.