在JComponent和模型对象之间传输数据

我需要使用UI组件更新模型类的数据,同时使用数据对象中的更改更新UI组件。 详细说明大量数据依赖于其他数据。 ea:A和B的SUM。SUM需要在UI上显示并存储在Model Class中。

在实际情况中,我有大约58个可编辑的字段,混合了文本和数字。 计算字段的一半。

思考我有很多解决方案。 我的问题是,我没有经验来决定或判断什么是最好的方式,如果有的话。 两个主要候选人是:

  1. 第一个是将DocumentListeners添加到所有可编辑的UI字段。 更改后,它们会更新模型中的数据并调用方法来更新UI中的所有字段。 缺点 – 我粗暴的看法 – 是我有超过50个领域。 我不知道如何编写代码而不为每个UI组件编写特定的监听器。 也许以后可能会很难处理代码的变化。
  2. 创建一个类的数组,用于注册每个可编辑或计算的UI组件。 该类不仅将注册UI组件,还将使用reflection注册要从模型对象设置或检索信息的方法。 Document Lister仍将处理更改,但现在所有UI组件都可以相同,因为arrays可以处理更改。 一个好的观点是Model和UI之间的所有转换都可以在一个类中编码。 缺点是反思,人们似乎总是建议避免它。

处理这种情况的最好或最好的方法是什么?

代码我用来测试:

public class Comunication { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { //Main Window JFrame frame = new JFrame(); frame.setTitle("SumTest"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setMinimumSize(new Dimension(500,200)); frame.setVisible(true); //Main Panel JPanel pane = new JPanel(); frame.setContentPane(pane); //Component JTextField valueA = new JTextField("VALUE A"); JTextField valueB = new JTextField("VALUE B"); JTextField valueSum = new JTextField("VALUE SUM"); pane.add(valueA); pane.add(valueB); pane.add(valueSum); } }); } } class Data { private int a; private int b; private int sum; public Data() { a = 1; b = 2; Calculate(); } public void Calculate() { sum = a + b; } public int getA() { return a; } public int getB() { return b; } public int getSUM() { return sum; } public void setA(int i) { a = i; } public void setB(int i) { b = i; } } 

第2部分:

通过试验用户提供的信息,我可以自由地尝试别的东西。 一种解决方案是创建一个链接View和Model的侦听器类。 每次将其添加到字段(视图)时,应该稍微更改此侦听器,这是在不使用reflection的情况下将字段与模型中的方法链接的唯一方法。

因此,更新算法是:更改后,视图会更新模型。 之后:gobal控制器使用模型上的新信息更新所有视图。

我发现的问题,可能是由于缺乏经验:

1)由于所有视图都有文档更改侦听器:当全局控制器更新所有视图/字段时,它们再次调用侦听器。 我发现的一种解决方法是向Listener添加一个“silent”标志。

2)当视图更新模型并调用全局控制器以更新所有其他视图时,它无法自行更新。 我还没有找到原因,我认为可能会导致循环。 解决方法是告诉调用它的全局控制器,并更新除调用者之外的每个视图。

