Excel VBA own function in worksheet which changes value on a sheet - vba

I'm trying to use my own VBA function in an Excel sheet with a return value and the same function manipulates a cell on the same or on an other sheet, but the result is #VALUE! A minimal working example (Office Prof Plus 2010, 32-bit):
Function abc() As Integer
Dim i%
i = 0
Sheet1.Cells(1, 2).Value = 2
abc = i
End Function
When I execute Debug.Print abc it obviously writes a 2 to the cell B2 and the printed result is 0. What I want to do now is =abc() in cell A1 on Sheet1, but I only get #VALUE!. Btw, it doesn't work either if Application.EnableEvents and Application.Calculation is disabled resp. set to manual.
Two questions: Why? And any idea how to resolve? Thx.

It's well known that you can't update the worksheet with a UDF (other than the cell with the UDF in). However, there is a pretty awful workaround which checks every time you make a change on your worksheet and places a 2 in B1 for you if your function happens to be =abc()
Have your dummy function in in a standard module
Public Function abc() As Integer
abc = 0
End Function
Then in the Sheet1 module place the following code
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Cells.Count > 1 Then Exit Sub
If Target.Formula = "=abc()" Then
Me.Cells(1, 2).Value = 2
End If
End Sub

This type of User Defined Function (called from within a cell) can't modify cell values other than to return a value to the cell containing the UDF.

Related

Excel - Grab Username for who ever last modified a cell

is it possible to have some VBA to record the user for who ever last modified a specific cell? i.e., if someone opens the workbook and enters a value into A1, I'd like B1 to show the username of the person who did that, and then, if someone else opens the workbook and enters a value into A2, i'd like their user name in B2 and so on and so forth... I've playing around with examples like the below, but I'm not sure if I'm getting any closer, seems i can only grab the username for whoever last modified the workbook.
Function LastAuthor()
LastAuthor = ActiveWorkbook.BuiltinDocumentProperties("Last Author")
End Function
This inserts the windows user/network user in column B whenever one or more values are added/modified/deleted in column A.
Add this to the worksheet's private code sheet; not a public module code sheet.
private sub worksheet_change(byval target as range)
if not intersect(range("A:A"), target) is nothing then
on error goto meh
application.enableevents = false
dim t as range
for each t in intersect(range("A:A"), target)
t.offset(0, 1) = environ("user")
't.offset(0, 2) = now
next t
end if
meh:
application.enableevents = true
end sub

Find Range Name Using Active Cell

I'm a beginner when it comes to programming in VBA.
I have one cell that is part of a named range. Using that active cell, I want to be able to find what range that cell is a part of and pass it into a VBA function or subroutine as a Range object.
Can anyone provide me with guidance as to how to proceed, or is this not possible?
Thanks in advance!
Here is a simple example.
The code checks if the selected cell is part of a named range. If so, the named range is passed to a function:
Sub Main()
Dim nm As Integer
For nm = 1 To ActiveWorkbook.Names.Count
If Not Intersect(Selection, Range(ActiveWorkbook.Names(nm).Name)) Is Nothing Then
Debug.Print MyFunc(Range(ActiveWorkbook.Names(nm).Name)) // Prints TRUE or FALSE
End If
Next nm
End Sub
Function MyFunc(Named_Range As Range) As Boolean
MyFunc = Named_Range.Cells.Count > 2 ~~>Courtesy of `Thomas Inzina`
End Function

Count visible blank cells using VBA?

When I enter the following function as a UDF in a cell:
Function VisibleBlankCells(r As Range) As Long
On Error Resume Next
VisibleBlankCells = Intersect(r.SpecialCells(xlCellTypeVisible), r.SpecialCells(xlCellTypeBlanks)).Count
On Error GoTo 0
End Function
r.SpecialCells(xlCellTypeBlanks) evaluates ALL cells in r as empty regardless of whether they contain text or not. What might be the cause of this and an alternative solution?
Get rid of the On Error Resume Next for a start - you should always assume that your code will fail and account for it accordingly, simply ignoring errors will just complicate matters.
Secondly ,there is no need to use Intersect - just identify the visible cells directly, and then use a further SpecialCells() method to identify the blank child cells.
Function VisibleBlankCells(r As Range) As Long
VisibleBlankCells = r.SpecialCells(xlCellTypeVisible).SpecialCells(xlCellTypeBlanks).Count
End Function
tested with this:
Sub test_code()
Dim r As Range: Set r = Selection
Debug.Print CountBlanks(r)
End Sub
Function CountBlanks(r As Range) As Long
CountBlanks = r.SpecialCells(xlCellTypeVisible).SpecialCells(xlCellTypeBlanks).Count
End Function
This kind of filter mechanism won't work in an UDF (see this for information on that). I suggest a looping inside your UDF:
Public Function VisibleBlankCells(rng As Range) As Long
Dim i As Integer
Dim cell As Range
i = 0
For Each cell In rng
If cell.Rows.Hidden = False And _
cell.Columns.Hidden = False And _
cell.Value = "" Then
i = i + 1
End If
Next
VisibleBlankCells = i
End Function
However, there may be some problems regarding the updating and functionality:
The value of the UDF only updates after editing the referenced range or calling other UDFs. So if you hide a column or row in that range, it won't have an instant effect
In the (working) execution of your code in a Sub, the visible cells (also) refer to yet unused cells in your worksheet to be "not visible". In my solution however, all cells that are not contained in a hidden row/column are considered visible.

