Skip to main content

J2ME Tutorial, Part 2: User Interfaces with MIDP 2.0

May 3, 2005

{cs.r.title}









Contents
User Interface Architecture
Alert
List
TextBox
Form
Images, Tickers, and Gauges
Handling User Commands
Working with the Low-Level API

This is part two in a series that explores J2ME with MIDP 2.0. "Part 1: Creating MIDlets" showed you how to acquire, install, and use the Wireless Toolkit for developing MIDlets. Part one also showed how to develop MIDlets without using the Toolkit, which is important in order to understand the behind-the-scenes activity involved in creating a MIDlet. Part one finished with an exploration of the lifecycle of a MIDlet, with a step-by-step guide through the events in the life of a MIDlet.

In this article, you will create the user interface (UI) elements
of a MIDlet. Since the interaction with a user is a paramount concern in any MIDlet, due to the size of the screens, it is important for you to understand the basics of this side of MIDlets. Any interaction with a user is done via a UI element. In fact, when you created the simplistic Date-Time MIDlet in part one, you used one such element called Alert to show an alert message on the screen. This message was actually shown on the screen by the help of another UI element called Display.

Let's start with a discussion of the overall architecture of the UI elements.

User Interface Architecture

MIDP 2.0 provides UI classes in two packages, javax.microedition.lcdui and javax.microedition.lcdui.game, where lcdui stands for liquid crystal display user interface (LCD UI). As expected, the game package contains classes for development of a wireless game UI. I will discuss this package in the next part of this series.

The UI classes of of MIDP 2.0's javax.microedition.lcdui package can be divided into two logical groups: the high- and low-level groups. The classes of the high-level group are perfect for development of MIDlets that target the maximum number of devices, because these classes do not provide exact control over their display. The high-level classes are heavily abstracted to provide minimal control over their look and feel, which is left for device on which they are deployed to manage, according to its capabilities. These classes are shown in Figure 1.

Figure 1
Figure 1. High-level MIDP 2.0 UI classes

The classes of the low-level group are perfect for MIDlets where precise control over the location and display of the UI elements is important and required. Of course, with more control comes less portability. If your MIDlet is developed using these classes, it may not be deployable on certain devices, because they require precise control over the way they look and feel. There are only two classes in this group, and they are shown in Figure 2.

Figure 2
Figure 2. Low-level MIDP 2.0 UI classes

There is another class in the low-level group called GameCanvas, which is not shown here, as it will be discussed in the next part of this series.

For you to be able to show a UI element on a device screen, whether high- or low-level, it must implement the Displayable interface. A displayable class may have a title, a ticker, and certain commands associated with it, among other things. This implies that both the Screen and Canvas classes and their subclasses implement this interface, as can be seen in Figure 3. The Graphics class does not implement this interface, because it deals with low-level 2D graphics that directly manipulate the device's screen.

Figure 3
Figure 3. Canvas and Screen implement the Displayable interface

A Displayable class is a UI element that can be shown on the device's screen while the Display class abstracts the display functions of an actual device's screen and makes them available to you. It provides methods to gain information about the screen and to show or change the current UI element that you want displayed. Thus, a MIDlet shows a Displayable UI element on a Display using the setCurrent(Displayable element) method of the Display class.

As the method name suggests, the Display can have only one Displayable element at one time, which becomes the current element on display. The current element that is being displayed can be accessed using the method getCurrent(), which returns an instance of a Displayable element. The static method getDisplay(MIDlet midlet) returns the current display instance associated with your MIDlet method.

A little bit of actual code here would go a long way in helping understand the MIDlet UI concepts that we have just discussed. Rather than write new code, let's try and retrofit our understanding on the Date-Time MIDlet example from part one, which is reproduced in Listing 1.

package com.j2me.part1;


import java.util.Date;


import javax.microedition.lcdui.Alert;

import javax.microedition.lcdui.Display;
import javax.microedition.midlet.MIDlet;


public class DateTimeApp extends MIDlet {

  Alert timeAlert;


  public DateTimeApp() {
    timeAlert = new Alert("Alert!");
    timeAlert.setString(new Date().toString());
  }

  public void startApp() {
    Display.getDisplay(this).setCurrent(timeAlert);
  }

  public void pauseApp() {
  }

  public void destroyApp(boolean unconditional) {
  }
}

Listing 1. DateTimeApp MIDlet

A Displayable UI element, an Alert, is created in the constructor. When the device's Application Management Software (AMS) calls the startApp() method, the current display available for this MIDlet is extracted using the Display.getDisplay() method. The Alert is then made the current item on display, by setting it as a parameter to the setCurrent() method.

