18.3. Event Programming

If you have done Win32 GUI-based or Java Swing/AWT-based programming, you are probably familiar with event programming. If you have dabbled in JavaBeans, you are probably familiar with the concept of listeners listening to changes to a JavaBeans property, and you know how these listeners can be added or deleted dynamically. Listeners in Java are essentially classes whose methods get called when a particular event happens. An event is also a class that is generated by yet another class, called the source. Because everything in Java is modeled as a class, it is sometimes confusing to differentiate a listener (the event receiver) from the source (the event generator).

Whether in Java or C#, an event is a message sent by an object to signal the occurrence of an action. The action can be triggered by a GUI control or by a daemon process. Every event has an event sender and one or more event receivers. The senders and receivers are totally unaware of each other.

In C#, delegates are used to tie the sender and the receiver. As explained earlier in this chapter, a delegate is a type-safe function pointer. When a source generates an event, an event object is instantiated and filled with properties called event arguments. The event object is then passed to a delegate, which gets invoked. An event listener registers or deregisters its interest in the event by adding or removing its own delegate to the event handler delegate. The .NET Framework provides an event keyword. An event, then, is essentially a delegate with a very specific signature. Events are usually declared public, although other access modifiers can be used.

To illustrate the basics of event programming, we will introduce the various components. Listing 18.6 shows an event source class. All events originate from a source.

