How to change value of the active cell when clicked using VBA? - excel

I am not sure whether this is running, has errors, or whether the variables are correct. I followed steps online to check what my variables are, like typing ?variable in the immediate window, checking the locals window, and hovering my mouse over the variable, but nothing comes up.
Nothing happens regardless when I go back to the workbook.
Here's a screenshot:
Included a screenshot because the problem might not be just with the code.
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Target.address = "C5:I5" Then
Dim row As Integer
row = Application.WorksheetFunction.Match(ActiveCell.Offset(0, -1).Value, Range("$n$1:$n$365"), 0)
Dim address As Long
address = Application.WorksheetFunction.address(row, 15)
Range(address).Value = Range(address).Value + 1
ActiveCell.Value = Range(address).Value
End If
End Sub
The purpose is to add 1 to the value of the active cell when clicked. The cell's value will change based on the date in the cell directly above it; the value needs to be tied to the date. I plan to accomplish this using a hidden array of ascending dates and values, located at n1:o365.

(a) Probably your intention is to check if the target cell is within the range "C5:I5" - what your checking is if target has the address "C5:I5" so the if fails.
Use for example the function Intersect for that
(b) (Minor thing) Declare row as Long
(c) There is no .WorksheetFunction.address function. A Range has an Address property, eg Target.Address. Note that this will return a String, not a Long. But you don't need this anyhow. Use Cells if you know row and column of a cell.
Note that I haven't checked your logic to find the correct row.
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Intersect(Target, Range("C5:I5")) Is Nothing Then Exit Sub
Dim row As Long
On Error Resume Next
row = Application.WorksheetFunction.Match(Target.Offset(0, -1).Value, Range("$n$1:$n$365"), 0)
On Error GoTo 0
If row = 0 Then Exit Sub ' Row not found
Dim cell As Range
Set cell = Cells(row, 15)
cell.Value = cell.Value + 1
Target.Value = cell.Value
End Sub

Related

Running a Worksheet_change function when closing the workbook

I want to run a Worksheet_change function that will collect the cell references of any changed cells into an array of "Cells" objects but I keep getting the error "Type mismatch". This is what i've got so far:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim arArray(1 To 70) As Range
Dim K As Integer
K = 1
For i = 1 To 70
For j = 2 To 14
If Target.Column = j And Target.Row = i Then
Set arArray(K) = Target.Address
K = K + 1
End If
Next j
Next i
End Sub
Currently the code looks for any changes within the grid B1 to N70 and stores the changed cell if a change has occurred to a cell within that grid.
Any help would be greatly appreciated.
Right now, your code is set to look over many cells every time any cell changes. Based on your initial description, I'm sure that this is not what you really want. In the following code, Worksheet_Change keeps track of each cell that gets changed in B1:N70 by putting its address in a collection named "changed_cells". While "show_changes" prints the address of the cells that got changed to the immediate window.
Option Explicit
Dim changed_cells As New Collection
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Range("B1:N70"), Target) Is Nothing Then
changed_cells.Add Target.Address(False, False)
End If
End Sub
Private Sub show_changes()
Dim x As Long
For x = 1 To changed_cells.Count
Debug.Print changed_cells(x)
Next
End Sub
Note: If the immediate window is not visible, press ctrl+g to see the ouptut
You declared an array of Range objects at the top and the Target.Address property returns a string.
Your line
Set arArray(K) = Target.Address
Should be
Set arArray(K) = Range(Target.Address)

Can a VBA function in excel be assigned to always be tied to a Range?

