1

Whenever I make changes across standard files, I like to take two copies of the sheets I'm changing to (1) preserve the original values, and (2) compare to the changed sheet (Sheet1!A1 - Sheet2!A1) to make sure there are no unexpected differences. Once I'm comfortable with the updates, I then delete the duplicate sheets.

The code that loops through the files and makes the changes works fine, it's the code that copies the sheets that I can't figure out. Each file has two sheets, the sheet names are all different.

This is my code. It's the Sheets(Array(Sheet... lines that are giving me error code 424, object required.

Sub PleaseWork()
  Dim wb As Workbook
  For Each wb In Application.Workbooks
    If wb.Name <> "PERSONAL.XLSB" Then

        Debug.Print wb.Name
        wb.Activate
        
        Sheets(Array(Sheet1.Name, Sheet2.Name)).Copy Before:=Sheets(1) 
        Sheets(Array(Sheet1.Name, Sheet2.Name)).Copy Before:=Sheets(1)

        Sheets(1).UsedRange.SpecialCells(xlCellTypeFormulas, 23).FormulaR1C1 = _
           "=ROUND('" & Sheets(3).Name & "'!RC-'" & Sheets(5).Name & "'!RC,4)"
        Sheets(2).UsedRange.SpecialCells(xlCellTypeFormulas, 23).FormulaR1C1 = _
           "=ROUND('" & Sheets(4).Name & "'!RC-'" & Sheets(6).Name & "'!RC,4)"
    
        Sheet2.Activate
        Columns("J:J").Insert Shift:=xlToRight, CopyOrigin:=xlFormatFromLeftOrAbove
        Range("I16:I18,AD2:AG14").ClearContents
        Range("N3,N21,X3,X21,AC21,AC3").Value = "Cash Flows"
    End If
    Next wb
  MsgBox "All Finished!"    
End Sub

I've tried variations using ActiveWorkbook, ThisWorkbook, Application.ActiveWorkbook, Application.ThisWorkbook, etc. but nothing works. I'd really appreciate some help!

My code is stored in my Personal.xlsb workbook.

Thanks in advance.

berclaw
  • 13
  • 2
  • `Sheets` only takes in 1 value, either name of the worksheet in string or the index so you can't pass an array of names. Try 2x `wb.Sheets(2).Copy Before:=wb.Sheets(1)`. More importantly, please read on [how to avoid using Select/Activate](https://stackoverflow.com/questions/10714251/how-to-avoid-using-select-in-excel-vba) and also, always fully qualify your range, not doing so will make vba refers to `ActiveWorkbook`/`ActiveSheet` which is very bad practice (and set yourself in a landmine of bugs). – Raymond Wu Oct 02 '21 at 03:34
  • 1
    @Raymond Wu: I agree with your *more importantly* part, but `Sheets` **can** take an array of sheet names. Try the following in a new workbook with at least two sheets: `Dim shs As Sheets: Set shs = Sheets(Array("Sheet1", "Sheet2")): Debug.Print TypeName(shs): Debug.Print Sheets(Array("Sheet1", "Sheet2")).Count, shs.Count: Debug.Print shs(1).Name, shs(2).Name`. You can also declare it `As Object` or `As Variant`. Manually you can achieve this by pressing `Ctrl` before selecting another sheet. – VBasic2008 Oct 02 '21 at 04:36
  • @VBasic2008 Thanks for enlightening me! I would delete my comment in light of this but I am keeping it due to "more importantly part" – Raymond Wu Oct 02 '21 at 04:44
  • 1
    @Raymond Wu: That's ok. Here's an example where this is most useful. You have a workbook with several worksheets and you want to copy two of them to another workbook and they have references to each other (e.g. `=Sheet2!A1`). If you copy them one after the other, you will notice that e.g. the references from the **new** first worksheet refer to the second **original** worksheet (e.g. `'OriginalPath\[Original.xlsx]Sheet2'!A1`). Copying the worksheets together (using an array) copies also their references (`Sheet2!A1` i.e. `'NewPath\[New.xlsx]Sheet2'!A1`). – VBasic2008 Oct 02 '21 at 05:01

2 Answers2

0

In this bit, Sheets is expecting the name or index of a sheet. Sheet1.Name implies that you have an object named Sheet1 which you don't. Try changing it to Sheet(1).Name. Sheet(1) is an object representing the first sheet in the workbook and Sheet(1).Name is the name of that worksheet.

Same issue with Sheet2.

Your code

Sheets(Array(Sheet1.Name, Sheet2.Name)).Copy Before:=Sheets(1)

Should be

Sheets(Array(Sheets(1).Name, Sheets(2).Name)).Copy Before:=Sheets(1)

Your code will also fail on

Sheet2.Activate

Need to be

Sheets(2).Activate

