Last active
March 13, 2019 21:18
-
-
Save pcluddite/4f36ebcc1a7decaf9ea3e45b3fa54ca7 to your computer and use it in GitHub Desktop.
VBA module to ease manipulation of sheets and ranges
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
' | |
' SheetEx | |
' Copyright (c) 2016-2017 Timothy Baxendale (pcluddite@outlook.com) | |
' | |
' This library is free software; you can redistribute it and/or | |
' modify it under the terms of the GNU Lesser General Public | |
' License as published by the Free Software Foundation; either | |
' version 2.1 of the License, or (at your option) any later version. | |
' | |
' This library is distributed in the hope that it will be useful, | |
' but WITHOUT ANY WARRANTY; without even the implied warranty of | |
' MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
' Lesser General Public License for more details. | |
' | |
' You should have received a copy of the GNU Lesser General Public | |
' License along with this library; if not, write to the Free Software | |
' Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
' | |
Option Explicit | |
' | |
' Last update: 11/17/17 | |
' Requires: N/A | |
' Description: Miscelaneous functions for interacting with sheets and ranges | |
' | |
' Change the SHEETEX_VER constant to keep certain backward compatability | |
' Revisions stack on top of one another, i.e. If you roll back to Rev 1000, you will revert | |
' any changes made after that version. New functions may still exist when reset | |
' to an earlier version, but this is not guaranteed. | |
' | |
' Revisions: | |
' 1000 - use exBoth for `Shrink` default in ShrinkToFit/ResizeToFit | |
' 1001 - use False for `KeepFormula` default in ClearTable | |
' 1002 - use `AllowTables` param name instead of `StopAtTables` for FindLastRow/FindLastColumn | |
' 1004 - use exRow for `Shrink` default in ShrinkToFit/ResizeToFit | |
' 1005 - use empty string default value for JoinReplace | |
' 1007 - use True for `IncludeHeader` default in GetRange and do not check if range object fits ListObject.DataBodyRange | |
' 1009 - do not use clipboard by default | |
' 1010 - CURRENT VERSION | |
' | |
#Const SHEETEX_VER = 1010 | |
#Const SAFE_RANGE_ENABLED = True ' set to True for bounds checking on ranges | |
' integer constants | |
Public Const MAX_BYTE As Byte = 255 | |
Public Const MIN_BYE As Byte = 0 | |
Public Const MAX_INT As Integer = 32767 | |
Public Const MIN_INT As Integer = -32768 | |
Public Const MAX_LONG As Long = 2147483647 | |
Public Const MIN_LONG As Long = -2147483648# | |
' | |
' Clear value types | |
' | |
Enum ExClearType | |
exClearContents = 1 | |
exClearFormats = 2 | |
exClearComments = 4 | |
exClearHLinks = 8 | |
exClearNotes = 16 | |
exClearOutline = 32 | |
exClearAll = 63 ' this is the sum of all the previous | |
End Enum | |
' | |
' Column/Row or both | |
' | |
Enum ExColumnRow | |
exColumn = 1 | |
exRow = 2 | |
exBoth = 3 | |
End Enum | |
' | |
' Appends a new row of values to a range | |
' | |
Function AppendRow(ByRef Range As Variant, ParamArray Values() As Variant) As Range | |
Values = GetArray(Values) | |
Set AppendRow = RangeBelow(Range, FindLast:=True, Height:=1) | |
With AppendRow | |
Dim i As Long, Value As Variant | |
i = 1 | |
For Each Value In Values | |
.Columns(i).Value2 = Value | |
i = i + 1 | |
Next Value | |
End With | |
End Function | |
' | |
' Inserts a value in an array | |
' | |
Sub ArrayInsert(ByRef arr As Variant, ByVal Index As Long, ByRef Value As Variant) | |
Dim newarr() As Variant, x As Long | |
ReDim newarr(LBound(arr) To UBound(arr) + 1) | |
For x = 0 To Index - 1 | |
LetOrSet newarr(x), arr(x) | |
Next x | |
LetOrSet newarr(Index), Value | |
For x = Index + 1 To UBound(newarr) | |
LetOrSet newarr(x), arr(x - 1) | |
Next x | |
arr = newarr | |
End Sub | |
' | |
' Converts a single dimentional array to a list | |
' | |
Function ArrayToList(ByRef arr As Variant) As Collection | |
Dim x As Long | |
Set ArrayToList = New Collection | |
For x = LBound(arr) To UBound(arr) | |
ArrayToList.Add arr(x) | |
Next x | |
End Function | |
' | |
' Builds a formula with given ranges | |
' | |
' Ranges - a range or array of ranges that will be used as an argument for this formula | |
' Formula - the formula that should be applied to the array. Arguments can be used in the formula string | |
' in the format %1 is arg1, %2 is arg2, etc. | |
' | |
Function BuildFormula(ByVal Ranges As Variant, ByVal Formula As String) As String | |
Dim i As Long | |
If Not IsArray(Ranges) Or TypeName(Ranges) = "Range" Then Ranges = Array(Ranges) | |
For i = LBound(Ranges) To UBound(Ranges) | |
Dim arg As String, argRange As Range | |
Set argRange = GetRange(Ranges(i)).Resize(RowSize:=1) | |
arg = argRange.Address(RowAbsolute:=False, ColumnAbsolute:=False) | |
Formula = Replace(Formula, "%" & (i + 1), arg) | |
Next i | |
BuildFormula = Formula | |
End Function | |
' | |
' Clears all tables in a worksheet | |
' | |
Sub ClearAllTables(ByRef Worksheet As Worksheet, Optional ByVal KeepFormula As Boolean = False) | |
Dim n As Long | |
With Worksheet | |
Dim loTable As ListObject | |
For Each loTable In Worksheet.ListObjects | |
ClearTable loTable, NewHeight:=1, KeepFormula:=KeepFormula | |
Next loTable | |
End With | |
End Sub | |
' | |
' Clears a range | |
' | |
Private Sub ClearEx(ByRef Range As Range, ByVal ClearType As ExClearType) | |
If HasFlag(ClearType, exClearAll) Then | |
Range.Clear | |
Exit Sub | |
End If | |
If HasFlag(ClearType, exClearContents) Then Range.ClearContents | |
If HasFlag(ClearType, exClearComments) Then Range.ClearComments | |
If HasFlag(ClearType, exClearFormats) Then Range.ClearFormats | |
If HasFlag(ClearType, exClearHLinks) Then Range.ClearHyperlinks | |
If HasFlag(ClearType, exClearNotes) Then Range.ClearNotes | |
If HasFlag(ClearType, exClearOutline) Then Range.ClearOutline | |
End Sub | |
' | |
' Clears the filters for a given table | |
' | |
Sub ClearFilters(ByRef Table As Variant) | |
Dim loTable As ListObject | |
Set loTable = TryGetTable(Table) | |
If loTable Is Nothing Then | |
With GetRange(Table, IncludeHeader:=True) | |
If .Worksheet.AutoFilterMode Then .AutoFilter | |
End With | |
Else | |
loTable.AutoFilter.ShowAllData | |
End If | |
End Sub | |
' | |
' Clear data from a Range | |
' Range - the Range to clear | |
' KeepFormula - whether or not to keep formulas | |
' ClearType - the type of data to clear | |
' | |
Sub ClearRange(ByRef Range As Variant, Optional ByVal KeepFormula As Boolean = True, Optional ByVal ClearType As ExClearType = ExClearType.exClearContents) | |
Dim nCol As Long | |
Dim rSrc As Range, rCol As Range | |
Set rSrc = GetRange(Range) | |
If Not KeepFormula Then | |
ClearEx rSrc, ClearType | |
Else | |
For Each rCol In rSrc.Columns | |
If Not rCol.HasFormula Then ClearEx rCol, ClearType | |
Next rCol | |
End If | |
End Sub | |
' | |
' Clears the data from a table | |
' Table - the table name, or a ListObject with the table | |
' NewRows - resize the table to this number of Rows. values less than 1 will not resize the table | |
' KeepFormula - whether or not to keep formulas. this is false by default. | |
' | |
#If SHEETEX_VER > 1001 Then | |
Sub ClearTable(ByRef Table As Variant, Optional ByVal NewHeight As Long = 1, Optional ByVal KeepFormula As Boolean = True) | |
#Else | |
Sub ClearTable(ByRef Table As Variant, Optional ByVal NewHeight As Long = 1, Optional ByVal KeepFormula As Boolean = False) | |
#End If | |
With GetTable(Table) | |
ClearFilters .Range | |
' trim any extra Rows at the bottom and resize the table | |
If NewHeight > 0 Then | |
If Not (.DataBodyRange Is Nothing) Then | |
If NewHeight < .DataBodyRange.Rows.Count Then RShrink(.DataBodyRange, Height:=-NewHeight).Clear | |
If NewHeight = 1 And Not KeepFormula Then .DataBodyRange.Delete | |
End If | |
#If SAFE_RANGE_ENABLED Then | |
.Resize ROffset(.Range, Height:=NewHeight + 1) | |
#Else | |
.Resize .Range.Resize(RowSize:=NewHeight + 1) | |
#End If | |
End If | |
If Not (.DataBodyRange Is Nothing) Then | |
ClearRange .DataBodyRange, KeepFormula | |
End If | |
End With | |
End Sub | |
' | |
' Performs an index match (hlookup) | |
' LookupValue - the key to search for in the LookupRange | |
' LookupRange - the range of keys in which to look for the LookupValue | |
' ValueRange - the range of values corresponding to the keys in the LookupRange | |
' DefaultValue - the default value used in case a match was not found | |
' | |
Function CLookup(ByRef LookupValue As Variant, ByRef LookupRange As Variant, ByRef ValueRange As Variant, Optional ByRef DefaultValue As Variant) As Variant | |
Dim nMatch As Variant | |
If IsMissing(DefaultValue) Then DefaultValue = CVErr(xlErrNA) | |
nMatch = MMatch(LookupValue, GetRange(LookupRange)) | |
If IsError(nMatch) Then | |
CLookup = DefaultValue | |
Else | |
CLookup = GetRange(ValueRange).Columns(nMatch).Value2 | |
End If | |
End Function | |
' | |
' Union a variable number of ranges, or an array of ranges. | |
' Use the built in Union() function if the following can be guaranteed: | |
' - the number of ranges is known at compile time | |
' - all objects are of type "Range" | |
' - the order of the ranges does not matter | |
' | |
Function CombineRange(ParamArray Ranges() As Variant) As Range | |
Dim n As Long | |
Ranges = GetArray(Ranges) | |
Set CombineRange = GetRange(Ranges(LBound(Ranges))) | |
For n = LBound(Ranges) + 1 To UBound(Ranges) | |
Set CombineRange = Union(CombineRange, GetRange(Ranges(n))) | |
Next n | |
SortRange CombineRange | |
End Function | |
Private Function ConcatRangeAddr(ByRef Ranges As Variant, Optional ByRef nRows As Long, Optional ByVal Absolute As ExColumnRow = exColumn) As String | |
If Not IsArray(Ranges) Or TypeName(Ranges) = "Range" Then | |
With GetRange(Ranges) | |
ConcatRangeAddr = .Cells(1, 1).Address(RowAbsolute:=HasFlag(Absolute, exRow), ColumnAbsolute:=HasFlag(Absolute, exColumn), External:=True) | |
nRows = .Rows.Count | |
End With | |
Else | |
Dim addstrs() As String | |
Dim i As Long, curr As Range | |
ReDim addstrs(UBound(Ranges)) | |
nRows = 0 | |
For i = LBound(Ranges) To UBound(Ranges) | |
If TypeName(Ranges(i)) = "String" Then | |
addstrs(i) = Ranges(i) | |
Else | |
With GetRange(Ranges(i)) | |
addstrs(i) = .Cells(1, 1).Address(RowAbsolute:=HasFlag(Absolute, exRow), ColumnAbsolute:=HasFlag(Absolute, exColumn), External:=True) | |
nRows = WorksheetFunction.Max(nRows, .Rows.Count) | |
End With | |
End If | |
Next i | |
ConcatRangeAddr = Join(addstrs, "&") | |
End If | |
End Function | |
' | |
' Gets a true or false value indicating whether a given workbook contains a sheet | |
' | |
Function ContainsSheet(ByRef Workbook As Workbook, ByRef Index As Variant) As Boolean | |
Dim Sheet As Worksheet | |
On Error GoTo catch | |
Set Sheet = Workbook.Worksheets(Index) | |
On Error GoTo 0 | |
ContainsSheet = True | |
Exit Function | |
catch: | |
ContainsSheet = False | |
End Function | |
' | |
' Copies the widths of each column from one range to another | |
' | |
Function CopyColumnWidth(ByRef Source As Variant, ByRef Destination As Variant) | |
Dim rSrc As Range, rDest As Range | |
Dim rCol As Range, n As Long | |
If TypeName(Source) = "Workbook" And TypeName(Destination) = "Workbook" Then | |
Dim SrcSheet As Worksheet, DestSheet As Worksheet | |
Dim wbDest As Workbook | |
Set wbDest = Destination | |
For Each SrcSheet In Source.Worksheets | |
Set DestSheet = TryGetSheet(wbDest, SrcSheet.Name) | |
If Not (DestSheet Is Nothing) Then CopyColumnWidth SrcSheet, DestSheet | |
Next SrcSheet | |
Else | |
Set rSrc = GetRange(Source) | |
Set rDest = GetRange(Destination) | |
If rSrc.Columns.Count <> rDest.Columns.Count Then Set rDest = ROffset(rDest, Width:=rSrc.Columns.Count) | |
For Each rCol In rDest.Columns | |
n = n + 1 | |
rCol.EntireColumn.ColumnWidth = rSrc.Columns(n).EntireColumn.ColumnWidth | |
Next rCol | |
End If | |
End Function | |
' | |
' Copies one or more ranges to given destinations | |
' Ranges - an array of pairs, the first element the source and the second the destination | |
' returns - the most Rows copied | |
' | |
Function CopyMult(ByRef Ranges As Variant, Optional ByVal CellType As XlCellType, _ | |
Optional ByVal Paste As XlPasteType = xlPasteValues, _ | |
Optional ByVal Append As Boolean = False, Optional ByVal ForceColumnVisible As Boolean = False, Optional ByVal FindLast As Boolean = False) As Long | |
Dim n As Long | |
For n = LBound(Ranges) To UBound(Ranges) Step 2 | |
CopyMult = WorksheetFunction.Max(CopySingle(Ranges(n), Ranges(n + 1), CellType, Paste, Append, ForceColumnVisible, FindLast), CopyMult) | |
Next n | |
End Function | |
' | |
' Copies the widths of each row from one range to another | |
' | |
Function CopyRowHeight(ByRef Source As Variant, ByRef Destination As Variant) | |
Dim rSrc As Range, rDest As Range | |
Dim rRow As Range, n As Long | |
If TypeName(Source) = "Workbook" And TypeName(Destination) = "Workbook" Then | |
Dim SrcSheet As Worksheet, DestSheet As Worksheet | |
Dim wbDest As Workbook | |
Set wbDest = Destination | |
For Each SrcSheet In Source.Worksheets | |
Set DestSheet = TryGetSheet(wbDest, SrcSheet.Name) | |
If Not (DestSheet Is Nothing) Then CopyRowHeight SrcSheet, DestSheet | |
Next SrcSheet | |
Else | |
Set rSrc = GetRange(Source) | |
Set rDest = GetRange(Destination) | |
If rSrc.Rows.Count <> rDest.Rows.Count Then Set rDest = ROffset(rDest, Width:=rSrc.Rows.Count) | |
For Each rRow In rDest.Rows | |
n = n + 1 | |
rRow.EntireRow.RowHeight = rSrc.Rows(n).EntireRow.RowHeight | |
Next rRow | |
End If | |
End Function | |
' | |
' Copies a single range to a destination | |
' | |
Function CopySingle(ByRef Source As Variant, ByRef Dest As Variant, _ | |
Optional ByVal CellType As XlCellType, Optional ByVal Paste As XlPasteType = xlPasteValues, _ | |
Optional Append As Boolean = False, Optional ByVal ForceColumnVisible As Boolean = False, Optional ByVal FindLast As Boolean = True) As Long | |
Dim rSrc As Range, rDest As Range | |
Dim Hidden() As Boolean, LastRow As Long | |
Dim Area As Range, Column As Range, n As Long | |
Set rSrc = GetRange(Source) | |
SortRange rSrc | |
If CellType <> Empty Then | |
Set rSrc = SafeSpecial(rSrc, CellType) | |
If rSrc Is Nothing Then Exit Function | |
End If | |
If Append Then | |
Set rDest = RangeBelow(Dest, FindLast:=FindLast).Resize(RowSize:=CountRows(rSrc, True), ColumnSize:=CountColumns(rSrc, True)) | |
Else | |
Set rDest = GetRange(Dest).Resize(RowSize:=CountRows(rSrc, True), ColumnSize:=CountColumns(rSrc, True)) | |
End If | |
If ForceColumnVisible Then | |
Dim Size As Long | |
Let n = 0 | |
For Each Area In rSrc.Areas | |
Size = Size + Area.Columns.Count | |
ReDim Preserve Hidden(Size) | |
For Each Column In Area.Columns | |
Hidden(n) = ShowColumn(Column) | |
n = n + 1 | |
Next Column | |
Next Area | |
End If | |
rSrc.Copy | |
rDest.PasteSpecial Paste | |
If ForceColumnVisible Then | |
Let n = 0 | |
For Each Area In rSrc.Areas | |
For Each Column In Area.Columns | |
If Hidden(n) Then HideColumn Column | |
n = n + 1 | |
Next Column | |
Next Area | |
End If | |
If Append Then EnsureTableSize rSrc, Dest, rDest | |
CopySingle = CountRows(rSrc, AssumeOrder:=True) | |
End Function | |
' | |
' Gets the number of columns from a multi-area range | |
' | |
' AssumeOrder - Assumes that the columns of each area are in ascending order. | |
' This is faster but won't be correct unless the areas in the range are in order. | |
' | |
Property Get CountColumns(ByVal Range As Variant, Optional ByVal AssumeOrder As Boolean = False) As Long | |
Dim rArea As Range | |
With GetRange(Range) | |
If .Areas.Count = 1 Then | |
CountColumns = .Columns.Count | |
ElseIf AssumeOrder Then | |
Dim rLast As Range | |
Set rLast = .Areas(1) | |
CountColumns = rLast.Columns.Count | |
For Each rArea In .Areas | |
If rArea.Column >= rLast.Columns.Count + rLast.Column Then | |
CountColumns = CountColumns + rArea.Columns.Count | |
Set rLast = rArea | |
End If | |
Next rArea | |
Else | |
Dim dict As New Collection, rCol As Range | |
For Each rArea In .Areas | |
For Each rCol In rArea.Columns | |
On Error Resume Next | |
dict.Add rCol.Column, CStr(rCol.Column) | |
On Error GoTo 0 | |
Next rCol | |
Next rArea | |
CountColumns = dict.Count | |
End If | |
End With | |
End Property | |
' | |
' Gets the number of rows from a multi-area range | |
' | |
' AssumeOrder - Assumes that the rows of each area are in ascending order. | |
' This is faster but won't be correct unless the areas in the range are in order. | |
' | |
Property Get CountRows(ByVal Range As Variant, Optional ByVal AssumeOrder As Boolean = False) As Long | |
Dim rArea As Range | |
With GetRange(Range) | |
If .Areas.Count = 1 Then ' most likely case | |
CountRows = .Rows.Count | |
ElseIf AssumeOrder Then | |
Dim rLast As Range | |
Set rLast = .Areas(1) | |
CountRows = rLast.Rows.Count | |
For Each rArea In .Areas | |
If rArea.Row >= rLast.Rows.Count + rLast.Row Then | |
CountRows = CountRows + rArea.Rows.Count | |
Set rLast = rArea | |
End If | |
Next rArea | |
Else | |
Dim dict As New Collection, rRow As Range | |
For Each rArea In .Areas | |
For Each rRow In rArea.Rows | |
On Error Resume Next | |
dict.Add rRow.Row, CStr(rRow.Row) | |
On Error GoTo 0 | |
Next rRow | |
Next rArea | |
CountRows = dict.Count | |
End If | |
End With | |
End Property | |
Private Function CreateArrayLookup(ByRef SourceKeys As Variant, ByRef SourceValues As Variant, ByRef DestKeys As Variant) As String | |
Dim Matches() As String | |
Dim n As Long | |
' SourceKeys and DestKeys MUST have the same bounds | |
Debug.Assert LBound(SourceKeys) = LBound(DestKeys) And UBound(SourceKeys) = UBound(DestKeys) | |
ReDim Matches(LBound(SourceKeys) To UBound(SourceKeys)) | |
For n = LBound(SourceKeys) To UBound(SourceKeys) | |
Matches(n) = "(" & GetRange(SourceKeys(n)).Address(External:=True) & "=" & GetRange(DestKeys(n)).Cells(1, 1).Address(RowAbsolute:=False, ColumnAbsolute:=False, External:=True) & ")" | |
Next n | |
CreateArrayLookup = Join(Matches, "*") | |
CreateArrayLookup = "INDEX(" & GetRange(SourceValues).Address(External:=True) & ", MATCH(1, " & CreateArrayLookup & ", 0))" | |
End Function | |
' | |
' Creates an INDEX/MATCH from the given criteria | |
' if the source keys are an array, the destination keys must also be an array of the same size. | |
' if the destinations keys are an array but the source is not, the destination ranges are concatenated to form a key | |
' | |
Function CreateLookup(ByRef SourceKeys As Variant, ByRef SourceValues As Variant, ByRef DestKeys As Variant, Optional ByVal KeyAbsolute As ExColumnRow = exColumn) As String | |
If IsArrayNotRange(SourceKeys) And IsArrayNotRange(DestKeys) Then | |
CreateLookup = CreateArrayLookup(SourceKeys, SourceValues, DestKeys) | |
Else | |
Dim rSrcKeys As Range, rSrcValues As Range | |
Dim MatchAddr As String | |
Set rSrcKeys = GetRange(SourceKeys) | |
Set rSrcValues = GetRange(SourceValues) | |
If IsArrayNotRange(DestKeys) Then | |
MatchAddr = ConcatRangeAddr(DestKeys, Absolute:=KeyAbsolute) | |
Else | |
MatchAddr = GetRange(DestKeys).Range("A1").Address(RowAbsolute:=HasFlag(KeyAbsolute, exRow), ColumnAbsolute:=HasFlag(KeyAbsolute, exColumn), External:=True) | |
End If | |
CreateLookup = "INDEX(" & rSrcValues.Address(RowAbsolute:=True, ColumnAbsolute:=True, External:=True) & ", MATCH(" & MatchAddr & ", " & rSrcKeys.Address(RowAbsolute:=True, ColumnAbsolute:=True, External:=True) & ", 0))" | |
End If | |
End Function | |
' | |
' Creates an INDEX/MATCH with two lookups (matches) for row and column respectively | |
' | |
Function CreateDualLookup(ByRef SourceRange As Variant, ByRef SourceRows As Variant, ByRef SourceColumns As Variant, ByRef DestRow As Variant, ByRef DestColumn As Variant) | |
Dim rSrc As Range, rSrcRows As Range, rSrcCols As Range | |
Dim rDestRow As Range, rDestCol As Range | |
Set rSrc = GetRange(rSrc) | |
Set rSrcRows = GetRange(SourceRows) | |
Set rSrcCols = GetRange(SourceColumns) | |
Set rDestRow = GetRange(DestRow) | |
Set rDestCol = GetRange(DestColumn) | |
CreateDualLookup = "=INDEX(" & rSrc.Address(External:=True) & _ | |
", MATCH(" & rDestRow.Address(RowAbsolute:=False) & ", " & rSrcRows.Address(External:=True) & ", 0)" & _ | |
", MATCH(" & rDestRow.Address(ColumnAbsolute:=False) & ", " & rSrcCols.Address(External:=True) & ", 0))" | |
End Function | |
Private Sub EnsureTableSize(ByRef rSrc As Range, ByRef Dest As Variant, ByRef rDest As Range) | |
Dim loTable As ListObject, TableLast As Long, DestLast As Long | |
Set loTable = TryGetTable(Dest) | |
If Not (loTable Is Nothing) Then | |
TableLast = FindLastRow(GetBodyRange(loTable)) | |
DestLast = rDest.Row + rDest.Rows.Count - 1 | |
If DestLast > TableLast Then | |
ResizeTable loTable, loTable.Range.Resize(RowSize:=DestLast - TableLast + 1) | |
End If | |
End If | |
End Sub | |
' | |
' Exclude a range from another range (the inverse of Intersect) | |
' DO NOT USE THIS ON CELLS THAT HAVE VALIDATION | |
' | |
Function Exclude(ByRef Range1 As Variant, ByRef Range2 As Variant) As Range | |
Dim R1 As Range, R2 As Range, Overlap As Range | |
Set R1 = GetRange(Range1) | |
Set R2 = GetRange(Range2) | |
Debug.Assert SafeSpecial(R1, xlCellTypeSameValidation) Is Nothing And SafeSpecial(R2, xlCellTypeSameValidation) Is Nothing | |
With Union(R1, R2).Validation | |
.Add xlValidateInputOnly, 1 | |
Intersect(R1, R2).Validation.Delete | |
Set Exclude = SafeSpecial(R1, xlCellTypeAllValidation) | |
.Delete | |
End With | |
End Function | |
' | |
' Copies ranges without putting the copied data on the clipboard. With a large number of areas | |
' in a multi-area range, CopyMult() may be faster. | |
' | |
' Ranges - expected to be an even numbered array, with every even index containing the source range | |
' and every odd index containing the destination for that source | |
' | |
' returns - the largest number of Rows copied | |
' | |
Function FastCopy(ParamArray Ranges() As Variant) As Long | |
FastCopy = FastCopyEx(GetArray(Ranges)) | |
End Function | |
' | |
' Copies ranges without putting the copied data on the clipboard. With a large number of areas | |
' in a multi-area range, CopyMult() may be faster. | |
' | |
' Ranges - expected to be an even numbered array, with every even index containing the source range | |
' and every odd index containing the destination for that source | |
' CellType - the type of cell to copy | |
' NumberFormat - the number format to use | |
' Append - place the copied values at the bottom of the destination range rather than overwriting it | |
' | |
' returns - the largest number of Rows copied | |
' | |
Function FastCopyEx(ByRef Ranges As Variant, Optional ByVal CellType As XlCellType, Optional ByVal NumberFormat As String, Optional ByVal Append As Boolean = False, Optional ByVal ForceColumnVisible As Boolean = False, Optional ByVal StackSources As Boolean = True) As Long | |
Dim n As Long | |
For n = LBound(Ranges) To UBound(Ranges) Step 2 | |
Dim rSrc As Range, rDest As Range | |
Dim rSrcArea As Range, rDestArea As Range | |
Dim srcRows As Long, bHidden As Boolean | |
Set rSrc = ShrinkToFit(Ranges(n), exRow) | |
Set rDest = GetRange(Ranges(n + 1)) | |
SortRange rSrc | |
If Append Then Set rDest = RangeBelow(rDest, FindLast:=False) | |
If ForceColumnVisible Then bHidden = ShowColumn(rSrc) | |
If CellType <> Empty Then | |
Set rSrc = SafeSpecial(rSrc, CellType) | |
If rSrc Is Nothing Then GoTo continue | |
End If | |
Let srcRows = CountRows(rSrc, AssumeOrder:=True) | |
rDest.ClearContents | |
If srcRows <> rDest.Rows.Count Then Set rDest = rDest.Resize(RowSize:=srcRows) | |
For Each rSrcArea In rSrc.Areas | |
If rDestArea Is Nothing Then | |
Set rDestArea = rDest.Resize(RowSize:=rSrcArea.Rows.Count, ColumnSize:=rSrcArea.Columns.Count) | |
Else | |
If StackSources Then | |
Set rDestArea = rDestArea.Offset(RowOffset:=rDestArea.Rows.Count).Resize(rSrcArea.Rows.Count, rSrcArea.Columns.Count) | |
Else | |
Set rDestArea = rDestArea.Offset(ColumnOffset:=rDestArea.Columns.Count).Resize(rSrcArea.Rows.Count, rSrcArea.Columns.Count) | |
End If | |
End If | |
#If SAFE_RANGE_ENABLED Then | |
SafeSetValue rSrcArea, rDestArea | |
#Else | |
rDestArea.Value2 = rSrcArea.Value2 | |
#End If | |
Next rSrcArea | |
If NumberFormat <> Empty Then rDest.NumberFormat = NumberFormat | |
Let FastCopyEx = WorksheetFunction.Max(srcRows, FastCopyEx) | |
If ForceColumnVisible And bHidden Then HideColumn rSrc | |
If Append Then EnsureTableSize rSrc, Ranges(n + 1), rDest | |
continue: | |
Set rDestArea = Nothing | |
Next n | |
End Function | |
' | |
' Fills a formula in a given range and then saves the values | |
' | |
' Range - the range to fill in with the formula | |
' Formula - the formula | |
' Calculate - whether or not the worksheet should be calculated after the values are filled | |
' Clipboard - whether or not to use the clipboad. Using the clipboard may be slower, but it will ensure the source formatting and data types | |
' ArrayFormula - The formula given is an array formula | |
' | |
#If SHEETEX_VER < 1010 Then | |
Function FillValues(ByRef Range As Variant, ByVal Formula As String, Optional ByVal Calculate As Boolean = True, Optional ByVal Clipboard As Boolean = False, Optional ByVal ArrayFormula As Boolean = False) | |
#Else | |
Function FillValues(ByRef Range As Variant, ByVal Formula As String, Optional ByVal Calculate As Boolean = True, Optional ByVal Clipboard As Boolean = True, Optional ByVal ArrayFormula As Boolean = False) | |
#End If | |
With GetRange(Range) | |
With .Range("A1") | |
If ArrayFormula Then | |
.FormulaArray = Formula | |
Else | |
.Formula = Formula | |
End If | |
End With | |
If .Rows.Count > 1 Then .FillDown | |
If .Columns.Count > 1 Then .FillRight | |
If Calculate Then .Worksheet.Calculate | |
If Clipboard Then | |
CopySingle Range, Range, Paste:=xlPasteValues | |
Else | |
.Value2 = .Value2 | |
End If | |
End With | |
End Function | |
' | |
' Applies a fiilter to a pivot table on a given field | |
' | |
Sub FilterPivot(ByRef Pivot As Variant, ByRef Field As Variant, ByRef Criteria As Variant) | |
Dim PivotTable As PivotTable, Item As PivotItem | |
Dim x As Long, Match As Boolean, CriteriaArray As Variant | |
If IsArrayNotRange(Criteria) Then | |
CriteriaArray = Criteria | |
Else | |
CriteriaArray = Array(Criteria) | |
End If | |
Set PivotTable = GetPivot(Pivot) | |
For Each Item In PivotTable.PivotFields(Field).PivotItems | |
Match = False | |
For x = LBound(CriteriaArray) To UBound(CriteriaArray) | |
Match = CriteriaArray(x) Like Item.Name | |
If Match Then Exit For | |
Next x | |
On Error Resume Next | |
Item.Visible = Match | |
On Error GoTo 0 | |
Next Item | |
End Sub | |
' | |
' Applies a filter to the specified header/filter. | |
' | |
Sub FilterTable(ByRef Range As Variant, ByRef Field As Variant, ByRef Criteria1 As Variant, Optional ByVal Operator As XlAutoFilterOperator = xlAnd, Optional ByRef Criteria2 As Variant, Optional ByRef VisibleDropDown As Variant) | |
With GetRange(Range, IncludeHeader:=True) | |
' If .Worksheet.AutoFilterMode Then | |
' .Worksheet.AutoFilterMode = False | |
' .AutoFilter | |
' End If | |
.AutoFilter GetColumnRange(Range, Field).Column - .Column + 1, Criteria1, Operator, Criteria2, VisibleDropDown | |
End With | |
End Sub | |
' | |
' Finds the last column in a given row. If no row is supplied, finds the last column in the range. | |
' NOTE - merged cells are not supported and could give unexpected results | |
' | |
' Range - the range to find the last row | |
' RowIndex - the index of the row. If this is not specified, all rows are counted | |
' StopAtTables - Whether or not to stop when a table is reached (i.e., count tables as non-empty cells) | |
' | |
' returns - the (absolute) index of last column | |
' | |
#If SHEETEX_VER > 1002 Then | |
Function FindLastColumn(ByRef Range As Variant, Optional ByRef RowIndex As Variant, Optional ByVal StopAtTables As Boolean = False) As Long | |
#Else | |
Function FindLastColumn(ByRef Range As Variant, Optional ByRef RowIndex As Variant, Optional ByVal AllowTables As Boolean = False) As Long | |
Dim StopAtTables As Boolean | |
StopAtTables = AllowTables | |
#End If | |
Dim CurrCell As Range, Hidden As Boolean | |
If IsMissing(RowIndex) Then | |
Dim n As Long, rng As Range | |
Set rng = GetRange(Range) | |
FindLastColumn = 1 | |
If rng.Columns.Count >= rng.Rows.Count Then | |
If rng.Worksheet.Columns("A").Hidden Then Hidden = ShowColumn(rng.Worksheet.Columns("A")) | |
For n = 1 To rng.Rows.Count | |
Set CurrCell = rng.Worksheet.Cells(rng.Rows(n).Row, rng.Column + rng.Columns.Count - 1) | |
While IsEmpty(CurrCell) And CurrCell.Column <> 1 And (Not StopAtTables Or CurrCell.ListObject Is Nothing) | |
Set CurrCell = CurrCell.End(xlToLeft) | |
Wend | |
FindLastColumn = WorksheetFunction.Max(FindLastColumn, CurrCell.Column) | |
Next n | |
If Hidden Then HideColumn rng.Worksheet.Columns("A") | |
Else | |
For n = rng.Columns.Count To 1 Step -1 | |
Dim LastRow As Long | |
LastRow = FindLastRow(rng, n, StopAtTables) | |
Set CurrCell = rng.Cells(LastRow, n) | |
If LastRow < rng.Row Or (Not IsEmpty(CurrCell) Or (StopAtTables And Not (CurrCell.ListObject Is Nothing))) Then | |
FindLastColumn = n + rng.Column - 1 | |
Exit For | |
End If | |
Next n | |
End If | |
Else | |
With GetRange(Range) | |
Set CurrCell = .Worksheet.Cells(.Rows(RowIndex).Row, .Column + .Columns.Count - 1) | |
If .Worksheet.Columns("A").Hidden Then Hidden = ShowColumn(.Worksheet.Columns("A")) | |
While IsEmpty(CurrCell) And CurrCell.Column <> 1 And (Not StopAtTables Or CurrCell.ListObject Is Nothing) | |
Set CurrCell = CurrCell.End(xlToLeft) | |
Wend | |
FindLastColumn = CurrCell.Column | |
If Hidden Then HideColumn .Worksheet.Columns("A") | |
End With | |
End If | |
End Function | |
' | |
' Finds the last non-empty row in a range | |
' NOTE - merged cells are not supported and could give unexpected results | |
' | |
' Range - the range to find the last row | |
' ColumnIndex - the index of the column. If this is not specified, all columns are counted | |
' StopAtTables - Whether or not to stop when a table is reached (i.e., count tables as non-empty cells) | |
' | |
' returns - the (absolute) index of the last row | |
' | |
#If SHEETEX_VER > 1002 Then | |
Function FindLastRow(ByRef Range As Variant, Optional ByRef ColumnIndex As Variant, Optional ByVal StopAtTables As Boolean = False) As Long | |
#Else | |
Function FindLastRow(ByRef Range As Variant, Optional ByRef ColumnIndex As Variant, Optional ByVal AllowTables As Boolean = False) As Long | |
Dim StopAtTables As Boolean | |
StopAtTables = AllowTables | |
#End If | |
Dim CurrCell As Range | |
If IsMissing(ColumnIndex) Then | |
Dim n As Long, rng As Range | |
Set rng = GetRange(Range) | |
FindLastRow = 1 | |
If rng.Rows.Count >= rng.Columns.Count Then | |
For n = 1 To rng.Columns.Count | |
Set CurrCell = rng.Worksheet.Cells(rng.Row + rng.Rows.Count - 1, rng.Columns(n).Column) | |
While IsEmpty(CurrCell) And CurrCell.Row <> 1 And (Not StopAtTables Or CurrCell.ListObject Is Nothing) | |
Set CurrCell = CurrCell.End(xlUp) | |
Wend | |
FindLastRow = WorksheetFunction.Max(FindLastRow, CurrCell.Row) | |
Next n | |
Else | |
For n = rng.Rows.Count To 1 Step -1 | |
Dim LastColumn As Long | |
LastColumn = RelativeColumn(rng, FindLastColumn(rng, n, StopAtTables)) | |
Set CurrCell = rng.Cells(n, LastColumn) | |
If LastColumn < rng.Column Or (Not IsEmpty(CurrCell) Or (StopAtTables And Not (CurrCell.ListObject Is Nothing))) Then | |
FindLastRow = n + rng.Row - 1 | |
Exit For | |
End If | |
Next n | |
End If | |
Else | |
Dim Hidden As Boolean | |
With GetRange(Range) | |
Set CurrCell = .Worksheet.Cells(.Row + .Rows.Count - 1, .Columns(ColumnIndex).Column) | |
Hidden = .Worksheet.Rows(1).Hidden | |
If Hidden Then .Worksheet.Rows(1).Hidden = False | |
While IsEmpty(CurrCell) And CurrCell.Row <> 1 And (Not StopAtTables Or CurrCell.ListObject Is Nothing) | |
Set CurrCell = CurrCell.End(xlUp) | |
Wend | |
FindLastRow = CurrCell.Row | |
.Worksheet.Rows(1).Hidden = Hidden | |
End With | |
End If | |
End Function | |
' | |
' Finds the first column in a given row. If no row is supplied, finds the first column in the range. | |
' NOTE - merged cells are not supported and could give unexpected results | |
' | |
' Range - the range to find the last row | |
' RowIndex - the index of the row. If this is not specified, all rows are counted | |
' StopAtTables - Whether or not to stop when a table is reached (i.e., count tables as non-empty cells) | |
' | |
' returns - the (absolute) index of first column | |
' | |
Function FindFirstColumn(ByRef Range As Variant, Optional ByRef RowIndex As Variant, Optional ByVal StopAtTables As Boolean = False) As Long | |
Dim CurrCell As Range, Hidden As Boolean | |
If IsMissing(RowIndex) Then | |
Dim n As Long, rng As Range | |
Set rng = GetRange(Range) | |
FindFirstColumn = rng.Columns.Count | |
If rng.Columns.Count >= rng.Rows.Count Then | |
If rng.Worksheet.Columns(rng.Worksheet.Columns.Count).Hidden Then Hidden = ShowColumn(rng.Worksheet.Columns(rng.Worksheet.Columns.Count)) | |
For n = 1 To rng.Rows.Count | |
Set CurrCell = rng.Worksheet.Cells(rng.Rows(n).Row, rng.Column) | |
While IsEmpty(CurrCell) And CurrCell.Column <> rng.Columns.Count And (Not StopAtTables Or CurrCell.ListObject Is Nothing) | |
Set CurrCell = CurrCell.End(xlToRight) | |
Wend | |
FindFirstColumn = WorksheetFunction.Min(FindFirstColumn, CurrCell.Column) | |
Next n | |
If Hidden Then HideColumn rng.Worksheet.Columns("A") | |
Else | |
For n = 1 To rng.Columns.Count | |
Dim LastRow As Long | |
LastRow = FindFirstRow(rng, n, StopAtTables) | |
Set CurrCell = rng.Cells(LastRow, n) | |
If LastRow < rng.Row Or (Not IsEmpty(CurrCell) Or (StopAtTables And Not (CurrCell.ListObject Is Nothing))) Then | |
FindFirstColumn = n + rng.Column - 1 | |
Exit For | |
End If | |
Next n | |
End If | |
Else | |
With GetRange(Range) | |
Set CurrCell = .Worksheet.Cells(.Rows(RowIndex).Row, .Column) | |
If .Worksheet.Columns(.Worksheet.Columns.Count).Hidden Then Hidden = ShowColumn(.Worksheet.Columns(.Worksheet.Columns.Count)) | |
While IsEmpty(CurrCell) And CurrCell.Column <> .Worksheet.Columns.Count And (Not StopAtTables Or CurrCell.ListObject Is Nothing) | |
Set CurrCell = CurrCell.End(xlToRight) | |
Wend | |
FindFirstColumn = CurrCell.Column | |
If Hidden Then HideColumn .Worksheet.Columns(.Worksheet.Columns.Count) | |
End With | |
End If | |
End Function | |
' | |
' Finds the first non-empty row in a range | |
' NOTE - merged cells are not supported and could give unexpected results | |
' | |
' Range - the range to find the first row | |
' ColumnIndex - the index of the column. If this is not specified, all columns are counted | |
' StopAtTables - Whether or not to stop when a table is reached (i.e., count tables as non-empty cells) | |
' | |
' returns - the (absolute) index of the first row | |
' | |
Function FindFirstRow(ByRef Range As Variant, Optional ByRef ColumnIndex As Variant, Optional ByVal StopAtTables As Boolean = False) As Long | |
Dim CurrCell As Range | |
If IsMissing(ColumnIndex) Then | |
Dim n As Long, rng As Range | |
Set rng = GetRange(Range) | |
FindFirstRow = rng.Worksheet.Rows.Count | |
If rng.Rows.Count >= rng.Columns.Count Then | |
For n = 1 To rng.Columns.Count | |
Set CurrCell = rng.Worksheet.Cells(rng.Row, rng.Columns(n).Column) | |
While IsEmpty(CurrCell) And CurrCell.Row <> rng.Worksheet.Rows.Count And (Not StopAtTables Or CurrCell.ListObject Is Nothing) | |
Set CurrCell = CurrCell.End(xlDown) | |
Wend | |
FindFirstRow = WorksheetFunction.Min(FindFirstRow, CurrCell.Row) | |
Next n | |
Else | |
For n = 1 To rng.Rows.Count | |
Dim FirstColumn As Long | |
FirstColumn = RelativeColumn(rng, FindFirstColumn(rng, n, StopAtTables)) | |
Set CurrCell = rng.Cells(n, FirstColumn) | |
If FirstColumn < rng.Column Or (Not IsEmpty(CurrCell) Or (StopAtTables And Not (CurrCell.ListObject Is Nothing))) Then | |
FindFirstRow = n + rng.Row - 1 | |
Exit For | |
End If | |
Next n | |
End If | |
Else | |
Dim Hidden As Boolean | |
With GetRange(Range) | |
Set CurrCell = .Worksheet.Cells(.Row, .Columns(ColumnIndex).Column) | |
Hidden = .Worksheet.Rows(1).Hidden | |
If Hidden Then .Worksheet.Rows(1).Hidden = False | |
While IsEmpty(CurrCell) And CurrCell.Row <> .Worksheet.Rows.Count And (Not StopAtTables Or CurrCell.ListObject Is Nothing) | |
Set CurrCell = CurrCell.End(xlDown) | |
Wend | |
FindFirstRow = CurrCell.Row | |
.Worksheet.Rows(1).Hidden = Hidden | |
End With | |
End If | |
End Function | |
Private Function GenerateTableName(ByRef wb As Workbook) As String | |
Dim loTable As ListObject, ws As Worksheet | |
Dim n As Long, Found As Boolean | |
Let n = 1 | |
Do | |
For Each ws In wb.Worksheets | |
For Each loTable In ws.ListObjects | |
If StrComp(loTable.Name, "Table" & n, vbTextCompare) = 0 Then | |
Found = True | |
GoTo Break_Out | |
End If | |
Next loTable | |
Next ws | |
Break_Out: | |
If Not Found Then GenerateTableName = "Table" & n | |
n = n + 1 | |
Found = False | |
Loop While GenerateTableName = "" | |
End Function | |
' | |
' Returns an array in the case that a ParamArray function was passed an array | |
' | |
Function GetArray(ParamArray arr() As Variant) As Variant | |
If UBound(arr) < LBound(arr) Then Exit Function | |
If IsArrayNotRange(arr(LBound(arr))) Then | |
Dim newarr As Variant | |
LetOrSet newarr, arr(LBound(arr)) | |
If LBound(newarr) = UBound(newarr) Then | |
If IsArrayNotRange(newarr(LBound(newarr))) Then | |
LetOrSet GetArray, newarr(LBound(newarr)) | |
Exit Function | |
End If | |
End If | |
LetOrSet GetArray, newarr | |
Else | |
GetArray = arr | |
End If | |
End Function | |
Private Function GetBodyRange(ByRef Range As Variant) As Range | |
If Range.DataBodyRange Is Nothing Then | |
Set GetBodyRange = Range.Range.Resize(Range.Range.Rows.Count - 1).Offset(RowOffset:=1) ' if no data body, use range below the header | |
Else | |
Set GetBodyRange = Range.DataBodyRange | |
End If | |
End Function | |
' | |
' Gets a column as a range | |
' You can use a column header in the case of a list object, or a column letter or number. | |
' Column headers will take precedence over column letters | |
' (e.g. a table with a column named PO will take precedence over $PO:$PO) | |
' | |
Function GetColumnRange(ByRef Range As Variant, ByRef Column As Variant) As Range | |
If TypeName(Column) = "String" Then | |
Set GetColumnRange = ListObjCol(GetRange(Range), Column) | |
If GetColumnRange Is Nothing Then | |
Set GetColumnRange = GetRange(Range).Columns(Column) | |
End If | |
ElseIf TypeName(Column) = "Range" Then | |
Set GetColumnRange = Column | |
ElseIf IsNumeric(Column) Then | |
Set GetColumnRange = GetRange(Range).Columns(Column) | |
Else | |
Err.Raise 5 | |
End If | |
End Function | |
' | |
' Alias for GetColumnRange | |
' | |
Function GCR(ByRef Range As Variant, ByRef Column As Variant) As Range | |
Set GCR = GetColumnRange(Range, Column) | |
End Function | |
' | |
' Converts the given value into a PivotTable object | |
' | |
Function GetPivot(ByRef Pivot As Variant) As PivotTable | |
' this is in the order that I think is most likely to happen | |
Select Case TypeName(Pivot) | |
Case Is = "PivotTable" | |
Set GetPivot = Pivot | |
Case Is = "Range" | |
Set GetPivot = Pivot.PivotTable | |
Case Is = "String" | |
Set GetPivot = ThisWorkbook.PivotTables(Pivot) | |
Case Else | |
Set GetPivot = Nothing | |
End Select | |
End Function | |
' | |
' Converts a String, ListObject or Worksheet to a Range | |
' | |
#If SHEETEX_VER > 1007 Then | |
Function GetRange(ByRef Range As Variant, Optional ByVal IncludeHeader As Boolean = False) As Range | |
#Else | |
Function GetRange(ByRef Range As Variant, Optional ByVal IncludeHeader As Boolean = True) As Range | |
#End If | |
Set GetRange = TryGetRange(Range, IncludeHeader) | |
' check if the Range object could be converted | |
If GetRange Is Nothing Then Err.Raise 5, Source:="Range", Description:="Argument of type '" & TypeName(Range) & "' cannot be converted to a Range." | |
End Function | |
' | |
' Gets a table from the workbook | |
' table - the table to look for. if this is a string, the table with that name will be returned | |
' if this is a ListObject, the same object is returned. otherwise an error is raised | |
' | |
Function GetTable(ByRef Table As Variant) As ListObject | |
Set GetTable = TryGetTable(Table) | |
If GetTable Is Nothing Then Err.Raise 5, "Table", "Could not convert " & TypeName(Table) & " to a ListObject" | |
End Function | |
Private Function HasFlag(ByVal Value As Variant, ByVal Flag As Variant) | |
HasFlag = (Value And Flag) = Flag | |
End Function | |
' | |
' Hides a single column or the entire group that the column is in | |
' | |
Function HideColumn(ByRef Column As Variant) As Boolean | |
With GetRange(Column).EntireColumn | |
If Not .Hidden Then | |
If .OutlineLevel > 1 Then | |
.ShowDetail = False | |
Else | |
.Hidden = True | |
End If | |
HideColumn = True | |
Else | |
HideColumn = False | |
End If | |
End With | |
End Function | |
Private Property Get IsArrayNotRange(ByRef Range As Variant) As Boolean | |
IsArrayNotRange = IsArray(Range) And TypeName(Range) <> "Range" | |
End Property | |
' | |
' Keeps the current format of a range and removes conditional formats associated with it | |
' | |
Sub KeepCurrentFormat(ByRef Range As Variant) | |
Dim Cell As Range | |
With GetRange(Range) | |
For Each Cell In .Cells | |
With Cell | |
.Interior.Color = .DisplayFormat.Interior.Color | |
.Font.FontStyle = .DisplayFormat.Font.FontStyle | |
.Font.Strikethrough = .DisplayFormat.Font.Strikethrough | |
.Font.Color = .DisplayFormat.Font.Color | |
.Font.Bold = .DisplayFormat.Font.Bold | |
.Font.Italic = .DisplayFormat.Font.Italic | |
.Font.Underline = .DisplayFormat.Font.Underline | |
End With | |
Next Cell | |
.FormatConditions.Delete | |
End With | |
End Sub | |
Private Function ListObjCol(ByRef Range As Range, ByRef Column As Variant) As Range | |
Dim loTable As ListObject | |
Set loTable = TryGetTable(Range) | |
If loTable Is Nothing Then GoTo catch | |
On Error GoTo catch | |
Set ListObjCol = loTable.ListColumns(Column).DataBodyRange | |
On Error GoTo 0 | |
If ListObjCol Is Nothing Then | |
Set ListObjCol = loTable.ListColumns(Column).Range | |
Set ListObjCol = ListObjCol.Offset(1).Resize(1) | |
End If | |
With ListObjCol | |
Dim oRange As Range, StartRow As Long | |
Set oRange = GetRange(Range, IncludeHeader:=False) | |
Let StartRow = WorksheetFunction.Max(oRange.Row, .Row) | |
#If SAFE_RANGE_ENABLED Then | |
Set ListObjCol = SafeRange(.Worksheet, StartRow, .Column, Height:=oRange.Row + oRange.Rows.Count - StartRow) | |
#Else | |
Set ListObjCol = .Worksheet.Cells(StartRow, .Column).Resize(RowSize:=oRange.Row + oRange.Rows.Count - StartRow) | |
#End If | |
End With | |
Exit Function | |
catch: | |
Set ListObjCol = Nothing | |
End Function | |
' | |
' Gets the largest number in a series of numbers | |
' | |
Property Get Max(ParamArray args() As Variant) As Variant | |
If UBound(args) = LBound(args) + 1 Then ' most likely case | |
Max = WorksheetFunction.Max(args(LBound(args)), args(UBound(args))) | |
Else | |
Dim i As Integer | |
If UBound(args) < LBound(args) Then Exit Property | |
args = GetArray(args) | |
If UBound(args) < LBound(args) Then Exit Property | |
Let Max = args(LBound(args)) | |
For i = LBound(args) + 1 To UBound(args) | |
Max = WorksheetFunction.Max(args(i), Max) | |
Next i | |
End If | |
End Property | |
' | |
' Gets the maximum number of columns in a range with one or more areas. | |
' If no range is supplied, the current sheet is used | |
' | |
Property Get MaxColumn(Optional ByRef Range As Variant) As Long | |
If IsMissing(Range) Then | |
Static MaxStatic As Long | |
If MaxStatic = Empty Then MaxStatic = ActiveSheet.Range("1:1").Columns.Count | |
MaxColumn = MaxStatic | |
Else | |
Dim Area As Range | |
For Each Area In GetRange(Range).Areas | |
MaxColumn = WorksheetFunction.Max(MaxColumn, Area.Columns.Count) | |
Next Area | |
End If | |
End Property | |
' | |
' Gets the maximum number of rows in a range with one or more areas. | |
' If no range is supplied, the current sheet is used | |
' | |
Property Get MaxRow(Optional ByRef Range As Variant) As Long | |
If IsMissing(Range) Then | |
Static MaxStatic As Long | |
If MaxStatic = Empty Then MaxStatic = ActiveSheet.Range("A:A").Rows.Count | |
MaxRow = MaxStatic | |
Else | |
Dim Area As Range | |
For Each Area In GetRange(Range).Areas | |
MaxRow = WorksheetFunction.Max(MaxRow, Area.Rows.Count) | |
Next Area | |
End If | |
End Property | |
' | |
' Gets the smallest number in a series of numbers | |
' | |
Property Get Min(ParamArray args() As Variant) As Variant | |
If UBound(args) = LBound(args) + 1 Then ' most likely case | |
Min = WorksheetFunction.Min(args(LBound(args)), args(UBound(args))) | |
Else | |
Dim i As Integer | |
If UBound(args) < LBound(args) Then Exit Property | |
args = GetArray(args) | |
If UBound(args) < LBound(args) Then Exit Property | |
Let Min = args(LBound(args)) | |
For i = LBound(args) + 1 To UBound(args) | |
Min = WorksheetFunction.Min(args(i), Min) | |
Next i | |
End If | |
End Property | |
' | |
' Gets the Missing value | |
' | |
Property Get Missing() As Variant | |
Missing = SheetXArg | |
End Property | |
Private Property Get SheetXArg(Optional ByRef arg As Variant) As Variant | |
SheetXArg = arg | |
End Property | |
' | |
' Finds the first index of a given value in a range | |
' | |
Function MMatch(ByRef Value As Variant, ByRef Range As Variant, Optional ByRef DefaultValue As Variant) As Variant | |
If IsMissing(DefaultValue) Then DefaultValue = CVErr(xlErrNA) | |
On Error GoTo catch | |
MMatch = WorksheetFunction.Match(Value, GetRange(Range), 0) | |
On Error GoTo 0 | |
If IsError(MMatch) Then GoTo catch | |
Exit Function | |
catch: | |
LetOrSet MMatch, DefaultValue | |
End Function | |
' | |
' Creates an array with numbers through | |
' | |
Function NumberArray(ByRef Lower As Variant, ByRef Upper As Variant, Optional ByRef Increment As Variant) As Variant | |
Dim x As Variant, NewArray() As Variant | |
Dim Index As Long, Count As Variant, ArrSize As Long | |
If IsMissing(Increment) Then Increment = IIf(Upper < Lower, -1, 1) | |
Count = Abs(Upper - Lower) + 1 | |
ArrSize = WorksheetFunction.Ceiling(Abs(Count / Increment), 1) | |
If Count = 0 Or ArrSize <= 0 Then | |
NumberArray = Array() | |
Else | |
ReDim NewArray(0 To ArrSize - 1) | |
Index = 0 | |
For x = Lower To Upper Step Increment | |
NewArray(Index) = x | |
Index = Index + 1 | |
Next x | |
NumberArray = NewArray | |
End If | |
End Function | |
Private Sub QuickSort(ByRef arr As Variant, ByVal Low As Long, ByVal High As Long) | |
Dim pvt As Variant | |
Dim tmpLo As Long, tmpHi As Long | |
tmpLo = Low | |
tmpHi = High | |
LetOrSet pvt, arr((Low + High) \ 2) | |
While tmpLo <= tmpHi | |
While arr(tmpLo) < pvt And tmpLo < High | |
tmpLo = tmpLo + 1 | |
Wend | |
While arr(tmpHi) > pvt And tmpHi > Low | |
tmpHi = tmpHi - 1 | |
Wend | |
If tmpLo <= tmpHi Then | |
Swap arr(tmpLo), arr(tmpHi) | |
tmpLo = tmpLo + 1 | |
tmpHi = tmpHi - 1 | |
End If | |
Wend | |
If Low < tmpHi Then QuickSort arr, Low, tmpHi | |
If tmpLo < High Then QuickSort arr, tmpLo, High | |
End Sub | |
Private Sub QuickSortRev(ByRef arr As Variant, ByVal Low As Long, ByVal High As Long) | |
Dim pvt As Variant | |
Dim tmpLo As Long, tmpHi As Long | |
tmpLo = Low | |
tmpHi = High | |
LetOrSet pvt, arr((Low + High) \ 2) | |
While tmpLo <= tmpHi | |
While arr(tmpLo) > pvt And tmpLo < High | |
tmpLo = tmpLo + 1 | |
Wend | |
While arr(tmpHi) < pvt And tmpHi > Low | |
tmpHi = tmpHi - 1 | |
Wend | |
If tmpLo <= tmpHi Then | |
Swap arr(tmpHi), arr(tmpLo) | |
tmpLo = tmpLo + 1 | |
tmpHi = tmpHi - 1 | |
End If | |
Wend | |
If Low < tmpHi Then QuickSortRev arr, Low, tmpHi | |
If tmpLo < High Then QuickSortRev arr, tmpLo, High | |
End Sub | |
' | |
' Gets a range immediately below another range | |
' | |
' FindLast - whether or not to find the first non empty row in the range | |
' | |
Function RangeBelow(ByRef Range As Variant, Optional ByVal FindLast As Boolean = True, Optional ByVal Height As Long) As Range | |
Dim rSrc As Range | |
Dim nHeight As Long | |
If FindLast Then | |
Dim LastRow As Long | |
Set rSrc = GetRange(Range) | |
LastRow = FindLastRow(rSrc) | |
nHeight = rSrc.Rows.Count | |
Set rSrc = ROffset(rSrc, RowOffset:=LastRow - rSrc.Row, Height:=1) | |
Else | |
Set rSrc = GetRange(Range) | |
nHeight = rSrc.Rows.Count | |
End If | |
If Height = Empty Then Height = nHeight | |
Set RangeBelow = ROffset(rSrc, RowOffset:=rSrc.Rows.Count, Height:=Height) | |
End Function | |
' | |
' Converts a Range object into an array with bounds 1 to Range.Cells.Count | |
' | |
Function RangeToArray(ByRef Range As Variant, Optional ByVal SkipBlanks As Boolean = False) As Variant | |
Dim arr() As Variant, n As Long, nIdx As Long | |
With GetRange(Range) | |
ReDim arr(1 To .Cells.Count) | |
nIdx = LBound(arr) | |
For n = LBound(arr) To UBound(arr) | |
If Not IsEmpty(.Cells(n)) Or Not SkipBlanks Then | |
arr(nIdx) = .Cells(n).Value | |
nIdx = nIdx + 1 | |
End If | |
Next n | |
If UBound(arr) <> nIdx - 1 Then ReDim Preserve arr(1 To nIdx - 1) | |
End With | |
RangeToArray = arr | |
End Function | |
' | |
' Gets the relative row from an absolute row | |
' | |
Property Get RelativeColumn(ByRef RelativeTo As Variant, ByRef ColumnAbsolute As Variant) As Long | |
With GetRange(RelativeTo) | |
RelativeColumn = GetColumnRange(.Worksheet, ColumnAbsolute).Column - .Column + 1 | |
End With | |
End Property | |
' | |
' Gets the relative row from an absolute row | |
' | |
Property Get RelativeRow(ByRef RelativeTo As Variant, ByRef RowAbsolute As Variant) As Long | |
With GetRange(RelativeTo) | |
RelativeRow = .Worksheet.Rows(RowAbsolute).Row - .Row + 1 | |
End With | |
End Property | |
' | |
' Remove an element from an array at a given index | |
' | |
Function RemoveAt(ByRef arr As Variant, ByVal Index As Long) As Variant | |
Dim NewArray() As Variant, x As Long | |
ReDim NewArray(LBound(arr) To UBound(arr) - 1) | |
For x = LBound(arr) To Index - 1 | |
LetOrSet NewArray(x), arr(x) | |
Next x | |
For x = Index + 1 To UBound(arr) | |
LetOrSet NewArray(x - 1), arr(x) | |
Next x | |
RemoveAt = NewArray | |
End Function | |
' | |
' Resizes a table and deletes any rows that are no longer in the table | |
' Headers must be in the same row and the resulting table must overlap the original table range. | |
' | |
Sub ResizeTable(ByRef Table As Variant, ByRef NewRange As Variant) | |
Dim NewHeight As Long, NewWidth As Long | |
Dim rng As Range | |
Set rng = GetRange(NewRange, IncludeHeader:=True) | |
NewHeight = rng.Rows.Count | |
NewWidth = rng.Columns.Count | |
With GetTable(Table) | |
If rng.Row <> .HeaderRowRange.Row Or Intersect(rng, .Range) Is Nothing Then | |
Err.Raise 1004, Description:="Headers must be in the same row and the resulting table must overlap the original table range." | |
End If | |
If NewHeight > 0 Then | |
If Not (.DataBodyRange Is Nothing) Then | |
If NewHeight < .DataBodyRange.Rows.Count Then RShrink(.DataBodyRange, Height:=-NewHeight).Clear | |
End If | |
End If | |
If NewWidth > 0 Then | |
If Not (.DataBodyRange Is Nothing) Then | |
If NewWidth < .DataBodyRange.Columns.Count Then RShrink(.DataBodyRange, Width:=-NewWidth).Clear | |
End If | |
End If | |
.Resize rng | |
End With | |
End Sub | |
' | |
' Resises a table or a worksheet by removing empty rows or columns | |
' | |
#If SHEETEX_VER > 1000 And SHEETEX_VER < 1005 Then | |
Function ResizeToFit(ByRef Range As Variant, Optional ByVal Shrink As ExColumnRow = exRow) As Range | |
#Else | |
Function ResizeToFit(ByRef Range As Variant, Optional ByVal Shrink As ExColumnRow = exBoth) As Range | |
#End If | |
Set ResizeToFit = ShrinkToFit(Range, Shrink) | |
Select Case TypeName(Range) | |
Case Is = "ListObject" | |
If ResizeToFit.Rows.Count > 1 Then | |
ResizeTable Range, ResizeToFit | |
Else | |
ResizeTable Range, Range.HeaderRowRange.Resize(RowSize:=2) | |
End If | |
Case Is = "Worksheet" | |
Dim ws As Worksheet | |
Dim LastRow As Long, LastCol As Long | |
Set ws = Range | |
With ResizeToFit | |
LastRow = .Rows.Count + .Row - 1 | |
LastCol = .Columns.Count + .Column - 1 | |
End With | |
If ws.UsedRange.Rows.Count > LastRow Then | |
ws.Range(ws.Cells(LastRow + 1, 1), ws.Cells(ws.UsedRange.Rows.Count, 1)).EntireRow.Delete | |
End If | |
If ws.UsedRange.Columns.Count > LastCol Then | |
With ws.UsedRange | |
ws.Range(ws.Cells(1, LastCol + 1), ws.Cells(.Rows.Count, .Columns.Count)).EntireColumn.Delete xlToLeft | |
End With | |
End If | |
End Select | |
End Function | |
' | |
' Expands a given range | |
' Height - the number of Rows to expand. Use negative numbers to expand upward. | |
' Width - the number of columns to expand. Use negative numbers to expand left. | |
' | |
Function RExpand(ByRef Range As Variant, Optional ByVal Width As Long, Optional ByVal Height As Long) As Range | |
Dim nRow As Long, nCol As Long, oRange As Range | |
Set oRange = GetRange(Range) | |
With oRange | |
Let nRow = IIf(Height < 0, Height, 0) | |
Let nCol = IIf(Width < 0, Width, 0) | |
#If SAFE_RANGE_ENABLED Then | |
Set RExpand = ROffsetInternal(oRange, RowOffset:=nRow, ColumnOffset:=nCol, Width:=Math.Abs(Width) + .Columns.Count, Height:=Math.Abs(Height) + .Rows.Count) | |
#Else | |
Set RExpand = .Offset(nRow, nCol).Resize(ColumnSize:=Math.Abs(Width) + .Columns.Count, RowSize:=Math.Abs(Height) + .Rows.Count) | |
#End If | |
End With | |
End Function | |
' | |
' Copies a column based on a key, replacing any values that were there previously | |
' | |
' SourceKeys - the range that contains keys on the source table | |
' SourceValues - the range of values to copy to the destination | |
' DestKeys - the range that contains keys on the destination table | |
' DestValues - the range to place the copied values | |
' | |
#If SHEETEX_VER < 1010 Then | |
Sub JoinReplace(ByRef SourceKeys As Variant, ByRef SourceValues As Variant, ByRef DestKeys As Variant, ByRef DestValues As Variant, Optional ByVal DefaultValue As Variant, Optional ByVal Calculate As Boolean = True, Optional ByVal Clipboard As Boolean = False) | |
#Else | |
Sub JoinReplace(ByRef SourceKeys As Variant, ByRef SourceValues As Variant, ByRef DestKeys As Variant, ByRef DestValues As Variant, Optional ByVal DefaultValue As Variant, Optional ByVal Calculate As Boolean = True, Optional ByVal Clipboard As Boolean = True) | |
#End If | |
Dim Formula As String, ArrayFormula As Boolean | |
Formula = CreateLookup(SourceKeys, SourceValues, DestKeys) | |
ArrayFormula = IsArrayNotRange(SourceKeys) And IsArrayNotRange(DestKeys) | |
If IsMissing(DefaultValue) Then | |
#If SHEETEX_VER > 1005 Then | |
Formula = "=" & Formula | |
#Else | |
Formula = "=IFNA(" & Formula & ", """")" | |
#End If | |
Else | |
If TypeName(DefaultValue) = "String" Then DefaultValue = """" & Replace(DefaultValue, """", """""") & """" | |
Formula = "=IFNA(" & Formula & ", " & DefaultValue & ")" | |
End If | |
FillValues DestValues, Formula, Calculate, Clipboard, ArrayFormula | |
End Sub | |
' | |
' Performs an index match (vlookup) | |
' LookupValue - the key to search for in the LookupRange | |
' LookupRange - the range of keys in which to look for the LookupValue | |
' ValueRange - the range of values corresponding to the keys in the LookupRange | |
' DefaultValue - the default value used in case a match was not found | |
' | |
Function RLookup(ByRef LookupValue As Variant, ByRef LookupRange As Variant, ByRef ValueRange As Variant, Optional ByRef DefaultValue As Variant) As Variant | |
Dim nMatch As Variant | |
nMatch = MMatch(LookupValue, GetRange(LookupRange)) | |
If IsError(nMatch) Then | |
RLookup = DefaultValue | |
Else | |
RLookup = GetRange(ValueRange).Rows(nMatch).Value2 | |
End If | |
End Function | |
' | |
' Gets the offset of a given range | |
' RowOffset - the number of Rows from the topmost cells to offset | |
' ColumnOffset - the number of columns from the leftmost cells to offset | |
' Width - the number of columns in the resulting range | |
' Height - the number of Rows in the resulting range | |
' | |
Function ROffset(ByRef Range As Variant, Optional ByVal RowOffset As Long, Optional ColumnOffset As Long, Optional ByVal Width As Long, Optional ByVal Height As Long) As Range | |
Dim oRange As Range, Area As Range | |
Set oRange = GetRange(Range) | |
For Each Area In oRange.Areas | |
If ROffset Is Nothing Then | |
Set ROffset = ROffsetInternal(Area, RowOffset, ColumnOffset, Width, Height) | |
Else | |
Set ROffset = Union(ROffset, ROffsetInternal(Area, RowOffset, ColumnOffset, Width, Height)) | |
End If | |
Next Area | |
End Function | |
Private Function ROffsetInternal(ByRef Range As Range, ByVal RowOffset As Long, ByVal ColumnOffset As Long, ByVal Width As Long, ByVal Height As Long) | |
With Range | |
If Width = Empty Then Width = .Columns.Count | |
If Height = Empty Then Height = .Rows.Count | |
#If SAFE_RANGE_ENABLED Then | |
Set ROffsetInternal = SafeRange(.Worksheet, .Row + RowOffset, .Column + ColumnOffset, Width, Height) | |
#Else | |
Set ROffsetInternal = .Resize(Height, Width).Offset(RowOffset, ColumnOffset) | |
#End If | |
End With | |
End Function | |
' | |
' Converts a collection to an array. Keys are ignored. | |
' | |
Function ListToArray(ByRef List As Collection) As Variant | |
Dim arr() As Variant, n As Long | |
ReDim arr(1 To List.Count) | |
For n = LBound(arr) To UBound(arr) | |
LetOrSet arr(n), List(n) | |
Next n | |
ListToArray = arr | |
End Function | |
' | |
' Shrinks a given range | |
' Height - the number of Rows to shrink. Use negative numbers to shrink from top. | |
' Width - the number of columns to expand. Use negative numbers to shrink left. | |
' | |
Function RShrink(ByRef Range As Variant, Optional ByVal Width As Long, Optional ByVal Height As Long) As Range | |
Dim nRow As Long, nCol As Long, oRange As Range | |
Set oRange = GetRange(Range) | |
With oRange | |
Let nRow = IIf(Height < 0, -Height, 0) | |
Let nCol = IIf(Width < 0, -Width, 0) | |
#If SAFE_RANGE_ENABLED Then | |
Set RShrink = ROffsetInternal(oRange, RowOffset:=nRow, ColumnOffset:=nCol, Width:=.Columns.Count - Math.Abs(Width), Height:=.Rows.Count - Math.Abs(Height)) | |
#Else | |
Set RShrink = .Offset(nRow, nCol).Resize(ColumnSize:=.Columns.Count - Math.Abs(Width), RowSize:=.Rows.Count - Math.Abs(Height)) | |
#End If | |
End With | |
End Function | |
' | |
' Takes a set number of rows/columns from a given range | |
' Rows - the number of rows to take from the range. Use negative numbers to shrink from the bottom. | |
' Columns - the number of columns to take from the range. Use negative numbers to take from the left. | |
' | |
Function RTake(ByRef Range As Variant, Optional ByVal Rows As Long, Optional ByVal Columns As Long) As Range | |
Set RTake = GetRange(Range) | |
With RTake | |
If Rows < 0 Then | |
Set RTake = .Rows(.Rows.Count).Offset(RowOffset:=Rows + 1).Resize(RowSize:=Math.Abs(Rows)) | |
ElseIf Rows > 0 Then | |
Set RTake = .Resize(RowSize:=Rows) | |
End If | |
End With | |
With RTake | |
If Columns < 0 Then | |
Set RTake = .Columns(.Columns.Count).Offset(ColumnOffset:=Columns + 1).Resize(ColumnSize:=Math.Abs(Columns)) | |
ElseIf Columns > 0 Then | |
Set RTake = .Resize(ColumnSize:=Columns) | |
End If | |
End With | |
End Function | |
' | |
' Gets a range without going past any bounds | |
' | |
' Range - the starting range or worksheet | |
' RowIndex - the absolute index of the first row for the new range | |
' ColumnIndex - the absolute index of the first column for the new range | |
' Width - the number of columns in the new range | |
' Height - the number of rows in the new range | |
' | |
Function SafeRange(ByRef Range As Variant, ByVal RowIndex As Variant, ByVal ColumnIndex As Variant, Optional ByVal Width As Long = 1, Optional ByVal Height As Long = 1) As Range | |
If IsNumeric(RowIndex) Then RowIndex = WorksheetFunction.Max(WorksheetFunction.Min(RowIndex, MaxRow), 1) | |
If IsNumeric(ColumnIndex) Then ColumnIndex = WorksheetFunction.Max(WorksheetFunction.Min(ColumnIndex, MaxColumn), 1) | |
With GetRange(Range).Cells(RowIndex, ColumnIndex) | |
Set SafeRange = SafeRangeInternal(GetRange(.Worksheet).Resize(MaxRow, MaxColumn), .Row, .Column, Width, Height) | |
End With | |
End Function | |
Function SafeRangeInternal(ByRef Range As Range, ByVal RowIndex As Long, ByVal ColumnIndex As Long, Optional ByVal Width As Long = 1, Optional ByVal Height As Long = 1) As Range | |
RowIndex = WorksheetFunction.Min(Range.Rows.Count, WorksheetFunction.Max(RowIndex, 1)) | |
ColumnIndex = WorksheetFunction.Min(Range.Columns.Count, WorksheetFunction.Max(ColumnIndex, 1)) | |
With Range.Cells(RowIndex, ColumnIndex) | |
Dim LastRow As Long, LastCol As Long | |
LastRow = WorksheetFunction.Max(WorksheetFunction.Min(.Row + Height - 1, MaxRow), .Row) | |
LastCol = WorksheetFunction.Max(WorksheetFunction.Min(.Column + Width - 1, MaxColumn), .Column) | |
Set SafeRangeInternal = .Worksheet.Range(.Worksheet.Cells(.Row, .Column), .Worksheet.Cells(LastRow, LastCol)) | |
End With | |
End Function | |
Private Function SafeSetValue(ByVal Source As Range, ByVal Destination As Range, Optional ByVal Fit As Boolean = True) As Boolean | |
On Error GoTo catch | |
Destination.Value2 = Source.Value2 | |
On Error GoTo 0 | |
SafeSetValue = True | |
Exit Function | |
catch: | |
If Fit Then | |
Set Source = ShrinkToFit(Source, exRow) | |
Set Destination = Destination.Resize(RowSize:=Source.Rows.Count) | |
Destination.Value2 = Source.Value2 ' if this errors out, it needs to be brought to your attention | |
End If | |
SafeSetValue = False | |
End Function | |
' | |
' Gets the special cells in a range. If there are no special cells, returns Nothing. | |
' | |
Function SafeSpecial(ByRef Range As Variant, ByVal CellType As XlCellType) As Range | |
On Error GoTo catch | |
Set SafeSpecial = GetRange(Range, IncludeHeader:=False).SpecialCells(CellType) | |
Exit Function | |
catch: | |
Set SafeSpecial = Nothing | |
End Function | |
' | |
' Sets all column widths in a range | |
' | |
Sub SetColumnWidths(ByRef Range As Variant, Optional ByRef Width As Variant) | |
Dim NewWidth As Variant, Column As Range | |
NewWidth = IIf(IsMissing(Width), 8.43, Width) | |
With GetRange(Range) | |
For Each Column In .Columns | |
Column.EntireColumn.ColumnWidth = NewWidth | |
Next Column | |
End With | |
End Sub | |
' | |
' Sets all row heights in a range | |
' | |
Sub SetRowHeights(ByRef Range As Variant, Optional ByRef Height As Variant) | |
Dim NewHeight As Variant, Row As Range | |
NewHeight = IIf(IsMissing(Height), 15, Height) | |
With GetRange(Range) | |
For Each Row In .Rows | |
Row.EntireRow.RowHeight = NewHeight | |
Next Row | |
End With | |
End Sub | |
' | |
' Shows a group of columns | |
' | |
Function ShowColumn(ByRef Column As Variant) As Boolean | |
With GetRange(Column).EntireColumn | |
If .Hidden Then | |
If .OutlineLevel > 1 Then | |
.ShowDetail = True | |
Else | |
.Hidden = False | |
End If | |
ShowColumn = True | |
Else | |
ShowColumn = False | |
End If | |
End With | |
End Function | |
' | |
' Gets a range to exclude empty columns and rows from the end | |
' | |
' Shrink - resize the row, column, or both | |
' | |
#If SHEETEX_VER > 1000 And SHEETEX_VER < 1005 Then | |
Function ShrinkToFit(ByRef Range As Variant, Optional ByVal Shrink As ExColumnRow = exRow) As Range | |
#Else | |
Function ShrinkToFit(ByRef Range As Variant, Optional ByVal Shrink As ExColumnRow = exBoth) As Range | |
#End If | |
Dim firstRow As Long, firstCol As Long | |
Dim LastRow As Long, LastCol As Long | |
Dim StopAtTables As Boolean | |
StopAtTables = (TypeName(Range) = "Worksheet") | |
#If SAFE_RANGE_ENABLED Then | |
Dim rng As Range | |
Set rng = GetRange(Range) | |
With rng | |
#Else | |
With GetRange(Range) | |
#End If | |
If (Shrink And exRow) = exRow Then | |
LastRow = FindLastRow(Range, , StopAtTables) | |
Else | |
LastRow = .Rows.Count + .Row - 1 | |
End If | |
If (Shrink And exColumn) = exColumn Then | |
LastCol = FindLastColumn(Range, , StopAtTables) | |
Else | |
LastCol = .Columns.Count + .Column - 1 | |
End If | |
#If SAFE_RANGE_ENABLED Then | |
Set ShrinkToFit = SafeRange(rng.Worksheet, .Row, .Column, Height:=LastRow - .Row + 1, Width:=LastCol - .Column + 1) | |
#Else | |
Set ShrinkToFit = .Resize(WorksheetFunction.Max(LastRow - .Row + 1, 1), WorksheetFunction.Max(LastCol - .Column + 1, 1)) | |
#End If | |
End With | |
End Function | |
' | |
' Sorts an array of values in ascending order | |
' This sorts the array in place. Make a copy if you want to keep the original. | |
' | |
Sub SortArray(ByRef UnsortedArray As Variant, Optional ByVal Sort As XlSortOrder = xlAscending) | |
If Sort = xlAscending Then | |
QuickSort UnsortedArray, LBound(UnsortedArray), UBound(UnsortedArray) | |
ElseIf Sort = xlDescending Then | |
QuickSortRev UnsortedArray, LBound(UnsortedArray), UBound(UnsortedArray) | |
End If | |
End Sub | |
Private Sub SortRange(ByRef Range As Range) | |
Dim AddrArray() As String, n As Long | |
If Range.Areas.Count = 1 Then Exit Sub | |
ReDim AddrArray(Range.Areas.Count - 1) | |
For n = LBound(AddrArray) To UBound(AddrArray) | |
AddrArray(n) = Range.Areas(n + 1).Address(External:=True) | |
Next n | |
SortArray AddrArray, xlAscending | |
Set Range = Application.Range(AddrArray(LBound(AddrArray))) | |
For n = LBound(AddrArray) + 1 To UBound(AddrArray) | |
Set Range = Union(Range, Application.Range(AddrArray(n))) | |
Next n | |
End Sub | |
' | |
' Sorts a table on a given column | |
' Range - the table to sort | |
' Order - the sort order | |
' Column - the column to sort on | |
' | |
Sub SortTable(ByRef Range As Variant, ByRef Key As Variant, Optional ByVal Order As XlSortOrder = XlSortOrder.xlAscending, Optional ByVal Header As XlYesNoGuess = xlYes) | |
With GetRange(Range, IncludeHeader:=True) | |
.Sort Key1:=GetColumnRange(Range, Key), Order1:=Order, DataOption1:=xlSortNormal, Header:=Header | |
End With | |
End Sub | |
' | |
' Sums a column based on a key | |
' | |
' SourceLookup - the range that contains keys on the source table | |
' SourceValues - the range of values to copy to the destination | |
' DestLookup - the range that contains keys on the destination table | |
' DestValues - the range to place the copied values | |
' | |
Sub SumColumnByKey(ByRef SourceLookup As Variant, ByRef SourceValues As Variant, ByRef DestLookup As Variant, ByRef DestValues As Variant, Optional ByVal Calculate As Boolean = True) | |
Dim rDestLookup As Range, rSrcLookup As Range, rDestValues As Range, rSrcValues As Range | |
Dim Formula As String | |
Set rDestLookup = GetRange(DestLookup) | |
Set rSrcLookup = GetRange(SourceLookup) | |
Set rDestValues = ROffset(DestValues, Height:=rDestLookup.Rows.Count) | |
Set rSrcValues = ROffset(SourceValues, Height:=rSrcLookup.Rows.Count) | |
'If IsMissing(DefaultValue) Then DefaultValue = """""" | |
rDestValues.Formula = "=SUMIFS(" & rSrcValues.Address(External:=True) & ", " & rSrcLookup.Address(External:=True) & ", " & rDestLookup.Address(RowAbsolute:=False, External:=True) & ")" | |
If Calculate Then rDestValues.Worksheet.Calculate | |
rDestValues.Value2 = rDestValues.Value2 ' keep values | |
End Sub | |
' | |
' Swaps the values of the given variables | |
' | |
Sub Swap(ByRef A As Variant, ByRef B As Variant) | |
Dim tmp As Variant | |
LetOrSet tmp, A | |
LetOrSet A, B | |
LetOrSet B, tmp | |
End Sub | |
' | |
' Attempts to convert a variant to a range. If it cannot, Nothing is returned. | |
' | |
#If SHEETEX_VER > 1007 Then | |
Function TryGetRange(ByRef Range As Variant, Optional ByRef IncludeHeader As Boolean = False) As Range | |
#Else | |
Function TryGetRange(ByRef Range As Variant, Optional ByRef IncludeHeader As Boolean = True) As Range | |
#End If | |
' this is in the order that I think is most likely to happen | |
Select Case TypeName(Range) | |
Case Is = "Range" | |
Set TryGetRange = Range | |
#If SHEETEX_VER > 1007 Then | |
If IncludeHeader Then | |
Dim loTable As ListObject: Set loTable = TryGetRange.ListObject | |
Dim HeaderRange As Range | |
If Not (loTable Is Nothing) Then | |
With GetBodyRange(loTable) | |
If .Worksheet Is TryGetRange.Worksheet And .Row = TryGetRange.Row And .Row + .Rows.Count = TryGetRange.Row + TryGetRange.Rows.Count Then | |
Set HeaderRange = loTable.HeaderRowRange.Resize(ColumnSize:=TryGetRange.Columns.Count).Offset(ColumnOffset:=TryGetRange.Column - .Column) | |
Set TryGetRange = Union(HeaderRange, TryGetRange) | |
End If | |
End With | |
End If | |
End If | |
#End If | |
Case Is = "ListObject", "ListColumn" | |
If IncludeHeader Then | |
Set TryGetRange = Range.Range | |
Else | |
Set TryGetRange = GetBodyRange(Range) | |
End If | |
Case Is = "String" | |
Set TryGetRange = Application.Range(Range) | |
Case Is = "Worksheet" | |
With Range | |
Set TryGetRange = .Range("A1", .Cells(.UsedRange.Rows.Count + .UsedRange.Row - 1, .UsedRange.Columns.Count + .UsedRange.Column - 1)) | |
End With | |
Case Is = "PivotTable" | |
If Range.TableRange2 Is Nothing Then | |
Set TryGetRange = Range.TableRange1 | |
Else | |
Set TryGetRange = Union(Range.TableRange1, Range.TableRange2) | |
End If | |
Case Else | |
Set TryGetRange = Nothing | |
End Select | |
End Function | |
' | |
' Attempts to get a ListObject from a given range. If the range cannot be converted to a table, Nothing is returned | |
' | |
Function TryGetTable(ByRef Table As Variant) As ListObject | |
Select Case TypeName(Table) | |
Case "ListObject" | |
Set TryGetTable = Table | |
Case Else | |
Dim rng As Range | |
Set rng = TryGetRange(Table) | |
If rng Is Nothing Then | |
Set TryGetTable = Nothing | |
Else | |
Set TryGetTable = rng.ListObject | |
End If | |
End Select | |
End Function | |
' | |
' Attempts to get a sheet at a given index. If no sheet can be found, Nothing is returned | |
' | |
Function TryGetSheet(ByRef Workbook As Workbook, ByRef Index As Variant) As Worksheet | |
On Error GoTo catch | |
Set TryGetSheet = Workbook.Worksheets(Index) | |
GoTo cleanup | |
catch: | |
Set TryGetSheet = Nothing | |
cleanup: | |
On Error GoTo 0 | |
End Function | |
' | |
' Create a table in a worksheet | |
' (provided by Michael Cowan) | |
' | |
Sub CreateTable(ByRef Range As Variant, Optional Name As String, Optional ByVal Header As XlYesNoGuess = xlGuess, _ | |
Optional ByVal ShowHeader As Boolean = True, Optional ByVal ShowFilter As Boolean = True, _ | |
Optional ByVal BandedRows As Boolean = False) | |
Dim InputRange As Range | |
Set InputRange = GetRange(Range) | |
With InputRange.Worksheet | |
If Name = Empty Then Name = GenerateTableName(.Parent) | |
.ListObjects.Add(xlSrcRange, InputRange, , Header).Name = Name | |
With .ListObjects(Name) | |
.ShowAutoFilterDropDown = ShowFilter | |
.ShowHeaders = ShowHeader | |
.ShowTableStyleRowStripes = BandedRows | |
End With | |
End With | |
End Sub | |
' | |
' Search for the index of a given value in a single dimensional array. | |
' If the value is not found, LBound(Arr)-1 is returned | |
' (provided by Michael Cowan) | |
' | |
Function IndexOf(ByRef arr As Variant, ByRef Value As Variant, Optional ByVal IgnoreCase As Boolean = False) As Long | |
Dim i As Long, Match As Boolean | |
IndexOf = LBound(arr) - 1 | |
For i = LBound(arr) To UBound(arr) | |
If TypeName(arr(i)) = "String" Then | |
Match = StrComp(arr(i), Value, IIf(IgnoreCase, vbTextCompare, vbBinaryCompare)) = 0 | |
Else | |
If IsObject(arr(i)) Then | |
Match = (arr(i) Is Value) | |
Else | |
Match = (arr(i) = Value) | |
End If | |
End If | |
If Match Then | |
IndexOf = i | |
Exit Function | |
End If | |
Next i | |
End Function | |
' | |
' Gets the last cell in a given range | |
' | |
Property Get LastCell(ByRef Range As Variant) As Range | |
With GetRange(Range) | |
Set LastCell = .Worksheet.Cells(.Row + .Rows.Count - 1, .Column + .Columns.Count - 1) | |
End With | |
End Property | |
' | |
' Gets the absolute column number of the last column in this range | |
' | |
Property Get LastColumn(ByRef Range As Variant) As Long | |
With GetRange(Range) | |
LastColumn = .Column + .Columns.Count - 1 | |
End With | |
End Property | |
' | |
' Gets the absolute row number of the last row in this range | |
' | |
Property Get LastRow(ByRef Range As Variant) As Long | |
With GetRange(Range) | |
LastRow = .Row + .Rows.Count - 1 | |
End With | |
End Property | |
' | |
' Gets the last used cell in a given range | |
' | |
Property Get LastUsedCell(ByRef Range As Variant) As Range | |
Dim rng As Range, nRow As Long, nCol As Long | |
Set rng = GetRange(Range) | |
nRow = FindLastRow(rng) | |
nCol = FindLastColumn(rng) | |
Set LastUsedCell = rng.Worksheet.Cells(nRow, nCol) | |
End Property | |
' | |
' Gets the last used Column in a given range | |
' | |
Property Get LastUsedColumn(ByRef Range As Variant) As Range | |
Dim nLast As Long, rng As Range | |
Set rng = GetRange(rng) | |
nLast = FindLastColumn(rng) | |
With rng | |
Set LastUsedColumn = .Columns(nLast - .Column + 1) | |
End With | |
End Property | |
' | |
' Gets the last used row in a given range | |
' | |
Property Get LastUsedRow(ByRef Range As Variant) As Range | |
Dim nLast As Long, rng As Range | |
Set rng = GetRange(Range) | |
nLast = FindLastRow(rng) | |
With rng | |
Set LastUsedRow = .Rows(nLast - .Row + 1) | |
End With | |
End Property | |
Private Sub LetOrSet(ByRef LValue As Variant, ByRef RValue As Variant) | |
If IsObject(RValue) Then | |
Set LValue = RValue | |
Else | |
LValue = RValue | |
End If | |
End Sub | |
' | |
' Ignores any cell warnings in a given range | |
' | |
Sub IgnoreErrors(ByRef Range As Variant, ByRef ErrorIndex As Variant) | |
Dim Cell As Range | |
With GetRange(Range) | |
For Each Cell In .Cells | |
Cell.Errors(ErrorIndex).Ignore = True | |
Next Cell | |
End With | |
End Sub | |
' | |
' Copies the first row of a range down to match a specified size | |
' If no size is specified, adjacent cells are used to determine it | |
' | |
Sub DragDown(ByRef ColumnRange As Variant, Optional ByVal Size As Long, Optional ByVal Header As XlYesNoGuess = xlYes) | |
Dim Col As Range, nLast As Long, nOffset As Long, nFirst As Long | |
Set Col = GetRange(ColumnRange) | |
Let nOffset = IIf(Header = xlYes, 2, 1) | |
With Col | |
Let nLast = FindLastRow(.EntireColumn) | |
Let nFirst = FindFirstRow(.EntireColumn) | |
With .Worksheet.Range(.Rows(nFirst), .Rows(nLast)) | |
.Resize(RowSize:=.Rows.Count - nOffset).Offset(RowOffset:=nOffset).Clear | |
End With | |
If Size = Empty Then | |
Dim nLeft As Long, nRight As Long | |
If Col.Column > 1 Then nLeft = FindLastRow(Col.Resize(ColumnSize:=1).Offset(ColumnOffset:=-Col.Columns.Count)) | |
If Col.Column < Col.Worksheet.Columns.Count Then nRight = FindLastRow(Col.Resize(ColumnSize:=1).Offset(ColumnOffset:=Col.Columns.Count)) | |
Size = WorksheetFunction.Max(nLeft, nRight) | |
End If | |
.Rows(nFirst + nOffset - 1).AutoFill .Rows(nFirst + nOffset - 1).Resize(RowSize:=Size - nFirst) | |
End With | |
End Sub |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment