HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML

The Standard Widget Toolkit (SWT) is a Java class library that allows you to create native user interfaces. It's designed to provide efficient, portable access to the underlying facilities of the operating system on which it's implemented. SWT uses native widgets wherever possible, giving an SWT program a native look and feel and a high level of integration with the desktop. In addition, SWT includes a rich set of controls such as tree, table, and tab folder. This article introduces SWT by describing some of the basic concepts and classes.

Hello World: A Simple SWT Program
The easiest way to learn SWT is to study a simple example. The following code shows a complete SWT program that creates and displays a new window on the desktop with "Hello World" in the title bar. Figure 1 shows the result of running this program on Windows XP.

Figure 1

1 import org.eclipse.swt.*;
2 import org.eclipse.swt.graphics.*;
3 import org.eclipse.swt.widgets.*;

4 public class HelloWorld {
5 public static void main(String[] args){
6 Display display = new Display();
7 Shell shell = new Shell(display);
8 shell.setText("Hello World");
9 shell.setSize(200,100);
10 shell.open ();
11 while (!shell.isDisposed()) {
12 if (!display.readAndDispatch())
13 display.sleep ();
14 }
15 display.dispose ();
16 }
17 }

Lines 1-3: SWT is divided into a number of packages, each modeling a different area of the user interface. All packages begin with the prefix org.eclipse.swt. The package org.eclipse.swt itself contains classes used by every package in SWT, such as constants and exceptions, while org.eclipse.swt.widgets contains only the widget classes. We didn't actually need to import org.eclipse.swt itself or org.eclipse.swt.graphics in this example because we didn't use any classes from these packages. However, any significant SWT program will use these three packages and this is a good place to introduce them.

Line 6: Every SWT program must create a Display. Typically, the first line of an SWT program creates a display, which SWT uses to establish the connection with the underlying platform window system. The class Display is very important in SWT. For readers familiar with the X Windows system, an SWT Display is equivalent to an X Windows Display.

Lines 7-9: These lines create a shell, and set the title and the size. Top-level windows are represented by the class Shell and are created on a Display. It's not necessary to set either the title or the size of a shell when it's created. A shell can have an empty title and the window system will assign the initial position and size using the standard algorithm of the desktop.

Line 10: A shell is invisible when it's created. The method open() makes the shell visible, brings it to the front on the desktop, and sets user input so when keys are typed, they go to the shell.

Lines 11-14: Like most modern window systems, SWT supports an event-driven user interface. This requires an explicit event loop that repeatedly reads and dispatches the next user interface event from the operating system. When there are no more events to dispatch, the program goes to sleep waiting for the next event, yielding the CPU to other programs on the desktop. The loop terminates when the programmer decides that the program has ended, typically when the application's main window, in our case the shell, closes. It's up to the application programmer to decide the exit condition for the event loop, as only he or she knows when the program is finished.

Line 15: This line disposes the Display. Strictly speaking, it's not necessary to dispose the Display as long as the program exits to the operating system. In all modern operating systems and on every platform SWT supports, exiting to the operating system releases any resources that were acquired by the process, including the resources acquired by SWT through the Display. So why include this line in the sample program? Not only is it good form to dispose of resources that you acquire, it can help you find places where you are not explicitly disposing resources, especially when using third-party or operating system-level tools that look for leaks.

As long as the window remains open, the event loop keeps Java alive and the SWT program keeps running.

Widgets: Shells and Controls
A Control is a user-interface element that's contained in a shell. Controls are common in all user interfaces. Buttons, labels, trees, and tables are all controls and users are familiar with these from other programs on the desktop.

Taking a bottom-up view of the world, every control has a parent and this parent can be another control, called a Composite. Shell is a subclass of Composite, so shells can have children. The parent of a shell is the display or another shell. Stated another way, this time from the top down, a display contains a list of top-level shells, where each shell is the root of a tree containing subtrees composed of composites and controls. Composites contain other composites, allowing the tree to have an arbitrary depth. When the child of a shell is another shell, the child is commonly called a dialog shell. A dialog shell floats on top of its parent shell.

It's important to understand the difference between the runtime and inheritance hierarchy of controls. The hierarchies constructed at runtime are containment hierarchies. A Shell is not a subclass of Display, but a display can contain a list of shells. Figure 2 shows the inheritance hierarchy of Display, Shell, Button, and Group, along with the containment hierarchy that's built at runtime from instances of these classes.

Figure 2

