SWT Event Handling, Threading, and Displays

This chapter introduces SWT event handling, multiple threading, and the event model in SWT. The Display class, which plays the most important role in SWT event handling, is introduced as well.

SWT's event handling is quite different from that of Swing. Even the simplest SWT application requires a certain amount of code to manage event loop explicitly. The first few questions from developers with a Swing background are usually about the "mystery" event loop, which is why you should read this chapter before you move on to the more advanced material. After you are comfortable with SWT event handling, you can continue on your SWT journey. If you need to know more about event handling and threading, refer back to this chapter. Finally, you learn how to listen and react to various SWT events using either a typed or untyped model.

SWT Event Handling Fundamentals

This section first introduces the native event handling mechanism, and then it shows how SWT handles events with Display objects.

Native Event Handling Mechanism

For all kinds of GUI applications, regardless of programming languages and user interface toolkits, the underlying operating system is responsible for detecting GUI events and placing them into appropriate event queues. GUI events include events such as mouse movements, mouse clicks, keystrokes, window repaint, and so on. For example, when a user clicks a mouse button, the operating system generates an event representing this mouse click. It then determines which application should accept this event and places the event in the target application's event queue.

How does the GUI application handle events? Any GUI application utilizes an event loop to detect the GUI event from the event queue and react appropriately. This event loop plays a critical role in GUI applications. Without an event loop, a GUI application terminates immediately when the main function exits.

Most C programmers are very familiar with this event loop. However, Swing developers are rather new to this mechanism. The Swing toolkit shields the event loop from programmers. In Swing, a dedicated toolkit user interface (UI) thread is used to read and dispatch events from the event loop and redirect events to an internal maintained queue, which is serviced by the application running in separate threads.

SWT Event Handling Basics

SWT follows the threading model supported directly by the platform. The application manages the event loop in its user interface thread and dispatches events from the same thread.

So why doesn't SWT use toolkit UI threads as Swing does to shield event loops from developers? There are two main reasons why SWT uses the threading model directly:

  • First, from the performance perspective, the threading model is more efficient than the toolkit UI thread model. Most operating systems perform considerable optimizations with the event queue. For the toolkit UI thread model, the toolkit UI thread pulls the events off the queue so fast that event optimizations are almost impossible. This could be one of the reasons that SWT is faster than Swing.
  • Second, because SWT is built on JNI, it would be very difficult to debug and to diagnose problems if GUI event processing depends on the Java threading implementation. (Note: On different platforms, the Java threading implementations vary.)

For these reasons, SWT uses the threading model instead of the toolkit UI thread model. Developers have to manage event loops explicitly in all SWT applications. The Display plays the most important role in event handling, and I will introduce it before discussing event handling further.

Using Displays

Display instances act as the middleware, responsible for managing the connection between SWT and underlying native operating systems. The Display class is in the package org.eclipse.swt.widgets. For those who are familiar with the X Windows system, a Display instance is equivalent to an X Windows display. The primary functions of Display instances are as follows:

  • Implementing the SWT event loop in terms of a platform event model
  • Providing methods for accessing information about the operating system
  • Managing operating system resources used by SWT

Only a single Display instance is required in most SWT-based applications. Furthermore, some platforms do not allow more than one active display — a display that has not been disposed of.

Accessing Operating System Information

Besides event handling, the Display class provides several methods for you to access information about the underlying operating system. The functions of these methods are as follows:

  • void beep(): Causes the system hardware to emit a short sound if it supports this capability.
  • Rectangle getBounds(): Returns a rectangle describing the display's size and location. For instance, on a 1024 × 768 resolution Windows system, getBounds returns Rectangle {0, 0, 1024, 768}.
  • Rectangle getClientArea(): Returns a rectangle describing the area of the display that is capable of displaying data. The client area can be the same size as the bounds area, although this is not always true.
  • int getDismissalAlignment(): Returns the button dismissal alignment; can be either SWT.LEFT or SWT.RIGHT. The button dismissal alignment is the ordering that should be used when positioning the default dismissal button for a dialog. For instance, for a dialog containing OK and CANCEL buttons, on platforms where the button dismissal alignment is LEFT, the button order should be OK, CANCEL; on RIGHT-aligned button dismissal platforms, this order is reversed.
  • int getDoubleClickTime(): Returns the longest duration in milliseconds between two mouse button clicks that will be deemed a double-click by the operating system.
  • int getIconDepth(): Returns the maximum allowed depth of icons on the display.
  • Monitor[] getMonitors(): Returns all the monitors attached to the device. To find the primary monitor, call getPrimaryMonitor.
  • Color getSystemColor(int id): Returns the matching standard color for the given constant, which should be one of the color constants specified in class SWT. If it is not a constant defined in SWT, the returning color will be black. Notice that the returned color should not be freed because it was allocated and managed by the system.
  • Font getSystemFont(): Returns a reasonable font for the application to use.

SWT Event Handling with Displays

Earlier in this chapter, I discussed the basic SWT event-handling mechanism. Here, you get a detailed look at how it works with the following sample code extracted from the "Hello, World" example from the last chapter.

 public static void main(String[] args) {
 Display display = new Display();
 Shell shell = new Shell(display);
 shell.setText("Hello, world!");
 shell.open();

 while(! shell.isDisposed()) { // Event loop.
 if(! display.readAndDispatch())
 display.sleep();
 }
 display.dispose();
 }

When you run the "Hello, World" example, a thread is required to execute these instructions; I'll call this thread the user interface (UI) thread. (Strictly speaking, the UI thread is the thread that creates the Display instance.) The UI thread first creates the Display object, and then it creates and displays the shell. After the shell has been displayed, the UI thread reaches the event loop. It constantly checks whether the shell has been disposed of. If the shell has been disposed of, then it disposes of the display instance and releases all the associated operating system resources. Otherwise, the UI thread reads and dispatches GUI events by calling the method display.readAndDispatch.

The flow of the event loop is illustrated in Figure 4-1.

image from book
Figure 4-1

The readAndDispatch method of the Display class reads events from the operating system's event queue and then dispatches them appropriately. Besides events, this method also checks and dispatches inter-thread messages. I discuss the inter-thread messaging in subsequent sections. The readAndDispatch method returns true if there is potentially more work to do or false if the caller (the UI thread) can sleep upon return from it.

The UI thread can sleep (be placed in a state that it does not consume CPU cycles) if there are no more tasks to perform. The Display.sleep method causes the UI thread to sleep until an event is queued or it is awakened.

Suppose the shell has been displayed; the user can terminate this sample application by closing the shell. An event will be sent to the event queue when the user closes the shell. The UI thread will be awakened if it is sleeping, and it will read the event and dispatch the event. In this case, shell.dispose() will be called. The event loop breaks because the shell has been disposed — shell.isDisposed() returns true.

Finally, display.dipose() disposes of all the native operating system resources used and disconnects the display instance from the underlying native window system. Note that in all modern operating systems, exiting to the operating system releases all resources acquired by the process, so it is not necessary to dispose of the display object as long as the program exits. However, it is always good to remember disposal of resources that you acquired, so the line is kept to remind you of disposing of acquired resources.

Multithreaded UI Programming

Multithreading helps to make UI responsive. In SWT, you can use various methods provided by the Display class to execute tasks in separate threads.

Multithreading with the UI Thread and Non UI threads

The last section illustrated the event handling mechanism. The UI thread is responsible for reading and dispatching events from the operating system event queue, and invoking listeners in response to these events. All the listener code is executed in this UI thread. A long operation executed by a listener will run in the UI thread and block it from reading and dispatching events — thus the application hangs.

A common solution to this problem is to fork another thread to perform the operation and update the user interface. However, SWT does not allow non-UI threads to access user interface components directly.

Only the UI thread (the thread that creates the display) is allowed to perform all of the UI-related calls. If a non-UI thread tries to make calls that must be made from the UI thread, an SWTException is thrown.

Let's look at an example that calculates the value of π (PI, the ratio of a circle's circumference to its diameter). Figure 4-2 shows the user interface of the sample application.

image from book
Figure 4-2

Because the calculation of PI's value may take considerable time, obviously, it should be put in a separate thread. Programmers familiar with Swing could rush to the following code:

 Button buttonThread = new Button(shell, SWT.PUSH);

 buttonThread.addSelectionListener(new SelectionListener() {
 public void widgetDefaultSelected(SelectionEvent e) { }
 public void widgetSelected(SelectionEvent e) {
 buttonThread.setText("Calculation in progress ...");
 getTask(buttonThread).start();
 }
 });

 public Thread getTask(Button button) {
 final Button theButton = button;
 return new Thread() {
 public void run() {
 double pi = calculatePI(9999999);
 theButton.setText("PI = " + pi); // Updates UI.
 }
 };
 }

