List hidden worksheets - excel

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

Related

Skip some worksheets in loop function in Excel VBA

I want to have a button replace the info on MOST sheets with the info from a master sheet when a button is clicked. However, I want it to skip some sheets.
I have the below code that works, but there are 2 sheets I want it to skip when running. How can I specify that is skip the sheets named "Dates" and "Monthly"
Sub Button4_Click()
Dim wsVar As Worksheet
For Each wsVar In ThisWorkbook.Sheets
With wsVar
.Range("B9:M30").Value = Worksheets("BASE").Range("B9:M30").Value
End With
Next wsVar
End Sub
You can use an IF statement to check the name of the worksheet and then act accordingly.
For Each wsVar In ThisWorkbook.Sheets
If wsVar.Name = "foo" Or wsVar.Name = "bar" Then
' do nothing
Else
With wsVar
.Range("B9:M30").Value = Worksheets("BASE").Range("B9:M30").Value
End With
End If
Next wsVar
This code will exclude all sheets listed in the Exclude array. Note that sheets name comparisons are carried out case-insensitive and leading or trailing blanks are deemed unintentional and removed.
Sub Button4_Click()
Dim wsVar As Worksheet
Dim Exclude() As String
Dim i As Integer
Exclude = Split("Dates,Monthly", ",")
For Each wsVar In ThisWorkbook.Worksheets
With wsVar
For i = UBound(Exclude) To 0 Step -1
If StrComp(wsVar.Name, Trim(Exclude(i))) = 0 Then Exit For
Next i
If i >= 0 Then .Range("B9:M30").Value = Worksheets("BASE").Range("B9:M30").Value
End With
Next wsVar
End Sub
With a small change, you might convert this same function to process only listed sheets. To identify sheets for action positively, rather than negatively, is the safe way, generally speaking. But if you decide to do that, please also do rename the array :-)

How to refer multiple sheets in case statements

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

VBA Add second Sheet with same Name

I have a CommandButton which opens a UserForm and create a copied Sheet with the name of the ComboBox Value.
This is My Code:
Private Sub CommandButton1_Click()
[UserForm1].Show ' Open UserForm
End Sub
Private Sub CommandButton2_Click()
Dim ws As Worksheet
ActiveWorkbook.Sheets("Sheet1").Visible = True ' Unhide Sheet
Sheets("Sheet1").Copy _
Before:=ActiveWorkbook.Sheets("Sheet1") ' Copy Sheet
Set ws = ActiveSheet
ws.Name = ComboBox1.Value ' Name Sheet
[UserForm1].Hide ' Close UserForm
ActiveWorkbook.Sheets("Sheet1").Visible = False ' Hide Sheet again
End sub
Now my problem is, if there are two machines with name "Machine Type 1" Excel gets an Error. So what do i have to change in my code, that the second sheet would named e.g. "Machine Type 1 (2)?
Thanks for your help.
you could try this
Private Sub CommandButton1_Click()
If IsSheetThere(ComboBox1.Value) Then 'if some sheet with chosen name already there
Sheets(ComboBox1.Value).Copy Before:=Sheets(10) ' copy the existing sheet
With ActiveSheet 'reference just copied sheet
.UsedRange.Clear 'clear its content
Sheets("Sheet1").UsedRange.Copy ActiveSheet.Range("A1") ' copy Sheet1 content and paste into it
End With
Else 'otherwise
Sheets("Sheet1").Copy Before:=Sheets(Sheets.Count) ' make a copy of "Sheet1" sheet
ActiveSheet.Name = ComboBox1.Value 'and rename it as per chosen name
End If
Me.Hide
End Sub
Function IsSheetThere(shtName As String) As Boolean
On Error Resume Next
IsSheetThere = Not Sheets(shtName) Is Nothing
End Function
the code line:
Sheets(ComboBox1.Value).Copy Before:=Sheets(10) ' copy the existing sheet
is the one that leaves Excel the burden of somehow "counting" the number of already existing sheets with the chosen name, and name the new one appropriately
You can use the following sub which calls the below function, just apply the same logic using .Copy
Sub create_new_sheet_with_name(name As String, wb As Workbook, aftersheet As Variant)
Dim i As Integer
i = 2
If sheet_name_exists(name, wb) Then
Do While sheet_name_exists(name & " (" & i & ")", wb)
i = i + 1
Loop
wb.Sheets.Add(after:=aftersheet).name = name & " (" & i & ")"
Else
wb.Sheets.Add(after:=aftersheet).name = name
End If
End Sub
Function sheet_name_exists(name As String, wb As Workbook) As Boolean
For Each sheet In wb.Worksheets
If sheet.name = name Then
sheet_name_exists = True
Exit Function
End If
Next sheet
sheet_name_exists = False
End Function
here's an example of how to use the sub:
Sub test()
create_new_sheet_with_name "hi", ThisWorkbook, ThisWorkbook.Sheets(1)
'this adds a new sheet named "hi" to thisworkbook after thisworkbook.sheets(1)
End Sub
Technically this isn't an answer to this question... but it's better because it will help you solve this and many other coding tasks on your own.
There is a simple way to create VBA code for most basic tasks.
If there's something Excel can do that you want to be able to do programmatically, just Record a Macro of yourself performing the action(s), and then look at the code that Excel generated.
I have a terrible memory, I can't remember commands I used yesterday. So it's not only quicker and less frustrating for others for me to figure it out myself, but the more often I do that, the quicker I'll learn (without asking others to do the thinking for me on a basic question).
I fact, I'm guess that the majority of veteran VBA coders learned at least partly by analyzing recorded macros. I know I did.

