Log changes to multiple cells - excel

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

Related

How to return a header name based on a cell value? VBA

I created a log details sheet to track changes made in an excel spreadsheet, but my code is not returning the column/header name.
The column name should return the column where changes occurred. In this case, it would be employee status.
This is what my excel file looks like.
Here is my VBA Code
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
Assuming that the column name is located in row 1:
Dim colName As String
colName = ActiveSheet.Cells(1, Target.Column)
Sheets("logdetails").Range("A" & Rows.Count).End(xlUp).Offset(0, 1).Value = colName
A Workbook Sheet Change: Logging Old and New (Application.Undo)
This is a different approach that first writes the Target formulas (if no formula, it's equivalent to values) to an array, then uses Application.Undo, then writes the old Target values to another array, then writes back the new values and populates the log worksheet using the information from the arrays.
It covers multiple cells when e.g. copy-pasting.
It partially covers multi-ranges (discontinuous, incontiguous) i.e. you can only write to those by using VBA e.g. Range("A1,C3").Value = "Test". But the issue is that in the case of using VBA, Application.Undo will not work so you cannot get the old data in the 3rd column. Even worse, it might work wrong e.g. if you have previously changed something in a worksheet that is not affected by the code (in this case, only the log worksheet).
Sheet Module e.g. Sheet1
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
Const dName As String = "LogDetails"
Const HeaderRow As Long = 1
Const dcCount As Long = 6
On Error GoTo ClearError
If Not TypeOf Sh Is Worksheet Then Exit Sub
Dim sName As String: sName = Sh.Name
If StrComp(sName, dName, vbTextCompare) = 0 Then Exit Sub
Dim uName As String: uName = Environ("USERNAME")
Dim tStamp As Date: tStamp = Now
Dim aData: aData = GetCellAddresses(Target)
Dim hData: hData = GetHeaders(Target, HeaderRow)
Dim drCount As Long: drCount = UBound(aData)
Dim nJag(): nJag = GetMultiRangeFormulas(Target)
Application.EnableEvents = False
Dim IsUndoClear As Boolean
On Error Resume Next
Application.Undo
IsUndoClear = Err.Number = 0
On Error GoTo ClearError
Dim oJag(): Dim arg As Range, a As Long
If IsUndoClear Then
oJag = GetMultiRangeFormulas(Target)
For Each arg In Target.Areas
a = a + 1
arg.Value = nJag(a)
Next arg
End If
Dim dData(): ReDim dData(1 To drCount, 1 To dcCount)
Dim r As Long, c As Long, dr As Long
For a = 1 To UBound(nJag)
For r = 1 To UBound(nJag(a), 1)
For c = 1 To UBound(nJag(a), 2)
dr = dr + 1
dData(dr, 1) = dName & "-" & aData(dr)
dData(dr, 2) = hData(dr)
If IsUndoClear Then dData(dr, 3) = oJag(a)(r, c)
dData(dr, 4) = nJag(a)(r, c)
dData(dr, 5) = uName
dData(dr, 6) = tStamp
Next c
Next r
Next a
Dim dws As Worksheet: Set dws = Sh.Parent.Sheets(dName)
Dim dfcell As Range
Set dfcell = dws.Cells(dws.Rows.Count, "A").End(xlUp).Offset(1)
Dim drg As Range: Set drg = dfcell.Resize(drCount, dcCount)
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 & "':" & Err.Description
Resume ProcExit
End Sub
Standard Module e.g. Module1
Function GetHeaders( _
ByVal mrg As Range, _
Optional ByVal HeaderRow As Long = 1) _
As Variant
Dim Data() As String: ReDim Data(1 To mrg.Cells.CountLarge)
Dim mCell As Range, c As Long
For Each mCell In mrg.Cells
c = c + 1
Data(c) = mCell.EntireColumn.Cells(HeaderRow).Value
Next mCell
GetHeaders = Data
End Function
Function GetCellAddresses( _
ByVal mrg As Range) _
As Variant
Dim Data() As String: ReDim Data(1 To mrg.Cells.CountLarge)
Dim mCell As Range, c As Long
For Each mCell In mrg.Cells
c = c + 1
Data(c) = mCell.Address(0, 0)
Next mCell
GetCellAddresses = Data
End Function
Function GetMultiRangeFormulas( _
ByVal mrg As Range) _
As Variant
Dim Jag(): ReDim Jag(1 To mrg.Areas.Count)
Dim arg As Range, Data(), a As Long
For Each arg In mrg.Areas
a = a + 1
Data = GetRangeFormulas(arg)
Jag(a) = Data
Next arg
GetMultiRangeFormulas = Jag
End Function
Function GetRangeFormulas( _
ByVal rg As Range) _
As Variant
Dim Data()
If rg.Rows.Count * rg.Columns.Count = 1 Then
ReDim Data(1 To 1, 1 To 1): Data(1, 1) = rg.Formula
Else
Data = rg.Formula
End If
GetRangeFormulas = Data
End Function

Two questions about VBA in excel: 1. Return a column name and return old value of a cell

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

"Compile Error - Sub or Error Function Not Defined" Excel VBA Error for Trying to Filter Across Columns

I am trying to find an excel VBA macro that allows me to filter across columns instead of rows. If anyone has any code or a template for excel that already does that, please share.
Otherwise I was watching a video that had code that'd do exactly what I want. However, when I do the keyboard shortcut for "FillFilters" I get the error message "Compile Error - Sub or Error Function Not Defined". Can anyone help on this issue? Code is posted below, thanks
Sub FillFilters() 'with sortcut
Dim oRange As Range, l As Long, r As Long, c As Long, sFilter As String, arr As Variant, sTemp As String, bDone As Boolean
If Range("A1") = "Filters" Then Exit Sub
Range("A2").EntireColumn.Insert
Set oRange = Sheet4.Range("A2").CurrentRegion
With oRange
.Cells.EntireColumn.Hidden = False
.Cells(1, 1) = "Filters"
For r = 2 To .Rows.Count
For c = 3 To .Columns.Count
If WorksheetFunction.CountIf(Range(Cells(r, 3), Cells(r, c)), Cell(r, c)) = 1 Then
sFilter = sFilter & "," & Cells(r, c)
End If
Next c
arr = Split(sFilter, ",")
Do
bDone = True
For I = 1 To UBound(arr) - 1
If arr(I) > arr(I + 1) Then
bDone = False: sTemp = arr(I): arr(I) = arrr(I + 1): arr(I + 1) = sTemp
End If
Next I
Loop While bDonw = False
sFilter = Join(arr, ",")
With .Cells(r, I).Validation
.Delete
.Add Type:=xiValidateList, Formula1:="-" & sFilter & ",Blanks"
.InCellDropdown = True
End With
sFilter = ""
Next r
.Cells.EntireColumn.Hidden = False
End With
End Sub
Sub DeleteFilters() 'with shortcut
If Range("A1") = "Filters" Then
Cells.EntireColumn.Hidden = False
Range("A1").EntireColumn.Delete
Range("A1").Select
End If
End Sub
Sub Filtering(oFIlter As Range)
Dim oRange As Range, sCell As String, r As Long, c As Long, iRow As Long, iCount As Long
Set oRange = Sheet1.Range("A2").CurrentRegion
sCell = oFIlter.Value
If sCell = "Filters" Then oRange.Cells.EntireColumn.Hidden = False: Exit Sub
If sCell = "" Then oRange.Cells.EntireColumn.Hidden = False: Exit Sub
If sCell = "-" Then oRange.Cells.EntireColumn.Hidden = False: Exit Sub
If sCell = "Blanks" Then oRange.Cells.EntireColumn.Hidden = False: sCell = ""
oRange.Cells.EntireColmn.Hidden = False
iRow = oFIlter.Row
With oRange
For c = 3 To .Columns.Count
If .Cells(iRow, c) <> sCell Then
.Cell(iRow, c).EntireColumn.Hidden = True
Else
iCount = iCount + 1
End If
Next c
MsgBox iCount & " columns for row " & iRow
End With
End Sub
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
If Range("A1") <> "FIlters" Then Exit Sub
If Target Is Nothing Then Exit Sub
If Target.Column = 1 Then Filtering Target
End Sub
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Range("A1") <> "Filters" Then Exit Sub
If Target Is Nothing Then Exit Sub
If Target.Column <> 1 Then Exit Sub
If Target.Cells.Count > 1 Then Exit Sub
If Target.Value = "" Then Exit Sub
Filtering Target

how to add the above row x times in excel based on the number y which can change

I am trying to make an excel file for my parents so that they have it easier than writing all the info in a book X amount of times.
i have A; B; C; D; E; F; G; H; I; J; and L columns and want to automate and duplicate the data in A to G in rows below xn-1 times when
there is a number on Hx cell x amount of times,
where x can be from 1 to 50.
https://preview.redd.it/8p19v7ncjyo91.png?width=1859&format=png&auto=webp&s=5265abb1f6c77b418c409197e19ab836f62bd5ec
before typing 10
https://preview.redd.it/xq9p3m69kyo91.png?width=1384&format=png&auto=webp&s=b06512811b45d8d7c33ff8072d58bc1f8603fa46
example data after inputting 10 or 5 respectively
thus will be inputting all the details in rows 17 and 27
Please, test the next code. It iterates backwards, inserts the necessary number of rows (from "H" cell) and copy on them the values of between columns "A:G" of the row where "H" cell is not empty and numeric:
Sub CopyRowsNTimes()
Dim sh As Worksheet, lastRH As Long, i As Long
Set sh = ActiveSheet 'use here the sheet you need
lastRH = sh.Range("H" & sh.rows.count).End(xlUp).row 'last row on column "H:H")
Application.ScreenUpdating = False: Application.Calculation = xlCalculationManual
For i = lastRH To 2 Step -1
If IsNumeric(sh.Range("H" & i).Value) And sh.Range("H" & i).Value <> "" Then
Application.CutCopyMode = False
sh.rows(i + 1 & ":" & i + sh.Range("H" & i).Value - 1).Insert Shift:=xlShiftDown, CopyOrigin:=xlFormatFromLeftOrAbove
sh.Range("A" & i + 1, "G" & i + 1 + sh.Range("H" & i).Value - 2).Value = _
sh.Range("A" & i, "G" & i).Value
End If
Next i
Application.ScreenUpdating = True: Application.Calculation = xlCalculationAutomatic
MsgBox "Ready..."
End Sub
Please, send some feedback after testing it.
I think, clearing the content of H:H column after processing will be a good idea. For the case you run the code for the second time, by mistake. I let it as it was, only to easily check the inserted rows...
Duplicate Rows
Sheet Module e.g. Sheet1
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
DuplicateRows Target, "H2", 1, 50
End Sub
Standard Module e.g. Module1
Option Explicit
Sub DuplicateRows( _
ByVal TargetCell As Range, _
ByVal CriteriaColumnFirstCellAddress As String, _
Optional ByVal MinTargetValue As Long = 1, _
Optional ByVal MaxTargetValue As Long = 1)
Const ProcName As String = "DuplicateRows"
On Error GoTo ClearError
' Validate 'TargetCell'.
'If TargetCell Is Nothing Then Exit Sub
If TargetCell.Cells.CountLarge > 1 Then Exit Sub ' not a single cell
' Validate 'CriteriaColumnFirstCellAddress'.
Dim ws As Worksheet: Set ws = TargetCell.Worksheet
Dim fCell As Range
On Error Resume Next
Set fCell = ws.Range(CriteriaColumnFirstCellAddress)
On Error GoTo ClearError
If fCell Is Nothing Then Exit Sub ' invalid address
If fCell.Cells.CountLarge > 1 Then Exit Sub ' not a single cell
' Build the Criteria (one-column) range ('crg').
Dim rg As Range: Set rg = ws.UsedRange
Dim crg As Range
With fCell
Set crg = Intersect(rg, .Resize(ws.Rows.Count - .Row + 1))
End With
If crg Is Nothing Then Exit Sub ' not intersecting
If Intersect(TargetCell, crg) Is Nothing Then Exit Sub ' not intersecting
' Validate 'MinTargetValue' and 'MaxTargetValue'.
If MinTargetValue < 1 Then Exit Sub
If MaxTargetValue < 1 Then Exit Sub
Dim MinValue As Long
Dim MaxValue As Long
' Handle if min and max are switched.
If MinTargetValue < MaxTargetValue Then
MinValue = MinTargetValue
MaxValue = MaxTargetValue
Else
MinValue = MaxTargetValue
MaxValue = MinTargetValue
End If
' Validate the Target value.
Dim TargetValue As Variant: TargetValue = TargetCell.Value
If Not VarType(TargetValue) = vbDouble Then Exit Sub ' not a number
If Int(TargetValue) <> TargetValue Then Exit Sub ' not a whole number
Select Case TargetValue
Case MinValue To MaxValue
Case Else: Exit Sub ' exceeds the range of numbers
End Select
Dim rrg As Range: Set rrg = Intersect(rg, TargetCell.EntireRow)
Dim LastRow As Long: LastRow = crg.Cells(crg.Cells.Count).Row
Dim MaxInsertRows As Long: MaxInsertRows = ws.Rows.Count - LastRow
If TargetValue > MaxInsertRows Then Exit Sub ' doesn't fit in the worksheet
' (Insert) Copy the data.
With Application
.ScreenUpdating = False
.EnableEvents = False
End With
With rrg
If .Row < LastRow Then
.Offset(1).Resize(TargetValue).Insert _
Shift:=xlShiftDown, CopyOrigin:=xlFormatFromLeftOrAbove
End If
TargetCell.ClearContents
.Copy Destination:=.Resize(TargetValue + 1)
End With
ProcExit:
On Error Resume Next
With Application
If Not .EnableEvents Then .EnableEvents = True
If Not .ScreenUpdating Then .ScreenUpdating = True
End With
On Error GoTo 0
Exit Sub
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Sub

Excel locking cells with a date and time not working

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

Resources