tsc_Event

Introduction

The tsc_Event class, along with its partners tsc_EventDelegate and tsc_EventArgs, provide a multi-cast event system that has some parallels with the .NET event system that uses the event and delegate objects.  tsc_Event can be thought of as the producer of an event, since a class which wishes to signal an event defines an instance of it and uses its methods to signal events to anything that chooses to listen (the consumers).

tsc_EventDelegate is a C++ macro which defines an event delegate class and an instance of it, within some parent class.  tsc_EventDelegate is really an SC Monitor (it is subclassed from tsc_IEventMonitor) and its purpose is to call the event handler function in the parent class.  It can be thought of as the consumer of an event.

For simplicity, all event handlers have the same function signature:

void OnEventName (void* sender, tsc_EventArgs* eventArgs);

If information needs to be passed between the event producer (tsc_Event) and the event consumer (tsc_EventDelegate) then it must happen through the two arguments, the sender and the eventArgs.  If the eventArgs is to be used, then the tsc_EventArgs class should be subclassed and custom members defined in the subclass.  If eventArgs is not required, NULL may be passed.  The event's eventArgs will not be automatically deleted, but for BeginInvoke calls there is an overridable function tsc_EventArgs::OnInvokeComplete() within which the event args instance can delete itself.

The sender parameter normally points to the object that is raising the event; the event producer would pass this as the argument.  However, this is simply a convention, and anything may be passed in the sender parameter, including NULL.

Public methods

tsc_Event();
Constructs an event producer.

void Add (tsc_IEventMonitor& monitor);
Adds an event delegate which is to be notified of the event. The tsc_EventDelegate macro should be used to generate an event consumer class, which may then be added to a tsc_Event.  As soon as Add (or +=) has been executed, the event handler function will start to receive all events which are raised on the tsc_Event object.

void operator+= (tsc_IEventMonitor& monitor);
Alias for Add.

void Remove (tsc_IEventMonitor& monitor);
Removes a previously added event delegate and stops events from calling the event handling method. Note that it is unlikely but possible, that an event that was queued to the delegate before Remove was called, may still invoke the handler function. Removing a delegate which was not added, or has already been removed, has no effect.

void operator-= (tsc_IEventMonitor& monitor);
Alias for Remove.

void Invoke (void* sender, class tsc_EventArgs* eventArgs);
Calls all subscribed delegates (ie, the event handler functions) and waits until all of them have processed the event, before returning.  This function should only be called from the class that owns (contains) the tsc_Event object, and never by a method in any other class.

The event handlers are called one at a time in the order they were added, and each handler is called only after the previous has completed.  Handlers which are running on the same thread as the caller of Invoke will be called directly; handlers on other threads will have the event queued to them, and they will only be called when that thread enters a wait or calls tsc_SurveyCore::DoEvents().

If no delegates are currently subscribed to the event, Invoke does nothing.

void BeginInvoke (void* sender, class tsc_EventArgs* eventArgs);
Queues an event to all subscribed delegates and returns immediately.  This function should only be called from the class that owns (contains) the tsc_Event object, and never by a method in any other class.  SCAPI has no equivalent of the .NET EndInvoke method.

The events are queued in the order the delegates were added, but because of the way in which threads are scheduled to run, the order of handler execution will be unpredictable, except that for any given consumer, events will arrive in the same order they were produced by BeginInvoke calls.

If no handlers are currently subscribed to the event, BeginInvoke does nothing.

bool Subscribed ();
Returns true if one or more event handlers are currently subscribed to this event.

virtual ~tsc_Event();
The destructor will remove all subscribed handlers.  If any unhandled events are pending when the object is destroyed, it is unpredictable whether the handlers will be called or not.

Sample code

This is a working example of a string stack class which generates events when the stack changes.  Another class uses the string stack and listens to its events.  Note that everything runs on a single thread in this example, and for this reason Invoke was used rather than BeginInvoke.


// Define a String Stack class, with Push and Pop methods and

// events which are raised on either of these occurrences.

class StringStack

{

    tsc_StringList  strings;

public:

    tsc_Event Pushed;     // Define an event to be raised when a string is Pushed.

    tsc_Event Popped;     // and another for Pop.


    void Push (tsc_String item)    // Push a string to the top of the stack.

    {

        strings.Append (item);

        Pushed.Invoke(this, NULL);  // Raise the "Pushed" event.

    }


    tsc_String Pop ()     // Pops the top string off the stack.

    {

        int last = strings.Count() - 1;

        tsc_String item = strings[last];

        strings.Remove (last);

        Popped.Invoke(this, NULL);       // Raise the "Popped" event.

        return item;

    }

};


// A class to use the String Stack and its events. 

class HelloGoodbye 

{

    tsc_EventDelegate (Pushed, HelloGoodbye);    // Define a delegate (callback) for the Pushed event.

    tsc_EventDelegate (Popped, HelloGoodbye);    // and another for the Pop.

    StringStack strings;                         // An instance of the String Stack.

    int         count;                           // An event handler to be called when a Push occurs.


    void OnPushed (void* sender, tsc_EventArgs* eargs)

    {

        count++;

    }


    // An event handler to be called when a Pop occurs.

    void OnPopped (void* sender, tsc_EventArgs* eargs)

    {

        count--;

    }


public:

    HelloGoodbye() : count(0)

    {

        Pushed.Bind(this);    // Bind the delegates to this class.

        Popped.Bind(this);

    }


    void Run() 

    {

        strings.Pushed += Pushed;     // Subscribe to the events.

        strings.Popped += Popped;

        strings.Push ("Hello");

        strings.Push ("Good morning");

        strings.Push ("Good evening");

        strings.Push ("Goodbye");

        tsc_String top = strings.Pop  ();

        tsc_MessageBox::Show (

              "StringListEvents",

              tsc_String::Format("Number of strings = %d, Top was %s", count, top));

    }

};