java swing mvc architecture - 带有MCVE示例的

时间:2015-12-04 13:26:10

标签: java swing model-view-controller

考虑到多个问题excellent_informative_for_me - trashgod的回答,thatthat以及其他几个不回答我问题的问题,
如何设计一个关于ActionListeners位置的类
(以及整体MVC分离 - 更多解释如下)。

TOC

  1. 问题解释
  2. 我的例子(4)
  3. 的文件结构树
  4. 编译/清除源代码(4)
  5. 来源
  6. 1。问题解释

    我读过关于MVC的内容,我认为我理解了大部分内容,让我们假设这是真的,为了这个问题。不详细说明:

    1. 根据控制器请求,从模型生成视图 在大多数实现中,View可以访问Model实例。
    2. Controller与用户交互,将更改传播到Model和View。
    3. 模型极其简化,是数据的容器 它可以通过View观察到。
    4. 现在,我的困惑与ActionListeners有关 - 哪个类应该注册 - 反过来也包含 - 按钮的代码,或者实际上是大多数View元素的代码,它们实际上不仅仅是指标,而是模型操纵器?

      假设我们在视图中有两个项目 - 按钮可更改模型数据,而某些视觉项目仅用于更改视图外观。让代码负责在View类中更改View外观似乎是合理的。我的问题与第一个案件有关。我有几个想法:

      • View创建按钮,因此在View中创建ActionListeners并同时注册回调是很自然的。 但这要求View拥有与模型相关的代码,打破封装。 View应该只知道底层的Controller或Model,只能通过Observer与它交谈。
      • 我可以公开View项目,比如按钮等,并从Controller,将ActionListeners附加到它们,但这又打破了封装。
      • 我可以为每个按钮实现一些回调 - View会询问控制器是否有任何代码应该注册为给定按钮名称​​的ActionListener但这看起来过于复杂,并且需要同步控制器和视图之间的名称。
      • 我可以假设,理智;),TableFactory中的按钮可能会公开,允许将ActionListeners注入任何代码。
      • 控制器可以替换整个View项目(创建按钮并替换现有项目)但这看起来很疯狂,因为它不是它的角色

      2。我的例子(4)

      的文件结构树
      .
      └── test
          ├── controllers
          │   └── Controller.java
          ├── models
          │   └── Model.java
          ├── resources
          │   └── a.properties
          ├── Something.java
          └── views
              ├── TableFactory.java
              └── View.java
      

      3。编译/清除源代码(4)

      编译:

      • javac test / Something.java test / models / * .java test / controllers / * .java test / views / * .java

      使用以下命令运行:

      • java test.Something

      清洁:

      • 找到。 -iname“* .class”-exec rm {} \;

      4。来源

      此代码还包含国际化存根,为此我提出了单独的问题,这些行已明确标记,不应对答案产生任何影响。

      控制器=> Controller.java
      package test.controllers;
      import test.models.Model;
      import test.views.View;
      public class Controller {
      // Stub - doing nothing for now.
      }
      
      Models => Model.java
      package test.models;
      import java.util.Observable;
      public class Model extends Observable {
      }
      
      Something.java
      package test;
      
      import test.views.View;
      import test.models.Model;
      import test.controllers.Controller;
      
      public class Something {
      
          Model m;
          View v;
          Controller c;
      
          Something() {
              initModel();
              initView();
              initController();
          }
      
          private void initModel() {
              m = new Model();
          }
      
          private void initView() {
              v = new View(m);
          }
          private void initController() {
              c = new Controller(m, v);
          }
      
          public static void main(String[] args) {
              javax.swing.SwingUtilities.invokeLater(new Runnable() {
                  public void run() {
                      Something it = new Something();
                  }
              });
          }
      }
      
      View => View.java
      package test.views;
      
      import java.awt.*;              // layouts
      import javax.swing.*;           // JPanel
      
      import java.util.Observer;      // MVC => model
      import java.util.Observable;    // MVC => model
      import test.models.Model;       // MVC => model
      
      import test.views.TableFactory;
      
      public class View {
      
          private JFrame root;
          private Model model;
          public JPanel root_panel;
      
          public View(Model model){
              root = new JFrame("some tests");
              root.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
              root_panel = new JPanel();
      
              root_panel.add(new TableFactory(new String[]{"a", "b", "c"}));
      
              this.model = model;
              this.model.addObserver(new ModelObserver());
      
              root.add(root_panel);
              root.pack();
              root.setLocationRelativeTo(null);
              root.setVisible(true);
          }
      }
      
      class ModelObserver implements Observer {
      
          @Override
          public void update(Observable o, Object arg) {
              System.out.print(arg.toString());
              System.out.print(o.toString());
          }
      }
      
      View => TableFactory.java
      package test.views;
      
      import java.awt.*;
      import java.awt.event.*;
      import java.util.*;
      import javax.swing.*;
      import javax.swing.table.DefaultTableModel;
      
      public class TableFactory extends JPanel {
      
          private String[] cols;
          private String[] buttonNames;
          private Map<String, JButton> buttons;
          private JTable table;
      
          TableFactory(String[] cols){
      
              this.cols = cols;
              buttonNames = new String[]{"THIS", "ARE", "BUTTONS"};
              commonInit();
          }
          TableFactory(String[] cols, String[] buttons){
      
              this.cols = cols;
              this.buttonNames = buttons;
              commonInit();
          }
      
          private void commonInit(){
      
              this.buttons = makeButtonMap(buttonNames);
              DefaultTableModel model = new DefaultTableModel();
              this.table = new JTable(model);
              for (String col: this.cols)
                  model.addColumn(col);
      
              setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
      
              JPanel buttons_container = new JPanel(new GridLayout(1, 0));
              for (String name : buttonNames){
                  buttons_container.add(buttons.get(name));
              }
      
              JScrollPane table_container = new JScrollPane(table);
      
              this.removeAll();
              this.add(buttons_container);
              this.add(table_container);
              this.repaint();
          }
      
          private Map<String, JButton> makeButtonMap(String[] cols){
              Map<String, JButton>  buttons = new HashMap<String, JButton>(cols.length);
              for (String name : cols){
                  buttons.put(name, new JButton(name));
              }
              return buttons;
          }
      }
      

      编辑(回应下面的评论)

      下一个信息来源here

      经过一番思考之后,我理解了Olivier的评论以及后来的Hovercraft充满了鳗鱼的细节... javax.swing.Action =&gt; setAction是我的方式。 Controller访问View的JPanels,获取对包含按钮或任何JComponent的映射的引用,并为其添加操作。 View不知道Controllers代码中的内容。

      ,当我把它弄得很好的时候,我会更新这个答案

      只有两件令我担心的事情(两者都非常罕见,但仍然存在):

      1. 因为我将View的添加动作公开的方法,实际上我相信任何人只能从控制器或视图添加它。但如果该模型可以访问视图 - 它也可以覆盖操作。
      2. 也是压倒性的。如果我设置一些对象的动作,然后忘记并从不同的地方设置另一个动作,它就会消失,这可能会使调试变得困难。

