I am trying to write a macro that will scrape data from a huge set of already existing excel files. These files have the same format just with different info. I am able to pull any data written in a cell, even if it's in the form of a drop down. However, since checkboxes are not tied to a cell range, I'm not sure how to interact with them in VBA.
I've tried using
? Activesheet.checkbox1.value
? Activesheet.checkbox(1).value
but it's not giving me anything.
Is there a way to reference these? I'm guessing they were placed using "developer tab -> insert objects" so there is no userform area in the project tree in VBA
Thanks,
Try this code to find checkboxes on the worksheet:
Sub InspectCheckBoxes()
Dim sh As Shape
For Each sh In ActiveSheet.Shapes
Select Case sh.Type ' see MsoShapeType enumeration
Case msoFormControl
If sh.FormControlType = xlCheckBox Then ' see XlFormControl enumeration
Debug.Print "Found CheckBox as FormControl named '" & sh.Name & "' at left "; sh.Left & "; top " & sh.Top
End If
Case msoOLEControlObject
If InStr(1, sh.OLEFormat.progID, "CheckBox", vbTextCompare) > 0 Then
Debug.Print "Found CheckBox as OLEControlObject named '" & sh.Name & "' at left "; sh.Left & "; top " & sh.Top
End If
End Select
Next
End Sub
Prints:
Found CheckBox as FormControl named 'Check Box 2' at left 207; top 78
Found CheckBox as OLEControlObject named 'CheckBox1' at left 244,5; top 142,5
You didn't specify which type of checkboxes...
Dim cbx As Excel.CheckBox
Dim msfCbx As msforms.CheckBox
Dim ole As OLEObject
Debug.Print "Forms type checkboxes"
For Each cbx In ActiveSheet.CheckBoxes
Debug.Print cbx.Value, cbx.Name, cbx.Caption, cbx.TopLeftCell.Address
Next
Debug.Print "ActiveX checkboxes"
For Each ole In ActiveSheet.OLEObjects
If ole.progID = "Forms.CheckBox.1" Then
Set msfCbx = ole.Object
Debug.Print msfCbx.Value, ole.Name, msfCbx.Caption, ole.TopLeftCell.Address
End If
Next
Both previous answers here assumed your checkboxes were on worksheets. Both checkbox types include a 'linked-cell' property which changes a cell's value.
Checkboxes on a userform are can't be directly linked to cells, as you say. Code in a checkbox event can perform some action, or other code might read the checkbox state later when required,
Unless I'm missing something the only way you might retrieve any useful data, just by reading checkbox values on a loaded userform, is if you can deduce some linked cell range simply from the checkbox's name. It would also seem likely code in the userform's initialize event, or perhaps a 'get data' type button, reads cells and updates checkboxes at runtime.
If that's the case try this - add a button to your form
Private Sub CommandButton1_Click()
Dim ctl As MSForms.Control
For Each ctl In Me.Controls
If TypeName(ctl) = "CheckBox" Then
Debug.Print ctl.Value, ctl.Name
End If
Next
End Sub
Related
I have the following in my userform:
CheckBox11 CheckBox12 CheckBox13 CheckBox14 CheckBox15
CheckBox21 CheckBox22 CheckBox23 CheckBox24 CheckBox25
Also, in my excel I have following named ranges:
range11 range12 range13 range14 range15
range21 range22 range23 range24 range25
I want to loop through each of the checkboxes, and in case of True, do something with the range, Say copy-paste the corresponding ranges to another location.
I have a nested For loop, i = 1 to 2
and j = 1 to 5
and then inside another long variable nm = i*10+j
now I want to refer to the CheckBox and Range with nm.
Any other alternative is also welcome.
Thanks in Advance.
Maybe something like this ?
Sub Test()
For Each objControl In ActiveSheet.OLEObjects
If TypeName(objControl.Object) = "CheckBox" And objControl.Object = True Then
rngName = Replace(objControl.Name, "CheckBox", "range")
Range(rngName).Select
MsgBox rngName & " selected"
End If
Next
End Sub
The sub will select the named range at each selected CheckBox (True value).
But that's if the checkbox is an ActiveX and the named range is within the same sheet where the checkboxes are.
Anyway, you can just change the Range(rngName).Select .... MsgBox rngName & " selected" line to whatever process you want to do with the corresponding named range.
Below if the checkbox is in a Userform:
Private Sub CommandButton1_Click()
For Each ctrl In Me.Controls
If TypeName(ctrl) = "CheckBox" And ctrl = True Then
rngName = Replace(ctrl.Name, "CheckBox", "range")
Range(rngName).Select
MsgBox rngName & " selected"
End If
Next
End Sub
I am building an Excel 2016 Userform using VBA and need to collect the row and column of the cell from which the form is opened. I open the form on a cell double click with Worksheet_BeforeDoubleClick and then initialize the Userform with UserForm_Initialize(). I would like to pass the Target of the double click event to UserForm_Initialize() but am not sure how to. This forum thread addresses this issue, but the provided solutions did not work for me.
Here is my Worksheet_BeforeDoubleClick:
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
Column = Target.Column
Row = Target.Row
'Find the last non-blank cell in column B(2)
lRow = Cells(Rows.Count, 2).End(xlDown).Row
'Find the last non-blank cell in row 2
lCol = Cells(2, Columns.Count).End(xlToRight).Column
If Not Intersect(Target, Range(Cells(3, 3), Cells(lRow, lCol))) Is Nothing Then
Cancel = True
EdgeEntryForm.Show
End If
End Sub
And my UserForm_Initialize():
Private Sub UserForm_Initialize()
Dim Column As Long, Row As Long 'I would like to fill these with the Target values
MsgBox ("Row is " & Row & " Column is " & Column)
'Description.Caption = "Fill out this form to define a network edge from " & Cells(2, Row).Value & " to " & Cells(Column, 2).Value
End Sub
As suggested in my comments, one way would be to just use the ActiveCell and assign that to a variable.
Alternatively, if you do want to pass it as a variable, you can do it with a bit of a workaround, by having a global variable to temporarly hold that information:
In your worksheet code:
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
'.....
With UserForm1
Set .rngTarget = Target
.Show
End With
'.....
End Sub
In your userform:
Public rngTarget As Range
Private Sub UserForm_Activate()
'....
If Not rngTarget Is Nothing Then
MsgBox ("Row is " & rngTarget.Row & " Column is " & rngTarget.Column)
Else
MsgBox "something went wrong with assigning rngTarget variable"
End If
'....
End Sub
EDIT: I was trying initially to propose something similar to #MathieuGuindon's answer, but was failing due to my limited knowledge on the difference between initialise and activate (thanks Mathieu).
I've updated the answer to make use of the global variable at userform level, rather than use one from a module.
The form is shown modally, so ActiveCell isn't going to change on you, and should be safe to use in the form's code-behind.
The problem with that, is that you've now tied the form to ActiveSheet/ActiveCell, and now in order to test anything you need to Select or Activate a cell.
If the form code only needs to know about the cell's Address, then it shouldn't be given a Range (give it a Range and it can access any cell in any sheet in any workbook in the Application instance) - that's the principle of least knowledge at play. But this is obviously example code, so let's go with a Range:
Option Explicit
Private internalWorkingCell As Range
Public Property Get WorkingCell() As Range
Set WorkingCell = internalWorkingCell
End Property
Public Property Set WorkingCell(ByVal value As Range)
Set internalWorkingCell = value
End Property
Now your form code can use WorkingCell or internalWorkingCell to do its thing, and no global variable needs to float around;
With New UserForm1 ' Initialize handler runs here
Set .WorkingCell = Target
.Show ' Activate handler runs here
End With
The WorkingCell belongs to the form - it has no business being in global scope.
Careful with the Initialize handler in forms - especially when you use its default instance (i.e. when you don't New it up): you don't control when that handler runs, the VBA runtime does; UserForm_Initialize will run the first time the form instance is referenced (in your case, immediately before the .Show call), and then never again unless the instance is destroyed (clicking the red X button would do that).
A MsgBox call in the Initialize handler will run before the form is shown; you probably want to move that code to the Activate handler before it causes problems.
I have several sheets of which house numerous checkboxes,of which when ticked, the value of cells next to the checkboxes will be copied into a textbox.Currently using ActiveX Textbox, below codes work just fine
Sub checkBoxHandler()
ActiveSheet.TextBox1.Text = ActiveSheet.TextBox1.Text & " " & ActiveSheet.Shapes(Application.Caller).TopLeftCell.Offset(1, 1).Value
End Sub
But now i would like to use Userform Textbox since it can float on the workbook. Modification to the codes such as below does not do the trick (the value of cells next to the ticked checkboxes are not copied into the Userform Textbox).
Sub ShowTextBox()
UserForm1.TextBox1.Text = UserForm1.TextBox1.Text & vbLf & ActiveSheet.Shapes(Application.Caller).TopLeftCell.Offset(1, 1).Value
UserForm1.Show vbModeless
End Sub
Does anybody know how to make it works?
I've done a similar project. I think userform does not support that kind of method. so i did this:
i put activex textbox on each of the sheets and named it accordingly (Textbox1,Textbox2..), and set the Visibility property to False
i assign macro (below) to a shape to pass the value of activex textbox to the Userform on every sheet :
Sub hocmsummary()
UserForm1.TextBox1.Text = UserForm1.TextBox1.Text & ActiveSheet.TextBox2.Text
End Sub
Remember to change the Textbox name accordingly.
I am new to macros so I'm not sure this is possible in VBA.
I am trying to create a document where is composed with many mini tables made of 4 rows.
One row is the title which have a checkbox and will always be shown and three rows below where contains data that I only what to see when I select the relevant checkbox.
This document will have many mini tables hence many check boxes and I was wondering if there is a generic selector for checkboxes where I can apply the same macro.
I have seen the following macro, but this will apply only to one check box and I was wondering if there was a way to apply one for all checkboxes saying that if checkbox in row 4 is selected then show row 5,6 and 7. If checkbox in row 8 is selected then show rows 9,10,and 11 and so on....
Private Sub CheckBoxRow4_Click()
Rows("5:6:7").Hidden = CheckBoxRow4.Value
End Sub
See screenshot for a better idea.
It would also be appreciated if you could indicate how can I get those three rows below hidden by default when opening the document.
I am using Excel 2011 for Mac if that makes any difference.
Thank you in advance.
I'm sure there will be several approaches to this. My first thought goes to adding checkboxes, linking them all to a single macro. When activated, you have to do several things:
find out who is calling the sub (which checkbox);
find out where that specific checkbox is located (which row);
hide / unhide the rows below it.
1:
The name of the checkbox is easy. Application Caller will give you that.
2:
Location is the real problem here. I don't see a simple solution here, other then giving the checkboxes such specific names, that it is clear which row it is in. If you add a checkbox, you can give the name in the 'named range' inputfield. If you give it names that will specify the rows it must hide, it is even better. So something like:
HIDE_4_7 would indicate the checkbox must hide / unhide rows 4 to 7.
3:
Hiding the rows is now easy.
total solution:
Sub HideRows()
Dim cbName As String
Dim cbValue As Boolean
Dim s() As String
Dim firstRow As Long
Dim lastRow As Long
On Error Resume Next
cbName = Application.Caller
If Err.Number <> 0 Then Exit Sub 'sub is not called from an application object
cbValue = (ActiveSheet.CheckBoxes(cbName) = xlOn)
If Err.Number <> 0 Then Exit Sub 'sub is not called from a checkbox
On Error GoTo 0
s = Split(cbName, "_")
If s(LBound(s)) <> "HIDE" Then Exit Sub 'name of the shape is not valid
firstRow = Val(s(LBound(s) + 1))
lastRow = Val(s(LBound(s) + 2))
Sheets(1).Rows(firstRow & ":" & lastRow).Hidden = Not cbValue
End Sub
You would have to call the checkboxes HIDE_*firstrow*_*lastrow*, and link them to this sub. That works on my side.
EDIT
To hide all rows on opening, you could use the Workbook_Open sub (in the workbook code storage thingy). Something like this:
Private Sub Workbook_Open()
Dim shp As Shape
Dim s() As String
Dim firstRow As Long
Dim lastRow As Long
Dim cbValue As Boolean
For Each shp In Sheets(1).Shapes
Debug.Print shp.Name
s = Split(shp.Name, "_")
If s(LBound(s)) <> "HIDE" Then GoTo nextShp
'set checkbox off:
Sheets(1).CheckBoxes(shp.Name) = xlOff
firstRow = Val(s(LBound(s) + 1))
lastRow = Val(s(LBound(s) + 2))
Sheets(1).Rows(firstRow & ":" & lastRow).Hidden = True
nextShp:
Next shp
End Sub
Hello so what i want to do is make this code work for all Check Box's 1-50 I want the code to only effect the box that is clicked.
Private Sub CheckBox1_Click()
If MsgBox("Do you want to lock this box?", vbYesNo, "Warning") = vbYes Then
ActiveSheet.CheckBox2.Enabled = False
Else
End If
End Sub
I see several options (none of which are pretty since this is VBA).
Option 1: generate the code for all of your check boxes. This is probably the most maintainable. You would first choose reasonable names for all your check boxes (you can assign them by selecting them in Excel and renaming in the top left corner, or run code which will do this for you if you already have a lot of check boxes. This may be useful).
You can then generate the code and have each one of your subprocedues as follows:
'example code for one checkbox
Private Sub chkBox_1_Click()
Call lockMeUp(Sheet1.chkBox_1.Object)
End Sub
After you're done with all your code for each checkbox, you could have your lockMeUp subprocedure as follows:
Sub lockMeUp(chkBox as Object)
If MsgBox("Do you want to lock this box?", vbYesNo, "Warning") = vbYes Then
chkBox.Enabled = False
End If
End Sub
Option 2: Keep track of all your checked/unchecked statuses through either an Array or a "Settings" hidden sheet, and watch out for that triggered event. You could fire off based off of a sheet's Changed event, and match the row number to your CheckBox number so that you can go off of the Target's row number.
Other options I can think of become more convoluted... I'd be interested to see what other suggestions people have. Thanks!
EDIT You can use some code to refer to a single function as in my example, in conjunction with brettdj's example to get your optimal solution. Bam!
The easy way is to write a class module that will apply one code routine to a collection of Checkboxes
Assuming yu want to run this on all ActiveX checkboxes on the ActiveSheet, then borrowing heavily from Bob Phillip's code from VBAX
Insert a Class Module named clsActiveXEvents
Option Explicit
Public WithEvents mCheckboxes As MSForms.CheckBox
Private Sub mCheckboxes_Click()
mCheckboxes.Enabled = (MsgBox("Do you want to lock this box?", vbYesNo, "Warning") = vbNo)
End Sub
In a normal module use this code
Dim mcolEvents As Collection
Sub Test()
Dim cCBEvents As clsActiveXEvents
Dim shp As Shape
Set mcolEvents = New Collection
For Each shp In ActiveSheet.Shapes
If shp.Type = msoOLEControlObject Then
If TypeName(shp.OLEFormat.Object.Object) = "CheckBox" Then
Set cCBEvents = New clsActiveXEvents
Set cCBEvents.mCheckboxes = shp.OLEFormat.Object.Object
mcolEvents.Add cCBEvents
End If
End If
Next
End Sub
In case you do not know, all Form Controls are treated as Shapes in a Worksheet.
I have a solution that you need to create a new Module, copy-paste in code below and then from Immediate window to the same module. With some assumptions:
All Check Box Objects are named "Check Box #" where # is a number
No macro named ResetCheckBoxes() in any other modules of the workbook
No macro named CheckBox#_Click() in any other modules of the workbook
Run this ResetCheckBoxes once to enable check boxes and Assign a macro to it for you, with relevant generated codes in the immediate window (you might want to put a pause in the loop every 25 check boxes as line buffer in it are limited).
Sub ResetCheckBoxes()
Dim oWS As Worksheet, oSh As Shape, sTmp As String
Set oWS = ThisWorkbook.ActiveSheet
For Each oSh In oWS.Shapes
With oSh
If .Type = msoFormControl Then
If InStr(1, .Name, "Check Box", vbTextCompare) = 1 Then
.ControlFormat.Enabled = True
sTmp = "CheckBox" & Replace(oSh.Name, "Check Box ", "") & "_Click"
.OnAction = sTmp
Debug.Print "Sub " & sTmp & "()"
Debug.Print vbTab & "ActiveSheet.Shapes(""" & .Name & """).ControlFormat.Enabled = False"
Debug.Print "End Sub" & vbCrLf
End If
End If
End With
Next
End Sub
Example Immediate window output (2 test check boxes):
Happy New Year mate!
To build on the solution offered by #brettdj, since he is specifying ActiveX Controls, I would suggest the following in the Standard Module:
Dim mcolEvents As Collection
Sub Test()
Dim cCBEvents As clsActiveXEvents
Dim o As OLEObject
Set mcolEvents = New Collection
For Each o In ActiveSheet.OLEObjects
If TypeName(o.Object) = "CheckBox" Then
Set cCBEvents = New clsActiveXEvents
Set cCBEvents.mCheckboxes = o.Object
mcolEvents.Add cCBEvents, o.Name
End If
Next
End Sub
The differences are:
I use the OLEObjects Collection because it is more direct and doesn't waste time on non-OLE shapes.
I use TypeName instead of (the mysterious) TypeOf operator because (apparently) the later does not discriminate between OptionButton and CheckBox.
I register the Object Name as Key in the Collection to allow for efficient indexing if required.
EDIT:
I should have followed the link provided by #brettdj before posting. My solution is using the same principles as are outlined there. Hopefully, its convenient to have it documented here as well?