Excel VBA "Subscript out of range" Run Error '9' - vba

I have tried to implement the following code so that I can read a cell value and then places this value into another cell on the same Worksheet.
The worksheet has the following name: TestUserGuidance (Has no spaces)
The code is as follows:
Sub GuideTest()
Dim dblPower, dblMass, dblRatedSpeed, dblRefLength, dblAwot, dblEngineSpeed, dblRoadSpeed As Double
Dim dblPMR As Double
dblPower = Worksheets("TestUserGuidance").Cell("B1").Value
Worksheets("TestUserGuidance").Cell("E1") = dblPower
End Sub
Could anyone please advise where I'm going wrong?
Thanks

I think vba can't find the TestUserGuidance sheet. If I run the following in the immediate window I get an "Object doesn't support this property or method" error:
?worksheets(1).cell("A1")
If I run this, I get the error you mention "Subscript out of range":
?worksheets("non-existent sheet").cell("A1")
This suggests to me that the active workbook is incorrect since you would have gotten the other error if vba was able to find the worksheet. Can you try adding ThisWorkbook (and also use range)?
dblPower = ThisWorkbook.Worksheets("TestUserGuidance").Range("B1").Value
ThisWorkbook.Worksheets("TestUserGuidance").Range("E1") = dblPower
Another option would be to rename your worksheet:
Then you won't have to worry about the active workbook (and also get intellisence):
shtTestUserGuidance.Range("E1") = shtTestUserGuidance.Range("B1")
Also, I would suggest you make all the corrections suggested by others. You will likely have other issues moving forward otherwise.
Lastly, if the worksheet is like a template and the structure won't change, you'd be better off just using a formula (i.e. E1's formula: =B1). Even if there's conditional logic, it might be easier to maintain as a formula rather than vba code.
Here are some troubleshooting steps:
Toggle a break point on the line where dblPower is set by clicking on the grey area to the left:
Also open the Immediate window if it's not already there by pressing ctrl+g:
When you run the code, it should pause at the break point. Now you can inspect what's in scope. Try typing the following into the Immediate window and press enter:
?Worksheets("TestUserGuidance").Name
or
?ActiveWorkbook.Name
Note that you can also step through the code line by line by pressing F8 while the cursor is somewhere within the subroutine. Bottom line is that you may need to examine the code line by line in order to figure this out.

This is the way to make your code working:
Sub GuideTest()
Dim dblPower, dblEngineSpeed, dblRoadSpeed As Double ' first two are of variant type
Dim dblPMR As Double
dblPower = Worksheets(1).Range("B1").Value
Worksheets(1).Range("E1") = dblPower
End Sub
When you use "E1" or "B1" you should use Range, not Cells. In my answer Worksheets(1) refers to Worksheets("TestUserGuidance").

Only dblRoadSpeed is declared as double.
To fix it, declare all your variables as individual, in vba you can do in 2 ways.
Dim dblPower As Double, dblMass As Double
Or
Dim dblPower As Double
Dim dblMass As Double
Also, you don't need to declare any variable to do what you are doing.
You can copy the value in one step.
Worksheets("TestUserGuidance").Range("E1").Value = Worksheets("TestUserGuidance").Range("B1").Value

Related

VBA Function #VALUE and debugging disabled

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

VBA macro not triggering when target cell changes via form control option buttons

I literally just got my feet wet with VBA as this is my first macro. After many hours of searching, I couldn't seem to find an answer that had a solution that worked for me so here I am.
On Sheet3 I have 3 option buttons in a group box that are linked to cell "B18" on Sheet4 (Sheet4 is hidden to the user, a backstage if you will). When any of the three option buttons are selected, 'Sheet4!B18' gets updated as it should (e.g. 1, 2, or 3). What I want to happen is to have 'Sheet3!B17' changed based upon the value in 'Sheet4!B18', or effectively: IF('Sheet4!B18'=2,SET('Sheet3!B17'="Some Text Here:"),SET('Sheet3!B17'="0%")), but still allow user input in 'Sheet3!B17'. I have one VBA macro on Sheet4 with the following code:
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Worksheet.Range("B18") = 2 Then
Worksheets("Sheet3").Range("B17") = "Some Text Here:"
Else
Worksheets("Sheet3").Range("B17") = "0%"
End If
End Sub
If I manually update 'Sheet4!B18' then the macro gets triggered with the desired results. If I use any of the 3 option buttons on Sheet3, the macro does not get triggered even though the target cell is getting updated.
In my searching I couldn't seem to find anything concrete, but from what I could tell the "Worksheet_Change" function doesn't see changes to cells from form control as changes to the linked cell are considered a "recalculation" as if it were from a formula. I don't know how correct that is, but my searching led me to believe that I would need another macro assigned on the 3 buttons and/or group box that when either of those get selected/changed, it would somehow trigger the working macro on Sheet4.
I thought that perhaps I could create a new macro that I would assign to the group box or option buttons themselves so I tried that and could not get anything to work. I tried adding the above macro code to another sub, Sub mode() and assigning to only the group box, then only the buttons, but nothing happened in either case. I proceeded to try tweaking the code just in case the references were not correct, but saw no change regardless of how I specified the reference. I am not getting any error messages, but nothing gets triggered unless I manually change the value in 'Sheet4!B18'.
Is there a way to get the first macro that I have working on Sheet4 to trigger off of the option buttons changing the target cell value, something like forcing it to look only at that one specific cell for changes? Am I stuck making another macro for the buttons and/or group box to trigger the macro on Sheet4? Am I over-complicating this and there is some built in Excel sheets function(s) that I can use?
IF/THEN is a fine way to do it. VBA also supports ternary logic with the IIF function, like this:
Worksheets("Sheet3").Range("B17") = IIF(Worksheets("Sheet4").Range("B18") = 2, "Some Text Here:", "0%")
That may seem a little difficult to read, but it's a good concept to understand, since it's present in many languages, and usually with a more simplified implementation that makes it very useful and concise.
Also, I would suggest making a couple of other alterations that may make your code easier to write, read and maintain (especially as it becomes more complex).
First, alias the worksheets, something like this:
Dim this as Worksheet: Set this = Worksheets("Sheet3")
Dim that as Worksheet: Set that = Worksheets("Sheet4")
Now you would be able to rewrite your code like this:
If that.Range("B18") = 2 Then
this.Range("B17") = "Some Text Here:"
Else
this.Range("B17") = "0%"
End If
And the ternary approach would now be:
this.Range("B17") = IIF(that.Range("B18") = 2, "Some Text Here:", "0%")
And you can get as specific as you like with the aliases. For instance, you could realias the ranges, instead of just the worksheets, like this:
Dim this as range: Set this = Worksheets("Sheet3").Range("B17")
Dim that as range: Set that = Worksheets("Sheet4").Range("B18")
this = IIf(that = 2, "Some Text Here:", "0%")
Also, I find it easier to use the cells property than the range property, especially when you start having to do cell math. In that case, Range("B17") becomes Cells(17, 2).
You can also change the way the cells are referenced in the spreadsheet by typing Application.ReferenceStyle = xlR1C1 into the immediate window. That way you don't have to mentally convert between A2 style ranges to Cartesian style (2,1).
Sometimes you just have to go through your entire thought process and type everything out before you have an "ah-hah!" moment because that is exactly what I had happen. I said to myself, "Why can't I have just one macro that gets triggered by the option buttons that checks my linked cell then proceeds to update the cell I want?" Well, eventually I was able to find the right code and this is what worked perfectly:
Sub mode() ' mode is the name of this macro
If Worksheets("Sheet4").Range("B18") = 2 Then
Worksheets("Sheet3").Range("B17") = "Some Text Here:"
Else
Worksheets("Sheet3").Range("B17") = "0%"
End If
End Sub
As it turns out, I was overlooking the simple solution and the above macro is all I need once I assigned it to the 3 option buttons in my group box, but not the group box itself. Since users will not have access to the hidden Sheet4 and therefore 'Sheet4!B18' will never have manual user input, the macro I first had on Sheet4 could be removed safely. Due to the fact that the option buttons being chosen is the trigger for the assigned macro, it executes each time the option is changed and only when the option is changed. Perfect!
EDIT:
Thanks to Chris Strickland for some tips for better code! I went on to modify the above into what you see below for slightly better performance (using Cells() instead of Range()), to save the original value to another cell and restore it if option 1 or 3 were selected, used aliases, and finally the IIf operator.
Sub mode() ' mode is the name of this macro
Dim S3 As Worksheet: Set S3 = Worksheets("Sheet3")
Dim S4 As Worksheet: Set S4 = Worksheets("Sheet4")
If IsNumeric(Cells(17, 2)) = True Then
S3.Activate
S4.Cells(18, 3) = Cells(17, 2).Value
End If
S3.Cells(17, 2) = IIf(S4.Cells(18, 2) = 2, "Some Text Here:", S4.Cells(18, 3))
End Sub

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.

VBA: How to get the last used cell by VBA code when the last error occured in a Workbook/Worksheet?

Eventually, I want to move the cell to the location where the last error occured. Edit: Forgot to say that I'm using Excel 2003.
As requested in comments...
Look up the 'Caller' property of the 'Application' object in the Excel VBA help. When you use it from a VBA routine, it will tell you where the call to the routine came from - what Range, Chart, etc.
An important thing to be aware of when using 'Application.Caller' is that it isn't always a Range object. Look at the help, but the property returns a Variant value that can be a Range, String, or Error. (It is a Range object in the case you're interested in, but you'll need to be aware of this.)
Because of the above, and the vagaries of VBA syntax when it comes to objects vs. values, it can be tricky to use 'Application.Caller'. Putting a line like:
Debug.Print Application.Caller.Address
in your code will fail when the caller isn't a Range. Doing something like:
Dim v
v = Application.Caller
will "compile", but will create circular references when the caller is a Range because you're trying to access the value of the calling Range.
This all means that it's probably best to write a little utility function for yourself:
Public Function currentCaller() As String
If TypeOf Application.Caller Is Range Then
Dim rng As Range
Set rng = Application.Caller
currentCaller = rng.Address(External:=True)
Else
currentCaller = CStr(Application.Caller)
End If
End Function
and then call it from your error handlers where you want to know where the call came from.
One more thing - obviously this can only tell you the caller once a VBA routine has actually been called. If you have errors in your calling formulas, Excel will return error values to your cells without ever calling your VBA routines.
Wrap your VBA function in another function that stores the cell location and value as variants. Keep this 'wrapper' function as basic as possible so it won't cause any additional errors.
If you're trying to debug app-crashing errors, the wrapper function could even store those values in a comma-delimited text file. Once stored, Excel can crash all it wants and you'll still know what the cell location and value were since you stored them outside of Excel beforehand.
Could this be done with an error handler?
An example of what I mean below:
sub code1()
on error goto cell A1
end sub

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.