I need to find out cell addresses which have called a specific function.
If my function is MyFunction (arg1, arg2),
I should be able to find the cell addreses using name "MyFunction".
Please help me to find out what would be the most efficient way to do this.
Thank You
I asked the same question, but worded differently so I didn't find this one when I searched.
The interesting point is that the answer I got is, I think, better than the looping one for two reasons:
Faster - no looping. Just use Application.Caller
Works if your function is called from more than one cell
You can loop through a range of cells, looking for that particular function:
Dim name as String
Dim searchRange as Range
Dim row as Integer
Dim col as Integer
name = "MyFunction" ''// for example
Set searchRange = Range("A1:P:50") ''// for example
For row = 1 to searchRange.Rows.Count
For col = 1 to searchRange.Columns.Count
If Left(searchRange.Cells(row, col).Formula, Len(name)) = name Then
''// do something with this cell
End If
Next col
Next row
Related
I'd think that the following code should produce a diagonal of numbers, and I am wondering why this code doesn't work as intended:
Sub RangeExample()
Dim a As Range
Set a = Sheets(1).Range("a1")
a.Value = 0
Dim i As Integer
For i = 1 To 100
a = a.Offset(1, 1)
a.Value = i
Next i
End Sub
I understand there are many ways of producing a diagonal of numbers, I'm not asking how to do that.
What I'm asking is how I would change my range variable a to become a different range, and do that iteratively. It seems to me that as a.offset(1,1) returns a range object that's one over and one down, I should be able to reassign a as this new range, assign a value, and move on.
Your current issue is that you're missing a Set:
Set a = a.Offset(1, 1)
Note that you could also just use i and not reSet:
a.Offset(i, i).Value = i
Another option is to use Cells, e.g.
Sheets(1).Cells(i + 1, i + 1).Value = i
There's more than one way to skin a cat - pick whatever is easiest and most intuitive to future you.
Thanks for the answer, I didn't know set was required in this case. The specific answer I was looking for I have now found at:
What does the keyword Set actually do in VBA?
Specifically, the following answer by LeppyR64. "Set is used for setting object references, as opposed to assigning a value."
I didn't know that equality alone only impacted the value of the range object a. To actually change the range a was referencing, I needed set because a is supposed to refer to a new range object.
the issue has already been addressed by #BigBen
but you could avoid re-setting the range at every iteration by means of With...End With block
Option Explicit
Sub RangeExample()
Dim i As Long
With Sheets(1).Range("a1") ' reference topleftmost cell
.Value = 0 ' write referenced cell value
For i = 1 To 100
.Offset(i, i).Value = i 'write referenced cell current offset value
Next
End With
End Sub
I have a situation where I am using these equations
=CELL("address",INDEX(J61:W61,MATCH(LARGE((J61:W61),1),J61:W61,0)))
To find the cell address of the largest value cell in that range.
For example, it gives me this as a result. $T$61 (which contains the highest value).
I now want to use that information and offset it upwards 51 rows to extract the title for this column. How can I use this information and a formula, or VBA to find the content of $T$10 in this case?
Obtaining the cell address in this case is not necessary. What's more, CELL is a volatile function, and so should be avoided if possible.
Simply:
=INDEX(J10:W10,MATCH(MAX(J61:W61),J61:W61,0))
Regards
I ended up finding a way to use vba to do what I need
Sub Top_3_Problems()
First_Address = Range("D62")
Second_Address = Range("D63")
Third_Address = Range("D64")
First_problem = Range(First_Address).Offset(-51)
Second_problem = Range(Second_Address).Offset(-51)
Third_problem = Range(Third_Address).Offset(-51)
Range("B40") = First_problem
Range("B41") = Second_problem
Range("B42") = Third_problem
End Sub
Quick example of how to do this in VBA
This is just a simple example. If implemented, objects need to be properly qualifed with a worksheet and the Find method needs to have options added
Option Explicit
Sub MaxHeader()
Dim Found As Range, SearchRange As Range
Set SearchRange = Range("J61:W61")
Set Found = SearchRange.Find(WorksheetFunction.Max(SearchRange))
If Not Found Is Nothing Then MsgBox Cells(10, Found.Column)
End Sub
This is the data I have.
I want to see if I can look up all the Korean stocks. So the input value should be "SOUTH KOREA" and the result table/array should look like this:
Apparately, I can't use VLOOKUP because 1) the lookup value (country name) is on the right column and 2) it will return the first value (STOCK A) only. I need to return all the values (STOCK A, STOCK B, STOCK E, AND STOCK F). I would really appreciate help!
I mean it seems far more logical to utilize the inbuilt filter function, but if you absolutely insist on using formulas / code then:
Just use this simple procedure
Private Sub sort_wares(From As String, toColumn As Integer)
Dim ws As Worksheet: Set ws = Sheets("Sheet1")
Dim tbl As ListObject: Set tbl = ws.ListObjects("Table1")
Dim index As Integer
index = 2
For Each Rng In tbl.ListColumns(2).DataBodyRange
If Trim(LCase(Rng)) = Trim(LCase(From)) Then
ws.Cells(index, toColumn + 1) = Rng
ws.Cells(index, toColumn) = Rng.Offset(0, -1)
index = index + 1
End If
Next Rng
End Sub
You can easily invoke the procedure, so for example if we from the following data wanted to look up all the wares from Zimbabwe and print them to the 5th Column (E) we would do the following
Call sort_wares("Zimbabwe", 5)
You can use below function. Note that it is array formula which needs to be committed by hitting CTRL+SHIFT+ENTER simultaneously. Excel will wrap formula with {} if done correctly!
=IFERROR(INDEX($A$1:$A$1000,SMALL(IF($B$1:$B$1000="SOUTH KOREA",ROW($B$1:$B$1000)),ROWS($A$1:A1))),"")
You can replace "SOUTH KOREA" by a cell reference.
Adjust the ranges to suit and copy down as much as you need.
I have a column number , say columnNumber = 4 . I need the used range of this column. I know how to find the last used row, and I could convert the column number to a column number like so
ColumnLetter = Split(Cells(1, ColumnNumber).Address, "$")(1)
LastRow = sht.Cells(sht.Rows.Count, ColumnLetter).End(xlUp).Row
and then build an address like so
rngaddy = ColumnLetter & "1:" & ColumnLetter & LastRow
and finally do
Range(rngaddy)
But is there an easier way to find the complete used range of a column given it's number ?
Dim rngaddy As Range
With Sheet1
Set rngaddy = .Range(.Cells(1, 4), .Cells(.Rows.Count, 4).End(xlUp))
End With
and if, for some reason, you want to see the address in A1 notation, merely:
debug.print rngaddy.address
Note that in doing it this way, rngaddy is, itself, the range object and not a string. So no need to do Range(rngaddy)
You could return the last populated cell is in columns # col with this:
MsgBox Cells(sht.Rows.Count,col).End(xlUp).Address
If you want to return the first populated cell as well, you could use:
MsgBox IIf(IsEmpty(Cells(1,col)),Cells(1,col).End(xlDown),Cells(1,col)).Address
Therefore this would return only the "used" range of Column #4 (D):
Sub Example_GetUsedRangeOfColumn()
Const col = 4
Dim sht As Worksheet
Set sht = Sheets("Sheet1")
MsgBox Range(IIf(IsEmpty(Cells(1, col)), Cells(1, col).End(xlDown), _
Cells(1, col)), Cells(sht.Rows.Count, col).End(xlUp)).Address
End Sub
So with this example:
...the above procedure would return: .
My preferred method is to use ListObjects aka Excel Tables to hold any input data whenever I possibly can. ListObjects are named ranges that Excel automatically maintains on your behalf, and because they grow automatically when new data is added, they give you a very robust way of referencing ranges in Excel from VBA, that is more immune to users doing things that might otherwise break code reliant on the .End(xlUp) approach.
? Range("MyTable").ListObject.ListColumns("Column 1").DataBodyRange.Address
$A$3:$A$7
Often I'll give the column concerned a named range of its own, in case the user (or a developer) later wants to change the Table column name, and use that name in my code instead.
? Range("FirstColumn").Address
$A$3:$A$7
If somebody (perhaps me) adds rows/columns above/left of the range of interest or shuffles the order of Table columns around, or changes the name of a column, the code still references the intended range and doesn't need to be changed.
? Range("FirstColumn").Address
$C$4:$C$8
? Range(Range("FirstColumn").Address & ":" & Range("FirstColumn").EntireColumn.cells(1).address).Address
$C$1:$C$8
Granted, that method of getting the range from the top cell (which may be above the ListObject) to the bottom of the column concerned is kinda long, but once you start using ListObjects more in your code you normally don't care what is above or below them...you just want the goods held inside.
I haven't used .End(xlUp) in years, other than to find where my data ends should I be in the process of turning it into a ListObject. But I'm a ListObject evangelist...your mileage may vary :-)
to get the real UsedRange of a columns you could use:
With Columns(columnNumber).SpecialCells(xlCellTypeConstants)
Set rngaddy = .Parent.Range(.Areas(1), .Areas(.Areas.Count))
End With
where rngaddy is a Range object
of course what above would fail if the column has no "constant" cells, then you may want to add some error trapping or entry check (e.g. If WorksheetFunction.CountA(Columns(columnNumber)) = 0 Then Exit Sub
Or
Option Explicit
Public Sub test()
Const columnNumber As Long = 4
Dim rngaddy As Range
Set rngaddy = Intersect(Columns(2), ActiveSheet.UsedRange): Debug.Print rngaddy.Address
End Sub
I am trying to average non-contiguous cells as shown.
I am taking the average of columns A and C for each row. I am trying to do the same but with a named range (including columns A and C), because my actual data have thousands of columns and it will be hell to write the formula let alone for the users to understand what is being averaged.
Obviously, I don't understand how indexing a named range works. I expected that index(RangeAC,2) would give me the second row of values in RangeAC. Instead, I get the second row in column A. Trying index(RangeAC,2,2) results in an error.
Is it possible to get this average with a named range or do I need a different approach?
I don't know if I'm missing something, but isnt this as simple as using the Excel intersect operator?:
=AVERAGE(RangeAC 8:8)
Put in the first row of the named Range data(which seems to be 8:8 in your case), and copy down...
Isnt that the same as the suggested VBA UDF from MrExcel forums?
Option 1:
Lets say the name of your range is my_data like this one:
This is the formula to use:
Public Function calculate_avg(rng As Range) As Double
calculate_avg = WorksheetFunction.Average(Range(rng.Cells(1, 1).Address, Cells(rng.Rows.Count + rng.Cells(1, 1).Row - 1, rng.Columns.Count + rng.Cells(1, 1).Column - 1).Address))
End Function
Option 2:
Your named range is the following:
You want the average of the 2. and the 3. column. (C&D).
This is how you get it:
Option Explicit
Public Function calculate_avg(rng As Range, Optional l_starting_col As Long = 1, Optional l_end_col As Long = 1) As Double
Dim my_start As Range
Dim my_end As Range
Set my_start = Cells(rng.Cells(1, 1).Row, l_starting_col + rng.Cells(1, 1).Column - 1)
Set my_end = Cells(rng.Cells(rng.Rows.Count, l_end_col).Row, rng.Columns.Count - rng.Cells(1, l_end_col).Column + l_end_col)
'Debug.Print my_start.Address
'Debug.Print my_end.Address
calculate_avg = WorksheetFunction.Average(Range(my_start, my_end))
End Function
You pass as arguments the starting and the end column. Thus something like this:
?calculate_avg([my_test_big],2,3) in the immediate window returns 72,5. The same can be used as an Excel formula. Good luck! :)+
Option 3
Public Function calculate_avg_row(rng As Range, Optional l_row As Long = 1) As Double
Dim my_start As Range
Dim my_end As Range
Set my_start = Cells(rng.Cells(l_row, 1).Row, rng.Cells(l_row, 1).Column)
Set my_end = rng.Cells(l_row, rng.Columns.Count)
Debug.Print my_start.Address
Debug.Print my_end.Address
calculate_avg_row = WorksheetFunction.Average(Range(my_start, my_end))
End Function
This one works like this:
calculate_avg_row([test_rng],5)
And gives the average of the 5th row of the named range, including all columns of the named range.
Could you not attach a name to a formula as well? If so,go to the "Formula" tab , "Define Name"and type in the "Refers to" box =Average(A1,C1)). In the name box, you could name it "Average" or whatever you choose to call it.The references would continue to be non-contiguous if you dragged to the right or down the sheet. I am not sure if that is exactly what you're seeking.
I appreciate everyone's help. This problem has taken me considerably longer than I was willing to spend on it. Non-contiguous ranges are a nightmare in Excel.
Eric at Mr Excel proposed the most elegant working solution - just one line of VBA.
The third parameter of the Index function Reference form can be used to specify the area number:
= AVERAGE( INDEX(RangeAC, ROW()), INDEX(RangeAC, ROW(), , 2) )
or if RangeAC does not start at row 1, something like:
= AVERAGE( INDEX(RangeAC, ROW()-ROW(RangeAC)+1), INDEX(RangeAC, ROW()-ROW(RangeAC)+1, , 2) )