View Full Version : [SOLVED:] How do you find which template a macro run by a command bar item is located?
johndavidson
02-11-2017, 08:03 PM
I have some code that lists all the macro names associated with all the command bar items. In my system, there are multiple templates, so the code can still leave me unsure of which template the macro is in. How can I discover the template in which the macro is located. The code (before formatting the output) looks like this. I want to add the template name to the output associated with each command bar item.
    
Dim cmd As CommandBar
    Dim ctrl As CommandBarControl
    Dim itm As CommandBarControl
    Dim itm2 As CommandBarControl
    Dim menuName As String
    Dim menuName2 As String
    For Each cmd In Application.CommandBars
        If cmd.BuiltIn = False Then
           Selection.TypeText (vbCrLf & vbCrLf & "Command Bar : " & cmd.Name & " Visible [" & cmd.Visible & "]" & vbCrLf & vbCrLf)
          
           For Each ctrl In cmd.Controls
                If ctrl.Type = 1 Then
                    Selection.TypeText (ChrW(9) & ctrl.Caption & ChrW(9) & ">" & ChrW(9) & ctrl.OnAction & vbCrLf)
                Else
                    menuName = ctrl.Caption
                    Selection.TypeText vbCrLf
                    For Each itm In cmd.Controls(menuName).Controls
                        If itm.Type = 1 Then
                            Selection.TypeText (ChrW(9) & menuName & " | " & itm.Caption & ChrW(9) & ">" & ChrW(9) & itm.OnAction & vbCrLf)
                        Else
                            menuName2 = itm.Caption
                            Selection.TypeText vbCrLf
                            For Each itm2 In cmd.Controls(menuName).Controls(menuName2).Controls
                              Selection.TypeText (ChrW(9) & menuName & " | " & menuName2 & " | " & itm2.Caption & ChrW(9) & ">" & ChrW(9) & itm2.OnAction & vbCrLf)
                            Next
                        End If
                    Next
                End If
           Next
        End If
    Next
In case it is of use to anyone, the code in its present form is in the attached Word doc.
Thanks
John Davidson
johndavidson
02-11-2017, 09:46 PM
I did some tinkering and came up with the solution.  Need to extract parent template name from ctrl.Parent.Context. Full code is attached in case anyone is interested:
    
