Different ways of using a variable across different subroutines - vba

I'm trying to set up a sub to be called upon and use the value of its result in the main sub. So far I've been using Function to carry over the value. However, I was wondering if there are any alternative ways of doing the same thing? I figured ByVal/ByRef is another way to do it by using a Sub instead of Function. My current codes are as follow:
Sub Main()
Dim i as Long
i = lr("A")
'some other calculations using i
End Sub
Function lr(Tar As String) As Long
Dim twb As Workbook
Set twb = ThisWorkbook
lr = ThisWorkbook.Sheets(1).Range(Tar & Rows.Count).End(xlUp).Row
End Function
My question is, How would I write this if I were to use a Sub instead of Function? Thanks!

So far I've been using Function to carry over the value.
Great, that's what functions are for! When you only need to return a single value, the best way is always going to be a function.
Things get fuzzier when you start needing to return two or more values. You could:
Use ByRef parameters and use them as "out" values.
This is "ok" for procedures (Sub), and confusing for functions - what determines which parameter gets to be the function's return value, and which parameters get to be passed ByRef? How does the calling code know whether or not to pass an initialized value to those ByRef parameters?
A naming convention can help here:
Public Sub Foo(ByVal foo1 As String, ByRef outBar1 As String, ByRef outBar2 As String)
An "out" prefix can tell the calling code that the parameter is an out value.
Scope the variables at a level that is accessible by both the caller and the callee.
This is a bad practice that can easily lead to spaghetti code. Avoid it - variables should have the smallest necessary scope possible, and be passed between methods/functions/procedures/modules, not just globally scoped and accessed by anyone at any given time!
Create a class to encapsulate all the values the function should return.
Definitely more object-oriented, results in much cleaner, readable code. The only downside is that VBA doesn't really encourage you to do this, and consistently doing that will result in a myriad of classes that you can't quite organize.

Related

Excel Visual Basic call function as stand-alone routine

I'll get straight to the point; I'm trying to define a Function in Visual Basic which can simply be called without having to have something on the 'other side of the equation' as it were. Essentially I want to be able to define a routine which can be passed a series of variables and executes a routine based on those variables.
I currently have the following Function defined:
Function ImportData(WebAddress As String, OutputCell As Range)
With ActiveSheet.QueryTables.Add(Connection:= _
"URL;" & WebAddress & _
"bin/excelget.exe?TICKER=msft", _
Destination:=OutputCell)
.BackgroundQuery = True
.TablesOnlyFromHTML = True
.Refresh BackgroundQuery:=False
.SaveData = True
End With
End Function
What I want to be able to do is simply call this Function but not necessarily make something equal to or use this Function to manipulate something. So, for example, I'd like to be able to do something like the following:
Private Sub ExampleButton_Click()
ImportData("http://www.exampleURL.com/examplejsonhtmlformat","A3")
End Sub
When this Function is called, it simply steps through the Function using the variables defined. There is already an output defined in OutputCell so a cell doesn't need to 'equal' the output of this Function.
If anybody has any input, it would be much appreciated.
Thanks.
You want to make it a Sub - it is exactly what you describe: code that can be called but that doesn't return a value. Note that you don't put the parameters of a sub in parentheses when you call it. If you have
Sub myTest(a,b)
Then you call it with
myTest thing1, thing2
And NOT with
myTest(thing1, thing2)
Update based on excellent comments from #hnk and #Ioannis:
It is possible to call a Sub with
Call myTest(thing1, thing2)
But there is a subtlety, which has to do with the difference between passing a variable by value or by reference. When you pass by value, you make a copy: changing the parameter in the program does not change the original. However, when you pass by reference, it becomes possible to change the value of the parameter inside the sub - and that becomes the new value of the parameter after the sub returns. I'd the prototype says you expect the value to be passed by reference:
Sub MyTest(ByRef a)
Then you can override this behavior as follows:
Call with Passing by
MyTest a Reference
MyTest (a) Value
Call MyTest(a) Reference
Call MyTest((a)) Value
In general it is better to be explicit in the function prototype - specify if you want byVal or byRef and if the calling program gets it wrong you get warned bu the compiler. More info at Hidden features of VBA
If you are not at least a little bit confused or at least annoyed at Microsoft after this, you were not paying attention...
afterword it was pointed out by Rory that it is possible to call functions without assigning their return value. So you can have either
X = myFunc(y)
Or
myFunc y
Both will call the function - but note that when you don't expect a return value you don't use parentheses. Oh Microsoft, what were you thinking...

