Unlock a specif Row Range based on the date - vba

I need some help to upgrade my VBA code.
I try to find a code which will unlock a specific row based on the current date. The problem is, I don't want all the row's cells to be unlocked but only a set of specific range. Like on the current date which are in the column "B", the cells unlocked will be from ("D" to "K"); ("M" to "P"); ("R"to"S") and ("U"to"V").
The cells in-between contain formulas that I don't want people to mess up or change by mistake.
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Range("B" & Selection.Row).Value <> Date Then
ActiveSheet.Protect Password:="3827"
MsgBox "Only today's date needs to be edited!", vbInformation, "REMINDER"
ElseIf Range("B" & Selection.Row).Value = Date Then
ActiveSheet.Unprotect Password:="3827"
ActiveSheet.EnableSelection = xlNoRestrictions
End If
End Sub

Why not take it a step further? Only let them select the row of Today's date of those columns when the worksheet is activated!
Option Explicit
Private Const PWD As String = "3827"
Private Const UNLOCK_COLS As String = "D:K,M:P,R:S,U:V"
Private Sub Worksheet_Activate()
Dim dToday As Date, oRng As Range, oItem As Variant
dToday = Date
With ActiveSheet
.Unprotect Password:=PWD
.Cells.Locked = True
' Look for row with today's date and unlock the row inside usedrange
Set oRng = .Columns("B").Find(What:=dToday)
If Not oRng Is Nothing Then
For Each oItem In Split(UNLOCK_COLS, ",")
Intersect(oRng.EntireRow, .Columns(oItem)).Locked = False
Next
End If
.Protect Password:=PWD
.EnableSelection = xlUnlockedCells
End With
End Sub
With optimisation sugguestion from Tim Williams, you can even skip the loop:
Option Explicit
Private Const PWD As String = "3827"
Private Const UNLOCK_COLS As String = "D1:K1,M1:P1,R1:S1,U1:V1"
Private Sub Worksheet_Activate()
Dim dToday As Date, oRng As Range
dToday = Date
With ActiveSheet
.Unprotect Password:=PWD
.Cells.Locked = True
' Look for row with today's date and unlock the specific columns in the row
Set oRng = .Columns("B").Find(What:=dToday)
If Not oRng Is Nothing Then oRng.EntireRow.Range(UNLOCK_COLS).Locked = False
.Protect Password:=PWD DrawingObjects:=False, Contents:=True, Scenarios:=True ' This allows Adding comments
.EnableSelection = xlUnlockedCells
End With
End Sub

Related

Attempting to step through columns in a search

I am attempting to create a module in Excel 2016 that will scan through a sheet and auto size any comments found. My current code requires me to adjust the Column Letter each time I run it. I am looking for a method to step through the columns in my loop. My current code is listed below and I am thanking anyone ahead of time for any assistance I can get. My current sheet only uses columns A through P.
Sub cmtsize()
ActiveSheet.Unprotect pswd
Range("a7:I7").Select
lrow = Cells(Rows.Count, 1).End(xlUp).Row
For xrow = 7 To lrow
xcell = "c" & lrow
Range(xcell).Select
If ActiveCell.Comment Is Nothing Then
GoTo nxt
Else
With Range(xcell).Comment.Shape
.TextFrame.AutoSize = True
End With
nxt:
End If
Next xrow
ActiveSheet.Protect pswd
Range("A6").Select
MsgBox "Finished!"
End Sub
This will resize all comments on the specified worksheet. [Update] included option for password protected sheets. As well as the Finished Msgbox.
Sub test()
Call ResizeComments(Sheet1)
MsgBox ("Finished!")
End Sub
Private Sub ResizeComments(ByVal ws As Worksheet, Optional ByVal Pass As String = "")
If Pass <> "" Then ws.Unprotect Pass
Dim oComment As Comment
For Each oComment In ws.Comments
oComment.Shape.TextFrame.AutoSize = True
Next
If Pass <> "" Then ws.Protect Pass
End Sub

Copy an sheet and lock certain cells from editing

