Fast method for determining unlocked cell range - excel

A common request in the online forums is for code to identify the unlocked cells within a sheet.
The standard solutions use a loop to iterate through each cell in the used portion of the active worksheet, testing each cell determine if it is locked or not. A code sample for this approach is listed below.
Given the inherent poor performance in looping through cell ranges what superior approaches are possible?
(NB: I do intend to add my own existing approach which was previously hosted on another forum as a potential approach - but I will accept another [suitable] method as the answer if it is provided)
Range Approach to identify unlocked cells
Sub SelectUnlockedCells()
`http://www.extendoffice.com/documents/excel/1053-excel-identify-select-locked-cells.html
Dim WorkRange As Range
Dim FoundCells As Range
Dim Cell As Range
On Error GoTo SelectUnlockedCells_Error
Set WorkRange = ActiveSheet.UsedRange
For Each Cell In WorkRange
If Cell.Locked = False Then
If FoundCells Is Nothing Then
Set FoundCells = Cell
Else
Set FoundCells = Union(FoundCells, Cell)
End If
End If
Next Cell
If FoundCells Is Nothing Then
MsgBox "All cells are locked."
Else
FoundCells.Select
End If
On Error GoTo 0
Exit Sub
SelectUnlockedCells_Error:
MsgBox "Error " & Err.Number & " (" & Err.Description & ") in procedure
SelectUnlockedCells of Module Module1"
End Sub

Using SpecialCells to quickly identify unlocked cells
The code below - QuickUnlocked - uses a workaround to quickly generate a SpecialCells collection of error cells to identify the unlocked cell range.
The key code steps are:
Alter the Application to suppress errors, code and screenupdating
Attempt to unlock the ActiveWorkbook and/or the ActiveSheet if they are protected. Exit the code if unsuccessful
Make a replica of the current sheet
Delete any existing formula errors in the replica using SpecialCells
Protect the replica worksheet and with the coverage of error handling, add a deliberate formula error that will only populate the unlocked cells
Clean up and report the results Reset the Application settings
Warning that SpecialCells is restricted to 8192 Areas prior to Xl2010
As per this Microsoft KB article, Excel-2007 and earlier versions supports up to a maximum of 8,192 non-contiguous cells through VBA macros. Rather surprisingly, applying a VBA macro to more than 8192 SpecialCells Areas in these Excel versions, will not raise an error message, and the entire area under consideration will be treated as being part of theSpecialCells` range collection.
Quick Unlocked code
Sub QuickUnlocked()
Dim ws1 As Worksheet
Dim ws2 As Worksheet
Dim rng1 As Range
Dim rng2 As Range
Dim rng3 As Range
Dim lCalc As Long
Dim bWorkbookProtected As Boolean
On Error Resume Next
'test to see if WorkBook structure is protected
'if so try to unlock it
If ActiveWorkbook.ProtectStructure Then
ActiveWorkbook.Unprotect
If ActiveWorkbook.ProtectStructure Then
MsgBox "Sorry, I could not remove the passsword protection from the workbook" _
& vbNewLine & "Please remove it before running the code again", vbCritical
Exit Sub
Else
bWorkbookProtected = True
End If
End If
Set ws1 = ActiveSheet
'test to see if current sheet is protected
'if so try to unlock it
If ws1.ProtectContents Then
ws1.Unprotect
If ws1.ProtectContents Then
MsgBox "Sorry, I could not remove the passsword protection from sheet" & vbNewLine & ws1.Name _
& vbNewLine & "Please remove it before running the code again", vbCritical
Exit Sub
End If
End If
On Error GoTo 0
'disable screenupdating, event code and warning messages.
'set calculation to manual
With Application
.ScreenUpdating = False
.EnableEvents = False
.DisplayAlerts = False
lCalc = .Calculation
.Calculation = xlCalculationManual
End With
On Error Resume Next
'check for existing error cells
Set rng1 = ws1.Cells.SpecialCells(xlCellTypeFormulas, 16)
On Error GoTo 0
'copy the activesheet to a new working sheet
ws1.Copy After:=Sheets(Sheets.Count)
Set ws2 = ActiveSheet
'delete any cells that already contain errors
If Not rng1 Is Nothing Then ws2.Range(rng1.Address).ClearContents
'protect the new sheet
ws2.Protect
'add an error formula to all unlocked cells in the used range
'then use SpecialCells to read the unlocked range address
On Error Resume Next
ws2.UsedRange.Formula = "=NA()"
ws2.Unprotect
Set rng2 = ws2.Cells.SpecialCells(xlCellTypeFormulas, 16)
Set rng3 = ws1.Range(rng2.Address)
ws2.Delete
On Error GoTo 0
'if WorkBook level protection was removed then reinstall it
If bWorkbookProtected Then ActiveWorkbook.Protect
'cleanup user interface and settings
With Application
.ScreenUpdating = True
.EnableEvents = True
.DisplayAlerts = True
lCalc = .Calculation
End With
'inform the user of the unlocked cell range
If Not rng3 Is Nothing Then
MsgBox "The unlocked cell range in Sheet " & vbNewLine & ws1.Name & " is " & vbNewLine & rng3.Address(0, 0)
Else
MsgBox "No unlocked cells exist in " & ws1.Name
End If
End Sub