As seen from Figure 1, there are four high-level UI elements that can be displayed on a MIDlet's screen. Let's discuss each of these elements in detail.

Alert

You already know how to create a basic alert message from Listing 1. Alerts are best used in informational or error messages that stay on the screen for a short period of time and then disappear. You can control several aspects of an alert by calling the relevant methods or using the right constructor.

  • The title must be set while creating the alert and it cannot be changed afterwards: Alert("Confirm?");.
  • To set the message the alert displays use, setString("Message to display") or pass the message as part of the constructor, Alert( "Confirm", "Are you sure?", null, null); .
  • Use setTimeout(int time) to set the time (in milliseconds) for which the alert is displayed on the screen. If you pass Alert.FOREVER as the value of time, you will show the alert forever and make the alert a modal dialog.
  • There are five types of alerts defined by the class AlertType: ALARM, CONFIRMATION, ERROR, INFO, and WARNING. These have different looks and feels and can have a sound played along with the alert.
  • Associate an image with the alert using the method
     
        setImage(Image img);
    .
  • Set an indicator with the alert using setIndicator(Gauge gauge); method.

List

A list contains one or more choices (elements), which must have a text part, an optional image part, and an optional font for the text part. The List element implements the Choice interface, which defines the basic operations of this element. The list must itself have a title, and must define a policy for the selection of its elements. This policy dictates whether only one element can be selected (Choice.EXCLUSIVE), multiple elements can be selected (Choice.MULTIPLE), or the currently highlighted element is selected (Choice.IMPLICIT). Figure 4 shows the difference between the three selection policies.

Figure 4

Figure 4. Selection policies for List elements

You can create a list in one of two ways.

  • Create an list that contains no elements, and then append or insert individual
    elements.
  • Create the elements beforehand and then create a list with these elements.

Listing 2 shows both ways.

  [prettify]
package com.j2me.part2;

import javax.microedition.lcdui.List;
import javax.microedition.lcdui.Choice;
import javax.microedition.lcdui.Display;
import javax.microedition.midlet.MIDlet;

public class ListExample extends MIDlet {

	List fruitList1;
	List fruitList2;

	public ListExample() {
	  fruitList1 = new List("Select the fruits you like",
	                         Choice.MULTIPLE);
	  fruitList1.append("Orange", null);
	  fruitList1.append("Apple", null);
	  fruitList1.insert(1, "Mango", null); 
	    // inserts between Orange and Apple

	  String fruits[] = {"Guava", "Berry", "Kiwifruit"};
	  fruitList2 =
	    new List(
				"Select the fruits you like - List 2",
	      Choice.IMPLICIT,
	      fruits,
	      null);
	}

	public void startApp() {
		Display display = Display.getDisplay(this);
		display.setCurrent(fruitList1);

		try{
		  Thread.currentThread().sleep(3000);
		} catch(Exception e) {}

		display.setCurrent(fruitList2);
	}

	public void pauseApp() {
	}

	public void destroyApp(boolean unconditional) {
	}
}
  
[/prettify]

Listing 2. Using Lists

List elements can be modified after the list has been created. You can modify individual elements by changing their text, text font, or image part, using the list index (starting at 0). You can delete elements using delete(int index) or deleteAll(). Any changes take effect immediately, even if the list is the current UI element being shown to the user.







TextBox

Text is entered by the user using a textbox. Like the other UI elements, a textbox has simple features that can be set based on your requirements. You can restrict the maximum number of characters that a user is allowed to enter into a textbox, but you need to be aware of the fact that the implementation of this value depends upon the device that you are running it on. For example, suppose that you request that a textbox is allowed a maximum of 50 characters, by using setMaxSize(50), but the device can only allocate a maximum of 32 characters. Then, the user of your MIDlet will only be able to enter 32 characters.

You can also constrain the text that is accepted by the textbox, as well as modify its display using bitwise flags defined in the TextField class. For example, to only accept email addresses in a textbox, you will need to set the TextField.EMAILADDR flag using the method setConstraints(). To make this field uneditable, you will need to combine it with the TextField.UNEDITABLE flag. This is done by doing a bitwise OR operation between these two flags: setConstraints(TextField.EMAILADDR | TextField.UNEDITABLE);.

There are six constraint settings for restricting content: ANY, EMAILADDR, NUMERIC, PHONENUMBER, URL, and DECIMAL. ANY allows all kinds of text to be entered, while the rest constrain according to their names. Similarly, there are six constraint settings that affect the display. These are: PASSWORD, UNEDITABLE, SENSITIVE, NON_PREDICTIVE, INITIAL_CAPS_WORD, and INITIAL_CAPS_SENTENCE. Not all of these settings may be functional in all devices.

