Find out reason for form instancing - vb.net

In my application, a certain form is instanciated, and I have no idea why this happens.
I would therefore like to ask if it's possible to detect the "caller" that loads / instanciates the form.
Is it possible to get it from here?
Public Sub New()
InitializeComponent()
Or is there any other way how I could do this?
Edit:
This is the callstack:

The issue here was due to that you were accessing frmMain's default instance from a background thread.
VB.NET includes default instances of every form so that you don't have to do a Dim ... As New myForm every time you want to open a new form. This behaviour will let you shorten:
Dim mainForm As New frmMain
mainForm.Show()
to:
frmMain.Show()
And although not specifically documented, from previously conducting my own test it appears that the default instance is specific to the current thread only. Thus if you try to access the default form instance in any way from a background thread it will create a new instance for that specific thread, and therefore not be the same as the one you're using on the UI thread.
In the end this brings us to one of the golden rules of WinForms, which LarsTech mentioned: Leave all (G)UI related work on the (G)UI thread!
If you really need to access your first instance of frmMain from a background thread you should make a Shared property that returns that specific instance:
Private Shared _instance As frmMain = Nothing
Public Shared ReadOnly Property MainInstance As frmMain
Get
Return _instance
End Get
End Property
Private Sub frmMain_Load(sender As Object, e As EventArgs) Handles MyBase.Load
If frmMain._instance Is Nothing Then frmMain._instance = Me 'Setting the main instance if none exists.
End Sub
Then from a background thread you'll be able to do:
frmMain.MainInstance.DoSomething

Related

VB .NET event handler of controls

