Magically Select All The Used Areas In A Sheet using VBA

Magically Select All The Used Areas In A Sheet using VBA

I recently published a post about automatically formatting a table in Excel using VBA. That got me thinking, how awesome it would be, if we could format all the tables in a sheet, with a single click. For that idea to work, we need to get all the used areas in a worksheet; and then use the Areas Collection to loop through the tables. We can access the Areas Collection through the Areas property of the Range object.

First, we will try to understand how the Areas Collection works. An Area as a single continuous rectangular block of cells; it could even be just a single cell. If the Range consists of multiple discontinuous rectangular blocks, each of those blocks, would be stored as separate Range Objects in the Areas Collection. The key word here is discontinuous, i.e. selecting two tables together in one big Selection does not constitute two Areas; it is still just one continuous block. In other words, we have to get rid of  the blank cells in between the two tables, to make them two distinct Areas.

Here is how we can get only the used areas in a Range:

  1. First, we get all the cells that either have formulas, or a values, and store them in a Range Object.
  2. Every table is bound to have blank cells, and we have to include them too. Therefore, we loop through all the Areas in the Range we created in Step 1, fetch its CurrentRegion, and accumulate the cells in another Range Object.
  3. Return the Range object that we created in Step 2.

Used Areas


Here is the Code

'#####################################################################
'UsedAreas  : Function that returns only the used reagions in a range
'If called without any arguments, it will return the used regions in
'the ActiveSheet.
'#####################################################################
'Author     : Ejaz Ahmed
'Email      : StrugglingToExcel@outlook.com
'Date       : 15 June 2014
'Website    : http://strugglingtoexcel.wordpress.com/
'#####################################################################
Function UsedAreas(Optional ByRef WhichRange As Range) _
    As Range

'Declare Runction level Variables and Objects
Dim ConstantsRange As Range 'Stores all the cells that have values
Dim FormulaRange As Range 'Stores all the cells that have formula
Dim UsedRange As Range 'Stores Used Area
Dim ContentRange As Range 'Includes Used Area's CurrentRegion
Dim EachArea As Range 'Used in the Loop

'If the user did not pass any range to the function, use the
'sheet's used range.
If WhichRange Is Nothing Then
    Set WhichRange = Application.ActiveSheet.UsedRange
End If

'The SpecialCells Method includes the entire sheet's UsedRange
'if called from a single cell. So only proceed if the user selected
'more than one cell
If WhichRange.Count > 1 Then
    'Ignore the errors produced if there are no cells with Formula
    'or Constants
    On Error Resume Next
    Set ConstantsRange = WhichRange.SpecialCells(xlCellTypeConstants)
    Set FormulaRange = WhichRange.SpecialCells(xlCellTypeFormulas)
    Err.Clear
    On Error GoTo 0

    'Combine both the Ranges together
    If Not ConstantsRange Is Nothing Then
        Set UsedRange = ConstantsRange
    End If

    If Not FormulaRange Is Nothing Then
        If UsedRange Is Nothing Then
            Set UsedRange = FormulaRange
        Else
            Set UsedRange = Application.Union( _
                UsedRange, FormulaRange)
        End If
    End If

    'We dont need these ranges anymore, forget them to save memory
    Set FormulaRange = Nothing
    Set ContentRange = Nothing

    'We already have all the cells that have stuff in them, but there
    'may be blank cells that are actually part of a table, but do not
    'contain data. Therefore, we loop through the areas and include the
    'current regions
    If Not UsedRange Is Nothing Then
        'Set the Final Range to the first Area, so we dont have to check
        'if it is not empty later in the loop
        Set ContentRange = UsedRange.Cells(1, 1).CurrentRegion
        For Each EachArea In UsedRange.Areas
            'Check if the Area is already in the Final Range
            If Application.Intersect(EachArea, ContentRange) _
                Is Nothing Then
                'Include its current region if it is not already in the
                'final range
                Set ContentRange = Application.Union( _
                    ContentRange, EachArea.CurrentRegion)
            End If
        Next EachArea
    End If
End If

'If the selection had used areas, return it, or
'just return the Range that was passed to the function
If ContentRange Is Nothing Then
    Set UsedAreas = WhichRange
Else
    Set UsedAreas = ContentRange
End If

End Function

A Cover Macro to loop through all the areas:

Sub SampleSub()
Application.ScreenUpdating = False
Application.EnableEvents = False

Dim EachArea As Range
Dim UsedAreasRange As Range

Set UsedAreasRange = UsedAreas()

'Select the Range if you choose to
UsedAreasRange.Select

For Each EachArea In UsedAreasRange.Areas
    'Your Code here
