did I accidentally create a vba race condition? - vba

I was hoping for some insight on a bug that I haven't been able to replicate.
I have a very complicated worksheet that changes many variables to get a certain cell aa5 to return a string. if any condition is not met, it returns 0. the formula for that cell is
=IF(SUM(AA2:AA4)=0,SubItem,0)
where aa2:aa4 are the conditions which must be 0 to return a string and subitem is a named cell range.
Once it returns a string, I have a module to paste the cell aa5 onto a different sheet export. The problem is that after I run the routine, (it takes about 20 min) I find 0 values in the export sheet.
I have tried manually changing all the variables to the condition that created the error and none appears. I've also tried running through the code line-by-line and can't seem to replicate it there either.
My last straw was inserting in the module that pastes into export sheet
If Worksheets("analysis").Range("aa5").Value = 0 Then
Exit Sub
And still I have 0 values after running!
I am not really a programmer but I have some experience with VBA code, is it possible I've created a race condition where the 0 is copied before the if is updated but it still passes the vba check?

Try to calculate your value before using it in VBA:
With Worksheets("analysis").Range("aa5")
.Calculate
If .Value <> 0 Then
Worksheets("export").Range("A1").Value = .Value
End If
End With

Related

VBA creating formulas referencing a range

After several hours of research, I still can't solve what seems to be a pretty simple issue. I'm new to VBA, so I will be as specific as possible in my question.
I'm working with a DDE link to get stock quotes. I have managed to work out most of the table, but I need a VBA to create a finished formula (i.e., without cell referencing) in order to the DDE link to work properly.
My first code is as follows:
Sub Create_Formulas()
Range("J1").Formula = "=Trade|Strike!" & Range("A1").Value
End Sub
Where J2 is the blank cell and A2 contains the stock ticker. It works fine, but when I try to fill out the rows 2 and bellow, it still uses A1 as a static value.
Sub Create_Formulas()
Dim test As Variant
ticker = Range("A1").Value
'Test to make variable change with each row
'Range("J1:J35").Formula = "=Trade|Strike!" & Range("A1:A35").Value
'not working
Range("J1:J35").Formula = "=Trade|Strike!" & ticker
'not working
End Sub
I couldn't find a way to solve that, and now I'm out of search queries to use, so I'm only opening a new topic after running out of ways to sort it by myself. Sorry if it is too simple.
You are referencing absolute cell adresses here. Like you would do when using $A$1 in a normal excel formula.
What you want to do is:
Dim row as Integer
For row = 1 to 35
Cells(row,10).Formula = "=Trade|Strike!" & Cells(row,1).Value
Next row
This will fill the range J1 to J35 with the formula. Since (row,10) indicates the intersection of row and column 10 (J)
Firstly, in your second set of code, you define a variable "test", but never give it a value.
You assign a value to the variable "ticker", and then never reference it.
Secondly, the value you have assigned to ticker is a static value, and will not change when it is entered in a different row.
Thirdly, I think your issue could be solved with a formula in Excel rather than VBA.
The "INDIRECT" function can be quite useful in situations like this.
Try inserting the formula
=INDIRECT("'Trade|Strike'!"&A1)
into cell A1, then copy down.
Note the ' ' marks around "Trade|Strike". This is Excels syntax for referencing other sheets.

Range SpecialCells ClearContents clears whole sheet instead

