 |
 |

03-19-2010, 05:38 PM
|
|
Newcomer
|
|
Join Date: Mar 2010
Posts: 5
|
|
VBA access to multiple word instances
|
Hi,
I have designed a spreadsheet to enable a user to import data from a word document form into the spreadsheet.
I have two options for the user:
a) excel will check the currently open word documents for a particular naming format. If it recognises a document it will ask the user if they would like to import this document. If yes...then import. If no...then check the next document name.
b) if word is not open or the user continued to hit no then it will prompt the user to open a file.
The issue is that the most frequent use will be when the user opens a word document from our company PDM system. When they open it from the PDM systems and Word (or Outlook with word as editor) is already open it creates it in a second instance of word. The getobject inevitably grabs the wrong instance.
I found the following post from Mike but am not sure how to covert it into VBA and to get Word instead of excel (his is in VB.Net)
Below is an excerpt of my code.
Code:
On Error Resume Next
Set wrdApp = GetObject(, "Word.Application") '' if no word application then wrdapp = nothing
If Not wrdApp Is Nothing Then '' if word application found
On Error GoTo 0
For Each wrdDoc In wrdApp.Documents
If wrdDoc.Name Like "*DR-*" Then
iAns = MsgBox("Would you like to use the open document " & wrdDoc.Name & "?", vbYesNoCancel)
If iAns = vbCancel Then Exit Sub '' cancel exit the whole sub
If iAns = vbYes Then Exit For '' yes uses this file
End If
Next wrdDoc
If iAns = vbNo Then wrdDoc = Nothing '' if use has said no to all files then wrddoc needs to equal nothing
End If
If wrdDoc Is Nothing Then '' either the user said no to all open files or there were no open files
On Error GoTo 0
'' get user to select file
FileToOpen = GetInputFile
If FileToOpen = "" Then
MsgBox "Cancelled by user....Exiting...."
Exit Sub
Else
Set wrdApp = CreateObject("Word.Application")
Set wrdDoc = wrdApp.Documents.Open(FileToOpen)
End If
End If
Any help will be appreciated.
|
|

03-22-2010, 10:58 AM
|
 |
Microsoft Excel MVP
Forum Leader * Guru *
|
|
Join Date: Jul 2003
Location: New York, NY, USA
Posts: 7,848
|
|
Hi Jason,
Welcome to the forum.
This is very advanced stuff. Unfortunately, I'm not very strong with using APIs so I would not be able to convert the VB.NET code for use in VBA or VB6. It shouldn't be that hard for someone who is good with Windows APIs, though.
Also, although I would assume that MS Word likely also has an 'AccessibleObject' within its windows hierarchy, but I'm not a Word programmer, so I don't know what it's called. For Excel, the window is named "EXCEL7", but for Word, I have no idea.
Lastly, this seems like a very complex approach. Why do you need to access an existing Word instance? It sounds like you want to load a file, yes? So why not create a *new* MS Word instance and then use that to upload the file and process it however you wish. I think that this would be much better than hijacking the Word instance that the user is currently using.
Does this make sense, or am I missing something in the requirements here?
-- Mike
|
|

03-22-2010, 11:07 AM
|
|
Newcomer
|
|
Join Date: Mar 2010
Posts: 5
|
|
Mike...thanks for the quick response!
I have the option for the user to open a word document ("FileToOpen = GetInputFile") opens a user selected file (I should have include the custom function GetInputFile).
The complication is that most often the user will have the document already opened from the company PDM system and it is not saved on their hard drive (or the network drive). The user could save it to a temp directory and then choose it from there but I would like to avoid the extra step.
Again...thanks for your response and the welcome. I will keep searching for some API info.....it really should be easier than this!! 
|
|

03-22-2010, 11:27 AM
|
 |
Microsoft Excel MVP
Forum Leader * Guru *
|
|
Join Date: Jul 2003
Location: New York, NY, USA
Posts: 7,848
|
|
Quote:
|
Originally Posted by JasonDW
it really should be easier than this!!
|
ROFL. I feel this way about everything, lol. I think the better you get, the tougher the issues you face... It's just part of life as a programmer.
Ok, I think you should be able to do this using VBA.GetObject alone.
I would try the following steps:
(1) Have the user locate the file using a File Common Dialog or GetInputFile() method.
(2) Then check if the file is in use.
(2a) If it is not in use, then create a new Word application and open it.
(2b) If the file is already open within an existing MS Word application, then access the correct Word application by supplying the full path of the Word file in question to the first parameter of the VBA.GetObject method. (For more on this, you can also see Using the CreateObject and GetObject Functions.)
Notice, by the way, that there is the minute possibility of a "race condition" between step (2) above and the sub-steps (2a) and (2b). For example, there is the extraordinarily slight possibility that between checking if the file is open in step (2) and acting on whether it was found to be open or not in steps (2a) and (2b), the user could open or close the file. Ouch. I can't think of any good ways around this, however, so, for the moment, just be aware of this extremely rare possibility.
Anyway, in the big picture, I think you should be ok here. Give it a try, and if you get stuck, show your code...
|
|

