Search code examples
excelexcel-2013vba

Trouble referencing sheet code names


Here's the basic problem: I am writing an Excel macro and I would like to use the worksheet code names to try to eliminate any errors down the road. I can use the code name for Sheet1 and it works fine, but when I try to use the other codes, like Sheet3 or Sheet7 the editor doesn't recognize them and if I run the macro Excel kicks up an error telling me that my "variable is not defined".

For example:

Option Explicit

Sub Test()

Dim SheetObject As Worksheet
Dim SheetObject2 As Worksheet

Set SheetObject = Sheet1
Set SheetObject2 = Sheet3

MsgBox (SheetObject.Name)
MsgBox (SheetObject2.Name)


End Sub

If I comment out any code referring to SheetObject2 the macro runs correctly. If I put them in I get the errors. I definitely have a Sheet3, and the code name is definitely Sheet3. I've looked around Google all day and can't seem to come up with any solutions, any help would be great.

Thanks in advance,

Jesse


Solution

  • My last employer collected data and created national statistics. Much of that data came in the form of Excel workbooks so I have had a lot of relevant experience.

    If you are running your own macro and if this is a one-off exercise then tests like this may be adequate:

    Debug.Assert WbookTgt.WsheetTgt.Range("A1").Value = "Date"
    

    Many languages have an Assert statement as a development aid; this is the VBA version. If the assertion is not true, the macro will stop with this statement highlighted.

    If this approach is not adequate, you should consider developing parameterised macros that perform checking and updating tasks. I have looked through some of my old macros but most would not be intelligible to someone new to VBA. I have extracted code to create two macros which I hope will give you some ideas.

    Macro 1 - OpenWorkbook

    Organisations that regularly supply data often use names like: "Xxxxx 1409.xlsx" and "Xxxxx 1410.xlsx" for the September and October versions of their data. You could, for example, update the macro each month for the latest name or you could change the filename to a standard value. Either of these possibilities would be a nuisance and I would be particularly opposed to the second idea because I like to archive all the workbooks I have processed.

    OpenWorkbook() uses the Dir statement to search a folder for a file that matches a template such as “Xxxxx*.xls*”. If a single file matches this template, the macro opens the workbook and returns a reference to it.

    Macro 2 – CheckWorksheets

    You may have noticed that some VBA routines have a fixed number of parameters while others have a variable number of parameters. For example, the following are all valid calls of CheckWorksheets:

    If CheckWorksheets(WbookTgt, WbookThis, “Name1”) then
    If CheckWorksheets(WbookTgt, WbookThis, “Name1”, “Name2”) then
    If CheckWorksheets(WbookTgt, WbookThis, “Name1”, “Name2”, “Name3”) then
    

    CheckWorksheets has three parameters. The first two are workbook references. The third is ParamArray SheetName() As Variant. Any parameter after the first two is placed in array SheetName which can be as large as necessary. Here all the trailing parameters are strings but they could be of any type.

    I can use OpenWorkbook to open this month’s version of the source file and then use CheckWorksheets to confirm all the worksheets required by my macro are present.

    Worksheet Errors”

    These two macros require a worksheet Errors be present in a specified workbook. If the macros detect an error, they add a detailed error message to this worksheet. I have found this a convenient technique for capturing the details of any errors.

    Macros Demo1 and Demo2

    I have included two macros that demonstrate the use of these macros with workbooks on my system. If you amend Demo1 and Demo2 to operate on some of your workbooks, you should get an idea of what OpenWorkbook and CheckWorksheets can do for you.

    Come back with questions as necessary but the more you can decipher OpenWorkbook and CheckWorksheets yourself, the faster you will develop your own skills

    Option Explicit
    Sub Demo1()
    
      Dim Path As String
      Dim WbookThis As Workbook
      Dim WbookTgt As Workbook
    
      ' Application.ThisWorkbook identifies the workbook containing this macro.
      Set WbookThis = Application.ThisWorkbook
    
      ' I find it convenient to place my target workbooks in the folder
      ' holding the workbook containing the macro(s).
      Path = WbookThis.Path
    
      Set WbookTgt = OpenWorkbook(Path, "Combined*.xls*", WbookThis)
    
      If WbookTgt Is Nothing Then
        ' Detailed error message already recorded in "Errors"
        Call MsgBox("Wokbook failed checks", vbOKOnly)
      Else
        With WbookTgt
          Debug.Print .Path & "\" & .Name & " opened."
          .Close SaveChanges:=False
        End With
      End If
    
    End Sub
    Sub Demo2()
    
      Dim Path As String
      Dim WbookThis As Workbook
      Dim WbookTgt As Workbook
    
      ' Application.ThisWorkbook identifies the workbook containing this macro.
      Set WbookThis = Application.ThisWorkbook
    
      ' I find it convenient to place my target workbooks in the folder
      ' holding the workbook containing the macro(s).
      Path = WbookThis.Path
    
      Set WbookTgt = OpenWorkbook(Path, "Combined 2.04.xls*", WbookThis)
    
      If WbookTgt Is Nothing Then
        ' Detailed error message already recorded in "Errors"
        Call MsgBox("Wokbook failed checks", vbOKOnly)
        Exit Sub
      End If
      With WbookTgt
        If Not CheckWorksheets(WbookTgt, WbookThis, "Critical Path", "Dyn Dims") Then
          Call MsgBox("Wokbook failed checks", vbOKOnly)
          .Close SaveChanges:=False
          Exit Sub
        End If
        Debug.Print .Path & "\" & .Name & " contains worksheets Critical and Dym Dims"
        .Close SaveChanges:=False
      End With
    
    End Sub
    Function CheckWorksheets(ByRef WbookTgt As Workbook, ByRef WbookError As Workbook, _
                             ParamArray SheetName() As Variant) As Boolean
    
      ' * Return True if WbookTgt contains every specified worksheet.
    
      ' * WbookTgt is the workbook to be checked
      ' * WbookError identifies the workbook containing worksheet "Error" to which any
      '   error message will be added.
      ' * SheetName() is an array of worksheet names.
    
      Dim ErrorMsg As String
      Dim FoundError As Boolean
      Dim FoundSheet() As Boolean
      Dim FoundSheetsCount As Long
      Dim InxName As Long
      Dim InxWsheet As Long
      Dim NotFoundSheetsCount As Long
      Dim RowErrorNext As Long
      Dim SheetNamesFound As String
    
      ' Size FoundSheet to match SheetName.  Array elements initialised to False
      ReDim FoundSheet(LBound(SheetName) To UBound(SheetName))
    
      FoundSheetsCount = 0
      NotFoundSheetsCount = 0
      With WbookTgt
        For InxName = LBound(SheetName) To UBound(SheetName)
          NotFoundSheetsCount = NotFoundSheetsCount + 1   ' Assume not found until found
          For InxWsheet = 1 To .Worksheets.Count
            If SheetName(InxName) = .Worksheets(InxWsheet).Name Then
              FoundSheet(InxName) = True
              FoundSheetsCount = FoundSheetsCount + 1
              NotFoundSheetsCount = NotFoundSheetsCount - 1
              Exit For
            End If
          Next
        Next
      End With
    
      If NotFoundSheetsCount = 0 Then
        CheckWorksheets = True
        Exit Function
      End If
    
      SheetNamesFound = ""
      ErrorMsg = WbookTgt.Path & "\" & WbookTgt.Name & " does not contain "
      If NotFoundSheetsCount = 1 Then
        ErrorMsg = ErrorMsg & "this expected worksheet:"
      Else
        ErrorMsg = ErrorMsg & "these expected worksheets:"
      End If
      For InxName = LBound(SheetName) To UBound(SheetName)
        If Not FoundSheet(InxName) Then
          ErrorMsg = ErrorMsg & vbLf & "  " & SheetName(InxName)
        Else
          SheetNamesFound = SheetNamesFound & vbLf & "  " & SheetName(InxName)
        End If
      Next
      If FoundSheetsCount = 0 Then
        ' No need to add list of found sheet names
      Else
        ErrorMsg = ErrorMsg & vbLf & "but does contain "
        If FoundSheetsCount = 1 Then
          ErrorMsg = ErrorMsg & "this expected worksheet:"
        Else
          ErrorMsg = ErrorMsg & "these expected worksheets:"
        End If
        ErrorMsg = ErrorMsg & SheetNamesFound
      End If
      With WbookError
        With .Worksheets("Errors")
          RowErrorNext = .Cells(Rows.Count, "A").End(xlUp).Row + 1
          With .Cells(RowErrorNext, "A")
            .Value = Now()
            .VerticalAlignment = xlTop
          End With
          .Cells(RowErrorNext, "B").Value = ErrorMsg
        End With
      End With
      CheckWorksheets = False
    
    End Function
    Function OpenWorkbook(ByVal Path As String, ByVal FileTemplate As String, _
                          ByRef WbookError As Workbook) As Workbook
    
      ' * If Path & FileTemplate identifies a single workbook, open it and return
      '   it as an object.  If Path & FileTemplate does not represent a single
      '   workbook, report the problem in worksheet Errors and return Nothing.
    
      ' * WbookError identifies the workbook containing worksheet "Error".
      ' * Path must be the name of the folder in which the required workbook is located
      ' * FileTemplate can either be a specific filename or can contain wild cards
      '   providing only one file matches the template.
      ' * WbookError identifies the workbook containing worksheet "Error" to which any
      '   error message will be added.
    
      Dim ErrorMsg As String
      Dim FileNameCrnt As String
      Dim FileNameMatch As String
      Dim RowErrorNext As Long
    
      FileNameMatch = Dir$(Path & "\" & FileTemplate, vbNormal)
      If FileNameMatch = "" Then
        ' No matches found
        ErrorMsg = "Template " & Path & "\" & FileTemplate & " does not match any file"
      Else
        ' At least one match.
        ' If only one match, its name is in FileNameMatch
        Do While True
          FileNameCrnt = Dir$
          If FileNameCrnt = "" Then
            ' No more matches
            Exit Do
          End If
          ' A second or subsequent match has been found.
          If FileNameMatch <> "" Then
            ' This is the second match.
            ' Initialise error message and report name of first match
            ErrorMsg = "Template " & Path & "\" & FileTemplate & " matches more than one file:" & _
                       vbLf & "  " & FileNameMatch
            FileNameMatch = ""      ' No single match
          End If
          ' Add name of current match to error message
          ErrorMsg = ErrorMsg & vbLf & "  " & FileNameCrnt
        Loop
      End If
    
      If FileNameMatch = "" Then
        ' No single match found.
        ' ErrorMsg contains an appropriate error message
        With WbookError
          With .Worksheets("Errors")
            RowErrorNext = .Cells(Rows.Count, "A").End(xlUp).Row + 1
            With .Cells(RowErrorNext, "A")
              .Value = Now()
              .VerticalAlignment = xlTop
            End With
            .Cells(RowErrorNext, "B").Value = ErrorMsg
            Set OpenWorkbook = Nothing
          End With
        End With
      Else
        ' Single match found
        Set OpenWorkbook = Workbooks.Open(Path & "\" & FileNameMatch)
      End If
    
    End Function
    

    Response to extra question

    VBA has nothing quite as convenient as VB's Try but it does have some error handling under programmer control.

    If you use a command such as:

    Worksheets("Sheet2").Delete
    

    the user will be asked to confirm the deletion. To avoid this, use:

    Application.DisplayAlerts = False
    Worksheets("Sheet2").Delete
    Application.DisplayAlerts = True
    

    I have seen code with Application.DisplayAlerts = False at the start of a macro which means no alert will be displayed for the user's attention even if the pogrammer was not expecting it. By bracketing the Delete, I ensure only the alert I was expecting is suppressed.

    Consider:

    Sub OpenFile()
      Dim InputFileNum As Long
      InputFileNum = FreeFile
      Open "Dummy.txt" For Input As InputFileNum
      Debug.Print "File successfully opened"
      Close InputFileNum
    End Sub
    

    The file "Dummy.txt" does not exist so the macro will stop on the Open statement.

    You will sometimes see code like this:

    Sub OpenFile()
    
      Dim InputFileNum As Long
    
      On Error GoTo ErrorCode
    
      InputFileNum = FreeFile
      Open "Dummy.txt" For Input As InputFileNum
      Call MsgBox("File successfully opened", vbOKOnly)
      Close InputFileNum
      Exit Sub
    
    ErrorCode:
      Debug.Print "Unexpected error: " & Err.Number & " " & Err.Description
    
    End Sub
    

    Here I have provided a general handler for any error condition that may occur. I do not approve although I accept that this is slightly better than having the non-technical user seeing the faulty statement highlighted. The trouble is any error will result in the same unhelpful error message.

    I never include error handling during development. If an error occurs, I want the macro to stop on the faulty statement so I can consider how to avoid the error. Here I should check the file exists before attempting to open it. I prefer something like this:

    Sub OpenFile()
    
      Dim FileSysObj As Object
      Dim InputFileNum As Long
    
      On Error GoTo ErrorCode
    
      Set FileSysObj = CreateObject("Scripting.FileSystemObject")
    
      If Not FileSysObj.FileExists("Dummy.txt") Then
        Call MsgBox("I am unable to find ""Dummy.txt"".  List of helpful suggestions.", vbOKOnly)
        Exit Sub
      End If
    
      InputFileNum = FreeFile
      Open "Dummy.txt" For Input As InputFileNum
      Call MsgBox("File successfully opened", vbOKOnly)
      Close InputFileNum
      Exit Sub
    
    ErrorCode:
      Debug.Print "Unexpected error: " & Err.Number & " " & Err.Description
    
    End Sub
    

    I have including checking code for the error I expect. If the file does not exist, I have displayed a message which I hope will help the user fix the problem for themselves.

    Sometimes you cannot avoid an error. To test the code below, I created file Dummy.txt but set the "Read access denied" flag. There is no easy method (to my knowledge) for a VBA macro to test this flag. I have a general handler for unexpected errors but I switch it off for the Open statment so I can include specific code for open failures. I have removed the code that uses FileExists() to test if Dummy.txt exists because it is easier to include it with the other open file error tests.

    Sub OpenFile()
    
      Dim FileSysObj As Object
      Dim InputFileNum As Long
    
      On Error GoTo ErrorCode       ' General handler for unexpected errors
    
      InputFileNum = FreeFile
      Err.Clear
      On Error Resume Next          ' Record error in Err object and continue
      Open "Dummy.txt" For Input As InputFileNum
      Select Case Err.Number
        Case 0
          ' No error.
        Case 53           ' File does not exist
          Call MsgBox("I am unable to find ""Dummy.txt"".  List of helpful suggestions.", vbOKOnly)
          Exit Sub
        Case 75           ' Path/File access error
          Call MsgBox("It appears file ""Dummy.txt"" exists but I do not have permission to read it.", vbOKOnly)
          Exit Sub
        Case Else
          Call MsgBox("My attempt to open ""Dummy.txt"" failed with an unexpected error condition" & vbLf & _
                      "  " & Err.Number & " " & Err.Description, vbOKOnly)
          Exit Sub
      End Select
    
      On Error GoTo ErrorCode      ' Restore general handler for unexpected errors
    
      Call MsgBox("File successfully opened", vbOKOnly)
      Close InputFileNum
      Exit Sub
    
    ErrorCode:
      Debug.Print "Unexpected error: " & Err.Number & " " & Err.Description
    
    End Sub
    

    Visit http://support.microsoft.com/kb/146864 for a long list of error codes and more information about error handling.