Events and event handlers.. pros and cons

BlitzMax Forums/BlitzMax Programming/Events and event handlers.. pros and cons

Jim Teeuwen(Posted 2009) [#1]
Hey people. Here's just a question to see some of your opinions and see if I can improve on my ways of dealing with events.

I never got into the whole Blitzmax EventHook mechanism myself. It always seemed a bit odd to me. The whole message pump mechanism is not my cup of tea.

I've been using my own eventhandling system for quite a while now and with success, as it allows me to expose events in a class/type pretty much the same way as you see them in C#. Also have multiple hooks for the same event all neatly contained in one place.

What i'm wondering is what one would consider a better solution and in particular: why?

Here's an example:

Type TApp
  '// These are the events I want to expose.
  '// They are simple lists that let me add/remove function pointers
  '// to event handlers whenever i want and however many I want.
  Field Load:TList;
  Field Unload:TList;

  Method New()
    Self.Load = New TList;
    Self.Unload = New TList;
  End Method
  Method Delete()
    Self.Load.Clear();
    Self.Unload.Clear();
  End Method

  '// these are called in order to trigger the events whenever required.
  '// loop through all hooked eventhandlers and execute them.
  Method OnLoad(e:TEventArgs)
    For Local eh:TEventHandler = EachIn Self.Load
      eh.Execute(Self, e);
    Next
  End Method
 Method OnUnload(e:TEventArgs)
    For Local eh:TEventHandler = EachIn Self.Unload
      eh.Execute(Self, e);
    Next
  End Method


  '// Run method does some arbitrary stuff and calls load/unload
  Method Run()
    Print("running app. Performing custom loading code");
    Self.OnLoad(TEventArgs.Create(123, "foo", 32.1));

    '// run main app here..

    Print("stopping app. Performing custom cleanup code.");
    Self.OnUnload(TEventArgs.Create(321, "bar", 12.3));
  End Method
End Type


'// The TEventHandler type
Type TEventHandler
  Field Execute:Int(sender:Object, e:TEventArgs);
  Function Create:TEventHandler(handler:Int(sender:Object, e:TEventArgs))
    Local eh:TEventHandler = New TEventHandler;
    eh.Execute = handler;
    Return eh;
  End Function
End Type


'// The TEventArgs type.
'// In order to send any kind of custom arguments to the event handler functions,
'// you can simply add fields to this class or create custom EventArgs types that deal
'// with different events/situations.
Type TEventArgs
  Field A:Int, B:String, C:Float;
  Function Create:TEventArgs(a:Int, b:String, c:Float)
    Local e:TEventArgs = New TEventArgs;
    e.A = a;
    e.B = b;
    e.C = c;
    Return e;
  End Function
End Type


To see it working, this is all that's needed:

SuperStrict

Framework brl.basic
Import brl.linkedlist

Local app:TApp = New TApp;

'// hook the events
app.Load.AddLast(TEventHandler.Create(HandleLoad));
app.Unload.AddLast(TEventHandler.Create(HandleUnload));

'// Run the app
app.Run();


'// These contain the code that actually handles the triggered events.
Function HandleLoad(sender:Object, e:TEventArgs)
  Print(e.A + ", " + e.B + ", " + e.C);
End Function
Function HandleUnload(sender:Object, e:TEventArgs)
  Print(e.A + ", " + e.B + ", " + e.C);
End Function



Basically, all this does is maintain a list of function pointers which get executed whenever an event is triggered and passes them some arbitrary set of arguments as defined in the TEventArgs type.

One of the drawbacks of this system is that eventhandlers can only be Static functions and not methods because they are function pointers. This is inconvenient at times, but hasn't posed a real problem for me yet. In the example, the current instance of TApp is passed as the sender argument, so any operations on the instance that triggered this event can still be performed normally.

Another drawback is that an event requires quite a bit of code to create, but once it's there it's very easy to use. This makes it particularly useful in situations where you have reusable classes with lots of events.
For short pieces of code however, this may not be the most ideal solution.


Any thoughts or improvements on this?