I'm trying to create some conditional formatting at runtime (huzzah) for an add-in (double huzzah), and have found that, apparently, some functions cannot be used as they would in a normal worksheet. (I just get an invalid procedure call error 5 when trying to create the CF referencing a VBA function I could call in a cell, even though it's in the add-in and not the workbook; I can create the CF fine with a built-in function.) The clearest confirmation I've found for this is here, but it doesn't really explain what the problem is; that's the esoteric part, would love to hear more about what I can expect with this.
The rubber-meets-road part is: can I avoid VBA altogether, and use a series of Excel-only, built-in functions to verify whether a given cell contains a constant (i.e. a value entered by a user), a formula (i.e. some kind of calculation, logical operation, etc.--pretty much starts with an =), or a link (i.e. a reference to a cell in another worksheet or another workbook)? I know Excel has this determination at its fingertips; witness the uses and speed of GoTo/Special. How can I get at it though?
Thanks in advance for your help.
Updated for Excel 2013:
For Office versions 2013 and higher, the ISFORMULA¹ function is available. Combining this with the NOT function, AND function and either the COUNTBLANK, ISBLANK or LEN function can produce a formula to determine whether a cell contains a constant.
The standard formulas in E2:F2 are,
=ISFORMULA(D2)
=AND(NOT(ISFORMULA(D2)), LEN(D2))
If further information on the nature of the cell value is required the TYPE function can be used to determine if the cell contents are a number, text, boolean, error or array.
When used in concert the native worksheet functions discussed here can reproduce the results available from VBA's Range.SpecialCells method and its xlCellTypeConstants or xlCellTypeFormulas xlCellType enumeration.
¹ The ISFORMULA function was introduced with Excel 2013. It is not available in earlier versions.
excel-2013
I don't think you can avoid VBA altogether but you can create a simple UDF and use it within Excel
Eg
Function IsFormula(Check_Cell As Range)
IsFormula = Check_Cell.HasFormula
End Function
and
Function IsLink(Check_Cell As Range)
If InStr(1, Check_Cell.Formula, "!", vbTextCompare) Then
IsLink = Check_Cell.HasFormula
End If
End Function
=IsFormula(A1) will return TRUE if there is a formula in A1 and FALSE otherwise
=IsLink(A1) will return TRUE if there is a formula in A1 containing '!' otherwise FALSE
You could combine these and create a string output "Formula","Link","Value"
Not sure if this is what you want, but it seems to do what you are asking, at least some of it.
http://www.ozgrid.com/VBA/special-cells.htm
It's the range.specialcells method.
It returns a range that contains only constants, or only formulas, etc.
An example of how this code would be used is shown below:
Sub CheckForConstants()
Dim x As Range
Set x = Selection.SpecialCells(xlCellTypeConstants, xlNumbers)
MsgBox "address of cells that contain numbers only is " & x.Address
Set x = Selection.SpecialCells(xlCellTypeConstants)
MsgBox "address of cells that contain constant of any type is " & x.Address
End Sub
You select a range and then execute this macro and it will return the address of those cells that meet the requirements.
The first x looks for cells that contains numbers only.
The second x looks for cells that contains any constants
The range in this case was selection, but you can set to what you want, i.e. range("a1:b5"), etc.
I went back to the worksheet and used the goto special method.
Apparently it also uses the range.special method.
I used the record macro option and this is what I got.
Selection.SpecialCells(xlCellTypeConstants, 23).Select
Range("M7").Select
Selection.SpecialCells(xlCellTypeFormulas, 23).Select
Range("I6:J16").Select
Selection.SpecialCells(xlCellTypeConstants, 1).Select
Range("L9").Select
ActiveWindow.ScrollWorkbookTabs Position:=xlFirst
Sheets("CP").Select
Application.CutCopyMode = False
Range("I21").Select
ActiveSheet.DrawingObjects.Select
Application.Goto Reference:="GoToSpecialRoutine"
The goto special feature on the worksheet uses the special cells method for some of what it does.
It also uses others as well. In the last 5 lines of codes I changed worksheet and asked it to go to objects.
It doesn't really go to them. It just selects them.
worksheet CP contained objects and it used the code in the last 3 lines to select all the objects on the worksheet.
Best bet to see the code behind goto special is to record a macro and then use the goto / special feature in the worksheet.
When finished, Stop recording and view the macro you recorded.
I don't know of any other features to select by type of cell, but I'm just a newby so it could be there very easily and not be known by me.
Related
In Excel, I often have complicated formulas where I need to know if the result of an expression is true before I use the value. Often times, I have to put the result in a separate cell, and then use that result in another calculation / formula.
I would like to create a UDF that allowed me to pass:
Function UseIf(left_expression, comparison_expression, value_if_false)
If the overall item is true, I want to return the value for the left expression, much the same way that IfError() returns the evaluation if it doesn't yield an error. Here, I want to return the evaluation if it meets another criteria. I want to pass in the matching criteria or comparison expression, much like you do in SUMIIF, etc. Then for good measure, accept a value to return if the comparison is false.
Yes, in Excel, I can simply create an Excel formula (in the worksheet) with an IF statement and I include the entire expression if the If Test and in the If Results, but that generates a lot of duplication in a formula.
I tried having the UDF insert the expressions as range.formula for cells in my Personal.xlsb, to have excel do the expression parsing & calculation for me. But Excel won't allow a UDF to update another cell's value or formula (it always goes to my OnError condition - The same functionality works in a macro subroutine, but not in a udf.) Also, I seem to run into a lot of problems with volatility in UDFs, with it recalculating much more often than I think it should. If it didn't recalculate when anything on the spreadsheet was updated, then the idea of using temporary cells and updating the associated formulas might work (if Excel allowed me to that is). However, if it is volatile, then the cells might have different values in them at a later point.
Any ideas? Is this possible? Or do I have to stick with long and messy simple IF statements?
Update: Thanks to Scott's help, I came up with the following solution. Please let me know if you see anything that needs improvement. Thanks again!
Function UseIf(left_expression As Variant, _
comparison_expression As Variant, _
Optional value_if_false As Variant, _
Optional value_if_error As Variant) As Variant
Dim vResult As Variant
On Error GoTo ErrorHandler
If (Not InStr("=><", Left(comparison_expression, 1)) > 0) Then comparison_expression = "=" & comparison_expression
If IsMissing(value_if_false) Then value_if_false = ""
vResult = Evaluate(left_expression)
UseIf = IIf(Evaluate(vResult & comparison_expression), vResult, value_if_false)
Exit Function
ErrorHandler:
UseIf = IIf(Not IsMissing(value_if_error), value_if_error, "#ERROR")
End Function
I hope I'm asking this in the correct forum:
I'm writing a UDF in VBA for MS-Excel; it basically builds a status message for the transaction on that row. It steps through a series of IF statements, evaluating cell values in different columns FOR THAT ROW.
However, this UDF will reside in multiple rows. So it might be in C12, C13, C14, etc. How would the UDF know which row to use? I'm trying something like this, to no effect
Tmp_Row = Application.Evaluate("Row()")
which appears to return a null
What am I missing here ?
Thanking everyone in advance
Application.Caller is seldom used, but when a UDF needs to know who called it, it needs to know about Application.Caller.
Except, you cannot just assume that a function was invoked from a Range. So you should validate its type using the TypeOf...Is operator:
Dim CallingCell As Excel.Range
If TypeOf Application.Caller Is Excel.Range Then
'Caller is a range, so this assignment is safe:
Set CallingCell = Application.Caller
End If
If CallingCell Is Nothing Then
'function wasn't called from a cell, now what?
Else
'working row is CallingCell.Row
End If
Suggestion: make the function take its dependent cells as Range parameters (if you need the Range metadata; if you only need the values then take in Double, Date, String parameters instead) instead of making it fetch values from the sheet. This decouples the worksheet layout from the function's logic, which in turn makes it much more flexible and easier to work with - and won't need any tweaks if/when the worksheet layout changes.
Application.ThisCell
MS Docs:
Returns the cell in which the user-defined function is being called from as a Range object.
You can put it to the test using the following code:
Function testTC()
testTC = Application.ThisCell.Row
End Function
In Excel use the formula
=testTC()
and (Cut)Copy/Paste to various cells.
What formula do you use to check if another cell has formula? For example, I have 2 columns, A has cells which contains either a formula or a value.
(Column A usually contains Formulas but other users try to change their values by directly typing and replacing the formula that was previously there)
In Column B I want to add a formula that will say "HasFormula" if the cell on Column A has formula and say "PlainValue" if it contains a value.
I'm thinking maybe using =ISNUMBER() but that may not be accurate.
I am using Excel 2010.
Excel actually has a builtin ISFORMULA() function.
Say A1 has a formula and you want to check that. In say B1, you can use:
=If(ISFORMULA(A1),"HasFormula","PlainValue")
Edit: Per your comment, you don't have ISFORMULA(). An alternative is to create a quick UDF, and use the custom function in the worksheet.
In a workbook module, put this code:
Function isFormula(ByVal target As Range) As Boolean
isFormula = target.hasFormula
End Function
Then you can call it like this: =isFormula(A1) and it will return TRUE if A1 has a formula.
If you can't use VBA, then you can use this formula:
=IF(ISERROR(FORMULATEXT(A1)),"PlainText","HasFormula")
The MrExcel website (link below) has this method which uses old code from Excel 4 (which is still present for backward compatibility)...
Define a NAME such as "CellToLeftHasFormula" and in the "refers to" box put
=GET.CELL(48,OFFSET(INDIRECT("RC",FALSE),0,-1))
Then in column B use the formula =CellToLeftHasFormula which will return TRUE if it has.
Be aware that this will mean your Excel will now contain a macro and so will need to be saved as such (xlsm). I use this in Excel 2010.
For full explanation (and other .CELL options, besides 48) see MrExcel link: https://www.mrexcel.com/forum/excel-questions/20611-info-only-get-cell-arguments.html
You can use the Range.HasFormula property.
https://learn.microsoft.com/en-us/office/vba/api/excel.range.hasformula
EDIT:
Text and code from the above link:
"True if all cells in the range contain formulas; False if none of the cells in the range contains a formula; null otherwise. Read-only Variant. ..."
Worksheets("Sheet1").Activate
Set rr = Application.InputBox( _
prompt:="Select a range on this worksheet", _
Type:=8)
If rr.HasFormula = True Then
MsgBox "Every cell in the selection contains a formula"
End If
You can restrict the user by protecting the column A.
You can directly check if a cell contains a formula by using a shortcut Ctrl + `.
You can use vba and write a user defined function :
1. Press alt + F11
2. Insert module in workbook
3. Paste this code
Function IsFormula(cell_ref As Range)
IsFormula = cell_ref.HasFormula
End Function
4. Now, use Isformula in the cell wherever you want.
What I would like to have is:
IF A1 in Sheet 2 is blue
Then A1 in Sheet 1 changes to blue
I know I can get the color of A1 in Sheet 2 by using:
=GET.CELL(63,Sheet2!A1)
(Excel: Can I create a Conditional Formula based on the Color of a Cell?)
But I can't figure out what I should do in the next step.
Update on 12.01.2015
At the beginning I thought a function would work, but as I considered my file, VBA may be needed.
It is about the output of a correlation analyse from SPSS, there are three columns: correlation coefficient, p-value and sample size.
I need to check the coefficient and p-value at the same time, and present the coefficient in a readable way. Say I run a correlation between 50 variables with 100 variables, I would not paste coefficient and p-value in one sheet, rather:
sheet one : coefficient
sheet two: p-value
What I would to have is:
If value of p-value is bigger than 0.05, then coefficient (cell) changes to blue/dark blue or black.
So that when I watch the first sheet, I know blue ones should be ignored because of non-significance.
What you need is a way to detect changes in cell format.
There appears to be no event that triggers upon change in format. See
How to detect changes in cell format?
I will describe a workaround, almost step-by-step. It is not keystroke-by-keystroke, so you may have to google a bit, depending on you background knowledge.
The description is not short, so please read it through.
You have to:
Detect change in Selection (there is an event for this).
Inquire about the color of your source cell.
Act if needed.
Go to the Visual Basic Editor (VBE) and add code in three modules:
A standard module (say, Module1). You have to first insert the module.
ThisWorkbook.
Sheet2.
In Module1:
Public prev_sel As Range
Public wssrc As Worksheet, wstrg As Worksheet
Public ssrc As String, strg As String
Public rngsrc As Range, rngtrg As Range
Sub copy_color(rngs As Range, rngt As Range)
Dim csrc As Long
csrc = rngs.Interior.Color
If (csrc = vbBlue) Then
rngt.Interior.Color = vbBlue
End If
End Sub
Sub copy_color2(rngs As Range, rngt As Range)
If (TypeName(prev_sel) = "Range") Then
Dim pss As String
pss = prev_sel.Parent.Name
If (pss = ssrc) Then
Dim ints As Range
Set ints = Application.Intersect(rngs, prev_sel)
If (Not (ints Is Nothing)) Then
Call copy_color(rngs, rngt)
End If
End If
End If
End Sub
In ThisWorkbook:
Private Sub Workbook_Open()
ssrc = "Sheet2"
strg = "Sheet1"
Set wssrc = Worksheets(ssrc)
Set wstrg = Worksheets(strg)
Set rngsrc = wssrc.Range("A1")
Set rngtrg = wstrg.Range("A1")
Call copy_color(rngsrc, rngtrg)
If (TypeName(Selection) = "Range") Then
Set prev_sel = Selection
Else
Set prev_sel = Nothing
End If
End Sub
In Sheet2:
Private Sub Worksheet_Deactivate()
Call copy_color(rngsrc, rngtrg)
If (TypeName(Selection) = "Range") Then
Set prev_sel = Selection
Else
Set prev_sel = Nothing
End If
End Sub
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Call copy_color2(rngsrc, rngtrg)
If (TypeName(Target) = "Range") Then
Set prev_sel = Target
End If
End Sub
I will soon edit with explanations. Reading carefully, it can be readily understood, though.
Notes:
This code does not act if the source cell color changes from vbBlue to something else. You did not specify anything for this action. Actually, your specification was not detailed enough to cover all possible cases.
There might be cases (extremely unlikely, I guess) where this code fails. For instance, if color is changed via other VBA code, without selecting/deselecting cells.
The idea is to check for the need of acting after as many relevant events as possible. Here I am detecting Workbook_Open, Worksheet_Deactivate, Worksheet_SelectionChange. You may add other events with suitable Subs, e.g., Workbook_BeforeClose, Workbook_BeforeSave. All this is a way of substituting for the non-existing event of changing cell format.
I like the answer by pnuts (although I did not have time to test it). But the present one gives a flexibility that is not available with the other. There might be some cases (depending on what you need to do) that would not be covered by it.
There are other possible combinations of places to locate variables declaration and other code, essentially performing the same actions.
Not recommended because of reliance on the XLM (not XML) Macro function GET.CELL. This from a technology introduced 30 years ago that was effectively superseded eight years later. With almost all its elements now defunct, the few that remain can be expected to have a low life expectancy. Microsoft encourages migration to VBA.
Nevertheless, you have asked ‘how’ rather than ‘why not’, so I suggest you proceed from where you have reached and select Sheet1 A1 and HOME > Styles - Conditional Formatting - New Rule..., Use a formula to determine which cells to format, Format values where this formula is true:
=CellColor=23
and select blue formatting of your choice, OK, OK, Apply.
23 is a fairly standard number for Blue (not Light, not Dark) but your configuration may expect a different number.
Note that another disadvantage is that, unlike CF in general, the response is not automatic – you may need to enter something in Sheet1 A1, or Shift+F9 to force an update.
If your data is spread across two sheets (Sheet1 and Sheet2, both ColumnA) and there is a 1:1 relationship (the p-value in A1 of Sheet2 is that for the correlation coefficient in A1 of Sheet1) then a simple Conditional Formatting rule may suffice:
Select Sheet1 ColumnA and HOME > Styles - Conditional Formatting, New Rule...
Use a formula to determine which cells to format
Format values where this formula is true:
=Sheet2!A1>0.05
Format..., select dark blue or to suit, OK, OK.
The same rule might be applied in Sheet2 (ColumnA) in the same way so the cells (by row) conditionally formatted in one sheet are those conditionally formatted in the other.
The GET.CELL function, whilst useful, comes from the old XLM macro language which was used before VBA. You may encounter restrictions using this e.g. at that time Excel used a limited number of colours (I have read somewhere around 60?).
Alternatively, with a bit of VBA, you could experiment with the Interior Object and also the Font Object :
Sheets("Sheet1").Range("A1").Interior.Color = vbBlue
Sheets("Sheet1").Range("A1").Font.Color = vbYellow
If Sheets("Sheet1").Range("A1").Interior.Color = vbBlue Then _
Sheets("Sheet2").Range("A1").Interior.Color = vbBlue
If Sheets("Sheet1").Range("A1").Font.Color = vbYellow Then _
Sheets("Sheet2").Range("A1").Font.Color = vbYellow
You will likely need to explore the various ways of specifying the colors to use to give you maximum control/flexibility.
Just to be clear and to keep the functionality you deliver simple, you could use conditional formatting and choose to set the format with a colour. This is incredibly easy once you know how. The main trick is what formula to enter and specifically which cell you need a conditional formats formula to reference when the conditional format applies to a multi cell range.
As an example. If your conditional formatting rule is created such that it applies to the range $C$5:$C$10 the formula you use will often need to be entered as =(A5="A"). Note this is a relative addressing formula ie no dollar signs. this has the effect of the cell c6 inspecting the value of a6 etc.
Your only complication now is to inspect the formatting of the cell rather than the value it stores. In 2013, you can still use =GET.CELL(63,A5) to do this, however this can't be entered in the formula of the CF rule ... Other posts discuss the whys and wherefores of using this. See this link which described how to get cell info.
So you'll end up with a formula in a cell next to the cell that has the colouring. The formula will use a named range that returns true or false depending on whether the colour of the cell matches the colour you specify in the named range. You conditional formatting on another sheet will reference this formula cell and set the colour of the new cell.
You would use the following formula in the named range called "Get .
=GET.CELL(65,OFFSET(INDIRECT("RC",FALSE),0,1))
I've got this to work, and the key information can be found one the referenced web site page.
Ok?
I am trying to get a cell to perform a function based on the hilight color of a cell.
Here is the function I currently have:
=IF(A6.Interior.ColorIndex=6,IF(ROUNDDOWN(IF(M6<3,0,IF(M6<5,1,IF(M6<10,3,(M6/5)+2))),0)=0,0,ROUNDDOWN(IF(M6<3,0,IF(M6<5,1,IF(M6<10,2,(M6/5)+2))),0)),IF(ROUNDDOWN(IF(M6<7,0,IF(M6<10,1,M6/5)),0)=0,0,ROUNDDOWN(IF(M6<7,0,IF(M6<10,1,M6/5)),0)))
Just so you don't have to read through all of that, here's a more simple example
=IF(A6.Interior.ColorIndex=6,"True","False")
All that his is returning is #NAME? . Is there any way that I can do this as a function in a cell or is VBA absolutely required?
Thanks,
Jordan
You cannot use VBA (Interior.ColorIndex) in a formula which is why you receive the error.
It is not possible to do this without VBA.
Function YellowIt(rng As Range) As Boolean
If rng.Interior.ColorIndex = 6 Then
YellowIt = True
Else
YellowIt = False
End If
End Function
However, I do not recommend this: it is not how user-defined VBA functions (UDFs) are intended to be used. They should reflect the behaviour of Excel functions, which cannot read the colour-formatting of a cell. (This function may not work in a future version of Excel.)
It is far better that you base a formula on the original condition (decision) that makes the cell yellow in the first place. Or, alternatively, run a Sub procedure to fill in the True or False values (although, of course, these values will no longer be linked to the original cell's formatting).
I don't believe there's any way to get a cell's color from a formula. The closest you can get is the CELL formula, but (at least as of Excel 2003), it doesn't return the cell's color.
It would be pretty easy to implement with VBA:
Public Function myColor(r As Range) As Integer
myColor = r.Interior.ColorIndex
End Function
Then in the worksheet:
=mycolor(A1)
Although this does not directly address your question, you can actually sort your data by cell colour in Excel (which then makes it pretty easy to label all records with a particular colour in the same way and, hence, condition upon this label).
In Excel 2010, you can do this by going to Data -> Sort -> Sort On "Cell Colour".
I had a similar problem where I needed to only show a value from another Excel cell if the font was black. I created this function:
`Option Explicit
Function blackFont(r As Range) As Boolean
If r.Font.Color = 0 Then
blackFont = True
Else
blackFont = False
End If
End Function
`
In my cell I have this formula:
=IF(blackFont(Y51),Y51," ")
This worked well for me to test for a black font and only show the value in the Y51 cell if it had a black font.
The only easy solution that I have applied is to recreate the primary condition that do the highlights as an IF condition and use it on the IF formula. Something like this. Depending on the highlight condition the formula will change but I think that should be recreated (es. highlight greater than 20).
=IF(B3>20,(B3)," ")