Tipswip   >   Windows   >   Events

Events

When I first started learning Omnis Studio, the 'Events and Messages' chapter in the manual was completely foreign to me. I skimmed through it comprehending very little. Eventually I wanted to add special behavior to some entry fields and was faced with attempting to understand this events handling business in Omnis Studio.

The Studio manual states that all user actions in Omnis generate an event. If the user clicks on a field in a window an event is generated.

My initial perception was that the entry field received the click and then reported it to Omnis Studio where it would be added to the event queue. I later discovered this was backwards.

THE BRICK AND THE HAND ILLUSTRATION

This following isnıt a perfect illustration, but work with me on this one anyway. Think of the entry field as your hand on top of a desk. Think of the mouse-click as being a brick falling from the sky, headed directly for your hand. Your eyes and nerves are the Omnis Studio event messaging system. Your brain is the $event method attached to the entry field (your hand).

Your eyes (the Omnis event messenger) see the falling brick and send a message to your brain (the $event method). Your brain makes a decision not to accept this event by instructing your hand to move out of the way. (Quit event handler, discard event)

Now on the other hand, if $100 of cash was falling from the sky, your brain (the $event method) may decide to leave your hand where it is and accept the event.

THE KEY

The key to creating an events-based application that properly functions is in the methods you write in the various objects in your library to intercept or handle those events.

So when you find event handling in Omnis Studio is misbehaving, the problem is likely in the code you've written. My experience has been it is usually my own misunderstanding of how event messaging is working in Omnis Studio.

Action Tab Event Properties

If you select a window object such as a pushbutton, then > F6 Property Manager > Action tab,
you will see the following properties for the window object

$keyevents (Keyboard events)
evKey events

$mousevents (Mouse Events)
evMouseDouble,evMouseDown,evMouseEnter,evMouseLeave,evMouseUp

$rmouseevents (Right Mouse Events)
evRMouseDouble,evRMouseDown,evRMouseUp

$statusevents (Status Events)
evDisabled,evEnabled,evHidden,evShown

Theses properties can be set to kTrue or kFalse.

If you > F2 Libraries, select a library > F6 Property Manager > Action tab, you will see the same properties for the library.


If you set $keyevents to kTrue for the library, the library setting takes precedence over the object settings. You can try to set $keyevents to kFalse for the object, but $keyevents event messages will still be sent to the object. This save you from having to think about setting it to kTrue for each object.

If you set $keyevents to kFalse for the library, you can set $keyevents to kTrue on an object by object basis.

To reduce the volume of event messaging Omnis gives you some pretty find tune control over event messaging.

I tend to set $keyevents and $mouseevents to kTrue for my libraries. So far I havenıt needed to trap right mouse or status events. Performance is not an issue.

Container Fields

An object can be nested inside of a container field such as a tab pane object.

For discussion purposes we'll use a pushbutton which receives and passes an evClick event.

The evClick event is passed from the $event method of the pushbutton, to the $control method of the tab pane object. (Assuming a $control method is present).

If allowed to pass, the evClick event then passes to the $control method of the window class.

If allowed to pass, the evClick event then passes to the $control method of the task instance.

NESTED CONTAINER FIELDS

What happens if we put the pushbutton inside a group box, and then place the group box inside the
tab pane?

Omnis draws the line on one level of nested containers. The evClick event passes to the Group Box,
but then skips any further level(s) of nested containers going directly to the window class
$control method, skipping any

Context Menu Demo

Omnis event handling makes creating context menus on the fly a snap.

This demo shows you how to add items to an empty context menu on the fly.

Items are added to the context menu in the $event method of the field and $control method of the window by calling the $addMenuItem method of the context menu.

The $control method of the window informs the context menu instance who to notify if user selects a context menu item by calling $AttachEventsObserver. For simplicity, this demo doesn't use the observer design pattern. The context menu simply store the observer window class instance and method to call in instance variables.

Go ahead and run the demo. Noisy OK messages have been scattered through the code so you can follow the sequence of events. You can hit Break (Win), or Cmd+Opt+Delete (Mac) at any OK message if you want to break in and look at the code.

Debugging Events

Debugging events can be a real pain. There is no way of looking at the event queue, and if you put in a breakpoint you mess up the event queue.

