1

我试图实现以下目标。

我想使用一个使用数字作为数据的 JSpinner,但我想让它呈现如下:“6 / 10”,即“值/最大值”,其中默认的 JSpinner 只显示“值”。当然,我使用 SpinnerNumberModel 只使用整数作为数据。

与 JCombobox 渲染器和/或编辑器、JTable 渲染器和/或编辑器相比,我一直在寻找模拟行为……但似乎 JSpinner 不能以这种方式工作。我找到了这个示例,用于微调器内的自定义内容。该示例使用 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, ... 它的工作原理。

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

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

4

3 回答 3

3

您可以避免创建专用编辑器,但我不知道它是否变得更简单:使用检索默认编辑器的文本字段

JFormattedTextField f = ((JSpinner.DefaultEditor) spinner.getEditor()).getTextField();

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

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

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

于 2013-05-29T16:41:42.277 回答
1

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

JSpinner 测试

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

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

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

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

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 + " ";
        }

    }

}
于 2013-05-29T17:27:02.103 回答
0

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

为什么要填充自定义编辑器的 actionmap 而不仅仅是编辑DefaultEditor?

让我们DefaultEditor输入通知事件JFormattedTextField提供一个自定义操作:

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);
    }
}

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

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);
    }
}
于 2018-04-04T11:19:00.000 回答