Event Handling
Visualize uses its event handling system as an asynchronous way to respond to various types of events raised within the application. Key presses, mouse motion, errors, and warnings are all examples of asynchronous events. This section will describe how you can intercept events and process them so that your application responds appropriately.
Event Handling System Anatomy
The event handler system consists of four main parts:
HPS::Event
objects, or a subclass thereofA number of channels, which direct events to the event handlers that are able to process them.
The
HPS::EventDispatcher
, which injects the event into the event queue based on the event’s assigned channel.The
HPS::EventHandler
object which responds to the event. Event handlers listen for events on a particular channel.
The sequence below shows how these parts work together to handle events:
1 - Something happens in the execution environment that requires a response from your application. This could be anything from a mouse click to a run-time error. You also have the ability to generate custom events at any time you require. For this example, we'll imagine Visualize has detected a mouse click in the main window. | |
2 - In order to process the mouse click, Visualize builds an HPS::Event object which includes information about what happened. At construction time, the event is assigned a channel which will later be used to get the event to a handler that is able to process it. | |
3 - Event is dispatched by the HPS::EventDispatcher. The dispatcher injects the event into the channel it was assigned to from the previous step. Visualize maintains a few default channels - examples include (but are not limited to) the error channel, warning channel, and keyboard event channel. | |
4 - In our example, the mouse event channel (highlighted in green) is chosen. A single event instance can only go into one channel, however, there are no resistrictions on what type of event can go into which channel. Be aware that placing an event on a channel that is not equipped to handle it will likely result in your event being ignored. | |
5 - Each line of small dots represents a stack of event handlers. Event handlers are associated with one or more channels. In this case, the orange dots represent a group of handlers that are able to process mouse click events. As the event is placed in the channel, the handler at the top of the stack gets to process the event first. The handler can consume the event or pass it on to the next handler in the stack. Our imaginary example ends with the first handler interpreting the mouse click as a selection event and highlighting a piece of geometry. |
The Event Handler Object
The first step to handling events is to create your own event handler by subclassing HPS::EventHandler
. The Handle
method is called when an event is dispatched, so any processing or recovery logic should be placed in that function, as shown below:
class MyEventHandler: public EventHandler {
public:
~MyEventHandler() { Shutdown(); }
EventHandler::HandleResult Handle(Event const* in_event)
{
// do any processing or recovery logic here
return HandleResult::Handled; // or return NotHandled, as the case may be
}
};
class MyEventHandler : HPS.EventHandler
{
public override HandleResult Handle(HPS.Event in_event)
{
// do any processing or recovery logic here
return HandleResult.Handled; // or return NotHandled, as the case may be
}
};
Note that the example above returns Handled
. You may also return NotHandled
if for some reason this handler does not consume the event. In this case, the event would be passed to the next handler that is subscribed to handle events on that channel. See the next subsection for a discussion on the event stack.
IMPORTANT: Your event handler must call HPS::EventHandler::Shutdown
in its destructor. Failure to do so may result in undefined behavior, including crashes or deadlocks.
The Event Dispatcher
The next step to handling events is to instantiate your handler and subscribe it to an HPS::EventDispatcher
. There are several event dispatchers - the Database and application window each have their own, as well as any dispatchers for custom events that you create.
All handlers live in a stack with others of the same type. By defaut, if one handler can’t process the event, (as indicated by returning NotHandled
from EventHandler::Handle()
), the handler below it will receive the event. There can be any number of handlers in a stack. The code below demonstrates how to add an event handler for errors.
MyEventHandler* myHandler = new MyEventHandler();
HPS::EventDispatcher dispatcher = HPS::Database::GetEventDispatcher();
// adds handler to dispatcher's subscription stack
dispatcher.Subscribe(*myHandler, HPS::Object::ClassID<ErrorEvent>());
// removes this handler from the subscription stack
dispatcher.UnSubscribe(*myHandler, HPS::Object::ClassID<ErrorEvent>());
MyEventHandler myHandler = new MyEventHandler();
HPS.EventDispatcher dispatcher = HPS.Database.GetEventDispatcher();
// adds handler to dispatcher's subscription stack
dispatcher.Subscribe(myHandler, HPS.Object.ClassID<HPS.ErrorEvent>());
// removes this handler from the subscription stack
dispatcher.UnSubscribe(myHandler, HPS.Object.ClassID<HPS.ErrorEvent>());
IMPORTANT: When your handler is destroyed, it will be automatically unsubscribed. Therefore, in order for the handler to work, it must not go out of scope. You are responsible for maintaining the pointer.
Note the second parameter to Subscribe
in the code snippet. This parameter indicates which channel the handler will be subscribing to. You can associate any type of event with any channel.
As stated above, the default behavior of the event subsystem will cause an event to be consumed when the HPS::EventHandler::Handle
function returns Handled
. However, this logic can be configured in different ways depending on your needs. For example, a channel can be set to never consume an event, or it could be set so that only the first handler in the stack processes the event, regardless of the result of HPS::EventHandler::Handle
.
Event objects
As mentioned previously, the error event is just one type of event. Warnings, errors, timer ticks, and key presses are all examples of Visualize events, and are typically injected on their own channel. Your handler can subscribe to any channel, or all of them. The HPS::Event
class is abstract. In order to raise an event, you must subclass it…
class MyEvent: public Event {
public:
MyEvent(): Event() {}
MyEvent(intptr_t type): Event(type) {}
virtual char const* ClassName() const override { return "MyEvent"; }
// called internally to copy an event. in the case your event contains pointers or dynamically
// allocated data, this function needs to be set up to handle the cloning
virtual Event* Clone() const { return new MyEvent(GetChannel()); }
};
class MyEvent : HPS.Event
{
public MyEvent() : base() { }
public MyEvent(IntPtr type) : base(type) { }
// called internally to copy an event. in the case your event contains pointers or dynamically
// allocated data, this function needs to be set up to handle the cloning
public override Event Clone() { return new MyEvent(GetChannel()); }
};
…before injecting your new event into the system.
MyEvent myEvent(HPS::Object::ClassID<MyEvent>());
dispatcher.InjectEvent(myEvent); // this event will be sent to the handler
MyEvent myEvent = new MyEvent(HPS.Object.ClassID<MyEvent>());
dispatcher.InjectEvent(myEvent); // this event will be sent to the handler
Notifiers
You can use a notifier to learn that an event has been processed. A notifier is an object that is associated with an event injection and can be used to track its progress. Querying the notifier will tell you the status of the event:
HPS::EventDispatcher dispatcher = HPS::Database::GetEventDispatcher();
HPS::EventNotifier eventNotifier = dispatcher.InjectEventWithNotifier(myEvent);
if (eventNotifier.Status() == HPS::Event::Status::InProgress) {
// the event has not yet been processed
}
if (eventNotifier.Status() == HPS::Event::Status::Completed) {
// the event has been processed
}
HPS.EventDispatcher dispatcher = HPS.Database.GetEventDispatcher();
HPS.EventNotifier eventNotifier = dispatcher.InjectEventWithNotifier(myEvent);
if (eventNotifier.Status() == HPS.Event.Status.InProgress)
{
// the event has not yet been processed
}
if (eventNotifier.Status() == HPS.Event.Status.Completed)
{
// the event has been processed
}
It is also possible to simply wait until an event is handled using an HPS::EventNotifier
. The following code will cause its thread to sleep until the event handling is completed:
eventNotifier.Wait();
eventNotifier.Wait();