Macro that automatically formats cell when value is entered. (convert macro to the event macro?) - excel

I've got a spreadsheet, where I'd like A:A range to be formatted automatically so that characters will show in red and digits stay the same color. The following macro seems to work OK, but I need to manually run it every time I change value in the cell:
Sub Red_text()
Dim i As Integer
Dim MyString As String
MyString = ActiveCell.Value
For i = 1 To Len(MyString)
If IsNumeric(Mid(MyString, i, 1)) = False Then
ActiveCell.Characters(i, 1).Font.Color = RGB(247, 66, 66)
End If
Next i
End Sub
So basically I need to change it into an event macro that will reformat the current cell every time it is edited. And limit this behavior to A:A range.
Any help would be greatly appreciated!!

First a slight change to your macro:
Sub Red_text(r As Range)
Dim i As Integer
Dim MyString As String
MyString = r.Value
For i = 1 To Len(MyString)
If IsNumeric(Mid(MyString, i, 1)) = False Then
r.Characters(i, 1).Font.Color = RGB(247, 66, 66)
End If
Next i
End Sub
and also include the following event macro in the worksheet code area:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim A As Range
Set A = Range("A:A")
If Intersect(A, Target) Is Nothing Then Exit Sub
Application.EnableEvents = False
Call Red_text(Target)
Application.EnableEvents = True
End Sub
The event macro detects entries to column A and then applies formatting.
EDIT#1:
The event macro must change to handle more than one cell at a time. Remove the original event macro and use this:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim A As Range, rBIG As Range, r As Range
Set A = Range("A:A")
Set rBIG = Intersect(A, Target)
If rBIG Is Nothing Then Exit Sub
Application.EnableEvents = False
For Each r In rBIG
Call Red_text(r)
Next r
Application.EnableEvents = True
End Sub

Related

How can I build For-Next-Loop in Change Event?

I've got a sheet with Data.
I want to calculate the difference between date now and the date which are in cells C3:C10. And the results are stored in cells D3:D10.
That part I got it so far.
But if someone manipulates the values in the result cells then the VBA should recalculate those cells and correct the results.
Private Sub Worksheet_Change(ByVal Target As Range)
For Zeile = 3 To 10
Sheets("Tabelle2").Cells(Zeile, "D") = WorksheetFunction.YearFrac(Sheets("Tabelle2").Cells(Zeile, "C"), Date)
If Sheets("Tabelle2").Cells(Zeile, "C") = 0 Then
Sheets("Tabelle2").Cells(Zeile, "D") = ""
End If
Next Zeile
End Sub
The first thing to do is check if the change has been made in C3:C10, you can use Intersect for that.
Then you should disable events to stop the code triggering itself, use Application.EnableEvents = False for that.
Next loop through Target in case more than one cell has been changed and perform the required actions/calculations.
Finally re-enable events using Application.EnableEvents = True
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rng As Range
Dim cell As Range
Dim Zeile As Long
Set rng = Intersect(Target, Range("C3:C10"))
If Not rng Is Nothing Then
Application.EnableEvents = False
For Each cell In rng.Cells
Zeile = cell.Row
If Cells(Zeile, "C") <> 0 Then
Cells(Zeile, "D") = Application.YearFrac(Cells(Zeile, "C").Value, Date)
Else
Cells(Zeile, "D") = ""
End If
Next cell
Application.EnableEvents = True
End If
End Sub
If you want the code to be triggered if a value is changed in either C3:C10 or D3:D10 change this,
Set rng = Intersect(Target, Range("C3:C10"))
to this.
Set rng = Intersect(Target, Range("C3:D10"))
You can also change the range address there if you want to further rows by changing 10.

Copy Cell to another column on change

I need to copy the contents of a cell in a particular column to another corresponding column on change so the old value is moved. Only wants to work for a particular column.
Private sub Worksheet_Change(ByVal Target As Range)
if Target.Range("L:L") then
'set I cell value = to original L cell value
ActiveCell.Offset(0,-3).Value = ActiveCell.Value
End If
End Sub
This code should do what you want. Please take note of the comments which explain some limitations I have imposed on the action of this procedure. The rule to follow is to not give it more power than it needs to do the job you want it to do.
Private Sub Worksheet_Change(ByVal Target As Range)
' 027
Dim Rng As Range
' don't react if the changed cell is in row 1 or
' if it is more than 1 row below the end of column L
Set Rng = Range(Cells(2, "L"), Cells(Rows.Count, "L").End(xlUp).Offset(1))
If Not Application.Intersect(Target, Rng) Is Nothing Then
With Target
' skip if more than 1 cell was changed
' meaning, exclude paste actions
If .Cells.CountLarge = 1 Then
.Offset(0, -3).Value = .Value
End If
End With
End If
End Sub
This will save the previous value in column I:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim v As Variant
If Target.Count > 1 Then Exit Sub
If Intersect(Range("L:L"), Target) Is Nothing Then Exit Sub
With Application
v = Target.Value
.EnableEvents = False
.Undo
Target.Offset(0, -3).Value = Target.Value
Target.Value = v
.EnableEvents = True
End With
End Sub
EDIT#1:
To update L without triggering the event, use something like:
Sub ScriptThatUpdatesColumn_L()
Application.EnableEvents = False
Range("L5").Value = "just me"
Application.EnableEvents = True
End Sub

How Can You Lock Rows of Cells in Excel Based on Cell Value?

