VBA Deleting buttons with a range - vba

I'm trying to figure out how to delete all buttons within a range. I've seen plenty of examples on how to delete all buttons within a sheet but not a range. I created a range variable the contains every possible occurance of a button (this is used to reinitialize a form of variable size). The problem is that range doesnt support the object .Shapes or .Buttons.
Set totalTable = Range(ActiveCell, ActiveCell.Cells(1000, 1000))
For Each gen_btn In totalTable.Shapes
gen_btn.Delete
Next
Any help would be appreciated. Also, I can't use ActiveSheet becuase there are buttons which i want to keep and becuase the macro is called by a button. Hence the need for a range. Thank you.

This solution uses the Intersect method to see whether the shape is in your range and deletes the shape if it is.
Sub Delete_Shapes_In_Range()
Dim btn As Shape
Dim totalTable As Range
Set totalTable = Range(ActiveCell, ActiveCell.Cells(1000, 1000))
For Each btn In ActiveSheet.Shapes
If Not Intersect(btn_rng, totalTable) Is Nothing Then btn.Delete
Next btn
End Sub
Note that this code will not only delete buttons, but will also delete other shapes. If this is a concern, you can add an If statement to skip certain shapes. For example:
If Not btn.Name Like "Picture*" Then '<~~Will skip pictures
or
If Not btn.Name Like "*box*" Then '<~~Will skip textboxes
etc. This assumes that you haven't renamed the shapes since creating them.

I'll show you how to extract the "position" of a button (it's not optimal, but it works). Up to you to adapt it to make it work as it should. This will dislpay the row and column of the top-left cell touched by each button (in the ActiveSheet) in successive message boxes.
Sub Testing()
For Each butt In ActiveSheet.Buttons
MsgBox "Row : " & butt.TopLeftCell.Row & vbCrLf & "Column : " & butt.TopLeftCell.Column
Next butt
End Sub

The complete code:
Sub DeleteRangeButtons()
rng = "A1:A10" ' Place range here.
Dim btn As Button, ws As Worksheet
For Each ws In ActiveWorkbook.Worksheets
For Each btn In ws.Buttons
If isinrange(btn.TopLeftCell.Row, btn.TopLeftCell.Column, rng) Then
btn.Delete
End If
Next btn
Next ws
End Sub
Function isinrange(x, y, rng)
Cells(x, y).Activate
If Intersect(ActiveCell, Range(rng)) Is Nothing Then
isinrange = False
Else
isinrange = True
End If
End Function

Commenting on answer by ARich (which was useful to me) since I couldn't add a comment directly. It misses setting btn_rng, but btn.TopLeftCell could be used instead.
Also, I prefer
btn.Type = msoPicture
instead of
btn.Name Like "Picture.
Here is my method based on that:
Public Sub DeleteIntersectingPictures(ByVal sheetToDeleteIn As Worksheet, ByVal rangeToLookIn As range)
Dim noOfRowsInSheet As Long
Dim pictureItem As Shape
Dim pictureRange As range
For Each pictureItem In sheetToDeleteIn.Shapes
If pictureItem.Type = msoPicture Then
Set pictureRange = sheetToDeleteIn.range( _
pictureItem.TopLeftCell.Address & ":" & pictureItem.BottomRightCell.Address)
If Not Intersect(pictureRange, rangeToLookIn) Is Nothing Then
Call pictureItem.Delete
End If
End If
Next pictureItem
End Sub

Related

VBA - Highlight Cell With Checkbox

