Why does the main form have different hashes from different threads? - vb.net

As far as I was aware, the main form (here I'll call it Form1 as is default) of a .Net windows form application was a bit like a singleton. It seems to be special in that you can access the instance from anywhere just by using Form1; even though you only have one form instance, you can access that without passing a variable around.
However, I was suprised to find, that if I use the TPL to make a number of tasks, and run them together, and each of them call Form1.GetHashCode they return different values.
Furthermore, if I place a public member object inside mainform, then set a value on one of its properties, that won't be reflected in the tasks.
What is going on here - it is as though there is a new instance for each task, but that can't be right? That would take ton's of memory/initialisation surely, and also I can't see lots of forms. I know I can't access controls from other threads, but this is just an integer. What is happening?
Example Code
Just make a new project and stuff this into Form1 one, with a single button on that form.
Imports System.Threading.Tasks
Imports System.Threading
Public Class Form1
Public foo As New Test
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
foo.bar = 99
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
ThisCouldBeAnywhere()
Dim Task1 As New Task(AddressOf ThisCouldBeAnywhere)
Dim Task2 As New Task(AddressOf ThisCouldBeAnywhere)
Dim Task3 As New Task(AddressOf ThisCouldBeAnywhere)
Task1.Start()
Task2.Start()
Task3.Start()
End Sub
End Class
Public Class Test
Public bar As Integer = 4
End Class
Public Module TestMod
Public Sub ThisCouldBeAnywhere()
MsgBox(Form1.GetHashCode & vbCrLf & Form1.foo.bar)
End Sub
End Module

Related

Passing value from one form to another using events in vb.net

I'm trying to pass a value from one form, let's call it Form1 to another form, Form2. When i double click a row in a listview that is in Form2, the value should be passed to a combobox in Form1.
I could get one of my other forms to do this using an event called PropertyChanged, but I can't seem to get it to work on other forms. I don't know if it is the fact that you can only have 1 event in the entire project and not have another with the same name. I'm missing something, but i just don't know where.
This is the code i used in Form2:
Public Event PropertyChanged As Action(Of Object)
Private Sub ListView2_DoubleClick(sender As Object, e as EventArgs) Handles ListView2.DoubleClick
RaiseEvent PropertyChanged(ListView1.SelectedItems(0).SubItems(0).Text)
End Sub
And this is the code I used in Form1:
Dim WithEvents f2 As Form2
Private Sub PropertyChanged(obj As Object) Handles f2.PropertyChanged
cmb_form1.Text = obj
End Sub
There are several ways to implement this. This one is quick and dirty.
It consists of writing up a property in Form2 which is public. When the user makes a choice, it goes in the property. Then Form1 reads the property before it disposes Form2.
Here's a little code snippet to better illustrate what I mean:
Public Class Form1
' This form has a button which opens a Form2 instance
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim form2 As New Form2 ' instantiate Form2
form2.ShowDialog() ' the user chooses a value
MessageBox.Show(form2.Result) ' get it before it's out of scope!
End Sub
End Class
Public Class Form2
Public Property Result As String ' the value is stored in there, it can be any type
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Result = "Clicked!" ' store the value you want to share between forms
Me.Close()
End Sub
End Class
Have fun!

VB.NET Absolutely strange form load behaviour

I am experiencing a strange behaviour when using RightToLayout layout:
My form automatically closes.
I have created a simple project to reproduce the problem:
Create a new VB.NET project in VS2012 and add forms "Form1" and "Form2" to it.
Set "Form1" to be the start form.
Then add the following code to "Form1":
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
Me.Hide()
Dim f As New Form2
f.ShowDialog()
MessageBox.Show("After dialog closed.")
modControls.setFormRTL(Me) 'This line causes the Form2 to automatically close. Why??? This line should only be processed AFTER the dialog has been shown and closed
End Sub
End Class
Add the following code to "Form2":
Public Class Form2
Private Sub Form2_Load(sender As Object, e As EventArgs) Handles Me.Load
modControls.setFormRTL(Me)
End Sub
End Class
Add a module with the name "modControls" to the project.
Add the following code to it:
Module modControls
Public Sub setFormRTL(ByVal uForm As Form)
uForm.RightToLeft = RightToLeft.Yes
End Sub
End Module
Without the setFormRTL, my project works perfectly fine, but with it, "Form2" automatically closes down. You can see this because the messagebox is shown.
When I remove the line
modControls.setFormRTL(Me)
from Form1 load, it works fine again.
Yes, I really mean from "Form1", not from "Form2"!!!
Now this is really strange because it should not matter at all because this line is not processed before the dialog is closed.
I hope somebody understands what I mean.
Can anybody shed some light on what might be happening here?
Yes, you'll get unexpected behavior if you set the RightToLeft property anywhere other than a control's constructor (in VB.NET parlance, that's the New method).
In fact, the constructor is where you should set all of the properties of a form or control object. If you come from VB 6, it might seem logical to do it in the Load event handler, but that's not idiomatic .NET and, as you've discovered, can cause problems when initializing certain properties.
The technical reason for this is that certain properties (like RightToLeft) can actually only be set on the native window (which is how the Form objects used in the .NET world are implemented behind the scenes) at the time that it is created. When you attempt to change the property, the framework code actually has to destroy and then re-create the native window with the new property values.
Change the code to look like this instead:
Public Class Form1
Public Sub New()
MyBase.New()
'This call is required by the Windows Form Designer.
InitializeComponent()
modControls.SetFormRtl(Me)
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
Me.Hide()
Dim f As New Form2
f.ShowDialog()
MessageBox.Show("After dialog closed.")
End Sub
End Class
Public Class Form2
Public Sub New()
MyBase.New()
'This call is required by the Windows Form Designer.
InitializeComponent()
modControls.SetFormRtl(Me)
End Sub
Private Sub Form2_Load(sender As Object, e As EventArgs) Handles Me.Load
End Sub
End Class
Speaking of non-idiomatic .NET code:
Methods should all be Pascal cased by convention. That means your setFormRTL method should be named SetFormRtl.
A helper function that sets the properties of an object just seems wrong to me. If anything, that's just bad OO design. If you want this method to be available for all of your Form objects, derive a custom form class and add this method (or even do the desired initialization in the constructor). Either way, all forms that you derive from this custom form object will inherit the functionality. Example:
Public Class MyCustomForm : Inherits System.Windows.Forms.Form
Public Sub New()
MyBase.New()
Me.SetRtl()
End Sub
Public Sub SetRtl()
Me.RightToLeft = RightToLeft.Yes
End Sub
End Class
Public Class Form1 : Inherits MyCustomForm
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
Me.Hide()
Dim f As New Form2
f.ShowDialog()
MessageBox.Show("After dialog closed.")
End Sub
End Class
Public Class Form2 : Inherits MyCustomForm
Private Sub Form2_Load(sender As Object, e As EventArgs) Handles Me.Load
End Sub
End Class
can you try this? all i am doing here is setting the RTL before the form is displayed.
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
Me.Hide()
Dim f As New Form2
modControls.setFormRTL(f)
f.ShowDialog()
MessageBox.Show("After dialog closed.")
modControls.setFormRTL(Me)
End Sub
End Class
and remove code form load event of form 2

Update label from mainform class with backgroundworker from another class

I have two classes.
Public Class MainForm
Private Project As clsProject
Private Sub btnDo_Click
...
Backgroundworker.RunWorkerAsync()
End Sub
Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Project = New clsProject
End Sub
and two methods inside MainForm
Public Shared Sub setLabelTxt(ByVal text As String, ByVal lbl As Label)
If lbl.InvokeRequired Then
lbl.Invoke(New setLabelTxtInvoker(AddressOf setLabelTxt), text, lbl)
Else
lbl.Text = text
End If
End Sub
Public Delegate Sub setLabelTxtInvoker(ByVal text As String, ByVal lbl As Label)
end class
I want to update the labels of MainForm from the clsProject constructor.
MainForm.setLabelTxt("Getting prsadasdasdasdasdry..", MainForm.lblProgress)
but it does not update them.
What am I doing wrong?
The problem is that you are using the global MainForm instance to access the label in a background thread here:
Public Class clsProject
Public Sub New()
' When accessing MainForm.Label1 on the next line, it causes an exception
MainForm.setLabelTxt("HERE!", MainForm.Label1)
End Sub
End Class
It's OK to call MainForm.setLabelTxt, since that is a shared method, so it's not going through the global instance to call it. But, when you access the Label1 property, that's utilizing VB.NET's trickery to access the global instance of the form. Using the form through that auto-global-instance variable (which always shares the same name as the type) is apparently not allowed in non-UI threads. When you do so, it throws an InvalidOperationException, with the following error message:
An error occurred creating the form. See Exception.InnerException for details. The error is: ActiveX control '8856f961-340a-11d0-a96b-00c04fd705a2' cannot be instantiated because the current thread is not in a single-threaded apartment.
I'm guessing that the reason you are not seeing the error is because you are catching the exception somewhere and you are simply ignoring it. If you stop using that global instance variable, the error goes away and it works. For instance, if you change the constructor to this:
Public Class clsProject
Public Sub New(f As MainForm)
' The next line works because it doesn't use the global MainForm instance variable
MainForm.setLabelTxt("HERE!", f.Label1)
End Sub
End Class
Then, in your MainForm, you would have to call it like this:
Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Project = New clsProject(Me) ' Must pass Me
End Sub
Using the global instance from the background thread is not allowed, but when we use the same label from the background thread, without going through that global variable it works.
So it's clear that you cannot use the global MainForm variable from a background thread, but what may not be clear is that it's a bad idea to use it ever. First, it's confusing because it shares the same name as the MainForm type. More importantly, though, it is a global variable, and global state of any kind is almost always bad practice, if it can be avoided.
While the above example does solve the problem, it's still a pretty poor way of doing it. A better option would be to pass the setLabelTxt method to the clsProject object or even better have the clsProject simply raise an event when the label needs to be changed. Then, the MainForm can simply listen for those events and handle them when they happen. Ultimately, that clsProject class is probably some sort of business class which shouldn't be doing any kind of UI work anyway.
You cannot execute any action on GUI-elements from the BackgroundWorker directly. One way to "overcome" that is by forcing the given actions to be performed from the main thread via Me.Invoke; but this is not the ideal proceeding. Additionally, your code mixes up main form and external class (+ shared/non-shared objects) what makes the whole structure not too solid.
A for-sure working solution is relying on the specific BGW methods for dealing with GUI elements; for example: ProgressChanged Event. Sample code:
Public Class MainForm
Private Project As clsProject
Public Shared bgw As System.ComponentModel.BackgroundWorker
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
bgw = BackgroundWorker1 'Required as far as you want to called it from a Shared method
BackgroundWorker1.WorkerReportsProgress = True
BackgroundWorker1.RunWorkerAsync()
End Sub
Private Sub BackgroundWorker1_DoWork(sender As System.Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Project = New clsProject
End Sub
Public Shared Sub setLabelTxt(ByVal text As String)
bgw.ReportProgress(0, text) 'You can write any int as first argument as far as will not be used anyway
End Sub
Private Sub BackgroundWorker1_ProgressChanged(sender As Object, e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
Me.Label1.Text = e.UserState 'You can access the given GUI-element directly
Me.Label1.Update()
End Sub
End Class
Public Class clsProject
Public Sub New()
MainForm.setLabelTxt("Getting prsadasdasdasdasdry..")
End Sub
End Class
Try:
Me.Invoke(...)
instead of lbl.Invoke(.... I had to do this. This is my implementation:
Delegate Sub SetTextDelegate(ByVal args As String)
Private Sub SetTextBoxInfo(ByVal txt As String)
If txtInfo.InvokeRequired Then
Dim md As New SetTextDelegate(AddressOf SetTextBoxInfo)
Me.Invoke(md, txt)
Else
txtInfo.Text = txt
End If
End Sub
And this worked for me.

Using Variables Across Forms - VB

I've got a fairly simple question (I think) on passing variables between forms using Visual Basic.
I've got a program with 2 forms (Form1 and Form2). Form1 has 3 radio buttons, which the user has to select one of and then loads Form2.
Now I've made it so that if radiobutton1 is picked, the Public Variable "radio_select" will equal "radiobutton1", if radiobutton2 is picked, "radio_select" will equal "radiobutton2".
But whenever I try call "radio_select" in my second form, it comes up blank. Why could this be? And how can I fix it.
I've tried using if form1.radiobutton1.checked = true but I keep getting the first radiobutton, regardless of the radio button I've selected.
I think the form is being unloaded, or there is an issue somewhere there, as it appears none of the variables get passed to the second form, once it has been initialized. Also note, the first form is hidden Me.Hide() when the second form is called.
Have you considered a slight re-design whereby you create a property on Form2 called RadioSelect and then set this from Form1 before showing Form2:
Class Form2
Public Property RadioSelect As String
...
End Class
...
Dim f2 as new Form2()
f2.RadioSelect = "radiobutton2"
f2.Show() ' Or f2.ShowDialog()
This gets you away from an unnecessary public variable and should also ensure Form2 can see what it needs from Form1, or whoever calls it.
Edit:
The following works for me:
Public Class Form1
Public Test As String
Private Sub Button1_Click(sender As Object, e As System.EventArgs) Handles Button1.Click
Test = "I'm Here"
Me.Hide()
Form2.ShowDialog()
End Sub
End Class
Public Class Form2
Private Sub Form2_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
Text = Form1.Test
End Sub
End Class

Threaded Sub-Procedure Within A Module Not Obtaining Correct Data From Other Form

I have an ArrayList that is set to Friend. Once I click my button "abc" is added to the ArrayList and then the form MsgBoxes out the Count of 1 (Correct).
When I use Threadpool to count the number of objects within the ArrayList it always returns 0.
Example:
Imports System.Threading
Public Class Form1
Friend Alphabet As New ArrayList
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Alphabet.Add("abc")
MessageBox.Show("Main Sub: " & Alphabet.Count().ToString())
ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf TestIt))
End Sub
End Class
Module MyModule
Public Sub TestIt()
MessageBox.Show("Threaded Sub: " & Form1.Alphabet.Count().ToString())
End Sub
End Module
I am obviously getting some sort of cross-thread issue here but have no idea how to correct this. I usually just setup single threads so this is my first time playing with ThreadPool & already lost at step 1!
When you QueueUseWorkItem you can pass in an object. Then your method will have one paramter only of type object. This will allow you to send the object at the time of the event since the Thread could run at anytime.