03-22-2010, 01:38 PM
|
|
Newcomer
|
|
Join Date: Mar 2010
Posts: 5
|
|
I have tried getobject. The main issue is that the user CANNOT select the file since it doesn't exist on the drive or the network (other than maybe in their temp directory).
When they double click on it from our company PDM software (TeamCenter) it opens in a new instance of word. GetObject only return the first instance (which in most cases is Outlook or an already opened instance of word). There is no way to get it to iterate through the instances and these two instances are not connected.
I have been searching all morning and can now get the 'hwnd' of the window I need but am struggling to convert that to an object I can manipulate in VBA.
If I could get the full path to the file using the HWND then I could use GetObject. So far I have been able to get the full path to the excel executable but not the open document.
Code:
Option Explicit
' Module Name: ModFindWindowLike
' (c) 2005 Wayne Phillips (http://www.everythingaccess.com)
' Written 02/06/2005
Private Declare Function EnumWindows Lib "user32" _
(ByVal lpEnumFunc As Long, _
ByVal lParam As Long) As Long
Private Declare Function GetWindowText Lib "user32" _
Alias "GetWindowTextA" _
(ByVal hwnd As Long, _
ByVal lpString As String, _
ByVal cch As Long) As Long
Private Declare Function GetWindowModuleFileName Lib "user32" _
Alias "GetWindowModuleFileNameA" (ByVal hwnd As Long, _
ByVal Filename As String, ByVal cch As Long) As Long
'Custom structure for passing in the parameters in/out of the hook enumeration function
'Could use global variables instead, but this is nicer.
Private Type FindWindowParameters
strTitle As String 'INPUT
hwnd As Long 'OUTPUT
End Type
Public Function FnFindWindowLike(strWindowTitle As String) As Long
'We'll pass a custom structure in as the parameter to store our result...
Dim Parameters As FindWindowParameters
Parameters.strTitle = strWindowTitle ' Input parameter
Call EnumWindows(AddressOf EnumWindowProc, VarPtr(Parameters))
FnFindWindowLike = Parameters.hwnd
End Function
Private Function EnumWindowProc(ByVal hwnd As Long, _
lParam As FindWindowParameters) As Long
Dim strWindowTitle As String
strWindowTitle = Space(260)
Call GetWindowText(hwnd, strWindowTitle, 260)
strWindowTitle = TrimNull(strWindowTitle) ' Remove extra null terminator
If strWindowTitle Like lParam.strTitle Then
lParam.hwnd = hwnd 'Store the result for later.
EnumWindowProc = 0 'This will stop enumerating more windows
End If
EnumWindowProc = 1
End Function
Private Function TrimNull(strNullTerminatedString As String)
Dim lngPos As Long
'Remove unnecessary null terminator
lngPos = InStr(strNullTerminatedString, Chr$(0))
If lngPos Then
TrimNull = Left$(strNullTerminatedString, lngPos - 1)
Else
TrimNull = strNullTerminatedString
End If
End Function
Sub test()
Dim MyAppHWnd As Long
Dim wrdApp As Word.Application
Dim wrdDoc As Word.Document
Dim sFile As String
Dim FileToOpen As String
Dim iLen As Long
MyAppHWnd = FnFindWindowLike("*DR-*")
sFile = String$(1024, 0)
iLen = GetWindowModuleFileName(MyAppHWnd, sFile, Len(sFile))
If iLen > 0 Then
sFile = Left$(sFile, iLen)
End If
Debug.Print sFile '' currently give me EXCEL executable full path
end sub
A lot of this goes over my head but I am getting there...now I just need to get the path of the open document instead.
|
|

03-22-2010, 01:52 PM
|
 |