So I know that one can format cells to be locked and then protect a worksheet to prevent that data being overwritten. But I'm looking to be able to dynamically lock cells within a sheet. From doing some Googling I've tried adapting the below block of code for my needs. The intent is that if column A has a value the rest of the row will be locked so no one can overwrite the rest of the row.
Private Sub Worksheet_Change(ByVal Target As Range)
If Intersect(ActiveSheet.Cells(18, 1), Target) Is Not Nothing Then
If ActiveSheet.Cells(18, 1).Text = "X" Then
ActiveSheet.Range(Cells(18, 2), Cells(18, 20)).Locked = True
Else
ActiveSheet.Range(Cells(18, 2), Cells(18, 20)).Locked = False
End If
End If
End Sub
Any help would be much appreciated, as well as tips for succinctly applying this to every row in the sheet.
UPDATE:
Per BigBen's answer I've revised to the following:
Private Sub Workbook_Open()
Sheets(“Sheet8”).Protect Password:="Secret", UserInterFaceOnly:=True
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rng As Range
Set rng = Intersect(Me.Columns(1), Target)
If rng Is Nothing Then Exit Sub
Dim cell As Range
For Each cell In rng
cell.EntireRow.Locked = (cell.Value = "X")
Next
End Sub
But that still doesn't seem to be working...
You need to change the Intersect to test if Target intersects column A, and not a particular cell:
Note also the Not syntax: If Not Intersect... Is Nothing, instead of If Intersect... Is Not Nothing.
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Me.Columns(1), Target) Is Nothing Then
Dim rng as Range
For Each rng in Intersect(Me.Columns(1), Target)
If rng.Value = "X" Then
rng.EntireRow.Locked = True
Else
rng.EntireRow.Locked = False
End If
Next
End If
End Sub
Or perhaps a bit more succinctly:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rng As Range
Set rng = Intersect(Me.Columns(1), Target)
If rng Is Nothing Then Exit Sub
Dim cell As Range
For Each cell In rng
cell.EntireRow.Locked = (cell.Value = "X")
Next
End Sub

How can I spread a sub to a multiple range of cells?

The purpose of this code is to update the date in a cell as a certain cell's contents are changed.
Since this was originally coded inside a sub, I now need to expand this code to a range of multiple cells. Ie. At this moment, the code only takes cell D4 and updates cell L4, I want to be able to drag this function down so it can reach a multiple range of cells; take D5 and update L5 etc.
Here's my code as the sub:
Dim oldValue
Public Sub Worksheet_SelectionChange(ByVal Target As Range)
oldValue = Target.Worksheet.Range("D4").Value
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Target.Worksheet.Range("D4")) Is Nothing Then
If oldValue <> Target.Worksheet.Range("D4").Value Then
Target.Worksheet.Range("L4").Value = Date
End If
End If
End Sub
The problem here, is that I don't know how to properly expand my code to match a further selection of cells. Here's my attempt:
Dim oldValue
Public Sub Worksheet_SelectionChange(ByVal Target As Range)
oldValue = Target.Worksheet.Range("D4", "D21").Value
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Target.Worksheet.Range("D4", "D21")) Is Nothing Then
If oldValue <> Target.Worksheet.Range("D4", "D21").Value Then
Target.Worksheet.Range("L4", "L21").Value = Date
End If
End If
End Sub
EDIT: The sub I have written only applies to one cell, I am trying to work out a way to have it spread out to a certain selection of cells. Ie. D4:D12 which updates the date in L4:L12 accordingly.
If anyone could help me, that would be greatly appreciated.
Try the following code:
Dim oldValue()
Public Sub Worksheet_SelectionChange(ByVal Target As Range)
oldValue = Me.Range("D4:D12").Value
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Me.Range("D4:D12")) Is Nothing Then
Application.EnableEvents = False
Dim c As Range
For Each c In Intersect(Target, Me.Range("D4:D12"))
'Check value against what is stored in "oldValue" (row 4 is in position 1, row 5 in position 2, etc)
If oldValue(c.Row - 3, 1) <> c.Value Then
'Update value in column L (8 columns to the right of column D)
c.Offset(0, 8).Value = Date 'or possibly "= Now()" if you need the time of day that the cell was updated
End If
Next
Application.EnableEvents = True
End If
End Sub
Set up a hidden sheet to hold the old values.
Sub SetupMirrorValues()
With Worksheets.Add
.Name = "MirrorValues"
.visibilty = xlSheetVeryHidden
.Range("D4:D10,D12,D14:D20") = Worksheets("Sheet1").Range("D4:D10,D12,D14:D20")
End With
End Sub
In the Worksheet_Change event handler, you would check the Target cells that intersect with the range you want to monitor. If there are differences then you update the timestamp and the cell on the hidden sheet that corresponds to the changed cell.
Private Sub Worksheet_Change(ByVal Target As Range)
Application.EnableEvents = False
Application.ScreenUpdating = False
Dim cell As Range, DRange As Range
Set DRange = Range("D4:D10,D12,D14:D20")
If Not Intersect(DRange, Target) Is Nothing Then
For Each cell In Intersect(DRange, Target)
If cell.Value <> Worksheets("MirrorValues").Range(cell.Address) Then
cell.EntireRow.Cells(1, "L").Value = Now
Worksheets("MirrorValues").Range(cell.Address) = cell.Value
End If
Next
End If
Application.EnableEvents = True
Application.ScreenUpdating = False
End Sub

Clear the contents of columns B to F if cell A is empty

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

Resources