In Python, we normally pass arguements in the function when we have to call them multiple times as below:
def function_add(a,b):
print(a+b)
function_add(4,5)
function_add(5,7)
function_add(10,4)
function_add(4,6)
Do we have a similar way to implement it in VBA too? I tried to implement it but I couldn't make it. Below is my code.
Private Sub SearchAndInsertRows()
Dim rng As Range
Dim cell As Range
Dim search As String
Dim kk As String
Set rng = ActiveSheet.Columns("A:A")
search = ""
Call searchGetKey(search)
Set cell = rng.Find(What:=search, LookIn:=xlFormulas, LookAt:=xlWhole, MatchCase:=False)
If cell Is Nothing Then
MsgBox "Not Found"
Else
kk = ""
Call searchSetKey(kk)
cell.Value = kk
End If
End Sub
Sub searchGetKey(ByRef getKey As String)
getKey = "a"
End Sub
Sub searchSetKey(ByRef setKey As String)
setKey = "b"
End Sub
Sub searchGetKey and searchSetKey modifies one cell but I need to do the same for number of cells. Is there any other ways to do it?
Please fell free to optimize the code wherever necessary.
Thank you very much and much appreciated. :)
A function in VBA must return something. Otherwise, you should use a Sub:
Function function_add(a As Long, b As Long) As Long
function_add = a + b
End Function
Sub TestFunction()
MsgBox function_add(3, 5)
End Sub
You can use a function without arguments, just returning according to a specific calculation algorithm. For instance:
Function tomorrow_Date() As Date
tomorrow_Date = Date + 1
End Function
It can be called as:
Sub testTommorrow_Date()
MsgBox tomorrow_Date
End Sub
Or a Sub which by default takes arguments ByRef, if not specified ByVal:
Sub Sub_add(a As Long, b As Long, c As Long)
c = a + b
End Sub
And test it as:
Sub TestSub_Add()
Dim c As Long
Sub_add 3, 2, c
MsgBox c
End Sub
Of course, a and b may be declared in the testing Sub and used like arguments, but I wanted saying that they are not relevant against c which was updated after the call...
Related
I have a formula that makes an API request every time it's executed, which makes it slow. I'd like to prevent Excel from automatically recalculating cells containing this formula but still automatically recalculate other cells.
I've tried setting calculation mode to Manual with:
Application.Calculation = xlCalculationManual
However this prevents other cells without my formula from calculating automatically.
Another idea I've had is to check if a cell has been "frozen" and then return it's current value instead of calling the API for a new value. The issue with this is that Excel doesn't provide a way to exit the function without altering the cell value.
Function MyFormula() As Variant
If CellIsFrozen() Then
MyFormula = Application.Caller.Value 'return current value
Else
MyFormula = GetNewValueFromAPI() 'expensive call to server
End If
End Function
My issue with the above is that Application.Caller.Value returns the cell value by performing a recalculation and results in an infinite recursion.
FYI - the CellIsFrozen method is just an example sub that would somehow check whether the cell was called automatically or manually.
I'm also aware of Application.Caller.Value2 and .text, unfortunately these don't help me. Value2 also causes a recalculation, and text just returns a string representation (which is not useful because it could be "######" if the value is a date and the column is too narrow).
Is there a way to interrupt Excel's recalculation process for specific formulas?
Otherwise, is it possible to extract a value of a cell without performing a recalculation - I'm guessing that Excel stores the value somewhere because it's visible on the worksheet, it makes no sense to insist on recalculating every time.
In the context my previous answer to the post involving single cell, i also want share our old experience involving multiple cells. that days We used the formula in an indexed fashion like =myformula(1)... etc and stored it in a global array. Now today thanks to your great idea of Caller function. I recreated another improvised solution involving multiple cells.
Here again in module1
Global Flag As Boolean, LastValArr(1 To 10, 1 To 2) As Variant, Ws As Worksheet, Rng As Range
Public Function MyFormula() As Variant
Dim Adr As String, X As Integer
If Flag Then
MyFormula = GetNewValueFromAPI() 'expensive call to server
Else
Adr = Application.Caller.Address
For X = 1 To 10
If InStr(1, LastValArr(X, 2), Adr) > 0 Then
MyFormula = LastValArr(X, 1)
Exit For
End If
Next
End If
End Function
Function GetNewValueFromAPI() As Variant
GetNewValueFromAPI = Application.WorksheetFunction.RandBetween(1, 1000)
End Function
Sub CalcA1()
Flag = True
Rng.Dirty
Flag = False
Ws.Range("F1").Value = IIf(Flag, "On", "Off")
End Sub
Sub ToggleFlag()
Flag = Not Flag
Ws.Range("F1").Value = IIf(Flag, "On", "Off")
If Flag Then Rng.Dirty
End Sub
in Workbook_Open event
Private Sub Workbook_Open()
Dim X As Integer
Dim Cell As Range
Set Ws = ThisWorkbook.Sheets("Sheet1")
Set Rng = Ws.Range("A1:A5")
Set Rng = Union(Rng, Ws.Range("C1:C5"))
Flag = True
Rng.Dirty
Flag = False
Ws.Range("F1").Value = IIf(Flag, "On", "Off")
End Sub
in Sheet1 Worksheet_Calculate event
Private Sub Worksheet_Calculate()
Dim X As Integer
Dim Cell As Range
X = 1
For Each Cell In Rng.Cells
LastValArr(X, 1) = Cell.Value
LastValArr(X, 2) = Cell.Address
X = X + 1
Next
End Sub
Edit: On second thought after initial feel good of posting the Demo answer, I found it lacks User friendliness and ease of copy pasting UDF formulas while working in Excel Therefore i tried improvise it further so it could be used by users don't have access to VBA code and could work with copy paste of the UDF.
So 1st I came across a solution to store the Last Values in a temp sheet (may be Very Hidden Sheet). with apprehension that working with cell access may degrade performance of the code, I refrained from posting it and I finally restored to Dictionary Object.
This solution have added with basic advantage of Auto mapping of formula cells (by searching "=myformula" in used range of the Sheet) to enable/disable calculation. This would enable users without access to code modules to work freely with UDF.
Here reference to Microsoft scripting runtime has been added.
Code in module:
Global Flag As Boolean, Ws As Worksheet, Rng As Range, Dict As Dictionary
Public Function MyFormula() As Variant
Dim Adr As String
If Flag Then
MyFormula = GetNewValueFromAPI() 'expensive call to server
Else
Adr = Application.Caller.Address
'Debug.Print Adr
MyFormula = IIf(Dict.Exists(Adr), Dict(Adr), 0)
End If
End Function
Function GetNewValueFromAPI() As Variant
'Delay (2)
GetNewValueFromAPI = Application.WorksheetFunction.RandBetween(1, 1000)
End Function
Sub CalcA1()
Flag = True
If Not Rng Is Nothing Then Rng.Dirty
'Debug.Print "in calA1"
Flag = False
Ws.Range("F1").Value = IIf(Flag, "On", "Off")
End Sub
Sub ToggleFlag()
Flag = Not Flag
Ws.Range("F1").Value = IIf(Flag, "On", "Off")
If Flag And Not Rng Is Nothing Then Rng.Dirty
End Sub
Sub BuildRange()
Application.EnableEvents = False
Dim Cell As Range
CalcCnt = CalcCnt + 1
Set Rng = Nothing
Dict.RemoveAll
For Each Cell In Ws.UsedRange.Cells
If Left(Cell.Formula, 10) = "=myformula" Then
'Debug.Print "From Sht Calc -" & Cell.Address
If Dict.Exists(Cell.Address) = False Then
Dict.Add Cell.Address, Cell.Value
Else
Dict(Cell.Address) = Cell.Value
End If
If Rng Is Nothing Then
Set Rng = Cell
Else
Set Rng = Union(Rng, Cell)
End If
End If
Next
Application.EnableEvents = True
End Sub
In Workbook_Open
Private Sub Workbook_Open()
'Dim X As Integer
Dim Cell As Range
Set Ws = ThisWorkbook.Sheets("Sheet1")
Set Dict = New Dictionary
Flag = True
BuildRange
If Not Rng Is Nothing Then Rng.Dirty
Flag = False
Ws.Range("F1").Value = IIf(Flag, "On", "Off")
End Sub
In Sheet Calculate event
Private Sub Worksheet_Calculate()
BuildRange
End Sub
If you are using an UDF in the cell, I will like to make it like this workaround.
For demo and test, Only used a single cell A1 in "Sheet1" , instead of using any API, I used WorksheetFunction.RandomBetween May use range and array if multiple cells are used.
In "Sheet1" cell A1 used =myFormula()
in a module
Public Flag As Boolean, LastVal As Variant
Public Function MyFormula() As Variant
If Flag Then
MyFormula = GetNewValueFromAPI() 'expensive call to server
Else
MyFormula = LastVal
End If
End Function
Function GetNewValueFromAPI() As Variant
GetNewValueFromAPI = Application.WorksheetFunction.RandBetween(1, 1000)
End Function
Sub CalcA1 in Module1 would be used to recalculate A1 whenever necessary. It could be called from any events also according to actual requirement.
Sub CalcA1()
Flag = True
Worksheets("Sheet1").Range("A1").Dirty
Flag = False
End Sub
In workbook Open event the the LastVal was calculated with Flag as true and then Flag was reset to false to prevent further calling GetNewValueFromAPI
Private Sub Workbook_Open()
Flag = True
Worksheets("Sheet1").Range("A1").Dirty
LastVal = Worksheets("Sheet1").Range("A1").Value
Flag = False
End Sub
In Worksheet_Calculate event of Sheet1 the LastVal is being recorded.
Private Sub Worksheet_Calculate()
LastVal = Worksheets("Sheet1").Range("A1").Value
End Sub
Working Demo
Regret, I came across this post (A Real Good Question) late, since We had already been used something in this line in our workplace. Thanks to #Pawel Czyz for editing the post it came under Active List today only.
I'm not sure how to ask this question and I'm relatively new to the community.
I am trying to get the position of a button, which I have the code for, and it works.
How can I call the Sub below from another Sub and get the integer value of the row position?
Sub ButtonRow()
' Mainlineup Macro
Dim b As Object, RowNumber As Integer
Set b = ActiveSheet.Buttons(Application.Caller)
With b.TopLeftCell
RowNumber = .Row
End With
'MsgBox "Row Number " & RowNumber
End Sub
There are two ways to pass variables around in VBA.
You can either use Public variables outside of the function, which will have scope outside of their sub, or use a Function, which is designed to return output.
Note that global public variables have certain security implications.
Functions must be named as the variable you intend to return - in this case Function RowNumber(). There are limitations on what you can do with Functions - IIRC functions cannot change any cell, worksheet, or workbook properties.
Sub WhatPosition()
MsgBox RowNumber
End Sub
Public Function RowNumber() As Integer
Dim b As Object
Set b = ActiveSheet.Buttons(Application.Caller)
With b.TopLeftCell
RowNumber = .Row
End With
End Function
or:
Public RowNumber As Integer
Sub WhatPosition()
ButtonRow
MsgBox RowNumber
End Sub
Sub ButtonRow()
Dim b As Object
Set b = ActiveSheet.Buttons(Application.Caller)
With b.TopLeftCell
RowNumber = .Row
End With
End Sub
This is a basic error or lack of understanding on my part. I've searched a number of questions here and nothing seems applicable.
Here is the code
Option Explicit
Public Function ReturnedBackGroundColor(rnge As Range) As Integer
ReturnedBackGroundColor = rnge.Offset(0, 0).Interior.ColorIndex
End Function
Public Function SetBackGroundColorGreen()
ActiveCell.Offset(0, 0).Interior.ColorIndex = vbGreen
End Function
Public Function CountBackGroundColorGreen(rnge As Range) As Integer
Dim vCell As Range
CountBackGroundColorGreen = 0
For Each vCell In rnge.Cells
With vCell
If ReturnedBackGroundColor(vCell) = 14 Then
CountBackGroundColorGreen = CountBackGroundColorGreen + 1
End If
End With
Next
End Function
Public Function GetBackgroundColor() As Integer
Dim rnge As Range
GetBackgroundColor = 3
rnge = InputBox("Enter Cell to get Background color", "Get Cell Background Color")
GetBackgroundColor = ReturnedBackGroundColor(rnge)
End Function
I was adding the last function and everything else was working prior to that and am getting the error on the first statement in that function.
For the error, one of the possible fixes is to add a reference the proper library. I don't know what is the proper library to be referenced and cannot find what library the InputBox is contained. It's an activeX control but I don't see that in the tools->reference pull down. I do have microsoft forms 2.0 checked.
I've tried various set statements but I think that the only object that I've added is the inputbox.
Any suggestions?
thanks.
Use application.inputbox, add the type as range and Set the returned range object.
Option Explicit
Sub main()
Debug.Print GetBackgroundColor()
End Sub
Public Function GetBackgroundColor() As Integer
Dim rnge As Range
Set rnge = Application.InputBox(prompt:="Enter Cell to get Background color", _
Title:="Get Cell Background Color", _
Type:=8)
GetBackgroundColor = ReturnedBackGroundColor(rnge)
End Function
Public Function ReturnedBackGroundColor(rnge As Range) As Integer
ReturnedBackGroundColor = rnge.Offset(0, 0).Interior.ColorIndex
End Function
I have a button someone can click. This button will create a range and pass it to another function that changes the value of that range.
Sub CommandButton21_Click()
Dim example As Range
Set example = Range("A1")
test (example)
End Sub
This function does not work. For some reason the range cannot be used by the other function.
Function test(x As Range)
x.Value = "changed"
End Function
Any help? The error says "Object required". I have tried to pass stuff like [A1] or making it a variant with no luck.
You can't "Call" a function, you Call a sub.....try this:
Sub CommandButton21_Click()
Dim example As Range
Set example = Range("A1")
MsgBox test(example)
End Sub
Function test(r As Range) As String
r.Value = "Changed"
test = "O.K."
End Function
Use Call:
Call test(example)
If you will not return any value, use Sub rather than Function.
I need to assign a unique name to a cell which calls a particular user defined function.
I tried
Dim r As Range
set r = Application.Caller
r.Name = "Unique"
The following code sets cell A1 to have the name 'MyUniqueName':
Private Sub NameCell()
Dim rng As Range
Set rng = Range("A1")
rng.Name = "MyUniqueName"
End Sub
Does that help?
EDIT
I am not sure how to achieve what you need in a simple way, elegant way. I did manage this hack - see if this helps but you'd most likely want to augment my solution.
Suppose I have the following user defined function in VBA that I reference in a worksheet:
Public Function MyCustomCalc(Input1 As Integer, Input2 As Integer, Input3 As Integer) As Integer
MyCustomCalc = (Input1 + Input2) - Input3
End Function
Each time I call this function I want the cell that called that function to be assigned a name. To achieve this, if you go to 'ThisWorkbook' in your VBA project and select the 'SheetChange' event then you can add the following:
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
If Left$(Target.Formula, 13) = "=MyCustomCalc" Then
Target.Name = "MyUniqueName"
End If
End Sub
In short, this code checks to see if the calling range is using the user defined function and then assigns the range a name (MyUniqueName) in this instance.
As I say, the above isn't great but it may give you a start. I couldn't find a way to embed code into the user defined function and set the range name directly e.g. using Application.Caller.Address or Application.Caller.Cells(1,1) etc. I am certain there is a way but I'm afraid I am a shade rusty on VBA...
I used this sub to work its way across the top row of a worksheet and if there is a value in the top row it sets that value as the name of that cell. It is VBA based so somewhat crude and simple, but it does the job!!
Private Sub SortForContactsOutlookImport()
Dim ThisCell As Object
Dim NextCell As Object
Dim RangeName As String
Set ThisCell = ActiveCell
Set NextCell = ThisCell.Offset(0, 1)
Do
If ThisCell.Value <> "" Then
RangeName = ThisCell.Value
ActiveWorkbook.Names.Add Name:=RangeName, RefersTo:=ThisCell
Set ThisCell = NextCell
Set NextCell = ThisCell.Offset(0, 1)
End If
Loop Until ThisCell.Value = "Web Page"
End Sub
I use this sub, without formal error handling:
Sub NameAdd()
Dim rng As Range
Dim nameString, rangeString, sheetString As String
On Error Resume Next
rangeString = "A5:B8"
nameString = "My_Name"
sheetString = "Sheet1"
Set rng = Worksheets(sheetString).Range(rangeString)
ThisWorkbook.Names.Add name:=nameString, RefersTo:=rng
End Sub
To Delete a Name:
Sub NameDelete()
Dim nm As name
For Each nm In ActiveWorkbook.Names
If nm.name = "My_Name" Then nm.Delete
Next
End Sub