3 个答案:

答案 0 :(得分:1)

虽然Model–View–Controller模式为no panacea,但 是Swing应用程序设计中的循环模式。如上所述here,Swing控件组件通常是视图包含层次结构的一部分:Swing应用程序的控制器几乎没什么可做的,只是将这些组件连接到相关的监听器; Action的实例,其中&#34;可用于将功能和状态与组件分开,&#34;特别方便。在这个example中,重置处理程序(一个简单的ActionListener)嵌套在控制器中,但它也可以作为{{1}从模型中导出}。正如建议here,您可能需要尝试不同的设计。引用了几种方法here

答案 1 :(得分:1)

虽然@trashgod的回答总结并扩展到评论的某种程度的讨论,但我发布了基于该讨论的丑陋(但工作)解决方案(在Controller类中实现Action)

有什么变化?

  1. TableFactory - 已添加public void setObjectAction(Action a, JButton b)并允许公开访问Map<String, JButton> buttons

  2. Controller类需要实现AbstractAction或从中继承的类,以及将此类的对象设置为View的深层嵌套JComponent的代码。我发现那部分非常难看,而且我不确定这种方法是否合理,或者是否有更好的解决方案(将在稍后检查气垫船中充满鳗鱼豆)。

  3. 我不需要进行任何其他更改但我稍微修改了View.java以稍微更好地显示此问题的目的,或者以不同的方式显示(这使得View.java比MCVE更多,但恕我直言更好地描述了目的)

  4. TableFactory.java

    package test.views;
    
    import java.awt.*;
    import java.awt.event.*;
    import java.util.*;
    import javax.swing.*;
    import javax.swing.table.DefaultTableModel;
    import javax.swing.Action;
    
    public class TableFactory extends JPanel {
    
        private String[] cols;
        private String[] buttonNames;
        public Map<String, JButton> buttons;
        private JTable table;
    
        TableFactory(String[] cols){
            this.cols = cols;
            buttonNames = new String[]{"some", "buttons"};
            commonInit();
        }
        TableFactory(String[] cols, String[] buttons){
    
            this.cols = cols;
            this.buttonNames = buttons;
            commonInit();
        }
    
        private void commonInit(){
    
            this.buttons = makeButtonMap(buttonNames);
    
            DefaultTableModel model = new DefaultTableModel();
            this.table = new JTable(model);
            for (String col: this.cols)
                model.addColumn(col);
    
            setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
    
            JPanel buttons_container = new JPanel(new GridLayout(1, 0));
            for (String name : buttonNames){
                buttons_container.add(buttons.get(name));
            }
    
            JScrollPane table_container = new JScrollPane(table);
    
            this.removeAll();
            this.add(buttons_container);
            this.add(table_container);
            this.repaint();
        }
    
        private Map<String, JButton> makeButtonMap(String[] cols){
            Map<String, JButton>  buttons = new HashMap<String, JButton>(cols.length);
            for (String name : cols){
                buttons.put(name, new JButton(name));
            }
            return buttons;
        }
    
        public void setObjectAction(Action a, JButton b){
            //it might be possible to set actions to something else than button, I imagine JComponent, but I havent figured out yet how
            b.setAction(a);
        }
    }
    

    View.java

    package test.views;
    
    import java.awt.*;              // layouts
    import javax.swing.*;           // JPanel
    
    import java.util.Observer;      // MVC => model
    import java.util.Observable;    // MVC => model
    import test.models.Model;       // MVC => model
    
    import test.views.TableFactory;
    
    public class View {
    
        private JFrame root;
        private Model model;
        public JPanel root_panel;
        public JPanel some_views[];
    
        public View(Model model){
            root = new JFrame("some tests");
            root.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            root_panel = new JPanel();
    
            some_views = new JPanel[] { 
                           new TableFactory(new String[]{"a", "b", "c"}),
                           new TableFactory(new String[]{"e", "e"}) };
            JTabbedPane tabs = new JTabbedPane();
            for (JPanel tab: some_views){
                String name = tab.getClass().getSimpleName();
                tabs.addTab(name, null, tab, name);
                //tab.setObjectAction(action, (JComponent) button); // can set internal 'decorative' View's action here, that are not interacting with Model
                // for example, add new (empty) row to JTable, as this does not modify Model (yet)
            }
            root_panel.add(tabs);
    
    
            this.model = model;
            this.model.addObserver(new ModelObserver());
    
            root.add(root_panel);
            root.pack();
            root.setLocationRelativeTo(null);
            root.setVisible(true);
        }
    }
    
    class ModelObserver implements Observer {
    
        @Override
        public void update(Observable o, Object arg) {
            System.out.print(arg.toString());
            System.out.print(o.toString());
        }
    }
    

    Controller.java

    package test.controllers;
    
    import test.models.Model;
    import test.views.View;
    
    
    import javax.swing.Action;
    import java.awt.event.*;
    import test.views.TableFactory;
    import javax.swing.*;
    
    public class Controller {
        private Model model;
        private View view;
        public Controller(Model model, View view){
            this.model = model;
            this.view = view;
    
            ((test.views.TableFactory)view.some_views[0]).setObjectAction(
                (Action) new ModelTouchingAction("move along, nothing here"),
                ((test.views.TableFactory)view.some_views[0]).buttons.get("FIRST") );
        }
    
        class ModelTouchingAction extends AbstractAction { 
            public ModelTouchingAction(String text) {
                super(text);
            }
            public void actionPerformed(ActionEvent e) {
                System.out.print("Invoked: " + e.toString());
            }
        }
    }
    

