I've searched for the difference about the use of the keyword Handles instead of AddHandler, in VB.NET, but I'm unable to explain why this code doesn't work..
Imports System.Threading
Public Class MyClass_EventArgs
Inherits System.EventArgs
End Class
Public Class MyClass
Public Event MainThreadFinished(ByVal sender As Object, ByVal e As MyClass_EventArgs)
Private WithEvents MyEvents As MyClass
Private trd As Thread
Public Sub New()
'AddHandler MainThreadFinished, AddressOf Me.MyEvents_ThreadFinished
trd = New Thread(AddressOf mainThread)
trd.IsBackground = True
trd.Start()
RaiseEvent MainThreadFinished(Me, Nothing)
End Sub
Protected Overrides Sub Finalize()
trd.Abort()
End Sub
Protected Sub MyEvents_ThreadFinished(ByVal sender As Object, ByVal e As MyClass_EventArgs) _
Handles MyEvents.MainThreadFinished
MessageBox.Show("AAA")
End Sub
Private Sub mainThread()
RaiseEvent MainThreadFinished(Me, Nothing)
End Sub
End Class
Well, this code never respond to the events, but if I uncomment the followin line, the code works and the messagebox appear...
'AddHandler MainThreadFinished, AddressOf Me.MyEvents_ThreadFinished
Why does this happen?
It looks like you've made a fine discovery! Per Microsoft documentation, RaiseEvent Statement,
Non-shared events should not be raised within the constructor of the
class in which they are declared. Although such events do not cause
run-time errors, they may fail to be caught by associated event
handlers. Use the Shared modifier to create a shared event if you need
to raise an event from a constructor.
In other words, Microsoft says you shouldn't be doing what you're doing - and if you must, to use shared events.
In looking through other sources, I would say that the difference between AddHandler and Handles is a matter of syntactic sugar. You may want to look into how events are done in C# for more insight (such as in C# Events). Handles is used in conjunction with WithEvents as a way for an instance of a class to automatically subscribe to events (which is otherwise explicitly done with += in C# and with AddHander in VB.NET).
It would seem that your explicit AddHandler ensures that the event hookups are in place before the RaiseEvent, and so then it works as you wanted. I can only guess that without that, those event hookups weren't yet done - that is, it didn't work because of however the compiler inserts the code that performs the equivalent of AddHandler behind the scenes, by whatever design pattern the compiler writers deemed as appropriate. It would seem that the designers were well aware of this possible consequence, given their warning about this.
Related
I am writing an Outlook 365 32 bit VSTO add-in that can perform code when the to-do list explorer is activated. Ideally, I would simply do something like the following:
Private WithEvents OlExplr As Outlook.Explorer
Private Sub ThisAddIn_Startup() Handles Me.Startup
OlExplr = Application.ActiveExplorer
End Sub
Private Sub OlExplr_Activate() Handles OlExplr.Activate
'''Do some stuff here
End Sub
Unfortunately, Activate is both a method and event for the Explorer class, and as such, compile errors will occur if I attempt to implement as above. I have seen several examples (below) of how to handle this ambiguity in C#, but I cannot translate to vb.net effectively, nor do I really understand what they mean by "cast OutlookExplorer variable above to ExplorerEvents interface". I know I will need to use ExplorerEvents_10_ActivateEventHandler or possibly ExplorerEvents_10_Event, but the actual implementation is beyond my current skill level.
VSTO Outlook AddIn: Cannot use Explorer Close event
What is the difference between _Application and Application
Can someone please explain what casting would mean in this context, and how to circumvent the ambiguity issue?
Edit:
Following Dmitry's answer, I got the following, though it is not triggering the event still (no errors are being flagged at least...)
Public Class ThisAddIn
Private WithEvents OlExplr As Microsoft.Office.Interop.Outlook.Explorer
Public Delegate Sub ExplorerEvents_10_ActivateEventHandler(ByRef sender As Object)
Public Event OlExplr_Activate As ExplorerEvents_10_ActivateEventHandler
Private Sub ThisAddIn_Startup() Handles Me.Startup
OlExplr = Application.ActiveExplorer
AddHandler OlExplr_Activate, New ExplorerEvents_10_ActivateEventHandler(AddressOf OlExplr.Activate)
End Sub
Private Sub ThisAddIn_OlExplr_Activate(ByRef sender As Object) Handles Me.OlExplr_Activate
MsgBox("Hello!")
End Sub
Instead of statically declaring the event handler, try to use AddHandler statement dynamically at run-time.
Off the top of my head (I don't use VB.Net):
Private Sub ThisAddIn_Startup() Handles Me.Startup
OlExplr = Application.ActiveExplorer
AddHandler (OlExplr As ExplorerEvents).Activate, AddressOf OlExplr_Activate
End Sub
Private Sub OlExplr_Activate()
'''Do some stuff here
End Sub
Using what Dmitry posted, I was able to tweak the syntax to capture and fire the event:
Private WithEvents OlExplrs As Outlook.Explorers
Private WithEvents Explr As Microsoft.Office.Interop.Outlook.Explorer
Private Sub ThisAddIn_Startup() Handles Me.Startup
OlExplrs = Application.Explorers
End Sub
Private Sub OlExplrs_NewExplorer(Explorer As Explorer) Handles OlExplrs.NewExplorer
If Explorer.Caption = "To-Do List..." Then
Explr = OlExplrs.Item(Explorer.Caption)
End If
AddHandler Explr.Activate, AddressOf MyActivateHandler
End Sub
Private Sub MyActivateHandler()
'''Do stuff...
End Sub
It turns out, however, that the way I have Outlook set up, the Activate method is not triggered when the desired explorer window (in this case, the to-do list) is brought into focus. The Activate method only fires if the explorer goes from a minimized to a maximized state or gets initialized and opened.
The Hide/Unhide event (or whatever it is that is happening when moving from the inbox window to the to-do list window and back, etc...) is really what I would need. This does not exist as a built in event for the explorer, so perhaps a custom event could work? I will open a separate question if I cannot find a solution.
I decided to turn on the strict option on one of my applications. And for the life of me I couldn't figure out how to make a small bit of code compile. In a module I had this bit of code
Sub Main()
AddHandler AppDomain.CurrentDomain.UnhandledException, AddressOf CurrentDomain_UnhandledException
End Sub
Private Sub CurrentDomain_UnhandledException(sender As Object, e As UnhandledExceptionEventArgs)
e.ExitApplication = False
End Sub
after looking around and seeing another post about putting it in the ApplicationEvents I got it to work by handling said event. So out curiosity I decided to move the AddHandler and into the same class and then it became clear that its the same class name but different namespaces:
Partial Friend Class MyApplication
Private Sub MyApplication_UnhandledException(sender As Object, e As UnhandledExceptionEventArgs) Handles Me.UnhandledException
e.ExitApplication = False
AddHandler AppDomain.CurrentDomain.UnhandledException, AddressOf CurrentDomain_UnhandledException
End Sub
Private Sub CurrentDomain_UnhandledException(sender As Object, e As System.UnhandledExceptionEventArgs)
e.ExitApplication = False
End Sub
End Class
Here is a link to both:
Microsoft.VisualBasic.ApplicationServices.UnhandledExceptionEventArgs
System.UnhandledExceptionEventArgs
So my question is, which one should I use? I'm wanting to prevent the app from shutting down... but both options seem like what I want.
The System.UnhandledException doesn't have an ExitApplication member (see the docs you linked), so cannot be used to stop the application exiting - once this is called the application will always terminate. Generally the Microsoft.VisualBasic namespace are helpers for VB that more-or-less duplicate functionality available elsewhere. The closest equivalent to the VisualBasic handler that you mention is instead the Application.ThreadException one. This and the AppDomain.CurrentDomain.UnhandledException are both described quite well in the MS docs.
To prevent the app from shutting either the VisualBasic one or the ThreadException one can be used. I've used the Microsoft.VisualBasic one in the past to achieve something similar to what you are doing.
To start with I have a fairly unique situation in that I am dealing with large amounts of data - multiple series of about 500,000 points each. The typical plot time is about 1s which is perfectly adequate.
The chart is created 'WithEvents' in code and the plot time doesn't change.
However, when I add the sub with the handler for the click event ..
Private Sub Chart_Main_Click(ByVal sender As Object, _
ByVal e As MouseEventArgs) Handles Chart_Main.Click
Dim y As Integer = Chart_Main.ChartAreas(0).AxisX.PixelPositionToValue(e.X)
'MsgBox(y)
End Sub
the plot time blows out to 3min. Even having no code in the sub, the result is the same. There is no reference to the click event in any of the code so I am at a loss as to why this is occurring. I suspect it has something to do with the number of points being added but not knowing the cause is frustrating.
Is anyone able to explain what is going on?
Ok, i don't know if the explanation in the comments was sufficient, so here some example code...
Also i wanted to try this myself!
Essencially, what you do is take control on when you want Windows to check the events.
For that, i suggested two wrappers on AddHandler and RemoveHandler that can safely be called from worker threads.
So, what you have to do, is:
Initialize the Handler in the constructor
Call RemoveClickHandler on your control, each time you want it to be left alone by the EventHandler
But don't forget to reinitialize the handler afterwards via AddClickHandler
Also, your handler method should not have the 'Handles' keyword anymore...
Public Class MainForm
Public Sub New()
' This call is required by the designer.
InitializeComponent()
m_pPictureClickHandler = New MouseEventHandler(AddressOf hndPictureClick)
AddClickHandler(pbxFirst, m_pPictureClickHandler)
End Sub
' Have a persistent local instance of the delegate (for convinience)
Private m_pPictureClickHandler As MouseEventHandler
Public Sub AddClickHandler(obj As Control, target As [Delegate])
If Me.InvokeRequired Then
Me.Invoke(New Action(Of Control, [Delegate])(AddressOf AddClickHandler), obj, target)
Else
AddHandler obj.MouseClick, target
End If
End Sub
Public Sub RemoveClickHandler(obj As Control, target As [Delegate])
If Me.InvokeRequired Then
Me.Invoke(New Action(Of Control, [Delegate])(AddressOf RemoveClickHandler), obj, target)
Else
RemoveHandler obj.MouseClick, target
End If
End Sub
' Here your Plot is done
Public Sub LockedPlot()
RemoveClickHandler(pbxFirst, m_pPictureClickHandler)
' do something on your handler free control ...
AddClickHandler(pbxFirst, m_pPictureClickHandler)
End Sub
' This is your handler (note without a 'Handles' keyword)
Private Sub hndPictureClick(sender As Object, e As MouseEventArgs)
' do something with the click
MessageBox.Show(String.Format("Yeah! You clicked at: {0}x{1}", e.X.ToString(), e.Y.ToString()))
End Sub
End Class
I suppose an even better design would be to create a child class of your chart that has an LPC style method called, say 'SafePlot', with folowing features:
It accepts a pointer (delegate) to a procedure
It will remove all the event handler before invoking the procedure
Finally it would reinitialize the handlers on it's own after the job is done.
It may require a collection to all handler refering to it's events.
-> For that reason i'd let the class manage the handlers entiraly...
Alternativly you could put the 'SafePlot' idea in your main class. then you could manage the event handler there... but that is disputable
Well i can think of a few other ways to do this, but i'm cutting the brainstorming now!
If interested in one of these design solutions, give me a poke.
I have created a Form with Objects like Progressbar and Button.
I have also created Public library code outside my Form
I want to modify the Progressbar Control or the Button's Text from a Sub that is written in a library (I try to pass my Form as a parameter):
Public Shared Sub ModifyItems(ByRef _WhichForm As Form)
_WhichForm.MyProgressBar1.visible = True
End sub
Unfortunately, the code Is not recognizing the Progressbar name MyProgressBar1
Is there a way to modify the Progressbar Control on the Form directly in a Sub or Function that is written in a class library, not directly in the Form Code ?
This type of approach would generally fall under the umbrella of "bad practice". Having objects modifying each others members directly introduces tight coupling that makes for difficult code to extend, debug, and maintain.
Rather than trying to modify something from within the library, better to think in terms of notifying that something within the library has changed. You could, for example, raise an event within the library. Form1 could then listen for that event and make any changes to its components that are appropriate. This way Form1 is solely responsible for modifying its components and the library is solely responsible for announcing changes to its internal state.
To give a good example - if, say, you were to change your Form's progress bar component (maybe you find a better one out there) you suddenly introduce a breaking change into your library.
To use an event you might do something like :
Public Class MyLibrary
Event OnSomethingHappened()
Private Sub SomethingHappened()
RaiseEvent OnSomethingHappened()
End Sub
End Class
And then in your form :
Public Class Form1
Private WithEvents _myLibrary as New MyLibrary
Private Sub LibraryDidSomething() Handles _myLibrary.OnSomethingHappened
MyProgressBar1.Visible = True
End Sub
End Class
This nicely decouples any dependence on Form1 - the library exists as a self-contained entity and doesn't care who listens to its events.
If you want to do this with a shared class you can also use shared events - these must be connected programmatically as :
Public Class MyLibrary
Shared Event OnSomething()
Public Shared Sub DoSomething()
RaiseEvent OnSomething()
End Sub
End Class
in the form :
Public Class Form1
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) _
Handles MyBase.Load
AddHandler MyLibrary.OnSomethingHappened, AddressOf LibDidSomething
End Sub
Private Sub Form1_FormClosed(sender As System.Object, e As _
System.Windows.Forms.FormClosedEventArgs) Handles MyBase.FormClosed
RemoveHandler MyLibrary.OnSomethingHappened, AddressOf LibDidSomething
End Sub
Private Sub LibDidSomething()
MyProgressBar1.Visible = True
End Sub
End Class
When programmatically adding events you must take care to remove them before disposing of the objects that have subscribed to them. In this case I have added the handler when the form loads and have removed it when the form closes. If you fail to remove an added handler then the form would not be garbage collected and this would cause a memory leak.
See here for more reading : Raising Events and Responding to Events (MSDN)
I'm seeing some strange behavior where the RunWorkerCompleted event for one of two threads I start isn't being called depending on how I call them. Check out the code below, and the two methods of firing the threads, good() and bad().
Public Class Form1
Private WithEvents bw As System.ComponentModel.BackgroundWorker
Private WithEvents bw2 As System.ComponentModel.BackgroundWorker
Private starts As Integer = 0
Private Sub bw_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bw.DoWork
starts += 1
Threading.Thread.Sleep(e.Argument)
End Sub
Private Sub bw_Completed(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles bw.RunWorkerCompleted
MessageBox.Show("Ending " + starts.ToString())
End Sub
Private Sub bad()
bw = New System.ComponentModel.BackgroundWorker()
bw.RunWorkerAsync(5000)
Threading.Thread.Sleep(500)
bw = New System.ComponentModel.BackgroundWorker()
bw.RunWorkerAsync(5)
End Sub
Private Sub good()
bw2 = New System.ComponentModel.BackgroundWorker()
AddHandler bw2.DoWork, AddressOf bw_DoWork
AddHandler bw2.RunWorkerCompleted, AddressOf bw_Completed
bw2.RunWorkerAsync(500)
bw2 = New System.ComponentModel.BackgroundWorker()
AddHandler bw2.DoWork, AddressOf bw_DoWork
AddHandler bw2.RunWorkerCompleted, AddressOf bw_Completed
bw2.RunWorkerAsync(5)
End Sub
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
'good()
bad()
End Sub
End Class
In both cases the DoWork event is called for both threads. But in the bad() case, only the second thread fires the RunWorkerCompleted event. This is obviously due to the two different ways I'm using VB to handle events here. I'm looking for an explanation of this behavior, preferably with a link to some documentation that could help me understand how these events are being handled in VB better. It seems strange to me that just reusing a variable name here seems to either dispose of the thread before it's done or else just make it stop firing events.
Where is this automatic unsubscribing documented?
In the Visual Basic Language Specification, a document you can download from Microsoft. Chapter 9.6.2 "WithEvents Variables" says this:
The implicit property created by a WithEvents declaration takes care of hooking and unhooking the relevant event handlers. When a value is assigned to the variable, the property first calls the remove method for the event on the instance currently in the variable (unhooking the existing event handler, if any). Next the assignment is made, and the property calls the add method for the event on the new instance in the variable (hooking up the new event handler).
The bolded phrase describes the behavior you see. It is rather important it works that way. If it didn't then you could never unsubscribe from an event and the event subscriptions would pile on without limit.