There are a lot of combobox in sheet and they are appending dynamic. But all of combobox' assignments are same. They will run a function in macro. Can I rename all of combobox with same name? Or how can I do what I want?
Sub ekranadi()
Dim mainworkBook As Workbook
Set mainworkBook = ActiveWorkbook
For i = 1 To mainworkBook.Sheets.Count
If Left(mainworkBook.Sheets(i).Name, 5) = "Ekran"
Then ComboBoxEkranAdı.AddItem mainworkBook.Sheets(i).Name
End If
Next i
End Sub
If my understanding of your requirement is correct, the macro below will show you how to achieve the effect you seek.
A user form has a collection named Controls that contains every control on the form. Instead of MyControl.Name you can write Controls(6).Name if 6 is the index number within Controls of MyControl.
The macro below outputs the index number, type name and name of every control on a form. If the control is a ComboBox, it adds three items to it with each item value being unique to the box.
Edit
Sorry I did not read your question carefully enough. I do not use controls on worksheets because I consider controls on user forms to be more powerful and more convenient. Controls on worksheets are further complicated by having two types: those loaded from the Controls toolbox and those loaded from the Forms toolbox. Functionality depends on which type you have.
To test the new macro DemoWorksheet, I loaded worksheet "Test" with both types of control. The macro shows how to fill both type of combo box via their collections.
Option Explicit
Sub DemoUserForm()
Dim InxCtrl As Long
Load UserForm1
With UserForm1
For InxCtrl = 0 To .Controls.Count - 1
Debug.Print Right(" " & InxCtrl, 2) & " " & _
Left(TypeName(.Controls(InxCtrl)) & Space(10), 15) & _
.Controls(InxCtrl).Name
If TypeName(.Controls(InxCtrl)) = "ComboBox" Then
With .Controls(InxCtrl)
.AddItem InxCtrl & " A"
.AddItem InxCtrl & " B"
.AddItem InxCtrl & " C"
End With
End If
Next
End With
UserForm1.Show
End Sub
Sub DemoWorksheet()
Dim Inx As Long
With Worksheets("Test")
Debug.Print "Shapes.Count=" & .Shapes.Count
Debug.Print "OLEObjects.Count=" & .OLEObjects.Count
For Inx = 1 To .Shapes.Count
With .Shapes(Inx)
Debug.Print "S " & Right(" " & Inx, 2) & " ShapeType=" & _
ShapeTypeName(.Type) & " Name=" & .Name
If .Type = msoFormControl Then
Debug.Print " FormControlType=" & FormControlTypeName(.FormControlType)
If .FormControlType = xlDropDown Then
.ControlFormat.AddItem "S " & Inx & " A"
.ControlFormat.AddItem "S " & Inx & " B"
.ControlFormat.AddItem "S " & Inx & " C"
.ControlFormat.DropDownLines = 3
End If
End If
End With
Next
For Inx = 1 To .OLEObjects.Count
With .OLEObjects(Inx)
Debug.Print "O " & Right(" " & Inx, 2) & " OleType=" & _
OLETypeName(.OLEType) & " Name=" & .Name
If Left(.Name, 8) = "ComboBox" Then
.Object.AddItem "O " & Inx & " A"
.Object.AddItem "O " & Inx & " B"
.Object.AddItem "O " & Inx & " C"
End If
End With
Next
End With
End Sub
Function FormControlTypeName(ByVal FCType As Long) As String
Dim Inx As Long
Dim TypeName() As Variant
Dim TypeNumber() As Variant
TypeName = Array("ButtonControl", "CheckBox", "DropDown", "EditBox", "GroupBox", _
"Label", "ListBox", "OptionButton", "ScrollBar", "Spinner")
TypeNumber = Array(xlButtonControl, xlCheckBox, xlDropDown, xlEditBox, xlGroupBox, _
xlLabel, xlListBox, xlOptionButton, xlScrollBar, xlSpinner)
For Inx = 0 To UBound(TypeNumber)
If FCType = TypeNumber(Inx) Then
FormControlTypeName = TypeName(Inx)
Exit Function
End If
Next
FormControlTypeName = "Unknown"
End Function
Function OLETypeName(ByVal OType As Long) As String
If OType = xlOLELink Then
OLETypeName = "Link"
ElseIf OType = xlOLEEmbed Then
OLETypeName = "Embed"
ElseIf OType = xlOLEControl Then
OLETypeName = "Control"
Else
OLETypeName = "Unknown"
End If
End Function
Function ShapeTypeName(ByVal SType As Long) As String
Dim Inx As Long
Dim TypeName() As Variant
Dim TypeNumber() As Variant
TypeName = Array("AutoShape", "Callout", "Canvas", "Chart", "Comment", "Diagram", _
"EmbeddedOLEObject", "FormControl", "Freeform", "Group", "Line", _
"LinkedOLEObject", "LinkedPicture", "Media", "OLEControlObject", _
"Picture", "Placeholder", "ScriptAnchor", "ShapeTypeMixed", _
"Table", "TextBox", "TextEffect")
TypeNumber = Array(msoAutoShape, msoCallout, msoCanvas, msoChart, msoComment, msoDiagram, _
msoEmbeddedOLEObject, msoFormControl, msoFreeform, msoGroup, msoLine, _
msoLinkedOLEObject, msoLinkedPicture, msoMedia, msoOLEControlObject, _
msoPicture, msoPlaceholder, msoScriptAnchor, msoShapeTypeMixed, _
msoTable, msoTextBox, msoTextEffect)
For Inx = 0 To UBound(TypeNumber)
If SType = TypeNumber(Inx) Then
ShapeTypeName = TypeName(Inx)
Exit Function
End If
Next
ShapeTypeName = "Unknown"
End Function
Related
I'm brand new to VBA and I have got a script that turns an Excel into a XML file.
What I need is to change it so that rather using a predefined Record Tag name, it uses data given in the Excel data table.
If we take this Excel table as an example, the XML file would end up being:
DeclarationFile>
<ID>
<k2>5555.555</k2>
<k7>2222.222</k7>
<k15>33.333</k15>
</ID>
<ID>
<k2>4444.444</k2>
<k7>1111.111</k7>
<k15>66.666</k15>
</ID>
<ID>
<k2>333.33</k2>
<k7>5.55</k7>
<k15>7.77</k15>
</ID>
</DeclarationFile>
But I need to change the code so that it uses the R14, R17 etc instead of ID
The whole code:
Sub CreateXMLFile()
Dim MN_Row As Integer, MN_Column As Integer, MN_TEMP As String, mn_YesOrNo As Variant, mndefine_folder As String
Dim mn_XML_FileName As String, mn_XML_Record_Name As String, mn_LF As String, mn_rtc1 As Integer
Dim mn_first_range As String, mn_second_range As String, mn_tt As String, mn_FieldName(99) As String
mn_LF = Chr(10) & Chr(13)
mndefine_folder = "C:\"
mn_YesOrNo = MsgBox("Vajadzigs:" & mn_LF _
& "1. XML File Name" & mn_LF _
& "2. Record Tag Name" & mn_LF _
& "3. A Range of Cells Containing Column Headers" & mn_LF _
& "4. A Range of Cells Containing the Data Table." & mn_LF _
& "If You Are Ready To Proceed, Click 'Yes'.", vbQuestion + vbYesNo, "CreateXMLFile")
If mn_YesOrNo = vbNo Then
Debug.Print "User Canceled With 'No'"
Exit Sub
End If
mn_XML_FileName = GapFiller(InputBox("1. Enter the XML File Name:", "CreateXMLFile", "xml_file"))
If Right(mn_XML_FileName, 4) <> ".xml" Then
mn_XML_FileName = mn_XML_FileName & ".xml"
End If
mn_XML_Record_Name = GapFiller(InputBox("2. Enter The Record Tag Name:", "CreateXMLFile", "ID"))
mn_first_range = InputBox("3. Enter The Range of Cells Containing Column Headers:", "CreateXMLFile", "A1:B1")
If MN_DataRange(mn_first_range, 1) <> MN_DataRange(mn_first_range, 2) Then
MsgBox "Error: Headers Must Be In The Same Row" & mn_LF & "Atcelts", vbOKOnly + vbCritical, "CreateXMLFile"
Exit Sub
End If
MN_Row = MN_DataRange(mn_first_range, 1)
For MN_Column = MN_DataRange(mn_first_range, 3) To MN_DataRange(mn_first_range, 4)
If Len(Cells(MN_Row, MN_Column).Value) = 0 Then
MsgBox "Error: Header Contains Empty Cell" & mn_LF & "Canceled", vbOKOnly + vbCritical, "CreateXMLFile"
Exit Sub
End If
mn_FieldName(MN_Column - MN_DataRange(mn_first_range, 3)) = GapFiller(Cells(MN_Row, MN_Column).Value)
Next MN_Column
mn_second_range = InputBox("4. Enter The Range of Cells Containing the Data Table:", "CreateXMLFile", "A2:B2")
If MN_DataRange(mn_first_range, 4) - MN_DataRange(mn_first_range, 3) <> MN_DataRange(mn_second_range, 4) - MN_DataRange(mn_second_range, 3) Then
MsgBox "Error: There Are More Or Less Headers Than Columns of Data" & mn_LF & "Canceled", vbOKOnly + vbCritical, "CreateXMLFile"
Exit Sub
End If
mn_rtc1 = MN_DataRange(mn_second_range, 3)
If InStr(1, mn_XML_FileName, ":\") = 0 Then
mn_XML_FileName = mndefine_folder & mn_XML_FileName
End If
Open mn_XML_FileName For Output As #1
Print #1, "<?xml version=" & Chr(34) & "1.0" & Chr(34) & " encoding=" & Chr(34) & "ISO-8859-1" & Chr(34) & "?>"
Print #1, "<DeclarationFile>"
For MN_Row = MN_DataRange(mn_second_range, 1) To MN_DataRange(mn_second_range, 2)
Print #1, "<" & mn_XML_Record_Name & ">"
For MN_Column = mn_rtc1 To MN_DataRange(mn_second_range, 4)
Print #1, "<" & mn_FieldName(MN_Column - mn_rtc1) & ">" & AmpersandEliminate(CheckForm(MN_Row, MN_Column)) & "</" & mn_FieldName(MN_Column - mn_rtc1) & ">"
Next MN_Column
Print #1, "</" & mn_XML_Record_Name & ">"
Next MN_Row
Print #1, "</DeclarationFile>"
Close #1
MsgBox mn_XML_FileName & " izveidots." & mn_LF & "Completed", vbOKOnly + vbInformation, "CreateXMLFile"
Debug.Print mn_XML_FileName & " saved"
End Sub
Function MN_DataRange(Rng_As_Text As String, MN_Item As Integer) As Integer
Dim MN_user_range As Range
Set MN_user_range = Range(Rng_As_Text)
Select Case MN_Item
Case 1
MN_DataRange = MN_user_range.Row
Case 2
MN_DataRange = MN_user_range.Row + MN_user_range.Rows.Count - 1
Case 3
MN_DataRange = MN_user_range.Column
Case 4
MN_DataRange = MN_user_range.Columns(MN_user_range.Columns.Count).Column
End Select
Exit Function
End Function
Function GapFiller(mn_my_Str As String) As String
Dim mn_Position As Integer
mn_Position = InStr(1, mn_my_Str, " ")
Do While mn_Position > 0
Mid(mn_my_Str, mn_Position, 1) = "_"
mn_Position = InStr(1, mn_my_Str, " ")
Loop
GapFiller = LCase(mn_my_Str)
End Function
Function CheckForm(mn_Row_Number As Integer, mn_Column_Number As Integer) As String
CheckForm = Cells(mn_Row_Number, mn_Column_Number).Value
If IsNumeric(Cells(mn_Row_Number, mn_Column_Number).Value) Then
CheckForm = Format(Cells(mn_Row_Number, mn_Column_Number).Value, "#,##0 ;(#,##0)")
End If
If IsDate(Cells(mn_Row_Number, mn_Column_Number).Value) Then
CheckForm = Format(Cells(mn_Row_Number, mn_Column_Number).Value, "dd mmm yy")
End If
End Function
Function AmpersandEliminate(mn_my_Str As String) As String
Dim mn_Position As Integer
mn_Position = InStr(1, mn_my_Str, "&")
Do While mn_Position > 0
Mid(mn_my_Str, mn_Position, 1) = "+"
mn_Position = InStr(1, mn_my_Str, "&")
Loop
AmpersandEliminate = mn_my_Str
End Function
Since I am completely clueless with VBA, I was getting array errors and "argument not optional"
Any tips on where to start are appreciated!
Try this:
Sub CreateXMLFile()
Const THE_FOLDER As String = "C:\"
Dim ws As Worksheet, rngData As Range, fName As String, rw As Long, col As Long
Dim xml As String, tagId As String, tagVal As String, v
If MsgBox("Vajadzigs:" & vbLf & "1. XML File Name" & vbLf & _
"2. A Range of Cells Containing the Data Table (with headers)." & vbLf & _
"If You Are Ready To Proceed, Click 'Yes'.", vbQuestion + vbYesNo, "CreateXMLFile") <> vbYes Then
Debug.Print "User Canceled With 'No'"
Exit Sub
End If
'would be better to use `Application.GetSaveAsFileName` here...
fName = Application.GetSaveAsFilename(filefilter:="XML (*.xml),*.xml", _
Title:="1. Select SaveAs name for XML file")
On Error Resume Next 'ignore error if not range selected
Set rngData = Application.InputBox("2. Select the range with data (include headers):", _
"CreateXMLFile", Type:=8)
On Error Resume Next 'stop ignoring error
If rngData Is Nothing Then
Debug.Print "User did not select a range"
Exit Sub
End If
Open fName For Output As #1
Print #1, "<?xml version=""1.0"" encoding=""ISO-8859-1""?>"
Print #1, "<DeclarationFile>"
For rw = 2 To rngData.Rows.Count
tagId = rngData.Cells(rw, 1).Value
Print #1, "<" & tagId & ">"
For col = 2 To rngData.Columns.Count
tagVal = rngData.Cells(1, col).Value
v = rngData.Cells(rw, col).Value
Print #1, "<" & tagVal & ">" & Replace(CheckForm(v), "&", "+") & "</" & tagVal & ">"
Next col
Print #1, "</" & tagId & ">"
Next rw
Print #1, "</DeclarationFile>"
Open fName For Output As #1
Close #1
MsgBox fName & " izveidots." & vbLf & "Completed", vbOKOnly + vbInformation, "CreateXMLFile"
Debug.Print fName & " saved"
End Sub
Function CheckForm(v) As String
If IsNumeric(v) Then v = Format(v, "#,##0 ;(#,##0)")
If IsDate(v) Then v = Format(v, "dd mmm yy")
CheckForm = CStr(v)
End Function
I have code below for a print function created in VBA. When I select "Return from leave" under the drop down "Type of Change", my print macro prints as a "salary" change type, not "return from leave". I cant see where I went wrong in my code or what is causing the issue... Any thoughts? Thanks in advance!
Sub pcf_print()
Dim ws As Worksheet
Dim datasheet As Worksheet
Dim fs As Object
Dim str As String
Dim bool As Boolean
If Len(ActiveSheet.Name) < 3 Then
MsgBox "This worksheet is not a PCF"
Exit Sub
End If
If Left(ActiveSheet.Name, 3) <> "PCF" Then
MsgBox "This worksheet is not a PCF"
Exit Sub
End If
'MsgBox Right(ActiveSheet.Name, Len(ActiveSheet.Name) - 1 - InStr(ActiveSheet.Name, " v")) 'Right(ActiveSheet.Name, 4)
If InStr(ActiveSheet.Name, " vv") Then
If (CDbl(Right(ActiveSheet.Name, Len(ActiveSheet.Name) - 1 - InStr(ActiveSheet.Name, " vv") - 1)) >= 1.2 And (ActiveSheet.Range("F10") = "(select)" Or ActiveSheet.Range("F10") = "" Or ActiveSheet.Range("F10") = "(sélect.)")) Then
MsgBox "This form has not been completed"
Exit Sub
End If
Else
If (CDbl(Right(ActiveSheet.Name, Len(ActiveSheet.Name) - 1 - InStr(ActiveSheet.Name, " v"))) < 1.2 And (ActiveSheet.Range("F9") = "(select)" Or ActiveSheet.Range("F9") = "")) Or (CDbl(Right(ActiveSheet.Name, Len(ActiveSheet.Name) - 1 - InStr(ActiveSheet.Name, " v"))) >= 1.2 And (ActiveSheet.Range("F10") = "(select)" Or ActiveSheet.Range("F10") = "" Or ActiveSheet.Range("F10") = "(sélect.)")) Then
MsgBox "This form has not been completed"
Exit Sub
End If
End If
Set datasheet = ActiveSheet
If ActiveWorkbook.Worksheets("Form Lists").Range("CorpOrStore") = "Corp" Then
str = "Corporate"
Else
str = "Stores"
End If
Set fs = CreateObject("Scripting.FileSystemObject")
bool = fs.FolderExists("H:\HR\Online PCF Archive\" & str & "\" & Trim(datasheet.Range("StoreDeptResult")) & "\")
If Not bool Then
MkDir "H:\HR\Online PCF Archive\" & str & "\" & Trim(datasheet.Range("StoreDeptResult")) & "\"
End If
If InStr(ActiveSheet.Name, " vv") Then
If CDbl(Right(ActiveSheet.Name, Len(ActiveSheet.Name) - 1 - InStr(ActiveSheet.Name, " vv") - 1)) >= 1.2 Then
ActiveWorkbook.SaveAs "H:\HR\Online PCF Archive\" & str & "\" & Trim(datasheet.Range("StoreDeptResult")) & "\" & Replace(datasheet.Range("F10"), "/", "_") & " for " & datasheet.Range("J17") & ", " & datasheet.Range("F17") & " effective " & Month(datasheet.Range("F12")) & "-" & Day(datasheet.Range("F12")) & "-" & Year(datasheet.Range("F12")) & ".xls"
End If
Else
If CDbl(Right(ActiveSheet.Name, Len(ActiveSheet.Name) - 1 - InStr(ActiveSheet.Name, " v"))) >= 1.2 Then
ActiveWorkbook.SaveAs "H:\HR\Online PCF Archive\" & str & "\" & Trim(datasheet.Range("StoreDeptResult")) & "\" & Replace(datasheet.Range("F10"), "/", "_") & " for " & datasheet.Range("J17") & ", " & datasheet.Range("F17") & " effective " & Month(datasheet.Range("F12")) & "-" & Day(datasheet.Range("F12")) & "-" & Year(datasheet.Range("F12")) & ".xls"
Else
ActiveWorkbook.SaveAs "H:\HR\Online PCF Archive\" & str & "\" & Trim(datasheet.Range("StoreDeptResult")) & "\" & datasheet.Range("F9") & " for " & datasheet.Range("J16") & ", " & datasheet.Range("F16") & " effective " & Month(datasheet.Range("F11")) & "-" & Day(datasheet.Range("F11")) & "-" & Year(datasheet.Range("F11")) & ".xls"
End If
End If
Set ws = ActiveWorkbook.Worksheets("Payroll Forms")
If Right(ActiveSheet.Name, 5) = "v1.20" Then
ActiveWorkbook.Worksheets("Form Lists").Unprotect "0nl1n3"
ActiveWorkbook.Worksheets("Form Lists").Range("B8") = "A1:G76"
ActiveWorkbook.Worksheets("Form Lists").Range("B9") = "A80:G157"
ActiveWorkbook.Worksheets("Form Lists").Range("B10") = "A160:G225"
ActiveWorkbook.Worksheets("Form Lists").Range("B11") = "A228:G259"
ActiveWorkbook.Worksheets("Form Lists").Range("B12") = "A228:G259"
ActiveWorkbook.Worksheets("Form Lists").Range("B13") = "A228:G259"
ActiveWorkbook.Worksheets("Form Lists").Range("B14") = "A263:G338"
ActiveWorkbook.Worksheets("Form Lists").Range("B15") = "A263:G338"
ActiveWorkbook.Worksheets("Form Lists").Range("B16") = "A343:G367"
ActiveWorkbook.Worksheets("Form Lists").Range("B17") = "A263:G338"
ActiveWorkbook.Worksheets("Form Lists").Range("B18") = "A160:G225"
ActiveWorkbook.Worksheets("Form Lists").Range("B19") = "A370:G420"
ActiveWorkbook.Worksheets("Form Lists").Protect "0nl1n3"
End If
If Right(ActiveSheet.Name, 5) = "v1.20" Or Right(ActiveSheet.Name, 5) = "v1.21" Or str = "Corporate" Then
ws.PageSetup.PrintArea = ActiveWorkbook.Worksheets("Form Lists").Range("H2")
Else
ws.PageSetup.PrintArea = ActiveWorkbook.Worksheets("Form Lists").Range("i2")
End If
ActiveWorkbook.Unprotect "0nl1n3"
ws.Visible = xlSheetVisible
ws.PrintOut
ws.Visible = xlSheetHidden
ActiveWorkbook.Protect "0nl1n3"
ActiveWorkbook.Close False
End Sub
OP says:
When I select "Return from leave" under the drop down "Type of Change", my print macro prints as a "salary" change type, not "return from leave"
Assuming that the
"salary" change type
corresponds to the "default print" i.e.:
ws.PageSetup.PrintArea = ActiveWorkbook.Worksheets("Form Lists").Range("i2")
It seems that the reason the code provided always prints the default range, it's because the lines that determine the printed output are validating the ActiveSheet.Name instead of the value in the "Type of Change field and print"
Solution proposed:
Change these lines to reflect the cell where the "Type of Change field and print" is located:
Replace ActiveSheet.Name with the corresponding cell.address i.e.: F10 and update as required the comparisons against "v1.20" and "v1.21"
If Right(ActiveSheet.Name, 5) = "v1.20" _
Or Right(ActiveSheet.Name, 5) = "v1.21" _
Or str = "Corporate" Then
ws.PageSetup.PrintArea = ActiveWorkbook.Worksheets("Form Lists").Range("H2")
Else
ws.PageSetup.PrintArea = ActiveWorkbook.Worksheets("Form Lists").Range("i2")
End If
It should become (comparison values shown as a reference, they should be updated in line with the choices in the drop-down list) :
If ActiveSheet.Range("F10").Value2 = "Return from leave" _
Or str = "Corporate" Then
ws.PageSetup.PrintArea = ActiveWorkbook.Worksheets("Form Lists").Range("H2")
Else
ws.PageSetup.PrintArea = ActiveWorkbook.Worksheets("Form Lists").Range("i2")
End If
Note:
1. Avoid using ActiveWorkbook and ActiveSheet, suggest to replace all instances of them by: ThisWorkbook and datasheet respectively.
2. Additionally, I would suggest to review and incorporate the use of With statement and Select Case statement throughout your procedure.
Task
My goal is to list all controls of all UserForms for ANY given workbook. My code works for all workbooks within the workbooks collection other than the calling workbook (ThisWorkBook).
Problem
If I try to list all the userforms' controls regarding the calling workbook, I get Error 91 Object variable or With block variable not set at numbered error line 200 (so called ERL). The code below is intently broken into 2 redundant portions, to show the error explicitly. Any help is appreciated.
Code
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
Helper function
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
When a form is displayed, you can't get programmatic access to its designer. You are calling ListWBControls from an open UserForm. You could close the form beforehand, and let the code which opened it in the first place build the list, and re-open it afterwards.
Example
This code goes in a Module:
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
This code goes in UserForm1 where you've put one CommandButton named cmdDoList:
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
The idea is to close the form, list the controls and re-open the form when cmdDoList is clicked, and to close the form for good if it is dismissed with the X button.
Found a direct solution covering most cases using the class properties of userforms and VBComponents.
I intently show the modified code below instead of re-editing. Of course, I highly appreciate the already accepted solution by #Excelosaurus :-)
Background
VBComponents have a .HasOpenDesigner property.
the calling userForm has the class properties .Controls AND can be referenced via the identifier Me.
(only the third seldom case remains unsolved and only if I don't reference these UFs directly: how to reference other userforms by a name string within the calling file IF they are active = .HasOpenDesigner is false; maybe worth a new question)
Modified code
Sub ListWBControls2()
' Purpose: list ALL userform controls of a given workbook within workbooks collection
' cf.: https://stackoverflow.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
Helper function
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
SOLVED. Solution at bottom!
Hopefully you brainiacs can help me out as I've apparently reached the limit of my programming capabilities.
I am looking for a way to write a VBA sub which duplicates another VBA Sub, but replace the name and another input. The case in details:
I am trying to build an Excel template for the organization, which will allow the users to inport/export data to/from Access databases (.accdb), as the end-users reluctance towards using real databases (as opposed to excel lists) apparently lies in their inability to extract/submit the data to/from Excel, where they are comfortable working with the data.
The challenge is, that users who don't know how to link to Access, for sure don't know anything about VBA code. Hence, I have created a worksheet from which the users selects a database using a file-path, table, password, set filters, define where to copy/insert datasets, fields to import etc. A Macro then handles the rest.
However, I want to create a macro which allows the user to create additional database links. As it is right now, this would require the user to open VBE and copy two macros and change one line of code... but that is a recipe for disaster. So how can I add a button to the sheet that copies the code I have written and rename the macro?
... I was considering if using a function, but cannot get my head around how that should Work.
Does it make sense? Any ideas/ experiences? Is there a completely different way around it that I haven't considered?
I'd really appreciate your inputs - even if this turns out to be impossible.
Edit:
Macro Man, you asked for the code - it's rather long due to all the user input fields, so I was trying to save you Guys for it since the code in and of itself is working fine...
Sub GetData1()
' Click on Tools, References and select
' the Microsoft ActiveX Data Objects 2.0 Library
Dim DBFullName As String
Dim Connect As String, Source As String
Dim Connection As ADODB.Connection
Dim Recordset As ADODB.Recordset
Dim Col As Integer
Dim DBInfoLocation As Range
Dim PW As String
Dim WSforData As String
Dim CellforData As String
Dim FieldList As Integer
'******************************
'Enter location for Database conectivity details below:
'******************************
Set DBInfoLocation = ActiveWorkbook.Sheets("DBLinks").Range("C15:I21")
FieldList = ActiveWorkbook.Sheets("DBLinks").Range("P1").Value
'******************************
' Define data location
WSforData = DBInfoLocation.Rows(4).Columns(1).Value
CellforData = DBInfoLocation.Rows(5).Columns(1).Value
'Set filters
Dim FilField1, FilField2, FilFieldA, FilFieldB, FilFieldC, FilFieldD, FilFieldE, FilOperator1, FilOperator2, FilOperatorA, FilOperatorB, FilOperatorC, FilOperatorD, FilOperatorE, FilAdMth1, FilAdMthA, FilAdMthB, FilAdMthC, FilAdMthD As String
Dim Filtxt1, Filtxt2, FiltxtA, FiltxtB, FiltxtC, FiltxtD, FiltxtE As String
Dim ExtFld1, ExtFld2, ExtFld3, ExtFld4, ExtFld5, ExtFld6, ExtFld7, ExtFld As String
Dim FilCnt, FilCntA As Integer
Dim FilVar1 As String
'Set DB field names
FilField1 = DBInfoLocation.Rows(1).Columns(5).Value
FilField2 = DBInfoLocation.Rows(2).Columns(5).Value
FilFieldA = DBInfoLocation.Rows(3).Columns(5).Value
FilFieldB = DBInfoLocation.Rows(4).Columns(5).Value
FilFieldC = DBInfoLocation.Rows(5).Columns(5).Value
FilFieldD = DBInfoLocation.Rows(6).Columns(5).Value
FilFieldE = DBInfoLocation.Rows(7).Columns(5).Value
'Set filter operators
FilOperator1 = DBInfoLocation.Rows(1).Columns(6).Value
FilOperator2 = DBInfoLocation.Rows(2).Columns(6).Value
FilOperatorA = DBInfoLocation.Rows(3).Columns(6).Value
FilOperatorB = DBInfoLocation.Rows(4).Columns(6).Value
FilOperatorC = DBInfoLocation.Rows(5).Columns(6).Value
FilOperatorD = DBInfoLocation.Rows(6).Columns(6).Value
FilOperatorE = DBInfoLocation.Rows(7).Columns(6).Value
'Run through criteria to find VarType(FilCrit1) (the Dimension data type) for the criteria field and set the appropriate data type for the filter
currentLoad = IIf(IsNumeric(DBInfoLocation.Rows(1).Columns(7).Value), CDbl(FilCrit1), IIf((DBInfoLocation.Rows(1).Columns(7).Value = "True" Or DBInfoLocation.Rows(1).Columns(7).Value = "False"), CBool(FilCrit1), IIf(IsDate(DBInfoLocation.Rows(1).Columns(7).Value), CDate(FilCrit1), CStr(FilCrit1))))
currentLoad = IIf(IsNumeric(DBInfoLocation.Rows(2).Columns(7).Value), CDbl(FilCrit2), IIf((DBInfoLocation.Rows(2).Columns(7).Value = "True" Or DBInfoLocation.Rows(2).Columns(7).Value = "False"), CBool(FilCrit2), IIf(IsDate(DBInfoLocation.Rows(2).Columns(7).Value), CDate(FilCrit2), CStr(FilCrit2))))
currentLoad = IIf(IsNumeric(DBInfoLocation.Rows(3).Columns(7).Value), CDbl(FilCrit3), IIf((DBInfoLocation.Rows(3).Columns(7).Value = "True" Or DBInfoLocation.Rows(3).Columns(7).Value = "False"), CBool(FilCrit3), IIf(IsDate(DBInfoLocation.Rows(3).Columns(7).Value), CDate(FilCrit3), CStr(FilCrit3))))
currentLoad = IIf(IsNumeric(DBInfoLocation.Rows(4).Columns(7).Value), CDbl(FilCrit4), IIf((DBInfoLocation.Rows(4).Columns(7).Value = "True" Or DBInfoLocation.Rows(4).Columns(7).Value = "False"), CBool(FilCrit4), IIf(IsDate(DBInfoLocation.Rows(4).Columns(7).Value), CDate(FilCrit4), CStr(FilCrit4))))
currentLoad = IIf(IsNumeric(DBInfoLocation.Rows(5).Columns(7).Value), CDbl(FilCrit5), IIf((DBInfoLocation.Rows(5).Columns(7).Value = "True" Or DBInfoLocation.Rows(5).Columns(7).Value = "False"), CBool(FilCrit5), IIf(IsDate(DBInfoLocation.Rows(5).Columns(7).Value), CDate(FilCrit5), CStr(FilCrit5))))
currentLoad = IIf(IsNumeric(DBInfoLocation.Rows(6).Columns(7).Value), CDbl(FilCrit6), IIf((DBInfoLocation.Rows(6).Columns(7).Value = "True" Or DBInfoLocation.Rows(6).Columns(7).Value = "False"), CBool(FilCrit6), IIf(IsDate(DBInfoLocation.Rows(6).Columns(7).Value), CDate(FilCrit6), CStr(FilCrit6))))
currentLoad = IIf(IsNumeric(DBInfoLocation.Rows(7).Columns(7).Value), CDbl(FilCrit7), IIf((DBInfoLocation.Rows(7).Columns(7).Value = "True" Or DBInfoLocation.Rows(7).Columns(7).Value = "False"), CBool(FilCrit7), IIf(IsDate(DBInfoLocation.Rows(7).Columns(7).Value), CDate(FilCrit7), CStr(FilCrit7))))
'Set Filter criteria
FilCrit1 = DBInfoLocation.Rows(1).Columns(7).Value
FilCrit2 = DBInfoLocation.Rows(2).Columns(7).Value
FilCrit3 = DBInfoLocation.Rows(3).Columns(7).Value
FilCrit4 = DBInfoLocation.Rows(4).Columns(7).Value
FilCrit5 = DBInfoLocation.Rows(5).Columns(7).Value
FilCrit6 = DBInfoLocation.Rows(6).Columns(7).Value
FilCrit7 = DBInfoLocation.Rows(7).Columns(7).Value
'Set additional filter-method
FilAdMth1 = DBInfoLocation.Rows(1).Columns(8).Value
FilAdMthA = DBInfoLocation.Rows(3).Columns(8).Value
FilAdMthB = DBInfoLocation.Rows(4).Columns(8).Value
FilAdMthC = DBInfoLocation.Rows(5).Columns(8).Value
FilAdMthD = DBInfoLocation.Rows(6).Columns(8).Value
'Set which fields to extract
ExtFld1 = DBInfoLocation.Rows(1).Columns(9).Value
ExtFld2 = DBInfoLocation.Rows(2).Columns(9).Value
ExtFld3 = DBInfoLocation.Rows(3).Columns(9).Value
ExtFld4 = DBInfoLocation.Rows(4).Columns(9).Value
ExtFld5 = DBInfoLocation.Rows(5).Columns(9).Value
ExtFld6 = DBInfoLocation.Rows(6).Columns(9).Value
ExtFld7 = DBInfoLocation.Rows(7).Columns(9).Value
'Filter on query
'Only criteria of value type string should have single quotation marks around them
FilCnt = 0
If FilField1 <> "" Then
If VarType(FilCrit1) = vbString Then
Filtxt1 = " WHERE [" & FilField1 & "] " & FilOperator1 & " '" & FilCrit1 & "'"
Else
Filtxt1 = " WHERE [" & FilField1 & "] " & FilOperator1 & " " & FilCrit1
End If
FilCnt = 1
End If
If FilField2 <> "" And FilCnt = 1 Then
If VarType(FilCrit2) = vbString Then
Filtxt2 = " " & FilAdMth1 & " [" & FilField2 & "] " & FilOperator2 & " '" & FilCrit2 & "'"
Else
Filtxt2 = " " & FilAdMth1 & " [" & FilField2 & "] " & FilOperator2 & " " & FilCrit2
End If
FilCnt = 2
End If
'Filter on Dataset
FilCntA = 0
If FilFieldA <> "" Then
If VarType(FilCrit3) = vbString Then
FiltxtA = FilFieldA & " " & FilOperatorA & " '" & FilCrit3 & "'"
Else
FiltxtA = FilFieldA & " " & FilOperatorA & " " & FilCrit3
End If
FilCntA = 1
End If
If FilFieldB <> "" And FilCntA = 1 Then
If VarType(FilCrit4) = vbString Then
FiltxtB = " " & FilAdMthA & " " & FilFieldB & " " & FilOperatorB & " '" & FilCrit4 & "'"
Else
FiltxtB = " " & FilAdMthA & " " & FilFieldB & " " & FilOperatorB & " " & FilCrit4
End If
FilCntA = 2
End If
If FilFieldC <> "" And FilCntA = 2 Then
If VarType(FilCrit5) = vbString Then
FiltxtC = " " & FilAdMthB & " " & FilFieldC & " " & FilOperatorC & " '" & FilCrit5 & "'"
Else
FiltxtC = " " & FilAdMthB & " " & FilFieldC & " " & FilOperatorC & " " & FilCrit5
End If
FilCntA = 3
End If
If FilFieldD <> "" And FilCntA = 3 Then
If VarType(FilCrit6) = vbString Then
FiltxtD = " " & FilAdMthC & " " & FilFieldD & " " & FilOperatorD & " '" & FilCrit6 & "'"
Else
FiltxtD = " " & FilAdMthC & " " & FilFieldD & " " & FilOperatorD & " " & FilCrit6
End If
FilCntA = 4
End If
If FilFieldE <> "" And FilCntA = 4 Then
If VarType(FilCrit7) = vbString Then
FiltxtE = " " & FilAdMthD & " " & FilFieldE & " " & FilOperatorE & " '" & FilCrit7 & "'"
Else
FiltxtE = " " & FilAdMthD & " " & FilFieldE & " " & FilOperatorE & " " & FilCrit7
End If
FilCntA = 5
End If
' Select Fields to Extract
ExtFld = "*"
If ExtFld1 <> "" Then
ExtFld = "[" & ExtFld1 & "]"
End If
If ExtFld2 <> "" Then
ExtFld = "[" & ExtFld1 & "],[" & ExtFld2 & "]"
End If
If ExtFld3 <> "" Then
ExtFld = "[" & ExtFld1 & "],[" & ExtFld2 & "],[" & ExtFld3 & "]"
End If
If ExtFld4 <> "" Then
ExtFld = "[" & ExtFld1 & "],[" & ExtFld2 & "],[" & ExtFld3 & "],[" & ExtFld4 & "]"
End If
If ExtFld5 <> "" Then
ExtFld = "[" & ExtFld1 & "],[" & ExtFld2 & "],[" & ExtFld3 & "],[" & ExtFld4 & "],[" & ExtFld5 & "]"
End If
If ExtFld6 <> "" Then
ExtFld = "[" & ExtFld1 & "],[" & ExtFld2 & "],[" & ExtFld3 & "],[" & ExtFld4 & "],[" & ExtFld5 & "],[" & ExtFld6 & "]"
End If
If ExtFld7 <> "" Then
ExtFld = "[" & ExtFld1 & "],[" & ExtFld2 & "],[" & ExtFld3 & "],[" & ExtFld4 & "],[" & ExtFld5 & "],[" & ExtFld6 & "],[" & ExtFld7 & "]"
End If
' Database path info
PW = DBInfoLocation.Rows(3).Columns(1).Value
' Your path will be different
DBFullName = DBInfoLocation.Rows(1).Columns(1).Value
DBTable = DBInfoLocation.Rows(2).Columns(1).Value
' Open the connection
Set Connection = New ADODB.Connection
Connect = "Provider=Microsoft.ACE.OLEDB.12.0;"
Connect = Connect & "Data Source=" & DBFullName & ";Jet OLEDB:Database Password=" & PW & ";"
Connection.Open ConnectionString:=Connect
' Create RecordSet & Define data to extract
Set Recordset = New ADODB.Recordset
With Recordset
'Get All Field Names by opening the DB, extracting a recordset, entering the field names and closing the dataset
Source = DBTable
.Open Source:=Source, ActiveConnection:=Connection
For ColH = 0 To Recordset.Fields.Count - 1
ActiveWorkbook.Worksheets("DBLinks").Range("A1").Offset(ColH + 3, FieldList - 1).Cells.Clear
ActiveWorkbook.Worksheets("DBLinks").Range("A1").Offset(ColH + 3, FieldList - 1).Value = Recordset.Fields(ColH).Name
ActiveWorkbook.Worksheets("RangeNames").Range("A1").Offset(ColH + 2, (DBInfoLocation.Rows(1).Columns(2).Value) - 1).Cells.Clear
ActiveWorkbook.Worksheets("RangeNames").Range("A1").Offset(ColH + 2, (DBInfoLocation.Rows(1).Columns(2).Value) - 1).Value = Recordset.Fields(ColH).Name
Next
Set Recordset = Nothing
End With
' Get the recordset, but only extract the field names of those defined in the spreadsheet.
' If no fields have been selected, all fields will be extracted.
Set Connection = New ADODB.Connection
Connect = "Provider=Microsoft.ACE.OLEDB.12.0;"
Connect = Connect & "Data Source=" & DBFullName & ";Jet OLEDB:Database Password=" & PW & ";"
Connection.Open ConnectionString:=Connect
Set Recordset = New ADODB.Recordset
With Recordset
If FilCnt = 0 Then 'No filter
Source = "SELECT " & ExtFld & " FROM " & DBTable
End If
' Filter Data if selected
If FilCnt = 1 Then
Source = "SELECT " & ExtFld & " FROM " & DBTable & Filtxt1
End If
If FilCnt = 2 Then
Source = "SELECT " & ExtFld & " FROM " & DBTable & Filtxt1 & Filtxt2
End If
.Open Source:=Source, ActiveConnection:=Connection
If FilCntA = 1 Then
Recordset.Filter = FiltxtA
End If
If FilCntA = 2 Then
Recordset.Filter = FiltxtA & FiltxtB
End If
If FilCntA = 3 Then
Recordset.Filter = FiltxtA & FiltxtB & FiltxtC
End If
If FilCntA = 4 Then
Recordset.Filter = FiltxtA & FiltxtB & FiltxtC & FiltxtD
End If
If FilCntA = 5 Then
Recordset.Filter = FiltxtA & FiltxtB & FiltxtC & FiltxtD & FiltxtE
End If
'Debug.Print Recordset.Filter
' Clear data
For Col = 0 To Recordset.Fields.Count - 1
If WSforData <> "" Then
ActiveWorkbook.Worksheets(WSforData).Range(CellforData).Offset(0, Col).EntireColumn.Clear
End If
'ActiveWorkbook.Worksheets("DBLinks").Range("A1").Offset(Col + 3, FieldList - 1).Cells.Clear
Next
' Write field names
For Col = 0 To Recordset.Fields.Count - 1
If WSforData <> "" Then
ActiveWorkbook.Worksheets(WSforData).Range(CellforData).Offset(0, Col).Value = Recordset.Fields(Col).Name
End If
Next
' Write recordset
If WSforData <> "" Then
ActiveWorkbook.Worksheets(WSforData).Range(CellforData).Offset(1, 0).CopyFromRecordset Recordset
ActiveWorkbook.Worksheets(WSforData).Columns.AutoFit
End If
End With
' Clear recordset and close connection
Set Recordset = Nothing
Connection.Close
Set Connection = Nothing
End Sub
This piece of the "DBLinks" worksheet is probably also needed for full understanding of the code:
DBLinks user input area for database connectivity
SOLUTION:
I followed the advice to look into VBProject.VBComponents which copied the macro. I created a simple form which asked for the name to use for the macro and the rest of the inputs comes from the relative reference. I will spare you for a full copy of my long and less than graceful code, but the essential of the code are:
In case someone else could benefit from my experience: In the Click-action of the command button on the form:
Private Sub cmdCreateDB_Click()
'Go to Tools, References and add: Microsoft Visual Basic for Applications Extensibility 5.3
Dim VBProj As VBIDE.VBProject
Dim VBComp As VBIDE.VBComponent
Dim CodeMod As VBIDE.CodeModule
Dim LineNum As Long
Const DQUOTE = """" ' one " character
Set VBProj = ActiveWorkbook.VBProject
Set VBComp = VBProj.VBComponents("Module1")
Set CodeMod = VBComp.CodeModule
Dim txtDBLinkName As String
txtDBLinkName = Me.txtDBName
With CodeMod
LineNum = .CountOfLines + 1
.InsertLines LineNum, " Sub " & txtDBLinkName & "()"
LineNum = LineNum + 1
.InsertLines LineNum, " ' Click on Tools, References and select"
LineNum = LineNum + 1
.InsertLines LineNum, " ' the Microsoft ActiveX Data Objects 2.0 Library"
' And then it goes on forever through all the lines of the original code...
' just remember to replace all double quotations with(Without Square brackets):
' [" & DQUOTE & "]
'And it ends up with:
LineNum = LineNum + 1
.InsertLines LineNum, " Set Recordset = Nothing"
LineNum = LineNum + 1
.InsertLines LineNum, " Connection.Close"
LineNum = LineNum + 1
.InsertLines LineNum, " Set Connection = Nothing"
LineNum = LineNum + 1
.InsertLines LineNum, " End Sub"
End With
Unload Me
End Sub
Thank you everyone for your help. - Especially you #findwindow for coming up with the path to a solution.
For the sake of completion, here's how this could be dealt with without metaprogramming.
Problems that boil down to "do the same thing - but..." can often be solved by making the program as generic as possible. All data specific to a single use-case should be passed down from above in a clear manner, allowing the program to be reused.
Let's look at an example of how this could be implemented in order to generate query strings from one or many ranges of varying sizes.
The first step is to group all data that belongs to the concept of a Filter. Since VBA doesn't have object literals, we can use an Array, a Collection or a Type to represent a Filter instead.
Generating the query strings requires distinction between QueryFilters and RecordFilters. Looking at the code, the two variants are similar enough to be handled by a simple Boolean within a single Type.
Option Explicit
Private Type Filter
Field As String
Operator As String
Criteria As Variant
AdditionalMethod As String
ExtractedFields As String
IsQueryFilter As Boolean
FilterString As String
End Type
Now we can use a single variable instead of keeping track of multiple variables to represent a single concept.
One way a Filter can be generated is by using a Range.
' Generates a Filter from a given Range of input data.
Private Function GenerateFilter(ByRef source As Range) As Filter
With GenerateFilter
.Field = CStr(source)
.Operator = CStr(source.Offset(0, 1))
.Criteria = source.Offset(0, 2)
.AdditionalMethod = CStr(source.Offset(0, 3))
.ExtractedFields = CStr(source.Offset(0, 4))
.IsQueryFilter = CBool(source.Offset(0, 5))
.FilterString = GenerateFilterString(GenerateFilter)
End With
End Function
Just as a single concept can be declared as a Type, a group of things can be declared as an Array (or a Collection, Dictionary, ...). This is useful, as it lets us decouple the logic from a specific Range.
' Generates a Filter for each row of a given Range of input data.
Private Function GenerateFilters(ByRef source As Range) As Filter()
Dim filters() As Filter
Dim filterRow As Range
Dim i As Long
ReDim filters(0 To source.Rows.Count)
i = 0
For Each filterRow In source.Rows
filters(i) = GenerateFilter(filterRow)
i = i + 1
Next
GenerateFilters = filters()
End Function
We now have a function that can return an Array of Filters from a given Range - and, as long as the columns are laid down in the right order, the code will work just fine with any Range.
With all of the data in a convenient package, it's easy enough to assemble the FilterString.
' Generates a FilterString for a given Filter.
Private Function GenerateFilterString(ByRef aFilter As Filter) As String
Dim temp As String
temp = " "
With aFilter
If .AdditionalMethod <> "" Then temp = temp & .AdditionalMethod & " "
If .IsQueryFilter Then
temp = temp & "[" & .Field & "]"
Else
temp = temp & .Field
End If
temp = temp & " " & .Operator & " "
If VarType(.Criteria) = vbString Then
temp = temp & "'" & .Criteria & "'"
Else
temp = temp & .Criteria
End If
End With
GenerateFilterString = temp
End Function
The data can then be merged to strings that can be used in queries regardless of how many Filters of either type are present in the specified Range.
' Merges the FilterStrings of Filters that have IsQueryString set as True.
Private Function MergeQueryFilterStrings(ByRef filters() As Filter) As String
Dim temp As String
Dim i As Long
temp = " WHERE"
For i = 0 To UBound(filters)
If filters(i).IsQueryFilter Then temp = temp & filters(i).FilterString
Next
MergeQueryFilterStrings = temp
End Function
' Merges the FilterStrings of Filters that have IsQueryString set as False.
Private Function MergeRecordFilterStrings(ByRef filters() As Filter) As String
Dim temp As String
Dim i As Long
For i = 0 To UBound(filters)
If Not filters(i).IsQueryFilter Then _
temp = temp & filters(i).FilterString
Next
MergeRecordFilterStrings = temp
End Function
' Merges the ExtractedFields of all Filters.
Private Function MergeExtractedFields(ByRef filters() As Filter) As String
Dim temp As String
Dim i As Long
temp = ""
For i = 0 To UBound(filters)
If filters(i).ExtractedFields <> "" Then _
temp = temp & "[" & filters(i).ExtractedFields & "],"
Next
If temp = "" Then
temp = "*"
Else
temp = Left(temp, Len(temp) - 1) ' Remove dangling comma.
End If
MergeExtractedFields = temp
End Function
With all of that done, we can finally plug a single Range in and get the generated strings out. It would be trivial to change filterRange or generate Filters from multiple Ranges.
Public Sub TestStringGeneration()
Dim filters() As Filter
Dim filterRange As Range
Set filterRange = Range("A1:A10")
filters = GenerateFilters(filterRange)
Debug.Print MergeQueryFilterStrings(filters)
Debug.Print MergeRecordFilterStrings(filters)
Debug.Print MergeExtractedFields(filters)
End Sub
TL;DR
Split code to reusable Functions & Subs
Favor sending data as arguments
Avoid hard-coding
Group data that represent a single concept
Use Arrays or other data structures over multiple variables
Reference
I am trying to add code (a subroutine call) to a procedure within Sheet1 by finding the line number of the procedure's statement within sheet1 in VBE then adding the code to the next line over. The following code attempts to achieve this.
' This will search for and modify the appropriate Node#button_Click() subroutine
With ActiveWorkbook.VBProject.VBComponents("Sheet1").CodeModule
ProcLineNum = .ProcStartLine("Node" & NumNodes & "Button" & "_Click", 0)
.InsertLines ProcLineNum + 1, "load_node_form(" & DQUOTE & "Node " & NumNodes & DQUOTE & ")"
End With
The entire subroutine is the following:
Public Sub Node_Button_Duplication()
'
'Com: Copies and pastes Node 1's button to the appropriate column
Dim shp As Shape
Dim code As String
Dim ProcLineNum As Long
Const DQUOTE = """"
' Copy Node 1 button and paste in appropriate location
ActiveSheet.Shapes("CommandButton1").Select
Selection.Copy
Cells(5, 10 + 7 * (NumNodes - 1) - 1).Select
ActiveSheet.Paste
Selection.ShapeRange.IncrementLeft 47.25
Selection.ShapeRange.IncrementTop -13.5
Set shp = ActiveSheet.Shapes(Selection.Name)
With shp.OLEFormat.Object
.Object.Caption = "Node" & Str(NumNodes)
.Name = "Node" & NumNodes & "Button"
End With
' This will search for and modify the appropriate Node#button_Click() subroutine
With ActiveWorkbook.VBProject.VBComponents("Sheet1").CodeModule
ProcLineNum = .ProcStartLine("Node" & NumNodes & "Button" & "_Click", 0)
.InsertLines ProcLineNum + 1, "load_node_form(" & DQUOTE & "Node " & NumNodes & DQUOTE & ")"
End With
End Sub
The subroutine will copy and paste a button ("CommandButton1"), rename it, then attempts to assign a subroutine call. The problem in finding the procedure is that once the new button is created, the "CommandButton#_Click() procedure doesn't show up in VBE until I go and select it from the editor, thus causing an error when my code tries to search for that procedure.
Is this what you are trying?
Option Explicit
Public Sub Node_Button_Duplication()
Dim shp As Shape
Dim code As String
Dim ProcLineNum As Long, NumNodes As Long
Const DQUOTE = """"
ActiveSheet.Shapes("CommandButton1").Select
Selection.Copy
Cells(5, 10 + 7 * (NumNodes - 1) - 1).Select
ActiveSheet.Paste
Selection.ShapeRange.IncrementLeft 47.25
Selection.ShapeRange.IncrementTop -13.5
Set shp = ActiveSheet.Shapes(Selection.Name)
With shp.OLEFormat.Object
.Object.Caption = "Node" & Str(NumNodes)
.Name = "Node" & NumNodes & "Button"
End With
With ActiveWorkbook.VBProject.VBComponents( _
ActiveWorkbook.Worksheets("Sheet1").CodeName).CodeModule
.InsertLines Line:=.CreateEventProc("Click", "Node" & NumNodes & "Button") + 1, _
String:=vbCrLf & _
"load_node_form(" & DQUOTE & "Node " & NumNodes & DQUOTE & ")"
End With
End Sub