【问题标题】:JSpinner with a custom renderer带有自定义渲染器的 JSpinner
【发布时间】:2013-05-24 22:30:17
【问题描述】:

我试图实现以下目标。

我想使用一个使用数字作为数据的 JSpinner,但我想让它呈现如下:“6 / 10”,即“值/最大值”,其中默认的 JSpinner 只显示“值” .当然,我使用 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 上的输入,......它可以工作。

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

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

【问题讨论】:

    标签: java swing editor jtextfield jspinner


    【解决方案1】:

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

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

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

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

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

    【讨论】:

      【解决方案2】:

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

      我创建了一个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 + " ";
              }
      
          }
      
      }
      

      【讨论】:

        【解决方案3】:

        是的,还有更简单的方法……

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

        让我们为DefaultEditor 的JFormattedTextField 提供input 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);
            }
        }
        

        【讨论】:

          猜你喜欢
          • 2011-03-20
          • 2017-12-24
          • 1970-01-01
          • 2017-06-09
          • 1970-01-01
          • 2014-01-19
          • 2017-11-24
          • 2013-09-25
          • 2011-07-14
          相关资源
          最近更新 更多