using old object instances/previous object instances - vb.net

In Visual Basic 2008 Express, I would like to reference a form. However, when I enter Dim mainmenu as New MainMenu, this creates a new instance. When I want to change a label in form main menu by using a button in form 2, I have to do the following:
Dim mainmenu As New MainMenu
Dim pitchint As Integer
pitchint = Val(Pitch_txt.Text) 'simple way will filter out trailing non-numerics input
If pitchint > 720 Then
pitchint -= 720
ElseIf pitchint > 360 Then
pitchint -= 360
End If
Pitch_txt.Text = pitchint '<--put this line here will solve your "070" issue
mainmenu.Pitchlbl.Text = pitchint
If Pitch_txt.Text.Length <> 0 Then
If Pitchlbl.Text <> Pitch_txt.Text Then
Pitchlbl.Text = Pitch_txt.Text
End If
End If
Dim yawint As Integer
yawint = Val(Yaw_txt.Text) 'simple way will filter out trailing non-numerics input
If yawint > 90 Then
yawint -= 90
End If
If yawint < -90 Then
yawint += 90
End If
Yaw_txt.Text = yawint '<--put this line here will solve your "070" issue
If Yaw_txt.Text.Length <> 0 Then
If Yawlbl.Text <> Yaw_txt.Text Then
Yawlbl.Text = Yaw_txt.Text
End If
End If
This will create a new instance of the form main menu. Then, when I insert the lines mainmenu.Yawlbl.Text = yawint and mainmenu.Pitchlbl.Text = pitchint, nothing happens. I do not get an error nothing. Please help. Thanks in advance.

My solution has been mischaracterized and there has been some confusion about the various possible approaches to answer this question, so I've edited my original post to compare and contrast the three major approaches discussed at length on this page.
Solution 1: Use VB.NET default form instances
Put this line after Dim mainmenu As New MainMenu:
mainmenu.Show()
You will probably have two MainMenu forms. This is because VB allows you to reference a static instance of a form simply by using its class name. So you can just be saying i.e. MainMenu.Property = value and it will operate on the static instance created by VB.
Try removing the line Dim mainmenu As New MainMenu. This might be all you need to do (as long as you Show() the form) since you called your reference the same as the class name.
Solution 2: Follow the Singleton design pattern (simplified version, no thread safety)
The singleton design pattern makes sure there can only be one instance of a class. Putting this code into your MainMenu will surely cause some errors to appear on your screen.
Public Class MainMenu
' static (shared) instance of this class
Private Shared _instance As MainMenu
' function which returns the static instance
' with lazy initialization (constructor is called once GetInstance is
Public Shared Function GetInstance() As MainMenu
If _instance Is Nothing Then
_instance = New MainMenu()
End If
Return _instance
End Function
' private constructor to restrict instantiation of this class (only allowed in GetInstance)
Private Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
End Sub
' all the rest of your original MainMenu code here
End Class
The way you fix the errors is by using a variable to hold a reference to the instance of MainMenu. Simply, replace mainmenu with a variable like myMainMenu and before you use the form, put this:
Dim myMainMenu As MainMenu = MainMenu.GetInstance()
myMainMenu.Show()
Solution 3: Create your own instance(s)
There are differences between solution 1 and solution 3 . In solution 1, you will only have one instance of the form if you use only default instances. This solution allows you to have any number of instances! You probably don't need this, but here goes...
You will make a new instance of MainMenu called myMainMenu again but this time you call the constructor directly.
Dim myMainMenu As New MainMenu()
myMainMenu.Show()
Wherever you call the form by the name mainmenu, replace that with myMainMenu. Did I mention that we call it myMainMenu instead of mainmenu because we don't want to use the same name as the class name? (VB is case-insensitive so mainmenu = MainMenu, but this is easily confusing because of the default instance. The compiler uses context to determine if we are talking about the class itself or the class' default instance...) Using the classname only works when you reference the default static instance as in solution 1.
The attractive thing about this solution is that you can have multiple instances of MainMenu alive at the same time. So you can put this after:
Dim myMainMenu2 As New MainMenu()
myMainMenu2.Show()
And voila, you have two MainMenu open. But you probably didn't need two!
Summary
Why are there so many methods?
Well, the first method was added to attract VB6 programmers to VB.NET because that was how it was done in VB6! To be accurate, it can be done this way in VB6, but some programmers with half a brain still chose to follow method 3. But the default instance method was so widespread because it was so easy for the casual programmer to use - especially all those laymen using it inside VBA!
The second method is preferred in some instances, but not in others. It is a simple implementation of the Singleton design pattern. Check out the link in Douglas Barbie's answer for a decent explanation of it and some examples in Java. It lists a good summary of all the times you'd need to use it - a logger which should only have once instance, a configuration loader, a factory or other instance generator - these are probably out of your scope, but maybe simply think about a Print dialog in Microsoft Office. There only needs to be one Print window open at a time. This is a good example of it. Does your code require this? Following the pattern allows for this behavior, but there is some additional configuration as I put in the example. If the App is simple enough, it probably doesn't need it.
The third is a quintessential example of object oriented programming (a good place to start is understanding OOP; this knowledge alone should give you the tools to solve problems like this in the future). It uses a class called MainMenu, which you created, and uses potentially more than one instance of the class, called objects. An advantage of this is that you can have two object instances of the same class, but the objects have properties with different values. Think two instances of class Car, one has Car.Make = "Ford", the other Car.Make = "Chevy". Both cars, but they differ by property values. This differs from the first two only by the fact that you can have multiple instances. If we want to get technical, you can merge solution 1 and 3 and use the default instance and make your own instances, but this is warned against by just about everyone who discusses it (Google "VB.NET form default instance" for more on that matter).
In the end, if you are coding something on a small scale, it's whatever works reliably for you.

