Goal and Problem
My goal is to restrict access to different worksheets according to the username that currently uses the excel file.
I will have a minimum of 14 users (1 admin and 13 heads of department) and each one will have different access do the multiple existents worksheets. The admin will have access to all worksheets while the heads of department will each have access to a worksheet only associated with their department and at least 2 or 3 other worksheets.
Currently, I'm able to grant access to one worksheet but, as I said previously, I want them to access multiple worksheets.
What I've tried
I've tried to use arrays in multiple ways but none of them worked so far.
Select Case Application.UserName
Case "User 2"
Set GetAllowedSheet = Sheets(Array("Sheet2", "Sheet3", "Sheet4"))
Dim ArrayOne as Variant
ArrayOne = Array("Sheet2", "Sheet3", "Sheet4")
Select Case Application.UserName
Case "User 2"
Set GetAllowedSheet = Sheets(ArrayOne)
I did some research on google but nothing seems to quite match what I'm looking for.
Code
Private Sub Workbook_Open()
Showorksheets
End Sub
Sub Showorksheets()
Dim ws As Worksheet
Dim wsAllowed As Worksheet
If Application.UserName = "User 0" Then
For Each ws In Worksheets
ws.Visible = xlSheetVisible
Next
Exit Sub
End If
Set wsAllowed = GetAllowedSheet
wsAllowed.Visible = xlSheetVisible
For Each ws In Worksheets
If ws.Name <> wsAllowed.Name Then
ws.Visible = xlSheetHidden
End If
Next
End Sub
Function GetAllowedSheet() As Worksheet
Select Case Application.UserName
Case "User 1"
Set GetAllowedSheet = Sheets("Sheet1")
Case "User 2"
Set GetAllowedSheet = Sheets("Sheet2")
Case "User 3"
Set GetAllowedSheet = Sheets("Sheet3")
'...
Case Else
'...
End Select
End Function
As #BigBen suggest, hiding/unhiding is not the best way, because it can be easily bypassed.
Also, I do not know if there are any other macros in that workbook that affect worksheets, but dealing with hidden worksheets while coding can be a headache.
But anyways something like this could help.
Private Sub Workbook_Open()
'A workbook must have always at least 1 visible worksheet
Application.ScreenUpdating = False
Dim DictWK As Object
Dim UserLevel As Byte
Dim wk As Worksheet
Set DictWK = CreateObject("Scripting.Dictionary")
With ThisWorkbook
DictWK.Add .Worksheets("ONLY ADMIN").Name, 0 '0 because only admin can have it
DictWK.Add .Worksheets("ADMIN AND HEADERS").Name, 1
DictWK.Add .Worksheets("ASSISTANTS").Name, 2
DictWK.Add .Worksheets("EVERYBODY").Name, 99 'A workbook must have at least 1 visible worksheet, so make sure there is 1 always visible to everybody
End With
UserLevel = LVL_ACCESS("User 1") 'change this to however you detect the username
For Each wk In ThisWorkbook.Worksheets
If UserLevel <= DictWK(wk.Name) Then
wk.Visible = xlSheetVisible
Else
wk.Visible = xlSheetHidden
End If
Next wk
DictWK.RemoveAll
Set DictWK = Nothing
Application.ScreenUpdating = True
End Sub
User's level:
Function LVL_ACCESS(ByVal vUsername As String) As Byte
Select Case vUsername
Case "User 1"
LVL_ACCESS = 0
Case "User 2"
LVL_ACCESS = 1
Case "User 3"
LVL_ACCESS = 2
Case Else
'not recognized, no access
LVL_ACCESS = 99
End Select
End Function
Uploaded a sample to Gdrive: https://drive.google.com/open?id=1mI3LQd8QxLDlMl1bzz5hCFIwdOFCS2Nc
Because of the way you set up your case select as a function, it's hard to change it into what you need, but not impossible. You are on the right track with using an array. Here is an approximation of what you will need to rework your code into:
Sub Shosheets()
Dim ws As Worksheet
Dim i As Long
Dim allowed As Variant
allowed = getallowed
Sheets(Sheets.Count).Visible = xlSheetVisible
For Each ws In ThisWorkbook.Sheets
For i = 0 To UBound(allowed)
If allowed(i) = ws.Name Then
If ws.Visible = xlSheetHidden Then ws.Visible = xlSheetVisible
GoTo Nextloop
Else
If ws.Visible = xlSheetVisible Then ws.Visible = xlSheetHidden
End If
Next i
Nextloop:
Next ws
End Sub
Function getallowed() As Variant
Dim blah As Long
blah = 3
Select Case blah
Case 1
getallowed = Array("Sheet1")
Case 2
getallowed = Array("Sheet2", "Sheet3")
Case 3
getallowed = Array("Sheet2", "Sheet3", "Sheet5")
End Select
End Function
What this does is first of all, change your function to take an array to accommodate the selection of one or multiple sheets.
It will then iterate over all the worksheets, and over your array and match whether your worksheet name is present within the array. If so, unhide the sheet and go to next sheet iteration, if not, default to hiding the sheet.
Please note this will throw an error if you unhide the last visible sheet, so to prevent this the last sheet will be unhidden at the start, and hidden as and when necessary last. This prevents any sheet being the last to be hidden and throwing an error.
Also if you do not skip onto the next iteration when you have a hit in your allowed array, the next iteration will mismatch and hide the just unhidden sheet, therefore the Goto.Nextloop
Related
I'm trying to write code to have several Sheets in a file printed in one print job.
The Sheets to be printed are created dynamically; their names and the number of sheets differ each time, but I know that I want to print all sheets in the workbook apart from Keep1 and Keep2 (In real 7 different sheet names).
The reason I want to print all sheets in one job is that it could be many sheets, and this would mean a long wait and lots of print job pop-ups.
To realize the above, I thought of creating a selection of the sheets I want to print and then order to print.
I wrote the following:
Sub printtest()
Dim arr As Variant, sht As Worksheet
arr = Array("Keep1", "Keep2")
Application.DisplayAlerts = False
For Each sht In ThisWorkbook.Worksheets
If Not UBound(Filter(arr, sht.Name, True, vbtruecompare)) >= 0 Then
With sht.PageSetup
.Zoom = False
.FitToPagesWide = 1
End With
sht.Select False
End If
Next sht
SelectedSheets.PrintOut
Application.DisplayAlerts = True
End Sub
After running the code, I run into the following:
sht.Select False adds up each Sheet meeting the conditions to the current selection, but since the button is on active sheet Keep1 this sheet is part of the selection (and should not be):
The .FitToPagesWide = 1 is performed for each Sheet in the selection, but .FitToPagesTall is also set to 1 (I want to keep this as Automatic, but don't know how to.
I don't know how to reference the selection in my print job properly.
I tried:
sht.PrintOut which results in Run-time error 91 (Object variable or With block variable not set).
SelectedSheets.PrintOut which results ion Run-time error 424 (Object required).
My vba knowledge is limited and I can't find a way to reference the selected pages for the printout.
Thanks for looking into this and explaining what is wrong in this approach.
Print Multiple Worksheets
You rarely need to select anything which is shown in the following code.
It writes the worksheet names to the keys of a dictionary, which are actually an array, and uses this array (the keys) to reference the worksheets to be printed.
Sub PrintTest()
Dim Exceptions() As Variant: Exceptions = Array("Keep1", "Keep2")
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
Application.DisplayAlerts = False
Dim ws As Worksheet
For Each ws In ThisWorkbook.Worksheets
If IsError(Application.Match(ws.Name, Exceptions, 0)) Then
With ws.PageSetup
.Zoom = False
.FitToPagesWide = 1
.FitToPagesTall = 2
End With
dict.Add ws.Name, Empty
End If
Next ws
ThisWorkbook.Worksheets(dict.Keys).PrintOut
Application.DisplayAlerts = True
End Sub
You could try to make a string with only the worksheet names you want, excluding Keep1 and Keep2. Then take that string into an unidimensional array and use that array as your selection of worksheets:
Dim wk As Worksheet
Dim StringWk As String
Dim ArrayWk As Variant
'string of wk names
For Each wk In ThisWorkbook.Worksheets
If wk.Name <> "Keep1" And wk.Name <> "Keep2" Then StringWk = StringWk & wk.Name & "|"
Next wk
StringWk = Left(StringWk, Len(StringWk) - 1) 'clean last | delimiter in string
ArrayWk = Split(StringWk, "|")
Sheets(ArrayWk).Select
'code to print to pdf or whatever
'
'
'
'
'
Sheets("Keep1").Select 'deactivate selection
Erase ArrayWk
To create the array we use SPLIT:
Split
function
When I set following code, I'd like to filter in array content.
So I would like to filter named test1 and sheet1 sheet
I developed sample script as follows
But it returned wrong type does not match in If ws.Name Like list Then
Dim list as variant
list = Array("test1", "sheet1")
For Each ws In wb.Worksheets
If ws.Name Like list Then
End If
next
How to avoid this error ?
And how can I filter sheets ?
If someone has opinion, please let me know
Thanks
Sub a()
List = Array("test1", "sheet1")
x = Application.Match("test1", List, 0)
If IsError(x) Then
Debug.Print "Not Found"
Else
Debug.Print "Found at position " & x
End If
End Sub
Filtering by showing only listed sheets
How can I filter sheets?
a) To find equivalent names you can check if Application.Match() returns an error for non-findings via IsError (or alternatively if there are findings via IsNumeric). Note that matching is case independant.
b) Eventually you can set the worksheet' s .Visible property to
True (i.e. xlSheetVisible) or
False (i.e. xlSheetHidden).
Sub ShowOnlyListedSheets()
Dim list As Variant
list = Array("test1", "sheet1")
Dim ws As Worksheet
For Each ws In ThisWorkbook.Worksheets
If IsError(Application.Match(ws.Name, list, 0)) Then
ws.Visible = xlSheetHidden ' or: False
Else
ws.Visible = xlSheetVisible ' or: True
End If
Next
End Sub
I have tried following VBA code, where I want to run this code for all available worksheets in active workbook, I think I am making small mistake and as I am beginner I am not able to find it out, please help to fix it up
Sub ProtectFormulas()
Dim strPassword As String
Dim ws As Worksheet
For Each ws In Sheets
ws.Activate
.Unprotect
.Cells.Locked = False
.Cells.SpecialCells(xlCellTypeFormulas).Locked = True
.Cells.SpecialCells(xlCellTypeFormulas).FormulaHidden = True
.Protect AllowDeletingRows:=True
strPassword = 123456
ActiveSheet.Protect Password:=strPassword
Next ws
End With
End Sub
Any help would be appriciated by word of thanks.
There are 3 issues with your code:
There is no With block.
The following 2 lines will error if there is no formula in one of the sheets:
.Cells.SpecialCells(xlCellTypeFormulas).Locked = True
.Cells.SpecialCells(xlCellTypeFormulas).FormulaHidden = True
Because if there is no formula then .Cells.SpecialCells(xlCellTypeFormulas) is Nothing and therefore nothing has no .Locked and no .FormulaHidden methods.
You mix using Sheets and Worksheets. Note that those are not the same!
Sheets is a collection of all type of sheets (worksheets, chart sheets, etc)
Worksheets is a collection of only type worksheet
If you declare Dim ws As Worksheet and there is for example a chart sheet in your file, then For Each ws In Sheets will error because you try to push a chart sheet into a variable ws that is defined as Worksheet and cannot contain a chart sheet. Be as specific as possible and use Worksheets whenever possible in favour of Sheets.
The following should work:
Option Explicit
'if this is not variable make it a constant and global so you can use it in any procedure
Const strPassword As String = "123456"
Sub ProtectFormulas()
'Dim strPassword As String
'strPassword = "123456" 'remove this here if you made it global
Dim ws As Worksheet
For Each ws In ActiveWorkbook.Worksheets
With ws
.Activate 'I think this is not needed
.Unprotect Password:=strPassword 'unprotect probably needs your password too or this will not work once the worksheet was protected.
.Cells.Locked = False
Dim FormulaCells As Range
Set FormulaCells = Nothing 'initialize (because we are in a loop!)
On Error Resume Next 'hide error messages (next line throws an error if no forumla is on the worksheet
Set FormulaCells = .Cells.SpecialCells(xlCellTypeFormulas)
On Error Goto 0 ' re-enable error reporting! Otherwise you won't see errors if they occur!
If Not FormulaCells Is Nothing Then 'check if there were formulas to prevent errors if not
FormulaCells.Locked = True
FormulaCells.FormulaHidden = True
End If
.Protect AllowDeletingRows:=True, Password:=strPassword
End With
Next ws
End Sub
I am trying to write a script which will cycle through the worksheets in my workbook and delete the worksheet if the cells directly under the strings "detected", "not detected" and "other" are empty. If there is something entered under any of the three strings the worksheet shouldn't be deleted.
I have some code (below) which will delete the worksheet if a specific cell is empty, but I need to integrate a piece to FIND any of the three strings (if they are there, they will be in column A), and to offset this to check whether the cell below is empty.
Sub DeleteEmptyWorksheets()
Dim MySheets As Worksheet
Application.DisplayAlerts = False
For Each MySheets In ActiveWorkbook.Worksheets
If MySheets.Range(“A1”) = “” Then
MySheets.Delete
End If
Next
Application.DisplayAlerts = True
End Sub
The script will be used in processing COVID19 test results, so if you can help it will be extra karma points!!
Thankyou.
Here's a code that should assist you.
Sub DeleteEmptyWorksheets()
Dim MySheets As Worksheet
Dim rngTest As Range
Dim arTest
Dim blNBFound As Boolean
arTest = Array("detected", "not detected", "other")
Application.DisplayAlerts = False
For Each MySheets In ActiveWorkbook.Worksheets
blNBFound = False
For i = LBound(arTest) To UBound(arTest)
Set rngTest = MySheets.Range("A:A").Find(arTest(i))
If Not rngTest Is Nothing Then
If Len(rngTest.Offset(1, 0)) > 0 Then
blNBFound = True
Exit For
End If
End If
Next i
If blNBFound = False Then MySheets.Delete
Next
Application.DisplayAlerts = True
End Sub
I have a materials register I am creating
Due to regulation when a material (each material has its own worksheet with a 3 digit random number added on the end to allow the same name multiple times) is deleted it cannot actually be deleted, so to work around this my workbook hides the sheet and using a deletion check on the summary page hides the appropriate row.
However what I am struggling with is a function to restore the sheet,
I have the code I need to do this however I cannot find any function to list hidden sheets.
This list can be put into the work book in a hidden column so I can reference it with my macro but as I said I cannot find anyway to list only sheets that are hidden.
Thanks for your help
You could add to your code that does the hiding to write the name of the sheet that it is hiding to your other hidden tab, and add the reverse to your code that unhides it.
Not sure if the below is applicable to your situation, but you could also put some code in worksheet events to capture when the sheet is being made invisible
Private Sub Worksheet_Deactivate()
If Me.Visible = xlSheetHidden Then MsgBox "I have been hidden"
End Sub
Does this help ..
' Function to be used in array formula on sheet to list hidden sheets
Public Function ListHiddenSheets()
Dim hiddenSheets As New dictionary
Dim sheet As Worksheet
For Each sheet In Worksheets
If sheet.Visible <> xlSheetVisible Then hiddenSheets.Add sheet.Name, Null
Next sheet
Dim vRes() As Variant
ReDim vRes(0 To hiddenSheets.Count, 0 To 0)
Dim idx As Integer
For idx = 0 To hiddenSheets.Count - 1
vRes(idx, 0) = hiddenSheets.keys(idx)
Next idx
ListHiddenSheets = vRes
End Function
?
Hidden sheets can be Hidden or VeryHidden, to capture these:
ub ListEm()
Dim ws As Worksheet
Dim StrHid As String
Dim strVHid As String
For Each ws In ActiveWorkbook.Worksheets
Select Case ws.Visible
Case xlSheetVisible
Case xlSheetHidden
StrHid = StrHid & ws.Name & vbNewLine
Case Else
strVHid = strVHid & ws.Name & vbNewLine
End Select
Next
If Len(StrHid) > 0 Then MsgBox StrHid, vbOKCancel, "Hidden Sheets"
If Len(strVHid) > 0 Then MsgBox strVHid, vbOKCancel, "Very Hidden Sheets"
End Sub