【问题标题】:Communication between Models in MVC-based GUI基于 MVC 的 GUI 中模型之间的通信
【发布时间】:2013-06-06 00:31:18
【问题描述】:

我正在根据 MVC 模式开发我的 GUI:

-GUIview:Swing 组件(JFrame 和几个 JTables)。 -GUIcontroller:侦听器(在此处添加,并在此处在内部类中定义) -GUImodel:修改和存储数据,触发更改事件。

模型中的更改通过控制器(而不是直接)传递给视图,就像在this 示例中一样。

我还为 View 类中包含的不同 JTable 编写了不同的自定义 JTableModel(扩展 AbstractTableModel)。所有的 JTableModel 都在“GUImodel”包内的不同类中定义。每个 JTableModel 都定义了一个 ArrayList 和一些操作 ArrayList 的方法。

根据 MVC 指南,模型应该对视图一无所知。其实main()方法的定义如下:

GUImodel model = new GUImodel();
GUIcontroller controller = new GUIcontroller();
GUIview view = new GUIview(controller, model);

controller.addView(view);
controller.addModel(model);

view.setVisible(true);
controller.addControllerListerners();

我的问题是: 当我在 GUImodel 中执行方法时(例如,因为按下了 JButton 并且我需要从外部文件加载数据),我需要修改一些 JTableModels(将数据/行添加到其 ArrayList)并获取更改反映在 JTable 中。我的第一个想法是:

ArrayList newArrayList = fileLoader(filePath);  //create ArrayList and load info
guiView.getTable1Model().updateArrayList(newArrayList);  //update JTableModel ArrayList

但是,这种方法是无效的,因为 GUImodel 应该完全独立于 GUIview。

有什么想法吗?

