Debugging eventable code, one stupid mistake and how to fix it.
Okay, so I was debugging a slideshow viewer that I had written because for some reason it was skipping pictures. I really couldn't figure out why, so I just assumed the timer was somehow being called twice. Now this took me just a couple of seconds to identify once I had found out the slideshow viewer was performing a skip.
So when did it happen? Well, I was starting slideshows on a form that I was re-using. I just reattached a new set of files and told it to go again. Each time I did this it was re-attaching the event handler. If you've worked with windows forms before, you know there isn't any way to discover if you've already hooked an event already. There also isn't any way to remove the event without save the event handler definition somewhere. So what was the easy fix? Well, it was to detach the event so I wrote the following code:
// void StartSlideshow()
this.slideshowTimer.Tick += new EventHandler(this.Form_SlideShowAdvance);
// void Form_VisibleChanged()
this.slideshowTimer.Tick -= new EventHandler(this.Form_SlideShowAdvance);
That turns out to be the easiest way. Is there a better way? Well, probably. So let's save the handler definition in a local so we can investigate if it has been hooked up.
// MyClass : Form {
private EventHandler tickEvent = null;
// void StartSlideshow()
if ( tickEvent == null ) {
tickEvent = new EventHandler(this.Form_SlideShowAdvance);
this.slideshowTimer.Tick += tickEvent;
} // else it is already hooked
// void Form_VisibleChanged()
if ( tickEvent != null ) {
this.slideshowTimer.Tick -= tickEvent;
tickEvent = null;
}
This is actually more powerful I think. I have a deterministic way to tell if I've hooked an event, and I can unhook it if I want to. The event takes on a special singleton type quality, because I won't hook the event multiple times and so my event handler won't get called multiple times. Other code can still hook it with a different handler using the normal syntax, so things behave as normal. What else can you do with this? If you wrap the setting of tickEvent into some nice helper functions you can gain the ability to switch your handlers around with minimal code. I see a lot of discussion online where people want to disable and re-hook event to other handlers and the #1 used syntax is the new EventHandler(*) method which doesn't permit very much control at all.
I would really like a syntax that is a bit more powerful still. A kind of event manager where all of the events hooked within my type somehow get managed. The basic scenario I would like automatically solved is:
-
Set up a tracing handler.
-
When the user clicks up a menu item the tracing handler's events get hooked by a new form that displays all of the tracing information.
-
The form gets closed and disposed.
-
If the trace event gets called again, an exception is thrown because the event handler on the form attempts to access disposed resources.
If the form used some sort of eventing manager when hooking up the events, the programmer doesn't have to explicitly remove those events later. In highly dynamic eventing scenarios, the code for making sure all of your handlers are detached can get extremely monotonous. Ideally the event manager might look something like the code shown below. While I have a small eventing manager programmed up, I'm not happy with it yet. If I find at some point it is saving me time or provides a syntax others might enjoy I'll try and release it.
// Create a new eventing manager
EventManager em = new EventManager();
// Attach an event to a timer
em.AttachEvent(timer, "Tick", new EventHandler(Timer_Tick), true /* singleton */);
// Try to attach another instance to the singleton. The attach never occurs
em.AttachEvent(timer, "Tick", new EventHandler(Timer_Tick), true /* singleton */);
// Try to detach tick events
em.DetachEvent(timer, "Tick");
// Detach only a specific handler
em.DetachEvent(timer, "Tick", new EventHandler(Timer_Tick), false /* detach one instance of handler */);
em.DetachEvent(timer, "Tick", new EventHandler(Timer_Tick), true /* detach all instances of handler */);
// Check the state
em.EventAttached(timer, "Tick"); /* return true/false */
em.EventAttached(timer, "Tick", new EventHandler(Timer_Tick)); /* return true/false */
// Dispose()...
em.DetachAllEvents();