Well, I've gone back to a loop, but I think this method is efficient because it only references those cells which are Unlocked (without selecting) using Next:
If the object is a range, this property emulates the TAB key, although
the property returns the next cell without selecting it.
On a protected sheet, this property returns the next unlocked cell. On
an unprotected sheet, this property always returns the cell
immediately to the right of the specified cell.
It stores the first (Next) Range.Address, loops through the others until it returns to this first one.
Sub GetUnlockedCells_Next()
Dim ws As Worksheet
Dim strFirst As String
Dim rngNext As Range
Dim strLocked As String
Set ws = Worksheets(1)
ws.Protect
Set rngNext = ws.Range("A1").Next
strFirst = rngNext.Address
Do
strLocked = strLocked & rngNext.Address & ","
Set rngNext = rngNext.Next
Loop Until rngNext.Address = strFirst
strLocked = Left(strLocked, Len(strLocked) - 1) 'remove the spare comma
ws.Range(strLocked).Select
ws.Unprotect
MsgBox strLocked
End Sub

Use Conditional Formatting with:- Use a formula to determine which cells to format, Format values where this formula is true: =CELL("protect",A1)=0 and Format of choice applied to occupied range?

I was looking for a way to clear the contents of my unlocked cells. The problem was that my sheet has hundreds, if not thousands, of unlocked cells and twice as many locked ones. Iterating through them was taking about 5-7 seconds and I wanted something more efficient.
brettdj's solution got me half way there, but having so many cells in my range broke the algorithm.
The line
Set rng3 = ws1.Range(rng2.Address)
Was not working because rng2's address was over the 256 character limit, so rng3 became "nothing".
I spent hours trying to work around the 256 limit but got nowhere. After almost giving up, I stumbled upon the "areas" object of a range. Life saver!
The adjusted code below works with sheets that have several unlocked cells. Thanks to brettdj for the original idea.
' Sub to clear unlocked cells.
Sub clearUnlockedCells()
On Error Resume Next
' If the Workbook is protected, unlock it.
Dim workbook_protected As Boolean
If ActiveWorkbook.ProtectStructure Then
workbook_protected = True
ActiveWorkbook.Unprotect
' If we failed to unlock the Workbook, error out and exit.
If ActiveWorkbook.ProtectStructure Then
MsgBox "Sorry, I could not remove the passsword protection from the workbook" _
& vbNewLine & "Please remove it before running the code again", vbCritical
Exit Sub
End If
End If
Dim source_sheet As Worksheet
Set source_sheet = ActiveSheet
' If the Worksheet is protected, unlock it.
Dim worksheet_protected As Boolean
If source_sheet.ProtectContents Then
worksheet_protected = True
source_sheet.Unprotect
' If we failed to unlock the Worksheet, error out and exit.
If source_sheet.ProtectContents Then
MsgBox "Sorry, I could not remove the passsword protection from sheet" & vbNewLine & source_sheet.name _
& vbNewLine & "Please remove it before running the code again", vbCritical
Exit Sub
End If
End If
On Error GoTo 0
' Disable screenupdating, event code and warning messages.
' Store the calculation and set it to manual.
Dim calc As Long
With Application
.ScreenUpdating = False
.EnableEvents = False
.DisplayAlerts = False
calc = .Calculation
.Calculation = xlCalculationManual
End With
On Error Resume Next
' Check for existing error cells.
Dim tmp_rng As Range
Set tmp_rng = source_sheet.Cells.SpecialCells(xlCellTypeFormulas, 16)
On Error GoTo 0
' Copy the ActiveSheet to a new working sheet.
source_sheet.Copy After:=Sheets(Sheets.Count)
Dim tmp_sheet As Worksheet
Set tmp_sheet = ActiveSheet
' Delete any cells that already contain errors.
If Not tmp_rng Is Nothing Then tmp_sheet.Range(tmp_rng.Address).ClearContents
' Protect the new sheet and add an error formula to all unlocked cells in the
' used range, then use SpecialCells to read the unlocked range address.
tmp_sheet.Protect
On Error Resume Next
tmp_sheet.UsedRange.Formula = "=NA()"
tmp_sheet.Unprotect
' Get the range of cells with "=NA()" in them.
Set tmp_rng = tmp_sheet.Cells.SpecialCells(xlCellTypeFormulas, 16)
' Iterate through the range and create a mirror of that range in the source sheet.
Dim area As Range
Dim source_sheet_range As Range
Dim unlocked_cells As Range
For Each area In tmp_rng.Areas
Set source_sheet_range = source_sheet.Range(area.Address)
If unlocked_cells Is Nothing Then
Set unlocked_cells = source_sheet_range
Else
Set unlocked_cells = Union(unlocked_cells, source_sheet_range)
End If
Next area
' Delete the temp sheet.
tmp_sheet.Delete
On Error GoTo 0
' Protect the Workbook and Worksheet as necessary.
If workbook_protected Then ActiveWorkbook.Protect
If worksheet_protected Then source_sheet.Protect
' Cleanup user interface and settings.
With Application
.ScreenUpdating = True
.EnableEvents = True
.DisplayAlerts = True
.Calculation = calc
End With
' Clean up the unlocked cells.
unlocked_cells.ClearContents
End Sub
Hope that helps someone else. If you just want to select them instead of clearing them, then change the second to last line from .ClearContents to .Select.

