代码之家  ›  专栏  ›  技术社区  ›  Psyny

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

  •  1
  • Psyny  · 技术社区  · 9 年前

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

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

    仔细想想,我找到了很多解决方案。我的问题是,我没有经验来决定或判断什么是最好的道路,如果有的话。两个主要候选人是:

    1. 第一个是将DocumentListeners添加到所有可编辑的UI字段。更改后,它们会更新模型中的数据,并调用一个方法来更新UI中的所有字段。缺点——我粗略的看法——是我有50多个领域。如果不为每个UI组件编写特定的侦听器,我不知道如何对其进行编码。Wich也可能会在以后处理代码更改时遇到困难。
    2. 创建一个类数组,用于注册每个可编辑或计算的UI组件。该类不仅会注册UI组件,还会使用反射注册要调用的方法,以从模型对象中设置或检索信息。Document Lister仍将处理这些更改,但现在它对所有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部分:

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

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

    我发现的问题,可能是因为缺乏经验:

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

    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<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 回复  |  直到 9 年前
        1
  •  2
  •   Community Mofi    7 年前
    1. 在此相关 example 任意数量的可编辑文本字段中的每一个都会添加 PropertyChangeListener 和一个 FocusListener 。每个侦听器调用一个公共 update() 重新计算派生函数的方法 sum 。在下面的变体中,字段共享 仅有一个的 的实例 UpdateListener 那就是 二者都 焦点侦听器 属性更改侦听器 允许 更新侦听器 也实施 DocumentListener 只有在你需要的时候 更新() 每次击键。

      实际上 模型 数据是否在 List<JFormattedTextField> ,以及 控制器 是常见的 更新() 方法,该方法强制输入字段之间的关系。这种方法的可扩展性取决于 更新() 。示例使用 JFormattedTextField 以方便相关字段的格式化。

    2. 每当我考虑使用 反射 ,我还考虑 strategy pattern enum 作为替代方案。示例如下 here here .

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