使用ItemStateListener接口检验输入数据

作者: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的约束,是用户输入不仅仅是数字字符。