Next EachArea

Application.EnableEvents = True
Application.ScreenUpdating = True
End Sub

Download

Download

Test the format-all-tables macros. I have four cover macros in there:

  1. Format all the tables Gold (CTRL + SHIFT + Q)
  2. Format all the tables Crimson (CTRL + SHIFT + W)
  3. Reset all formatting (CTRL + SHIFT + E)
  4. Just Select the used Areas (CTRL + SHIFT + R)

Hold the CTRL and SHIFT buttons, go bananas with “Q” and “W”, and watch the magic!

The code formats about 100 tables in seconds. Next time your boss asks you to change formats for a 100 tables, What would you say?

14 Comments

  1. Ejaz, using your macro, can I specify a used area? For example, I’m able to extract conditional formatting parameters onto a sheet; from there, I’d like to use the last area as the range to programmatically apply CF. How would I specifiy which area I’d like to use??

    Thanks again!

    • Ejaz

      You can loop through the areas collection. Use a for each or a for loop. Keep track of the row number of the first or last row in each area to determine which is the last area.

      If I had to do this, I’d keep track of the address of an area in a string variable, by comparing its row number with a maximum-row-number that I would keep track of in a long variable. Every time, the row of an area in the loop, is more than the max row, I update the range address string, and the max row long variable with that area’s details.

      At the end of the loop, you’d end up with the last area’s address in the string variable.

      • Right after I posted (why does inspiration always occur AFTER you hit Send??), I found that the code below worked. Is there any bad juju to doing it this way?? Thanks.

        Set rngCFSource = rng_UsedAreas(rngCFSource) ‘ ~~ Find each individual CF output
        Set rngCFSource = rngCFSource.Areas(rngCFSource.Areas.Count) ‘ ~~ Select LAST UsedArea on sht (most recent CF output)

        • Ejaz

          That relies on excel arranging the areas by itself. To be very honest, I do not know for sure if it does that.

          The round about method that I broke fingers typing out on my phone checks for the last are in terms of row number.

          You can easily test it out though. Set a range object like this:

          Set TestRange = Range(“A10:A11,A5:A7,A1:A2”)

          And then try to print its address, and see if excel rearranged the ranges.

          • You’re right – it does NOT re-arrange the range. I guess for my setup (same columns, no deviation of data location, no specific range designation) this would work but for anything that’s more free-form (or where the range is explicitly specified), my “solution” would give the bottom range listed rather than the last range on the sheet.

            The other issue with my idea is that it reads from top to bottom and may miss if the last range is the range at the far right and not the bottom range.

            Thanks again. Hope you find splints for your fingers!!

          • Ejaz

            How thoughtful of you! My fingers feel much better already.

            Thanks for testing. If I write a post about it, I’d include you in the credits!

          • No worries! I’m just happy to get such prompt and helpful insights. Thanks again for your great programming! I use it everyday.

          • Ejaz

            I am glad you use the stuff on my blog. Good to hear that from a reader.

  2. Ejaz, I have two (more) questions about how to adapt your function;
    1) I’m curious if there’s any way to limit each area to visible cells only? I tried re-defining [Set ConstantsRange = ConstantsRange.SpecialCells(xlCellTypeVisible)], but that doesn’t work at all. Is there some other way to limit the either the range or the area to visible cells only?
    2) Is there a way to specify which areas to keep? I have multiple areas and I only want to keep the first two; is that possible??

    Again, thanks so much.

    • Ejaz

      I am very happy that you are using this function regularly!

      You are thinking along the right lines with regards to the visible cells problem. You have to use the special cells method, but at a different point in the code. You wouldn’t even have to modify my function. All you have to do is use it as it is in one of your subs, and in the next line, just use the special cells method. The reason why doing it in the beginning does not work, is because I use the Current Region property to account for blank cells in a table, and it does not care whether a cell is visible or not. Therefore, even if you did start with visible cells, the current region property will include adjacent cells even when they are hidden.

      Now let us try to address the second problem that you have. You can use the FOR EACH loop, or even just a FOR loop to loop through areas. You can add a few IF THEN statements there to determine which areas to keep and store them in another range object. Look into the UNION function.

      Excel does not order areas according to row and column numbers. It remembers areas in the order they were added to the range object. And in instances like these, you can never be sure of the order. The first two areas need not be the top most or the left most area in your “Selection”. You would have to account for that all by yourself in your code.

      ALTERNATIVE: Hide the areas you don’t want to keep, temporarily, and use my answer to the first question; take down two birds with a stone!

    • Ejaz

      Looks like we have already discussed the Areas Order problem. I am sorry I made you sit through that again.

Comments are closed