recursive tree evaluation of excel formula - vba

I have a top cell which contains a formula referring to other cells.
Those cells contain formulas referring to other cells.
I would like to evaluate the top cell while replacing in the whole evaluation tree every occurrence of a cell, say A4, by another cell, say A5.
I know you can navigate the tree of dependants, but can you evaluate the formula after performing a replacement ?

Why not just set Calculation to Manual, make the changes to the cells and then call Application.Calculate. Seems a lot simpler and safer to use Excel's built-in dependency tracking rather than try to re-invent it.

Yes You can use the Application.Evaluate to evaluate a formula. See this example
Sub Sample()
Dim frmla As String
frmla = Range("E1").Formula
frmla = Replace(frmla, "A2", "A1")
MsgBox Application.Evaluate(frmla)
End Sub

Related

Excel formula within VBA

I have quite a few cells that contain formula, then with VBA the outcome of this formula is the value for a variable, like so:
On sheet in cell AS4:
=SUMPRODUCT(MAX((ROW($AE$4:$AE$997))*($AE$4:$AE$997<>"")))
and then in my VBA:
numRows = ws.Range("AS4").Value
However this is starting to get hard to keep track of which cell is feeding which variable, avoiding overwriting those cells on the sheet by accident, etc.
I need to be able to perform this calculation within VBA if I can, removing the need to have "calculation cells" on my sheet.
I have discovered there is a way to use formula with WorksheetFunction, but only found simple examples of this and cannot adapt it to my situation above.
numRows = WorksheetFunction.SumProduct(MAX((ROW($AE$4:$AE$997))*($AE$4:$AE$997<>"")))
Is not going to work...
Is there a way to do this, or am I better scrapping the idea of using formula and using a pure VBA method?
With help from SJR this was the answer:
numRows = [=SUMPRODUCT(MAX((ROW(Weights!$AE$4:$AE$997))*(Weights!$AE$4:$AE$997<>"""")))]
A bit more research taught me that evaluate(" ") can be just replaced with square brackets [ and ]. Although, if I had variables in the mix of this formula or the formula wasn't constant then I would have to use Evaluate.
I also needed to add the sheet name to the formula as this formula was no longer functioning within the sheet and AE4:AE997 was no longer referring to the correct sheet.
Doubling up on quotes is also necessary as it is code and sees " differently to a formula on the sheet

Macro to find and delete column based on cell value

I have two sheets in an Excel workbook. I would like a VBA code to search for the content of cell J19 in Sheet2 and delete the column with the matching cell in Sheet1. I have been searching all around Google to look for some VBA codes, but I haven't found anything yet that works.
Does anybody here know how to do this? I have zero experience in VBA.
You could try this code to perform such a task.
Sub FindMatchAndThenDeleteColumn()
FindContent = Worksheets("Sheet2").Range("J19")
For Each Cell In Worksheets("Sheet1").UsedRange.Cells
If Cell.Value = FindContent Then Columns(Cell.Column).Delete
Next
End Sub
Put the code above in your workbook's module. Note that FindContent and Cell are randomly chosen here, you can use any names. I use
For Each Cell In Worksheets("Sheet1").UsedRange.Cells
to loop through every cell that is in use in Sheet1. It will check everything in the range from cell A1 to the last cell with data (the bottom right-most cell). If the content of cell J19 is a text, you can declare the variable FindContent as a String type, i.e. Dim FindContent As String. If it's a number, you can declare it as a Long type or a Single type or any number type that fits the content. Here I don't declare it which means it defaulting to a Variant type. And since you're a beginner in VBA, you may learn it from Excel Easy. Hope this helps.

Convert Excel Formula to VBA

I have this formula that looks at various criteria across multiple columns and checks to see that if all the all the criteria match, it will paste data from one column to another. I've tried a couple ways to get it into VBA, but I can't seem to get anything to work. Thanks!
=INDEX($D$2:$D$1112,MATCH(1,($A$2:$A$1112=$U$7)*($C$2:$C$1112=$W$7)*($B$2:$B$1112=F3),0))
You are not going to be able to use that array formula to directly return a value to a cell. VBA does not process an array formula the way that the worksheet can. The best method is to use the worksheet's processing or one of the Application Evaluate methods.
Your lack of a worksheet to reference troubles me. When a formula is in a worksheet cell, it knows what worksheet it is on. When using formulas within VBA, the parent worksheet is a 'best guess' without explicit worksheet referencing.
Here are three methods to put the results from that array formula into Z2:Z4 on the active worksheet. Remember that these cell references should be modified to include the worksheet name.
With ActiveSheet
'this simply puts the formula into the worksheet then reverts the cell from the formula to the returned formula value
.Range("Z2").FormulaArray = "=INDEX($D$2:$D$1112, MATCH(1, ($A$2:$A$1112=$U$7)*($C$2:$C$1112=$W$7)*($B$2:$B$1112=F3), 0))"
.Range("Z2") = .Range("Z2").Value
'this uses the 'square bracket' method of evaluating a formula on-the-fly
'the formula being evaluated can be array or non-array
'this method is does not like building a formula string from pieces of text
.Range("Z3") = [INDEX($D$2:$D$1112, MATCH(1, ($A$2:$A$1112=$U$7)*($C$2:$C$1112=$W$7)*($B$2:$B$1112=F3), 0))]
'similar to the method directly above, Application.Evaluate does just that.
'the formula being evaluated can be array or non-array
'this method is easier to build a formula string from pieces of text
.Range("Z4") = Application.Evaluate("INDEX($D$2:$D$1112, MATCH(1, ($A$2:$A$1112=$U$7)*($C$2:$C$1112=$W$7)*($B$2:$B$1112=F3), 0))")
End With
You need 2 changes:
(1) To use a function in VBA when it is available in native Excel, you need to preface each function with Application.WorksheetFunction. ie:
x = Application.WorksheetFunction.Sum(y,z)
(2) To reference a cell within a sheet, in VBA, you need to access it specifically, in one of a few ways. The simplest for our purposes is the RANGE property, as follows:
x = Application.WorksheetFunction.Sum(Range("A1:A2"))
So to put those two changes together, your formula would look like this:
=Application.WorksheetFunction.INDEX(Range("$D$2:$D$1112",Application.WorksheetFunction.MATCH(1,(RANGE("$A$2:$A$1112"=RANGE("$U$7")*(Range("$C$2:$C$1112"=Range("$W$7")*(Range("$B$2:$B$1112"=Range("F3"),0))
Although I see now having gone through this that you seem to be using an Array Formula - not sure if any special jigging is required to get that to work.

I have created a macro using the if function but it doesn't seem to work. What am I misisng?

I created a macro to show the following:
If (I3<>0,I3*G3,H3*G3) and this repeats itself for cell N3, R3, V3, Z3 etc.
Option Explicit
Sub Eg()
Range("J3, N3,R3, V3,Z3,AD3,AH3,AL3,AP3,AT3,Ax3,BB3,XF3,BJ3").Formula = "=IF(RC[-1]<>0,RC[-1]*RC[-3],RC[-2]*RC[-3])"
End Sub
However this doesn't seem to work.
Let me explain a bit more how this should work:
This report needs to be downloaded from an application.
The macro needs to be attached to this report so that when I download the report the macro automatically runs this formula in the appropriate columns.
Also I'll have to populate the spreadsheet for all the rows with this formula.
The columns where the formula should sit are not blank but this needs to be catered for in the report automatically once the macro is run.
What am I missing here?
When you use Range.Formula = formulaAsString, and the range refers to multiple cells, you specify the exact formula string as required by (only) the first cell in the range, while appropriately using relative vs. absolute cell references because the assignment to multiple (succeeding) cells will occur as if you were pasting the first cell in the range into the others, exactly as if you'd done a copy & paste without VBA -- you use absolute addressing A1 vs. A$1 vs. $A$1 vs. $A1 etc... as desired to achieve the right alteration of the formula for the succeeding cells.
For example,
Range ("A1, C1, E1").Formula = "=A2+$A2"
will have the same result as
Range ( "A1" ).Formula = "=A2+$A2"
Range ( "C1" ).Formula = "=C2+$A2"
Range ( "E1" ).Formula = "=E2+$A2"
You are mixing up .Formula with .FormulaR1C1! Your string is R1C1 style, but you assign it to the A1 style formula.
Therefore, simply, change it to:
Range("J3, N3,R3, V3,Z3,AD3,AH3,AL3,AP3,AT3,Ax3,BB3,XF3,BJ3").FormulaR1C1 = _
"=IF(RC[-1]<>0,RC[-1]*RC[-3],RC[-2]*RC[-3])"
or
Range("J3, N3,R3, V3,Z3,AD3,AH3,AL3,AP3,AT3,Ax3,BB3,XF3,BJ3").Formula = _
"=IF(I3<>0,I3*G3,H3*G3)"
As Erik points out in his answer, also the later will work and adjust the formula for each cell in the same way (which is not necessary in R1C1 as the formula stays the same anyway...)

Excel VBA - Interpret "N/A" Values

I am traversing a spreadsheet which contains a column of prices, in the form of double types. I am trying to locate a missing value which is shown on the spreadsheet as "n/a", but it is not letting me interpret this as a string type.
The cell containing "n/a" seems to be an integer type; how can I read this?
If all you want to do is to check for the error value then:
Application.WorksheetFunction.IsNA(rngToCheck.Value)
where rngToCheck is the cell which you want to check for the #N/A error value
(There's a list of the worksheet functions which can be called from Excel VBA here)
You could also examine rngToCheck.Text as this will contain the string "#N/A"
If instead, you want to read the formula in the cell which generated the #N/A then rngToCheck.Formula would do that
A cell containing #N/A is retrieved by VBA as a variant containing an error code
In general its usually best to assign Excel cells to Variants because a cell can contain a number(double), logical, string or error and you cannot tell in advance what the cell wil contain.
You can prepare the spreadsheet you like to check as described below and evaluate the special cells containing the IS Functions, it is easy to check them for True or False in VBA. Alternatively, you can write your own VBA function as shown below.
There are Excel functions which check cells for special values, for example:
=ISNA(C1)
(assumed that C1 is the cell to check). This will return True if the cell is #N/A, otherwise False.
If you want to show whether a range of cells (say "C1:C17") has any cell containing #N/A or not, it might look sensible to use:
=if(ISNA(C1:C17); "There are #N/A's in one of the cells"; "")
Sadly, this is not the case, it will not work as expected. You can only evaluate a single cell.
However, you can do it indirectly using:
=if(COUNTIF(E1:E17;TRUE)>0; "There are #N/A's in one of the cells"; "")
assuming that each of the cells E1 through E17 contains the ISNA formulas for each cell to check:
=ISNA(C1)
=ISNA(C2)
...
=ISNA(C17)
You can hide column E by right-clicking on the column and selecting Hide in Excel's context menu so the user of your spreadsheet cannot see this column. They can still be accessed and evaluated, even if they are hidden.
In VBA you can pass a range object as RANGE parameter and evaluate the values individually by using a FOR loop:
Public Function checkCells(Rg As Range) As Boolean
Dim result As Boolean
result = False
For Each r In Rg
If Application.WorksheetFunction.IsNA(r) Then
result = True
Exit For
End If
Next
checkCells = result
End Function
This function uses the IsNA() function internally. It must be placed inside a module, and can then be used inside a spreadsheet like:
=checkCells(A1:E5)
It returns True, if any cell is #N/A, otherwise False. You must save the workbook as macro-enabled workbook (extension XLSM), and ensure that macros are not disabled.
Excel provides more functions like the above:
ISERROR(), ISERR(), ISBLANK(), ISEVEN(), ISODD(), ISLOGICAL(),
ISNONTEXT(), ISNUMBER(), ISREF(), ISTEXT(), ISPMT()
For example, ISERR() checks for all cell errors except #N/A and is useful to detect calculation errors.
All of these functions are described in the built in help of Excel (press F1 and then enter "IS Functions" as search text for an explanation). Some of them can be used inside VBA, some can only be used as a cell macro function.