I am trying to develop a simple class-based function that will modify a previous value determined by the function, that is, it is a recurrence relationship.
In essence, I am developing my own random number generator which will work the same way the current Random class works, i.e.
Dim ran as New Random(123456)
For i = 0 To 9
MessageBox.Show(ran.NextDouble & " " & ran.Next(1,11))
Next
I can successfully do this using a class-based method simply by sending a value ByRef, but as you know for a method call, the old value to be modified needs to be placed inside the call to the method. Thus, I am trying to overcome use of a method or a global typed variable, and rather would like the instantiated class to somehow remember what the current value is.
The example code below attempts to multiply the value _value by 2 during every function call, so the expected result would be 2, 4, 8, 16, etc. However, even though a 2 is initially sent to the constructor, the value of _value is always returned as zero.
Class Example
Public _value As Integer
Public Sub New(ByVal _value)
End Sub
Public Function Value() As Integer
_value *= 2
End Function
End Class
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim x As New Example(2)
For i = 0 To 9
MessageBox.Show(x.Value)
Next
End Sub
Normally fields are Private. If you want to expose data from your class you would use a Public Property.
Change the name of the parameter for Sub New. If properly qualified your name will work but it is confusing. You must do something with the passed in value! Assign it to your field _value.
Your Function has no return value. It simply changes the value of _value. If you don't return anything use a Sub. Change the name of your Function to something meaningful. Add a Return statement to send a value back to the calling code.
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim x As New Example(2)
For i = 0 To 9
MessageBox.Show(x.DoubleValue.ToString)
Next
End Sub
Class Example
Private _value As Integer
Public Sub New(ByVal Input As Integer)
_value = Input
End Sub
Public Function DoubleValue() As Integer
_value *= 2
Return _value
End Function
End Class
I think I'm missing an important idiosyncrasy of Visual Basic. I mostly just know C++, so I don't understand the need for the Dim keyword, and the As New keyword. However, I am familiar with the New keyboard in C++, which is used when you use a pointer.
So in Visual Basic are all variables pointers? What's going on here?
I want to make a custom Class. And have a list or array of say, four of them, and for them to be Globally accessible all throughout my program. Do I declare them in my Form Class? My program is just the one Form.
Ok, so I've got the list to exist, but only in the button click sub routine.
How do I make my List of Clocks Global?
Public Class MyClock
Public elapsedtime As New TimeSpan(0, 0, 0, 0, 0)
Public active As Boolean
Public Sub New()
elapsedtime = New TimeSpan(0, 0, 0, 0, 0)
End Sub
Public Function Display()
Return elapsedtime.ToString
End Function
Public Sub Start()
active = True
End Sub
Public Sub Stopclock()
active = False
End Sub
Public Sub Toggle()
If (active = True) Then
active = False
Stopclock()
Else
active = True
Start()
End If
End Sub
End Class
Public Class Form1
Dim ticincrement As New TimeSpan(0, 0, 0, 0, 100)
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
Dim ClockList As New List(Of MyClock)()
ClockList.Add(New MyClock)
ClockList.Add(New MyClock)
ClockList.Add(New MyClock)
ClockList.Add(New MyClock)
Console.WriteLine(ClockList.Count)
Console.WriteLine(ClockList(0).elapsedtime)
End Sub
End Class
Ok, the “new” keyword is just a short cut that allows you to declare the variable (it is a pointer) and create + initialize an instance of the class being assigned to that variable.
The following two are same.
Dim TestClock as MyClock
TestClock = New MyClock
And
Dim TestClock as New MyClock.
However, ONE very significant difference in the above two code snips should be noted:
In the 2nd example in which we declare using “new”, the new instance of the object (class) is thus created.
Often, we want to control WHEN the instance of the class (and the new (initialize) event of the class will run. (So don’t use new in the var define).
The other issue of that WHEN you leave out the NEW keyword, then the class variable is essentially a pointer to the class, but one that does not yet exist.
Eg this:
Dim pbar1 As New Pbar
Dim pbar2 As Pbar
pbar2 = pbar1
So, pbar2 we NEVER created an instance of the class, so it really is a pointer. So, from any code that follows, I can use pbar1, or pbar2 – they are pointing to and are the same instance of the one class we created with “new”.
This concept is important, since say if you going to pass a class to another form, then you could/would declare the class without new keyword as global scoped to that form.
Note that you should declare the variable as “public” if you want other forms/code to use that variable.
Eg:
Imports System.Threading
Public Class Form1
Dim MyPbar As Pbar
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Etc. etc.
And if public global, we would use:
Imports System.Threading
Public Class Form1
Public MyPbar As Pbar <--- pubic scope - can use outside of form
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Etc. etc.
Now
Then “any” place else in the project, you can go:
Form1.MyPbar.Min = 1
Form1.MyPbar.Max = 10
So, declaring as “public” will give you global scope. But you STILL must qualify the var by the form that variable resides in.
However, the scope is still limited to form that MUST be open. If that form is closed or goes out of scope, then of course you can’t reference that form in other forms/code.
What amount “many” forms, and wanting true global scope var?
A number of ways exist, but most easy is to add a standard code module to your project. So add a "module" as opposed to a "class". In that standard code module, you can place common routines (functions/subs) that you want for the whole application. So this approach is nice, since you don’t have to create an instance of that code module (it not consider class). It is just a standard “library” of code where you can shove in a bunch of routines. This is a carryover idea and concept from VB6, or VBA, but it really is nice.
And any public var declared in such modules are scoped appcation wide global.
(If using “dim”, then scope is only to that module).
So, if we add a code module we get this:
Module Module1
Public GlobalPbar As New Pbar
Public Sub Test()
MsgBox("test")
End Sub
End Module
So in place of “dim” I used “public” since I want use of this var outside of “module1”.
So for global scoped functions, subs and variables (including pointers to class vars)?
Simply create (add) to your project a standard code module. Some even often create/add a code module called MyGlobals, and they don’t even place any sub/functions, but just a list of “public” variables which of course become scoped to the whole application.
So in summary:
Declaring a class var without “new” creates a pointer to an instance of a given class. (And in this case the pointer is nothing - the object does not exist yet)
You can assign an existing instance of a class to the above declared variable – it works like a pointer. You have to use the new keyword in code to create that instance.
You can also for convenience sake combine the two above steps into one declare with the "new" keyword. This is often done, and of course with “overloading”, we can supply parameters when you do this.
You can create a global var in the standard code module. You can also create the “list of” variable in that code module, and it again will also be global scoped and not tied to one form, and you not be tied or limited to any instance of a form being open to use such global vars. And you don't have to qualify the sub or vars with module1, they are scoped global.
Comments and explanations are in-line.
Public Class Form2
Private ClockList As New List(Of MyClock) 'This can be seen anywhere in the Form code.
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
'Add items to list with and without setting properties
ClockList.Add(New MyClock(New TimeSpan(1, 13, 22, 7), True))
ClockList.Add(New MyClock)
ClockList.Add(New MyClock(New TimeSpan(0, 11, 3, 6), False))
ClockList.Add(New MyClock(New TimeSpan(3, 7, 11, 433), True))
Debug.Print(ClockList.Count.ToString)
Console.WriteLine(ClockList(0))
'The default properties will print for the second item in the list
Console.WriteLine(ClockList(1))
Console.WriteLine(ClockList(2))
Console.WriteLine(ClockList(3))
Console.WriteLine("Values for the second item in list after properties set")
'Setting properties of the second item in list
ClockList(1).active = False
ClockList(1).elapsedtime = New TimeSpan(14, 7, 22, 34)
Console.WriteLine(ClockList(1))
End Sub
End Class
Public Class MyClock
'TimeSpan(days,hours,minutes,seconds,milliseconds)
'There is no need to set default values for the TimeSpan or call New
'The TimeSpan is a Structure made up of Integers so the default values
'are automatically set
Public Property elapsedtime As TimeSpan
Public Property active As Boolean
Public Sub New() 'Add back the default constructor
'unnecessary to assign elapsedTime because it is already set to (0,0,0,0,0)
End Sub
Public Sub New(ETime As TimeSpan, act As Boolean)
elapsedtime = ETime
active = act
End Sub
Public Function Display() As String
Return elapsedtime.ToString
End Function
'Public Sub Start()
' active = True
'End Sub
'Public Sub Stopclock()
' active = False
'End Sub
Public Sub Toggle()
active = Not active
'If active = True Then
' active = False
'You just set active to False, why would you call a Sub to set it to False?
'Stopclock()
'Else
'active = True
'You just set active to True, why would you call a Sub to set it to True?
'Start()
'End If
End Sub
Public Overrides Function ToString() As String
Return $"Elapsed Time is {elapsedtime.Days} Days, {elapsedtime.Hours} Hours, {elapsedtime.Minutes} Minutes, {elapsedtime.Seconds} Seconds, {elapsedtime.Milliseconds} Milliseconds and Active is {active}"
End Function
End Class
Results displayed in Immediate Window
4
Elapsed Time is 1 Days, 13 Hours, 22 Minutes, 7 Seconds, 0
Milliseconds and Active is True
Elapsed Time is 0 Days, 0 Hours, 0 Minutes, 0 Seconds, 0 Milliseconds
and Active is False
Elapsed Time is 0 Days, 11 Hours, 3 Minutes, 6 Seconds, 0 Milliseconds
and Active is False
Elapsed Time is 3 Days, 7 Hours, 18 Minutes, 13 Seconds, 0
Milliseconds and Active is True
Values for the second item in list after properties set
Elapsed Time is 14 Days, 7 Hours, 22 Minutes, 34 Seconds, 0
Milliseconds and Active is False
I am trying to pass an equipment object to a form object and then use that equipment object in a click event from a button on the form. But I don't know how to properly reference the equipment object within the button event.
I set up the new form instance using:
Public Sub New(ByRef thisEquip As classEquipment)
Me.InitializeComponent()
Me.Text = thisEquip.equipName & " Tests"
End Sub
and set up the button click event like this:
Private Sub btnUpdateAndClose_Click(sender As Object, e As EventArgs) Handles btnUpdateAndClose.Click
Call updateTestList(thisEquip)
End Sub
But the 'thisEquip' object is not recognized. I think this is because the sender is the button and not the form itself. However, I don't know how to reference the equipment object from the form.
The Scope depends on where a variable is declared. You might have missed something skimming the link - each scope level summary includes the phrase in which it is declared.
Now look at your constructor:
Public Sub New(ByRef thisEquip As classEquipment)
thisEquip is declared as an argument to the constructor. Thus, it only exists in that procedure. The fact that the procedure is in a form or that thisEquip is mentioned in the form (or module or anything else) is incidental. While it is true that the constructor is special in several ways, in matters of Scope, it is just another procedure.
Form Level Scope
To save a reference to it for use elsewhere:
Public Class Form1
' declare a variable to hold the reference
Private myEquip As classEquipment
' declare an array
Private myImgs As Image()
Public Sub New(ByRef thisEquip As classEquipment)
InitializeComponent()
...
myEquip = thisEquip ' assign param to the var
' assign array of images to the Form level var
' via a temp array
myImgs = New Image() {My.Resources.add,
My.Resources.ballblack, My.Resources.ballblue,
My.Resources.ballgreen}
End Sub
Declared at the form level, it has form/class level scope. You can now reference myEquip or myImgs anywhere in the form. Do Not Use Dim when merely assigning something to a form level object - it will create a new local, but identically named variable.
Other common scope levels:
Procedure Level Scope
Private myFoo as Int32
Private Sub DoSomething()
Dim myBar As String
myBar = "Ziggy"
...
Dim myFoo As Int32 = 7
End Sub
This is more often called local scope. I am using procedure level because it compares and contrasts better to the other terms.
myBar is declared in the DoSomething method, so it has procedure level scope - it only exists in that method. Trying to use it elsewhere will result in an error. This is similar to the constructor example above with the main difference being that the thisEquip object was passed as a parameter rather than declared locally.
This leads some to get confused: the Dim myFoo in the method declares (creates!) a new, local-only myFoo variable which has no relation to the Form/Class level variable of the same name. The local version shadows out the other. Part of the confusion for this seems to be that some think they need to (re) use Dim before they can use a variable. You do not.
Block Level Scope
Directly from MSDN:
If n < 1291 Then
Dim cube As Integer
cube = n ^ 3
End If
A fair number of VB statements create a block scope (For Each/Next, If/End If and Using/End Using). Variables declared inside a Block, have a scope limited to that block. Basically, (almost) anything which results in indentation creates a Block Scope.
Private Sub .....
Dim cube As Int32
If n < 1291 Then
cube = n ^ 3
End If
Now, cube can be used elsewhere in the procedure: its scope has been changed from Block to Local.
For more details, see MSDN:
- Scope In Visual Basic
- Value Types vs Reference Types
VB NET 2010, Framework 3.5
Trying to understand why this works. I create two objects from the the same Class1. GlobalClass1 with Global Scope and LocalClass1 with Module Scope.
In UControl's Load event I set LocalClass1 = GlobalClass1 From this point on anytime I change value of GlobalClass1.TestProperty the property value is also updated in LocalClass1. The Events in LocalClass1 are triggered when GlobalClass1 events fire.
This is the result I was looking for => being able to have a Global Object's events fire in several other Class Modules and User Controls.
I don't quite understand why simply setting the Local Object = Global Object causes the Local Object to automatically update it's property values when the Global Object properties are updated or why the Local Events automatically fire when the Global Object is raising the event?
Module Module1
Public WithEvents frm As New MainForm
Public WithEvents GlobalClass1 As New Class1
Public Sub Main()
frm.Init()
frm.ShowDialog()
End Sub
End Module
Public Class MainForm
Private uiUserControl As UControl
Public Function Init() As Boolean
uiUserControl = New UControl
uiUserControl.Location = New System.Drawing.Point(60, 80)
Me.Controls.Add(uiUserControl)
Return True
End Function
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
Static count As Integer
GlobalClass1.TestProperty = count.ToString ' This line propagates to the Private Local LocalClass1 and cause it's event to fire??
count += 1
End Sub
End Class
Public Class UControl
Private WithEvents LocalClass1 As New Class1
Private Sub UControl_Load(sender As Object, e As System.EventArgs) Handles Me.Load
LocalClass1 = GlobalClass1
End Sub
Private Sub LocalClass1_TestPropertyChanged() Handles LocalClass1.TestEvent
Me.TextBox1.Text = LocalClass1.TestProperty 'This Event fires when events are raised in the other object => GlobalClass1??
End Sub
End Class
Public Class Class1
Public Event TestEvent()
Private _testProperty As String
Public Property TestProperty() As String
Get
Return _testProperty
End Get
Set(ByVal value As String)
_testProperty = value
RaiseEvent TestEvent()
End Set
End Property
End Class
The class instances (objects) are both referencing the same memory space allocated to them on the heap.
from comments:
The problem is with this line: LocalClass1 = GlobalClass1
They started out as different objects but then you made them refer to the same thing. Object references - or reference types - work differently than value types like integer:
Dim x As Int32
Dim y As Int32 = 42
x = y
As value types, the value of y is assigned to x. A reference type is essentially a wrapper or alias for a pointer. Your assignment code therefore replaced the original pointer to a New Class1, with the pointer already assigned to GlobalClass1
Read more about Value vs Reference types at MSDN
can we have a single global variable which can be manipulated by multiple forms
In short, yes. You can have a global variable in a module (.mod) file or a class (.vb) file.
Module Module2
Public variable As String = "Testing"
End Module
Declare a variable like this:
Public Shared myVariable as Type
and access it from any form.
You can access a single variable from any form, if it is declared as public.
If you are defining it in form1 and want to use it in form2, then from inside form2 you can call the variable as - form1.<variable_name>
Take an example-
Form1 code
Public Class Form1
Public a As Integer = 10
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Form2.Show()
End Sub
End Class
Form 2 code
Public Class Form2
Private Sub Form2_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
MsgBox(Form1.a)
End Sub
End Class
Yes, it can be done. If you declare it as shared it will exist in only one instance.
Public Class SomeClass
Public Shared SomeField As String
End Class
I would, however, recommend to wrap access to the field into a property:
Public Class SomeClass
Private Shared _someValue As String
Public Shared Property SomeProperty() As String
Get
Return _someValue
End Get
Set(ByVal value As String)
_someValue = value
End Set
End Property
End Class
By wrapping it into a property you will make it easier to troubleshoot problems around the value in case such scenarios would appear in the future.
What you are looking for is the "singleton pattern".
But first, you should ask yourself if you really need it. Maybe this variable could be passe as a parameter to a function or a property.
Use
Public x As Integer
On any Of the Forms and then when you want to use that variable on other form then you can type the form name and then a dot and then the variable name
like this
form1.x
Cheers!!!