The (J)Face of Eclipse, Page 2
The Standard Widget Toolkit, or SWT, provides the API for creating and managing widgets, interactive controls, graphics, and other display-related resources. It does not have dependencies on the Eclipse run-time, or any other Eclipse plug-in, which means that it is entirely possible to create SWT-only, non-Eclipse applications (in fact, there are several commercial as well as open source examples of such applications). You can download SWT packaged as a standalone library from the Eclipse downloads page, SWT section.
SWT is somewhat controversial in the Java community, partly because it is an alternative to Swing, the "official" Java UI framework (although starting with version 3.0, you can use Swing components in SWT), and partly because it is not pure Java—it uses the Java Native Interface (JNI) to interact with the OS platform's native widget toolkit. That is the most fundamental difference between SWT and Swing; whereas Swing uses emulated widgets, SWT uses window system native widgets, supplemented by custom implementations on those platforms where native versions are not available. This means that user interfaces built with SWT don't just look native, they are in fact native. This is more in line with user expectations—they get the same look and feel they see when using other applications in their OS. It also results in better application performance, although the performance gap between the two frameworks is reportedly getting narrower. The flip side is that SWT is OS-dependent (but then, so are JVM implementations). However, the set of supported platforms continues to expand.
Widget Hierarchies and Events
All widgets have the ability to emit events to their registered listeners to keep them informed of what is going on with the widget (most popular window-based user interface systems use this programming model). There are two types of events and listeners in SWT: typed and untyped (well, they're not really that different because the former is implemented in terms of the latter). Typed listeners are classes that handle a specific type of event; for example, the user's mouse click is delivered to MouseListeners. The events they handle are represented by specific event classes (for example, MouseEvent). Untyped events are designated by a unique constant defined in class SWT. They are delivered to implementors of org.eclipse.swt.widgets.Listener.
One important aspect of SWT programming, whether or not you know the intricate details of each widget's API, is how it processes events. To be compatible with the even-based programming model of most windowed GUI systems, SWT uses a single thread, called the UI thread, to process widget events placed into the application's event queue by the underlying window system. Thus, at the core of each SWT-based applications is an event processing loop (wait for event, dispatch it, repeat). The implication to the developer is that any interaction with widgets (in other words, creation, method invocation, and the like) must be done in the UI thread. In fact, SWT detects access from another thread and throws an exception as a result—you won't get far if you try it. If you do need to access your UI from a non-UI thread, you must ask the UI thread to do it on your behalf—obtain the singleton Display instance (responsible for managing the connection between SWT and the underlying OS platform) by calling Display.getDefault(), and use either its syncExec(Runnable) or asyncExec(Runnable) method to get your code (the Runnable) executed in the UI thread. The difference between the two methods is that the former blocks until the Runnable has been executed; the latter returns immediately after queueing it.
Composites and Layouts
A large group of controls are descendants of class Composite. As the name suggests, these controls may be composed of other controls (Composite itself is a subclass of Scrollable). You will notice that many methods throughout Eclipse UI that you're supposed to extend or implement to create your own user interface component provide an instance of Composite, which you are to use as the parent of your widgets. If you find yourself in need of drawing your widget from scratch (using SWT's graphics primitives), you'll want to subclass Canvas (itself a subclass of Composite), which is designed for that purpose.
Because composites usually contain several other widgets, they provide the ability to control their display layout. This is the responsibility of layouts (in other words, subclasses of Layout). There are several concrete layouts provided with SWT, the most commonly used one being the GridLayout (lays out controls in a cell grid). Others include FillLayout, FormLayout, and RowLayout. More advanced layouts are supported by other frameworks (such as the Eclipse UI Forms, which I do not cover here). A composite is given a layout using its setLayout(Layout) method, but many layout implementations in addition require that each control be annotated with a implementation-specific LayoutData, that the layout algorithm uses to compute the control's size and position. Each control can be assigned layout data using its setLayoutData(Object) method.
Custom Widgets and Extensibility
Even though most SWT classes are declared as public, only a few are meant to be extended; the Javadoc is usually good at indicating which ones they are (also, look in Javadoc for supported styles and events). To create custom reusable components, you can subclass Composite and populate it with child widgets in the constructor. Even though there are several "custom" widgets in package org.eclipse.swt.custom, you must subclass Canvas to create your own (to paint your widget, implement a PaintListener for it). Finally, you are free to implement your own composite layout algorithms by subclassing org.eclipse.swt.widgets.Layout. Following these guidelines will give your code the best chance to remain immune to changes in SWT.
Managing Operating System Resources
The last but nevertheless very important topic to mention is resource management. Fonts, colors, images, and even widgets are all operating system resources that are not automatically garbage collected. When you create one of these resources (usually by instantiating it, or by calling a method that creates a new resource), you must remember to dispose it—by calling its dispose() method—as soon as you're done using it. The sooner you do it, the better, because these resources are usually scarce (more so than your heap memory), and you can get a nasty, unrecoverable SWTError when you run out of them.
To simplify the management of your plug-in's or application's static resources, JFace provides several helper classes. ImageDescriptor, ColorDescriptor, and FontDescriptor allow you to define their respective resource, but delay its actual creation until needed. The corresponding registries—ImageRegistry, ColorRegistry, and FontRegistry—take the responsiblity for keeping track of resource creation and disposal; all you have to do is register your resource descriptors with the registry, and then ask it for the actual resource, which it creates on demand, as needed. Registries automatically dispose any resources they create when the Display they're associated with is disposed.
Growing the Superview
In this section, you will extend the example you developed in the previous article. To follow the steps, please download the example source code from the location listed in the Resources section.
In the previous installment, you developed a simple visual plug-in that contributed a custom view to the workbench. I already mentioned that you used a JFace viewer—the TableViewer—to display a list of views (their names and icons) in your custom view (the Superview). You will continue to treat the Superview as a convenient place for your user interface components; however, you won't go into the details of the Superview class itself (or its ViewPart superclass)—you will cover this in another installment. For now, all you need to know is that the Superview's createPartControl(Composite) method is where you construct the user interface for display in the view's content area. In this method, you are given a Composite that can act as the parent of any widgets that you create.
A brief note about your "application model": In the previous article, you used as your model an array of view descriptors, which you obtained from the view registry. This time, you want to show a tree consisting of view categories, each of which should expand into the list of views that belong to it. You can obtain these categories from the view registry as well. Thus, you will treat the view registry (IViewRegistry) as your application model.
Changing the Viewer
To switch from displaying a table to displaying a tree, the first thing you need to do is change the viewer's declared type to TreeViewer and instantiate it at the top of createPartControl. By using the constructor that only takes the parent composite, you leave it up to the viewer to create the underlying SWT Tree control (as a direct child of the given parent composite), using the default style. Alternatively, you could create the widget first, and then pass it to the viewer's constructor (with a different signature).
You then configure the viewer with a content provider, a label provider, and a sorter; however, this time you need to develop a custom content provider implementation—the TreeViewer requires its content provider to implement ITreeContentProvider (it asserts this in its setContentProvider(IContentProvider) method). Thus, you create a nested class called ViewContentProvider (for clarity, the customized label provider was also moved into a nested class named ViewLabelProvider).
The content provider is responsible for translating the viewer's input into a structure that the viewer understands. An ITreeContentProvider must support hierarchical tree navigation in both directions—downward (children) and upward (parent). The viewer uses the provider's getChildren(Object) method to obtain an element's array of children, and its getParent(Object) method to obtain an element's parent. Every child element also may act as the parent of another array of elements (the provider must indicate this for a given element by returning true from its hasChildren(Object) method). Your model doesn't have the API to allow for easy navigation upward—there is no getParent-like method in either IViewDescriptor or IViewCategory; therefore, you cache each element's parent in the getChildren(Object) method (you can do that because you know you're always asked for children first). You could choose to solve this differently; for example, you could wrap the model in a parallel hierarchy, which would provide access to each element's parent. Both approaches have some disadvantages; you should select an approach that seems most reasonable in your particular situation.
Page 2 of 5