Code Contest: Submissions And Voting Information

Ladies and gentlemen, here are the submissions for the Code Contest!

Entries are listed in no particular order. To vote on your favorite, send an email to email removed and indicate your choice. Just put the number of the contest entry into the Subject or Body.

For those of you just joining us, here is the original post w/ contest rules: 100th Post and a Code Contest

First, I'd like to thank everyone who submitted. You've obviously put a lot of work into your code and should be congratulated. Here we go!

Entry #1 »

Our first entry comes from Tim Buckingham in Australia. He uses the following sub to create a worksheet listing any formula errors found on the active sheet.

Sub FormulaErrorCheck()
Dim eSheet As Worksheet
Dim FormulaCells As Range, Cell As Range
Dim Row As Integer
Dim FormulaSheet As Worksheet
Dim errval As Integer

Application.ScreenUpdating = False

On Error Resume Next

    Set eSheet = ActiveSheet
    Set FormulaCells = eSheet.UsedRange.SpecialCells(xlFormulas, 23)

    '   Exit if no formulas are found
    If FormulaCells Is Nothing Then
        MsgBox "No Formulas"
        Exit Sub
    End If

'   Add a new worksheet
    Set FormulaSheet = ActiveWorkbook.Worksheets.Add
    FormulaSheet.Name = "Formulas Audit in " & FormulaCells.Parent.Name

'   Set up the column headings
    With FormulaSheet
        Range("A1") = "Address"
        Range("B1") = "Formula"
        Range("C1") = "Value"
        Range("A1:C1").Font.Bold = True
    End With

'   Process each formula
    Row = 2
    For Each Cell In FormulaCells

    If IsError(Cell.Value) Then
        errval = Cell.Value

        Select Case errval

            Case CVErr(xlErrDiv0)
                With FormulaSheet
                    Cells(Row, 1) = Cell.Address _
                    (RowAbsolute:=False, ColumnAbsolute:=False)
                    Cells(Row, 2) = " " & Cell.Formula
                    Cells(Row, 3) = Cell.Value
                    Row = Row + 1
            End With

            Case CVErr(xlErrNA)
                With FormulaSheet
                    Cells(Row, 1) = Cell.Address _
                    (RowAbsolute:=False, ColumnAbsolute:=False)
                    Cells(Row, 2) = " " & Cell.Formula
                    Cells(Row, 3) = Cell.Value
                    Row = Row + 1
                End With

            Case CVErr(xlErrName)
                With FormulaSheet
                    Cells(Row, 1) = Cell.Address _
                    (RowAbsolute:=False, ColumnAbsolute:=False)
                    Cells(Row, 2) = " " & Cell.Formula
                    Cells(Row, 3) = Cell.Value
                    Row = Row + 1
                End With

            Case CVErr(xlErrNull)
               With FormulaSheet
                    Cells(Row, 1) = Cell.Address _
                    (RowAbsolute:=False, ColumnAbsolute:=False)
                    Cells(Row, 2) = " " & Cell.Formula
                    Cells(Row, 3) = Cell.Value
                    Row = Row + 1
                End With

            Case CVErr(xlErrNum)
               With FormulaSheet
                    Cells(Row, 1) = Cell.Address _
                    (RowAbsolute:=False, ColumnAbsolute:=False)
                    Cells(Row, 2) = " " & Cell.Formula
                    Cells(Row, 3) = Cell.Value
                    Row = Row + 1
                End With

            Case CVErr(xlErrRef)
               With FormulaSheet
                    Cells(Row, 1) = Cell.Address _
                    (RowAbsolute:=False, ColumnAbsolute:=False)
                    Cells(Row, 2) = " " & Cell.Formula
                    Cells(Row, 3) = Cell.Value
                    Row = Row + 1
                End With

            Case CVErr(xlErrValue)
               With FormulaSheet
                    Cells(Row, 1) = Cell.Address _
                    (RowAbsolute:=False, ColumnAbsolute:=False)
                    Cells(Row, 2) = " " & Cell.Formula
                    Cells(Row, 3) = Cell.Value
                    Row = Row + 1
                End With
            End Select
        End If
    Next Cell

Columns("A:C").EntireColumn.AutoFit

If FormulaSheet.Range("A2") = "" Then
    MsgBox "No Formula errors found in " & FormulaCells.Parent.Name
    Application.DisplayAlerts = False
        FormulaSheet.Delete
    Application.DisplayAlerts = True
