VBA Function #VALUE and debugging disabled - vba

Every time I try to put some arguments in a Function Excel would return #VALUE. Below is one of the examples. Also, I cannot debug when I put arguments in. What is the possible cause? Thank you.
Function lastrowC(SelectedCell As Range)
sc = SelcetedCell.Column
lastrowC = ActiveSheet.Cells(Rows.Count, sc).End(xlUp).Row
End Function

Your code does not work due to a typo. If you add Option Explicit to the top of your code, then try to calculate, VBA will
show you the problem (you misspelled Selected)
Either way, please consider the below code which will target the correct worksheet rather the active worksheet. Your code, as is, will likely look to the wrong sheet to determine the last row under certain circumstances. You need to look at the sheet where the range was selected, which is not always going to be the same as the active sheet
Paste the below code in a Module to call function from excel
Function lastrowC(Target As Range) As Long
With Target.Worksheet
lastrowC = .Cells(.Rows.Count, Target.Column).End(xlUp).Row
End With
End Function

Related

I have failed endlessly trying to write the code in VBA to insert this formula into a cell

The formula:
=IFERROR(IF(OR(E10=0,D9=0),0,NETWORKDAYS(D9,E9))," ")
An example of what I've tried in VBA:
Sub inputWorkdays()
Range("h9").Formula = "=IFERROR(IF(OR(E9=0,D9=0),0,NETWORKDAYS(D9,E9)),""Yes"")"
End Sub
I'm trying to add the formula from above into cell H9.
Select the cell with the formula and write the following:
Sub TestMe
debug.print Selection.Formula
debug.print Selection.FormulaR1C1
End sub
In your case it would give:
=IFERROR(IF(OR(E10=0,D9=0),0,NETWORKDAYS(D9,E9)),"YES")
=IFERROR(IF(OR(R[-4]C[-1]=0,R[-5]C[-2]=0),0,NETWORKDAYS(R[-5]C[-2],R[-5]C[-1])),"YES")
Take the first one and use it like this:
Range("h9").Formula = "=IFERROR(IF(OR(E10=0,D9=0),0,NETWORKDAYS(D9,E9)),""YES"")"
I gather from the comments that there is no error, just "nothing happens". I see nothing wrong with your code. Except...
Range("h9").Formula = "..."
When Range is unqualified like this, you implicitly refer to the ActiveSheet; if the active sheet isn't the sheet you're expecting to write to, then it's easy to conclude that "nothing happens" and that the code doesn't work.
If you have Rubberduck installed (full disclosure: I'm heavily involved with the development of this open-source VBE add-in), you will see that Range in this case is a member of Excel._Global, and an inspection result will tell you that you're implicitly referring to the ActiveSheet:
Range("H9").Formula = "..."
Implicit references to the active sheet make the code frail and harder to debug. Consider making these references explicit when they're intended, and prefer working off object references.
http://rubberduckvba.com/Inspections/Details/ImplicitActiveSheetReferenceInspection
To fix this, qualify the Range call with a Worksheet object - now the Range call is a member of the Excel.Worksheet class:
Dim sheet As Worksheet
Set sheet = ThisWorkbook.Worksheets("Sheet1")
sheet.Range("H9") = "..."
By qualifying Range calls with a worksheet object, you make sure that you're always writing to the worksheet you mean to write to - not the worksheet that happens to be the active one when the code runs.

User-defined Function to change color of a cell

I've seen many users asking questions trying to change the colors of cells using User-defined functions. I was always under the impression that it was not possible to do so. My understanding was that a user-defined function cannot change any properties of a cell except the value of the cell that contains the formula. Subs are what change cells themselves.
However, when playing around with some code to test this, I found that it's not always the case.
Using the simple code:
Function ColorCell(rng As Range)
If rng.Value = 1 Then
ColorCell = False
Else
ColorCell = True
rng.Interior.ColorIndex = 3
End If
End Function
If I enter the function into a cell, I achieve expected results, no cells change colors. However, if I use the Formulas > Insert Function button and navigate to my formula to insert it this way, it does color the targeted cells.
How is this possible, and why did the function behave differently when entered in different ways?
EDIT: this was tested using Excel 2007
use this code...just replace sheet name and try
Sheets("sheet_name").range(j:j).clear
for j=2 to 15
if Sheets("sheet_name").Cells(j, 1).value=1 then
else
Sheets("sheet_name").Cells(j, 1).Interior.ColorIndex = 3
next j
As we all find out sooner or later, in user functions you can't access subs that change things in your spreadsheet directly.
But try this:
Dim ColorMeTarget As Range, ColorMeVal As Long
Public Function ColorMe(ByVal TargetRange As Range, ByVal ColVal As Long)
Set ColorMeTarget = TargetRange
ColorMeVal = ColVal
ColorMe = ColVal
End Function
Public Sub ColorMeSub()
Application.OnTime Now + TimeValue("00:00:05"), "ColorMeSub"
If ColorMeTarget.Interior.Color <> ColorMeVal Then ColorMeTarget.Interior.Color = ColorMeVal
End Sub
If you run the sub first, it will constantly scan the static variables ColorMeTarget and ColorMeVal to see if there is a change. The function ColorMe will set these values. Some additional code is needed in case ColorMeTarget is not yet initialized.
If you get smarter, you could have the function first check to see if there is indeed a change and add the new coloring requests to a stack. Your reoccurring sub can then 'catch up', especially if you have many functions like this.
You can then even have all kinds of additional controls added to your function/macro--EVEN STUFF NOT COVERED BY THE LATEST VERSIONS OF 'CONDITIONAL FORMATING'!!! YAY!!!!
Something to try: In some of my automated macros, I am able to set OnTime through a function but cannot make it work here. It would be cleaner to have the function set the OnTime and not have a reoccuring sub that needs initializing.
I use Worksheet_Change event to detect value change in working range. Example.I want to do something when range A1:A5 has been change. I use below event.
Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Range(<Your working range>)) Is Nothing Then 'Define range that you want to do
'Statement here
...
End If
End Sub
When the range value has been change. It will execute your code.
And other way. Use Conditional Formatting.
could also try the non-script method of auto-coloring a cell based on the condition of the cell (aka Conditional Formatting):