答案 2 :(得分:1)

工作版本,压缩到单个Something.java文件 使用javac Something.java && java Something

运行
import java.util.*;
import java.util.Observer;
import java.util.Observable;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.table.DefaultTableModel;

class Controller {
    private enum TYPE { RESET, ADD, DEL };
    private Model model;
    private View view;
    public Controller(Model model, View view){
        this.model = model;
        this.view = view;
        ((TableFactory) view.tf).setObjectAction(
            (Action) new ModelTouchingAction("reset*",TYPE.RESET),
            "BUTTON1" );
        ((TableFactory) view.tf).setObjectAction(
            (Action) new ModelTouchingAction("add*",TYPE.ADD),
            "BUTTON2" );
        ((TableFactory) view.tf).setObjectAction(
            (Action) new ModelTouchingAction("del*",TYPE.DEL),
            "BUTTON3" );
    }

    class ModelTouchingAction extends AbstractAction {
        private TYPE t;
        public ModelTouchingAction(String text, TYPE type) {
            super(text);
            this.t = type;
        }
        public void actionPerformed(ActionEvent e) {
            if(this.t == TYPE.ADD)
                model.add();
            else if(this.t == TYPE.DEL)
                model.del();
            else
                model.reset();
        }
    }
}

class Model extends Observable {
    private ArrayList<String[]> data;
    private static int cnt = 0;
    Model(){ reset(); }
    public void reset(){
        data = new ArrayList<String[]>();
        data.add(new String[]{"cell a1", "cell a2", "cell a3"});
        data.add(new String[]{"cell b1", "cell b2", "cell b3"});
        info();
    }
    public void add(){
        cnt++;
        data.add(new String[]{String.valueOf(cnt++), 
                 String.valueOf(cnt++), String.valueOf(cnt++)});
        info();
    }
    public void del(){
        if (data.size()>0){
            data.remove(data.size() - 1);
            info();
        }
    }
    private void info(){ setChanged();  notifyObservers(); }
    public ArrayList<String[]> get(){ return data; }
}

