Function to clear UserForms checkbox in VBA - vba

I am creating a program which has several UserForms.
At the end of the program I need to clear every Checkbox inside some UserForm. I have created a Function, but it cannot recognise which UserForm it should clear, can you help me there? Here is the code:
Function ClearUserForm(ByVal userf As String)
Dim contr As Control
For Each contr In userf.Controls
If TypeName(contr) = "CheckBox" Then
contr.Value = False
End If
Next
End Function
And I am calling the function like this, for example:
ClearUserForm ("UserForm2")
It seems not to recognize which UserForm it should act upon.

Shai Rado's advice is good and you should have a look at how he creates the object from its 'key'.
I only post this answer to check if you're aware that you could pass the object itself in the call. So your code could be like so:
Option Explicit
Public Sub RunMe()
ClearCBoxes UserForm1
End Sub
Private Sub ClearCBoxes(frm As MSForms.UserForm)
Dim ctrl As Control
For Each ctrl In frm.Controls
If TypeOf ctrl Is MSForms.ComboBox Then
ctrl.Value = False
End If
Next
End Sub

You don't need a Function (since you are not returning any arguments), in your case a Sub will do.
You need to qualify an Object to the User_Form selected by using:
Set objUserForm = UserForms.Add(userf)
Whole code
(Tested)
Option Explicit
Sub ClearUserForm(ByVal userf As String)
Dim contr As Control
Dim objUserForm As Object
Set objUserForm = UserForms.Add(userf)
For Each contr In objUserForm.Controls
If TypeName(contr) = "CheckBox" Then
contr.Value = False
End If
Next
' just to check that all checkboxes are cleared
objUserForm.Show
End Sub

Related

Code to account for all checkboxes in a userform?

I have code on a userform that contains several checkboxes and several DTPickers.
The code looks like so:
Private Sub CheckBox11_Click()
If CheckBox11.Value = True Then
DTPicker22.Enabled = True
Else
DTPicker22.Enabled = False
End If
End Sub
Private Sub CheckBox12_Click()
If CheckBox12.Value = True Then
DTPicker24.Enabled = True
Else
DTPicker24.Enabled = False
End If
End Sub
The Userform contains a lot of checkboxes that have clauses next to them. Upon their completion the DTPicker will enable entering the date of completion.
Whilst this does what I want, it only enables one DTPicker when the checkbox is ticked per private sub. There has to be some way to make this so I wouldn't need to create different private subs for every checkbox click event.
Could you also tell me where to put it, as in, what event?
A "control array" is the typical approach for something like this.
See:
http://www.siddharthrout.com/index.php/2018/01/15/vba-control-arrays/
eg:
Class module clsEvents
Option Explicit
'Handle events for a checkbox and a date control, associated with a worksheet cell
Private WithEvents m_CB As MSForms.CheckBox
Private WithEvents m_DP As DTPicker
Private m_dateCell As Range
'set up the controls and the cell
Public Sub Init(cb As MSForms.CheckBox, dp As DTPicker, rng As Range)
Set m_CB = cb
Set m_DP = dp
Set m_dateCell = rng
If rng.Value > 0 Then
cb.Value = True
m_DP.Value = rng.Value
Else
cb.Value = False
End If
m_DP.CustomFormat = "dd/MM/yyyy"
End Sub
Private Sub m_CB_Change()
m_DP.Enabled = (m_CB.Value = True)
End Sub
Private Sub m_DP_Change()
m_dateCell.Value = m_DP.Value 'update the cell
End Sub
Userform:
Option Explicit
Dim colObj As Collection 'needs to be a Global to stay in scope
Private Sub UserForm_Activate()
Dim obj As clsEvents, i As Long, ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Sheet1")
Set colObj = New Collection
'loop over controls and create a class object for each set
' 3 pairs of controls on my test form...
For i = 1 To 3
Set obj = New clsEvents
obj.Init Me.Controls("CheckBox" & i), _
Me.Controls("DTPicker" & i), _
ws.Cells(i, "B")
colObj.Add obj
Next i
End Sub
The first thing I'd recommend is following a proper naming convention. "CheckBox11" and "DTPciker1" are really vague and once you get further into your code, you'll forget which control is which. I would recommend naming them something that relates the two control together, like "firstDate" and "firstDateDTP". My alternate answer below uses this approach.
You could make a public function that enables the DTPicker based upon the checkbox's value.
Public Function EnableDTPicker(myPicker as String, enableBool as Boolean)
UserFormName.Controls(myPicker).Enabled = enableBool
End Function
Then, you can call the function in your CheckBox123_Click() subs like this:
Private Sub CheckBox123_Click()
EnableDTPicker("thePickerName", CheckBox123.Value)
End Sub
Alternatively, you could make a timer event that runs x number of seconds that just loops through the controls and performs the checks as needed. See this page on how to set up the timer. Using the code in the link shown, You could do something along the lines of:
'Put this in Workbook events
Private Sub Workbook_Open()
alertTime = Now + TimeValue("00:00:01")
Application.OnTime alertTime, "EventMacro"
UserForm1.Show
End Sub
'Put this in a Module
Public Sub EventMacro()
With UserForm1
For each ctrl in .Controls
If TypeName(ctrl) = "CheckBox" Then
'The code below assumes the naming convention outlined above is followed
.Controls(ctrl.Name & "DTP").Enabled = ctrl.Value
End If
Next ctrl
End With
alertTime = Now + TimeValue("00:00:01")
Application.OnTime alertTime, "EventMacro"
End Sub

