Michael L Brereton - 02 January 2008, http://www.ewesoft.com/
<<
Previous: Laying Out Controls
>>
Next: GUI Data Editing
The
PRESSED ControlEvent and DATA_CHANGED DataChangeEvent
Correctly
using show() and exec()
Using newEventThread()/resumeEventThread()
SELECTED and DESELECTED Events
Like most GUI based systems, Eve uses an event-based model
for receiving user input and for generating control actions. You react to user
input in your applications by trapping and handling the appropriate events. The
Events used by Eve are mostly contained in the package eve.ui.event.
The main events received by your controls will be pen/mouse
events (as represented by a PenEvent object) and keyboard events (as
represented by a KeyEvent object). These events are sent directly to a
single Control object as determined by the Window manager. For PenEvents the Window
manager determines the actual Control object being pressed and sends the
appropriate event directly to it, adjusting x and y co-ordinates of the
on-screen mouse/pen location to be relative to the target Control. For
KeyEvents the Window manager determines which Control has the current keyboard
“focus” and sends the KeyEvent directly to it.
In each case the appropriate Event is sent synchronously to
the target by calling the Control’s postEvent() method. This method will call the onEvent()
method of the Control which it then uses to adjust its internal state and, if
necessary, generate further Events in reaction to the user input.
The Events generated by the Control objects themselves are then passed to its parent container via a call to the parent’s onEvent() method. The event is passed all the way up the parent chain until it reaches the top-level Window. The parent can determine which control generated the Event by looking at the target member of the Event. Most of the events that you will be handling will be those generated by Controls in reaction to user input.
NOTE: Pen/Mouse and Key events are not passed
up the Gui control tree unless you modify the Control to set the SendUpUIEvents
modifier bit.
There are two main event types that are generated by Control
objects in reaction to user input and these are the two that you will be
handling the most.
Some Controls, such as Button controls, do not have an internal
state but will react to a pen press. When the appropriate user action is
performed on the Control they generate a ControlEvent with the type
of the Event set to ControlEvent.PRESSED.
Most other Controls, such as Input and CheckBox controls,
keep some form of data as an internal state. User input may cause that state to
change. These Controls generate a DataChangeEvent with the type
of the Event set to DataChangEvent.DATA_CHANGED.
Using these two events alone, you will be able to handle the
majority of user input in your programs. Note that some Controls (such as
CheckBox controls) will generate both a PRESSED and DATA_CHANGED event, but you
should handle only one of them.
An example of handling events is given below. It is a simple
Form that takes as input two numbers and calculates another value from them.
The numbers are either multiplied or divided depending on what the user chooses
in the Choice.
You will note that the calculation is done and the result
updated once the user changes any of the inputs. This is caused by handling the
DataChange event.
package evesamples.ui;
import eve.sys.Convert;
import eve.sys.Event;
import eve.ui.*;
import eve.ui.event.ControlEvent;
import eve.ui.event.DataChangeEvent;
//##################################################################
public class Events extends Form{
//##################################################################
Input firstNumber, secondNumber, result;
Choice operation;
Button message,quit;
//===================================================================
public Events()
//===================================================================
{
title = "Form Demo";
resizable = true;
addLast(firstNumber = new Input(),HSTRETCH,FILL);
addLast(secondNumber = new Input(),HSTRETCH,FILL);
addLast(result = new Input(),HSTRETCH,FILL);
result.modify(DisplayOnly,0);
addLast(operation = new Choice(new String[]{"Multiply","Divide"},0),HSTRETCH,FILL);
addNext(message = new Button("Message"),HSTRETCH,FILL);
addNext(quit = new Button("Exit"),HSTRETCH,FILL);
}
//-------------------------------------------------------------------
private void calculate()
//-------------------------------------------------------------------
{
try{
double one = Convert.toDouble(firstNumber.getText());
double two = Convert.toDouble(secondNumber.getText());
double answer =
operation.getInt() == 0 ? one*two : one/two;
result.setText(""+answer);
}catch(Exception e){
result.setText(e.getMessage());
}
}
//===================================================================
public void onEvent(Event ev)
//===================================================================
{
if (ev instanceof DataChangeEvent && ev.type == DataChangeEvent.DATA_CHANGED){
calculate();
}else if (ev instanceof ControlEvent && ev.type == ControlEvent.PRESSED){
if (ev.target == quit){
Application.exit(0);
}else if (ev.target == message){
new MessageBox("Hello","Hello there!",MBOK).execute();
}
}
super.onEvent(ev); //Make sure you call this.
}
//##################################################################
}
//##################################################################
This example shows how you normally
handle “Form-centric” data input. There is another method of handling input
which is more “Object-centric” and uses a eve.ui.data.Editor to
automatically update variables in an Object from data entered by the user. This
is discussed in a later chapter.
As we have seen, the event model
allows parent Controls to capture events generated by their child controls.
However it is also possible for an object which is not a container for a
Control to handle the Control’s events. Any object which implements the EventListener
interface can receive such events. You can do this by calling the addListener(EventListener
listener) method on a Control. You can also call removeListener()
later to remove the listener.
As mentioned before exec()/execute() display a Form modally and create a new Event Thread for a window if they are called within a window’s Event Thread. This allows the blocking of the old Event Thread but still allow the original window to receive repaint/resize events normally.
The show() method displays a
Form non-modally. The new Form is displayed and the method returns
immediately, but a new Event Thread for the old window will not be created.
Therefore you should not block the event delivery thread if you call the show()
method since there will be no other delivery thread to handling incoming user
events.
Here is an example that shows how you can display new Forms and the correct and incorrect way to handle waiting for the Form to close.
import eve.sys.Event;
import eve.sys.EventListener;
import eve.ui.*;
import eve.ui.event.ControlEvent;
//##################################################################
public class ShowExec extends Form{
//##################################################################
//===================================================================
public ShowExec()
//===================================================================
{
title = "Show/Exec Demo";
Button b;
addLast(b = new Button("Execute a Message Box!"));
//
//This functions correctly.
//
b.addListener(new EventListener(){
public void onEvent(Event ev){
if (ev.type == ControlEvent.PRESSED){
Control c = ((Control)ev.target);
String txt = c.getText();
c.setText("Waiting...");
new MessageBox("Executed","This will execute() OK!",MBOK).execute();
c.setText(txt);
}
}
});
//
//This functions correctly as well.
//
addLast(b = new Button("Show a Message Box!"));
b.addListener(new EventListener(){
public void onEvent(Event ev){
if (ev.type == ControlEvent.PRESSED){
Control c = ((Control)ev.target);
String txt = c.getText();
c.setText("Waiting...");
new MessageBox("Shown","This will show() OK!",MBOK).show();
c.setText(txt);
}
}
});
//
//This is wrong! It calls a show, which does NOT spawn a new event thread,
//and then attempts to wait, which blocks all Gui events. It eventually gives
//up and returns after 5 seconds.
//
addLast(b = new Button("Bad Show and Wait for Message Box!"));
b.addListener(new EventListener(){
public void onEvent(Event ev){
if (ev.type == ControlEvent.PRESSED){
Control c = ((Control)ev.target);
String txt = c.getText();
c.setText("Waiting...");
MessageBox mb = new MessageBox("Shown","This will show() OK!\nBut will block for 5 seconds.",MBOK);
mb.show();
//This will block this window's event thread for 5 seconds.
try{
Form.waitUntilClosed(mb.handle,new eve.sys.TimeOut(5000));
}catch(Exception e){}
c.setText(txt);
mb.exit(0);
}
}
});
//
// If you want to wait on a non-modal form to close or you
// want to block for any other reason, you will have to call
// newEventThread()
//
addLast(b = new Button("Good Show and Wait for Message Box!"));
b.addListener(new EventListener(){
public void onEvent(Event ev){
if (ev.type == ControlEvent.PRESSED){
final Control c = ((Control)ev.target);
final String txt = c.getText();
c.setText("Waiting...");
MessageBox mb = new MessageBox("Shown","This will show() OK!",MBOK);
mb.show();
if (false){
//
// This is one way to do it.
//
Object oldEvent = c.newEventThread(c);
try{
mb.waitUntilClosed();
}finally{
c.resumeEventThread(oldEvent);
}
}else{
//
// This uses a convenience method to wait on a Handle
//
c.waitEventThread(mb.handle);
}
c.setText(txt);
}
}
});
}
public static void main(String[] args)
{
Application.startApplication(args);
Form f = new ShowExec();
f.exitSystemOnClose = true;
f.show();
// The application will not exit here.
}
//##################################################################
}
//##################################################################
This method in Control is used if you are (or
may be) within an Event Thread but wish to wait on some external thread or
event without blocking events being delivered to the Window. You would use it
like this:
// Here I am in an Event handler for the Control c
Object oldEvent = c.newEventThread(c);
try{
//
// Now I can block for as long as I need.
// The Control c will not receive user events, only repaint/resize events.
//
}finally{
c.resumeEventThread(oldEvent);
}
If the parameter to newEventThread() is
null then all Controls will continue to receive events as normal, otherwise the
specified control will not receive pen/keyboard events, only repaint and resize
events.
The method pauseEventThread() is a
convenience method that calls newEventThread(this) , i.e. it calls
newEventThread() and disables itself.
You can safely call newEventThread() even if
the current thread is not a Window Event Thread.
These events are a little more complicated than the PRESSED and DATA_CHANGED events and need some explaining.
Menu and List Controls generate MenuEvent and ListEvent event objects. In fact ListEvent inherits from MenuEvent and so is very similar, just as List inherits from Menu. These events have a field called selectedItem and this will indicate the item selected (or deselected) that caused the event to be generated. This field is of type Object because it can be either a MenuItem object (which is usually the case) or a String representing the text of the item. So when you are handling a MenuEvent/ListEvent you should always check what type this value is at run-time and not assume it is one or the other.
Menu events also have a field called menu. This indicates the Menu or List that the selectedItem belongs to. This may be different from the target of the event, which is the usual way you determine the source of the event. However the target of a MenuEvent may change as the event propagates up chains of sub-menus. Each parent menu that detects a MenuEvent coming from one of its sub-menus will modify the target field so that it appears to come from itself. However, it will not change the menu field, so this always refers to the original generator of the event. This also happens with the MenuBar control – it too changes the target field so that any events generated by menus that it contains will look as though they come from the MenuBar itself.
Menus are generally used for the selection of a single item within a menu or a set of menus. As a result they will also generate a standard PRESSED event when an item (which is not associated with a sub-menu) is selected via the keyboard or pen/mouse press.
Lists are generally used differently, and frequently multiple item selection is allowed. Therefore the PRESSED event is generally not generated unless an item is double clicked with the pen/mouse. However a List will generate a DataChangedEvent when the user changes the currently selected item.