ACCtionMan
  • 511
  • 1
  • 3
  • 12
  • Keep in mind that *My code is stored in my Personal.xlsb workbook.* i.e. `Sheet1` and `Sheet2` can only be sheets in the personal workbook (they are not **it**, you got that, I know), `Sheets` could refer to any workbook which is currently active (selected), so qualify `Sheets` with `wb.Sheets...`. `Sheet(1)` looks like a typo (`Sheets(1)` is correct) and it is questionable if instead of `Before`, `After` should be used since OP is writing to Sheets 3-6. – VBasic2008 Oct 02 '21 at 06:37
  • Thanks for picking that up about ``Sheet(1)``, writing answers on a phone is not always the best. Have corrected. Yes, should ideally qualify which workbook. Not sure about ``Before`` or ``After`` as OP is doing it twice. I think they are writing the formula in Sheets 1 and 2, but referencing Sheets 3-6. Just noticed code will also fail on ``Sheet2.Activate`` for the same reason. – ACCtionMan Oct 02 '21 at 20:31
  • Thank you for the responses, all the comments have been very helpful & informative. A follow-up question - if I wanted to re-purpose this code to work with other files but (1) some of the files might have more than two sheets (people added sheets after the file was created), and (2) the order of the extra sheets may vary (sometimes at the beginning, sometimes at the end). How can I reference Sheet1 and Sheet2 (the first sheets created in the file)? – berclaw Oct 03 '21 at 20:04
  • If my answer helped can you upvote and if the answer solved your query can you mark as the correct answer please. – ACCtionMan Oct 04 '21 at 07:50
0

Identify Sheets in Another Workbook by Their CodeName

  • When you say

    the sheet names are all different,

    it's like one equation with two unknowns: unsolvable.

  • Your code hints that the worksheets will be the first and the second, yet in the comments you say:

    but (1) some of the files might have more than two sheets (people added sheets after the file was created), and (2) the order of the extra sheets may vary (sometimes at the beginning, sometimes at the end). How can I reference Sheet1 and Sheet2 (the first sheets created in the file)?

  • The only way to deal with this is by identifying the worksheets whose code names are Sheet1 and Sheet2, that is if nobody has messed with those ones.

  • To do this, I've written two functions and implemented another variable (CopySheets), a collection of sheets (object), which will hold the worksheets.

  • With StrComp using vbTextCompare, I'm allowing case-insensitivity i.e. Personal.xlsb vs PeRSonal.XLSB vs PERSONAL.XLSB.

  • Setting the variable to Nothing ensures that if something goes wrong in the functions (a worksheet is not found), the sheets from the previous workbook will not be tested in the line If Not CopySheets Is Nothing Then.

  • In the next line, the functions do the 'heavy lifting' which should yield in a sheet collection of the two worksheets.

  • If Not CopySheets Is Nothing Then tests if it was successful.

  • If CopySheets.Count = 2 Then additionally tests if both worksheets were found.

  • The Debug.Print line will write the workbook names and their respective worksheet names to the Immediate window (Ctrl + G).

  • This is where you continue with your code which is out of the scope of this post.

  • Note that nothing has been changed nor activated. Only a reference has been created. Your workbook is in the same state as it was before.

Option Explicit

Sub PleaseWork()
    
    Dim wb As Workbook
    Dim CopySheets As Sheets
    
    Debug.Print vbLf & "Name", "1st", "2nd"
    
    For Each wb In Workbooks
        If StrComp(wb.Name, "PERSONAL.XLSB", vbTextCompare) <> 0 Then
            Set CopySheets = Nothing
            Set CopySheets = RefSheetsFromCodeNames(wb, "Sheet1", "Sheet2")
            If Not CopySheets Is Nothing Then
                If CopySheets.Count = 2 Then
                    Debug.Print wb.Name, CopySheets(1).Name, CopySheets(2).Name
                    
                    ' Try first without your code.
                    ' Check the results In the Immediate window ('Ctrl+G').
                    ' If acceptable, continue by adding your code, e.g.:
                    
                    'CopySheets.Copy Before:=Sheets(1)
                
                'Else ' only one sheet
                End If
            'Else ' sheets not identified (found)
            End If
        'Else ' it's 'PERSONAL.XLSB'
        End If
    Next wb
    
    MsgBox "All Finished!", vbInformation, "Please work!"
    
End Sub

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose:      Returns a reference to a sheet collection identified
'               by the sheet code names.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function RefSheetsFromCodeNames( _
    ByVal wb As Workbook, _
    ParamArray CodeNames() As Variant) _
As Sheets
    Const ProcName As String = "RefSheetsFromCodeNames"
    'On Error GoTo ClearError

    Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")

    Dim CodeName As Variant
    Dim SheetName As String
    For Each CodeName In CodeNames
        SheetName = GetSheetNameFromCodeName(wb, CodeName)
        If Len(SheetName) > 0 Then
            dict(SheetName) = Empty
        End If
    Next CodeName
    
    If dict.Count = 0 Then Exit Function
    Set RefSheetsFromCodeNames = wb.Sheets(dict.Keys)
    
ProcExit:
    Exit Function
ClearError:
    Debug.Print "'" & ProcName & "': Unexpected Error!" & vbLf _
              & "    " & "Run-time error '" & Err.Number & "':" & vbLf _
              & "        " & Err.Description
    Resume ProcExit
End Function

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose:      Returns the sheet name of a sheet in a workbook ('wb')
'               identified by its code name ('CodeName').
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function GetSheetNameFromCodeName( _
    ByVal wb As Workbook, _
    ByVal CodeName As String) _
As String
    Const ProcName As String = "GetSheetNameFromCodeName"
    On Error GoTo ClearError

    GetSheetNameFromCodeName = wb.VBProject.VBComponents(CodeName) _
        .Properties("Name")

ProcExit:
    Exit Function
ClearError:
    Debug.Print "'" & ProcName & "': Unexpected Error!" & vbLf _
              & "    " & "Run-time error '" & Err.Number & "':" & vbLf _
              & "        " & Err.Description
    Resume ProcExit
End Function
VBasic2008
  • 44,888
  • 5
  • 17
  • 28
  • That's incredible and it works beautifully! Thank you for the solution, as well as taking the time to explain the ins-and-outs of the various components. – berclaw Oct 05 '21 at 22:05