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

标签 java swing jtextfield

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

在实际情况中,我有大约 58 个可编辑字段,其中包含文本和数字。计算字段数量减半。

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

  1. 第一个是将 DocumentListener 添加到所有可编辑的 UI 字段。更改后,它们会更新模型中的数据并调用方法来更新 UI 中的所有字段。缺点——我的粗略看法——是我有超过 50 个字段。我不知道如何在不为每个 UI 组件编写特定监听器的情况下对其进行编码。这也可能会使以后处理代码更改变得困难。
  2. 创建一个类数组,用于注册每个可编辑或计算的 UI 组件。该类不仅会注册 UI 组件,还会使用反射来注册要从模型对象设置或检索信息的方法。文档列表器仍将处理更改,但现在它对于所有 UI 组件都可以相同,因为数组可以处理更改。一个优点是模型和 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 和模型的监听器类。每次将监听器添加到字段( View )时,都应该稍微更改一下,这是在不使用反射的情况下将字段与模型中的方法链接起来的唯一方法。

因此,更新的算法是:当更改时, View 更新模型。 之后:全局 Controller 使用模型上的新信息更新所有 View 。

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

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

2) 当 View 更新模型并调用全局 Controller 更新所有其他 View 时,它无法更新自身。我还没找到原因,我认为可能是造成循环的原因。解决方法是告诉全局 Controller 谁调用了它,并更新除调用者之外的每个 View 。

包含解决方案和解决方法的完整工作代码如下。 非常欢迎提出意见,我想做得正确,或者更好。

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<StealthListener> viewList;

    public GlobalUpdateController() {
        this.viewList = new ArrayList<StealthListener>();
    }

    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. 在此相关example每个任意数量的可编辑文本字段都会添加一个 PropertyChangeListener 的实例和一个 FocusListener 。每个监听器都会调用一个公共(public) update()重新计算派生的方法 sum 。在下面的变体中,字段共享 UpdateListener单个实例。即两者都是FocusListener PropertyChangeListener 。让UpdateListener还实现DocumentListener仅当您需要 update()每次击键。

    实际上,模型List<JFormattedTextField> 中的数据, Controller 是常见的 update()强制输入字段之间关系的方法。这种方法的可扩展性取决于 update() 所产生的复杂性。 。该示例使用 JFormattedTextField方便相关字段的格式化。

  2. 每当我考虑使用反射时,我也会考虑 strategy patternenum 结合使用作为备选。例子见herehere .

image

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

关于java - 在 JComponent 和模型对象之间传输数据,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31763734/

相关文章:

java - JpaRepository 上的@Transactional

Java - 如何用颜色而不只是边框填充 Windows L&F 按钮?

java - 输入文本字段在显示 Jlabel 后消失。

java - 将 JTextField/JTextArea 堆叠在 ImageIcon 之上

java - 如何刷新JInternalFrame或JTable

java - 如何在 JTextField 中监听一个字符的输入?

java - 错误消息的顺序不正确

java - 以交替降序对 2 个整数数组列表进行排序

java - 使用jpa限制数字列的长度

java - 以团队形式构建 Swing 类(class)