Crash when deleting cell values Worksheet_Change event - excel

I would like to run a macro (say test1) continously on a worksheet whenever the value in a given range (F5 to LastRow). The Worksheet_Activate and Worksheet_Change event helped in this respect. However, Excel crashes whenever the values in the range are deleted. As example:
F5 = 100, F6 = 120,F7 = 140
Suppose the value of F5 is changed to 120. Then the macro and events are working fine. However, when all the values are deleted (so F5 uptill F7 are empty), Excel crashes.
I have tried to run each line in my code seperately, but I am not sure what is causing the crash (perhaps the loop as written in the macro)?
I am a beginner with VBA and any assistance is much appreciated :-)
Sub TEST()
Dim LastRow As Long
Dim i As Long
LastRow = Sheets("blad1").Range("F5").End(xlDown).Row
For i = 5 To LastRow
Range("Z" & i).Formula = "=ABS(F" & i & " -(J" & i & " *(100/21)))< 5"
'Checks if the value in column F matches the amount in column J for each
'cellin that column with a significance of 5. The return is shown as
'True or False.
Next i
For i = 5 To LastRow
If Range("Z" & i) = True Then Range("F" & i).Interior.Color = RGB(255,
255, 255) Else: Range("F" & i).Interior.Color = RGB(255, 0, 0)
'If the
'value in column Z is True, then the cell colour in column F is white.
'If False, then red.
Next i
End Sub
'These are the lines on the relevant worksheet:
Private Sub Worksheet_Activate()
Call test
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
Dim LastRow As Long
LastRow = Sheets("Test").Range("F5").End(xlDown).Row
If Not Intersect(Target, Me.Range("F5:F" & LastRow)) Is Nothing Then
Application.EnableEvents = False
Call test
Application.EnableEvents = True
End If
End Sub

The line
LastRow = Sheets("blad1").Range("F5").End(xlDown).Row
is returning a value equal to the absolute very last row possible (1048576) when column F is empty. The rest of your macro is then iterating through the entire sheet executing your code for every single row. You can imagine what happens when you try to insert 1048572 formulas into a spreadsheet. A better option would be to use
LastRow = Sheets("blad1").Range("F" & Rows.Count).End(xlUp).Row
To get the last used row searching from the bottom up. Then you could change your Worksheet_Change logic to
If LastRow > 1 Then
'Code Here
End if
EDIT:
Also worth noting, when LastRow = Sheets("blad1").Range("F5").End(xlDown).Row then this code
If Not Intersect(Target, Me.Range("F5:F" & LastRow)) Is Nothing Then
Application.EnableEvents = False
Call test
Application.EnableEvents = True
End If
will always evaluate True when you're editing values in Column F at any row number greater than row 4 because Intersect() basically says "If Range one and Range two overlap return true". So, Range("F7") is within Range("F5:F1048576") regardless of whether or not it has a value.

Related

Return text in cell based on value entered in another cell

I have columns in my table (F -> I) with potentially unlimited rows, which are drop downs with a simple Yes/No list.
It starts as an empty row and the user inputs data in other rows and then selects either Yes/No based on the questions.
What I'm looking for is some VBA to say If the user has selected 'No' in Column F, then in Column K, prepopulate with "Column F: ". The idea is that anything that is selected as "No", is populated in K so the user can add their notes and reference Column F. For example: "Column F: This did not meet requirements because xxxxx"
I have tried a few examples whilst searching the net but nothing seems to work:
R = 4
'loop to the last row
Do Until Range("F" & R) = ""
'check each cell if if contains 'apple' then..
'..place 'Contains Apple' on column B
If InStr(1, Range("F" & R), "No") Then
Range("K" & R) = "Test Plan"
End If
R = R + 1
Loop
I also tried putting that in a worksheet change sub but it didn't work as expected.
Any help appreciated. Thanks
Is this what you are trying? I have commented the code. For more explanation, I would recommend going through How to handle Worksheet_Change
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Dim aCell As Range
'~~> Error handling
On Error GoTo Whoa
'~~> Switch off events
Application.EnableEvents = False
'~~> Check of the change happened in Col F
If Not Intersect(Target, Columns(6)) Is Nothing Then
'~~> Loop through all the cells in col F where
'~~> the change happened
For Each aCell In Target.Cells
'~~> Check if the value is NO
If UCase(aCell.Value2) = "NO" Then
'~~> Update Col K
Range("K" & aCell.Row).Value = "Test Plan"
Else
'~~> If not NO then WHAT ACTION? For example user
'~~> deletes the existing NO
End If
Next
End If
Letscontinue:
'~~> Switch events back on
Application.EnableEvents = True
Exit Sub
Whoa:
MsgBox Err.Description
Resume Letscontinue
End Sub
In Action
Try this code in the Worksheet_Change
Option Explicit
Option Compare Text
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Column = 6 And Target.Value = "No" Then
Target.Parent.Range("K" & Target.Row).Value = "Column F: "
End If
End Sub

