Building User Interfaces
As shown in the gallery, GWT includes a variety of pre-built Java widgets and panels that serve as cross-browser building blocks for your application. GWT also includes unique and powerful optimization facilities such as image bundles.
Contents
- Overview As shown in the gallery, GWT includes a variety of pre-built Java widgets and panels that serve as cross-browser building blocks for your application. GWT also includes unique and powerful optimization facilities such as image bundles.
- Widgets and Panels Widgets and panels are client-side Java classes used to build user interfaces.
- Widgets Gallery A gallery of widgets and panels.
- Events and Listeners Widgets publish events using the well-known listener pattern.
- Creating Custom Widgets Create your own widgets completely in Java code.
- Understanding Layout Understanding how widgets are laid out within panels.
- Style Sheets Widgets are most easily styled using cascading style sheets (CSS).
- Image Bundles Optimize the performance of your application by reducing the number of HTTP requests for images.
- Creating and Using an Image Bundle Define an image bundle and use it in your application.
- Image Bundles and Localization Create locale-sensitive image bundles by using GWT's localization capabilities.
- Creating and Using an Image Bundle
Overview
While it is possible to manipulate the browser's DOM directly using the DOM interface, it is far easier to use classes from the Widget hierarchy. You should rarely, if ever, need to access the DOM directly. Using widgets makes it much easier to quickly build interfaces that will work correctly on all browsers.
Widgets and Panels
Widgets and panels work the same way on all browsers; by using them, you eliminate the need to write specialized code for each browser. But you are not limited to the set of widgets provided by the toolkit. There are a number of ways to create custom widgets yourself.
Panels
Panels, such as DockPanel, HorizontalPanel, and RootPanel, contain widgets and are used to define how they are laid out in the browser.
Styles
Visual styles are applied to widgets using Cascading Style Sheets (CSS). This section describes in detail how to use this feature.
Widgets Gallery
Button | RadioButton |
PushButton | ToggleButton |
CheckBox | TextBox |
PasswordTextBox | TextArea |
Hyperlink | ListBox |
MenuBar | Tree |
Table | TabBar |
DialogBox | PopupPanel |
StackPanel | HorizontalPanel |
VerticalPanel | FlowPanel |
VerticalSplitPanel | HorizontalSplitPanel |
DockPanel | TabPanel |
RichTextArea | DisclosurePanel |
SuggestBox | |
Events and Listeners
The Button class, for example, publishes click events. The associated listener interface is ClickListener.
public void anonClickListenerExample() {Using anonymous inner classes as in the above example can be inefficient for a large number of widgets, since it could result in the creation of many listener objects. Widgets supply their
Button b = new Button("Click Me");
b.addClickListener(new ClickListener() {
public void onClick(Widget sender) {
// handle the click event
}
});
}
this
pointer as the sender
parameter when they invoke a listener method, allowing a single listener to distinguish between multiple event publishers. This makes better use of memory but requires slightly more code, as shown in the following example: public class ListenerExample extends Composite implements ClickListener {Some event interfaces specify more than one event. If you are only interested in a subset of these events, subclass one of the event "adapters". Adapters are simply empty concrete implementations of a particular event interface, from which you can derive a listener class without having to implement every method.
private FlowPanel fp = new FlowPanel();
private Button b1 = new Button("Button 1");
private Button b2 = new Button("Button 2");
public ListenerExample() {
initWidget(fp);
fp.add(b1);
fp.add(b2);
b1.addClickListener(this);
b2.addClickListener(this);
}
public void onClick(Widget sender) {
if (sender == b1) {
// handle b1 being clicked
} else if (sender == b2) {
// handle b2 being clicked
}
}
}
public void adapterExample() {
TextBox t = new TextBox();
t.addKeyboardListener(new KeyboardListenerAdapter() {
public void onKeyPress(Widget sender, char keyCode, int modifiers) {
// handle only this one event
}
});
}
Creating Custom Widgets
Composites
Composites are by far the most effective way to create new widgets. You can easily combine groups of existing widgets into a composite that is itself a reusable widget. Composite is a specialized widget that can contain another component (typically, a panel) but behaves as if it were its contained widget. Using Composite is preferable to attempting to create complex widgets by subclassing Panel because a composite usually wants to control which methods are publicly accessible without exposing those methods that it would inherit from its panel superclass. This is an example of how to create a composite.From Scratch in Java code
It is also possible to create a widget from scratch, although it is trickier since you have to write code at a lower level. Many of the basic widgets are written this way, such as Button and TextBox. Please refer to the implementations of these widgets to understand how to create your own.Using JavaScript
When implementing a custom widget that derives directly from the Widget base class, you may also write some of the widget's methods using JavaScript. This should generally be done only as a last resort, as it becomes necessary to consider the cross-browser implications of the native methods that you write, and also becomes more difficult to debug. For an example of this pattern in practice, see the TextBox widget and its underlying implementation.Understanding Layout
RootPanel
The first panel you're likely to encounter is the RootPanel. This panel is always at the top of the containment hierarchy. The default RootPanel wraps the HTML document's body, and is obtained by calling RootPanel.get(). If you need to get a root panel wrapping another element in the HTML document, you can do so using RootPanel.get(String).CellPanel
CellPanel is the abstract base class for DockPanel, HorizontalPanel, and VerticalPanel. What these panels all have in common is that they position their child widgets within logical "cells". Thus, a child widget can be aligned within the cell that contains it, using setCellHorizontalAlignment() and setCellVerticalAlignment(). CellPanels also allow you to set the size of the cells themselves (relative to the panel as a whole) using CellPanel.setCellWidth and CellPanel.setCellHeight.Other Panels
Other panels include DeckPanel, TabPanel, FlowPanel, HTMLPanel, and StackPanel.Sizes and Measures
It is possible to set the size of a widget explicitly using setWidth(), setHeight(), and setSize(). The arguments to these methods are strings, rather than integers, because they accept any valid CSS measurements, such as pixels (128px), centimeters (3cm), and percentage (100%).Style Sheets
gwt-Button
. In order to give all buttons a larger font, you could put the following rule in your application's CSS file: .gwt-Button { font-size: 150%; }
Complex Styles
Some widgets have somewhat more complex styles associated with them. MenuBar, for example, has the following styles:
.gwt-MenuBar { the menu bar itself }
.gwt-MenuBar .gwt-MenuItem { menu items }
.gwt-MenuBar .gwt-MenuItem-selected { selected menu items }
In this example, there are two styles rules that apply to menu items. The first applies to all menu items (both selected and unselected), while the second (with the -selected suffix) applies only to selected menu items. A selected menu item's style name will be set to "gwt-MenuItem gwt-MenuItem-selected"
, specifying that both style rules will be applied. The most common way of doing this is to use setStyleName to set the base style name, then addStyleName() and removeStyleName() to add and remove the second style name.
CSS Files
Typically, stylesheets are placed in a package that is part of your module's public path. Then simply include a reference to the stylesheet in your host page, such as
Documentation
It is standard practice to document the relevant CSS style names for each widget class as part of its doc comment. For a simple example, see Button. For a more complex example, see MenuBar.
Image Bundles
Sending out requests and freshness checks for many images will slow down your application. HTTP 1.1 requires browsers to limit the number of outgoing HTTP connections to two per domain/port. A multitude of image requests will tie up the browser's available connections, which blocks the application's RPC requests. RPC requests are the real work that the application needs to do.
To solve this problem, GWT introduces the concept of an image bundle. An image bundle is a composition of many images into a single image, along with an interface for accessing the individual images from within the composite. Users can define an image bundle that contains the images used by their application, and GWT will automatically create the composite image and provide an implementation of the interface for accessing each individual image. Instead of a round trip to the server for each image, only one round trip to the server for the composite image is needed.
Since the filename of the composite image is based on a hash of the file's contents, the filename will change only if the composite image is changed. This means that it is safe for clients to cache the composite image permanently, which avoids the unnecessary freshness checks for unchanged images. To make this work, the server configuration needs to specify that composite images never expire.
In addition to speeding up startup, image bundles prevent the 'bouncy' effect of image loading in browsers. While images are loading, browsers put a standard placeholder for each image in the UI. The placeholder is a standard size because the browser does not know what the size of an image is until it has been fully downloaded from the server. The result is a 'bouncy' effect, where images 'pop' into the UI once they are downloaded. With image bundles, the size of each individual image within the bundle is discovered when the bundle is created, so the size of the image can be explicitly set whenever images from a bundle are used in an application.
Creating and Using an Image Bundle
The derived interface can have zero or more methods, where each method
- takes no parameters,
- has a return type of AbstractImagePrototype, and
- may have an optional
gwt.resource
metadata tag which specifies the name of the image file in the module's classpath
Valid image file types are png
, gif
, and jpg
. If the image name contains '/
' characters, it is assumed to be the name of a resource on the classpath, formatted as would be expected by ClassLoader.getResource(String)
. Otherwise, the image must be located in the same package as the user-defined image bundle.
If the gwt.resource
metadata tag is not specified, then
- the image filename is assumed to match the method name,
- the extension is assumed to be either
.png
,.gif
, or.jpg
, and - the file is assumed to be in the same package as the derived interface
In the event that there are multiple image files with different extensions, the order of extension precedence is (1) png
, (2) gif
, then (3) jpg
.
An image bundle for icons in a word processor application could be defined as follows:
public interface WordProcessorImageBundle extends ImageBundle {
/**
* Would match the file 'new_file_icon.png', 'new_file_icon.gif', or
* 'new_file_icon.png' located in the same package as this type.
*/
public AbstractImagePrototype new_file_icon();
/**
* Would match the file 'open_file_icon.gif' located in the same
* package as this type.
*
* @gwt.resource open_file_icon.gif
*/
public AbstractImagePrototype openFileIcon();
/**
* Would match the file 'savefile.gif' located in the package
* 'com.mycompany.mygwtapp.icons', provided that this package is part
* of the module's classpath.
*
* @gwt.resource com/mycompany/mygwtapp/icons/savefile.gif
*/
public AbstractImagePrototype saveFileIcon();
}
Methods in an image bundle return AbstractImagePrototype
objects (rather than Image
objects, as you might have expected) because AbstractImagePrototype
objects provide additional lightweight representations of an image. For example, the AbstractImagePrototype.getHTML() method provides an HTML fragment representing an image without having to create an actual instance of the Image widget. In some cases, it can be more efficient to manage images using these HTML fragments.
Another use of AbstractImagePrototype
is to use AbstractImagePrototype.applyTo(Image) to transform an existing Image
into one that matches the prototype without having to instantiate another Image
object. This can be useful if your application has an image that needs to be swapped depending on some user-initiated action. Of course, if an Image
is exactly what you need, the AbstractImagePrototype.createImage() method can be used to generate new Image
instances.
The following example shows how to use the image bundle that we just defined in your application:
public void useImageBundle() {
WordProcessorImageBundle wpImageBundle = (WordProcessorImageBundle) GWT.create(WordProcessorImageBundle.class);
HorizontalPanel tbPanel = new HorizontalPanel();
tbPanel.add(wpImageBundle.new_file_icon().createImage());
tbPanel.add(wpImageBundle.openFileIcon().createImage());
tbPanel.add(wpImageBundle.saveFileIcon().createImage());
}
Image Bundles and Localization
The best way to explain this technique is with an example. Suppose that we define the following ImageBundle
for use by a mail application:
public interface MailImageBundle extends ImageBundle {Suppose the application has to handle both English and French users. We define English and French variations of each image in
/**
* The default 'Compose New Message' icon if no locale-specific
* image is specified.
*
* @gwt.resource compose_new_message_icon.gif
*/
public AbstractImagePrototype composeNewMessageIcon();
/**
* The default 'Help' icon if no locale-specific image is specified.
* Will match 'help_icon.png', 'help_icon.gif', or 'help_icon.jpg' in
* the same package as this type.
*/
public AbstractImagePrototype help_icon();
}
MailImageBundle
by creating locale-specific image bundles that extend MailImageBundle
: public interface MailImageBundle_en extends MailImageBundle {
/**
* The English version of the 'Compose New Message' icon.
* Since we are not overriding the help_icon() method, this bundle
* uses the inherited method from MailImageBundle.
*
* @gwt.resource compose_new_message_icon_en.gif
*/
public AbstractImagePrototype composeNewMessageIcon();
}
public interface MailImageBundle_fr extends MailImageBundle {The final step is to create a mechanism for choosing the correct image bundle based on the user's locale. By extending Localizable, we can create a locale-sensitive factory that will return new instances of
/**
* The French version of the 'Compose New Message' icon.
*
* @gwt.resource compose_new_message_icon_fr.gif
*/
public AbstractImagePrototype composeNewMessageIcon();
/**
* The French version of the 'Help' icon.
*
* @gwt.resource help_icon_fr.gif
*/
public AbstractImagePrototype help_icon();
}
MailImageBundle
that match the factory's locale: public interface MailImageBundleFactory extends Localizable {
public MailImageBundle createImageBundle();
}
public class MailImageBundleFactory_en implements MailImageBundleFactory {
public MailImageBundle createImageBundle() {
return (MailImageBundle) GWT.create(MailImageBundle_en.class);
}
}
public class MailImageBundleFactory_fr implements MailImageBundleFactory {The application code that utilizes a locale-sensitive image bundle would look something like this:
public MailImageBundle createImageBundle() {
return (MailImageBundle) GWT.create(MailImageBundle_fr.class);
}
}
public void useLocalizedImageBundle() {
// Create a locale-sensitive MailImageBundleFactory
MailImageBundleFactory mailImageBundleFactory = (MailImageBundleFactory) GWT
.create(MailImageBundleFactory.class);
// This will return a locale-sensitive MailImageBundle, since we are using
// a locale-sensitive factory to create it.
MailImageBundle mailImageBundle = mailImageBundleFactory.createImageBundle();
// Get the image prototype for the icon that we are interested in.
AbstractImagePrototype helpIconProto = mailImageBundle.help_icon();
// Create an Image object from the prototype and add it to a panel.
HorizontalPanel panel = new HorizontalPanel();
panel.add(helpIconProto.createImage());
}
0 comments:
Post a Comment