Working with Embedded Wizard: Handling keyboard events

With the Mosaic class Core::KeyPressHandler you can react to keyboard events. This so-called Key Handler object can be used within your GUI component to add functionality to be executed when the user operates the keyboard or hardware buttons available in your device.

The Key Handler can be configured to react to a particular key code or a group of keys (e.g. alphanumeric keys). When the event with the matching key code arrives, the Handler sends a signal to its associated slot method where your particular implementation is executed.

The following sections are intended to provide you an introduction and useful tips of how to work with the Key Handler object and process keyboard or hardware button events. For the complete reference please see the documentation of the Core::KeyPressHandler class.

Add new Key Handler object

To add a new Key Handler object just at the design time of a GUI component do following:

First ensure that the Templates window is visible.

In Templates window switch to the folder Event Handlers.

In the folder locate the template Key Press Handler.

Drag & Drop the template into the Composer window:

Eventually name the new added Handler.

Inspect the Key Handler object

As long as the Handler object is selected you can inspect and modify its properties conveniently in the Inspector window as demonstrated with the property Enabled in the screenshot below:

This is in so far worth mentioning as all following sections describe diverse features of the Handler object by explicitly referring to its corresponding properties. If you are not familiar with the concept of a property and the usage of Inspector window, please read first the preceding chapter Compositing component appearance.

Configure the filter condition

Per default, new added Key Handlers are preset to react to any key event. With the property Filter you can explicitly configure the Key Handler to work more selectively. For example, initializing Filter with the value Core::KeyCode.Enter instructs the Handler to take in account the key Enter only. Any event generated by pressing other key is ignored by such configured Handler. The property Filter provides an integrated Inspector Assistant permitting you to conveniently select from the comprehensive set of options the desired key:

More sophisticated is to configure the Handler to react to events generated by a key belonging to a key group. For example, if Filter is initialized with Core::KeyCode.CursorKeys, the Handler will respond to events generated by any cursor key Left, Right, Up and Down. Similarly, initializing this property with Core::KeyCode.DigitKeys will configure the Handler to process any digit key 0 .. 9. Later, when you process the event you can evaluate the actual code of the pressed key.

If your keyboard layout provides modification keys Shift, Alt or Ctrl you can configure the Handler to combine them with the regular keys. For example, the property Filter initialized with the value Core::KeyCode.CtrlShiftSpace will instruct the Handler to react to events generated by the Space key pressed while holding down the modification keys Ctrl and Shift. Pressing Space without the specified modification keys is ignored by such configured Handler.

In addition to the keys typical for a PC keyboard, the Filter property includes also a large set of codes common for remote control devices e.g. the codes ChannelUp or VolumeDown. These codes exist primarily for compatibility purpose with older Embedded Wizard versions, which were used extensively to implement software for TV sets and other consumer electronic devices. If your developed product does include such keys, you can of course use them within your application.

If your device includes special keys or key combinations not listed implicitly in the Filter property, you can use the universal codes User0 .. User19 or you simply map them to one of the other available codes. In principle the key codes (e.g. Enter) are just names. What exactly Enter does mean is an individual aspect of your device. In your main software you are free to convert the codes received from your keyboard or hardware buttons to any of the Embedded Wizard own key codes and then within the GUI application react to these codes.

Since the TV set key codes and the universal codes User0 .. User19 are not available natively on the PC keyboard, the Prototyper window provides for every code a special shortcut permitting you to simulate the key and thus to test the behavior of the application on the PC. For details, please see Prototyper window and keyboard inputs.

IMPORTANT

If one GUI component contains several Key Handler configured to match the same key, the events are deliver per default to the Handler having the highest order number. Other Handler with lower order numbers are suppressed unless the active Handler decides to reject the event. The order numbers can be inspected conveniently in the members area of the Inspector window. If necessary, you can explicitly reorder the Key Handler.

Implement OnPress, OnHold and OnRelease slot methods

When the Key Handler receives a key press event matching its configured filter condition, a signal is sent to the slot method stored in the Handler's property OnPress. Later, when the user releases the previously pressed key again, the Handler signals the slot method assigned to its property OnRelease. If the keyboard hardware or its driver continues generating repetition events for the held down key, the Key Handler signals the slot method stored in the Handler's property OnHold. Within the slot methods your particular implementation can react and process the events.

