I have created a form where I am dynamically creating Textboxes and corresponding Comboboxes along with Combobox change event. Here is the class creating combobox event handler
Option Explicit
Public WithEvents cbx As MSforms.Combobox
Private avarSplit As Variant
Sub SetCombobox(ctl As MSforms.Combobox)
Set cbx = ctl
End Sub
Private Sub cbx_Change()
Dim i As Integer
If cbx.ListIndex > -1 Then
'MsgBox "You clicked on " & cbx.Name & vbLf & "The value is " & cbx.Value
avarSplit = Split(cbx.Name, "_")
'DecessionOnValue
End If
End Sub
And here is the code on the form which is dynamically creating textboxes and Comboboxes
Function AddTextBox(Frame1 As frame, numberOfColumns As Integer)
Dim counter As Integer
Dim i As Integer
Dim TxtBox As MSforms.TextBox
For counter = 1 To numberOfColumns
'Forms.CommandButton.1
Set TxtBox = Frame1.Controls.Add("Forms.TextBox.1")
TxtBox.Name = "tb_" + CStr(counter)
'Bouton.Caption = "Test"
TxtBox.Visible = True
i = Property.TextBoxDisable(TxtBox)
' Defining coordinates TextBox height is 18
If counter = 1 Then
TxtBox.Top = 23
Else
TxtBox.Top = (18 * counter) + 5 * counter
End If
TxtBox.Left = 50
Next counter
End Function
Function Combobox(Frame1 As frame, numberOfColumns As Integer)
Dim counter As Integer
Dim i As Integer
Dim CbBox As MSforms.Combobox
Dim cbx As ComboWithEvent
If pComboboxes Is Nothing Then Set pComboboxes = New Collection
For counter = 1 To numberOfColumns
'Forms.CommandButton.1
Set CbBox = Frame1.Controls.Add("Forms.ComboBox.1")
CbBox.Name = "cb_" + CStr(counter)
i = AddComboboxValues(CbBox)
' Defining coordinates TextBox height is 18
If counter = 1 Then
CbBox.Top = 23
Else
CbBox.Top = (18 * counter) + 5 * counter
End If
CbBox.Left = 150
Set cbx = New ComboWithEvent
cbx.SetCombobox CbBox
pComboboxes.Add cbx
Next counter
i = AddScrollBar(Frame1, counter)
End Function
Combobox event handler is working fine but my problem is that I dont know that how can I copy the text of textbox or enable disable the textbox according to the value selected in the dynamic combobox.
Thanks,
Jatin
you'll use this for example :
Me.Controls("ControlName").Visible = True or instead of Visible you can use enable, disable etc..
Related
I create a form dynamically and fill it with check boxes generated based on all column names of the Excel sheet it is launched from.
I add also a command button.
Here is the code put directly on the form:
Option Explicit
Dim cmdArray() As New Class1
Private Sub UserForm_Initialize()
Dim lastCol As Integer
Dim i As Integer
Dim chkBox As MSForms.CheckBox
Dim myButton As Control
lastCol = Worksheets(1).Cells(1, Columns.Count).End(xlToLeft).Column
Me.Height = 500
Me.Width = 600
For i = 1 To lastCol
Set chkBox = Me.Controls.Add("Forms.CheckBox.1", i)
chkBox.Caption = Worksheets(1).Cells(1, i).Value
' chkBox.Name = i
If i Mod 2 = 1 Then
chkBox.Left = 5
chkBox.Top = 5 + (i - 1) * 10
chkBox.Width = 200
Else
chkBox.Left = 250
chkBox.Top = 5 + (i - 2) * 10
chkBox.Width = 200
End If
Next i
i = 1
Set myButton = Me.Controls.Add("Forms.CommandButton.1", "MyButton", False)
With myButton
.Left = 500
.Top = chkBox.Top - 50
.Width = 50
.Caption = "Hide"
.Visible = True
End With
ReDim Preserve cmdArray(1 To i)
Set cmdArray(i).CmdEvents = myButton
Set myButton = Nothing
Me.ScrollBars = fmScrollBarsVertical
Me.ScrollHeight = chkBox.Top + 20
End Sub
The form is generated without issue: all check-boxes and the command button are set correctly.
I am then supposed to select which columns I want to hide from my Excel sheet, and therefore I tick the relevant checkbox. So far so good. Here is the code set is a Class :
Option Explicit
Public WithEvents CmdEvents As MSForms.CommandButton
Private Sub CmdEvents_Click()
Dim i As Integer
Dim cbx As MSForms.Control
Dim colNum As Integer
i = 0
For Each cbx In Me.UserForm1.Controls
If TypeName(cbx) = "CheckBox" Then
If cbx.Value = True Then
colNum = cbx.Name - i
Worksheets(1).Columns(colNum).EntireColumn.Delete
i = i + 1
End If
End If
Next ctrl
End Sub
When I click the button, it is supposed to trigger the hiding of the columns in the Excel sheet, however, I got the following error:
Compile error: Method or data member not found
This error is reported in the code in the Class module, highlighting the term .UserForm1 and if I remove this .UserForm1, then still the same error highlighting the .Controls.
I am not a great specialist of VBA, I manage usually to create simple codes and reusing samples I can find here and there, but this time, I run out of idea (and understanding), so thanks in advance for any help.
I have a UserForm with a MultipageControl (name Controller_MultiPage).
At runtime my code adds pages to the Multipage and creates a newListView on each page.
Every ListView has:
With newListView
.MultiSelect = False
.Width = Controller_MultiPage.Width - 10
.Height = Controller_MultiPage.Height - 20
.View = lvwReport
.HideColumnHeaders = False
.ColumnHeaders.Add Text:="Signal Name", Width:=.Width / 10 * 4
.ColumnHeaders.Add Text:="Type", Width:=.Width / 10
.ColumnHeaders.Add Text:="I/O", Width:=.Width / 10
.ColumnHeaders.Add Text:="Description", Width:=.Width / 10 * 4
.CheckBoxes = True
.FullRowSelect = True
End With
then I populate the newListView with data from an XML file:
For Each node In list
With node.Attributes
Set listItem = newListView.ListItems.Add(Text:=.getNamedItem("Name").Text)
listItem.ListSubItems.Add = .getNamedItem("Type").Text
listItem.ListSubItems.Add = IIf(.getNamedItem("Input").Text = "1", "IN", "OUT")
listItem.ListSubItems.Add = .getNamedItem("Description").Text
listItem.Checked = False
End With
Next
but the checkboxes do not show. I can see the space for them in front of the first column and by clicking that space the checkbox of that particular row then appears. What I also noticed is that if I change the property
listItem.Checked = True
the behavior described above does not change, and when I click the free space in front of the first column (checkboxes space) the chsckbox that then shows up is still unchecked.
Any idea?
The problem seems to be in the behavior of the MultiPage control.
What I noticed was that if I forced the checkboxes' status (checked or unchecked) from the code, using the MultiPage_Change event, then the checkboxes show up.
So what I did was to create a class that holds the status of all checkboxes of all listviews on a single page, instantiate the Class for each ListView and store everything into a Dictionary, using the newListView.Name as Key
Then when the user changes page, the MultiPage_Change event that fires resets all the values of the checkboxes according to the Dictionary stored values.
In the Listview_N_ItemChecked event some other code updates the status of the item stored in the Dictionary.
Kind of cumbersome but it works.
the class (updated):
' Class Name = ComponentsSignalsRecord
Option Explicit
Dim Name As String
' NOTE: Signals(0) will always be empty and status(0) will always be False
Dim Signals() As String
Dim Status() As Boolean
Dim Component As String
Property Let SetComponentName(argName As String)
Component = argName
End Property
Property Get GetComponentName() As String
GetComponentName = Component
End Property
Property Get getSignalName(argIndex) As String
If argIndex >= LBound(Signals) And argIndex <= UBound(Signals) Then
getSignalName = Signals(argIndex)
Else
getSignalName = vbNullString
End If
End Property
Property Get dumpAll() As String()
dumpAll = Signals
End Property
Property Get Count() As Long
Count = UBound(Signals)
End Property
Property Get getStatus(argName As String) As Integer
' returns: -1 = Not Found; 1 = True; 0 = False
getStatus = -1
Dim i As Integer
For i = 0 To UBound(Signals)
If argName = Signals(i) Then getStatus = IIf(Status(i) = True, 1, 0): Exit For
Next
End Property
Property Let setName(argName As String)
Name = argName
End Property
Property Get getName() As String
getName = Name
End Property
Public Sub UncheckAll()
Dim i As Integer
For i = 0 To UBound(Status)
Status(i) = False
Next
End Sub
Public Sub CheckAll()
Dim i As Integer
For i = 0 To UBound(Status)
Status(i) = True
Next
End Sub
Public Sub deleteSignal(argName As String)
Dim spoolSignals() As String
Dim spoolStatus() As Boolean
Dim i As Integer
spoolSignals = Signals
spoolStatus = Status
ReDim Signals(0)
ReDim Status(0)
For i = 1 To UBound(spoolSignals)
If argName <> spoolSignals(i) Then
ReDim Preserve Signals(UBound(Signals) + 1): Signals(UBound(Signals)) = spoolSignals(i)
ReDim Preserve Status(UBound(Status) + 1): Status(UBound(Status)) = spoolStatus(i)
End If
Next
End Sub
Public Sub addSignal(argName As String, argValue As Boolean)
Dim i As Integer
For i = 0 To UBound(Signals)
If argName = Signals(i) Then GoTo bye
Next
ReDim Preserve Signals(UBound(Signals) + 1)
ReDim Preserve Status(UBound(Status) + 1)
Signals(UBound(Signals)) = argName
Status(UBound(Status)) = argValue
bye:
End Sub
Public Sub setStatus(argName As String, argValue As Boolean)
Dim i As Integer
For i = 0 To UBound(Signals)
If argName = Signals(i) Then Status(i) = argValue: Exit For
Next
End Sub
Private Sub Class_Initialize()
ReDim Signals(0)
ReDim Status(0)
End Sub
The Form relevant code. Module level:
Dim myDict As New Dictionary ' the Dictionary
Dim ComponentsSignalsList As ComponentsSignalsRecord ' the Class
for each ListView created, may be one or more for every single MultiPage page :
Set ComponentsSignalsList = New ComponentsSignalsRecord
ComponentsSignalsList.setName = newListView.name
while populating the listview(s) in a loop for each single item added:
ComponentsSignalsList.addSignal List_Item.Text, List_Item.Checked
end of each loop, add the Class instance to the Dictionary:
myDict.Add ComponentsSignalsList.getName, ComponentsSignalsList
Now when changing Page in the MultiPage widget:
Private Sub Controller_MultiPage_Change()
If isLoading Then Exit Sub 'avoid errors and undue behavior while initializing the MultiPage widget
Dim locControl As Control
Dim controlType As String: controlType = "ListView"
With Controller_MultiPage
For Each locControl In .Pages(.value).Controls
If InStr(1, TypeName(locControl), controlType) > 0 Then
Call Check_CheckBoxes(locControl)
End If
Next
End With
End Sub
Private Sub Check_CheckBoxes(argListView As listView)
If argListView.CheckBoxes = False Then Exit Sub 'some ListViews don't have checkboxes
Dim myItem As ListItem
For Each myItem In argListView.ListItems
With myItem
.Checked = myDict.Item(argListView.name).getStatus(.Text)
End With
Next
End Sub
when ticking/unticking a checkbox (note the the ItemChecked event handler is defined in another Class Public WithEvents, where the handler calls this method passing both the ListView ID and the Item object) :
Public Sub ListViewsEvents_ItemCheck(argListView As listView, argItem As MSComctlLib.ListItem)
With argItem
myDict.Item((argListView .name).setStatus argName:=.Text, argValue:=.Checked
End With
End Sub
I just found the answer to the same problem that I also had and I feel so stupid. I had the first column of the Listview set to Width = 0... and thus the checkboxes would no longer show.
I gave it a width and everithing is back to normal...
I've created a code in VBA to collect data using a multi-page control. In each page, I've added checkboxes dynamically based on rows from the worksheet in Excel and, for each checkbox, there's a textbox and 2 command buttons, just like the image below:
Input Window:
The code to automatically add controls is:
Private Sub UserForm_Initialize()
fmat_disp.Value = 0
fmat_set.Value = 0
'---------------------------------------------------------------------------------------------
'Inspeção de Mecânica
Sheets("Mecânica").Activate
n_anom = Application.WorksheetFunction.CountA(Range("1:1")) - 1
AreasInspecao.mecanica.ScrollHeight = 10 + 18 * (n_anom)
For i = 1 To n_anom
'Selecionar anomalia
Set SelAnom = AreasInspecao.mecanica.Controls.Add("Forms.CheckBox.1", "sel_anom_" & i)
SelAnom.Caption = Worksheets("Mecânica").Cells(1, i + 1)
SelAnom.AutoSize = True
SelAnom.Height = 18
SelAnom.Left = 5
SelAnom.Top = 5 + (SelAnom.Height) * (i - 1)
SelAnom.Tag = i
Same goes to the textbox and plus/minus buttons, only changing the captions.
What I want is:
1) when CHECKBOX is CHECKED, respective TEXTBOX must show 1
2) when MINUS sign is PRESSED, respective TEXTBOX must decrement
3) when PLUS sign is PRESSED, respective TEXTBOX must increment
4) when "Finalizar Inspeção" is PRESSED, all data collected must be sent to Excel, filling a worksheet.
I simply don't know how to link each button/checkbox to your respective textbox without creating a subroutine for each one! I'll have ~500 subroutines....that's impossible to manage manually....
OK here's a rough outline for handling the click events on the checkboxes and buttons.
First two custom classes for capturing the clicks: each of these is very simple - all they do is call a method on the userform with the clicked control as an argument.
'clsCheck
Public WithEvents chk As MSForms.CheckBox
Private Sub chk_Click()
frmExample.HandleClick chk
End Sub
'clsButton
Public WithEvents btn As MSForms.CommandButton
Private Sub btn_Click()
frmExample.HandleClick btn
End Sub
Userform code - my form is named "frmExample".
Note the naming convention which allows groups of controls to be treated as a "unit".
Option Explicit
'These two global collections hold instances of the custom classes
Dim colCheckBoxes As Collection
Dim colButtons As Collection
Private Sub UserForm_Activate()
Const CON_HT As Long = 18
Dim x As Long, cbx As MSForms.CheckBox, t
Dim btn As MSForms.CommandButton, txt As MSForms.TextBox
Dim oCheck As clsCheck, oButton As clsButton
Set colCheckBoxes = New Collection
Set colButtons = New Collection
For x = 1 To 10
t = 5 + CON_HT * (x - 1)
Set cbx = Me.Controls.Add("Forms.CheckBox.1", "cbox_" & x)
cbx.Caption = "Checkbox" & x
cbx.Width = 80
cbx.Height = CON_HT
cbx.Left = 5
cbx.Top = t
colCheckBoxes.Add GetCheckHandler(cbx) '<< save in collection
Set btn = Me.Controls.Add("Forms.CommandButton.1", "btnplus_" & x)
btn.Caption = "+"
btn.Height = CON_HT
btn.Width = 20
btn.Left = 90
btn.Top = t
btn.Enabled = False '<<buttons start off disabled
colButtons.Add GetButtonHandler(btn) '<< save in collection
Set btn = Me.Controls.Add("Forms.CommandButton.1", "btnminus_" & x)
btn.Caption = "-"
btn.Height = CON_HT
btn.Width = 20
btn.Left = 130
btn.Top = t
btn.Enabled = False '<<buttons start off disabled
colButtons.Add GetButtonHandler(btn) '<< save in collection
'no events are captured for the textboxes...
Set txt = Me.Controls.Add("Forms.Textbox.1", "txt_" & x)
txt.Width = 30
txt.Height = CON_HT
txt.Left = 170
txt.Top = t
Next x
End Sub
'All "clicked" controls saved in instances of the custom classes
' get passed here. Handle based on control type/name
Public Sub HandleClick(ctrl As MSForms.Control)
Dim num
num = Split(ctrl.Name, "_")(1) 'which set of controls are we working with?
Dim txt As MSForms.TextBox
'get the matching text box...
Set txt = Me.Controls("txt_" & num)
If ctrl.Name Like "cbox_*" Then
If ctrl.Value Then txt.Value = 1
Me.Controls("btnplus_" & num).Enabled = ctrl.Value
Me.Controls("btnminus_" & num).Enabled = ctrl.Value
ElseIf ctrl.Name Like "btnplus_*" Then
txt.Value = txt.Value + 1
ElseIf ctrl.Name Like "btnminus_*" Then
txt.Value = txt.Value - 1
End If
End Sub
'couple of "factory" functions for the event-handling classes
Private Function GetCheckHandler(cb As MSForms.CheckBox)
Dim rv As New clsCheck
Set rv.chk = cb
Set GetCheckHandler = rv
End Function
Private Function GetButtonHandler(btn As MSForms.CommandButton)
Dim rv As New clsButton
Set rv.btn = btn
Set GetButtonHandler = rv
End Function
Sample file: https://www.dropbox.com/s/k74c08m0zkwn9l7/tmpFormEvents.xlsm?dl=0
I have a userform with multiple dependent Comboboxes. I would like to add the following code to 10 of the Comboboxes Change event. The Comboboxes to be coded are Numbered 11 to 20 (Combobox11, Combobox 12, etc) while the dependent Comboboxes are numbered 21 to 30.
I could copy and paste the code 10 times and then find and replace the relevant Combobox Nos.
Is there a way to use a loop through the Combo-Boxes to achieve this?
Any assistance would be most grateful.
Private Sub ComboBox11_Change()
Dim index As Integer
index = ComboBox11.ListIndex
ComboBox21.Clear
Select Case index
Case Is = 0
With ComboBox21
.RowSource = Range("SubCat1").Address(external:=True)
End With
Case Is = 1
With ComboBox21
.RowSource = Range("SubCat6").Address(external:=True)
End With
Case Is = 2
With ComboBox21
.RowSource = Range("SubCat7").Address(external:=True)
End With
Case Is = 3
With ComboBox21
.RowSource = Range("SubCat8").Address(external:=True)
End With
Case Is = 4
With ComboBox21
.RowSource = Range("SubCat9").Address(external:=True)
End With
'and several more case options
End Select
End Sub
You can use a Class Module, and a User_Init Sub to set each ComboBox control in the user form to this class.
In my code I used Main_Form as the name of the User_Form, modify the code according to your User_Form Name.
Add a Calls Module, and add this code below in Class 1:
Public WithEvents ComboBoxEvents As MSForms.ComboBox
' anytime a Change event occurs to any ComboBox, the Sub is triggered
Private Sub ComboBoxEvents_Change()
Dim ComboBox_Index As String
Dim index As Integer
With ComboBoxEvents
' read the index of the ComboBox, as long as the names remain ComboBox1, ComboBox2, ComboBox3, etc...
ComboBox_Index = Mid(.Name, 9)
' run this code if it's ComboBox 11 to 20
If ComboBox_Index >= 11 And ComboBox_Index <= 20 Then
index = .ListIndex
Select Case index
Case Is = 0
With Main_Form.Controls("ComboBox" & ComboBox_Index + 10)
.RowSource = Range("SubCat1").Address(external:=True)
End With
Case Is = 1
With Main_Form.Controls("ComboBox" & ComboBox_Index + 10)
.RowSource = Range("SubCat6").Address(external:=True)
End With
Case Is = 2
With Main_Form.Controls("ComboBox" & ComboBox_Index + 10)
.RowSource = Range("SubCat7").Address(external:=True)
End With
Case Is = 3
With Main_Form.Controls("ComboBox" & ComboBox_Index + 10)
.RowSource = Range("SubCat8").Address(external:=True)
End With
Case Is = 4
With Main_Form.Controls("ComboBox" & ComboBox_Index + 10)
.RowSource = Range("SubCat9").Address(external:=True)
End With
'and several more case options
End Select
End If
End With
End Sub
The code below goes in your User_Form_Init (in my code the name of the User_Form is Main-Form) :
Option Explicit
Dim ComboBoxes() As New Class1
Private Sub UserForm_Initialize()
Dim ComboBoxCounter As Integer, Obj As Control
For Each Obj In Me.Controls
If TypeOf Obj Is MSForms.ComboBox Then
ComboBoxCounter = ComboBoxCounter + 1
ReDim Preserve ComboBoxes(1 To ComboBoxCounter)
Set ComboBoxes(ComboBoxCounter).ComboBoxEvents = Obj
End If
Next Obj
Set Obj = Nothing
End Sub
the way is using Class
add a Class module and name it after "CmbBox" (you can choose whatever name but be consistent with it)
add the following code into the class code pane:
Option Explicit
Public WithEvents Cmb As MSForms.ComboBox
Private Sub Cmb_Change()
Dim index As Long
With Cmb
index = .ListIndex
With .Parent.Controls("ComboBox" & Mid(.Name, 9) + 10)
.Clear
Select Case index
Case 0
.RowSource = Range("SubCat1").Address(external:=True)
Case 1 To 4
.RowSource = Range("SubCat" & index + 5).Address(external:=True)
End Select
End With
End With
End Sub
Then switch to your userfom code pane and add this code:
Dim Cmbs(1 To 10) As New CmbBox '<--| this must be at the very top of your userform code pane
Sub Userform_Initialize()
Dim i As Long
With Me.Controls
For i = 11 To 20
Set Cmbs(i - 10).Cmb = .Item("ComboBox" & i)
Next i
End With
End Sub
and that's it
I'm creating a custom menu in Excel which consists of various sub-menus. It's for picking various machinery items and there's about 250 possible outcomes.
In any case, I've got the menu built and want it so that the .Caption is entered into the cell when the menu is used. I've put the .OnAction into the relevant buttons but, unfortunately, the .OnAction activates when the file is opened, not when the button is clicked. As such, all 250-odd .Captions are quickly entered into the same cell in quick succession.
Quick edit - the important bit is towards the bottom of the BuildMenus, where the .OnAction calls the function AddStuff. I know this is running on the Workbook_Activate which is why it runs straight away but everywhere else I've looked online does it the same way.
Private Sub Workbook_Activate()
BuildMenus
End Sub
Private Sub BuildMenus()
'Workbook_SheetBeforeRightClick(ByVal Sh As Object, ByVal Target As Range, Cancel As Boolean)
Dim AmountOfCats As Integer
Dim ThisIsMyCell As String
ThisIsMyCell = ActiveCell.Address
'this is where we would set the amount of categories. At the moment we'll have it as 15
AmountOfCats = 15
Dim cBut As CommandBarControl
Dim Cats As CommandBarControl
Dim SubCats As CommandBarControl
Dim MenuDesc As CommandBarButton
On Error Resume Next
With Application
.CommandBars("Cell").Controls("Pick Machinery/Plant...").Delete
End With
Set cBut = Application.CommandBars("Cell").Controls.Add(Type:=msoControlPopup, Temporary:=True)
cBut.Caption = "Pick Machinery/Plant.."
With cBut
.Caption = "Pick Machinery/Plant..."
.Style = msoButtonCaption
End With
Dim i As Integer
Dim j As Integer
Dim k As Integer
Dim SC As Integer
Dim AmountOfMenus As Integer
SC = 1
Dim MD As Integer
MD = 1
Dim MyCaption As String
For i = 0 To AmountOfCats - 1
Set Cats = cBut.Controls.Add(Type:=msoControlPopup, Temporary:=True)
Cats.Caption = Categories(i + 1)
Cats.Tag = i + 1
For j = 0 To (SubCatAmounts(i + 1) - 1)
Set SubCats = Cats.Controls.Add(Type:=msoControlPopup, Temporary:=True)
SubCats.Caption = SubCatArray(SC)
SubCats.Tag = j + 1
AmountOfMenus = MenuAmounts(SC)
For k = 0 To AmountOfMenus - 1
Set MenuDesc = SubCats.Controls.Add(Type:=msoControlButton)
With MenuDesc
.Caption = MenuArray(MD)
.Tag = MD
MyCaption = .Caption
.OnAction = AddStuff(MyCaption)
End With
MD = MD + 1
Next
SC = SC + 1
Next
Next
On Error GoTo 0
End Sub
Function AddStuff(Stuff As String)
Dim MyCell As String
MyCell = ActiveCell.Address
ActiveCell.Value = Stuff
End Function
OnAction expects a string value: instead you are calling your AddStuff sub while creating your menu...
.OnAction = "AddStuff """ & MyCaption & """"
is what you want (assuming I got my quotes right)
I was making a mistake with my AddStuff - I was calling it as a function when instead it should have been a macro (or a regular sub). A slight modification to Tim Williams' .OnAction code
MyButton.OnAction = "AddStuff(""" & MyButton.Caption & """)"
did the trick.