Excel no longer calculates my fields - excel

I have written a simple VBA function to look up a value by key in a long table of key/value pairs. It then returns that value as a Double:
Public Function GetValue(sheet As String, key As String) As Double
Dim WS As Worksheet
Dim K As Range
GetValue = 0
Set WS = ThisWorkbook.Worksheets(sheet)
If WS Is Nothing Then Exit Function
Set K = FindKeyAnywhere(WS, key)
If K Is Nothing Then Exit Function
GetValue = WS.Cells(K.Row, 5).Value
End Function
I have about 60 of these formulas in a summary sheet:
=GetValue("Data", B$41)
Where "Data" is the name of the page with key/values, and B$41 is a key. Everything was working perfectly, then...
I fat-fingered the VBA, changing the = 0 to = o and calced. Now every cell on the summary still said #VALUE. When I realized what happened, I fixed the error and recalced. However, every cell on the summary still said #VALUE.
If I click in the cell and hit return, it updated fine.
I checked, autocalculate is turned on. I also clicked calc sheet several times. Nothing.
So I went to every single cell and hit return, so they were all updated. Now they don't say #VALUE, but they still don't update when I change data on the data page.
Is there anything special I'm missing? It seems like Excel is "stuck" thinking the calculation isn't valid.
UPDATE:
Using a named range does not work well, because it has to be typed into every formula. Consider...
KEY1 KEY2 ANOTHERKEY
Data1 =GetValue(A$1,B$1)
Data2
When the user CnPs or drag-fills that formula, it will get the key and sheet automatically. If we use a range name instead, they would have to type in the name in every single cell, and there's hundreds.
Is there a way to take a string and return the named range? Like =Range(A1)

As John Coleman says: Excel does not know it needs to recalculate when something on the Data sheet changes because the precedents of your UDF do not include the range od information on the Data sheet.
You need to:
Either Make the function Volatile - but this has bad recalculation
consequences.
Or pass the range on the Data sheet that contains the information
instead of passing a string. This is the best solution.

Based on your update I think you are looking for INDIRECT, which can convert a string to a range.
But there are major downsides to INDIRECT: its volatile, single-threaded and behaves badly when the string the user gives it does not exist.
IMHO this is not a good direction to go: I would recommend you consider a redesign of your data/algorithms.

Related

XLookup function won't detect an exact match until I place the cell in edit mode and then exit

I am using the XLookup function in Excel 365. I use a VBA script to create an ID number for a row of data and then I place that ID number into a cell.
If I use the XLookup function with Exact Match to locate that ID number and give me some value in a column next to it, I get an N/A.
However, if I manually locate that exact ID number and place the cell in edit mode and then exit the cell without taking any other step to alter the data in any way, the XLookup function returns the correct result.
If I leave the cell in it's initial state but use the Match Mode "-1" to get an exact match or next smaller, it returns the correct result. I don't want to leave it in that mode, because my final function will be two nested Xlookup functions that check one table for the ID and if not there, looks at an alternate table, so I don't want the first function to return a "next smaller" result.
When I generate the ID number in the VBA script, I generate it as the type "Variant" because the ID number is a decimal, such as 210.1. I then simply set that cell's Value to the variant ID number.
Here is a how I place the data into the cell:
Dim myID As Variant
Dim myCell As Variant
Dim myRange As Range
Set myRange = Range(someNamedRange)
myID = GenerateNextID 'This generates a variant in the form of something like 201.1
For Each myCell In myRange
myCell.Value = myID
myID = myID + 0.1
Next myCell
Excel seems to recognize it as a number; at least I seem to be able to do math functions on the ID number and get correct results.
I tried putting the lookup ID number in quotes to see if looking it up as text instead of a number produced a different result, but it didn't change my result.
Why am I getting this result? Is there a way to resolve it without using a different Match Mode?
You have a floating point precision error. The value in E11 is 211.39999999999998 rather than 211.4. Excel corrects the error when you re-enter the cell's value.
Write the value using a line like:
myID = Round(myID + 0.1, 1)

Print array called from Excel cell (VBA)

I'm not sure if I need a sub or a function here, but I need something that can:
Be called from a cell in Excel
Can print values into other cells in the same worksheet
For eg.
Function PrintDemo(CellRef As Range)
Set CellRef = ActiveCell.CurrentRegion
Dim i as Integer
CellRef.Offset( , 5).Value = i
PrintDemo = 3
End Function
If I try to call this using =PrintDemo(D1) in an excel cell, hoping to print '123' 5 cells to the right, it just shows VALUE error. The function doesn't actually need to return anything, I made it hoping it would solve the error (it didn't). Nothing gets printed.
I haven't used VBA much (definitely not for printing) and I'm sure I'm missing something very fundamental in terms of how things are called. Please help me out.
EDIT: Makes sense that I can't change other cells, can I store my result as an array somewhere and have a range of cells access its values one at a time?
eg. outputArr=[1,2,3,4,5]
A2 -> =outputArr(0)
B2 -> =outputArr(1) and so on
It'll actually be 2D array but I don't think that should make much of a difference. The reason I need formulae everywhere is because this has to run in many places in the sheet, hence can't have any manual steps.

MS-Excel: Retrieve Row Number where VBA Function is located

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.

Custom Excel VBA Function (Modified VLOOKUP) from Cell referring to a range in different file gives an error