How to automatically delete rows that don't match a specific value in Excel?

I am trying to delete rows that don't match a specific value.
My current formula for identifying rows that don't match is
=IF(K3<>L3,"No match","")
This is stored in column "M" of my worksheet entitled Report One.
How do I get Excel to delete the rows automatically where value = "No Match" in column M.
If you genuinely want to delete the rows automatically whenever a cell value in column M changes to "No match", then you'll need a Private Sub Worksheet_Calculate() solution. The following code should give you what you want. It assumes your data starts on row 2.
Option Explicit
Private Sub Worksheet_Calculate()
On Error GoTo GetOut
Application.EnableEvents = False
Dim LastRow As Long, c As Range
LastRow = Cells(Rows.Count, 13).End(xlUp).Row
For Each c In Sheets("Report One").Range("M2:M" & LastRow)
If c.Value = "No match" Then
c.EntireRow.Delete
End If
Next c
Continue:
Application.EnableEvents = True
Exit Sub
GetOut:
MsgBox Err.Description
Resume Continue
End Sub
Loop through the rows and see if the value is "No match" using VBA.
Sub DeleteNoMatch()
LR = ActiveSheet.Cells(ActiveSheet.Rows.Count, "M").End(xlUp).Row ' last row
for r = LR to 1 step -1 ' adjust the 1 to what row to start at. (header on the table?)
if range("M" & r).value = "No match" then Range(r & ":" & r).EntireRow.Delete
next r
end sub
You may want to change the column "M" in the LR row to something else.
If you have a table with 100 lines of data but the column M extends to row 1000 then 900 lines (at least) will be deleted.
Either you change your formula to not give no match when it's empty or change what column to determin the column to "count"

Range of cells in a row being editable/not editable based on an adjacent cell value

What I require:
When the text Yes is entered into eg A2, I would like B2:E2 to become not able to be edited, but the range A3:E7 still editable.
When the text Yes is entered into eg A3, I would like B3:E3 to become not to be edited, in addition to B2:E2 (the row above) not being able to be edited.
The above functionality will continue for approx 100 rows on the sheet.
When the text Yes is removed from a cell eg A2, then only B2:E2 become editable again. When Yes is entered back into the cell they become not able to be edited again.
If cells in the A column are blank, the corresponding columns B:E for that row are editable.
Current State in the image attached:
Cells A2:E7 are unlocked, all other cells in the sheet remain locked
The remainder of the sheet is protected without a password/select unlocked cells only (but could contain a password if required)
Code below has to be entered in a Worksheet object, under VBA project properties. I have tested the code and it seems to be working. Please report if any problem comes up. If you want to extend the range on which the code works, simply edit currSheet.Range("A2") to currSheet.Range("A100") etc.
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Dim currSheet As Worksheet
Set currSheet = ThisWorkbook.ActiveSheet
'declare source range - can be a cell, a range of cells, entire column, entirerow etc.
Dim sourceCell As Range
Set sourceCell = currSheet.Range("A2")
'declare range that will be locked/unlocked
Dim tarRange As Range
Set tarRange = currSheet.Range("B2:E2")
Application.ScreenUpdating = False
currSheet.Unprotect
If Target = sourceCell Then
set_cellLockStatus sourceCell, "Yes", tarRange, True
End If
Application.ScreenUpdating = True
currSheet.Protect
End Sub
Public Sub set_cellLockStatus(source_rng As Range, sourceValueCondition As Variant, target_rng As Range, lockCell As Boolean)
'take source range, check if source condition is met - if yes, lock or unlock target range
source_rng.Locked = False
With source_rng
If source_rng.Value2 = sourceValueCondition Then
target_rng.Locked = lockCell
target_rng.Interior.ColorIndex = 8
Else:
target_rng.Locked = Not lockCell
target_rng.Interior.ColorIndex = 0
End If
End With
End Sub
Another solution. Code in Worksheet - Change event :
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Column = 1 Then ' column A changed
ThisRow = Target.Row 'get row number
If Target.Value = "Yes" Then
ThisWorkbook.ActiveSheet.Unprotect 'unprotect sheet
Range("B" & ThisRow).Locked = True
Range("C" & ThisRow).Locked = True
Range("D" & ThisRow).Locked = True
Range("E" & ThisRow).Locked = True
ThisWorkbook.ActiveSheet.Protect 'protect sheeet
Else
ThisWorkbook.ActiveSheet.Unprotect
Range("B" & ThisRow).Locked = False
Range("C" & ThisRow).Locked = False
Range("D" & ThisRow).Locked = False
Range("E" & ThisRow).Locked = False
ThisWorkbook.ActiveSheet.Protect
End If
End If
End Sub

