Tips_gui   >   Events   >   Events (All Contents)

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 Omnis 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 Studio events messenger) see the falling brick and sends 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 dollars 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.

Here is another quote from the Omnis Studio manual: 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 think the 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.

Event Messages and Parameters

User actions create an event. Events are reported in Omnis Studio 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.)

Each 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, enter Calculate #1 as pColumnNumber in a new method. Notice that Omnis Studio 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 task instance 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 Studio 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. On evClick) in your code, and with the On evClick line selected in the method editor press F9 Properties Manager > 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.

Tip

You can drag and drop event parameters from the F9 Catalog into your code.

Tip

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 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 Studio 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 Studio 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 Studio 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 quits event handling of the current event in that event handling method.

Note: The event message is sent to only one $event method É 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.)

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 a dispatcher method of the top window, you use the following line of code to call the dispatcher method from the $control method of the toobar.

On evClick

; Test to make sure the $doCmnd method exists.
If $topwind.$doCmnd.$cando
   
   ; Pass the name of the button pressed.
   Do $topwind.$doCmnd($cobj().$name)
   
End If

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 if a $control method is present.

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

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

What happens if we put the pushbutton inside a group box, and then place the group box inside the tab pane? Omnis Studio draws the line on one level of nested containers. The evClick event passes to the group box, but then skips any further levels of nested containers going directly to the $control method of the window class instance.

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 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 Studio 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 Studio 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 Studio to $ctask.$control.

One interesting solution to passing subwindow 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 download page at studiotips.net.

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:

  1. 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).
  2. 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.

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.

  1. evAfter will be sent to the field $event method.
  2. evTab will be sent to the field $event method.
  3. 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:

  1. evAfter will be sent to the field $event method
  2. evCloseBox will be sent to the window $event method
  3. evClose will be sent to the window $event method

You can append events to the event queue using the Queue... set of Omnis 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 object.

Omnis Studio doesnÕt let you tinker with events already in the event queue. I suspect Omnis Studio 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.

Quit Event Handler

If you want to discard or pass an event you can use the Quit event handler command to terminate an event.

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.
  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 and 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
    Do pContextMenu.$addMenuLine('Field Specific Item 1')
    Quit event handler (Pass to next handler)

    ; $control (window method)
    On evOpenContextMenu
    Do pContextMenu.$addMenuLine('Window Specific Item 1')
    Do pContextMenu.$addMenuLine('Window Specific Item 2')
    Quit event handler (Pass to next handler)



    The context menu has 3 items, 1 from the field, and 2 from the window. $addMenuLine is a custom method you add to the menu class. See the Context Menus Demo topic for more information.
  4. Quit event handler (Discard event,Pass to next handler)

    I donÕt believe Omnis Studio 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.

Process Event and Continue

This Omnis Studio 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:

  1. Process event and continue
  2. Process event and continue (Discard event)

Process event and continue

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:

On evDrop

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)

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. 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

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, which related to the events specified.

These 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. The library events related properties save you from having to think about setting them 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 reduces the volume of event messaging. Omnis Studio gives you some pretty fine 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 has not been an issue for me so far.

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 scope ($ctask). 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

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.Ķ

Preventing Double evAfter Event

You may run into a situation where closing a window after a user clicks a pushbutton causes an evAfter event to be sent twice to the current entry field in the window. The first evAfter occurs when the user clicks the pushbutton, the second evAfter occurs when the window receives a $close message.

To prevent the second evAfter message set the window instance $enterable property to kFalse before closing the window.

Do $cinst.$enterable.$assign(kFalse)

pClickedField

If you are trapping an evAfter event of an entry field and want to find out if the user clicked the Cancel button or the Okay button use the event parameter pClickedField. It is a reference to the field (object) that was clicked.

On evAfter

If pClickedField.$objtype=kPushbutton
   
   Switch low(pClickedField().$name)
      Case 'cancel'
         ; The user clicked the cancel button.
      Case 'okay'
         ; The user clicked the okay button.
   End Switch
   
End If

Context Menu Demo

Omnis Studio 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 stores the observer window class instance and the call back method in instance variables.

Go ahead and run the demo. 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.

Drag and Drop List Lines

Omnis Studio 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.

The following sample code adds lines dropped from another list.

On evDrop

; Add the lines being dropped from another list.