Macro launching when a cell value changes due to a formula not by the user

I would like my Macro to launch whenever a value in a cell containing a formula changes.
i.e. the user is modifying another cell thus changing the value of the cell in question.
I have noticed that using the statement (found herein), only works if the user modifies the cell itself but not if the cell changes automatically - due to a formula as specified above.
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Range("A20")) Is Nothing Then ...
Any thoughts??
I tried to follow the answers from this question "automatically execute an Excel macro on a cell change" but it did not work...
Thanks in advance :)
A possible work-around comes from the fact that, to change a value, the user needs to change the selection first. So I would:
1) Declare a global variable called "oldValue" on top of the WS source code module:
Dim oldValue As Variant
2) Register the old value of your formula before the user types anything (let's say it's in Range("A4"), I let you adapt with the others):
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
oldValue = Range("A4")
End Sub
3) Check if the change has affected the formula in the Change event:
Private Sub Worksheet_Change(ByVal Target As Range)
If Range("A4") <> oldValue Then
MsgBox "User action has affected your formula"
End If
End Sub
I've tested with a simple sum, I'm able to write cells that are not involved without any prompt but if I touch one of the cells involved in the sum the MsgBox will show up. I let you adapt for multiple cases, for user adding/removing rows (in that case I suggest to name the ranges containing the formulas you want to track) and the worksheet references.
EDIT I'd like to do it at once, not by going through 2 processes, is it possible? The problem is my macro involves a range containing more than one cell so it will be hard to store old values for 10 cells.
If ranges are next to each other, then instead of using a variable you can use a collection:
Dim oldValues As New Collection
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
For j = 1 To 10
oldValues.Add Range("A" & j).Value
Next j
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
For j = 1 To 10
If Range("A" & j).Value <> oldValues(j) Then
MsgBox "The value of Range(A" & j & ") has changed"
End If
Next j
End Sub
Of course, if ranges are not close to each other, you can just store them anyway in the SelectionChange event like this:
oldValues.Add Range("A1").Value
oldValues.Add Range("B7").Value
'...
and if you done this ONCE, with 10 ranges only, it should be a reasonable solution to your problem.
You said, "I would like my Macro to launch whenever a value in a cell containing a formula changes..."
If having your code run whenever a cell containing a formula is recalculated (which is not exactly what you asked for), one solution might be to create a VBA function that simply returns that value passed to it, plus does whatever else you want to do when the formula is recalculated...
Public Function Hook(ByVal vValue As Variant) As Variant
Hook = vValue
' Add your code here...
End Function
...then "wrap" your formula in a call to this function. For example, if the formula you are interested in is =A1+1, you would change this to =Hook(A1+1), and the Hook function would be called whenever A1+1 is recalculated (for example, when the value in A1 changes). However, it is possible that recalculating A1+1 will yield the same result and still call the Hook function (for example, if the user re-enters the same value in A1).
You can have a go at this:
First, in a Module Code declare a Public Variable.
Public r As Range, myVal '<~~ Place it in Module
Second, initialize your variables in Workbook_Open event.
Private Sub Workbook_Open()
Set r = Sheet1.Range("C2:C3") '<~~ Change to your actual sheet and range
myVal = Application.Transpose(r)
End Sub
Finally, set up your Worksheet_Calculate event.
Private Sub Worksheet_Calculate()
On Error GoTo halt
With Application
.EnableEvents = False
If Join(myVal) <> Join(.Transpose(r)) Then
MsgBox "Something changed in your range"
'~~> You put your cool stuff here
End If
myVal = .Transpose(r)
forward:
.EnableEvents = True
End With
Exit Sub
halt:
MsgBox "Error " & Err.Number & ": " & Err.Description
Resume forward
End Sub
Above will trigger the event when values in C2:C3 changes.
Not really very neat but works in detecting changes in your target range. HTH.
Declaring a module -level variable like Matteo describes is definitely one good way to go.
Brian 's answer is on the right track with regards to keeping all is the code in the same place, but it's missing one critical part : Application.Caller
When used in function that is called by a single cell, Application.Caller will return the Range object of that cell. This way you can store the old value within the function itself when it is called, then once you're done with calculating the new value you can compare it with the old and run more code as required.
Edit: The advantage with Application.Caller is that the solution scales in and of itself, and does not change no matter how the target cells are arranged (I.e. Continuous or not).

How to display cell value offset active cell

I'm trying to display the value of a cell according to my ActiveCell or Target Cell using a Function. The cell I'm trying to display is in the same spreadsheet.
My objective is to create a Header in the spreadsheet that would display information according to the position of the Active cell.
I've tried this code and typed the function =VendorName5() in the cell where I want the value to be displayed but it seems to be missing something. Can you help ?
Function VendorName5() As String
Name = ActiveCell.Offset(0, -4)
VendorName5 = Name
End Function
OK, found it:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Target.Column = 8 Then
Range("C2") = Cells(Target.Row, 2)
End If
End Sub
This is VBA not VB.NET
Try .Value of Cell
Function VendorName5() As String
Name = ActiveCell.Offset(0, -4).Value
VendorName5 = Name
End Function
Why not use the Offset function:
In cell A1:
=OFFSET(A1,0,4,1,1)
Or, refer to the cell directly:
=E1
etc.
This seems overkill to use a UDF to do something that worksheet functions ordinarily allow for, already.