I have a workbook with VBA code which copies a template sheet but I want to protect certain cells from editing when copied. The template sheet is protected by the locked cells which needs to be locked, but some cells are for user input and should be unlocked.
I cant get it to lock the cells in the copied sheet.
Sub MyCopySheet()
Dim myNewSheetName
myNewSheetName = InputBox("Enter Today's Date")
Worksheets.Add(After:=Worksheets("Home")).Name = myNewSheetName
Sheets(Sheets.Count - 1).Activate
Cells.Copy
Sheets(myNewSheetName).Activate
Range("A1").Select
ActiveSheet.Paste
Application.CutCopyMode = False
Range("F5:F69").ClearContents
Range("G5:G69").ClearContents
Range("H5:H69").ClearContents
Range("I5:I69").ClearContents
Range("J5:J69").ClearContents
Range("K5:K69").ClearContents
Range("Q5:Q59").ClearContents
Range("O5:O59").ClearContents
Range("L5:L69").ClearContents
Range("B23:B27").ClearContents
Range("B59:B63").ClearContents
Range("B32:B36").ClearContents
Range("B78:B94").ClearContents
Range("C78:C94").ClearContents
Range("F78:F94").ClearContents
Range("G78:G94").ClearContents
Range("J78:J94").ClearContents
Range("I78:I94").ClearContents
Range("K78:K94").ClearContents
Range("L78:L94").ClearContents
Range("B50:B54").ClearContents
End Sub
Sub lockcells()
Dim Rng
Dim MyCell
Set Rng = Range("A1:Q96")
For Each MyCell In Rng
If MyCell.Value = "" Then
Else: ActiveSheet.Unprotect Password:="password"
MyCell.Locked = True
MyCell.FormulaHidden = False
ActiveSheet.Protect Password:="password", UserInterFaceOnly:=True
End If
Next
End Sub
Basically all the cells with Range().ClearContent must be unlocked and the rest locked.
Sub MyCopySheet()
Dim myNewSheetName
myNewSheetName = InputBox("Enter Today's Date")
Worksheets.Add(After:=Worksheets("Home")).Name = myNewSheetName
Sheets(Sheets.Count - 1).Activate
Cells.Copy
Sheets(myNewSheetName).Activate
Range("A1").Select
ActiveSheet.Paste
Application.CutCopyMode = False
'clear contents
Range("P107,B23:B27,B32:B36,B50:B54,B59:B63,B78:C94,F78:G94,I78:L94,O5:O59,Q5:Q59,F5:L69").ClearContents
End Sub
I reduced your code to clear contents. And below, the code to unprotect cells from range where you cleared contents
Sub lockcells()
Dim Rng
Dim MyCell
Set Rng = Range("A1:Q96")
For Each MyCell In Rng
If MyCell.Value = "" Then
Else: ActiveSheet.Unprotect Password:="password"
MyCell.Locked = True
MyCell.FormulaHidden = False
ActiveSheet.Protect Password:="password", UserInterFaceOnly:=True
End If
Next
'now we unprotect the range we cleared contents
Range("P107,B23:B27,B32:B36,B50:B54,B59:B63,B78:C94,F78:G94,I78:L94,O5:O59,Q5:Q59,F5:L69").Locked = False
End Sub

Dynamic hyperlink that filters a table based on the ActiveCell value (VBA)