VBA: passing variables (from worksheet) into functions

A quick one for noob like me!
So usually I pass in some range of data from worksheet into function like that:
public function foo (someRange as range)
dim someData as double
if IsNumeric(someRange.value), do some crap
end function
The problem occur when I try to code some function that uses this function foo. Instead of range i would like to pass in a double(). So if I were to fix this, i can either:
a. I have seen some other site uses "someRange as Variant" instead (which then avoid problem like i face). However, is it "good coding practice"?
b. use a wrapper function foo_wrap instead
You could use a Variant and then TypeName to find out what kind of argument was supplied:
?TypeName(range("A1"))
Range
?TypeName(45.66)
Double
?TypeName(array(34.5,56.7))
Variant()
Personally, I would just create two separate Functions. Using one function would just be messy to me. For a single function NOT to just duplicate code, it would have to extract all the Range values into a Double array. This just tells us that it should be two functions.
Creating a wrapper-function is an option, but I'll leave you to decide whether this is a good solution for you.
If you did want to pursue a multi-purpose function then you could investigate ParamArray:
Used only as the last argument in arglist to indicate that the final
argument is an Optional array of Variant elements.
This would allow you to create a Function that behaves similarly to the built-in functions (SUM, etc.) which can accept a variable number of arguments, which can be Ranges and/or values. However, particularly as you are a noob, I would ignore this possibility for a while. The KISS principle.

Is it possible to declare a public variable in vba and assign a default value?

I want to do this but it won't compile:
Public MyVariable as Integer = 123
What's the best way of achieving this?
.NET has spoiled us :)
Your declaration is not valid for VBA.
Only constants can be given a value upon application load. You declare them like so:
Public Const APOSTROPHE_KEYCODE = 222
Here's a sample declaration from one of my vba projects:
If you're looking for something where you declare a public variable and then want to initialize its value, you need to create a Workbook_Open sub and do your initialization there.
Example:
Private Sub Workbook_Open()
Dim iAnswer As Integer
InitializeListSheetDataColumns_S
HideAllMonths_S
If sheetSetupInfo.Range("D6").Value = "Enter Facility Name" Then
iAnswer = MsgBox("It appears you have not yet set up this workbook. Would you like to do so now?", vbYesNo)
If iAnswer = vbYes Then
sheetSetupInfo.Activate
sheetSetupInfo.Range("D6").Select
Exit Sub
End If
End If
Application.Calculation = xlCalculationAutomatic
sheetGeneralInfo.Activate
Load frmInfoSheet
frmInfoSheet.Show
End Sub
Make sure you declare the sub in the Workbook Object itself:
Just to offer you a different angle -
I find it's not a good idea to maintain public variables between function calls. Any variables you need to use should be stored in Subs and Functions and passed as parameters. Once the code is done running, you shouldn't expect the VBA Project to maintain the values of any variables.
The reason for this is that there is just a huge slew of things that can inadvertently reset the VBA Project while using the workbook. When this happens, any public variables get reset to 0.
If you need a value to be stored outside of your subs and functions, I highly recommend using a hidden worksheet with named ranges for any information that needs to persist.
Sure you know, but if its a constant then const MyVariable as Integer = 123 otherwise your out of luck; the variable must be assigned an initial value elsewhere.
You could:
public property get myIntegerThing() as integer
myIntegerThing= 123
end property
In a Class module then globally create it;
public cMyStuff as new MyStuffClass
So cMyStuff.myIntegerThing is available immediately.
Little-Known Fact: A named range can refer to a value instead of specific cells.
This could be leveraged to act like a "global variable", plus you can refer to the value from VBA and in a worksheet cell, and the assigned value will even persist after closing & re-opening the workbook!
To "declare" the name myVariable and assign it a value of 123:
ThisWorkbook.Names.Add "myVariable", 123
To retrieve the value (for example to display the value in a MsgBox):
MsgBox [myVariable]
Alternatively, you could refer to the name with a string: (identical result as square brackets)
MsgBox Evaluate("myVariable")
To use the value on a worksheet just use it's name in your formula as-is:
=myVariable
In fact, you could even store function expressions: (sort of like in JavaScript)
(Admittedly, I can't actually think of a situation where this would be beneficial - but I don't use them in JS either.)
ThisWorkbook.Names.Add "myDay", "=if(isodd(day(today())),""on day"",""off day"")"
Square brackets are just a shortcut for the Evaluate method. I've heard that using them is considered messy or "hacky", but I've had no issues and their use in Excel is supported by Microsoft.
There is probably also a way use the Range function to refer to these names, but I don't see any advantage so I didn't look very deeply into it.
More info:
Microsoft Office Dev Center: Names.Add method (Excel)
Microsoft Office Dev Center: Application.Evaluate method (Excel)
As told above, To declare global accessible variables you can do it outside functions preceded with the public keyword.
And, since the affectation is NOT PERMITTED outside the procedures, you can, for example, create a sub called InitGlobals that initializes your public variables, then you just call this subroutine at the beginning of your statements
Here is an example of it:
Public Coordinates(3) as Double
Public Heat as double
Public Weight as double
Sub InitGlobals()
Coordinates(1)=10.5
Coordinates(2)=22.54
Coordinates(3)=-100.5
Heat=25.5
Weight=70
End Sub
Sub MyWorkSGoesHere()
Call InitGlobals
'Now you can do your work using your global variables initialized as you wanted them to be.
End Sub
You can define the variable in General Declarations and then initialise it in the first event that fires in your environment.
Alternatively, you could create yourself a class with the relevant properties and initialise them in the Initialise method
This is what I do when I need Initialized Global Constants:
1. Add a module called Globals
2. Add Properties like this into the Globals module:
Property Get PSIStartRow() As Integer
PSIStartRow = Sheets("FOB Prices").Range("F1").Value
End Property
Property Get PSIStartCell() As String
PSIStartCell = "B" & PSIStartRow
End Property
there is one way to properly solve your question. i have the same concern with you for a long time. after searching and learning for a long time, finally i get a solution for this kind of question.
The solution is that no need to declare the variable and no need to set value to the variable, and even no need VBA code. Just need the "named range" in excel itself.
For example, the "A1" cell content is "hello, world". and we define the "A1" cell a name as "hello", that is, the "A1" cell have a name now, it's called "hello".
In VBA code, we just need use this method [hello], then we can get the "A1" value.
Sub test()
msgbox [hello]
end sub
the msgbox will show "Hello, word".
this way, we get a global variable without any declaration or assignment. it can be used in any Sub or Function.
we can define many named range in excel, and in VBA code we just use [] method to get the range value.
in fact, the [hello] is a abbreviation of the function Evaluate["Hell"], but it's more shorter.
It's been quite a while, but this may satisfy you :
Public MyVariable as Integer: MyVariable = 123
It's a bit ugly since you have to retype the variable name, but it's on one line.

Should I use Call keyword in VB/VBA?

