[英]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分离 - 更多解释如下)。
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: 不详细说明:
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: 我有几个想法:
.
└── test
├── controllers
│ └── Controller.java
├── models
│ └── Model.java
├── resources
│ └── a.properties
├── Something.java
└── views
├── TableFactory.java
└── View.java
Compile with: 编译:
Run with: 运行:
Clean with: 清洁:
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.javapackage 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): 只有两件让我担心的事情(两者都非常罕见,但仍然如此):
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? 什么必须改变?
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
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). 我发现那部分非常难看,而且我不确定这种方法是否合理,或者是否有更好的解决方案(将在稍后检查气垫船中充满鳗鱼豆)。
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更多,但恕我直言更好地描述了目的)
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);
}
}
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());
}
}
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.