First time I'm writing a stackOverflow question so please let me know if I do anything wrong.
I've searched for a few hours now and haven't been able to find a solution to my problem (usually I find the answer hence why this is my first question after using stackoverflow for a few years as a lurker).
Basically I'm trying to write a modified VLOOKUP function that functions similar to VLOOKUP except it return the "next smallest larger" value instead of the default "previous largest smaller" value. I'm aware of the index/match method unfortunately I would need to carefully replace literally thousands of VLOOKUPs manually that already exist in the workbook I'm currently cleaning up at work. Therefore I resorted to writing a VLOOKUPnew function so I could just "find/replace" all the VLOOKUP with VLOOKUPnew.
Function VLOOKUPnew(lookup_value As Variant, table_array As Range, _
col_index_num As Integer, Optional exactMatch As Boolean) As Variant
Dim row As Integer
Debug.Print table_array.Address
With Application
On Error GoTo NO_ROW
row = .Match(lookup_value, table_array.Columns(1), 0)
On Error GoTo 0
If row = -1 And Not exactMatch Then
row = .Match(lookup_value, table_array.Columns(1), 1)
row = row + 1
End If
VLOOKUPnew = .index(table_array.Columns(col_index_num), row, 0)
End With
Exit Function
NO_ROW:
row = -1
Resume Next
End Function
And I succeeding in writing the function but hit a snag. Because I declared "table_array" as a Range, vba fails to identify range references to other workbooks
e.g. "=VLOOKUPnew($A432,'reallyLongFilepath/[filename.xlsx]tablename'!$B$6:$N$35,columnNumber,0),FALSE)" resolves to a #VALUE error
The really weird thing is that if I open the file, then the filepath drops out of the formula (becoming just "=VLOOKUPnew($A432,'[filename.xlsx]tablename'!$B$6:$N$35,columnNumber,0),FALSE)") and then my custom function works just fine returning the correct value.
So my problem is how do I resolve not having to open the other file to use this workbook. I'm not even sure how Excel is passing the address or range to the custom formula so I'm suspecting it's breaking when the filepath is included in the range reference. Is there a way to split the filepath, filename, sheet and address (after it has been passed in)? Or possibly pass it in as a string then easily split it? Or pass it in as something that will correctly identify the range in the different workbook?
Keep in mind that I'm trying to avoid changing the arguments of the function because I want to do the find/replace trick and that this is for work so there's a restraint on too much change in data layout. Also that the workbook is for other employees to use and I'm just setting it up for use.
Thanks in advance!
Andrew
You face quite a dilemma here!
The root problem is that while VLOOKUP can look into closed workbooks, a Range parameter in a UDF cannot. The range reference resolves to an error, so the function call fails with a type mismatch. If you change the table_array parameter type to Variant and put a break on the function header, you will see the parameter value as Error 2036.
While there are ways to look into closed workbooks, all of them (AFAIK) are quite slow. Since you mention ... I would need to carefully replace literally thousands of VLOOKUPs ... I suspect any solution along these lines would be unacceptably slow.
My reccomendation would be to go the INDEX/MATCH route, and write a VBA macro to do the formula updates for you.

Writing an input integer into a cell

I am writing a quick application myself - first project, however I am trying to find the VBA code for writing the result of an input string to a named cell in Excel.
For example, a input box asks the question "Which job number would you like to add to the list?"... the user would then enter a reference number such as "FX1234356". The macro then needs to write that information into a cell, which I can then use to finish the macro (basically a search in some data).
You can use the Range object in VBA to set the value of a named cell, just like any other cell.
Range("C1").Value = Inputbox("Which job number would you like to add to the list?)
Where "C1" is the name of the cell you want to update.
My Excel VBA is a little bit old and crusty, so there may be a better way to do this in newer versions of Excel.
I recommend always using a named range (as you have suggested you are doing) because if any columns or rows are added or deleted, the name reference will update, whereas if you hard code the cell reference (eg "H1" as suggested in one of the responses) in VBA, then it will not update and will point to the wrong cell.
So
Range("RefNo") = InputBox("....")
is safer than
Range("H1") = InputBox("....")
You can set the value of several cells, too.
Range("Results").Resize(10,3) = arrResults()
where arrResults is an array of at least 10 rows & 3 columns (and can be any type). If you use this, put this
Option Base 1
at the top of the VBA module, otherwise VBA will assume the array starts at 0 and put a blank first row and column in the sheet. This line makes all arrays start at 1 as a default (which may be abnormal in most languages but works well with spreadsheets).
When asking a user for a response to put into a cell using the InputBox method, there are usually three things that can happen¹.
The user types something in and clicks OK. This is what you expect to happen and you will receive input back that can be returned directly to a cell or a declared variable.
The user clicks Cancel, presses Esc or clicks × (Close). The return value is a boolean False. This should be accounted for.
The user does not type anything in but clicks OK regardless. The return value is a zero-length string.
If you are putting the return value into a cell, your own logic stream will dictate what you want to do about the latter two scenarios. You may want to clear the cell or you may want to leave the cell contents alone. Here is how to handle the various outcomes with a variant type variable and a Select Case statement.
Dim returnVal As Variant
returnVal = InputBox(Prompt:="Type a value:", Title:="Test Data")
'if the user clicked Cancel, Close or Esc the False
'is translated to the variant as a vbNullString
Select Case True
Case Len(returnVal) = 0
'no value but user clicked OK - clear the target cell
Range("A2").ClearContents
Case Else
'returned a value with OK, save it
Range("A2") = returnVal
End Select
¹ There is a fourth scenario when a specific type of InputBox method is used. An InputBox can return a formula, cell range error or array. Those are special cases and requires using very specific syntax options. See the supplied link for more.
I've done this kind of thing with a form that contains a TextBox.
So if you wanted to put this in say cell H1, then use:
ActiveSheet.Range("H1").Value = txtBoxName.Text

Resources