Here's a general solution that is much faster than looping through ranges of cells and is much simpler, more straightforward, than cloning temporary worksheets, etc. It is relatively fast because it takes advantage of the high-speed compiled code in which Excel VBA's Find method is implemented.
Function GetUnlockedCells(SearchRange As Range) As Range 'Union
'
'Finds all unlocked cells in the specified range and returns a range-union of them.
'
'AUTHOR: Peter Straton
'
'*************************************************************************************************************
Dim FoundCell As Range
Dim FirstCellAddr As String
Dim UnlockedUnion As Range
'NOTE: When finding by format, you must first set the FindFormat specification:
With Application.FindFormat
.Clear
.Locked = False 'This is the key to this technique
End With
'NOTE: Unfortunately, the FindNext method does not remember the SearchFormat:=True specification so it is
'necessary to capture the address of the first cell found, use the Find method (instead) inside the find-next
'loop and explicitly terminate the loop when the first-found cell is found a second time.
With SearchRange
Set FoundCell = .Find(What:="", After:=.Cells(1, 1), LookIn:=xlFormulas, LookAt:=xlPart, _
SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:=False, _
SearchFormat:=True)
If Not FoundCell Is Nothing Then
FirstCellAddr = FoundCell.Address
Do
' Debug.Print FoundCell.Address
If UnlockedUnion Is Nothing Then
Set UnlockedUnion = FoundCell.MergeArea 'Include merged cells, if any
Else
Set UnlockedUnion = Union(UnlockedUnion, FoundCell.MergeArea) ' "
End If
Set FoundCell = .Find(What:="", After:=FoundCell, SearchDirection:=xlNext, SearchFormat:=True)
Loop Until FoundCell.Address = FirstCellAddr
End If
End With
Application.FindFormat.Clear 'Cleanup
Set GetUnlockedCells = UnlockedUnion
End Function 'GetUnlockedCells