public class Something {

    Model m;
    View v;
    Controller c;
    Something() {
        initModel();
        initView();
        initController();
    }

    private void initModel() {
        m = new Model();
    }
    private void initView() {
        v = new View(m);
    }
    private void initController() {
        c = new Controller(m, v);
    }

    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                Something it = new Something();
            }
        });
    }
}

class View {

    private JFrame root;
    private Model model;
    public JPanel root_panel;
    public TableFactory tf;

    public View(Model model){
        root = new JFrame("some tests");
        root.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        root_panel = new JPanel();
        tf = new TableFactory(new String[]{"col1", "col2", "col3"});
        root_panel.add(tf);

        this.model = model;
        this.model.addObserver(new ModelObserver(tf));

        root.add(root_panel);
        root.pack();
        root.setLocationRelativeTo(null);
        root.setVisible(true);
    }
}

class ModelObserver implements Observer {
    TableFactory tf;
    ModelObserver(TableFactory tf){ this.tf = tf; }
    @Override
    public void update(Observable o, Object arg) {
        if (null != o)
            this.tf.populate(((Model) o).get());
            // view reloads ALL from model, optimize it
            // check what to check to get CMD from Observable
        else
            System.out.print("\nobservable is null");
        if (null != arg)
            System.out.print(arg.toString());
        else
            System.out.print("\narg is null. No idea if it should be.");
    }
}

class TableFactory extends JPanel {

    private String[] cols;
    public String[] buttonNames;
    private Map<String, JButton> buttons;
    private JTable table;

    TableFactory(String[] cols){

        this.cols = cols;
        buttonNames = new String[]{"BUTTON1", "BUTTON2", "BUTTON3"};
        commonInit();
    }
    TableFactory(String[] cols, String[] buttons){

        this.cols = cols;
        this.buttonNames = buttons;
        commonInit();
    }

    private void commonInit(){

        this.buttons = makeButtonMap(buttonNames);
        DefaultTableModel tabModel = new DefaultTableModel();
        this.table = new JTable(tabModel);
        for (String col: this.cols)
            tabModel.addColumn(col);

        setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));

        JPanel buttons_container = new JPanel(new GridLayout(1, 0));
        for (String name : buttonNames){
            buttons_container.add(buttons.get(name));
        }

        JScrollPane table_container = new JScrollPane(table);

        this.removeAll();
        this.add(buttons_container);
        this.add(table_container);
        this.repaint();
    }
    public void populate(ArrayList<String[]> data){
        ((DefaultTableModel) table.getModel()).setRowCount(0);
        for(String[] row:data) addRow(row); 
    }
    private void addRow(String[] row){
        ((DefaultTableModel) table.getModel()).addRow(row);
        // this is actually called only by populate, model does not have single 
        // row update here (and onUpdate ModelObserver cannot differentiate 
        // yet what method to call on Observable, TODO: check CMD? )
    }
    private void delRow(int rowID){
        System.out.print("\nJPanel should be deleting table row " + rowID);
    }
    public void setObjectAction(Action action, String buttonName){
        buttons.get(buttonName).setAction(action);
    }
    private Map<String, JButton> makeButtonMap(String[] cols){
        Map<String, JButton>  buttons = new HashMap<String, JButton>(cols.length);
        for (String name : cols){
            buttons.put(name, new JButton(name));
        }
        return buttons;
    }
}