Listing 18.6. The Event Source (C#)
using System;

public delegate void TornadoHandler(object sender, EventArgs
                                                             e);

public delegate void HurricaneHandler(object sender, EventArgs
                                                             e);

//The source of all events
abstract public class Weather {
  abstract protected void OnEvent(EventArgs args);
  public void Happen() {
    OnEvent(EventArgs.Empty);
  }
}
public class Tornado : Weather {
  public event TornadoHandler myEvent ;
  protected override void OnEvent(EventArgs args) {
    if (myEvent != null) {
        myEvent (this, args);
    }
  }
}
public class Hurricane : Weather {
  public event HurricaneHandler myEvent ;
  protected override void OnEvent(EventArgs args) {
    if (myEvent != null) {
        myEvent(this, args);
    }
  }
}

In this case the Weather class is the source of the event. It is an abstract class and has two subclasses (Tornado and Hurricane), each specifying its respective events. The Happen() method calls the OnEvent() method with an empty EventArgs argument. Because we are overriding the method in the subclass, the OnEvent method of the subclass gets called, and the respective event is generated. In this case there is no event object as such; instead, the communication is done through the EventArgs argument.

The listener of the events is shown in Listing 18.7, which is the BasicWeatherStation class.

Listing 18.7. The Event Listener (C#)
using System;
public class BasicWeatherStation {

  public BasicWeatherStation () {
  }
  public void AddWeather2Watch(Weather weather) {
    Tornado tor = weather as Tornado;
    if (tor != null) {
      tor.myEvent += new
           TornadoHandler(BasicTornadoHandler);
    }
    Hurricane cane = weather as Hurricane;
    if (cane != null) {
      cane.myEvent += new
             HurricaneHandler(BasicHurricaneHandler);
    }
  }

  public void BasicTornadoHandler(object sender, EventArgs e) {
    Console.WriteLine("Station has issued a tornado watch");
}
  public void BasicHurricaneHandler(object sender, EventArgs e)
{
    Console.WriteLine("Station has issued a hurricane warning");
  }

  public static void Main(string[] args) {

    Weather tornado = new Tornado();
    Weather hurricane = new Hurricane();
    BasicWeatherStation station = new
                 BasicWeatherStation();
    station.AddWeather2Watch(tornado);
    station.AddWeather2Watch(hurricane);
    tornado.Happen();
    hurricane.Happen();
  }
}

The weather station class is interested in knowing about all sorts of weather and therefore has weather event handlers for each type of weather. The BasicWeatherStation class registers its interest in knowing about the weather by adding its event handlers BasicTornadoHandler and BasicHurricaneHandler to the respective event delegates of the specific weathers, as shown in Listing 18.8.

Listing 18.8. Adding Event Handlers to Event Delegates (C#)
Tornado tor = weather as Tornado;
if (tor != null) {
  tor.myEvent += new
  TornadoHandler(BasicTornadoHandler);
}
Hurricane cane = weather as Hurricane;
if (cane != null) {
  cane.myEvent += new
  HurricaneHandler(BasicHurricaneHandler);
}

The output of Listing 18.8 is as follows:

Station has issued a tornado watch
Station has issued a hurricane warning

In this example the subclasses take care of declaring the specific events and must implement the code in the OnEvent method even though the code is similar for both subclasses.

It is possible for the weather class to maintain an internal map of delegates that will get called based on a specific key. Listing 18.9 shows a smarter Weather class, which is no longer abstract. The Weather class now implements an internal map of weather types to actual handlers. All tornadoes are handled by BasicWeatherStation.BasicTornadoHandler, and all hurricanes are handled by BasicWeatherStation.BasicHurricaneHandler.

Listing 18.9. The New Weather Class (C#)
using System;
using System.Collections;

public delegate void WeatherHandler(object sender, EventArgs e);
public class Weather {

  string key;
  static Hashtable weathers = new Hashtable();
  public Weather (string key, WeatherHandler wh) {
    this.key = key;
    weathers[key] = (WeatherHandler)weathers[key] + wh;
  }
  public event WeatherHandler weatherEvent
  {
    add
    {
      weathers[key]
        Delegate.Combine(( Delegate)weathers[key], value);
    }
    remove
    {
      weathers[key] = Delegate.Remove((
        Delegate)weathers[key], value);
    }
  }

  public void Happen() {
    OnEvent(EventArgs.Empty);
  }
  protected void OnEvent(EventArgs args) {
    WeatherHandler wh = (WeatherHandler) weathers[key];
    wh(this, args);
  }
}

Note that the compiler provides default implementation of the add and remove methods of the event keyword. In the class in Listing 18.9 we chose to implement our own versions of these methods so that the delegate instances are stored in a central hash table. This class also uses the static methods of the Delegate class as opposed to using the “+” and “-” operators. The Weather constructor now registers the passed-in WeatherHandler with the internal map. The Happen() method calls the OnEvent method, which, based on the key field of the Weather class, picks up the appropriate event delegate and invokes it.

Listing 18.10 shows the corresponding BasicWeatherStation class. The usage of the delegates is simplified. Most of the work is done by the Weather class. The BasicWeatherStation is used only for the callback methods.

Listing 18.10. The New BasicWeatherStation Class (C#)
using System;
public class BasicWeatherStation {

  public static void BasicTornadoHandler(object sender,
                                               EventArgs e) {
    Console.WriteLine("Station has issued a tornado watch");
  }
  public static void BasicHurricaneHandler(object sender,
                                           EventArgs e) {
    Console.WriteLine("Station has issued a hurricane
                                                     warning");
  }

  public static void Main(string[] args) {

    Weather tornado = new Weather ("Tornado", new
      WeatherHandler(BasicWeatherStation.BasicTornadoHandler));

    Weather hurricane = new Weather("Hurricane", new
    Weather(BasicWeatherStation.BasicHurricaneHandler));

    tornado.Happen();
    hurricane.Happen();
  }
}

So far we have registered the weather listeners based on the type of the weather—tornado or hurricane. This may not seem specific enough because multiple listeners could be interested in a particular tornado, and a listener could be interested in knowing about multiple tornadoes. Also, there is no way to inform all stations of a particular event. Listing 18.11 shows a modified version of the Weather class.

Listing 18.11. Modified Version of the Weather Class (C#)
using System;
using System.Collections;

public delegate void WeatherHandler(object sender,
                                       EventArgs e);

public class Weather {

  string key;
  object station;
  static Hashtable weathers = new Hashtable();
  private Weather(){}

  public static void AddHandlers(string key, IStation station) {
    Hashtable stationHandlers = (Hashtable) weathers[station];
    if (stationHandlers == null) {
      stationHandlers = new Hashtable();
      stationHandlers[key] = station.GetHandler(key);
      weathers[station] = stationHandlers;
    } else {

      stationHandlers[key] = (WeatherHandler)
      stationHandlers[key] + station.GetHandler(key);
    }
  }

  public static void Happen(string key, IStation station) {

    Hashtable stationHandlers = (Hashtable) weathers[station];
    if (stationHandlers == null) {
      stationHandlers = new Hashtable();
      stationHandlers[key] = station.GetHandler(key);
      weathers[station] = stationHandlers;
    }
    WeatherHandler wh = (WeatherHandler) stationHandlers[key];
    wh(new Weather(), EventArgs.Empty);
  }

  public static void Happen(string key) {
    ICollection keys = weathers.Keys;
    foreach (object o in keys) {
      Hashtable handlers = (Hashtable) weathers[o];
      WeatherHandler wh = (WeatherHandler) handlers[key];
      wh(new Weather(), EventArgs.Empty);
    }
    if (keys.Count == 0)
      throw new ArgumentException("No station is interested in
                                                   {0} ", key);
  }

}

To maintain the many-to-many relationship between the weather type and the weather station, the Weather class maintains a static Hashtable of Hashtables. The primary Hashtable is keyed off the station object. The secondary (or inner) Hashtable is keyed off the weather type, where the value is the handler delegate. This data structure allows us to query for the following:

  • All handlers of a given weather type regardless of the station

  • The handler of a specific station and a specific weather

  • All handlers of a specific station and a specific weather

Handlers of a particular station and weather type are registered using the AddHandlers method. This method takes care of storing the delegates to be called based on the station and the weather type. The Happen method is overloaded. The two-argument version notifies a specific station of a specific event. The single-argument version notifies all handlers of all stations for a given weather.

Listing 18.12 implements the listener classes. The two weather stations implement an IStation interface and indicate which delegate is to be used for which weather type. In the Main method we then register the stations (listeners) with the Weather. Depending on the Happen method called, the weather stations are notified.

Listing 18.12. Entry Point and Driver for Listing 18.11 (C#)
using System;

public class BasicWeatherStation : IStation {

  public void BasicTornadoHandler(object sender, EventArgs e) {
    Console.WriteLine("Station has issued a tornado watch");
  }

  public void BasicHurricaneHandler(object sender, EventArgs e) {
    Console.WriteLine("Station has issued a hurricane warning");
  }

  public WeatherHandler GetHandler(string key) {
    if ("Tornado".Equals(key)) {
      return new WeatherHandler(BasicTornadoHandler);
    } else if ("Hurricane".Equals(key)) {
      return new WeatherHandler(BasicHurricaneHandler);
    }
    throw new ArgumentException("Can handle only
                  Tornadoes and Hurricanes at this time");
  }

  public static void Main(string[] args) {
    BasicWeatherStation station1 = new BasicWeatherStation();
    AdvancedWeatherStation station2 = new AdvancedWeather
                                                   Station();

    Weather.AddHandlers("Tornado", station1);
    Weather.AddHandlers("Tornado", station2);
    Weather.AddHandlers("Hurricane", station1);
    Weather.AddHandlers("Hurricane", station2);

    //Response of all stations interested in the tornado
    Weather.Happen("Tornado");

    //Station2's response to the hurricane
    Weather.Happen("Hurricane", station2);
  }
}

public class AdvancedWeatherStation : IStation {

  public void AdvancedTornadoHandler(object sender,
                                       EventArgs e) {
    Console.WriteLine("Advanced Station
                   has issued a tornado watch");
  }

  public void AdvancedHurricaneHandler(object sender, EventArgs
                                                           e) {
    Console.WriteLine("Advanced Station
                  has issued a hurricane warning");
  }

  public WeatherHandler GetHandler(string key) {

    if ("Tornado".Equals(key)) {
      return new WeatherHandler(AdvancedTornadoHandler);
    } else if ("Hurricane".Equals(key)) {
      return new WeatherHandler(AdvancedHurricaneHandler);
    }
    throw new ArgumentException("Can handle only
                  Tornadoes and Hurricanes at this time");
  }
}
public interface IStation {
  WeatherHandler GetHandler(string key);
}

The output of the forecast in Listing 18.12 is as follows:

Advanced Station has issued a tornado watch
Station has issued a tornado watch
Advanced Station has issued a hurricane warning

From the prior discussions it should be clear that the event programming paradigm in C# is similar to JavaBeans programming. Essentially, listeners interested in the property change register themselves with the property. When the property is changed, each of the registered listeners is notified of the change. In Java, the event receiver (listener), event generator (source), and the event itself are all classes. In C#, the event itself is modeled as a delegate callback.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.188.187.165