I was exploring this but I've come full-circle to, more or less, Brett's approach. The slight difference is that I use the current worksheet rather than creating a new one. I'm also initially assuming that there are no errors in the worksheet. (Code could be added similar to Brett's to account for these.)
I wanted to flood the UsedRange with "#N/A", ignore errors, and use Application.Undo to quickly get back. Unfortunately, I couldn't use Undo (unlike in Word). So I resorted to using a Variant to grab the whole area's data, and then re-insert it.
Sub GetUnlockedCells()
Dim ws As Worksheet
Dim rngUsed As Range
Dim varKeep As Variant
Application.ScreenUpdating = False
Set ws = Worksheets(1)
ws.Protect
Set rngUsed = ws.UsedRange
varKeep = rngUsed.Value
On Error Resume Next
rngUsed.Value = "#N/A"
On Error GoTo 0
ws.Unprotect
MsgBox "Unlocked cells are " & _
rngUsed.SpecialCells(xlCellTypeConstants, xlErrors).Address
rngUsed.Value = varKeep
Application.ScreenUpdating = True
End Sub
So, unfortunately, I haven't advanced much beyond Brett's cool code. Maybe it will inspire someone else, or someone might discover a way to use Undo ;)
I'm also losing formulas as well (converted to values) so some work required!

If there are lots of formulas, general approach is
For each row in ...
lockedR = row.locked
for each cell in row
if isnull(lockedR) then ' inconsistent in row
locked = cell.locked
else
locked = lockedR ' consistent from row, no need to get it.
This pattern works fine for many properties such as HasArray. But just for Locked it is grossly (100 times) slower. Don't know why so inefficient.
Goto Special would be a cute trick, but there isn't one for locked cells.
A good solution would be wonderful but I suspect impossible.

Related

Returning value of next visible cell from structured table in a different sheet