The following sequence diagram demonstrates a typical order in which the slot methods receive signals while the user interacts with the Key Handler. Please note, that every interaction starts with a signal sent to the OnPress and ends with a signal sent to the OnRelease slot method. If the user continues pressing the key, the slot method stored in the property OnHold is signaled. The period in which OnHold receives signals depends on how often the keyboard hardware or its driver generates the key events:

Providing slot methods for all OnPress, OnHold and OnRelease is not obligatory. If your application case requires the release key events being processed only, then you leave the properties OnPress and OnHold initialized with null. You can initialize the properties with already existing slot methods or you add a new one and implement it as desired. The following steps describe how to do this:

First add a new slot method to your GUI component.

To react to the event when the user has just pressed the key: assign the slot method to the property OnPress of the Handler object.

To react to the event when the user has released the key: assign the slot method to the property OnRelease of the Handler object.

To react to repetition events when the user continues pressing the key: assign the slot method to the property OnHold of the Handler object.

Open the slot method for editing.

In the Code Editor implement your desired operation to execute when the event occurs.

The Key Handler object manages several variables reflecting its current state. You can evaluate these variables whenever your GUI component implementation requires the corresponding information. Usually, however, you do this from the OnPress, OnHold and OnRelease slot methods. The following table provides an overview of the available variables:

Variable

Description

Down

The variable is true if the Handler has recently received the key press event without the corresponding key release event. In other words, the user still holds the affected key pressed.

Code

The code of the affected key accordingly to the enumeration Core::KeyCode. If the key has no equivalent in the Core::KeyCode enumeration, the variable is initialized with the value Core::KeyCode.NoKey.

CharCode

The UNICODE number of the affected character key (e.g. 'A', 'y' or '%'). If the user has pressed a special key (e.g. cursor key Down), the variable is initialized with the value '\0' (zero) character.

Repetition

The variable is false if the user has just pressed the key. It is true if the the user holds the key pressed and the keyboard continues generating the repetition events for the affected key.

RepetitionCount

The variable stores the number of repetition events received by this Key Handler after the user has pressed a key. Just after pressing a key the variable RepetitionCount is 0 (zero). Later every repetition event causes the variable to be incremented. In this manner the implementation of the OnHold or OnRelease slot method can determine how long the user holds down the key.

Time

This variable stores a time stamp when the key event has been generated by the keyboard device.

The variable Down is useful if you have implemented a single slot method for both the OnPress and OnRelease events. In such case you can easily distinguish whether the method is called because of the key being pressed or released. Similarly, by evaluating the variable Repetition you can react to the first key press event and ignore eventual events generated when the user holds the key pressed. The following Chora code demonstrates the usage of the both variables:

if ( !KeyHandler.Down )
{
  // The user has released the key.
}

if ( KeyHandler.Down && !KeyHandler.Repetition )
{
  // The user has just pressed the key.
}

if ( KeyHandler.Down && KeyHandler.Repetition )
{
  // The user holds the key pressed. The event is repeated.
  // The variable 'RepetitionCount' determines how often the
  // event has been repeated
}

The variables are also useful if the Handler is configured to match several keys and the implementation of the associated OnPress, OnHold or OnRelease slot method requires to know the exact code of the affected key. For example, a Handler configured with the filter Core::KeyCode.AlphaKeys reacts to events generated by any of the letter keys A..Z and a..z. In order to implement a simple text input GUI component, the OnPress slot method evaluates the variable CharCode and e.g. appends its content at the end of a string displayed in a Text view. While the user presses the alpha keys, the corresponding signs appear in the Text view:

TextView.String = TextView.String + KeyHandler.CharCode;

Similarly, if you configure a Handler to match Core::KeyCode.CursorKeys (Left, Right, Up or Down) you can evaluate the variable Code and thus depending on the pressed key decide what to do. You can for example move the position of the blinking cursor within a text input component:

// The user has pressed the 'Left' key -> move the cursor one character to the left.
if ( KeyHandler.Code == Core::KeyCode.Left )
  CursorPos = CursorPos - 1;

// The user has pressed the 'Right' key -> move the cursor one character to the right.
if ( KeyHandler.Code == Core::KeyCode.Right )
  CursorPos = CursorPos + 1;

