簡體   English   中英

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

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

鑒於多個問題excellent_informative_for_me - trashgod的回答, 和其他幾個人沒有回答我的問題,
應該如何設計一個關於ActionListeners位置的類
(以及整體MVC分離 - 更多解釋如下)。

TOC

  1. 問題解釋
  2. 我的例子的文件結構樹(4)
  3. 編譯/清除源代碼命令(4)
  4. 來源

1.問題解釋

我讀過關於MVC的內容,我認為我理解了大部分內容,讓我們假設這是真的,為了這個問題。 不詳細說明:

  1. 視圖是根據Controller請求從Model生成的。
    在大多數實現中,View可以訪問Model實例。
  2. Controller與用戶交互,將更改傳播到Model和View。
  3. 模型是極端簡化的數據容器。
    它可以通過View觀察到。

現在,我的混淆涉及ActionListeners - 哪個類應該注冊 - 反過來也包含 - 按鈕代碼,或者實際上大多數View元素的代碼,實際上不僅僅是指標,而是模型操縱器?

假設我們在視圖中有兩個項目 - 更改模型數據的按鈕僅用於更改視圖外觀的一些視覺項目 讓代碼負責在View類中更改View外觀似乎是合理的。 我的問題與第一個案件有關。 我有幾個想法:

  • View創建按鈕,因此在View中創建ActionListeners並同時注冊回調是很自然的。 但這需要View具有與模型相關的代碼,從而打破封裝。 View應該只知道底層控制器或模型,只能通過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 {} \\;

來源

這段代碼還包含國際化存根,為此我提出了單獨的問題,這些行被明確標記,不應對答案產生任何影響。

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
 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; } } 

編輯(回應下面的評論)


這里有下一個信息來源

經過一番思考之后,我理解了奧利維爾的評論以及后來充滿鰻魚細節的氣墊船...... javax.swing.Action => setAction是我要走的路。 Controller訪問View的JPanels,獲取對包含按鈕或任何JComponent的映射的引用,並為其添加操作。 View不知道Controllers代碼中的內容。 當我使它工作得很好時,我會更新這個答案,所以任何在這里絆倒的人都可能擁有它。

只有兩件讓我擔心的事情(兩者都非常罕見,但仍然如此):

  1. 因為我將View的添加動作公開的方法,實際上我相信任何人只能從控制器或視圖添加它。 但如果該模型可以訪問視圖 - 它也可以覆蓋操作。
  2. 壓倒一切。 如果我設置一些對象的動作,然后忘記並從不同的地方設置另一個動作,它就會消失,這可能會使調試變得困難。

雖然模型 - 視圖 - 控制器模式不是靈丹妙葯 ,但它 Swing應用程序設計中的一種循環模式。 如前所述這里 ,擺動控制組件是視圖層次結構中的頻繁一部分:Swing應用程序的控制器可能不大,但這些組件連接到相關的偵聽器; Action實例,“可用於將功能和狀態與組件分開”,特別方便。 在此示例中Reset處理程序(一個簡單的ActionListener )嵌套在控制器中,但它也可以作為Action從模型中導出。 正如這里建議的那樣,您可能需要嘗試不同的設計。 這里引用幾種方法。

雖然@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更多,但恕我直言更好地描述了目的)

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

工作版本,壓縮到單個Something.java文件。
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