End If

Set FormulaCells = Nothing
Set eSheet = Nothing
Set FormulaSheet = Nothing
Application.ScreenUpdating = True
End Sub

To vote for Entry #1, send an email to email removed and put the number "1" in the subject or body.

Entry #2 »

Peder Schmedling from Norway submitted the following code, which returns the last used cell for a column. The comments above the code will show you exactly how to use it. It even works on filtered columns!

'******************************************************************************
'* Purpose   :
'*  Determines the last used cell for a given column in a given worksheet.
'* Parameters:
'*  [In] lngColNum, column number to get last used cell from
'*  [In] sht, reference to the worksheet to look in (as object)
'* Return    :
'*  Long, Row number of the last used cell in column lngColNum. If the function
'*  fails -1 is returned. If no cells are used the function returns 0.
'******************************************************************************
Public Function GetLastUsedCellOfColumn(ByVal lngColNum As Long, _
    ByRef sht As Worksheet) As Long
    'Error handling
    On Error GoTo ErrorHandler

    'Declaration
    Dim lngLastUsedCell As Long
    Dim lngUsedCellCnt As Long
    Dim lngMaxRow As Long
    Dim rngLowerCell As Excel.Range
    Dim rng As Excel.Range

    With sht
        'Max number of rows
        lngMaxRow = .Rows.Count

        'Check if bottom cell has content
        If Not IsEmpty(.Cells(lngMaxRow, lngColNum)) Then
            'Return row number of the last row
            GetLastUsedCellOfColumn = lngMaxRow
            GoTo ExitProc:
        End If

        'Estimate position of last used cell using .End() method
        lngLastUsedCell = .Cells(lngMaxRow, lngColNum).End(xlUp).Row
        If lngLastUsedCell = 1 Then
            'Check the first cell as it may be empty. If so, return 0.
            If IsEmpty(.Cells(1, lngColNum)) Then
                GetLastUsedCellOfColumn = 0
                GoTo ExitProc:
            End If
        End If

        'Let Excel attempt to estimate the last used cell
        ' this will return as Nothing if the UsedRange does
        ' not return cells intersecting with cells below the
        ' range currently determined as the last cell
        Set rngLowerCell = Intersect( _
            .UsedRange, _
            .Range( _
                .Cells(lngLastUsedCell + 1, lngColNum), _
                .Cells(lngMaxRow, lngColNum)))

        'Used cell(s) was found below the range
        'currently determined as the last cell
        If Not rngLowerCell Is Nothing Then
            'Check for hidden non-empty cells
            ' using the worksheetfunction CountA
            lngUsedCellCnt = Application.WorksheetFunction.CountA(rngLowerCell)

            'If CountA() returns a count,
            'hidden non-empty cells exists
            If lngUsedCellCnt > 0 Then
                Set rng = .Cells(lngLastUsedCell + rngLowerCell.Rows.Count, _
                    lngColNum)

                'Check vertically from bottom (of rngLowerCell)
                'until first hidden non-empty cell is found
                Do While IsEmpty(rng)
                    Set rng = rng.Offset(-1)
                Loop
                lngLastUsedCell = rng.Row
            End If 'lngUsedCellCnt > 0
        End If 'rngLowerCell Is Nothing
    End With 'sht

    'Return the result
    GetLastUsedCellOfColumn = lngLastUsedCell
ExitProc:
    On Error Resume Next

    'Cleanup
    Set rng = Nothing
    Set rngLowerCell = Nothing
    Exit Function
ErrorHandler:
    'Return -1
    GetLastUsedCellOfColumn = -1
    Resume ExitProc
End Function

To vote for Entry #2, send an email to email removed and put the number "2" in the subject or body.

Entry #3 »

Stan Scott from New York provided the following code. It displays a messagebox showing the free meeting time (from Outlook) of a given recipient. Very useful if you were trying to automate your project scheduling.