On the sheet named "Data" I have an Excel Table. This table has a variable number of rows, typically 20k to 30k. Column A is "JobNo"
On the sheet named "Main" I have cell where I show the "JobNo". That value starts as the first visible JobNo from the filtered table.
I have buttons for "Next Record". When I click this button and run it's associated VBA code, I need that code to move the "Data" sheet's cell pointer to the next visible (filtered) value in column A.
I've tried several samples of code found here to find the first visible cell, and to move to the next visible cell, but most of them relied on "Activecell". I need to move a "virtual" pointer to the next visible cell because that sheet, where the table is located is not visible and so the ActiveCell is not there.
This for example works to move the cell pointer to the next visible cell, but it only works if "Dat" sheet is selected:
Sub movetest()
Sheets("Data").Range("A1").EntireColumn.SpecialCells(xlCellTypeVisible).Find(What:="*", After:=ActiveCell).Activate
End Sub
What I need is something that can do what the above line does, but do it to a sheet that is not selected. Bonus to me if it was in structured table syntax.
I also tried to use some variant of this, which moves to the first visible cell, but only when the "Data" sheet is selected:
Range("Data[[#All],[PACEJob]]").SpecialCells(xlCellTypeVisible).Find _
(What:="*", After:=ActiveSheet.Range("Data[[#Headers],[PACEJob]]"), _
LookIn:=xlFormulas, lookat:=xlPart, searchorder:=xlByColumns, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=False).Activate
EDIT:
This does what I need for non filtered table. Just need to replicate this to do the same thing with a filtered table and only show visible.
(GLobal selectedRow)
selectedJobRow = selectedJobRow + 1
Sheets("Main").Range("O2").Value = Sheets("Data").Range("A" & selectedJobRow).Value
I gave up trying to work around the sheet not being active and Activecell. This seems to work, although it seems like there would be a more elegant way, No?
Sub movePointerDown()
Application.ScreenUpdating = False
Set wksToCheck = Sheets("Data")
Sheets("data").Select
Sheets("Data").Range("A1").EntireColumn.SpecialCells(xlCellTypeVisible).Find(What:="*", After:=ActiveCell).Activate
Sheets("Main").Range("O2").Value = ActiveCell.Value
Sheets("Main").Select
Application.ScreenUpdating = False
End Sub
And its companion:
Sub movePointerUp()
Application.ScreenUpdating = False
Set wksToCheck = Sheets("Data")
Sheets("data").Select
Sheets("Data").Range("A1").EntireColumn.SpecialCells(xlCellTypeVisible).Find(What:="*", After:=ActiveCell, Searchdirection:=xlPrevious).Activate
Sheets("Main").Range("O2").Value = ActiveCell.Value
Sheets("Main").Select
Application.ScreenUpdating = False
End Sub
Don't need to work with ActiveCell if you use Excel objects, there is plenty of information about the subject on the internet.
This proposed solution returns the next Cell record, and it's wrapped in a Function
to allow for flexibility. It uses a Static variable to keep track of actual record (see link provided for details) and validates the ListObject (excel Table) field, its AutoFilter and whether the actual record is the last visible record.
Function ListObject_ƒNextVisibleCell(rOutput As Range, sMsgOut As String, sFld As String) As Boolean
Static rCll As Range
Const kMsg1 As String = "Field [ #FLD ] not found."
Const kMsg2 As String = "ListObject filter returned zero records"
Const kMsg3 As String = "Actual record is the last visible record"
Dim wsDATA As Worksheet
Dim lo As ListObject
Dim rTrg As Range
Dim rCllLast As Range
Set wsDATA = ThisWorkbook.Worksheets("DATA")
Set lo = wsDATA.ListObjects("lo.DATA") 'update as required
With lo
On Error Resume Next
Rem Validate Field
Set rTrg = .ListColumns(sFld).DataBodyRange
If rTrg Is Nothing Then
sMsgOut = Replace(kMsg1, "#FLD", sFld)
Exit Function
End If
Rem Validate ListObject AutoFilter
Set rTrg = Nothing
Set rTrg = .ListColumns(sFld).DataBodyRange.SpecialCells(xlCellTypeVisible)
If rTrg Is Nothing Then sMsgOut = kMsg2: Exit Function
On Error GoTo 0
End With
Select Case (rCll Is Nothing)
Case True
Rem No Previous Record
Set rCll = rTrg.Cells(1)
Case False
With lo.ListColumns(sFld).DataBodyRange
Rem Validate Last Record
Set rCllLast = rTrg.Areas(rTrg.Areas.Count).Cells(rTrg.Areas(rTrg.Areas.Count).Cells.Count)
If rCll.Address = rCllLast.Address Then
sMsgOut = kMsg3
Exit Function
Else
Rem Reset Visible Cells Range
Set rTrg = Range(rCll.Offset(1), .Cells(.Cells.Count))
Set rTrg = rTrg.SpecialCells(xlCellTypeVisible)
Rem Set Next Record
Set rCll = rTrg.Cells(1)
End If: End With: End Select
Rem Set Results
Set rOutput = rCll
ListObject_ƒNextVisibleCell = True
End Function
It should be called in this manner
Sub ListObject_ƒNextVisibleCell_TEST()
Const kTitle As String = "ListObject Next Visible Cell"
Dim wsMain As Worksheet, rCll As Range
Dim sFld As String, sMsg As String
sFld = "JobNo"
Set wsMain = ThisWorkbook.Worksheets("Main")
If ListObject_ƒNextVisibleCell(rCll, sMsg, sFld) Then
wsMain.Range("O2").Value2 = rCll.Value2
Else
MsgBox sMsg, vbCritical, kTitle
End If: End With
End Sub
Suggest to check the following pages for details about the resources used:
Worksheet object (Excel)
ListObject object (Excel)
Application.Range property (Excel)
With statement
MsgBox function

Can't refer the range of selection to any specific sheet