Access VBA put a form element name as an argument

Let's see if anyone knows how to solve this problem:
I have a form with several elements: Some of them are textboxes called A1, A2, A3, A4...
Now, their AfterUpdate SubProcedure is extremely long but barely similar for each of them: A1_AfterUpdate, A2_AfterUpdate, A3_AfterUpdate...etc... are very similar but for the names of the textboxes they change.
My idea was to gather all that was equal in a subprocedure defined this way:
Private Sub Update(Box As String, Menu As Boolean)
If Menu=True{
Me!Box.Text = "This is the text that is going to change"
}
End Sub
So, the only thing I must do is to call it this way, for instance:
Update(A1, True)
But it doesn't seems to work. Any idea on how to reach this objective?
Add a class module - I've called it clsTextBoxEvents.
Add this code to the class:
Public WithEvents txt As Access.TextBox
Private Sub txt_AfterUpdate()
MsgBox txt.Name & " has been updated."
End Sub
In your form module add this code:
Public MyTextBoxes As New Collection
Private Sub Form_Open(Cancel As Integer)
Dim ctl As Control
Dim txtBoxEvent As clsTextBoxEvents
For Each ctl In Me.Controls
If TypeName(ctl) = "TextBox" Then
Set txtBoxEvent = New clsTextBoxEvents
Set txtBoxEvent.txt = ctl
txtBoxEvent.txt.AfterUpdate = "[Event Procedure]"
MyTextBoxes.Add txtBoxEvent
End If
Next ctl
End Sub
The MyTextBoxes declaration must be at the very top of the module.
This just adds the AfterUpdate event to all textboxes on the form. You'll probably want to refine that a bit to textboxes with specific text in the name, or controls that are in a specific frame on the form.
If you use a function instead of a sub:
Private Function UpdateCtl(Menu As Boolean)
If Menu Then
activecontrol = "This is the text that is going to change"
End If
End Sub
then you can call it directly from the control's AfterUpdate property: =UpdateCtl(True).
Simple and fast

VBA Input Value From Another UserFormB into TextBox From UserFormA

