lab j2me gui
DESCRIPTION
This is useful to understand how to use J2ME applicationTRANSCRIPT
-
FMC-Lab
APU 1/28
Lab J2ME GUI
In this lab, 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.
Let's start with a discussion of the overall lifecycle of a MIDlet.
The MIDlet Lifecycle
Mobile devices, whether emulators or real, interact with a MIDlet using their own software,
which is called Application Management Software (AMS). The AMS is responsible for
initializing, starting, pausing, resuming, and destroying a MIDlet. (Besides these services, AMS
may be responsible for installing and removing a MIDlet, as well.) To facilitate this
management, a MIDlet can be in one of three states which is controlled via the MIDlet class
methods, that every MIDlet extends and overrides. These states are active, paused and destroyed.
Figure. The possible states of a MIDlet and the transition between them
-
FMC-Lab
APU 2/28
As you can see from above Figure, an installed MIDlet is put into a paused state by the AMS
creating an instance of it, by calling its no-args constructor. This is of course, not the only way
that the MIDlet can be in a paused state. It can enter this state when the AMS calls the
pauseApp() method on an active MIDlet (and the method returns successfully). It can also enter
this state when the MIDlet pauses itself by calling the notifyPaused() method, as opposed to the
pauseApp() method, which is called by the AMS. However, what exactly is happening with the
MIDlet in the paused state?
In a paused state, the MIDlet is waiting for a chance to get into the active state. Theoretically, in
this state, it should not be holding or using any of the device resources and should be passive in
nature. Once the MIDlet is created, this is the state to be in before becoming active. Also,
entering the paused state is necessary when the device requires it to consume fewer resources,
because these resources may be required for handling other device functions, like handling an
incoming call. This is when the device invokes the pauseApp() method through the AMS. If the
MIDlet should inform the AMS that it has paused, it should invoke the notifyPaused() method,
which tells the AMS that the MIDlet has indeed paused.
One final way in which a MIDlet can get into a paused state is when the MIDlet's startApp()
method, which is called when the AMS invokes it to start the MIDlet (either the first time or
from a paused state), throws a MIDletStateChangeException. Essentially, in case of an error, the
MIDlet takes the safe road of staying in the paused state.
The active state is where every MIDlet wants to be! This is when the MIDlet can do its
functions, hold the device resources and generally, do what it is supposed to do. As said
previously, a MIDlet is in an active state when the AMS calls the startApp() method on a paused
MIDlet (actually, the MIDlet enters the active state just before this method is called by the
AMS). A paused MIDlet can request to go into the active state by calling the method
resumeRequest(), which informs the AMS that the MIDlet wishes to become active. The AMS
may of course, choose to ignore this request or, alternatively, queue it if there are other MIDlets
requesting the same.
The destroyed state is entered when a MIDlet's destroyApp(boolean unconditional) method is
called and returns successfully, either from an active or paused state. This method is called by
the AMS when it feels that there is no need for the MIDlet to keep running and is the place the
MIDlet may perform cleanup and other last minute activities. The MIDlet can enter this state
itself, by calling the notifyDestroyed() method, which informs the AMS that the MIDlet has
cleaned up its resources and is eligible for destruction. Of course, since in this case, the
destroyApp(boolean unconditional) method is not called by the AMS, any last-minute activities
must be done before this method is invoked.
What happens if the AMS calls the destroyApp(boolean unconditional) method in the middle of
an important step that the MIDlet may be doing, and may be loath to be destroyed? This is where
the Boolean unconditional flag comes into the picture. If this flag is set to true, the MIDlet will
be destroyed, irrespective of what the MIDlet is doing. However, if this flag is false, effectively,
the AMS is telling the MIDlet that it wants the MIDlet to be destroyed, but if the MIDlet is doing
something important, it can raise a MIDletStateChangeException, and the AMS will not destroy
-
FMC-Lab
APU 3/28
it just yet. However, note that even then, there are no guarantees that the MIDlet will not be
destroyed, and it remains up to each device to decide how they should handle the request. If the
device does honor the MIDlet's request, it may try and invoke the destroyApp(boolean
unconditional) at a later stage.
Note that a destroyed state means that the MIDlet instance has been destroyed, but not
uninstalled from the device. The MIDlet remains installed in the device, and a new instance of it
may be created later.
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. We
will discuss this package in the next part of this series.
The UI classes 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. 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. Low-level MIDP 2.0 UI classes
-
FMC-Lab
APU 4/28
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. 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 lab 1, which is reproduced in Listing 1.
import java.util.Date;
import javax.microedition.lcdui.Alert;
import javax.microedition.lcdui.Display;
import javax.microedition.midlet.MIDlet;
public class DateTimeApp extends MIDlet {
-
FMC-Lab
APU 5/28
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.
-
FMC-Lab
APU 6/28
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. 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.
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() {
-
FMC-Lab
APU 7/28
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) {
}
}
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
-
FMC-Lab
APU 8/28
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.
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() {
-
FMC-Lab
APU 9/28
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.
-
FMC-Lab
APU 10/28
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.)
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;
-
FMC-Lab
APU 11/28
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
-
FMC-Lab
APU 12/28
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
-
FMC-Lab
APU 13/28
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
-
FMC-Lab
APU 14/28
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. 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.
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;
-
FMC-Lab
APU 15/28
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);
}
-
FMC-Lab
APU 16/28
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
-
FMC-Lab
APU 17/28
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.
-
FMC-Lab
APU 18/28
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.
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 {
-
FMC-Lab
APU 19/28
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
-
FMC-Lab
APU 20/28
// 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() {
-
FMC-Lab
APU 21/28
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.
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() {
-
FMC-Lab
APU 22/28
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.
-
FMC-Lab
APU 23/28
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.
-
FMC-Lab
APU 24/28
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. 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
-
FMC-Lab
APU 25/28
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. 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.
-
FMC-Lab
APU 26/28
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.
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
-
FMC-Lab
APU 27/28
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();
}
-
FMC-Lab
APU 28/28
// 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 lab , you created the UI elements and were introduced to much of the user interface APIs
for MIDlets.