Check out the singleton pattern: http://www.oodesign.com/singleton-pattern.html
Make sure that you never instantiate more than one instance of the MainMenu form, then refer to that form whenever you want to. You may have to instantiate it at a larger scope, such as the "Main" Sub (or whatever the VB equivalent is to public static void Main() in C#).
EDIT: In a VB Windows Forms project, if you don't feel comfortable disbling the application framework, you can still instantiate any other forms from your startup form. If MainMenu is your startup form, then be sure to set MainMenu as the owner of Form2. So it might look like:
Public Class MainMenu
Public Sub Foo()
' This is wherever you instantiate Form2
Dim frm2 As New Form2()
' There are a few ways to declare frm2's owner:
frm2.Owner = Me
frm2.Show(Me)
End Sub
End Class
Then in Form2:
Public Class Form2
Private Sub Form2_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
Dim pitchint As Integer
pitchint = Val(Pitch_txt.Text) 'simple way will filter out trailing non-numerics input
If pitchint > 720 Then
pitchint -= 720
ElseIf pitchint > 360 Then
pitchint -= 360
End If
Pitch_txt.Text = pitchint '<--put this line here will solve your "070" issue
' refer to MainMenu as Form2's owner:
Me.Owner.Pitchlbl.Text = pitchint
' Etc...
End Sub
End Class

You need to pass the reference to MainMenu form into Form2. So Form2 might look something like this (code for illustrative purposes only. Not complete):
Public Class Form2
'This will hold a reference to the MainMenu instance
Private mainMenuForm As MainMenu
Public Sub New(parent As MainMenu)
mainMenuForm = parent
End Sub
Private Sub SomeMethod()
mainmenuForm.Pitchlbl.Text = "Some new value"
End Sub
End Class
And then in the MainMenu form when you need to show Form2:
Dim frm2 As New Form2(Me) 'Pass the reference to the current MainMenu form to Form2
frm2.Show()

Related

Pass value from form to button text vb.net

I am learning vb.net and I'm having issues searching for what I need. I want to create a button that is "re-usable" throughout my application without needing to write code for each instance. So, what I would like to start with is take a variable in a form, example, public integer value and when this value changes I want to write to the text of a button. I know I can easily do this by writing code in the form btn_xxx.text = variable, but what if I have several buttons and each button looks at the same variable? Currently what I do is create a component which inherits a button and have a timer that on tick will look at the variable and write to the text. I'm sure there is a better way. Can anyone point me in the right direction? I know part of my problem is I don't know the nomenclature on what things are called, so hopefully I asked my question without too much confusion.
I saw this, https://www.daniweb.com/programming/software-development/threads/124842/detect-variable-change, but I don't see how to adapt that to my situation.
Here is what I have:
Private WithEvents Active_Alarm As New Nav_Active_Alarm
Then inside of a sub that calculates the count:
Active_Alarm.Count = CInt(dt_Active_Alarms.Rows.Count)
The user control:
Public Class Nav_Active_Alarm
Private mActive_Alarm_Count As Integer
Public Event Active_Alarm_Count_Changed(ByVal mvalue As Integer)
Public Property Count() As Integer
Get
Count = mActive_Alarm_Count
End Get
Set(ByVal value As Integer)
mActive_Alarm_Count = value
If Not Me.DesignMode Then
RaiseEvent Active_Alarm_Count_Changed(mActive_Alarm_Count)
test()
End If
End Set
End Property
Private Sub test()
If Not Me.DesignMode Then
If mActive_Alarm_Count = 0 Then
Me.btn_Goto_Active_Alarm.Image = My.Resources.Alarm_Clock_Static
'console or msgbox will work but updating the image will not
Else
Me.btn_Goto_Active_Alarm.Image = My.Resources.Alarm_Clock_Animation
'console or msgbox will work but updating the image will not
End If
End If
End Sub
End Class
If I write to console or add a msgbox I will see the event working. But, the image will not change. If I call the test sub from a timer it will work. Why won't the button update (by the way, I did try refresh and update in the code)?
Observer pattern is what you probably looking for.
This is quick and dirty.
Create a class to hold the variable value. Add a method that adds a button instance to a list.
Then a button that needs to know about the variable calls the register method.
When the value of the variable changes, it iterates through the list of buttons and sets the Text property of each one.
You might have jumped in a bit too deep too quick here. Google Custom data binding in .net, there's loads of built in stuff you can use. Though do it yourself is a good exercise.
A simple method to do this might be:
Create a form level list to hold the buttons you are interested in
Add the buttons you are interested in, into the list (maybe in form load or some other place where you have initialization code)
Create a private property in your form with a backing variable to hold the value you want to have applied to the buttons. In the setter portion spin through the list and set each buttons text.
Dim lstButtons As New List(Of Button)
Sub SetupButtons()
'call from form load or other init code
lstButtons.Add(btnPopulate)
lstButtons.Add(btnPopulate)
End Sub
Private _buttonText As String
Private Property ButtonText As String
Get
Return _buttonText
End Get
Set(value As String)
_buttonText = value
For Each b As Button In lstButtons
b.Text = value
Next
End Set
End Property
When you set the property - which now acts as your variable - it will update all of your textboxes for you.
I realize you mentioned without having to write code - but something has to tie things together. Even if you used the observer pattern (which is an elegant solution for this - so props to those who suggested it) you'd probably end up creating a class to hold the property and have that class implement the INotifyPropertyChanged from System.ComponentModel, and then you'd also have to have each button have a databinding for its text property to the property in the object of your class. There isn't really a way (that I can think of) to get around having to write some code for each form you do this in (though the class part you'd only have to write once of course).

