I have a problem about dynamic controls. I create a dynamic userform with single frames depending by a number (rngprt in my example dependent by user input). No problem till now. Now I want to enable a textbox (in order to change its value) inside a specific frame when the relative checkbox is click. I used a Class Module (Classe1), but with my code I succeeded only to enable the last textbox of the n-frames when its checkbox is clicked (e.g. if I have 3 frames with 3 textboxes and 3 checkboxes, only the third checkbox it's able to enable the third textbox, the first and the second don't work).
Class Module: Classe1
Option Explicit
Public WithEvents cmbEvent1 As MSForms.CommandButton
Public WithEvents txbEvent1 As MSForms.TextBox
Public WithEvents frmEvent1 As MSForms.Frame
Public WithEvents ckbEvent1 As MSForms.CheckBox
Private Sub cmbEvent1_Click()
UserForm3.Hide
End Sub
Private Sub frmEvent1_Click()
End Sub
Public Sub txbEvent1_Change()
End Sub
Private Sub ckbEvent1_Click()
If UserForm3.Controls("CK" & xx).Value = True Then
UserForm3.Controls("TB" & xx).Enabled = True
End If
End Sub
Module: UserForm3
Option Explicit
Dim cmdB As New Classe1
Dim txtB As New Classe1
Dim chkB As New Classe1
Dim frm As New Classe1
Dim chkBColl As New Collection
Private Sub UserForm_Initialize()
Dim x As Long
Dim c As Variant
Dim cmdB1 As MSForms.CommandButton
Dim frm1 As MSForms.Frame
Dim txtB1 As MSForms.TextBox
Dim chkB1 As MSForms.CheckBox
Set cmdB1 = UserForm3.Controls.Add("Forms.CommandButton.1")
With cmdB1
.Name = "OKButton"
.Caption = "OK"
.Top = 40 * rngprt
.Left = 120
.Width = 40
.Height = 25
End With
Set cmdB.cmbEvent1 = cmdB1
For x = cel.Row To cel.Row + rngprt - 1 '**rngprt is a number from a Module1**
xx = x - cel.Row + 1 '**for progessive name of controls Dim xx as long into the Module1**
Set frm1 = UserForm3.Controls.Add("Forms.Frame.1")
frm1.Top = 40 * (xx - 1)
frm1.Left = 10
frm1.Width = 300
frm1.Height = 35
frm1.Name = "FR" & xx
With frm1.Controls
Set txtB1 = .Add("Forms.TextBox.1")
With txtB1
.Name = "TB" & xx
.Top = 10
.Left = 160
.Width = 30
.Height = 15
.TextAlign = fmTextAlignRight
.Enabled = False
.Value=50
End With
Set txtB.txbEvent1 = txtB1
Set chkB1 = .Add("Forms.CheckBox.1")
With chkB1
.Name = "CK" & xx
.Caption = "Part"
.Top = 10
.Left = 245
.Width = 45
.Height = 15
End With
Set chkB.ckbEvent1 = chkB1
'Here I added the code below
End With
Set frm.frmEvent1 = frm1
Next x
End Sub
I tried also to add this code below the chkB1 setting, but nothing.
Set chkB = New Classe1
Set chkB.ckbEvent1 = Me.Controls(chkB.txbEvent1)
chkBColl.Add chkB
Anybody have idea. Thank in advance to all for help.
Your code will not work.
You need to create an array of classe1.
Check this changes:
Classe1
Option Explicit
Public WithEvents cmbEvent1 As MSForms.CommandButton
Public WithEvents txbEvent1 As MSForms.TextBox
Public WithEvents frmEvent1 As MSForms.Frame
Public WithEvents ckbEvent1 As MSForms.CheckBox
Public xx As Integer
Private Sub cmbEvent1_Click()
UserForm3.Hide
End Sub
Private Sub frmEvent1_Click()
End Sub
Public Sub txbEvent1_Change()
End Sub
Private Sub ckbEvent1_Click()
If UserForm3.Controls("CK" & xx).value = True Then
UserForm3.Controls("TB" & xx).Enabled = True
End If
End Sub
UserForm3
Dim cmdB() As New Classe1
Dim txtB() As Classe1
Dim chkB() As Classe1
Dim frm() As New Classe1
Dim chkBColl As New Collection
Private Sub UserForm_Initialize()
Dim x As Long
Dim c As Variant
Dim cmdB1 As MSForms.CommandButton
Dim frm1 As MSForms.Frame
Dim txtB1 As MSForms.TextBox
Dim chkB1 As MSForms.CheckBox
ReDim Preserve cmdB(1)
Set cmdB1 = UserForm3.Controls.Add("Forms.CommandButton.1")
With cmdB1
.Name = "OKButton"
.Caption = "OK"
.Top = 40 * rngprt
.Left = 120
.Width = 40
.Height = 25
End With
Set cmdB(0).cmbEvent1 = cmdB1
For x = cel.Row To cel.Row + rngprt - 1 '**rngprt is a number from a Module1**
xx = x - cel.Row + 1 '**for progessive name of controls Dim xx as long into the Module1**
ReDim Preserve txtB(xx)
Set txtB(xx) = New Classe1
Set frm1 = UserForm3.Controls.Add("Forms.Frame.1")
frm1.Top = 40 * (xx - 1)
frm1.Left = 10
frm1.Width = 300
frm1.Height = 35
frm1.Name = "FR" & xx
With frm1.Controls
Set txtB1 = .Add("Forms.TextBox.1")
With txtB1
.Name = "TB" & xx
.Top = 10
.Left = 160
.Width = 30
.Height = 15
.TextAlign = fmTextAlignRight
.Enabled = False
.Value=50
End With
txtB(xx).xx = xx
Set txtB(xx).txbEvent1 = txtB1
ReDim Preserve chkB(xx + 1)
Set chkB(xx) = New Classe1
Set chkB1 = .Add("Forms.CheckBox.1")
With chkB1
.Name = "CK" & xx
.Caption = "Part"
.Top = 10
.Left = 245
.Width = 45
.Height = 15
End With
Set chkB(xx).ckbEvent1 = chkB1
chkB(xx).xx = xx
'Here I added the code below
End With
ReDim Preserve frm(xx)
Set frm(xx).frmEvent1 = frm1
Next x
End Sub
Related
I am trying to dynamically add images to a form in a grid like fashion. The images are from a folder for now then, I'm trying to add a click function to each dynamically created images on the form to execute something but I'm stuck on that part. I am successful in importing the images to a form and showing it in a grid like fashion with the code below I came up with.
Option Explicit
Private Sub Pic2_Click()
'not working :(
MsgBox "worked!!"
End Sub
Private Sub UserForm_Initialize()
Dim img As Object
Dim picSheet As Worksheet
'Add Dynamic Image and assign it to object 'Img'
Dim i, h, t As Integer
' Set picSheet = ThisWorkbook.Worksheets("Themes")
' Dim pictureList As Object
' Dim pics As Shapes
' Dim pic As Shape
' Set pics = ThisWorkbook.Worksheets("Themes").Shapes
Dim picPath As String
t = 10
h = 10
picPath = Dir(Environ("USERPROFILE") & "\Pictures\*pic*") 'getting pictures from picpath folder image names are ranged from "pic1.jpg" to "pic12.jpg"
Do While Not Blank(picPath) ' blank determines if a string is empty (boolean value)
i = i + 1
If i > 1 Then h = h + 90
Set img = Me.Controls.Add("Forms.image.1", picPath, True)
If i Mod 4 = 1 And i > 1 Then 'new row after first 4 images to create grid
t = t + 100
h = 10
End If
With img
.Picture = LoadPicture(Environ("USERPROFILE") & "\Pictures\" & picPath)
.PictureSizeMode = fmPictureSizeModeStretch
.Left = h
.Top = t
.name = Split(picPath, ".")(0)
Debug.Print "name is: " & .name
End With
picPath = Dir
Loop
End Sub
This is my entire solution and it worked for me. I changed a few things by using buttons instead that had the images embedded in them.
Option Explicit
Dim ColTB As Collection
Private Sub UserForm_Initialize()
Dim picPath, path As String
Dim i, h, t As Integer
Dim button As Object
Dim fso As New FileSystemObject
t = 10
h = 10
Set ColTB = New Collection
picPath = Dir(Environ("USERPROFILE") & "\OneDrive\Pictures\Themes\" & "*.jpg")
Do While Not Blank(picPath)
i = i + 1
If i > 1 Then h = h + 120
If i Mod 4 = 1 And i > 1 Then
t = t + 100
h = 10
End If
Set button = Me.Controls.Add("Forms.CommandButton.1", picPath, True)
With button
.Font.Bold = True
.Left = h
.Top = t
.Picture = LoadPicture(Environ("USERPROFILE") & "\OneDrive\Pictures\Themes\" & picPath)
.Height = 72
.Width = 100
End With
ColTB.Add EventObj(button)
picPath = Dir
Loop
End Sub
Function EventObj(obj As MSForms.CommandButton) As Class1
Dim o As New Class1
Set o.buttonClickEvent = obj
Set EventObj = o
End Function
Class1 module
Option Explicit
Public WithEvents buttonClickEvent As MSForms.CommandButton
Private Sub buttonClickEvent_Click()
Dim name, imgpath, sheetName As String
Dim answer
Dim ac As Worksheet
Set ac = ThisWorkbook.ActiveSheet
name = buttonClickEvent.name
answer = MsgBox("You want to apply the " & Split(name, ".")(0) & " theme now?", vbQuestion + vbYesNo + vbDefaultButton2, "Apply Theme")
If answer = vbYes Then
imgpath = Environ("USERPROFILE") & "\OneDrive\Pictures\Themes\" & name
ac.SetBackgroundPicture Filename:=imgpath
End If
End Sub
I have read a lot here so far and I have been helped a lot. Thanks for that! Now there is one thing I can't get further with.
I want to create a userform dynamically in Excel/VBA. This works so far, here an example how I create 5 TextBoxes with Names TextBox1-5.
For cTextBox = 1 To 5
Set edtBox_n = usrFrm.Controls.Add("Forms.textbox.1", "TextBox" & cTextBox)
With edtBox_n
.Top = nTop
.Left = 200
' .MultiLine = True
' .EnterKeyBehavior = True
.Height = 20
.Width = 150
.Text = .Name
'.Name = "Textbox" & cTextBox
End With
nTop = nTop + 20
Next cTextBox
But I cannot address the Textboxes with the command e.g.
Sub CommandButton1_Click()
test = usrFrm.TextBox1.Value
End Sub
Does anyone have an idea? Excel says that TextBox does not exist / the object does not exist.
Thanks a lot!
The way I handle this is by creating a class which will handle a lot of the creation of the Control and also the Events. e.g. Class ctrlTextBox
Option Explicit
Public WithEvents edtBox_n As MSForms.TextBox
Private UForm As UserForm
Public Sub Initialize(frm As UserForm, nme As String)
Set UForm = frm
Set editbox_n = UForm.Controls.Add(bstrProgID:="Forms.TextBox.1", Name:=nme)
End Sub
Private Sub edtBox_n_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
MsgBox edtBox_n.value
End Sub
The in my Userform I first declare a public Collection that is available throughout the form. I will store all of my dynamically created controls in here.
Option Explicit
Private cControls As Collection
Private Sub UserForm_Initialize()
Dim cTextBox As Long
Dim edtBox_n As ctrlTextBox
Dim nTop As Long
Set cControls = New Collection
For cTextBox = 1 To 5
Set edtBox_n = New ctrlTextBox
edtBox_n.Initialize frm:=Me, nme:="TextBox" & cTextBox
With edtBox_n.edtBox_n
.Top = nTop
.Left = 200
' .MultiLine = True
' .EnterKeyBehavior = True
.Height = 20
.Width = 150
.Text = .Name
End With
cControls.Add edtBox_n
nTop = nTop + 20
Next cTextBox
End Sub
I am trying to create some buttons dynamically, and assign code to them.
The following code works
Dim MyR As Range, MyB As OLEObject
Dim MyR_T As Long, MyR_L As Long
Set MyR = Cells(i + 1, 11) 'just an example - you get that from your own script
MyR_T = MyR.Top 'capture positions
MyR_L = MyR.Left '...
'create button
Set MyB = ActiveSheet.OLEObjects.Add(ClassType:="Forms.CommandButton.1", Link:=False, DisplayAsIcon:=False)
'set main button properties
With MyB
.Name = "MyPrecodedButton" 'important - code must exist ... see below
.Object.Caption = "MyCaption"
.Top = MyR_T
.Left = MyR_L
.Width = 50
.Height = 18
.Placement = xlMoveAndSize
.PrintObject = True 'or false as per your taste
End With
It creates the buttons within my loop. However, I want to assign something to the on click, so I use the following code
Dim MyR As Range, MyB As OLEObject
Dim MyR_T As Long, MyR_L As Long
Set MyR = Cells(i + 1, 11) 'just an example - you get that from your own script
MyR_T = MyR.Top 'capture positions
MyR_L = MyR.Left '...
'create button
Set MyB = ActiveSheet.OLEObjects.Add(ClassType:="Forms.CommandButton.1", Link:=False, DisplayAsIcon:=False)
'set main button properties
With MyB
.OnAction = "interpHere"
.Name = "MyPrecodedButton" 'important - code must exist ... see below
.Object.Caption = "MyCaption"
.Top = MyR_T
.Left = MyR_L
.Width = 50
.Height = 18
.Placement = xlMoveAndSize
.PrintObject = True 'or false as per your taste
End With
Sub interpHere()
MsgBox "hi"
End Sub
I have basically added .OnAction = "interpHere" but when I run it, I get an error, unable to set the onaction property.
Where am I going wrong?
try this code
Sub CreateButtons()
Dim btn As Button
ActiveSheet.Buttons.Delete
Dim t As Range
For i = 2 To 6 Step 2
Set t = ActiveSheet.Cells(i, 3)
Set btn = ActiveSheet.Buttons.Add(t.Left, t.Top, t.Width, t.Height)
With btn
.OnAction = "interpHere"
.Caption = "Btn " & i
.Name = "Btn" & i
End With
Next i
End Sub
Sub interpHere()
MsgBox "hi"
End Sub
UPDATE: Upon further research in the object browser... it appears that an MSForms.TextBox implements neither the .Name property or _Exit events - only _Change events. Is there a way to determine which specific TextBox generated a change event?
Alternately is it possible to use the MSForms.Control with this technique? The Control object implements the .Name property and _Exit event.
Can you listen for a TextBox exit event? Similarly to how a normal TextBox event would work? E.g.
Private Sub TextBox1_Exit(ByVal Cancel As MSForms.ReturnBoolean)
'Update a certain label based on the value of the TextBox
End Sub
The following doesn't catch the exit event. Moreover, while I can see the .Name property of the TextBox which generated the event for MyTextBox in the locals window, I cannot access that info to determine which label to act on.
This class technique was adapted from this post, and this post, which caught the change events.
Class clsTextBox:
Private WithEvents MyTextBox As MSForms.TextBox
Public Property Set Control(tb As MSForms.TextBox)
Set MyTextBox = tb
End Property
' Want to handle this event, but it's not caught when exiting the TextBox control
Private Sub MyTextBox_Exit(ByVal Cancel As MSForms.ReturnBoolean)
'Debug.Print me.Control.name
'Update a certain label based on the value of the TextBox
Stop
End Sub
' Catching this event but can't identify the control which triggered it
Private Sub MyTextBox_Change()
Debug.Print MyTextBox.Value ' <--- This prints the correct value
Debug.Print Me.Control.Name ' <--- ERROR here on any variation of Me or MyTextBox
'Update a certain label based on the value of the TextBox
Stop
End Sub
I have a series of dynamically created controls which need listeners. Code follows:
Option Explicit
Dim tbCollection As Collection
Private Sub UserForm_Initialize()
Dim ctrl As MSForms.Control
Dim obj As clsTextBox
Dim acftNumber As Long
Dim mPage As MSForms.MultiPage ' Control
Dim lbl_acftName As MSForms.Label
Dim lbl_currentHrs As MSForms.Label
Dim lbl_hrsDUE As MSForms.Label
Dim lbl_dateXFRIn As MSForms.Label
Dim lbl_dateXFROut As MSForms.Label
Dim lbl_hrsOnXFROut As MSForms.Label
Dim txb_currentHrs As MSForms.TextBox
Dim txb_hrsDUE As MSForms.TextBox
Dim txb_dateXFRIn As MSForms.TextBox
Dim txb_dateXFROut As MSForms.TextBox
Dim txb_hrsOnXFROut As MSForms.TextBox
Dim i As Double
Dim pgName As String
Dim acftName As String
' Correct for border size calculations bug in Excel 2016
Me.Height = 249.75
Me.Width = 350.25
acftNumber = Range("aircraft").Count 'Unknown value from 3 to 10
Set mPage = Me.multipage_file_week 'set Multipage variable
For i = 1 To acftNumber
'set name/title for new page
pgName = "pg_acft_" & i
acftName = Range("aircraft").Cells(i, 1).Value
'mPage.Pages.Add pgName, pgTitle
With mPage 'add acft tab
' add the aircraft page to the multipage
.Pages.Add pgName, acftName
' Aircraft Name Label
Set lbl_acftName = .Pages(i).Controls.Add("Forms.Label.1", "lbl_acftName_" & i, True)
With lbl_acftName
.Caption = acftName
.Font = "Arial"
.Font.Size = 12
.Font.Bold = True
.Left = 10
.Width = 55
.Top = 0
End With
' Current Hours Label and TextBox
Set lbl_currentHrs = .Pages(i).Controls.Add("Forms.Label.1", "lbl_currentHrs_" & i, True)
With lbl_currentHrs
.Caption = "Current Asset Hours:"
.TextAlign = fmTextAlignRight
.Font = "Arial"
.Font.Size = 10
.Font.Bold = False
.Left = 20
.Width = 120
.Top = 25
End With
Set txb_currentHrs = .Pages(i).Controls.Add("Forms.TextBox.1", "txb_currentHrs_" & i, True)
With txb_currentHrs
.Value = "16004.5"
.Text = "16004.5"
.Font = "Arial"
.Font.Size = 10
.Font.Bold = False
.Left = 150
.Width = 70
.Top = 25
End With
' Hours DUE Label and TextBox
Set lbl_hrsDUE = .Pages(i).Controls.Add("Forms.Label.1", "lbl_hrsDUE_" & i, True)
With lbl_hrsDUE
.Caption = "Hours next HMC DUE:"
.TextAlign = fmTextAlignRight
.Font = "Arial"
.Font.Size = 10
.Font.Bold = False
.Left = 20
.Width = 120
.Top = 50
End With
Set txb_hrsDUE = .Pages(i).Controls.Add("Forms.TextBox.1", "txb_hrsDUE_" & i, True)
With txb_hrsDUE
.Value = "16004.5"
.Text = "16004.5"
.Font = "Arial"
.Font.Size = 10
.Font.Bold = False
.Left = 150
.Width = 70
.Top = 50
End With
' Date XFR In Label and TextBox
Set lbl_dateXFRIn = .Pages(i).Controls.Add("Forms.Label.1", "lbl_dateXFRIn_" & i, True)
With lbl_dateXFRIn
.Caption = "Estimated arrival date:"
.TextAlign = fmTextAlignRight
.Font = "Arial"
.Font.Size = 10
.Font.Bold = False
.Left = 20
.Width = 120
.Top = 75
End With
Set txb_dateXFRIn = .Pages(i).Controls.Add("Forms.TextBox.1", "txb_hrsDUE_" & i, True)
With txb_dateXFRIn
.Value = "4/16/2019"
.Text = "4/16/2019"
.Font = "Arial"
.Font.Size = 10
.Font.Bold = False
.Left = 150
.Width = 70
.Top = 75
End With
' Date XFR Out Label and TextBox
Set lbl_dateXFROut = .Pages(i).Controls.Add("Forms.Label.1", "lbl_dateXFROut_" & i, True)
With lbl_dateXFROut
.Caption = "Estimated departure date:"
.TextAlign = fmTextAlignRight
.Font = "Arial"
.Font.Size = 10
.Font.Bold = False
.Left = 20
.Width = 120
.Top = 100
End With
Set txb_dateXFROut = .Pages(i).Controls.Add("Forms.TextBox.1", "txb_hrsDUE_" & i, True)
With txb_dateXFROut
.Value = "4/16/2019"
.Text = "4/16/2019"
.Font = "Arial"
.Font.Size = 10
.Font.Bold = False
.Left = 150
.Width = 70
.Top = 100
End With
' Hours on XFR Out Label and TextBox
Set lbl_hrsOnXFROut = .Pages(i).Controls.Add("Forms.Label.1", "lbl_hrsOnXFROut_" & i, True)
With lbl_hrsOnXFROut
.Caption = "Desired hours remaining on departure:"
.TextAlign = fmTextAlignLeft
.Font = "Arial"
.Font.Size = 10
.Font.Bold = False
.Left = 20
.Width = 170
.Top = 125
End With
Set txb_hrsOnXFROut = .Pages(i).Controls.Add("Forms.TextBox.1", "txb_hrsDUE_" & i, True)
With txb_hrsOnXFROut
.Value = "35"
.Text = "35"
.Font = "Arial"
.Font.Size = 10
.Font.Bold = False
.Left = 200
.Width = 35
.Top = 125
End With
End With
'Debug
Debug.Print Me.multipage_file_week.Pages(i).Name & ":"
For Each ctrl In Me.multipage_file_week.Pages(i).Controls
Debug.Print " - " & ctrl.Name
Next ctrl
Next i
mPage.Value = 0
Me.Caption = FILE_WEEK_FORM_TITLE
Set tbCollection = New Collection
For Each ctrl In Me.Controls
If TypeOf ctrl Is MSForms.TextBox Then
Set obj = New clsTextBox
Set obj.Control = ctrl
tbCollection.Add obj
End If
Next ctrl
Set obj = Nothing
End Sub
MSForms.Control defines the Enter and Exit events: if you need to handle TextBox.Change, then you need two WithEvents variables:
Private WithEvents TextBoxEvents As MSForms.TextBox
Private WithEvents ControlEvents As MSForms.Control
Public Property Set Control(ByVal tb As Object)
Set TextBoxEvents = tb
Set ControlEvents = tb
End Property
MSForms.Control is also the interface through which you get to access properties like Name, Top, Left, Visible, etc.
Tip: Never type event handler procedure signatures by hand. Select the source interface from the dropdown in the upper-left corner of the code pane, then select an event to handle from the upper-right dropdown; let the VBE generate the members with the correct signature. If you're in a handler procedure and the upper-left dropdown says "(general)", you're not in an event handler.
EDIT
While the above code compiles fine and the MSForms.Control interface does expose the events we're looking to handle...
?TypeOf tb Is MSForms.Control
True
?TypeOf tb Is MSForms.TextBox
True
...there's a bit of COM hackery going on behind the scenes; there's enough smokes & mirrors for VBA to successfully compile the above, but, basically, you're looking at a glitch in The Matrix (Rubberduck's resolver has similar "nope" issues with MSForms controls): there isn't any obvious way to get VBA to bind a dynamic control object to its MSForms.Control events.
With the help of the ConnectToConnectionPoint API you can catch the Event (Every Event, also Enter and Exit) for every control.
Have a look here: Trigger Enter field behaviour through class for a control
For Exit it will be
Public Sub myExit(ByVal Cancel As MSForms.ReturnBoolean)
Attribute myExit.VB_UserMemId = -2147384829
'code
End Sub
My question is related to this one. I created a class:
Public WithEvents btn As MSForms.CommandButton
Private Sub btn_Click()
MsgBox "Hello"
End Sub
The code below works well. I mean 10 buttons are displayed and after they are clicked, the message-box appears.
Dim collBtns As Collection
Public Sub UserForm_Initialize()
Dim btn As CommandButton
Dim btnH As cButtonHandler
Set collBtns = New Collection
For k = 1 To 10
Set btn = testform.Controls.Add("Forms.CommandButton.1", True)
With btn
.Caption = "Title"
.Left = 80
.Width = 80
.Top = 20 * k
Set btnH = New cButtonHandler
Set btnH.btn = btn
collBtns.Add btnH
End With
Next k
End Sub
However, I need to use this procedure inside another procedure. The code below does not work.
Dim collBtns As Collection
Public Sub UserForm_Initialize()
Call Click100
End Sub
Public Sub Click100()
Dim btn As CommandButton
Dim btnH As cButtonHandler
Set collBtns = New Collection
For k = 1 To 10
Set btn = testform.Controls.Add("Forms.CommandButton.1", True)
With btn
.Caption = "Title"
.Left = 80
.Width = 80
.Top = 20 * k
Set btnH = New cButtonHandler
Set btnH.btn = btn
collBtns.Add btnH
End With
Next k
End Sub