0

I've searched high and low and the following code is the closest I've come to my objective.
This is what I'm working on:
I wrote some code (OK, honestly, mostly copied bits and pieces and pasted into what is probably jumbled code that works) to email documents to my students. If a doc is open, I get and error, which allows me to manually save and close the doc (thx to Debug), and continue on. I would like to automate this, but Word seems to make things a tad difficult by opening each doc in a separate instance. I can get one instance and its doc, but if it is not the one I need, I cannot save and close it. I found how to get the other instances, but I have not found how to check each instance to see if the doc which it opened is the one I want.

I used ZeroKelvin's UDF in (Check if Word instance is running), which I modified a little bit...

Dim WMG As Object, Proc As Object
Set WMG = GetObject("winmgmts:")
For Each Proc In WMG.InstancesOf("win32_process")
  If UCase(Trim(Proc.Name)) = "WINWORD.EXE" Then

              *'Beginning of my code...*
    *'This is what I need and have no idea how to go about*
    Dim WdApp as Word.Application, WdDoc as Object
            *' is it better to have WdDoc as Document?*
    set WdDoc =       ' ### I do not know what goes here ...
    If WdDoc.Name = Doc2Send Or WdDoc.Name = Doc2SendFullName Then
            *' ### ... or how to properly save and close*
      WdApp.Documents(Doc2Send).Close (wdPromptToSaveChanges)
      Exit For
    End If
              *'... end of my code*

    Exit For
  End If
Next 'Proc
Set WMG = Nothing

Thank you for your time and effort.
Cheers

braX
  • 11,506
  • 5
  • 20
  • 33
Alphonse68
  • 13
  • 1
  • 3
  • See: https://stackoverflow.com/questions/67420602/copy-data-from-an-excel-file-to-an-open-word-file/67425818#67425818 – macropod Jun 06 '21 at 01:34
  • @macropod Thanks for your response, but that answer if for when there is only one instance of Word open or, if none, open the doc in question. I am looking for a doc that is one of several docs open, which I might have opened through one of several ways, and who knows if it was the first or the Nth doc opened, so it is unknown in which instance of Word it lives. – Alphonse68 Jun 06 '21 at 01:47
  • Obviously, you'd do the same test for each instance... At a more fundamental level, though, you should only be creating a new instance when necessary. Your «Word seems to make things a tad difficult by opening each doc in a separate instance» is the root cause of your problem - and is the result of how you've coded things. The link I posted also shows how to avoid that. – macropod Jun 06 '21 at 03:54
  • 1
    See here for dealing with multiple instances of Excel/Word https://stackoverflow.com/questions/30363748/having-multiple-excel-instances-launched-how-can-i-get-the-application-object-f – Tim Williams Jun 06 '21 at 04:36
  • @macropod and Tim Williams, thank you for the advice and the links. I will have to do some reading and some playing around, I guess. All of you, do take care. Cheers. – Alphonse68 Jun 07 '21 at 05:28

1 Answers1

1

You may like to consider controlling the number of instances of the Word application that are created. The function below, called from Excel, will return an existing instance of Word or create a new one only if none existed.

Private Function GetWord(ByRef WdApp As Word.Application) As Boolean
    ' 256
    ' return True if a new instance of Word was created
    
    Const AppName As String = "Word.Application"

    On Error Resume Next
    Set WdApp = GetObject(, AppName)
    If Err Then
        Set WdApp = CreateObject(AppName, "")
    End If
    WdApp.Visible = True
    GetWord = CBool(Err)
    Err.Clear
End Function

The function is designed for early binding, meaning you need to add a reference to the Microsoft Word Object Library. During development it's better to work that way. You can change to late binding after your code has been fully developed and tested.

Please take note of the line WdApp.Visible = True. I added it to demonstrate that the object can be modified. A modification done within the If Err bracket would apply only to a newly created instance. Where I placed it it will apply regardless of how WdApp was created.

The next procedure demonstrates how the function might be used in your project. (You can run it as it is.)

Sub Test_GetWord()
    ' 256
    
    Dim WdApp       As Word.Application
    Dim NewWord     As Boolean
    Dim MyDoc       As Word.Document
    
    NewWord = GetWord(WdApp)
    If NewWord Then
        Set MyDoc = WdApp.Documents.Add
        MsgBox "A new instance of Word was created and" & vbCr & _
               "a document added named " & MyDoc.Name
    Else
        MsgBox "Word is running and has " & WdApp.Documents.Count & " document open."
    End If