To set the contents of a textbox, you can use a couple of methods. Use setString(String text) to set the contents with a String value. Use insert(String text, int position) to position text where you want it to go. Listing 3 shows how to use both these methods, along with some constraints.

package com.j2me.part2;

import javax.microedition.lcdui.TextBox;
import javax.microedition.lcdui.TextField;
import javax.microedition.lcdui.Display;
import javax.microedition.midlet.MIDlet;

public class TextBoxExample extends MIDlet {

private TextBox txtBox1;
private TextBox txtBox2;

public TextBoxExample() {
txtBox1 = new TextBox(
"Your Name?", "", 50, TextField.ANY);

txtBox2 = new TextBox(
"Your PIN?",
"",
4,
  TextField.NUMERIC | TextField.PASSWORD);
}

public void startApp() {
Display display = Display.getDisplay(this);
display.setCurrent(txtBox1);

try{
  Thread.currentThread()Sleep(5000);
} catch(Exception e) {}

txtBox1.setString("Bertice Boman");

try{
  Thread.currentThread()Sleep(3000);
} catch(Exception e) {}

// inserts 'w' at the 10th index to make the
// name Bertice Bowman
txtBox1.insert("w", 10);

try{
  Thread.currentThread()Sleep(3000);
} catch(Exception e) {}


display.setCurrent(txtBox2);
}

public void pauseApp() {
}

public void destroyApp(boolean unconditional) {
}
}

Listing 3. Using TextBoxes

The listing creates two textboxes: one that accepts anything under 50 characters, and one that accepts only four numeric characters that are not shown on the screen. If you try entering anything other than numbers in the numeric-only box, the device will not accept it, but the actual behavior may vary across actual devices.

Form

A form is a collections of instances of the Item interface. The TextBox class (discussed in the preceding section) is a standalone UI element, while the TextField is an Item instance. Essentially, a textbox can be shown on a device screen without the need for a form, but a text field requires a form.

An item is added to a form using the append(Item item) method, which simply tacks the added item to the bottom of the form and assigns it an index that represents its position in the form. The first added item is at index 0, the second at index 1, and so on. You can also use the insert(int index, Item newItem) method to insert an item at a particular position or use set(int index, Item newItem) to replace an item at a particular position specified by the index.

There are eight Item types that can be added to a form.

  1. StringItem: A label that cannot be modified by the user. This item may contain a title and text, both of which may be null to allow it to act as a placeholder. The Form class provides a shortcut for adding a StringItem, without a title: append(String text)
  2. DateField: Allows the user to enter date/time in one of three formats: DATE, TIME, or DATE_TIME.
  3. TextField: Same as a TextBox.
  4. ChoiceGroup: Same as a List.
  5. Spacer: Used for positioning UI elements by putting some space between them. This element is an invisible UI element and can be set to a particular size.
  6. Gauge: A gauge is used to simulate a progress bar. However, this progress bar look can also be used in an interactive mode by the user. For example, if you wanted to show the user a volume control, a gauge would be used to show an interactive knob.
  7. ImageItem: An item that holds an image! Like the StringItem, the Form class provides a shortcut method for adding an image: append(Image image). More about images in a later section.
  8. CustomItem: CustomItem is an abstract class that allows the creation of subclasses that have their own appearances, their own interactivity, and their own notification mechanisms. If you require a UI element that is different from the supplied elements, you can subclass CustomItem to create it for addition to a form.

These items (except CustomItem) can be seen in Figure 5, and the corresponding code is shown in Listing 4. (NOTE: The image, duke.gif, should be kept in the res folder of this MIDlet application.)

package com.j2me.part2;

import javax.microedition.lcdui.Form;
import javax.microedition.lcdui.Gauge;
import javax.microedition.lcdui.Spacer;
import javax.microedition.lcdui.ImageItem;
import javax.microedition.lcdui.TextField;
import javax.microedition.lcdui.DateField;
import javax.microedition.lcdui.StringItem;
import javax.microedition.lcdui.ChoiceGroup;

import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.Choice;
import javax.microedition.lcdui.Display;
import javax.microedition.midlet.MIDlet;

