A Form is a screen that can include an arbitrary collection of user-interface controls, called items. In a movie ticket reservation MIDlet, you might use a form to allow the user to enter a date and a Zip code on one screen.
Keep in mind that the minimum screen size for a MID is 96 ?54 pixels. You can't fit a whole lot on a screen this size, nor should you try to. Forms that don't fit on the screen will automatically scroll as needed, so your MIDlet will be able to show forms, regardless of the screen size. Scrolling forms tend to be confusing to users, however, so you should keep your forms as small as possible
The javax.microedition.ldcui.Form class itself is fairly simple. One way to create a Form is by specifying a title:
In essence, a Form is a collection of items. Each item is represented by an instance of the Item class. If you have all the items ahead of time, you can pass them to Form's other constructor:
public Form(String title, Item[] items)
As a subclass of Screen and Displayable, Form inherits both a title and a ticker. Given the small screen size of a typical MIDP device, however, you may want to avoid using a ticker with your forms.
Form's grandparent class, Displayable, gives Form the capabilities of displaying commands and firing command events. Again, you should probably keep commands simple with forms; in many cases a Next and a Back will probably be sufficient.
As with any Displayable, the basic strategy for showing a Form is to create one and pass it to Display's setCurrent() method. MIDP 2.0 offers an additional option, the setCurrentItem() method in Display. This method makes the form containing the item visible, then it scrolls the form so the item is visible and has input focus.
Items may be added and removed, even while the Form is showing. The order of items is important, as well; most MIDP implementations will display a form's items top to bottom and possibly left to right, scrolling the form vertically as needed if there are more items than available screen space.
To add an Item to the bottom of a form, use one of the append() methods. The first one can be used to add any Item implementation. The second two append() methods are strictly for convenience; behind the scenes, a StringItem or an ImageItem will be created for you.
public int append(Item item) public int append(String str) public int append(Image image)
Every item in a form has an index. You can place an item at a specific index (replacing the previous item at that index) using the method:
Alternately, if you'd like to add an item somewhere in the middle of the form, just supply the desired index for the new item to the insert() method. Subsequent items will move up by one index.
public void insert(int index, Item item)
To remove an item from a form, use delete().
public void delete(int index)
MIDP 2.0 also includes a deleteAll() method, which removes all of a Form's Items.
If you forget what you put in a form, you can find out the number of items and retrieve them with the following methods:
public int size() public Item get(int index)
In MIDP 1.0 implementations, Forms are mostly vertical beasts. In general, items added to a form will appear in a vertical stack. If the items don't all fit on the screen, the form allows the user to scroll as needed.
The exceptions to this rule are StringItems and ImageItems. These items may be laid out left-to-right if there is enough space on the screen. However, it's up to the implementation to decide exactly how a form is laid out. In the J2ME Wireless Toolkit 1.0.1 emulator, StringItems and ImageItems are always stacked vertically.
MIDP 2.0 acknowledges the ever-expanding screens of MIDP devices by adding support for more specific layout. There's an exhaustive description of the layout algorithm in the documentation for javax.microedition.lcdui.Form, in the section titled "Layout." Stated briefly, Form attempts to lay out items left-to-right in rows, stacking rows top-to-bottom, just like English text on a page. The Item class, as you'll see, includes extra plumbing in MIDP 2.0 that allows some control over the layout of individual items.
The MIDP specification includes a handy toolbox of items that can be used to build forms. I'll cover each of them briefly in this section and show how some of them look in Sun's MIDP reference implementation.
All of the items that can be added to forms descend from the class javax.microedition.lcdui.Item. In MIDP 1.0, Item doesn't specify much, just a getLabel() and setLabel() method. All Items have a string label, although it may or may not be shown by the specific subclass.
MIDP 2.0 expands the Item class considerably in two areas. The first is command handling. In MIDP 2.0, Items can have commands, just like Displayables. When an Item is selected in a form, the Item's commands are shown along with the commands in the form. Figure 6-3 shows a form with four string items, cunningly named "one," "two," "three," and "four." The form itself has one command, "Exit." None of the string items has commands, except for "three," which has one command named "Details."
Note how the toolkit emulator indicates the presence of one or more commands on the item with a light underline. When you navigate through the form to the item with the additional command, it shows up just like any other command, as shown in Figure 6-4.
The semantics for managing item commands are nearly identical to the semantics for managing form commands. You can manage the commands on an Item using addCommand() and removeCommand(). Note that the command type should be ITEM for commands added to Item, although no exception will be thrown if this is not true. A command listener may be assigned using the setItemCommandListener() method. The ItemCommandListener interface contains a single method, similar to CommandListener's single method:
public void commandAction(Command c, Item item)
It's up to the implementation to figure out how to show commands for an item. All you do in a MIDlet is add commands, set a listener, and wait for command events.
Items also support a default command. This command may be invoked if the runtime device has a button or knob or other user interface control that is appropriate for a default command. You can set an Item's default command by calling setDefaultCommand().
Item's greatest API changes in MIDP 2.0 are related to layout control. Items have a minimum size and a preferred size that can be used to control how large an item appears in a form. The minimum size is computed by the implementation and can be retrieved using getMinimumWidth() and getMinimumHeight(). The minimum size depends on the contents of the Item and can be changed by the implementation every time the contents change. There's no way to change an item's minimum size, but examining the minimum size may be useful to your application in deciding how to lay out a form.
The preferred size, by contrast, can either be computed by the implementation or specified by you. The default values for preferred width and height are -1, a special value that tells the implementation "I don't care, you go ahead and figure out the best size for this item." If you pass a specific positive value for the width or height in setPreferredSize(), that dimension is said to be locked and the implementation will attempt to use it during layout.
The getPreferredWidth() and getPreferredHeight() methods don't always return the values you've passed to setPreferredSize(). For example, if you've unlocked the width and height by calling setPreferredSize(-1, -1) the values returned from getPreferredWidth() and getPreferredHeight() are the preferred sizes that the implementation has computed.
Finally, the MIDP 2.0 Item class includes a layout directive, accessed using getLayout() and setLayout(). Represented by an integer, the layout value is usually a combination of LAYOUT_2 (a flag indicating MIDP 2.0 layout), a horizontal value and a vertical value. LAYOUT_2 is a flag to the implementation that the item should be laid out using MIDP 2.0 rules. The horizontal values are
LAYOUT_LEFT
LAYOUT_RIGHT
LAYOUT_CENTER
The vertical values are
LAYOUT_TOP
LAYOUT_BOTTOM
LAYOUT_VCENTER
In addition, a layout value may include shrinking or expanding. Shrinking means that an item's minimum width or height is used, while expanding means that an item's size is stretched to fill the available width or row height. The constants for shrinking and expanding are
LAYOUT_SHRINK (for width)
LAYOUT_EXPAND (for width)
LAYOUT_VSHRINK (for height)
LAYOUT_VEXPAND (for height)
Finally, an Item's layout may include a request for a new line before or after the item using the LAYOUT_NEWLINE_BEFORE or LAYOUT_NEWLINE_AFTER constants. Items are laid out in Forms much like text flows on a page, so these constants allow you to request a new row before or after an item.
Figure 6-5 shows a simple example, three components with the following layouts:
LAYOUT_2 | LAYOUT_LEFT | LAYOUT_NEWLINE_AFTER
LAYOUT_2 | LAYOUT_CENTER | LAYOUT_NEWLINE_AFTER
LAYOUT_2 | LAYOUT_RIGHT | LAYOUT_NEWLINE_AFTER
StringItem represents a simple text label. For example, consider the following code:
Form form = new Form("Form Title");
StringItem stringItem = new StringItem("Label: ", "Value");
form.append(stringItem);
The form produced by this code (plus a Back command) is shown in Figure 6-6.
You can use null for the StringItem's label or value to indicate that it should not be shown on the screen. (Better yet, you could just use Form's append(String) method.) StringItem inherits setLabel() and getLabel() methods from Item. It also includes getText() and setText() methods for accessing and adjusting the string value.
MIDP 2.0 adds support for appearance modes for both StringItem and ImageItem. The appearance mode allows the item to look like a URL link or a button, although in all other respects the item behaves the same as a regular StringItem or ImageItem. The three appearance modes (which are defined in the Item class) are
PLAIN shows the item in its normal state.
HYPERLINK shows the item as a URL. A typical action would be to attempt to open the link using MIDlet's platformRequest() method.
BUTTON shows the item as a button. Note that this may be clumsy, especially on devices without pointer events, and you should generally use a Command where you feel tempted to use an item with a BUTTON appearance mode.
As with almost everything else in the javax.microedition.lcdui package, it's the implementation's responsibility to show different appearance modes, and your application may look different on different devices. Furthermore, it is your application's responsibility to implement appropriate behavior. For example, you might want to add a command to a HYPERLINK StringItem that calls MIDlet's platformRequest() method to open the link.
| Note?/td> |
The J2ME Wireless Toolkit beta release emulators don't show HYPERLINK or BUTTON StringItems any differently from PLAIN ones, except for one special case. If the StringItem has a BUTTON type and it has an associated item command, it is shown with a beveled border. |
Finally, MIDP 2.0 offers getFont() and setFont() methods in the StringItem class. I'll describe the Font class in Chapter 10.
MIDP 2.0 introduces a new Item, Spacer, that represents empty space in a Form. It may be used for layout purposes. All you need to do is specify a minimum width and height:
public Spacer(minWidth, minHeight)
TextField represents an editable string. Figure 6-7 shows a TextField with a label of "TextFieldTitle" and a value of "text".
In Sun's MIDP 2.0 emulator, text can be entered directly into a TextField either by clicking on the number buttons in the emulator or by typing on the keyboard. Of course, it's up to the implementation to decide exactly how to allow editing. Some implementations may even show a separate screen for editing.
TextFields can limit input. The following constants are defined:
ANY allows any type of input.
NUMERIC restricts the input to numbers.
DECIMAL (new in MIDP 2.0) allows numbers with fractional parts.
PHONENUMBER requires a telephone number.
EMAILADDR input must be an e-mail address.
URL input must be a URL.
These input constraints might look familiar; they're the same ones used by TextBox, which I covered in the previous chapter. As with TextBox, the flags PASSWORD, SENSITIVE, UNEDITABLE, NON_PREDICTIVE, INITIAL_CAPS_WORD, and INITIAL_CAPS_SENTENCE can be combined with constraints using the OR operator.
To create a TextField, you need to supply the label, text value, maximum length, and input constraints.
public TextField(String label, String text, int maxSize, int constraints)
For an initially empty TextField, pass null for the text parameter.
As with TextBox, the TextField class in MIDP 2.0 includes a setInitialInputMode() method for suggesting to the implementation an appropriate input mode.
Forms can also contain images, which are represented by instances of ImageItem. ImageItems have several pieces of associated data:
A label may be displayed with the image.
The layout determines the placement of the image.
Alternate text is displayed if the image cannot be shown.
To create an ImageItem, just supply the Image that is to be displayed, the label, layout, and alternate text.
ImageItem defines constants for the layout parameter. The simplest thing is to specify the default value, LAYOUT_DEFAULT. If you need more control, combine a horizontal value with a vertical value. The horizontal values are LAYOUT_LEFT, LAYOUT_CENTER, and LAYOUT_RIGHT. The vertical values are LAYOUT_NEWLINE_BEFORE and LAYOUT_NEWLINE_AFTER. In MIDP 2.0, layout is controlled with the layout constants in the Item class. The constants in the ImageItem class are present for backward compatibility.
ImageItem supports appearance modes in MIDP 2.0, just like StringItem. ItemItem includes a new constructor that allows you to set the appearance mode.
Figure 6-8 shows a form containing a single ImageItem.
DateField is an extremely handy mechanism by which users can enter dates, times, or both. It's up to the implementation to determine some reasonable way for users to enter dates and times; you, as the MIDlet programmer, can simply use DateField and not worry about the implementation.
To create a DateField, specify a label and a type. Three constants in the DateField class describe the different types:
DATE displays an editable date.
TIME displays an editable time.
DATE_TIME displays both a date and a time.
DateField provides two constructors. The first uses the default time zone, while the second allows you to specify a TimeZone explicitly:
public DateField(String label, int mode) public DateField(String label, int mode, TimeZone timeZone)
In essence, a DateField is an editor for a java.util.Date. As you saw in Chapter 4, Dates represent points in time. DateField takes the role of translating between a Date and strings that humans can read, much like the Calendar class. You can set or get the Date represented by the DateField using the following methods:
public Date getDate() public void setDate(Date date)
In the J2ME Wireless Toolkit emulator, a DateField appears as shown in Figure 6-9a. Note that if you do not supply a Date to setDate() before showing the DateField, it will appear unitialized, as shown in Figure 6-9b.
When the user selects either the date or time portion of the DateField and selects it for editing, the MIDP implementation provides some kind of appropriate editor. Sun's emulator provides the editors shown in Figure 6-9c and Figure 6-9d.
A Gauge represents an integer value. It's up to the implementation to decide how to display it. In Sun's MIDP implementation, a Gauge appears as shown in Figure 6-10.
The value of the Gauge can be retrieved and modified with the getValue() and setValue() methods. This value runs from 0 to a variable maximum value. The maximum for the Gauge can be retrieved and modified with the getMaxValue() and setMaxValue() methods.
The visual appearance of the Gauge is an approximation of the Gauge's value. The Gauge shown in Figure 6-10 could, for example, have a value of 7 and a maximum of 10, or perhaps a value of 42 and a maximum of 61.
In an interactive Gauge, the user can modify the value. Again, it's up to the implementation to decide exactly how this works. In Sun's reference implementation, the left and right navigation buttons can be used to modify a Gauge's value.
Gauge's constructor is straightforward:
public Gauge(String label, boolean interactive,
int maxValue, int initialValue)
For example, the following code creates an interactive Gauge with a maximum value of 24 and an initial value of 2:
Gauge g = new Gauge("Power", true, 24, 2);
MIDP 2.0 expands Gauge's role. Interactive gauges are the same as before, but there are three varieties of noninteractive gauges that can be useful as progress indicators. You can use a regular noninteractive gauge with a known maximum value to show the progress of a download or a calculation. For example, if you were going to run through a loop 20 times, you could create a Gauge with a maximum of 20 and update its value each time through the loop.
There are two kinds of noninteractive gauges with no maximum value. In this case, you use the special value INDEFINITE for the maximum. Such gauges can be either incremental or continuous. An incremental gauge shows an operation with measurable steps; your application will update the gauge every time it does something significant. For example, if you were downloading a file, but you didn't know how big it was, you could use an incremental gauge and update the gauge whenever you read some data. A continuous gauge shows progress, probably using an animation, with no prodding needed from the application. This type of gauge is useful for operations where you can't measure the progress.
The gauge value itself can be set to one of the following:
INCREMENTAL_UPDATING indicates that you have just accomplished something and the gauge should be updated to reflect it.
INCREMENTAL_IDLE means that you want the gauge to be incremental but nothing is currently happening.
CONTINUOUS_RUNNING indicates a continuous gauge in its running mode.
CONTINUOUS_IDLE is used for a continuous gauge, indicating that no progress is currently being made.
The following example shows interactive, continuous, and incremental gauges. Commands (Update and Idle) set the appropriate values on the continuous and incremental gauges. Normally you would set these from separate threads, but using commands makes it easy to understand what's going on in this example.
In Sun's MIDP 2.0 emulator, the continuous and idle gauges use simple Duke animations to show progress. See Figure 6-11 for a screen shot. Listing 6-2 contains the source code for a MIDlet that demonstrates different kinds of Gauges.
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
public class GaugeMIDlet
extends MIDlet
implements CommandListener {
private Display mDisplay;
private Form mGaugeForm;
private Command mUpdateCommand, mIdleCommand;
private Gauge mInteractiveGauge;
private Gauge mIncrementalGauge;
private Gauge mContinuousGauge;
public GaugeMIDlet() {
mGaugeForm = new Form("Gauges");
mInteractiveGauge = new Gauge("Interactive", true, 5, 2);
mInteractiveGauge.setLayout(Item.LAYOUT_2);
mGaugeForm.append(mInteractiveGauge);
mContinuousGauge = new Gauge("Non-I continuous", false,
Gauge.INDEFINITE, Gauge.CONTINUOUS_RUNNING);
mContinuousGauge.setLayout(Item.LAYOUT_2);
mGaugeForm.append(mContinuousGauge);
mIncrementalGauge = new Gauge("Non-I incremental", false,
Gauge.INDEFINITE, Gauge.INCREMENTAL_UPDATING);
mIncrementalGauge.setLayout(Item.LAYOUT_2);
mGaugeForm.append(mIncrementalGauge);
mUpdateCommand = new Command("Update", Command.SCREEN, 0);
mIdleCommand = new Command("Idle", Command.SCREEN, 0);
Command exitCommand = new Command("Exit", Command.EXIT, 0);
mGaugeForm.addCommand(mUpdateCommand);
mGaugeForm.addCommand(mIdleCommand);
mGaugeForm.addCommand(exitCommand);
mGaugeForm.setCommandListener(this);
}
public void startApp() {
if (mDisplay == null) mDisplay = Display.getDisplay(this);
mDisplay.setCurrent(mGaugeForm);
}
public void pauseApp() {}
public void destroyApp(boolean unconditional) {}
public void commandAction(Command c, Displayable s) {
if (c.getCommandType() == Command.EXIT)
notifyDestroyed();
else if (c == mUpdateCommand) {
mContinuousGauge.setValue(Gauge.CONTINUOUS_RUNNING);
mIncrementalGauge.setValue(Gauge.INCREMENTAL_UPDATING);
}
else if (c == mIdleCommand) {
mContinuousGauge.setValue(Gauge.CONTINUOUS_IDLE);
mIncrementalGauge.setValue(Gauge.INCREMENTAL_IDLE);
}
}
}
The final class in the Form arsenal of Items is ChoiceGroup. ChoiceGroup offers a list of choices. It is very similar to javax.microedition.lcdui.List, which was described at the beginning of this chapter. This similarity is more than coincidental; ChoiceGroup and List both implement the Choice interface, which is the wellspring of all of the instance methods in both classes.
If you read the section about List, you already know almost everything you need to know to use ChoiceGroup because the instance methods work exactly the same way.
ChoiceGroup features the following constructors:
public ChoiceGroup(String label, int choiceType)
public ChoiceGroup(String label, int choiceType, String[] stringElements,
Image[] imageElements)
The choiceType should look familiar; it can be either EXCLUSIVE or MULTIPLE, the constants defined in the Choice interface. In fact, ChoiceGroup's constructors work exactly like List's constructors, except that IMPLICIT is not allowed. This makes sense, since a ChoiceGroup is one item in a form, not an entire screen. MIDP 2.0 also adds a POPUP type for ChoiceGroup that makes it appear like a combo box or a drop-down menu. The ChoiceGroup appears like any other element in the Form; Figure 6-12 shows examples.
Most items in a Form fire events when the user changes them. Your application can listen for these events by registering an ItemStateListener with the Form using the following method:
public void setItemStateListener(ItemStateListener iListener)
ItemStateListener is an interface with a single method. This method is called every time an item in a Form is changed:
public void itemStateChanged(Item item)
Listing 6-3 creates a Form with two items, an interactive Gauge and a StringItem. As you adjust the Gauge, its value is reflected in the StringItem using the ItemStateListener mechanism.
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
public class GaugeTracker
extends MIDlet
implements ItemStateListener, CommandListener {
private Gauge mGauge;
private StringItem mStringItem;
public GaugeTracker() {
int initialValue = 3;
mGauge = new Gauge("GaugeTitle", true, 5, initialValue);
mStringItem = new StringItem(null, "[value]");
itemStateChanged(mGauge);
}
public void itemStateChanged(Item item) {
if (item == mGauge)
mStringItem.setText("Value = " + mGauge.getValue());
}
public void commandAction(Command c, Displayable s) {
if (c.getCommandType() == Command.EXIT)
notifyDestroyed();
}
public void startApp() {
Form form = new Form("GaugeTracker");
form.addCommand(new Command("Exit", Command.EXIT, 0));
form.setCommandListener(this);
// Now add the selected items.
form.append(mGauge);
form.append(mStringItem);
form.setItemStateListener(this);
Display.getDisplay(this).setCurrent(form);
}
public void pauseApp() {}
public void destroyApp(boolean unconditional) {}
}