Putting a breakpoint after On evBefore will keep looping back each time you click the go button in the method editor because another evBefore event message will be generated each time the window comes to the front.

There are 2 techniques that I know of for debugging event handling problems:

SILENT DEBUGGING

Instead of OK messages you can send information to the trace log.

Send to trace log {[sys(85)] (Icon) {Event: [pEventCode]////Next event: [pNextCode]////More info.}

Then open the trace log and see what methods were called in what order.

NOISY DEBUGGING

OK messages do not affect the event queue, so you can place them in your various $event, $control, and other methods to figure out what methods are being called and in what order.

OK message [sys(85)] (Icon) {Event: [pEventCode]////Next event: [pNextCode]////More info.}

I tend to use OK messages for debugging events. The handy thing with OK messages is you can break into your code on an OK message by pressing Break (Win) or Cmd+Opt+Delete (Mac).

Drag and Drop Demo

Omnis event handling makes creating it easy to write drag and drop code.

This demo shows you how to copy items from one list to another, reorder them in a list, or delete them from a list.

All the code is in the $event methods of the objects.

Event Handling

The event message is sent to the $event method of the object where the event occurred. (Report events are sent to the $print method.)

The $event method can choose to handle, pass, or discard the event.

An event will automatically pass to the $control method of an objectıs container under certain conditions.

Note: An object could be contained directly in a window class or nested inside a container object such as a group box. The objectıs container could be a window class instance or a group box depending where it is located.

An event can automatically pass up to the $control method of the task instance under certain conditions.

For discussion purposes weıll consider an evClick event on a pushbutton.

To handle the evClick event in the $event method of the pushbutton object an On evClick is required in the $event method.

The presence of the On evClick line of code will cause the event to be handled right then and there and not go any further. Even if there is no code after the On evClick.

The default entry fields in the component store have an $event method with On evBefore and On evAfter already in the code. This is likely done so that evBefore and evAfter events don't get passed up to the $control method.

PASSING EVENTS

There are several ways you can cause an event to pass to the $control method of its container.

1. Delete the $event method from the object. When Omnis sends the event message to the object, if
an $event method does not exist, the message is automatically sent to the $control method of the objectıs container.

2. Donıt include an On evEventName to handle the event. If the event isnıt handled by the $event method Omnis automatically passes the event to the $control method of the objectıs container.

3. Use If evClick instead of On evClick. If evClick will still trap the event, but it won't prevent Omnis from automatically passing the event to the next $control.

4. Handle the event but then pass it to the $control method using ŒQuit event handler (Pass to next handler)ı. This doesnıt quit all event handling, it just quit event handling of the current event in that event handling method.

Note: The event message is sent to only ONE $event method Š and that is the object where the event occurred. After that it will ONLY be passed to $control methods. A window class can have a $event method and a $control method. It is easy to get confused and try to trap a window event like evOkay in the windowıs $control method. (I know from experience.)

WHY BOTHER ALLOWING EVENTS TO PASS TO $CONTROL?

You can save a lot of code by allowing events to pass to a $control method.

Say your have a toolbar with 10 buttons on it. You could have a $event method for each toolbar button, or you could allow the evClick events to pass to the $control method of the toolbar.

In the $control method, $cobj().$name tells you the name of the toolbar button which the use just clicked. If you are passing the toolbar button clicks to public methods by the same name in the top window, you use the following line of code to call those methods.

Event Messages and Parameters

User actions create an event. Events are reported in Omnis as event messages. An event message is sent to the $event method of the object where the event occurred. In many cases, even though the event has occurred the $event method of the object can cancel the event by discarding it.

(That would be like allowing the brick to hit your hand and then being able to move time back a few seconds and stopping the brick before it hits your hand.)

EVENT PARAMETERS

The event message has one or more event parameters. The first parameter is always pEventCode. Additional parameters tell you more about the event. (pNextCode, pDragField, pColumnNumber,Š)

The cool thing about event parameters is that they have task instance scope and you never have to (nor should you!) create them as parameter variables in your methods.

To prove the point, type ŒCalculate #1 as pColumnNumberı in a new method. Notice that Omnis allows you to enter pColumnNumber even though you didnıt create it as a parameter in your method.

When an event is occurring any method can use the event parameters without declaring them and without having them passed to them. Remember, event parameters have current task scope.

You can find out what events can be handled for an object by double-clicking an object and going to its $event method and typing On in method editor. Omnis lists for you the possibe events that the selected object can handle.

You can find out what event parameters are available for any event by typing On evEventName (e.g. evClick) in your code, and with the On evClick line selected in the method editor press F9 > Variables tab > click Event Parameters in the left list. The possible event parameters will be shown in the right list. I say Œpossibleı parameters, because not all event parameters listed are applicable to all objects. For the On evClick event the event parameters, pLineNumber and pRow only apply to list objects.

Note: You can drag and drop event parameter from the F9 Catalog into your code.

Note: If you have Help Tips turned on in your F9 Catalog you can hover over an event parameter on a tooltip will appear with more information about the event parameter. (Right-click anywhere the F9 Catalog to check the Help Tips context menu item. Then right-click > Save Window Setup)

Event Queue

Omnis uses an event queue. There can be several events in the queue.

When you are in an entry field and you press the tab key there are several events that go into the queue.

evAfter will be sent to the field $event method
evTab will be sent to the field $event method
evBefore will be sent to the next field $event method

If you are in an entry field and click the close box on a window you will generate:

evAfter will be sent to the field $event method
evCloseBox will be sent to the window $event method
evClose will be sent to the window $event method
You can append events to the event queue using the ŒQueueı set of 4GL commands.

Queue set current field {FIELDNAME}

... will cause Omnis to set the FIELDNAME as the current field after the other events in the queue have been processed.

Setting the current field will then trigger an evBefore event message to be sent to the $event method of FIELDNAME.

Omnis doesnıt let you tinker with events already in the event queue. I suspect Omnis is receiving the event messages from the operating system and therefore canıt be trying to take control of stuff going on inside the operating system.

Events Summary

The key to writing an events-based application that properly functions is in the methods you write in the various objects in your library to intercept or handle those events. These methods are called event handling methods.

Event Messages are sent to the $event method of the objects.

The event messages contain event parameters. (pEventCode is the 1st parameter)

Use On ev... or pEventCode in your event handling methods to detect specific events.

Use event parameters to test the contents of the event messages.

Event parameters have current task ($ctask) scope. You do not need to (nor should you) create event parameters in your methods.

Event messages can be passed to the $control method of an objectıs container. If there are several layers of nested window object containers, the event message can pass to the first container, but then passes directly to the window $control method.

Event messages can be passed from the window $event or $control method to the $ctask.$control method.

Warning from the Studio Manual: ³The Omnis event processing mechanism gives you absolute control over what is going on in your application, but it also means you need to design your event handling methods with care. It is important not to pass an event to higher levels unnecessarily and to keep control methods short, to limit the time spent processing each event.²
Note: It is surprising how quickly events are processed. With todayıs faster hardware I have not noticed a performance hit.

Process Event and Continue

This 4GL command had me baffled for quite some time. I tried using it here and there and didnıt find any particular use for it.

The command has 2 possible forms:

Process event and continue
Process event and continue (Discard event)

PROCESS EVENT AND CONTINUE ­ EXAMPLE

One situation where you would use this command is if you are allowing a user to drag an object in a window to reposition it. When they drop the object, you want to test if the new position is within the bounds of the window before deciding whether or not to accept the new position.

The windowıs $event method will receive an evDrop event message, but if you test the location of pDragField you will find it is the old position, not the new position because the evDrop event has not been processed yet. You have the opportunity to reject the event, leaving the object where it was.

To find out the new position you need to:

Process event and continue

then test the new position and decide if that is acceptable.

Of course now youıve got the problem that the event has been processed so no longer have the option of rejecting the event.

To work around that problem, in the window $event method you would:

Calculate Top as pDragField.$top
Calculate Left as pDragField.$left
Processs event and continue

Now if you want to reject the evDrop:
Do pDragField.$top.$assign(Top)
Do pDragField.$left.$assign(Left)

PROCESS EVENT AND CONTINUE (DISCARD EVENT) ­ EXAMPLE

One example of processing an event and discarding it could be an evOK event message that you want to toss before opening a modal prompt window followed by Enter data. (Example from the Studio manual) Allowing the evOK event will cause Enter data to proceed as soon as the window opens. Quit event handler (Discard event) wonıt allow you to have event handling code after that line, so Process event and continue (Discard event) is the solution.

On evOK
Process event and continue (Discard event)
Open window instance wPrompt
Enter data

Quit Event Handler

If you want to discard or pass an event you can use the Quit event handler command to terminate an on construct. (Studio Manual)

The Quit event handler command has several formats:

1. Quit event handler

This stops your event handling code in the current method. The default action is processed immediately after this command. (I havenıt found a use for this format.)

2. Quit event handler (Discard event)

This stops your event handling code in the current method and discards the event. The event dies right then an there.

Note: If you are validating entry field data On evAfter and using Quit event handler (Discard event) on
incorrect data, you might want to test the pNextCode event parameter to see if the user is trying to close the window.

On evAfter
If pNextCode < > evCloseBox&pNextCode < >evStandardMenu
If $cobj.$contents=''
OK message {You must fill in a value.}
Quit event handler (Discard event)
End If

3. Quit event handler (Pass to next handler)

This stops your event handling code in the current method and passes the event to the next $control method in the event handling chain. The next $control method can decide to handle, pass, or discard the event.

I use this format if I want to do some processing of the event in the field and then allow the $control method to do additional processing of the event. An example might be adding menu items on the fly to a context menu. There might be some specific field menu items I want to add to the context menu, plus there might be some general window specific items I want to add to the context menu.

$event (field method)
On evOpenContextMenu pContextMenu.$addMenuItem(ŒField Specific Item 1ı)
Quit event handler (Pass event)

$control (window method)
On evOpenContextMenu
pContextMenu.$addMenuItem(ŒWindow Specific Item 1ı)
pContextMenu.$addMenuItem(ŒWindow Specific Item 2ı)
Quit event handler (Pass event)

The context menu has 3 items, 1 from the field, and 2 from the window. $addMenuItem is a custom method you add to the menu class. See Context Menus on-the-fly for more information.

4. Quit event handler (Discard event,Pass to next handler)

I donıt believe Omnis intended to make this combination possible. Correct me if Iım wrong but if the event is discarded there is nothing to pass to the next handler. Maybe there should be 3 radio buttons for Quit event handler? (None, Discard, Pass) Not a big issue, as long as we understand what is going on, weıll know not to try this combination.

SubWindow Fields

A subwindow field is a container field, but with a different twist.

A subwindow field contains a window instance within a parent window instance.

Lets say the subwindow window instance contains a pushbutton object. If the user clicks
the pushbutton, it receives an evClick event message, and if it allow the message to pass, the event message passes to the $control method of the subwindow window instance.

One might assume that if the event is allowed to pass again the next level it would pass to the $control method of the parent window, or the $control method of the task instance.


Wrong, and wrong again. The evClick event doesn't go anywhere further than the $control method of the subwindow window instance's $control method! This fact has caused a lot of head scratching on the part of the author.

I believe there was considerable OO logic that went into making this decision in the Omnis engineering department. The subwindow window class instance is not a full blooded window instance. You won't find it listed in the $iwindows group in the F4 Notation Inspector. So it doesn't have the 'right' to pass events to the current task $control method.

Following proper OO guidelines an object (the subwindow window instance) should not have to know anything about things outside of itself, therefore without someone explicitly informing the subwindow where to pass it's events, it can't make that decision on its own.

PASSING SUBWINDOW EVENTS

You could explicitly pass events from the subwindow window instance $control to the parent window $control event using:

Do $cwind.$control()

But be mindful, that you might be instantiating the same window class in another subwindow, or as a window instance on its own, and Do $cwind.$control() might not be ideal in every situation.

Note: As soon as you take over control by explicitly passing events, Omnis washes its hands and says "Fine, you're in charge now. I quit". This means that events you pass to $cwind.$control will NOT be passed by Omnis to $ctask.$control.

USING AN EVENTS OBSERVER

One interesting solution to passing events is to use the observer design pattern. With the observer design pattern a 'subjects' object is instantiated by the subwindow window class instance. Object(s) which want to receive event messages subscribe to the 'subjects' object. Whenever an event occurs in the subwindow window instance, the event is passed to the 'subjects' object which then notifies all the subscriber(s).

For more information on the Observer Design Pattern visit the free Downloads page at www.vencor.ca