-1

I’m trying to loop a Macro that will run through all worksheets, Except the "TOC" sheet.
-Each worksheet will have a Table Created, formatted, and have formulas put in.

However, the macro tries to begin on the "TOC" sheet, which then causes an error.

I currently have:

Sub BrandRank_()

Dim wb As ThisWorkbook
Dim ws As Worksheet
Dim TableName As String
Dim LstObj As ListObjects
Dim LastRow As Long

For Each ws In ThisWorkbook.Worksheets
Select Case ws.Name
Case Is = "TOC"

Case Else
With ws

‘Insert Table
TableName = "MyTable"
Range("A3").CurrentRegion.Select
ActiveSheet.ListObjects.Add(xlSrcRange, Selection.CurrentRegion, , xlYes).Name = TableName

'Apply a filter to $ Share for all Brands (Largest to Smallest)
ws.AutoFilter.Sort.SortFields.Clear
ws.AutoFilter.Sort.SortFields.Add2 Key:=Range("C3"), SortOn:=xlSortOnValues, Order:=xlDescending
ws.AutoFilter.ApplyFilter
ActiveSheet.ListObjects("Table1").ShowAutoFilterDropDown = False

'More Table Formatting Code

End With
End Select
Next ws
End Sub`

Update below to reflect Samuel Everson's suggestions:

In my scenario, Sheet1 (TOC) is the WorkSheet I want to skip over. So, should I write the code this way..?

Sub LoopThroughWorkSheets_()

    Dim wb As ThisWorkbook
    Dim ws As Worksheet
    Dim TableName As String
    Dim LstObj As ListObjects

    Set ws = ThisWorkbook.Sheets("Sheet2")

For Each ws In ThisWorkbook.Worksheets
    Select Case ws.Names
    Case Is = "TOC"

    Case Else
        With ws

'Insert Table with the Data starting in Column A, Row 3
    TableName = "MyTable"
    .Range("A3").CurrentRegion.Select
    .ListObjects.Add(xlSrcRange, Selection.CurrentRegion, , xlYes).Name = TableName

'Apply a filter to C3 (Largest to Smallest)
    .AutoFilter.Sort.SortFields.Clear
    .AutoFilter.Sort.SortFields.Add2 Key:=Range("C3"), SortOn:=xlSortOnValues, Order:=xlDescending
    .AutoFilter.ApplyFilter
    .ListObjects("TableName").ShowAutoFilterDropDown = False

'Add a Formula to F4, and Copy Down to Last Row in F (Using Column A as the LastRowData Reference)
    .Range("F4").Formula = "=(E4/(E4-G4))-1"
    .Range("F4").NumberFormat = "0.0%"
    .Range("F4").AutoFill Destination:=Range("F4:F" & Range("A" & Rows.Count).End(xlUp).Row)

'Update Number Format in Column E & G
    .Columns("E").NumberFormat = "$#,##0"
    .Columns("G").NumberFormat = "$#,##0"


        End With
    End Select
Next ws

End Sub
CDay
  • 99
  • 1
  • 10
  • 3
    The problem is that you're not actually using the `With ws`. – BigBen Apr 27 '20 at 19:58
  • are you sure the worksheet is named `TOC`. No extra space or anything? Also, you can remove `Select Case` and just use `If ws.Name <> "TOC" Then ...` (assuming "TOC" is correct name.) – Scott Holtzman Apr 27 '20 at 20:40
  • @BigBen - that wouldn't cause the code to not execute the `Case Is = "TOC"` though. – Scott Holtzman Apr 27 '20 at 20:41
  • 3
    But `Range("A3").CurrentRegion.Select` is problematic @ScottHoltzman, as it will execute on the `ActiveSheet` which is presumably the ToC. – BigBen Apr 27 '20 at 20:42
  • 1
    @BigBen - 100% agree, but it's not the problem the OP is asking about. That will be question two after he solves the first one! (hahaha) – Scott Holtzman Apr 27 '20 at 20:42
  • 1
    My guess is this is an X-Y problem... OP thinks the current setup should be the approach to do X (namely skip the TOC sheet), but the actual problem (Y) is the implicit and explicit `ActiveSheet`s. – BigBen Apr 27 '20 at 20:44
  • 1
    @BigBen - good point. I think you are 100% right. You can refactor that code as answer. I am signing off. I am too tired to see the obvious. – Scott Holtzman Apr 27 '20 at 20:46
  • Re your updated code: you have removed the implicit references to the ActiveSheet, that's good! But you continue to use Select and Selection, which only work on the ActiveSheet. You need to remove those, and act on your ranges directly. Something like `.ListObjects.Add(xlSrcRange, .Range("A3").CurrentRegion, , xlYes).Name = TableName` – chris neilsen Apr 29 '20 at 05:25
  • @chrisneilsen if I use: `.ListObjects.Add(xlSrcRange, .Range("A3").CurrentRegion, , xlYes).Name = TableName` then I get a "Compile Error: Invalid or unqualified reference"..? – CDay May 06 '20 at 16:10

1 Answers1

3

As pointed out in comments, you're not using your With ws statement. I saw this issue arrise in another one of your questions earlier in the month too so I thought I'd give an explanation for how to use the statement correctly.

Consider this code:

Sub WithStatementExample()
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets("Sheet1")

With ws
    Range("A1").Value = "This cell is A1 and this sheet name is " & ws.Name
    ActiveSheet.Range("A2").Value = "This cell is A2 and this sheet name is " & ws.Name
End With

End Sub 

Now although our statements are encapsulated within the With statement, the output to cells A1 and A2 will actually both be on whatever sheet is active at the time the code is executed.

As example, consider this new workbook with 3 sheets, all with default names:

New workbook with 3 blank sheets

If we execute the code based on the workbook in that screenshot, the results will go where we are assuming they will - Sheet1 cells A1 and A2. But if we run the code whilst Sheet3 is active, the code outputs the values to Sheet3 cells A1 and A2.

Example output of code with Sheet3 selected

This is because within our With statement, we have not used our ws worksheet as the object qualifier for the Range property.

The range property can apply to both the Application and Worksheet objects and per Application.Range documentation:

When used without an object qualifier, this property is a shortcut for ActiveSheet.Range (it returns a range from the active sheet; if the active sheet isn't a worksheet, the property fails).

To get the statements within the With block using the object, type a . in front of your statements:

Sub WithStatementExample()
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets("Sheet1")

With ws
    .Range("A1").Value = "This cell is A1 and this sheet name is " & ws.Name
    .Range("A2").Value = "This cell is A2 and this sheet name is " & ws.Name
End With

End Sub 

Now the output will only go to the sheet assigned to the ws variable - in this case, Sheet1.

Gif showing all from sheet3 to sheet1, demonstrating where the output is

If you need some statements to be applied to other sheets but executed within your With block, you are better off using an explicit reference to the workbook rather than ActiveSheet - This will help explain more - (How to avoid using select in vba)

So if the ListObjects in your code were actually on Sheets("TOC") not whichever ws the loop is referring to, I'd change ActiveSheet.ListObjects... and ActiveSheet.ListObjects("Table1")... to:

wb.Sheets("TOC").ListObjects...
wb.Sheets("TOC").ListObjects("Table1")...

With your For Each...Next loop, your variable ws in this case represents an object - specifically a worksheet. The variable ws is assigned the next object at each iteration, so you don't need to set the worksheet object to the variable independently to the loop.

In my example above I had 3 sheets. The two below code blocks will achieve the same outcome, one via a For Each...Next loop and the other without.

For Each...Next approach:

Sub ForEachExample()
Dim ws As Worksheet

For Each ws In ThisWorkbook.Worksheets
    Debug.Print ws.Name
Next ws
End Sub

No loop approach:

Sub NoLoopExample()
Dim ws As Worksheet

Set ws = ThisWorkbook.Worksheets(1)     'The same as Sheets(1) or Sheets("Sheet1")
Debug.Print ws.Name

Set ws = ThisWorkbook.Sheets(2)         'The same as Worksheets(2) or Sheets("Sheet2")
Debug.Print ws.Name

Set ws = ThisWorkbook.Sheets("Sheet3")  'The same as Worksheets(3) or Sheets(3)
Debug.Print ws.Name

End Sub

Both of them will output the following to the immediate window in the VBE:

Sheet1
Sheet2
Sheet3

In your updated code, you are setting ws to ThisWorkbook.Sheets("Sheet2").

  1. You will need to have a worksheet named "Sheet2" in your workbook or you will get a Runtime Error 9: subscript out of range error.
  2. The next line in your code is the For Each...Next statement which will set ws to the first sheet in the Worksheets collection, regardless of the previous line of code.

What I'm pointing out is that line of code is redundant in your current procedure and depending on the names of your worksheets, will probably be causing a runtime error.

See the For Each...Next statement documentation.


With your Select Case statement, your first line Select Case ws.Names should throw a Compile Error: Type mismatch. ws.Names returns the collection of worksheet specific names based on the object qualifyer (to it's left) - in this case ws. Because we are looking for the sheets name specifically, it should be ws.Name which returns the name of the object qualifyer.

So to take that into account it could look something like:

For Each ws In ThisWorkbook.Worksheets
    Select Case ws.Name
        Case "TOC"
            'Do nothing
        Case Else
            'Do your actions here
    End Select
Next ws

As a side note, you could also achieve the same with an If...Else statement like so:

For Each ws In ThisWorkbook.Worksheets
    If ws.Name = "TOC" Then    'Note by using = "TOC" must match exactly, including letter casing. 
        'Do nothing
    Else
        'Do your actions here
    End If
Next ws
Samuel Everson
  • 2,097
  • 2
  • 9
  • 24
  • Thank you so much for taking the time to explain the `with ws` statement! I still have a long way to go with understanding object qualifiers and other requirements. I do try to not use `Select` or ActiveSheet`, but can’t seem to get the code to execute correctly. Which is probably the issue with the `with ws` statement. Your explanation did help me better understand the `with ws` statement… However, if I use `Set ws = ThisWorkbook.Sheets(“Sheet2”), How would the Macro know to loop through to “Sheet3” – “Sheet6”? – CDay Apr 28 '20 at 18:59
  • In my scenario, Sheet1 (TOC) is the WorkSheet I want to skip over. So, should I write the code this way..? `Sub LoopThroughWorkSheets_() Dim wb As ThisWorkbook Dim ws As Worksheet Dim TableName As String Dim LstObj As ListObjects Set ws = ThisWorkbook.Sheets("Sheet2") For Each ws In ThisWorkbook.Worksheets Select Case ws.Names Case Is = "TOC" Case Else With ws` – CDay Apr 28 '20 at 19:09
  • Updated in OP. Sorry, I don't know how to insert a comment with the New Code and make it 'Eye Friendly' – CDay Apr 28 '20 at 19:10
  • My answer was more general principal in nature about the with statement as the rest of your code looks OK. I'll update my answer to include some information on incorporating the for loop. – Samuel Everson Apr 28 '20 at 22:15
  • @CariDay I'd added some more info now to the answer about how the `For Each...Loop` works, and an error in the `Select Case` – Samuel Everson Apr 29 '20 at 04:25
  • 1
    This makes a lot of sense! Yes - the TOC is Sheet1 (TOC).. With using the `If...Else` statement, the first action `'Insert Table with the Data starting in Column A, Row 3` is stating: Invalid or unqualified reference. I have: `For Each ws In ThisWorkbook.Worksheets If ws.Name = "TOC" Then 'Do Nothing Else 'Insert Table with the Data starting in Column A, Row 3 TableName = "MyTable" .ListObjects.Add(xlSrcRange, Range("A3").CurrentRegion, , xlYes).Name = "TableName"` – CDay May 06 '20 at 16:21
  • 1
    Should I be adding `For Each ws In ThisWorkbook.Worksheets - If ws.Name = "TOC" Then 'Do Nothing - Else - With ws`. That way I'm not worrying about all the `.Range and .Autofilter (s)` showing as Invalid or Unqualified reference? – CDay May 06 '20 at 16:50
  • He @CariDay. The `With` statement can be before or within your loop. Either way will work fine, It all comes down to where you actually need it. From the looks of things starting it after the `else` statement looks good. – Samuel Everson May 06 '20 at 20:42
  • So I tried all of that, and now I'm getting an `Run-time error '1004': The worksheet data for a table needs to be on the same sheet as the table.` `For Each ws In ThisWorkbook.Worksheets If ws.Name = "TOC" Then 'Do Nothing Else With ws 'Insert Table TableName = "MyTable" .ListObjects.Add(xlSrcRange, Range("A3").CurrentRegion, , xlYes).Name = TableName` – CDay May 07 '20 at 16:16
  • @CariDay you should ask a new question regarding your new issue. – Samuel Everson May 07 '20 at 20:36