Event handling

Overview

Events that are generated within Survey Core can be directed to handler functions within a plugin.  Events that are part of the 'normal' windowing system, such as form load and close events, or control events such as mouse clicks, are handled directly by the form with no further effort by the programmer.  Windowing events are described separately in the relevant classes - see tsc_Form and tsc_AppMainWindow.

This page explains the event monitor system, which is used to pass system (Survey Core) events to the plugin.  For wiring up events that are generated by the plugin, see events and delegates.

For all other events - for example the About to suspend event, the file system full event, or the various instrument events, a separate mechanism is used as described here.  Ironically, the most useful place to receive these events is on a form since most operations are directed via UI.

Please remember that Survey Core has a complete event handling system, and this should be used directly for all events.  Using Windows' API calls that block or wait for long periods (such as Sleep, WaitForMultipleEvents, etc) should be avoided, since they will often cause system lockups, either temporary or permanent.  For information about using Windows API wait functions, please see the general information about threading, and the tsc_Thread class.

Events and waits

There are important facts to remember about performing synchronous operations - that is, where an operation blocks the thread until it is complete, such as tsc_TsModes::ApplyModes().  In contrast, most SCAPI methods work asynchronously - the operation is started and returns immediately, and completion is signalled by an event.

The problem with synchronous (blocking) operations is that they are interruptible - a new 'inner' event occurring while the thread is blocked must be careful not to modify the ongoing synchronous operation.  Furthermore, the synchronous operation cannot complete until the inner event has returned and the synchronous wait is removed from the stack.

For this reason it is recommended that synchronous methods be avoided unless they are being used in a separate non-UI thread.

Also see Pitfalls in the use of synchronous (blocking) methods.

Event grouping

Events are grouped into a number of classes known as monitors.  They are implemented in interfaces as follows:

Using the monitor interfaces

The monitor interfaces are simple.  Each contains a virtual member function for each event that may be handled.  To use the interface, it is subclassed and an implementation is written for any event the programmer wishes to handle.  Those functions that are not overidden default to the base class function which simply ignores the event, so it is not necessary to implement every function.  Avoid writing empty handlers and never call the base class handlers from your own.  This is because many base class handler functions will disable the event to improve performance, under the assumption that no overriding function is present.

Multiple different monitor interfaces may be implemented within the same subclass if a mixture of event handler types is required.  To make this possible, all event handler methods have different names.

Event thread marshalling is automatically performed for all events.  This means that event handler functions are always called on the correct thread, which is the thread that started the monitor. This means that (unlike .NET), Invoke is never required to interact with a form class from an event handler, for instance.

There are two main types of events - global and local.  Global events do not necessarily relate to any particular operation done by the plugin, and they are broadcast to all threads and windows.  Local events are tied to one particular operation and events are sent to the specified monitor only.

Global events

Global event monitor classes have a Start function which is called to start global SC events flowing into your subclass.  Because many monitor interfaces have the same Start method, the call must be prefixed by the name of the interface (as in tsc_IJobMonitor::Start()) to resolve the ambiguity if more than one interface is used.  See the example below.  There is a corresponding Stop() method for every start, although a Stop is performed automatically in the monitor's destructor.  The effect of Stop is immediate, and all queued events will be discarded.

Global events are only sent to a plugin while its application is active.  If the user hasn't started the application or has shut it down, global events are stopped.

Local events

Monitor classes for local events (tsc_ITsModesMonitor, tsc_MeasureFormMonitor, tsc_IGnssModesMonitor, tsc_ITimerMonitor, tsc_IEventMonitor etc) deal with events from a specific operation have a slightly different usage.  The monitor class is still subclassed and event handlers are implemented in the same way, but often instead of calling Start, the subclass is passed to some function which itself starts an operation such as setting the modes on a total station.  The monitor's event handlers will be invoked for events relating to that operation only.

Custom events

Custom events operate in a similar fashion to the .NET event and delegate objects.  The equivalent classes in the API are tsc_Event and tsc_EventDelegate.  The tsc_Event class allows a number of tsc_EventDelegate objects to subscribe to its events.  The class which is generating events calls tsc_Event::BeginInvoke or tsc_Event::Invoke which will result in an event handling method to be executed in the class that subscribed to the event.  This is the extent to which this system parallels .NET event handling; most other .NET functionality is not provided.

Dispatching and timing of events

Because events are automatically invoked on the caller's thread regardless of which thread generated the event, some timing differences may be noticeable. 

All processing in a plugin is done within event handlers (with the single exception of a UITask's Run function which is a thread's main body).  Event handling is recursive; a handler may, for instance, create a form and call ShowDialog which will in turn call event handlers on the form in response to (for example) a button click, and these handlers in turn may display other forms which in turn may generate and handle events.

While the plugin is "away" from event handling and executing within Survey Core, the thread will usually be in a wait state and any event that occurs will be dispatched to all appropriate event handler(s), regardless of how deeply nested they are.  Even when a form has been "buried" under other forms or event-handling classes, all events (UI, timers, global events, local events) will continue to be dispatched to all monitors and forms.

