![]() ![]() ![]() |
|
![]() |
||||||||||||||||||
![]() |
||||||||||||||||||
![]() |
||||||||||||||||||
![]() |
||||||||||||||||||
![]() |
||||||||||||||||||
![]() |
||||||||||||||||||
![]() |
||||||||||||||||||
![]() |
||||||||||||||||||
|
||||||||||||||||||
Tech Tips archive
J2ME Tech TipsJune 22, 2001WELCOME to the Java Developer ConnectionSM (JDC) JavaTM 2 Platform, Micro Edition (J2METM) Tech Tips, for June 22, 2001. This issue covers: The J2ME Tech Tips are written by Eric Giguere (http://www.ericgiguere.com), an engineer at iAnywhere Solutions, inc. Eric is the author of the book "Java 2 Micro Edition: Professional Developer's Guide" and co-author of the upcoming "Mobile Information Device Profile for Java 2 Micro Edition," both books in John Wiley & Sons' Professional Developer's Guide series. USING THE MIDP LIST COMPONENTThe Mobile Information Device Profile (MIDP) groups its user interface components into two sets of classes: the high-level API and the low-level API. With the high-level API you define your user interface using a set of platform-independent abstractions and let the MIDP implementation decide the look and behavior of the user interface. The low-level API gives you complete control (and responsibility) of the drawing area and the processing of the raw input events. The low-level API was covered in the J2ME Tech Tip for March 19, 2001 "Using the MIDP Low-Level User Interface API".
This Tech Tip examines the
The
The
A
The List l = new List( "Choose fruit:", List.EXCLUSIVE ); l.append( "orange", null ); l.append( "apple", null ); l.append( "pear", null ); The alternative is to use the array form of the constructor: String[] fruitNames = { "orange", "apple", "pear" }; List l = new List( "Choose fruit:", List.EXCLUSIVE, fruitNames, null ); Note that images are entirely optional. Even if you specify them, the implementation might ignore them. So it's safe to pass in null whenever an image or array of images is requested.
Before displaying a List l = ....; CommandListener listener = ....; // often "this" l.setCommandListener( listener );
A command listener is an instance of any class that implements
the
It's usually a good idea to add commands to lists in public void commandAction( Command c, Displayable d ){ if( c == List.SELECT_COMMAND ){ // implicit selection... } else if( ..... ){ ..... // etc. etc. } }
Make sure that any operation performed in response to
The public static void deleteListContents( List l ){ int n = l.size(); while( n-- > 0 ){ l.delete( n ); } }
The
If you find yourself doing these kinds of operations repeatedly,
you might want to extend public class ExtendedList extends List { public ExtendedList( String title, int mode ){ super( title, mode ); } public ExtendedList( String title, int mode, String[] itemText, Image[] itemImages ){ super( title, mode, itemText, itemImages ); } public void deleteAll(){ int n = size(); while( n-- > 0 ){ delete( n ); } } }
After a List l = ....; // some list int which = l.getSelectedIndex();
Lists in List l = ....; // some list boolean[] selected = new boolean[ l.size() ]; l.getSelectedFlags( selected ); for( int i = 0; i < selected.length; ++i ){ if( selected[i] ){ System.out.println( "Item " + i + " is selected!" ); } }
You can also check the selected state of an individual item at
any time by calling public void toggleItems( List l ){ boolean[] selected = new boolean[ l.size() ]; l.getSelectedFlags( selected ); for( int i = 0; i < selected.length; ++i ){ selected[i] = !selected[i]; } l.setSelectedFlags( selected ); } Another way to toggle the selected items is to set each item's selected state individually: public void toggleItems( List l ){ int n = l.size(); for( int i = 0; i < n; ++i ){ l.setSelectedIndex( i, !l.isSelected( i ) ); } }
The latter method might cause excessive repainting of the list,
however, and should be avoided in favor of calling
Each item in a List can have an associated image. An image is an
instance of the Image checked = null; Image unchecked = null; try { checked = Image.createImage( "/images/check.png" ); unchecked = Image.createImage( "/images/unchecked.png" ); } catch( java.io.IOException e ){ }
You can also create images dynamically by using the
Once you have an image or set of images, you can assign them to
the items either in the constructor or as arguments to the List l = ....; // some list try { l.append( "orange", Image.createImage( "/orange.png" ) ); l.append( "apple", Image.createImage( "/apple.png" ) ); l.append( "pear", Image.createImage( "/pear.png" ) ); } catch( IOException e ){ } Keep the images as small as possible, no more than 10-to-16 pixels high. Make them all the same size to ensure that the text is painted correctly. Don't depend on the images being there, however, and make sure the item text is descriptive enough without the image.
Let's end the tip with a simple MIDlet that demonstrates the use
of the import javax.microedition.lcdui.*; import javax.microedition.midlet.*; public class ListDemo extends MIDlet { private Display display; private int mode = List.IMPLICIT; private Command exitCommand = new Command( "Exit", Command.SCREEN, 2 ); private Command selectCommand = new Command( "Select", Command.OK, 1 ); private Command nextCommand = new Command( "Next", Command.SCREEN, 2 ); public ListDemo(){ } protected void destroyApp( boolean unconditional ) throws MIDletStateChangeException { exitMIDlet(); } protected void pauseApp(){ } protected void startApp() throws MIDletStateChangeException { if( display == null ){ // first time called... initMIDlet(); } } private void initMIDlet(){ display = Display.getDisplay( this ); display.setCurrent( new SampleList( mode ) ); } public void exitMIDlet(){ notifyDestroyed(); } public static final String[] items = { "First", "Second", "Third", "Fourth" }; class SampleList extends List implements CommandListener { private int mode; SampleList( int mode ){ super( "", mode, items, null ); addCommand( exitCommand ); addCommand( selectCommand ); addCommand( nextCommand ); setCommandListener( this ); switch( mode ){ case IMPLICIT: setTitle( "Implicit" ); break; case EXCLUSIVE: setTitle( "Exclusive" ); break; case MULTIPLE: setTitle( "Multiple" ); break; } this.mode = mode; } public void commandAction( Command c, Displayable d ){ if( c == exitCommand ){ exitMIDlet(); } else if( c == selectCommand ){ showSelection( false ); } else if( c == SELECT_COMMAND ){ showSelection( true ); } else if( c == nextCommand ){ if( mode == List.IMPLICIT ){ mode = List.EXCLUSIVE; } else if( mode == List.EXCLUSIVE ){ mode = List.MULTIPLE; } else { mode = List.IMPLICIT; } display.setCurrent( new SampleList( mode ) ); } } private void showSelection( boolean implicit ){ Alert alert = new Alert( implicit ? "Implicit Selection" : "Explicit Selection" ); StringBuffer buf = new StringBuffer(); if( mode == MULTIPLE ){ boolean[] selected = new boolean[ size() ]; getSelectedFlags( selected ); for( int i = 0; i < selected.length; ++i ){ if( selected[i] ){ if( buf.length() == 0 ){ buf.append( "You selected: " ); } else { buf.append( ", " ); } buf.append( getString( i ) ); } } if( buf.length() == 0 ){ buf.append( "No items are selected." ); } } else { buf.append( "You selected " ); buf.append( getString( getSelectedIndex() ) ); } alert.setString( buf.toString() ); alert.setTimeout( Alert.FOREVER ); display.setCurrent( alert, display.getCurrent() ); } } } ENUMERATING, FILTERING, AND SORTING MIDP RECORD STORESThe Record Management System (RMS), part of the Mobile Information Device Profile (MIDP), lets you store arbitrary byte arrays as records in a database. The set of records is referred to as a record store. The RMS assigns a record ID to each record when it is created. The record ID is an integer greater than or equal to 1. The application uses this ID to get and set the content of a record. Record IDs are unique within a record store. In other words, after a record ID has been assigned to a record, the record ID is not reused, even if that record is deleted. This makes it easy to track individual records. It does, however, pose a problem: how do you get a list of all the records in a record store, given that they aren't necessarily in a continuous, sequential order? The answer is to use an enumeration.
The public interface RecordEnumeration { void destroy(); boolean hasNextElement(); boolean hasPreviousElement(); boolean isKeptUpdated(); void keepUpdated( boolean keepUpdated ); byte[] nextRecord() throws InvalidRecordIDException, RecordStoreNotOpenException, RecordStoreException; int nextRecordId() throws InvalidRecordIDException; int numRecords(); byte[] previousRecord() throws InvalidRecordIDException, RecordStoreNotOpenException, RecordStoreException; int previousRecordId() throws InvalidRecordIDException; void rebuild(); void reset(); }
To iterate over the entire set of records in a record store, do the following: RecordStore rs = .....; // an open record store RecordEnumeration enum = null; try { enum = rs.enumerateRecords( null, null, false ); while( enum.hasMoreElements() ){ int id = enum.getNextRecordId(); // do something here with the record } } catch( RecordStoreException e ){ } finally { enum.destroy(); }
The first two arguments to the
You can reset the enumeration at any time by calling
When an application is done with an enumeration it must call the
enumeration's
If the first two arguments to
The public interface RecordFilter { boolean matches( byte[] recordData ); }
A filter is an instance of a class that implements the
public class MyFilter implements RecordFilter { public boolean matches( byte[] recordData ){ return( recordData.length > 0 && recordData[0] != 0 ); } } Use the filter like this: enum = rs.enumerateRecords( new MyFilter(), null, false ); Note that the filter is passed the contents of a record, but not the record ID or a reference to the record store itself.
The public interface RecordComparator { boolean compare( byte[] rec1, byte[] rec2 ); int EQUIVALENT = 0; int FOLLOWS = 1; int PRECEDES = -1; }
A comparator is an instance of a class that implements the
public class MyComparator implements RecordComparator { public boolean compare( byte[] rec1, byte[] rec2 ){ if( rec1.length == rec2.length ){ return EQUIVALENT; } else if( rec1.length < rec2.length ){ return PRECEDES; } else { return FOLLOWS; } } } Of course, most comparators would compare records based on their contents. Use the comparator like this: enum = rs.enumerateRecords( null, new MyComparator(), false ); Again, only the contents of the records to compare are passed to the comparator. Both a filter and a comparator can be associated with an enumeration. The filter is called first to determine which records are in the enumeration. The comparator is then called to sort those records.
If tracking is enabled, filtering and sorting occurs whenever the
record store is modified. This can be quite expensive to perform.
You can enable or disable an enumeration's tracking of the
underlying record store by using the
If tracking is disabled but you want to "refresh" the enumeration
from the current set of records in the record store, call
the
Let's end this tip with a simple MIDlet that demonstrates how to
fill a import java.io.*; import javax.microedition.lcdui.*; import javax.microedition.midlet.*; import javax.microedition.rms.*; // Demonstrates simple record sorting and filtering public class EnumDemo extends MIDlet implements CommandListener { // Data members we need private Display display; private RecordStore rs; private EnumList enumListScreen; private SortOptions sortOptionsScreen; private String firstName; private String lastName; private byte[] data = new byte[200]; private ByteArrayInputStream bin = new ByteArrayInputStream( data ); private DataInputStream din = new DataInputStream( bin ); // Define the four commands we use private Command exitCommand = new Command( "Exit", Command.EXIT, 1 ); private Command sortCommand = new Command( "Sort", Command.SCREEN, 1 ); private Command cancelCommand = new Command( "Cancel", Command.CANCEL, 1 ); private Command okCommand = new Command( "OK", Command.OK, 1 ); // Define the sorting modes private static final int SORT_NONE = 0; private static final int SORT_FIRST_LAST = 1; private static final int SORT_LAST_FIRST = 2; // Define the filtering modes private static final int FILTER_NONE = 0; private static final int FILTER_STARTSWITH = 1; private static final int FILTER_CONTAINS = 2; // A simple class to hold the parts of a record private static class Record { String firstName; String lastName; } // Precanned names private static final String[][] names = { { "Eric", "Giguere" }, { "John", "Doe" }, { "Lisa", "Munro" }, { "Stephanie", "Kwaly" }, { "Dave", "Boyer" }, { "Gwen", "Stephens" }, { "Jennifer", "Lopert" }, { "Ed", "Ort" }, }; // Constructor public EnumDemo(){ } // Clean up protected void destroyApp( boolean unconditional ) throws MIDletStateChangeException { exitMIDlet(); } // Close the record store for now protected void pauseApp(){ closeRecordStore(); } // Initialize things and reopen record store // if not open protected void startApp() throws MIDletStateChangeException { if( display == null ){ // first time called... initMIDlet(); } if( rs == null ){ openRecordStore(); } } // Once-only initialization code private void initMIDlet(){ display = Display.getDisplay( this ); enumListScreen = new EnumList(); sortOptionsScreen = new SortOptions(); if( openRecordStore() ){ enumListScreen.resort(); display.setCurrent( enumListScreen ); } } // Termination code public void exitMIDlet(){ closeRecordStore(); notifyDestroyed(); } // Add a first-last name pair to the record store private void addName( String first, String last, ByteArrayOutputStream bout, DataOutputStream dout ){ try { dout.writeUTF( first ); dout.writeUTF( last ); dout.flush(); byte[] data = bout.toByteArray(); rs.addRecord( data, 0, data.length ); bout.reset(); } catch( Exception e ){ } } // Fill record store with some precanned names private void fillRecordStore(){ ByteArrayOutputStream bout = new ByteArrayOutputStream(); DataOutputStream dout = new DataOutputStream( bout ); for( int i = 0; i < names.length; ++i ){ addName( names[i][0], names[i][1], bout, dout ); } } // Open record store, if empty then fill it private boolean openRecordStore(){ try { if( rs != null ) closeRecordStore(); rs = RecordStore.openRecordStore( "EnumDemo", true ); if( rs.getNumRecords() == 0 ){ fillRecordStore(); } return true; } catch( RecordStoreException e ){ return false; } } // Close record store private void closeRecordStore(){ if( rs != null ){ try { rs.closeRecordStore(); } catch( RecordStoreException e ){ } rs = null; } } // Move to and read in a record private boolean readRecord( int id, Record r ){ boolean ok = false; r.firstName = null; r.lastName = null; if( rs != null ){ try { rs.getRecord( id, data, 0 ); r.firstName = din.readUTF(); r.lastName = din.readUTF(); din.reset(); ok = true; } catch( Exception e ){ } } return ok; } // Event handling public void commandAction( Command c, Displayable d ){ if( c == exitCommand ){ exitMIDlet(); } else if( c == sortCommand ){ display.setCurrent( sortOptionsScreen ); } else if( d == sortOptionsScreen ){ if( c == okCommand ){ enumListScreen.resort(); } display.setCurrent( enumListScreen ); } } // Main screen -- a list of names class EnumList extends List implements RecordComparator, RecordFilter { private int sortBy; private int filterBy; private String filterText; private Record r1 = new Record(); private Record r2 = new Record(); // Constructor EnumList(){ super( "Enum Demo", IMPLICIT ); addCommand( exitCommand ); addCommand( sortCommand ); setCommandListener( EnumDemo.this ); } // Resort the data and refill the list public void resort(){ sortBy = sortOptionsScreen.getSortType(); filterBy = sortOptionsScreen.getFilterType(); filterText = sortOptionsScreen.getFilterText(); RecordComparator comparator = null; RecordFilter filter = null; if( sortBy != 0 ){ comparator = this; } if( filterBy != 0 ){ filter = this; } deleteAll(); try { RecordEnumeration enum = rs.enumerateRecords( filter, comparator, false ); Record r = new Record(); while( enum.hasNextElement() ){ int id = enum.nextRecordId(); if( readRecord( id, r ) ){ if( sortBy == SORT_LAST_FIRST ){ append( r.lastName + ", " + r.firstName, null ); } else { append( r.firstName + " " + r.lastName, null ); } } } enum.destroy(); } catch( RecordStoreException e ){ } } // Delete all items in the list private void deleteAll(){ int n = size(); while( n > 0 ){ delete( --n ); } } // The comparator public int compare( byte[] rec1, byte[] rec2 ){ try { ByteArrayInputStream bin = new ByteArrayInputStream( rec1 ); DataInputStream din = new DataInputStream( bin ); r1.firstName = din.readUTF(); r1.lastName = din.readUTF(); bin = new ByteArrayInputStream( rec2 ); din = new DataInputStream( bin ); r2.firstName = din.readUTF(); r2.lastName = din.readUTF(); if( sortBy == SORT_FIRST_LAST ){ int cmp = r1.firstName.compareTo( r2.firstName ); System.out.println( r1.firstName + " compares to " + r2.firstName + " gives " + cmp ); if( cmp != 0 ) return ( cmp < 0 ? PRECEDES : FOLLOWS ); cmp = r2.lastName.compareTo( r2.lastName ); if( cmp != 0 ) return ( cmp < 0 ? PRECEDES : FOLLOWS ); } else if( sortBy == SORT_LAST_FIRST ){ int cmp = r1.lastName.compareTo( r2.lastName ); if( cmp != 0 ) return ( cmp < 0 ? PRECEDES : FOLLOWS ); cmp = r2.firstName.compareTo( r2.firstName ); if( cmp != 0 ) return ( cmp < 0 ? PRECEDES : FOLLOWS ); } } catch( Exception e ){ } return EQUIVALENT; } // The filter public boolean matches( byte[] rec ){ try { ByteArrayInputStream bin = new ByteArrayInputStream( rec ); DataInputStream din = new DataInputStream( bin ); r1.firstName = din.readUTF(); r1.lastName = din.readUTF(); if( filterBy == FILTER_STARTSWITH ){ return( r1.firstName.startsWith( filterText ) || r1.lastName.startsWith( filterText ) ); } else if( filterBy == FILTER_CONTAINS ){ return( r1.firstName.indexOf( filterText ) >= 0 || r1.lastName.indexOf( filterText ) >= 0 ); } } catch( Exception e ){ } return false; } } // The options screen for choosing which sort and // filter modes to use, including the filter text class SortOptions extends Form { // Constructor SortOptions(){ super( "Sort Options" ); addCommand( okCommand ); addCommand( cancelCommand ); setCommandListener( EnumDemo.this ); append( sortTypes ); append( filterTypes ); append( filterText ); } // Return current sort type public int getSortType(){ return sortTypes.getSelectedIndex(); } // Return current filter type public int getFilterType(){ return filterTypes.getSelectedIndex(); } // Return current filter text public String getFilterText(){ return filterText.getString(); } // Labels and user interface components private String[] sortLabels = { "None", "First Last", "Last, First" }; private String[] filterLabels = { "None", "Starts With", "Contains" }; private ChoiceGroup sortTypes = new ChoiceGroup( "Sort Type:", ChoiceGroup.EXCLUSIVE, sortLabels, null ); private ChoiceGroup filterTypes = new ChoiceGroup( "Filter Type:", ChoiceGroup.EXCLUSIVE, filterLabels, null ); private TextField filterText = new TextField( "Filter Text:", null, 20, 0 ); } } Note Sun respects your online time and privacy. The Java Developer Connection mailing lists are used for internal Sun MicrosystemsTM purposes only. You have received this email because you elected to subscribe. To unsubscribe, go to the Subscriptions page, uncheck the appropriate checkbox, and click the Update button. As of May 22, 2001, Sun Microsystems updated its Privacy Policy to give you a better understanding of Sun's Privacy Policy and Practice. If you have any questions, contact privacy@sun.com. Subscribe To subscribe to a JDC newsletter mailing list, go to the Subscriptions page, choose the newsletters you want to subscribe to, and click Update. Feedback Comments? Send your feedback on the J2ME Tech Tips to: Archives You'll find the J2ME Tech Tips archives at: http://java.sun.com/jdc/J2METechTips/index.html Copyright
Copyright 2001 Sun Microsystems, Inc. All rights reserved. This Document is protected by copyright. For more information, see: http://java.sun.com/jdc/copyright.html - LINKS TO NON-SUN SITES The J2ME Tech Tips may provide, or third parties may provide, links to other Internet sites or resources. Because Sun has no control over such sites and resources, You acknowledge and agree that Sun is not responsible for the availability of such external sites or resources, and does not endorse and is not responsible or liable for any Content, advertising, products, or other materials on or available from such sites or resources. Sun will not be responsible or liable, directly or indirectly, for any damage or loss caused or alleged to be caused by or in connection with use of or reliance on any such Content, goods or services available on or through any such site or resource. J2ME Tech Tips June 22, 2001 Sun, Sun Microsystems, Java, Java Developer Connection, and J2ME and are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. |