Introduction
Any seasoned developer remembers the days when reacting to changes in the state of an object was done by setting flags and using loops to poll the state of that object. While effective, this tactic does require the developer to invest some time in setting this up. However, if we learn to leverage the power of custom events, we can greatly simplify the effort as well as increase our potential for reuse.
Recently I was called on to create an application that had to monitor and route messages between several TCP/IP based connections to remote devices. Yeah, multi-threaded, asynchronous communications! That’s sarcasm folks. The real challenge here was to ensure the application was easy to maintain. I needed to break the work down into small units that fulfilled specific functions.
It didn’t take long to realize I wanted a user control that contained the socket object and managed all communication between my application and a specific device. An instance of this “monitor” control would then be placed on a form for each device I wanted to monitor. The form would handle routing messages between devices as well as logging all message traffic. Additionally, I was going to need a couple modal dialog boxes to handle specific interactions with my devices.
This left only one final challenge, since the “monitor” controls would handle all aspects of communication with the remote devices, how to alert objects outside of the monitor control of changes. The solution, as you can likely guess based on the title of this article, was to use customized event delegates.
The monitor control would raise specific events that other controls could then subscribe to. If a message arrived, the main form could log it. A sub-form could check to see if it was the type of message it was waiting for, the control itself could simply display it on its form, etc.
Now I only had to set it up.
Defining My Events
I knew I had 5 “events” I wanted to be able to monitor for (waiting for connection, got connection, lost connection, message sent, and message received). Additionally, I knew I could break the signatures for these events into two categories: changes in connection state and message traffic notification.
For changes in connection state, there were several pieces of information I wanted to pass along in the event. To keep things simple, I decided to define a class that contained all these values instead of passing them back as individual elements. I defined a custom event argument called ConnEventArgs.
ConnEventArgs is a simple class that inherits from the .NET Framework EventArgs class. It has a constructor and several properties that expose private values. This will make it easy for me to pass these values back to the event without having to declare all the individual properties.
Listing 1: The definition of ConnEventArgs, my custom event argument
For message traffic events, I’m not going to pass some descendent of EventArgs, but instead the message object I had already defined.
So, parameters determined, next I had to define the signatures of my custom events. I defined 5 publically accessible event delegates that corresponded to the items I wanted to be able to react too. Each of them receives both a sender, as well an argument specific to the type of event.
Well, we’ve defined our events and the arguments they pass along, but unless we declare instances of our events, the definitions themselves won’t actually do anything. Next stop… the monitor control.
Raising the Events
As previously mentioned, my monitor control was going to handle all communication between the application and the remote device. As such, it is perfectly positioned to know when each of these events needs to be raised. So the next step is to create instances of my events and make sure they are raised when they need to be.
We’ve defined our custom events, but now we have to declare instances of them. These really don’t look much different than any other object declaration. They are public, use the event keyword to specify that they are events, have the type of event, and have names associated with them.
Listing 2: Declaring instances of our custom events
Of course, at this point, these are just placeholders and have no value; we’ll get to that in a bit. We now need a way for my monitor control to raise the events. The following listing shows the event invoker methods I created.
Listing 3: Methods my control will use to help us raise our custom events
These are protected methods, so they can only be used by my control or children that descend from it. And because these methods are also virtual, they can be overridden by any children. I could just as easily have made them private, but this way, things are a bit more flexible if we have to tweak this code down the road.
Lastly, when appropriate, I need to make sure I raise my new events, using these invoker methods.
This creates an instance of the MyMessage object, then calls the OnMessageReceived invoker to raise the event. It is important to note that the signature of the event and the invoker do not have to match. In fact, the use of the invoker is entirely optional. If you look at the invokers, they’re just checking to make sure the instances of the events I created have value and then calling those events with the appropriate parameters. You could easily do that yourself, but if you need to raise the event in multiple locations, using the invoker will save you typing later on.
Subscribing to Events
So we’ve defined our events and their parameters. We then declared the events in an object and given it the ability to raise them. At this point the application could run and we’d happily start raising events. Problem is that null check that the invoker is doing…. It would be null, so the events wouldn’t really be raised. We have to have controls subscribe to our new events so we can perform actions when they are raised. This requires two steps. First we define an event handler, and secondly we associate that handler with the events being raised by the monitor control.
Now the event handlers are really just methods. However, since they are specifically made to handle events, they need to match the signature of the event they are handling. We defined those signatures back in Figure 2. Now we just need to create a method that implements one of those. We singled out OnMessageReceived earlier, so we’ll stick with it.
Listing 4: Sample event handler for OnMessageReceived
Note that the MessageReceived method matches the parameters we defined in the declaration of ucMessageReceived. It also matches the parameters we used in the controls invoker method. Hopefully, the picture should be starting to come together now.
Inside this event handler, we can do whatever we need to. In the case of my applications main form I’m going to make sure the message had a payload I want to act on, then log it and process (aka route) the message.
Before this method can even be called, we need to subscribe to the event.
Listing 5: Wiring up event handlers
A handful of assignment statements and we are done. On the left-hand side, we have my monitor control and those public events we defined in the previous section. On the right-hand side, we define new instances of the event objects and are passing along to them the names of our event handler methods. This is sometimes referred to as a “callback reference” as we are passing along a reference to a method that will be called by another class at the appropriate time.
Note also that our assignment is +=, or add to what already exists. This is because other objects may also be subscribing to these events. So we want to keep any pre-existing subscriptions as we add our own.
Event Marshalling
One last topic I want to touch on briefly is how events are marshaled. When we subscribed to our events up above, this was likely done from within a form or another user control. And normally, under those circumstances you’re going to want to interact with other controls, status messages on labels, updating a displayed log of events, etc… However, depending on which thread the event marshaled into, you may encounter the dreaded “invalid cross thread operation” error. There are several ways to handle this, but my favorite is also the most straight-forward.
By making a minor change to the code for our event handler, we can force the event to be marshaled into the thread responsible for all our other UI operations.
Listing 6: A thread safe version of our event handler
InvokeRequired is a property of a control (and therefore form) that allows you to detect if the event was called from another thread. If it was, we then use the control’s Invoke method to execute the event delegate on the same thread that owns the control. It’s a simple addition that can save you some headache later on.
Closing Review
Time for a quick summary of what we’ve done:
- Optionally define any custom event parameters we want to pass
- Define our custom event signatures
- Declare placeholders for the events in the class that will be raising the events
- Optionally define an invoker to help raise your event
- In the class that is raising the events, call the invokers when appropriate
- Create event handlers in classes that want to respond to our custom events
- Wire up or subscribe to the events.
Using this technique, you greatly expand the interaction between components in our windows applications. No longer are you confined by procedural based constructs like polling and status flags. Now you can alert other classes to changes in state proactively and let them react to it as they feel they should. Better yet, you can now enforce encapsulation and get rid of all those unnecessary global values you were using to communicate between components of your application.
Explore, use, and enjoy this new and important addition to your developer’s toolkit.
About Brent Stineman
 |
Sr. Consultant with Sogeti USA
Over 15 years of development experience on multiple platforms. Currently focused on distributed application development with .NET and SOA Governance.
View complete profile here.
|
You might also be interested in the following related blog posts
How To: Silverlight grid hierarchy load on demand using MVVM and RIA services
read more
Health Monitoring and ASP.NET MVC
read more
Designer Support for One-Way navigations in Entity Framework 4
read more
How to: Validate data with SL/WPF RadGridView Part I validating on property/cell level via Data Annotations
read more
Migrated from Community Server to DasBlog
read more
The Telerik CAB Enabling Kit and SCSF - Tutorial 6: The RadTreeView UIExtensionSite
read more
The ESRI Dev Summit 2010 hosted in Palms Springs CA is open for registration.
read more
Introducing RadScrollablePanel for Windows Forms
read more
Faking the Initialized Event in Silverlight
read more
Faking the Initialized Event in Silverlight
read more
|
|
Please login to rate or to leave a comment.