I'm trying to set a global variable in Visual Studio, but I can't make it static. Is there any way for me to set the variable as static and share it across different methods, or some way to save the variable each time it changes?
You have two options:
1 - Create a class that contains a Shared variable (this is the same as a static variable in C#)
Public Class GlobalVariables
Public Shared Bar As String
End Class
You can then access this using the class name:
GlobalVariables.Bar = "Hello world"
2 - Create a module (this is akin to a static class in C#)
Public Module GlobalVariables
Public Bar As String
End Module
You can then access this value in code like this:
Bar = "Goodbye cruel world"
If you use the number 1 option presented by #Matt Wilko, you can reference the shared member either through an object instance of the class or by referencing the class without an object reference. Both point to and increment the same variable and therefore reference the same value. Although, the Visual Studio compiler provides a warning about referencing an object instance and says that it will not be evaluated, it still compiles. The compiler's recommendation is to use the class name.
Public Class GlobalVariables
Public Shared Foo As Integer
End Class
Insert the following into a form and call IncrementIntegers() from a button click event procedure and you will find that myGlobalVariables.Foo and GlobalVariables.Foo both return 20.
Private Sub IncrementIntegers()
Dim myGlobalVariables As New GlobalVariables
myGlobalVariables.Foo = 0
GlobalVariables.Foo = 0
myGlobalVariables.Foo += 10
GlobalVariables.Foo += 10
Dim iLocalInt1 = myGlobalVariables.Foo
MessageBox.Show("myGlobalVariables.Foo = " & iLocalInt1.ToString)
Dim iLocalInt2 = GlobalVariables.Foo
MessageBox.Show("GlobalVariables.Foo = " & iLocalInt2.ToString)
End Sub
Note that with option 1, Foo must be qualified with either the class name or an object name. With option 2, it is a module and not a class so an object reference cannot be created. The public variable can be referenced without qualifying it with the module name unless a variable with the same name appears in another module in which case the compiler with throw a name conflict error. For example,
Public Module1
Public Foo As String
End Module
Public Module2
Public Foo As String
End Module
Remove Module2 and Foo can be called unqualified from anywhere.
Foo = "Happy birthday"
With Module2 present, Foo must be qualified with the name as both point to different variable and represent different and independent values.
Module1.Foo = "Goodbye cruel world"
Module2.Foo = "Hello new world"
Related
One of the main problems in VBA are custom data structures and lists.
I have a loop which generates with each iteration multiple values.
So as an example:
Each loop iteration generates a string "name" an integer "price" and an integer "value".
In C# for example I'd create a class which can hold these three values and with each loop iteration I add the class object to a list.
How can I do the same thing in VBA if I want to store multiple sets of data when not knowing how many iterations the loop will have (I cant create an array with a fixed size)
Any ideas?
The approach I use very frequently is to use a class and a collection. I also tend to use an interface model to make things more flexible. An example would look something like this:
Class Module IFoo
Option Explicit
Public Sub Create(ByVal Name as String, ByVal ID as String)
End Property
Public Property Get Name() as String
End Property
Public Property Get ID() as String
End Property
This enforces the pattern I want for my Foo class.
Class Module Foo
Option Explicit
Private Type TFoo
Name as String
ID as String
End Type
Private this as TFoo
Implements IFoo
Private Sub IFoo_Create(ByVal Name as String, ByVal ID as String)
this.Name = Name
this.ID = Name
End Sub
Private Property Get IFoo_Name() as String
IFoo_Name = this.Name
End Property
Private Property Get IFoo_ID() as String
IFoo_ID = this.ID
End Property
We get intellisense from the Private Type TFoo : Private this as TFoo where the former defines the properties of our container, the latter exposes them privately. The Implements IFoo allows us to selectively expose properties. This also allows you to iterate a Collection using an IFoo instead of a Foo. Sounds pointless until you have an Employee and a Manager where IFoo_BaseRate changes depending on employee type.
Then in practice, we have something like this:
Code Module Bar
Public Sub CollectFoo()
Dim AllTheFoos as Collection
Set AllTheFoos = New Collection
While SomeCondition
Dim Foo as IFoo
Set Foo = New Foo
Foo.Create(Name, ID)
AllTheFoos.Add Foo
Loop
For each Foo in AllTheFoos
Debug.Print Foo.Name, Foo.ID
Next
End Sub
While the pattern is super simple once you learn it, you'll find that it is incredibly powerful and scalable if implemented properly. It also can dramatically reduce the amount of copypasta that exists within your code (and thus reduce debug time).
You can use classes in VBA as well as in C#: Class Module Step by Step or A Quick Guide to the VBA Class Module
And to to the problem with the array: you can create an array with dynamic size like this
'Method 1 : Using Dim
Dim arr1() 'Without Size
'somewhere later -> increase a size to 1
redim arr1(UBound(arr1) + 1)
You could create a class - but if all you want to do is hold three bits of data together, I would define a Type structure. It needs to be defines at the top of an ordinary module, after option explicit and before any subs
Type MyType
Name As String
Price As Integer
Value As Integer
End Type
And then to use it
Sub test()
Dim t As MyType
t.Name = "fred"
t.Price = 12
t.Value = 3
End Sub
For years I've been avoiding the use of Public Type UDT's in VBA, because they're hard to pass around and I never really bothered trying to understand why.. until now - it was simply easier to just create a class module and work with actual objects instead.
But recently I gave it a shot, and once I figured they had to be passed ByRef (as an array would), things started to look like I could start using them.
So I defined a Public Type in a standard module, got this compile error:
So I moved the Public Type into a class module, made the class PublicNotCreatable, and then got this compile error:
Here's some code to reproduce the compile error.
Class module "Something":
Option Explicit
' cannot define a public user-defined type within an object module
Public Type TSomething
Foo As Integer
End Type
Public Function Create(ByRef info As TSomething) As Something
End Function
If you move the definition of TSomething to a standard module, you'll get the other compiler error, telling you that the public UDT must be defined in a public object module (i.e. a class module)... which takes you back to square one.
So if you cannot define a Public Type in a class module, why would the compiler throw a fit and even mention "public user defined types defined in public object modules" if such a thing can't legally exist?
Did it work in VB6 and the compiler message is a remnant of that version? Or is the reason somewhere in how COM works? Is it just me or the two error messages are contradicting each other? Or there's something I'm not understanding?
Obviously I'm misusing/abusing UDT's here. So what are they supposed to be used for, if not for passing a "record" to some method?
From standard module it works without any error. Following code threw no error.
Public Type TEST_TYPE
Prop1 As String
End Type
Public Function fTest(ByRef param1 As TEST_TYPE) As String
param1.Prop1 = "Hello from function"
End Function
Public Sub sTest(ByRef param1 As TEST_TYPE)
param1.Prop1 = "Hello from Sub"
End Sub
Public Sub caller()
Dim p As TEST_TYPE
'/Call Sub
Call sTest(p)
MsgBox p.Prop1
'/Call Function
Call fTest(p)
MsgBox p.Prop1
End Sub
One issue with UDT is about Forward referencing. So this will not compile, apart from that It works perfectly fine with standard modules.
Public Type TEST_TYPE
Prop1 As String
Prop2 As TEST_TYPE2 '/ Fails due to Forward referencing. TEST_TYPE2 should be declared before this UDT.
End Type
Public Type TEST_TYPE2
Prop3 As String
End Type
Edit:
However, the work around to use the UDT in class is Friend
VBA Code for Class
'/ Using UDT in VBA-Class
Private Type TEST_TYPE3
Prop3 As String
End Type
Public Sub caller()
Dim p As TEST_TYPE3
p.Prop3 = "Hello from Class"
Call testClassUDT(p)
End Sub
Friend Sub testClassUDT(p As TEST_TYPE3)
MsgBox p.Prop3
End Sub
Here's a Type being passed as a parameter to a class method, and being returned by a class method.
First the class SomeClass (doesn't need to be PublicNotCreatable)
Option Explicit
Sub test(foo As TFooBar)
Dim s As String
s = foo.foo
End Sub
Function ReturnTFoo() As TFooBar
ReturnTFoo.bar = "bar"
ReturnTFoo.foo = " bar"
End Function
And the Module:
Option Explicit
Public Type TFooBar
foo As String
bar As String
End Type
Sub test()
Dim c As SomeClass
Set c = New SomeClass
Dim t1 As TFooBar
Dim t2 As TFooBar
t1.bar = "bar"
t1.foo = "Foo"
c.test t1
t2 = c.ReturnTFoo
End Sub
I have series of basic calculations on a form triggered by the form load event:
Dim someVariableA As Integer
Dim someVariableB As Integer
Dim someVariableX As Integer = 1
Dim someVariableY As Integer = 2
someVariableA = someVariableX + someVariableY
someVariableB = someVariableX * someVariableY
I now require the exact calculations for a separate form. Rather than pasting the same again, is there a means by which I can place the calculation in a method that both forms can call upon?
Public Function someFunction()
' Above calculations placed here instead.
End Function
Private Sub someSub()
' Call calculations.
someFunction()
' ...now output and use variables from function.
TextBox1.Text = someVariableA
TextBox2.Text = someVariableB
End Sub
Ultimately, I'm expecting something that behaves like PHP's include function.
You are running into the issue of Scope. Where a variable is declared determines its availability. You probably know how to make variables visible to all methods in a form:
Public Class Form1
Private varA As String
Private var2 As Integer
These will be available to all methods in the form because the are declared at the Form level (unlike a variable declared (Dim) inside a procedure which will exist only locally). To make them visible to all methods in the app's forms, declare them in a module:
Public Module1
Friend varA As String
Friend var2 As Integer
Friend varX As DateTime
Declared in a module (1980s style!), they become global variables for your app. But there is good reason to avoid this. It is so easy to change a value, you can have methods which accidentally or unwittingly do so - remember they are now visible to everything even those procedure which might have no good reason to change them! Then, you spend time trying to locate those methods which are changing the value(s) but should not be.
A gigantic benefit of OOP is the ability to avoid this by using classes to hold the data and contain methods to manage that data - they can do everything from loading and saving to the calcualations you need. A sign that this might be what you need is that you have some variables you want to be global and already have methods which are global, combine them and you have a class:
Public Class Foo
Private varA As String
Private var2 As Integer
' some of these things might be better as Properties
' this allows the subscribers (users of the class) to change the
' values directly:
Public Property SomeDate As DateTime
Public Property Name As String
Public Property Value As Integer
Public Function GetSomething(aVar As Integer) As Integer
var2 += aVar ' update var2 for example
Return var2 ' return new value
End Function
To make the class available to all forms:
Public Module1
Friend myFoo As Foo ' makes it visible to all forms
Then create an instance of your class from your main form:
Public Class Form1
Private Sub Form_Load(....
myFoo = new Foo
Now, myFoo is an instance of the Foo class which not only houses those variables, but the methods to manage them:
Private Sub button_click(....
someVar = myFoo.DoSomething(42)
Came across something I found interesting and would love an explanation.
Edit
This question is not meant to be answered with what should be done to fix it. I know the fixes. I want an explanation of why the compiler does what it does. Ex. Are the private functions not considered given this scenario?
Problem
I have a class that has a public shared(static) function called WhatIs. WhatIs takes a parameter that has a collection of objects. the code iterates over this collection and calls a WhatIs function that has a parameter matching type of what the object is.
When executed, an InvalidCastException exception is thrown because the execution is trying to call the WhatIs function that started this, not the one for the type provided.
That's weird, but what made it odd to me was when you change the private shared functions to public shared then it works fine.
Even odder, when you explicit cast the object then it works even if the function is private.
What?! someone please explain
Code
the guts:
Public Class House
Public Property Furniture As ICollection(Of Object)
Public Sub New()
Furniture = New List(Of Object)
End Sub
End Class
Public Class Chair
Public Property IsComfortable As Boolean
End Class
Public Class Table
Public Seats As Integer
End Class
Public Class HouseExaminer
Public Shared Function WhatIs(thing As House) As String
Dim isA As String = "a house that contains "
For Each item In thing.Furniture
isA &= WhatIs(item)
Next
Return isA
End Function
Private Shared Function WhatIs(thing As Chair) As String
Return "a " & If(thing.IsComfortable, "comfortable", "uncomfortable") & " chair "
End Function
Private Shared Function WhatIs(thing As Table) As String
Return "a table that seats " & thing.Seats & " iguanas"
End Function
End Class
to test
Imports System.Text
Imports Microsoft.VisualStudio.TestTools.UnitTesting
Imports stuff
<TestClass()>
Public Class HouseExaminerTests
<TestMethod()>
Public Sub TestWhatIs()
Dim given As New House()
Dim expected As String
Dim actual As String
given.Furniture.Add(New Chair() With {.IsComfortable = True})
given.Furniture.Add(New Table() With {.Seats = 4})
expected = "a house that contains a comfortable chair a table that seats 4 iguanas"
actual = HouseExaminer.WhatIs(given)
Assert.Equals(expected, actual)
End Sub
End Class
result
debug the test and you get this:
InvalidCastException
Method invocation failed because 'Public Shared Function WhatIs(thing As stuff.House) As String' cannot be called with these arguments:
Argument matching parameter 'thing' cannot convert from 'Chair' to 'House'.
These changes make it work but why?!
make em public
change the private shared functions in HouseExaminer to public, rerun test. spoiler, it works
explicitly cast the objects
change them back to private then replace
isA &= WhatIs(item)
with
If TypeOf item Is Chair Then isA &= WhatIs(CType(item, Chair))
If TypeOf item Is Table Then isA &= WhatIs(CType(item, Table))
rerun test, and what do u know, it works
Firstly, it looks like you have implicit conversions turned on. That is the start of the issue. Secondly, you define Furniture as a List(of Object). Your first call to WhatIs is succeeding. The compiler is making a best guess as to which overload to use when passing what it sees as simply Object as it iterates through thing.Furniture, and it determines the public static version of the WhatIs method to be the most appropriate. It then attempts to implicitly convert Object to House, and inevitably fails.
Why does casting work? Because it takes the guess work out of determining which overload to use.
Moral of the story is: Don't make the compiler guess. Implicit conversion can lead to tricky bugs.
Edit: Why doesn't the compiler see the other overloaded functions?
The compiler has to determine the correct overload to use at compile time. It does not wait until runtime to determine which overload to use, and therefore doesn't have the luxury of inspecting the type of the object to determine the most appropriate overload.
Since the compiler only knows that furniture is a List(Of Object), technically (with implicit conversion turned on) all three of the overloads are deemed "appropriate," but the compiler must choose one. It ranks the possible overload candidates, and chooses the public version ahead of the private ones.
Use always
Option Strict On
You cannot make it more flexible by adding Methods equal in name, just with different parametertypes.
Update
Private Function ShowMe(data As Integer) As String
Return data.ToString
End Function
Private Function ShowMe(data As String) As String
Return data
End Function
Private Function ShowMe(data As Double) As String
Return data.ToString
End Function
Dim bla As New List(Of Object)
if you then call
bla.Add(12)
bla.Add("hi")
bla.Add(1.2)
Dim text As String
text = ShowMe(bla(0))
text = ShowMe(bla(1))
text = ShowMe(bla(2))
then the compiler will always complain that the correct method does not exist, because the correct method is not selected by checking the type, instead it is selected by the definition, for which type the container is defined for.
Private Function ShowMe(data As Object) As String
Return data.ToString
End Function
this would be called for all integer, doubles and strings. If it is not available, then some methods are used that can do some kind of automatic conversion. Thats why you can put an integer in a float, or put a number in a string.
One way would be to check for its type and do an explizit type conversion
For Each ele As Object In bla
If TypeOf ele Is Integer Then
text = ShowMe(CInt(ele))
ElseIf TypeOf ele Is Double Then
text = ShowMe(CDbl(ele))
Else
text = ShowMe(CStr(ele))
End If
Next
But this is still not so clean. If you want to access properties that all objects should support, then put them in a container and define the type as something that assures that those properties exist.
Is it possible to create a class in VB.NET that can be compared to a string in a switch statement? For example, let's say I have a class Foo:
Public Class Foo
Public Bar As String = "test"
End Class
Is it possible to implement some interface or override some equality operator so that I could use Foo like so?
Dim foo As New Foo()
Select Case "test"
Case foo
' It worked!
End Select
Yes, you can define implicit conversion operators in the .NET languages that allow the compiler to implicitly convert an instance of your class to another type.
In VB.NET, this is called the "Widening" operator. You define it like this:
Public Class Foo
Public Bar As String = "test"
Public Shared Widening Operator CType(ByVal f As Foo) As String
Return f.Bar
End Operator
End Class
There is also explicit conversion, which is called the "Narrowing" operator in VB.NET. Just as it sounds, the former conversion can happen automatically, while the latter requires you to explicitly instruct the compiler to perform the conversion. This can prevent some nasty surprises, but also clutters the code.
I think, based on your question, that you want to see if Bar in a given class instance is equal to test.
If so, you can leverage the ToString() override:
Public Class Foo
Public Bar As String = "test"
Public Overrides Function ToString() As String
Return Bar
End Function
End Class
and your case statement then becomes:
Dim foo As New Foo()
Select Case "test"
Case foo.ToString
' It worked!
End Select
You could also implement a default property, but that isn't as clean because a default property are required to have a parameter.
Public Class Foo
Public Bar As String = "test"
Default Public ReadOnly Property DefaultProp(JustUseZero As Integer) As String
Get
Return Bar
End Get
End Property
End Class
which would then be called as:
Dim foo As New Foo()
Select Case "test"
Case foo(0)
' It worked!
End Select