简体   繁体   English

java swing mvc架构 - 用MCVE示例Q.

[英]java swing mvc architecture - Q with MCVE example

Given multiple questions excellent_informative_for_me - trashgod 's answer, that , and that and several others that do not answer my question, 鉴于多个问题excellent_informative_for_me - trashgod的回答, 和其他几个人没有回答我的问题,
how should one design classes in regard to ActionListeners location 应该如何设计一个关于ActionListeners位置的类
(and overall MVC separation - more explained below). (以及整体MVC分离 - 更多解释如下)。

TOC TOC

  1. Question explained 问题解释
  2. Tree of files structure of my example (4) 我的例子的文件结构树(4)
  3. Compile/clean commands for sources (4) 编译/清除源代码命令(4)
  4. Sources 来源

1. question explained 1.问题解释

I've read about MVC, and I presume I understood most of it, let us assume that is true, for the sake of this question. 我读过关于MVC的内容,我认为我理解了大部分内容,让我们假设这是真的,为了这个问题。 Not going into details: 不详细说明:

  1. View is produced from Model, on Controller request. 视图是根据Controller请求从Model生成的。
    In most implementations View has access to Model instance. 在大多数实现中,View可以访问Model实例。
  2. Controller interacts with user, propagates changes to Model and View. Controller与用户交互,将更改传播到Model和View。
  3. Model is, in extreme simplification, container for data. 模型是极端简化的数据容器。
    It can be observed by View. 它可以通过View观察到。

Now, my confusion concerns ActionListeners - which class should register - and in turn also contain - code for buttons, or in fact code for most View elements, that aren't actually just indicators, but Model manipulators? 现在,我的混淆涉及ActionListeners - 哪个类应该注册 - 反过来也包含 - 按钮代码,或者实际上大多数View元素的代码,实际上不仅仅是指标,而是模型操纵器?

Let's say, that we have two items in View - button to change Model data and some visual item used ONLY to change View appearance . 假设我们在视图中有两个项目 - 更改模型数据的按钮仅用于更改视图外观的一些视觉项目 It seems reasonable to leave code responsible for changing View appearance in View class. 让代码负责在View类中更改View外观似乎是合理的。 My question relates to first case. 我的问题与第一个案件有关。 I had several ideas: 我有几个想法:

  • View creates buttons, so it's kind of natural to create ActionListeners, and register callbacks at the same time, in View. View创建按钮,因此在View中创建ActionListeners并同时注册回调是很自然的。 But this requires that View had code related to model, breaking encapsulation. 但这需要View具有与模型相关的代码,从而打破封装。 View was supposed to know only little about underlying Controller or Model, talking to it via Observer only. View应该只知道底层控制器或模型,只能通过Observer与它交谈。
  • I could expose View items like buttons, etc and attach ActionListeners to them from Controller, but this again breaks encapsulation. 我可以公开View项目,比如按钮等,并从Controller中将ActionListeners附加到它们, 但这又打破了封装。
  • I could implement somewhat of a callback, for each button - View would ask controller if it has any code that is supposed to be registered as ActionListener for given button name, but this seems overly complicated, and would require synchronization of names between controller and view. 我可以为每个按钮实现一些回调 - View会询问控制器是否有任何代码应该注册为给定按钮名称的ActionListener, 但这看起来过于复杂,并且需要在控制器和视图之间同步名称。
  • I could assume, being sane ;), that buttons in TableFactory might be made public, allow injecting ActionListeners to any code. 我可以假设,理智;),TableFactory中的按钮可能被公开,允许将ActionListeners注入任何代码。
  • Controller could replace whole View items (creating button and replacing existing one) but this seems insane, as its not it's role 控制器可以替换整个View项目(创建按钮并替换现有项目) 但这看起来很疯狂,因为它不是它的角色

2. tree of files structure of my example (4) 2.我的例子的文件结构树(4)

.
└── test
    ├── controllers
    │   └── Controller.java
    ├── models
    │   └── Model.java
    ├── resources
    │   └── a.properties
    ├── Something.java
    └── views
        ├── TableFactory.java
        └── View.java

3. compile/clean commands for sources (4) 3.编译/清除源代码命令(4)

Compile with: 编译:

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

Run with: 运行:

  • java test.Something java test.Something

Clean with: 清洁:

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

4. sources 来源

This code contains also internationalization stub, for which I asked separate question, those lines are clearly marked and should not have any impact on answer. 这段代码还包含国际化存根,为此我提出了单独的问题,这些行被明确标记,不应对答案产生任何影响。