Get the cell reference of the value found by Excel INDEX function

The Problem
Assume that the active cell contains a formula based on the INDEX function:
=INDEX(myrange, x,y)
I would like to build a macro that locates the value found value by INDEX and moves the focus there, that is a macro changing the active cell to:
Range("myrange").Cells(x,y)
Doing the job without macros (slow but it works)
Apart from trivially moving the selection to myrange and manually counting x rows y and columns, one can:
Copy and paste the formula in another cell as follows:
=CELL("address", INDEX(myrange, x,y))
(that shows the address of the cell matched by INDEX).
Copy the result of the formula above.
Hit F5, Ctrl-V, Enter (paste the copied address in the GoTo dialog).
You are now located on the very cell found by the INDEX function.
Now the challenge is to automate these steps (or similar ones) with a macro.
Tentative macros (not working)
Tentative 1
WorksheetFunction.CELL("address", ActiveCell.Formula)
It doesn't work since CELL for some reason is not part of the members of WorksheetFunction.
Tentative 2
This method involves parsing the INDEX-formula.
Sub GoToIndex()
Dim form As String, rng As String, row As String, col As String
form = ActiveCell.Formula
form = Split(form, "(")(1)
rng = Split(form, ",")(0)
row = Split(form, ",")(1)
col = Split(Split(form, ",")(2), ")")(0)
Range(rng).Cells(row, CInt(col)).Select
End Sub
This method actually works, but only for a simple case, where the main INDEX-formula has no nested subformulas.
Note
Obviously in a real case myrange, x and ycan be both simple values, such as =INDEX(A1:D10, 1,1), or values returned from complex expressions. Typically x, y are the results of a MATCH function.
EDIT
It was discovered that some solutions do not work when myrange is located on a sheet different from that hosting =INDEX(myrange ...).
They are common practice in financial reporting, where some sheets have the main statements whose entries are recalled from others via an INDEX+MATCH formula.
Unfortunately it is just when the found value is located on a "far" report out of sight that you need more the jump-to-the-cell function.
The task could be done in one line much simpler than any other method:
Sub GoToIndex()
Application.Evaluate(ActiveCell.Formula).Select
End Sub
Application.Evaluate(ActiveCell.Formula) returns a range object from which the CELL function gets properties when called from sheets.
EDIT
For navigating from another sheet you should first activate the target sheet:
Option Explicit
Sub GoToIndex()
Dim r As Range
Set r = Application.Evaluate(ActiveCell.Formula)
r.Worksheet.Activate
r.Select
End Sub
Add error handling for a general case:
Option Explicit
Sub GoToIndex()
Dim r As Range
On Error Resume Next ' errors off
Set r = Application.Evaluate(ActiveCell.Formula) ' will work only if the result is a range
On Error GoTo 0 ' errors on
If Not (r Is Nothing) Then
r.Worksheet.Activate
r.Select
End If
End Sub
There are several approaches to select the cell that a formula refers to...
Assume the active cell contains: =INDEX(myrange,x,y).
From the Worksheet, you could try any of these:
Copy the formula from the formula bar and paste into the name box (to the left of the formula bar)
Define the formula as a name, say A. Then type A into the Goto box or (name box)
Insert hyperlink > Existing File or Web page > Address: #INDEX(myrange,x,y)
Adapt the formula to make it a hyperlink: =HYPERLINK("#INDEX(myrange,x,y)")
Or from the VBA editor, either of these should do the trick:
Application.Goto Activecell.FormulaR1C1
Range(Activecell.Formula).Select
Additional Note:
If the cell contains a formula that refers to relative references such as =INDEX(A:A,ROW(),1) the last of these would need some tweaking. (Also see: Excel Evaluate formula error). To allow for this you could try:
Range(Evaluate("cell(""address""," & Mid(ActiveCell.Formula, 2) & ")")).Select
This problem doesn't seem to occur with R1C1 references used in Application.Goto or:
ThisWorkbook.FollowHyperlink "#" & mid(ActiveCell.FormulaR1C1,2)
You could use the MATCH() worksheet function or the VBA FIND() method.
EDIT#1
As you correctly pointed out, INDEX will return a value that may appear many times within the range, but INDEX will always return a value from some fixed spot, say
=INDEX(A1:K100,3,7)
will always give the value in cell G3 so the address is "builtin" to the formula
If, however, we have something like:
=INDEX(A1:K100,Z100,Z101)
Then we would require a macro to parse the formula and evaluate the arguments.
Both #lori_m and #V.B. gave brilliant solutions in their own way almost in parallel.
Very difficult for me to choose the closing answer, but V.B. even created Dropbox test file, so...
Here I just steal the best from parts from them.
'Move to cell found by Index()
Sub GoToIndex()
On Error GoTo ErrorHandler
Application.Goto ActiveCell.FormulaR1C1 ' will work only if the result is a range
Exit Sub
ErrorHandler:
MsgBox ("Active cell does not evaluate to a range")
End Sub
I associated this "jump" macro with CTRL-j and it works like a charm.
If you use balance sheet like worksheets (where INDEX-formulas, selecting entries from other sheets, are very common), I really suggest you to try it.