Recursive event processing

While the plugin is processing an event, it is possible for other event handlers to be called, or even the same one to be re-entered.  This can only occur if the first event handler has performed a wait (such as tsc_SurveyCore::DoEvents(), tsc_SurveyCore::Sleep(), or any other long-running method that causes a wait, such as tsc_TsModes::Measure(), or displayed a form using tsc_Form::ShowDialog().  Where this type of recursion is a possibility, event handlers may be protected against re-entry by a simple boolean gate.  Read more about this important subject on the Pitfalls of synchronous methods page.

CPU-Intensive processing

If a plugin performs some lengthy calculations without any type of wait, events for any monitors on the same thread will not be dispatched to handlers, although they will still be queued. This may cause the device's UI to become unresponsive until the calculations finish.  Once the processing finishes, all queued events will be dispatched.  To keep events flowing during lengthy cpu-intensive processing, the plugin should periodically call tsc_SurveyCore::DoEvents(), or tsc_SurveyCore::Sleep().  To keep the UI responsive, DoEvents should be called every 0.2 to 0.4 seconds if the processing is likely to take more than 1 to 2 seconds.

Another cure for cpu-intensive tasks is to use tsc_Thread or tsc_MonitorThread to run the computation on a separate thread.

"Abandoned monitor" errors

As already mentioned, the methods of a monitor subclass are always invoked on the caller's thread.  If the caller's thread should terminate, it is no longer possible for Survey Core to invoke those methods.  The action taken by SC in this situation depends on the type of event; for simple "fire-and-forget" events, the event is simply not posted.  For events which require the event source to wait or to pass data back to the handler, Survey Core will generate an assert message and terminate.

This problem is often caused by using new to create a monitor subclass in a UiTask and then allowing the UiTask to terminate, without stopping the monitor.  Calling delete or otherwise causing the monitor subclass to be destroyed will automatically call Stop on the monitor.  The following simplistic example illustrates this problem:

class ExampleTask : public tsc_UITask 

public:

    ExampleTask (x_Code runCode) : tsc_UITask (runCode)

    {

    }

    virtual tsc_SurveyState RequiredSurveyState() const

    {

        return tsc_SurveyStateSurveyActive;

    }

    virtual void Run ()

    {

        tsc_MeasureForm mf (tsc_MtTopo);

        tsc_IMeasureFormMonitor* imon = new tsc_IMeasureFormMonitor();

        mf.Start (imon);

        // Return from the Run function.  This will terminate the UI Task's thread.

        // When a message is posted from the Measure form, the application will crash.

    } 

};  

The solution is to run the measure form either directly from the main menu window or from some event handler in a form.

Example implementing both windowing and non-windowing events

The following code displays a form with four event handlers, a windowing event which fires after five seconds, another windowing event handler for when a softkey is clicked, a database transaction complete handler from tsc_IJobMonitor, and a method from tsc_IScAppMonitor to handle a resumption after the controller was suspended.

class MyApiForm : public tsc_Form, tsc_IJobMonitor, tsc_IScAppMonitor

{

    bool   _handling;

public:

    MyApiForm ()

    {

        this->_handling = false;

        tsc_IJobMonitor::Start();

        tsc_IScAppMonitor::Start();

        this->Text ("API sample");

        this->StartTimer (5.0, double_Null);     // A one-off timer.

        this->Controls.Add (new tsc_SoftkeyControl (X_SoftkeyAddCode));

        this->Controls.Add (...);

    }

    virtual void OnTimer (int timerId)

    {

        tsc_MessageBox::Show("Timeout!", "The five seconds is up.");

    }

    virtual tsc_DialogResult OnSoftkeyClick (x_Code sk)

    {

        if (!_handling)

        {

            _handling = true;

            tsc_MessageBox::Show("SK Press", tsc_String(tsc_SurveyCore::Translate(sk)));

            _handling = false;

        }

        return tsc_DialogResult_None;

    }

    virtual void OnGroupEnd()

    { 

        tsc_MessageBox::Show("Event", "A database transaction has ended"); 

    }

    virtual void OnResumedFromSuspend()

    {

        tsc_MessageBox::Show("Resumed", "Welcome back!");

    }

}; 


Simple example monitoring the controller suspend event

As soon as the following SuspendMonitor class is created (perhaps by new'ing it in the initialization function), it will begin monitoring the suspend event, and any action can be performed in the handler.  For the OnAboutToSuspend event, the function must not take very long (a few tens of milliseconds) to process the event, before the device is suspended.

class SuspendMonitor : public tsc_IScAppMonitor

{

    SuspendMonitor()

    {

        tsc_IScAppMonitor::Start();

    }

    void OnAboutToSuspend()

    {

        if (MyOtherClass != NULL)

        {

            MyOtherClass->StopDoingThings();

        }

    }

};