How to declare a worksheet name as wildcard?

I have a worksheet named "Photo Sheet" that i would like to declare in my codes.
Const myWorksheet = "Photo Sheet"
My question, if i have another sheet called "Photo Sheet (2)" is there a way to declare the variable as wildcard that would take any sheet starting with "Photo Sheet*" ?
Not quite clear what you want to do, but you can iterate over the worksheets, using the Like operator to select the ones which have the appropriate name:
Sub test()
Dim ws As Worksheet
For Each ws In Worksheets
If ws.Name Like "Photo Sheet*" Then Debug.Print ws.Name
Next ws
End Sub
This will print the names of all worksheets that begin "Photo Sheet". Of course, rather than printing their names you could e.g. put these worksheets in a collection for further processing.
There isn't if you use an equity operator for testing it, but you can do something similar with Like:
Const SHEET_NAME = "Photo Sheet*"
Sub WhateverYourThingDoes()
Dim ws As Worksheet
For Each ws in Worksheets
If ws.Name Like SHEET_NAME Then
'Your code here.
End If
Next
End Sub
You can't use a wildcard to declare a worksheet directly, so no set shtPhotos = Sheets(Worksheet & "*"). A declaration like that has to be unambiguous or it would potentially return a collection, which can't be assigned to a non-array variable.
So, no wildcards. What you can do is loop through all your worksheets and check whether the sheet's name contains whatever text you're looking for:
Sub FindPhotos()
Const csSheet As String = "Photo Sheet"
Dim shtPhotos As Worksheet
For Each shtPhotos In ActiveWorkbook.Sheets
If InStr(1, shtPhotos.Name, csSheet) <> 0 Then
'do something
End If
Next shtPhotos
End Sub
This is going to look at every worksheet in the active workbook and see if their name contains the text in the constant. This will work fine if you want to perform the same steps with every worksheet that begins "Photo Sheets"; if you only want it to perform those steps with one such sheet, you'd add an Exit For after performing your steps:
For Each shtPhotos In ActiveWorkbook.Sheets
If InStr(1, shtPhotos.Name, csSheet) <> 0 Then
'do something
Exit For
End If
Next shtPhotos
You could also look for a flag on the sheets that are found, so that only sheets that have, say, today's date in a specified cell would be processed:
For Each shtPhotos In ActiveWorkbook.Sheets
If InStr(1, shtPhotos.Name, csSheet) <> 0 AND _
shtphotos.range("A1").value = Date Then
'do something
End If
Next shtPhotos

Obtain displayed order of Excel worksheets

I'd like to find the position of a worksheet as it is displayed in a workbook.
For example, assume I have a workbook starting with Sheet1, Sheet2 and Sheet3 in that order. Then a user drags Sheet2 to left, before Sheet1.
I want Sheet2 to return 1, Sheet1 to return 2 (and Sheet3 still to return 3).
I can't find a way to determine this in VBA.
This should do it:
Worksheets("Sheet1").Index
https://msdn.microsoft.com/en-us/library/office/ff836415.aspx
You can just iterate the Worksheets collection of the Workbook object. You can test yourself by running the following code, switch the order around in the UI, then run it again:
Option Explicit
Sub IterateSheetsByOrder()
Dim intCounter As Integer
Dim wb As Workbook
Set wb = ThisWorkbook
For intCounter = 1 To wb.Worksheets.Count
Debug.Print wb.Worksheets(intCounter).Name
Next intCounter
End Sub
To loop through all worksheets in a workbook use For Each WS in ThisWorkbook.Worksheets where WS is a worksheet object. Hence to obtain order of Excel worksheets as shown, we may also use the following code:
Sub LoopThroughWorksheets()
Dim WS As Worksheet
For Each WS In ThisWorkbook.Worksheets
Debug.Print WS.Name
Next
End Sub
To obtain an output like Worksheets("Sheet1").Index then you may use this code
Sub IndexWorksheet()
Dim WS As Worksheet, n As Long
For Each WS In ThisWorkbook.Worksheets
n = n + 1
If WS.Name = "Sheet1" Then Debug.Print n
Next
End Sub
You can use the Sheets object. In your example, reading Sheets(2).Name should return Sheet1.
Right answer provided by Anastasiya-Romanova, but missing some important details.
There are two methods of doing this. First, with a For Each loop:
Sub ListSheetNames()
Dim ws As Worksheet
For Each ws In ThisWorkbook.Worksheets
Debug.Print ws.Name
Next ws
End Sub
Second, with a basic For loop:
Sub ListSheetNames
Dim i As Long
For i = 1 to ThisWorkbook.Worksheets.Count
Debug.Print ThisWorkbook.Worksheets(i).Name
Next i
End Sub
You will find the second method will always output the names in the sheet index order, which is generally the order the sheets were created in unless you change the index. Simply rearranging the sheets from the workbook window won't change the index.
Therefore, the first method is the correct way to do this. It will always follow the tab order as you see on your screen.
Below code works even if sheet is renamed or its sequence is changed.
Sub Display_Sheet_Tab_Number()
Dim WorksheetName As String
Dim n As Integer
WorksheetName = Sheet1.Name
MsgBox Worksheetname
n = Sheets(WorksheetName).Index 'n is index number of the sheet
MsgBox "Index No. = " & n
End Sub

Resources