带有自定义渲染器的JSpinner

时间:2013-05-29 15:13:59

标签: java swing editor jtextfield jspinner

我试图实现以下目标。

我想使用一个使用数字作为数据的JSpinner,但是我想让它像这样渲染:“6/10”,即“value / maximum”,默认的JSpinner只显示“value” 。当然,我使用SpinnerNumberModel只能将整数用作数据。

与JCombobox渲染器和/或编辑器,JTable渲染器和/或编辑器相比,我一直在寻找模拟行为......但似乎JSpinner不能以这种方式工作。我找到this example,用于微调器内的自定义内容。该示例使用JLabel来显示图标,但在我的情况下,我必须能够在点击 Enter 之后编辑内容并检查输入。

所以这就是我基本上做的事情:

JSpinner spinner = new JSpinner(new SpinnerNumberModel(1,1,10,1));
spinner.setEditor(new CustomSpinnerEditor(spinner));

CustomSpinnerEditor正在扩展JTextField。我在其上释放了一些inputmap / actionmap(对于 Enter 行为),将一个ChangeListener添加到微调器,以便在用户点击箭头时刷新文本字段内容,添加了一个FocusListener以选择全部关于focusGained的文本并验证focusLost上的输入,......并且它可以正常工作。

所以问题不是让它发挥作用。问题是,这对于非常简单的事情来说太过分了,而且我可能会因为没有使用默认编辑器而丢失一些功能。如果组件只提供了在其上设置渲染器的功能,我就会有一个钩子,我只需返回一个带有正确String的JLabel,就像我对JComboBox一样。

有没有更简单的方法来实现我想要的目标?

3 个答案:

答案 0 :(得分:2)

您可以避免创建专用编辑器,但我不知道它是否变得更简单:使用

检索默认编辑器的文本字段
JFormattedTextField f = ((JSpinner.DefaultEditor) spinner.getEditor()).getTextField();

然后在该文本字段上设置格式化程序工厂:

f.setFormatterFactory(new AbstractFormatterFactory() {
    @Override
    public AbstractFormatter getFormatter(JFormattedTextField tf) {
        // return your formatter
    }
});

getFormatter方法返回的格式化程序必须使用stringToValuevalueToString方法在您的值和字符串表示形式之间进行转换。

答案 1 :(得分:1)

我不知道你是否会认为这更简单。

JSpinner Test

我创建了一个Renderer类,其中包含要显示的值和值字符串。

在自定义微调器模型中,我创建了一个Renderer实例列表,并导航了列表。

我将这些类放在一起,这样我就可以复制并粘贴一段代码。这三个类应保存在不同的源模块中。

这是做你想做的事的一种方法。

import java.awt.Color;
import java.awt.Dimension;
import java.util.ArrayList;
import java.util.List;

import javax.swing.AbstractSpinnerModel;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.SwingUtilities;
import javax.swing.border.Border;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class SpinnerTesting implements Runnable {

    @Override
    public void run() {
        JFrame frame = new JFrame("JSpinner Test");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JPanel mainPanel = new JPanel();
        final JSpinner spinner = new JSpinner(new CustomSpinnerModel(4, 1, 10,
                1));
        setBorder(spinner);
        spinner.addChangeListener(new ChangeListener() {
            @Override
            public void stateChanged(ChangeEvent event) {
                Renderer renderer = (Renderer) spinner.getValue();
                System.out.println(renderer.getValue());
            }
        });
        mainPanel.add(spinner);

        frame.add(mainPanel);
        frame.setSize(new Dimension(300, 100));
        frame.setVisible(true);
    }

    private void setBorder(JSpinner spinner) {
        Border lineBorder = BorderFactory.createLineBorder(Color.BLACK);
        Border insetBorder = BorderFactory.createEmptyBorder(0, 10, 0, 10);
        spinner.setBorder(BorderFactory.createCompoundBorder(lineBorder,
                insetBorder));
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new SpinnerTesting());
    }

    public class CustomSpinnerModel extends AbstractSpinnerModel {

        private int             value;
        private int             minimum;
        private int             maximum;
        private int             stepSize;

        private int             listIndex;

        private List<Renderer>  spinnerList;

        public CustomSpinnerModel(int value, int minimum, int maximum,
                int stepSize) throws IllegalArgumentException {
            if (!((minimum <= value) && (value <= maximum))) {
                throw new IllegalArgumentException(
                        "(minimum <= value <= maximum) is false");
            }

            this.value = value;
            this.minimum = minimum;
            this.maximum = maximum;
            this.stepSize = stepSize;

            this.spinnerList = new ArrayList<Renderer>();
            setSpinnerList();
        }

        private void setSpinnerList() {
            int index = 0;
            for (int i = minimum; i <= maximum; i += stepSize) {
                Renderer renderer = new Renderer(i, maximum);
                if (i == value) {
                    listIndex = index;
                }
                spinnerList.add(renderer);
                index++;
            }
        }

        @Override
        public Object getNextValue() {
            listIndex = Math.min(++listIndex, (spinnerList.size() - 1));
            fireStateChanged();
            return spinnerList.get(listIndex);
        }

        @Override
        public Object getPreviousValue() {
            listIndex = Math.max(--listIndex, 0);
            fireStateChanged();
            return spinnerList.get(listIndex);
        }

        @Override
        public Object getValue() {
            return spinnerList.get(listIndex);
        }

        @Override
        public void setValue(Object object) {

        }

    }

    public class Renderer {

        private int     value;

        private String  valueString;

        public Renderer(int value, int maximum) {
            this.value = value;
            this.valueString = value + " / " + maximum;
        }

        public int getValue() {
            return value;
        }

        public String getValueString() {
            return valueString;
        }

        @Override
        public String toString() {
            return valueString + " ";
        }

    }

}