Microsoft Excel MVP
Forum Leader * Guru *
|
|
Join Date: Jul 2003
Location: New York, NY, USA
Posts: 7,848
|
|
Quote:
|
Originally Posted by JasonDW
A lot of this goes over my head but I am getting there...
|
The API translation is, unfortunately, over my head as well, or I'd do it for you. It looks like you are making good progress though!
Quote:
Originally Posted by JasonDW
I have tried getobject. The main issue is that the user CANNOT select the file since it doesn't exist on the drive or the network (other than maybe in their temp directory).
When they double click on it from our company PDM software (TeamCenter) it opens in a new instance of word.
|
Ok, it opens a new instance of Word, that's fine, I get that, but within this new instance of Word it opens a previously-existing Word file, does it not?
This is what you wrote in an earlier post:
Quote:
|
Originally Posted by JasonDW
I have the option for the user to open a word document ("FileToOpen = GetInputFile") opens a user selected file (I should have include the custom function GetInputFile).
The complication is that most often the user will have the document already opened from the company PDM system
|
So, it sounds like this file exists. I do understand that the user may have it open and may have even modified the file since opening it without saving, and therefore the file might be in an unsaved state. But I am saying that you absolutely *can* use GetObject to return the Word application instance in which this document is open. You would need to provide GetObject with the full path to the file. (See my explanation of how to do this in my post, above.)
Something tells me that I'm not quite understanding your full process, but If I understand this right, then GetObject really will work...
|
|

03-22-2010, 02:05 PM
|
|
Newcomer
|
|
Join Date: Mar 2010
Posts: 5
|
|
I am probably not being clear....
Quote:
|
So, it sounds like this file exists. I do understand that the user may have it open and may have even modified the file since opening it without saving, and therefore the file might be in an unsaved state. But I am saying that you absolutely *can* use GetObject to return the Word application instance in which this document is open. You would need to provide GetObject with the full path to the file. (See my explanation of how to do this in my post, above.)
|
There are 2 options:
a) the user identifies an existing file on the drive or the network using the file open dialog (GetInputFile)
b) the user opens file from company PDM system (database system not accessible through file dialog)
Option A works fine with GetObject, for option B the file does not exist on the network / drive it effectively is similar to the user opening the file from the internet (get put into a temp directory).
When the user does this you have 2 Word instances running. Instance ONE contains open Outlook messages (if user has Word has e-mail editor) and any Word documents opened BEFORE the user opened docs from the PDM system (assuming most users will have Outlook open).
Instance TWO contains the document I want.
I have tested this by using GetObject. It 'gets' the Outlook instance of word first. If there are no open emails or word docs then the document count is 0.
I just can't get GetObject to select the second instance of word.
I just need to work out where I can find an API expert.
Jason
|
|

03-22-2010, 02:56 PM
|
 |
Microsoft Excel MVP
Forum Leader * Guru *
|
|
Join Date: Jul 2003
Location: New York, NY, USA
Posts: 7,848
|
|
Ok, got it 100% now. Yes, there is no other way other than the API approach.
Basically, we need to be able to translate the following from VB.NET to VB6:
Code:
<DllImport("Oleacc.dll")> _
Public Shared Function AccessibleObjectFromWindow( _
ByVal hwnd As Integer, ByVal dwObjectID As UInteger, ByVal riid As Byte(), _
ByRef ptr As Microsoft.Office.Interop.Excel.Window) As Integer
End Function
<DllImport("User32.dll")> _
Public Shared Function EnumChildWindows(ByVal hWndParent As Integer, _
ByVal lpEnumFunc As EnumChildCallback, ByRef lParam As Integer) As Boolean
End Function
<DllImport("User32.dll")> _
Public Shared Function GetClassName(ByVal hWnd As Integer, _
ByVal lpClassName As StringBuilder, ByVal nMaxCount As Integer) As Integer
End Function
Public Function EnumChildProc( _
ByVal hwndChild As Integer, ByRef lParam As Integer) As Boolean
Dim buf As New StringBuilder(128)
GetClassName(hwndChild, buf, 128)
If buf.ToString() = "EXCEL7" Then
lParam = hwndChild
Return False
End If
Return True
End Function
Public Delegate Function EnumChildCallback(ByVal hwnd As Integer, _
ByRef lParam As Integer) As Boolean
Private cb As EnumChildCallback
Private xl As Excel.Application
Public Sub Connect()
' First, get Excel's main window handle.
Dim hwnd As Integer = CInt(Process.GetCurrentProcess().MainWindowHandle)
' We need to enumerate the child windows to find one that
' supports accessibility. To do this, instantiate the
' delegate and wrap the callback method in it, then call
' EnumChildWindows, passing the delegate as the 2nd arg.
If hwnd <> 0 Then
Dim hwndChild As Integer = 0
cb = New EnumChildCallback(EnumChildProc)
EnumChildWindows(hwnd, cb, hwndChild)
' If we found an accessible child window, call
' AccessibleObjectFromWindow, passing the constant
' OBJID_NATIVEOM (defined in winuser.h) and
' IID_IDispatch - we want an IDispatch pointer
' into the native object model.
If hwndChild <> 0 Then
Const OBJID_NATIVEOM As UInteger = &Hfffffff0
Dim IID_IDispatch As New Guid("{00020400-0000-0000-C000-000000000046}")
Dim ptr As Excel.Window = Nothing
Dim hr As Integer = _
AccessibleObjectFromWindow( _
hwndChild, _
OBJID_NATIVEOM, _
IID_IDispatch.ToByteArray(), _
ptr)
If hr >= 0 Then
' If we successfully got a native OM
' IDispatch pointer, we can QI this for
' an Excel Application (using the implicit
' cast operator supplied in the PIA).
xl = ptr.Application
End If
End If
End If
End Sub
I'll see if I can get an API expert to come by...
|
|