Referring to Dynamic Named Ranges in VBA

I'm having troubling referring to a Dynamic Name Range in VBA.
My ranges are defined as
=OFFSET(Sheet!$B$2,0,0,COUNTA(Sheet!$B:$B)-1,1)
My code should search one range for all entries in another range, the intention being that any missing entries will be added. So far I have
Sub UpdateSummary()
Dim Cell As Range
Dim rngF As Range
Set rngF = Nothing
' Step through each cell in data range
For Each Cell In Worksheets("Aspect").Range("A_Date")
' search Summary range for current cell value
Set rngF = Worksheets("Summary").Range("Sum_Date").Find(Cell.Value) // Does not work
If rngF Is Nothing Then
' Add date to Summary
End If
Set rngF = Nothing
Next Cell
End Sub
The For loop seems to work ok. However, using the .Find method is giving me an error message.
Application-defined or object-defined error
It does work if I replace the named range with a specific range ($B$2:$B$5000), so it seems to be down to how the named range is being passed.
Any ideas would be appreciated.
Thanks.
The error is almost definitely because Excel can't find a named range Sum_Date that refers to a range on a worksheet named Summary. The most common causes are
Sum_Date refers to a sheet other than Summary. Check the RefersTo property of Sum_Date and make sure nothing is misspelled.
There is not a named range Sum_Date, that is, it's misspelled in the VBA code. Check the spelling of the named range in the Name Manager.
There is an error in the RefersTo formula of Sum_Date. It sounds like you already verified that this isn't the case.
I've had the a similar if not the same problem & here's how I solved it:
I first realized that the method I used to create my named range, using the Name Manager, my named range had a scope of Workbook. This is important because, it doesn't belong to the worksheet, & therefore will not be found there.
So, Worksheets("Summary").Range("Sum_Date") would not work for me.
Since my range belonged to the workbook, the way I was able to find is to use ActiveWorkbook.Names("Sum_Date")
For me I used it to remove the formula from named range that I am using in many places. The huge advantage is that named range is updated only once instead of the formula being called for every cell location that ranged is called. Huge time delay difference!
Public last_Selection As String
Private Sub Worksheet_Change(ByVal Target As Range)
'excel data change detection
If Range(last_Selection).Column = 2 Then
'Disable events, so this only executes once
Application.EnableEvents = False
'This can be done with a complex formula in a cell,
'but this is easily understood
Range("B1").End(xlDown).Select
ActiveWorkbook.Names("last_Entry").Value = ActiveCell.Row
'Re-enable so this routine will execute on the next change
Application.EnableEvents = True
End If
End Sub
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
'constantly store the last cell to know which one was previously edited
last_Selection = Target.Address
End Sub
I know this is a very old thread, but I had the same issue today and I was looking for solution for quite a long time. So maybe this will help someone.
The named "range" defined by the =OFFSET(...) formula is actually a named FORMULA, so in VBA you have to evaluate it first to get the range. E.g.:
Set rgNamedRange = Worksheets("Summary").Evaluate("Sum_Date")
Credits to a guy named "shg" from mrexcel.com, who got me on right track. :)
I have been experimenting with this for a few days and eventually I came up with the following. It may not be the most efficient but it did work for me!
The named range of "OhDear" was set up in the normal way
Dim vItem As Variant
Set vItem = Names("OhDear")
Debug.Print vItem.Name
Worth a try don't you think!
This does not work if instead of using a variant you use something like: Dim Nm as Name: Set Nm = Names("OhDear"). Any variations using 'Nm' failed!!!

