Protecting workbook structure will prevent a user from deleting sheets. But how could I (using VBA) prevent a user from deleting a particular sheet I designate? I've seen examples where an active sheet is prevented from deletion by
Set mymenubar = CommandBars.ActiveMenuBar
mymenubar.Controls("Edit").Controls("Delete sheet").Visible = False
in its Worksheet_Activate event but that of course only works if the sheet is activated.
Is there a way to prevent a sheet from being deleted whether active or no?
For clarity: I'm fine with the user deleting some sheets, just not a couple of particular sheets.
So protecting workbook structure won't work.
As far as I can tell, it isn't possible to natively tag a single sheet as non-deletable; and there isn't an event that can be used to detect when a sheet is about to be deleted so the workbook can be protected preventively.
However, here is one potential workaround:
Protect workbook structure: this will, as you indicate, prevent all sheets from being deleted.
Create a "Controls" sheet. On this sheet, maintain a list of all sheet names (except those you don't want to be deletable).
If users want to delete a sheet, they will have to select its name on the Controls sheet (e.g. in a data validation drop-down menu) and press a "Delete" button. This button will call a macro that temporarily unprotects the workbook, deletes the selected sheet, and then reprotects the workbook.
Of course, the users will have to get used to this way of deleting sheets (as opposed to just right-click > Delete on the sheet's tab). Still, this isn't crazy complicated.
As for how to achieve #2 i.e. maintaining that list of sheet names, I suppose you could make use of a UDF like this one (must be called as an array formula):
Function DeletableSheetNames() As String()
Application.Volatile
Dim i As Long
Dim sn() As String
With ThisWorkbook
ReDim sn(1 To .Sheets.Count)
For i = 1 To .Sheets.Count
With .Sheets(i)
If .Name = "DataEntry1" Or .Name = "DataEntry2" Then
'Don't include it in the list.
Else
sn(i) = .Name
End If
End With
Next i
End With
DeletableSheetNames = sn
End Function
You cannot stop users to delete a particular sheet but you could use the Workbook_BeforeSave() event to prevent the workbook from being saved if a particular sheet is missing. The documentation on this event precisely shows how to allow saving a workbook only when certain conditions are met. See http://msdn.microsoft.com/en-us/library/office/ff840057(v=office.14).aspx
I can prevent a sheet from being deleted via the Worksheet_BeforeDelete Event as follows:
Private Sub Worksheet_BeforeDelete()
Call ThisWorkbook.Protect("password")
Call MsgBox("This sheet cannot be deleted.", vbExclamation)
End Sub
This protects all sheets from being deleted, however if you add some event code on the ThisWorkbook module like the following :
Private Sub Workbook_SheetActivate(ByVal Sh As Object)
Call ThisWorkbook.Unprotect("password")
End Sub
I will then be able to delete any other sheet as soon as it is selected.
Bear in mind, you will lose copy and paste functionality between pages due to the page unlocking when it is selected.
"there isn't an event that can be used to detect when a sheet is about to be deleted"
Since Office 2013, it is possible with the SheetBeforeDelete event.
I found this solution, similar to Dan's, on ExtendOffice.com. Put this code on the Worksheet's module:
Private Sub Worksheet_Activate()
ThisWorkbook.Protect "yourpassword"
End Sub
Private Sub Worksheet_Deactivate()
ThisWorkbook.Unprotect "yourpassword"
End Sub
When you activate the sheet in question, the whole workbook is protected, and the "Delete" option is grayed out. When you switch to any other sheet, the workbook is free again. It's subtle because you only notice the change when you go to the "safe" sheet.
Answer is by adding the following code to each of the protected sheets:
Private Sub Worksheet_Deactivate()
ThisWorkbook.Protect , True
Application.OnTime Now, "UnprotectBook"
End Sub
And the following to a Module:
Sub UnprotectBook()
ThisWorkbook.Unprotect
End Sub
Check https://www.top-password.com/blog/prevent-excel-sheet-from-being-deleted/ for credits accordingly.
The following disables the menu when you right click on tab. This stops the delete option being available.
Sub tab_rclick_off()
Application.CommandBars("Ply").Enabled = False
End Sub
The following turns the menu back on.
Sub tab_rclick_on()
Application.CommandBars("Ply").Enabled = True
End Sub
This option is simple, concise, prevents any issues with data entry/editing with protected sheets and can be called from anywhere in code, ie in conjunction with log on permissions can be given to some and not others etc. foremost yourself.
Maybe you could try to protect the structure of the workbook in SheetBeforeDelete.
See my example:
Private Sub Workbook_SheetActivate(ByVal Sh As Object)
ThisWorkbook.Protect Structure:=False
End Sub
Private Sub Workbook_SheetBeforeDelete(ByVal Sh As Object)
If Sh.Name = "Example" Then
ThisWorkbook.Protect Structure:=True
End If
End Sub
Here is another answer from mine base on the idea of #Jean-François Corbett
You can use 'Protect WB Structure' and Event 'Workbook_SheetBeforeDelete' to achieve this.
The result is that an dialog will pop up said "Workbook is protected and cannot be changed."
Private Sub zPreventWShDel(WSh As Worksheet, Protection As Boolean)
Dim zPassword As String: zPassword = ""
Dim zWB As Workbook
Set zWB = WSh.Parent
If Protection Then
zWB.Protect zPassword, Protection
Else
zWB.Protect zPassword, Protection
End If
'Stop
End Sub
Private Sub Workbook_SheetBeforeDelete(ByVal Sh As Object)
Call zPreventWShDel(Sh, True)
End Sub
Private Sub Workbook_SheetActivate(ByVal Sh As Object)
Call zPreventWShDel(Sh, False)
End Sub
Do not Call the code on Sheet Deactivation like below. Because it will deactivate it. Sequence of running is Event_SheetBeforeDelete -> Event_SheetDeactivate.
Private Sub Workbook_SheetDeactivate(ByVal Sh As Object)
Call zPreventWShDel(Sh, False)
End Sub
I created a different approach to this.
On the sheet(s) you want protected, add this code:
Private Sub Worksheet_BeforeDelete()
ThisWorkbook.Worksheets("Unprotect Workbook").Visible = True
ThisWorkbook.Worksheets("Unprotect Workbook").Activate
ThisWorkbook.Protect
End Sub
Create the ("Unprotect Workbook") sheet and make the visibility: xlSheetVeryHidden
on the "Unprotect Workbook" sheet add a button or shape that you can assign a macro to.
on the "Unprotect Workbook" sheet, add this code:
Sub unprotectThisWorkbook()
ThisWorkbook.unprotect
ActiveSheet.Visible = xlSheetVeryHidden
End Sub
Assign the sub you added, "Sub unprotectThisWorkbook()", to the button on the "Unprotect Worksheet" sheet
When you delete the sheet you protected, the workbook is protected and it takes you to the unprotect worksheet as a notice to the user and as a way to unprotect the workbook. Once the button is clicked, the workbook is unprotected and the "unprotect sheet" is hidden again.
This will work for any sheet you want to protect.
Related
I want to clear the selection on sheet "sheet2" when leaving the sheet. (e.g reset to cell A1)
I tried:
Private Sub Worksheet_Deactivate()
ActiveSheet.Range("A1").Select
End Sub
And:
Private Sub Worksheet_Deactivate()
Sheets("Sheet2").Range("A1").Select
End Sub
But this is not working. (first one selects A1 on the current sheet, and the second one gives an error)
The reason why I want this, is because a macro has selected an object (Shape form control) that is protected (locked text). When a user leaves and returns to the sheet, while this object is still selected an error occurs:
You cannot use this command on a protected sheet. To use this command... etc
The reason why a macro selected the object in the first place, is because the user clicked on a hyperlink that would highlight this object. (I can't think of a different way then 'select' to highlight a shape form control)
Possible solution:
The only other method I can think of is have a Sub "Worksheet_Deactivate()" that first activates sheet "Sheet2" clears the selection to A1 and then returns to the sheet the user has initially clicked on when leaving the sheet..... but this feels cumbersome.
Is there another solution/method? any help is appreciated!
This will work so long as you are not working in a shared workbook:
Private Sub Workbook_SheetActivate(ByVal Sh As Object)
If Sh.Name = "Sheet2" Then Sh.Protect
End Sub
Private Sub Workbook_SheetDeactivate(ByVal Sh As Object)
If Sh.Name = "Sheet2" Then Sh.Unprotect
End Sub
Cheers!
Based on this answer, you can use the Workbook_SheetDeactivate event and get a WorkSheet object that you can then change the selection on without switching the active sheet.
Further reading seems to indicate Me will contain the sheet that has just been deactivated in the Worksheet_Deactivate handler, so you could also use that.
I have searched around for help but have been unsuccessful. I have found different codes to use a button on a worksheet to go back to the previous worksheet the user was on. What the company I work for wants are links on the top of almost each worksheet to function as a navigation pane for the users. I have created links at the top of the necessary sheets which all work as expected. I originally added code to my workbook to use a back link (previous sheet user was on, not the sheet before the current sheet) at cell "A5" and it worked for awhile but as I've been progressing on other items within the workbook, the code stopped working. I have compared the code on the working workbook with the nonworking workbook and they're the same and I don't believe other code is causing it to malfunction. Looking at the code below, does anyone have a suggestion for me?
On worksheets that will have a back button I have:
Option Explicit
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Selection.Count = 1 Then
If Not Intersect(Target, Range("A5")) Is Nothing Then
Call SelectLast
End If
End If
End Sub
In the ThisWorkbook object I have the following:
Private Sub Workbook_SheetDeactivate(ByVal Sh As Object)
LastSheet = Sh.Name
End Sub
Finally, I have the following in it's own module:
Public LastSheet As String
Sub SelectLast()
Application.Sheets(LastSheet).Select
End Sub
Any assistance would be greatly appreciated. Thanks!
Add a workbook-level name "LastSheet":
Then add this code in the ThisWorkbook module:
Private Sub Workbook_SheetDeactivate(ByVal Sh As Object)
'EDIT
ThisWorkbook.Names("lastsheet").RefersToR1C1 = "='" & Sh.Name & "'!R1C1"
End Sub
For your "Previous sheet" hyperlinks use:
=HYPERLINK("#LastSheet","Previous sheet")
If you wanted to get fancy with your "back" link and show the destination sheet name in the link:
=HYPERLINK("#lastsheet",
"<< back to - " & SUBSTITUTE(MID(CELL("address",lastsheet),1+
FIND("]",CELL("address",lastsheet)),200),"!$A$1",""))
Craig, this should do the trick for you
I made a label look like a hyperlink & operate as one. I have basically stored the name of the sheet in the label's captions and have the workbook select the sheet that matches that text
Private Sub Label1_Click()
Dim strLoc As String
strLoc = Label1.Caption
ThisWorkbook.Sheets(strLoc).Select
End Sub
As I very often have to problem, that the tick vanishes in the settings: Calculate before save. (I don't know the exact term as my office version is in German).
That's why I tried to use VBA to solve the problem. I used the following code in my Excel file:
Option Explicit
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, _
Cancel As Boolean)
If Application.CalculateBeforeSave = False Then
If MsgBox("Caution! Should >calculatebeforesave< be activated?", vbYesNo) = vbYes Then
Application.CalculateBeforeSave = True
Else
End If
Else
End If
End Sub
I put this into "Thisworkbook". But I would like this code to be ran in every workbook I work with (at least all these which allow for macros).
My suggestion was to write ActiveWorkbook_BeforeSave... instead of Workbook_BeforeSave and then put the code in a module in the PERSONAL Macro file. But this doesn't work.
I think you need to use the Excel Applications events rather than workbook events to achieve this, such as in this example
In your PERSONAL workbook right click and insert a a Class Module (Class 1)
Add something similar to below to Class 1:
Public WithEvents appevent As Application
Private Sub appevent_WorkbookBeforeClose(ByVal Wb As Workbook, Cancel As Boolean)
'Add what you would like to happen before a workbook closes
End Sub
Next open ThisWorksheet and add code along these lines (I think the PERSONAL workbook opens automatically when Excel starts):
Dim myobject As New Class1
Private Sub Workbook_Open()
Set myobject = Application
End Sub
I want the sheet Administrator to act like a button but I don't want to go to the actual sheet.
It should open up an UserForm and stay on the active sheet if click on Administrator.
You can create a public variable CurrentSheet, initialize it to ActiveSheet in the Workbook_Open event and then in the workbook's SheetActivate event either update the value of CurrentSheet or switch back to the previous current sheet and show the user form. Something like (in the Workbook code sheet):
Public CurrentSheet As Worksheet
Private Sub Workbook_Open()
Set CurrentSheet = ActiveSheet
End Sub
Private Sub Workbook_SheetActivate(ByVal Sh As Object)
If Sh.Name = "Administrator" Then
CurrentSheet.Activate
UserForm1.Show
Else
Set CurrentSheet = Sh
End If
End Sub
On Edit: To be safe you can also add the following code. This adds a layer of protection if something causes your project to reset after the Workbook_Open event. In the original code I was able to generate a crash when I purposely reset the project before activating Administrator.
Private Sub Workbook_SheetDeactivate(ByVal Sh As Object)
Set CurrentSheet = Sh
End Sub
This might render the code in Workbook_Open redundant, but personally I would keep it in since I don't like the idea of having uninitialized global variables, even if they will be initialized before I use them. Also -- if the workbook opens in Administrator (which might be some error condition) this will guarantee that CurrentSheet has a value.
I have a macro in my Excel file that is meant to determine whether a user can open it or not. However, it seems like, depending on the settings of the user's machine, Excel by default disables the macro. This ended up opening and revealing the contents in the xls file.
How can I make sure that the user has to accept running the macro or Excel will close the workbook?
UPDATED the code as it was not doing exactly what it was meant to...
This is pretty much the only way you can do this WITH a macro (thanks to #OlleSjögren for the insight :p)
Private Sub Workbook_BeforeClose(Cancel As Boolean)
Dim Current As Worksheet
Worksheets.Add(Before:=Worksheets(1)).Name = "Protection"
Worksheets("Protection").Cells(1, 1) = "Please activate macros to view this workbook."
For Each Current In Worksheets
If Current.Name <> "Protection" Then
Current.Visible = xlSheetVeryHidden
End If
Next Current
End Sub
Private Sub Workbook_Open()
Dim Current As Worksheet
For Each Current In Worksheets
Current.Visible = xlSheetVisible
Next Current
Application.DisplayAlerts = False
Worksheets("Protection").Delete
Application.DisplayAlerts = True
End Sub
The property xlSheetVeryHidden means that it cannot be made visible through the UI (VBA code only can change it).
I think you can't do it. But we can make a workaround.
First of all you need to hide all your worksheet and prevent user to unhide it. Link. Following the link you will see that you can make a sheet veryHidden. As the link shows, you need to add a password to vba code too.
Then you can to create a macro that adds an InputBox with password
Sub CheckPassword()
Dim password As String
password = InputBox("Enter the password") 'You can use a Form with Textbox [PasswordChar] to put ****
If password = "myPass" Then
Sheets(3).Visible = xlSheetVisible
Sheets(3).Select
End If
End Sub
Finally you can attach a sub in the close event.
Private Sub Workbook_BeforeClose(Cancel As Boolean)
Sheets(3).Visible = xlSheetVeryHidden
End Sub