I've created a macro to print the range of cells and it's content in the console. The macro is doing just fine. However, the problem is I can't use a button (in another sheet) conected to that macro. To be clearer - I created a macro-enabled button in sheet2 whereas the range of cells I wanaa select and print are within sheet1.
I've tried so far:
Sub LoopAndPrintSelection()
Dim ocel As Range, RangeSelected As Range
Set RangeSelected = Application.Selection
For Each ocel In RangeSelected.Cells
Debug.Print ocel.Address, ocel.value
Next ocel
End Sub
How can I refer the range of selection to any specific sheet?
As others have already mentioned, the "Application.Selection" property will refer to what you have selected in your active sheet. I would recommend that you assign a hotkey to this macro and then you can select the cells you want to print and use the macro's hotkey.
This is one possible solution, but if you need that button on a different sheet and want people to interact with the button (rather than a hotkey) then this won't solve your issue.
This should help with the issue of two different tabs
Sub DUMMY_TEST()
Dim myAREA As Range
Dim mySELECTION As Range
On Error GoTo error_spot
'Stop Excel from "blinking" as tabs are selected/changed and calculating.
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Set mySELECTION = Application.Selection 'Used to get back to same spot after code has executed
If Sheets("Sheet1").Visible = True Then
Sheets("Sheet1").Activate
Else
'tab not visible, end sub
GoTo error_spot
End If
Set myAREA = Application.Selection
For Each ocel In myAREA.Cells
Debug.Print ocel.Address, ocel.Value
Next ocel
mySELECTION.Worksheet.Activate
mySELECTION.Select
error_spot:
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
End Sub

Excel Macro - Unlock, Spellcheck, Lock

I have a macro that needs to unlock all the sheets in a workbook, run the spell checker, and lock all the sheets (with the original column/cell formatting allowed). The locking errors out each time and I can't figure out why.
I know this doesn't include the formatting aspect, but here's what I've got.
Sub SpellChecker()
'unprotect all sheets
For i = 1 To Sheets.Count
Sheets(i).Unprotect "Password"
Next i
'select all sheets
Dim ws As Worksheet
For Each ws In Sheets
If ws.Visible Then ws.Select (False)
Next
'run spellchecker
Application.CommandBars.FindControl(ID:=2).Execute
'protect sheets
For i = 1 To Sheets.Count
Sheets(i).Protect "Password"
Next i
'selects one sheet/deselect all
Sheets("Sheet1").Select
End Sub
At the point of protecting the sheets, you still have all the sheets selected.
Select just one before
Sheets("Sheet1").Select
'protect sheets
For I = 1 To Sheets.Count
Sheets(I).Protect "Password"
Next I
However, perhaps doing them one sheet at a time is an idea..?
Sub SpellChecker()
For Each ws In Sheets
If ws.Visible Then
ws.Unprotect "Password"
ws.Select
Application.CommandBars.FindControl(ID:=2).Execute
ws.Protect "Password"
End If
Next
End Sub
Here is a method that does not require that the Worksheets ever be unprotected - instead, it will change the Protection on Protected Sheets to allow VBA to edit cells (but not allow the User to edit them) - however, this requires Range.CheckSpelling instead of Application.CommandBars.FindControl(ID:=2).Execute
Sub CheckAllSpelling()
Dim CheckSheet As Worksheet, CheckRange As Range, CheckCell As Range, SheetVisible AS XlSheetVisibility
'Loop through Worksheets in the Workbook
For Each CheckSheet In ThisWorkbook.Worksheets
'Allow VBA to edit a Protected Sheet, but not the User
If CheckSheet.ProtectContents Then CheckSheet.Protect Password:="Password", UserInterfaceOnly:=True
'Filter for Cells with Text to check
On Error Resume Next
Set CheckRange = CheckSheet.UsedRange.SpecialCells(xlCellTypeConstants, xlTextValues)
On Error GoTo 0
'If there are Cells to Check
If Not CheckRange Is Nothing Then
SheetVisible = CheckSheet.Visible
'Loop through cells
For Each CheckCell In CheckRange.Cells
With CheckCell
'If there is a typo, show the cell and Spellcheck it
If Not Application.CheckSpelling(.Text) Then
CheckSheet.Visible= xlSheetVisible
CheckSheet.Activate
.Select
.Show
DoEvents
'This next line is to fix a bug when checking a single cell
CheckSheet.Range(.MergeArea.Address & ", " & .MergeArea.Address) _
.CheckSpelling
End If
End With
Next CheckCell
CheckSheet.Visible= SheetVisible
End If
'Tidy up the Loop
Set CheckRange = Nothing
Next CheckSheet
'Same message as normal Spellcheck
MsgBox "Spell check complete. You're good to go!", vbExclamation
End Sub
(Note the fix for a bug where checking a Single Cell will instead check the entire Sheet)