Controllers => Controller.java Controllers => Controller.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();
            }
        });
    }
}
Models => Model.java Models => Model.java
 package test.models; import java.util.Observable; public class Model extends Observable { } 
Something.java 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 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 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; } } 

EDIT (in response to comments below) 编辑(回应下面的评论)


Next informative sources here 这里有下一个信息来源

After some more thought I understood Olivier's comment and later Hovercraft full of eels's details... javax.swing.Action => setAction was my way to go. 经过一番思考之后,我理解了奥利维尔的评论以及后来充满鳗鱼细节的气垫船...... javax.swing.Action => setAction是我要走的路。 Controller accesses View's JPanels, get's reference to map containing buttons or any JComponent, and adds action to it. Controller访问View的JPanels,获取对包含按钮或任何JComponent的映射的引用,并为其添加操作。 View has no clue what is in Controllers code. View不知道Controllers代码中的内容。 I'll update this answer when I make it work nicely, so anyone that stumbles here might have it. 当我使它工作得很好时,我会更新这个答案,所以任何在这里绊倒的人都可能拥有它。

Only two things that worries me are (both pretty rare, but still): 只有两件让我担心的事情(两者都非常罕见,但仍然如此):

  1. since I make View's method of adding action public, actually i trust anyone to add it only from controller or view. 因为我将View的添加动作公开的方法,实际上我相信任何人只能从控制器或视图添加它。 But had the model had access to view - it could override actions too. 但如果该模型可以访问视图 - 它也可以覆盖操作。
  2. overriding, too. 压倒一切。 If I set some object's action, and then forget and set another action from different place, it's just gone, which might make debugging hard. 如果我设置一些对象的动作,然后忘记并从不同的地方设置另一个动作,它就会消失,这可能会使调试变得困难。

While the Model–View–Controller pattern is no panacea , it is a recurrent pattern in Swing application design. 虽然模型 - 视图 - 控制器模式不是灵丹妙药 ,但它 Swing应用程序设计中的一种循环模式。 As noted here , Swing control components are frequently part of the view containment hierarchy: A Swing application's controller may have little to do but connect such components to a relevant listener; 如前所述这里 ,摆动控制组件是视图层次结构中的频繁一部分:Swing应用程序的控制器可能不大,但这些组件连接到相关的侦听器; instances of Action , which "can be used to separate functionality and state from a component," are particularly convenient. Action实例,“可用于将功能和状态与组件分开”,特别方便。 In this example , the Reset handler, a simple ActionListener , is nested in the controller, but it could just as well be exported from the model as an Action . 在此示例中Reset处理程序(一个简单的ActionListener )嵌套在控制器中,但它也可以作为Action从模型中导出。 As suggested here , you may have to experiment with different designs. 正如这里建议的那样,您可能需要尝试不同的设计。 Several approaches are cited here . 这里引用几种方法。

While @trashgod answer summarizes and extends to some degree discussion from comments, I'm posting, ugly (but working) solution based on that discussion (implementing Action in Controller class) 虽然@trashgod的答案总结并扩展到评论的某种程度的讨论,我发布,基于该讨论的丑陋(但工作)解决方案(在Controller类中实现Action)

What had to change? 什么必须改变?

  1. TableFactory - added public void setObjectAction(Action a, JButton b) and allowed public access to Map<String, JButton> buttons TableFactory - 添加了public void setObjectAction(Action a, JButton b)并允许公共访问Map<String, JButton> buttons

  2. Controller class needed implementation of AbstractAction or class that inherits from it, and code that sets object of this class to View's deeply nested JComponent. Controller类需要实现AbstractAction或从中继承的类,以及将此类的对象设置为View的深层嵌套JComponent的代码。 I find that part quite ugly, and I'm not sure if this approach is sane, or is there any better solution (will check Hovercraft full of eels Beans sometime later). 我发现那部分非常难看,而且我不确定这种方法是否合理,或者是否有更好的解决方案(将在稍后检查气垫船中充满鳗鱼豆)。

  3. No additional changes were needed yet I modified View.java a little to show purpose of this question slightly better, or in different light (this makes View.java more than MCVE but IMHO describes purpose better) 我不需要进行任何其他更改但我稍微修改了View.java以稍微更好地显示此问题的目的,或者以不同的方式显示(这使得View.java比MCVE更多,但恕我直言更好地描述了目的)

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

Working version, compacted to single Something.java file. 工作版本,压缩到单个Something.java文件。
Run with javac Something.java && java Something javac Something.java && java Something运行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;
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM