SpinnerNumberModel的PropertyChangeSupport

7
我想监听JSpinner中SpinnerNumberModel的值的更改。
我创建了一个PropertyChangeSupport并将模型放入其中。

我需要propertyChangeListener,因为它展示了属性的旧值和新值。

以下代码片段不起作用:当我单击JSpinner时,propertyChange方法没有输出任何内容。
简单的ChangeListener只提供新值,但我也需要旧值,如何获取?

package de.unikassel.jung;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

import javax.swing.JFrame;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;

public class PropertyChangeTest implements PropertyChangeListener {

    public static void main(String[] args) {
        new PropertyChangeTest();
    }

    public PropertyChangeTest() {
        JFrame frame = new JFrame();
        frame.setBounds(100, 100, 450, 300);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        int value = 1;
        int min = 0;
        int max = 10;
        int step = 1;
        SpinnerNumberModel spinnerModel = new SpinnerNumberModel(value, min, max, step);

        PropertyChangeSupport pcs = new PropertyChangeSupport(spinnerModel);
        pcs.addPropertyChangeListener("value", this);

        JSpinner spinner = new JSpinner(spinnerModel);
        frame.getContentPane().add(spinner);
        frame.setVisible(true);
    }

    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        System.out.println(evt);
        System.out.println(evt.getSource());
    }

}
4个回答

6

建议使用编辑器的JFormattedTextField而不是听从模型。

JSpinner spinner = new JSpinner(new SpinnerNumberModel(1, 0, 10, 1));
JSpinner.DefaultEditor editor = (JSpinner.DefaultEditor) spinner.getEditor();
editor.getTextField().addPropertyChangeListener("value", this);

1
1+可能比我的答案更好。 - Hovercraft Full Of Eels
谢谢,这很好用。是否有类似于JCheckBox的编辑器?JCheckBox的属性更改监听器?@trashgod - timaschew
请参见所选 - trashgod
找到了这个:checkBox.getAccessibleContext().addPropertyChangeListener(listener) - timaschew
这拯救了我的一天...为什么我们必须通过编辑器来获取TextField? - Chan
@Duli-chan:JSpinner是一个拥有嵌套的DefaultEditor的组合体。此外,也请参阅这里的注意事项。 - trashgod

5

周一早上...经典的时刻,不要抵制几个评论 :-)

@timaschew

  • "需要propertyChangeListener,因为它向我显示属性的旧值和新值。" - (吹毛求疵-但总是有这种强烈的冲动将需求和解决方案分开:),我认为反过来:在更改通知中,您需要访问旧值和新值,propertyChangeEvent / Listener是支持它的通知类型,可能还有其他类型
  • PropertyChangeSupport不应该在观察代码的部分使用,它应该在可观察的一侧使用(就像@Hovercraft在他的示例中所做的那样):它的唯一责任是管理并通知注册到可观察对象的监听器
  • 偶尔,accessibleContext提供了一个钩子进行黑客攻击-尽管如此,将其挂钩是一种黑客攻击(除非您确实需要支持可访问性,这可能是合理的 :-)与所有黑客攻击一样,这是一种脆弱的解决方案,很可能会在未来某个时候造成痛苦。跟随有关Action和AbstractButton交互方式的链接要稳定得多

@Hovercraft

  • 通过更丰富的更改通知增强模型是正确的方法(就像:我绝对最喜欢的:-)
  • 只有一个小细节:如果您有一个从属让他做全部工作-PropertyChangeSupport具有获取旧/新值的方法,不需要在可观察对象上创建事件。当旧值和新值相等时,它将被丢弃
  • 对于通知事件中的newValue,请不要使用参数,而是再次使用getValue(超类可能已拒绝更改)

@trashgod

哈哈-您已经猜到我不喜欢解决方案:它破坏了封装性,因为它依赖于实现细节,因此除非完全控制JSpinner创建并绝对确定其编辑器永远不会更改,否则不要期望它


很好的观点。我的建议是_ad hoc_。就像在去规范化数据库时,必须知道并准备好后果一样。 - trashgod

4
为了使 PropertyChangeSupport 正常工作,您需要调用它的 firePropertyChange 方法,但更重要的是支持对象需要访问其侦听属性的 setXXX 方法,在该方法中需要调用 PropertyChangeSupport 的 firePropertyChange 方法。因此,我认为要实现您的想法,您需要扩展模型的类,并为其提供一个 PropertyChangeSupport 对象,给它添加和删除监听器方法,并确保监听模型的 setValue 方法所做的更改,这是关键。在我的示例中,该方法如下所示:
   @Override
   public void setValue(Object newValue) {
      // store old value and set the new one
      Object oldValue = getValue();
      super.setValue(newValue);

      // construct the event object using these saved values
      PropertyChangeEvent evt = new PropertyChangeEvent(this, VALUE, oldValue,
               newValue);

      // notify all of the listeners
      pcs.firePropertyChange(evt);
   }

以下是一个使用PropertyChangeSupport的示例模型类:

这是我的示例模型类,它使用PropertyChangeSupport:

import java.beans.*;
import javax.swing.*;
import javax.swing.event.*;

@SuppressWarnings("serial")
class MySpinnerNumberModel extends SpinnerNumberModel {
   public static final String VALUE = "value";
   private SwingPropertyChangeSupport pcs = new SwingPropertyChangeSupport(this);

   // you will likely need to create multiple constructors to match
   // the ones available to the SpinnerNumberModel class
   public MySpinnerNumberModel(int value, int min, int max, int step) {
      super(value, min, max, step);
   }

   public void addPropertyChangeListener(PropertyChangeListener listener) {
      pcs.addPropertyChangeListener(listener);
   }

   public void removePropertyChangeListener(PropertyChangeListener listener) {
      pcs.removePropertyChangeListener(listener);
   }

   @Override
   public void setValue(Object newValue) {
      // store old value and set the new one
      Object oldValue = getValue();
      super.setValue(newValue);

      // construct the event object using these saved values
      PropertyChangeEvent evt = new PropertyChangeEvent(this, VALUE, oldValue,
               newValue);

      // notify all of the listeners
      pcs.firePropertyChange(evt);
   }
}

最后是测试类,用于测试上述类是否正常工作:

import java.beans.*;
import javax.swing.*;

public class TestSpinnerPropChange {

   private static void createAndShowUI() {
      final MySpinnerNumberModel myModel = new MySpinnerNumberModel(1, 0, 10, 1);
      final JSpinner spinner = new JSpinner(myModel);

      final JTextField oldValueField = new JTextField(10);
      final JTextField newValueField = new JTextField(10);

      JPanel panel = new JPanel();
      panel.add(spinner);
      panel.add(new JLabel("old value:"));
      panel.add(oldValueField);
      panel.add(new JLabel("new value:"));
      panel.add(newValueField);

      myModel.addPropertyChangeListener(new PropertyChangeListener() {
         public void propertyChange(PropertyChangeEvent evt) {
            // checking the property name is overkill here, but is a good habit
            // to get into, especially if listening to more than one property.
            if (evt.getPropertyName().equals(MySpinnerNumberModel.VALUE)) {
               oldValueField.setText(evt.getOldValue().toString());
               newValueField.setText(evt.getNewValue().toString());
            }
         }
      });

      JFrame frame = new JFrame("TestSpinnerPropChange");
      frame.getContentPane().add(panel);
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.pack();
      frame.setLocationRelativeTo(null);
      frame.setVisible(true);
   }

   public static void main(String[] args) {
      java.awt.EventQueue.invokeLater(new Runnable() {
         public void run() {
            createAndShowUI();
         }
      });
   }
}

1
+1 好的示例,添加了属性更改支持,并回答了实际问题! :-) - trashgod
2
@trashgod:是的,但是解决问题比回答问题更好。你的答案做到了这一点,更重要的是,代码少了很多。 :) - Hovercraft Full Of Eels
@mKorbel a) 在示例中使用最简单的组件来显示某些内容没有任何问题 b) MySpinnerModel 对于不正确的输入与其父类一样健壮(或不健壮) - kleopatra

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接