I use the Call keyword when calling subs in VB/VBA. I know it's optional, but is it better to use it or leave it off? I've always thought it was more explicit, but maybe it's just noise.
Also, I read this on another forum: Using the Call keyword is faster because it knows that it is not going to return any values, so it doesn't need to set up any stackspace to make room for the return value.
Ah ha. I have long wondered about this and even reading a two inch thick book on VBA basically says don't use it unless you want to use the Find feature of the VBE to easily find calls in large projects.
But I just found another use.
We know that it's possible to concatenate lines of code with the colon character, for example:
Function Test(mode as Boolean)
if mode = True then x = x + 1 : Exit Sub
y = y - 1
End Sub
But if you do this with procedure calls at the beginning of a line, the VBE assumes that you're referring to a label and removes any indents, aligning the line to the left margin (even though the procedure is called as intended):
Function Test()
Function1 : Function2
End Function
Using the Call statement allows concatenation of procedure calls while maintaining your code indents:
Function Test()
Call Function1 : Call Function2
End Function
If you don't use the Call statement in the above example, the VBE will assume that "Function1" is an label and left align it in the code window, even though it won't cause an error.
For VB6, if there is any chance it will be converted to VB.NET, using Call means the syntax doesn't change. (Parentheses are required in VB.NET for method calls.) (I don't personally think this is worth the bother -- any .NET converter will at least be able to put in parentheses when required. I'm just listing it as a reason.)
Otherwise it is just syntactic sugar.
Note the Call keyword is likely not to be faster when calling some other method/function because a function returns its value anyway, and VB didn't need to create a local variable to receive it, even when Call is not used.
I always use Call in VBA. To me, it just looks cleaner. But, I agree, it's just syntactic sugar, which puts it squarely the realm of personal preference. I've come across probably a dozen full time VBA guys in the past few years, and not one of them used Call. This had the added advantage that I always knew which code was mine. :p
No, it'll just add 7 characters per call with no given benefit.
No one covered this important distinction: in some (common) situations, Call prevents parentheses around function (and sub) arguments from causing the arguments to be strictly interpreted as ByVal.
The big takeaway for you is that if you DO use parentheses around arguments to a routine, perhaps by rote or habit, even though they are not required, then you SHOULD USE Call to ensure that the routine's implicit or explicit ByRef is not disregarded in favor of ByVal; or, instead, you should use an "equal sign" assignment of the return value to prevent the disregard (in which case you would not use Call).
Again, that is to protect you from unfavorably getting ByVal from a routine. Conversely, of course, if you WANT ByVal interpretation regardless of the routine's declaration, then LEAVE OFF the Call (and use parentheses).
Rationale: summarizing "ByRef and ByVal Parameters"
If
1. there is an assignment of a function call retval, e. g.
iSum = myfunc(myArg)
or
2. "Call" is used, e. g.
call myFunc(myArg)
or
call mySub(myArg)
then the parentheses strictly delineate the calling argument list; the routine declaration determines ByVal or ByRef. OTHERWISE the parentheses force ByVal to be used by the routine - even though ByVal was not specified in the routine. Thus,
mySub(myArg) 'uses ByVal regardless of the routine's declaration, whereas
Call mySub(myArg) 'uses ByRef, unless routine declares ByVal
Also note that Call syntactically mandates use of parentheses. You can go
mySub myArg
but you can't go
call mySub myArg
but you CAN go
call mySub(myArg)
(and parentheses are syntactically required for assignment of Function return value)
NOTE however that ByVal on the routine declaration overrides all of this. And FYI, ByRef is always implied in the declaration if you are silent; thus TMK ByRef has no apparent value other than documentary.
Repeating from above: The big takeaway for you is that if you DO use parentheses around arguments to a routine, perhaps by rote or habit, even though they are not required, then you SHOULD USE Call to ensure that the routine's implicit or explicit ByRef is not disregarded in favor of ByVal; or, instead, you should use an "equal sign" assignment of the return value to prevent the disregard (in which case you would not use Call).
Again, that is to protect you from unfavorably getting ByVal from a routine. Conversely, of course, if you WANT ByVal interpretation regardless of the routine's declaration, then LEAVE OFF the Call (and use parentheses).
I use Call for all VBA development of common library functions that I possibly will use in VB.NET. This allows me to move code using copy and paste between all the flavors of VB. I do this to avoid the syntax errors that the code editor creates when it "formats" or "pretty prints" the pasted code. The only edits are usually Set statement inclusion/exclusion.
If you have no plans to move your VB/VBA code to VB.NET, then there is no need to use the Call statement.
The only case I found "call" is useful is quite an accident, about some special operators.
Dim c As IAsyncOperation(Of StartupTask) = StartupTask.GetAsync("Startup")
……
(Await c).Disable()
I got a syntax error for the second line, just like what you'll get with a "New" operator. I really don't want a new variable, which is too inelegant for me. So I tried:
DirectCast(Await c, StartupTask).Disable()
This is syntactically correct. But then the IDE hinted me that the "DirectCast" is unnecessary and gave a simplification. Yes, that is:
Call (Await c).Disable()
That's why I love VS2017 Preview. 😄
If you read the MSDN Support page for the Call Statement, for the specific case o VBA, at least, it does say that Call is optional, but what is very relevant about it and nobody seems to notice is this quoted line:
"If you use either Call syntax to call any intrinsic or user-defined function, the function's return value is discarded."
This is why Call is far from useless. Say you're writing Sub SupportTasks that does a lot of very relevant stuff for you Main Subs (for example, it imports data from a file to be used by different procedures). Now, notice that since SupportTasks is reading external data, there's always a fat chance this data will not come standard and the sub will not be able to fulfill its role. What do you do?
You could, for example, use boolean functions that return False if something goes wrong. Instead of calling a sub, call a function SupportTasks inside and If statement that will exit the Main sub if there's an anomaly:
If Not SupportTasks(SomeArgument) Then
Application.ScreenUpdating = True
Exit Sub
'Else continue the Main sub regularly without writing anything in here
End If
If you're wondering what the heck this has to do with Call, consider the following: in another sub, I call SupportTasks, but I do not need its returned boolean value (for instance, I'm certain an error won't occur). Well, if I don't put it in an If statement or assign the function to a useless variable, VBA will not compile and return me an error (procedure call invalid blah blah blah must assign value to something blah blah blah). That's where Call comes in to save the day!
Call SupportTasks(SomeArgument) '<< "Call Function" call doesn't return an error
If you still think it's useless, think of it as a resource to stay organized. Writing separate procedures for routines shared by many procedures makes your code shorter and more comprehensible, specially when you're writing really large applications. ERPs built out of Excel-Access integrations, for example, can be easier to operate, repair and customize if your IT dept slow to deliver/implement the real system...
To conclude, some internet wisdom:
Always write your code as if the person who will review it is a murderous psychopath who knows where you live.
Amen.
I'm 7 years late to the party, but I just happened to come across the Call keyword a few minutes ago while reading something on MSDN. In particular, it was used to do something I thought was impossible in VB.NET (as opposed to C#) -- which is related to #FCastro's answer.
Class Test
Public Sub DoSomething()
Console.WriteLine("doing something")
End Sub
End Class
Sub Main()
Call (New Test()).DoSomething()
End Sub
In the odd case you don't need the actual object instance but require one of its methods, you can use Call to save a line. Note that this is unnecessary when it's the right-hand side of an operation:
Class Test
Public Function GetSomething() As Integer
Return 0
End Function
End Class
Sub Main()
Dim x As Integer = (New Test()).GetSomething()
End Sub