The concept of a containment hierarchy is an important one in SWT, so much so that it's reflected in the constructor of almost every object.

Controls are created using a constructor that takes the parent and a style. Styles are bit values used to represent operating system features that are create-only, such as multi- or single-line text edit capability, scroll bars, or borders. Because these operating-system features cannot be changed after creation, the style of a control cannot be altered after instantiation. Style bits provide a compact and efficient method of describing the create-only properties of a control. An alternative implementation would be a large number of constructors in each class containing an arbitrary list of boolean parameters, each parameter in an arbitrary order.

As expected, you can combine styles by using a bitwise-or operation. All styles are defined as constants in the class org.eclipse.swt.SWT. For example, the following code fragment creates a multiline text control that has a border and horizontal and vertical scroll bars:

new Text(composite, SWT.MULTI |
SWT.V_SCROLL | SWT.H_SCROLL |
SWT.BORDER);

To use the default style of a control, the style value SWT.NONE is specified in the constructor. The list of the style constants that correspond to each control is described in the documentation for the control.

A list of the basic SWT controls is shown in Table 1.

Table 1

Events
So far we've described how to create controls and set various properties, configuring them either in the constructor or through methods that are public APIs. These are all operations performed on a control by the programmer. However, controls are also visual user-interface elements. What happens when the user presses a button or selects an item from a list?

We have already described the display and its role in the implementation of the event loop, reading and dispatching events from the operating system. In fact, without an event loop, an SWT program will terminate and return to the operating system when main() terminates. The event loop plays a critical role interacting with the user. Events are generated by the user, dispatched by the event loop, and application code is invoked in response. Application code registers interest in events by adding an event listener.

