有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

基于MVC的GUI中模型间的java通信

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

-GUIview:Swing组件(JFrame和几个JTable)。 -GUIcontroller:listeners(在这里添加,并在内部类中定义) -GUI模型:修改和存储数据,触发更改事件

模型中的更改通过控制器(而不是直接)传递给视图,如this示例中所示

我还为视图类中包含的不同JTables编写了不同的定制JTableModels(扩展AbstractTableModel)。所有JTableModels都在包“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

知道吗


共 (4) 个答案

  1. # 1 楼答案

    However, this approach is not valid, since GUImodel should be totally independent of 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方法可以用于侦听器。这是我从^{复制的一个fire方法

    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

  2. # 2 楼答案

    我在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;
            }
        }
    
  3. # 3 楼答案

    可以很好地认识到,MVC主要是一种与数据封装有关的模式,它使用另一种模式Observer来传达更改。作为数据封装者,模型对视图和控制器一无所知,但作为可观察对象,它确实知道它有观察者,当发生更改时需要通知观察者

    A Description of the Model-View-Controller User Interface Paradigm in the Smalltalk-80 System, page 4很好地解释了这一点:

    To manage change notification, the notion of objects as dependents was developed. Views and controllers of a model are registered in a list as dependents of the model, to be informed whenever some aspect of the model is changed. When a model has changed, a message is broadcast to notify all of its dependents about the change. This message can be parameterized (with arguments), so that there can be many types of model change messages. Each view or controller responds to the appropriate model changes in the appropriate manner.

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

    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是否对他们特别有用(尽管在实践中,您希望这样)。因此,在这方面,独立是实现的

    观察者和观察者之间的通信没有独立性,但这不属于MVC模式的要求

    如果您希望GUI在现有Swing Observer模式(侦听器)的基础上搭便车,那么您的类应该继承自适当的类:

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

    等等。由于JTable同时实现了TableModelListener和CellEditorListener,因此它实际上是视图和控制器的组合。因此,您可以选择使用组合的ViewController类来扩展JTable,或者单独使用它们。在后一种情况下,视图可以扩展JTable,覆盖控件侦听器,以便它们将事件传递给控制器类。但这听起来像是做了很多不值得做的工作

  4. # 4 楼答案

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

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