Client-side GUIs can be powerful and speedy Like many Java programmers, you may have given up on writing client-side window applications. There’s a lot of debate about why client-side Java is out of fashion. But most of it boils down to the fact that Java—until now—could not produce native window applications. With the arrival of SWT you now can put together fast and attractive GUIs using relatively straightforward Java code. SWT applications are largely undistinguishable from natively written applications and, since many concepts will also be familiar to Java Swing programmers, the learning curve should prove to be gentle.This article presents a common use for any windowing toolkit: displaying and editing data-bound widgets.To get up and running with SWT, you need to download the SWT runtime. Check Resources for a link to the latest stable build. For this article, we use Eclipse 3.0 M6, which contains 2 DLLs, or dynamic link libraries, (in the Windows download) and the Java JNI (Java Native Interface) library, swt.jar. To compile or run any SWT code, you must pass a Java property that locates the runtime libraries. On Windows, for example, you could install to C:SWT and set this property like so: -Djava.libary.path=c:SWT However, you must ensure other native libraries in your application, like a database driver, are not using this property.Of course, swt.jar needs to be in the Java classpath. You may also wish to include jface.jar from the Eclipse distribution to take advantage of the JFace library. However, for this article, I do not discuss JFace, and the library is not required. Running from EclipseMuch of the interest in SWT stems from the Eclipse IDE. To easily run or compile SWT applications with Eclipse, use the SWT runtime download mentioned above. From the Project Properties dialog, select Java Build Path and add the swt.jar library, as shown in Figure 1.To run any SWT application, you still must alter the java.library.path and point it to the SWT installation directory. You can do this in the Run dialog box, where you set VM arguments, as shown in Figure 2.A quick tour of SWT basicsBefore you get started with any SWT application, you need to understand some of the basics. Display and ShellThere is a distinct and important line drawn between the Display and Shell classes. Shell represents the Java application window. Display represents the display area, including operating system resources like fonts, images, and graphics contexts. Shell references a chain of windows that includes the main window itself and all its child widgets. Included in that list are all the dialog boxes and other windows that use the main Shell as a parent.The distinction between these two classes point to an important facet of SWT development. Unlike most of Swing and AWT, an SWT application needs to clean up its own resources. No garbage collector is available for these types of resources. And failure to clean up can result in memory leaks, some of them severe. In some operating systems, for example, graphics contexts are limited to less than a dozen instances for the whole system. Failure to dispose of these resources can bring the entire system to a halt.The Shell and its children are constructed such that when the application dies, each parent calls dispose() on its children. However, resources constructed from Display, which include the main Shell itself, must be disposed manually. The lesson to learn is: you need to call the dispose() method on resources absent from the Shell chain of reference. WidgetsAll windowing systems contain windows representing text fields, labels, drop-down menus, radio buttons, push buttons, and so on. What separates SWT from most of the Java variants is that you construct each widget by passing a reference to the parent along with defining style integers. So a Button object is created with this line of code:Button button = new Button (shell, SWT.PUSH); The rest of the object definition is handled by setting properties:button.setText("Properties..."); button.addSelectionListener( this); button.setLayoutData(new GridData(GridData.FILL_BOTH)); LayoutsLike their Swing and AWT cousins, SWT widgets are usually arranged within a Shell using a Layout. Each of SWT’s Layout subclasses define a widget’s left and top properties, and frequently, its width and height. As of this writing, SWT has four layout classes; two of these, FillLayout and RowLayout, prove useful for only the simplest cases. GridLayout and the recent FormLayout handle more complex layouts. As was the case with the early days of Swing, expect some unique third-party Layout subclasses to follow. EventsSWT designers decided to utilize the Swing/AWT pattern of creating Event objects passed from Event sources like widgets to Listener implementations. Each widget maintains its list of interested Listeners. The addListener() adds a Listener to its internal event notification list. When an event like a key-press occurs, the widget calls the appropriate method on each object in its Listener list.An interesting aside in the Event story is the pattern of declaring listeners. Whereas most Swing and AWT implementations favor controller or main classes that implement the appropriate listener methods, SWT favors a pattern of declaring listeners as anonymous inner classes—a style that works well to encapsulate behavior, but also tends to degrade code readability when communication is required between inner and outer classes.First SWT applicationThe primordial “Hello World” application requires less than a dozen lines of code: Display display = new Display(); Shell shell = new Shell(display); Label label = new Label(shell, SWT.NONE); label.setText("Hello world"); label.pack(); shell.pack(); shell.open (); while (!shell.isDisposed ()) { if (!display.readAndDispatch ()) display.sleep (); } display.dispose (); Note that Display and Shell occupy the first two lines of code. But notice that the last four lines require the code to enter a loop that leaves Display in sleep mode until the main Shell is disposed. At that time, Display reawakens and then disposes of itself. Just before it destroys itself, the programmer has an opportunity to clean up any resources not referenced by the main Shell.A read-only data viewNow let’s examine the first of two data entry screens presented in this article: a data table that allows a user to view and select a row for editing. First, let’s discuss the data. For this article, we utilize two classes, PersonData and PhoneData, which provide both the database and methods for accessing the data. As much as possible, data is accessible either as complete rowsets, using string arrays, or as individual items available by row and column index. The class structure is intended entirely for ease of use by the widgets. It also decouples the view from a data access layer, which might contain references to JDBC (Java Database Connectivity) ResultSet objects. Throughout the code, you will see:PersonData data = PersonData.getInstance(); column.setText(data.getHeaders()[0]); //Show one column header items[i].setText(data.getRow(i)); //Set an entire row of data... Each data object has its own finder methods, which populate the two screens presented in this article with actual rows and columns from a simple database: public String[] find(String key){} A read-only view, as shown in Figure 3, with a selectable rowset not only allows relatively easy viewing of data, it also leads the user to highlight a row and select it for editing. The column header permits sizing and may be actionable for modifying the display sorting.A simple widget layoutPresenting read-only data on any screen is relatively straightforward—at least compared to its editing and data update. The approach here is to design a screen consisting of two widgets: a table to display multiple columns and rows, and a button to navigate to an editing screen. The two-widget screen allows us to avoid—at least for the moment—the complexity of choosing and manipulating a layout in SWT.For the two-widget placement, we skip using a Layout class altogether. Because only two widgets are on the form, we can manually set each widget’s exact location by setting both the Shell‘s size and the Widget‘s bounds properties. The latter defines the widget in a Rectangle within the Shell. It works this way: shell.setSize(640,350); //... table.setBounds(0, 0, 640,320); //.. button.setBounds(300,325,50,30); Widget‘s getBounds() method returns a Rectangle that contains a specification for the top, left, width, and height. If you observe the code above, some careful calculation is necessary to lay the widgets out so they don’t overlap each other. Add to that the complexity of repeating the layout calculations if the Shell is resized. When you add several widgets to the mix, the complexity is daunting. That’s when you often defer to a Layout class, which handles all of the many Rectangle‘s bounds for you.A simple layout for this two-widget screen might utilize a RowLayout to set columnar placement. Each widget instance is placed under the previous one. You can avoid all these bounds calculations with this simple setting:RowLayout rl = new RowLayout(); rl.type=SWT.VERTICAL; shell.setLayout(rl); That code provides a similar look without all the arithmetic. The setting’s only problem is that you usually must tweak individual widgets using a RowData object, for example: button.setLayoutData(new RowData(50, 40)); The above setting increases the Button‘s width and height. When you arrive at more complex layouts like GridLayout, you have even more opportunity for tweaking the individual widget’s look.Tabular viewWhile the layout presents only two widgets, the table-type widget with multiple data cells is one of the more difficult constructs, as it normally consists of multiple label and text editing areas. Surprisingly, a read-only SWT Table is relatively straightforward. We can instantiate it just as we would any other widget. Then we add TableItems—consisting of rows of data—to the Table. For the data entry form, we also add a series of TableColumn objects, which display as gray buttons on the first row.The Table is instantiated using a constructor that references the Shell and a series of style integers. You can select among these styles: SWT.SINGLE, SWT.MULTI, SWT.CHECK, SWT.FULL_SELECTION, and SWT.HIDE_SELECTION. We must select one row only, so SWT.FULL_SELECTION and SWT.SINGLE are required. We could also choose SWT.CHECK to provide a small check box at the front of each row. SWT.HIDE_SELECTION will hide the selected row when the focus is lost, so we skip that one: private Table table = null; ... table = new Table(shell, SWT.SINGLE|SWT.FULL_SELECTION); table.setBounds(0, 0, 640,320); table.setFont(font); table.setLinesVisible(true); table.setHeaderVisible(true); We also add grid lines dividing data items and present column headers, which explain the last two code lines above.To set the dataset’s column names, we utilize TableColumn objects that each present small gray, clickable buttons in the header:int width = 0; //Headers for (int i = 0; i < data.getColumnCount(); i ++){ width = data.getColumnWidth(i); TableColumn column = new TableColumn(table, SWT.NONE); column.setWidth((int)(width * 8)); //Characters by pixels... rough guess column.setText(data.getHeaders()[i]); } The PersonData database class provides methods for determining the count of columns in the dataset as well as their names. In the code above, we create a TableColumn object for each dataset column. One interesting feature is the setting of column widths, which prove essential to the correct initial data display. PersonData metadata also provides an array of column widths, representing roughly the count of characters in each database column. However, since the Table features a variable-sized font, the setting of widths is difficult. The column is set by pixel width; the database provides a character count. The column names also influence the display. If you want a column name like “MiddleName” when the column itself is only five characters, some adjustments prove necessary.The Table/TableColumn interaction ameliorates this predicament: you can modify the field width by holding the mouse pointer between TableColumns and dragging them to the right or left.Fitting the data inside the grid lines is a feature of Table/TableItem that really sparkles. TableItem accepts no useful style integer in its constructor, so we pass a reference to the parent widget (which must be a Table instance) and pass SWT.NONE as the style. Each TableItem represents one row of data, and the setText() method handles either a string array or text for a single cell: TableItem[] items = new TableItem[data.getRowCount()]; // An item for each field for (int i = 0 ; i < data.getRowCount(); i++){ items[i] = new TableItem(table, SWT.NONE); items[i].setText(data.getRow(i)); } TableItem also permits setting fonts, images, and background and foreground colors. For ease of development, setting the entire row’s text by passing a string array proves convenient. However, it requires some application design to ensure that a row from java.sql.ResultSet, for example, appropriately converts to a string array.Setting Table’s fontsLike most SWT widgets, you can set font information. That is particularly important for the Table, since the default font may be too large or small for the viewing area. While setting fonts proves easy enough, their care and management harkens back to my earlier discussion of resource cleanup. Fonts, like images, colors, and graphics contexts are system resources. Like all Shell and Shell widgets, fonts must be disposed or a memory leak may result. Unfortunately, the developer must code the disposal, since no mechanism handles it.Disposable objects are not always obvious. A clue to their nature is their constructors. For example, Font requires a Display object:Font font = new Font(display,fd); The display reference indicates that the underlying OS generated the resource. Once you have figured that out, you can dispose of objects like Fonts after the main Shell itself has been disposed: while (!shell.isDisposed ()) { if (!display.readAndDispatch ()) display.sleep (); } for (int i = 0; i < fontResources.size(); i++){ ((Font) fontResources.get(i)).dispose(); } display.dispose(); In the code above, we maintain a list of Font objects that can be registered as they are built and then disposed as the program quits.Now that you are fully warned about this disposal issue, a peculiarity about how fonts are constructed emerges. Fonts are built by passing a FontData[] array in the constructor. FontData contains information on width, name, and style. On some operating systems, like Windows, only one actual FontData specification is returned or set, while others return an array. The SWT designers decided to standardize with the array. An example of FontData follows:FontData[] fd = shell.getFont().getFontData(); for (int i = 0; i Font font = new Font(display,fd); Buttons and eventsThe SWT Button class represents numerous widget types, some of which hardly resemble Buttons. The classic clickable push button is specified in the constructor with a SWT.PUSH style integer. You can also request SWT.RADIO, SWT.CHECK, SWT.TOGGLE, and SWT.FLAT.Another style, SWT.ARROW, ignores any caption and provides a small up or down arrow (you need to OR SWT.ARROW with SWT.UP, SWT.DOWN, SWT.RIGHT, or SWT.LEFT). This proves useful when creating a spinner or navigator combination.Finally, you can align text by adding SWT.LEFT, SWT.CENTER, or SWT.RIGHT. To combine all the integers, you must OR integers in the constructor’s last parameter:button = new Button(shell, SWT.FLAT|SWT.LEFT); The code above gives you a flat button with text aligned on the left.The code below provides a button you are probably most familiar with—centered caption and clickable.private Button button = null; ... button = new Button(shell, SWT.PUSH); button.setBounds(300,325,50,30); button.setText("Details...") ; You create a Button so the user can click and perform a task. On the DataGrid form, the user expects to see another form containing more details about a selected row of data. As with other Java implementations, SWT requires that you add a Listener implementation class to the Button‘s internal listener list. Button has a addSelectionListener() method. The class you add must implement SelectionListener and do something in its widgetSelected() method.One design style the SWT designers seem to adore is the use of anonymous inner classes as Listeners, which allows the logic to occur rather close to the widget itself. For brief methods that occupy one line of code, this style works well. For this form, I really had to struggle to keep the logic simple:button.addSelectionListener(new SelectionAdapter(){ public void widgetSelected(SelectionEvent e) { if (e.getSource() == DataGrid.this.button){ TableItem[] items = DataGrid.this.table.getSelection(); if (items == null || items.length < 1){ Toolkit.getDefaultToolkit().beep(); } else { System.out.println("Selected key..." + items[0]); EditForm form = new EditForm(DataGrid.this.shell); form.setKey(items[0].getText()); form.show(); DataGrid.this.refreshData(); } } } }); In the code above, the SelectionAdapter looks for a selected row. If nothing is selected, we get a system beep. If a row is selected, we access a TableItem returned from the Table‘s getSelection() method. The first element is the record key for the EditDialog. We then display this dialog on top of the grid using its show() method.To keep things simple, the anonymous inner class is a subclass of SelectionAdapter, a concrete implementation of SelectionListener, where all methods do nothing. This is helpful because SelectionListener has another method that we don’t need and would muddy our code even more.Another problem with the use of inner classes is that when you need to refer to the outer classes, variables referenced need to be declared final. A class’s member variables can be referenced this way:DataGrid.this.button DataGrid.this.table.getSelection(); Dialog viewOnce the user clicks on the button at the bottom of the DataGrid form, the Edit dialog pops up over the grid. This dialog consists of multiple text-type widgets that contain details obtained from the selected row in the DataGrid form.Our application’s Edit portion consists of a dialog-type Shell for editing one row of data, as well as any phone information. This Shell appears modally over the main Shell such that the user cannot access the window below it. The user must edit the row, then close the screen.EditDialog is a subclass of the abstract Dialog, which provides title and style information. You can specify SWT.APPLICATION_MODAL, SWT.MODELESS, SWT.PRIMARY_MODAL, or SWT.SYSTEM_MODAL. Depending on the OS, not all these styles may truly be available. EditDialog utilizes an APPLICATION_MODEL style, which lets the user move to another application, but freezes every window under the current dialog in the current application.If you want to see a title area around the dialog, you must OR the application mode with the SWT.DIALOG_TRIM integer.Although Dialog does not descend from either Shell or even Widget, it still requires a Shell for its constructor. This is to ensure, once again, that, at shut-down, disposal occurs seamlessly throughout the application. There are no interfaces to contend with, so you can define any method for the caller to implement. In our case, we implement a show() method where all widgets are spawned. Within show(), we create a Shell just as we did in the main form.Below is the type of code you would find in the show() method to initialize a new Shell and populate it with some widgets:private Shell shell = null; ... shell = new Shell(this.getParent(), SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL); shell.setText("Edit"); shell.setBounds(this.getDialogBounds(300,300)); shell.setLayout(new FillLayout()); this.initData(); this.initWidgets(); shell.pack(); shell.open(); Display display = this.getParent().getDisplay(); while (!shell.isDisposed()) { if (!display.readAndDispatch()) display.sleep(); } The code above is a variation of the primordial dozen lines of code I mentioned earlier. Noteworthy is that the Shell doesn’t use Display as its parent, but the Shell that spawned it. Also, the reference to Display is obtained from the parent Shell.The Edit dialog has a member variable called row, which refers to the single data row the user is trying to edit. The call to initData() instantiates this row array and its phone data information. However, the real brunt of the view initialization happens in the initWidgets() method.The importance of layoutThe Edit dialog illustrates two layout styles, as shown in Figure 4. Since the data represents person and phone information, we divide the editing area into two panels. Each panel is a Group instance, which allows us to split the presentation between the two areas.The Group class provides a panel with an etched border. It is useful as a container for other widgets. The two Groups are distributed on the Edit screen using a FillLayout instance. This Layout subclass distributes all objects equally on a horizontal plane.Within the Groups, it is possible to specify more complex layouts. We utilize GridLayout within each of the two panels. This allows quite a bit more control over the widgets. We can, for example, specify a two-column grid within each panel. If we wanted to add eight widgets within the panel, we would end up with four rows with two widgets in each. With GridLayout, we can specify a GridData object to be applied to each individual widget. That allows a particular widget to either size itself according to its internal size computation or grab all the space within the column it finds itself in:Group group = new Group(shell, SWT.NONE); group.setText("Person"); GridLayout layout = new GridLayout(); layout.numColumns=2; group.setLayout(layout); Labels and textsWithin the Group, a series of labels and text fields display information about the row of data the user is editing:new Label(group, SWT.NONE).setText("FirstName"); firstName = new Text(group, SWT.BORDER); firstName.setText(row[1]); A Label may represent text, image, or a separator line. Alignment can be left, right, or center. Various styles may also be supported like SWT.SHADOW_IN or SWT.SHADOW_OUT to give the widget a sense of depth. Note: often these specifications are hints and may be ignored in a certain context or not even supported. For the purpose of data entry, no particular styles are required.The Text widget also supports a variety of styles. You can request a multiline, read-only widget like this: SWT.MULTI|SWT.READ_ONLY|SWT.WRAP .The default is, of course, single-line, no-wrap, and editable, so we do not need to set anything beyond SWT.NONE. Since we want borders around the text fields, as commonly seen in data entry, we specify SWT.BORDER, but, to conserve space, we could omit it.Each Text setText() method sets an element of the row array.The second panel in our Edit dialog features a drop-down box that holds four values. The Combo is a combination of a text field and a drop-down list. We can add a string array to the list’s item to set the drop-down portion, and the text field has a setText() method. By setting a default from one item in the list and specifying the list as SWT.READ_ONLY, we have a drop-down that lets a user select only what is in the list portion:new Label(group2, SWT.NONE).setText("Phone Type"); phoneType = new Combo (group2, SWT.READ_ONLY); phoneType.setItems (new String [] {"Fax", "Business", "Cell", "Home"}); phoneType.setText("Home"); MessageBoxesThe use of message dialogs to let users make a yes or no decision is pretty common in GUI development. SWT lets you specify the type of MessageBox by utilizing style integers. The open() method returns an integer that indicates what the user has selected. For example, the user could return SWT.YES if we had set up SWT.NO|SWT.YES styles.The user is given the choice of continuing the editing or returning to the DataGrid:MessageBox mb = new MessageBox(shell,SWT.OK|SWT.CANCEL|SWT.ICON_QUESTION); mb.setText("Quit Edit?"); mb.setMessage("Are you finished editing?"); int reply = mb.open(); if (reply == SWT.OK){ shell.dispose(); } In the code above, we let the user either quit the application or select Cancel and return to editing. If the user returns SWT.OK, EditDialog is disposed and we return to the main Shell.In conclusionOnce you’ve set up a data-entry application like the one in this article, you’ll get an idea that SWT is sufficiently fully featured to support many types of applications beyond the Eclipse framework. While few books and documents detail SWT’s ins and outs, the Eclipse Website features a pretty substantial series of articles. Considering some of the companies backing this open source initiative, it is hard to imagine it losing steam in the near future. Given the sharp looks and speed of SWT, it is even possible to imagine the growth of Java on the client side.Gervase Gallant has been an applications architect and coder since 1990, specializing in J2EE, application servers, and Web interfaces. He has also written many Swing and AWT applications. In a previous lifetime, he was a columnist and technical writer and worked at sea for seven years. He maintains a collection of Java articles and toolsets on his site http://www.javazoid.com. Software DevelopmentJavaOpen Source