I have a userForm (mappingGuide) that allows user to pick a smartyTag from a list of more user-friendly names.
I have a second user-form (conditionalBuilder) that I would like to call this userForm upon double-clicking a text field so that a user can lookup which smartyTag to apply (in case they don't know).
So logic, is:
open conditionalBuilder
double-click Field text box
mappingGuide opens
pick a smartytag from listbox
fill smartytag value into field text-box in conditionalBuilder
unload mappingGuide
The issue I think I having with completing the requirement is that when I load the forms themselves I cannot find a way to set the text of the fieldName textbox of the loaded instance of conditionalBuilder (see last code block below). I've been searching around, but cannot figure it out.
Here is relevant code:
conditionalBuilder loads from Custom UI ribbon
Sub RunCode(ByVal Control As IRibbonControl)
Select Case Control.ID
Case Is = "mapper": LoadMappingGuide
Case Is = "conditional": LoadConditionalBuilder
End Select
End Sub
Sub LoadConditionalBuilder()
Dim conditionalForm As New conditionalBuilder
conditionalForm.Show False
End Sub
double-click event of fieldName then loads mappingGuide
Private Sub fieldName_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
Me.hide
Dim pickField As New mappingGuide
pickField.Show False
End Sub
smartTag listbox click event then attempts to place selection into fieldName (or selection if form not loaded)
Private Sub smartTagList_Click()
If smartTagList.ListIndex > -1 And smartTagList.Selected(smartTagList.ListIndex) Then
Dim smartyTag As String
smartyTag = smartTagList.List(smartTagList.ListIndex, 2)
If isUserFormLoaded(conditionalBuilder.Name) Then
'*** ---> below is my issue how to reference instance of form
conditionalBuilder.fieldName.Text = smartyTag
conditionalBuilder.Show
Else
Selection.Range.Text = smartyTag
End If
End If
Unload Me
End Sub
If there is a better set-up that would be great to know too. I have the forms separate because there's a couple of levels a user can create tags with.
This is how I would do it, a bit of overkill but in case of multiple forms it will be beneficial.
Module 1:
Option Explicit
Sub test()
frmMaster.Show False
End Sub
Form 1 : frmMaster:
Option Explicit
'/ Declare with events
Dim WithEvents frmCh As frmChild
Private Sub TextBox1_DblClick(ByVal cancel As MSForms.ReturnBoolean)
handleDoubleClick
End Sub
Sub handleDoubleClick()
If frmCh Is Nothing Then
Set frmCh = New frmChild
End If
frmCh.Show False
End Sub
'/ Handle the event
Private Sub frmCh_cClicked(cancel As Boolean)
Me.TextBox1.Text = frmCh.bChecked
End Sub
Form 2: frmChild:
Option Explicit
Event cClicked(cancel As Boolean)
Private m_bbChecked As Boolean
Public Property Get bChecked() As Boolean
bChecked = m_bbChecked
End Property
Public Property Let bChecked(ByVal bNewValue As Boolean)
m_bbChecked = bNewValue
End Property
Private Sub CheckBox1_Click()
Me.bChecked = Me.CheckBox1.Value
'/ Raise an event when something happens.
'/ Caller will handle it.
RaiseEvent cClicked(False)
End Sub
You can do this with a presenter class which controls userform instances and pass values between them. I mocked up something similar to give you an idea.
Presenter. This is a class module which creates the userforms, controls their scope, and catches the event thrown by the
ConditionalBuilder. It makes it super easy to pass values between
userforms.
Private WithEvents CB As ConditionalBuilder
Private MG As MappingGuide
Public Sub ShowCB()
Set CB = New ConditionalBuilder
CB.Show vbModal
End Sub
Private Sub CB_ShowMappingGuide()
Set MG = New MappingGuide
MG.Show vbModal
CB.UpdateTB1 Value:=MG.SmartTag
End Sub
ConditionalBuilder.
This has a simple function to update your textbox and also an event which raises actions in the presenter.
Public Event ShowMappingGuide()
Public Function UpdateTB1(Value As String)
TextBox1.Value = Value
End Function
Private Sub TextBox1_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
RaiseEvent ShowMappingGuide
End Sub
MappingGuide.
The Type and Property could be overkill since we just want one value from the mapping guide but it's still good practice.
Private Type TView
Tag As String
End Type
Private this As TView
Public Property Get SmartTag() As String
SmartTag = this.Tag
End Property
Private Sub UserForm_Initialize()
Tags.List = Array("a", "b", "c")
End Sub
Private Sub Tags_Click()
this.Tag = Tags.List(Tags.ListIndex, 0)
Me.Hide
End Sub
I have one final Standard Module which creates the Presenter. This is what you'd hook up to your ribbon.
Public Sub ShowProject()
With New Presenter
.ShowCB
End With
End Sub
Step 1 (double click text field)
Step 2 (selecting "b")
Step 3 (result)
I actually solved it by placing the below block inside the IF where I check for the form being loaded and I will leave open for better answers, if there are any.
Dim uForm As Object
For Each uForm In VBA.UserForms
If uForm.Name = conditionalBuilder.Name Then
uForm.fieldName.Text = smartyTag
uForm.Show
End If
Next

How make VBA run on clicking any checkbox in a userform?

I have a userform with a multiple frames, all filled with multiple checkboxes. I've named the checkboxes to their corresponding Excel cells. Now I want to make VBA run on clicking any of these checkboxes on run time. I know I can do this by creating a click-sub for every individual checkbox, but there must be a cleaner way to do this.
So far I've tried to put this code in the userform_Click and userform_Mousedown events, but they don't run when I click the checkboxes. Does anyone have an idea how to do this?
Dim iControl As Control
For Each iControl In Me.Controls
If TypeName(iControl) = "CheckBox" Then
If iControl.Value = True And Range(iControl.Name).Value = "" Then
Range(iControl.Name).Value = Format(Now, "dd.mm.yyyy")
ElseIf iControl.Value = True And Range(iControl.Name).Font.Color = vbWhite Then
Range(iControl.Name).Font.Color = vbBlack
ElseIf iControl.Value = False And Range(iControl.Name).Value <> "" Then
Range(iControl.Name).Font.Color = vbWhite
End If
End If
Next
As SilentRevolution said - you need an event to fire when you click the button. You're just after a single procedure to fire all check box click events.
So:
Create a class module called cls_ChkBox.
In the class module you'll add the Click event code:
Option Explicit
Private WithEvents chkBox As MSForms.CheckBox
Public Sub AssignClicks(ctrl As Control)
Set chkBox = ctrl
End Sub
Private Sub chkBox_Click()
ThisWorkbook.Worksheets("Sheet1").Range(chkBox.Name).Value = Format(Now, "dd.mm.yyyy")
End Sub
Now you just need to attach the chkBox_Click event to each check box on your form. In your user form add this code:
Option Explicit
Private colTickBoxes As Collection
Private Sub UserForm_Initialize()
Dim ChkBoxes As cls_ChkBox
Dim ctrl As Control
Set colTickBoxes = New Collection
For Each ctrl In Me.Controls
If TypeName(ctrl) = "CheckBox" Then
Set ChkBoxes = New cls_ChkBox
ChkBoxes.AssignClicks ctrl
colTickBoxes.Add ChkBoxes
End If
Next ctrl
End Sub
Each check box is given its own instance of the class, which is stored in the colTickBoxes collection.
Open the form and the cell in Sheet1 will update to show the date depending on the name of the check box.
You need an event to run code, if there is no event, the macro cannot start. I don't know if there is a single event that triggers for any button or checkbox that is clicked.
If the code you want to execute is the same every time except the control, you could write a private sub in the userform module which is called for each event, the private sub can take an input between the () for example.
Private Sub CheckBox1_Click()
Call ExecuteCode(Me.CheckBox1)
End Sub
Private Sub CheckBox2_Click()
Call ExecuteCode(Me.CheckBox2)
End Sub
Private Sub CheckBox3_Click()
Call ExecuteCode(Me.CheckBox2)
End Sub
Private Sub ExecuteCode(IControl As Control)
If TypeName(IControl) = "CheckBox" Then
If IControl.Value = True And Range(IControl.Name).Value = "" Then
Range(IControl.Name).Value = Format(Now, "dd.mm.yyyy")
ElseIf IControl.Value = True And Range(IControl.Name).Font.Color = vbWhite Then
Range(IControl.Name).Font.Color = vbBlack
ElseIf IControl.Value = False And Range(IControl.Name).Value <> "" Then
Range(IControl.Name).Font.Color = vbWhite
End If
End If
End Sub
I just learned this today and thought it might help? Most of the questions, and responses for that matter, to questions regarding checkboxes, listboxes, etc. seem to not distinguish between those forms are inserted directly on the worksheet or are imbedded in a UserForm.
This may work for a checkbox - it works for a ListBox on a UserForm. If you want code to run after a selection, the code you must write has to be in module of the UserForm. I had no clue how to access. Once you have inserted a UserForm in your VBE, add the ListBoxes or whatever to the UserForm. Right click on the UserForm in the upper left project window and select "view code." Here is where you'll place the code, in my case a "change event" such that after a selection is made from the ListBox, other code is automatically run. Her for example:
Sub lstBoxDates_Change()
Dim inputString1 As String
inputString1 = Format(UserForm1.lstBoxDates.Value, "mm-dd-yyyy")
Call EnterDates(inputString1)
Unload Me
End Sub
To explain: Again this code is in the UserForm Module. I named my ListBox,
lstBoxDates. In my main code that I call - EnterDates, I use the variable name = inputString1. The value or date that I have selected from the ListBox is captured from the UserForm1 by UserForm1.lstBoxDates.Value - and I format that to a date, otherwise you see just a number. This is for only one selection.
Because I Dim here, no need to Dim in your main code. The sub for the main code
needs to accept the variable you are passing to it:
Sub EnterDates(inputString1)
This is very generalized but maybe something will click so you can get what you are after. I hope so, for I've worked on this a full two days!!

Function and class to control a TextBox in word (VBA)

I have the following problem:
Private Sub TextBox1_Change()
Control (this) <<<<----- this is Empty
End Sub
Private Sub TextBox2_Change()
Control (this) <<<<----- this is Empty
End Sub
Private Sub TextBox3_Change()
Control (this) <<<<----- this is Empty
End Sub
Public Sub Control(asdf As MSForms.TextBox)
asdf.Font.Size = 11
asdf.Font.Bold = True
End Sub
The compiler says that 'this' is empty. What should I put there to recognize the TextBox?
Thx
When you use parentheses in your code you're evaluating Me.TextBox1, which ends up passing a String to Control. If you drop the parens it will work.
Private Sub TextBox1_Change()
Control Me.TextBox1 'without the parens
End Sub
Typically you don't use () when calling a Sub, unless you're using Call
If you end up having a lot of textboxes, it may be easier to customize the event handler in a custom class.
In a standard module, put a globally scoped collection variable
Public gcolTextboxes As Collection
Create a custom class module called CTbxEvents
Private WithEvents mtb As MSForms.TextBox
Public Property Get tb() As MSForms.TextBox
Set tb = mtb
End Property
Public Property Set tb(otb As MSForms.TextBox)
Set mtb = otb
End Property
Private Sub mtb_Change()
tb.Font.Size = 11
tb.Font.Bold = True
End Sub
Finally, in ThisDocument, load up all the textboxes when the document opens.
Private Sub Document_Open()
Dim f As Field
Dim clsTbxEvents As CTbxEvents
'Globally scoped collection to hold the classes
Set gcolTextboxes = New Collection
'Loop throught the fields
For Each f In Me.Fields
'Only fields that are activex controls
If f.Type = wdFieldOCX Then
'only activex controsl that are textboxes
If TypeOf f.OLEFormat.Object Is MSForms.TextBox Then
'create a new class, add the textbox, add to collection
Set clsTbxEvents = New CTbxEvents
Set clsTbxEvents.tb = f.OLEFormat.Object
gcolTextboxes.Add clsTbxEvents
End If
End If
Next f
End Sub
Now any textbox you add will use the same event handler and you don't keep having to call a separate sub. If you truly only have three textboxes, it's probably overkill.
The this keyword is specific to C++/C#/Java, the corresponding keyword for VB/VB.NET is Me