What is the Fastest Way to Find the First Formula in an Excel Range with VBA? - excel

Is there any quicker method than using a for loop to find the first instance of a formula in a cell?
For Each dc In .Worksheets("testWS").Range(searchRange)
If dc.hasFormula() = True Then
formulaRow = Split(dc.Address, "$")(2)
formula = dc.formula
Exit For
End If
Next

No loop needed - use Range.SpecialCells. Include error handling since there may be no cells with formulas.
On Error Resume Next
Dim formulaRng As Range
Set formulaRng = .Worksheets("testWS").Range(searchRange).SpecialCells(xlCellTypeFormulas)
On Error GoTo 0
If Not formulaRng Is Nothing Then
Debug.Print formulaRng.Cells(1).Row
Debug.Print formulaRng.Cells(1).Formula
End If

Function FindFirstFormulaRow(ByRef rng As Range) As Long
Dim arrFormulas As Variant
Set arrFormulas = rng.SpecialCells(xlCellTypeFormulas)
Set rng = arrFormulas
If Not rng Is Nothing Then
FindFirstFormulaRow = Split(rng.Cells(1).Address, "$")(2)
Set rng = rng.Cells(1)
End If
End Function`

Related

Is it possible to get the name of a range that the active cell is in?

Scenario: Range is named "Dog" and the named range Dog refers to A1:D4. The active cell is in cell B3, which is within the named range.
Is it possible to get the name of the named range that the active cell is in? ie return the name "Dog"?
Perhaps something like the following, which tests the Intersection of the ActiveCell and each named range.
The On Error Resume Next...On Error GoTo 0 is necessary since Intersect will fail when the ActiveCell and the named range are on different sheets, or if n is not a named range but if it refers to a constant or formula, for example.
Sub test()
Dim n As Name
For Each n In ActiveWorkbook.Names
Dim rng As Range
Set rng = Nothing
On Error Resume Next
Set rng = Intersect(ActiveCell, n.RefersToRange)
On Error GoTo 0
If Not rng Is Nothing Then
Debug.Print n.Name
End If
Next
End Sub
This should be a more robust way...
Sub Test()
MsgBox NamesUsedBy(ActiveCell)
End Sub
Function NamesUsedBy(r As Range)
Dim s$, n
On Error Resume Next
For Each n In ThisWorkbook.Names
If Intersect(r, Evaluate(Mid(n, 2))).Row Then
If Err = 0 Then s = s & ", " & n.Name
End If
Err.Clear
Next
NamesUsedBy = Mid(s, 3)
End Function
There is probably a more elegant way of doing this, but this should work.
Sub test()
Dim currentrange As Range
Dim r As Variant
Set currentrange = ActiveCell
For Each r In ThisWorkbook.Names
If Not Application.Intersect(currentrange, Range(Right(r, InStr(1, r, "$")))) Is Nothing Then
Debug.Print r.Name
End If
Next r
End Sub

how to find multiple strings using range.value?

i tried to use range("A1:I1").value to find multiple strings at the first row however it shows that error "mismatch". What have i done wrong here? Is there another way to do it?
Dim sht as worksheet
Set sht = ThisWorkbook.Sheets("Result")
If sht.range("A1:I1").value = " Voltage" and sht.range("A1:I1").value = " Time" ,<---------error 'mismatch' occurs here
call powerandtime
The problem here is that you are comparing an array of values against a single value. In case of such a small array you can make use of some Application.Methods. Another option would be to use Range.Find on the actual Range object. I'll demonstrate both below:
Application.Methods
Sub Test()
Dim ws As Worksheet: Set ws = ThisWorkbook.Worksheets("Result")
Dim arr As Variant
With Application
arr = .Transpose(ws.Range("A1:I1").Value)
If .Count(.Match(Array("Voltage", "Time"), arr, 0)) = 2 Then
Call PowerAndTime
End If
End With
End Sub
What happens here is that .Match will return an array of two elements. It will either return an error value to the array if either "voltage" or "time" is not found, or it would return a numeric value when either one of them is found. Then .Count will count numeric values within that returned array, and only if the count would be 2, is when both values are present within your initial range.
Note: .Match needs a 1D-array, hence the .Transpose at the start.
Range.Find
Sub Test()
Dim ws As Worksheet: Set ws = ThisWorkbook.Worksheets("Result")
Dim rng1 As Range, rng2 As Range
Set rng1 = ws.Range("A1:I1").Find("Voltage", lookat:=xlWhole)
Set rng2 = ws.Range("A1:I1").Find("Time", lookat:=xlWhole)
If Not rng1 Is Nothing And Not rng2 Is Nothing Then
Call PowerAndTime
End If
End Sub
So only when both "Voltage" and "Time" are found as xlWhole values within your specific range, it would continue to call PowerAndTime.
Sub testMatchBis()
Dim sh As Worksheet, rng As Range, voltPos As Long, timePos As Long
Dim rngBis As Range, arrBis as Variant
Set sh = ActiveSheet ' use please your sheet here
Set rng = sh.Range("A1:I1")
voltPos = IsMatch(rng, "Voltage")
timePos = IsMatch(rng, "Time")
If voltPos <> 0 And timePos <> 0 Then
Set rngBis = sh.Columns(voltPos)
Set rngBis = Union(rngBis, sh.Columns(timePos))
arrBis = rngBis.Value 'the both columns content will be input in an array
rngBis.Select 'both columns will be selected. Of course, you need to determine
'only part of the comumn keeping values (their last row) and limit the range
'Call call powerandtime 'You must know what this sub must do...
Else
MsgBox "(At least) one of your searched strings could not be found in the range..."
End If
End Sub
Private Function IsMatch(rng As Range, strS As String) As Long
On Error Resume Next
IsMatch = WorksheetFunction.Match(strS, rng, 0)
If Err.Number <> 0 Then
Err.Clear: On Error GoTo 0
IsMatch = 0
End If
On Error GoTo 0
End Function
You could try:
Sub test()
Dim arrStrings As Variant
Dim i As Long, Counter As Long
Dim rng As Range
Set rng = ThisWorkbook.Worksheets("Sheet1").Range("A1:I1")
Counter = 0
arrStrings = Split("Time,Electric", ",")
For i = LBound(arrStrings) To UBound(arrStrings)
If Not rng.Find(arrStrings(i), lookat:=xlWhole) Is Nothing Then
Counter = Counter + 1
GoTo NextIteration
End If
NextIteration:
Next i
If Counter = UBound(arrStrings) + 1 Then
Call PowerAndTime
End If
End Sub

Using a VBA Try/Except Equivalent for If/Else

I am trying to run through some spreadsheet range and use a try/except in order to build an if/else statement. The reason I am doing this is because IsNumeric() is not working for me so I am trying to do something like this (try except formatting from python)
Dim Temp as Integer
Dim Myrange as Range
Dim Myrow as Range
Set Myrange = Range("A1","A1000")
For Each Myrow in Myrange.Row
If IsEmpty(Range("A" & Myrow.Row)) Then
Exit For 'To escape the loop at the end of the filled cells
Else
Try:
Temp = (Myrow.Value() - 0) 'This causes a #VALUE! error when the Myrow.Value is not a number.
Except:
Range("B" & Myrow.Row).Value = Temp 'this sets the value of the rightmost cell to whatever current value of Temp is.
I have also tried some other error catching but can't seem to get it in VBA.
For Each Myrow In Myrange.Rows
If IsEmpty(Range("A" & Myrow.Row)) Then
Exit For
Else
On Error Resume Next
Temp = Myrow.Value() - 0
If Err.Number = 0 Then
Range("A" & Myrow.Row).Value = ""
ElseIf Err.Number <> 0 Then
Range("B" & Myrow.Row) = Temp
End If
End If
Next Myrow
I am really just looking to run down the list, see the first number, set value of B0:Bn1 = Temp, when An is hit (new number), The value of Temp changes to temp2 and then cells Bn1+1 -> Bn2-1 is temp2 until a new number is found etc.
in the worksheet I can do it fine with dragging down formula =(A1-0) to see the error message for those that are not numeric but for some reason I can't code it.
Solved this using advice of #MathieuGuindon by using variant type and testing isnumeric on that. Solution code:
Dim Myrange As Range
Dim Myrow As Range
Dim Temp As Variant
Dim NextTemp As Variant
Set Myrange = Selection
For Each Myrow In Myrange.Rows
NextTemp = Range("A" & Myrow.Row).Value
If IsEmpty(Range("A" & Myrow.Row)) Then
Exit For
ElseIf IsNumeric(NextTemp) Then
Temp = NextTemp
Range("A" & Myrow.Row).Value = ""
Else
Range("B" & Myrow.Row).Value = Temp
End If
Next Myrow
A bit of simplification, and picking up on Mathieu's comments, try this. Not sure what you're doing though so may no be quite right.
Sub x()
Dim Temp As Variant
Dim Myrange As Range
Dim Myrow As Range
Set Myrange = Range("A1", "A1000")
For Each Myrow In Myrange
If Not IsEmpty(Myrow) Then
Temp = Myrow.Value - 0
If IsNumeric(Temp) Then
Myrow.Value = vbNullString
Else
Myrow.Offset(, 1).Value = Temp
End If
End If
Next Myrow
End Sub
One way is to have a dedicated error handler at the end of your sub, and check the error code (13 for Type Mismatch):
Option Explicit
Public Sub EnumerateValues()
On Error GoTo err_handle
Dim Temp As Integer
Dim Myrange As Range
Dim Myrow As Range
Dim myNumber As Double ' Int? Long?
Set Myrange = Range("A1", "A1000")
For Each Myrow In Myrange.Rows
If IsEmpty(Range("A" & Myrow.Row)) Then
Exit For ' to escape loop at end of filled cells
Else
myNumber = CDbl(Myrow.Value())
Debug.Print myNumber
End If
' use label, since VBA doesn't support Continue in loop.
loop_continue:
Next Myrow
exit_me:
Exit Sub
err_handle:
Select Case Err.Number
Case 13 ' Type Mismatch
GoTo loop_continue
Case Else
MsgBox Err.Description, vbOKOnly + vbCritical, Err.Number
GoTo exit_me
End Select
End Sub
This way, if we encounter a value for which CDbl (or the equivalent function) fails, we just continue on to the next row.
While the first example contains Try: and Except: as labels, they provide no error control. Try/Except are vb.net error control methods, not vba.
It's unclear whether you might have text that looks like numbers in column A. If the Temp = (Myrow.Value() - 0) is only meant to determine whether the value in column A is a number and not used as a conversion then SpecialCells can quickly find the numbers in column A.
dim rng as range
on error resume next
'locate typed numbers in column A
set rng = Range("A:A").SpecialCells(xlCellTypeConstants, xlNumbers)
on error goto 0
If not rng is nothing then
rng = vbNullString
End If
on error resume next
'locate text values in column A
set rng = Range("A:A").SpecialCells(xlCellTypeConstants, xlTextValues)
on error goto 0
If not rng is nothing then
rng.Offset(0, 1) = rng.Value
End If
You can also use xlCellTypeFormulas to return numbers or text returned by formulas.

runtime error 91 object variable or with block variable not set database

I have a database where all the data are in majuscule and I'm trying to keep only the first letter like that, my code is
Sub nompropio()
Dim rng As Range
Dim cell As Range
Set rng = Range("A1:T17058")
For Each cell In rng
Next cell
If Not cell.HasFormula Then >>>here is the eror
End If
cell.Value = WorksheetFunction.Proper(cell.Value)
End Sub
I don't know if having blank cells is a problem or if some columns are only numbers but none of those cells have formula i just put it because the example was like that and I tried to work it without that part but neither it worked.
It should work with this syntax:
Sub nompropio()
Dim rng As Range
Dim cell As Range
Set rng = Range("A1:T17058")
For Each cell In rng
If Not cell.HasFormula Then cell.Value = WorksheetFunction.Proper(cell.Value)
Next
End Sub
updated post
The version below uses variant arrays and SpecialCells to run the same process much faster than the range loop version above.
Sub Recut()
Dim X
Dim rng1 As Range
Dim rng2 As Range
Dim lngRow As Long
Dim lngCol As Long
On Error Resume Next
Set rng1 = Range("A1:T17058").SpecialCells(xlConstants)
On Error GoTo 0
If rng1 Is Nothing Then Exit Sub
For Each rng2 In rng1.Areas
If rng2.Cells.Count > 1 Then
X = rng2.Value2
For lngRow = 1 To UBound(X, 1)
For lngCol = 1 To UBound(X, 2)
X(lngRow, lngCol) = WorksheetFunction.Proper(X(lngRow, lngCol))
Next
Next
rng2.Value2 = X
Else
rng2.Value = WorksheetFunction.Proper(rng2.Value)
End If
Next
End Sub
The error you're indicating comes because the variable is out of scope. In fact,
In your loop, you define the variable implicitly:
For Each cell In rng
Next cell
Out of the Each loop, you try to call the variable:
If Not cell.HasFormula Then '>>>here is the error, because "cell" it's something within the previous loop, but it's nothing here so the compiler tells you "hey, I'm sorry, but I didn't set your variable".
End If
Clearly, the variable is out-of-scope because it is defined in the For Each loop so it exists only within the scope of the loop. If you want to perform something on each cell without formulas, then this is the right way to go:
For Each cell In rng '<-- for each cell...
If Not cell.HasFormula Then '<-- perform if the cell has no formula
cell.Value = WorksheetFunction.Proper(cell.Value) '<--action to perform
End If '<-- end the if-then statement
Next cell '<-- go to the next cell of the range

In range find this and do that

Have a range of cell with column headings as weeks In the range of cells I want to look for a number, say
1 if it finds a 1 then look at a column in said row for a variable, 2 or 4 whatever Now I want to put a triangle (can be copy and paste a cell) in the cell that has the "1" in it then skip over the number of week variable and add another triangle and keep doing this until the end of the range. Then skip down to the next row and do the same, until the end of the range.
Then change to the next page and do the same thing... through the whole workbook.
I think I have it done, don't know if it's the best way.
I get a error 91 at the end of the second loop, the first time the second loop ends it goes through the error code.
The second time the second loop ends it errors.
I don't understand it runs through once, but not twice.
Sub Add_Triangles2()
Dim Rng As Range
Dim OffNumber As Integer
Dim SetRange As Range
Dim OffsetRange As Range
Dim ws As Worksheet
Set SetRange = Sheets("Sheet1").Range("G25") ' Used to stop the second loop in range
Worksheets(1).Activate
Worksheets(1).Range("A1").Select ' Has item to be pasted (a triangle)
Selection.Copy
For Each ws In Worksheets
Worksheets(ws.Name).Activate
With Range("C4:G25")
Set Rng = .Find(1, LookIn:=xlValues)
If Not Rng Is Nothing Then
FirstAddress = Rng.Address
Do
Rng.Activate
ActiveSheet.Paste
Do
OffNumber = Range("A" & ActiveCell.Row)
Set OffsetRange = SetRange.Offset(0, -OffNumber)
If Not ActiveCell.Address < OffsetRange.Address Then
Exit Do
Else
End If
ActiveCell.Offset(, OffNumber).Select
ActiveSheet.Paste
Loop While (ActiveCell.Address <= OffsetRange.Address)
On Error GoTo ErrorLine
Set Rng = .FindNext(Rng)
Loop While Not Rng Is Nothing And Rng.Address <> FirstAddress
End If
End With
ErrorLine:
On Error GoTo 0
Application.EnableEvents = True
Next ws
Application.CutCopyMode = False
End Sub
I was not able to get an Error 91 using the data set I built from your explanation, maybe a screenshot of the layout could help recreate the issue.
However, I would do something like this, it will look at the value of each cell in the range C4:G25, and if it equals 1, it will paste the symbol stored in Cell A1.
Sub Add_Triangles2()
Dim Rng As Range
Dim rngSymbol As Range
Dim intFindNum As Integer
Dim ws As Worksheet
Set rngSymbol = Range("A1") 'Set range variable to hold address of symbol to be pasted
intFindNum = 1 'Used to hold number to find
Worksheets(1).Activate
For Each ws In Worksheets
Worksheets(ws.Name).Activate
For Each Rng In Range("C4:G25")
If Rng.Value = intFindNum Then
rngSymbol.Copy Rng
End If
Next Rng
Next ws
End Sub
I got it....
Sub Add_TriWorking()
Dim Rng As Range
Dim rngSymbol As Range
Dim intFindNum As Integer
Dim ws As Worksheet
Dim OffNumber As Integer
Dim SetRange As Range
Dim OffsetRange As Range
Set SetRange = Sheets("Sheet1").Range("G25") ' Used to stop the second loop in range
Set rngSymbol = Range("A1") 'Set range variable to hold address of symbol to be pasted
intFindNum = 1 'Used to hold number to find
Worksheets(1).Activate
For Each ws In Worksheets
Worksheets(ws.Name).Activate
For Each Rng In Range("C4:G25")
If Rng.Value = intFindNum Then
rngSymbol.Copy Rng
Rng.Activate
ActiveCell.Copy
Do
OffNumber = Range("A" & ActiveCell.Row)
Set OffsetRange = SetRange.Offset(0, -OffNumber)
If Not ActiveCell.Address < OffsetRange.Address Then
Exit Do
Else
End If
ActiveCell.Offset(, OffNumber).Select
ActiveSheet.Paste
Loop While (ActiveCell.Address <= OffsetRange.Address)
On Error GoTo ErrorLine
End If
Next Rng
ErrorLine:
On Error GoTo 0
Application.EnableEvents = True
Next ws
Application.CutCopyMode = False
End Sub

Resources