The State of JVM Desktop Frameworks: SWT
A discussion of how to the SWT framework can be used to work with and display data in a Java-based application.
Join the DZone community and get the full member experience.
Join For FreeThis series is dedicated to the state of JVM desktop frameworks. After having had a look at Swing last week, this post focuses on the Standard Widget Toolkit.
What Is SWT?
SWT originated with the Eclipse project as an IDE. The developers built a dedicated framework for Eclipse in which to build their graphic components. Swing and SWT have widely different designs. Swing implements the drawing of widgets in Java from scratch. SWT is a thin wrapper API that relies on native graphic objects. This has two main benefits:
- Widgets look native to the platform.
- Rendering is faster.
SWT APIs
There's one guiding principle behind SWT: because it depends on native graphic objects, every component requires a "parent" object as its first parameter. The parent is the object on which the child will be drawn. Every SWT component's constructor takes the parent as its first argument.
Fun With SWT
SWT has some peculiarities, most of them related to its design based on system libraries.
Native Dependency
SWT provides a JAR for each mainstream operating system, e.g. Windows, Mac OSX, etc. For example, this is the Maven dependency for my laptop:
<dependency>
<groupId>org.eclipse.platform</groupId>
<artifactId>org.eclipse.swt.cocoa.macosx.x86_64</artifactId> <!--1-->
<version>3.114.100</version>
<scope>runtime</scope> <!--2-->
</dependency>
- JAR coordinates are platform-dependent. They contain the required native libraries in the form of JNI bindings.
- The JAR is only required at runtime.
SWT Event Control Loop
Swing provides an event control loop out-of-the-box. This is not the case with SWT. We need to copy-paste the following code into each of our applications:
xxxxxxxxxx
val display = Display() // 1
val shell = Shell(display) // 2
shell.open() // 3
while (!shell.isDisposed) { // 4
if (!display.readAndDispatch()) display.sleep() // 5
}
display.dispose() // 6
- Bridge between SWT and the OS.
- Create the top-level window.
- Display it.
- While the system's native resources for the window have not been released.
- Handle queued events. If nothing needs to be done... do nothing.
- Free all system native resources.
No-arg Constructors
Both windows and dialogs are represented as Shell
instances in SWT. The top-level window requires no parent and thus Shell
offers a no-arg constructor. But since Shell
is a graphical control, all its parent classes do also offer such a constructor. Those constructors have an empty body and calling them doesn't do anything.
Component Creation Order
The order in which components are instantiated on a parent is the order in which they will be added to the layout of that parent. If you need to decouple them, you need to be creative, e.g. wrap the call to the constructor in a lambda.
Here's an SWT sample displaying a label, a text field, and a button in this order:
xxxxxxxxxx
val label = Label(shell, SWT.LEFT)
val text = Text(parent, SWT.SINGLE or SWT.LEFT or SWT.BORDER)
val button = Button(shell, SWT.PUSH)
Styling
As seen in the previous snippet, the styling of widgets happens during their instantiation. Those styles are coded in the SWT
class in the form of style bits:
LEAD = 1 << 14
LEFT = LEAD
SINGLE = 1 << 2
BORDER = 1 << 11
PUSH = 1 << 3
- etc.
Circular Dependency
Note that the constructor of Control
takes a Composite
instance, which itself is a subclass of Control
. This circular dependency is bound to within the same package.
Displaying Tabular Data
SWT concerns itself only with the widgets and their rendering. As opposed to Swing and JavaFX, it has no concept of data models: you need to manage the data yourself. It's manageable for 0-D data, e.g. text fields, and even for 1-D data, e.g. list boxes. For 2-D data, i.e. tables, it's a lot of trouble.
For this reason, most graphic frameworks introduce a model abstraction between the component and the data it manages. For example, Swing has JTable
and a TableModel
.
Eclipse delivers the JFace library, which provides a data model abstraction over the SWT API, among others. For example, for tables, JFace has the TableViewer
class. At its core, every JFace viewer class wraps an SWT control.
The wrapping applies at a deep level: SWT's TableColumn
is wrapped by JFace's TableColumnViewer
.
The Viewer
class has a rich type hierarchy to handle data of different dimensions. IStructuredContentProvider
provides multiple rows of data such as those found in tables. Because the API was designed before generics, there's a runtime check at the StructuredViewer
level to verify the type of the set IContentProvider
. Moreover, StructuredViewer
also provides sorting, filtering, and "decorating" capabilities.
Note that there's a library that manages two-way data bindings between the model and the control: JFace Data Binding. I couldn't find a compatible version though.
Conclusion
There's no denying SWT's success. This is seen in the number of software projects developed with it.
SWT provides a fully native look-and-feel GUI. On the good side, it means that applications behave natively. On the flip side, there's a cost associated with this capability:
- A dependency to the platform library, which breaks the Compile Once, Run Anywhere promise.
- Runtime checks instead of compile-time checks, because of the lack of generics.
- For the two reasons above, the API feels unwieldy at times.
Thanks a lot to Benjamin Muskalla for his offer to review this post.
The complete source code for this post can be found on GitHub in Maven format.
To go further:
Originally published at A Java Geek on January 24th 2020
Published at DZone with permission of Nicolas Fränkel, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments