Excel-VBA - 列出任何给定工作簿的所有用户窗体的控件

Posted

技术标签:

【中文标题】Excel-VBA - 列出任何给定工作簿的所有用户窗体的控件【英文标题】:Excel-VBA - list controls of all userforms for ANY given workbook 【发布时间】:2018-04-04 07:13:36 【问题描述】:

任务

我的目标是列出任何给定工作簿的所有用户窗体的所有控件。我的代码适用于工作簿集合中的所有工作簿其他而不是调用工作簿 (ThisWorkBook)。

问题

如果我尝试列出有关 调用工作簿 的所有用户窗体控件,我会在编号错误行 200(所谓的ERL)。下面的代码被特意分成 2 个冗余部分,以明确显示错误。任何帮助表示赞赏。

代码

 Sub ListWBControls()
 ' Purpose: list ALL userform controls of a given workbook within workbooks collection
 '
 Dim bProblem As Boolean
 Dim vbc      As VBIDE.VBComponent          ' module, Reference to MS VBA Exte 5.3 needed !!!
 Dim ctrl     As MSForms.Control
 Dim i        As Integer, imax As Integer   ' control counters
 Dim cnr      As Long, vbcnr As Long
 Dim sLit     As String
 Dim sMsg     As String                     ' result string
 Dim owb      As Workbook                   ' workbook object
 Dim wb       As String                     ' workbook name to choose by user
 ' --------------------
 ' choose Workbook name
 ' --------------------
   wb = Me.ComboBox1.List(Me.ComboBox1.ListIndex, 0)  ' << existing workbook name chosen in combobox
 ' check if wb is calling workbook or other
        For Each owb In Workbooks
          If owb.Name = wb And ThisWorkbook.Name = wb Then
             bProblem = True
             Exit For
          End If
        Next owb
 ' count workbooks
   imax = Workbooks.Count
   i = 1
 ' a) start message string showing workbook name
   sMsg = sMsg & vbNewLine & String(25, "=") & vbNewLine & _
          sLit & " WorkBook: " & Workbooks(i).Name & vbNewLine & String(25, "=")
 '------------------------------
 'Loop thru components (modules) - if of UserForm type
 '------------------------------
 For Each vbc In Workbooks(wb).VBProject.VBComponents
  ' Only if Component type is UserForm
    If vbc.Type = vbext_ct_MSForm Then
     ' increment component and ctrl counters
       sLit = Chr(i + 64) & "."
       vbcnr = vbcnr + 1000
       cnr = vbcnr

     ' b) build message new component
       sMsg = sMsg & vbNewLine & String(25, "-") & vbNewLine & sLit & cnr & " '" & _
              vbc.Name & "'" & vbNewLine & String(25, "-")
     '-------------------
     ' Loop thru controls
     '-------------------
     ' ===================================================================
     ' Code is intently broken into 2 portions, to show error explicitly !
     ' ===================================================================
       On Error GoTo OOPS   ' Error handler --> Error 91: Object variable or With block variable not set

       If Not bProblem Then    ' part 1 - other workbooks: shown explicitly, are no problem
100         For Each ctrl In Workbooks(wb).VBProject.VBComponents(vbc.Name).Designer.Controls
             ' increment ctrl counter
               cnr = cnr + 1
             ' c) build messages controls)
               sMsg = sMsg & vbLf & "  " & Format(cnr, "0 000") & " " & ctrlInfo(ctrl)
            Next
        Else                    ' part 2 - problem arises here (wb = calling workbook)
200         For Each ctrl In Workbooks(wb).VBProject.VBComponents(vbc.Name).Designer.Controls    ' << ERROR 91
             ' increment ctrl counter
               cnr = cnr + 1
             ' c) build messages controls)
               sMsg = sMsg & vbLf & "  " & Format(cnr, "0 000") & " " & ctrlInfo(ctrl)
           Next

        End If

       i = i + 1        ' increment letter counter i
    End If
 Next vbc
 ' show result
 Debug.Print sMsg
 Exit Sub

 OOPS:
 MsgBox "Error No " & Err.Number & " " & Err.Description & vbNewLine & _
        "Error Line " & Erl
 End Sub

辅助功能

 Private Function ctrlInfo(ctrl As MSForms.Control) As String
 ' Purpose: helper function returning userform control information
   ctrlInfo = Left(TypeName(ctrl) & String(5, " "), 5) & " " & _
           Left(ctrl.Name & String(20, " "), 20) & vbTab & _
           " .." & IIf(TypeName(ctrl.Parent) = "UserForm", "Me    " & String(15, " "), _
                       TypeName(ctrl.Parent) & ": " & _
                           Left(ctrl.Parent.Caption & String(15, " "), 15)) & vbTab & _
           " T " & Format(ctrl.Top, "# 000") & "/ L " & Format(ctrl.Left, "# 000")
 End Function

【问题讨论】:

【参考方案1】:

使用用户窗体和 VBComponents 的类属性找到了一个直接解决方案,涵盖了大多数情况

我特意在下面显示修改后的代码,而不是重新编辑。当然,我非常感谢@Excelosaurus 已经接受的解决方案:-)

背景

VBComponents 有一个.HasOpenDesigner 属性。 调用 userForm 具有 class 属性 .Controls 并且可以通过标识符 Me 引用。 (只有第三种很少的情况仍未解决,只有当我不直接引用这些 UF 时:如何通过调用文件中的名称字符串引用其他用户表单 IF 它们是活动的 = @987654324 @ 是错误的;也许值得提出一个新问题)

修改后的代码

 Sub ListWBControls2()
 ' Purpose: list ALL userform controls of a given workbook within workbooks collection
 ' cf.:   https://***.com/questions/46894433/excel-vba-list-controls-of-all-userforms-for-any-given-workbook
 Dim bProblem As Boolean
 Dim vbc      As VBIDE.VBComponent          ' module, Reference to MS VBA Exte 5.3 needed !!!
 Dim ctrl     As MSForms.Control
 Dim i        As Integer, imax As Integer   ' control counters
 Dim cnr      As Long, vbcnr As Long
 Dim sLit     As String
 Dim sMsg     As String                     ' result string
 Dim owb      As Workbook                   ' workbook object
 Dim wb       As String                     ' workbook name to choose by user
 ' ------------------
 ' chosen Workbook
 ' ------------------
     wb = Me.ComboBox1.List(Me.ComboBox1.ListIndex, 0)  ' << existing workbook name chosen in combobox
 ' count workbooks
   imax = Workbooks.Count
   i = 1
 ' a) build message new workbook
   sMsg = sMsg & vbNewLine & String(25, "=") & vbNewLine & _
          sLit & " WorkBook: " & Workbooks(i).Name & vbNewLine & String(25, "=")
 '------------------------------
 'Loop thru components (modules)
 '------------------------------
 For Each vbc In Workbooks(wb).VBProject.VBComponents
  ' Only if Component type is UserForm
    If vbc.Type = vbext_ct_MSForm Then
     ' increment component and ctrl counters
       sLit = Chr(i + 64) & "."
       vbcnr = vbcnr + 1000
       cnr = vbcnr

     ' b) build message new component
       sMsg = sMsg & vbNewLine & String(25, "-") & vbNewLine & sLit & cnr & " '" & _
              vbc.Name & "'" & vbNewLine & String(25, "-")
     '-------------------
     ' Loop thru controls
     '-------------------
       If vbc.HasOpenDesigner Then     ' i) problem for closed userforms in same file resolved
            sMsg = sMsg & vbNewLine & "** " & vbc.Name & " active via Designer.Controls"
            For Each ctrl In Workbooks(wb).VBProject.VBComponents(vbc.Name).Designer.Controls    ' << ERROR 91
               ' increment ctrl counter
                 cnr = cnr + 1
               ' c) build messages controls)
                 sMsg = sMsg & vbLf & "  " & Format(cnr, "0 000") & " " & ctrlInfo(ctrl)
               Next
        ElseIf vbc.Name = Me.Name Then  ' ii) problem for calling userform resolved
              sMsg = sMsg & vbNewLine & "** " & vbc.Name & " active via Me.Controls"
              For Each ctrl In Me.Controls
              ' increment ctrl counter
                cnr = cnr + 1
              ' c) build messages controls)
                sMsg = sMsg & vbLf & "  " & Format(cnr, "0 000") & " " & ctrlInfo(ctrl)

              Next ctrl
                    ' -----------------------------------------------------------
         Else     ' iii) problem reduced to other userforms within the calling file,
                    ' but only IF OPEN
                    ' -----------------------------------------------------------
               sMsg = sMsg & vbLf & "** Cannot read controls in calling file when HasOpenDesigner property is false! **"
          End If
        End If

       i = i + 1        ' increment letter counter i


 Next vbc
 ' show result in textbox
 Me.tbCtrls.Text = sMsg
 Debug.Print sMsg

 End Sub

辅助功能

Private Function ctrlInfo(ctrl As MSForms.Control) As String
' Purpose: helper function returning userform control information
  ctrlInfo = Left(TypeName(ctrl) & String(5, " "), 5) & " " & _
           Left(ctrl.Name & String(20, " "), 20) & vbTab & _
           " .." & IIf(TypeName(ctrl.Parent) = "UserForm", "Me    " & String(15, " "), _
                       TypeName(ctrl.Parent) & ": " & _
                           Left(ctrl.Parent.Caption & String(15, " "), 15)) & vbTab & _
           " T " & Format(ctrl.Top, "# 000") & "/ L " & Format(ctrl.Left, "# 000")
End Function

【讨论】:

【参考方案2】:

显示表单时,您无法以编程方式访问其设计器。您正在从打开的用户窗体调用 ListWBControls。您可以事先关闭表单,让最初打开它的代码构建列表,然后重新打开它。

示例

这段代码放在一个模块中:

Public Sub Workaround()
    On Error GoTo errHandler

    Dim frmUserForm1 As UserForm1
    Dim bDone As Boolean

    bDone = False

    Do
        Set frmUserForm1 = New UserForm1
        Load frmUserForm1
        frmUserForm1.Show vbModal

        If frmUserForm1.DoList Then
            Unload frmUserForm1
            Set frmUserForm1 = Nothing

            ListWBControls
        Else
            bDone = True
        End If
    Loop Until bDone

Cleanup:
    On Error Resume Next
    Unload frmUserForm1
    Set frmUserForm1 = Nothing
    Exit Sub

errHandler:
    MsgBox Err.Description, vbExclamation + vbOKOnly, "Error"
    Resume Cleanup
End Sub

此代码位于 UserForm1 中,您在其中放置了一个名为 cmdDoList 的 CommandButton:

Option Explicit

Private m_bDoList As Boolean

Public Property Get DoList() As Boolean
    DoList = m_bDoList
End Property

Private Sub cmdDoList_Click()
    m_bDoList = True
    Me.Hide
End Sub

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
    Cancel = True
    m_bDoList = False
    Me.Hide
End Sub

这个想法是关闭表单,列出控件并在单击 cmdDoList 时重新打开表单,如果使用 X 按钮将其关闭,则永久关闭表单。

【讨论】:

我担心您的有用提示是正确的,即在用户窗体中 没有直接的方法来构建完整列表。不过希望能收到其他想法:-) 使用用户窗体的类方法是一个绝妙的主意。

以上是关于Excel-VBA - 列出任何给定工作簿的所有用户窗体的控件的主要内容,如果未能解决你的问题,请参考以下文章

VBA - 另存为后工作簿的对象会发生啥?

怎样设定EXCEL整个工作簿的顶端标题栏?

Excel索引与引用另一个工作簿的多个条件匹配

如何从 Libreoffice Calc 工作簿的所有工作表中删除所有图像

VBA Excel 备份:通过 VBA 以新名称保存当前工作簿的副本,保留 VBA 脚本,无提示

vbscript 将来自不同工作簿的所有工作表合并到一个工作簿中