End Sub

As you see, the variable WdApp is declared here and passed to the function. The function assigns an object to it and returns information whether that object previously existed or not. I use this info to close the instance if it was created or leave it open if the user had it open before the macro was run.

The two message boxes are for demonstration only. You can use the logical spaces they occupy to do other things. And, yes, I would prefer to assign each document in an instance I'm looking at to an object variable. While using early binding you will get the added benefit of Intellisense.

EDIT

Your procedure enumerates processes. I wasn't able to find a way to determine convert the process into an instance of the application. In other words, you can enumerate the processes and find how many instances of Word are running but I can't convert any of these instances into a particular, functioning instance of the application so as to access the documents open in it. Therefore I decided to enumerate the windows instead and work from there back to the document. The function below specifically omits documents opened invisibly.

Option Explicit

Private Declare PtrSafe Function apiGetClassName Lib "user32" Alias _
                "GetClassNameA" (ByVal Hwnd As Long, _
                ByVal lpClassname As String, _
                ByVal nMaxCount As Long) As Long
Private Declare PtrSafe Function apiGetDesktopWindow Lib "user32" Alias _
                "GetDesktopWindow" () As Long
Private Declare PtrSafe Function apiGetWindow Lib "user32" Alias _
                "GetWindow" (ByVal Hwnd As Long, _
                ByVal wCmd As Long) As Long
Private Declare PtrSafe Function apiGetWindowLong Lib "user32" Alias _
                "GetWindowLongA" (ByVal Hwnd As Long, ByVal _
                nIndex As Long) As Long
Private Declare PtrSafe Function apiGetWindowText Lib "user32" Alias _
                "GetWindowTextA" (ByVal Hwnd As Long, ByVal _
                lpString As String, ByVal aint As Long) As Long
Private Const mcGWCHILD = 5
Private Const mcGWHWNDNEXT = 2
Private Const mcGWLSTYLE = (-16)
Private Const mcWSVISIBLE = &H10000000
Private Const mconMAXLEN = 255
 
Sub ListName()
' 256
    ' adapted from
    ' https://www.extendoffice.com/documents/excel/4789-excel-vba-list-all-open-applications.html
    
    Dim xStr            As String
    Dim xStrLen         As Long
    Dim xHandle         As Long
    Dim xHandleStr      As String
    Dim xHandleLen      As Long
    Dim xHandleStyle    As Long
    Dim WdDoc           As Word.Document
    Dim Sp()            As String
    
    On Error Resume Next
    xHandle = apiGetWindow(apiGetDesktopWindow(), mcGWCHILD)
    Do While xHandle <> 0
        xStr = String$(mconMAXLEN - 1, 0)
        xStrLen = apiGetWindowText(xHandle, xStr, mconMAXLEN)
        If xStrLen > 0 Then
            xStr = Left$(xStr, xStrLen)
            xHandleStyle = apiGetWindowLong(xHandle, mcGWLSTYLE)
            If xHandleStyle And mcWSVISIBLE Then
                Sp = Split(xStr, "-")
                If Trim(Sp(UBound(Sp))) = "Word" Then
                    ReDim Preserve Sp(UBound(Sp) - 1)
                    xStr = Trim(Join(Sp, "-"))
                    Set WdDoc = Word.Application.Documents(xStr)
                    ' this applies if the document was not saved:-
                    If WdDoc.Name <> xStr Then Set WdDoc = GetObject(xStr)
                    Debug.Print xStr,
                    Debug.Print WdDoc.Name
                End If
            End If
        End If
        xHandle = apiGetWindow(xHandle, mcGWHWNDNEXT)
    Loop
End Sub

Note that it's important to have the API functions at the top of the module - no code above them. Your question doesn't extend to what you want to do with the files but you wanted them listed, and that is accomplished.