Public Sub GetFreeBusyInfo()
    Dim hr As Integer, t As Integer
    Dim myFBInfo As String, disp As String, person As String, checkDate As String
    Dim myOlApp As Object, myNameSpace As Object, myRecipient As Object

    On Error GoTo ErrorHandler

    person = InputBox("Enter email address (sscott) or name (Stan Scott)", "Person to Check")
    If Len(person) = 0 Then Exit Sub

    checkDate = InputBox("Enter date (mm/dd/yy), or press [ENTER] for today", "Date to Check")
    If Len(checkDate) = 0 Then
        checkDate = Now
    End If

    Set myOlApp = CreateObject("Outlook.Application")
    Set myNameSpace = myOlApp.GetNamespace("MAPI")
    Set myRecipient = myNameSpace.CreateRecipient(person)

    myFBInfo = Mid(myRecipient.FreeBusy(checkDate, 30), 19, 18)
    hr = 9
    For t = 1 To Len(myFBInfo) Step 2
        disp = disp & Format(hr, "00") & ":00 - " & showFB(Mid(myFBInfo, t, 1))
        disp = disp & Format(hr, "00") & ":30 - " & showFB(Mid(myFBInfo, t + 1, 1))
        If hr = 12 Then
            hr = 1
        Else
            hr = hr + 1
        End If
    Next t

    MsgBox disp, vbOKOnly, myRecipient.Name & " for " & Format(checkDate, "mm/dd/yy")

    Set myRecipient = Nothing
    Set myNameSpace = Nothing
    Set myOlApp = Nothing
    Exit Sub
ErrorHandler:
    MsgBox "Cannot access the information..."
End Sub

Function showFB(val As String) As String
    If val = "0" Then showFB = "Free"           'leaves blank if NOT free
    showFB = showFB & Chr(13) & Chr(10)
End Function

Here's a screenshot of the output:

freebusytime

To vote for Entry #3, send an email to email removed and put the number "3" in the subject or body.

Entry #4 »

Finally, Paul Gillespie submitted the following routine. It checks a given range for hard coded numbers (i.e. not a formula cell) and highlights them in the color of your choice (default Orange).

Sub Highlight_Hard_Coded_Numbers(rngRange As Range, Optional intColor As Integer = 45)
Dim celCell As Range

For Each celCell In rngRange
    If Application.WorksheetFunction.IsNumber(celCell.Value) Then
        If Left(celCell.Formula, 1) <> "=" Then celCell.Interior.ColorIndex = intColor
    End If
Next
End Sub

To vote for Entry #4, send an email to email removed and put the number "4" in the subject or body.

Voting will close at 12:00 AM EST on January 6th, so get your votes in as soon as possible!

Related Articles:

About JP

I'm just an average guy who writes VBA code for a living. This is my personal blog. Excel and Outlook are my thing, with a sprinkle of Access and Word here and there. Follow this space to learn more about VBA. Keep Reading »

Share This Article:

Share and bookmark this articledelicious buttonfacebook buttonlinkedin buttonstumbleupon buttontwitter button