Form Controls not updated from Class methods

I am a newcomer to Visual Studio 2013, so my question is probably -and hopefully- a simple one.
I am currently writing a small program (in VB) that will essentially add/update/delete users from a table.
The main form has three TextBoxes (ID#, name, last name), a button to check if the user already exists and a couple more buttons (Save and Cancel)
I have also created a Class (dataLookup) where all the functions for adding, updating or deleting users are stored.
The way the program works is as follows:
1.- The user enters an ID# in the main Form's ID field and clicks on the "check user" button.
2.- The system calls a function stored in the datalookup Class that verifies if the ID# already exists.
3.- If it does, the function retrieves the name and last name from the table, assigns them to two local variables (vName and vLastName) populates the corresponding fields on the Main Form and returns TRUE. User can then either update data or Cancel (See code sample below)
MainFormName.TextBox1.Text = vName
MainFormName.TextBox2.Text = vLastName
return True
4.- If the ID# doesn't exist, the function returns FALSE. User is then able to enter new data in the three textboxes.
My problem is that I can't populate the TextBox fields from the function stored in the dataLookup Class. After the instructions are processed, the TextBoxes (which are both Enabled and have their Read Only property set to false) remain empty.
If I add the exact same code that populates the fields to the Main Form code, and assign values to the vName and vLastName variables, it works perfectly:
vName = "John"
vLastName = "Doe"
MainFormName.TextBox1.Text = vName
MainFormName.TextBox2.Text = vLastName
FYI, no errors are reported when I compile/run the program.
I am aware that I can modify the function so it will also return the name and last name and then I will be able to update the TextBox fields from the Main Form, but I am just curious: Why can't I do that from the function stored in the Class?
Hope my description was reasonably clear :) Any help will be much appreciated. Many thanks in advance!
Randy
Forms are classes (it says so at the top of each of them):
Public Class MainForm
....
As a class, an instance should be created however, VB allows what is called a default instance using the class name: MainForm.Show()
Under the hood, the compiler creates an instance of MainForm named MainForm and uses it. This is handy for developers hobbyists dabbling in code, but there are numerous ways this can bite you:
Sub DoSomeThing()
Dim frm As New Form1
frm.TextBox1.Text = cp.ToString
End Sub
Here a local instance of Form1 is created and used which has no relation to the Global Form1 which VB created. The local object goes out of scope at the end of the Sub never to be used by the rest of the app.
' in sub main:
Application.Run(New Form1) ' main form/pump
... elsewhere
Form1.TextBox1.Text = "hello, world!"
In spite of using the same name, these are actually different instances. The text will not show up and if the next line was Form1.Show(), a second copy of Form1 would display complete with the 'hello, world' text. These will also create/show new instances:
Form2.Show()
' or
Dim frm As New Form2
frm.Show()
In general, the more complex the app, the less appropriate using default instances is. For serious apps, create explicit form instances:
Dim myFrm = New Form7() ' myFrm is an instance of Form7
...
myFrm.TextBox1.Text = vName
myFrm.TextBox2.Text = vLastName
myFrm.Show
Classes or other forms can be told about this form various ways such as via a property or the constructor:
Class Foo
Private myFrm As Form7
Public Sub New(frm As Form7)
myFrm = frm
End Sub
...
End Class
Dim myFoo = New Foo(Me)
For the main/startup form, it can help to create a global reference:
Module Program
Friend frmMain As MainForm
End Module
Then set the variable when the main form loads:
Public Class MainForm
Private Sub MainForm_Load(sender ...)
frmMain = Me
End Sub
...
frmMain will be a valid reference to the main form for the entire application.