The first line creates a button, and the next few lines add a selection listener to the button. When the button is clicked, the widgetSelected method in the listener will be called. This method starts a new thread obtained from the getTask method. This non-UI thread calculates the value of PI, and updates the button's text with calculated PI value.

The above describes how the application works. Everything seems logical, but when you run it, the following exception is thrown:

org.eclipse.swt.SWTException: Invalid thread access
 at org.eclipse.swt.SWT.error(SWT.java:2369)
 at org.eclipse.swt.SWT.error(SWT.java:2299)
 at org.eclipse.swt.widgets.Widget.error(Widget.java:388)
 at org.eclipse.swt.widgets.Widget.checkWidget(Widget.java:318)
 at org.eclipse.swt.widgets.Button.setText(Button.java:607)
 at com.asprise.swt.example.PICalculator$3.run(PICalculator.java:92)
 at java.lang.Thread.run(Thread.java:536)

This exception is thrown because the newly created thread tries to update the UI, and this operation can be performed only by the UI thread. Non-UI threads can request the UI thread to perform UI calls only on behalf of themselves. The right way to perform this lengthy operation is as follows.

 Button buttonAsyncExec = new Button(shell, SWT.PUSH);

 buttonAsyncExec.addSelectionListener(new SelectionListener() {
 public void widgetDefaultSelected(SelectionEvent e) {
 }
 public void widgetSelected(SelectionEvent e) {
 buttonAsyncExec.setText("Calculation in progress ...");
 getTask2(buttonAsyncExec).start();
 }
 });

 public Thread getTask2(Button button) {
 final Button theButton = button;
 return new Thread() {
 public void run() {
 final double pi = calculatePI(9999999);

 display.asyncExec(new Runnable() {
 public void run() {
 theButton.setText("PI = " + pi);
 }
 });
 }
 };
 }

Instead of directly making UI calls in the non-UI thread, you simply pass a Runnable instance to the asyncExec method of the display. The Runnable instance is put into the inter-thread message queue, and the UI thread checks this queue and executes the run method of the Runnable object. Now the code can be properly executed without any exceptions thrown. The asyncExec method is explained in detail shortly. For the complete list of code, please refer to the sample program PICalculator.java.

Some Swing developers may feel it is stupid that only one thread can access the UI calls. Actually, there is a good reason for this — most UI calls are not thread-safe. (Code is thread-safe if it works without any problem even if many threads execute it simultaneously.) When two threads try to access the same UI calls, this may result in a crash or a deadlock. It is true that Swing does allow any thread to access UI calls. However, this does not mean that Swing UI calls are all thread-safe — in fact, most of Swing UI calls are not thread-safe. We have encountered several problems arising from multithreads accessing UI calls in some of our Swing projects. Even in Swing, safe updates to components must be executed within the UI thread. In Swing, safe multithread UI programming must follow the same rule: only the UI thread should make most of the UI calls. Swing provides a help class javax.swing.SwingUtilities that allows Runnable instances to be put in a queue and the UI thread then executes them. The invokeLater method of SwingUtilities is the Swing counterpart of the method asyncExec used in the preceding code. To summarize then: Swing allows any thread to access UI calls at the risk of crashes and deadlocks; SWT allows only the UI thread to make most UI calls. In both frameworks, safe multithread UI programming requires that only the UI thread should make UI calls, and there are some methods by which non-UI threads can delegate their UI calls to the UI thread.

Putting the time-consuming operation and UI updating procedures altogether into the run() method of the Runnable instance passed to the asyncExec(syncExec, timerExec) method is actually no different than executing everything in the UI thread. Remember that the Runnable instance passed to asyncExec is not used to create a new thread. asyncExec simply calls its run() method. Many developers often make this mistake, so it is good to create a small pattern about it.

SWT Time Consuming Operation UI Pattern

The follow pseudo-code illustrates how to run time-consuming operations in a separate thread and update the UI after finishing:

 Thread operationThread = new Thread() {
 public void run() { // Override the method.
 // Your time-consuming operations go here
 ...

 // Update UI.
 display.asyncExec/syncExec(new Runnable() {
 public void run() {

 // UI Updating procedures go here ...

 }
 });
 }
 };

 operationThread.start();