Trim all cells within a workbook(VBA)

I have attempted to add functionality to an excel add-in ave been developing which trims the leading spaces at the end of used cells, and maybe even parse the text, The reason I need to do this is simply to have it turn into a hyperlink which I have already working but that parts fine.
This is what I have attempted so far, I have it trimming the active.worksheet am on which is fine but I can't figure out how to:
Trim Every cell being used across the whole workbook.
And also parse the text if possible
This is my attempt at Trimming the entire workbook, Its something simple I just know it, I just cant figure it out:
Sub DoTrim(Wb As Workbook)
Dim cell As Range
Dim str As String
Dim nAscii As Integer
Dim wsh As Worksheet
For Each wsh In Worksheets
With wsh.UsedRange
For Each cell In ActiveSheet.UsedRange
str = Trim(cell)
If Len(str) > 0 Then
nAscii = Asc(Left(str, 1))
If nAscii < 33 Or nAscii = 160 Then
If Len(str) > 1 Then
str = Right(str, Len(str) - 1)
Else
str = ""
End If
End If
End If
cell = str
Next cell
End With
Next wsh
End Sub
Any advice would be welcome am fairly new to this Language so sorry if I sound like a complete Newb!
TL;DR Trims cells only worksheet am on, needs to run across whole workbook I cant figure out how to iterate it across the whole thing.
EDIT: Is that also a quicker way of trimming these cells, the spreadsheets that are created for whom am designing this are massive and takes a while to trim the cells at times
Try this
Sub DoTrim(Wb As Workbook)
Dim aCell As Range
Dim wsh As Worksheet
'~~> If you are using it in an Add-In, it is advisable
'~~> to keep the user posted :)
Application.StatusBar = "Processing Worksheets... Please do not disturb..."
DoEvents
Application.ScreenUpdating = False
For Each wsh In Wb.Worksheets
With wsh
Application.StatusBar = "Processing Worksheet " & _
.Name & ". Please do not disturb..."
DoEvents
For Each aCell In .UsedRange
If Not aCell.Value = "" And aCell.HasFormula = False Then
With aCell
.Value = Replace(.Value, Chr(160), "")
.Value = Application.WorksheetFunction.Clean(.Value)
.Value = Trim(.Value)
End With
End If
Next aCell
End With
Next wsh
Application.ScreenUpdating = True
Application.StatusBar = "Done"
End Sub
I agree with Siddarth:
For Each cell In ActiveSheet.UsedRange
Should be:
For Each cell In wsh.UsedRange
I would have thought you should be able to remove with 'With wsh.UsedRange' statement around the loop as well.
As you are passing in a WorkBook reference, perhaps you should consider changin your outer For loop from:
For Each wsh In Worksheets
to:
For Each wsh In Wb.Worksheets

How do I return the location of the marching ants in Excel? [duplicate]

