Excel VBA: Show/Hide Pictures on yes/no dropdowns; troubleshooting simultaneous .Visible? - excel

Forgive me, I know nothing about VBA and I had the crazy idea to make Xmas presents that digitally replicate a scratch-off bucket list or novelogue poster in Excel. After lots of frustration, I got as far as being able to turn on/off a picture with an individual dropdown ("Unread"/"Complete"). It's a big table of book titles with book covers hidden until each one is marked Complete, then that book cover should display.
The problem is that only one image will show at a time. If two books are marked complete, only the most recent changed to Complete will be visible. Is this because I have them all running in the same Sub? Do I need a separate Sub for every image (that would be 100 Subs)?
Snippet, let's say there were only 5 books:
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Range("B4")) Is Nothing Then
Shapes("Picture 1").Visible = Range("B4").Value = "Complete"
Else
Shapes("Picture 1").Visible = False
End If
If Not Intersect(Target, Range("D4")) Is Nothing Then
Shapes("Picture 2").Visible = Range("D4").Value = "Complete"
Else
Shapes("Picture 2").Visible = False
End If
If Not Intersect(Target, Range("F4")) Is Nothing Then
Shapes("Picture 3").Visible = Range("F4").Value = "Complete"
Else
Shapes("Picture 3").Visible = False
End If
If Not Intersect(Target, Range("H4")) Is Nothing Then
Shapes("Picture 4").Visible = Range("H4").Value = "Complete"
Else
Shapes("Picture 4").Visible = False
End If
If Not Intersect(Target, Range("J4")) Is Nothing Then
Shapes("Picture 5").Visible = Range("J4").Value = "Complete"
Else
Shapes("Picture 5").Visible = False
End If
End Sub
I'm sure there's some more efficient way to do this but I'm more concerned with getting the images to show independently of other images' visible values. Thanks!
EDIT: I'm sorry my explanation wasn't clear enough. I don't have enough reputation to share an image, unfortunately. There are 10 columns of book covers, and 10 rows. Every other column holds images (in between is spacer). See below for rows.
When marked "Unread" in the cell below, the image should be hidden. When marked "Complete" below, the image should be revealed.
To explain cell layout:
B2 holds the image
B3 holds the title
B4 holds the data validated dropdown (that VBA checks for B2's image value of visible)
Spreadsheet layout:
B2:T2 have the first 10 images and thus,
B4:T4 have the first 10 data validated dropdowns
B7:T7 next dropdowns
B10:T10 " "
B13:T13, then B16:T16, B19:T19, B22:T22, B25:T25, B28:T28, B31:T31
https://i.imgur.com/RKC5AzG.png

I'm not 100% sure if I understood your question correctly but I tried just putting the repetitive parts of your code into a loop. Also, it will now not turn pictures invisible again.
If you are unfamiliar with the Cells property I used, you can find more information on it here.
Also, I split up this piece of code:
Shapes("Picture 1").Visible = Range("B4").Value = "Complete"
How was that supposed to work? I guess you wanted the picture to be visible and the cell to show "Complete"...
The picture name inside the loop is created by string concatenation from "Picture " & i
Private Sub Worksheet_Change(ByVal Target As Range)
Dim i as Long
Dim totalNumberOfPictures as Long
totalNumberOfPictures = 8 'Input number of pictures you have here
For i=1 To totalNumberOfPictures
If Not Intersect(Target, Cells(4, 2 * i)) Is Nothing Then
Shapes("Picture " & i).Visible = True
Cells(4, 2 * i) = "Complete"
End If
Next i
End Sub
Or if you meant every picture where the assigned cells value is changed to "Complete" should be visible, you can try this:
Edit: Updated to address the added detail in your question...
Private Sub Worksheet_Change(ByVal Target As Range)
Dim i as Long
Dim totalNumberOfPictures as Long
Dim count as Long
Dim rowOffset as Long
totalNumberOfPictures = 100 'Input number of pictures you have here
count = 1
rowOffset = 0
For i=1 To totalNumberOfPictures
If Cells(4 + rowOffset, 2 * count) = "Complete" Then
Shapes("Picture " & i).Visible = True
Else
Shapes("Picture " & i).Visible = False
End If
If count = 10 Then ' 10 is the number of pictures per "row"
rowOffset = rowOffset + 3 ' 3 is the row difference between your dropdown rows
count = 0
End if
count = count+ 1
Next i
End Sub
Let me know if it works, or if this is not what you meant, what you actually want...

If it was my project, I’d do it a slightly different way. I’ll offer this as a suggestion – let me know if it doesn’t suit you & I’ll delete it.
Step 1 – rename the pictures to match the book titles. This will be tiresome to start with, but it means you won’t be tied to a loop based on “Picture #” because picture names can vary. Also makes the names more intuitive.
Step 2 – copy the code below into a standard module & run it when you’re on the sheet with the pictures. It will create a named, non-contiguous range. If you look at the code, it’s fairly obvious how you can adjust it over time to suit your changing needs. Simply add more ranges as you need and rerun it.
Sub MakeRangeName()
ActiveSheet.Range _
("B4:T4,B7:T7,B10:T10,B13:T13,B16:T16,B19:T19,B22:T22,B25:T25,B28:T28,B31:T31") _
.Name = "myRange"
End Sub
Step 3 – copy the code below to the Sheet code module where you have the pictures. It will run whenever there’s a change in the range you created in Step 2, but will only affect the actual picture of interest.
Private Sub Worksheet_Change(ByVal Target As Range)
On Error GoTo GetOut
Application.EnableEvents = False
Dim s As String
If Not Intersect(Range("myRange"), Target) Is Nothing Then
If ActiveCell.Offset(-1, 0) = "" Then GoTo Letscontinue '<< to ignore the inbetween columns
s = ActiveCell.Offset(-1, 0).Text
If ActiveCell.Value = "Complete" Then
ActiveSheet.Shapes(s).Visible = True
Else
ActiveSheet.Shapes(s).Visible = False
End If
End If
Letscontinue:
Application.EnableEvents = True
Exit Sub
GetOut:
MsgBox Err.Description
Resume Letscontinue
End Sub
I’ve tested it and it works for me. Let me know if you get stuck on anything.

Related

Worksheet_change hide rows when value in cell is changed by form control element

I am doing some changes in wb created by someone else and need do as less harm as possible as this excel will be used company wide. My issue is, that there are two form controls buttons which changes value in Z1 to 1 or 2, based on the selection.
Option 2 is for one row only, so I would need to hide several lines before this row. I am trying this through worksheet change, but without no luck as the value is not changed in proper way for VBA. The change macro works when I use F2+enter manually, but not when I change it by button only.
I tried to overpass F2+Enter through
Range("Z1").FormulaR1C1 = Range("Z1").FormulaR1C1
But with no luck. I tried several versions of code. My favourite one is this one
If Target.Address = "$Z$1" Then
Range("Z1").FormulaR1C1 = Range("Z1").FormulaR1C1
If Target = "2" Then
Rows("5:13").EntireRow.Hidden = True
Else
Rows("5:13").EntireRow.Hidden = False
End If
End If
My others are
If Not Intersect(Target, Range("Z1")) Is Nothing Then
Rows("5:13").EntireRow.Hidden = CBool(Range("Z1").Value = 2)
End If
========
If Not Application.Intersect(Target, Range(Target.Address)) Is Nothing Then ', Range(Target.Address)
Application.EnableEvents = False
Range("Z1").FormulaR1C1 = Range("Z1").FormulaR1C1
Select Case Target.Value
Case Is = "2": Rows("5:13").EntireRow.Hidden = True
Case Is = "1": Rows("5:13").EntireRow.Hidden = False
End Select
Application.EnableEvents = True
End If
========
If Intersect(Range("Z1"), Target) Is Nothing Then Exit Sub
Range("Z1").FormulaR1C1 = Range("Z1").FormulaR1C1
Select Case Range("Z1").Value
Case Is = 2
Set HideRows = Rows("5:13")
Set ViewRows = Nothing
Case Is = 1
Set ViewRows = Rows("5:13")
End Select
On Error Resume Next
HideRows.Hidden = True
ViewRows.Hidden = False
Neither one is working, I made a video and uploaded on YouTube
What am I missing? I need it for the user to be connected with form control selection. I am not able to pursue change of the form control.
Changes to "linked cell" values do not trigger the worksheet_change event.
You could make it trigger the Worksheet_Calculate event by placing (eg) =Z1 in Z2, then that formula would calculate whenever the value in Z1 is changed. That would mean you'd be responding to every calculation on the sheet though, so you can make sure you only hide/unhide when the value in Z1 has changed:
Private Sub Worksheet_Calculate()
Dim opt, cCache As Range
Set cCache = Me.Range("Z3") 'cell with last value
opt = Me.Range("Z1").Value 'get current value
If opt <> cCache.Value Then 'compare to last value
Debug.Print "Rows toggle"
Me.Rows("5:13").EntireRow.Hidden = (opt = 2) 'hide/unhide
cCache.Value = opt 'save this for next change
End If
End Sub

Hide Entire given rows if a specified cell is blank

I am a newbie in VBA coding.
I am trying to achieve that inside my function
sub Private Sub Worksheet_Change(ByVal Target As Range)
where it checks some specified cells value change to run a given macro. The additional macro that i want to add is that when ever cell a62 is Empty, it will hide rows a56:a61. and consecutively if a82 is empty, it will hide rows a78:a82.
i have the following code but it only hides the rows from the first empty cell until end of sheet.
Sub Test()
Dim i As Long
For i = 4 To 800
If Sheets("Results").Cells(i, 1).Value = "" Then
Rows(i & ":" & Rows.Count).EntireRow.Hidden = True
Rows("1:" & i - 1).EntireRow.Hidden = False
Exit Sub
End If
Next
End Sub
Please, check the next event code:
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Address = "$A$62" Or Target.Address = "$A$82" Then
Select Case Target.Address
Case "$A$62"
If Target.Value = "" Then
Range("A56:A61").EntireRow.Hidden = True
Else
Range("A56:A61").EntireRow.Hidden = False
End If
Case "$A$82"
If Target.Value = "" Then
Range("A78:A81").EntireRow.Hidden = True
Else
Range("A78:A81").EntireRow.Hidden = False
End If
End Select
End If
End Sub
It will be triggered only if you MANUALLY change the value of one of the two required cells.
If their value is the result of a formula, Worksheet_Calculate event must be used, but in a different way. This event does not have any argument (no Target) and you must check the two cells in discussion and act according to their value, independent if they were changed or not when the Calculate event is triggered. If this is the case, I can post such an event code, too.
Edited:
For the version which does not involve the manual changing of the values, please copy this event code in the sheet code module:
Private Sub Worksheet_Calculate()
If Me.Range("A62").Value = "" Then
Me.Range("A56:A61").EntireRow.Hidden = True
Else
Me.Range("A56:A61").EntireRow.Hidden = False
End If
If Me.Range("A82").Value = "" Then
Me.Range("A78:A82").EntireRow.Hidden = True
Else
Me.Range("A78:A82").EntireRow.Hidden = False
End If
'Edited:
'The part for both analyzed ranges being empty:
If Me.Range("A62").Value = "" And _
Me.Range("A82").Value = "" Then
'Do here what you need...
End If
End Sub

How to apply code to all the following rows

I have this code but it only work for my first row.
It is suppose to look if the checkbox on B, C or D is checked, and if so, a date + username will automaticaly fill in F and G.
here is a picture of my table:
This is what my code looks like:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Range("B2") Or Range("C2") Or Range("D2") = True Then
Range("G2").Value = Environ("Username")
Range("F2").Value = Date
Else
Range("F2:G2").ClearContents
End If
End Sub
Enter this code in a regular module, select all your checkboxes and right-click >> assign macro then choose ReviewRows.
This will run the check whenever a checkbox is clicked - a bit of overhead since all rows will be checked, but should not be a big deal.
Sub ReviewRows()
Dim n As Long
For n = 1 To 100 'for example
With Sheet1.Rows(n)
If Application.CountIf(.Cells(2).Resize(1, 3), "TRUE") > 0 Then
If Len(.Cells(6).Value) = 0 Then 'only enter if currently empty?
.Cells(6) = Date
.Cells(7) = Environ("Username")
End If
Else
.Cells(6).Resize(1, 2).ClearContents
End If
End With
Next n
End Sub
If you want to be more precise then Application.Caller will give you the name of the checkbox which was clicked, and you can use that to find the appropriate row to check via the linkedCell.
Sub ReviewRows()
Dim n As Long, shp As CheckBox, c As Range, ws As Worksheet
Set ws = ActiveSheet
On Error Resume Next 'ignore error in case calling object is not a checkbox
Set shp = ActiveSheet.CheckBoxes(Application.Caller) 'get the clicked checkbox
On Error GoTo 0 'stop ignoring errors
If Not shp Is Nothing Then 'got a checkbox ?
If shp.LinkedCell <> "" Then 'does it have a linked cell ?
With ws.Range(shp.LinkedCell).EntireRow
If Application.CountIf(.Cells(2).Resize(1, 3), "TRUE") > 0 Then
If Len(.Cells(6).Value) = 0 Then 'only enter if currently empty?
.Cells(6) = Date
.Cells(7) = Environ("Username")
End If
Else
.Cells(6).Resize(1, 2).ClearContents
End If
End With
End If 'has linked cell
End If 'was a checkbox
End Sub
However this appraoch is sensitive to the exact positioning of your checkbox
You have a long way to go!
Unfortunately, If Range("B2") Or Range("C2") Or Range("D2") = True Then is beyond repair. In fact, your entire concept is.
Start with the concept: Technically speaking, checkboxes aren't on the worksheet. They are on a layer that is superimposed over the worksheet. They don't cause a worksheet event, nor are they responding to worksheet events. The good thing is that they have their own.
If Range("B2") Or Range("C2") Or Range("D2") = True Then conflates Range with Range.Value. One is an object (the cell), the other one of the object's properties. So, to insert sense into your syntax it would have to read, like, If Range("B2").Value = True Or Range("C2").Value = True Or Range("D2").Value = True Then. However this won't work because the trigger is wrong. The Worksheet_Change event won't fire when when a checkbox changes a cell's value, and the SelectionChange event is far too common to let it run indiscriminately in the hope of sometimes being right (like the broken clock that shows the correct time twice a day).
The answer, therefore is to capture the checkbox's click event.
Private Sub CheckBox1_Click()
If CheckBox1.Value = vbTrue Then
MsgBox "Clicked"
End If
End Sub
Whatever you want to do when the checkbox is checked must be done where it now shows a MsgBox. You can also take action when it is being unchecked.

Excel images to appear based on cell value

I have 2 sheets on excel, one called Raw and one called Graphs. What I want to do is have some cells in Raw and it they =TRUE then I want a shape to appear on the Graphs page.
I am pretty new to VBA so i havent tried much :(
Private Sub Worksheet_Calculate()
With Worksheets("Graph")
If Me.Range("FK45").Value = True Then
.Shapes("Test1").Visible = True
Exit Sub
ElseIf Me.Range("FK45").Value = False Then
.Shapes("Test1").Visible = False
Exit Sub
End If
End With
End Sub
I can get this to work so if FK45 is TRUE the image shows but if FK45 is FALSE it doesn't, But what I want to be able to do is add more to this e.g.
Private Sub Worksheet_Calculate()
With Worksheets("Graph")
If Me.Range("FK45").Value = True Then
.Shapes("Test1").Visible = True
Exit Sub
ElseIf Me.Range("FK45").Value = False Then
.Shapes("Test1").Visible = False
Exit Sub
End If
End With
With Worksheets("Graph")
If Me.Range("FK46").Value = True Then
.Shapes("Test2").Visible = True
Exit Sub
ElseIf Me.Range("FK46").Value = False Then
.Shapes("Test2").Visible = False
Exit Sub
End If
End With
End Sub
I want them all to be independent from each other and be able to add more if necessary
If FK45 is TRUE Image1 shows
If FK45 is FALSE Image1 doesn't show
and/or
If FK46 is TRUE Image2 shows
If FK46 is FALSE Image2 doesn't show
and/or
If FK47 is TRUE Image3 shows
If FK47 is FALSE Image3 doesn't show
and so on...
This is how I would do it
In VB Editor find your Worksheet and Shape objects and rename their system name to something intutive in their respective property windows.
This way you can get rid of With Worksheets("Graph") construction, instead you can call them by their system name like With Graph. This will also come in handy if you wish to rename your worksheets or shapes.
Note that you Exit Sub after each cell check, your procedure stops after first cell and doesn't proceed any further.
Instead of Worksheet.Calculate event I advise using Worksheet.Change. This way you can iterate through your cells one by one.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rngCell As Range
Dim shpImage As Shape
For Each rngCell In Target.Cells
' check if change was made in "FK"
If Not Intersect(rngCell, Me.Columns("FK")) Is Nothing Then
Select Case rngCell.Row
Case 45: Set shpImage = Graph.MyImage_1
Case 46: Set shpImage = Graph.MyImage_2
End Select
' if only boolean values present, no need for IF construction
shpImage.Visible = rngCell.Value : Set shpImage = Nothing
End If
Next
End Sub
If you had separate column for image name it would be much easier, you could check like this (for example, shape names are located in "FL" column)
Graph.Shapes(rngCell.Offset(0, 1).Value).Visible = rngCell.Value

Looping over checkboxes with VBA in Excel very slow

I have an Excel Sheet with about 4500 checkboxes (I know, it sounds stupid, but it is for a customer, please do not ask...).
Just wrote the VBA Sub below to uncheck all the boxes together. So far it works, but it is terribly slow, it takes more than 5 minutes until all boces are unchecked and while the Sub is running, the whole Excel Applikation grays out freezes. I know, 4500 Checkboxes is quiet a lot, but I wonder that it is really enough to bring Excel in such a trouble....Has anyone an idea?
Best
Michael
Sub DeselectAll()
Application.EnableCancelKey = False
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
Application.EnableEvents = False
Dim wksA As Worksheet
Dim intRow As Integer
Set wksA = Worksheets("Companies")
For intRow = 1 To 4513
wksA.CheckBoxes("Checkbox_" & intRow).Value = False
Next
End Sub
Without selection:
Sub DeselectAll()
With Worksheets("Companies").CheckBoxes
.Value = xlOff
End With
End Sub
Just don't loop.
This is a good example of when Selection can help:
To set all checkboxes:
Sub dural()
ActiveSheet.CheckBoxes.Select
Selection.Value = xlOn
End Sub
To uncheck all checkboxes:
Sub dural2()
ActiveSheet.CheckBoxes.Select
Selection.Value = xlOf
End Sub
( tested on Forms-type checkboxes )
The best answer I thumbs up for is #EvR solution. I am not trying to answer but offering an idea of a workaround.
I checked the time by adding 4000 ComboBox in blank sheet in a blank workbook with a simple 3 line loop (omg I forgot to off screen updating and calculations etc). It took around 10 minutes in my old laptop. I don’t have courage to repeat the ordeal again.
When I tried to use your piece of code with looping it is taking 3-4 seconds only and with #EvR’s solution without loop and selection is taking 1-2 seconds. These times are actual time taken with Debug.Print or writing to some cells. Actual drama unfolds after screen updates, calculations, events are enabled with the sheet active. It become highly unstable and any careless click etc cause excel to ‘not responding’ state for 2-5 mintues.
Though Customer and Boss are always right. Once in my life I succeeded to persuade someone in a similar approach of hundreds of buttons on a worksheet to something virtual. My Idea is to create virtual checkbox in the sheet. Proper cell sizing and border with validation of the cells to `=ChrW(&H2714)’ and ignore blank and a simple code like below can make it a pass-through type of work-around.
Public Prvsel As Range
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim isect, Cl As Range
Set isect = Application.Intersect(Target, Range("C1:C4000"))
If isect Is Nothing Then
Set Prvsel = Nothing 'Release multiple selection
Exit Sub
End If
If isect.Cells.Count > 1 Then
Set Prvsel = isect 'storing multiple selection for next click event
Else
If Target.Value = ChrW(&H2714) Then
Target.Value = ""
Else
Target.Value = ChrW(&H2714)
End If
If Not Prvsel Is Nothing Then
For Each Cl In Prvsel.Cells
Cl.Value = Target.Value
Next Cl
End If
End If
End Sub
Elaborating on #Ahmed AU solution.
Select/Deselect signal/ multiple virtual checkboxs
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim isect As Range
Dim Cl As Range
Dim Prvsel As Range
Set isect = Application.Intersect(Target, Range("C1:C4000"))
If isect Is Nothing Then
Set Prvsel = Nothing 'Release multiple selection
Exit Sub
End If
' Use WINGDING font Chr (254) for checked
' Use WINGDING font Chr (111) for uncheck
If isect.Cells.Count >= 1 Then
Set Prvsel = isect
For Each Cl In Prvsel.Cells
If Cl.Value = Chr(111) Then
Cl.Value = Chr(254)
Else
Cl.Value = Chr(111)
End If
Next Cl
End If
'Go to offset cell selection
Selection.Offset(0, 1).Select
End Sub

Resources