I'm creating a dynamic hyperlink that will filter a table on another sheet (Sheet15).
My goal is to have the user be able to select a cell on Sheet3 and have the VALUE of this cell be the filter on the other sheet.
Here is my code so far:
Private Sub Worksheet_FollowHyperlink(ByVal Target As Hyperlink)
If Target.Type = msoHyperlinkRange And Target.Range.Address = "$S$15" Then
Application.ScreenUpdating = False
Sheet15.Visible = True
Sheet15.ListObjects("Table17").Range.AutoFilter Field:=19, Criteria1:=ActiveCell.Value
Sheet15.Activate
Application.ScreenUpdating = True
End If
End Sub
However, when I click the hyperlink, the table is not filtered at all, so I gotta be doing something wrong.
Can anyone assist?
UPDATE
Here is updated code.
Cell S17 is now the value that I want to filter the table to:
Private Sub Worksheet_FollowHyperlink(ByVal Target As Hyperlink)
If Target.Type = msoHyperlinkRange And Target.Range.Address = "$S$15" Then
Application.ScreenUpdating = False
Sheet15.Visible = True
Sheet15.ListObjects("Table17").Range.AutoFilter Field:=19, Criteria1:=Sheet3.Range("S17").Value
Sheet15.Activate
Application.ScreenUpdating = True
End If
End Sub
But the issue remains. When I click they hyperlink, I will be brought to this other sheet, but the table is not filtered at all.
sticking to your original plans, and assuming column "A" is the one with cities names, place the following in your worksheet code pane
Option Explicit
Dim lastCell As Range '<--| declare a module scoped range variable to store the last cell selected by the user, if "valid"
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Target.Address = "$S$15" Then Exit Sub '<-- do nothing if user selected cell with hyperlink
Set lastCell = Intersect(Target, Columns("A")) '<-- change "Columns("A") to a named range with your cities
End Sub
Private Sub Worksheet_FollowHyperlink(ByVal Target As Hyperlink)
If lastCell Is Nothing Then Exit Sub '<--| no action if lastCell has not been properly set by 'Worksheet_SelectionChange()'
If Target.Type = msoHyperlinkRange And Target.Range.Address = "$S$15" Then
Application.ScreenUpdating = False
Sheet15.Visible = True
Sheet15.ListObjects("Table17").Range.AutoFilter Field:=19, Criteria1:=lastCell.Value '<--| set the criteria as 'lastCell' value
Sheet15.Activate
Application.ScreenUpdating = True
End If
End Sub
as per comments, you change Columns("A") reference in Worksheet_SelectionChange() to your actual range with cities names (perhaps a named range)
Note: unless the hyperlink points to itself, ActiveCell.Value will be the value at the link destination: use Target.Range.Value if you want the value from the cell containing the link.
Private Sub Worksheet_FollowHyperlink(ByVal Target As Hyperlink)
If Target.Type = msoHyperlinkRange And Target.Range.Address = "$S$15" Then
Application.ScreenUpdating = False
With Sheet15
.Visible = True
.ListObjects("Table17").Range.AutoFilter Field:=19, _
Criteria1:=Target.Range.Value
.Activate
End With
Application.ScreenUpdating = True
End If
End Sub

How to remain a code active after all macros have been executed

I have written multiple macros that will be executed with a command button however I want my last macro to remain active after all before macros have been executed. I want Macro15 to remain active after. And for for Macro15 I want if any cell changes I want to highlight that cell with colorindex 3
Sub RunallMacros()
macro1
macro2
macro3
macro5
Macro12
Macro13
Macro14
Macro15
End Sub
Sub macro1()
ThisWorkbook.Sheets("Main").Activate
End Sub
Sub macro2()
Dim myvalue As Variant
myvalue = InputBox("Enter Safety Stock Days")
Range("R5").value = myvalue
End Sub
Sub macro5()
Dim answer As Integer
answer = MsgBox("Are There Any ICF Forms?", vbYesNo + vbQuestion, "Other Sales")
If answer = vbYes Then ICFUserForm.Show
End Sub
Sub macro3()
Dim MyAnswer1 As Variant
Dim MyAnswer2 As Variant
Dim MyAnswer3 As Variant
Dim MyAnswer4 As Variant
Dim MyAnswer5 As Variant
MyAnswer1 = InputBox("Enter Growth Current Month")
Range("m3").value = MyAnswer1
MyAnswer2 = InputBox("Enter Growth Current Month+1")
Range("n3").value = MyAnswer2
MyAnswer3 = InputBox("Enter Growth Current Month+2")
Range("o3").value = MyAnswer3
MyAnswer4 = InputBox("Enter Growth Current Month+3")
Range("p3").value = MyAnswer4
MyAnswer5 = InputBox("Enter Growth Current Month+4")
Range("q3").value = MyAnswer5
End Sub
Sub Macro12()
ActiveCell.FormulaR1C1 = "='raw data'!R[-5]C"
Range("A7").Select
Selection.AutoFill Destination:=Range("A7:A500"), Type:=xlFillDefault
End Sub
Sub Macro13()
Range("C7").Select
Selection.ClearContents
End Sub
Sub Macro14()
For Each ws In ActiveWorkbook.Worksheets
If ws.Name <> "Raw" And ws.Name <> "Main" And ws.Name <> "Calendar" Then
For Each c In ws.Range("A50:A300")
ws.Rows(c.Row).Hidden = c.value = 0
Next
End If
Next
End Sub
Sub Macro15()
If Not Intersect(Target, Range("A7:AH500")) Is Nothing Or _
Not Intersect(Target, Range("A7:AH500")) Is Nothing Then
Target.Interior.ColorIndex = 3
End If
End Sub
A macro that is "active" is doing something, i.e. it is executing code. While it is executing code, the user can't do anything. So either the macro is active or the user is active.
What you want is to respond to an event, in this case the Worksheet.Change event:
Private Sub Worksheet_Change(ByVal Target as Range)
Target.Interior.ColorIndex = 3
End Sub
See https://msdn.microsoft.com/en-us/library/office/ff839775.aspx
Great answer from Paul - try this to get it working;
Private Sub Worksheet_Change(ByVal Sh As Object, ByVal Target as Range)
Target.Interior.ColorIndex = 3
End Sub