/*
  Update the component to reflect the modified cursor position.
*/

The following example demonstrates the implementation of a simple text input component. Here the user can input alphanumeric characters and by using the cursor keys Left and Right navigate conveniently within the entered text:

DOWNLOAD EXAMPLE

Please note, the example presents eventually features available as of version 8.20

Keyboard events and the focus path

What happens if the GUI application contains several components all willing to handle keyboard events? How will the user interact with all of them? Evidently it doesn’t make a lot of sense to interact with all sliders or buttons simultaneously when the user presses a key.

The Mosaic framework implements for this purpose a sophisticated concept to dispatch keyboard events, the so-called focus path. Whenever the user presses a key on the keyboard, the corresponding event is routed along this path directly to the last focused GUI component, so it and only it can react to the event.

If the component ignores the event, the event is delivered to the next superior component on the focus path. This permits the hierarchical event dispatching until a willing component has handled the event or all components on the focus path have been evaluated. In plain text, keyboard events can be processed by GUI components lying on the focus path only, whereby the last GUI component on the focus path will receive the event first.

The focus path is established through the initialization of the properties Focus, available in every GUI component. Starting with the application component, its property Focus can refer to a GUI component embedded inside it. This embedded GUI component, in turn, can store in its Focus property a reference to its own embedded GUI component, and so far. In other words, the Focus properties chain the nested components creating in this manner a kind of path and the application component serves as the root of this path.

The following figure demonstrates the principle of the focus path and how it is established. In this example the Application component contains two embedded Panel components, each containing several Slider components. The property Focus of the Application refers to the right Panel. This Panel belongs thus to the focus path. The property Focus of the Panel itself refers to its second Slider resulting in this Slider being also a part of the focus path. All other components are not included:

When the user presses a key, the corresponding event reaches first the Slider component as it lies at the end of the focus path. The Slider has thus the chance to process the event first. If it has decided to ignore the event, the event is delivered to its superior component, to the Panel, which then has the chance to process it. If again, the Panel has no interest in the event, the event is delivered finally to the Application component.

Let's assume, the Slider component is implemented to react to Plus and Minus key events causing the slider's thumb being moved accordingly. The Panel component, in turn, should process the Up and Down keys permitting the user to navigate conveniently between the Sliders. Similarly, the Application component reacts to Tab key events, so that by pressing this key the user can toggle between the left and right Panel. In this scenario the Slider and the Panel will ignore the Tab events while the Plus key, after being handled in the Slider, will never reach the Panel nor the Application component.

With this technique you can implement complex GUI applications consisting of various nested components, every implemented to handle its particular key events. At the runtime, when the user navigates within the application the focus path is updated accordingly. For example, as described above pressing the key Tab should toggle between the both Panel components. The OnPress slot method of the corresponding Key Handler could be implemented as follows:

// Depending on the currently focused Panel switch to the another one.
if ( Focus == Panel1 )
  Focus = Panel2;
else
  Focus = Panel1;

Accordingly, when the user presses the key Tab the property Focus of the Application component is switched to refer to the left panel changing completely the entire focus path. Now, the second slider in the left panel is able to handle the Plus and Minus events. The right Panel, in turn, is not focused anymore and will not receive any key events:

In practice, your GUI components should give the user a visual feedback when they are currently able to handle keyboard events. For example, the focused slider can appear highlighted with a kind of focus border. Every time, the property Focus is changed, the components affected by this alternation do update is internal state between selected, focused, not selected and not focused. You can implement the components to react to their state alternation and update their appearance accordingly. This is addressed in detail in the chapter Managing component state.

The following example implements the above described Panel and Slider application including the navigation between Sliders and Panels:

DOWNLOAD EXAMPLE

Please note, the example presents eventually features available as of version 8.20

Keyboard events and the grab cycle

When the user presses a key, the corresponding event is dispatched along the focus path until a Key Handler is found, which processes the event. With this begins the so-called grab cycle. The grab cycle means, that the Key Handler becomes the direct receiver for all subsequent events triggered by the originally pressed key. In other words, the Key Handler grabs temporarily the keyboard.