Byref New Object. Is it okay top pass New Object as "byref"

Below I tried to do an Example:
Public Function UserData(ByVal UserDN As String) As DataTable
Dim myTable As DataTable = UserData_Table()
Dim dr As DataRow
dr = myTable.NewRow()
SplitOU2(UserDN, dr("OUDN"), dr("Organisation"), New Object)
dr("UserDN") = UserDN
myTable.Rows.Add(dr)
Return myTable
End Function
Below is the called method:
Friend Sub SplitOU2(ByVal inDN As String, ByRef OUDN As Object, ByRef Organisation As Object, ByRef VerksamhetTyp As Object)
By doing this I can skip to declare the in this example "useless" variable
Dim VerksamhetTyp as Object = "".
Perhaps it looks a little ugly but to have to declare unused variables can also be confusing.
Summary: Check whether or not the method really needs those parameters to be ByRef. Also check that you really don't care about anything it does to the parameters. After scrupulous checking, it's okay to do this - nothing "bad" will happen in terms of the CLR, because it's just a compiler trick under the hood.
Well, VB (unlike C#) will let you do this. Behind the scenes it's effectively creating a new variable and passing it by reference - after all, it has to for the method to be called properly. However, I'd say this is usually a bad idea. The point of ByRef is that you use the value after it's been set within the method.
Do you really need all those parameters to be ByRef in the first place? If you find yourself doing this a lot for a particular method, you could always write a wrapper method which called the original one, but didn't have the ByRef parameters itself.
(I usually find that methods with a lot of ByRef parameters indicate either a lack of understanding of reference types in .NET, or that the parameters should be encapsulated in their own type.)
Having said all of this, it's not always incorrect to ignore the value of a ByRef argument after calling the method. For example, if you just want to know whether or not some text can be parsed as an integer, then using Int32.TryParse is reasonable - but only the return value is useful to you.
The reason that I consider to use this has to do with that the method has even more parameters and that different operation overloads gets the same signature ….
The fact that it works is quite fun and somthing I became awarae óff by chance ...