Some logic to my process:
In column K on my worksheet I have inserted check boxes from cell K3 - K53 (this could become longer in the future) using the developer tab.
I then associated the check box with the same cell it is placed in.
I formatted the cells in this column by going to 'Format Cells', clicking on 'Custom' then typing in ';;;'. This was to HIDE the 'True/False' text from view.
My next step is to change the cell colour based on the text.
Note:
I have searched through a few forums and combined some code samples from them all, so I will not be able to reference the sources exactly, but below is what I have so far:
Code:
Sub Change_Cell_Colour()
Dim xName As Integer
Dim xChk As CheckBox
Dim rng As Range
Dim lRow As Long
lRow = ActiveWorksheet.Cells(Rows.Count, "B").End(xlUp).Row
Set rng = ActiveWorksheet.Range("K2:K" & lRow)
For Each xChk In ActiveSheet.CheckBoxes
xName = Right(xChk.Name, Len(xChk.Name) - 10)
If (Range(xChk.LinkedCell) = "True") Then
rng.Interior.ColorIndex = 6
Else
rng.Interior.ColorIndex = xlNone
End If
Next
End Sub
I keep getting an error on the line where I try to get the last row.
Code:
lRow = ActiveWorksheet.Cells(Rows.Count, "B").End(xlUp).Row
Error:
Object Required
I am not even sure if the code I have will solve my issue, so any help solving the main issue highlighting a cell based on the check box being checked or not, will be greatly appreciated.
Here's a quick rewrite with LOTS of comments explaining:
Sub Change_Cell_Colour()
Dim xChk As CheckBox
'Be explicit about which worksheet. Leaving it to "Activeworksheet" is going to cause problems
' as we aren't always sure which sheet is active...
'Also in this case we don't need to know the last row. We will iterate checkbox objects, not
' populate rows.
'lRow = ActiveWorksheet.Cells(Rows.Count, "B").End(xlUp).Row
'Again... we don't need this. We just need to iterate all the checkboxes on the sheet
'Set rng = ActiveWorksheet.Range("K2:K" & lRow)
'This is good stuff right here, just change the ActiveSheet to something more explicit
' I've changed this to the tab named "Sheet1" for instance.
For Each xChk In Sheets("Sheet1").CheckBoxes
'Getting the name of the checkbox (but only the last 10 characters)
xName = Right(xChk.Name, Len(xChk.Name) - 10)
'We can check the linked cell's value, but we can also just check if the
' if the checkbox is checked... wouldn't that be easier?
'If (Range(xChk.LinkedCell) = "True") Then
If xChk.Value = 1 Then
'Now we can use the "LinkedCell", but it's a STRING not a RANGE, so we will have
' to treat it as the string name of a range to use it properly
Sheets("Sheet1").Range(xChk.LinkedCell).Interior.ColorIndex = 6
Else
Sheets("Sheet1").Range(xChk.LinkedCell).Interior.ColorIndex = xlNone
End If
Next
End Sub
Here's the barebones version just to get it working
Sub Change_Cell_Colour()
Dim xChk As CheckBox
'Loop through each checkbox in Sheet1. Set it to color 6 if true, otherwise no color
For Each xChk In Sheets("Sheet1").CheckBoxes
If xChk.Value = 1 Then
Sheets("Sheet1").Range(xChk.LinkedCell).Interior.ColorIndex = 6
Else
Sheets("Sheet1").Range(xChk.LinkedCell).Interior.ColorIndex = xlNone
End If
Next
End Sub
I'm totally assuming here, but I would imagine you want this macro to fire when a checkbox is clicked. There is a handy Application.Caller that holds the name of the object that caused a macro to be called. You can set the "Assign Macro.." of each checkbox to this new code and then you can figure out which checkbox called the subroutine/macro using application.caller and follow the same logic to toggle it's linked cell color:
Sub Change_Cell_Colour()
Dim xChk As CheckBox
'Who called this subroutine/macro?
Dim clickedCheckbox As String
clickedCheckbox = Application.Caller
'Lets check just this checkbox
Set xChk = Sheets("Sheet1").CheckBoxes(clickedCheckbox)
'toggle its color or colour if you are a neighbour
If xChk.Value = 1 Then
Sheets("Sheet1").Range(xChk.LinkedCell).Interior.ColorIndex = 6
Else
Sheets("Sheet1").Range(xChk.LinkedCell).Interior.ColorIndex = xlNone
End If
End Sub
highlighting a cell based on the check box being checked or not
Select the sheet and apply a CF formula rule of:
=A1=TRUE
ActiveWorksheet doesn't exist, and because you haven't specified Option Explicit at the top of your module, VBA happily considers it an on-the-spot Variant variable.
Except, a Variant created on-the-spot doesn't have a subtype, so it's Variant/Empty.
And ActiveWorksheet.Cells being syntactically a member call, VBA understands it as such - so ActiveWorksheet must therefore be an object - but it's a Variant/Empty, hence, object required: the call is illegal unless ActiveWorksheet is an actual Worksheet object reference.
Specify Option Explicit at the top of the module. Declare all variables.
Then change ActiveWorksheet for ActiveSheet.

Worksheet_Change setting target range is slow

