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.
This section first introduces the native event handling mechanism, and then it shows how SWT handles events with Display objects.
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 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:
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.
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:
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:
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.
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.
Multithreading helps to make UI responsive. In SWT, you can use various methods provided by the Display class to execute tasks in separate 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.
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.
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();
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.
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.
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:
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.
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.
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:
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).
Figure 4-5
Figure 4-6
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 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:
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.
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.
Part I - Fundamentals
Part II - Design Basics
Part III - Dynamic Controls
Part IV - Application Development