Excel VBA: Application defined or Object defined error - vba

I've written some code to look for sets of brackets in an excel file and white out the contents of the cells in between them. The code I have works for 26-27 lines before I get the error message.
Here is the code:
Sub macro()
Dim white As Long
Dim rowIndex As Long
Dim colIndex As Long
Dim lastRow As Long
Dim lastCol As Long
white = RGB(Red:=255, Green:=255, Blue:=255)
With ActiveSheet
lastRow = .Cells.Find("*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
lastCol = .Cells.Find("*", SearchOrder:=xlByColumns, SearchDirection:=xlPrevious).Column
For rowIndex = 1 To lastRow
For colIndex = 1 To lastCol
If .Cells(rowIndex, colIndex).Text = "[" Then
colIndex = colIndex + 1
Do While .Cells(rowIndex, colIndex).Value <> "]"
.Cells(rowIndex, colIndex).Font.Color = white
colIndex = colIndex + 1
Loop
End If
Next colIndex
Next rowIndex
End With
End Sub
The error occurs on this line:
Do While Cells(rowIndex, colIndex).Value <> "]"
I tried adding in:
With ActiveSheet
Along with . before each Cell command but it did not make a difference.
Any help is greatly appreciated.

If one of the cells containing [ or ] may have rogue leading trailing spaces/non-breaking spaces then a wildcard comparison should be made. Additionally, the worksheet's MATCH function can locate the bracketing cells with a wildcard search more efficiently than looping through each cell row-by-row.
Sub hide_cell_values()
Dim whiteOut As String '<~~ using alternate method .NumberFormat ;;;
Dim rw As Long, n As Long, f As Long, l As Long
whiteOut = ";;;" 'custom cell number format to show nothing in cell
With ActiveSheet
'process row by row in the .UsedRange
With .Range(.Cells(1, 1), .Cells.SpecialCells(xlCellTypeLastCell))
For rw = 1 To .Rows.Count
' check for existance of matching pairs
If Not IsError(Application.Match("*[*", .Rows(rw), 0)) And _
Application.CountIf(.Rows(rw), "*[*") = _
Application.CountIf(.Rows(rw), "*]*") Then
' [ and ] pairs exist and match in row.
f = 0: l = 0
For n = 1 To Application.CountIf(.Rows(rw), "*[*")
'this looks complicated but it just references the cells between [ & ]
f = Application.Match("*[*", .Rows(rw).Cells.Offset(0, l), 0) + l + 1
' last safety check to ensure that [ comes before ]
If Not IsError(Application.Match("*]*", .Rows(rw).Cells.Offset(0, f), 0)) Then
l = Application.Match("*]*", .Rows(rw).Cells.Offset(0, f), 0) + f - 1
With .Range(.Cells(rw, f), .Cells(rw, l))
'this is a better method of not displaying text in a cell
.NumberFormat = whiteOut '<~~ e.g. ;;;
'the old method of white-text-on-white-background (not reliable as .Interior.Color can change)
'.Font.Color = vbWhite
End With
End If
Next n
Else
' [ and ] pairs do not match or do not exist in row. do nothing.
End If
Next rw
End With
End With
End Sub
I have opted for a custom cell number format of ;;; rather than altering the font color to RGB(255, 255, 255) (see footnote ¹). A Range.NumberFormat property of three semi-colons in a row simply shows nothing; a white font's apparent visibility is subject to the cell's Range.Interior.Color property, the worksheet backgroun or even the 'Window background' in the computer's system settings.
        Before running sub
        After running sub
In the before and after images above, you can see that D2 retains its Range.Value property (visible in the formula bar) while showig nothing on the worksheet. Note: cell values can still be copied from a cell displaying nothing but that is a caveat of using the vbWhite method as well.
¹ There are predefined RGB long type constants for the basic VBA pallette. RGB(255, 255, 255) is equal to vbWhite. Full list available at Color Constants.

Related

Using cell reference formula in VBA to return cell to use in VBA code

I am trying to use a formula with INDEX and MATCH to return a cell reference to enter TEXT into with VBA.
I have a list of vendors in column A and to find the cell to the right of it I can use the following
=CELL("address";INDEX(A29:C42;MATCH("***";A29:A42;0);2))
However I am struggling with how to get this in my VBA code. (note the value *** is changing as I need to run the sub several times for different vendors.
Can i use Function sub for this? I have tried as below with no luck:
Sub CellRef()
'
' CellRef
'
'
Function.Range(="CELL("ADDRESS";INDEX(A29:C42;MATCH("Accenture";A29:A42;0);2))")
End Sub
If I understand your question correctly you want to match the value/vendor in column A and return the address of the cell to its right?
You can do like so:
Cell2WorkWith = Cells(Application.Match(Searchvalue, Searchrange, 0),2).Address
If you don't want the "$", then replace them like so:
Cell2WorkWith = Replace(Cells(Application.Match(Searchvalue, Searchrange, 0),2).Address,"$","")
Or, even better, like:
Cell2WorkWith = Cells(Application.Match(Searchvalue, Searchrange, 0),2).Address(0,0)
If I misunderstood and it is the value from that cell to the right then below will do:
Value2WorkWith = Cells(Application.Match(Searchvalue, Searchrange, 0),2).Value
Remember to only use match when the value CAN be found within the range, else you'll have to catch an error.
One alternative would be to look for the cell with "Find":
Option Explicit
Sub CellRef()
Dim SearchString As String
Dim ra, cell, VendorsRange As Range
Dim k As Integer
Set VendorsRange = Range("E1:E10")
k = 1
For Each cell In VendorsRange
SearchString = cell.Value
Set ra = Range("A29:A42").Find(What:=SearchString, LookIn:=xlValues, LookAt _
:=xlWhole, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:= _
False, SearchFormat:=False)
If ra Is Nothing Then
MsgBox "String not available"
Else
Range("D" & k).Value = ra.Offset(0, 1).Address 'Change "D1" to whereever you want to put your result in
End If
k = k + 1
Next cell
End Sub
The code would check for every vendor (in my code range "E1:E10"), where is the cell in your range A29:A42 and returns the address of the cell next to it.
Some people prefer to find all occurrences of a searched item, and then change the value or the formula, or do else. Here is some code allowing great flexibility using an array.
'**************************************************************************************************************************************************************
'To return an array of information (value, formula, address, row, and column) for all the cells from a specified Range that have the searched item as value
'Returns an empty array if there is an error or no data
'**************************************************************************************************************************************************************
Public Function makeArrayFoundCellInfoInRange(ByVal itemSearched As Variant, ByVal aRange As Variant) As Variant
Dim cell As Range, tmpArr As Variant, x As Long
tmpArr = Array()
If TypeName(aRange) = "Range" Then
x = 0
For Each cell In aRange
If itemSearched = cell.Value Then
If x = 0 Then
ReDim tmpArr(0 To 0, 0 To 4)
Else
tmpArr = reDimPreserve(tmpArr, UBound(tmpArr, 1) + 1, UBound(tmpArr, 2))
End If
tmpArr(x, 0) = cell.Value
tmpArr(x, 1) = cell.Formula
tmpArr(x, 2) = cell.Address(0, 0) 'Without the dollar signs
tmpArr(x, 3) = cell.Row
tmpArr(x, 4) = cell.Column
x = x + 1
End If
Next cell
End If
makeArrayFoundCellInfoInRange = tmpArr
Erase tmpArr
End Function

Concatenate Columns Of Data

*Edited To Add: Current error I'm receiving. See bottom of this post for screenshot.
I have text in column D. The macro should find blank cells, and then concatenate the text from all cells below it.
Example
Text starting in D2, displaying like this...
Blank Cell
SampleText1
SampleText2
SampleText3
Blank Cell
SampleText4
SampleText5
SampleText6
The macro should display the text in D2...
SampleText1, SampleText2, SampleText3
and then in D6, like this...
SampleText4, SampleText5, SampleText6
..and so on.
This only needs to work in column D, so I'm guessing I can write it to that range.
The closest answer I've come across is here:
Excel Macro to concatenate
Here is the code I'm currently working with...
Sub ConcatColumns()
Do While ActiveCell <> "" 'Loops until the active cell is blank.
'The "&" must have a space on both sides or it will be
'treated as a variable type of long integer.
ActiveCell.Offset(0, 1).FormulaR1C1 = _
ActiveCell.Offset(0, -1) & " " & ActiveCell.Offset(0, 0)
ActiveCell.Offset(1, 0).Select
Loop
End Sub
Edit: Now using great code from #jeeped but receiving an error, seen in the below screenshot
Start from the bottom and work up, building an array of the strings. When you reach a blank cell, Join the strings using your preferred deliminator.
Sub build_StringLists()
Dim rw As Long, v As Long, vTMP As Variant, vSTRs() As Variant
Dim bReversedOrder As Boolean, dDeleteSourceRows As Boolean
ReDim vSTRs(0)
bReversedOrder = False
dDeleteSourceRows = True
With Worksheets("Sheet4")
For rw = .Cells(Rows.Count, 1).End(xlUp).Row To 1 Step -1
If IsEmpty(.Cells(rw, 1)) Then
ReDim Preserve vSTRs(0 To UBound(vSTRs) - 1)
If Not bReversedOrder Then
For v = LBound(vSTRs) To UBound(vSTRs) / 2
vTMP = vSTRs(UBound(vSTRs) - v)
vSTRs(UBound(vSTRs) - v) = vSTRs(v)
vSTRs(v) = vTMP
Next v
End If
.Cells(rw, 1) = Join(vSTRs, ", ")
.Cells(rw, 1).Font.Color = vbBlue
If dDeleteSourceRows Then _
.Cells(rw, 1).Offset(1, 0).Resize(UBound(vSTRs) + 1, 1).EntireRow.Delete
ReDim vSTRs(0)
Else
vSTRs(UBound(vSTRs)) = .Cells(rw, 1).Value2
ReDim Preserve vSTRs(0 To UBound(vSTRs) + 1)
End If
Next rw
End With
End Sub
I've left options for reversing the string list as well as removing the original rows of strings.
                  Before build_StringLists procedure
                  After build_StringLists procedure

Replace cells containing zero with blank

I have a very large amount of data A4:EW8000+ that I want to replace cells containing a zero with a blank cell. Formatting the cells is not an option as I need to retain the current format. I'm looking for the fastest way to replace zeros with blank cells.
I can do this with looping but its very slow. Below code:
Sub clearzero()
Dim rng As Range
For Each rng In Range("A1:EW10000")
If rng.Value = 0 Then
rng.Value = ""
End If
Next
End Sub
Is there an easy way I can do this without looping?
I tried the below code, but it doesn't seem to work correctly. It hangs Excel for a while (not responding) then it loops through the range and blanks every cell.
Sub RemoveZero()
Dim LastRow As Long
Const StartRow As Long = 2
LastRow = Cells.Find(What:="0", SearchOrder:=xlRows, SearchDirection:=xlPrevious, LookIn:=xlValues).Row
With Range("B:EW")
.Value = Range("B:EW").Value
.Replace "0", "0", xlWhole, , False
On Error Resume Next
.SpecialCells(xlConstants).Value = ""
.SpecialCells(xlFormulas).Value = 0
End With
End Sub
This is all the VBA you need to automate the replacements:
[a4:ew10000].Replace 0, "", 1
.
UPDATE
While the above is concise, the following is likely the fastest way possible. It takes less than a quarter of a second on my computer for your entire range:
Sub RemoveZero()
Dim i&, j&, v, r As Range
Set r = [a4:ew10000]
v = r.Value2
For i = 1 To UBound(v, 1)
For j = 1 To UBound(v, 2)
If Len(v(i, j)) Then
If v(i, j) = 0 Then r(i, j) = vbNullString
End If
Next
Next
End Sub
I have found that sometimes it is actually more expedient to cycle through the columns on bulk replace operations like this.
dim c as long
with worksheets("Sheet1")
with .cells(1, 1).currentregion
for c = 1 to .columns.count
with .columns(c)
.replace what:=0, replacement:=vbNullString, lookat:=xlWhole
end with
next c
end with
end with
Splitting the overall scope into several smaller operations can improve overall performance. My own experience with this is on somewhat larger data blocks (e.g. 142 columns × ~250K rows) and replacing NULL from an SQL feed not zeroes but this should help.

Clear Constants in a range without clearing references and formulas

I am trying to clear all the number constants in a range of cells without clearing any formulas or cell references. Clearing the constants from cells without any formulas or cell references is simple but I am having trouble doing it when those are present. Below is the code I have so far.
Range("B2:B11").Select
Selection.SpecialCells(xlCellTypeConstants, 1).Select
Selection.ClearContents
In this range cells B5 and B7 have formulas with cell references as follows:
B5: =(G83*H1)+1181.05
B7: =E33+1292.76
The cell references will also at times reference cells on other sheets in the same workbook. I need to clear the constants from these formulas while leaving the references intact.
This will remove constants from all formulas in current workbook based on 2 patterns:
"=Formula-[Space]-PlusSign-[Space]-Constant" (space optional)
=(G83*H1)+1181.05 or =(G83*H1) +1181.05 or =(G83*H1)+ 1181.05 becomes =(G83*H1)
=E33+1292.76 or =E33 +1292.76 or =E33+ 1292.76 or =E33 + 1292.76 becomes =E33
"=Formula-[Space]-MinusSign-[Space]-Constant" (space optional)
Public Sub clearConstantsFromWorkBookFormulas()
Const PATTERNS As String = "~+*|~+ *|~ +*| ~+ *|~-*|~- *|~ -*|~ - *"
Dim pat As Variant
For Each pat In Split(PATTERNS, "|")
Cells.Replace What:=pat, _
Replacement:=vbNullString, _
LookAt:=xlPart, _
SearchOrder:=xlByRows, _
MatchCase:=False
Next
End Sub
.
This is a more generic option using regEx pattern matching and arrays:
Public Sub testClear()
Dim ws As Worksheet
For Each ws In Application.ActiveWorkbook.Worksheets
removeConstantsFromFormulas ws.Range("B2:B11"), getRegEx
Next
End Sub
Public Sub removeConstantsFromFormulas(ByRef rng As Range, ByRef regEx As Object)
Dim v As Variant, r As Long, c As Long, lr As Long, lc As Long
lr = rng.Rows.Count
lc = rng.Columns.Count
If lr > 0 And lc > 0 Then
v = rng.Formula
For r = 1 To lr
For c = 1 To lc
If Left(v(r, c), 1) = "=" Then
If regEx.Test(v(r, c)) Then v(r, c) = regEx.Replace(v(r, c), vbNullString)
End If
Next
Next
rng.Formula = v
End If
End Sub
Private Function getRegEx() As Object
Set getRegEx = CreateObject("VBScript.RegExp")
getRegEx.Pattern = "[^a-zA-Z][0-9]+(\.?[0-9]+)"
getRegEx.Global = True
getRegEx.IgnoreCase = True
End Function
RegEx pattern: one or more digits, digit group not preceded by a letter, with or without a fraction part
This attempt should handle most examples using a Regexp.
There may be some edge cases as the discussion above points out. For the code below
=(G83*H1)+1181.05
=10+A1
=A1+10
=A1+(10)
=A1+10.0
becomes
=(G83*H1)
=+A1
=A1
=A1
=A1
I note it would also take out the ^2 in =A1^2
It clearly also won't cater for named formulae (named ranges).
Updated: Now handles cascading parentheses, ie
=A1+(27+(11-2))
becomes
=A1
Sub Format()
Dim objRegexB As Object
Dim lngCnt As Long
Dim X
X = [b2:b11].Formula
Set RegExB = CreateObject("vbscript.regexp")
With RegExB
.Pattern = "[=\+\/\*^\-](\([0-9]\d*(\.\d+)?\)|[0-9]\d*(\.\d+)?|\.\d+)"
.Global = True
For lngCnt = 1 To UBound(X)
Do While .Test(X(lngCnt, 1))
X(lngCnt, 1) = .Replace(X(lngCnt, 1), vbNullString)
Loop
Next
End With
[b2:b11].Formula = X
End Sub

VBA: copying the first empty cell in the same row

I am a new user of VBA and am trying to do the following (I got stuck towards the end):
I need to locate the first empty cell across every row from column C to P (3 to 16), take this value, and paste it in the column B of the same row.
What I try to do was:
Find non-empty cells in column C, copy those values into column B.
Then search for empty cells in column B, and try to copy the first non-empty cell in that row.
The first part worked out fine, but I am not too sure how to copy the first non-empty cell in the same row. I think if this can be done, I might not need the first step. Would appreciate any advice/help on this. There is the code:
Private Sub Test()
For j = 3 To 16
For i = 2 To 186313
If Not IsEmpty(Cells(i, j)) Then
Cells(i, j - 1) = Cells(i, j)
End If
sourceCol = 2
'column b has a value of 2
RowCount = Cells(Rows.Count, sourceCol).End(xlUp).Row
'for every row, find the first blank cell, copy the first not empty value in that row
For currentRow = 1 To RowCount
currentRowValue = Cells(currentRow, sourceCol).Value
If Not IsEmpty(Cells(i, 3)) Or Not IsEmpty(Cells(i, 4)) Or Not IsEmpty(Cells(i, 5)) Or Not IsEmpty(Cells(i, 6)) Then
Paste
~ got stuck here
Next i
Next j
End Sub
Your loop is really inefficient as it is iterating over millions of cells, most of which don't need looked at. (16-3)*(186313-2)=2,422,043.
I also don't recommend using xlUp or xlDown or xlCellTypeLastCell as these don't always return the results you expect as the meta-data for these cells are created when the file is saved, so any changes you make after the file is saved but before it is re-saved can give you the wrong cells. This can make debugging a nightmare. Instead, I recommend using the Find() method to find the last cell. This is fast and reliable.
Here is how I would probably do it. I'm looping over the minimum amount of cells I can here, which will speed things up.
You may also want to disable the screenupdating property of the application to speed things up and make the whole thing appear more seemless.
Lastly, if you're new to VBA it's good to get in the habit of disabling the enableevents property as well so if you currently have, or add in the future, any event listeners you will not trigger the procedures associated with them to run unnecessarily or even undesirably.
Option Explicit
Private Sub Test()
Dim LastUsed As Range
Dim PasteHere As Range
Dim i As Integer
Application.ScreenUpdating=False
Application.EnableEvents=False
With Range("B:B")
Set PasteHere = .Find("*", .Cells(1, 1), xlFormulas, xlPart, xlByRows, xlPrevious, False, False, False)
If PasteHere Is Nothing Then Set PasteHere = .Cells(1, 1) Else: Set PasteHere = PasteHere.Offset(1)
End With
For i = 3 To 16
Set LastUsed = Cells(1, i).EntireColumn.Find("*", Cells(1, i), xlFormulas, xlPart, xlByRows, xlPrevious, False, False, False)
If Not LastUsed Is Nothing Then
LastUsed.Copy Destination:=PasteHere
Set PasteHere = PasteHere.Offset(1)
End If
Set LastUsed = Nothing
Next
Application.ScreenUpdating=True
Application.EnableEvents=True
End Sub
Sub non_empty()
Dim lstrow As Long
Dim i As Long
Dim sht As Worksheet
Set sht = Worksheets("Sheet1")
lstrow = sht.Cells(sht.Rows.Count, "B").End(xlUp).Row
For i = 1 To lstrow
If IsEmpty(Range("B" & i)) Then
Range("B" & i).Value = Range("B" & i).End(xlToRight).Value
End If
Next i
End Sub