This question already has answers here:
Can I Get the Source Range Of Excel Clipboard Data?
(3 answers)
Closed 2 years ago.
I know about Application.CutCopyMode, but that only returns the state of the CutCopyMode (False, xlCopy, or xlCut).
How do I return the address of the currently copied range in Excel using VBA? I don't need the currently selected range (which is Application.Selection.Address). I need the address of the range of cells with the moving border (marching ants) around it.
In other words, if you select a range of cells, hit CTRL+C, and then move the selection to another cell, I need the address of the cells that were selected when the user hit CTRL+C.
Thanks!
As far as I know you can't do that with vba. You can however code your own copy sub and store the source in a global variable.
Something like this:
Option Explicit
Dim myClipboard As Range
Public Sub toClipboard(Optional source As Range = Nothing)
If source Is Nothing Then Set source = Selection
source.Copy
Set myClipboard = source
End Sub
10 years later you still can't refer directly to a copied Range
(shown by the "marching ants border" aka "dancing border", "moving border").
But you can get its address by copying the cells as link to a temporary worksheet. There you can collect the desired range's address.
Private Sub ThereAreTheMarchingAnts()
Dim rngCopied As Range ' the copied range with the marching ants border
Dim rngSelected As Range ' the selected range
Dim tmpWorksheet As Worksheet ' a temporary worksheet
Dim c As Range ' a cell for looping
' Exit, if nothing was copied (no marching ants border):
If Not (Application.CutCopyMode = xlCopy Or Application.CutCopyMode = xlCut) Then Exit Sub
' Exit, if no range is selected (just for demonstration)
If Not TypeName(Selection) = "Range" Then Exit Sub
' remember selected Range:
Set rngSelected = Selection
' add a temporary sheet and paste copied cells as link:
Set tmpWorksheet = ActiveWorkbook.Sheets.Add
tmpWorksheet.Paste link:=True
' go through all pasted cells and get the linked range from their formula:
For Each c In tmpWorksheet.UsedRange
If rngCopied Is Nothing Then
Set rngCopied = Range(Mid(c.Formula, 2))
Else
Set rngCopied = Union(rngCopied, Range(Mid(c.Formula, 2)))
End If
Next c
' delete the temporary worksheet without asking:
Application.DisplayAlerts = False
tmpWorksheet.Delete
Application.DisplayAlerts = True
' show the addresses:
MsgBox "Copied Range: " & rngCopied.Address(0, 0, xlA1, True) & vbLf & _
"Selected Range: " & rngSelected.Address(0, 0, xlA1, True)
End Sub
The code also works with multiranges and also if the copied range and the selected range are on different sheets.
When you copy a Range, the address is copied to the Clipboard along with other formats. You can check that with Clipboard Viewer application.
So if you need the copied Range, get it from Clipboard. It will be something like> $A2:$B5 or similar
The only way i can think of doing this is tracking the last range selected with a global variable and then waiting until you think a copy action is done. Unfortunately neither is easy.
The following is a quick attempt that has two problems;
If you copy the same data twice it
isn't updated
If a copy or paste is
fired from another app, the results
may vary.
This is one of those last hope tricks when tracking events that don't really exist. Hope this helps.
''# Add a reference to : FM20.dll or Microsoft Forms 2.0
''# Some more details at http://www.cpearson.com/excel/Clipboard.aspx
Option Explicit
Dim pSelSheet As String
Dim pSelRange As String
Dim gCopySheet As String
Dim gCopyRange As String
Dim gCount As Long
Dim prevCBText As String
Dim DataObj As New MSForms.DataObject
Private Sub Workbook_SheetSelectionChange(ByVal Sh As Object, _
ByVal Target As Excel.Range)
CopyTest
pSelSheet = Sh.Name
pSelRange = Target.Address
''# This is only so you can see it working
gCount = gCount + 1
application.StatusBar = gCopySheet & ":" & gCopyRange & ", Count: " & gCount
End Sub
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Source As Range)
CopyTest ''# You may need to call CopyTest from other events as well.
''# This is only so you can see it working
gCount = gCount + 1
application.StatusBar = gCopySheet & ":" & gCopyRange & ", Count: " & gCount
End Sub
Sub CopyTest()
Dim curCBText As String
Dim r As Range
DataObj.GetFromClipboard
On Error GoTo NoCBData
curCBText = DataObj.GetText
On Error Resume Next
''# Really need to test the current cells values
''# and compare as well. If identical may have to
''# update the gCopyRange etc.
If curCBText <> prevCBText Then
gCopySheet = pSelSheet
gCopyRange = pSelRange
prevCBText = curCBText
End If
Exit Sub
NoCBData:
gCopySheet = ""
gCopyRange = ""
prevCBText = ""
End Sub
Oh and excuse the wierd comments ''# they're just there to help the syntax highlighter of SO.
I think you can use this method
https://learn.microsoft.com/en-us/office/vba/api/Excel.Application.OnKey
This method assigns a function to the hot key Ctrl+C, every time this combination is used, the function will be triggered and you can get the address of the range.

Resources