How to update main form from thread utilizing a module creates new mainform?

So my use for a module is so I can use the same functions across different programs that I develope for my employer. They also want my module to be distributed amongst other programmers so they can use it as well. The programs need to know when there is a thread still running (SQL code is running (there are no problems with the sql side) and it needs to notify the user when all work is done but the user needs to be able to queue work)
From the main form I am using this code:
Dim thread1 As New System.Threading.Thread(AddressOf ModuleTesting.Testing)
thread1.SetApartmentState(Threading.ApartmentState.STA)
thread1.IsBackground = True
thread1.Name = "ModuleLabelCrossThreading"
thread1.Start()
This is the code for my module:
Public Sub Testing()
Form1.threadsrunning += 1
Form1.accesscontrolsmoduletesting()
'THIS IS WHERE THE PROGRAM DOES STUFF ILLUSTRATED BY SLEEPING'
System.Threading.Thread.Sleep(2000)
Form1.threadsrunning -= 1
Form1.accesscontrolsmoduletesting()
end sub
The code to access the controls on the main form is
Public Sub accesscontrolsmoduletesting()
If Me.InvokeRequired = True Then
Me.Invoke(New MethodInvoker(AddressOf accesscontrolsmoduletesting))
Else
If threadsrunning > 0 Then
Label4.Text = threadsrunning & " threads running"
Else
Label4.Text = "0 threads running"
End If
End If
End Sub
I already know the issue is the new thread is creating a new form. I tested this by showing the form and making it wait so it didnt immediately dispose itself and I seen the label was updated. How do I make this thread update the main form instead of just creating a new mainform and then disposing itself after the thread dies?
To reiterate on my Comment you need to get the actual Form1 that is being shown, you should change your Testing Method to accept a Parameter of Form1, then you can use a Parameterized Thread.Start to pass in the Calling Form. You are running into a feature that was left in place to placate Vb6 programmers transitioning to VB.net as this answer by Hans states. And you may find this Blog Post by John Mcllhinney an interesting read.
From Second Link(emphasize mine):
In order to access a form from a secondary thread you generally need to test its InvokeRequired property and then call its Invoke method. I said earlier that there is only ever one default instance of a form class. That’s not strictly true. In fact, default instances are thread-specific, so there is only ever one default instance per thread. As such, if you test the InvokeRequired property of the default instance you will always be accessing the default instance for the current thread, not the one that was displayed on the main thread.
So in response to above I would change your Module Test Method to:
Public Sub Testing(myForm As Form1)
myForm.threadsrunning += 1
myForm.accesscontrolsmoduletesting()
'THIS IS WHERE THE PROGRAM DOES STUFF ILLUSTRATED BY SLEEPING'
System.Threading.Thread.Sleep(2000)
myForm.threadsrunning -= 1
myForm.accesscontrolsmoduletesting()
End Sub
And I would change your Form1's Thread Start Code to look like this.
Dim thread1 As New System.Threading.Thread(AddressOf ModuleTesting.Testing)
thread1.SetApartmentState(Threading.ApartmentState.STA)
thread1.IsBackground = True
thread1.Name = "ModuleLabelCrossThreading"
thread1.Start(Me) 'Note the passing in the instance of the calling Form
After making these few changes your code will work

