Difference between two ranges Difference between two ranges vba vba

Difference between two ranges


Try this function after I have improved it a bit:

Function SetDifference(Rng1 As Range, Rng2 As Range) As RangeOn Error Resume NextIf Intersect(Rng1, Rng2) Is Nothing Then    'if there is no common area then we will set both areas as result    Set SetDifference = Union(Rng1, Rng2)    'alternatively    'set SetDifference = Nothing    Exit FunctionEnd IfOn Error GoTo 0Dim aCell As RangeFor Each aCell In Rng1    Dim Result As Range    If Application.Intersect(aCell, Rng2) Is Nothing Then        If Result Is Nothing Then            Set Result = aCell        Else            Set Result = Union(Result, aCell)        End If    End IfNext aCellSet SetDifference = ResultEnd Function

Remember to call it like this:

Set Rng = SetDifference(Rng, highlightedColumns)


^Iterating by each cell is very slow for calls like

SetDifference(ActiveSheet.Cells, ActiveSheet.Range("A1")) 'All cells except A1

Therefore:

'(needed by the 2nd function)Public Function Union(ByRef rng1 As Range, _                      ByRef rng2 As Range) As Range    If rng1 Is Nothing Then        Set Union = rng2        Exit Function    End If    If rng2 Is Nothing Then        Set Union = rng1        Exit Function    End If    If Not rng1.Worksheet Is rng2.Worksheet Then        Exit Function    End If    Set Union = Application.Union(rng1, rng2)End FunctionPublic Function Complement(ByRef rngA As Range, _                           ByRef rngB As Range) As Range    Dim rngResult As Range    Dim rngResultCopy As Range    Dim rngAreaA As Range    Dim rngAreaB As Range    Dim lngX1 As Long    Dim lngY1 As Long    Dim lngX2 As Long    Dim lngY2 As Long    Dim lngX3 As Long    Dim lngY3 As Long    Dim lngX4 As Long    Dim lngY4 As Long    Dim lngX5 As Long    Dim lngY5 As Long    Dim lngX6 As Long    Dim lngY6 As Long    If rngA Is Nothing Then        Exit Function    End If    If rngB Is Nothing Then        Set Complement = rngA        Exit Function    End If    If Not rngA.Worksheet Is rngB.Worksheet Then        Exit Function    End If    Set rngResult = rngA    With rngA.Worksheet        For Each rngAreaB In rngB.Areas            If rngResult Is Nothing Then                Exit For            End If            lngX3 = rngAreaB.Row            lngY3 = rngAreaB.Column            lngX4 = lngX3 + rngAreaB.Rows.Count - 1            lngY4 = lngY3 + rngAreaB.Columns.Count - 1            Set rngResultCopy = rngResult            Set rngResult = Nothing            For Each rngAreaA In rngResultCopy.Areas                lngX1 = rngAreaA.Row                lngY1 = rngAreaA.Column                lngX2 = lngX1 + rngAreaA.Rows.Count - 1                lngY2 = lngY1 + rngAreaA.Columns.Count - 1                If lngX3 > lngX1 Then lngX5 = lngX3 Else lngX5 = lngX1                If lngY3 > lngY1 Then lngY5 = lngY3 Else lngY5 = lngY1                If lngX4 > lngX2 Then lngX6 = lngX2 Else lngX6 = lngX4                If lngY4 > lngY2 Then lngY6 = lngY2 Else lngY6 = lngY4                If lngX5 <= lngX6 And lngY5 <= lngY6 Then                    If lngX5 > lngX1 Then                        Set rngResult = Union(rngResult, .Range(.Cells(lngX1, lngY1), .Cells(lngX5 - 1, lngY2)))                    End If                    If lngY5 > lngY1 Then                        Set rngResult = Union(rngResult, .Range(.Cells(lngX5, lngY1), .Cells(lngX6, lngY5 - 1)))                    End If                    If lngY2 > lngY6 Then                        Set rngResult = Union(rngResult, .Range(.Cells(lngX5, lngY6 + 1), .Cells(lngX6, lngY2)))                    End If                    If lngX2 > lngX6 Then                        Set rngResult = Union(rngResult, .Range(.Cells(lngX6 + 1, lngY1), .Cells(lngX2, lngY2)))                    End If                Else                    Set rngResult = Union(rngResult, rngAreaA)                End If            Next rngAreaA        Next rngAreaB    End With    Set Complement = rngResultEnd Function


When ranges have both multiple areas, you will need a different approach. I did not make up the core idea of this example and do not remember where I found this idea (using xlCellTypeConstants). I adapted it to make it work for ranges with areas:

' Range operator that was missingPublic Function rngDifference(rn1 As Range, rn2 As Range) As RangeDim rnAreaIntersect As Range, varFormulas As VariantDim rnAreaS As Range, rnAreaR As Range, rnAreaDiff As RangeDim rnAreaModified As Range, rnOut As Range On Error Resume Next Set rngDifference = Nothing If rn1 Is Nothing Then Exit Function If rn2 Is Nothing Then Set rngDifference = rn1: Exit Function Set rnOut = Nothing For Each rnAreaS In rn1.Areas    Set rnAreaModified = rnAreaS    For Each rnAreaR In rn2.Areas        Set rnAreaIntersect = Intersect(rnAreaModified, rnAreaR)        If rnAreaIntersect Is Nothing Then            Set rnAreaDiff = rnAreaModified        Else ' there is interesection            'save            varFormulas = rnAreaS.Formula            rnAreaS.Value = 0:  rnAreaIntersect.ClearContents            If rnAreaS.Cells.Count = 1 Then               Set rnAreaDiff = Intersect(rnAreaS.SpecialCells(xlCellTypeConstants), rnAreaS)            Else               Set rnAreaDiff = rnAreaS.SpecialCells(xlCellTypeConstants)            End If            'restore            rnAreaS.Formula = varFormulas        End If        If Not (rnAreaModified Is Nothing) Then            Set rnAreaModified = Intersect(rnAreaModified, rnAreaDiff)        End If    Next    If Not (rnAreaModified Is Nothing) Then        If rnOut Is Nothing Then            Set rnOut = rnAreaModified        Else            Set rnOut = Union(rnOut, rnAreaModified)        End If    End If Next Set rngDifference = rnOutEnd Function

Hope this helps