I'm using VB .NET to create a planning, and I got a little problem with events.
In the main form, I put a panel in which I add programatically rows and boxes in those rows. I have inside the form a TextBox and the panel that contains all the boxes. I want to change a the text of the TextBox when I click on a box, so I use the AddHandler statement but it doesn't work. I tried to debug it and I realised that it actually calls the sub and inside it, I can see the changes it makes (TextBox.Text becomes what I want), but when it exits the sub, it is like nothing has changed.
I don't know if I was clear enough.
Thanks
Here is a simplified code (I removed all the graphics functions to resize the controls...)
Public Class frmPrinc
Public actEditing As Object
Private Class boxAct
Inherits Label
Public act As Integer
Public Sub New(ByVal a As Integer)
act = a
AddHandler Me.Click, AddressOf clickBox
End Sub
Private Sub clickBox(sender As Object, e As EventArgs)
Dim boxact As boxAct = DirectCast(sender, boxAct)
frmPrinc.actEditing = boxact
boxact.Text = "Clicked"
End Sub
End Class
Private Sub showPlanning()
pan_plan.Controls.Clear()
Dim plan As New Control ' Control that will be used as a row
For i As Integer = 0 To 10
plan.Controls.Add(New boxAct(i))
Next
Panel1.Controls.Add(plan)
End Sub
End Class
When I run that, the text of the box changes but actEditing is still Nothing...
Instead of boxAct trying to directly update frmPrinc of the current "box" being clicked, it should instead raise a Custom Event that frmPrinc subscribes to. frmPrinc can use that information as it then sees fit. Below I've added the custom event and raise it in class boxAct. The form subscribes to that event using AddHandler when each instance of boxAct is created. All together, this looks something like:
Public Class frmPrinc
Public actEditing As boxAct
Public Class boxAct
Inherits Label
Public act As Integer
Public Event BoxClicked(ByVal box As boxAct)
Public Sub New(ByVal a As Integer)
act = a
End Sub
Private Sub boxAct_Click(sender As Object, e As EventArgs) Handles Me.Click
Me.Text = "Clicked"
RaiseEvent BoxClicked(Me)
End Sub
End Class
Private Sub showPlanning()
pan_plan.Controls.Clear()
Dim plan As New Control ' Control that will be used as a row
For i As Integer = 0 To 10
Dim box As New boxAct(i)
AddHandler box.BoxClicked, AddressOf box_BoxClicked
plan.Controls.Add(box)
Next
Panel1.Controls.Add(plan)
End Sub
Private Sub box_BoxClicked(box As boxAct)
actEditing = box
Debug.Print("Box Clicked: " & actEditing.act)
End Sub
End Class
From the comments:
Thanks man, it worked! I'd like to know though why I need to make such
a structure to raise a simple event that modifies the main form...
Just to not do the same mistake again – Algor Frile
This a design decision everyone must make: "Loosely Coupled" vs. "Tightly Coupled". The approach I gave above falls into the Loosely Coupled category. The main benefit to a loosely coupled solution is re-usability. In your specific case, we have Class boxAct being reused multiple times, albeit all within the same form. But what if that wasn't the case? What if you wanted to use boxAct on multiple forms (or even have multiple "groups" of them)? With your original approach, you had this line:
frmPrinc.actEditing = boxact
which means that if wanted to use Class boxAct with a different form you'd have to make a copy of Class boxAct, give it a new name, and then manually change that one line to reference the new form:
Public Class boxAct2
Private Sub clickBox(sender As Object, e As EventArgs)
Dim boxact As boxAct = DirectCast(sender, boxAct)
frmSomeOtherForm.actEditing = boxact
boxact.Text = "Clicked"
End Sub
End Class
This shows the disadvantage of the Tightly Coupled approach, which uses references to specifics types (the Form in this case) to communicate. The Tightly coupled approach might be initially easier to implement when you're coding fast and furious, but then it suffers from re-usability down the line. There are scenarios in which a tightly coupled solution make sense, but only you can make that decision; those scenarios usually involve some kind of "sub-control" that will only ever get used within some kind of custom container/control and will never be used on its own somewhere else.
Conversely, with the loosely coupled approach, if we wanted to re-use Class boxAct in a different form, then no changes to it would be required at all (though at that point you'd probably not want it declared within your original Form!). In the new form you'd simply add a handler for the BoxClicked() event and then do what you need to do. Each form would receive the events for its respective instances of boxAct.
Final thoughts...your original approach could actually work, but most likely was failing at this line (same as above):
frmPrinc.actEditing = boxact
Here you were referencing to frmPrinc using what is known as the Default Instance of that form. This would have worked if frmPrinc was the "Startup Object" for your application. I'm guessing it wasn't, however, and you were creating an instance of frmPrinc from somewhere else. To make the original approach work you would have had to pass a reference to your ACTUAL instance of frmPrinc into Class boxAct (usually via the Constructor in tightly coupled solutions).

Update control on main form via Background Worker with method in another class VB.Net

I have been banging my head against the wall all day trying to figure this one out.
I am finishing up a program to simply delete files in specific temp folders. I have read that it is sometimes good practice to create separate classes for methods and variables. So I have created a separate class for a couple methods to delete files and folders in a specified directory. I am using a Background Worker in my Form1 class and am calling my deleteFiles() method from my WebFixProcesses class in the DoWork event in the Form1 class. I am using a Background Worker so that I can easily report progress back to a progress bar on my main form.
The files get deleted without an issue but I just can't get the label on my main form to reflect the current file being deleted. the label doesn't change in any way.
I know the formula is correct as I can get this working if the method is in the Form1 class. and I simply use:
Invoke(Sub()
lblStatus.Text = File.ToString
lblStatus.Refresh()
End Sub)
here is my method that I am calling from the WebFixProcesses class:
Public Shared Sub deleteFiles(ByVal fileLocation As String)
For Each file As String In Directory.GetFiles(fileLocation)
Try
fileDisplay.Add(file)
For i = 1 To fileDisplay.Count
file = fileDisplay(i)
Form1.BackgroundWorker1.ReportProgress(CInt(i / fileDisplay.Count) * 100)
Next
IO.File.Delete(file)
Form1.labelText(file)
Form1.labelRefresh()
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
Next
End Sub
labelText() and labelRefresh() are methods from my main form which are using delegates to try to pass information to the control:
Public Sub labelText(ByVal file As String)
If lblStatus.InvokeRequired Then
Dim del As New txtBoxDelegate(AddressOf labelText)
Me.Invoke(del, file)
Else
lblStatus.Text = file.ToString()
End If
End Sub
Public Sub labelRefresh()
If lblStatus.InvokeRequired Then
Dim del As New txtBoxRefDelegate(AddressOf labelRefresh)
Me.Invoke(del)
Else
lblStatus.Refresh()
End If
End Sub
If anyone can help me out to inform me what I may be doing wrong it would be immensely appreciated as my head is in a lot of pain from this. And maybe I am going at it all wrong, and just being stubborn keeping my methods in their own class. But any help would be awesome. Thanks guys!
What Hans wrote on the question comment is true: Form1 is a type, not an instance, but to make things easier to newbye programmes (coming from VB6), M$ did a "mix", allowing you to use the form name as the instance of the form in the main thread.
This however works only if you are on that thread.
If you reference Form1 from another thread, a new instance of Form1 is created.
To solve the issue, add this code to the form:
Private Shared _instance As Form1
Public ReadOnly Property Instance As Form1
Get
Return _instance
End Get
End Property
We will use this property to store the current instance of the form. To do so, add this line to the Load event:
Private Sub Form1_Load(sender As Object, e As System.EventArgs) Handles Me.Load
_instance = Me
'other code here
End Sub
Now, from every class, in any thread, if you use
Form1.Instance
...you get the actual form. Now you can Invoke, even from the same form:
Me.instance.Invoke(Sub()
Me.lblStatus.Text = "Hello World"
End Sub)

When are default form instances created?

The startup sequence and handling of form instances is quite weird in VB.NET. When you refer to a form as for example Form2.Textbox1.Text = "Foo" then the CLR automatically uses the instance of the form that is created in the background and can be directly acessed through My.Forms.Form2.
I am using a WinForms application that starts up through a custom Sub Main(). Here the application is run by calling Application.Run(frmMain).
Since I have multiple forms that needs initilializing I would like to know exactly at which point the real form instances are created. Are they all created at Application.Run or are they only created when I call Form2.Show()? My startup sequence is as follows right now:
Sub Main()
Sub Main() calls Application.Run(frmMain)
frmMain.Load calls frmNotMain.Show()
I can nowwhere find a line like My.Forms.frmNotMain = New frmNotMain, so it's not apparent where the instance is created.
Thank you in advance.
According to MSDN:
When you access Form through My.Forms.Form1, the factory method checks to see if an instance of Form1 is already open. If it is, that instance is returned. Otherwise, an instance of Form1 is created and returned.
So essentially it is created and Sub New called just before it is shown (not created somewhere and held until needed). The link includes this code showing how it creates those default instances:
'Code ... generated by the compiler
Public m_Form1 As Form1
Public Property Form1() As Form1
Get
m_Form1 = Create__Instance__ (Of Form1)(m_Form1)
Return m_Form1
End Get
Set(ByVal Value As Form1)
If Value Is m_Form1
Return
End If
If Not Value Is Nothing Then
Throw New ArgumentException("Property can only be set to Nothing.")
End If
Dispose__Instance__ (Of Form1)(m_Form1)
End Set
End Property
However, you are talking about the default ("weird") instance method which is ill-advised to begin with. This largely exists to provide compatibility with VB6 type code where you did just do myForm.Show() to instance and show a form (and probably for tinkerers who do not really understand instancing or OOP).
Forms are classes and should be treated as such by explicitly creating instances; so, generally:
Dim frm As New frmMain ' NEW creates the instance
frm.Show
You can set a breakpoint on InitializeComponent in the form's Sub New to see when it is invoked. To create a global reference to it, like you might with any other class:
Friend frmMain As MainForm ' no instance yet
Friend myMain As MainClass
Public Sub Main
' do this before any forms are created
Application.EnableVisualStyles()
myMain = New MainClass()
myMain.DoStuff()
frmMain = New MainForm() ' instanced (NEW)
Application.Run(frmMain)
End Sub
Likewise:
Dim frm2 = New frmNotMain ' declare and instance
' short for:
Dim frm2 As frmNotMain ' declare frm2
frm2 = New frmNotMain ' create instance
frm2.Show
In all cases, Sub New for your form(s) would be called when you use the New operator to create a New form. VB tries to make this clear thru the repeated use of New, but with the default instance all that is actually tucked away in the form factory.

Delegates not working in another module

Running into an odd issue with tasks and delegates. Code in question is running under dotNET 4.5.1, VS2013. On the form's code I have a sub that updates a grid, it checks to see if an invoke is required, and if it is it calls a delegate. When a task runs that's called in the same module, it works as expected, no problems. Threaded or not, the grid updates properly.
However, if the same thing is called from another module, the delegate never gets called and the visual component doesn't get updated. Just a watered down bit of pseudocode to clarify..
In the form's module:
Private Delegate Sub DoWhateverDelegate(ByVal _____)
Public Sub DoWhatever(ByVal _____)
If MyComponent.InvokeReqired
Dim Delegated As New DoWhateverDelegate(AddressOf DoWhatever)
Debug.Print("The delegate fired")
Invoke(Delegated, _____)
Else
' .. carry on as usual ..
End If
End Sub
Elsewhere....
Task.Run(Sub()
' .. various things I'd rather not block the UI thread with ..
DoWhatever()
End Sub)
Works fine. I can do Task.Run__ that calls DoWhatever and it's all happy and good. However if I create a task in another module and call DoWhatever, it doesn't fire the delegate and that visual component doesn't update. The code is identical, in the same module it works, in another module it does not.
I'm probably missing something blatantly obvious.. anyone care to point out my mistake? Thanks.
Edit -- just to clarify, that other module is just code, there's only one form in the entire solution. It's created at program startup automatically, there is no other form creation going on.
Should be a thread-specific issue. Check this:
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
foo.DoSomething()
End Sub
End Class
The class with the delegate:
Public Class foo
Public Shared Sub DoSomething()
Task.Run(Sub() UpdateText())
End Sub
Public Delegate Sub UpdateTextDelegate()
Public Shared Sub UpdateText()
Dim f = Form1
'Dim f As Form1 = Application.OpenForms("Form1")
If f.InvokeRequired Then
Dim d As UpdateTextDelegate = AddressOf UpdateText
f.Invoke(d)
Else
f.TextBox1.Text = "Hi"
End If
End Sub
End Class
Run the code and the textbox will not be updated. Use the second f=.... (that one that take a reference from OpenForms) and it will be updated.
If you just try to access the default instance and you are outside the UI-thread, a new instance of the form will be created. That means, the content IS updated, but because that form is not shown, you will not see it.
NOTE I do NOT advise to solve your problem, by using OpenForms. I'd advise to correctly instantiate forms!
Add a new module/class to your code:
Module Startup
Public MyForm1 As Form1
Public Sub main()
MyForm1 = New Form1
Application.Run(MyForm1)
End Sub
End Module
Go to project properties -> application. Disable application framework and choose Sub Main as your start object. In the app, access your form via MyForm1 - or whatever you want to name it. Problem should be gone then.

VS 2010 Ultimate VB.NET Project form won't compile/show up

When press F5 to compile a project, there are no errors or warnings but the form won't show up. What's up?
Every time that you try to run your code, it starts by creating an instance of frmMain, your default form and the one that is shown at application startup.
When this form is created, it automatically instantiates an instance of Form3 because you instantiate a variable of that type called modifyForm at the top of this form's code file:
Dim modifyForm As New Form3 'modify student
The problem is that, when the runtime goes to instantiate an object of type Form3, it gets called right back to where it was because of this statement at the top of the Form3 code file:
Dim frmMain As New frmMain
Rinse, lather, and repeat. Your code turns into an infinite loop, trying to instantiate an instance of frmMain, which tries to instantiate an instance of Form3, which tries to instantiate an instance of frmMain, ad nauseum. Eventually, this will overflow your available memory and cause a StackOverflowException.
It's important to note that all of this happens before the default instance of frmMain is even shown, because these variables are declared outside of any methods in your code. Because the computer never can escape this infinite loop, it never gets a chance to move on and actually display your form.
And the moment you've all been reading so patiently for:
Fix it by removing the declaration of frmMain at the top of the Form3 code file. I don't know what that's there for, anyway.
EDIT: Hopefully, I can clear up a little confusion regarding passing values between forms. Upon further study of your code, my instincts tell me that the best solution for you is to overload the constructor for Form3 to accept the calling form (the existing instance of frmMain) as an argument. This will allow you to access all of the public members of frmMain from within your Form3 class.
Here's a rough sketch of how you might do this in your code:
Public Class frmMain
''#The private data field that stores the shared data
Private _mySharedData As String = "This is the data I want to share across forms."
''#A public property to expose your shared data
''#that can be accessed by your Form3 object
Public Property MySharedData As String
Get
Return _mySharedData
End Get
Set(ByVal value As String)
_mySharedData = value
End Set
End Property
Private Sub frmMain_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
''#Do all of your other stuff here...
''#Create a new instance of Form3, specifying this form as its caller
Dim otherForm As New Form3(Me)
''#Show the new instance of Form3
otherForm.Show()
End Sub
End Class
Public Class Form3
''#The private field that holds the reference to the main form
''#that you want to be able to access data from
Private myMainForm As frmMain
Public Sub New(ByVal callingForm As frmMain)
InitializeComponent()
''#Save the reference to the calling form so you can use it later
myMainForm = callingForm
End Sub
Private Sub Form3_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
''#Access anything that you need from the main form
MessageBox.Show(myMainForm.MySharedData)
End Sub
End Class
As you can see, frmMain exposes a public property (backed by a correspondingly-named private variable) called MySharedData. This can be absolutely anything you want, and you can have as many of these as you want.
Also notice that how the constructor (the New method) for Form3 accepts an instance of frmMain as an argument. Whenever you create a new Form3 object from frmMain, you just specify Me, which indicates that you want to pass the current instance of frmMain. In the constructor method, Form3 stores that reference to its calling form away, and you can use this reference any time you like in Form3's code to access the public properties exposed by frmMain.
In VS2010 menu, go to Build -> Configuration Manager, does your project have the checkbox in the "Build" column enabled?
If it's project upgraded from an older Visual Studio version it may be that it is not targeting .NET Framework 4.0. In that case you should change it as explained here.
To analyze the problem press F8 (or F10, depends on your default keyboard settings) to step into the code instead of running the app. This should take you to the main method where the main form would be initialized.