A collection of learnings and opinions.

Monday, March 5, 2007

Tell Me When Something Happens

Events are a staple of object-oriented design. They are a way decoupling objects and that is almost always a good thing. A short synopsis of the idea:

You have some object (Foo) and you want to be able to know when something happens to Foo. Say Foo is a button and you want to know when it's been pressed in order to do something. You could just code Foo to do the thing you wanted, but then you'd have to code every button for everything you want to do. You could tell Foo to execute some method (possibly in some other Object) when it was pressed, but then you'd lock Foo and the method together. You'd have problems if someone later changed that method also, as they may not know that Foo calls it. The solution to this is to let Foo have events, and code the actual behavior in some other Object or method which handles that event.

In Java this is done through a pattern called the event-listener. I call it that, anyway. What you do is you let Foo implement a method that takes the listener (addListener(FooListener l)).

public class Foo {
private List(FooListener) listeners = new LinkedList(FooListener);

public void addListener(FooListener l) {
listeners.add(l);
}
}


FooListener is an interface that includes one method to handle the event when the listener gets it. You can put whatever you want into the FooEvent, depending on what kind of event your particular implementation raises. Often you only need to send an event, that's enough information in itself.

public interface FooListener {
public void handleEvent(FooEvent e);
}


In Foo you decide when you want to send an event to the listeners, and you do it something like this:
private void doSomething() {
for(FooListener l : listeners) {
l.handleEvent(new FooEvent("Something"));
}
}


This works, but it's a bit verbose, and worse - it's up to the developers to decide how to do this and if they will.

In .Net classes have events themselves. The engineers over at MS have taken this pattern and hardcoded it into their platform. So, what you do here is slightly different:

Events and listeners are what I know from Java, but in VB.NET they're called Events and Handlers. I can live with that, it parses. What's really cool though is that like properties (which I really like) the events are language-features. So, you've got some kind of standard to follow, it's not something you have to explore on your own or make up on the spot.

When you add a button in VB.NET your app already knows about the events that can generate (they are part of the specification of the Button class). This makes it easy for the code-generator to give you the boilerplate for a handler. In fact, that's what it does when you double-click a button in the form-designer. Coding has never been so easy, and I like that.

The problem with this easy code-generation (for me, and this may be a problem with me) is that it doesn't teach you how to do this yourself. My case was that I wanted to add a handler to a control that only exists after a certain set of actions have been taken in my program.

When I add a handler to a button which exists the code is easy. Let's say I have a button called btnFoo on my form. To handle a click from this I just type in (VB.Net):
Public Sub handleButtonFoo(ByVal sender as System.Object, ByVal e as System.EventArgs) Handles btnFoo.Click
Console.Out.WriteLine("Click")
End Sub


It gets a little more hairy when you're handling dynamically created Objects, though. This took me some time to find, and it led me to a foray into delegates. I'll get back to them some other time though.

What you have to do is create a method with the same signature as the event. Say I'm looking to handle a LostFocus event on a Control. That has the signature: Public Event LostFocus(ByVal sender As Object, ByVal e As System.EventArgs). So what I need is a method that matches that signature. Behold!

Private Sub HandleSomethingLostFocus(ByVal sender As System.Object, ByVal a As EventArgs)
Console.Out.WriteLine("Give me back my focus!")
End Sub


This is not the whole story though. You may have noticed that the handleButtonFoo method had the keyword handles after it with a reference to the event it wanted. My little lost-focus method has no such thing, as no object exists yet that I want to handle. When one exists though, I can pipe the events to my method with the following code:
Private Sub createWidget()
'Make something that can lose focus
Dim widget As New TextBox

'Add the widget to the main form (which we're in)
Me.Controls.Add(widget)

'pipe the LostFocus events to the handler we wrote
AddHandler widget.LostFocus, AddressOf Me.HandleSomethingLostFocus

End Sub


This will delegate the LostFocus events to my method. Yay!

As you may have noticed this is a better way to handle this than in Java. You get a set pattern on how to do it, defined at the language level (AddHandler is a reserved word). It's also less verbose than the Java-way, as that one included code in two classes and one interface. It may do the same, but I like it better.

Now I've got to get home, I have a sick child. Till next time!

No comments: