在JComponent和模型对象之间传输数据 [英] Transit data between JComponent and a model Object

查看:129
本文介绍了在JComponent和模型对象之间传输数据的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I need to keep the data of a model class updated with UI components, and at same time UI components updated with changes in the data object. With a detail that a considerable ammount of data is dependent of other data. e.a.: SUM of A and B. The SUM need to be displayed at UI and stored in the Model Class.

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

In the real case I have around 58 editable fields, mixed of texts and numbers. And half as much calculated fields.

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

Thinkering around I've come with many solutions. My problem is that I have no experience to decide or judge what's the best way to go, if any at all. The two main candidates are:


  1. 第一个是将DocumentListeners添加到所有可编辑的UI字段。更改后,它们会更新模型中的数据并调用方法来更新UI中的所有字段。缺点 - 我粗暴的看法 - 是我有超过50个领域。我不知道如何编写代码而不为每个UI组件编写特定的监听器。 Wich也可能会在以后处理代码更改。

  2. 创建一个类的数组,用于注册每个可编辑或计算的UI组件。该类不仅将注册UI组件,还将使用反射注册要从模型对象设置或检索信息的方法。 Document Lister仍将处理更改,但现在所有UI组件都可以相同,因为阵列可以处理更改。一个好的观点是Model和UI之间的所有转换都可以在一个类中编码。缺点是反思,人们似乎总是建议避免它。

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

Whats the best, or a good, way to handle the situation?

我用来测试代码:

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的监听器类。每次将它添加到字段(视图)时,应该稍微更改一下这个监听器,这是在不使用反射的情况下将字段与模型中的方法链接的唯一方法。

Experimenting with information provided bu the users, I've took the freedom to try something else. One solution would be creating a listener class that links the View and the Model. This listener should be changed a bit each time its added to a field(view), it's the only way o found to link a field with a method in the model without using reflection.

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

So, the algorithm for updates is: When changed, a view update the model. Afterwards: a gobal controller update all views with the new information on the model.

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

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

1) Since all views have document change listeners: when the global controller update all views/fields they call the listener again. A workaround I've found was to add a "silent" flag to the Listener.

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

2) When a view updates the model and call the global controller to update all other views, it cannot update itself. I've not found the reason yet, I think may be causing a loop. The workaround was tell the global controller who called it, and update every view except the caller.

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

The full working code with the solution and workarounds is below. Opinions would be very welcome, i want to do it right, or better.

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. 在这个相关的示例中任意数量的可编辑文本字段添加 PropertyChangeListener FocusListener 的实例。每个侦听器调用一个公共 update()方法来重新计算派生的总和。在下面的变体中,字段共享 c> UpdateListener 的实例,两者 a FocusListener a PropertyChangeListener 。只有当你需要 update() UpdateListener 也实现 DocumentListener c>每次击键。

  1. In this related example each of an arbitrary number of editable text fields adds an instance of a PropertyChangeListener and a FocusListener. Each listener invokes a common update() method to recompute a derived sum. In the variation below, the fields share a single instance of an UpdateListener that is both a FocusListener and a PropertyChangeListener. Let UpdateListener also implement DocumentListener only if you need to update() with each keystroke.

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

In effect, the model is the data in a List<JFormattedTextField>, and the controller is the common update() method that enforces the relationship among the input fields. The scalability of this approach hinges on the resulting complexity of update(). The example uses JFormattedTextField for convenient formatting of related fields.

每当我考虑使用反射时,我还会同时考虑 策略模式 使用枚举作为替代方案。可以参见此处这里

Whenever I consider using reflection, I also consider the strategy pattern in conjunction with enum as an alternative. Examples are seen here and here.

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

这篇关于在JComponent和模型对象之间传输数据的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