Passing two similar forms as the same type and still being able to access its objects

I am trying to implement another form into already complete routine. Basically all the code is there, all I need to do is manipulate the data in a different manner.
I have a routine that looks like this for instance.
This is a paraphrase example:
Private Sub getReportValues(ByRef fr As frmCustomReport, ByRef ReportInfo As ReportValues)
ReportInfo.eHeaderColor = Microsoft.Win32.Registry.CurrentUser.CreateSubKey("Software\FE Jupiter\MSSMonitor").GetValue("Report Equipment Header Color", "DCDCDC") 'Gainsboro
ReportInfo.mHeaderColor = Microsoft.Win32.Registry.CurrentUser.CreateSubKey("Software\FE Jupiter\MSSMonitor").GetValue("Report Monitor Header Color", "FFF8DC") 'Cornsilk
fr.btnEquipColor.PickedColor = System.Drawing.ColorTranslator.FromHtml("#" & Microsoft.VisualBasic.Conversion.Hex("&HFF" & ReportInfo.eHeaderColor))
The problem lays with the (fr as frmCustomReport) I want to make it a system.windows.forms.form but then I would lose the ability to use its objects. I should also mention that fr is a modal dialog and that I don't want a really hacky controlcollection work around for this. Does anyone have a good direction on this?
Note Also!!! The controls I want to access on both forms are almost identical. The only diffrence is layout and some added functionality.
Without a little more information,it is a little hard to give a concrete example. This will work depending on the amount of interaction you need to do. Create a subroutine that accepts the Base Class as an argument, take a look at the Name value and base your conditional logic off of that using CType to cast the Form to the proper type.
Private Sub clickOtherFormsButton(Value As Form)
If Value.Name = "Form3" Then
Dim formObject As Form3 = CType(Value, Form3)
formObject.Button1.PerformClick()
ElseIf Value.Name = "Form2" Then
Dim formObject As Form2 = CType(Value, Form2)
formObject.Button1.PerformClick()
End If
End Sub

How can I load a 'future' forms controls before I use them? Explained in detail:

I am performing a migration on a vb6 program, to vb.net. The basic knowledge you need to understand this question is that there are two forms that need to talk to each other, frmInput1 and frmInput2. I have the following code (behind frmInput1) that checks if a textbox on frmInput2 has a certain value, seemingly before it has loaded:
If frminput2.lblInputMac.Text <> "(no filename)" Then
Dim calc As CalculationCaster = New CalculationCaster
Call calc.FillMac()
cmdNext.Enabled = False
frminput2.FraInner.Enabled = True
I get the following error on the If line when i run it:
"Object reference not set to an instance of an object."
Which i assume means that the object in frmInput2 has not been loaded yet. How can i load frmInput2 before i show it?
Thanks
Nick
frminput2 is probably the implicit global instance of the type frminput2.
If you define a form type in VB6 called MyForm, the platform automatically creates an implicit global variable of the same name MyForm. Whenever you refer to this variable in code, it automatically loads an instance of the form for you.
It's rather as if you had this code.
Public Function MyForm() As MyForm
Static f As MyForm
If f Is Nothing Then
f = New MyForm
End If
Return f
End Function
dim frm1 as new frmInput1
dim frm2 as new frmInput2
At this point, you should be able to communicate between forms without them being displayed. You should not reference forms without explicitly instantiating them.
Create an instance of the form.
Dim f As New frmInput2
Then you can use any properties, methods, or controls on the form.
If f.lblInputMac.Text <> "(no filename)" Then
...
End If