; Make sure this is not a drag and drop from the field itself
If pDragField<>$cobj
   
   ; $mousevents must be set to kTrue in the library properties or the list object's Actions properties.
   ; Capture the line which the data was drop onto.
   Calculate LineNum as mouseover(kMLine)
   
   ; Event parameter pDragValue is the entire list
   ; We only want the selected lines, so loop through the selected lines, in reverse order
   Do pDragValue.$first(kTrue,kTrue) ;; (Selected, Backwards)
   While pDragValue.$line
      
      ; Add the line, return a reference to the added line
      Do [$cobj.$dataname].$addbefore(LineNum) Returns rLine
      
      ; Assign the values to the line, optional parameter kTrue matches column names
      Do rLine.$assignrow(pDragValue,kTrue) ;; (Match column names)
      
      ; Go to the next selected line in reverse order
      Do pDragValue.$next(0,kTrue,kTrue) ;; (Selected, Backwards)
   End While
   
   ; Redraw the field.
   Do $cobj.$redraw()
End If

Drag and Drop Text

This demo shows you how to drag and drop text from one text field to another and within the same text field.

The code is in the event_evDrop window class method.

The following sample code appropriately copies or moves dropped text.

On evDrop

; Capture the drop point text character position in the text
Calculate CharPosnDrop as mouseover(kMCharpos)

; Use local variables for easier to read code.
Calculate OrigText as [$cobj.$dataname]
Calculate LeftText as mid(OrigText,1,CharPosnDrop-1)
Calculate RightText as mid(OrigText,CharPosnDrop,len(OrigText))
Calculate FirstSel as pDragField.$firstsel
Calculate LastSel as pDragField.$lastsel

; Where is the drag coming from? This field or another field?
If pDragField=$cobj
   
   ; The drop is coming from this field. We'll need to move the text within this field.
   ; Is the drop point before or after the selected text?
   If CharPosnDrop<pDragField.$firstsel
      
      ; The drop point if before the drag text. Remove the drag text from the right text.
      Calculate Substring1 as mid(OrigText,CharPosnDrop,FirstSel-CharPosnDrop+1)
      Calculate Substring2 as mid(OrigText,LastSel+1,len(OrigText))
      
      ; If there is going to be a double space, remove it.
      If pos(' ',mid(Substring1,len(Substring1),1))&pos(' ',mid(Substring2,1,1))
         Calculate Substring2 as mid(Substring2,2)
      End If
      
      Calculate RightText as con(Substring1,Substring2)
      
   Else If CharPosnDrop>pDragField.$firstsel
      
      ; The drop point is after the drag text, so we need to remove the drag text from the left text.
      Calculate Substring1 as mid(OrigText,1,FirstSel)
      Calculate Substring2 as mid(OrigText,LastSel+1,CharPosnDrop-LastSel-1)
      
      ; If there is going to be a double space, remove it.
      If pos(' ',mid(Substring1,len(Substring1),1))&pos(' ',mid(Substring2,1,1))
         Calculate Substring2 as mid(Substring2,2)
      End If
      
      Calculate LeftText as con(Substring1,Substring2)
      
   Else
      ; The drop point is the same as the drag point, do nothing
      Quit method kTrue
   End If
End If

; Glue the left, drag value, and right text back together.

; Handle spaces between words and strings appropriately.
If mid(LeftText,len(LeftText),1)<>' '&mid(RightText,1,1)<>' '
   
   ; There are no spaces before or after the drop point.
   Calculate NewText as con(LeftText,pDragValue,RightText)
   
Else If len(trim(pDragValue))<>len(pDragValue)
   
   ; There are leading or trailing spaces on the drag value.
   Calculate NewText as con(LeftText,pDragValue,RightText)
   
Else
   
   ; There are no leading or trailing spaces on the drag value.
   
   If mid(LeftText,len(LeftText),1)=' '
      
      ; The left text has a trailing space. Add a space to the right.
      Calculate NewText as con(LeftText,pDragValue,' ',RightText)
      
   Else If mid(RightText,1,1)=' '
      
      ; The right text has a leading space. Add a space to the left.
      Calculate NewText as con(LeftText,' ',pDragValue,RightText)
      
   Else
      
      ; There are no spaces before or after the drop point.
      Calculate NewText as con(LeftText,pDragValue,RightText)
      
   End If
End If

; Copy the text to the variable.
Calculate [$cobj.$dataname] as NewText

; Redraw the field to load the contents.
Do $cinst.$redraw()

; Select the new text and set the currrent field to the drop field.
; Depending on spaces the text might be offset by one character.
If mid($cobj.$contents,len(LeftText)+1,1)=mid(pDragValue,1,1)
   Do $cobj.$lastsel.$assign(len(LeftText)+len(pDragValue))
   Do $cobj.$firstsel.$assign(len(LeftText))
Else
   Do $cobj.$lastsel.$assign(len(LeftText)+len(pDragValue)+1)
   Do $cobj.$firstsel.$assign(len(LeftText)+1)
End If

Queue set current field {[$cobj().$name]}

Quit method kTrue