I know the topic "Excel formulas not updating" has been discussed a lot on many forums but I haven't found a useful solution to my problem.
In a worksheet, I am using built-in Excel formulas as well as own functions written with VBA in the module of the worksheet and I am referencing them within the worksheet.
There is a binary code which gets generated from a hexadecimal code in cell A1. The binary code gets calculated in cell B1.
Let's take following code as an example: 100001101110
Cell C1 contains following:
=DecodeVal(B1;0;20)
If I now paste a hex code into A1 and the binary code gets created in B1, cell C1 is displaying an #VALUE! error.
If I go back to cell A1, click in the textbox and press enter again, the correct value (= 2158) gets displayed.
Why is there a Value error at first, but not if I press enter one more time?
If I paste the binary code directly as text, there is no error at all.
This is the function I'm referring to:
Public Function DecodeVal(value, start As Integer, length As Integer) As Long
Dim abschnitt As String
Dim i As Integer
Dim valueText As String
valueText = value.Text
If (Len(valueText) - start - length + 1 > 0) Then
abschnitt = Mid(valueText, Len(valueText) - start - length + 1, length)
Else
If (Len(valueText) > start) Then
abschnitt = Left(valueText, Len(valueText) - start)
length = Len(valueText) - start
End If
End If
Do
If (Int(Left(abschnitt, 1)) = 1) Then
DecodeVal = DecodeVal * 2 + 1
Else
DecodeVal = DecodeVal * 2
End If
abschnitt = Right(abschnitt, length - 1)
length = length - 1
Loop While length > 0
End Function
Yes, calculation options are set to automatic.
Any suggestions?
Thanks
Using Range.Text is not recommended practice. This will particularly cause errors when the input column has less width than data and cell is displaying ######. If the formula is calculated then it will return #VALUE error. See the snapshot below.
If you adjust the column width and recalculate it will then show the correct result.
To get around this, use either valueText = value.Value2 or valueText = value.Value instead of valueText = value.Text.
Also, I'd suggest using something like RngValue as a declared variable/argument instead of value which is a Range property as well (as it may result in conflicts).
The next two things would solve your problem:
Change
Public Function DecodeVal(value, start As Integer, length As Integer) As Long
with
Public Function DecodeVal(val As Range, start As Integer, length As Integer) As Long
and use valueText = val.value
Be also sure that B1 is formatted as Text. The best way of formatting would be TextToColumns. Manually (from Data Tab) or in code:
Dim sh As Worksheet, rng As Range
Set sh = ActiveSheet
Set rng = sh.Range("B1:B10")
rng.TextToColumns Destination:=rng, fieldInfo:=Array(1, 2)
Otherwise, Excel guess can be a scientific format...
Within your Do loop, you are using DecodeVal in calculations, but never assigned it an initial value.
(there may be other errors)
Related
With Excel 2019 and Office 365, MS introduced changes to array functions such that they are at least a bit less rigid.
Suppose I have the following VBA function
Public Function fooo(a As Integer, b As Integer) As Variant
Dim mat() As Integer
ReDim mat(0 To a - 1, 0 To b - 1) As Integer
Dim i As Integer
Dim j As Integer
For i = 0 To a - 1
For j = 0 To b - 1
mat(i, j) = a
Next j
Next i
fooo = mat
End Function
and go into a sheet and type =fooo(4,4) into a cell. All I have to do is press enter, not control-shift-enter, and Excel executes my array function, leaving me an 4 x 4 array of 4s. So far so good.
Now if I try the following from VBA
Public Sub tryit()
Range("a40").FormulaArray = "=fooo(3,10)"
Range("a40").Calculate
End Sub
I see a 3 in cell a40, but not the rest of the matrix. Only when I click on the cell and press enter does the rest of the matrix appear. 😞
Moving right along, I can now try something like this
Public Sub tryit2()
Dim i As Integer
For i = 1 To 10
Range("a40").FormulaArray = "=fooo(" & (i + 3) & "," & (i + 3) & ")"
Application.Wait Now + TimeValue("0:00:01")
Next i
End Sub
If cell a40 contains the matrix from before, this code happily overwrites the matrix there already. Yeah, sure, there are some #N/As, but I can live with that. But if that cell was previously fresh, Excel only shows that first number.
It almost looks like Excel pre-allocates a block of cells for the result, just not with VBA.
How can I get that initial array function to display from VBA?
In tryit, use Formula2 instead of FormulaArray.
From the docs:
Formulas set using Range.Formula2 will always be evaluated as an array and may spill if more than 1 result is returned.
In SQL Server, MIN and MAX can act on text that doesn't evaluate to numbers, returning the text item with the lowest or highest text sort order, or as it's known in SQL Server-speak, "collation order".
Is it possible to do that in Excel without going to a UDF that actually sorts?
For example, for MIN("bb","aa","cc") to return "aa", and MAX("bb","cc","aa") to return "cc".
Excel's MIN/MAX ignore text, and although MINA/MAXA can work on text, they break on text that doesn't resolve to a number. LARGE/SMALL don't do it either.
FWIW, a coworker asked me how to do this in a pivot. I don't see a way without going to a custom function. Am I wrong?
This array formula looks promising. since it is an array it needs to be entered with ctrl-shift-enter.
Max:
=INDEX(A2:A6,MATCH(0,COUNTIF(A2:A6,">"&A2:A6), 0))
Min:
=INDEX(A2:A6,MATCH(0,COUNTIF(A2:A6,"<"&A2:A6), 0))
Change the three ranges to what you want.
The max and min versions are the same except for the > versus <.
I believe you are correct, a custom function is best. The good thing to note is the normal comparator operators work similarly as you described.
Public Function MinStr(ByVal strVal As Range) As String
Dim i As Integer
Dim cell As Range
MinStr = ""
'Check to make sure the range is not empty
if strVal.Rows.Count > 0 then
'Initialize MinStr to a known value
MinStr = strVal.cells(1,1).Value
'Iterate through the entire range
For Each cell in strVal.Cells
if(MinStr > cell.Value) then
MinStr = cell.Value
end if
Next cell
end if
End Function
Public Function MaxStr(ByVal strVal As Range) As String
Dim i As Integer
Dim cell As Range
MaxStr = ""
'Check to make sure the range is not empty
if strVal.Rows.Count > 0 then
'Initialize MaxStr to a known value
MaxStr = strVal.cells(1,1).Value
'Iterate through the entire range
For Each cell in strVal.Cells
if(MaxStr < cell.Value) then
MaxStr = cell.Value
end if
Next cell
end if
End Function
I wanted to know a way to reference part of a value in a cell in Excel in VBA. For instance if in cell A32 I have stored 4200 cc. I would like to reference only the 4200 for calculations in a macro in VBA. So like one can reference strings or lists in python with indices, I'd like to be able to do the same. Thank you!
Something like this(?):
Dim tmpArr
tmpArr = Split(Range("A32"), " ")
'any calculation in vba in this way:
Range("B32") = tmpArr(0) / 100
'results with 42 in cell B32
EDIT If there is any doubt about recognition of number for first part of the split results you could make additional conversion in this way:
'conversion to Double type
Range("B32") = CDbl(tmpArr(0))/100
You may create a function to split the content of the cell and then handle the parts you want separately.
Dim content As Variant
content = Split(Range("A32").Value, " ")
Get the cell value as a string s and return the numeric part?
Function GetNumber(s As String)
Dim j As Long
While Not IsNumeric(Left(s, 1))
If Len(s) <= 1 Then
Exit Function
Else
s = Mid(s, 2)
End If
Wend
GetNumber = Val(s)
End Function
I'm trying myself on vba with little success. I would like to achieve a simple function that sums the content of a range of cells based on the beginning of the year till today. Unfortunately, I get back a "circular reference" error when I call the function, and I just can't see why. Any help will be appreciated.
Public Function til2day(r As Integer) As Long ''supposed to receive cell("row") as parameter
Dim c As Integer
Dim c1 As Integer
Dim c_here As Integer
Application.Volatile True
c_here = ActiveCell.Column
c = 0
c1 = 34 ''column contains 1/1/2013 date
Range("AH4:OM4").Activate ''contains a timeline
Do While ActiveCell.Offset(0, c).Value <> Date
c = c + 1
Loop
If ActiveCell.Offset(0, c).Value = Date Then
c = ActiveCell.Offset(0, c).Column
End If
til2day = Application.WorksheetFunction.Sum(Range(Cells(r, c1).Address, Cells(r, c).Address))
Range(Cells(r, c_here).Address).Activate
End Function
It is a really bad idea to use "activate" in a function; I can't explain exactly why this is, except that you are changing the selection of the cell during the calculation. In the following scenario this is going to cause a problem:
multiple cells are being calculated with this function, and
you use `Application.Volatile`, and
you refer to the active cell inside your function, and
you allow multi-threaded calculation,
Things will not happen in the order you expect, and at some point the active cell will be different than you thought. Function ends up referring to the cell it's in, and you have a circular reference. This doesn't happen when you run the debugger since it by definition runs as a single thread - which is why you can't find the problem then...
Here is a suggested rewrite of your function - it doesn't do any activating of cells, but attempts to maintain the same functionality:
Public Function til2day(r As Integer) As Long ''supposed to receive cell("row") as parameter
Dim c As Integer
Dim c1 As Integer
Dim dateRange as Range
Dime dateCell as Range
Application.Volatile True
c = 0
c1 = 34 ''column contains 1/1/2013 date
set dateRange = Range("AH4:OM4")
For Each dateCell in dateRange
If dateCell.Value = Date Then Exit For
Next dateCell
c = dateCell.Column
til2day = Application.WorksheetFunction.Sum(Range(Cells(r, c1).Address, Cells(r, c).Address))
End Function
Note: I attempted to reproduce the functionality of your function - but without a good example of the worksheet you are using, and the values you are expecting to return, it's hard to test. Please try to run this on your worksheet - and let me know if things don't work as you expected.
Note also that the SUMIF function could be used with good effect:
=SUMIF(range, criteria, sum_range)
In your case, use
=SUMIF($AH$4:$OM$4, "<=" & NOW(), $AH18:$OM18)
Where "18" is whatever row you need it to be (and when you drag the formula to a different row, it will continue to refer to the date row because of the $4 absolute reference, but calculate the sum for a different row because of the relative row reference in $AH18:$OM18.
An example of the use of this function (simplified range...)
As you can see, the function is summing columns C through F only since I did this on June 15th.
I'm trying to write a function that merges multiple rows of text in a column into a single cell based on a pre determined count. My goal is to generate a flexible function to aid in compiling / interperting large quantaties of data. The code I've written returns #NAME? and I cant figure out where the error is. My code is as follows:
Function vmrg(countref As Integer, datref As Integer) As String
If IsEmpty(ActiveCell.Offset(0, -countref)) Then % check if cell containing count is blank
vertmerge = "N/A" % if blank, state N/A
Else
Dim datlst(0 To ActiveCell.Offset(0, -countref).Value - 1) As String
Dim i As Integer
For i = 0 To ActiveCell.Offset(0, -countref).Value - 1
datlst(i) = ActiveCell.Offset(i, -datref).Text %fill array with data
End
vertmerge = datlst(0)
For i = 1 To ActiveCell.Offset(0, -countref).Value - 1 % merge array to a single string
vertmerge = vertmerge & ", " & datlst(i)
End
End
End Function
I have matlab and some C++ experience but this is the first time I've used VBA so my syntax is probably odd in some areas and wrong in others. Ideally I would like to reference the cells where the data and count info are stored, but for now I'm hoping to correct my syntax and set a jumping off point for further development of this function. Any reccomendations are appreciated.
Code Rev_1: I still have an output of #NAME? but I think I've corrected(?) some of the issues
Function vertmerge(countref As Range, datref As Integer) As String
If IsEmpty(countref) = True Then
vertmerge = "NA"
Else
Dim datlst(0 To countref.Value - 1) As String
Dim i As Integer
For i = 0 To countref.Value - 1
datlst(i) = countref.Offset(i, datref).Text
Next i
vertmerge = datlst(0)
For i = 1 To countref.Value - 1
vertmerge = vertmerge & ", " & datlst(i)
Next i
End
End Function
You are doing some dangerous things here!
First - you are referencing "ActiveCell" from inside a function; but you have NO IDEA what cell will be active when the function runs! Instead, pass the target cell as a parameter:
=vmrg("B6", 5, 6)
and change your function prototype to
Function vmrg(r as Range, countref as Integer, datref as Integer)
Now you can reference things relative to r with
r.Offset(1,2)
etc.
Next - you are never assigning anything to vmrg. In VBA, the way a function returns a value is with (in this case)
vmrg = 23
You are assigning things to a variable called vertmerge - but that is not the name of your function. At least add
vmrg = vertmerge
Just before returning. That might do it. Without a full sample of your spreadsheet I can't help you more.