作者:Eric Giguere
Dec.20, 2001
MIDP高层用户界面API定义了几个组件,用于在Form对象中使用。这些组件被作为“项目”,因为它们是javax.microedition.lcdui.Item类的扩展。一些Item是可以被用户修改的,包括ChoiceGroup,Gauge,DateField和TextField项目。
有时,应用需要知道什么时候可编辑项目的值或选项发生变化。例如,应用可能希望更新一个gauge的基于Label的当前值。或者应用希望确认test域中的输入数值。这时ItemStateListener接口是非常重要的。这个接口很简单,是由一些简单的方法组成的:
package javax.microedition.lcdui.*; public interface ItemStateListener { void itemStateChanged(Item item); }
如果你想知道项目状态的变化,只需的注册一个对象来实现ItemStateListener接口:
Form f = new Form(); ItemStateListener listener = ....; // form.setItemStateListener( listener );
无论何时一个可编辑项目由于用户的交互而改变了状态,它调用ItemStateChanged方法。状态改变的项目只是作为一个参数被传递。注意:这种提示是在项目的状态变化以后发生的—如果你需要了解旧的状态,你必须另外跟踪它。
让我们来使用ItemStateListener实现一些数值输入的确认。像我们在2001年11月14日的Tip中讨论的“Developing Custom Components for the Mobile Information Device Profile”,一个TextField组件中可以接受的类型能够被简单的限制。一个TextField可以通过设置TextField.NUMERIC约束来限制它只接收数值输入。不幸的是,对于这种简单的约束没有检验。例如,假设你的应用希望用户输入一个电话号码。尽管TextField类支持一个PHONENUMBER约束,但是它的功能太有限或不能满足你的想法。使用ItemStateListener,你可以创建一个电话号码输入Form,使它只接收特定类型的电话号码。实际上,在这个Tip中,你将看到一个更通用的Form,成文NumericInputForm,可以被用于数字数值输入,而不仅仅是电话号码。
一个NumericInputForm上有两个Item:一个标题为TextField和另一个StringItem。TextField是用户输入电话号码的。它使用了NUMERIC约束――换句话说,用户输入一个非格式化的数字。(这在MIDP设备上很容易使用,因为它意味着用户可以直接在键盘上输入数字,而无须象通常的文本输入那样,在字符和符号之间进行切换。)
StringItem显示数字格式化的形式。Form有两个命令对象与它关联:一个是“Done”命令和一个“Cancel”命令。“Cancel”命令总是可用的,但是“Done”命令只有在用户的输入与某个输入掩码匹配是才有效。下面是NumericInputForm的代码:
import javax.microedition.lcdui.*; // Defines a form that prompts the user to // enter a numeric value and checks it against // a set of input masks. If the number matches // a mask, the "Done" command is made visible // and the user can then proceed to the next // form. public class NumericInputForm extends Form implements ItemStateListener { public static final Command DONE_COMMAND = new Command("Done", Command.OK, 1); public static final Command CANCEL_COMMAND = new Command( "Cancel", Command.CANCEL, 1); private String[] masks; private int[] digits; private Command done; private Command cancel; private TextField field; private StringItem match; public NumericInputForm(String title, String label, String[] masks, String value) { this(title, label, masks, value, null, null); } public NumericInputForm(String title, String label, String[] masks, String value, Command done, Command cancel) { super(title); this.masks = masks; this.done = (done != null ? done : DONE_COMMAND); this.cancel = (cancel != null ? cancel : CANCEL_COMMAND); digits = new int[masks.length]; int maxlen = 0; for (int i = 0; i < masks.length; ++i) { int mlen = countDigitsInMask(masks[i]); if (mlen > maxlen) { maxlen = mlen; } digits[i] = mlen; } field = new TextField(label, value, maxlen, TextField.PHONENUMBER); append(field); match = new StringItem(null, ""); append(match); adjustState(); addCommand(this.cancel); setItemStateListener(this); } // Adjust the state of the form based on changes // made to the input field. Adds or removes the // "done" command as appropriate, and also adjusts // the value of the label showing the masked // input that matches, if any. protected void adjustState() { String val = field.getString(); String applied = null; for (int i = 0; i < masks.length; ++i) { applied = matches( val, digits[i], masks[i]); if (applied != null) { break; } } if (applied != null) { match.setText(applied); addCommand(done); } else { match.setText(""); removeCommand(done); } } // Figure out how many digits need to be entered // for a particular mask. private int countDigitsInMask(String mask) { int count = 0; for (int i = 0; i < mask.length(); ++i) { char ch = mask.charAt(i); if (ch == '#' || Character.isDigit(ch)) { ++count; } } return count; } // Return the masked value. public String getMaskedValue() { return match.getText(); } // Return the raw value (numbers only). public String getRawValue() { return field.getString(); } // Our callback, just calls adjustState. public void itemStateChanged(Item item) { adjustState(); } // Check to see if the given string matches // the given mask. protected String matches(String value, int digits, String mask) { if (value.equals(mask)) { return value; } int vlen = value.length(); if (vlen != digits) { return null; } int mlen = mask.length(); int vindex = 0; StringBuffer b = new StringBuffer(mlen); for (int i = 0; i < mlen; ++i) { char mch = mask.charAt(i); char vch = mch; if (mch == '#') { vch = value.charAt(vindex++); } else if (Character.isDigit(mch)) { vch = value.charAt(vindex++); if (mch != vch) { return null; } } b.append(vch); } return b.toString(); } // Adjust the field's value. public void setRawValue(String value) { field.setString(value); adjustState(); } }
注意:构造函数带有一个字符串数组。这些字符串是Form接收的数据的掩码。使用“#”字符来指示一个数字的位置。例如,接收北美类型的电话号码,掩码为:
String[] masks = new String[] {"###-####", "(###) ###-####"};
下面是简单的MIDlet使用NumericInputForm类来引导用户输入电话号码。
import javax.microedition.lcdui.*; import javax.microedition.midlet.*; import javax.microedition.rms.*; // A simple test of the NumericInputForm class // that prompts the user to enter a phone number // in one of two acceptable formats. public class NumericInputTest extends MIDlet { private Display display; private Command exitCommand = new Command("Exit", Command.EXIT, 1); private Command okCommand = new Command("OK", Command.OK, 1); public NumericInputTest() { } protected void destroyApp(boolean unconditional) throws MIDletStateChangeException { exitMIDlet(); } protected void pauseApp() { } protected void startApp() throws MIDletStateChangeException { if (display == null) { initMIDlet(); } } private void initMIDlet() { display = Display.getDisplay(this); display.setCurrent(new PromptForPhone()); } public void exitMIDlet() { notifyDestroyed(); } private String[] phoneMasks = new String[] {"###-####", "(###) ###-####"}; // A subclass of NumericInputForm that does nothing // but set the masks and the initial values and // registers itself as a command listener. class PromptForPhone extends NumericInputForm implements CommandListener { public PromptForPhone() { super("Test", "Enter a phone number:", phoneMasks, ""); setCommandListener(this); } // Called when the user presses either the // "done" or "cancel" commands on the // numeric input form. public void commandAction(Command c, Displayable d) { if (c == CANCEL_COMMAND) { exitMIDlet(); } else { Alert a = new Alert("Result"); a.setString("You entered the number " + getMaskedValue() + " from the string " + getRawValue()); a.setTimeout(Alert.FOREVER); display.setCurrent(a, this); setRawValue(""); } } } }
对NumericInputForm类可以有多种改进。例如,它可以允许部分匹配,或它可以动态调整后台的TextField的约束,是用户输入不仅仅是数字字符。