comment bubble 7 Comments:

  1. Jon Peltier writes:

    No offense, but item 2 is rather verbose. My ten-liner returns the last row used below any range, one or more columns wide:

    Function LastRow(rTest As Range) As Long
      Dim lTest As Long
      Dim iCol As Range
      For Each iCol In rTest.Columns
        With rTest.Parent.Cells(65536, iCol.Column)
          lTest = IIf(.End(xlUp).Row > lTest, .End(xlUp).Row, lTest)
        End With
      Next
      LastRow = lTest
    End Function

    Ten more lines tells you the last column:

    Function LastCol(rTest As Range) As Long
      Dim lTest As Long
      Dim iRow As Range
      For Each iRow In rTest.Rows
        With rTest.Parent.Cells(iRow.Row, "IV")
          lTest = IIf(.End(xlToLeft).Column > lTest, .End(xlToLeft).Column, lTest)
        End With
      Next
      LastCol = lTest
    End Function

    These need adjustments for Excel 2007's larger grid. In the first routine, replace 65536 with rTest.Parent.Rows.Count, and in the second, replace "IV" with rTest.Parent.Columns.Count.

  2. Peder Schmedling writes:

    @JP: yes, I agree that the function can appear somewhat verbose and tedious. Most of the comments where added for illustration purposes due to the fact that the procedure was to be posted on the web, hopefully this helps readers new to VBA to understand the inner workings of it :-)

    Down to business, your "ten-liner" is surely compact and elegant but consider if the row containing the last used cell for a given column is hidden.. Your code would then yield the "wrong" result as my routine would detect this and return the correct row number. I say "wrong" here because this is a per-construction judgment, if one is comfortable with disregarding hidden cells/rows it's absolutely fine to use the "ten-liner". On a side-note I can mention that the "ten-liner" seems to handle hidden columns just fine, the problems occur only when the row of the last used cell is hidden.

    Another point is that my function returns zero if there's no last used cell in the column (i.e. no content in any cells), whereas the "ten-liner" returns one (this is also a per-construction thing though).

    I haven't tested/debugged my function using Excel 2007 (as we are still using 2003 where I work). A quick test I performed indicates that the function seems to work fine in Excel 2007 as well..

  3. Jon Peltier writes:

    Peder -

    Comments take up less than half of your code.

    Changing 1 to 0 if a row has no values at all is a very short if statement.

    However, your observation that .End misses hidden cells is a fact that I didn't know. Learn something every day.

    I looked at your code, and this part concerned me:

    Do While IsEmpty(rng)
      Set rng = rng.Offset(-1)
    Loop

    Any time a range is looped cell-by-cell, there is a chance that performance will be impacted. I tested with a used range that was defined by the hidden cell in my working column, and the result was instantaneous. However, when I placed a dummy value in the next column, to make the used range 65500 rows tall, there was a noticeable delay of around 0.5 seconds (I used a timer to measure the duration, and it ranged from 0.46875 to 0.59375 seconds in half a dozen trials).

    I instead devised a technique that loads the range to be tested into a VB array, and searches the array for data. This is faster because it reads from the sheet one time rather than one time per loop. In a number of trials this routine tool between 0.046875 and 0.109375 seconds (with the same 65500+ rows in the used range). Here is the code:

    Function NewLastRow(rTest As Range) As Long
      Dim vTest As Variant
      Dim lTest As Long
      Dim iCol As Long
      Dim iRow As Long
      Dim iMax As Long
      Dim t As Double
    
      t = Timer
    
      With ActiveSheet.UsedRange
        iMax = .Row + .Rows.Count - 1
      End With
    
      vTest = rTest.EntireColumn.Resize(iMax).Value
    
      lTest = 0
    
      For iCol = LBound(vTest, 2) To UBound(vTest, 2)
        For iRow = UBound(vTest, 1) To lTest + 1 Step -1
          If Len(vTest(iRow, iCol)) > 0 Then
            lTest = iRow
            Exit For
          End If
        Next
      Next
    
      NewLastRow = lTest
    
      Debug.Print Timer - t
    End Function

    I didn't comment initially on your code to be critical, just sharing my impressions. Your response and this show the kind of improvement that we can reach by exchanging ideas.

    Since my old code will give the wrong result if there are hidden but filled cells, I will update my code library with this new routine.

  4. Peder Schmedling writes:

    Jon,
    you are absolutely right. The loop where I loop cell-by-cell has been one of my concerns with this code. During simulations I found the same as you, the function used noticeable time to complete.. I haven't had any suggestions as to how I could fix this, but thanks to you I now have :-D

    I was aware of, and have often used, the technique of loading values from a range into an array, but I very often find that I lack the experience that allows me to implement the technique(s) in practical examples. I'm therefore thrilled to be pointed in the right direction by a Excel heavyweighter as yourself.

    I have one comment to the test you perform within your loop; testing the length of the current array element would not work for a cell containing the formula ="" (an empty string), also testing the length of anything implicitly casts the data type to string (regardless of if it's a Double, Empty, Integer etc.) this is time consuming. My suggestion is to use the IsEmpty function instead, using this function you don't implicitly cast each array element to a different data type. By introducing the IsEmpty function in the NewLastRow function I found a 45% reduction in time used by the function (based on 50 iterations). Considering that this both improves the accuracy of the NewLastRow function as well as performance, the IsEmpty function is probably the way to go..?

    For reference, this is how the If statement looked like when I measured performance:

    If Not IsEmpty(vTest(iRow, iCol)) Then
        lTest = iRow
        Exit For
    End If
    

    Now, one wouldn't want to use the NewLastRow function in all cases as this is slow compared to the .End method when no hidden rows exist; therefore I figured that a compromise between your approach and mine would probably be the best. A function that uses the .End method when applicable and the array technique when necessary, this is what I came up with:

    '******************************************************************************
    '* Purpose   :
    '*  Determines the last used cell for a given column in a given worksheet.
    '* Parameters:
    '*  [In] lngColNum, column number to get last used cell from
    '*  [In] sht, reference to the worksheet to look in (as object)
    '* Return    :
    '*  Long, Row number of the last used cell in column lngColNum. If the function
    '*  fails -1 is returned. If no cells are used the function returns 0.
    '******************************************************************************
    Public Function NewGetLastUsedCellOfColumn(ByVal lngColNum As Long, _
        ByRef sht As Worksheet) As Long
        On Error GoTo ErrorHandler
        Dim lngLastUsedCell As Long
        Dim lngUsedCellCnt As Long
        Dim lngMaxRow As Long
        Dim rng As Excel.Range
        Dim avarVals As Variant
        Dim iavar As Long
    
        With sht
            'Max number of rows
            lngMaxRow = .Rows.Count
    
            'Bottom cell has content?
            If Not IsEmpty(.Cells(lngMaxRow, lngColNum)) Then
                NewGetLastUsedCellOfColumn = lngMaxRow
                GoTo ExitProc:
            End If
    
            'Estimate position of last used cell using .End() method
            lngLastUsedCell = .Cells(lngMaxRow, lngColNum).End(xlUp).Row
    
            'Let Excel attempt to estimate the last used cell
            Set rng = Intersect( _
                .UsedRange, _
                .Range( _
                    .Cells(lngLastUsedCell + 1, lngColNum), _
                    .Cells(lngMaxRow, lngColNum)))
    
            'Excel found cells, check for hidden non-empty cells
            If Not rng Is Nothing Then
                If Application.WorksheetFunction.CountA(rng) > 0 Then
                    If rng.Count = 1 Then
                        'If just one cell the array technique can't be used
                        lngLastUsedCell = lngLastUsedCell + 1
                    Else
                        'Load values into array and loop from the end
                        avarVals = rng.Value
                        For iavar = UBound(avarVals) To LBound(avarVals) Step -1
                            If Not IsEmpty(avarVals(iavar, 1)) Then
                                lngLastUsedCell = lngLastUsedCell + iavar
                                Exit For
                            End If
                        Next iavar
                    End If
                End If
            End If
    
            'Return 0 if no cells are used
            If lngLastUsedCell = 1 Then
                If IsEmpty(.Cells(1, lngColNum)) Then
                    NewGetLastUsedCellOfColumn = 0
                    GoTo ExitProc:
                End If
            End If
        End With
    
        NewGetLastUsedCellOfColumn = lngLastUsedCell
    ExitProc:
        On Error Resume Next
        Set rng = Nothing
        Exit Function
    ErrorHandler:
        NewGetLastUsedCellOfColumn = -1
        Resume ExitProc
    End Function
    

    This can easily be adapted to accept any range as input (as in your examples), but I needed it to be compatible with my older version of the function.

    What do you think? Am I making headway?

  5. Jon Peltier writes:

    Peder -

    Ha, I forgot IsEmpty works on a variant array. That's better than the string coercion. I get in the habit of LEN() because when looking for an empty string,

      If LEN(string) = 0 Then

    is faster than

      If string = "" Then

    since before VB reads a string's contents it first checks its length. Your other refinements are also good ones. Two heads are better than one.

    Jimmy -

    I hate to say it, but I'm not a fan of this code formatter. The highlighted version comes out double spaced, so I have to scroll a lot to read a long procedure, and the width is restrictively narrow. I won't say my technique is better (i.e., pre tags using a special vba class in the stylesheet).

    Part of my objective for my own blog this year, and it might just take all year, is to switch over to a new theme and check out a couple of other code highlighters. The problem is that WordPress still insists on messing with the content within the vba brackets, as seen by the greater than symbol in Peder's latest procedure. I have found an enhanced version of the tinymce editor, which I have only given a cursory run through.

  6. JP writes:

    I agree, but it's the best I could find. I used style tags for a while, but it's too tedious to post code. I'll try to use the syntax highlighter that I use on the rest of the site; It'll take all day to change over, but I don't have anything else to do :)

    Actually I found a plugin that can do search and replace on all the posts, so I just updated the syntax highlighter for all posts in about five minutes. :)

  7. JP writes:

    I turned off the "Show Line Numbers" feature in the iG:Syntax Hiliter, and the spacing issue is resolved. I also hacked the CSS and made the colors a bit more in line with the VBIDE. For the time being, there will be two different syntax plugins (one for the posts, another for comments).

Comments on this article are closed. Why?

Site last updated: February 12, 2012