I am aiming to add VBA that hides or shows rows depending on whether a user clicks on a specific cell that needs to loop many times.
I was wondering how to possibly combine Target.Parent.Range with Worksheet.Cells so that I can write a loop for it rather than repeating the code multiple times. The below code works fine but seems pretty inefficient:
'Hide1
If (ActiveSheet.Name = "Dashboard") And Not Intersect(Target, Target.Parent.Range("G38")) Is Nothing Then
If Rows("40:47").EntireRow.Hidden = True Then
Rows("40:47").EntireRow.Hidden = False
Range("G38").Value = "Hide"
ActiveSheet.Range("A1").Select
Else
Rows("40:47").EntireRow.Hidden = True
Range("G38").Value = "Show"
ActiveSheet.Range("A1").Select
End If
End If
'Hide2
If (ActiveSheet.Name = "Dashboard") And Not Intersect(Target, Target.Parent.Range("G48")) Is Nothing Then
If Rows("50:57").EntireRow.Hidden = True Then
Rows("50:57").EntireRow.Hidden = False
Range("G48").Value = "Hide"
ActiveSheet.Range("A1").Select
Else
Rows("50:57").EntireRow.Hidden = True
Range("G48").Value = "Show"
ActiveSheet.Range("A1").Select
End If
End If
This will need to be repeated 10's of times as buttons are located at similar intervals down the sheet, so looping makes the most sense. Any help would be of great help as my attempts to combine the two functions have failed thus far.
Your code could be shortened to this
Private Sub Worksheet_Change(ByVal Target As Range)
Dim buttonRng As Range, hideRng As Range
Application.EnableEvents = False
Set buttonRng = Target
' Add in your ranges into this If statement
If Not Intersect(Target, Me.Range("G38")) Is Nothing Then
Set hideRng = Me.Rows("40:47")
ElseIf Not Intersect(Target, Me.Range("G48")) Is Nothing Then
Set hideRng = Me.Rows("50:57")
Else
Set hideRng = Nothing
End If
If Not hideRng Is Nothing Then
With hideRng
.Hidden = Not .Hidden
End With
buttonRng.Value2 = IIf(buttonRng.Value2 = "Show", "Hide", "Show")
End If
Application.EnableEvents = True
End Sub
You could add an additional sheet in with a list of the button location addresses and the range for them to hide.
You will need to set column B to text
and then use the following code
Private Sub Worksheet_Change(ByVal Target As Range)
Dim buttonRng As Range, hideRng As Range
Application.EnableEvents = False
Set buttonRng = Target
With Sheets("Button Hide Range").Columns(1)
Set hideRng = .Find(Target.Address(False, False))
End With
If Not hideRng Is Nothing Then
With Me.Rows(hideRng.Offset(0, 1).Value2)
.Hidden = Not .Hidden
End With
buttonRng.Value2 = IIf(buttonRng.Value2 = "Show", "Hide", "Show")
End If
Application.EnableEvents = True
End Sub
This sheet can then be hidden or set to xlVeryHidden if desired so it is not viewable by the end user.
Or if all of the rows to be hidden are the same offset away from the buttons you could use
Private Sub Worksheet_Change(ByVal Target As Range)
Dim buttonRng As Range
Dim i As Long
Application.EnableEvents = False
' i = row of first button to row of last button. Assuming each button is 10 rows apart from the previous
For i = 38 To 78 Step 10
If buttonRng Is Nothing Then
Set buttonRng = Me.Range("G" & i)
Else
Set buttonRng = Union(buttonRng, Me.Range("G" & i))
End If
Next i
If Not Intersect(Target, buttonRng) Is Nothing Then
' Assuming rows to be hidden are starts 2 rows away from button and ends 9 rows away
With Me.Rows(Target.Offset(2).Row & ":" & Target.Offset(9).Row)
.Hidden = Not .Hidden
End With
Target.Value2 = IIf(Target.Value = "Show", "Hide", "Show")
End If
Application.EnableEvents = True
End Sub
Related
By using Concatenate to combine 6 entries and then evaluate them, my worksheet changes the value of cell AA1 to be greater than 1 when a duplicate entry of any other row has been made. The Excel formula works well, but I need help on the VBA side: The code below is part of a Private Sub Worksheet_Change(ByVal Target As Range) with many operations that continue to work perfectly, while this does nothing at all. I already have Conditional Formatting highlighting the duplicate row entries, but I need a msg box to tell users what they have done wrong and how to fix it.
Dim fng As Range
Set fng = Range("$AA$1")
If Not Intersect(Target, fng) Is Nothing Then
If Target.Value > 1 Then
MsgBox "You already entered this barrel -- Enter another barrel over the duplicate. If no more barrels, change Grade to X, amounts to 0, and Producer to Z ZZ", vbOKOnly, "OOPS!"
End If
End If
In case it makes more sense to see the entire code, he it is:
Private Sub Worksheet_Change(ByVal Target As Range)
Target.Worksheet.Unprotect Password:="Cami8"
Dim rng As Range
Set rng = Range("F3:F10001")
If Not Intersect(Target, rng) Is Nothing Then
Target.Offset(0, 4) = Now
ActiveWorkbook.Save
End If
Application.EnableEvents = True
Dim ung As Range
Set ung = Range("J3:J10005")
If Not Intersect(Target, ung) Is Nothing Then
Target.Offset(-1, -3).Locked = True
End If
Application.EnableEvents = True
Dim wng As Range
Set wng = Range("J3:J10005")
If Not Intersect(Target, wng) Is Nothing Then
Target.Offset(-1, -4).Locked = True
End If
Application.EnableEvents = True
Dim xng As Range
Set xng = Range("J3:J10005")
If Not Intersect(Target, xng) Is Nothing Then
Target.Offset(-1, -5).Locked = True
End If
Application.EnableEvents = True
Dim kng As Range
Set kng = Range("J3:J10005")
If Not Intersect(Target, kng) Is Nothing Then
Target.Offset(-1, -6).Locked = True
End If
Application.EnableEvents = True
Dim qng As Range
Set qng = Range("J3:J10005")
If Not Intersect(Target, qng) Is Nothing Then
Target.Offset(-1, -7).Locked = True
End If
Application.EnableEvents = True
Dim cng As Range
Set cng = Range("C3:E10001")
If Not Intersect(Target, cng) Is Nothing Then
Application.EnableEvents = False
Target.Value = UCase(Target.Value)
Target.NumberFormat = "General"
Application.EnableEvents = True
End If
Dim sng As Range
Set sng = Range("E3:E10002")
If Not Intersect(Target, sng) Is Nothing Then
If Len(Target) > 1 Then
MsgBox "You entered GRADE with a letter and a space -- Click on the cell and enter only a letter", vbOKOnly, "OOPS!"
End If
End If
Dim fng As Range
Set fng = Range("$AA$1")
If Not Intersect(Target, fng) Is Nothing Then
If Target.Value > 1 Then
MsgBox "You already entered this barrel -- Enter another barrel over the duplicate. If no more barrels, change Grade to X, amounts to 0, and Producer to Z ZZ", vbOKOnly, "OOPS!"
End If
End If
Target.Worksheet.Protect Password:="Cami8"
End Sub
Thanks for the great tip about Worksheet_Calculate, since that did the trick! I just removed the code I had in Worksheet_Change and input this:
Private Sub Worksheet_Calculate()
Const lVal As Long = 2
Dim rCell As Range
Set rCell = Range("AA1")
If rCell.Value = lVal Then
MsgBox "You already entered this barrel -- Enter another barrel over the duplicate. If no more barrels, change Grade to X, amounts to 0, and Producer to Z ZZ", vbOKOnly, "OOPS!"
End If
End Sub
I have been able to find some code that zoom in when a cell containing a drop-down list is selected and then zoom out when a cell without a drop-down list is selected. I was wondering if there is a simple way to zoom in when the arrow to show the list is selected, and then zoom out when a value in the drop-down list has been selected. After some researches, I have not been able to find what is the corresponding target value for the arrow. The code I am currently using is the following:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim Rng As Range
Dim bZoom As Boolean
Const IncreasedZoomPercentage = "150" '<<=== Change to suit
Const NormalZoomPercentage As Long = "70" '<<=== Change to suit
If Target.Cells.Count > 1 Then
GoTo XIT
End If
On Error Resume Next
Application.EnableEvents = False
Set Rng = Me.UsedRange.SpecialCells(xlCellTypeAllValidation)
Application.EnableEvents = True
On Error GoTo 0
If Not Rng Is Nothing Then
If Not Intersect(Target, Rng) Is Nothing Then
If Target.Validation.Type = xlValidateList Then
bZoom = True
End If
End If
End If
XIT:
With ActiveWindow
If bZoom Then
.Zoom = IncreasedZoomPercentage
Target.EntireColumn.AutoFit
Else
.Zoom = NormalZoomPercentage
End If
End With
End Sub
To protect a second section of dynamic data when rows are removed in the first section, I need to change the last section of existing code below to a dynamic range that begins at E3 and ends either at the first row where column E is blank, last row where it is => zero or use a dynamic cell reference (N2) that shows # of last row (or anything that will work). At present, I handle this new need by manually changing E10001 to the new end of the first section of data (i.e, E5006). All of my attempts (used every option I could find) at this dynamic code resulted in the date being inserted 3 columns to right of any entry I make in my test spreadsheet. Thanks in advance for any help.
Private Sub Worksheet_Change(ByVal Target As Range)
Target.Worksheet.Unprotect Password:="Midnight"
On Error Resume Next
Application.EnableEvents = True
Dim hng As Range
Set hng = Range("F3:F10001")
If Intersect(Target, hng) Is Nothing Then
Target.Offset(-1, -4).Locked = True
End If
Application.EnableEvents = True
Dim xng As Range
Set xng = Range("F3:F10001")
If Intersect(Target, xng) Is Nothing Then
Target.Offset(-1, -3).Locked = True
End If
Application.EnableEvents = True
Dim wng As Range
Set wng = Range("F3:F10001")
If Intersect(Target, wng) Is Nothing Then
Target.Offset(-1, -2).Locked = True
End If
Application.EnableEvents = True
Dim qng As Range
Set qng = Range("F3:F10001")
If Intersect(Target, qng) Is Nothing Then
Target.Offset(-1, -1).Locked = True
End If
Application.EnableEvents = True
Dim sng As Range
Set sng = Range("F3:F10001")
If Intersect(Target, sng) Is Nothing Then
Target.Offset(-1, 0).Locked = True
End If
Dim cng As Range
Set cng = Range("B3:C10001")
If Not Intersect(Target, cng) Is Nothing Then
Application.EnableEvents = False
Target.Value = UCase(Target.Value)
Application.EnableEvents = True
End If
Dim rng As Range
Set rng = Range("E3:E10001")
If Not Intersect(Target, rng) Is Nothing Then
Target.Offset(0, 3) = Now
End If
Target.Worksheet.Protect Password:="Midnight"
End Sub
I am concluding that this is not possible. I have created a workaround using reference to calculated values in another sheet to fill the cells not needed with null data.
I'm struggling with the speed at which the following VBA code executes.
The goal of this code is to activate whenever "C4" changes, and then scan column "R" for the value 'Y'. If there's a 'Y', then it hides the row, and if not, it unhides the row. The code works, it's just not speedy - for 500 rows, it can take 30 or more seconds every time I change the value of "C4".
Does anyone have any suggestions to improve the speed at which this code executes? Or another method of accomplishing this?
Thanks for taking a look.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim L As Long
Dim r As Range
L = Cells(Rows.Count, "R").End(xlUp).Row
If Not Intersect(Target, Range("C4")) Is Nothing Then
For Each r In Range("R2:R" & L)
If r.Value = "Y" Then
Rows(r.Row).Hidden = True
Else
Rows(r.Row).Hidden = False
End If
Next
End If
End Sub
In attempting to apply the suggestion below - use Union() - I have come up with the below, not working, code. Any help would be greatly appreciated.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim L As Long
Dim r As Range
Dim RowsToHide As Range
Dim RowsToUnhide As Range
L = Cells(Rows.Count, "R").End(xlUp).Row
If Not Intersect(Target, Range("C4")) Is Nothing Then
For Each r In Range("R2:R" & L)
If r.Value = "Y" Then
RowsToHide = Union(RowsToHide, r.Row)
Else
RowsToUnhide = Union(RowsToUnhide, r.Row)
End If
Next
End If
RowsToHide.Hidden = True
RowsToUnhide.Hidden = False
End Sub
Adding Application.EnableEvents = False at the beginning of the code then turning back to true will help, Also using Applciation.ScreenUpdating = False should help as well.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim L As Long
Dim r As Range
Application.EnableEvents = False
Application.ScreenUpdating = False
L = Cells(Rows.Count, "R").End(xlUp).Row
If Not Intersect(Target, Range("C4")) Is Nothing Then
For Each r In Range("R2:R" & L)
If r.Value = "Y" Then
Rows(r.Row).Hidden = True
Else
Rows(r.Row).Hidden = False
End If
Next
End If
Application.EnableEvents = True
Application.ScreenUpdating = True
End Sub
There are several techniques that will help speed this up
Writing to .Hidden is much slower than reading it. So check if the row is already hidden or showing before setting Hidden
Collect the rows to Hide or Show into a range (Union) and Hide/Show tehm in one go.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim r As Range
Dim rngCheck As Range
Dim rngHide As Range, rngShow As Range
Application.ScreenUpdating = False
If Not Intersect(Target, Me.Range("C1")) Is Nothing Then
Set rngCheck = Me.Range(Me.Cells(1, "R"), Me.Cells(Me.Rows.Count, "R").End(xlUp))
For Each r In rngCheck.Cells
If r.Value2 = "Y" Then
If Not r.EntireRow.Hidden Then
If rngHide Is Nothing Then
Set rngHide = r.EntireRow
Else
Set rngHide = Union(rngHide, r.EntireRow)
End If
End If
Else
If r.EntireRow.Hidden Then
If rngShow Is Nothing Then
Set rngShow = r.EntireRow
Else
Set rngShow = Union(rngShow, r.EntireRow)
End If
End If
End If
Next
End If
If Not rngHide Is Nothing Then
rngHide.EntireRow.Hidden = True
End If
If Not rngShow Is Nothing Then
rngShow.EntireRow.Hidden = False
End If
Application.ScreenUpdating = True
End Sub
I have a worksheet with values depending on Cell A. If a row in column A contains a value then cells from Columns B through H will be changed accordingly.
If Cell of Column A is empty I want to reset the cells from columns D through F.
I wrote down the following VBA Code
Private Sub Worksheet_Change(ByVal Target As Range)
Dim n As Integer
For n = 5 To 75
Application.EnableEvents = False
If VarType(Cells(n, 1)) = vbEmpty Then
Cells(n, 4).ClearContents
Cells(n, 5).ClearContents
Cells(n, 6).ClearContents
Application.EnableEvents = True
End If
Next n
End Sub
The "FOR" Loop is annoying, and making the Excel to pause for 1 second or more after any entry to any Cell, can anyone help me correct the above code to do what I need to do without the "FOR" loop.
You are using a Worksheet_Change event and you iterating through 70 rows each time something changes.. this is a bad approach for this kind of problem and that's why there is a delay.
Instead, try
Private Sub Worksheet_Change(ByVal Target As Range)
Dim n As Long
If Target.Column = 1 Then
If IsEmpty(Cells(Target.Row, 1)) Then
Range("B" & Target.Row & ":F" & Target.Row).ClearContents
End If
End If
End Sub
this will only clear the cells if you remove a value from column A => when cell in column A is empty
Try this:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim i As Integer
If Mid(Target.Address(1, 1), 1, 2) = "$A" Then
If Target.Cells(1, 1).Value = "" Then
For i = 4 To 6
Target.Cells(1, i).Value = ""
Next i
End If
End If
End Sub
Give this a try:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rLook As Range, r As Range, Intr As Range
Set rLook = Range("A5:A75")
Set Intr = Intersect(rLook, Target)
If Intr Is Nothing Then Exit Sub
Application.EnableEvents = False
For Each r In Intr
If r.Value = "" Then
rw = r.Row
Range("D" & rw & ":F" & rw).ClearContents
End If
Next r
Application.EnableEvents = True
End Sub
It should have minimal impact on timing.
Use a range object.
The following line of code will print the address of the Range we'll use to clear the contents. The first cells call gets the upper left corner of the range, the second cells call gets the lower right corner of the range.
Private Sub test()
Debug.Print Range(Cells(5, 4), Cells(75, 6)).Address
End Sub
We apply this to your code like this:
Private Sub Worksheet_Change(ByVal Target As Range)
If VarType(Cells(Target.Row, 1)) = vbEmpty Then
Application.EnableEvents = False
Range(Cells(Target.Row, 4), Cells(Target.Row, 6)).ClearContents
Application.EnableEvents = True
End If
End Sub
One final sidenote: You should use an error handler to make sure events are always enabled when the sub exits, even if an error occurs.
Private Sub Worksheet_Change(ByVal Target As Range)
On Error GoTo ErrHandler
If VarType(Cells(Target.Row, 1)) = vbEmpty Then
Application.EnableEvents = False
Range(Cells(Target.Row, 4), Cells(Target.Row, 6)).ClearContents
End If
ExitSub:
Application.EnableEvents = True
Exit Sub
ErrHandler:
MsgBox "Oh Noes!", vbCritical
Resume ExitSub
End Sub
You should disable events and cater for multiple cells when using the Change event.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rng1 As Range
Dim rng2 As Range
Set rng1 = Intersect(Columns("A"), Target)
If rng1 Is Nothing Then Exit Sub
With Application
.EnableEvents = False
.ScreenUpdating = False
End With
For Each rng2 In rng1.Cells
If IsEmpty(rng2.Value) Then rng2.Offset(0, 1).Resize(1, 5).ClearContents
Next
With Application
.EnableEvents = True
.ScreenUpdating = True
End With
End Sub
For those that need to have data entered in one cell cleared (in a column) when there's a change in another column use this, which is a modification of Gary's Student.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rLook As Range, r As Range, Intr As Range
Set rLook = Range("D:D")
Set Intr = Intersect(rLook, Target)
If Intr Is Nothing Then Exit Sub
Application.EnableEvents = False
For Each r In Intr
If r.Value = "" Then
rw = r.Row
Range("L:L").ClearContents
End If
Next r
Application.EnableEvents = True
End Sub