Variatus
  • 14,293
  • 2
  • 14
  • 30
  • Thanks, but when I ran your code it says that there is only one doc open and right now I have three. Being a teacher in this new virtual world, I have to open a varied number of documents on any given class, so I cannot consider controlling the number of Word instances, unless there is a trick to it that I do not know. What I am looking for is **how to get the name of each open document from each instance of Word** until I find the one that I need at than instant. The code that I posted gives the number of instances there are, but I do not know how to grab the docs' names from them. – Alphonse68 Jun 06 '21 at 02:12
  • @Alphonse68 - For a teacher you seem very reluctant to learn. What you are being taught here is how to deal with the cause of the problem, rather than the effects. Word does not, by itself, create a new instance each time a document is opened or created. That is an effect of badly written code. Learn your lesson, apply what you have been taught, and your code will only create a single instance of Word with multiple documents open within it. You can then loop through the documents collection of that instance to get the document you require. – Timothy Rylatt Jun 06 '21 at 06:40
  • It should be possible to iterate through all instances but the better way is to limit them which you can do with the code I provided. The code will return the first instance which has one document open. The other 2 docs are in other instances. So you close that one document and quit the instance. Then run the procedure again to access the next instance which probably also contains one document.. Quit and repeat until all unwanted instances are closed. – Variatus Jun 07 '21 at 01:23
  • @TimothyRylatt - Not reluctant, just ignorance... I don't understand or see how to make it work. I don't know if it makes a difference, but I open Word docs thru Explorer, Recents in Word, Taskbar-pinned, DL'd from emails... I don't have code to open all my docs. Also, if I close all instances looking for the one I need at the moment, I will have to remember which they were so I can open them up again... and back to square one. I guess I'll have to accept Variatus' answer as the answer and play with it to try to make it work for me. Thank you for your time and your input. – Alphonse68 Jun 07 '21 at 05:24
  • @Alphonse68 - that explanation should actually have been included in your question so that we could better understand your issue. It is highly unusual for documents that are opened via the UI, using any of the methods you list, to result in additional instances of Word. But, unless used with care, the `CreateObject` method will result in additional instances. So it is likely that the code you are using is resulting in the additional instances, in which case replacing any code that uses `CreateObject` with the code in Variatus' answer should solve the problem. – Timothy Rylatt Jun 07 '21 at 07:54
  • @Variatus, first off, my apologies; I wrote something the day that you posted the Edit, but I must have not sent it. I thank you so very much, sir, for having taken the time to write that procedure which is perfect, exactly what I need. I came back today because I wanted to say Thank you! again. I have not discarded your initial code; I'm sure it'll come in handy one day. Oh, I also modified this line in the middle of the Do While as such: `If Trim(Sp(UBound(Sp))) = "Microsoft Word" Then` --might be due to MSO2010? Cheers! – Alphonse68 Jun 11 '21 at 03:37
  • Thanks for letting me know. "Word" or "Microsoft Word" might indeed be a matter of version. It's "Word" on Office 365. To overcome this one might also use `Sp = Split(xStr, "-"): Sp = Split(Sp(Ubound(Sp))` (adding one line of code) which would cause my code to be able to handle both versions. – Variatus Jun 11 '21 at 07:35
  • Thanks for the extra line, but I couldn't get it to work: If I leave "MS Word" or "Microsoft" in `If Trim(Sp(UBound(Sp))) = "Microsoft Word" ` it drops out of the IF. If I leave "Word", it goes in, but it doesn't keep the doc's name. Watch window values: `Sp = Split(xStr, "-"): Sp = Split(Sp(UBound(Sp))): If Trim(Sp(UBound(Sp))) = "Word" Then: ...: xStr = Trim(Join(Sp, "-"))` "Microsoft" for lines 1, 2, & 3. xStr shows "[filename] - Microsoft Word" before executing that line and "-Microsoft" after execution, instead of the filename. I wish I knew enough VBA to fix it. – Alphonse68 Jun 13 '21 at 05:54
  • Yes indeed. I was getting too smart for my boots. Sorry about that. You would need to use `If Trim(Split(Sp(UBound(Sp)))(UBound(Split(Sp(UBound(Sp)))))) = "Word" Then` or assign the 2nd split to a dedicated variable. That's because you need the first split to get the document's name from in the next step. – Variatus Jun 13 '21 at 08:39
  • :-) it happens to the best of us. Thanks once again; it works like a charm. This is great because if I ever change versions, I will not have to try to remember why it does not work. Cheers! Stay safe. – Alphonse68 Jun 14 '21 at 17:37