I have an excel macro used to manage button visibility in Excel in the "Worksheet_Change" function based from another SO question here.
The problem is the although the macro works it makes updating the Excel sheet rather laggy. I have managed to pin down the slowness to a single line:
Set rUpdated = Range(Target.Dependents.Address)
This sets the range of cells updated to a variable to be iterated through later in the script. If I call a script with just this line I found this is where all the delay is. It seems a rather simple line, but is there a better way to do it?
Full disclosure:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rUpdated As Range
Dim shp As Shape
Dim rCell As Range
Set rUpdated = Range(Target.Dependents.Address)
If Not rUpdated Is Nothing Then
For Each rCell In rUpdated
If rCell.Column = 1 Then
'Look at each shape in the sheet and cross-reference with rCell.
For Each shp In Target.Parent.Shapes
If shp.TopLeftCell.Row = rCell.Row Then
shp.Visible = (rCell.Value <> "")
Exit For 'Exit the loop - the correct button has been found.
End If
Next shp
End If
Next rCell
End If
End Sub
So if i understood it correctly you want to make a button visible if the cell in the row as been changed. The only things i can think of to slow it down are, that is has to check many rCell or Shapes. I dont know what the structure of your document is. So my Idea would be: instead of going through all shapes every time, i would name them in a pattern that you can identify them with the row they are in so you use the name to address them (i.e Row2 for the Button in Row 2).
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rUpdated As Range
Dim shp As Shape
Dim rCell As Range
Dim obj As OLEObject
Set rUpdated = Range(Target.Dependents.Address)
If Not rUpdated Is Nothing Then
For Each rCell In rUpdated
If rCell.Column = 1 Then
On Error Resume Next
Set obj = ActiveSheet.OLEObjects("Row" & rCell.Row)
If Err.Number = 0 Then
obj.Visible = (rCell.Value <> "")
End If
End If
Next rCell
End If
End Sub
I replaced that config with the following single line (and companion line):
On Error Resume Next
ActiveSheet.Shapes("buttonRow" & Target.Row).Visible = (ActiveSheet.Cells(Target.Row, 1).Value <> "")
However to get this to work I first needed to rename all my shapes. I used this function to do that:
Function renamebuttons()
For Each shp In ActiveSheet.Shapes
shp.name = "buttonRow" & shp.TopLeftCell.Row
Next shp
End Function
I ran that function once and deleted it. Once done my shapes can now be referred to by name and I no longer incur the delay of cycling through every shape and every target dependent. The delay experienced in the worksheet is now minimal.

Get Position of a combobox in an Excel worksheet

I have a sheet with comboboxes in it. To the left of the comboboxes there is a column where the user can mark positions with an "X" if the combobox to its right should be doing stuff (filling itself with Values that are taken from a dynamic range). I was thinking of something like this but am not managing to make it work:
Dim ComBx As ComboBox
Dim ws2 As Worksheet
Set ws2 = ActiveWorkbook.Worksheets("Tool")
For Each ComBx In ws2.ComboBox
If ComBx.Offset(0, -1).Value = "X" Then
With ComBx
'do stuff
End With
End If
Next ComBx
Any ideas how to reference the position of a combobox anyone? I had to do something similar for checkboxes, where it worked just fine, but this eludes me.
If your Combo-boxes type are Active-X try the code below.
ComBx.TopLeftCell.Offset(, -1).Value returns the value of the cell located one column to the left of the cell where your Combo-Box is located.
Note: besides that, you have a Typo in your code, you defined and set ComBx, but then using If CmBx.Offset(0, -1).Value = "X" Then and also closing with Next CmBx - this shouldn't even compile.
Code
Option Explicit
Sub CmbBoxPosition()
Dim ComBx As OLEObject
Dim ws2 As Worksheet
Set ws2 = ActiveWorkbook.Worksheets("Tool")
For Each ComBx In ws2.OLEObjects
If ComBx.progID Like "Forms.ComboBox.1" Then
' for DEBUG Only
Debug.Print ComBx.Name & " located at " & ComBx.TopLeftCell.Address(False, False, xlA1)
If ComBx.TopLeftCell.Offset(, -1).Value = "X" Then
With ComBx
' the rest of your code goes here
End With
End If
End If
Next ComBx
End Sub
If you are using ActiveX combo boxes then you can run this example to see what the 'TopLeftCell' value (or any offset from it) for all your combo boxes are just to be certain you're looking at the right ones.
Sub GetCombos()
Dim shp As Shape
Dim ws2 As Worksheet
Dim cel As Range
Set ws2 = ActiveWorkbook.Worksheets("Tool")
For Each shp In ws2.Shapes
If shp.FormControlType = xlDropDown Then
Set cel = shp.TopLeftCell.Offset(0, -1)
If cel.Value = "X" Then
Debug.Print "cell at row=" & cel.Row & " column=" & cel.Column & " has an X in it"
' do stuff
End If
End If
Next
End Sub
If it's not the right one you can change the x and y values of shp.TopLeftCell.Offset(x,y) to the correct offset and update your code accordingly.

Click and Unclick X boxes in VBA excel formula