With the grab cycle the Mosaic framework ensures, that Key Handler which has processed the key press event also receives the corresponding key release event. Disabling the Key Handler, hiding the parent GUI component or removing it from the focus path has no effect on the event delivery. Even if the parent component is removed from the screen, the grab cycle persists and the Key Handler will continue receiving all key events associated with the pressed key.

The grab cycle ends automatically when the user releases the key or presses another one. In any case, with the end of the grab cycle the Handler will receive the final release key event. The grab cycle technique is thus convenient if it is essential in the implementation of your GUI component to correctly finalize an interaction started by the previously pressed key.

Under normal circumstances you don't need to think about the mechanisms behind the grab cycle. You should however understand that when processing the key press event the Handler will also receive the corresponding key release event.

Reject the just received key press event

As described above the keyboard events are dispatched to GUI components lying on the focus path starting always with the GUI component at the end of this path. Within the GUI component the event is processed by the Key Handler which is enabled and configured to match the affected key. Other Key Handlers configured also to match the key but existing in GUI components lying closer to the origin of the focus path will not receive this event. This is important as handling the same event simultaneously by multiple components or handler is little useful.

Similarly, if the GUI component contains several Key Handlers configured to match the same key code, the event is delivered to the Handler with the highest order number. The order numbers can be inspected conveniently in the members area of the Inspector window. If necessary, you can explicitly reorder the Key Handler and thus promote it.

Sometimes, however, after receiving and evaluating the key press event the Handler may decide to not being responsible for it. In practice this is the case when the Handler is configured to match a group of keys (e.g. alphanumeric) but it is interested in processing only few of them (e.g. lowercase letters and digits but not uppercase).

In such case, the Handler can reject the event and give other Handler (be it within the same GUI component or within another component lying further on the focus path) a chance to process it. This is achieved by assigning the value true to the variable Continue while processing the key press event in the Handler's OnPress slot method. The following Chora code demonstrates such implementation:

// Not a lowercase letter? Then reject the event.
// Other Handler will process it.
if ( KeyHandler.CharCode != KeyHandler.CharCode.lower )
{
  KeyHandler.Continue = true;
  return;
}

/*
  Otherwise handle the event as usual ...
*/

In this manner, the process of event delivery is continued and the next Key Handler candidate can receive the event. Please note, that rejecting the event excludes the affected Handler from the grab cycle. Accordingly, the Handler will not receive any corresponding OnHold nor OnRelease events.

Key Handler and invisible components

Please note, the function of a Key Handler is not affected by the visibility status of the GUI component, the handler is used inside. Embedded Wizard differentiates explicitly between the status ready to be shown on the screen and able to handle user inputs. As long as the GUI component lies on the focus path, hiding the component will not suppress the key even handler from being able to react to user inputs. Thus if you want a GUI component to be hidden and to not be able to handle user inputs you have to hide and disable the component.

Generally, whether a GUI component is visible or not is determined by its property Visible. If the component is embedded inside another component, the resulting visibility status depends also on the property Visible of every superior component. In other words, a component is visible only if its property Visible and the property of every superior component are true. If one of these properties is false the component is considered as hidden.

In turn, the ability to react to user inputs is controlled by the component's property Enabled. Again, if the component is embedded inside another component, the resulting enabled status depends also on the property Enabled of every superior component. In other words, a component is able to handle user inputs only if its property Enabled and the property of every superior component are true. If one of these properties is false the component is considered as disabled.

IMPORTANT

A hidden component will receive and handle all user inputs as if it were visible. If it is not desired, you have to set both the property Visible and Enabled of the affected component to the value false.

Disable a Key Handler

By initializing the property Enabled with the value false you can explicitly suppress a Key Handler from being able to react to user keyboard inputs. In this manner, other Handlers lying on the focus path can respond to the events.

Please note, that modifying Enabled while the Handler is currently involved in a grab cycle has no immediate effect. In other words, as long as the user does not release the previously pressed key, the Handler will continue receiving events even if it has been disabled in the meantime. Finally, the grab cycle guarantees that Handler, which have received the key press event will also receive the corresponding key release event.

TIP

In the GUI application an individual Key Handler can handle events only when the Handler and all of its superior Owner components are enabled, the Handler together with its superior components lie on the Focus path and there is no other Key Handler further at the end of the Focus path configured to handle the same key events.

Please note, Key Handlers continue working even if you hide the superior components. See: Key Handler and invisible components.