AWE_SHEET - Easily Find Ranges, Columns, Rows, and Cells
Published on 8 March 2023 at 16:02
AWE_Sheet removes the complexity of finding, retrieving, and updating data in your spreadsheet that would otherwise require Named Ranges, a Table, or a dozen lines of code. AWE_Sheet is packed with macros that allows you to quickly find, retrieve, and manipulate data on your sheet with just a few lines of code so you can more easily focus on the business problem. AWE_Sheet extends Worksheet functionality allowing you to continue using Excel's great interface, i.e., AutoFilter, SpecialCells… Best yet, all source code is included (see the below implementation section for the code) and it’s FREE.
Implementation
Copy the below code to a Class Module called AWE_Sheet in your Workbook (see How to Create a Class for more information). Function definitions, parameters and examples are included in the comments before each function. Additionally, Visual Basics Class IntelliSense will provide a code completion aid for the AWE_Sheet Class Module by listing its functions and parameters while you code.
'*****************************************************************************************************
'*****************************************************************************************************
'* AWE_Sheet: AWE_Sheet removes the complexity of finding, and updating data in a spreadsheet **
'* that would otherwise require cumbersome Named Ranges, a Table, or a dozen lines of **
'* code. AWE_Sheet is packed with macros that allow you to find, retrieve, and **
'* manipulate data on your sheet with just a few calls so you can more easily focus on **
'* the business problem. AWE_Sheet extends Worksheet functionality allowing you to **
'* continue using Excel's great interface, i.e., AutoFilter, SpecialCells… **
'* Author: Mike Libby **
'* Website: AutomationWithExcel.com
'*****************************************************************************************************
'*****************************************************************************************************
OptionExplicit'====================================================================================================
'== PRIVATE VARIABLES ===============================================================================
'====================================================================================================
Privatem_WorksheetAsWorksheet'--- Worksheet that is used by this class ---
Privatem_HdrRowNbAsLong'--- The one and only header row number that is on the sheet
Privatem_TimerAsSingle'--- Timer that is set in ScreenOff and retuned in ScreenOn
Privatem_HdrArrAsVariant'--- An array of header columns
Privatem_RowKeyDtaArr()AsVariant'--- An array of rows with one or more column values
Privatem_RowKeyHdrArr()AsVariant'--- An array of column names that defines RowKeyArray
'====================================================================================================
'== INITIALIZE ======================================================================================
'====================================================================================================
'----------------------------------------------------------------------------------------------------
' Initialize - Initialize and extend Worksheet functionality
' Parameters:
' TableOrSheetNmOrObj as Variant - A Worksheet name (String), Worksheet Object, or Table ListObject
' Optional HdrRowNb as Long - The header's row number. Default = 1
' Optional WB as Workbook - The Worksheet's Workbook Object or FullFileName. Default = ThisWorkbook.
'
' Examples:
' ActiveSheet - Dim AWESh as new AWE_Sheet: AWESh.Initialize ActiveSheet, 3
' Sheet name - Dim AWESh as new AWE_Sheet: AWESh.Initialize "Worksheet Name", 3
' Table Name - Dim AWESh as new AWE_Sheet: AWESh.Initialize ActiveSheet.ListObjects("TableNm")
' Read only Workbook - Dim AWESh as new AWE_Sheet: AWESh.Initialize "ShNm", 3, "FullFileNm", True
'----------------------------------------------------------------------------------------------------
PublicFunctionInitialize(TableOrSheetNmOrObjAsVariant,OptionalHdrRowNbAsLong=1,_OptionalWBFullFileNmOrObjAsVariant=Nothing,_OptionalWBReadOnlyAsBoolean=False)DimwbAsWorkbookSetm_Worksheet=Nothing'--- Workbook ---
SelectCaseTypeName(WBFullFileNmOrObj)Case"Workbook":Setwb=WBFullFileNmOrObjCase"String":Setwb=Workbooks.Open(Filename:=WBFullFileNmOrObj,ReadOnly:=WBReadOnly)CaseElse:Setwb=ThisWorkbookEndSelect'--- Worksheet ---
SelectCaseTypeName(TableOrSheetNmOrObj)Case"Worksheet":Setm_Worksheet=TableOrSheetNmOrObjCase"String":Setm_Worksheet=wb.Sheets(CStr(TableOrSheetNmOrObj))Case"ListObject"Setm_Worksheet=TableOrSheetNmOrObj.ParentHdrRowNb=TableOrSheetNmOrObj.HeaderRange.RowEndSelect'--- Worksheet not found, Critical Error Msg, Stop Processing ---
Ifm_WorksheetIsNothingThenMsgBox"Invalid Sheet - Ensure the sheet exists within the Workbook",vbCritical:EndEndIf'--- Header Row ---
m_HdrRowNb=HdrRowNbRefreshHdrRowEndFunctionPublicFunctionRefreshHdrRow()IfIsArray(m_HdrArr)ThenErasem_HdrArrm_HdrArr=HeaderRange.ValueEndFunction'====================================================================================================
'== COLUMNS =========================================================================================
'====================================================================================================
'----------------------------------------------------------------------------------------------------
' ColNbr - Search for and return a Column's number on the header row
' Params: ColNbrOrNameOrRange as Variant. A column number, name, or Range.
' Example: Dim iCol as Long: iCol = AWESh.ColNbr("Column Name")
' Note: a critical message displays if the column is not found and then processing stops.
'----------------------------------------------------------------------------------------------------
PublicFunctionColNbr(ColNbrOrNameOrRangeAsVariant)AsLongOnErrorResumeNext:ColNbr=-1IfIsNumeric(ColNbrOrNameOrRange)ThenColNbrOrNameOrRange=CLng(ColNbrOrNameOrRange)SelectCaseTypeName(ColNbrOrNameOrRange)Case"Integer","Long":ColNbr=CLng(ColNbrOrNameOrRange)Case"Range":ColNbr=HeaderRange.Columns(ColNbrOrNameOrRange.Column).ColumnCase"String":ColNbr=SearchArr(CStr(ColNbrOrNameOrRange),m_HdrArr)'--- Refresh header row if not found. Column could be added in processing ---
IfColNbr=-1ThenRefreshHdrRowColNbr=SearchArr(CStr(ColNbrOrNameOrRange),m_HdrArr)EndIfEndSelectIfColNbr=-1ThenMsgBox"ColNbr::ColNbrOrNameOrRange "&ColNbrOrNameOrRange&_" does not exist in the header row on sheet "&m_Worksheet.Name:EndEndIfEndFunction'----------------------------------------------------------------------------------------------------
' FirstCol - Return the header row's first column number
' Example: Dim iCol as Long: iCol = AWESh.FirstCol
'----------------------------------------------------------------------------------------------------
PublicPropertyGetFirstCol()AsLongFirstCol=1EndProperty'----------------------------------------------------------------------------------------------------
' LastCol - Return the header row's last column number
' Example: Dim iCol as Long: iCol = AWESh.LastCol
'----------------------------------------------------------------------------------------------------
PublicPropertyGetLastCol()AsLongLastCol=m_Worksheet.Cells(m_HdrRowNb,m_Worksheet.Columns.Count).End(xlToLeft).ColumnEndProperty'====================================================================================================
'== ROWS ============================================================================================
'====================================================================================================
'----------------------------------------------------------------------------------------------------
' FirstDataRow - Return the first data row's (the row under the header row) number.
' Example: Dim iRow as Long: iRow = AWESh.FirstDataRow
'----------------------------------------------------------------------------------------------------
PublicPropertyGetFirstDataRow()AsLongFirstDataRow=m_HdrRowNb+1EndProperty'----------------------------------------------------------------------------------------------------
' HdrRow - Return the header row that was specified in the Intialize Function
' Example: Dim iRow as Long: iRow = AWESh.HdrRow
'----------------------------------------------------------------------------------------------------
PublicPropertyGetHdrRow()AsLongHdrRow=m_HdrRowNbEndProperty'----------------------------------------------------------------------------------------------------
' LastDataRow - Return the last data row's number
' Example: Dim iRow as Long: iRow = AWESh.LastDataRow
'----------------------------------------------------------------------------------------------------
PublicPropertyGetLastDataRow()AsLongLastDataRow=m_Worksheet.Cells.Find("*",LookAt:=xlPart,LookIn:=xlFormulas,_SearchOrder:=xlByRows,SearchDirection:=xlPrevious).Row'--- Ensure that the HdrRow isn't returned in the range ---
IfLastDataRow<=m_HdrRowNbThenLastDataRow=m_Worksheet.UsedRange.Rows.CountIfLastDataRow<=m_HdrRowNbThenLastDataRow=m_HdrRowNb+1EndIfEndProperty'----------------------------------------------------------------------------------------------------
' RowNbr - Search for a Row by Row number or search value and if found, return the row number.
' Params: RowNbrOrSrchVal As Variant - A sheet's row number or value to find within the column.
' ColNbrOrName As Variant - A column number or name on the sheet.
' Optional SearchDirection As Long = xlNext - Search direction (xlNext or xlPrevious) to use.
' Example: Dim iRow as Long: iRow = AWESh.RowNb("SearchVal", "Column Name")
'----------------------------------------------------------------------------------------------------
PublicFunctionRowNbr(RowNbrOrSrchValAsVariant,ColNbrOrNameAsVariant,_OptionalSearchDirectionAsXlSearchDirection=xlNext)AsLongDimiColAsLong,rngAsRange,iRowAsLongIfIsNumeric(ColNbrOrName)TheniCol=CLng(ColNbrOrName)ElseiCol=ColNbr(ColNbrOrName)IfIsNumeric(RowNbrOrSrchVal)TheniRow=RowNbrOrSrchValElseSetrng=SearchFor(DataColumns(iCol),CStr(RowNbrOrSrchVal),SearchDirection)IfrngIsNothingThenRowNbr=-1ElseRowNbr=rng.RowEndIfEndFunction'====================================================================================================
'== RANGE ===========================================================================================
'====================================================================================================
'----------------------------------------------------------------------------------------------------
' Cell As Range - Search for and return cell's range; otherwise, return nothing
' Params: RowNbrOrSrchVal As Variant - A sheet's row number or value to find within the column.
' ColNbrOrName As Variant - A column number or name on the sheet.
' Optional SearchDirection As Long = xlNext - Search direction (xlNext or xlPrevious) to use.
' Example: Dim rng As Range: set rng = AWESh.Cell ("Search Value", "Column Name", xlNext)
'----------------------------------------------------------------------------------------------------
PublicFunctionCell(RowNbrOrSrchValAsVariant,ColNbrOrNameAsVariant,_OptionalSearchDirectionAsXlSearchDirection=xlNext)AsRangeDimiColAsLong,iRowAsLongIfIsNumeric(ColNbrOrName)TheniCol=CLng(ColNbrOrName)ElseiCol=ColNbr(ColNbrOrName)IfIsNumeric(RowNbrOrSrchVal)TheniRow=CLng(RowNbrOrSrchVal)_Else:iRow=RowNbr(RowNbrOrSrchVal,iCol,SearchDirection)IfiRow<>-1ThenSetCell=m_Worksheet.Cells(iRow,iCol)ElseSetCell=NothingEndFunction'----------------------------------------------------------------------------------------------------
' FindCells As Array - Search for and return cell ranges in an array; otherwise, return empty array
' Params: RowSrchVal As Variant - A sheet's row value to find within the column.
' ColNbrOrName As Variant - A column number or name on the sheet.
' Optional SearchDirection As Long = xlNext - Search direction (xlNext or xlPrevious) to use.
' Returns: Array of found cells. If no cells are found then ubound(array) will equal -1
' Example: Dim arr As Variant: arr = AWESh.FindCells ("Search Value", "Column Name", xlNext)
' or: Dim rng as Range
' For Each rng In AWESh.FindCells ("Search Value", "Column Name", xlNext)
' Debug.Print AWESh.Cell(rng.Row, "Column Name").Value
' Next rng
'----------------------------------------------------------------------------------------------------
PublicFunctionFindCells(RowSrchValAsVariant,ColNbrOrNameAsVariant,_OptionalSearchDirectionAsXlSearchDirection=xlNext)AsVariantDimiColAsLong,rColAsRange,rFirstAsRange,rCellAsRangeDimdictAsObject:Setdict=CreateObject("Scripting.Dictionary")IfIsNumeric(ColNbrOrName)TheniCol=CLng(ColNbrOrName)ElseiCol=ColNbr(ColNbrOrName)SetrCol=DataColumns(ColNbrOrName)SetrFirst=rCol.Find(What:=CStr(RowSrchVal),LookIn:=xlValues,SearchOrder:=xlByRows,_SearchDirection:=SearchDirection,LookAt:=xlWhole)SetrCell=rFirstWhileNotrCellIsNothingSetdict(rCell.Address)=rCellSetrCell=rCol.FindNext(rCell)'--- Exit loop if first cell reached again ---
IfrCell.Address=rFirst.AddressThenSetrCell=NothingWendFindCells=dict.ItemsEndFunction'----------------------------------------------------------------------------------------------------
' ColHdr - Find and return a column header's cell range.
' Params: ColNbrOrNameOrRange as Variant. A column number, name, or Range.
' Example: Dim rng As Range: set rng = AWESh.ColHdr "Column Name"
'----------------------------------------------------------------------------------------------------
PublicFunctionColHdr(ColNbrOrNameOrRangeAsVariant)AsRangeSetColHdr=m_Worksheet.Cells(m_HdrRowNb,ColNbr(ColNbrOrNameOrRange))EndFunction'----------------------------------------------------------------------------------------------------
' DataBodyRange - Return the range for all rows under the sheet header
' Example: Dim rng As Range: set rng = AWESh.DataBodyRange
'----------------------------------------------------------------------------------------------------
PublicPropertyGetDataBodyRange(OptionalIncludeHeaderAsBoolean=False)AsRangeDimiFirstRowAsLong:IfIncludeHeader=TrueTheniFirstRow=m_HdrRowNbElseiFirstRow=m_HdrRowNb+1Withm_WorksheetSetDataBodyRange=.Range(.Cells(iFirstRow,1),.Cells(LastDataRow,LastCol))EndWithEndProperty'----------------------------------------------------------------------------------------------------
' DataColumns - Return the range for one or more columns
' Params: ColNbrOrNameOrRanges as Variant. A list of column numbers, names, or Ranges.
' Example: Dim rng As Range: set rng = DataColumns("Column 1", "Column 5", "Column 6")
'----------------------------------------------------------------------------------------------------
PublicFunctionDataColumns(ParamArrayColNbrsOrNamesOrRanges()AsVariant)AsRangeDimRtnRngAsRange,varAsVariantForEachvarInColNbrsOrNamesOrRangesIfRtnRngIsNothingThenSetRtnRng=DataBodyRange.Columns(ColNbr(var))ElseSetRtnRng=Union(RtnRng,DataBodyRange.Columns(ColNbr(var)))EndIfNextvarSetDataColumns=RtnRngEndFunction'----------------------------------------------------------------------------------------------------
' HeaderRange - Return the range for the entire header row
' Example: Dim rng As Range: set rng = AWESh.HeaderRange
'----------------------------------------------------------------------------------------------------
PublicPropertyGetHeaderRange()AsRangeWithm_WorksheetSetHeaderRange=.Range(.Cells(m_HdrRowNb,1),.Cells(m_HdrRowNb,LastCol))EndWithEndProperty'----------------------------------------------------------------------------------------------------
' UsedRange - Return the used range for the entire sheet (more reliable the Worksheet.UsedRange)
' Example: Dim rng As Range: set rng = AWESh.UsedRange
'----------------------------------------------------------------------------------------------------
PublicPropertyGetUsedRange()AsRangeWithm_WorksheetSetUsedRange=.Range("A1",.Cells(LastDataRow,LastCol))EndWithEndProperty'----------------------------------------------------------------------------------------------------
' SearchFor - Searches through a range of data for a value and returns its cell.
' Params: SearchRange as Range - The range on the sheet to search through.
' SearchVal as String - The value to search for.
' Optional SearchDirection As Long = xlNext - Search direction (xlNext or xlPrevious) to use.
' Example: Dim rng As Range: set rng = AWESh.SearchFor (SrchRange, "Search Value", xlNext)
' Note: Use Excel's FindNext and FindPrevious to find the next/previous SearchVal in the column.
'----------------------------------------------------------------------------------------------------
PublicFunctionSearchFor(ByRefSearchRangeAsRange,SearchValAsString,_OptionalSearchDirectionAsXlSearchDirection=xlNext)AsRangeDimSrchOrdAsVariant,SrchAfterAsRangeIfSearchRange.Columns.Count>1ThenSrchOrd=xlByColumnsElseSrchOrd=xlByRowsOnErrorResumeNext:SetSearchFor=NothingWithSearchRangeIfSearchDirection=xlPreviousThenSetSearchFor=.Find(What:=SearchVal,LookIn:=xlValues,LookAt:=xlWhole,_SearchOrder:=SrchOrd,SearchDirection:=SearchDirection,MatchCase:=False)ElseSetSearchFor=.Find(What:=SearchVal,after:=.Cells(.Cells.Count),LookIn:=xlValues,_LookAt:=xlWhole,SearchOrder:=SrchOrd,SearchDirection:=SearchDirection,_MatchCase:=False)EndIfEndWithEndFunction'====================================================================================================
'== SHEET ===========================================================================================
'====================================================================================================
'----------------------------------------------------------------------------------------------------
' ScreenOff - Turns screen off.
' Example - AWESh.ScreenOff
'----------------------------------------------------------------------------------------------------
PublicFunctionScreenOff()WithApplication.ScreenUpdating=False:.EnableEvents=False:.Calculation=xlCalculationManual:.Cursor=xlWait:m_Timer=TimerEndWithEndFunction'----------------------------------------------------------------------------------------------------
' ScreenOn - Turns screen on and returns the process time (hh:mm:ss) from when ScreenOff was called.
' Example - Dim ProcessTime as string: ProcessTime = AWESh.ScreenOn
'----------------------------------------------------------------------------------------------------
PublicFunctionScreenOn()AsStringWithApplicationScreenOn=Format((Timer-m_Timer)/86400,"hh:mm:ss")&" seconds".ScreenUpdating=True:.EnableEvents=True:.Calculation=xlCalculationAutomatic:.Cursor=xlDefaultEndWithEndFunction'----------------------------------------------------------------------------------------------------
' Sheet - Returns the worksheet used by AWE_Sheet
' Examples - Dim sh as Worksheet: set sh = AWESh.Sheet
'----------------------------------------------------------------------------------------------------
PublicPropertyGetSheet()AsWorksheetSetSheet=m_WorksheetEndProperty'----------------------------------------------------------------------------------------------------
' Workbook - Returns the Workbook used by AWE_Sheet
' Examples - Dim wb as Workbook: set wb = AWESh.Workbook
'----------------------------------------------------------------------------------------------------
PublicPropertyGetWorkbook()AsWorkbookSetWorkbook=m_Worksheet.ParentEndProperty'----------------------------------------------------------------------------------------------------
' SortSheet - Sort the sheet by values in one or more columns.
' Parameter:
' ColIdxOrNames As Variant - Column numbers or names to sort by. Prepend "<" to reverse sort.
' Example: Sort Col1 ascending and Col2 decending - AWESh.Sort "Col1 Name", "<Col2 Name"
'----------------------------------------------------------------------------------------------------
PublicFunctionSortSheet(ParamArrayColIdxOrNames()AsVariant)DimColIdxOrNameAsVariant,ColIdxAsLongWithm_Worksheet.SortForEachColIdxOrNameInColIdxOrNamesIfLeft(ColIdxOrName,1)="<"ThenColIdxOrName=Trim(Right(ColIdxOrName,Len(ColIdxOrName)-1)).SortFields.Addkey:=DataColumns(ColNbr(ColIdxOrName)),Order:=xlDescendingElseIfLeft(ColIdxOrName,1)=">"Then_ColIdxOrName=Trim(Right(ColIdxOrName,Len(ColIdxOrName)-1)).SortFields.Addkey:=DataColumns(ColNbr(ColIdxOrName)),Order:=xlAscendingEndIfNext.SetRangeDataBodyRange:.Header=xlNo:.Apply:.SortFields.ClearEndWithEndFunction'====================================================================================================
'== FILTER ==========================================================================================
'====================================================================================================
'----------------------------------------------------------------------------------------------------
' ClearSheetFilters - Clears all Filters on the Sheet
' Parameter:
' Optional SheetPWD As String - Password to unprotect the sheet. Default = ""
' Example: AWESh.ClearSheetFilters
' Note: Displays a message and ends processing if the sheet is protected and the PWD is not provided.
'----------------------------------------------------------------------------------------------------
PublicFunctionClearSheetFilters(OptionalSheetPWDAsString="")DimrColAsRange,cntAsLong,dictAsObject'--- Unprotect the Sheet and get protection parameters ---
IfSheetPWD<>""ThenSetdict=UnprotectSheet(SheetPWD)'--- Verify that the sheet isn't protected ---
Ifm_Worksheet.ProtectContents=TrueThenMsgBox"Error: "&m_Worksheet.Name&" - Unprotect sheet before using VBA to clear filters.":EndEndIf'--- Perform test to ensure that the sheet has filter drop down buttons ---
OnErrorResumeNextcnt=m_Worksheet.AutoFilter.Range.Areas.CountIfErr.Number>0ThenHeaderRange.AutoFilterfield:=1,VisibleDropDown:=TrueEndIfOnErrorGoTo0'--- Remove all filters ---
IfNotm_Worksheet.AutoFilterIsNothingAndm_Worksheet.FilterMode=TrueThenm_Worksheet.ShowAllDataEndIf'--- Protect the sheet with previously retrieved sheet protection parameters ---
IfSheetPWD<>""ThenProtectSheetdictEndFunction'----------------------------------------------------------------------------------------------------
' IsFiltered - Returns True if there is a filter on the sheet.
' Example: Dim bFiltered as Boolean: bFiltered = AWESh.IsFiltered
'----------------------------------------------------------------------------------------------------
PublicPropertyGetIsFiltered()AsBooleanIsFiltered=FalseIfm_Worksheet.FilterModeThenIfm_Worksheet.AutoFilter.Range.Areas.Count>0ThenIsFiltered=TrueEndIfEndIfEndProperty'----------------------------------------------------------------------------------------------------
' IsRowVisible - Returns True if a role is visible; otherwise, False.
' Example: Dim bIsRowVisible as boolean: bIsRowVisible = AWESh.IsRowVisible(iRow)
'----------------------------------------------------------------------------------------------------
PublicPropertyGetIsRowVisible(iRowAsLong)AsBooleanIfm_Worksheet.Rows(iRow).Hidden=FalseThenIsRowVisible=TrueEndProperty'====================================================================================================
'== Protection ======================================================================================
'====================================================================================================
'----------------------------------------------------------------------------------------------------
' UnprotectSheet - Unprotects the sheet and return a dictionary (key pairs) of propection parameters.
' Parameter:
' Optional SheetPWD As String - Password to unprotect the sheet. Default = ""
' Example: Dim dict as Object: set dict = AWESh.UnprotectSheet ("Password")
'----------------------------------------------------------------------------------------------------
PublicFunctionUnprotectSheet(SheetPWDAsString)AsObjectDimpropAsString,arrAsVariant,keyAsVariant,dictAsObject:Setdict=CreateObject("Scripting.Dictionary")dict("SheetPWD")=SheetPWDForEachkeyInArray("ProtectDrawingObjects","ProtectContents","ProtectScenarios","ProtectionMode",_"Protection.AllowFormattingCells","Protection.AllowFormattingColumns","Protection.AllowFormattingRows",_"Protection.AllowInsertingColumns","Protection.AllowInsertingRows","Protection.AllowInsertingHyperlinks",_"Protection.AllowDeletingColumns","Protection.AllowDeletingRows",_"Protection.AllowSorting","Protection.AllowFiltering","Protection.AllowUsingPivotTables")arr=Split(key,".")SelectCasearr(0)Case"Protection":dict(key)=CallByName(m_Worksheet.Protection,arr(1),VbGet)CaseElse:dict(key)=CallByName(m_Worksheet,arr(0),VbGet)EndSelectNextkeyIfm_Worksheet.ProtectContents=TrueThenm_Worksheet.UnprotectSheetPWDSetUnprotectSheet=dictEndFunction'----------------------------------------------------------------------------------------------------
' ProtectSheet - Protects the sheet using the parameters retrieved in the call to UnprotectSheet.
' Parameter:
' dict as Object - Scripting.Dictionary (key pairs) that was created in the call to UnprotectSheet.
' Example: AWESh.ProtectSheet (dict)
'----------------------------------------------------------------------------------------------------
PublicFunctionProtectSheet(dictAsObject)Ifm_Worksheet.ProtectContents=FalseThenm_Worksheet.ProtectPassword:=dict("SheetPWD"),_DrawingObjects:=dict("ProtectDrawingObjects"),contents:=dict("ProtectContents"),Scenarios:=dict("protectScenarios"),userinterfaceonly:=dict("ProtectionMode"),_AllowFormattingCells:=dict("Protection.AllowFormattingCells"),AllowFormattingColumns:=dict("Protection.AllowFormattingColumns"),AllowFormattingRows:=dict("Protection.AllowFormattingRows"),_AllowInsertingColumns:=dict("Protection.AllowInsertingColumns"),AllowInsertingRows:=dict("Protection.AllowInsertingRows"),AllowInsertingHyperlinks:=dict("Protection.AllowInsertingHyperlinks"),_AllowDeletingColumns:=dict("Protection.AllowDeletingColumns"),AllowDeletingRows:=dict("Protection.AllowDeletingRows"),_AllowSorting:=dict("Protection.AllowSorting"),AllowFiltering:=dict("Protection.AllowFiltering"),AllowUsingPivotTables:=dict("Protection.AllowUsingPivotTables")EndIfEndFunction'====================================================================================================
'== Index ===========================================================================================
'====================================================================================================
'----------------------------------------------------------------------------------------------------
' SearchArr as long - Returns the array occurance where the search string was found; otherwise, -1
' Params: SrchStr as String - Value to search for
' Arr as Variant - An array of values
' Example: Dim filter as Boolean: filter = AWESh.IsFiltered
'----------------------------------------------------------------------------------------------------
PublicFunctionSearchArr(SrchStrAsString,arrAsVariant)AsLongOnErrorResumeNextSearchArr=-1:SearchArr=Application.Match(SrchStr,arr,0)EndFunction'----------------------------------------------------------------------------------------------------
' CreateRowKeys - Create a row key index based on one or more columns.
' Return: An Array of rows with combined column values.
' Params: ParamArray ColIdxOrNames - One or more column indexes or names to use in creating the index.
' Example: Dim arrRowKeys as variant: arrRowKeys = CreateRowIdx("Column1", "Column2")
'----------------------------------------------------------------------------------------------------
PublicFunctionCreateRowKeys(ParamArrayColIdxOrNames()AsVariant)AsVariantDimiArrAsLong,arrAsVariant,iColAsLong,iRowAsLong,ColIdxOrNameAsVariant'--- Reset arrays ---
ReDimm_RowKeyHdrArr(LBound(ColIdxOrNames)ToUBound(ColIdxOrNames))ReDimm_RowKeyDtaArr(1ToLastDataRow-HdrRow)'--- Iterate and retrieve and store column headers and data in arrays ---
ForiCol=LBound(ColIdxOrNames)ToUBound(ColIdxOrNames)ColIdxOrName=ColIdxOrNames(iCol)'--- Retrieve the colun name or index ---
m_RowKeyHdrArr(iCol)=ColIdxOrName'--- Save the column name or index ---
arr=DataColumns(ColIdxOrName).Value'--- Retrieve all data rows for the column ---
iRow=1'--- Reset the m_RowKeyDtaArr row counter ---
ForiArr=LBound(arr)ToUBound(arr)m_RowKeyDtaArr(iRow)=m_RowKeyDtaArr(iRow)&"["&Trim(arr(iArr,1))&"]"iRow=iRow+1'--- Increment the m_RowKeyDtaArr row counter ---
NextiArr'--- Next array row ---
NextiCol'--- Next column in the ColIdxOrNames ---
CreateRowKeys=m_RowKeyDtaArrEndFunction'----------------------------------------------------------------------------------------------------
' AppendRowKey - Add a row to the KeyArray and to the Worksheet
' Params: ParamArray ColValues - Column values to use in appending the KeyArray and Worksheet.
' Example: AppendRowKey("Column1Value", "Column2Value")
'----------------------------------------------------------------------------------------------------
PublicFunctionAppendRowKey(ParamArrayValues()AsVariant)AsLongDimvalAsVariant,sSrchStrAsString,iRowAsLong,iColAsLong,ColIdxOrNameAsVariantDimiAppendRowAsLong:iAppendRow=LastDataRow+1'--- Append new row values to the sheet and create row index key ---
ForiCol=LBound(Values)ToUBound(Values)val=Values(iCol)'--- Retrieve the value from ParamArray ---
ColIdxOrName=m_RowKeyHdrArr(iCol)'--- Retrieve the column header name or idx ---
Cell(iAppendRow,ColIdxOrName).Value=val'--- Append the value to the sheet ---
sSrchStr=sSrchStr&"["&Trim(val)&"]"'--- Create the new row index key ---
NextiCol'--- Append the row index key to the m_rowKeyDtaArr array ---
ReDimPreservem_RowKeyDtaArr(LBound(m_RowKeyDtaArr)ToUBound(m_RowKeyDtaArr)+1)m_RowKeyDtaArr(UBound(m_RowKeyDtaArr))=sSrchStrAppendRowKey=iAppendRowEndFunction'----------------------------------------------------------------------------------------------------
' UpdateRowKey - Update a row in the KeyArray and the Worksheet
' Params: RowNbr to update, ParamArray ColIdxOrNames - One or more column indexes or names to use in creating the index.
' Example: UpdateRowKey(RowNbr, "Column1Value", "Column2Value")
'----------------------------------------------------------------------------------------------------
PublicFunctionUpdateRowKey(RowNbrAsLong,ParamArrayValues()AsVariant)DimvalAsVariant,sSrchStrAsString,iRowAsLong,iColAsLong,ColIdxOrNameAsVariant'--- Update row values on the Worksheet and row key array ---
ForiCol=LBound(Values)ToUBound(Values)val=Values(iCol)'--- Retrieve the value from ParamArray ---
ColIdxOrName=m_RowKeyHdrArr(iCol)'--- Retrieve the column header name or idx ---
Cell(RowNbr,ColIdxOrName).Value=val'--- Append the value to the sheet ---
sSrchStr=sSrchStr&"["&Trim(val)&"]"'--- Create the new row index key ---
NextiCol'--- Update the row index key to the m_rowKeyDtaArr array ---
m_RowKeyDtaArr(RowNbr-m_HdrRowNb)=sSrchStrEndFunction'----------------------------------------------------------------------------------------------------
' FindRowKey - Find a row in the KeyArray
' Returns: corresponding row in the spreadsheet
' Params: ParamArray ColIdxOrNames - One or more column indexes or names to use in creating the index.
' Example: CreateRowIdx("Column1", "Column2")
'----------------------------------------------------------------------------------------------------
PublicFunctionFindRowKey(ParamArrayValues()AsVariant)AsLongDimvalAsVariant,sSrchStrAsStringForEachvalInValuessSrchStr=sSrchStr&"["&Trim(val)&"]"NextvalFindRowKey=SearchArr(sSrchStr,m_RowKeyDtaArr)IfFindRowKey>0ThenFindRowKey=FindRowKey+m_HdrRowNbEndFunction
AWE_Sheet Examples
Finding, retrieving and updating cells typically require “Named Ranges” a “Table” or a dozen lines of code. AWE_Sheet only requires only a few lines of code, with or without using “Named Ranges” or Tables!
The below examples provide 3 methods for finding, retrieving and updating data on a Worksheet, Filters, Find, and a Loop. The best part, AWE_Sheet simplifies worksheet functionality. It does not change how you work with the sheet or ranges so you still have access to all of the power native Excel VBA has to offer.
Search Using a Filter - Create a filter to find multiple values in a column. Then loop through visible rows to retrieve and update cells.
Pros: Fast performance with the ability to create complex search criteria (see Microsoft AutoFilter Documentation). Cons: Filters can be a little complex to implement but extremely powerful once the learning curve is mastered.
PublicFunctionSearchUsingFilter()DimrngAsRange,aweShAsNewAWE_Sheet:aweSh.InitializeActiveSheet,3aweSh.ClearSheetFilters'--- Create Filter using an Array ---
aweSh.ColHdr("TaskID").AutoFilterfield:=aweSh.ColNbr("TaskID"),_Criteria1:=Array("Task-127","Task-145"),Operator:=xlFilterValues'--- Add a From and To Date to the Filter ---
aweSh.ColHdr("Date").AutoFilterfield:=aweSh.ColNbr("Date"),Operator:=xlAnd,_Criteria1:=">="&CDate("1/1/2021"),Criteria2:="<="&CDate("12/31/2021")'--- Loop through each found row and calculate\update its revenue ---
ForEachrngInaweSh.DataColumns("Rate").SpecialCells(xlCellTypeVisible)aweSh.Cell(rng.Row,"Rate").Value=227.5aweSh.Cell(rng.Row,"Revenue").Value=227.5*aweSh.Cell(rng.Row,"Hours").Value'--- Add an astrict (*) next to the name to show that the value changed ---
aweSh.Cell(rng.Row,"Employee Name").Value="*"&aweSh.Cell(rng.Row,"Employee Name").ValueNextrng'--- Print the sum in the immediate window and verify ---
Debug.Print"Verify filter displays Task-127 & Task-145 and that their Revenue sum equals: "&_WorksheetFunction.Sum(aweSh.DataColumns("Revenue").SpecialCells(xlCellTypeVisible))EndFunction
Search Using Find - Behind the scenes, AWE_Sheet.FindCells uses Excel's FindNext and FindPrevious to create and returns an array of found cells or ranges. Simply loop through the array to retrieve and update cells.
Pros: Fast performance, easy to implement. Cons: Lacks the ability to create complex searches
PublicFunctionSearchUsingFind()DimvTaskIDasVariant,rngAsVariant,AWEshAsNewAWE_Sheet:AWEsh.InitializeActiveSheet,3'--- loop through rows that were returned from FindCells ---
ForEachvTaskIDInArray("Task-127","Task-145")ForEachrngInAWEsh.FindCells(CStr(vTaskID),"TaskID")IfNotrngIsNothingThenAWEsh.Cell(rng.Row,"Rate").Value=227.5AWEsh.Cell(rng.Row,"Revenue").Value=227.5*AWEsh.Cell(rng.Row,"Hours").ValueAWEsh.Cell(rng.Row,"Employee Name")="*"&AWEsh.Cell(rng.Row,"Employee Name")EndIfNextrngNextvTaskIDEndFunction
Note: Use the astrisk symbol(*) as a wild card when searching for text. The asterisk represents one or more characters. For example "Mi*" will find ranges beginning with "Mi". "*ik* will find ranges containing "ik", "*ke" will find ranges ending with "ke". All of these searches will find ranges with the name, "Mike". As such, AWEsh.FindCells("Task-14*", "TaskID") will return all cells in the "TaskID" column that start with "Task-14".
Search Using a Multi-Column Key - Create a multi-column key to find data based on values in multiple columns
Pros: Fast performance even when searching for data in multiple columns. Ability to add data to the key.
Cons: Lacks the ability to search based on complex criteria such as looping through rows or using filters. Unique keys are required to a specific row. There is no next/previous features but you can iterate after the first found row to other rows if your data is sorted correctly.
PublicFunctionSearchUsingMultiColKey()DimaweShAsNewAWE_Sheet:aweSh.InitializeTableOrSheetNmOrObj:=ActiveSheet,HdrRowNb:=3'--- Create & searchfor, multi-column, row keys ---
aweSh.CreateRowKeys"Employee Name","Date"Debug.Print"Name-0001 1/7/2021 Row:"&aweSh.FindRowKey("Name-0001",CDate("1/7/2021"))'--- If the key isn't found then append it to the end of the sheet and the RowKeys array ---
IfaweSh.FindRowKey("Name-0001",CDate("1/1/2022"))=-1Then_aweSh.AppendRowKey"Name-0001",CDate("1/1/2022")Debug.Print"Appended Name-0001 1/1/2022 Row:"&aweSh.FindRowKey("Name-0001",CDate("1/1/2022"))EndFunction
Search Looping Through Rows - Loop through all rows on the sheet looking for and updating values.
PublicFunctionSearchUsingLoop()DimsTaskIDAsString,rngAsRange,AWEshAsNewAWE_Sheet:AWEsh.InitializeActiveSheet,3'--- Loop through all rows ---"
ForEachrngInAWEsh.DataBodyRange().RowssTaskID=AWEsh.Cell(rng.Row,"TaskID")IfsTaskID="Task-127"OrsTaskID="Task-145"ThenAWEsh.Cell(rng.Row,"Rate").Value=227.5AWEsh.Cell(rng.Row,"Revenue").Value=227.5*AWEsh.Cell(rng.Row,"Hours").ValueAWEsh.Cell(rng.Row,"Employee Name")="*"&AWEsh.Cell(rng.Row,"Employee Name")EndIfNextrngEndFunction
Open, Read, and Updating Shared and Web Enabled (Browser/Teams) Workbooks - If you work in an enterprise environment, you will undoubtedly need to automate a shared workbook located in Teams, SharePoint... Columns and Rows will be protected, filtered...
Before you start automating, realize the limitations of Excel Macros in Shared Workbooks and that Excel Macros can NOT be run from a web enabled Workbook. There are workarounds, a macro can run from an Excel Workbook opened as a Desktop App even if the Workbook is shared or web enabled by others.
Two primary work around options for automating shared workbooks:
Open a Shared Workbook in ReadOnly Mode and copy the desired sheet(s) to a macro enabled, non-shared workbook. Then perform your queries.
Pros: Allows the Shared Workbook to be non-Macro Enabled. Remove shared workbook limitations. Manipulate unprotected sheets...
Cons: Unable to update the shared Workbook.
Open a the workbook as a Desktop App and work within the limitations of a Shared Workbook. My preference is to place macros in an entirely different Workbook (xlxm, Macro-enabled Workbook) altogether that then update the Shared Workbook (xlsx, non-Macro-enabled Workbook).
Pros: Update the Workbook while it is being shared by others
Cons: You are constricted to Shared Workbook limitations. Additionally you may need to quickly unprotect the sheet to access protected ranges, change the filter...
The below Code provides a brief implementation of these options.
FunctionUpdateSharedWorkbooks()ConstSharePointFileNmAsString=https://YourDomain/Sites/SiteName/.../...DimshRemoteAsNewAWE_Sheet,shLocalAsNewAWE_Sheet,oProtectAsObject'-------------------------------------------------------------------------------------------------
' Option 1 - Copy a remote Workbook's sheet to a local Workbook. Close the remote Workbook. ---
' Then manipulate the local Workbook. ---
'-------------------------------------------------------------------------------------------------
'--- Open the remote workbook ---
shRemote.Initialize"SheetName",3,SharePointFileNm,True'--- Turn off error handling and delete the local sheet if it exists ---
OnErrorResumeNextIfNotThisWorkbook.Sheets("SheetName")IsNothingThenThisWorkbook.Sheets("SheetName").DeleteOnErrorGoTo0'--- Copy the remote worksheet to the local workbook ---
shRemote.Sheet.Copyafter:=ThisWorkbook.Sheets(ThisWorkbook.Sheets.Count)'--- Close the remote workbook ---
shRemote.Workbook.Close'--- Initialize the worksheet, unprotect it, clear and set filters... ---
shLocal.Initialize"SheetName",3shLocal.UnprotectSheet"1234"shLocal.ClearSheetFilters'--- Manipulate data here. Use any of the above examples ---
'----------------------------------------------------------------------------------
' Option 2 - Open the remote sheet, query and directly update ranges or cells ---
'----------------------------------------------------------------------------------
'--- Open the remote workbook ---
shRemote.Initialize"SheetName",3,SharePointFileNm,True'--- Capture protection parameters and unprotect the sheet ---
SetoProtect=shRemote.UnprotectSheet("password")'--- Quickly perform queries and updates here (use any of the above examples) ---
shRemote.ClearSheetFilters' and so on...
'--- Re-protect the workbook ---
shRemote.ProtectSheet(oProtect)'--- If the workbook is being shared, then accept all changes on the sheet ---
IfshRemote.Workbook.MultiUserEditing=TrueThenshRemote.Workbook.AcceptAllChangeswhere:=shRemote.Sheet.nameEndIf'--- Turn off file prompts, save, and close the workbook ---
Application.DisplayAlerts=FalseshRemote.Workbook.SaveshRemote.Workbook.CloseApplication.DisplayAlerts=TrueEndFunction
Note, there are two workarounds options for updating protected Workbooks:
Unprotect and update the Workbook when everyone has left for the day. This is good for long end of day processing.
Open the Shared Workbook, unprotect it, quickly make your changes, re-protect the Shared workbook. This works only for quick processing.
Create Dummy Data - Create thousands of rows of dummy data to test with. Steps: 1) Implement the AWE_Sheet class in your workbook (see the below section called Implementation). 2) Create a worksheet called "Timecard" that has the following column header names on row three: Employee Name, Date, Week, TaskID, Task Desc, Hours, Rate, Revenue, and Email Address. 3) Finally, copy, paste and run the below code along with any of the above examples in your worksheet's VBA editor.
Note: AWE_Array provides an alternate way to create upto 100,000 rows of dummy data in under 3 seconds.
FunctionCreate100KRows()DimdtAsDate,iNmAsLong,sNmAsString,iRndmAsLong,iHrAsLong,iTtlHrsAsLongDimiRowAsLong,iHdrRowAsLong,iMaxRowsAsLong:iHdrRow=3:iMaxRows=100000DimwsAsWorksheet:Setws=ThisWorkbook.Sheets("Timecard")'--- Turn ScreenUpdating Off, Clear filters, delete used rows, create range ---
Application.ScreenUpdating=FalseIfws.FilterMode=TrueThenws.ShowAllDataws.Range(ws.Cells(iHdrRow+1,1),ws.Cells(ws.Cells(ws.Rows.Count,1).End(xlUp).Row+1,1)).EntireRow.DeleteDimrngAsRange:Setrng=ws.Range(ws.Cells(iHdrRow+1,1),ws.Cells(iHdrRow+1+iMaxRows,9))'--- Create AWE_Array Object ---
DimAWEArrAsNewAWE_Array:AWEArr.Initializerng:=rng,HdrRowNb:=iHdrRow'--- Iterate through all rows, populating names, dates, tasks... with ficticious data ---
DimiTmrAsSingle:iTmr=TimeriRow=AWEArr.FirstDataRow:DoWhileiRow<=iMaxRowsiNm=iNm+1:sNm="Name-"&Format(CStr(iNm),"0000")'--- Add weekdays for an entire year ---
Fordt=CDate("1/1/2021")ToCDate("12/31/2021")IfWeekday(dt,vbMonday)>5TheniTtlHrs=0DoWhileiTtlHrs<8'--- Update Employee Name thru Week Columns ---
AWEArr.Cell(iRow,Cols.EmpNm)=sNmAWEArr.Cell(iRow,Cols.EmailAddr)=sNm&"@Domain.com"AWEArr.Cell(iRow,Cols.dates)=dtAWEArr.Cell(iRow,Cols.Week)=WorksheetFunction.WeekNum(dt)'--- Create a random number from 1 to 100 for the TaskID, Task Desc, and Rate ---
iRndm=Int((100*Rnd)+1)+100AWEArr.Cell(iRow,Cols.TaskID)="Task-"&iRndmAWEArr.Cell(iRow,Cols.TaskDesc)="Desc for Task-"&iRndmAWEArr.Cell(iRow,Cols.Rate)=iRndm'--- Create random hours from 1 to 8. Ensure only 8 hours are worked in a day ---
iHr=Int((8*Rnd)+1):IfiTtlHrs+iHr>8TheniHr=8-iTtlHrsiTtlHrs=iTtlHrs+iHrAWEArr.Cell(iRow,Cols.hours)=iHr'--- Update Revenue ---
AWEArr.Cell(iRow,Cols.Revenue)=iHr*iRndm'--- Increment iRow and display progress on the StatusBar every 1000 rows---
iRow=iRow+1:IfiRow>(iMaxRows+1)ThenGoToExit_Create100KRowsIfiRowMod10000=0ThenApplication.StatusBar=Int((iRow/iMaxRows)*100)&"% Complete"EndIfLoopEndIfNextdtLoopExit_Create100KRows:'--- Write the array to the sheet ---
AWEArr.WriteArrayApplication.ScreenUpdating=TrueMsgBox"Created "&iMaxRows&" rows in "&_Format((Timer-iTmr)/86400,"hh:mm:ss")&" seconds."EndFunction
Add comment
Comments