public class FormExample extends MIDlet {

private Form form;
private Gauge gauge;
private Spacer spacer;
private ImageItem imageItem;
private TextField txtField;
private DateField dateField;
private StringItem stringItem;
private ChoiceGroup choiceGroup;

public FormExample() {
form = new Form("Your Details");

// a StringItem is not editable
stringItem = new StringItem("Your Id: ", "WXP-890");
form.append(stringItem);

// you can accept Date, Time or DateTime formats
dateField = new DateField("Your DOB: ", DateField.DATE);
form.append(dateField);

// similar to using a TextBox
txtField = new TextField(
"Your Name: ", "", 50, TextField.ANY);
form.append(txtField);

// similar to using a List
choiceGroup = new ChoiceGroup(
"Your meals: ",
Choice.EXCLUSIVE,
new String[] {"Veg", "Non-Veg"},
null);
form.append(choiceGroup);

// put some space between the items to segregate
spacer = new Spacer(20, 20);
form.append(spacer);

// a gauge is used to show progress
gauge = new Gauge("Step 1 of 3", false, 3, 1);
form.append(gauge);

// an image may not be found,
// therefore the Exception must be handled
// or ignored
try {
imageItem = new ImageItem(
"Developed By: ",
Image.createImage("/duke.gif"),
ImageItem.LAYOUT_DEFAULT,
"DuKe");
form.append(imageItem);
} catch(Exception e) {}
}

public void startApp() {
Display display = Display.getDisplay(this);
display.setCurrent(form);
}

public void pauseApp() {
}

public void destroyApp(boolean unconditional) {
}
}

Listing 4. Using forms

Figure 5

Figure 5. The elements of a form







Images, Tickers, and Gauges

Using images, tickers, and gauges as UI elements in MIDlets is quite straightforward. A gauge, as you saw in the last section, is an item that can only be displayed on a form to indicate progress or to control a MIDlet feature (like volume). A ticker, on the other hand, can be attached to any UI element that extends the Displayable abstract class, and results in a running piece of text that is displayed across the screen whenever the element that is attached to it is shown on the screen. Finally, an image, can be used with various UI elements, including a form, as we saw in the last section.

Since the ticker can be used with all displayable elements, it provides a handy way to display information about the current element on the screen. The Displayable class provides the method setTicker(Ticker ticker), and the ticker can itself be created using its constructor Ticker(String msg), with the message that you want the ticker to display. By using setString(String MSG), you can change this message, and this change is effected immediately. For example, the form used in the previous section can have its own ticker displayed by setting form.setTicker(new Ticker("Welcome to Vandalay Industries!!!")). This will result in a ticker across the top of the screen (in the Toolkit's emulator), while the user is filling out the form. This is shown in Figure 6.

Figure 6
Figure 6. Setting a Ticker on a Displayable

In the previous section, we saw an example of a gauge in a non-interactive mode. It was there to represent the progress of a form being filled out by the MIDlet user. A non-interactive gauge can be used to represent the progress of a certain task; for example, when the device may be trying to make a network connection or reading a datastore, or when the user is filling out a form. In the previous section, we created a gauge by specifying four values. The label ("Step 1 of 3"), the interactive mode (false), the maximum value (3) and the initial value (1). However, when you don't know how long a particular activity is going to take, you can use the value of INDEFINITE for the maximum value.

A non-interactive gauge that has an INDEFINITE maximum value acquires special meaning. (You cannot create an interactive gauge with an INDEFINITE maximum value.) This type of gauge can be in one of four states, and this is reflected by the initial value (which is also the current value of the gauge). These states are CONTINUOUS_IDLE, INCREMENTAL_IDLE, CONTINUOUS_RUNNING, and INCREMENTAL_UPDATING. Each state represents the best effort of the device to let the user know the current activity of the MIDlet, and you can use them to represent these states yourself. Listing 5 shows an example of using these non-interactive gauges, along with an example of an interactive gauge. Remember that a Gauge is a UI Item, and therefore, can only be displayed as part of a Form.

Package com.j2me.part2;

import javax.microedition.lcdui.Form;
import javax.microedition.lcdui.Gauge;
import javax.microedition.lcdui.Display;
import javax.microedition.midlet.MIDlet;

public class GaugeExample extends MIDlet {

private Form form;
private Gauge niIndefinate_CI;
private Gauge niIndefinate_II;
private Gauge niIndefinate_CR;
private Gauge niIndefinate_IU;

private Gauge interactive;

public GaugeExample() {
form = new Form("Gauge Examples");

niIndefinate_CI =
  new Gauge(
"NI - Cont Idle",
false,
Gauge.INDEFINITE,
Gauge.CONTINUOUS_IDLE);
form.append(niIndefinate_CI);

niIndefinate_II =
  new Gauge(
"NI - Inc Idle",
false,
Gauge.INDEFINITE,
Gauge.INCREMENTAL_IDLE);
form.append(niIndefinate_II);

niIndefinate_CR =
  new Gauge(
"NI - Cont Run",
false,
Gauge.INDEFINITE,
Gauge.CONTINUOUS_RUNNING);
form.append(niIndefinate_CR);

niIndefinate_IU =
  new Gauge(
"NI - Inc Upd",
false,
Gauge.INDEFINITE,
Gauge.INCREMENTAL_UPDATING);
form.append(niIndefinate_IU);

interactive =
  new Gauge(
"Interactive ",
true,
10,
0);
form.append(interactive);

}

public void startApp() {
Display display = Display.getDisplay(this);
display.setCurrent(form);
}

public void pauseApp() {
}

public void destroyApp(boolean unconditional) {
}
}

Listing 5. Using Gauges

Each mobile device will use its own set of images to represent these gauges. This will, in all probability, be different from the gauges that you will see if you run this listing in the Emulator supplied with the Toolkit.

You can also associate an image with an Alert and a Choice-based UI element. When an image is created, either by reading from a physical location or by making an image in-memory, it exists only in the off-screen memory. You should, therefore, be careful while using images, and restrict the size of images to the minimum possible to avoid filling the device's available memory.

The Image class provides several static methods to create or acquire images for use in MIDlets. An image that is created in-memory, by using the createImage(int width, int height) method, is mutable, which means that you can edit it. An image created this way initially has all of its pixels set to white, and you can acquire a graphics object on this image by using the method getGraphics() to modify the way it is rendered on screen. More about the Graphics object follows in the low-level API section.

To acquire an immutable image, you can use one of two methods: createImage(String imageName) or createImage(InputStream stream). The first method is used for looking up images from an associated packaged .jar file, while the second method is good for reading an image over a network. To create an immutable image from in-memory data, you can either use createImage(byte[] imageData, int imageOffset, int imageLength) or createImage(Image source). The first method allows you to form an image out of a byte array representation, while the second allows the creation of an image from an existing image.

Note that the MIDlet specification mandates support for the Portable Network Graphics (PNG) format for images. Thus, all devices that support MIDlets will display a *.png image. These devices may support other formats, especially GIF and JPEG formats, but that is not a guarantee.

You have already seen an example of acquiring an image in the section on forms. In Listing 4, an image was wrapped up in an ImageItem class so that it could be displayed in a form. The image was kept in the res folder of the MIDlet for the Toolkit and the Emulator to find. The createImage(String imageName) method uses the Class.getResourceAsStream(String imageName) method to actually locate this image. The Toolkit takes care of packaging this image in the right folder when you create a ,jar file. In this case, this will be the top-level .jar folder. Make sure that whenever you reference images in your MIDlets that the images are kept in the right location. For example, if you want to keep all of your images for a MIDlet in an image folder in the final packaged .jar file, and not the top-level .jar folder, you will need to keep these images under an image folder under the res folder itself. To reference any of these images, you will need to ensure that you reference them via this images folder. For example: createImage("/images/duke.gif"); will reference the image duke.gif under the images folder.

Handling User Commands

None of the UI elements so far have allowed any interaction from the user! A MIDlet interacts with a user through commands. A command is the equivalent of a button or a menu item in a normal application, and can only be associated with a displayable UI element. Like a ticker, the Displayable class allows the user to attach a command to it by using the method addCommand(Command command). Unlike a ticker, a displayable UI element can have multiple commands associated with it.

The Command class holds the information about a command. This information is encapsulated in four properties. These properties are: a short label, an optional long label, a command type, and a priority. You create a command by providing these values in its constructor:

Command exitCommand = new Command("EXIT", Command.EXIT, 1);

Note that commands are immutable once created.

By specifying the type of a command, you can let the device running the MIDlet map any predefined keys on the device to the command itself. For example, a command with the type OK will be mapped to the device's OK key. The rest of the types are: BACK, CANCEL, EXIT, HELP, ITEM, SCREEN, and STOP. The SCREEN type relates to an application-defined command for the current screen. Both SCREEN and ITEM will probably never have any device-mapped keys.

By specifying a priority, you tell the AMS running the MIDlet where and how to show the command. A lower value for the priority is of higher importance, and therefore indicates a command that the user should be able to invoke directly. For example, you would probably always have an exit command visible to the user and give it a priority of 1. Since the screen space is limited, the device then bundles less-important commands into a menu. The actual implementation varies from device to device, but the most likely scenario involves one priority-1 command displayed along with an option to see the other commands via a menu. Figure 7 shows this likely scenario.

Figure 7
Figure 7. The way commands are displayed. The menu pops up when the user presses the key corresponding to the menu command.

The responsibility for acting on commands is performed by a class implementing the CommandListener interface, which has a single method: commandAction(Command com, Displayable dis). However, before command information can travel to a listener, The listener is registered with the method setCommandListener(CommandListener listener) from the Displayable class.

Putting this all together, Listing 6 shows how to add some commands to the form discussed in Listing 4.

package com.j2me.part2;

import javax.microedition.lcdui.Form;
import javax.microedition.lcdui.Gauge;
import javax.microedition.lcdui.Spacer;
import javax.microedition.lcdui.ImageItem;
import javax.microedition.lcdui.TextField;
import javax.microedition.lcdui.DateField;
import javax.microedition.lcdui.StringItem;
import javax.microedition.lcdui.ChoiceGroup;

import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.Choice;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Command;
import javax.microedition.midlet.MIDlet;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.CommandListener;

public class FormExample
  extends MIDlet
  implements CommandListener {

private Form form;
private Gauge gauge;

private Spacer spacer;
private ImageItem imageItem;
private TextField txtField;
private DateField dateField;
private StringItem stringItem;
private ChoiceGroup choiceGroup;

public FormExample() {
form = new Form("Your Details");

// a StringItem is not editable
stringItem = new StringItem("Your Id: ", "WXP-890");
form.append(stringItem);

// you can accept Date, Time or DateTime formats
dateField =
  new DateField("Your DOB: ", DateField.DATE);
form.append(dateField);

// similar to using a TextBox
txtField = new TextField(
"Your Name: ", "", 50, TextField.ANY);
form.append(txtField);

// similar to using a List
choiceGroup = new ChoiceGroup(
"Your meals: ",
Choice.EXCLUSIVE,
new String[] {"Veg", "Non-Veg"},
null);
form.append(choiceGroup);

// put some space between the items
spacer = new Spacer(20, 20);
form.append(spacer);

// a gauge is used to show progress
gauge = new Gauge("Step 1 of 3", false, 3, 1);
form.append(gauge);

// an image may not be found,
// therefore the Exception must be handled
// or ignored
try {
imageItem = new ImageItem(
"Developed By: ",
Image.createImage("/duke.gif"),
ImageItem.LAYOUT_DEFAULT,
"Duke");
form.append(imageItem);
} catch(Exception e) {}

// create some commands and add them
// to this form
form.addCommand(
new Command("EXIT", Command.EXIT, 2));
form.addCommand(
new Command("HELP", Command.HELP, 2));
form.addCommand(
new Command("OK", Command.OK, 1));

// set itself as the command listener
form.setCommandListener(this);

}

// handle commands
public void commandAction(
Command com, Displayable dis) {

String label = com.getLabel();

if("EXIT".equals(label))
  notifyDestroyed();
else if("HELP"Equals(label))
  displayHelp();
else if("OK"Equals(label))
  processForm();
}

public void displayHelp() {
// show help
}

public void processForm() {
// process Form
}


public void startApp() {
Display display = Display.getDisplay(this);
display.setCurrent(form);
}

public void pauseApp() {
}

public void destroyApp(boolean unconditional) {
}
}

Listing 6. Adding commands to a form

The differences from Listing 4 are highlighted in bold. The command listener in this case is the form class itself, and therefore, it implements the commandAction() method. Note that this method also accepts a displayable parameter, which is very useful. Because commands are immutable, they can be attached to multiple displayable objects, and this parameter can help distinguish which displayable object invoked the command.







Working with the Low-Level API

The low-level API for MIDlets is composed of the Canvas and Graphics classes (we will discuss the GameCanvas class in the next article). The Canvas class is abstract; you must create your own canvases to write/draw on by extending this class and providing an implementation for the paint(Graphics g) method, in which the actual drawing on a device is done. The Canvas and Graphics classes work together to provide low-level control over a device.

Let's start with a simple canvas. Listing 7 shows an example canvas that draws a black square in the middle of the device screen.

package com.j2me.part2;

import javax.microedition.lcdui.Canvas;
import javax.microedition.midlet.MIDlet;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Graphics;

public class CanvasExample
  extends MIDlet {

Canvas myCanvas;

public CanvasExample() {
myCanvas = new MyCanvas();
}

public void startApp() {
Display display = Display.getDisplay(this);

// remember, Canvas is a Displayable so it can
// be set on the display like Screen elements
display.setCurrent(myCanvas);

// force repaint of the canvas
myCanvas.repaint();
}

public void pauseApp() {
}

public void destroyApp(boolean unconditional) {
}
}

class MyCanvas extends Canvas {
public void paint(Graphics g) {
// create a 20x20 black square in the center
g.setColor(0x000000); // make sure it is black
g.fillRect(
getWidth()/2 - 10,
getHeight()/2 - 10,
20, 20);
}
}

Listing 7. Creating and displaying a Canvas

The class MyCanvas extends Canvas and overrides the paint() method. Although this method is called as soon as the canvas is made the current displayable element (by setCurrent(myCanvas)), it is a good idea to call the repaint() method on this canvas soon afterwards. The paint() method accepts a Graphics object, which provides methods for drawing 2D objects on the device screen. For example, in Listing 7, a black square is created in the middle of the screen using this Graphics object. Notice that before drawing the square, using the fillRect() method, the current color of the Graphics object is set to black by using the method g.setColor(). This is not necessary, as the default color is black, but this illustrates how to change it if you wanted to do so.

If you run this listing, the output on the emulator will be as shown in Figure 8.

Figure 8
Figure 8. Drawing a single square in the middle of a Canvas

Notice the highlighted portion at the top in Figure 8. Even though the MIDlet is running, the AMS still displays the previous screen. This is because in the paint() method, the previous screen was not cleared away, and the square was drawn on the existing surface. To clear the screen, you can add the following code in the paint() method, before the square is drawn.

g.setColor(0xffffff); 
     // sets the drawing color to white
g.fillRect(0, 0, getWidth(), getHeight());
     // creates a fill rect which is the size of the screen

Note that the getWidth() and getHeight() methods return the size of the display screen as the initial canvas, which is the whole display screen. Although the size of this canvas cannot be changed, you can change the size and location of the clip area in which the actual rendering operations are done. A clip area, in Graphics, is the area on which the drawing operations are conducted. The Graphics class provides the method setClip(int x, int y, int width, int height) to change this clip area, which in an initial canvas is the whole screen, with the top left corner as the origin (0, 0). Thus, if you use the method getClipWidth() (or getClipHeight()) on the Graphics object passed to the paint method in Listing 7, it returns a value equal to the value returned by the getWidth() (or getHeight()) method of the Canvas.

The Graphics object can be used to render not only squares and rectangles, but arcs, lines, characters, images, and text, as well. For example, to draw the text "Hello World" on top of the square in Listing 7, you can add the following code before or after the square is drawn:

g.drawString("Hello World", getWidth()/2, getHeight()/2 - 10, 
              Graphics.HCENTER | Graphics.BASELINE);

This will result in the screen shown in Figure 9.

Figure 9
Figure 9. Drawing text using the Graphics object

Text, characters, and images are positioned using the concept of anchor points. The full syntax of the drawString() method is drawstring(String text, int x, int y, int anchor). The anchor positioning around the x, y coordinates is specified by bitwise ORing of two constants. One constant specifies the horizontal space (LEFT, HCENTER, RIGHT) and the other specifies the vertical space (TOP, BASELINE, BOTTOM). Thus, to draw the "Hello World" text on top of the square, the anchor's horizontal space needs to be centered around the middle of the canvas (getWidth()/2) and hence, I have used the Graphics.HCENTER constant. Similarly, the vertical space is specified by using the BASELINE constant around the top of the square (getHeight()/2 - 10). You can also use the special value of 0 for the anchor, which is equivalent to TOP | LEFT.

Images are similarly drawn and positioned on the screen. You can create off-screen images by using the static createImage(int width, int height) method of the Image class. You can get a Graphics object associated with this image by using the getGraphics() method. This method can only be called on images that are mutable. An image loaded from the file system, or over the network, is considered an immutable image, and any attempt to get a Graphics object on such an image will result in an IllegalStateException at runtime.

Using anchor points with images is similar to using them with text and characters. Images allow an additional constant for the vertical space, specified by Graphics.VCENTER. Also, since there is no concept of a baseline for an image, using the BASELINE constant will throw an exception if used with an image.

Listing 8 shows the code snippet from the MyCanvas class paint() method that creates an off-screen image, modifies it by adding an image loaded from the file system, and draws a red line across it. Note that you will need the image duke.gif in the res folder of the CanvasExample MIDlet.

// draw a modified image
try {
// create an off screen image
Image offImg = Image.createImage(25, 19);

// get its graphics object and set its
// drawing color to red
Graphics offGrap = offImg.getGraphics();
offGrap.setColor(0xff0000);

// load an image from file system
Image dukeImg =
  Image.createImage("/duke.gif");

// draw the loaded image on the off screen
// image
offGrap.drawImage(dukeImg, 0, 0, 0);

// and modify it by drawing a line across it
offGrap.drawLine(0, 0, 25, 19);

// finally, draw this modified off screen
// image on the main graphics screen
// so that it is just under the square
g.drawImage(
offImg, getWidth()/2,
getHeight()/2 + 10,
Graphics.HCENTER | Graphics.TOP);

} catch(Exception e) { e.printStackTrace(); }

Listing 8. Creating, modifying, and displaying an off-screen image on a Canvas

The resultant screen, when combined with the "Hello World" text drawn earlier, will look like Figure 10.

Figure 10
Figure 10. Text, a square, and a modified image drawn on a Canvas

The Canvas class provides methods to interact with the user, including predefined game actions, key events, and, if a pointing device is present, pointer events. You can even attach high-level commands to a canvas, similar to attaching commands on a high-level UI element.

Each Canvas class automatically receives key events through the invocation of the keyPressed(int keyCode), keyReleased(int keyCode), and keyRepeated(int keyCode). The default implementations of these methods are empty, but not abstract, which allows you to only override the methods that you are interested in. Similar to the key events, if a pointing device is present, pointer events are sent to the pointerDragged(int x, int y), pointerPressed(int x, int y), and pointerReleased(int x, int y) methods.

The Canvas class defines constants for key codes that are guaranteed to be present in all wireless devices. These key codes define all of the numbers (for example, KEY_NUM0, KEY_NUM1, KEY_NUM2, and so on) and the star (*) and pound (#) keys (KEY_STAR and KEY_POUND). This class makes it even easier to capture gaming events by defining some basic gaming constants. There are nine constants that are relevant to most games: UP, DOWN, LEFT, RIGHT, FIRE, GAME_A, GAME_B, GAME_C, and GAME_D. But how does a key event translate to a gaming event?

By the use of the getGameAction() method. Some devices provide a navigation control for moving around the screen, while some devices use the number keys 2, 4, 6, and 8. To find out which game action key was pressed, the Canvas class encapsulates this information and provides it in the form of the game actions. All you, as a developer, need to do is to grab the key code pressed by the user in the right method, and use the getGameAction(int keyCode) method to determine if the key pressed corresponds to a game action. As you can guess, several key codes can correspond to one game action, but a single key code may map to, at most, a single game action.

Listing 9 extends the original code from Listing 7 to add key code handling. In this listing, the square in the middle of the screen is moved around with the help of the navigation buttons.

Package com.j2me.part2;

import javax.microedition.lcdui.Canvas;
import javax.microedition.midlet.MIDlet;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Graphics;

public class CanvasExample
  extends MIDlet {

Canvas myCanvas;

public CanvasExample() {
myCanvas = new MyCanvas();
}

public void startApp() {
Display display = Display.getDisplay(this);

// remember, Canvas is a Displayable so it can
// be set on the display like Screen elements
display.setCurrent(myCanvas);

// force repaint of the canvas
myCanvas.repaint();
}

public void pauseApp() {
}

public void destroyApp(boolean unconditional) {
}
}

class MyCanvas extends Canvas {
public void paint(Graphics g) {
// create a 20x20 black square in the center

// clear the screen first
g.setColor(0xffffff);
g.fillRect(0, 0, getWidth(), getHeight());

g.setColor(0x000000); // make sure it is black

// draw the square, changed to rely on instance variables
g.fillRect(x, y, 20, 20);
}

public void keyPressed(int keyCode) {

// what game action does this key map to?
int gameAction = getGameAction(keyCode);

if(gameAction == RIGHT) {
x += dx;
} else if(gameAction == LEFT) {
x -= dx;
} else if(gameAction == UP) {
y -= dy;
} else if(gameAction == DOWN) {
y += dy;
}

// make sure to repaint
repaint();
}

// starting coordinates
private int x = getWidth()/2 - 10;
private int y = getHeight()/2 - 10;

// distance to move
private int dx = 2;
private int dy = 2;
}

Listing 9. Handling key events to move the square

Notice that in this listing, the code to paint the square has been modified to rely upon instance variables. The keyPressed() method has been overridden and therefore, whenever the user presses a key, this method is invoked. The code checks if the key pressed was a game key, and based on which game key was pressed, changes the coordinates of the square accordingly. Finally, the call to repaint() in turn calls the paint() method, which moves the square on the screen as per the new coordinates.

In this article, you created the UI elements and were introduced to much of the user interface APIs for MIDlets. In the next installment, you will learn to use the Gaming API of MIDP 2.0 present in the package javax.microedition.lcdui.game.

width="1" height="1" border="0" alt=" " />
Vikram Goyal is the author of Pro Java ME MMAPI.