Auto-Updated Validated Cell When Source Value Changes - excel

I'm trying to update cells that have data validation restrictions on them automatically.
For example - Sheet1 has below column (Column E):
Package Identifier
A
B
C
where the values are taken from the same named column (Column D) in Sheet2.
The below code works for MANUAL changes only
Sheet2 Code:
Private Sub Worksheet_Change(ByVal Target As Range)
Application.ScreenUpdating = False
Dim count_cells As Integer
Dim new_value As String
Dim old_value As String
Dim rng As Range
For count_cells = 1 To Range("D1").CurrentRegion.Rows.Count - 1
Set rng = Worksheets("Sheet1").Range("E3:E86")
If Intersect(Target, Range("D" & count_cells + 1)) Is Nothing Then
Else
Application.EnableEvents = False
new_value = Target.Value
Application.Undo
old_value = Target.Value
Target.Value = new_value
rng.Replace What:=old_value, Replacement:=new_value, LookAt:=xlWhole
Target.Select
End If
Next count_cells
Application.EnableEvents = True
Application.ScreenUpdating = True
End Sub
So, if i manually change value B to Z, all the corresponding values that were B on Sheet1 now change to Z. The problem is, Package Identifier on Sheet2 is dictated by concatenating other columns
=CONCATENATE(B35, "-", "Package", "-", TEXT(C35, "#000"))
This piece of code breaks when trying to use it with the above formula. How can i make this set of code trigger on this formula based output?

Assuming this is how the Validation sheet looks
and this is how the Source sheet looks
Let's say user selects first option in Validation sheet.
Now go back to Source sheet and change 1 to 2 in cell C2.
Notice what happens in Validation sheet
If this is what you are trying then based on the file that you gave, test this code.
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Dim aCell As Range
Dim NewSearchValue As String
Dim OldSearchValue As String
Dim NewArrayBC As Variant
Dim OldArrayA As Variant, NewArrayA As Variant
Dim lRow As Long, PrevRow As Long
On Error GoTo Whoa
Application.EnableEvents = False
If Not Intersect(Target, Range("B:C")) Is Nothing Then
lRow = Range("A" & Rows.Count).End(xlUp).Row
'~~> Store new values from Col A, B and C in an array
NewArrayBC = Range("B1:C" & lRow).Value2
NewArrayA = Range("A1:A" & lRow).Value2
Application.Undo
'~~> Get the old values from Col A
OldArrayA = Range("A1:A" & lRow).Value2
'~~> Paste the new values in Col B/C
Range("B1").Resize(UBound(NewArrayBC), 2).Value = NewArrayBC
'~~> Loop through the cells
For Each aCell In Target.Cells
'~~> Check if the prev change didn't happen in same row
If PrevRow <> aCell.Row Then
PrevRow = aCell.Row
NewSearchValue = NewArrayA(aCell.Row, 1)
OldSearchValue = OldArrayA(aCell.Row, 1)
Worksheets("Validation").Columns(2).Replace What:=OldSearchValue, _
Replacement:=NewSearchValue, Lookat:=xlWhole
End If
Next aCell
End If
Letscontinue:
Application.EnableEvents = True
Exit Sub
Whoa:
MsgBox Err.Description
Resume Letscontinue
End Sub

A different approach from Sid's...
Instead of updating values in the DV cells when the source range changes, this replaces the selected value with a link to the matching cell in the DV source range.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rngV As Range, rng As Range, c As Range, rngList As Range
Dim f As Range
On Error Resume Next
'any validation on this sheet?
Set rngV = Me.Cells.SpecialCells(xlCellTypeAllValidation)
On Error GoTo 0
If rngV Is Nothing Then Exit Sub 'no DV cells...
Set rng = Application.Intersect(rngV, Target)
If rng Is Nothing Then Exit Sub 'no DV cells in Target
For Each c In rng.Cells
If c.Validation.Type = xlValidateList Then 'DV list?
Set rngList = Nothing
On Error Resume Next
'see if we can get a source range
Set rngList = Evaluate(c.Validation.Formula1)
On Error GoTo 0
If Not rngList Is Nothing Then
Application.EnableEvents = False
'find cell to link to
Set f = rngList.Find(c.Value, LookIn:=xlValues, lookat:=xlWhole)
If Not f Is Nothing Then
Application.EnableEvents = False
c.Formula = "='" & f.Parent.Name & "'!" & f.Address(0, 0)
Application.EnableEvents = True
End If
Else
Debug.Print "No source range for " & c.Address
End If
End If
Next c
End Sub