How to lock (make read-only) a specific range based on a cell value?

I'm working on a planning monitoring tool. I need to lock a range of cells based on a cell value.
I would like when the value in column "Q" is "Confirmed", then cells on the same row from Column M to Q are locked.
Sub planning_blocker()
Dim last_row As Integer
' Compute the last row
last_row = Worksheets("Planning").Cells(Rows.Count, 1).End(xlUp).Row
Debug.Print last_row
For i = 3 To last_row
If Worksheets("Planning").Cells(i, 17).Value = "" Then
Sheets("Planning").Range("M" & i & ":" & "P" & i).Locked = False
Else
Sheets("Planning").Range("M" & i & ":" & "P" & i).Locked = True
End If
Next i
Sheets("Planning").Protect Password:="User"
End Sub
This works partially because:
it locks the entire row where "confirmed" is detected and not only the range
it consider only the first row where "confirmed" is detected and not the remaining ones (if more than one row is marked with "confirmed", only the first row is blocked).
i tested your code and it works for me (Excel2016). the ranges (M:P) are locked if 17th column (col Q) of current row isn't empty. don't no what could be your problem here...
Well, if you need to watch the status column for changes, I would suggest to use the Sub Worksheet_Change. this will trigger your code every time something changes in your sheet.
I made some changes to adapt your code and here is the result:
Sub Worksheet_Change(ByVal target As Range)
Dim intesection As Range
Dim rowIndex As Integer
Set intesection = Intersect(target, Range("Q:Q"))
'watch changes in intersection (column Q)
If Not intesection Is Nothing Then
'get row index of changed status
rowIndex = Range(intesection.Address(0, 0)).Row
If Cells(rowIndex, 17).Value = "" Then
'unlock if status is blank
ActiveSheet.Range("M" & rowIndex & ":" & "P" & rowIndex).Locked = False
Call ActiveSheet.Protect(UserInterfaceOnly:=True, Password:="User")
Else
'lock if not blank
ActiveSheet.Range("M" & rowIndex & ":" & "P" & rowIndex).Locked = True
Call ActiveSheet.Protect(UserInterfaceOnly:=True, Password:="User")
End If
End If
End Sub
And you need to add this to the sheet where you have the table you want to lock/unlock.
Something like this:
Sources:
How to Lock the data in a cell
How to Tell if a Cell Changed

Hide table rows *unless* any of 3 columns (in that row) are not blank