For Each cmd In Application.CommandBars
        If cmd.BuiltIn = False Then
           Selection.TypeText (vbCrLf & vbCrLf & "Command Bar : " & cmd.Name & " Visible [" & cmd.Visible & "]" & vbCrLf & vbCrLf)
          
           For Each ctrl In cmd.Controls
                If ctrl.Type = 1 Then
                    Selection.TypeText (ChrW(9) & ctrl.Caption & ChrW(9) & ">" & ChrW(9) & ctrl.OnAction & "  (" & _
                        Mid(ctrl.Parent.Context, InStrRev(ctrl.Parent.Context, "\") + 1, InStrRev(ctrl.Parent.Context, ".") - InStrRev(ctrl.Parent.Context, "\") - 1) & ")" & vbCrLf)
                Else
                    menuName = ctrl.Caption
                    Selection.TypeText vbCrLf
                    For Each itm In cmd.Controls(menuName).Controls
                        If itm.Type = 1 Then
                            Selection.TypeText (ChrW(9) & menuName & " | " & itm.Caption & ChrW(9) & ">" & ChrW(9) & itm.OnAction & "  (" & _
                                Mid(ctrl.Parent.Context, InStrRev(ctrl.Parent.Context, "\") + 1, InStrRev(ctrl.Parent.Context, ".") - InStrRev(ctrl.Parent.Context, "\") - 1) & ")" & vbCrLf)
                        Else
                            menuName2 = itm.Caption
                            Selection.TypeText vbCrLf
                            For Each itm2 In cmd.Controls(menuName).Controls(menuName2).Controls
                              Selection.TypeText (ChrW(9) & menuName & " | " & menuName2 & " | " & itm2.Caption & ChrW(9) & ">" & ChrW(9) & itm2.OnAction & "  (" & _
                                Mid(ctrl.Parent.Context, InStrRev(ctrl.Parent.Context, "\") + 1, InStrRev(ctrl.Parent.Context, ".") - InStrRev(ctrl.Parent.Context, "\") - 1) & ")" & vbCrLf)
                            Next
                        End If
                    Next
                End If
           Next
        End If
    Next
gmaxey
02-12-2017, 02:22 AM
You can streamline your code a bit like this:
Sub ScratchMacro()
Dim cmd, ctrl, menuname, menuname2
Dim itm, itm2
Dim strFile As String
  For Each cmd In Application.CommandBars
    strFile = vbNullString
    If cmd.BuiltIn = False Then
      Selection.TypeText vbCrLf & vbCrLf & "Command Bar : " & cmd.Name & _
                         " Visible [" & cmd.Visible & "]" & vbCrLf & vbCrLf
      For Each ctrl In cmd.Controls
       If strFile = vbNullString Then
         strFile = CreateObject("scripting.filesystemobject").GetBaseName(ctrl.Parent.Context)
       End If
       If ctrl.Type = 1 Then
         Selection.TypeText vbTab & ctrl.Caption & vbTab & ">" & vbTab & ctrl.OnAction & " (" & strFile & ")" & vbCrLf
       Else
         menuname = ctrl.Caption
         Selection.TypeText vbCrLf
         For Each itm In cmd.Controls(menuname).Controls
           If itm.Type = 1 Then
             Selection.TypeText vbTab & menuname & " | " & itm.Caption & vbTab & ">" & vbTab & _
                                itm.OnAction & "  (" & strFile & ")" & vbCrLf
           Else
             menuname2 = itm.Caption
             Selection.TypeText vbCrLf
             For Each itm2 In cmd.Controls(menuname).Controls(menuname2).Controls
               Selection.TypeText vbTab & menuname & " | " & menuname2 & " | " & _
                         itm2.Caption & vbTab & ">" & vbTab & itm2.OnAction & "  (" & strFile & ")" & vbCrLf
             Next
          End If
        Next
      End If
     Next
    End If
  Next
lbl_Exit:
  Exit Sub
End Sub
johndavidson
02-12-2017, 02:44 AM
Mmmm ... thanks Greg, that looks a whole lot tidier! Good to have a professional on the job!
Maxeyfied version attached.
John
johndavidson
02-12-2017, 03:35 AM
Greg - just noticed that there is a bug in my logic, copied through into yours. One of my templates (MenusToolbar.dot) contains a collection of additions to the main menu bar, without any code, just the menu additions. The macros called from menu items in those menus show the template as MenusToolbar.dot, rather than the actual template in which the macro is located. Seems like we need to dig down to the bottom to be sure of getting the actual template in which the macro is stored. How to do that?
John
gmaxey
02-12-2017, 08:29 AM
John,
I edited to remove the "A basic macro coded by Greg Maxey" in my last post.  That is part of an autotext  I use when starting a macro and got left in my mistake.
Not sure how to do that if it can be done.  Can you share the template and I will try to look at it later
johndavidson
02-12-2017, 09:01 PM
Greg - Actually, it's not just the Menus Toolbar. It's ALL the toolbars. For any toolbar/menu item, the context that gets shown, using either your method or mine, is the template that contains the toolbar, not the template containing the macro. So we are still short on how to discover the template that contains the macro that is run from a toolbar item.
Any code that can locate which template is invoked by a menu item would need to be done at the item level, where I am presently doing it (but getting the wrong  answer).
I have tinkered with the various pop-up options, even tried ctrl.Parent.Controls.Parent.Context which still gives the name of the template containing the toolbar.
Since we do have the code module name, one way to handle the need to know the parent template name may be identify the template containing that particular code module. Is there a way to do that?
Alternatively, one could add some code that lists the modules in all templates that have toolbars. Then the user can simply look up which template(s) have that module name. There may be some code around which does that for Word templates.
gmaxey
02-13-2017, 05:58 PM
John
Here I some code I had squirreled away for listing macros in a document.  You may be able to adapt it to suit your needs:
Option Explicit
'Procedures require a reference set to Microsoft Visual Basice for Application Extensibility
Sub MacroList()
Dim oRng As Word.Range
Dim oProject As VBIDE.VBProject
Dim strName As String
Dim i As Long
Dim lngLineNumber As Long
Dim j As Long
Dim lngLineCount As Long
Set oProject = ActiveDocument.VBProject
Set oRng = ActiveDocument.Range
oRng.InsertAfter "The project Name is: " & oProject.Name & "." & vbCr & vbCr
With oProject
  For i = 1 To .VBComponents.Count
    oRng.InsertAfter vbTab & "Component " & i & " " & Chr(150) & " Name: " _
                     & .VBComponents(i).Name & " Type: " & ComponentTypeToString(.VBComponents(i).Type) & vbCr
    lngLineCount = .VBComponents(i).CodeModule.CountOfLines
    If lngLineCount > 3 Then
       strName = ""
       lngLineNumber = 1
       j = 1
       Do While strName = ""
          strName = .VBComponents(i).CodeModule.ProcOfLine(lngLineNumber, vbext_pk_Proc)
          lngLineNumber = lngLineNumber + 1
       Loop
       oRng.InsertAfter vbTab & vbTab & "The procedure(s) in this component are:" & vbCr _
         & vbTab & vbTab & vbTab & "Procedure " & j & ": " & strName & vbCr
       For lngLineNumber = lngLineNumber To lngLineCount
         If .VBComponents(i).CodeModule.ProcOfLine(lngLineNumber, vbext_pk_Proc) <> strName Then
            j = j + 1
            strName = .VBComponents(i).CodeModule.ProcOfLine(lngLineNumber, vbext_pk_Proc)
            oRng.InsertAfter vbTab & vbTab & vbTab & "Procedure " & j & ": " & strName & vbCr
         End If
       Next
     Else
       oRng.InsertAfter vbTab & vbTab & "There are no procedures in this component." & vbCr
     End If
     oRng.InsertAfter vbCr
  Next i
End With
Set oRng = Nothing
Set oProject = Nothing
End Sub
Sub ProjectModuleExport()
Dim oProject As VBProject
Dim strProject As String
Dim strName As String
Dim i As Long
Dim lngLineNumber As Long
Dim j As Long
Dim lngLineCount As Long
Set oProject = ActiveDocument.VBProject
With oProject
  For i = 1 To .VBComponents.Count
    MsgBox .VBComponents(i).Name
      Select Case .VBComponents(i).Type
      Case vbext_ct_StdModule, vbext_ct_ClassModule, vbext_ct_MSForm
        .VBComponents(i).Export ("C:\" & .Name & "_" & .VBComponents(i).Name & ".bas")
      Case Else
    End Select
  Next
End With
End Sub
Sub CopyMacroCodeToDoc()
Dim oRng As Word.Range
Dim oProject As VBProject
Dim strName As String
Dim i As Long
Dim lngLineNumber As Long
Dim j As Long
Dim lngLineCount As Long
Set oProject = ActiveDocument.VBProject
Set oRng = ActiveDocument.Range
oRng.InsertAfter oProject.Name & "." & vbCr & vbCr
With oProject
  For i = 1 To .VBComponents.Count
    oRng.InsertAfter .VBComponents(i).CodeModule.Lines(1, .VBComponents(i).CodeModule.CountOfLines)
  Next i
End With
Set oRng = Nothing
Set oProject = Nothing
End Sub
Function ComponentTypeToString(ComponentType As VBIDE.vbext_ComponentType) As String
Select Case ComponentType
  Case vbext_ct_ActiveXDesigner
    ComponentTypeToString = "ActiveX Designer"
  Case vbext_ct_ClassModule
    ComponentTypeToString = "Class Module"
  Case vbext_ct_Document
    ComponentTypeToString = "Document Module"
  Case vbext_ct_MSForm
    ComponentTypeToString = "UserForm"
  Case vbext_ct_StdModule
    ComponentTypeToString = "Code Module"
  Case Else
    ComponentTypeToString = "Unknown Type: " & CStr(ComponentType)
End Select
End Function
johndavidson
02-13-2017, 07:57 PM
Greg - That's a useful bit of code and it will do the trick - but there's a bug in it! I have a number of templates with Class Modules and on some types of module, the program loops forever on this bit of code (lngLineNumber just goes on getting bigger and bigger):
If lngLineCount > 3 Then
                strName = ""
                lngLineNumber = 1
                j = 1
                Do While strName = ""
                    strName = .VBComponents(i).CodeModule.ProcOfLine(lngLineNumber, vbext_pk_Proc)
                    lngLineNumber = lngLineNumber + 1
                Loop
                oRng.InsertAfter vbTab & vbTab & "The procedure(s) in this component are:" & vbCr _
                & vbTab & vbTab & vbTab & "Procedure " & j & ": " & strName & vbCr
                For lngLineNumber = lngLineNumber To lngLineCount
                    If .VBComponents(i).CodeModule.ProcOfLine(lngLineNumber, vbext_pk_Proc) <> strName Then
                        j = j + 1
                        strName = .VBComponents(i).CodeModule.ProcOfLine(lngLineNumber, vbext_pk_Proc)
                        oRng.InsertAfter vbTab & vbTab & vbTab & "Procedure " & j & ": " & strName & vbCr
                    End If
                Next
I'm attaching a template (it italicizes words, and corrects italicization of surrounding punctuation, individually, with a global option) that causes the above code to loop forever. I tried another template with class modules, which generated the same error, so it is probably generic, rather than specific to a particular template.
Over to you
John
johndavidson
02-13-2017, 11:27 PM
Greg - I now have the listing of the project and Code Module part of your code adapted for my purposes. Looks good. But your original code looks useful, in case you care to fix the Class Module bug. Looks like something simple. I'll upload my completed code later, after a little more testing.
John
johndavidson
02-14-2017, 03:13 AM
Greg - And ... My code simply opens and closes all the templates in Word's startup folder in order to read the project names. I thought it would be better to use the Templates collection, but unless a template happens to be open, I get the message "Cant perform operation since project is protected VBA Word Templates". These templates have no protection (I've double checked). The code it doesn't like is
With oProject      For i = 1 To .VBComponents.Count
I read somewhere that .VBComponents does like the use of "With", so I redid it as:
        For i = 1 To oProject.VBComponents.Count
            oRng.InsertAfter vbTab & oProject.VBComponents(i).Name & ".dot" & vbTab & ComponentTypeToString(oProject.VBComponents(i).Type) & vbCrLf
        Next i
... but it made no difference. The full code snippet is:
Sub Test()
    Dim oRng As Word.Range
    Dim oProject As VBIDE.VBProject     ' Requires reference to MicroSoft Visual Basic for Applications Extensibility 5.3
    Dim i As Long
    Dim vFile
    Dim sTemp As String, WordStartUpPath As String
    On Error GoTo 0
    WordStartUpPath = Options.DefaultFilePath(wdStartupPath)
    Documents.Add DocumentType:=wdNewBlankDocument
       
    Set oRng = ActiveDocument.Range
    oRng.InsertAfter vbCrLf & vbCrLf & "User Template Code Module List"
    
    For Each vFile In Templates
        Set oProject = vFile.VBProject
        oRng.InsertAfter vbCrLf & vbCrLf & oProject.Name & vbCrLf
        With oProject
            For i = 1 To .VBComponents.Count
                oRng.InsertAfter vbTab & .VBComponents(i).Name & ".dot" & vbTab & ComponentTypeToString(.VBComponents(i).Type) & vbCrLf
            Next i
        End With
        Set oProject = Nothing
    Next
End Sub
Function ComponentTypeToString(ComponentType As VBIDE.vbext_ComponentType) As String
' Code by Greg Maxey
    Select Case ComponentType
    Case vbext_ct_ActiveXDesigner
        ComponentTypeToString = "ActiveX Designer"
    Case vbext_ct_ClassModule
        ComponentTypeToString = "Class Module"
    Case vbext_ct_Document
        ComponentTypeToString = "Document Module"
    Case vbext_ct_MSForm
        ComponentTypeToString = "UserForm"
    Case vbext_ct_StdModule
        ComponentTypeToString = "Code Module"
    Case Else
        ComponentTypeToString = "Unknown Type: " & CStr(ComponentType)
    End Select
End Function
Any ideas?
Thanks
John
gmaxey
02-14-2017, 05:28 AM
John,
Yes the problem was the code was looking for "procedures" only and not Property Gets, Lets or Sets.  That is why is crashed and burned in the class modules. To complicate things your Class StackItem didn't have any of them.  Here is modified version:
Option Explicit
'Procedures require a reference set to Microsoft Visual Basice for Application Extensibility
Sub MacroList()
Dim oRng As Word.Range
Dim oProject As VBIDE.VBProject
Dim strName As String
Dim i As Long
Dim lngLineNumber As Long
Dim j As Long
Dim lngLineCount As Long
Set oProject = ActiveDocument.VBProject
Set oRng = ActiveDocument.Range
oRng.InsertAfter "The project Name is: " & oProject.Name & "." & vbCr & vbCr
With oProject
  For i = 1 To .VBComponents.Count
    oRng.InsertAfter vbTab & "Component " & i & " " & Chr(150) & " Name: " _
                     & .VBComponents(i).Name & " Type: " & ComponentTypeToString(.VBComponents(i).Type) & vbCr
    lngLineCount = .VBComponents(i).CodeModule.CountOfLines
    If lngLineCount > 3 Then
       strName = ""
       lngLineNumber = 1
       j = 1
       Do
          strName = .VBComponents(i).CodeModule.ProcOfLine(lngLineNumber, vbext_pk_Get)
          If strName <> vbNullString Then
            lngLineNumber = lngLineNumber + 1
            Exit Do
          End If
          strName = .VBComponents(i).CodeModule.ProcOfLine(lngLineNumber, vbext_pk_Let)
         If strName <> vbNullString Then
            lngLineNumber = lngLineNumber + 1
            Exit Do
          End If
          strName = .VBComponents(i).CodeModule.ProcOfLine(lngLineNumber, vbext_pk_Set)
          If strName <> vbNullString Then
            lngLineNumber = lngLineNumber + 1
            Exit Do
          End If
          strName = .VBComponents(i).CodeModule.ProcOfLine(lngLineNumber, vbext_pk_Proc)
          If strName <> vbNullString Then
            lngLineNumber = lngLineNumber + 1
            Exit Do
          End If
          lngLineNumber = lngLineNumber + 1
          If lngLineNumber > lngLineCount Then Exit Do
       Loop
       If lngLineNumber < lngLineCount Then
        oRng.InsertAfter vbTab & vbTab & "The procedure(s) in this component are:" & vbCr _
          & vbTab & vbTab & vbTab & "Procedure " & j & ": " & strName & vbCr
        For lngLineNumber = lngLineNumber To lngLineCount
          If .VBComponents(i).CodeModule.ProcOfLine(lngLineNumber, vbext_pk_Proc) <> strName Then
             j = j + 1
             strName = .VBComponents(i).CodeModule.ProcOfLine(lngLineNumber, vbext_pk_Proc)
             oRng.InsertAfter vbTab & vbTab & vbTab & "Procedure " & j & ": " & strName & vbCr
          End If
          If .VBComponents(i).CodeModule.ProcOfLine(lngLineNumber, vbext_pk_Proc) <> strName Then
             j = j + 1
             strName = .VBComponents(i).CodeModule.ProcOfLine(lngLineNumber, vbext_pk_Get)
             oRng.InsertAfter vbTab & vbTab & vbTab & "Procedure " & j & ": " & strName & vbCr
          End If
          If .VBComponents(i).CodeModule.ProcOfLine(lngLineNumber, vbext_pk_Proc) <> strName Then
             j = j + 1
             strName = .VBComponents(i).CodeModule.ProcOfLine(lngLineNumber, vbext_pk_Let)
             oRng.InsertAfter vbTab & vbTab & vbTab & "Procedure " & j & ": " & strName & vbCr
          End If
          If .VBComponents(i).CodeModule.ProcOfLine(lngLineNumber, vbext_pk_Proc) <> strName Then
             j = j + 1
             strName = .VBComponents(i).CodeModule.ProcOfLine(lngLineNumber, vbext_pk_Set)
             oRng.InsertAfter vbTab & vbTab & vbTab & "Procedure " & j & ": " & strName & vbCr
          End If
          
        Next
       Else
         oRng.InsertAfter vbTab & vbTab & "There are no procedures in this component." & vbCr
       End If
     Else
       oRng.InsertAfter vbTab & vbTab & "There are no procedures in this component." & vbCr
     End If
     oRng.InsertAfter vbCr
  Next i
End With
Set oRng = Nothing
Set oProject = Nothing
End Sub
Sub ProjectModuleExport()
Dim oProject As VBProject
Dim strProject As String
Dim strName As String
Dim i As Long
Dim lngLineNumber As Long
Dim j As Long
Dim lngLineCount As Long
Set oProject = ActiveDocument.VBProject
With oProject
  For i = 1 To .VBComponents.Count
    MsgBox .VBComponents(i).Name
      Select Case .VBComponents(i).Type
      Case vbext_ct_StdModule, vbext_ct_ClassModule, vbext_ct_MSForm
        .VBComponents(i).Export ("C:\" & .Name & "_" & .VBComponents(i).Name & ".bas")
      Case Else
    End Select
  Next
End With
End Sub
Sub CopyMacroCodeToDoc()
Dim oRng As Word.Range
Dim oProject As VBProject
Dim strName As String
Dim i As Long
Dim lngLineNumber As Long
Dim j As Long
Dim lngLineCount As Long
Set oProject = ActiveDocument.VBProject
Set oRng = ActiveDocument.Range
oRng.InsertAfter oProject.Name & "." & vbCr & vbCr
With oProject
  For i = 1 To .VBComponents.Count
    oRng.InsertAfter .VBComponents(i).CodeModule.Lines(1, .VBComponents(i).CodeModule.CountOfLines)
  Next i
End With
Set oRng = Nothing
Set oProject = Nothing
End Sub
Function ComponentTypeToString(ComponentType As VBIDE.vbext_ComponentType) As String
Select Case ComponentType
  Case vbext_ct_ActiveXDesigner
    ComponentTypeToString = "ActiveX Designer"
  Case vbext_ct_ClassModule
    ComponentTypeToString = "Class Module"
    Stop
  Case vbext_ct_Document
    ComponentTypeToString = "Document Module"
  Case vbext_ct_MSForm
    ComponentTypeToString = "UserForm"
  Case vbext_ct_StdModule
    ComponentTypeToString = "Code Module"
  Case Else
    ComponentTypeToString = "Unknown Type: " & CStr(ComponentType)
End Select
End Function
gmaxey
02-14-2017, 05:30 AM
You might want to  use something like:
  Dim oTmpDoc as Document
  Set oTmpDoc = vFile.OpenAsDocument
    'do your stuff
  oTmpDoc
johndavidson
02-14-2017, 07:04 AM
Greg - Excellent. That does the trick.
Thanks for your help
John
johndavidson
02-14-2017, 07:08 AM
So there isn't a way of doing it without actually opening the template as a document? I was hoping to be able to read the module names etc. through the templates collection, as one can do when making hotkey changes, for example. Feels a little safer that way.
John
gmaxey
02-14-2017, 06:06 PM
No, not that I know of.
johndavidson
02-14-2017, 10:44 PM
Greg - It's all working fine now. Thanks for the help. I'll post the code in a few days.
John
johndavidson
02-15-2017, 04:57 AM
Greg - Here's the package, some of it based on your code. Nothing very elaborate. Provides these routines:
commandBarList                                 List all command bars and derivatives, and code modules, in a new document. Helps to keep track of which macros are associated with which toolbar/menu items.
 
projectList                                           List all code modules in all user templates.
 
procedureList                                       List all procedures (macros etc.) in all user templates.
 
copyMacroCodeToDoc                        Copy all macro code from current document to new document
 
projectModuleExport                          Exports the modules from the active document (if any) to the C:\ drive root folder.
John
johndavidson
02-15-2017, 11:52 PM
This is an updated version of the code. Does the same thing, just a little tidier.
johndavidson
02-16-2017, 08:48 PM
This update fixes an issue with the Today routine that occurs in some setups. Also prevents the templates being added to the RecentFiles  list.
Powered by vBulletin® Version 4.2.5 Copyright © 2025 vBulletin Solutions Inc. All rights reserved.