Thread Safe UI calls

Most UI calls can be accessed only through the UI thread; however, a few UI calls are accessible for all kinds of threads. Methods asyncExec, syncExec, and timerExec are used for inter-thread messaging between non-UI threads and the UI thread. The wake method can be called from any thread to wake up the sleeping UI thread.

asyncExec

Syntax:

 public void asyncExec(Runnable runnable)

This method takes a Runnable object as the argument. It causes the run method of the Runnable instance to be executed by the UI thread at the next reasonable opportunity. This method returns immediately. The caller thread continues to run in parallel, and is not notified when the run method of the Runnable instance has completed. Note that by calling this method there is no guaranteed relationship between the timing of the background thread and the executing of the Runnable instance. If UI access must be finished before the non-UI thread continues its execution, syncExec should be used instead. The Swing counterpart of asyncExec is SwingUtilities.invokeLater().

When to Use It

Use asyncExec when you have a background thread to perform a lengthy operation and the GUI needs to be updated. For example, the following code sample shows a background thread downloading a file and continuously updating a progress indicator:


 Thread downloadThread = new Thread() {
 Runnable runnable;
 double ratio;

 public void run() {
 int fileSize = 0;
 int currentDownloaded = 0;

 ... // Determine file size here
 while(currentDownloaded < fileSize) {
 // Download part of the file
 currentDownloaded += downloadFilePart();

 ratio = currentDownloaded * 1.0 / fileSize;

 // update the progress indicator.
 if(runnable == null) // Lazy initialization
 runnable = new Runnable() {
 public void run() {
 progressBar.setSelection((int)(ratio*100));
 }

 display.asyncExec(runnable);
 }
 }
 }

 downloadThread.start();

You do not have to create a new Runnable instance every time you need to call asyncExec or syncExec. Reusing Runnable instances can save a lot of object creation overheads.

syncExec

Syntax:

 public void syncExec(Runnable runnable)

This method is very similar to asynExec, except that the calling thread will be blocked (suspended) until the UI thread has completed the execution of the Runnable instance. Its Swing counterpart is SwingUtilities.invokerAndWait().

When to Use It

Use syncExec when the code in a non-UI thread depends on the return value from the UI code or it needs to ensure that the Runnable instance must be completed before returning to the thread.

The following code demonstrates how syncExec works:

 final Runnable print = new Runnable() {
 public void run() {
 System.out.println("Print from thread: 	" +
 Thread.currentThread().getName());
 }
 };

 final Thread applicationThread = new Thread() {
 public void run() {
 System.out.println("Hello from thread: 	" +
 Thread.currentThread().getName());
 display.syncExec(print);
 System.out.println("Bye from thread: 	" +
 Thread.currentThread().getName());
 }
 };

 button.addSelectionListener(new SelectionListener() {
 public void widgetDefaultSelected(SelectionEvent e) { }
 public void widgetSelected(SelectionEvent e) {
 applicationThread.start();
 }
 });
 ...

In the preceding code, when the button is clicked, a new thread applicationThread starts. The applicationThread first prints a message with the name of the running thread (applicationThread). The applicationThread then calls display.syncExec with the Runnable object. syncExec blocks applicationThread until the run() method of the Runnable instance has been executed by the UI thread. The UI thread executes the run() method of the Runnable object and syncExec returns. The applicationThread continues to execute and prints another message with the name of the executing thread (applicationThread). The output is as follows.

 Hello from thread: applicationThread
 Print from thread: main
 Bye from thread: applicationThread

Note that "applicationThread" is the name of the applicationThread thread, and "main" is the name of the main thread of the program, (the UI thread).

For the complete program, please refer to SyncExecExample.java.

timerExec

Syntax:

 public void timerExec(int milliseconds, Runnable runnable)

The timerExec method behaves similarly to asyncExec, except that the Runnable instance is invoked by the UI thread after the specified number of milliseconds has elapsed. If the number of milliseconds is set to less than zero, the Runnable instance is never executed.

When to Use It

Use timerExec when UI access should be delayed for a certain amount of time and the code in the non-UI thread does not depend on the return value from the UI code.

wake

Syntax:

 public void wake()

If the UI thread is sleeping, any other threads can call this method to wake it. However, in most cases, it is not necessary to call this method. When a Runnable instance is passed to asyncExec, syncExec, or timerExec, the UI thread will be automatically awakened if it is sleeping. Any event queued in the operating system event queue will wake the sleeping UI thread also. Except for the above thread-safe methods, you should not try to access UI calls from non-UI threads. Most other methods are not thread-safe, so they cannot be accessed directly from non-UI threads.

The Event Model

This section introduces event model and event listeners in detail.

SWT uses the observer design pattern based event model, as other modern MVC frameworks such as SmallTalk and Swing do. The observer design pattern defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically, as shown in Figure 4-3.

image from book
Figure 4-3

The observer pattern applied in the SWT event model is presented in Figure 4-3. An observer pattern–based event model has several components:

  • Event sources (instances of Widget and Display): Widgets or displays that generate events
  • Event listeners (objects implementing interface Listener and instances of EventListener): Listeners to particular types of events
  • Event listener registration process (for example, Display.addListener, Widget.addListener, Button.addSelectionListener): The process that a listener registers as an interested party to a particular type of event that can occur in specific event sources
  • Event data: A data representation of an event that occurs, allowing listeners to react based on the event context
  • Event delivery process: The process by which events are delivered to interested listeners. SWT handles this internally.

Events, Listeners, and the Listener Notification Process

When the user clicks a mouse button or presses a key on the keyboard, an event occurs. Any object can be notified of the event provided that the object implements the appropriate interface and registers as an event listener on the event source. Multiple listeners can be registered to be notified of events of a particular type from a particular source. In SWT, only Widgets and Displays can have event listeners.

The following code demonstrates usage of a listener:

 Button button = new Button(shell, SWT.PUSH);

 button.addSelectionListener(new SelectionListener() {
 public void widgetDefaultSelected(SelectionEvent e) {
 }
 public void widgetSelected(SelectionEvent e) {
 System.out.println("Button pushed.");
 }
 });

In preceding code, a Listener object (instance of the anonymous inner class) is registered to the button for the selection event. This listener implements the SelectionListener interface. When the button is pushed, a selection event occurs and the event listener is invoked (the method widgetSelected of the listener instance is called).

You have seen how to create an event loop to read and dispatch events in the first section of this chapter. So how exactly does the Display instance read and dispatch events?

In the event loop, the method readAndDispatch plays the most important role. This method is constantly invoked in the event loop. When readAndDispatch is invoked, it first checks the thread event queue for a posted event. If no events are queued, this method is returned. Otherwise, an event is popped out and passed to various listeners. First, all the Display instance's filter listeners (the filter listeners will be notified when an event of a given type occurs anywhere in the display — any shell, widget, and controls based on the display) to this type of event will be notified. After that, listeners (of the event source) to this type of event will be called. After all interested listeners have been properly called, this method will return. Figure 4-4 is the flow chart of the method readAndDispatch.

image from book
Figure 4-4

In this process, only two sets of listeners are notified. One set includes the Display instance's filter listeners to the event type, and the other set comprises the event source's (the widget or display that generates the event) listeners to the event type. If you need to listen for an event and react accordingly, your listener must be put in one or both of the two sets of listeners. More details on Display filter listeners and Widget listeners are covered later in this section.

The following code sample illustrates the listener notification process:

 class SimpleListener implements Listener{
 String name;

 public SimpleListener(String name) {
 this.name = name;
 }

 public void handleEvent(Event e) {
 System.out.println("Event: [" + EventUtil.getEventName(e.type) + "] from "
 + name + ". 	Current Time (in ms): " + System.currentTimeMillis());
 }
 }
 ...
 Display display = new Display();
 Shell shell = new Shell(display);
 shell.setText("Left click your mouse");
 shell.setSize(200, 100);
 shell.open();

 shell.addListener(SWT.MouseDown, new SimpleListener("Shell mouse down listener"));

 display.addFilter(SWT.MouseDown,
 new SimpleListener("Display mouse down Listener"));

 display.addFilter(SWT.MouseUp,
 new SimpleListener("Display mouse up Listener"));

In the preceding code, the first few lines define the SimpleListener class, which implements the org.eclipse.swt.widgets.Listener interface. The only method, handleEvent, has been implemented. This method simply prints out the name of the event and the system current time. Next, the display and the shell are created. A SimpleListener is then added to the shell to listen for the mouse down event. A mouse down filter listener is registered with the display instance. A mouse up filter listener is added to the display.

After the user launches the program, the shell will be displayed. If the user clicks one of the mouse buttons on the shell, a mouse down event is generated followed by the mouse up event. (A click comprises two actions: mouse button down and mouse button up.) The readAndDispatch method first reads the mouse down event because the event queue is on a First-In First-Out (FIFO) basis. The mouse down filter listener of display will be called first, and then the mouse down listener of the shell will be invoked. The mouse up event is then dispatched in a similar way. This time, because no mouse up listeners have been registered to the shell, only the mouse up filter listener of display install will be invoked.

The output is as follows:

 Event: [mouse down] from Display mouse down Listener. Current Time (in ms):
 1067275267676
 Event: [mouse down] from Shell mouse down listener. Current Time (in ms):
 1067275267676
 Event: [mouse up] from Display mouse up Listener. Current Time (in ms):
 1067275267746

Note that, for the same event, filter listeners registered to displays will always be invoked before any widgets' listeners get called. See ListenerTest.java for the complete program.

SWT provides two kinds of event listening mechanism: typed and untyped. A typed listener can be used to listen for only one particular typed event. For example, in the code at the beginning of this section, SelectionListener is a typed listener for event SelectionEvent. Untyped event listeners offer a generic, low-level mechanism to listen for events. You can use either or both of them to handle events effectively.

Untyped Events and Untyped Event Listeners

An untyped event listener can be registered to listen for any type of event. SWT provides only two classes for untyped event listening: an interface Listener and an event class named Event. Both classes are in the org.eclipse.swt.widgets package. There is only one method that needs to be implemented for an untyped listener in the Listener interface:

 public void handleEvent(Event event)

This method will be called by the UI thread when the untyped listener is notified. Note that an Event object will be passed to the handleEvent method. The event object provides a description of a particular event. Some properties (fields) of the event object include:

  • Button: The button that was pressed or released
  • character: The character represented by the key that was typed
  • detail: The event specified field
  • display: The display where the event occurred
  • gc: The graphics context to use
  • height: The height of the bounding rectangle of the region that requires painting
  • item: The item that the event occurred in (possibly null)
  • keyCode: The key code of the key that was typed
  • time: The time that the event occurred. For example, Event.time returns the time in milliseconds that the event occurred. It is important to note that the time may not necessarily be expressed in the conventional way, i.e., the difference, measured in milliseconds, between the current time and midnight, January 1, 1970 UTC. On Microsoft Windows platforms, the time is an integer that specifies the elapsed time, in milliseconds, from the time the operating system was started to when the event occurred.
  • type: The type of event
  • x: The x offset of the bounds that requires repainting or the x coordinate of the pointer
  • y: The y offset of the bounds that requires repainting or the y coordinate of the point
  • type: All kinds of untyped events are represented by the same Event class, and the only way to tell the type of a particular event is through its Event.type (public int type) field. Class SWT defines all the event types as constant integers. For example, if a key is pressed down, the Event.type of the generated event will be SWT.KeyDown. Many other properties of Event are event type–dependent. For instance, for a SWT.MouseDown type of event, event properties such as x, y (coordinates of pointer) prove very useful; however, contents of properties such as character are undefined.
  • item: Another very important property of Event is Event.item (public Widget item), which gives the widget that the event occur in. If you register an untyped event listener to multiple widgets for a particular type of event, you have to check this field for the event source to react accordingly.

An untyped listener must be registered to displays or widgets to listen for interested events. In the Display class and the Widget class, the methods used to add and remove an untyped event are:

 public void addListener(int eventType, Listener listener)
 public void removeListener(int eventType, Listener listener)

All kinds of methods for adding or removing event listeners are accessible only from the UI thread. NonUI thread can use only Display.asyncExec, Display.syncExec, or Display.timerExec methods to delegate the operation to the UI thread.

You can specify the type of event listened to in the first argument eventType. The second argument listener is the untyped listener to be added or removed from the widget or display for the specified event type. The Display class provides the following additional methods to add and remove filter listeners:

 public void addFilter(int eventType, Listener listener)
 public void removeFilter(int eventType, Listener listener)

All filter listeners will be notified when an event of the specified type occurs anywhere in the display. In the last section, you saw an example using filter listeners. Filter listeners can be useful if you need to implement a special mechanism such as logging all events.

I will use a sample application to show how to implement an untyped listener and register it to a widget for a particular event.

Figure 4-5 shows the initial shell displayed. When the mouse pointer moves onto the text (a Label object) in the center, the label is highlighted, as you can see in Figure 4-6. When the cursor moves outside the label, its background is restored (see Figure 4-7).

image from book
Figure 4-5

image from book
Figure 4-6

image from book
Figure 4-7

The code for the sample application is as follows:

 public class MouseEnterExit1 {
 Display display = new Display();
 Shell shell = new Shell(display);
 Label label = new Label(shell, SWT.SHADOW_IN | SWT.CENTER);
 Listener listener = new MouseEnterExitListener();

 public MouseEnterExit1() {
 label.setText("Point your cursor here ...");
 label.setBounds(30, 30, 200, 30);

 label.addListener(SWT.MouseEnter, listener);
 label.addListener(SWT.MouseExit, listener);

 shell.setText("Move your cursor to test ...");
 shell.setSize(260, 120);
 shell.open();


 while(! shell.isDisposed()) {
 if(! display.readAndDispatch()) {
 display.sleep();
 }
 }

 display.dispose();
 }

 class MouseEnterExitListener implements Listener {
 public void handleEvent(Event e) {
 switch (e.type) {
 case SWT.MouseEnter :
 display.syncExec(new Runnable() {
 public void run() {
 label.setBackground(
 display.getSystemColor(SWT.COLOR_YELLOW));
 label.setText("Cursor enters the label");
 }
 });

 break;
 case SWT.MouseExit :
 display.syncExec(new Runnable() {
 public void run() {
 label.setBackground(
 display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
 label.setText("Cursor leaves the label");
 }
 });

 break;
 }
 }
 }


 public static void main(String[] args) {
 new MouseEnterExit1();
 }
 }

An inner class named MouseEnterExitListener implementing the Listener interface is defined. This class monitors two types of events: SWT.MouseEnter and SWT.MouseExit. When the mouse pointer enters a widget, an event of type SWT.MouseEnter occurs. Similarly, when the mouse pointer leaves a widget, an event of type SWT.MouseExit occurs. If this type of listener is notified, its method handleEvent is called. This method will first check its event type. If the event is of type SWT.MouseEnter, the label's background will be changed using SyncExec. In case of SWT.MouseExit, the label's background will be restored. If the event is not of type SWT.MouseEnter or SWT.MouseExit, nothing happens.

An instance of class MouseEnterExitListener is created at the beginning of the constructor. Remember that an event listener is never notified unless it has been registered to one or more event sources, so you register this untyped event listener to the label for both events of SWT.MouseEnter and SWT.MouseExit. Finally, the event loop is set up to read and dispatch events.

You can try the program yourself by running MouseEnterExit1 (source code: MouseEnterExit1.java).

The untyped event listener is very similar to the callback window procedure in native UI programming. Both use the message type as an argument in a big switch statement with individual messages handled by separate case statements. Those with native UI programming experience may easily adopt the untyped event handling mechanism; however, Swing developers may have some trouble because there are no such generic event listeners in Swing. The good news is that SWT also provides a rich set of typed event listeners, as Swing does.

Typed Events and Typed Event Listeners

Typed event listeners are event-specific listeners. A typed event listener listens only for one particular typed event. Common typed listeners and typed events are located in the org.eclipse.swt.events package. Some special typed listeners and typed events scatter over packages org.eclipse.swt.dnd, org.eclipse.swt.custom, and org.eclipse.swt.graphics. A typed event represents a particular set of similar events. (The term "events" here refers to low-level events as discussed in untyped event listeners. To avoid confusion, when I say events without type, I mean low-level events, otherwise, typed events.) A typed listener listens for a particular typed event only. For instance, KeyEvent is a typed event comprising low-level key-pressed events and key-released events. Correspondingly, the typed listener for KeyEvent, KeyListener listens for the KeyEvent exclusively.

In the last section, you saw the Event class with a long list of fields describing all kinds of information for every type of low-level event. For a typed event, only a very small set of essential properties exclusive to that particular typed event are included as its fields. All typed events are a direct or indirect subclass of the TypedEvent class. TypeEvent, a subclass of java.util.EventObject, provides the following useful public fields:

  • data [public Object data]: Field for application use
  • display [public Display display]: The display where the event occurred
  • time [public int time]: The time that the event occurred
  • widget [public Widget widget]: The widget that generated the event. The same object will be returned when getSource() is called.

Each subclass of TypedEvent provides some extra fields describing itself. For example, KeyEvent has a character field representing the key that was typed.

All typed listeners are a subclass of java.util.EventListener. Examples of typed listeners are KeyListener, MenuListener, and FocusListener. If a widget is capable of generating a particular typed event, methods must exist to add or remove typed listeners for the typed event in its class definition. For example, a Button can generate SelectionEvent. It has addSelectionListener and removeSelectionListener methods to add and remove SelectionListener typed event listeners:

 public void addSelectionListener(SelectionListener listener)
 public void removeSelectionListener(SelectionListener listener)

Because a typed listener listens for a fixed set of low-level events, when adding a typed listener, the declaration of the event types to be listened for is not required.

In the preceding section, we used an untyped listener to listen for menu events. Now we will rewrite the label highlighting sample application using a typed listener:

 class MouseEnterExitListener implements MouseTrackListener {
 public void mouseEnter(MouseEvent e) {
 display.syncExec(new Runnable() {
 public void run() {
 label.setBackground(
 display.getSystemColor(SWT.COLOR_YELLOW));
 label.setText("Cursor enters the label");
 }
 });

 }

 public void mouseExit(MouseEvent arg0) {
 display.syncExec(new Runnable() {
 public void run() {
 label.setBackground(
 display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
 label.setText("Cursor leaves the label");
 }
 });
 }


 public void mouseHover(MouseEvent arg0) {
 // do nothing
 }
 }

 ...

 MouseTrackListener listener = new MouseEnterExitListener();

 ...

 label.addMouseTrackListener(listener);
 ...

First, an inner class implementing the MouseTrackListener interface is defined. The MouseTrackListener listens for mouse track events including low-level events: mouse pointer passing into the control area, mouse pointer passing out of the control area, and mouse pointer hovering over the control. There are three methods defined in the class corresponding to three types of low-level event. An instance of the MouseExterExitListener is created and then registered to the label.

The complete list of code is available in LabelHighlighting2.java. When you run the code, you will find that the application behaves exactly the same as the one using untyped listeners in the last section. The listener notification mechanism in LabelHighLighting2 is very similar to that of LabelHighlighting. When the mouse pointer moves into the control area, the listener is notified. There are three methods declared in the MouseTrackListener interface, so which method or which set of methods will be called? Because the low-level event is the mouse passing into the control area, as expected, only the method mouseEnter will be called, with the MouseEvent object as the only argument. More details about typed events and listeners for particular widgets appear in subsequent chapters.

If you do not want to implement each method in a typed listener interface, you can simply extend its corresponding adapter class. For example, in LabelHighlighting2.java, you can rewrite the MouseEnterExitListener, extending the MouseTrackAdapter class and override only the mouseEnter and mouseExit methods. See LabelHighlighting3.java for the complete code.

We've used both untyped and typed event listeners to accomplish the same task. Both models are very easy to understand and to code for. Which model should you choose, untyped or typed? Actually, either model will be okay because the performance of both models is almost the same. However, most of my colleagues who worked with Swing before prefer the typed model, and they also find that it is easy to read code with the typed model. However, if you plan to port your applications to embedded platforms such as Windows CE, you should use the untyped event model. On embedded platforms, a typed listener API may be removed from SWT due to the constraints of limited memory and storage. Whichever model you choose, keep in mind that it is not a good idea to mix them. It makes your code hard to maintain.

Summary

This chapter discusses SWT event handling and the threading mechanism. SWT follows the threading model supported by the native platform. The application manages the event loop and dispatches events in the UI thread. You can easily use the Display class to create an event loop. Multithreading in SWT can be realized by using various methods provided by the Display class. Non-UI thread can access only UI objects through the asyncExec and syncExec methods. The chapter then covered the event model and event dispatching process. Typed and untyped listeners can be registered to widgets to listen for and react to corresponding events. An untyped event listener can be registered to listen for any type of events; however, a typed event listener can be registered to listen only for a certain type of event.

You should now have a general understanding of SWT/JFace. The next chapter introduces basic SWT widgets in detail.



Professional Java Native Interfaces with SWT. JFace
Professional Java Native Interfaces with SWT/JFace (Programmer to Programmer)
ISBN: 0470094591
EAN: 2147483647
Year: 2003
Pages: 147

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net