I have a table with the following values:
Now, I would like to call the Userform in column H based on the value in column G, but I can't work out how to call the Userform based on the cell value. The error occurs in line
form.Name = wsControls.Cells(loop2, 8).Value
Here is my code:
Sub Check_Scenarios()
Dim wsAbsatz As Worksheet
Dim wsControls As Worksheet
Dim wsData As Worksheet
Dim loop1 As Long
Dim loop2 As Long
Dim lngKW As Long
Dim form As UserForm
Set wsAbsatz = ThisWorkbook.Worksheets("Production")
Set wsData = ThisWorkbook.Worksheets("Data")
Set wsControls = ThisWorkbook.Worksheets("Controls")
lngKW = wsControls.Cells(1, 2).Value + 2
If lngKW = 3 Then
Exit Sub
End If
For loop1 = wsControls.Cells(10, 2).Value To wsControls.Cells(19, 2).Value Step 10
If wsData.Cells(loop1 + 3, lngKW).Value <> "" Then
MsgBox (wsData.Cells(loop1 + 3, lngKW).Value)
For loop2 = 2 To 16
If wsData.Cells(loop1 + 3, lngKW).Value = wsControls.Cells(loop2, 7).Value Then
form.Name = wsControls.Cells(loop2, 8).Value 'error occurs here
form.Show
End If
Next loop2
End If
Next loop1
End Sub
Project:
Many thanks for your help!
You are trying to assign a Name to a blueprint. These are two errors.
You have to initialize your blueprint as something. Like this:
Dim form As New UserForm
Then, most probably your UserForm does not have a property called Name. It is called Caption. Thus it is like this:
Sub TestMe()
Dim uf As New UserForm1 'judging from your screenshot
uf.Caption = "Testing"
uf.Show
End Sub
Disclaimer:
There is a better way to work with UserForms, not abusing the blueprint, although almost every VBA book shows this UserForm.Show method (in fact every single one I have read so far).
If you have the time and the OOP knowledge implement the ideas from here - or from my interpretation of the ideas. There was also a documentation article about it in StackOverflow, but it was deleted with the whole documentation idea.
You don't "call" a userform. You instantiate it, and then you Show it.
UserForm is the "base class" from which all userforms are derived. See there is inheritance in VBA, only not with custom classes.
So you have a UserForm2 class, a UserForm3 class, a UserForm4 class, and so on.
These classes need to be instantiated before they can be used.
Dim theForm As UserForm
Set theForm = New UserForm2
theForm.Show
Set theForm = New UserForm3
theForm.Show
'...
So what you need is a way to parameterize this Set theForm = New ????? part.
And you can't. Because whatever you're going to do, the contents of a cell is going to be a string, and there's no way you can get an instance of a UserForm3 out of a String that says "UserForm3".
Make a factory function that does the translation:
Public Function CreateForm(ByVal formName As String) As UserForm
Select Case formName
Case "UserForm1"
Set CreateForm = New UserForm1
Case "UserForm2"
Set CreateForm = New UserForm2
Case "UserForm3"
Set CreateForm = New UserForm3
'...
End Select
End Function
And then call that function to get your form object:
Set form = CreateForm(wsControls.Cells(loop2, 8).Value)
If Not form Is Nothing Then form.Show
Related
I'm going crazy trying to find a way for code to run when I click on ANY of the checkboxes on my sheet. I've seen multiple articles talking about making a class module, but I can't seem to get it to work.
I have code that will populate column B to match column C. Whatever I manually type into C10 will populate into B10, even if C10 is a formula: =D9. So, I can type TRUE into D10 and the formula in C10 will result in: TRUE and then the code populates B10 to say: TRUE. Awesome... the trick is to have a checkbox linked to D10. When I click the checkbox, D10 says TRUE and the formula in C10 says TRUE, but that is as far as it goes. The VBA code does not recognize the checkbox click. If I then click on the sheet (selection change), then the code will run, so I know I need a different event.
It is easy enough to change the event to "Checkbox1_Click()", but I want it to work for ANY checkbox I click. I'm not having ANY luck after days of searching and trying different things.
here is the code I'm running so far
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim i As Long
For i = 3 To 11
Range("B" & i).Value = Range("c" & i)
Next i
End Sub
Any help would be appreciated.
this works
' this goes into sheet code
Private Sub Worksheet_Activate()
activateCheckBoxes
End Sub
.
' put all this code in class a module and name the class module "ChkClass"
Option Explicit
Public WithEvents ChkBoxGroup As MSForms.CheckBox
Private Sub ChkBoxGroup_Change()
Debug.Print "ChkBoxGroup_Change"
End Sub
Private Sub ChkBoxGroup_Click()
Debug.Print "ChkBoxGroup_Click"; vbTab;
Debug.Print ChkBoxGroup.Caption; vbTab; ChkBoxGroup.Value
ChkBoxGroup.TopLeftCell.Offset(0, 2) = ChkBoxGroup.Value
End Sub
.
' this code goes into a module
Option Explicit
Dim CheckBoxes() As New ChkClass
Const numChkBoxes = 20
'
Sub doCheckBoxes()
makeCheckBoxes
activateCheckBoxes
End Sub
Sub makeCheckBoxes() ' creates a column of checkBoxes
Dim sht As Worksheet
Set sht = ActiveSheet
Dim i As Integer
For i = 1 To sht.Shapes.Count
' Debug.Print sht.Shapes(1).Properties
sht.Shapes(1).Delete
DoEvents
Next i
Dim xSize As Integer: xSize = 2 ' horizontal size (number of cells)
Dim ySize As Integer: ySize = 1 ' vertical size
Dim t As Range
Set t = sht.Range("b2").Resize(ySize, xSize)
For i = 1 To numChkBoxes
sht.Shapes.AddOLEObject ClassType:="Forms.CheckBox.1", Left:=t.Left, Top:=t.Top, Width:=t.Width - 2, Height:=t.Height
DoEvents
Set t = t.Offset(ySize)
Next i
End Sub
Sub activateCheckBoxes() ' assigns all checkBoxes on worksheet to ChkClass.ChkBoxGroup
Dim sht As Worksheet
Set sht = ActiveSheet
ReDim CheckBoxes(1 To 1)
Dim i As Integer
For i = 1 To sht.Shapes.Count
ReDim Preserve CheckBoxes(1 To i)
Set CheckBoxes(i).ChkBoxGroup = sht.Shapes(i).OLEFormat.Object.Object
Next i
End Sub
All you need is to let EVERY checkbox's _Click() event know that you want to run the Worksheet_SelectionChange event. To do so you need to add the following line into every _Click() sub:
Call Worksheet_SelectionChange(Range("a1"))
Please note that it is irrelevant what range is passed to the SelectionChange sub since you do not use the Target in your code.
I am using Ms-Access and I created a userform which has a number of Textboxes on it. The boxes are named: Box1, Box2, Box3 ...
I need to loop through all boxes, but I don't know which is the last one. To avoid looping through all userform controls I thought of trying the following:
For i =1 To 20
If Me.Controls("Box" & i).value = MyCondition Then
'do stuff
End If
Next i
This errors at Box6, which is the first box not found. Is there a way to capture this error and exit the loop when it happens.
I know I could use On Error but I 'd rather capture this specific instance with code instead.
Thanks,
George
A Controls collection is a simplified collection of controls (obviously) and share a same order as a placement order of controls.
First of all, even a creatable collection object lacks methods such as Exists or Contains , hence you need a function with error handling to checking/pulling widget from a collection.
Public Function ExistsWidget(ByVal Name As String) As Boolean
On Error Resume Next
ExistsWidget = Not Me.Controls(Name) Is Nothing
On Error GoTo 0
End Function
If you really doesnt like "ask forgiveness not permission" option you can pull entire ordered collection of your textboxes (and/or check existance by name in another loop with similar logic).
Public Function PullBoxes() As Collection
Dim Control As MSForms.Control
Set PullBoxes = New Collection
For Each Control In Me.Controls
If TypeOf Control Is MSForms.TextBox And _
Left(Control.Name, 3) = "Box" Then
Call PullBoxes.Add(Control)
End If
Next
End Function
Since names of widgets are unique - you can return a Dictionary from that function with (Control.Name, Control) pairs inside and able to check existance of widget by name properly w/o an error suppression.
There's a good guide to Dictionary if it's a new information for you.
Anyway, no matter what object you choose, if user (or code) is unable to create more of thoose textboxes - you can convert this Function above to a Static Property Get or just to a Property Get with Static collection inside, so you iterate over all controls only once (e.g. on UserForm_Initialize event)!
Public Property Get Boxes() As Collection
Static PreservedBoxes As Collection
'There's no loop, but call to PullBoxes to reduce duplicate code in answer
If PreservedBoxes Is Nothing Then _
Set PreservedBoxes = PullBoxes
Set Boxes = PreservedBoxes
End Property
After all, the last created TextBox with name Box* will be:
Public Function LastCreatedBox() As MSForms.TextBox
Dim Boxes As Collection
Set Boxes = PullBoxes
With Boxes
If .Count <> 0 Then _
Set LastCreatedBox = Boxes(.Count)
End With
End Function
I think that now things are clearer to you! Cheers!
Note: All code are definitely a bunch of methods/properties of your form, hence all stuff should be placed inside of form module.
Long story short - you cannot do what you want with VBA.
However, there is a good way to go around it - make a boolean formula, that checks whether the object exists, using the On Error. Thus, your code will not be spoiled with it.
Function ControlExists(ControlName As String, FormCheck As Form) As Boolean
Dim strTest As String
On Error Resume Next
strTest = FormCheck(ControlName).Name
ControlExists = (Err.Number = 0)
End Function
Taken from here:http://www.tek-tips.com/viewthread.cfm?qid=1029435
To see the whole code working, check it like this:
Option Explicit
Sub TestMe()
Dim i As Long
For i = 1 To 20
If fnBlnExists("Label" & i, UserForm1) Then
Debug.Print UserForm1.Controls(CStr("Label" & i)).Name & " EXISTS"
Else
Debug.Print "Does Not exist!"
End If
Next i
End Sub
Public Function fnBlnExists(ControlName As String, ByRef FormCheck As UserForm) As Boolean
Dim strTest As String
On Error Resume Next
strTest = FormCheck(ControlName).Name
fnBlnExists = (Err.Number = 0)
End Function
I would suggest testing the existence in another procedure per below: -
Private Sub Command1_Click()
Dim i As Long
i = 1
Do Until Not BoxExists(i)
If Me.Conrtols("Box" & i).Value = MyCondition Then
'Do stuff
End If
i = i + 1
Next
End Sub
Private Function BoxExists(ByVal LngID As Long) As Boolean
Dim Ctrl As Control
On Error GoTo ErrorHandle
Set Ctrl = Me.Controls("BoX" & LngID)
Set Ctrl = Nothing
BoxExists = True
Exit Function
ErrorHandle:
Err.Clear
End Function
In the above, BoxExists only returns true if the box does exists.
You have taken an incorrect approach here.
If you want to limit the loop, you can loop only in the section your controls reside e.g. Detail. You can use the ControlType property to limit controls to TextBox.
Dim ctl As Control
For Each ctl In Me.Detail.Controls
If ctl.ControlType = acTextBox Then
If ctl.Value = MyCondition Then
'do stuff
End If
End If
Next ctl
I believe the loop will be faster than checking if the control name exists through a helper function and an On Error Resume Next.
But this only a personal opinion.
I have been trying to write a macro that will dynamically fill a textbox on a new sheet will the value of a cell from another sheet.
I have managed to get it working using this:
Sub copyDetail()
' Define variables
Dim pre As Worksheet
Dim des As Worksheet
Set pre = Sheets("Presentation")
Set des = Sheets("Description")
Dim i As Integer
Dim lbl As String
' Scroll through labels and copy where boolean = 1
For i = 2 To 17
If des.Cells(i, 2) = 1 Then
lbl = des.Cells(i, 11)
Sheets("Presentation").Select
ActiveSheet.Shapes.Range(Array("TextBox 1")).Select
Selection.Text = lbl
Else
End If
Next i
End Sub
I basically want to be able to do exactly what this does but without using select all the time as this changes sheets and slows down my code (I have many other sub's to run alongside this one). I've tried things like defining the textbox using this:
Dim myLabel As Object
Set myLabel = pre.Shapes.Range(Array("TextBox 1"))
But then I get an "object doesn't support this property or method" error when I try and call:
myLabel.Text = lbl
You can set the text of a TextBox like so:
ActiveSheet.Shapes("TextBox 1").TextFrame.Characters.Text = "Hello world"
You can set-up a little helper Sub in a Module to make the code re-usable:
Public Sub SetTextBoxText(ws As Worksheet, strShapeName As String, strText As String)
Dim shp As Shape
On Error Resume Next
Set shp = ws.Shapes(strShapeName)
If Not shp Is Nothing Then
shp.TextFrame.Characters.Text = strText
Else
Debug.Print "Shape not found"
End If
End Sub
I have an excel form with a large number of checkboxes that are added at runtime. I would like to add a handler to each one of those checkboxes that will run when the value is changed. I know in other versions of Visual Basic I would use AddHandler, but that doesn't work in Excel VBA.
Following an example, I came up with the following code:
'This is in a class module called CheckboxHandler
Public WithEvents cb As MSForms.CheckBox
Private Sub cb_change()
MsgBox ("test")
end sub
And, in my userform, I have this code:
With CreateObject("Scripting.Dictionary")
.....'Unrelated code omitted
'Variable Checkboxes
'Add Handler to checkboxes
Dim colCBHandlers As Collection
Set colCBHandlers = New Collection
Dim objHandler As CheckboxHandler
Dim i As Long
Dim chkBox As MSForms.CheckBox
For i = 1 To .count - 1
Set chkBox = Me.Controls.Add("Forms.Checkbox.1", "Checkbox" & i)
chkBox.Caption = .Keys()(i)
chkBox.VALUE = False
chkBox.Top = (chkBox.Height + 10) * (i - 1) + 55
chkBox.Left = 725
Set objHandler = New CheckboxHandler
Set objHandler.cb = chkBox
colCBHandlers.Add objHandler
Next i
End With
colCBHandlers will go out of scope as soon as the sub which creates the checkboxes exits.
You need to declare that collection as a global (at the module level) so it doesn't get lost once it has been created and populated.
Ok so what I am trying to accomplish: I have one userform that asks how many new orders the user needs to process. I set the user input to a variable in this code (not sure if it even does anything).
Private Sub CommandButton1_Click()
UserForm2.Show
OrderNum.Text = NewOrders
'I changed the textbox name to OrderNum
End Sub
Then when UserForm2 pops up, I want to be able to input more data with more specific information about the orders. So if on Userform1 I entered in 3, I want to have to submit new data into UserForm2 3 different times. I tried using a For - Next loop (below) but it doesn't work. I'm not sure how (or if) I can store variables like that between Userforms.
Private Sub CommandButton1_Click()
Dim lRow As Long
Dim ws As Worksheet
Set ws = Worksheets("Core Info")
OrderNum.Text = NewOrders
lRow = ws.Cells(Rows.Count, 2).End(xlUp).Offset(1, 0).Row
For i = 1 To NewOrders
ws.Cells(lRow, 1).Value = TextBox1.Text
ws.Cells(lRow, 3).Value = TextBox2.Text
Next i
UserForm2.Hide
End Sub
The the second userform pops up as it should, but then nothing works after that. Can anyone tell me what I could do to fix this?
Note: I realize that those of the above start with CommandButton1 (default) but they are on different Userforms.
It will be helpful (and is good coding practice) to give your form controls more easily recognizable names, instead of the ambiguous CommandButton1 nomenclature. Revise CommandButton1's name on UserForm1 to Form1_SubmitButton. Then, use this code to handle the form submission.
Sub Form1_SubmitButton_Click()
' a textbox control named OrderNum on UserForm1 has captured the value
'Assign the value from the OrderNum textbox to a named variable in the worksheet
ActiveWorkbook.Names.Add "OrderNum", Me.OrderNum.Text
UserForm2.Show
End Sub
Then, in UserForm2 change the name of your command button to something like Form2_SubmitButton and use this code:
Sub Form2_SubmitButton_Click()
Dim orderNum as Long
orderNum = Replace(ActiveWorkbook.Names("OrderNum").Value,"=",vbNullString)
'the rest of your code goes here.
End Sub
So, what we have done above is to create a Name in the worksheet which contains the value you want to use on the several forms. You can always obtain this value by Replace(ActiveWorkbook.Names("OrderNum").Value,"=",vbNullString)
Alternatively, you could use a public variable in your code module.