【问题讨论】:

    标签: java swing model-view-controller model jtable


    【解决方案1】:

    意识到 MVC 主要是一种与数据封装有关的模式,它使用另一种模式,观察者来传达变化,这可能是件好事。作为数据封装者,Model 对 Views 和 Controllers 一无所知,但作为 Observable,它确实知道它有 Observers,当发生变化时需要通知它们。

    A Description of the Model-View-Controller User Interface Paradigm in the Smalltalk-80 System, page 4解释得很好:

    为了管理变更通知,开发了对象作为依赖项的概念。意见和 模型的控制器在列表中注册为模型的依赖项,以便随时通知 模型的某些方面发生了变化。当模型发生变化时,会广播消息通知 它的所有依赖于变化。该消息可以参数化(带参数),所以 可以有多种类型的模型更改消息。每个视图或控制器都响应 以适当的方式改变适当的模型。

    为了说明这个概念,您可以从自己的 Observer/Observable 类开始:

    public interface Observer {
        public void update(int message);
    }
    public interface Observable {
        public void registerObserver(Observer observer);
    }
    
    public class Model implements Observable {
        List<Observer> observers;
    
        public void registerObserver(Observer observer) {
            observers.add(observer);
        }
    
        public void loadFile(String path) {
            // load file and change data
            foreach (Observer observer: observers)
                observer.update(READ_NEW_DATA);
        }
    
        public ArrayList getData() { return data; }
    }
    
    public class View implements Observer {
        public void update(int message) {
            doWhateverWith(model.getData());
        }
    }
    
    public class Controller implements Observer {
        public void update(int message) {
            doWhateverWith(model.getData());
        }
    
        public void onClick() {
            model.loadFile("someFile");
        }
    }
    

    如您所见,模型对视图和控制器的内部运作一无所知。它甚至不知道返回 ArrayList 是否对他们特别有用(尽管在实践中你希望是这样)。所以在这方面,实现了独立性。

    Obervable 和 Observers 之间的通信没有独立性,但这不是 MVC 模式要求的一部分。

    如果您希望您的 GUI 在现有的 Swing 观察者模式(侦听器)之上搭便车,那么您的类应该从适当的类继承:

    public class Model extends AbstractTableModel...
    
    public class View implements TableModelListener...
    
    public class Controller implements CellEditorListener...
    

    等等。由于JTable同时实现了TableModelListener和CellEditorListener,它实际上是View和Controller的组合。因此,您可以选择让组合的 ViewController 类扩展 JTable,或者将它们分开。在后一种情况下,视图可以扩展 JTable,覆盖控件侦听器,以便它们将事件传递给控制器​​类。但这听起来比它的价值更多。

    【讨论】:

      【解决方案2】:

      正如here 所讨论的,您将模型和视图松散耦合是正确的。 JTable 实现 TableModelListener 来监听它自己的模型,而你的 AbstractTableModel 无疑会触发导致监听表自我更新的事件。

      在这种情况下,让依赖TableModel 将自己作为TableModelListener 添加到主TableModel。然后,依赖模型可以触发所需的事件,以通知它自己的侦听器从主节点传播的更改。

      【讨论】:

        【解决方案3】:

        但是,这种方法是无效的,因为 GUImodel 应该完全独立于 GUIview。

        Swing 组件本身使用 MVC 模型。模型的变化必须触发视图的变化。问题是如何做到这一点?

        一种方法是让模型访问视图实例,正如您在问题中所说明的那样。

        ArrayList newArrayList = fileLoader(filePath);  //create ArrayList and load info
        guiView.getTable1Model().updateArrayList(newArrayList);  //update JTableModel ArrayList
        

        另一种方式是让控制器更新模型并更新视图。这是我通常在 Swing 应用程序中所做的。

        model.loadArrayList(filePath);
        frame.getFrame().getMainPanel().repaint();
        

        另一种方法是触发动作。这就是 Swing 组件更新 GUI 的方式。

        ArrayList newArrayList = fileLoader(filePath);  //create ArrayList and load info
        fireAction(newArrayLiat);
        

        fireAction 方法适用于侦听器。这是我从AbstractListModel复制的一个开火方法。

        protected void fireContentsChanged(Object source, int index0, int index1) {
        
            Object[] listeners = listenerList.getListenerList();
            ListDataEvent e = null;
        
            for (int i = listeners.length - 2; i >= 0; i -= 2) {
                if (listeners[i] == ListDataListener.class) {
                    if (e == null) {
                        e = new ListDataEvent(source,
                                ListDataEvent.CONTENTS_CHANGED, index0, index1);
                    }
                    ((ListDataListener) listeners[i + 1]).contentsChanged(e);
                }
            }
        }
        

        您必须在模型类中编写侦听器,以便视图类可以编写代码来更改视图。

        EventListenerList 的 Javadoc 包含有关侦听器的更多信息。谢谢 Catalina Island.

        【讨论】:

        【解决方案4】:

        我在 Swing 中的 MVC 风格是,模型和视图相互忽略以及控制器,但控制器非常了解视图和模型。这样,我完成了控制器中的所有逻辑。我只是在视图中留下了 UI + 复杂布局的长代码,并考虑了应用程序模型所需的所有数据,并决定是否应该在我的视图中出现某些数据。我通过view.getBtn().setAction(new ActionForThisOrThatInnerClass()) 之类的东西将向按钮等添加侦听器的功能添加到控制器中

        在您的情况下,我同意表将使用的数据应该以理想的List 形式存储在您的主模型中,但我不会打扰自己将新的TableModel 子类化为处理这些数据,我认为DefaultTableModel 足够强大,可以做很多事情。

        如果我要编写您的要求,这是可运行的示例

        public class Sample {
            public static void main(String[] args){
                View view = new View();
                Model model = new Model();
                Controller controller = new Controller(view, model);
        
                JFrame frame = new JFrame("MVC Demo");
                frame.getContentPane().setLayout(new BorderLayout());
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.getContentPane().add(view.getUI());
                frame.pack();
                frame.setVisible(true);
        
                view.getBtnFileLoader().doClick();
            }
        }
        
        class View{
        
            private JButton btnFileChooser;
            private JButton btnFileLoader;
            private JTable tblData;
            private JPanel pnlMain;
        
            public View(){
                pnlMain = new JPanel(new BorderLayout()){
                    @Override public Dimension getPreferredSize(){
                        return new Dimension(300, 400); 
                    }
                };
                JPanel pnlFileLoader = new JPanel();
                pnlFileLoader.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
                pnlFileLoader.setLayout(new BoxLayout(pnlFileLoader, BoxLayout.LINE_AXIS));
        
                JTextField txtFileDir = new JTextField();
                pnlFileLoader.add(txtFileDir);
        
                btnFileLoader = new JButton();
                pnlFileLoader.add(btnFileLoader);
        
                btnFileChooser = new JButton();
                pnlFileLoader.add(btnFileChooser);
        
                tblData = new JTable();
                JScrollPane pane = new JScrollPane(tblData);
        
                pnlMain.add(pane);
                pnlMain.add(pnlFileLoader, BorderLayout.PAGE_START);
            }
        
            public JPanel getUI(){
                return pnlMain;
            }
        
            public JButton getBtnFileLoader(){
                return btnFileLoader;
            }
        
            public JButton getBtnFileChooser(){
                return btnFileChooser;
            }
        
            public JTable getTblData(){
                return tblData;
            }
        }
        
        class Controller implements PropertyChangeListener{
        
            private View view;
            private Model model;
            private DefaultTableModel tmodel;
        
            public Controller(View view, Model model){
                this.view = view;
                this.model = model;
        
                model.addModelListener(this);
                setupViewEvents();
                setupTable();
            }
            private void setupTable(){
                tmodel = new DefaultTableModel();
        
                tmodel.addColumn("First Name");
                tmodel.addColumn("Last Name");
                tmodel.addColumn("Occupation");
        
                view.getTblData().setModel(tmodel);
            }
        
            private void setupViewEvents(){
                view.getBtnFileChooser().setAction(new AbstractAction("Choose"){
                    @Override
                    public void actionPerformed(ActionEvent arg0) {
                        //choose the file then put the dir
                        //in the txtfield
                    }
                });
        
                view.getBtnFileLoader().setAction(new AbstractAction("Load"){
                    @Override
                    public void actionPerformed(ActionEvent arg0) {
                        //validate if the dir in the textfield exists and the file is loadable
                        //load the file specified in the textfield
        
                        //assumming the list is already retrieved from the file
                        //and the list contains the following person
                        List<Person> list = new ArrayList<Person>();
                        Person p1 = new Person("Bernardo", "Santos", "Developer");
                        Person p2 = new Person("Robert", "Erasquin", "Architect");
                        Person p3 = new Person("Klarrise", "Caparas", "Food Scientist");
                        list.add(p1);
                        list.add(p2);
                        list.add(p3);
        
                        //now update the model of the new value for the list
                        model.setTheList(list);
        
                    }
                });
        
            }
        
            @Override
            @SuppressWarnings("unchecked")
            public void propertyChange(PropertyChangeEvent evt) {
                if(evt.getPropertyName().equals("theList")){
        
                    List<Person> newVal = (List<Person>) evt.getNewValue();
                    DefaultTableModel tmodel = (DefaultTableModel)view.getTblData().getModel();
        
                    for(Person p : newVal){
                        tmodel.addRow(new Object[]{p.getFirstName(), p.getLastName(), p.getOccupation()});
                    }
        
                }
            }
        }
        
        
        
        class Model{
        
            private List<Person> theList;
            private SwingPropertyChangeSupport propChangeFirer;
        
            public Model(){
                propChangeFirer = new SwingPropertyChangeSupport(this);
            }
        
            public void setTheList(List<Person> theList){
                List<Person> oldVal = this.theList;
                this.theList = theList;
        
                //after the model has been updated, notify its listener about
                //the update, in our case the controller itself listens to the model
                propChangeFirer.firePropertyChange("theList", oldVal, theList);
            }
        
            public void addModelListener(PropertyChangeListener prop) {
                propChangeFirer.addPropertyChangeListener(prop);
            }
        
        }
        
        class Person{
                private String firstName;
                private String lastName;
                private String occupation;
        
                public Person(String firstName, String lastName, String occupation){
                    this.firstName = firstName;
                    this.lastName = lastName;
                    this.occupation = occupation;
                }
        
                public String getFirstName() {
                    return firstName;
                }
                public void setFirstName(String firstName) {
                    this.firstName = firstName;
                }
                public String getLastName() {
                    return lastName;
                }
                public void setLastName(String lastName) {
                    this.lastName = lastName;
                }
                public String getOccupation() {
                    return occupation;
                }
                public void setOccupation(String occupation) {
                    this.occupation = occupation;
                }
            }
        

        【讨论】:

          猜你喜欢
          • 2011-09-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-09-18
          • 2017-12-15
          • 2012-06-28
          • 2016-09-11
          • 2012-06-02
          相关资源
          最近更新 更多