I have a spreadsheet that is edited by multiple users. To prevent tampering with previous data the cells are locked once data has been entered and the file saved. I have a few small bugs in the code though:
Even if the user has saved manually and then exits the application they are still prompted to save again.
The cells should be locked after a save when the application is running and not just when it is exited. Previously I had this code in the before_save event but the cells were being locked even if a save_as event was cancelled so I removed the code for now. Fixed
(Edit: I've just realised how obvious this error was. I even said it in this statement! Trying to lock cells after a save event using a before save event sub! )
Code
With ActiveSheet
.Unprotect Password:="oVc0obr02WpXeZGy"
.Cells.Locked = False
For Each Cell In ActiveSheet.UsedRange
If Cell.Value = "" Then
Cell.Locked = False
Else
Cell.Locked = True
End If
Next Cell
.Protect Password:="oVc0obr02WpXeZGy"
End With
The workbook open, hide all sheets and show all sheets subs are used to force the end user into enabling macros. Here is the full code:
Option Explicit
Const WelcomePage = "Macros"
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
Dim ws As Worksheet
Dim wsActive As Worksheet
Dim vFilename As Variant
Dim bSaved As Boolean
'Turn off screen updating
With Application
.EnableEvents = False
.ScreenUpdating = False
End With
'Record active worksheet
Set wsActive = ActiveSheet
'Prompt for Save As
If SaveAsUI = True Then
vFilename = Application.GetSaveAsFilename("", fileFilter:="Excel Files (*.xls), *.xls")
If CStr(vFilename) = "False" Then
bSaved = False
Else
'Save the workbook using the supplied filename
Call HideAllSheets
ThisWorkbook.SaveAs vFilename
Application.RecentFiles.Add vFilename
Call ShowAllSheets
bSaved = True
End If
Else
'Save the workbook
Call HideAllSheets
ThisWorkbook.Save
Call ShowAllSheets
bSaved = True
End If
'Restore file to where user was
wsActive.Activate
'Restore screen updates
With Application
.ScreenUpdating = True
.EnableEvents = True
End With
'Set application states appropriately
If bSaved Then
ThisWorkbook.Saved = True
Cancel = True
Else
Cancel = True
End If
End Sub
Private Sub Workbook_Open()
Application.ScreenUpdating = False
Call ShowAllSheets
Application.ScreenUpdating = True
ThisWorkbook.Saved = True
End Sub
Private Sub HideAllSheets()
Dim ws As Worksheet
Worksheets(WelcomePage).Visible = xlSheetVisible
For Each ws In ThisWorkbook.Worksheets
If Not ws.Name = WelcomePage Then ws.Visible = xlSheetVeryHidden
Next ws
Worksheets(WelcomePage).Activate
End Sub
Private Sub ShowAllSheets()
Dim ws As Worksheet
For Each ws In ThisWorkbook.Worksheets
If Not ws.Name = WelcomePage Then ws.Visible = xlSheetVisible
Next ws
Worksheets(WelcomePage).Visible = xlSheetVeryHidden
End Sub
'Lock Cells upon exit save if data has been entered
Private Sub Workbook_BeforeClose(Cancel As Boolean)
Dim Cell As Range
With ActiveSheet
.Unprotect Password:="oVc0obr02WpXeZGy"
.Cells.Locked = False
For Each Cell In ActiveSheet.UsedRange
If Cell.Value = "" Then
Cell.Locked = False
Else
Cell.Locked = True
End If
Next Cell
.Protect Password:="oVc0obr02WpXeZGy"
End With
End Sub
Thanks :)
It is asking for them to save before exiting even though they have already saved because of these lines:
'Save the workbook
Call HideAllSheets
ThisWorkbook.Save
Call ShowAllSheets
bSaved = True
You are changing the worksheet after saving it (by calling ShowAllSheets) so it does need to be saved again. The same is true of the saveAs code.
I fixed the second problem by using another IF. This ensures the cells are only locked if the data is saved:
'Lock Cells before save if data has been entered
Dim rpcell As Range
With ActiveSheet
If bSaved = True Then
.Unprotect Password:="oVc0obr02WpXeZGy"
.Cells.Locked = False
For Each rpcell In ActiveSheet.UsedRange
If rpcell.Value = "" Then
rpcell.Locked = False
Else
rpcell.Locked = True
End If
Next rpcell
.Protect Password:="oVc0obr02WpXeZGy"
Else
MsgBox "The LogBook was not saved. You are free to edit the RP Log again", vbOKOnly, "LogBook Not Saved"
End If
End With
Related
I've got a list of hyperlinks leading to multiple different hidden sheets in a workbook, using the following for each:
Private Sub Worksheet_FollowHyperlink(ByVal Target As Hyperlink)
Application.ScreenUpdating = False
Worksheets("LL - JLL").Visible = xlSheetVisible
Sheets("LL - JLL").Visible = True
Sheets("LL - JLL").Select
Application.ScreenUpdating = True
End Sub
From what I can tell this now applies to every hyperlink on the sheet. Eevery hyperlink now leads to the same sheet, LL - JLL, whereas I would need each hyperlink to lead to a different sheet. For example,
Private Sub Worksheet_FollowHyperlink(ByVal Target As Hyperlink)
Application.ScreenUpdating = False
Worksheets("LL - JLL").Visible = xlSheetVisible
Sheets("LL - JLL").Visible = True
Sheets("LL - JLL").Select
Application.ScreenUpdating = True
Application.ScreenUpdating = False
Worksheets("LL - EMS").Visible = xlSheetVisible
Sheets("LL - EMS").Visible = True
Sheets("LL - EMS").Select
Application.ScreenUpdating = True
Application.ScreenUpdating = False
Worksheets("LL- CCURE").Visible = xlSheetVisible
Sheets("LL- CCURE").Visible = True
Sheets("LL- CCURE").Select
Application.ScreenUpdating = True
End Sub
The following code makes all hyperlinks on the sheet lead to the LL-CURE sheet, rather than their correspondent sheets.
Creating a new Sub for different hyperlinks leads to
Compile error:
Ambiguous name detected: Worksheet_FolowHyperlink
Any guidance would be greatly appreciated :)
Logic:
Find the range which the hyperlink is pointing to
Find the name of the sheet to which the above range refers to
Pass the name to a common sub to unhide and activate the sheet
Code:
Is this what you are trying?
Option Explicit
Private Sub Worksheet_FollowHyperlink(ByVal Target As Hyperlink)
Dim rng As Range
'~~> Get the range the hyperlink is referrig to
Set rng = Application.Evaluate(Target.SubAddress)
'~~> Unhide and activate the sheet
UnHideAndActivate rng.Parent.Name
End Sub
Private Sub UnHideAndActivate(shName As String)
Dim scrnUpdating As Boolean
Dim dsplyAlerts As Boolean
On Error GoTo Whoa
With Application
'~~> Get user's current setting
scrnUpdating = .ScreenUpdating
dsplyAlerts = .DisplayAlerts
'~~> Set it to necessary setting
.ScreenUpdating = False
.DisplayAlerts = False
End With
'~~> Unhide and activate the sheet
Worksheets(shName).Visible = xlSheetVisible
Worksheets(shName).Activate
LetsContinue:
With Application
'~~> Reset original settings
.ScreenUpdating = scrnUpdating
.DisplayAlerts = dsplyAlerts
End With
Exit Sub
Whoa:
MsgBox Err.Description
Resume LetsContinue
End Sub
I get the run time error when I open the workbook. The open function works great without the close function, but as soon as I add the close function I get the error. Any suggestions?
Private Sub Workbook_Open()
Application.ScreenUpdating = False
For Each cell In Range("A1:Z1")
If cell.Value = "X" Then
cell.EntireColumn.Hidden = True
Else
cell.EntireColumn.Hidden = False
End If
Next cell
End Sub
Private Sub Workbook_BeforeClose(Cancel As Boolean)
Dim ws As Worksheet
For Each ws In ThisWorkbook.Worksheets
ws.Protect "1962"
Next ws
ThisWorkbook.Protect "1962", True
ThisWorkbook.Save
End Sub
The error occurs because you protect the worksheet in the BeforeClose routine. Hence the Workbook_Open doesn't have access to update it the next time it is being opened. Try this:
Private Sub Workbook_Open()
Dim cell As Range
Application.ScreenUpdating = False
ActiveSheet.Unprotect "1962" '<<<<
For Each cell In Range("A1:Z1")
If cell.Value = "X" Then
cell.EntireColumn.Hidden = True
Else
cell.EntireColumn.Hidden = False
End If
Next cell
ActiveSheet.Protect "1962" '<<<<
End Sub
I'm currently working on a set of code that locks and unlocks a sheet based on the username of the current user, nothing fancy. This code works quite well, except during the after save portion. Intermittently, when saved on the company server, on my computer only (though its only been lightly tested on 3 computers), I get a 50290 error with anything that tries to modify the sheet - even application.wait. Eventually I traced this to the workbook not being ready (application.ready returns false after save, but true if I manually run the code or during the open workbook event). It seems that the standard procedure is to do while loop until application.ready = true, but that locks the computer up with no recovery. I've tried methods of slowing the loop down (sleep, doevent, wait) and none of those seem to fix the issue.
Any ideas?
Sub AuthorizedUser()
- initialize variables here
On Error GoTo errorhandler
Do 'attempt to wait until sheet is ready
DoEvents
Loop Until Application.Ready = True
- Do stuff to protect sheet here -
- If the sheet isn't ready, error state -
- Any change, such as application.wait, coloring cells, or protecting sheet is what the error state occurs on -
errorhandler:
MsgBox "Unlocker broke. Please hit the unlock button"
End Sub
Private Sub Workbook_AfterSave(ByVal Success As Boolean)
Call AuthorizedUser
End Sub
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
- do stuff to protect worksheet -
End Sub
Private Sub Workbook_Open()
Call AuthorizedUser
Application.Run "sheet1.ClearSheet"
End Sub
editted to remove the inner workings of the code. This code works just fine when excel is ready and does things as intended.
Let me know how this one works for you. If it works and you want it, I can make a list of the changes that I made
Option Explicit
Private Const THE_PASSWORD As String = "TDM"
Private Sub Auto_Open()
Call AuthProtect(False)
ThisWorkbook.Sheets(1).Cells.Clear
End Sub
Private Function GetAuth() As Long
With ThisWorkbook.Sheets("Authorized users")
Dim managers As Range
Set managers = .Range("A1").Resize(.Range("A1").End(xlDown).Row)
Dim workers As Range
Set workers = .Range("B1").Resize(.Range("B1").End(xlDown).Row)
End With
On Error GoTo errorhandler
While Not Application.Ready
DoEvents
Wend
On Error GoTo 0
Dim currentUser As String
currentUser = Environ$("username")
Dim auth As Long
Dim cell As Range
For Each cell In Union(managers, workers)
If LCase$(currentUser) = LCase$(cell.Value2) Then
auth = cell.Column
Exit For
End If
Next cell
GetAuth = auth
Exit Function
errorhandler:
GetAuth = -1
End Function
Private Sub AuthProtect(ByVal doProtect As Boolean)
On Error GoTo errorhandler
SpeedUp True
If doProtect Then
With ThisWorkbook
.Unprotect THE_PASSWORD
With .Sheets("Authorized users")
.Unprotect THE_PASSWORD
.Columns("B").Locked = True
.Protect THE_PASSWORD
.Visible = xlVeryHidden
End With
With .Sheets("Part Tracker")
.Unprotect THE_PASSWORD
.Rows("6:" & Rows.Count).Locked = True
.Protect THE_PASSWORD
End With
.Protect THE_PASSWORD
End With
Else
Select Case GetAuth
Case 1
With ThisWorkbook
.Unprotect THE_PASSWORD
With .Sheets("Authorized users")
.Visible = xlSheetVisible
.Unprotect THE_PASSWORD
.Columns("B").Locked = False
.Protect THE_PASSWORD
End With
.Protect THE_PASSWORD
End With
Case 2
With ThisWorkbook.Sheets("Part Tracker")
.Unprotect THE_PASSWORD
.Rows("6:" & Rows.Count).Locked = False
.Protect THE_PASSWORD, _
AllowInsertingRows:=True, _
AllowInsertingHyperlinks:=True, _
AllowDeletingRows:=True, _
AllowFiltering:=True, _
UserInterfaceOnly:=True, _
DrawingObjects:=False
.EnableOutlining = True
End With
Case -1
MsgBox "Error with Application.Ready state"
Case Else
With ThisWorkbook.Sheets("Authorized users")
Dim managers As Range
Set managers = .Range("A1").Resize(.Range("A1").End(xlDown).Row)
End With
Dim managerList As String
Dim cell As Range
For Each cell In managers
managerList = managerList & " " & cell.Value2 & vbCrLf
Next cell
MsgBox "You do not have write access to this file." & vbNewLine & "To request access, please seek out any of the following managers: " & vbCrLf & vbCrLf & managerList
End Select
End If
errorhandler:
SpeedUp False
End Sub
Sub SpeedUp(ByVal toggleOn As Boolean)
With Application
.Calculation = IIf(toggleOn, xlCalculationManual, xlCalculationAutomatic)
.ScreenUpdating = Not toggleOn
.DisplayStatusBar = Not toggleOn
.EnableEvents = Not toggleOn
End With
End Sub
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
Call AuthProtect(True)
End Sub
Private Sub Workbook_AfterSave(ByVal Success As Boolean)
Call AuthProtect(False)
End Sub
Private Sub Workbook_BeforeClose(Cancel As Boolean)
Call AuthProtect(True)
End Sub
i need some help to make my code work. My code is pretty simple. It goes through all my worksheets and make simple check in Columns A:D. If one cell has some text it will be locked. All free cells will stay for users unlocked.
It starts with other macros from my worksheet with Workbook_Open as Call command.
I used it all the time in each Worksheet separatly, but it won't work with new sheets so i decided to make it somehow global and dynamic for old sheets and new added sheets.
Old code:
Public Sub auo_open()
Dim strPassword As String
strPassword = "Athens"
With Tabelle1
.Unprotect Password:=strPassword
.Cells.Locked = True
On Error Resume Next
.Range("A:D").SpecialCells(xlCellTypeBlanks).Locked = False
On Error GoTo 0
.Protect Password:=strPassword, UserInterfaceOnly:=True, AllowFormattingCells:=True, AllowInsertingRows:=True
End With
Exit Sub
As you see it wasn't that good i had to put for each sheet a call command
new Code:
Public Sub Protection()
Dim ws As Worksheet
Dim strPassword As String
strPassword = "Athens"
For Each ws In ThisWorkbook.Worksheets
ws.Unprotect Password:=strPassword
ws.Cells.Locked = True
On Error Resume Next
ws.Range("A:D").SpecialCells(xlCellTypeBlanks).Locked = False
On Error GoTo 0
ws.Protect Password:=strPassword, UserInterfaceOnly:=True, AllowFormattingCells:=True, AllowInsertingRows:=True
Next ws
End Sub
Further to my comment above, try something like this. This code will automatically be applicable to the newly added worksheets
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
Dim strPassword As String: strPassword = "Athens"
On Error GoTo Whoa
Application.EnableEvents = False
If Not Intersect(Target, Columns("A:D")) Is Nothing Then
With Sh
.Unprotect strPassword
Cells.Locked = True
Range("A:D").SpecialCells(xlCellTypeBlanks).Locked = False
.Protect strPassword
End With
End If
Letscontinue:
Application.EnableEvents = True
Exit Sub
Whoa:
MsgBox Err.Description
Resume Letscontinue
End Sub
Screenshots
Put the code in the ThisWorkbook code area.
This is a follow up from this question, Lock Cells after Data Entry. I have progressed from asking that question but encountered more problems so felt I should ask a new question. The workbook is edited by multiple users. To prevent tampering with previous data the cells are locked once data has been entered and the file saved.
I have a couple of small bugs in the code:
If the user chooses to SaveAs then tries to save over an existing file the usual ' Do you want to replace this file?' dialog appears. If the user selects no there is a run time error. I have highlighted where the error is in the code below but I am unsure how to fix it.
If the user has entered data then tries to exit and save the file using the save dialog box that appears on close the file is saved but the data is not locked. I have been trying to call my main code to lock the cells upon an exit save but I keep encountering argument not optional errors.
Here is the full code:
Option Explicit
Const WelcomePage = "Macros"
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
'Written by Alistair Weir (alistair.weir#communitypharmacyscotland.org.uk, http://alistairweir.blogspot.co.uk/)
Dim ws As Worksheet
Dim wsActive As Worksheet
Dim vFilename As Variant
Dim bSaved As Boolean
'Turn off screen updating
With Application
.EnableEvents = False
.ScreenUpdating = False
End With
'Record active worksheet
Set wsActive = ActiveSheet
'Prompt for Save As
If SaveAsUI = True Then
MsgBox "Are you sure you want to save? Data entered cannot be edited once the file has been saved. Press cancel on the next screen to edit your data or continue if you are sure it is correct.", vbCritical, "Are you sure?"
vFilename = Application.GetSaveAsFilename("", fileFilter:="Excel Files (*.xls), *.xls")
If CStr(vFilename) = "False" Then
bSaved = False
Else
'Save the workbook using the supplied filename
Call HideAllSheets
'--> The vFilename Variant in the next line is the problem **
'--> when trying to overwrite an existing file **
ThisWorkbook.SaveAs vFilename
Application.RecentFiles.Add vFilename
Call ShowAllSheets
bSaved = True
End If
Else
'Save the workbook, prompt if normal save selected not save As
Call HideAllSheets
If MsgBox("Are you sure you want to save? Data entered cannot be edited after saving", vbYesNo, "Save?") = vbYes Then
ThisWorkbook.Save
Call ShowAllSheets
bSaved = True
Else
Cancel = True
End If
Call ShowAllSheets
End If
'Restore file to where user was
wsActive.Activate
'Restore screen updates
With Application
.ScreenUpdating = True
.EnableEvents = True
End With
'Set application states appropriately
If bSaved Then
ThisWorkbook.Saved = True
Cancel = True
Else
Cancel = True
End If
'Lock Cells before save if data has been entered
Dim rpcell As Range
With ActiveSheet
If bSaved = True Then
.Unprotect Password:="oVc0obr02WpXeZGy"
.Cells.Locked = False
For Each rpcell In ActiveSheet.UsedRange
If rpcell.Value = "" Then
rpcell.Locked = False
Else
rpcell.Locked = True
End If
Next rpcell
.Protect Password:="oVc0obr02WpXeZGy"
Else
MsgBox "The LogBook was not saved. You are free to edit the RP Log again", vbOKOnly, "LogBook Not Saved"
End If
End With
End Sub
Private Sub Workbook_Open()
Application.ScreenUpdating = False
Call ShowAllSheets
Application.ScreenUpdating = True
ThisWorkbook.Saved = True
End Sub
'Called to hide all the sheets but enable macros page
Private Sub HideAllSheets()
Dim ws As Worksheet
Worksheets(WelcomePage).Visible = xlSheetVisible
For Each ws In ThisWorkbook.Worksheets
If Not ws.Name = WelcomePage Then ws.Visible = xlSheetVeryHidden
Next ws
Worksheets(WelcomePage).Activate
End Sub
'Called to show the data sheets when macros are enabled
Private Sub ShowAllSheets()
Dim ws As Worksheet
For Each ws In ThisWorkbook.Worksheets
If Not ws.Name = WelcomePage Then ws.Visible = xlSheetVisible
Next ws
Worksheets(WelcomePage).Visible = xlSheetVeryHidden
End Sub
Thanks :)
Edit
For now I am solving problem 2 by bypassing excel's default 'do you want to save?' by doing this:
Private Sub Workbook_BeforeClose(Cancel As Boolean)
If MsgBox("Are you sure you want to quit? Any unsaved changes will be lost.", vbYesNo, "Really quit?") = vbNo Then
Cancel = True
Else
ThisWorkbook.Saved = True
Application.Quit
End If
End Sub
I am open to suggestions of a better way and still haven't solved the first problem.
One possibility is to write your own confirmations in a save function, like so:
Private Function SaveSheet(Optional fileName) As Boolean
HideAllSheets
If fileName = "" Then
ThisWorkbook.Save
SaveSheet = True
Else
Application.DisplayAlerts = False
If Dir(fileName) <> "" Then
If MsgBox("Worksheet exists. Overwrite?", vbYesNo, "Exists") = vbNo Then Exit Function
End If
ThisWorkbook.saveAs fileName
SaveSheet = True
Application.DisplayAlerts = True
End If
ShowAllSheets
End Function
And change your original code to something like:
If SaveAsUI Then
If MsgBox( _
"Are you sure you want to save? Data entered cannot be edited once the file has been saved. " & _
"Press cancel on the next screen to edit your data or continue if you are sure it is correct.", _
vbYesNo, "Are you sure?" _
) = vbYes Then
vFilename = Application.GetSaveAsFilename("", fileFilter:="Excel Files (*.xls), *.xls")
If vFilename <> "" Then
If SaveSheet(vFilename) Then bSaved = True
End If
End If
Else
If MsgBox( _
"Are you sure you want to save? Data entered cannot be edited after saving", _
vbYesNo, "Save?" _
) = vbYes Then
If SaveSheet("") Then bSaved = True
End If
End If
I've not fully tested the above, but it should give you some ideas.