How to Use Objects: Code and Concepts (2016)
Part III: Events
Chapter 7. Introduction to the Standard Widget Toolkit
Object-oriented software at its technical core is constructed from objects that receive method calls, change their internal state, and compute some return value. Part II created a solid and detailed conceptual basis for reasoning about these method calls and about the correctness of software in general. Such a basis is indispensable for developing any substantial software product: If we are not able to capture the expected behavior of its different components precisely, we will never be able to divide the work among a team and still be confident that the parts will fit together seamlessly in the end.
The question of correctness naturally focuses on the caller’s perspective: 1.1 4.1 The caller always invokes a method to achieve a particular result. It obeys 4.2.3 the method’s pre-condition and expects the method to provide the result described in the post-condition. As a rather subtle consequence, that postcondition also determines the method’s behavior to a large degree: The method will perform only such actions as are necessary to fulfill the postcondition, since any further effort is wasted and indeed unexpected from the caller’s point of view.
In many situations, the contracts cannot be as precise as would be necessary to specify the result completely. We have seen one instance in the case of the OBSERVER pattern: The subject sends out notifications about 2.1 state changes to many different kinds of observers, by calling the methods of a common interface. Of course, the concrete behavior of the observers can then vary greatly, according to their respective purposes. The subject 12.2.1.8 cannot prescribe a single contract for the method it calls. Similarly, at the system boundary, the network card may signal the arrival of a data packet and the operating system has to deal with it appropriately; when it hands the packet on to some application, it can, again, not prescribe the reaction of that application.
In all of these examples, the focus in reasoning about what is “correct” shifts from the caller to the callee. The observer, the operating system, and the networking application all exhibit some reaction that is appropriate from their own point of view, rather than serving to fulfill any expectation on the caller’s part. Software structured in this way is called event-driven: Something relevant—the event—happens, the software is notified, and it reacts appropriately. Although internally such software will employ contracts 7.11 to specify the behavior of its objects, its overall structure is shaped by the view that events are delivered and must be acted upon.
Part III presents and examines the specifics of event-driven software. In this chapter, we look at an important and prototypical example, the construction of graphical user interfaces. It serves to approach the topic from a technical perspective, by just working out the concrete API of the Standard Widget Toolkit (SWT) used by Eclipse. Looking beyond, it analyzes the concept of frameworks and its central feature, inversion of control. Chapter 8 treats the additional complications arising when code is executed concurrently, in several threads. In particular, the communication between threads is best understood as being event-driven. Chapter 9 complements the discussion of user interfaces by examining a practically crucial aspect, that of model-view separation: Professional developers strive to keep as much of an application’s functionality independent of the user interface as possible. Finally, Chapter 10 comes back to the original question: How can we talk about the correctness of event-driven software? It turns out that in many situations, the desired behavior of objects can be captured by finite state machines.
7.1 The Core: Widgets, Layouts, and Events
A graphical user interface is more than just a pixel-based image on the screen. The interface will reflect the user’s mouse gestures, for instance by highlighting a button when the mouse moves in. A window will repaint itself when the user first minimizes and then reopens it. Different parts will interact when the user performs drag-and-drop gestures. In short, the user interface is a highly reactive piece of software.
The technical structure of user interfaces is well understood and there 7.3 are many frameworks that implement the fundamental mechanisms and graphical components. The Standard Widget Toolkit (SWT), which is used in Eclipse, is one prototypical example. Having mastered this specimen, you will easily find your way into the others as well.
84The Eclipse web site offers a rich collection of SWT snippets, which show in a minimal environment how particular user interface elements are created and how the SWT API is supposed to be used. In short, they provide a helpful cookbook for creating your own user interfaces, covering basic patterns as well as special knowledge for experienced developers.
To examine the structure and usage of SWT, let us try to implement the simple web browser shown in Fig. 7.1. We need a field to type in the URL, a “Load” button, and a display area for the web page itself. Since SWT already contains a Browser component, as well as all the smaller elements shown in the figure, all we have to do is wire them up appropriately.
Figure 7.1 A Simple Browser
The user interface is represented in the software as a tree of widgets.
At the software level, the overall user interface is constructed as a tree of widgets (Fig. 7.2). Each widget covers a rectangular area of the screen, and it may have children according to the COMPOSITE pattern. In SWT, the 2.3.1 top-level window is called a shell and is represented by an object of class Shell. Inside the shell, and below it in the widget tree, we have placed top, browser, and statusBar. The browser is simply a predefined object of class Browser. The other two are Composites, which is SWT’s class that manages child widgets. The top composite contains the url, which 2.3.1 is a Text, the load button of class Button, and a progress bar of class ProgressBar. The lower statusBar contains a single Label called status for displaying a text.
Figure 7.2 Widget Tree of the Browser
The class hierarchy of SWT may be somewhat misleading at this point. The visible widgets on the screen derive from Control. The root class Widget, from which Control 7.4.1 also derives, covers all elements of the user interface that may be associated with resources and that may send notifications. Other subclasses of Widget represent, e.g., tool tips, rows in tables, and the system tray. In the following discussion, we will nevertheless continue to speak of “widgets” for anything that is painted on the screen.
2.3.1Let us look at the code that creates the tree in Fig. 7.2. The COMPOSITE pattern suggests introducing methods addChild and removeChild for the purpose. SWT uses a different approach: Each widget is constructed below a specific parent and that parent must be passed to the widget’s constructor. The construction of the tree from Fig. 7.2 then proceeds top-down, starting with the top-level window. The second parameter of each constructor is a generic bit set of flags, which can provide widget-specific customizations needed at creation time. Here, we do not use that feature and pass SWT.NONE.
swt.browser.SimpleBrowser.createContents
shell = new Shell();
top = new Composite(shell, SWT.NONE);
url = new Text(top, SWT.NONE);
load = new Button(top, SWT.NONE);
progress = new ProgressBar(top, SWT.NONE);
browser = new Browser(shell, SWT.NONE);
status = new Composite(shell, SWT.NONE);
statusText = new Label(status, SWT.NONE);
The advantage of the top-down construction is that each widget is properly 2.2.1 owned by its parent, except for the top-level shells, which transitively 7.4 own all widgets in the tree below them. In this way, the operating system resources invariably associated with an SWT widget cannot be leaked easily.
SWT is very consistent in this approach. Even the single lines in tables and trees, which at the implementation level may allocate icon resources, are represented as Widgets. For instance, to create a table with random people (Fig. 7.3), we add each new line by the following code:
swt.intro.AddRemoveRowDemo.createContents
TableItem item = new TableItem(table, SWT.NONE);
item.setText(new String[] { randomName(), randomDate() });
Figure 7.3 SWT Table
To remove a row, or a widget from the tree in general, we dispose of that widget, rather than asking its parent to remove it as done in COMPOSITE. In the example, the following code removes the currently selected person:
swt.intro.AddRemoveRowDemo.createContents
int sel = table.getSelectionIndex();
if (sel != -1)
table.getItem(sel).dispose();
Layout managers position widgets within their parents.
Once the widget tree is created, the technical backbone of the user interface is in place. However, the visual position of the single widgets is unclear. For instance, how is SWT supposed to know that we expect url, load, and progress to appear in the configuration shown in Fig. 7.1?
The solution consists of making each Composite responsible for positioning its children—that is, for computing a layout for them. However, the positions must be computed in a very flexible, application-specific manner. Composites therefore employ the STRATEGY pattern: They are configured 1.3.4 by a LayoutManager and delegate any positioning requests to that object.
In the example, the top composite uses a GridLayout (lines 1–3 in the next code snippet; the parameters to GridLayout() in line 1 mean two columns, not using equal-width columns). That layout manager arranges the children in a table, but it is very flexible: Children may span multiple columns and rows (below the progress bar spans two columns), they are positioned within their table cell (e.g., left, center, fill), and some table cells may expand to fill the available space (true, false). These parameters are attached to the children as layout constraints of classGridData (lines 5–8).
swt.browser.SimpleBrowser.createContents
1 GridLayout gl_top = new GridLayout(2, false);
2 gl_top.marginWidth = 0;
3 gl_top.marginHeight = 0;
4 top.setLayout(gl_top);
5 url.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true,
6 false, 1, 1));
7 progress.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false,
8 false, 2, 1));
SWT offers a rich collection of layout managers that can be attached to arbitrary composite widgets. In practice, it is hardly ever necessary to write actual code to lay out children.
Do not specify pixel-based positions and sizes of widgets.
Using layouts requires some experience, since sometimes the widgets do not 7.2 come out as expected. The graphical WindowBuilder helps a lot, because you get an immediate preview of the later runtime behavior. However, you may still be tempted to just position widgets explicitly, because that gives you pixel-level control of the outcome:
swt.intro.XYLayoutDemo.createContents
text = new Text(shell, SWT.BORDER);
text.setBounds(53, 10, 387, 25);
Do not yield to this temptation: If the font sizes or the behavior of the 7.4 native widgets underlying SWT differ only very slightly on the user’s platform, the display will be ruined. Also, users like resizing windows to fit their current needs, and this is possible only if the inner layout adapts automatically.
Laying out widgets is a recursive top-down process.
In most situations, it is sufficient to configure the local layout managers with the desired constraints for all children. Sometimes, however, the result is not exactly as expected, and then one needs to understand the overall process. Fortunately, that process is straightforward: Each composite widget assigns positions and sizes to its children and lays them out recursively.
The core of the process can be seen in the following snippet from Composite. The composite delegates the positioning of the children to its layout manager (line 2). This assigns all children a position and size via setBounds(). The process then continues recursively to the children (lines 4–7). The parameter all is an optimization for contexts where it is known that the children have not changed internally.
org.eclipse.swt.widgets.Composite
1 void updateLayout (boolean all) {
2 layout.layout (this, changed);
3 if (all) {
4 Control [] children = _getChildren ();
5 for (int i=0; i<children.length; i++) {
6 children [i].updateLayout (all);
7 }
8 }
9 }
At several places, layout managers may wish to take into account the preferred sizes of the children to ensure that the content fits the available screen space. This information is provided by the method computeSize() of each widget. Atomic controls just measure their texts and images and compute the size. Composites again delegate the decision to their layout managers (line 3) but ensure that only the available space, given by the hint parameters, is taken up (lines 4–5). It remains to add any local borders and margins (line 6).
org.eclipse.swt.widgets.Composite
1 public Point computeSize (int wHint, int hHint, boolean changed) {
2 Point size;
3 size = layout.computeSize (this, wHint, hHint, changed);
4 if (wHint != SWT.DEFAULT) size.x = wHint;
5 if (hHint != SWT.DEFAULT) size.y = hHint;
6 Rectangle trim = computeTrim (0, 0, size.x, size.y);
7 return new Point (trim.width, trim.height);
8 }
Since laying out widgets is a top-down process, it is usually triggered on the top-level window. The first choice is to set the size explicitly and then lay out the content again. Essentially the same thing happens when the user resizes the window.
swt.browser.SimpleBrowser
shell.setSize(550, 300);
shell.layout();
The second choice, which is often preferable for dialogs, is to pack the 7.6 top-level shell.
swt.browser.SimpleBrowser.open
shell.open();
shell.pack();
Packing is, in fact, available on any widget and means that the widget will be resized to its preferred size:
org.eclipse.swt.widgets.Control
public void pack (boolean changed) {
setSize (computeSize (SWT.DEFAULT, SWT.DEFAULT, changed));
}
Events notify the application about the user’s actions.
In user interfaces, many things can happen almost at any time: The user moves the mouse, clicks a mouse button, presses a key on the keyboard, moves a window, closes the application, and many more. The application can never keep track of all of those things all by itself. Fortunately, this is also not necessary: SWT will notify the application whenever something interesting happens. These “interesting things” are called events in this 10.1 context. To be notified about events, the application has to register an event-listener with the widgets on which they can occur. Between events, the application can “lay back” and “relax”—there is no need to work unless 7.3.2 the user makes a request by doing something to the application’s windows.
In the browser example, the user will type in some URL, but there is no need to load the page until the user clicks the “Load” button. At this point, the entered URL has to be sent to the browser for display. In SWT, the event is called “selection” and is sent to a SelectionListener. The following event-listener contains the action to be taken directly in the method widgetSelected.
swt.browser.SimpleBrowser.createContents
load.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
if (!url.getText().isEmpty())
browser.setUrl(url.getText());
}
});
The generic selection event is used by many different widgets to signal that the user has “clicked” onto something, whether by selecting a table row or by pressing “enter” inside a text field. Simple selection calls method widgetSelected; double-clicks or pressing “enter” inside a text field are usually associated with some default action to 3.1.4 be taken, so they are reported by calling widgetDefaultSelected. The base class SelectionAdapter provides empty implementations, so that the application simply has to override the interesting case.
2.1Listening to events is very similar to the Observer pattern. However, most of the time events do not refer to changes in the widgets’ state, as in the pattern, but to some external action performed by the user. The message given to the listeners is not so much “something has changed” as “something has happened.” While the change is persistent and can be queried by getter methods, the event is ephemeral and cannot be obtained otherwise. Nevertheless, the technical basis of event notification is the same as 2.1.2 2.1.3 for the Observer pattern, so the same design considerations apply.
First-time developers of user interfaces sometimes find the event-based approach a little disconcerting: You add a tiny snippet of code and hope for the best, but can you really rely on being called by SWT? The abstract 7.3.2 answer is that SWT is built to guarantee the delivery of the events, so that you can rely on the behavior. It may also help to take a look at the internal processing steps, shown in Fig. 7.4 (much simplified and generalized). When the user clicks, the mouse sends some signal to the computer’s USB port, where it is picked up as a data packet and handed on to the computer’s window system. That part of the operating system determines the top-level window over which the mouse is located and passes the relevant data 7.10.1 about the click to that window, where SWT is ready to receive it. SWT determines the single widget at the mouse location, which in the figure 10.1 is a button. The button first goes through the visual feedback. (Observe closely what happens between pressing and releasing the mouse button over a button widget!) Finally, the button notifies the application code in the listener.
Figure 7.4 Overview: Events in a Button Click
Choose the correct event.
Every widget offers several events to which the application can react. The reason is very simple: Listening to events is all that an application can do to implement its functionality, so SWT has to be general and powerful enough to enable all desirable functionality to be implemented.
For instance, the Browser has to access the network and download possibly large amounts of data from possibly slow servers. It is absolutely necessary to provide the user with feedback about progress. The widget therefore offers progress reports as events. The following listener translates 7.10.2 these into a concrete display for the user, by setting the progress bar from Fig. 7.1 appropriately.
swt.browser.SimpleBrowser.createContents
browser.addProgressListener(new ProgressListener() {
public void completed(ProgressEvent event) {
progress.setSelection(0);
}
public void changed(ProgressEvent event) {
progress.setSelection(event.current);
progress.setMaximum(event.total);
}
});
In choosing among the different events of a widget, it is sometimes easy to fall into traps. For instance, a Button offers to notify MouseListeners about mouse clicks. At first, this is what you want: When the user clicks, you receive an event. However, there are two problems. First, when the user presses a mouse button, moves the cursor outside the button, and then releases the button, then nothing should happen—the button is “armed” only as long as the cursor is inside. (Observe the visual feedback on your screen!) 10.1 Second, a button can be “clicked” by pressing “enter” on the keyboard as well, either by moving the focus to the button or by making the button the default one within the window. In summary, you always have to proceed based on the meaning of events—the precise conditions under which they will be signaled.
User interfaces offer different access paths to a given functionality.
229 One of the core elements of usability is the goal of adapting the user interface to the user’s work flows and expectations. Users will have different preferences on how to access some piece of functionality: Some prefer clicking with the mouse, some use keyboard shortcuts, yet others search through menus.
In the present example, novice users might actually click the “Load” button. More experienced users will expect that pressing “enter” in the URL field will start loading the page. So, let us set up this reaction (note that now we use widgetDefaultSelected):
swt.browser.SimpleBrowser.createContents
url.addSelectionListener(new SelectionAdapter() {
public void widgetDefaultSelected(SelectionEvent e) {
if (!url.getText().isEmpty())
browser.setUrl(url.getText());
}
});
Menus are similar to widgets in that they form a tree. The next code snippet shows the construction of a “Quit” entry in a “File” menu. The overall menu bar is attached to the shell (line 2). Menu items, like buttons, notify their selection-listeners when the user clicks on the item (lines 9–13).
swt.browser.SimpleBrowser.createContents
1 Menu menu = new Menu(shell, SWT.BAR);
2 shell.setMenuBar(menu);
3 MenuItem mitemFile = new MenuItem(menu, SWT.CASCADE);
4 mitemFile.setText("File");
5 Menu menuFile = new Menu(mitemFile);
6 mitemFile.setMenu(menuFile);
7 MenuItem mitemQuit = new MenuItem(menuFile, SWT.NONE);
8 mitemQuit.setText("Quit");
9 mitemQuit.addSelectionListener(new SelectionAdapter() {
10 public void widgetSelected(SelectionEvent e) {
11 shell.close();
12 }
13 });
To avoid re-implementing the same reaction in different event-listeners, you should usually factor this reaction out into a protected method of the surrounding class. From a broader perspective, the decisions involved here 2.1.3 are really the same as in the case of implementing the observer interface, where we asked whether the event-listener or the surrounding class should contain the update logic of the observer.
9.3.4In most contexts, one does not create menus by hand. JFace wraps the bare functionality in a MenuManager, which keeps track of current “contributions” and reflects them in menus. It also enables navigation by paths for more declarative building of menu structures. The Eclipse platform uses the latter feature to render additions by multiple 174 plugins into one consistent menu structure.
The Display is the application’s access to the window system.
A last element of SWT interfaces is the global Display. It represents the application’s access to the underlying window system. In particular, it serves as an anchor point for resources such as Images, Fonts, and Shells. Furthermore, the Display offers global SWT functionality such as timer-based 7.9 execution. Since the display is so important, it is passed to all created elements and can usually be retrieved by getDisplay(), for instance from the surrounding widget from within event-listeners. Globally, a special default display opens the connection to the window system when it is first requested:
swt.browser.SimpleBrowser.open
Display display = Display.getDefault();
Building user interfaces with SWT is technically straightforward.
This finishes the basic setup of any user interface: You create a widget tree, specify the layout of child widgets within their parent, and attach listeners to react to user interactions. The complexity of user interfaces results from keeping track of events and the proper reactions, and from matching the 10 user’s expectations.
Do not map data to widgets by hand.
User interfaces usually display some of the data managed by the application: A text field might contain the name of a customer; a table might show the orders placed by that customer, where each order is represented by the product’s name and quantity, and the overall price. Such mappings are both tedious and commonplace. To deal with them, Eclipse’s JFace layer, which resides on top of the SWT widget set, offers two abstractions: Viewers keep the displayed data synchronized with the underlying application’s 9.3.1 data structures through changes, and data binding links single input 9.3.3 fields to properties of objects. Most user interface frameworks offer this kind 1.3.3 of division, and it must be mastered before attempting any serious project.
7.2 The WindowBuilder: A Graphical Editor for UIs
The introduction in the previous section outlined the standard procedure for building user interfaces: create a widget tree, define the local layouts, and attach event-listeners to the relevant widgets. The process is, in fact, a little tedious and very time-consuming. Although the conceptual basis is fairly obvious, the technical code to be written quickly becomes lengthy and requires a detailed knowledge of the API, in particular for specifying the layout constraints.
Many widget toolkits come with a graphical editor for the user interface. With this tool, you can place widgets by drag-and-drop, specify layout parameters by simple clicks, and attach listeners through the context menu. We present here Eclipse’s WindowBuilder.
From the start, we point out that such tools do not relieve you from knowing details of UI programming yourself. That is, the tools help you with standard tasks, but they will not support you through the detailed adaptations that customers invariably expect from professional user interfaces. In fact, most of the UI code of the Eclipse platform is written 1.4.5 1.4.8 by hand, and standard code structuring techniques keep it readable and maintainable.
7.2.1 Overview
Fig. 7.5 gives an overview of the WindowBuilder. The right-hand side pane contains a sketch of our simple browser application (Fig. 7.1), with the central browser widget highlighted. The controls at the top represent the layout constraints of the GridLayout, with the current choices depressed. Dragging handles on the selection changes the rows and columns spanned by the widget. To the left of this main area, the palette presents the available elements. The left-hand side gives the overall widget tree at the top and 1.3.3 the detailed properties of the currently selected widget.
Figure 7.5 The WindowBuilder
The lower-left corner shows a strength of the WindowBuilder: The tool does not keep a separate description of the user interface, for instance in XML. It extracts the graphical view from the source code instead, probably 9.1 building an internal model for caching the analysis. As a result, you can switch freely between the two perspectives, according to the best fit for your current goals. Modifying the graphical representation generates the corresponding code in the background, while manual modifications of the code are integrated into the graphical view as well. This feature is also highlighted by the ability to reparse the code from scratch (at the top of Fig. 7.5), which is sometimes necessary if the graphical view has gotten out of sync.
Events are handled via the context menu (Fig. 7.6). The WindowBuilder keeps track of the currently defined events for each widget and offers all available events as a simple choice. Selecting any menu item here jumps to the corresponding code section defining the event-listener. The WindowBuilder is even a bit smarter: If the listener calls a single method, then it 7.1 jumps to that method instead. This makes it easy to factor out common reactions.
Figure 7.6 Events in the WindowBuilder
You still have to be able to write user interface code by hand.
The WindowBuilder certainly boosts the productivity of professionals, but it can easily lead the novice to make wrong decisions. For instance, the 7.1 choice between attaching a selection-listener or a mouse-listener to a button, as mentioned before, must be made by the developer. Also, the abstraction offered by the tool is not perfect. For instance, it sometimes sets 7.1 the preferred sizes of widgets in pixels, which later destroys the previews and application behavior. Finally, the tool does not always deal well with legacy code created by hand, so you will have to massage that into a more suitable form.
There is one possible trap when combining handwritten widgets with code generated by the WindowBuilder: Constructors must not take custom parameters, such as 9.2 the application data that a widget is supposed to display. The WindowBuilder expects 7.5 custom widgets to obey the SWT conventions, where the constructor accepts the parent widget and possibly some flags. For Swing, it expects a default, no-argument constructor.
7.2.2 Creating and Launching SWT Applications
SWT is not part of the regular Java library, but must be linked in from the Eclipse environment. The WindowBuilder offers a tempting shortcut: When you create a new SWT application window in a regular Java project through New/Other/WindowBuilder/SWT Designer/Application Window, the WindowBuilder will find the relevant JAR files from the local Eclipse installation and put them on the project’s class path. Unfortunately, it uses the absolute local paths, so that the project cannot be shared within the development team.
Use SWT only in plugin projects.
A.1Eclipse is based on the OSGi module system, where a module is called a bundle. A plugin in Eclipse is nothing but a special bundle. SWT itself is wrapped up as a bundle, to make it accessible to other parts of the Eclipse platform. Fortunately, OSGi is very lightweight and straightforward to use.
A.1.2 The way to access SWT is to create a plugin project, rather than a plain Java project (or to convert an existing Java project to a plugin project through the context menu’s Convert entry). Then, in the plugin project’s MANIFEST.MF, you can add the SWT bundle as a dependency. These dependencies are captured by bundle names instead of local paths and can therefore be shared in the team.
A.2.5Within Eclipse, this setup enables you to run the SWT application as a simple Java application. The Eclipse launch configuration recognizes the OSGi dependencies and converts them to references to the corresponding JARs from the local Eclipse installation on the fly. What is more, the setup also allows you to export the SWT application as stand-alone software, A.2.3 complete with a native launcher similar to the eclipse launcher of Eclipse itself.
Let us summarize this explanation as a simple step-by-step recipe:
Tool: Create and Run an SWT Application
• Create a plugin project through New/Other/Plug-in Project
• Open the project’s MANIFEST.MF and add a dependency on org. eclipse.swt
• Create a package in the project as usual
• Use New/Other in the context menu
• In the dialog, type Application on top
• Select the SWT/Application Window
• Create the window’s content with the WindowBuilder
• Launch the window with Run as/Java Application
7.3 Developing with Frameworks
SWT offers a rich functionality for building user interfaces, but it is not completely trivial to use and to learn. You have to understand concepts 7.1 such as widget trees and layouts, and then you have to learn how to exploit this infrastructure for your own applications. In practice, this often involves looking for hours for the “right way” to do a particular thing. You will encounter this phenomenon with many other reusable software products, such as application servers, extensible editors such as GIMP, and IDEs such 201 117 as Eclipse: You browse tutorials at length to find out what to do and then you do it within 15 minutes with a few lines of code. This experience can be quite frustrating. It is then good to know the conceptual basis of these complexities. This section explains that the approach taken by SWT and other such tools is essentially the only possible way to go.
If you are currently more interested in learning SWT from a technical perspective, feel free to skip this section. But be sure to come back later—knowing the concepts of frameworks is essential for any professional developer, since they help in learning new frameworks more quickly.
7.3.1 The Goals of Frameworks
Many reusable software products come in the form of frameworks that 131,130,97,244 tackle complex tasks. Libraries such as the JDK’s classes for I/O or collections offer small, stand-alone pieces of functionality in the form of individual objects. You can take your pick and understand them one by one. Frameworks, in contrast, can solve their larger tasks only by providing entire networks 1.1 11.1 of collaborating objects, which you have to buy complete or not at all. Some are finished but extensible applications, such as the Eclipse IDE. Some are semi-complete applications that just miss any specific functionality, 174 such as the Eclipse Rich Client Platform. Some form only the 201 backbone of applications, such as SWT or application servers. In any case, the framework you choose determines the overall structure of your application to a large degree.
Before we continue, we wish to point out that our subsequent description of “frameworks” follows the classical object-oriented definition from the cited literature. It is not uncommon, however, to find the term “framework” applied in a much broader sense. 14,13Often, it is used simply as an alternative for “complex library.” MacOS uses it in the specific sense of a reusable software bundle installed on the system. Despite these different uses of the term, we find that the concepts attached to the classical definition are worth studying in any case, because they help developers to create and to understand professional software structures.
Frameworks provide ready-made structures for specific types of applications.
Frameworks offer complete or semi-complete networks of collaborating objects that support certain types of applications. When the framework starts working, everything falls into place, the large structures of the application are safely fixed, and the software is up and running.
It is useful to distinguish between two types of frameworks, because the type tells you what you can expect. Application frameworks offer infrastructure that is useful for building applications of certain shapes, independent 174 of the particular domain that they address. For instance, the Eclipse Rich 9.3 201,268,9 Client Platform, the SWT and JFace layers, and frameworks for developing web applications do not care about the content of the applications they support. You are free to develop just the application you need. At the same time, there is no support at all for the application’s business logic.
Domain frameworks, in contrast, help in building applications for particular domains. For instance, bioinformatics is a discipline that is quickly maturing and that is developing standard processing steps for genome and other data. New scientific discoveries require building on existing techniques, 40,41,25 and many frameworks seek to support this approach. Domain frameworks do include support for the application’s business logic, but at the expense of also constraining the implementation of that logic to fit the framework’s given structures.
7.3.2 Inversion of Control
The approach of building applications on top of existing networks of objects has dramatic consequences for the framework’s API, which are summarized under the term “inversion of control.” We will now examine this central switch of perspective, starting with a brief motivation.
Frameworks define generic mechanisms.
The tasks that frameworks take on are often complex and require an intricate software machinery. For instance, SWT handles layout, repainting, 7.1 and event dispatching, among many other things. All of these tasks require collaborations among many objects, both along the structure of the widget tree and outside it.
Frameworks set up such mechanisms, such planned sequences of collaborations 11.1 to relieve the application programmer from tedious standard tasks. The experience from building many applications has told the framework designers which mechanisms recur very often, and now the framework implements them once and for all. The application programmer can rely on the mechanisms, without understanding them in detail.
Frameworks rely on inversion of control.
Since the collaborations within the framework are complex, it is clear that the application cannot determine and prescribe every single step. With libraries, the application calls methods of service provider objects to get 1.8.2 things going. With frameworks, things are already going on all the time. The application is notified only at specific points that the framework designers have judged useful based on their experience.
For instance, SWT offers buttons that the user can click. The button itself handles the visual feedback on mouse movements and the keyboard focus. The application is notified when the only really interesting thing happens—that is, when the user has clicked the button.
Since some applications require it, there are also low-level notifications about the 7.8 mouse movements and the keyboard focus. These are not specific to buttons, but work for almost any widget. They are introduced higher up in the class hierarchy, at Control. Again, the framework designers had to anticipate the need for these events.
At the crucial points, the collaboration between framework and application is thus reversed, compared to the case of libraries: It is the framework that invokes application methods, while previously it was the application that invoked library methods. Since method calls are part of the software’s control flow, one speaks of inversion of control. The framework determines the control flow in general; it passes control to the application only at designated points. A snappy formulation that has stuck is the Hollywood Principle: 7.3.3 242 “Don’t call us, we’ll call you.”
If you already know SWT in some detail, you may feel at this point that SWT is 7.10.1 not a framework at all. From a technical perspective, it is actually the application that drives everything. The main method typically sets up the content of the application window and then keeps dispatching the incoming events by calling SWT methods:
swt.browser.SimpleBrowser.open
Display display = Display.getDefault();
... create shell and window contents
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
From a conceptual point of view, however, the application does not really control anything: The whole logic of event handling is determined by SWT, after the application calls readAndDispatch(). The application code in event handlers is called back only when and if SWT sees fit.
For development purposes, it is better to understand SWT as a framework with inversion of control. In fact, the boilerplate code for the main method is a peculiarity 7.6 of SWT: Other UI frameworks usually incorporate and encapsulate the dispatch loop completely.
Develop a laid back attitude toward frameworks.
We have found that novices often experience inversion of control first as a loss of control. That is, since they cannot see the code that makes things happen, they feel insecure about whether and when they will happen at all.
When working with frameworks, it is better to take the opposite perspective. You trust the framework to do its job properly. You can then conclude that you do not have to do anything until the framework explicitly asks you to do it. At this point, the framework will even hand to you all the information required for the task—you do not have to ask for it. Your software can “lay back” and “relax” in the meantime. As a developer, you can relax because you do not have to provide any code to make things happen, except for the special contributions at the callback points.
Know the framework’s mechanisms.
The biggest hurdle in learning frameworks is to understand which mechanisms exist and which mechanism is supposed to be used for which purpose. Framework designers envision the applications that will be built on top of the framework, and they provide mechanisms for those use cases that are likely to arise. Your job as an application programmer is to take the reverse step: Based on your use case, you check out which mechanism the designer has provided.
The most common way of learning frameworks is by studying examples and tutorials. If they are created by the original designers, these tutorials usually show how to use particular mechanisms. It is important to see a tutorial not as a source for copy-and-paste programming, but as a prototypical case demonstrating a greater idea. Look for the point behind the code, and ask which larger purpose each statement in the example code fulfills.
Sometimes choosing a mechanism requires some thought. As a simple 7.1 example, SWT provides two events that react to mouse clicks on Buttons: mouseClicked() and widgetSelected(). The second one is the correct one for ordinary “button clicks,” because it also covers activations created by pressing the “enter” key and as the default button of a dialog.
Frameworks cannot be coaxed by force to exhibit some behavior you need. Until you have found the mechanism that supports your use case, there is nothing to do but keep looking. Many frameworks have forums on the web, and many discussions in these forums circle around the problem of finding the right mechanism for a particular purpose. The framework users with guru status are usually distinguished by having more mechanisms right at their fingertips.
Very often, finding a mechanism involves understanding it in some detail. For instance, it is simple enough to create application-specific SWT 7.8 widgets that paint directly on the screen. For larger ones displaying complex data structures, however, you have to paint only the changed parts. 9.4.3 Fig. 7.7 sketches the involved steps, and each step has a particular conceptual justification. (The “model” contains the application’s data and business 9.2.1 logic, the “view” displays the data, and the “graphics context” GC is used for painting on the screen.)
Figure 7.7 Preview: Process of Incremental Screen Updates
Do not look at the framework’s internal implementation.
Even if mechanisms must be understood in some detail, their precise implementation is none of your business. It must be treated as encapsulated, even if it happens to involve protected or even public methods. The framework designers specify the possible points of interaction with the application, but 7.3.3 apart from these, the framework code may change without further notice. Only novices use the debugger to examine sequences of method calls and to override arbitrary protected methods that they happen to find. Doing this causes the Fragile Base Class Problem and will almost certainly break 3.1.11 the application within a few revisions of the framework.
To be entirely fair, it may be necessary to trace through the details of framework methods to understand the mechanisms. If the framework’s documentation is sketchy and consists of just a few tutorials, looking at the execution is the only chance you get. However, having obtained the information you need to understand the tutorials, you should forget whatever internal details you have learned on the way.
7.3.3 Adaptation Points in Frameworks
Inversion of control demands that the framework code calls application code at specified points. In other words, application code is injected into the control flow established by the framework. In object-oriented frameworks, this 1.4.1 is accomplished through polymorphism and dynamic method dispatch: The application code overrides or implements some method that the framework provides for the purpose. These designated methods are also called the hot spots of the framework. The remainder of the framework is supposed to be treated as a fixed, black-box setup called the frozen spots. The frozen spots constitute the infrastructure that the framework provides. Ignoring 3.1.11 the frozen spots leads to the Fragile Base Class Problem: The framework’s classes cannot change without breaking the application. With any framework you learn, it is therefore important to recognize the adaptation points and to understand their intended usage.
White-box frameworks: Applications override methods in framework classes.
The most straightforward way of specifying adaptation points is the direct 1.4.9 method overriding undertaken in the TEMPLATE METHOD pattern: The framework implements the general mechanisms and algorithms, but at specific points it passes control to designated protected methods. Applications 1.4.11 can override these to add new behavior. The documentation of the 3.1.2 framework class should then specify the method precisely: Which kind of behavior is expected to fit into it? Which kind of use case is it supposed to cover?
Frameworks that rely on this kind of collaboration are called white-box frameworks, because the application developer has to understand the framework’s class hierarchy and some of the classes’ internal mechanisms.
It is important to note that the application must not override just any protected method that it finds. Many of these methods are used for internal 3.1.4 purposes, such as to factor out reusable functionality within the framework’s own class hierarchy.
It can, however, be seen as an advantage of white-box frameworks that an application can in principle override any existing protected methods. It can therefore modify the framework’s behavior even in cases that the framework developers had not foreseen. As a result, it is slightly simpler to 7.3.4 develop white-box frameworks than black-box frameworks. Also, the risk that the framework might fall short of the application’s expectations is reduced. Of course, this flexibility comes at the price of increasing the learning curve.
Black-box frameworks: Applications plug in objects with designated interfaces.
Black-box frameworks hide the implementation of their mechanisms completely from applications. They define special interfaces that applications can implement to receive notifications at relevant points in the mechanisms. The API then resembles that of the OBSERVER pattern: The framework 2.1 defines notification interfaces; the application registers listeners for the notifications.
In SWT (and JFace), all events are delivered through black-box mechanisms. Most widgets in SWT are entirely black-box, because they just wrap native widgets from the window system; the underlying, fixed C-implementation 7.4 cannot easily call back protected methods on the Java side. Even the low-level callback for repainting a custom widget is invoked 7.8 on a dedicated PaintListener, while Java’s Swing, for instance, uses overriding of the paint() or paintComponent() methods from JComponent.
One advantage of black-box frameworks is that they are easier to learn. The application programmer can understand one adaptation point at a time and does not have to understand the framework’s class hierarchy and its possibly intricate sequences of up-calls and down-calls. The application is not constrained in the choice of super-classes, which enables reuse of 3.1.4 application-specific functionality through common super-classes. Also, the application is shielded from restructurings within the framework and the Fragile Base Class Problem is avoided. Many larger frameworks, such as 3.1.11 the Eclipse platform, rely on black-box mechanisms because they are likely to change over time and have large user bases who depend on backward compatibility.
One disadvantage of black-box frameworks is that any use cases not envisaged by the framework designers cannot be implemented at all, not even by low-level tweaks found only through debugging. As a result, developing useful black-box frameworks is more difficult and takes much more experience.
Also, it may be harder to actually implement the objects to plug into the framework, because the framework itself offers no infrastructure for this step. Many black-box frameworks remedy this by providing abstract base classes for their interfaces. For instance, JFace asks the application to 9.3.1 provide ILabelProviders to show data on the screen, but it offers a class LabelProvider that implements the obvious bookkeeping of listeners. Be sure to look for such classes: They can also help you understand what the object is supposed to do and which kinds of functionality are envisioned for an adaptation point.
Most frameworks are hybrids between black-box and white-box.
Because white-box and black-box frameworks have complementary advantages and disadvantages, most frameworks are actually hybrids: They offer the most frequently used adaptations in black-box style, and the more advanced (or obscure) adaptations in white-box style. Be sure to understand which kind you are currently dealing with; also, try to find a black-box mechanism first, because it may be more stable through evolutions of the framework.
7.3.4 Liabilities of Frameworks
Frameworks are great tools if they work for your application, but they can bring your project to a grinding halt if they don’t. Once you have decided on one framework, you are usually tied to it for the rest of the project. Be extremely conservative when choosing your frameworks.
Check early on whether the framework covers all of your use cases.
The biggest problem with frameworks is that their web pages look great and promise the world. But when it comes to actually coding the more advanced use cases of your application, you may find that the required adaptation points are missing. This is the most costly scenario for your company: You may have invested 90% of the project’s budget, only to find that you will have to redo the whole thing, because your code is tied to the one framework and will usually not be portable to others.
Two immediate countermeasures suggest themselves. First, before fixing the decision for a framework, build a throwaway prototype of your application; be sure to include the most advanced and most intricate requirements you can think of. The code you produce will probably be horrible, because you are only just learning the framework. But at least you will know that everything can be done. The second idea is related: You should check whether your use cases fall into the broad, well-trodden paths covered in tutorials and examples. That functionality is well tested and stable. Furthermore, if you need something similar, you are likely to find it covered as well.
Check whether the developers are really domain experts.
A good strategy to estimate whether a framework is likely to cover your needs is to look at its design team. If they are experienced professionals who have long worked in the domain that the framework addresses, you can be pretty sure that they have thought of all of your needs even before 174 you were aware of them. While exploring new corners of the Eclipse and 50 Netbeans platforms, I’m constantly surprised by the amount of detailed thought that must have gone into the API design. Anticipating the use cases of a framework is foremost a question of experience in the domain.
One reason why Eclipse has come out so well designed is certainly that IBM already had years of experience in building its Visual Age for Java development environment, and related products for other languages, starting with SmallTalk in 1993. The developers already knew the software structures that worked well, and more importantly those that did not work so well.
Opposite examples can be found in the myriad text editors written by hobbyists. Usually, the editors are the first ones that their developers have ever created. The projects start small, which means they do not have sufficient provisions for extensions. The authors have no strong background in software architecture, so they are likely to get the overall structures wrong and miss the important design constraints on APIs. If you build your application as a plugin to such an editor, your project is most certainly doomed to die within a few years (unless you get involved in developing the underlying platform, which will be a heavy investment).
Check whether there are detailed tutorials and examples.
Learning frameworks is best accomplished by studying their API definition and checking out the concrete use cases in their tutorials and examples. Finding the right way to do something in a tutorial saves you the effort of reading through the written documentation. To evaluate a framework, start by gathering the known requirements and checking whether they are covered in official tutorials.
Beware of tutorials written in blog style. They may show a possible way of accomplishing something, but this may not be the way that the framework designers had planned for. As a result, the steps in the tutorial may be more complicated than necessary or may use fringe features broken with the next release. Tutorials are more reliable if the authors do not simply list the steps they have taken, but also explain their meaning and purpose in a larger context. If a tutorial does not do that, try to link its proposals to the official documentation.
Check whether the framework has reliable long-term backing.
Another problem with frameworks is that there is usually a flurry of activity when they first appear, but over time the developers have to move on to other projects. For your own company, the effects of this lack of support may be dramatic: You may be stuck with an old version of a software whose bugs will remain unfixed forever. Indications to look out for are rather obvious: the size of the user base, the organization supporting the framework development, and the history of the project.
7.4 SWT and the Native Interface
Java is designed to run anywhere, on top of the JVM installed locally. Accordingly, the Swing and JavaFX user interface frameworks provide a common look-and-feel across platforms. As a result, users familiar with Java applications will be able to work with it regardless of the operating system they are currently using.
SWT lets you build professional applications that users will like.
SWT deviates from this Java point of view and accesses the native widget toolkit of the platform. As a result, an SWT application will look like a Windows, MacOS, or GTK application, depending on the environment. This is, in fact, what most users will prefer: A Swing application on Windows looks out of place to the ordinary Windows user, since everything looks a bit different, the keyboard shortcuts are different, and so on. The native toolkit also has the advantage of speed, since it is usually written in C and integrated tightly with the specific window system.
7.4.1 Influence on the API
Even if the native widgets themselves are encapsulated by the SWT widgets, the decision to target the native widget toolkit will influence the behavior of the SWT widgets at several places. It is therefore necessary to take a brief look at the relationship.
Fig. 7.8 gives an overview. The SWT widget tree is mirrored, although perhaps not one-to-one, in a native widget tree. Each SWT widget is a 2.4.3 2.4.4 PROXY for its native counterpart: It holds a handle to the native widget and uses it to invoke native functionality. However, the SWT widget is also 2.4.1 an ADAPTER, in that it translates an expected, cross-platform API to a platform-specific API.
Figure 7.8 SWT and Native Widget Trees
SWT widgets are not usable without their native counterparts.
Because of the close relationship, SWT widgets can work only if their native counterparts exist. Each constructor therefore creates the corresponding 7.1 native widget immediately. Because the constructor receives the parent widget as a parameter, it can add the native widget to the tree andobtain any context information—for instance, about fonts and background colors—that may be necessary at this point.
Furthermore, any access to an SWT widget first checks this fundamental 1.5.4 condition before possibly messing up the native C-level memory.
org.eclipse.swt.widgets.Widget
protected void checkWidget() {
if (display == null)
error(SWT.ERROR_WIDGET_DISPOSED);
...
if ((state & DISPOSED) != 0)
error(SWT.ERROR_WIDGET_DISPOSED);
}
The exception Widget is disposed occurs when you happen to access an SWT widget without a native counterpart.
Most widgets must not be subclassed.
Many user interface frameworks are white-box frameworks: You see a widget 7.3.3 that suits your needs in general, but it would need to be modified in the details—for instance, by painting some fancy overlay when the mouse is moved over it. In such a case, you just subclass and override the method 3.1.7 that paints the widget on the screen.
Since SWT widgets do not really live completely in the Java world, such ad-hoc adaptations by inheritance are not allowed. The interactions with the behavior of the native widgets could hardly be controlled. The method checkSubclass() in SWT widgets checks the restriction at runtime. Only a few widgets, such as Composite and Canvas, are meant to be 7.5 7.8 subclassed.
The application must dispose of any SWT widget or resource.
Java objects are managed by the JVM’s garbage collector: When the program cannot access a given object, that object’s memory is reclaimed and reused. This mechanism does not work for the C-level native widgets, because the garbage collector cannot work on the C stack and C data 133structures reliably.
When working with SWT, all widgets, as well as other resources such as images and fonts, must be freed explicitly by calling dispose() on the SWT widget. It is advisable to do so as soon as possible. For instance, if a dialog requires a particular image, then that image should be freed as soon as the dialog is closed.
Do not override dispose on widgets such as dialogs to free extra resources allocated. That method is used for the internal disposal of the native widgets. Attach a Dispose Listener instead.
The correct way to proceed is shown in the following snippet from a dialog that displays an image sun.png. Lines 1–2 load the image into memory. This data is an ordinary Java object that is handled by the garbage collector. Line 3 allocates a native image resource, which is not garbage-collected. Lines 4–8 therefore ensure that the image is disposed as soon as the dialog is closed.
swt.resources.DialogWithImage.createContents
1 ImageData imageData = new ImageData(
2 DialogWithImage.class.getResourceAsStream("sun.png"));
3 imageResource = new Image(shell.getDisplay(), imageData);
4 shell.addDisposeListener(new DisposeListener() {
5 public void widgetDisposed(DisposeEvent e) {
6 imageResource.dispose();
7 }
8 });
The WindowBuilder allows you to set the images of labels and buttons very conveniently through the widget’s properties. However, it creates a class SWTResource Manager, which owns and caches all images loaded in this manner. This class remains in memory for the whole runtime of the application, and the loaded images will not be freed unless the dispose() method of the class is called. This strategy is acceptable for simple applications, but large images should always be freed as soon as possible. Simple icons and fonts are usually obtained from the platform or are cached in ImageRegistrys associated with plugins.
Test an SWT application on every window system that you support.
Even though SWT provides a pretty good abstraction layer over the native widgets, that abstraction is not perfect. The behavior of widgets and the framework may differ in subtle details that break your application in special situations. Before shipping a product, be sure to test it on each supported target platform.
7.4.2 Influence on Launching Applications
Since SWT widgets internally create and manage native widgets, the SWT implementation is platform-specific and the SWT OSGi bundle contains native libraries accessed through the Java Native Interface (JNI). When an A.2 application is launched from within Eclipse, the correct plugin is selected automatically from the Eclipse installation. Similarly, when using SWT within an Eclipse plugin, the SWT implementation from the host Eclipse platform is used.
It is only when creating stand-alone applications that a little care must be taken to provide the right JAR file for the target platform. When that file is included in the class path, the application can be started as usual from the command line.
A more elegant approach is to use the Eclipse Rich Client Platform 174 directly. For this platform, the Eclipse developers provide a delta pack plugin, A.3 which contains the SWT plugins for all supported platforms. Also, the Eclipse export wizards for building the product are aware of the different target systems and will create different deliverables automatically.
7.5 Compound Widgets
Object-oriented programming makes it simple to aggregate combinations 1.8.5 2.2 of objects into higher-level components: Just create a new class to hold the necessary helpers, then wire up the helpers internally to create the desired functionality. The same can be done with widgets:
Derive reusable compound widgets from Composite.
What is more, the WindowBuilder actively supports this strategy: 7.2
Tool: Create Composite with WindowBuilder
From a package’s context menu, select New/Other and then SWT/Composite in the dialog. (Type “Composite” at the top.)
As a final touch, you can place any such widget easily into a larger context.
Tool: Reference Custom Widgets in WindowBuilder
In the WindowBuilder’s palette, use the System/Choose Component button to select any widget from the class path and drop it into the preview pane. Alternatively, you can create new categories and items in the palette from the palette’s context menu. Finally, you can bundle palette entries with the project using New/Other/WindowBuilder/Project Palette (type “palette” into the filter).
Follow the SWT convention of passing only the parent widget and possibly a bit-set of style flags to the constructor. Otherwise, the WindowBuilder might have problems creating the preview.
As an example, we will build a calendar view that enables the user to pick out a date in the expected way (Fig. 7.9, the upper component). That is, first the user switches to the correct month (starting from the current month), and then the user clicks on a day within the month. The widget has a property selectedDate and implements the OBSERVER pattern, so 2.1 that the example can track the current selection in the text field.
Figure 7.9 The DatePicker Widget
We will also use this example as a walk-through of user interface development in general. After all, any window or dialog we construct is just a compound widget, one that serves as a root of the widget tree. Although it may not be as stand-alone and reusable as the DatePicker, the general programming guidelines outlined here still apply. The overall setup is this: We subclass Composite and fill in the desired behavior.
swt.compound.DatePicker
public class DatePicker extends Composite {
...
public DatePicker(Composite parent) {
super(parent, SWT.BORDER);
...
}
...
}
You might be tempted to derive your widget from something like Text or Label, 7.4.1 because its behavior is similar to the one you need to create. However, most widgets in SWT must not be subclassed. Composite is an exception to the rule, as it explicitly allows for subclassing and disabling the corresponding safety check:
org.eclipse.swt.widgets.Composite
protected void checkSubclass() {
/* Do nothing - Subclassing is allowed */
}
If your widget is essentially a variant of an existing one, set the existing one as the Composite’s only child and let it fill the space with a FillLayout.
Make compound widgets into proper objects.
One thing to be aware of is that compound widgets are not just arbitrary collections of sub-widgets or nice visual gadgets. From a development perspective, they are first of all compound objects that function within 2.2 the software machinery. In the best case, they adhere to the fundamental 1.1 characteristics of objects: They provide a consistent API for a small, well-defined piece of functionality; they encapsulate the necessary machinery; and they communicate with their collaborators through well-defined channels. Furthermore, they take the ownership of their child widgets seriously, 2.2.1 2.2.3 in creating and disposing of them. Also, they define and maintain invariants 6.2 between their different parts and between their own fields and the parts. Because these aspects look slightly different in an event-driven, visual environment, let us look at them in some more detail for the date picker widget.
Implement a small, well-defined piece of functionality.
The action of “picking a date” is certainly a small enough task, and it appears in many applications. Also, the conventions for picking dates from a calendar are quite obvious, so that we know how the widget is supposed to behave. The idea of objects taking on a single task is therefore 1.1 11.2 obeyed.
Give the widgets a proper API.
To work well as a software object, a compound widget must be a convenient collaborator for the software’s other objects. It is therefore crucial to get the API right—just a nice visual appearance does not make for a great widget. As usual, it is a good idea to start from the clients’ perspective. 1.1 3.2.2 5.4.3 What is the result of picking a date, from a technical point of view? A date is commonly represented as a Date, so it is natural to provide a getter and setter for a property selectedDay:
swt.compound.DatePicker
public Date getSelectedDay()
public void setSelectedDay(Date date)
Fit compound widgets into the event-driven context.
User interfaces are all about the application reacting to user actions. If 7.1 the new widget is to work well in such an environment, it must offer all necessary events to its clients. For the example widget, it is sufficient to 2.1 implement the OBSERVER pattern for its single property. In doing so, we have to follow one more guideline:
Adapt the design to the context.
It is a fundamental design strategy to solve similar problems by similar means, since this keeps the overall software readable and understandable. 2.1.2 While the standard implementation of OBSERVER is clear, the context of SWT widgets suggests a different solution: to use SWT’s standard “selection” event and to reuse the notification infrastructure from the Widget class. First, here are the methods for the clients:
swt.compound.DatePicker
public void addSelectionListener(SelectionListener l) {
TypedListener typedListener = new TypedListener(l);
addListener(SWT.Selection, typedListener);
}
public void removeSelectionListener(SelectionListener l) {
removeListener(SWT.Selection, l);
}
Sending the notifications is then straightforward, since the superclass already provides the necessary method:
swt.compound.DatePicker.setSelectedDayInternal
notifyListeners(SWT.Selection, new Event());
2.1.2The default construction of new Event() might seem odd, since a subject should at least provide a reference to itself. To simplify this common coding idiom, the notification infrastructure in Widget fills in the basic fields of the event that have not yet been initialized.
Encapsulate internal data structures.
Compound widgets will usually require internal data structures and maintain invariants on them. The DatePicker widget shows how these can be used in the context of event-driven software.
Technically, the internal widget structure of compound widgets is accessible via 7.1 getChildren(), because compound widgets are part of the usual widget tree. However, clients are sure to know that such accesses are undesirable.
As a first point, the DatePicker (Fig. 7.9) must somehow maintain the current month, chosen in the upper part of the widget, and display its days in the lower part, aligned correctly with the days of the week. We can create the upper part and place a Composite to maintain the month’s days using the WindowBuilder:
swt.compound.DatePicker
private Label curMonth;
private Composite days;
But how do we represent the current month and the current selection? This question can be answered only by truly internal data structures. At the core of our application, we keep a Calendar, because this makes it simple to do arithmetic for the forward/backward buttons. We maintain the invariant that cal always contains the first day of the current month. 4.1 We store the currently selected day separately.
swt.compound.DatePicker
private Calendar cal;
private Calendar selectedDay;
The choice of selectedDay is especially interesting, because it links the internal data structures and the external behavior. Since we already have the current month, we might have used a simple int to store the selected day within that month. However, this choice would entail that when switching to the next month, the selected day would also change, while the user probably perceives the upper part of the widget only as a browser for weeks. With the choice of selectedDay, we can simply highlight in the lower part just the day that is equal to the selectedDay. 1.4.13
The next question is how to perform the display of the days. The simplest choice is to set a GridLayout on days and place the different text snippets as labels, as shown in Fig. 7.10. The GridLayout has seven columns, so we can easily place the first row as a list of labels with bold font (addDay places a Label with fixed size into days).
Figure 7.10 Days Area of the DatePicker Widget
swt.compound.DatePicker.updateDisplay
for (String head : DAY_HEADS) {
Label l = addDay(head);
l.setFont(bold);
}
Next, we have to leave a few fields blank (Fig. 7.10; note that fields in Calendar are 1-based).
swt.compound.DatePicker.updateDisplay
int startWeekDay = cal.get(Calendar.DAY_OF_WEEK);
for (int i = 1; i < startWeekDay; i++)
new Label(days, SWT.TRANSPARENT);
Finally, we can fill in the actual days. In general, this is just a loop over the days of the month (line 4). To retrieve the label for a given day easily, we keep the index of the first such label (line 1); the others follow sequentially. For each day, we create a label (line 6); if it is the currently selected day, we highlight it (lines 7–9). Finally, we make the label clickable by registering our own handler (line 10). That handler will have to find out the day that the label represents. We exploit the widget’s generic data slot (line 11), which is meant to contain just such application-specific data associated with a visual element.
swt.compound.DatePicker.updateDisplay
1 firstDayChildIndex = days.getChildren().length;
2 int lastDay = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
3 Calendar tmp = (Calendar) cal.clone();
4 for (int i = cal.getActualMinimum(Calendar.DAY_OF_MONTH);
5 i <= lastDay; i++) {
6 Label l = addDay(Integer.toString(i));
7 tmp.set(Calendar.DAY_OF_MONTH, i);
8 if (tmp.equals(selectedDay))
9 l.setFont(bold);
10 l.addMouseListener(selectionHandler);
11 l.setData(tmp.clone());
12 }
With this setup, it is straightforward to react to clicks: Just extract the data associated with the label and set it as the currently selected date. The helper method also unhighlights the previously selected label and highlights the new one.
swt.compound.DatePicker
private class SelectionHandler extends MouseAdapter {
public void mouseUp(MouseEvent e) {
Label clicked = (Label) e.getSource();
setSelectedDayInternal((Calendar) clicked.getData());
}
}
The example shows a particular characteristic of custom widgets, and 7.11 event-driven programming in general: The event handlers require information that must be set up beforehand, and about which invariants need to be maintained.
Layouts are not automatic.
The process of computing layouts is comparatively expensive. It is therefore 7.1 performed only at special points, such as when a window is first opened. In the current context, we have to trigger the layout of the newly added children of the compound days explicitly:
swt.compound.DatePicker.updateDisplay
days.layout();
Keep track of resources.
The DatePicker also contains an example of using resources: The bold font set on labels must be allocated before it can be used. From a user’s perspective, a font is just given by its name, size, and flags such as “italic” or “bold.” Within the window system, each font is associated with actual resources. Specifically, the relevant font definition (usually in the form of splines) must be loaded from disk, and each character must be rendered into a bitmap for quick rendering according to the font size, the screen resolution, and the color depth.
SWT also differentiates between the two views: FontData objects contain the external description of a desired font, whereas a Font is the actual window system resource associated with the loaded font data and bitmap cache. The next code snippet first creates a bold variant of the currently used default font by overriding the flag in the FontData (lines 1–3) and allocating the corresponding resources (line 4). Lines 5–9 then make sure that the resources are freed once the DatePicker is no longer used. 7.1
swt.compound.DatePicker.DatePicker
1 FontData[] fds = getFont().getFontData();
2 for (FontData fd : fds)
3 fd.setStyle(SWT.BOLD);
4 bold = new Font(getDisplay(), fds);
5 addDisposeListener(new DisposeListener() {
6 public void widgetDisposed(DisposeEvent e) {
7 bold.dispose();
8 }
9 });
This kind of behavior is typical of compound objects. First, ownership 2.2.1 dictates that the owner determines the life cycle of its parts; when the DatePicker is disposed, so is its font. Second, there is a consistency condition 2.2.3 6.2.2 or invariant between the parts: The bold font will be allocated as long as it may still be used in any Label created for the days.
Keep widgets independent of their surroundings to enable reuse.
Developing user interfaces can be quite an effort. It is therefore highly desirable to develop elements such as the DatePicker that are reusable 12.1 12.4 in different environments. To achieve this, it is necessary to decouple the elements from their context.
Let us briefly review the relevant decisions in the current example. One central element to achieve this is to use the OBSERVER pattern rather than sending specific messages to specific collaborators. Since UIs are event-driven anyway, this choice also fits in well with the general style of programming. Furthermore, the DatePicker’s API offers a generic property selectedDay. In contrast, a widget SelectFlightStartDate with method getSelectedFlightDate, which might occur in a travel booking application, would certainly be not reusable immediately. Also, making the API of the OBSERVER consistent with SWT’s conventions will lower the effort required for reuse because other team members will understand exactly what to do.
Refactor parts of existing widgets into compound widgets.
1.4.8.3Reusable objects are often obtained by extracting reusable parts from larger objects. It is useful to consider compound widgets to be usual objects in this respect, too: Since the WindowBuilder parses the source code, you can actually use the Extract Class tool to create sub-widgets. When you find a UI element in one part of the application that might be reused in a different place, invest the bit of time needed to refactor the part into a stand-alone compound widget, if there is any chance that you can reuse it a third time.
1.4.12 The WindowBuilder itself similarly offers to introduce factory methods that create a chosen composite and can then be invoked from different points of the application as well, since they appear in the palette. Although it requires a bit more effort, creating a separate class gives you better control over encapsulation and makes you independent of the WindowBuilder.
7.6 Dialogs
Most applications have one window and the user interacts with the application mostly through that window. However, the same applications usually do employ additional or auxiliary windows. For instance, these windows may be used for the following purposes:
• To ask for more information or for a decision from within an event handler and wait for the answer before proceeding, such as in confirming the deletion of a file
• To display detailed information or enable editing of some element shown in the main window, such as in the “properties” dialog on files in the package explorer
• To show a small direct editing field for a specific aspect of the data 9.3.1 214 displayed
• To display additional information or choices, such as in Eclipse’s Java auto-completion
The common theme of these situations is that the application’s main window is overlaid by a smaller window and that the new window receives all the input for the application, so that access to the main window is temporarily blocked. We will examine these elements of auxiliary Shellobjects in more detail in this section.
If you wish to experience the different settings discussed, you can use the DialogDemo application from the online supplement (Fig. 7.11). The names of flags and choices in the figure correspond directly to the technical flags at the SWT level that we will discuss.
Figure 7.11 Dialog Demo
In the presentation, we encounter the slight problem that all windows are really “shells.” To simplify the reading, we will speak of the “application window” and the “dialog,” even if strictly speaking the “dialog” is yet another shell that does not differ from the “application window” at the technical level, and “dialogs” can even open “dialogs” themselves.
Both SWT and its abstraction layer JFace contain classes named Dialog. These are 9.3 not shells themselves, but merely provide a framework for creating shells.
The modality of a shell determines whether it blocks other windows.
Dialogs are usually employed to focus users’ attention on a particular point—for instance, to force them to make a decision or to enable them to edit the properties of a particular element of a data structure. From this perspective, it is sensible to block the remainder of the application as long as the dialog is being shown.
Dialogs that block the application window are called modal; those that do not are called modeless. The window system will silently ignore all input such as mouse clicks that the user may try to send to the application window.
SWT provides different levels of modality: The default, primary modal, means that only the window that opens the dialog will be blocked; application modal blocks all windows of the same application; and system modal blocks the remainder of the screen, if this capability is supported by the underlying window system. All of these modality levels should be read as hints given to the window system. For instance, if it does not support primary modal dialogs, it will silently fall back to application modal. It is therefore best for applications not to rely on these fine distinctions.
Blocking dialogs can also greatly simplify the code structure. While the dialog is open, the application’s data structures cannot be modified in some other way, and one can even wait for an answer or decision by the user. To demonstrate the mechanism, the simple application in Fig. 7.12(a)asks the user for a name, as shown in Fig. 7.12(b), and shows the answer.
Figure 7.12 Modal Dialog Demo
The actual code can simply open the dialog. That call blocks until the dialog is closed again, at which point the result is available. (The parameter to the AskMeDialog constructor is the parent shell of the new window, to be discussed later.)
swt.dialogs.ModalDemo.ModalDemo
AskMeDialog dlg = new AskMeDialog(getShell());
String result = dlg.open();
if (result != null)
txtResult.setText(result);
else
txtResult.setText("- no answer -");
The AskMeDialog derives from SWT’s Dialog. It demonstrates two important points in the event-listener attached to the “OK” button. When the user clicks that button, the dialog is closed in line 4. Before that, the user’s answer is read from the input text field in line 3, and is stored in a field of the dialog class.
swt.dialogs.AskMeDialog.createContents
1 btnOk.addSelectionListener(new SelectionAdapter() {
2 public void widgetSelected(SelectionEvent e) {
3 result = txtName.getText();
4 shell.close();
5 }
6 });
Always retrieve values from widgets before closing the dialog. Otherwise, the contained 7.4.1 widgets are disposed and are no longer accessible.
A final question that needs to be addressed is how the result is actually returned to the caller after the dialog closes. In SWT, the answer can even be traced in the code: The open() method in the following snippet first makes the new shell appear on the screen and then keeps dispatching events 7.10.1 until the dialog is closed (lines 6–10). At this point, the result has been set by the listener and can be returned to the caller.
swt.dialogs.AskMeDialog.open
1 public String open() {
2 createContents();
3 shell.open();
4 shell.layout();
5 Display display = getParent().getDisplay();
6 while (!shell.isDisposed()) {
7 if (!display.readAndDispatch()) {
8 display.sleep();
9 }
10 }
11 return result;
12 }
The parent element of a shell determines z-order and minimization.
The most important aspect of dialogs is that they appear “above the main window,” where they are expected to catch the user’s attention immediately. In practice, this simple demand has three facets: In a setup with multiple monitors or workspaces, dialogs appear on the same monitor and workspace as the main window. Within that workspace, they usually appear centered over the application window. And, finally, they appear over the application window, in the sense that the application window never partially conceals them. This visual layering of elements on the screen is usually referred to as their z-order. All of those aspects must be taken into account to achieve a satisfactory user experience.
The single most important setting toward that end is to pass the proper parent shell to the constructor of a new shell. That setting links the new window to its parent, so that the new window appears at the expected location on the screen and above its parent in the z-order. The dialog even remains on top if the user reactivates the main window by clicking into that window (which is possible only with modeless dialogs).
A shell without a parent, in contrast, will appear in the middle of the screen when opened. It may also be buried behind the main application window, or the user can place the application window on top of the dialog. However, that behavior is system-dependent and depends on further settings. For instance, a modal dialog may still appear on top of the window that was active before the dialog was opened, even if that window is not its explicit parent shell. Here, the window system has made an informed guess at what was probably the user’s intention in opening the dialog. You should not rely on this behavior, but rather explicitly set a parent shell on each dialog.
Shells always involve interaction with the window manager.
Users appreciate and expect consistency: The windows on their desktop must look and behave the same, regardless of which application happens to open them. Window systems therefore include a window manager component that handles the drawing of and interaction with top-level windows. As a result, the visual appearance of a window is split into two parts (Fig. 7.13). The window’s content is created by the application, possibly using a systemwide library of available widgets. The title bar and border, in contrast, are drawn by the window manager. The window manager also handles moving, resizing, minimizing, and maximizing of windows. SWT calls the elements collectively the trimmings of a window. (Other frameworks call these the window’s decorations.)
Figure 7.13 The Trimmings of a Shell
The trimmings can be specified by many style-bits passed to the constructor of a Shell (see Fig. 7.11). However, many of these should be taken as only hints to the window manager, which is ultimately responsible for creating and handling the visual appearance consistently with the system’s overall look-and-feel. Also, there may be interdependencies. For instance, some window managers will not provide minimize and maximize buttons if the shell is not resizable.
Place shells in global, absolute coordinates.
Several applications mentioned in the introduction require the dialog to appear at specific positions and even to be moved along programmatically. For instance, an auto-completion pop-up in a source code editor may follow the cursor to reflect the current insertion point. We demonstrate here only the basics: We open a list of selections below a button that triggers the appearance of the list (Fig. 7.14). The general style of the dialog is set by SWT.NO_TRIM, meaning that the window manager will not provide additional decorations.
Figure 7.14 Opening a Dialog at a Given Position
The code that follows demonstrates how the special layout requirements are fulfilled. Lines 1–2 first compute the desired size, by determining the preferred width of the list and enlarging the size by any borders that the 7.1 window manager may wish to add despite the “no trim” flag. Lines 3–4 compute the lower-left corner of the widget below which the dialog is to appear. Line 3 works in coordinates local to the target widget, and line 4 translates the chosen point to the global coordinate system in which the top-level windows are placed. Lines 5–8 then lay out and display the dialog.
swt.dialogs.DialogDemo.openDialogBelow
1 Point listSize = select.computeSize(-1, -1, true);
2 Rectangle dialogSize = dialog.computeTrim(0, 0, listSize.x, 100);
3 Rectangle targetBounds = target.getBounds();
4 Point anchor = target.toDisplay(0, targetBounds.height);
5 dialog.setBounds(anchor.x, anchor.y, dialogSize.width,
6 dialogSize.height);
7 dialog.layout();
8 dialog.open();
Use standard dialogs where possible.
The discussion presented here has introduced the conceptual and technical points of using dialogs. Since dialogs are such a standard feature, it is not sensible to consider the details over and over again for each instance. Instead, many dialog-style interactions are already supported by standard dialogs. Choosing files, fonts, or printers, as well as requesting confirmation and displaying errors—all of these are hardly new challenges. Indeed, 9.3 A SWT, JFace, and the Eclipse platform layer already contain standard implementations for them—FileDialog (orDirectoryDialog), FontDialog, PrintDialog, and MessageDialog, respectively.
Using standard dialogs has the additional advantage that they are usually shared across applications, so that the user is familiar with them. Besides simplifying the implementation, they will increase the acceptance of your application.
7.7 Mediator Pattern
Event-driven software comes with one fundamental complexity: Each possible event can be fired at almost any time and events can be fired in almost any order, and by different sources of events. In the case of user interfaces, the user’s mouse gestures and keyboard input, the state changes of widgets, as well as the scheduled timers all cause events that must be processed correctly and reliably. The MEDIATOR pattern helps in organizing the reaction 100 to events.
Pattern: Mediator
In a situation where a group of objects sends messages, such as event notifications, to one another, a central mediator object can help to localize the necessary logic and to keep the other participating objects independent of one another.
7.1For an example, let us go back to the introductory simple browser [Fig. 7.1; Fig. 7.15(a)]: The user can target the browser to a new URL either by pressing “enter” in the text field or by pushing a button. The browser then fires notifications about progress and address changes. The “load” button will be enabled only if some URL has been entered. The code implementing these reactions is scattered throughout the various listeners, so that it is hard to trace whether the desired behavior is achieved.
Figure 7.15 Motivation for Mediators
The situation changes dramatically if we introduce a central mediator object [Fig. 7.15(b)]. It receives all events, decides on the proper reaction, and calls the corresponding methods on the involved widgets.
swt.mediator.BrowserWithMediator.createContents
url.addSelectionListener(mediator);
url.addModifyListener(mediator);
load.addSelectionListener(mediator);
browser.addStatusTextListener(mediator);
browser.addProgressListener(mediator);
browser.addTitleListener(mediator);
We make the mediator a nested class within the application window so that it has access to all relevant objects. The mediator is then kept in a field of the browser object:
swt.mediator.BrowserWithMediator
private Mediator mediator = new Mediator();
The Mediator class is set up to receive all occurring events. As a result, it can also contain all the logic that is necessary for handling these events.
swt.mediator.BrowserWithMediator
private class Mediator implements SelectionListener, ModifyListener,
ProgressListener, StatusTextListener, TitleListener {
...
}
7.1The mediator’s methods contain code that is similar to the original event handlers. This is not surprising, since they serve the same purpose and implement the same reactions. However, because in principle events can come in from several widgets, they check the source at the beginning (line 2 in the next code snippet). The example also shows an immediate benefit of the approach: Because the code is centralized within Mediator, 7.1 it becomes simpler to extract reactions triggered by different access paths 1.4.5 into helper methods such asretargetBrowser.
swt.mediator.BrowserWithMediator.widgetDefaultSelected
1 public void widgetDefaultSelected(SelectionEvent e) {
2 if (e.getSource() == url) {
3 retargetBrowser();
4 }
5 }
6 public void widgetSelected(SelectionEvent e) {
7 if (e.getSource() == load) {
8 retargetBrowser();
9 }
10 }
11 protected void retargetBrowser() {
12 if (!url.getText().isEmpty())
13 browser.setUrl(url.getText());
14 }
That’s it for the basics. Let us now examine some consequences of using mediators.
Mediators can encapsulate complex logic.
In the example, the interdependencies between the objects were not really complex. However, in dialogs with many widgets that influence one another’s state, the communication between those widgets can itself be substantial. Placing it in a separate mediator object helps keep the code readable.
This result is similar to that of introducing objects as containers for 1.8.6 algorithms and the data structures that they work on. There, too, the complexity of a piece of logic suggests that it should be stored away in a separate object, rather than let it clutter its surroundings.
Delegating all events to the host object introduces an implicit mediator.
In the context of the OBSERVER pattern, we have discussed the approach 2.1.3 of having observers call protected methods in a surrounding object. The focus there was on handling the state changes induced by the events in one class, rather than throughout the code.
Mediators serve a similar role, but they usually receive the events immediately, rather than providing specialized methods that are called from the listeners attached to the different widgets. However, the resulting structure is similar, and the similarity creates a new perspective on the former approach: the surrounding object becomes an implicit mediator for its parts.
Mediators make the code more complex by an extra indirection.
Finally, it must be said that mediators are not the only possible solution to handling events and that the original structure in Fig. 7.15(a) does, in fact, have its merits: Each widget in the code immediately exhibits the reactions associated with it. One can answer questions like “What happens if...” straightforwardly, because the event-listener is right there on the spot, close to the creation of the widget. In the mediator, in contrast, one has to find the called method and the right branch in the case distinction on the event source.
Introducing MEDIATOR is therefore really a decision. You have to consider whether it is better to have localized reactions at the cost of scattering the overall logic throughout the code, or a centralized logic at the cost of introducing an indirection.
7.8 Custom Painting for Widgets
In some situations, the predefined widgets are not sufficient. For instance, your application data may require special rendering, or you may want to 11.3.1 brand your application with special elements to make it stand out.
Avoid creating custom widgets.
Custom widgets imply a lot of work. Besides the actual painting with pixel-based computations of geometry, you have to place their elements manually, compute the optimal size based on the local fonts, and deal with low-level mouse and keyboard input. Also, it is best to check that the widget actually behaves as expected on the different operating systems. Think twice before setting out on that journey.
Very often what seems to be a special widget at first can still be created as a compound widget with special listeners attached. In particular, static graphics can often be simulated by setting icons on Labels without text; the label will render only the given image.
Restrict the custom painting to the smallest possible element.
Since managing a custom widget entails so much work, it is good to keep the work minimal by restricting the new widget to just the things that really need special painting. For example, if you wanted to write a memory game, the largest solution would be a widget MemoryBoard that does everything: places the cards, handles the mouse clicks, implements the rules of the game, and so on. Of course, we need custom painting, because we want to have nice-looking cards with custom borders, and so on. However, the cards are laid out in a grid, so that part is already handled by the GridLayout.
So we decide to implement a custom widget MemoryCard instead. Fig. 7.16 shows two such cards placed side by side. One shows a checkered back-side, and the other shows the card’s image.
Figure 7.16 Memory Cards
Derive custom widgets from Canvas.
7.4.1In SWT, one cannot simply subclass any likely-looking widget and adapt its behavior by method overriding. New widgets requiring custom painting are to be derived from Canvas, which is a raw rectangular space onto which arbitrary graphics can be painted.
swt.custom.MemoryCard
public class MemoryCard extends Canvas {
...
public MemoryCard(Composite parent) {
super(parent, SWT.NONE);
...
}
...
}
Canvas, according to its documentation, does not handle children properly, so you can use it only for atomic special elements. Possibly, you will have to stitch together your desired component from a core Canvas, embedded into other elements as before.
Apply the object-oriented structuring guidelines.
As for compound widgets, the new widget should always be considered as 7.5 a software artifact: How must its API and behavior be defined to make it a useful collaborator that works well in different contexts? We have discussed the relevant aspects for the compound widgets. For the current example, they translate to the following decisions. We will be brief, because the considerations closely resemble those from the DatePicker.
First, we see that the reaction to a mouse click is context-dependent. While it would usually flip the card, it might also cause two revealed images to be hidden at some other point in the game. We therefore decide to let the MemoryCard provide a generic “selection” mechanism and give it a proper imageName that serves as an identifier for the shown image. Also, the client can explicitly choose whether the image is shown. In this way, different decks of cards can be chosen. Together, these considerations lead to the following API:
swt.custom.MemoryCard
public String getImageName()
public void setImageName(String name)
public boolean isImageShown()
public void setImageShown(boolean show)
public void addSelectionListener(SelectionListener l)
public void removeSelectionListener(SelectionListener l)
The imageName is a property rather than a parameter to the constructor because the WindowBuilder does not handle such constructors well. 7.5
Painting on the screen is event-driven.
The central question is, of course, how to paint on the Canvas. In other frameworks, such as Swing and the Graphical Editing Framework’s Draw2D 214 layer, the solution is to override one or several methods that are responsible for drawing different parts of a figure in an application of the TEMPLATE 1.4.9 METHOD pattern. SWT instead lets you register PaintListeners that are called back whenever some part of the Canvas needs re-drawing. The MemoryCard’s constructor sets this up, as shown in the next example. We choose to delegate to the outer class here, because painting is one of the 2.1.3 MemoryCard’s tasks and the PaintListener is only a technical necessity.
swt.custom.MemoryCard.MemoryCard
addPaintListener(new PaintListener() {
public void paintControl(PaintEvent e) {
paintWidget(e.gc);
}
});
The actual painting code writes to the display through the given GC (for graphics context) object. Lines 2–5 paint either the image or back-side of the card; lines 6–13 draw a series of rounded rectangles with different shades of gray to create a 3D effect around the card (Fig. 7.16).
swt.custom.MemoryCard
1 protected void paintWidget(GC gc) {
2 if (imageShown)
3 gc.drawImage(image, BORDER_WIDTH, BORDER_WIDTH);
4 else
5 gc.drawImage(checkered, BORDER_WIDTH, BORDER_WIDTH);
6 Rectangle bounds = getBounds();
7 for (int i = 0; i != BORDER_WIDTH; i++) {
8 int val = 150 + 10 * i;
9 gc.setForeground(new Color(null, val, val, val));
10 gc.drawRoundRectangle(i, i, bounds.width - 2 * i,
11 bounds.height - 2 * i, 5, 5);
12
13 }
14 }