I have a sheet in Excel 2010 which is setup as a pseudo form (I didn't create it, I'm just trying to fix it) so formatting suggests that the user can only enter in certain cells. Depending on certain functionality these areas need to be reset, i.e. cleared although formulae and standard/conditional formatting need to be kept. I have defined each of these cells/ranges as named ranges so I can easily loop through them using the following code: -
Public Sub ResetDetailSheet()
Dim nm As Name
With ThisWorkbook
For Each nm In .Names
If Left(nm.Name, 9) = "nmrDetail" Then
Range(nm.Name).SpecialCells(xlCellTypeConstants).ClearContents
End If
Next
End With
End Sub
For some reason instead of clearing the constants from the specific range it is clearing constants from the entire sheet so I am losing all titles/headings. Formulae and standard/conditional formatting are staying as expected.
What am I doing wrong?!?!
As a test using the immediate window I tried clearing a specific cell, e.g.
Range("G7").SpecialCells(xlCellTypeConstants).ClearContents
But this still cleared all constants from the entire sheet.
What am I missing? I don't understand. Maybe I'm being dumb.
Sorry, I can't upload an example. This place is pretty locked down.
Range({any single cell}).SpecialCells({whatever}) seems to work off the entire sheet.
Range({more than one cell}).SpecialCells({whatever}) seems to work off the specified cells.
So, make sure your range has more than a single cell before you clear it - if the range is only a single cell, then check if it .HasFormula; if that's the case then its .Value isn't a constant:
With ThisWorkbook
For Each nm In .Names
If Left(nm.Name, 9) = "nmrDetail" Then
If nm.RefersToRange.Count > 1 Then
nm.RefersToRange.SpecialCells(xlCellTypeConstants).ClearContents
ElseIf Not nm.RefersToRange.HasFormula Then
nm.RefersToRange.ClearContents
End If
End If
Next
End With
Note that I'm using Name.RefersToRange instead of fetching the range by name off the active sheet.

Excel VBA: Insheet function code can not access other cells on sheet