Related

Automatically show date when particular result detected from Excel formula

I am trying with VBA to get the current date on column H (Date).
In column F (Result)
If I manually type Preferred or Non-preferred.
After pressing Enter, today's date will be automatically put on column H (Date)
When I paste formula instead (which will consider data from column A-E to show result on its cell).
Even if the result gives Preferred or Non-preferred the date will not automatically show up.
Unless I press double-click and enter on each result cell then it will show up.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim c As Range
Dim St As String
St = "Preferred|Non-Preferred"
If Not Intersect(Columns("F"), Target) Is Nothing Then
Application.EnableEvents = False
For Each c In Intersect(Columns("F"), Target).Cells
If InStr(1, St, c.Value, vbTextCompare) >= 1 Then
Cells(c.Row, "H").Value = Date
Else
If IsEmpty(c) Then Cells(c.Row, "H").Value = ""
End If
Next c
Application.EnableEvents = True
End If
End Sub
Example
A Worksheet Change: Using Precedents
In column F is a formula that calculates dependent on the values of columns A:E (precedents). The code will trigger when the values in columns A:E are manually changed, possibly modifying the value in column H (see OP's description (requirements)).
The Select Case statement could be reduced to two cases: either it begins with Criteria (1) or it doesn't (Else).
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
On Error GoTo ClearError
Const sFirstCellAddress As String = "F2"
Const dCol As String = "H"
Const Criteria As String = "Preferred"
Dim srg As Range
With Me.Range(sFirstCellAddress)
Set srg = .Resize(Me.Rows.Count - .Row + 1)
End With
Dim irg As Range: Set irg = Intersect(srg.Precedents, Target)
If irg Is Nothing Then Exit Sub
Set srg = Intersect(irg.EntireRow, Columns(srg.Column))
Dim drg As Range: Set drg = srg.EntireRow.Columns(dCol)
Application.EnableEvents = False
Dim dCell As Range
Dim n As Long
For Each dCell In drg.Cells
n = n + 1
Select Case InStr(1, CStr(srg.Cells(n).Value), Criteria, vbTextCompare)
Case 0
dCell.Value = Empty ' criteria not found
Case 1
dCell.Value = Date ' begins with criteria
Case Else
dCell.Value = Empty ' contains criteria
End Select
Next dCell
SafeExit:
If Not Application.EnableEvents Then Application.EnableEvents = True
ProcExit:
Exit Sub
ClearError:
Debug.Print "Run-time error '" & Err.Number & "': " & Err.Description
Resume SafeExit
End Sub

change the range of function automatically when rows are added

[1
I wrote =sum(A2:A11) in cell A1, and I wrote random numbers in A2:A11. Then I deleted some rows and then the A1 cell's range changed automatically. But I don't understand why the range does not change automatically when I add new rows and intert new values. How can I make it change automatically? Do I have to use vba to do this?
A Worksheet Change Event: Monitor Change in Column's Data
I personally would go with JvdV's suggestion in the comments.
On each manual change of a cell, e.g. in column A, it will check the formula
=SUM(A2:ALastRow) in cell A1 and if it is not correct it will overwrite it with the correct one.
You can use this for multiple non-adjacent columns e.g. "A,C:D,E".
Nothing needs to be run. Just copy the code into the appropriate sheet module e.g. Sheet1 and exit the Visual Basic Editor.
Sheet Module e.g. Sheet1 (not Standard Module e.g. Module1)
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
UpdateFirstRowFormula Target, "A"
End Sub
Private Sub UpdateFirstRowFormula( _
ByVal Target As Range, _
ByVal ColumnList As String)
On Error GoTo ClearError
Dim ws As Worksheet: Set ws = Target.Worksheet
Dim Cols() As String: Cols = Split(ColumnList, ",")
Application.EnableEvents = False
Dim irg As Range, arg As Range, crg As Range, lCell As Range
Dim n As Long
Dim Formula As String
For n = 0 To UBound(Cols)
With ws.Columns(Cols(n))
With .Resize(.Rows.Count - 1).Offset(1)
Set irg = Intersect(.Cells, Target.EntireColumn)
End With
End With
If Not irg Is Nothing Then
For Each arg In irg.Areas
For Each crg In arg.Columns
Set lCell = crg.Find("*", , xlFormulas, , , xlPrevious)
If Not lCell Is Nothing Then
Formula = "=SUM(" & crg.Cells(1).Address(0, 0) & ":" _
& lCell.Address(0, 0) & ")"
With crg.Cells(1).Offset(-1)
If .Formula <> Formula Then .Formula = Formula
End With
End If
Next crg
Next arg
Set irg = Nothing
End If
Next n
SafeExit:
If Not Application.EnableEvents Then Application.EnableEvents = True
Exit Sub
ClearError:
Debug.Print "Run-time error '" & Err.Number & "': " & Err.Description
Resume SafeExit
End Sub
Use a nested function as below:
=SUM(OFFSET(A2,,,COUNTA(A2:A26)))

Private sub update date automatically when value in a cell changes

Im trying to automatically update current date in cell T when text in cell Q is "won" and a value in cell AM is > 0. I tried the code below and it is working if first the value in cell is > 0 and then you update the text in cell Q BUT if you do it in another way (first update cell Q and secondly the value in cell AM) the date doesn't appear in cell T.
Any idea, what Im I missing?
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, [Q:Q]) Is Nothing Then
If UCase(Target) = UCase("won") And Target.Offset(, 22) > 0 Then
Target.Offset(, 2) = Int(Now())
End If
End If
End sub
Your code only checks for changes in Q therefore the update does not take place if you change AM first.
My solution has three parts:
use constants for the columns - in case there are changes to the sheet layout you only have to make adjustments here
worksheet_change: only check if one of the columns is affected then call the according sub - by that the reader of the code immediately understands what is going on here
the main routine that inserts the date if condition is met or removes the date if not (maybe you want to adjust this)
Option explicit
Private Const colStatus As String = "Q"
Private Const colValue As String = "AM"
Private Const colDateWon As String = "S"
Private Sub Worksheet_Change(ByVal Target As Range)
Dim c As Range
Set c = Target.Cells(1, 1)
If c.Column = Me.Columns(colStatus).Column Or c.Column = Me.Columns(colValue).Column Then
updateDateWon c.row
End If
End Sub
Private Sub updateDateWon(row As Long)
'--> adjust the name of the sub to your needs
Dim valueToInsert As Variant
With Me
If .Range(colStatus & row) = "won" And .Range(colValue & row) > 0 Then
valueToInsert = Int(Now)
Else
'reset the date in case conditions are not met
valueToInsert = vbNullString
End If
Application.EnableEvents = False 'disable events so that change-event isn't called twice
.Range(colDateWon & row) = valueToInsert
Application.EnableEvents = True
End With
End Sub
A Worksheet Change Applied to Two Non-Adjacent Columns
You need to monitor columns Q and AM for changes.
You need to account for Target being multiple adjacent and non-adjacent cells.
You need to disable events when writing to the worksheet containing this code to not retrigger this event (or trigger any other events).
It is good practice to ensure the re-enabling of events (by using error-handling).
You can combine the cells to be written to (dCell) into a range (drg) and write the stamp in one go.
Int(Now()) or Int(Now) is actually Date.
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
On Error GoTo ClearError
Const sColsAddress As String = "Q:Q,AM:AM"
Const dCol As String = "T"
Const fRow As Long = 2 ' (e.g. 2 for excluding headers in the first row)
Const sCriteria As String = "won"
Dim srg As Range
With Range(sColsAddress)
Set srg = Intersect(.Cells, Rows(fRow).Resize(Rows.Count - fRow + 1))
End With
Dim sirg As Range: Set sirg = Intersect(srg, Target)
If sirg Is Nothing Then Exit Sub
Dim sirg1 As Range: Set sirg1 = Intersect(sirg.EntireRow, srg.Areas(1))
Dim siCol2 As Long: siCol2 = srg.Areas(2).Column
'Dim dirg As Range: Set dirg = sirg1.EntireRow.Columns(dCol) ' not used
Dim siCell1 As Range
Dim siValue2 As Variant
Dim drg As Range
For Each siCell1 In sirg1.Cells
If StrComp(CStr(siCell1.Value), sCriteria, vbTextCompare) = 0 Then
siValue2 = siCell1.EntireRow.Columns(siCol2).Value
If IsNumeric(siValue2) Then
If siValue2 > 0 Then
If drg Is Nothing Then
Set drg = siCell1.EntireRow.Columns(dCol)
Else
Set drg = Union(drg, siCell1.EntireRow.Columns(dCol))
End If
End If
End If
End If
Next siCell1
If Not drg Is Nothing Then
' Prevent retriggering the event when writing to the worksheet.
Application.EnableEvents = False
drg.Value = Now ' only after testing, use 'dDate = Date'
End If
SafeExit:
' Enable events 'at all cost'.
If Not Application.EnableEvents Then Application.EnableEvents = True
Exit Sub
ClearError:
Debug.Print "Run-time error '" & Err.Number & "': " & Err.Description
Resume SafeExit
End Sub

Add or Delete Columns Based on Cell Value

I'm trying to add columns (or delete them if the number is reduced) between where "ID" and "Total" are based on the cell value in B1.
How could this be done automatically every time the cell is updated?
Code I have so far
Private Sub Worksheet_Change(ByVal Target As Range)
Dim KeyCells As Range
Set KeyCells = Range("B1")
If Not Application.Intersect(KeyCells, Range(Target.Address)) _
Is Nothing Then
Dim i As Integer
For i = 1 To Range("B1").Value
Columns("C:C").Insert Shift:=xlToRight, CopyOrigin:=xlFormatFromLeftOrAbove
Next i
End If
End Sub
There are a number of issues in your code:
Unqualified range references refer to a default sheet object. While it won't be a problem in this instance (in a worksheet code behind module that object is sheet sheet containing the code, in any other module its the Activesheet), it's a bad habit to get into. Use the keyword Me to refer to the sheet the code is in.
When changing the sheet in a Worksheet_Change event, use Application.EnableEvents = False to prevent an event cascade (any time the code changes the sheet the event is called again)
Use an Error Handler to turn it back on (Application.EnableEvents = True)
Calculate how many columns to Insert or Delete based on existing columns
Range check the user input to ensure it's valid
Insert or delete in one block
On the assumption the "Totals" column contains a formula to sum the row (eg for 2 columns, row 4 it might be =Sum($C4:$D4), when you insert columns at column C the formula won't include the new columns. The code can update the formulas if required.
Target is already a range. No need to get its address as a string, then turn it back into a range, use it directly
Your code, refactored:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim KeyCells As Range
Dim NumColumnsRequired As Long
Dim NumExistingColumns As Long
Dim NumToInsertOrDelete As Long
Dim TotalsRange As Range
On Error GoTo EH
Set KeyCells = Me.Range("B1")
If Not Application.Intersect(KeyCells, Target) Is Nothing Then
' Validate Entry
If Not IsNumeric(KeyCells.Value) Then Exit Sub
NumColumnsRequired = KeyCells.Value
If NumColumnsRequired <= 0 Or NumColumnsRequired > 16380 Then Exit Sub
Application.EnableEvents = False
NumExistingColumns = Me.Cells(3, Me.Columns.Count).End(xlToLeft).Column - 3
NumToInsertOrDelete = NumColumnsRequired - NumExistingColumns
Select Case NumToInsertOrDelete
Case Is < 0
' Delete columns
Me.Columns(3).Resize(, -NumToInsertOrDelete).Delete
Case Is > 0
' Insert columns
Me.Columns(3).Resize(, NumToInsertOrDelete).Insert CopyOrigin:=xlFormatFromLeftOrAbove
'Optional: update Total Formulas
Set TotalsRange = Me.Cells(Me.Rows.Count, Me.Cells(3, Me.Columns.Count).End(xlToLeft).Column).End(xlUp)
If TotalsRange.Row > 3 Then
Set TotalsRange = Me.Range(TotalsRange, Me.Cells(4, TotalsRange.Column))
TotalsRange.Formula2R1C1 = "=Sum(RC3:RC" & TotalsRange.Column - 1 & ")"
End If
Case 0
' No Change
End Select
End If
EH:
Application.EnableEvents = True
End Sub
may try the code below to have the result like
code is more or less self explanatory
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Dim KeyCells As Range, ColNum As Long, TotalCol As Long, LeftFixedCol As Long
Dim Rng As Range, c As Range
Set KeyCells = Range("B1")
If Application.Intersect(KeyCells, Target) Is Nothing Then Exit Sub
If IsNumeric(KeyCells.Value) = False Then Exit Sub
ColNum = KeyCells.Value
If ColNum <= 0 Then Exit Sub
Set Rng = Range(Cells(3, 1), Cells(3, Columns.Count))
Set c = Rng.Find("Total") 'the find is case senseticve, Change "Total" to desired key word to find
If c Is Nothing Then Exit Sub
TotalCol = c.Column
LeftFixedCol = 2 'Column A & B for Company and ID
Dim i As Integer
If TotalCol < LeftFixedCol + ColNum + 1 Then ' Add column
For i = TotalCol To LeftFixedCol + ColNum
Columns(i).Insert Shift:=xlToRight, CopyOrigin:=xlFormatFromLeftOrAbove
Cells(3, i).Value = "Column " & i - LeftFixedCol ' may not use this line
Next i
End If
If TotalCol > LeftFixedCol + ColNum + 1 Then ' Add column
For i = TotalCol - 1 To LeftFixedCol + ColNum + 1 Step -1
Columns(i).Delete
Next i
End If
End Sub
However to keep the Sum formula on total Column consistence with added column, may limit number of minimum columns to 2 and inserting columns in between existing columns, by changing following
If ColNum <= 1 Then Exit Sub
and
Columns(i - 1).Insert Shift:=xlToRight, CopyOrigin:=xlFormatFromLeftOrAbove
also delete line inserting column heading
Cells(3, i).Value = "Column " & i - LeftFixedCol ' may not use this line
otherwise may add VBA code to change formula of total column to requirement.
You can try the following.
the named ranges are defined:
"B1" -> "ColumnNumber"
"B3" -> "Header.ID"
"F3" -> "Header.Total" (but it changes as you add / remove columns)"
Private Sub Worksheet_Change(ByVal Target As Range)
Dim headerId As Range, headerTotal As Range, columnNumber As Range
Dim currentNumberOfColumns As Integer, targetNumberOfColumns As Integer
Dim columnsToAdd As Integer, columnsToRemove As Integer
Dim i As Integer
On Error GoTo error_catch
Application.EnableEvents = False
Set columnNumber = Me.Range("ColumnNumber")
If Not Application.Intersect(columnNumber, Target) Is Nothing Then
Set headerId = Me.Range("Header.ID")
Set headerTotal = Me.Range("Header.Total")
targetNumberOfColumns = columnNumber.Value
If targetNumberOfColumns <= 0 Then
Application.EnableEvents = True
Exit Sub
End If
currentNumberOfColumns = headerTotal.Column - headerId.Column - 1
Debug.Print "Currently there are " & currentNumberOfColumns & " columns"
If currentNumberOfColumns = targetNumberOfColumns Then
Application.EnableEvents = True
Exit Sub
Else
If targetNumberOfColumns > currentNumberOfColumns Then
columnsToAdd = targetNumberOfColumns - currentNumberOfColumns
Debug.Print "Need to add " & columnsToAdd & " columns"
For i = 1 To columnsToAdd
headerTotal.Offset(0, -1).EntireColumn.Select
Selection.Copy
headerTotal.EntireColumn.Select
Selection.Insert Shift:=xlToRight
Next i
Else
columnsToRemove = -(targetNumberOfColumns - currentNumberOfColumns)
Debug.Print "Need to remove " & columnsToRemove & " columns"
For i = 1 To columnsToRemove
headerTotal.Offset(0, -1).EntireColumn.Select
Selection.Delete Shift:=xlToLeft
Next i
End If
End If
End If
columnNumber.Select
Application.CutCopyMode = False
Application.EnableEvents = True
Exit Sub
error_catch:
MsgBox Err.Description
Application.EnableEvents = True
End Sub

worksheet change event only works when region selected - how to adjust to automatic update

the combination of this sub in a module
Sub hithere3()
Dim Rng As Range
Dim Unique As Boolean
For Each Rng In Worksheets("Sheet8").Range("FS3:FS30") 'for each cell in your B1 to B30 range, sheet1
Unique = True 'we'll assume it's unique
Lastunique = Worksheets("TRADES").Range("C:C").Find("*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
For i = 3 To Lastunique 'for each cell in the unique ID cache
If Rng.Value = Worksheets("TRADES").Cells(i, 3).Value Then 'we check if it is equal
Unique = False 'if yes, it is not unique
End If
Next
If Unique Then Worksheets("TRADES").Cells(Lastunique + 1, 3) = Rng 'adds if it is unique
Next
End Sub
with the loop check in a worksheet change events
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Intersect(Target, Range("FS3:FS33")) Is Nothing Then
'Do nothing '
Else
Call hithere3
End If
End Sub
works except it only updates when I select one of the cells in FS3:FS33
Can anyone suggest how this can be overcome?
maybe with a workchange change range selection type from below?
Private Sub Worksheet_Change(ByVal Target As Range)
Dim Rng As Range, Dn As Range, n As Long
Dim RngB As Range, RngC As Range
If Target.Column = 2 And Target.Count = 1 And Target.Row > 1 Then
With CreateObject("scripting.dictionary")
.CompareMode = vbTextCompare
Set RngB = Range(Range("B2"), Range("B" & Rows.Count).End(xlUp))
Set RngC = Range(Range("C2"), Range("C" & Rows.Count).End(xlUp))
ray = Array(RngB, RngC)
For n = 0 To 1
For Each Dn In ray(n)
If Not Dn.Address(0, 0) = "C1" And Not Dn.Value = "" Then
.Item(Dn.Value) = Empty
End If
Next Dn
Next n
Range("C2").Resize(.Count) = Application.Transpose(.Keys)
End With
End If
Use either the worksheet Calculate event or the worksheet Change event:
use Calculate if the range contains formulas
use Change if the cells in the range are changed manually
If Intersect(Target, Range("FS3:FS33")) Is Nothing is the culprit. You must change Range("FS3:FS33") to whatever range you want to affect this change.
Private Sub Worksheet_Change(ByVal Target As Range) '<<delete the "Selection" from the name of event
If Intersect(Target, Range("FS3:FS33")) Is Nothing Then
'Do nothing '
Else
Call hithere3
End If
End Sub
Finally figured it out, the following code works :
Private Sub Worksheet_calculate()
If Range("FS3:FS33") Is Nothing Then
'Do nothing'
Else
Call hithere3
End If
End Sub

Resources