I want to have a sheet were after I enter a truck number it automatically places the date and time in a following cell, that works no problem, but when I try to place a protection on the cells after data has been enter so it cannot be modify it gives me errors, what am I doing wrong?
I have selected the complete sheet and unlock the cells, then I have wrote this code on the visual basic for excel
Private Sub Worksheet_Change(ByVal Target As Range)
ActiveSheet.Unprotect Password:="YourPassword"
Target.Locked = True
ActiveSheet.Protect Password:="YourPassword"
Dim x As Integer
Dim y As Integer
For x = 2 To 1000
For y = 2 To 1000
If Cells(x, 3).Value <> "" And Cells(x, 5).Value = "" Then
Cells(x, 5).Value = Date & " " & Time
Cells(x, 5).NumberFormat = "m/d/yyyy h:mm:ss AM/PM"
End If
Next
Range("B:B").EntireColumn.AutoFit
If Cells(y, 6).Value <> "" And Cells(y, 7).Value = "" Then
Cells(y, 7).Value = Date & " " & Time
Cells(y, 7).NumberFormat = "m/d/yyyy h:mm:ss AM/PM"
End If
Next
Range("B:B").EntireColumn.AutoFit
End Sub
[ ]2]2
this is the code and error
after I apply the code for the protection the time and date doesnt stamp anymore
Worksheet Change: Monitoring 2 Non-Adjacent Columns
Adjust the values in the constants section, especially the password ('pw').
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
ScanTrucks Target
End Sub
Private Sub ScanTrucks(ByVal Target As Range)
On Error GoTo ClearError
' Scan In
Const siCol As String = "C"
Const diCol As String = "E"
' Scan Out
Const soCol As String = "F"
Const doCol As String = "G"
' Both
Const fRow As Long = 2
Const afCol As String = "B"
Const pw As String = "123"
Dim ws As Worksheet: Set ws = Target.Worksheet
Dim scrg As Range
Dim srg As Range
Dim sCell As Range
Dim drg As Range
Dim dCell As Range
' Scan In
Set scrg = ws.Columns(siCol)
With scrg
Set srg = Intersect(Target, _
.Resize(.Rows.Count - fRow + 1).Offset(fRow - 1))
End With
If Not srg Is Nothing Then
For Each sCell In srg.Cells
If Len(CStr(sCell.Value)) > 0 Then
Set dCell = sCell.EntireRow.Columns(diCol)
If Len(CStr(dCell.Value)) = 0 Then
Set drg = RefCombinedRange(drg, dCell)
End If
End If
Next sCell
End If
' Scan Out
Set srg = Nothing
With scrg.EntireRow.Columns(soCol)
Set srg = Intersect(Target, _
.Resize(.Rows.Count - fRow + 1).Offset(fRow - 1))
End With
If Not srg Is Nothing Then
For Each sCell In srg.Cells
If Len(CStr(sCell.Value)) > 0 Then
Set dCell = sCell.EntireRow.Columns(doCol)
If Len(CStr(dCell.Value)) = 0 Then
Set drg = RefCombinedRange(drg, dCell)
End If
End If
Next sCell
End If
If drg Is Nothing Then Exit Sub
' Unprotect, format, write and protect.
ws.Unprotect Password:=pw
Application.EnableEvents = False
ws.Columns(afCol).AutoFit
With drg
.NumberFormat = "m/d/yyyy h:mm:ss AM/PM"
.Value = Now
.Locked = True
End With
SafeExit:
If Not Application.EnableEvents Then Application.EnableEvents = True
If Not ws.ProtectionMode Then ws.Protect Password:=pw
Exit Sub
ClearError:
Debug.Print "Run-time error '" & Err.Number & "':" & Err.Description
Resume SafeExit
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Creates a reference to a range combined from two ranges.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function RefCombinedRange( _
ByVal CombinedRange As Range, _
ByVal AddRange As Range) _
As Range
If CombinedRange Is Nothing Then
Set RefCombinedRange = AddRange
Else
Set RefCombinedRange = Union(CombinedRange, AddRange)
End If
End Function
Related
I'm creating a spreadsheet with logdetails of another sheet with information that is changing constantly and I have to keep tracking the changes. I was able to record into the logdetails spreadsheet part of the changes
but not the column name (based on the cell address and the old value).
Here it is my VBA code so far.
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
If ActiveSheet.Name <> "logdetails" Then
Application.EnableEvents = False
Sheets("logdetails").Range("A" & Rows.Count).End(xlUp).Offset(1, 0).Value = ActiveSheet.Name & "-" & Target.Address(0, 0)
Sheets("logdetails").Range("A" & Rows.Count).End(xlUp).Offset(0, 3).Value = Target.Value
Sheets("logdetails").Range("A" & Rows.Count).End(xlUp).Offset(0, 4).Value = Environ("username")
Sheets("logdetails").Range("A" & Rows.Count).End(xlUp).Offset(0, 5).Value = Now
Sheets("logdetails").Columns("A:H").AutoFit
Application.EnableEvents = True
End If
End Sub
First you need to save the old value somewhere through workbook event. The variable lastRng bellow will save the value of every active cell and it will be restored in case of change
Dim lastRng
Private Sub Workbook_Open()
Set lastRng = ActiveCell
End Sub
Private Sub Workbook_SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range)
lastRng = Target.Value
End Sub
After, you add the next two lines
Sheets("logdetails").Range("A" & Rows.Count).End(xlUp).Offset(0, 1).Value = Target.Address
Sheets("logdetails").Range("A" & Rows.Count).End(xlUp).Offset(0, 2).Value = lastRng
to complete your table as you desire. I didn't understand very well what you means by column name, but if you want the letter instead column number or cell address, you can find good solutions here in this question to convert one in another
All in all, your consolidated code will be like this:
Dim lastRng
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
If ActiveSheet.Name <> "logdetails" Then
Application.EnableEvents = False
Sheets("logdetails").Range("A" & Rows.Count).End(xlUp).Offset(1, 0).Value = ActiveSheet.Name & "-" & Target.Address(0, 0)
Sheets("logdetails").Range("A" & Rows.Count).End(xlUp).Offset(0, 1).Value = Target.Address
Sheets("logdetails").Range("A" & Rows.Count).End(xlUp).Offset(0, 2).Value = lastRng
Sheets("logdetails").Range("A" & Rows.Count).End(xlUp).Offset(0, 3).Value = Target.Value
Sheets("logdetails").Range("A" & Rows.Count).End(xlUp).Offset(0, 4).Value = Environ("username")
Sheets("logdetails").Range("A" & Rows.Count).End(xlUp).Offset(0, 5).Value = Now
Sheets("logdetails").Columns("A:H").AutoFit
Application.EnableEvents = True
End If
End Sub
Private Sub Workbook_Open()
Set lastRng = ActiveCell
End Sub
Private Sub Workbook_SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range)
lastRng = Target.Value
End Sub
A Workbook Sheet Change: Log Changes in Multiple Worksheets
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
Const LogName As String = "logdetails"
Const DST_COLUMNS_COUNT As Long = 6
On Error GoTo ClearError
If Sh.Name = "logdetails" Then Exit Sub
If Not TypeOf Sh Is Worksheet Then Exit Sub ' not a worksheet
Dim twsName As String: twsName = Sh.Name
Dim usName As String: usName = Environ("USERNAME")
Dim cTime As String: cTime = Now
Dim nDict As Object: Set nDict = DictRangeAddressAndFormulas(Target)
Application.EnableEvents = False
Dim oDict As Object
Application.Undo
Set oDict = DictRangeAddressAndFormulas(Target)
Application.Undo
Dim drCount As Long, nKey
For Each nKey In nDict.Keys
drCount = drCount + UBound(nDict(nKey), 1)
Next nKey
Dim dData() As Variant: ReDim dData(1 To drCount, 1 To DST_COLUMNS_COUNT)
Dim sr As Long, sc As Long, dr As Long, nString As String, oString As String
For Each nKey In nDict.Keys
Debug.Print nKey, nDict(nKey)(1, 1), oDict(nKey)(1, 1)
For sr = 1 To UBound(nDict(nKey), 1)
For sc = 1 To UBound(nDict(nKey), 2)
nString = CStr(nDict(nKey)(sr, sc))
oString = CStr(oDict(nKey)(sr, sc))
If StrComp(nString, oString, vbBinaryCompare) <> 0 Then
dr = dr + 1
With Sh.Range(nKey).Cells(sr, sc)
dData(dr, 1) = twsName & "-" & .Address(0, 0)
dData(dr, 2) = Split(.Address, "$")(1)
End With
dData(dr, 3) = oDict(nKey)(sr, sc)
dData(dr, 4) = nDict(nKey)(sr, sc)
dData(dr, 5) = usName
dData(dr, 6) = cTime
End If
Next sc
Next sr
Next nKey
Dim dws As Worksheet: Set dws = Me.Sheets(LogName)
Dim dlCell As Range: Set dlCell = dws.Cells(dws.Rows.Count, "A").End(xlUp)
Dim drg As Range: Set drg = dlCell.Offset(1).Resize(dr, DST_COLUMNS_COUNT)
drg.Value = dData
drg.EntireColumn.AutoFit
ProcExit:
On Error Resume Next
If Not Application.EnableEvents Then Application.EnableEvents = True
On Error GoTo 0
Exit Sub
ClearError:
Debug.Print "Run-time error '" & Err.Number & "':" & vbLf & Err.Description
Resume ProcExit
End Sub
Function DictRangeAddressAndFormulas( _
ByVal rg As Range) _
As Object
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
Dim arg As Range
For Each arg In rg.Areas
dict(arg.Address) = GetRangeFormulas(arg)
Next arg
Set DictRangeAddressAndFormulas = dict
End Function
Function GetRangeFormulas( _
ByVal rg As Range) _
As Variant
Dim Data() As Variant
If rg.Rows.Count * rg.Columns.Count = 1 Then ' one cell
ReDim Data(1 To 1, 1 To 1): Data(1, 1) = rg.Formula
Else ' multiple cells
Data = rg.Formula
End If
GetRangeFormulas = Data
End Function
I have received a request to fill in date based on specific status chosen (cell value) in a column representing that status.
For example if I choose a Status "Event_1" in column A from a drop down list, macro should find a column with the same name (Event_1) and fill in date in that column for the Row where the status was changed.
I only got as far as filling adjacent cell with a date when said cell is changed. I know I should probably adjust offset to a column number representing my status, however I'm not sure how to achieve this.
Private Sub Worksheet_Change(ByVal Target As Excel.Range)
Dim ColNum As Integer
With Target
If .Count > 1 Then Exit Sub
If Not Intersect(Range("A:A"), .Cells) Is Nothing Then
Application.EnableEvents = False
If IsEmpty(.Value) Then
.Offset(0, 1).ClearContents
Else
With .Offset(0, 1)
.NumberFormat = "dd mmm yyyy"
.Value = Now
End With
End If
Application.EnableEvents = True
End If
End With
End Sub
What about this?
Private Sub Worksheet_Change(ByVal Target As Range)
Dim ColNum As Integer
With Target
If .Count > 1 Then Exit Sub
If Not Intersect(Range("A:A"), .Cells) Is Nothing Then
Application.EnableEvents = False
If IsEmpty(.Value) Then
'.Offset(0, 1).ClearContents
'Why???
Else
ColNum = Application.WorksheetFunction.Match(Target.Value, Range("1:1"), 0)
With .Offset(0, ColNum - 1)
.NumberFormat = "dd mmm yyyy"
.Value = Now
End With
End If
Application.EnableEvents = True
End If
End With
End Sub
The only part I don't get is this one:
If IsEmpty(.Value) Then
.Offset(0, 1).ClearContents
Why? You should specify what you want to do when there is no option selected.
A Worksheet Change: One Timestamp Per Row
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
On Error GoTo ClearError
Const FirstColCellAddress As String = "B1"
Const FirstRowCellAddress As String = "A2"
Const TimeFormat As String = "dd/mm/yyyy hh:mm:ss"
Dim scrg As Range
With Range(FirstRowCellAddress)
Set scrg = .Resize(.Worksheet.Rows.Count - .Row + 1)
End With
Dim irg As Range: Set irg = Intersect(scrg, Target)
If irg Is Nothing Then Exit Sub
Dim srrg As Range
Dim sCell As Range
With Range(FirstColCellAddress)
Set sCell = .Resize(, .Worksheet.Columns.Count - .Column + 1) _
.Find("*", , xlFormulas, , , xlPrevious)
Set srrg = .Resize(, sCell.Column - .Column + 1)
End With
Dim sIndex As Variant
Dim sString As String
Dim drrg As Range
Dim dCell As Range
Dim rgClear As Range
Dim rgTime As Range
For Each sCell In irg.Cells
Set drrg = srrg.Rows(sCell.Row - srrg.Row + 1)
If rgClear Is Nothing Then
Set rgClear = drrg
Else
Set rgClear = Union(rgClear, drrg)
End If
sString = CStr(sCell.Value)
sIndex = Application.Match(sString, srrg, 0)
If IsNumeric(sIndex) Then
Set dCell = drrg.Cells(sIndex)
If rgTime Is Nothing Then
Set rgTime = dCell
Else
Set rgTime = Union(rgTime, dCell)
End If
End If
Next sCell
Application.EnableEvents = False
If Not rgClear Is Nothing Then rgClear.Clear
If Not rgTime Is Nothing Then
Dim TimeStamp As Date: TimeStamp = Now
With rgTime
.NumberFormat = TimeFormat
.Value = TimeStamp
End With
End If
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
I found this code on this forum to log changes to multiple cells to a sheet called "LOG". Not sure how it work but it works great. However, the log showed in single cell on the "LOG" sheet. Is there a way to modify the code so information appear in "LOG" sheet in different row such as date/time in row A, user in row B, etc. Also, can I add to the code so that the sheet "LOG" is password protect from user (but still adding log). Here's the code. Thank you for any help.
Dim RangeValues As Variant
Dim lCols As Long, lRows As Long
Private Sub Worksheet_Change(ByVal Target As Range)
Dim UN As String: UN = Application.UserName
If Target.Cells.Count = 1 Then
If Target.Value <> RangeValues Then
Sheets("LOG").Cells(65000, 1).End(xlUp).Offset(1, 0).Value = Now & " / " & UN & " / changed cell " & Target.Address & " /from/ " & RangeValues & " to " & Target.Value
End If
Exit Sub
End If
' More than one cell in the range
Dim r As Long, c As Long
For r = 1 To lRows
For c = 1 To lCols
If Target.Cells(r, c).Value <> RangeValues(r, c) Then
Sheets("LOG").Cells(65000, 1).End(xlUp).Offset(1, 0).Value = Now & " / " & UN & " / changed cell " & Target.Cells(r, c).Address & " /from/ " & RangeValues(r, c) & " to " & Target.Cells(r, c).Value
End If
Next c
Next r
End Sub
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
RangeValues = Target.Value
lCols = Target.Columns.Count
lRows = Target.Rows.Count
End Sub
Please, test the next updated solution:
Option Explicit
Dim RangeValues As Variant
Dim lCols As Long, lRows As Long
Private Sub Worksheet_Change(ByVal Target As Range)
Dim sh As Worksheet: Set sh = Sheets("LOG_")
Dim UN As String: UN = Application.userName
If sh.Range("A1") = "" Then sh.Range("A1").Resize(1, 5) = Array("Time", "User Name", "Changed cell", "From", "To")
sh.Unprotect "1234" 'use here your real password
If Target.cells.Count = 1 Then
If Target.value <> RangeValues Then
sh.cells(rows.Count, 1).End(xlUp).Offset(1, 0).Resize(1, 5).value = _
Array(Now, UN, Target.Address(0, 0), RangeValues, Target.value)
End If
Exit Sub
End If
' More than one cell in the range
Dim r As Long, c As Long
For r = 1 To lRows
For c = 1 To lCols
If Target.cells(r, c).value <> RangeValues(r, c) Then
sh.cells(rows.Count, 1).End(xlUp).Offset(1, 0).Resize(1, 5).value = _
Array(Now, UN, Target.cells(r, c).Address(0, 0), RangeValues(r, c), Target.cells(r, c).value)
End If
Next c
Next r
sh.Protect "1234"
End Sub
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
RangeValues = Target.value
lCols = Target.Columns.Count
lRows = Target.rows.Count
End Sub
In fact, only Worksheet_Change event has been changed and added Option Explicit on top of the module.
Edited:
Please, use the next code. It uses only an event and no necessary to previously select the range where to copy:
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Dim RangeValues As Variant, lCols As Long, lRows As Long
Dim sh As Worksheet: Set sh = Sheets("LOG_")
Dim UN As String: UN = Application.userName
If sh.Range("A1") = "" Then sh.Range("A1").Resize(1, 5) = Array("Time", "User Name", "Changed cell", "From", "To")
sh.Unprotect "1234" 'use here your real password
Dim TgValue 'the array to keep Target values (before UnDo)
Application.ScreenUpdating = False 'to optimize the code (make it faster)
Application.Calculation = xlCalculationManual
TgValue = Target.value
Application.EnableEvents = False
Application.Undo
RangeValues = Range(Target.Address).value 'define the RangeValue
lCols = Target.Columns.Count
lRows = Target.rows.Count
Range(Target.Address).value = TgValue 'Put back the Target value (changed using UnDo)
Application.EnableEvents = True
'One cell in the range
If Target.cells.Count = 1 Then
If Target.value <> RangeValues Then
sh.cells(rows.Count, 1).End(xlUp).Offset(1, 0).Resize(1, 5).value = _
Array(Now, UN, Target.Address(0, 0), RangeValues, Target.value)
End If
sh.Protect "1234"
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
Exit Sub
End If
' More than one cell in the range
Dim r As Long, c As Long
For r = 1 To lRows
For c = 1 To lCols
If Target.cells(r, c).value <> RangeValues(r, c) Then
sh.cells(rows.Count, 1).End(xlUp).Offset(1, 0).Resize(1, 5).value = _
Array(Now, UN, Target.cells(r, c).Address(0, 0), RangeValues(r, c), Target.cells(r, c).value)
End If
Next c
Next r
sh.Protect "1234"
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub
Second edit:
The version able to deal with continuous ranges, but built by consecutively selecting cells:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim RangeValues As Variant, lCols As Long, lRows As Long, contRng As Range
Dim sh As Worksheet: Set sh = Sheets("LOG_")
Dim UN As String: UN = Application.userName
If sh.Range("A1") = "" Then sh.Range("A1").Resize(1, 5) = Array("Time", "User Name", "Changed cell", "From", "To")
sh.Unprotect "" 'use here your real password
Dim TgValue 'the array to keep Target values (before UnDo)
Application.ScreenUpdating = False 'to optimize the code (make it faster)
Application.Calculation = xlCalculationManual
If Target.cells.Count > 1 Then
TgValue = Range(Target.Address).value
If Not IsArray(TgValue) Then 'if the range is discontinuous and its first area means single cell
Set contRng = ContRange(Target) 'the discontinuous range is transformed in continuous
TgValue = contRng.value 'only now the range value can be (correctly) put in an array
Set Target = contRng 'the target range is also built as continuous
' Debug.Print "Target = " & Target.Address: 'Stop
Else
Set contRng = Target 'for continuous ranges, even made of a single cell
End If
Else
TgValue = Target.value 'put the target range in an array (or as a string for a single cell)
Set contRng = Target 'set contRng (to be used later) as Target
End If
Application.EnableEvents = False 'avoiding to trigger the change event after UnDo
Application.Undo
RangeValues = Range(contRng.Address).value 'define the RangeValue
'If IsArray(RangeValues) Then Debug.Print RangeValues(1, 1): ' Stop
lCols = Target.Columns.Count 'extract the target number of rows and columns:
lRows = Target.rows.Count
Range(Target.Address).value = TgValue 'Put back the Target value (changed using UnDo)
Application.EnableEvents = True
'One cell in the range
If Target.cells.Count = 1 Then
If Target.value <> RangeValues Then
sh.cells(rows.Count, 1).End(xlUp).Offset(1, 0).Resize(1, 5).value = _
Array(Now, UN, Target.Address(0, 0), RangeValues, Target.value)
End If
sh.Protect ""
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
Exit Sub
End If
' More than one cell in the range
Dim r As Long, c As Long
For r = 1 To lRows
For c = 1 To lCols
If Target.cells(r, c).value <> RangeValues(r, c) Then
sh.cells(rows.Count, 1).End(xlUp).Offset(1, 0).Resize(1, 5).value = _
Array(Now, UN, Target.cells(r, c).Address(0, 0), RangeValues(r, c), Target.cells(r, c).value)
End If
Next c
Next r
sh.Protect ""
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub
Function ContRange(rng As Range) As Range
Dim a As Range, rngCont As Range
For Each a In rng.Areas
If rngCont Is Nothing Then
Set rngCont = a
Else
Set rngCont = Union(rngCont, a)
End If
Next
If Not rngCont Is Nothing Then Set ContRange = rngCont
End Function
The code can be adapted to also handle real discontinue ranges, but it is a little more complicated and no interested in taking such a challenge...
Third edit:
The next version, follow a different logic and is able to log all kind of modifications, even in discontinuous ranges:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim RangeValues As Variant, r As Long, boolOne As Boolean, TgValue 'the array to keep Target values (before UnDo)
Dim sh As Worksheet: Set sh = Sheets("LOG_")
Dim UN As String: UN = Application.userName
If sh.Range("A1") = "" Then sh.Range("A1").Resize(1, 6) = _
Array("Time", "User Name", "Changed cell", "From", "To", "Sheet Name")
sh.Unprotect "" 'use here your real password
Application.ScreenUpdating = False 'to optimize the code (make it faster)
Application.Calculation = xlCalculationManual
If Target.cells.count > 1 Then
TgValue = extractData(Target)
Else
TgValue = Array(Array(Target.value, Target.Address(0, 0))) 'put the target range in an array (or as a string for a single cell)
boolOne = True
End If
Application.EnableEvents = False 'avoiding to trigger the change event after UnDo
Application.Undo
RangeValues = extractData(Target) 'define the RangeValue
putDataBack TgValue, ActiveSheet 'put back the changed data
If boolOne Then Target.Offset(1).Select
Application.EnableEvents = True
For r = 0 To UBound(RangeValues)
If RangeValues(r)(0) <> TgValue(r)(0) Then
sh.cells(rows.count, 1).End(xlUp).Offset(1, 0).Resize(1, 6).value = _
Array(Now, UN, RangeValues(r)(1), RangeValues(r)(0), TgValue(r)(0), Target.Parent.Name)
End If
Next r
sh.Protect ""
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub
Sub putDataBack(arr, sh As Worksheet)
Dim i As Long, arrInt, El
For Each El In arr
sh.Range(El(1)).value = El(0)
Next
End Sub
Function extractData(rng As Range) As Variant
Dim a As Range, arr, count As Long, i As Long
ReDim arr(rng.cells.count - 1)
For Each a In rng.Areas
For i = 1 To a.cells.count
arr(count) = Array(a.cells(i).value, a.cells(i).Address(0, 0)): count = count + 1
Next
Next
extractData = arr
End Function
Objective is to highlight rows that meet two different conditions:
If column A is equal to the previous workday (taking into consideration of holidays mentioned in the Reference sheet)
If column B is not equal to "AA"
I have the following code, but am unable to get appropriate rows highlighted (no rows get highlighted due to condition #1 not being met):
Sub code()
Dim lrow As Long
lrow = Cells(Rows.Count, "A").End(xlUp).Row
For i = 2 To lrow
If Cells(i, "A").Value = "=WORKDAY(today(),-1,Reference!$A$2:$A$12)" And Cells(i, "B").Value <> "AA" Then Cells(i, 1).EntireRow.Interior.ColorIndex = 6
Next i
End Sub
You could try this:
Option Explicit
Sub code()
Dim i As Long, lrow As Long
Dim objRangeHolidays As Range
Set objRangeHolidays = Worksheets("Reference").Range("$A$2", "$A$12")
lrow = Cells(rows.Count, "A").End(xlUp).row
For i = 2 To lrow
If CDate(Cells(i, "A").Value) = CDate(Application.WorksheetFunction.WorkDay(Date, -1, objRangeHolidays)) And Cells(i, "B").Value <> "AA" Then
Cells(i, 1).EntireRow.Interior.ColorIndex = 6
End If
Next i
Set objRangeHolidays = Nothing
End Sub
Your original code does not work as "=WORKDAY(today(),-1,Reference!$A$2:$A$12)" is a literal string on VBA, not a function call.
We use CDate() function to make our cell values comparable with WorksheetFunction.Workday() function.
WorksheetFunction.Today() is the same as Date() in VBA.
objRangeHolidays holds holidays defined in Reference sheet.
This is my test result:
Highlight Entire Rows
Adjust the values in the constants section.
Option Explicit
Sub highlightPreviousWorkday()
' Source
Const sName As String = "Sheet1"
Const sFirst As String = "A2"
Const sCritCol As String = "B"
Const sCriteria As String = "AA"
Const sColorIndex As Long = 6
' Holiday
Const hName As String = "Reference"
Const hFirst As String = "A2"
' Workbook
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
wb.Activate ' `Evaluate` will fail if not active.
' Source
Dim srg As Range
With wb.Worksheets(sName).Range(sFirst)
Dim slCell As Range
Set slCell = .Resize(.Worksheet.Rows.Count - .Row + 1) _
.Find("*", , xlFormulas, , , xlPrevious)
If slCell Is Nothing Then Exit Sub
Set srg = .Resize(slCell.Row - .Row + 1)
End With
' Holiday
Dim Holiday As String
With wb.Worksheets(hName).Range(hFirst)
Dim hlCell As Range
Set hlCell = .Resize(.Worksheet.Rows.Count - .Row + 1) _
.Find("*", , xlFormulas, , , xlPrevious)
If Not hlCell Is Nothing Then
Holiday = ",'" & hName & "'!" _
& .Resize(hlCell.Row - .Row + 1).Address
End If
End With
' Evaluation
Dim evDate As Variant
evDate = Evaluate("WORKDAY(TODAY(),-1" & Holiday & ")")
' Combine
Dim drg As Range
If VarType(evDate) = vbDouble Then
Dim sCell As Range
Dim sValue As Variant
Dim sString As String
For Each sCell In srg.Cells
sValue = sCell.Value
If VarType(sValue) = vbDate Then
If CDbl(sValue) = evDate Then
sString = CStr(sCell.EntireRow.Columns(sCritCol).Value)
If sString <> sCriteria Then
Set drg = getCombinedRange(drg, sCell)
End If
End If
End If
Next sCell
End If
' Color
Application.ScreenUpdating = False
srg.EntireRow.Interior.ColorIndex = xlNone
If Not drg Is Nothing Then
drg.EntireRow.Interior.ColorIndex = sColorIndex
End If
Application.ScreenUpdating = True
End Sub
Function getCombinedRange( _
ByVal BuiltRange As Range, _
ByVal AddRange As Range) _
As Range
If BuiltRange Is Nothing Then
Set getCombinedRange = AddRange
Else
Set getCombinedRange = Union(BuiltRange, AddRange)
End If
End Function
I have been using this below code to get the Unique sorted value from Sheet1.Range("C4:C") and to paste into Sheet2.Range("C4"). This is working fine
But now i want use the same code to get the Unique sorted value from Sheet3.Range("C4:C") and to paste into Sheet2.Range("G4").
now the problem is that how to mention the Sheet reference in code that which sheet range unique sorted value will be paste.
Standard Module (e.g. Module1)
Option Explicit
Sub copySortedUniqueColumn( _
SourceRange As Range, _
DestinationCell As Range, _
Optional ByVal doSort As Boolean = True)
Dim Data As Variant
If SourceRange.Rows.Count > 1 Or SourceRange.Columns.Count > 1 Then
Data = SourceRange.Value
Else
ReDim Data(1 To 1, 1 To 1): Data(1, 1) = SourceRange.Value
End If
Dim arl As Object: Set arl = CreateObject("System.Collections.ArrayList")
Dim Key As Variant
Dim i As Long
With CreateObject("Scripting.Dictionary")
.CompareMode = vbTextCompare
For i = 1 To UBound(Data, 1)
Key = Data(i, 1)
If Not IsError(Key) Then
If Len(Key) > 0 Then
If Not .Exists(Key) Then
.Item(Key) = Empty
arl.Add Key
End If
End If
End If
Next i
If .Count = 0 Then Exit Sub
End With
If doSort Then
arl.Sort
End If
ReDim Data(1 To arl.Count, 1 To 1)
i = 0
For Each Key In arl
i = i + 1
Data(i, 1) = Key
Next Key
With DestinationCell
.Resize(.Worksheet.Rows.Count - .Row + 1).ClearContents
.Resize(i).Value = Data
End With
End Sub
Function defineColumnRange( _
FirstCellRange As Range) _
As Range
On Error GoTo clearError
If FirstCellRange Is Nothing Then GoTo ProcExit
With FirstCellRange
Dim cel As Range: Set cel = .Resize(.Worksheet.Rows.Count - .Row + 1) _
.Find("*", , xlFormulas, , , xlPrevious)
If cel Is Nothing Then GoTo ProcExit
Set defineColumnRange = .Resize(cel.Row - .Row + 1)
End With
ProcExit:
Exit Function
clearError:
Resume ProcExit
End Function
Sheet Module (Sheet1)
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Const srcFirst As String = "C4"
Const dstFirst As String = "C4"
Dim srg As Range: Set srg = defineColumnRange(Range(srcFirst))
If srg Is Nothing Then
With Sheet2.Range(dstFirst)
.Resize(.Worksheet.Rows.Count - .Row + 1).ClearContents
End With
Exit Sub
End If
Dim rg As Range: Set rg = Intersect(srg, Target)
If rg Is Nothing Then Exit Sub
Dim dCel As Range: Set dCel = Sheet2.Range(dstFirst)
On Error GoTo clearError
Application.EnableEvents = False
copySortedUniqueColumn srg, dCel
SafeExit:
Application.EnableEvents = True
Exit Sub
clearError:
Resume SafeExit
End Sub
Simply do the following:
Copy the sheet module code (which you initially put in Sheet1) to the sheet module of Sheet3 and change dstFirst to G4.
(This is a follow-up question on my answer to filter unique values and sort A to Z Excel VBA.)