Track changes by creating timestamp

The original code (Excel VBA) I found works fine for keeping track of one column:
Private Sub Worksheet_Change(ByVal Target As Range)
'Update 20140722
Dim WorkRng As Range
Dim Rng As Range
Dim xOffsetColumn As Integer
Set WorkRng = Intersect(Application.ActiveSheet.Range("P:P"), Target)
xOffsetColumn = 2
If Not WorkRng Is Nothing Then
Application.EnableEvents = False
For Each Rng In WorkRng
If Not VBA.IsEmpty(Rng.Value) Then
Rng.Offset(0, xOffsetColumn).Value = Date
Rng.Offset(0, xOffsetColumn).NumberFormat = "dd-mm-yyyy"
Else
Rng.Offset(0, xOffsetColumn).ClearContents
End If
Next
Application.EnableEvents = True
End If
End Sub
I want to track two columns. Below, you will find the newly added code. It does not work, even though I changed variable names after the Dim (by adding a b). Simple copy-pasting the old code and then only change the range from P:P to S:S and the xOffsetColumn also does not work.
Private Sub Worksheet_Change_b(ByVal Target As Range)
'Update 20140722
Dim WorkRngb As Range
Dim Rngb As Range
Dim xOffsetColumnb As Integer
Set WorkRngb = Intersect(Application.ActiveSheet.Range("S:S"), Target)
xOffsetColumnb = 3
If Not WorkRngb Is Nothing Then
Application.EnableEvents = False
For Each Rngb In WorkRngb
If Not VBA.IsEmpty(Rngb.Value) Then
Rngb.Offset(0, xOffsetColumnb).Value = Date
Rngb.Offset(0, xOffsetColumnb).NumberFormat = "dd-mm-yyyy"
Else
Rngb.Offset(0, xOffsetColumnb).ClearContents
End If
Next
Application.EnableEvents = True
End If
End Sub
This modification to your original Worksheet_Change event macro should take care of both columns including pasting multiple values into a range that encompasses one or both columns.
Private Sub Worksheet_Change(ByVal Target As Range)
'Update 20150930
If Not Intersect(Target, Union(Columns("P"), Columns("S"))) Is Nothing Then
On Error GoTo bm_Safe_Exit
Application.EnableEvents = False
Dim rng As Range
For Each rng In Intersect(Target, Union(Columns("P"), Columns("S")))
If Not VBA.IsEmpty(rng) Then
rng.Offset(0, 2 - CBool(rng.Column = 19)) = Date
rng.Offset(0, 2 - CBool(rng.Column = 19)).NumberFormat = "dd-mm-yyyy"
Else
rng.Offset(0, 2 - CBool(rng.Column = 19)).ClearContents
End If
Next rng
End If
bm_Safe_Exit:
Application.EnableEvents = True
End Sub
To simply the offset, I simply worked back two columns from column P to column N. I'm not sure why the second event macro sample only moved back to column P; I didn't think it was your intention to overwrite the values in column P.
The Application.ActiveSheet.Range("P:P") column reference was unnecessary and potentially dangerous if the event macro was triggered by code that changed one of the values while another worksheet held the ActiveSheet property. Worksheet code pages are private by default; module code pages are public by default. You can reference cells and ranges without explicitly declaring their parent in a worksheet code sheet while that is bad coding practice on a module code sheet.
I also changed the value used for the timestamp from Date to Now. The cell formatting will still only display the date but if you ever need it, you will have the time as well.