I am an Excel-VBA newcomer and I am trying to write a short piece of code which is triggered by somebody changing the value of cells in a worksheet. It should set the value of the changed cell to zero if it's less than zero. The code looks like this:
Private Sub Worksheet_Change(ByVal Target As Range)
'Debug.Print Target.Address
If Target.Column = 6 Then
For Each Cell In Target.SpecialCells(xlCellTypeConstants, 3)
If Cell.Value < 0 Then
Cell.Value = 0
End If
Next
End If
End Sub
Now what happens is that when I change the value of any cell in column 6, every cell in the sheet containing numbers less than zero is also changed to zero.
I was thinking that the "Target" Object that is created for the Worksheet_Change event handler would only contain the cell/cells changed, and thus my code would only change the value of cells that were modified and triggered the event in the first place.
I tried to help myself by using Debug.Print to output the address of the object. It printed out the address of every cell in the sheet with a value less than zero, so I assume the handler ran several times.
I actually found a workaround for the problem itself but my questions are these: how did I fail in using the Worksheet_Change event and what can I do in the future to not have such problems?
EDIT 1: Updated code with bug fix suggested in comments
EDIT 2: Updated code with error handling to deal with Array Formulae
In answer to your question
[1] how did I fail in using the Worksheet_Change event and [2] what can I do in the future to not have such problems?
Technically you didn't
Nothing for this type of problem, although the following rule helps in general
You are correct in your understanding of the Targetobject. Your code failed because SpecialCells doesn't like a single cell to operate on. Give it one and it expands it to the entire sheet! Give it anything else and it works just fine.
The reason your Debug.Print displays all the cells is that every time your code changes a cell, another change event is triggered. Luckily for you the second one finds a zero so it doesn't trigger another one. The following general rule should help avoid a lot of problems, just not this particular one:
Always surround any code in an event handler that changes any part of the workbook with Application.EnableEvents.
To fix you code so it works, simply remove the SpecialCells method call. Since you are using Cell.Value instead of the highly recommended Cell.Value2 (see here), VBA implicit type converts numbers formatted as text to actual numbers for you. Thus the code works both on numeric and text values.
Code:
Private Sub Worksheet_Change(ByVal Target As Range)
'Debug.Print Target.Address;
Application.EnableEvents = False
For Each Cell In Target '.SpecialCells(xlCellTypeConstants, 3)
If Cell.Column = 6 And Cell.Value < 0 Then
On Error GoTo Error:
Cell.Value = 0
On Error GoTo 0
End If
Next
GoTo ExitSub:
Error:
If Err.Number = 1004 Then ' 1004 -> "You cannot change part of an array."
'Application.Undo ' Uncomment to disallow array entering a negative value formula into/across column 6
MsgBox "A cell could not be zeroed as" & vbCr & "it is part of an array formula.", vbExclamation, "Microsoft Office Excel"
On Error GoTo 0
Else
On Error GoTo 0
Resume
End If
ExitSub:
Application.EnableEvents = True
End Sub
Notes:
- Update 1: Code now correctly deals with multi-cell changes.
- Update 2: Code now traps error 1004 to deal with entering Array Formulae. It can either allow the array formula to be entered resulting in negative values in column 6, or stop the entry altogether.
This works (Updated)
Private Sub Worksheet_Change(ByVal Target As Range)
Dim c As Range
For Each c In Target
If c.Column = 6 Then If IsNumeric(c) Then If c < 0 Then c = 0
Next c
End Sub
And please, learn and use OPTION EXPLICIT !
I came here looking for a way to stop my worksheet from "flashing" as it ran code to format columns and rows, due to users changing data anywhere in the sheet, including Filters and Sorting.
Aside, from the official answer to the OP's problem, If you're trying to control the Worksheet_Change() event from firing at all (it will still fire but you can bypass it), in circumstances where you want users to change data for example, but not areas around the data, such as fields or labels as part of your VBA system, I use the Target.Row element to determine my limits. Combined with Target.Column, and you can hone-in on specific cells that are free for the user to control.
i.e.
Dim myTopLimit AS Integer
myTopLimit = 6
etc..etc
if Target.Row < myTopLimit and Target.Row > myBottomLimit and Target.Column < myLeftLimit and Target.Column > myRightLimit then
.... code to allow WorkSheet_Change() to do somthing
else
.... code to force WorkSheet_Change() to drop through or no code
End If
Related
I'm evaluating a column of cells, for example A:A, and every time a value within a cell changes (not including the initial value), I want to be able to log the change to the appropriate cell of another column, say B:B.
The following is a pair of before and after screenshots demonstrating what is required:
A2has been updated once, so B2 should show a count of 1 and A6 has been updated twice, so B6 should show 2.
A similar solution can be found here, however this only applies to one cell:
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Address = "$A$1" Then [A2].Value = 1
End Sub
Cell values aren't connected to different sheets and can be hard coded.
None of the solutions in your link are both particularly good and comprehensive enough to modify, in my opinion, so I will be posting a better one.
Also your requirements are also not quite specific enough, so I've made up a few extra ones.
Assumptions:
All the A:A cell values start off empty. (Can be trivially modified to allow non-empty initial values.)
An edit of a cell that results in the same value is still considered a "change". This would normally also include where the cell was initially empty, but my code specifically excludes this edge case.
The "initial" value is the value after the first "change" to the cell. (Can be trivially modified to allow the initial value to be "empty", if that is the actual requirement.)
This is the relatively simple code:
'============================================================================================
' Module : <The appropriate sheet module>
' Version : 1.0
' Part : 1 of 1
' References : N/A
' Source : https://stackoverflow.com/a/47405528/1961728
'============================================================================================
Option Explicit
Private Sub Worksheet_Change _
( _
ByVal Target As Range _
)
Const s_CheckColumn As String = "A:A"
Const s_CountColumn As String = "B:B"
If Intersect(Target, Range(s_CheckColumn)) Is Nothing Then Exit Sub
Dim rngCell As Range
For Each rngCell In Intersect(Target, Range(s_CheckColumn))
With Range(s_CountColumn).Cells(rngCell.Row)
.Value2 = IIf(.Value2 <> vbNullString, .Value2 + 1, IIf(rngCell.Value2 <> vbNullString, 0, vbNullString))
End With
Next rngCell
End Sub
This solution correctly allows for multiple cells to be edited simultaneous, as happens when you do a multi-cell delete/paste or a fill.
Notes:
The initial "change" results in a count of 0. If you wish to hide these zeroes, set a custom number format of #;; for the count column.
The code can be modified to stop a "same value" edit from counting as a "change". One of the simplest ways of doing this is to use an extra column to store the old values.
Caveats:
Undo will no longer work correctly. This can be rectified, but it is not trivial to do so.
Pressing an ESC after starting an edit is required to cancel the edit and not trigger an update.
Put this in your worksheet code after you've entered the initial values, or add code to check if this is the initial value or not.
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Range("A:A")) Is Nothing Then
Range("B" & Target.Row) = Range("B" & Target.Row) + 1
End If
End Sub
I am using a tracker for testing new changes, and when. If a new change is N/A, I don't want to delete it, I want to disable (and turn gray) all the cells that are available to select the date it was completed. But only in that row. I've tried using the following methods but haven't had luck with any:
Conditional Formatting on the sheet
VBA code that executes when a change is made
Data Validation (think this is possible but am unfamiliar with how this really works)
Here is the code I have:
Private Sub worksheet_change(ByVal Target As Range)
Dim keycells As Range
Set keycells = Range("G:G")
lastcol = CInt(Sheet1.Cells(1,Sheet1.Columns.Count).End(xlToLeft).Column)
If Not Application.Intersect(keycells, Range(Target.Address)) Is Nothing Then
r = Range(Target.Address).Row
MsgBox "There was a change"
If Range(Target.Address).Value = "N/A" Then
MsgBox "we got this far"
Range("H" & r & ":" & Cells(r, lastcol).Address).Locked = True
End If
End If
End Sub
Both message boxes show, but the cells don't lock. So I tried unlocking all cells, then protecting the sheet. Then I set something to "N/A" and get the error "Unable to set the Locked property of the Range Class".
Here is an idea of what my data looks like:
Thanks in advance!
Well, Community has been insisting (for days) that I look at this question, relentlessly pushing it to the top of my "relevant" queue, probably because it's tagged with my top 4 tags, and it doesn't technically have an Answer.
So, sorry Moacir, I'm swiping your "commented answer":
Leave it protected,
do Worksheet.Unprotect,
run your code (and Worksheet.Protect) after that.
More Info:
Microsoft : Worksheet.Protect Method (Excel)
Microsoft : Worksheet.Unprotect Method (Excel)
S.O. : How to protect cells in Excel but allow these to be modified by VBA script
I have been trying to work this out myself for the last few days and caught myself in a bit of a one step forward three steps back cycle. I've been reluctant to bother you thinking this would have been answered somewhere else before now.
The idea is that I have a spreadsheet that has criteria in rows with separate entries in rows; in row 6 it is the status of each column entry, which when changed to "Completed" I would like the column to be hidden.
I've been floundering around with Worksheet_Change and been able to hide specific columns, but not the active column.
Any help offered would be much appreciated and I'm sorry if this has been covered elsewhere, but I've not been able to successfully apply any examples out there.
Thanks.
Whenever you have to work with worksheet_change events, you have to consider a cycle for it, due to user may delete multiple data at the same time or do a copy paste, if you only consider "Target" It would give a debugger error.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim ItemMultipleData As Range
For Each ItemMultipleData In Target 'handles multiple cells, paste, del, etc
'your code (instead of using "Target" change to ItemMultipleData. IE:
'If ItemMultipleData.Value = "Completed" Then
Next ItemMultipleData
End Sub
Here is a starting point. It only checks row # 6:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rng As Range
Set rng = Range("6:6")
If Intersect(Target, rng) Is Nothing Then Exit Sub
If Target.Count <> 1 Then Exit Sub
If Target.Value = "Completed" Then
Application.EnableEvents = False
Target.EntireColumn.Hidden = True
Application.EnableEvents = True
End If
End Sub
EDIT#1:
This approach assumes that only one cell at a time is being changed........that makes it easy to "find the active cell"
First off, I'm a noob when it comes to Macros and VBA, so please forgive me if I don't make sense.
I've got an Excel spreadsheet which is basically a list of users and their mobile phone numbers and some other bits (columns A-K are currently used) and it's ordered by rows.
What I need is a way of copying the whole row if I change a cell. So if I change the username, it copies the whole row of that user to the next blank row on a second sheet.
The purpose of this is to keep an audit trail allowing us to see who's previously used a number etc.
I found this: Copy row to another sheet in excel using VBA which is working as intended, but I can't for the life of me get it to a, copy the cells to the next free row, or b, not overwrite the existing entry.
This is the code I'm using:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim a As Range, rw As Range
For Each a In Selection.Areas
For Each rw In a.Rows
If rw.Row >= 2 Then
rw.EntireRow.Copy Sheet2.Cells(2 + (rw.Row - 2) * 3, 1)
End If
Next rw
Next a
End Sub
I'd really appreciate it if someone could help me customise it.
I'm using Excel 2010 on Win7.
Many thank in advance.
Typically the Intersect method is used to determine if the cell or cells receiving a change involve one or more columns that you are concerned with. You can add additional parameters; in this case, I've .Offset the Worksheet.UsedRange property down one row to make sure that row 1 is not involved.
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Columns(1), Me.UsedRange.Offset(1, 0)) Is Nothing Then
On Error GoTo bm_Safe_Exit
Application.EnableEvents = False 'not really necessary in this case but never a bad idea within a Worksheet_Change
Dim a As Range
For Each a In Intersect(Target, Columns(1), Me.UsedRange.Offset(1, 0))
If CBool(Len(a.Value2)) Then _
a.EntireRow.Copy _
Destination:=Sheet2.Cells(Rows.Count, 1).End(xlUp).Offset(1, 0) 'not really sure this is the correct destination
Next a
End If
bm_Safe_Exit:
Application.EnableEvents = True
End Sub
I've included a call to disable event handling for the duration of the Worksheet_Change event macro. While this is a critical step when the Worksheet_Change modifies values, it is not really important to incorporate here. However, it does not harm and is already in place in case you want to augment the Worksheet_Change to include something like a timestamp that would change the values on the worksheet.
I'm doing some work on an already done excel program, but I'm very rusty in this area or probably never had done something in this part, I'm very new in VBA so please don't judge me if it's a simple mistake or error the links are in the description.
So I have problem where if I put the confirmation option, for example: in one cell press "1" in the product you have, and in another cell write "S" if you already have the product and it puts you a date of today in another cell.
The problem is when I delete the info that I inserted, and reenter it the date deformats itself becoming smaller and the location on the cell changes too.
I'm going to put the links because, like I said I'm rusty and I can't find where the code of this date comes.
http://www.docdroid.net/12dh4/master-atual-20155.xls.html -->This one is the Excel
http://www.docdroid.net/12dhj/errorphotos.pdf.html --> photos showing error
this is the code of one of the sheets the other ones are almost the same, If you guys see the photos it would help to understand the error itself.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rng1 As Range
Set rng1 = Intersect(Range("v3:v500"), Target)
If rng1 Is Nothing Then Exit Sub
Application.EnableEvents = False
On Error Resume Next
If rng1.Value <> "" Then
rng1.Offset(0, 2).Value = Now()
Else
rng1.Offset(0, 2).Clear
End If
Application.EnableEvents = True
End Sub
The code would be triggered when you do the changes at column V. You may find the code in the worksheet itself:
it will clear the date if the column V contains no value.
By the way, the date is not getting smaller but it show the date of today in different format. you may check the date format by right click the cell and choose the format cells... option to look at it.