I have simple code that clears out the "State" cell whenever the "Payroll Country" cell changes. For example if the user selects "USA" in A6 and then selects "Arizona" in X6, then maybe later for some reason they change their mind and want to pick "CAN" for the country, the state cell will clear out.
But if someone in the future decides to insert a column before the X column, it will obviously move my State column over. Is there a way to make the VBA smarter (or make me smarter) so that the function will be tied to the "State" column rather than the specific "X" column?
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.CountLarge > 1 Then Exit Sub 'CountLarge handles larger ranges...
'check Target column and row...
If Target.Column = 1 And Target.Row >= 6 Then
With Target.EntireRow
'State column
.Columns("X").Value = ""
End With
End If
End Sub
You can use a named range, or you can use .Find to determine where your State column currently is. Here is an example using .Find
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.CountLarge > 1 Then Exit Sub 'CountLarge handles larger ranges...
'check Target column and row...
If Target.Column = 1 And Target.Row >= 6 Then
Dim StateCol As Long
StateCol = Me.Range("1:5").Find("State", LookIn:=xlValues, LookAt:=xlPart).Column
With Target.EntireRow
'State column
.Columns(StateCol).Value = ""
End With
End If
End Sub
If you were to use a named range instead, you can define StateCol using StateCol = Me.Range("NamedRange").Column, it would be a little bit faster, since it doesn't need to search the row each time the user changes a value.
Side Note: The search range for .Find is Rows 1 to 5, but you may want to restrict or expand that range based on how you expect the data to move.
I (always) define an enum for the columns, like this
Public enum col_TableXXX 'adjust to your needs
col_ID = 1
col_PayrollCountry
col_State
end enum
enums are numbered automatically - so col_PayrollCountry equals to 2, col_State equals to 3 etc.
In case there are new columns or the order changes you only have to move the enums around or add a new enum.
(You can avoid code typing by transpose-pasting the column titels on an excel sheet and then create the code via formulas)
You can then use the enums like this:
If target.column = col_PayrollCountry then
target.entireRow.columns(col_State) = vbnullstring
End If
This is also much more "readable" than columns("X")
Culprit of this solution: you have to know that the columns changed. It is not an automatism that is based on the columns name.
There is another solution to your question (I was influenced by the short discussion with ACCtionMan regarding the enum-stuff):
If you can insert a table (insert > table) then you can use the listobject. Among a lot of other advantages you can reference the column by its name.
I assume that the table is named "tblData"
Private Sub Worksheet_Change(ByVal Target As Range)
Dim lo As ListObject
Set lo = Me.ListObjects("tblData")
If Not Intersect(Target, lo.ListColumns("Payroll Country").DataBodyRange) Is Nothing Then
'changed cell is in Payroll country column then
'take the intersection of the targets row and the State column to change the value
Intersect(Target.EntireRow, lo.ListColumns("State").DataBodyRange) = vbNullString
End If
End Sub
But I would prefer the following solution - because I like to have business logic in the event handlers.
If a collegue of you (or even you in 6 months) looks into the change-event code he/she will immediately understand what is happening here - without reading how it is done.
Option Explicit
Private m_loData As ListObject
Private Sub Worksheet_Change(ByVal target As Range)
'if target cells is not within loData we don't need to check the entry
If Intersect(target, loData.DataBodyRange) Is Nothing Then Exit Sub
If ColumnHeaderOfRange(target) = "Payroll Country" Then
resetStateColumnToEmptyValue target
End If
End Sub
Private Sub resetStateColumnToEmptyValue(c As Range)
Intersect(c.EntireRow, loData.ListColumns("State").DataBodyRange) = vbNullString
End Sub
'this could go in a general module - then add listobject as parameter
Private Function ColumnHeaderOfRange(c As Range) As String
On Error Resume Next ' in case c is outside of listobject
ColumnHeaderOfRange = Intersect(c.Cells(1, 1).EntireColumn, loData.HeaderRowRange)
On Error GoTo 0
End Function
'this could be public then you can access the table from outside the worksheet module
Private Function loData() As ListObject
If m_loData Is Nothing Then
Set m_loData = Me.ListObjects("tblData")
End If
Set loData = m_loData
End Function

Add a Value Automatically to a Cell Based on Another Cell