03-25-2010, 12:01 PM
|
 |
Multi-Technologist
Super Moderator * Expert *
|
|
Join Date: May 2004
Location: Michigan
Posts: 3,734
|
|
__________________
"May the code that you write never work in ways that you didn't expect; and may the code that you didn't write never require you to maintain it". - Ancient Chinese Proverb
|

03-25-2010, 12:12 PM
|
 |
Microsoft Excel MVP
Forum Leader * Guru *
|
|
Join Date: Jul 2003
Location: New York, NY, USA
Posts: 7,848
|
|
I don't know a thing about PDM, and I don't have personal experience regarding the Word instance within Outlook versus the stand-alone version, however, the following MSDN article states that Word is a global single-use COM server, so multiple application instances can be created:
Single-Use vs. Multi-Use Applications.
Testing this myself in VBA, the following produces two separate versions of "WINWORD" in the task manager:
Code:
Dim o1 As Object
Set o1 = VBA.CreateObject("Word.Application")
Dim o2 As Object
Set o2 = VBA.CreateObject("Word.Application")
So, it does look like the *need* could be real, unless PDM really can't create another instance?
|
Last edited by Mike Rosenblum; 03-25-2010 at 04:13 PM.
|

03-25-2010, 04:03 PM
|
 |
Multi-Technologist
Super Moderator * Expert *
|
|
Join Date: May 2004
Location: Michigan
Posts: 3,734
|
|
|
Running your code certainly generates multiple instances, but I don't get them otherwise. So I agree that the need could be real (but not on my current platform, which is odd). I'm curious to resolve this since I (actually a friend of mine) have some need to do this for Excel. I'll try and get that working first and then convert to Word. Between the .NET code you posted and the code links I posted, it is all there.
|
__________________
"May the code that you write never work in ways that you didn't expect; and may the code that you didn't write never require you to maintain it". - Ancient Chinese Proverb
|

03-25-2010, 04:15 PM
|
 |
Microsoft Excel MVP
Forum Leader * Guru *
|
|
Join Date: Jul 2003
Location: New York, NY, USA
Posts: 7,848
|
|
|
The Excel code I gave definitely works for .NET, which is cool.
For VB6/VBA it should be doable as long as the APIs are converted correctly, I would think. Would be awesome if you could nail this! I definitely agree that this is a need that does come up in some scenarios.
For Word we would also need to know the name of the Accessible Object Window, which for Exce is "Excel7", but I don't have a clue what it would be for Word.
|
|

03-25-2010, 05:58 PM
|
 |
Multi-Technologist
Super Moderator * Expert *
|
|
Join Date: May 2004
Location: Michigan
Posts: 3,734
|
|
__________________
"May the code that you write never work in ways that you didn't expect; and may the code that you didn't write never require you to maintain it". - Ancient Chinese Proverb
|