I'm having some issues with an insheet function that I am writing in VBA for Excel. What I eventually am trying to achieve is an excel function which is called from within a cell on your worksheet, that outputs a range of data points underneath the cell from which it is called (like the excel function =BDP() of financial data provider Bloomberg). I cannot specify the output range beforehand because I don't know how many data points it is going to output.
The issue seems to be that excel does not allow you to edit cells on a sheet from within a function, apart from the cell from which the function is called.
I have created a simple program to isolate the problem, for the sake of this question.
The following function, when called from within an excel sheet via =test(10), should produce a list of integers from 1 to 10 underneath the cell from which it is called.
Function test(number As Integer)
For i = 1 To number
Application.Caller.Offset(i, 0) = i
Next i
End Function
The code is very simple, yet nothing happens on the worksheet from which this formula is called (except a #Value error sometimes). I have tried several other specifications of the code, like for instance:
Function test(number As Integer)
Dim tempRange As Range
Set tempRange = Worksheets("Sheet1").Range(Application.Caller.Address)
For i = 1 To number
tempRange.Offset(i, 0) = i
Next i
End Function
Strangely enough, in this last piece of code, the command "debug.print tempRange.address" does print out the address from which the function is called.
The problem seems to be updating values on the worksheet from within an insheet function. Could anybody please give some guidance as to whether it is possible to achieve this via a different method?
Thanks a lot, J
User defined functions are only allowed to alter the values of the cells they are entered into, because Excel's calculation method is built on that assumption.
Methods of bypassing this limitation usually involve scary things like caching the results and locations you want to change and then rewriting them in an after calculate event, whilst taking care of any possible circularity or infinite loops.
The simplest solution is to enter a multi-cell array formula into more cells than you will ever need.
But if you really need to do this I would recommend looking at Govert's Excel DNA which has some array resizer function.
Resizing Excel UDF results
Consider:
Public Function test(number As Integer)
Dim i As Long, ary()
ReDim ary(1 To number, 1 To 1)
For i = 1 To number
ary(i, 1) = i
Next i
test = ary
End Function
Select a block of cells (in this case from C1 through C10), and array enter:
=test(10)
Array formulas must be entered with Ctrl + Shift + Enter rather than just the Enter key.

Runtime Error 1004 Copy Formula Array

I have the following array formula in cell B2 in my Excel spreadsheet:
{=IF(COUNT(IF(ISNUMBER(A30:A1000);IF(B30:B1000>A30:A1000-1;A30:A1000)))>=COUNT(IF(ISNUMBER(A30:A1000);COUNT(B30:B1000>A30:A1000-1;A30:A1000)));COUNT(IF(ISNUMBER(A30:A1000);COUNT(B30:B1000>A30:A1000-1;A30:A1000))))}
Now I want to use the following VBA code to copy this code into cell A2:
Sheets("Sheet1").Range("A2").FormulaArray = Sheets("Sheet1").Range("B2").Formula
However, when I use this code I get runtime error 1004.
Do you have any idea how to solve this issue?
Your array formula is too long to pass along like that as the Range.FormulaArray property.
You don't need to keep repeating all of the conditions. As you cycle through rows 30 to 1000, if the first or second condition fails, the remainder of the formula for that cycle is not processed. IFs in a formula stop processing at the first FALSE.
=IF(COUNT(IF(ISNUMBER(A30:A1000), IF(B30:B1000>A30:A1000-1, A30:A1000)))>=COUNT(B30:B1000>A30:A1000-1,A30:A1000),COUNT(B30:B1000>A30:A1000-1,A30:A1000))
Now the code works just fine.
With Worksheets("Sheet3")
.Range("A2").FormulaArray = .Range("b2").Formula
End With
Note that I could not test this using semi-colons as the system list separator; only with my own system's commas. VBA does not like anything by EN-US regional settings in a .Formula, .FormulaR1C1 or .FormulaArray property. If you still have trouble, use debug,print to see how the .Formula is being returned. If it contains semi-colons, then use,
With Worksheets("Sheet3")
.Range("A2").FormulaArray = Replace(.Range("b2").Formula, Chr(59), Chr(44))
End With

UDF's on different sheets calling eachother return error 2015

I have 2 sheets with 3 UDF's in the first and 2 in the second.
sheet 1 is a monthly matrix with 1 column for each day where people put in their hours on the rows beneath. On 3 specific rows there are UDF's that consolidate the data in the column above, referencing the row as an argument. I do the function call like below to avoid having to make the UDF volatile (which prolongs calculation time greatly if I do), so the UDF's result updates when anything changes in column R:
calculateOvertime(R:R)
On sheet 2, the days of the month are in rows (not columns) where one can put in details about their day IF they did overtime. This is detected by one of the UDF's in sheet 1, so the 2 UDF's here require data calculated by a UDF in sheet 1
I have some strange issues with this setup:
For some reason, when I switch tabs, all cells containing any of these functions show up as #VALUE!. I have to add "Application.CalculateFull" to an event handler that fires whenever the tab is activated:
Private Sub Worksheet_activate()
Application.CalculateFull
End Sub
The UDF's that reference a cell containing another UDF on the other tab, will always get '2015!' as a value, referring to error 2015 (a Value error, because the cell contains #VALUE! when the sheet is not active)
Obviously these 2 issues are connected because when I shift sheets, the UDF-calculated values in the non-active sheet are somehow lost.
My method of getting a value from a cell is as follows. I figure out on which row the label in column A is by using the Find() function
Dim compensationRowIndex As Integer
compensationRowIndex = CInt(othersheet.Range("A1:A250").Find("COMPENSATION").Row)
then I get the value and cast it to a Single
Dim compensation As Single
compensation = CSng(othersheet.Cells(compensationRowIndex , columnIndex).Value)
the variable 'compensation' holds the value 2015 always.
Is there any way around this? Also when I want to print the sheets, all cells containing UDF's are filled with #VALUE!. My guess is : If I can make issue 1 go away, so will issue 2.
ANY help on this is much appreciated. I've been troubleshooting this for almost a whole day now and haven't found a solution googling the symptoms.
Problem Solved!
"ActiveSheet" inside a UDF doesn't mean "The Sheet the UDF-containing cell is on" It literally means "the sheet that's active".
When referencing a UDF on anoter sheet, things go horribly wrong. It was all a matter of replacing ActiveSheet with a variable that's set in an If statement that decides from where the UDF is called.
In my case the second sheet always has the word "Info" in it. When on that sheet, you should go one sheet to the left:
Dim ws As Worksheet
If InStr(ActiveSheet.Name, "Info") = 0 Then
Set ws = ActiveSheet
Else
Set ws = Worksheets(ActiveSheet.Index - 1)
End If
Not a 100% waterproof solution (e.g. what if someone reorders the sheets), but for my purposes it's close enough.
The following code snippet (used within a UDF) should do what you want:
Dim ws As Worksheet
If TypeOf Application.Caller Is Range Then
Set ws = Application.Caller.Parent
End If
The Caller property of the Application object points to the Range covering the cell(s) where the UDF was called from. ws will point to the containing worksheet then.
The If TypeOf clause avoids errors in case the function has been called by something else than a UDF (for instance, another VBA procedure), where Caller might not point to a Range object. In such a case, ws remains unassigned (Nothing).