How to get/set unique id for cell in Excel via VBA

I want to have/define a unique id for each data row in my Excel data sheet - such that I can use it when passing the data onwards and it stays the same when rows are added/deleted above it.
My thoughts are to use the ID attribute of Range (msdn link)
So, I have a user defined function (UDF) which I place in each row that gets/sets the ID as follows:
Dim gNextUniqueId As Integer
Public Function rbGetId(ticker As String)
On Error GoTo rbGetId_Error
Dim currCell As Range
'tried using Application.Caller direct, but gives same error
Set currCell = Range(Application.Caller.Address)
If currCell.id = "" Then
gNextUniqueId = gNextUniqueId + 1
'this line fails no matter what value I set it to.
currCell.id = Str(gNextUniqueId)
End If
rbGetId = ticker & currCell.id
Exit Function
rbGetId_Error:
rbGetId = "!ERROR:" & Err.Description
End Function
But this fails at the line mentioned with
"Application-defined or object-defined error"
I thought perhaps its one of those limitations of UDFs, but I also get the same error if I try it from code triggered from a ribbon button...
Any other suggestions on how to keep consistent ids - perhaps I should populate the cells via my ribbon button, finding cells without IDs and generating/setting the cell value of those...
EDIT:
As Ant thought, I have the sheet protected, but even in an unlocked cell it still fails. Unprotecting the sheet fixes the problem.... but I have used "Protect UserInterFaceOnly:=True" which should allow me to do this. If I manually allow "Edit Objects" when I protect the sheet it also works, but I don't see a programmatic option for that - and I need to call the Protect function in AutoOpen to enable the UserInterfaceOnly feature...
I guess I need to turn off/on protect around my ID setting - assuming that can be done in a UDF... which it seems it cannot, as that does not work - neither ActiveSheet.unprotect nor ActiveWorkbook.unprotect :(
Thanks in advance.
Chris
Okay...
It does appear that if the sheet is locked, macros do not have write access to low-level information such as ID.
However, I do not think it is possible to unprotect the sheet within a UDF. By design, UDFs are heavily restricted; I think having a cell formula control the sheet protection would break the formula paradigm that a cell formula affects a cell only.
See this page on the Microsoft website for more details.
I think this limits your options. You must either:
give up sheet protection
give up the UDF, use a Worksheet_Change event to capture cell changes and write to ID there
use a UDF that writes the ID into the cell value, rather than save to ID
The UDF approach is fraught with problems as you are trying to use something designed for calculation of a cell to make a permanent mark on the sheet.
Nonetheless, here's an example of a UDF you can use to stamp a "permanent" value onto a cell, which works on unlocked cells of a protected sheet. This one only works for single cells (although it could be adapted for an array formula).
Public Function CellMark()
Dim currCell As Range
Set currCell = Range(Application.Caller.Address)
Dim myId As String
' must be text; using .value will cause the formula to be called again
' and create a circular reference
myId = currCell.Text
If (Trim(myId) = "" Or Trim(myId) = "0") Then
myId = "ID-" & Format(CStr(gNextUniqueId), "00000")
gNextUniqueId = gNextUniqueId + 1
End If
CellMark = myId
End Function
This is quite flawed though. Using copy or the fillbox will, however, retain the previous copied value. Only by explicitly setting cells to be a new formula will it work. But if you enter in the formula into the cell again (just click it, hit ENTER) a new value is calculated - which is standard cell behaviour.
I think the Worksheet_Change event is the way to go, which has much more latitude. Here's a simple example that updates the ID of any cell changes. It could be tailored to your particular scenario. This function would need to be added to every Worksheet the ID setting behaviour is required on.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim currCell As Range
Set currCell = Target.Cells(1, 1)
Dim currId As String
currId = currCell.ID
If Trim(currCell.ID) = "" Then
Target.Parent.Unprotect
currCell.ID = CStr(gNextUniqueId)
Target.Parent.Protect
gNextUniqueId = gNextUniqueId + 1
End If
End Sub
Last note; in all cases, your ID counter will be reset if you re-open the worksheet (at least under the limited details presented in your example).
Hope this helps.
Concur with Ant - your code works fine here on Excel 2003 SP3.
I've also been able to use:
Set currCell = Application.Caller
If Application.Caller.ID = "" Then
gNextUniqueId = gNextUniqueId + 1
'this line fails no matter what value I set it to.
currCell.ID = Str(gNextUniqueId)
End If
Aha! I think I have it.
I think you're calling this from an array formula, and it only gets called ONCE with the full range. You can't obtain an ID for a range - only a single cell. This explains why Application.Caller.ID fails for you, because Range("A1:B9").ID generates an Application-defined or object-defined error.
When you use Range(Application.Caller.Address) to get the "cell" you just defer this error down to the currCell.ID line.
I think we may have a few issues going on here, but I think they are testing issues, not problems with the code itself. First, if you call the function from anything other than a Cell, like the immediate window, other code, etc. Application.Caller will not be set. This is what is generating your object not found errors. Second, if you copy/paste the cell that has the function, they you will by copy/pasting the ID too. So wherever you paste it to, the output will stay the same. But if you just copy the text (instead of the cell), and then paste then this will work fine. (Including your original use of Application.Caller.)
The problem is with Application.Caller.
Since you are calling it from a user defined function it is going to pass you an error description. Here is the remark in the Help file.
Remarks
This property returns information about how Visual Basic was called, as shown in the following table.
Caller - Return value
A custom function entered in a single cell - A Range object specifying that cell
A custom function that is part of an array formula in a range of cells - A Range object specifying that range of cells
An Auto_Open, Auto_Close, Auto_Activate, or Auto_Deactivate macro - The name of the document as text
A macro set by either the OnDoubleClick or OnEntry property - The name of the chart object identifier or cell reference (if applicable) to which the macro applies
The Macro dialog box (Tools menu), or any caller not described above - The #REF! error value
Since you are calling it from a user defined function, what is happening is Application.Caller is returning a String of an error code to your range variable curCell. It is NOT causing an error which your error handler would pick up. What happens after that is you reference curCell, it's not actually a range anymore. On my machine it tries setting curCell = Range("Error 2023"). Whatever that object is, it might not have an ID attribute anymore and when you try to set it, it's throwing you that object error.
Here's what I would try...
Try removing your error handler and see if VBA throws up any exceptions on Range(Application.Caller.Address). This won't fix it, but it could point you in the right direction.
Either through logic or Application.ActiveCell or however you want to do it, reference the cell directly. For example Range("A1") or Cells(1,1). Application.Caller.Address just doesn't seem like a good option to use.
Try using Option Explicit. This might make the line where you set curCell throw up an error since Range(Application.Caller.Address) doesn't look like it's passing a range back, which is curCell's datatype.
I have found that if I protect the sheet with "Protect DrawingObjects:=False", the UDF can set the Id. Strange.
Thanks for all the help with this.