03-25-2010, 06:13 PM
|
|
Newcomer
|
|
Join Date: Mar 2010
Posts: 5
|
|
Cerian Knight.
I will read the links you provided but just to clarify for the winword.exe process.
If I start with both outlook and word closed - no process (obviously)
I start outlook - one WINWORD.exe process
I open a word doc from my PDM system - one WINWORD.exe process
If I now open excel and run "Set wrdApp = GetObject(, "Word.Application")" where wrdApp is defined as Word.Application it captures A word application.
If there are no open e-mails the document count = 0, otherwise the document count is > 0. If there are open word documents (opened through windows explorer or similar) then they are also included in this captured word instance and count > 0.
The word document opened from PDM is not included in this captured word instance. The count is not increased and if iterate through the documents within wrdApp the item opened from PDM is not shown.
Soooo...my system also only shows one WINWORD.exe process but 2 instances somehow..
I have found some code out there that gets me to the handle of the window (see below) but I don't know what to do once I get there. I believe GetObject could be used if I know the full path of the file that the user has opened from PDM (e.g. c:\temp\username\pdm\h876484\.....doc) and if I could get to the child window from the handle to the main window perhaps that would work? The code below gets me the executable of the main window but that is just winword.exe and is not helpful.
Code:
Option Explicit
' Module Name: ModFindWindowLike
' (c) 2005 Wayne Phillips (http://www.everythingaccess.com)
' Written 02/06/2005
Private Declare Function EnumWindows Lib "user32" _
(ByVal lpEnumFunc As Long, _
ByVal lParam As Long) As Long
Private Declare Function GetWindowText Lib "user32" _
Alias "GetWindowTextA" _
(ByVal hwnd As Long, _
ByVal lpString As String, _
ByVal cch As Long) As Long
Private Declare Function GetWindowModuleFileName Lib "user32" _
Alias "GetWindowModuleFileNameA" (ByVal hwnd As Long, _
ByVal Filename As String, ByVal cch As Long) As Long
'Custom structure for passing in the parameters in/out of the hook enumeration function
'Could use global variables instead, but this is nicer.
Private Type FindWindowParameters
strTitle As String 'INPUT
hwnd As Long 'OUTPUT
End Type
Public Function FnFindWindowLike(strWindowTitle As String) As Long
'We'll pass a custom structure in as the parameter to store our result...
Dim Parameters As FindWindowParameters
Parameters.strTitle = strWindowTitle ' Input parameter
Call EnumWindows(AddressOf EnumWindowProc, VarPtr(Parameters))
FnFindWindowLike = Parameters.hwnd
End Function
Private Function EnumWindowProc(ByVal hwnd As Long, _
lParam As FindWindowParameters) As Long
Dim strWindowTitle As String
strWindowTitle = Space(260)
Call GetWindowText(hwnd, strWindowTitle, 260)
strWindowTitle = TrimNull(strWindowTitle) ' Remove extra null terminator
If strWindowTitle Like lParam.strTitle Then
lParam.hwnd = hwnd 'Store the result for later.
EnumWindowProc = 0 'This will stop enumerating more windows
End If
EnumWindowProc = 1
End Function
Private Function TrimNull(strNullTerminatedString As String)
Dim lngPos As Long
'Remove unnecessary null terminator
lngPos = InStr(strNullTerminatedString, Chr$(0))
If lngPos Then
TrimNull = Left$(strNullTerminatedString, lngPos - 1)
Else
TrimNull = strNullTerminatedString
End If
End Function
Sub test()
Dim MyAppHWnd As Long
Dim wrdApp As Word.Application
Dim wrdDoc As Word.Document
Dim sFile As String
Dim FileToOpen As String
Dim iLen As Long
MyAppHWnd = FnFindWindowLike("*DR-*")
sFile = String$(1024, 0)
iLen = GetWindowModuleFileName(MyAppHWnd, sFile, Len(sFile))
If iLen > 0 Then
sFile = Left$(sFile, iLen)
End If
End Sub
Thank you so much for your interest in my problem.  I will look over your links and let you know how I go with a solution.
|
|

03-25-2010, 08:08 PM
|
 |
Multi-Technologist
Super Moderator * Expert *
|
|
Join Date: May 2004
Location: Michigan
Posts: 3,734
|
|
Hmm... what version of Word are you using? The following link is based on the assumption that this PDM problem is related to a known ODMA problem: http://support.microsoft.com/kb/252449.
Otherwise, if you have the hWnd of the PDM spawned Word window and there is only one WINWORD.exe (which already existed before the PDM Word), then I suggest you try this approach first:
Code:
SendMessage hWndWord, WM_USER + 18, 0, 0
Set objWord = Getobject(, "Word.Application")
'code here to look in objWord.Documents
or this:
Code:
SendMessage hWndWord, WM_ACTIVATEAPP, 0, 0
Set objWord = Getobject(, "Word.Application")
'code here to look in objWord.Documents
or some similar method of changing focus: http://support.microsoft.com/kb/238610
|
__________________
"May the code that you write never work in ways that you didn't expect; and may the code that you didn't write never require you to maintain it". - Ancient Chinese Proverb
Last edited by Cerian Knight; 03-25-2010 at 08:28 PM.
|
|
Currently Active Users Viewing This Thread: 1 (0 members and 1 guests)
|
|
|
| Thread Tools |
|
|
| Display Modes |
Linear Mode
|
Posting Rules
|
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts
HTML code is Off
|
|
|
|
|
|
|
|
 |
|