I want to add a value to a cell based on another with VBA but I'm not sure how. I already searched on internet about it but can't find anything.
I have a table, and on the Column C, if any cell contains the text "MAM" (because it might have MAM-565), then change the value from Cell A to "Wrong", but if it contains "NAC", then change value to "Correct". It should be in the same row as the text found.
Also, I want to add the date automatically to cell E every time Cell in D is filled.
This the code I have already:
Private Sub Worksheet_Change(ByVal Target As Range)
'Add Issue Type'
Dim Code As Range
Set Code = Range("C2:C100000")
For Each Cell In Code
If InStr(1, Cell, "NAC") Then
Range("A2:A10000").Value = "Correct"
ElseIf InStr(1, Cell, "MAM") Then
Range("A2:A10000").Value = "Wrong"
End If
Next
End Sub
This how my table looks like:
Table
Thanks in advance guys :)
To automatically add the datestamp:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rng as Range
Set rng = Intersect(Target, Me.Range("D:D"))
If rng Is Nothing Then Exit Sub
On Error GoTo SafeExit
Application.EnableEvents = False
Dim cell as Range
For Each cell in rng
If Not IsEmpty(cell) Then ' don't do anything if cell was cleared
cell.Offset(,1).Value = Date
End If
Next
SafeExit:
Application.EnableEvents = True
End Sub
As far as the Correct/Wrong, this can easily be done with a formula (ISNUMBER(SEARCH(...)). I don't see the need for VBA here.
Even better, create a table using Ctrl+T. Excel will automatically add the formula in column A in new rows.

Using a Worksheet_Change target value that is a formula output not a manual input

I am writing code to clear the contents of a cell if another cell equals 2 different values. The first Target Range value is a formula pulling in the number from another sheet. The second target value is a manual input.
The second part of this code works, but not the first part. If I change the value in D9 to a manual input then the first part of the code works.
What do I need to change so it recognizes the value brought in from another sheet?
If Not Intersect(Target, Range("D9")) Is Nothing Then
If Target = 1 Then
Range("D23").ClearContents
Range("E23").ClearContents
Range("F23").ClearContents
ElseIf Target = 2 Then
Range("D23").ClearContents
Range("E23").ClearContents
Range("F23").ClearContents
End If
ElseIf Not Intersect(Target, Range("D11")) Is Nothing Then
If Target = 1 Then
Range("E13").ClearContents
Range("F13").ClearContents
Range("E23").ClearContents
Range("F23").ClearContents
ElseIf Target = 2 Then
Range("F13").ClearContents
Range("F23").ClearContents
End If
End If
Intersect only gives you a value if the interactions is done in the range you insert:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Intersect(Target, Columns(1)) Is Nothing Then
ThisWorkbook.Sheets(1).Range("D1").Value = "The active cell does NOT Intersect"
ThisWorkbook.Sheets(1).Range("D2").Value = Target
Else
ThisWorkbook.Sheets(1).Range("D1").Value = "The active cell does Intersect"
ThisWorkbook.Sheets(1).Range("D2").Value = Target
End If
End Sub
if you want to take the range from another sheet you need to write: sheets("YOUR_SHEETNAME").Range("D11").
you are right now looking if the cell selected is cell D9 or D11 in one sheet

Excel detecting and keeping track of (value) changes in any worksheet

I've managed to write a code that detects value changes of particular cells in any worksheet, but I've struggled to construct something that detects and keeps track of ranged (value) changes.
For example, if a user decides to copy and paste some range of data (lets say more than 1 cell), it will not get caught by the macro. Same goes for a user selecting a range and then manually entering values into each cell while range is still selected.
My current code is constructed of 2 macros, the first runs anytime a worksheet selection change occurs and it stores the target.value into a previous value variable. The second macro runs anytime a worksheet change occurs and it tests if the targeted value is different than the previous one, if so it then notifies the user of the change that had occurred.
OK I don't really see anything here which covers the whole thing, so here's a rough attempt.
It will handle single or multi-cell updates (up to some limit you can set beyond which you don't want to go...)
It will not handle multi-area (non-contiguous) range updates, but could be extended to do so.
You likely should add some error handling also.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim Where As String, OldValue As Variant, NewValue As Variant
Dim r As Long, c As Long
Dim rngTrack As Range
Application.EnableEvents = False
Where = Target.Address
NewValue = Target.Value
Application.Undo
OldValue = Target.Value 'get the previous values
Target.Value = NewValue
Application.EnableEvents = True
Set rngTrack = Sheets("Tracking").Cells(Rows.Count, 1).End(xlUp).Offset(1, 0)
'multi-cell ranges are different from single-cell ranges
If Target.Cells.CountLarge > 1 And Target.Cells.CountLarge < 1000 Then
'multi-cell: treat as arrays
For r = 1 To UBound(OldValue, 1)
For c = 1 To UBound(OldValue, 2)
If OldValue(r, c) <> NewValue(r, c) Then
rngTrack.Resize(1, 3).Value = _
Array(Target.Cells(r, c).Address, OldValue(r, c), NewValue(r, c))
Set rngTrack = rngTrack.Offset(1, 0)
End If
Next c
Next r
Else
'single-cell: not an array
If OldValue <> NewValue Then
rngTrack.Resize(1, 3).Value = _
Array(Target.Cells(r, c).Address, OldValue, NewValue)
Set rngTrack = rngTrack.Offset(1, 0)
End If
End If
End Sub
"Undo" part to get the previous values is from Gary's Student's answer here:
Using VBA how do I detect when any value in a worksheet changes?
This subs will work for you but you have just implement codes in every sheet manually. Just need to copy paste. See below screenshot which is for 1 sheet Sheet1
(1) Declare a public variable.
Public ChangeTrac As Variant
(2) Write below codes in Worksheet_SelectionChange event
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
ChangeTrac = Target.Value
End Sub
(3) write below codes in Worksheet_Change event
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Application.Intersect(Target, Cells()) Is Nothing Then
If ChangeTrac <> Target.Value Then
MsgBox "Value changed to Sheet1 " & Target.Address & " cell."
Range(Target.Address).Select
End If
End If
End Sub
Then test by changing data in any cell. It will prompt if any cell value is changed.

Resources