I am creating a survey that I want to be simple for the end user. I have created a document that operates on a Likert scale, where things range from Disagree to Agree on a 6 point scale (with a no answer field). The questions I have run on rows 3 to 152 and the choices are in cells C:H on each row. Currently, I have it to where the taker can click on a cell and produce an X in the box, indicating their choice. I also have it so that they may only click one option in the row, and if they select another option, it removes the first X and places the X in the new cell that they have clicked.
Here is what I want. Right now, if they place an X in a cell, and click that same X, my code goes through and takes the X away and replaces it again with that same X. I would like it to replace the clicked X with nothing, so that it can toggle on and off with a click. I want to keep it so that if they select another cell in the same row, the X in the original cell disappears and the X pops up in the clicked cell, however. I only want them to be able to select one cell in each row for each question. Sorry for the length, but I just wanted to be clear. Here is my code currently.
Thanks for any help you can offer!
Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim rInt As Range
Dim rCell As Range
For rw = 3 To 152
Set rInt = Intersect(Target, Range("C" & rw & ":H" & rw))
If Not rInt Is Nothing Then
If Application.WorksheetFunction.CountA(Range("C" & rw & ":H" & rw)) > 0 Then
Range("C" & rw & ":H" & rw).Value = ""
End If
For Each rCell In rInt
If rCell.Value = "" Then
rCell.Value = "X"
End If
Next
End If
Set rInt = Nothing
Set rCell = Nothing
Cancel = True
Next
End Sub
Try the below code. Notice I removed your loop; there's no reason to loop through every row when we can pinpoint the row the user selected and focus on that row. I also changed the way rInt is set, replaced the CountA function with .Find, and wrapped the entire procedure with a test to see whether the user has selected a cell in our predefined range (so the code doesn't needlessly run when other cells are selected).
Option Explicit
Sub Worksheet_SelectionChange(ByVal Target As Range)
'Only run the code if the user selected a cell in our defined range:
If Not Intersect(Target, Me.Range("C3:H152")) Is Nothing Then
'Declare variables
Dim rInt As Range
Dim rCell As Range
Dim rw As Long
Dim xLoc As Range
Set rInt = Me.Range(Me.Cells(Target.Row, "C"), Me.Cells(Target.Row, "H"))
If Not rInt Is Nothing Then
'Look for a response in our answer range
Set xLoc = rInt.Find("x")
If Not xLoc Is Nothing Then
'If there was a response and the response was in the same column _
'we selected, wipe the response and exit the sub.
If Target.Column = xLoc.Column Then
rInt.Value = vbNullString
Exit Sub
'Else, wipe the previous response and add the new response
Else
rInt.Value = vbNullString
Target.Value = "x"
End If
'If there were no previous responses...
Else: Target.Value = "x"
End If
End If
End If
End Sub
All you need here are radio buttons that are linked to a cell and then edit the click code.
Private Sub OptionButton1_Click()
Range("D3:H3") = False
Range("C3") = True
End Sub
Private Sub OptionButton2_Click()
Range("C3") = False
Range("D3") = True
Range("E3:H3") = False
End Sub
Then format the cells to make the text the same color as the background and use conditional formatting to change the color of the cell the radio button is in front of by referencing the TRUE or FALSE, very nice for the user and easy.
I'm guessing you haven't used them before so just so you know, you go to the developer tab, I always use the ActiveX radio buttons. Then use design mode to edit the properties of the option button, and change "GroupName" to link any the radio buttons together where you only want one to be clicked by naming them with the same group name.

Selecting a range by mouse and setting parameters

I am trying to do the following with Excel 2010 VBA:
set a toggle button on a form.
move to the worksheet
use the mouse to select groups (range) of cells
set a cell parameter (background colour) to the back colour of the toggle button
.
.
.
continue select cells or deselect the toggle button.
What I have so far is this but I get a global range error on the line setting the colour:
Public Sub ToggleButton1_Click()
Dim ActRange As Range
Dim ActSheet As Worksheet
Dim bgndColour As Variant
bgndColour = ToggleButton1.BackColor
Set ActSheet = ActiveSheet
Set ActRange = Selection
ActSheet.Select
ActRange.Select
Range(ActRange).Interior.Color = bgndColour
End Sub
ActRange is already a range. No need to use the Range() object
Try this
ActRange.Interior.Color = bgndColour
Your code can be reduced to
Public Sub ToggleButton1_Click()
If TypeName(Selection) <> "Range" Then
MsgBox "Select a range first."
Exit Sub
End If
Selection.Interior.Color = ToggleButton1.BackColor
End Sub
I dont like to add inecesary lines, I see you can resume your code in
Selection.Interior.Color = bgndColour
if that is not working what is wrong is your bgndcolour variable