答案 2 :(得分:0)

是的,有一种更简单的方法......

为什么要填充自定义编辑器的动作地图而不仅仅是编辑DefaultEditor的动作地图?

DefaultEditor JFormattedTextFieldinput notify事件提供自定义操作:

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.SpinnerNumberModel;
import javax.swing.text.TextAction;

public class OnEnterSpinner extends JPanel {
    private final JSpinner spin;
    private final JSpinner.DefaultEditor editor;
    private final JFormattedTextField field;

    private final class OnEnterAction extends TextAction {
        private OnEnterAction() {
            super(JTextField.notifyAction); //The name of the TextAction. Let's say it is its key.
        }

        @Override
        public void actionPerformed(final ActionEvent actevt) {
            if (getFocusedComponent() == field)
                System.out.println("The user pressed ENTER for: \"" + field.getText() + "\".");
        }
    }

    private OnEnterSpinner() {
        spin = new JSpinner(new SpinnerNumberModel(0, 0, 10000, 1)); //Your values here. Let's say for now this is a spinner for integers.
        spin.setPreferredSize(new Dimension(100, 20));

        //We need a DefaultEditor (to be able to obtain the JFormattedTextField and customize it).
        editor = new JSpinner.DefaultEditor(spin);

        field = editor.getTextField();
        field.setEditable(true); //Allow user input (obvious reasons).

        //And here, you register the custom action!
        field.getActionMap().put(JTextField.notifyAction, new OnEnterAction()); //I found the proper key for the map: "notifyAction".

        spin.setEditor(editor);

        add(spin);
    }

    public static void main(final String[] args) {
        final JFrame frame = new JFrame("OnEnterSpinner demo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(new OnEnterSpinner());
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
}

此外,如果您需要通过提供给JFormattedTextField的自定义AbstractFormatter检查输入值的有效性,请在输入时调用JSpinner#commitEdit()

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.text.ParseException;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.SpinnerNumberModel;
import javax.swing.text.TextAction;

public class CommitOnEnterSpinner extends JPanel {
    private final JSpinner spin;
    private final JSpinner.DefaultEditor editor;
    private final JFormattedTextField field;

    private final class OnEnterAction extends TextAction {
        private OnEnterAction() {
            super(JTextField.notifyAction);
        }

        @Override
        public void actionPerformed(final ActionEvent actevt) {
            if (getFocusedComponent() == field) { //This check may be redundant in this case.
                System.out.println("The user pressed ENTER for: \"" + field.getText() + "\".");
                try {
                    //Commit the edits, upon carriage return:
                    spin.commitEdit();

                    //If successfull, then handle it as desired:
                    JOptionPane.showMessageDialog(null, "Yes! This is a valid value.", "OK", JOptionPane.PLAIN_MESSAGE);
                }
                catch (final ParseException pe) {
                    //If failed, then handle the unaccepted value:
                    JOptionPane.showMessageDialog(null, "Sorry, \"" + field.getText() + "\" is not a valid value.", "Oups!", JOptionPane.INFORMATION_MESSAGE);
                }
            }
        }
    }

    private CommitOnEnterSpinner() {
        spin = new JSpinner(new SpinnerNumberModel(0, 0, 10000, 1)); //Your values here. Let's say for now this is a spinner for integers.
        spin.setPreferredSize(new Dimension(100, 20));

        //We need a DefaultEditor (to be able to obtain the JFormattedTextField and customize it).
        editor = new JSpinner.DefaultEditor(spin);

        field = editor.getTextField();
        field.setEditable(true); //Allow user input (obvious reasons).

        //And here, you register the custom action!
        field.getActionMap().put(JTextField.notifyAction, new OnEnterAction());

        spin.setEditor(editor);

        add(spin);
    }

    public static void main(final String[] args) {
        final JFrame frame = new JFrame("CommitOnEnterSpinner demo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(new CommitOnEnterSpinner());
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
}