A final word about events: they are synchronous (note: don't confuse this term with the Java keyword "synchronized"). This means that your code will never be interrupted to handle an event. Events are only delivered when you call an SWT method. In the case of the event loop, calling readAndDispatch() allows any events that the user has generated to be delivered to your program. If you don't call readAndDispatch(), you'll never see events from the user; your program will appear hung, and the user will eventually kill it.

Listeners: Typed and Untyped
SWT has two ways to listen for events: typed and untyped.

A typed listener is exactly the same as a JavaBean listener. Typed listeners and their events are found in the package org.eclipse.swt.events. For example, to listen for a selection event in a push button, application code uses addSelectionListener(SelectionListener listener). When the user selects the button, widgetSelected(SelectionEvent event) is called from the event loop. SelectionListener is an interface. If there's more than one method defined in the listener interface, an adapter class exists that provides default no-op implementations of the listener methods. This means that you could use a SelectionAdapter to determine when a button has been selected instead of a SelectionListener. Listing 1 adds two typed listeners to a button, listening for selection and a change in focus, respectively.

Untyped listeners provide a generic, low-level mechanism to listen for any event. There are only two classes involved: a single generic interface called Listener and a single event class called Event. These two classes are found in org.eclipse.swt.widgets. Instead of following the JavaBeans pattern that requires a specific method to add each kind of listener, untyped listeners are added using only one method, addListener (int type, Listener listener). The type argument specifies the event you are interested in receiving. Type arguments are constants in the class SWT and are mixed case by convention. For example, SWT.Selection is an event type constant. All other constants in SWT are uppercase.

The Listener interface has a single method, handleEvent(Event event), that's called when the event occurs. One possible way to listen for untyped events is to implement a single listener and use the event argument to determine the type of event that occurred (see Listing 2).

The trade-off between the two listener models is one of speed and space. Using untyped listeners, it's possible to minimize the number of classes and methods used to listen for events.

As well as adding listeners, it's also possible to remove them, but this is generally unnecessary. Listeners are garbage-collected when a control is disposed, providing there are no other references to the listener in the application program.

Table 2

Disposing Controls
An SWT control is explicitly disposed when it's no longer required. Sometimes the dispose operation is initiated by the user. For example, the user may click on the close box of a shell. More often, a control is no longer required by the programmer and can be given back to the operating system. Controls are disposed using the dispose() method.

When the dispose() method is called, the underlying window system resources are released, giving memory and other operating system resources, such as handles, back to the operating system. When the root of a control hierarchy is disposed, the children are automatically disposed. Therefore, disposing a shell disposes the children. Similarly, disposing the display, disposes all the shells that were created on the display.

When a control is disposed, either explicitly from dispose() or implicitly when an ancestor is disposed, a SWT.Dispose event is sent. The dispose event is a good place to dispose graphics resources that you have created for that control. If you try to access a control that has been disposed, the operating system won't crash. Instead, SWT will raise an exception because it's a programming error to access a disposed control.

If you never dispose a control, but instead hide it or never make it visible, eventually the operating system will run out of resources. In practice, it's hard to write code that does this by accident. For one thing, programmers generally don't lose track of their controls because they require them to present information to the user. Because controls are usually visible, it's obvious when there are too many on the screen.

Positioning and Sizing Controls
Each control is sized and positioned relative to its parent using the methods setLocation(int x, int y), setSize(int width, int height), and setBounds(int x, int y, int width, int height). The following code fragment positions and sizes three different controls:

control1.setLocation(12,23);
control2.setSize(100,100);
control3.setBounds(10,10,200,100);

Sizing and positioning of a control always refers to the entire control, not just the contents. For example, the size and position of a shell includes the window trim and the menu bar. The rectangle that contains the entire control is called the bounds of the control.

The client area of a control is the smaller rectangle within the bounds that the control uses to show its contents. For example, the client area of a text widget is the area where the lines of text are drawn, not including the scroll bars or borders. Child controls are always positioned relative to the client area of the parent, not its bounds. The client area of a control is never explicitly resized or positioned through API. Rather, setting the bounds of a control implicitly sets the client area. Figure 3 shows the bounds and client area of a text control.

Figure 3

Controls can compute their preferred size using the method computeSize(int wHint, int hHint). This method doesn't actually set the size of a control but rather returns a size that's a good default for the control. When the constant SWT.DEFAULT is used for both the width and height hint arguments, the control computes the smallest size necessary to completely show its contents. When a width or height value is used to compute the preferred size instead of the constant SWT.DEFAULT, the control computes its smallest size based on the hint arguments. For example, specifying a width of 100 and height of SWT.DEFAULT when computing the preferred size of a label that wraps is equivalent to asking the question: "If the label were to be resized to be 100 pixels wide, how tall would the label need to be to show the contents, wrapping lines of text as necessary?"

Use the pack() method of a control and set its bounds to the preferred size. Using pack() is equivalent to computing the preferred size and then using the result to set the size of the control.

As we've seen, the preferred size of a control depends on its contents. The preferred size can be queried from a control and used to position it within its parent. Writing code to explicitly size and position each control can be quite tedious and error prone. In addition, this code must almost always run when the parent is resized, so that the children will remain visible as the user resizes the window. SWT provides layout classes to compute the size of the controls and automate positioning of children when the parent is resized.

Layouts and Layout Data
Layouts are found in the package org.eclipse.swt.layout. Layouts are used to encode a positioning algorithm that's applied to the children of a composite whenever it's resized. The method setLayout(Layout layout) is used to set a layout into a composite. The details of the algorithm and API used to configure a layout are specific to each individual layout. Layout strategies are usually quite different and are built around different concepts, causing each layout to have a unique API. By convention, layout classes end with the suffix "Layout".

Some layouts allow each child to supply data that's specific to the positioning of the child, in addition to the preferred size, giving finer control over positioning and sizing. This is done using the method setLayoutData(Object layoutData) on each control. Layout data and its API are very specific to the layout used by the composite. For maximum flexibility, layout data can be of any type but must match the type that is expected by the layout class. By convention, layout data classes end with the suffix "Data".

It's important to note that layouts are set on the parent while layout data is set on the children.

Two of the simplest layouts are FillLayout and RowLayout. More advanced positioning can be achieved using GridLayout and FormLayout. These last two layouts are more complicated but offer increased flexibility. The following section provides a quick overview of these four layout algorithms. In some situations it may be necessary to provide a customized layout algorithm, which can be achieved by subclassing the abstract class org.eclipse.swt.widgets.Layout.

FillLayout
FillLayout is intended to position a single child so that the child fills up all the available client area of a composite. FillLayout has no corresponding layout data. Figure 4 shows a shell using FillLayout with a single text control. The text control occupies all the available space in the shell. Currently, FillLayout provides no margins or spacing between controls.

Figure 4

If more than one control is added, the space is divided evenly between each of the controls, either vertically or horizontally. Figure 5 shows a horizontal FillLayout in a shell with three controls where the size of each is the height of the shell's client area and each control takes up one-third of the width.

Figure 5

In practice, you typically don't add more than one child to a FillLayout because it's not really intended to be used this way. Instead, you might use a RowLayout.

RowLayout
RowLayout lays out controls in a single row, either horizontally or vertically. Details such as wrapping of controls, margins, and the spacing between controls can be configured.

RowLayout has a corresponding layout data that's rarely used, called RowData. Normally, RowLayout uses SWT.DEFAULT when calling computeSize() to get the preferred size of a control. This happens when no RowData is provided for the control. When RowData is provided, it's used to specify values in place of SWT.DEFAULT, allowing you to explicitly set the size of a control within a RowLayout.

Unlike FillLayout, RowLayout wraps controls by default. Figure 6 show a RowLayout that has three controls on it. Although the label and button fit on the same line, the progress bar is placed beneath them because the shell has been resized so there's not enough space to show all three controls on the same row.

Figure 6

RowLayout is more flexible than FillLayout and is generally used to lay out rows of buttons. In practice, most windows are a lot more complicated and require more flexible layout algorithms such as GridLayout.

GridLayout
GridLayout divides a composite into a grid of rectangular cells. The number of columns is critical and determines the final number of rows. This depends on the number of children in the composite. By default, controls are placed into cells in the order they are created. As each child control is created, it's placed in the next available column, which, depending on the number of columns and the span of the control, may be on a new row. By default each control occupies one column.

GridLayout has corresponding layout data, called GridData, that's almost always used by the programmer. Using a GridData, the programmer can control the number of columns to be spanned by a control, the anchoring, the alignment against the edges of the cell, and the width and height values to be passed to computeSize().

Figure 7 shows a GridLayout shell with two columns and three children that are two group boxes and a scale. The group boxes occupy the first and second columns, while the scale occupies both columns on the next line. The scale uses a GridData with a horizontal spanning of two, so the cell it will occupy will include both columns. To force the scale to fill the entire area of the cell, a horizontal fill property was also specified.

Figure 7

The "Gender" group box uses GridLayout with one column to place its two radio button children on separate lines. The "Likes" group box is using a GridLayout with two equal width columns. Its first two check buttons are placed in columns one and two on the first row, and the next two are placed on the second row. Because GridLayout's "makeColumnsEqualWidth" property is true, this makes "Music" and "Art" have the same width as the two wider buttons.

Using GridLayout, it's possible to construct sophisticated user interfaces and achieve just about any positioning and sizing requirement. Many user interfaces, especially those found in dialogs, are inherently grid-based, making GridLayout a good choice. For user interfaces that are not grid-based, FormLayout can be used.

FormLayout
A FormLayout allows you to specify the position of a control in terms of its edges. Edges of a control are attached to a position in the parent. This can be an absolute position, a fraction, or a percentage of the width or height of the parent. Edges can also be attached to the edge of another control, giving FormLayout maximum flexibility.

The corresponding layout data class FormData is almost always used by the programmer. Each FormData has fields that represent the top, left, right, and bottom edges of the control. Each field is an instance of a FormAttachment, which specifies the attachment to use for that edge.

The constructor FormAttachment(int numerator, int denominator, int offset) creates a new form attachment that's used to attach the edge of a control to a position in its parent. In Figure 8, the right edge of the list box is assigned the attachment new FormAttachment(4,5,3), attaching it to be 4/5 of the width of the parent plus an extra 3 pixels. Instead of fractions, percentages can be specified.

Figure 8

The constructor FormAttachment(Control control, int offset) creates a new form attachment that's used to attach the edge of a control to the edge of a sibling. In Figure 8, the combo box attaches its left edge to be 5 pixels from the right edge of the slider using the attachment FormAttachment(slider,5). By default, the opposite edge of a sibling is attached, although an alignment value can be provided to allow attachment to another edge. Under rare circumstances, it might make sense to attach the left edge of a control to the center or left edge of a sibling, potentially causing one control to be positioned on top of another.

Figure 8 shows a shell at its preferred size, while Figure 9 shows the same window resized. As the shell resizes, the controls are repositioned appropriately. The size of the list is automatically increased because of its attachments. The left, bottom, and right edges were expressed in terms of the parent, and the size of the parent has been increased. The slider and combo remain at their original positions. The combo, although attached to the slider, remains at the same location because the scale did not move.

Figure 9

As you can imagine, FormLayout is probably the most flexible layout of all. While it's possible to get almost any positioning imaginable using FormLayout, some user interfaces are more naturally expressed using the other layouts. Most SWT user interfaces are built from a combination of forms and grid layouts.

Forcing a Layout
When a composite is resized, its layout will automatically position and size the children based on the new size of the composite. There are times when the information that was used to calculate the position or size of a control can change without the layout being informed. For example, changing the text of a label will alter its preferred size. When this happens, the positioning and sizing calculations of the layout are no longer valid and children need to be repositioned even though the size of the parent has not changed. To force a layout to recalculate the position and size of its controls without resizing the composite, the layout() method is used.

Conclusion
This article has introduced some of the basic principles behind SWT and shows how to create simple windows and controls. One of the design goals of SWT was to create a UI toolkit that has a high level of integration with the operating system. This is achieved by using native widgets and operating system resources. SWT has been ported to a number of different operating systems, giving it wide coverage on the desktop. Part 2 of this article will show how to use the graphics capabilities of SWT, as well as menus, tab folders, trees, and tables.

Resources

  • Eclipse project home page allows you to download Eclipse, including swt.jar, swtsrc.zip, and the SWT shared library: www.eclipse.org
  • The SWT home page contains lots of code samples and on-line documentation: http://dev.eclipse.org/viewcvs/index.cgi/%7Echeckout%7E/ platform-swt-home/dev.html
  • Contains articles describing SWT, written by members of the SWT team: www.eclipse.org/articles/index.html
  • PDF documentation for the Eclipse project including SWT: www.eclipse.org/documentation/pdf/ org.eclipse.platform.doc.isv.pdf
  • Contains useful links for SWT including how to run it on a Pocket PC, a list of frequently asked questions, and how to use SWT with native Active-X integration: http://eclipsewiki.swiki.net/2
  • Gallery that shows many examples of SWT applications running on different operating systems and desktops: http://gallery.livemedia.com.au/

    SIDEBAR

    Obtaining SWT
    SWT was created as part of the IBM-led Eclipse open-source project whose members include Borland, Rational Software, RedHat, and TogetherSoft. Eclipse is an IDE that's implemented using SWT and is available at www.eclipse.org. On each platform, native widgets are used wherever possible. When a widget is not available, SWT provides an API-compatible emulated control.

    As well as the swt.jar that contains the Java code required to run SWT, there's also a shared library that allows SWT to make operating system calls. The name of the library varies between operating systems, and on Windows is named swt-xxx-nnnn.dll, where xxx is the operating system and nnnn is a version number. When you use the Java command to run a program that uses SWT, you need to specify the location of the shared library. Assuming that you've just installed the Eclipse 2.0.1 to run a class HelloWorld contained in helloworld.jar on Windows, the Java command would be:

    -classpath"helloworld.jar";C:
    \ECLIPSE\eclipse\plugins\
    org.eclipse.swt.win32_2.0.1\ws\win32\swt.jar
    Djava.library.path=C:\ECLIPSE\eclipse\plugins\
    org.eclipse.swt.win32_2.0.1\os\win32\x86
    HelloWorld

    The directory used in the VM argument is the one that contains the file swt-win32-2049.dll, although the exact name and location of the library varies between Eclipse versions and operating systems.

    Author Bios
    Steve Northover is the principal architect of SWT. He is the SWT team lead for the Eclipse project and works at the IBM OTI Lab in Ottawa.

    Joe Winchester is a developer for IBM in Hursley, UK, where he works on GUI software tooling for WebSphere.
    [email protected]

    "SWT - A Native Widget Toolkit for Java Part 1 of 2"
    Vol. 8, Issue 4, p. 46

    	
    
    
    
    Listing 1
    
    Button b = new Button(shell,SWT.PUSH);
    b.addSelectionListener(
      new SelectionAdapter(){
        public void widgetSelected(
          SelectionEvent e){
    // button was selected
        } 
    });
    b.addFocusListener(
      new FocusAdapter(){
        public void focusGained(
          FocusEvent e){
    // button got focus
        } 
    });
    
    
    
    Listing 2
    
    Button b = new Button(shell,SWT.PUSH);
    Listener listener = new Listener() {
      public void handleEvent(Event event) {
        switch (event.type) {
          case SWT.Selection:
    	  // button was selected
            break;
          case SWT.FocusIn:
      // button got focus
            break;
        }
      }
    };
    b.addListener(SWT.Selection,listener);
    b.addListener(SWT.FocusIn,listener);
    
     
    

    All Rights Reserved
    Copyright ©  2004 SYS-CON Media, Inc.
      E-mail: [email protected]

    Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.