下面是解决方案和解决方法的完整工作代码。 意见非常受欢迎,我想做得对或更好。

 import java.awt.*; import java.util.ArrayList; import javax.swing.*; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; public class Comunication { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { @Override public void run() { //Main Window JFrame frame = new JFrame(); frame.setTitle("SumTest"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setMinimumSize(new Dimension(500,200)); frame.setVisible(true); //Main Panel JPanel pane = new JPanel(); frame.setContentPane(pane); //Data Model DataModel model = new DataModel(); GlobalUpdateController viewUpdateController = new GlobalUpdateController(); //Component JTextField valueA = new JTextField(""); JTextField valueB = new JTextField(""); JTextField valueSum = new JTextField(""); valueA.setPreferredSize(new Dimension(30, 20)); valueB.setPreferredSize(new Dimension(30, 20)); valueSum.setPreferredSize(new Dimension(30, 20)); pane.add(valueA); pane.add(valueB); pane.add(valueSum); //Listeners valueA.getDocument().addDocumentListener(new StealthListener(valueA , viewUpdateController) { @Override public void updateView() { this.view.setText( Integer.toString( model.getA() ) ); } @Override public void updateModel() { model.setA( Integer.parseInt( this.view.getText() ) ); } }); valueB.getDocument().addDocumentListener(new StealthListener(valueB , viewUpdateController) { @Override public void updateView() { this.view.setText( Integer.toString( model.getB() ) ); } @Override public void updateModel() { model.setB( Integer.parseInt( this.view.getText() ) ); } }); valueSum.getDocument().addDocumentListener(new StealthListener(valueSum , viewUpdateController) { @Override public void updateView() { this.view.setText( Integer.toString( model.getSUM() ) ); } @Override public void updateModel() { //Do nothing } }); //Initial Update viewUpdateController.updateAllViews(null); } }); } } class DataModel { private int a; private int b; private int sum; public DataModel() { a = 3; b = 5; Calculate(); } public void Calculate() { sum = a + b; } public int getA() { return a; } public int getB() { return b; } public int getSUM() { return sum; } public void setA(int i) { a = i; Calculate(); } public void setB(int i) { b = i; Calculate(); } } class StealthListener implements DocumentListener { JTextField view; GlobalUpdateController viewList; private boolean silent; public StealthListener(JTextField view, GlobalUpdateController viewList) { this.view = view; this.viewList = viewList; this.silent = false; this.viewList.add(this); } public void setSilent(boolean val) { this.silent = val; } public void updateView() { // Unique to each view, to be Overriden } public void updateModel() { // Unique to each view, to be Overriden } public void update() { //The silent flag is meant to avoid ListenerLoop when changing the document. //When the silent is true is meant to listen to internal changes. if(this.silent == false) { updateModel(); this.viewList.updateAllViews(this); } } @Override public void insertUpdate(DocumentEvent e) { update(); } @Override public void removeUpdate(DocumentEvent e) { update(); } @Override public void changedUpdate(DocumentEvent e) { update(); } } class GlobalUpdateController { private ArrayList viewList; public GlobalUpdateController() { this.viewList = new ArrayList(); } public void add(StealthListener control) { this.viewList.add(control); } public void updateAllViews(StealthListener caller) { for( StealthListener view : viewList) { if( caller==null || view != caller ) { view.setSilent(true); view.updateView(); view.setSilent(false); } } } } 

  1. 在此相关示例中,每个任意数量的可编辑文本字段都添加了PropertyChangeListenerFocusListener的实例。 每个侦听器都调用一个公共update()方法来重新计算派生的sum 。 在下面的变体中,这些字段共享UpdateListener单个实例, 它既FocusListener PropertyChangeListener 。 如果需要每次击键update()UpdateListener也只实现DocumentListener

    实际上, 模型List的数据, 控制器是强制输入字段之间关系的公共update()方法。 这种方法的可扩展性取决于update()复杂性。 该示例使用JFormattedTextField方便地格式化相关字段。

  2. 每当我考虑使用reflection时 ,我也会将策略模式enum一起考虑作为替代方案。 这里和这里都可以看到例子。

图片

 import java.awt.EventQueue; import java.awt.GridLayout; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.text.NumberFormat; import java.util.ArrayList; import java.util.List; import javax.swing.JFormattedTextField; import javax.swing.JFrame; import javax.swing.JPanel; /** * @see https://stackoverflow.com/a/31764798/230513 * @see https://stackoverflow.com/q/8703464/230513 * @see https://stackoverflow.com/questions/6803976 */ public class Adder extends JPanel { private static final int MAX = 3; private final List fields = new ArrayList<>(); private final NumberFormat format = NumberFormat.getNumberInstance(); private final JFormattedTextField sum = new JFormattedTextField(format); private final UpdateListener listener = new UpdateListener(); private class UpdateListener extends FocusAdapter implements PropertyChangeListener { @Override public void propertyChange(PropertyChangeEvent e) { update(); } @Override public void focusLost(FocusEvent e) { EventQueue.invokeLater(new Runnable() { @Override public void run() { update(); } }); } } public Adder() { this.setLayout(new GridLayout(0, 1)); for (int i = 0; i < MAX; i++) { JFormattedTextField tf = init(); fields.add(tf); this.add(tf); } sum.setHorizontalAlignment(JFormattedTextField.RIGHT); sum.setEditable(false); sum.setFocusable(false); this.add(sum); } private JFormattedTextField init() { JFormattedTextField jtf = new JFormattedTextField(format); jtf.setValue(0); jtf.setHorizontalAlignment(JFormattedTextField.RIGHT); jtf.addFocusListener(listener); jtf.addPropertyChangeListener("value", listener); return jtf; } private void update() { long total = 0; for (JFormattedTextField tf : fields) { Number v = (Number) tf.getValue(); total += v.longValue(); } sum.setValue(total); } private void display() { JFrame f = new JFrame("Adder"); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.add(this); f.pack(); f.setLocationRelativeTo(null); f.setVisible(true); } public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { @Override public void run() { new Adder().display(); } }); } }