I've built this code, and it's working fine. However I expect there must be a more elegant way to embed the range 'c' into the Evaluate function rather than how I've used 'r' to determine the row number, and build that into the reference.
(I'm learning). Copy of (very stripped down) xlsm available here: https://www.dropbox.com/s/e6pcugqs4zizfgn/2018-11-28%20-%20Hide%20table%20rows.xlsm?dl=0
Sub HideTableRows()
Application.ScreenUpdating = False
Dim c As Range
Dim r As Integer
For Each c In Range("ForecastTable[[Group]:[Item]]").Rows
r = c.Row
If Application.Evaluate("=COUNTA(B" & r & ":D" & r & ") = 0") = True Then
c.EntireRow.Hidden = True
Else: c.EntireRow.Hidden = False
End If
Next c
Application.ScreenUpdating = True
End Sub
There's no specific question/problem, but here's my suggested code improvements.
Most notably, I wouldn't execute the Hidden procedure until you have all the rows. That way you don't have repeatedly do something that only need be completed once. This will always be the best practice when looping and manipulating data. Make changes to the sheet AFTER you have identified the range.
With the above change, you don't need to turn off ScreenUpdating.
The Evaluate function is fine, but isEmpty is probably the best option. There are probably slightly faster methods, perhaps checking multiple if-statements, but that's getting into fractions of a second over thousands of rows (probably not worth researching).
Technically you don't really need to loop by rows. You can get by with a single cell in a row, then checking the next two over, see utilization of Offset to generate that range. This also creates a more dynamic than using hard-coded columns ("A"/"B"...etc")
Long is recommended over Integer but this is pretty small, and I'm only mentioning it because I posted about it here.. Technically you don't even need it with the above changes.
Here's the code:
Sub HideTableRows()
Dim c As Range, hIdeRNG As Range, WS As Worksheet
'based on OP xlsm file.
Set WS = Sheet4
'used range outside of used range to avoid an if-statement on every row
Set hIdeRNG = WS.Cells(Rows.Count, 1)
'loops through range of single cells for faster speed
For Each c In Range("ForecastTable[Group]").Cells
If IsEmpty(Range(c, c.Offset(0, 2))) = 0 Then
'only need a single member in the row.
Set hIdeRNG = Union(hIdeRNG, c)
End If
Next c
'Hides rows only if found more than 1 cell in loop
If hIdeRNG.Cells.Count > 1 Then
Intersect(WS.UsedRange, hIdeRNG).EntireRow.Hidden = True
End If
End Sub
Final Thought: There's some major enhancements coming out to Excel supposedly in early 2019 that might be useful for this type of situation if you were looking for a non-VBA solution. Click here for more info from MS.
Flipping the logic a bit, why not just filter those three columns for blanks, then hide all the visible filtered blank rows in one go?
Something like this:
Sub DoTheHide()
Dim myTable As ListObject
Set myTable = Sheet4.ListObjects("ForecastTable")
With myTable.Range
.AutoFilter Field:=1, Criteria1:="="
.AutoFilter Field:=2, Criteria1:="="
.AutoFilter Field:=3, Criteria1:="="
End With
Dim rowsToHide As Range
On Error Resume Next
Set rowsToHide = myTable.DataBodyRange.SpecialCells(xlCellTypeVisible)
On Error GoTo 0
myTable.AutoFilter.ShowAllData
If Not rowsToHide Is Nothing Then
rowsToHide.EntireRow.Hidden = True
End If
End Sub
Since c is used to iterate over the rows and each row contains the 3 cells in question ("=COUNTA(B" & r & ":D" & r & ") = 0") is equivalent to ("=COUNTA(" & c.Address & ") = 0"). But using the WorksheetFunction directly is a better appraoch.
It should be noted that Range("[Table]") will return the proper result as long as the table is in the ActiveWorkbook. It would be better to useThisWorkbook.Worksheets("Sheet1").Range("[Table]")`.
Sub HideTableRows()
Application.ScreenUpdating = False
Dim row As Range, target As Range
With Range("ForecastTable[[Group]:[Item]]")
.EntireRow.Hidden = False
For Each row In .rows
If Application.WorksheetFunction.CountA(row) = 0 Then
If target Is Nothing Then
Set target = row
Else
Set target = Union(target, row)
End If
End If
Next
End With
If Not target Is Nothing Then target.EntireRow.Hidden = True
Application.ScreenUpdating = True
End Sub

Resources