3

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

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

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

我还为 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。

任何想法?

4

4 回答 4

3

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

Smalltalk-80 系统中的模型-视图-控制器用户界面范式的描述,第 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,覆盖控件侦听器,以便它们将事件传递给控制器​​类。但这听起来比它的价值更多。

于 2013-06-10T19:04:31.897 回答
2

正如这里所讨论的,您将模型和视图松散耦合是正确的。JTable实现TableModelListener侦听自己的模型,并且您AbstractTableModel无疑会触发导致侦听表自我更新的事件。

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

于 2013-06-10T14:47:20.107 回答
2

However, this approach is not valid, since GUImodel should be totally independent of GUIview.

The Swing Components themselves use the MVC model. Changes in the model have to trigger changes in the view. The question is how do you do this?

One way is for the model to have access to the view instance(s), as you've illustrated in your question.

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

Another way is for the controller to update the model and update the view. This is what I usually do in a Swing application.

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

Another way is to fire actions. This is how the Swing components update the GUI.

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

The fireAction method would work with listeners. Here's a fire method I copied from 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);
        }
    }
}

You would have to write listeners in your model classes that the view classes can write code to change the view.

The Javadoc for the EventListenerList has more information about listeners. Thanks Catalina Island.

于 2013-06-10T14:50:59.117 回答
2

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

在您的情况下,我同意该表将使用的数据应该以理想的形式存储在您的主模型中 a 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;
        }
    }
于 2013-06-12T15:03:46.660 回答