I have a vb.net application which contains two forms. One is called "MapComponentForm" and another called "ComponentPropertiesForm". In the MapComponentForm I have defined an event which I want fired when the OK button is clicked. If the ComponentPropertiesForm is open, I want it to "hear" this event and act accordingly. Stepping through the code the event seems to fire as expected but the ComponentPropertiesForm seems to be oblivious to this. I've attached the code from the two forms after stripping out the non relevant code in hopes that someone can tell me why my event goes unheeded. I used the information here:
https://learn.microsoft.com/en-us/dotnet/visual-basic/programming-guide/language-features/events/walkthrough-handling-events
in constructing my own code.
Thanks for any suggestions.
Public Class MapComponentForm
Public Event PartMapped(ByRef status As Boolean)
Public Sub New(shape As Visio.Shape)
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
_shape = shape
End Sub
Private Sub OkButton_Click(sender As Object, e As EventArgs) Handles OkButton.Click
'If the component properties window is open, refresh it
If Application.OpenForms().OfType(Of ComponentPropertiesForm).Any Then
RaiseEvent PartMapped(True)
End If
end sub
end class
Public Class ComponentPropertiesForm
Private WithEvents mPartMap As MapComponentForm
Private Sub ComponentPropertiesForm_Load(sender As Object, e As EventArgs) Handles Me.Load
mPartMap = New MapComponentForm(Nothing)
End Sub
Private Sub mPartMap_PartMapped(ByRef status As Boolean) Handles mPartMap.PartMapped
If status = True Then
MsgBox("something got mapped")
End If
End Sub
end class
Related
I need to display a form for some amount of time - basically a "please wait, loading" form with progress bar. When certain operation completes, I want this window to disappear. Here's my try at it:
If IsNothing(mlLabels) Or mblnIsLoading Then Exit Sub
If mstrPrinterA.Equals(Me.cmbPrinters.Text, StringComparison.OrdinalIgnoreCase) Then
Exit Sub
End If
Dim th As New Threading.Thread(AddressOf WaitPrinter)
th.Start()
If mlLabels.IsPrinterOnLine(Me.cmbPrinters.Text) Then
Me.cmbPrinters.BackColor = Drawing.Color.Green
Else
Me.cmbPrinters.BackColor = Drawing.Color.Red
End If
th.Abort()
Do While th.IsAlive
Loop
th = Nothing
mstrPrinterA = Me.cmbPrinters.Text
Private Sub WaitPrinter()
Dim fw As New FormWaiting
fw.ShowDialog()
fw = Nothing
End Sub
However, I then read that using Thread.Start() and Thread.Abort() is not considered a good practice. Is there another way I can do that?
Here is a simple example of what I described in my comment above. Create a WinForms project with two forms, adding a Button to Form1 and a BackgroundWorker to Form2. Add this code to Form1:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
'Display a dialogue while the specified method is executed on a secondary thread.
Dim dialogue As New Form2(New Action(Of Integer)(AddressOf Pause), New Object() {5})
dialogue.ShowDialog()
MessageBox.Show("Work complete!")
End Sub
Private Sub Pause(period As Integer)
'Pause for the specified number of seconds.
Threading.Thread.Sleep(period * 1000)
End Sub
and this code to Form2:
Private ReadOnly method As [Delegate]
Private ReadOnly args As Object()
Public Sub New(method As [Delegate], args As Object())
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Me.method = method
Me.args = args
End Sub
Private Sub Form2_Load(sender As Object, e As EventArgs) Handles MyBase.Load
BackgroundWorker1.RunWorkerAsync()
End Sub
Private Sub BackgroundWorker1_DoWork(sender As Object, e As ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
'Execute the specified method with the specified arguments.
method.DynamicInvoke(args)
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
'Close the dialogue when the work is complete.
Close()
End Sub
Run the project and click the Button on the startup form. You'll see the dialogue displayed while the work is executed and then disappear when it's done. The dialogue is written in such a way that it can be used to invoke any method with any arguments. It's the caller that gets to define what the work to be performed is.
In this particular case, the "work" is a simple sleep but you can put anything you like in there. Just note that it is executed on a secondary thread so no direct interaction with the UI is allowed. If you need UI interaction then that could be accomplished but you'd need slightly more complex code. Note that the code as it is also does not allow for returning a result from the executed method, but you could support that fairly easily too.
I have a form (frmwizard) which I am using to create a wizard like interface. The form contains a usercontrol and a button (for testing). There is also a function on the form called "NextPage"
The form loads a usercontrol (ucpage1) on load and the usercontrol has a button on it that when clicked attempts to call a function on the main form as per below:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
frmWizard.Nextpage(me.name)
End Sub
Within my function called "NextPage" have the following for testing.
Public Sub NextPage(ByVal CurrentPage As String)
MessageBox.Show(UserControl1.Controls.Count)
End Sub
When I call the function from the form itself (via the button) I get the result of 1, when i call the function via the User Control, I get the result as 0
I'm sure there is something simple I need to do, but i'm unsure what i've overlooked.
I am trying to make the button on the user-control Save the data within the control and then to browse to the next wizard page. Hopefully this is enough information
Codependance is a bad idea, as it locks the two types together for the future. If your user control really needs to invoke the form, you should instead have it raise an event and handle the event in your form.
Public Class MyUserControl
Public Event OnNextPage(ByVal sender As Object, ByVal e As EventArgs)
Private Sub btnNextPage_Click(sender As Object, e As EventArgs) Handles btnNextPage.Click
RaiseEvent OnNextPage(Me, New EventArgs)
End Sub
End Class
Public Class Form1
Private Sub MyUserControl_OnNextPage(sender As Object, e As EventArgs) Handles MyUserControl1.OnNextPage ' , MyUserControl2.OnNextPage, etc...
MessageBox.Show(DirectCast(sender, MyUserControl).Controls.Count)
End Sub
End Class
On every DataGridView1_SelectionChanged event I need to run a Private Sub OnSelectionChanged() of the form that is loaded into Panel1 (see the image http://tinypic.com/r/2nu2wx/8).
Every form that can be loaded into Panel1 has the same Private Sub OnSelectionChanged() that initiates all the necessary calculations. For instance, I can load a form that calculates temperatures or I can load a form that calculates voltages. If different element is selected in the main form’s DataGridView1, either temperatures or voltages should be recalculated.
The problem is - there are many forms that can be loaded into Panel1, and I’m struggling to raise an event that would fire only once and would run the necessary Sub only in the loaded form.
Currently I’m using Shared Event:
'Main form (Form1).
Shared Event event_UpdateLoadedForm(ByVal frm_name As String)
'This is how I load forms into a panel (in this case frm_SCT).
Private Sub mnu_SCT_Click(sender As Object, e As EventArgs) Handles mnu_SCT.Click
frm_SCT.TopLevel = False
frm_SCT.Dock = DockStyle.Fill
Panel1.Controls.Add(frm_SCT)
frm_SCT.Show()
Var._loadedForm = frm_SCT.Name
RaiseEvent event_UpdateLoadedForm(Var._loadedForm)
End Sub
‘Form that is loaded into panel (Form2 or Form3 or Form4...).
Private WithEvents myEvent As New Form1
Private Sub OnEvent(ByVal frm_name As String) Handles myEvent.event_UpdateLoadedForm
‘Avoid executing code for the form that is not loaded.
If frm_name <> Me.Name Then Exit Sub
End Sub
This approach is working but I’m sure it can be done way better (I'd be thankful for any suggestions). I have tried to raise an event in the main form like this:
Public Event MyEvent As EventHandler
Protected Overridable Sub OnChange(e As EventArgs)
RaiseEvent MyEvent(Me, e)
End Sub
Private Sub DataGridView1_SelectionChanged(sender As Object, e As EventArgs) _
Handles DataGridView1.SelectionChanged
OnChange(EventArgs.Empty)
End Sub
but I don't know to subscribe to it in the loaded form.
Thank you.
Taking into account Hans Passant’s comments as well as code he posted in related thread I achieved what I wanted (see the code below).
Public Interface IOnEvent
Sub OnSelectionChange()
End Interface
Public Class Form1
' ???
Private myInterface As IOnEvent = Nothing
' Create and load form.
Private Sub DisplayForm(frm_Name As String)
' Exit if the form is already displayed.
If Panel1.Controls.Count > 0 AndAlso _
Panel1.Controls(0).GetType().Name = frm_Name Then Exit Sub
' Dispose previous form.
Do While Panel1.Controls.Count > 0
Panel1.Controls(0).Dispose()
Loop
' Create form by its full name.
Dim T As Type = Type.GetType("Namespace." & frm_Name)
Dim frm As Form = CType(Activator.CreateInstance(T), Form)
' Load form into the panel.
frm.TopLevel = False
frm.Visible = True
frm.Dock = DockStyle.Fill
Panel1.Controls.Add(frm)
' ???
myInterface = DirectCast(frm, IOnEvent)
End Sub
Private Sub DataGridView1_SelectionChanged(sender As Object, e As EventArgs) _
Handles DataGridView1.SelectionChanged
' Avoid error if the panel is empty.
If myInterface Is Nothing Then Return
' Run subroutine in the loaded form.
myInterface.OnSelectionChange()
End Sub
End Class
One last thing – it would be great if someone could take a quick look at the code (it works) and confirm that it is ok, especially the lines marked with “???” (I don’t understand them yet).
This is my situation, there are 2 Classes and my main form Form1:
Class1: has a method doSomethingAndCall(callback) which creates a new thread
Class2: has dynamic created controls with a button that fires Class1.doSomethingAndCall(newCallback)
in code it looks like this (it starts at Class2.Button_Click):
Class Class1
public shared sub doSomethingAndCallAsync(state as object)
Console.WriteLine(Form1.InvokeRequired) 'output: false
Console.WriteLine(Form1.IsHandleCreated) 'output: false
Form1.Invoke(state.callback) 'throws System.InvalidOperationException
end sub
public shared sub doSomethingAndCall(callback as object)
System.Threading.ThreadPool.QueueUserWorkItem(AddressOf doSomethingAndCallAsync, New With {.callback = callback})
end sub
End Class
Class Class2
Public Delegate Sub doSomethingDelegate()
Public Sub doSomething()
Console.WriteLine("success!")
End Sub
Public Sub Button_Click(ByVal sender As Object, ByVal e As System.EventArgs)
Class1.doSomethingAndCall(New doSomethingDelegate(AddressOf doSomething))
End Sub
End Class
The exact exception I get is:
Invoke or BeginInvoke cannot be called on a control until the window handle has been created
and as I can see the console.WriteLine in line 4 shows me that the form is realy not created. So I added this handlers, and now it get's really confusing:
Private Sub Form1_HandleCreated(sender As Object, e As System.EventArgs) Handles Me.HandleCreated
Console.WriteLine("Handle created") 'Output: Handle created, when running program
End Sub
Private Sub Form1_HandleDestroyed(sender As Object, e As System.EventArgs) Handles Me.HandleDestroyed
Console.WriteLine("Handle destroyed") 'Will never Output!
End Sub
So it's created and never destroyed but if i click the button it's nevertheless not avaible? -Can anyone explain me what is going on and how to call a callback correct, thanks!
The instance of My.Forms.Form1 aka. Form1 will be different in each thread. You need a handle to the correct instance. Drop a button onto your Form1 and add the following code:
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Threading.Tasks.Task.Factory.StartNew(Sub() Class1.Wrong())
Threading.Tasks.Task.Factory.StartNew(Sub() Class1.Correct(Me))
End Sub
End Class
Public Class Class1
Public Shared Sub Wrong()
Debug.WriteLine(String.Format("(Other thread, wrong) InvokeRequired={0}, IsHandleCreated={1}", Form1.InvokeRequired, Form1.IsHandleCreated))
End Sub
Public Shared Sub Correct(instance As Form1)
Debug.WriteLine(String.Format("(Other thread, correct) InvokeRequired={0}, IsHandleCreated={1}", instance.InvokeRequired, instance.IsHandleCreated))
End Sub
End Class
Output
(Other thread, correct) InvokeRequired=True, IsHandleCreated=True
(Other thread, wrong) InvokeRequired=False, IsHandleCreated=False
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