我正在学习高级Java并尝试编写一个利用MVC设计模式的程序。程序需要绘制一个字符串,可以通过JTextField
中的用户输入进行修改。用户还可以分别通过JComboBox
和JSpinner
调整文本的颜色和字体大小。
这是我到目前为止所做的:
public class MVCDemo extends JApplet {
private JButton jBtnController = new JButton("Show Controller");
private JButton jBtnView = new JButton("Show View");
private TextModel model = new TextModel();
//constructor
public MVCDemo(){
//set layout and add buttons
setLayout(new FlowLayout());
add(jBtnController);
add(jBtnView);
jBtnController.addActionListener(new ActionListener(){
@Override
public void actionPerformed(ActionEvent e){
JFrame frame = new JFrame("Controllor");
TextController controller = new TextController();
controller.setModel(model);
frame.add(controller);
frame.setSize(200, 100);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
jBtnView.addActionListener(new ActionListener(){
@Override
public void actionPerformed(ActionEvent e){
JFrame frame = new JFrame("View");
TextView view = new TextView();
view.setModel(model);
frame.add(view);
frame.setSize(500, 200);
frame.setLocation(200, 200);
frame.setVisible(true);
}
});
}
public static void main(String[] args){
MVCDemo applet = new MVCDemo();
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setTitle("MVCDemo");
frame.getContentPane().add(applet, BorderLayout.CENTER);
frame.setSize(400, 90);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
public class TextModel {
private String text = "Your Student ID #";
//utility field used by event firing mechanism
private ArrayList<ActionListener> actionListenerList;
public void setText(String text){
this.text = text;
//notify the listener for the change on text
processEvent(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "text"));
}
public String getText(){
return text;
}
//register an action event listener
public synchronized void addActionListener(ActionListener l){
if (actionListenerList == null)
actionListenerList = new ArrayList<ActionListener>();
}
//remove an action event listener
public synchronized void removeActionListener(ActionListener l){
if (actionListenerList != null && actionListenerList.contains(l))
actionListenerList.remove(l);
}
//fire TickEvent
private void processEvent(ActionEvent e){
ArrayList<ActionListener> list;
synchronized (this){
if (actionListenerList == null)
return;
list = (ArrayList<ActionListener>)(actionListenerList.clone());
}
}
}
public class TextView extends JPanel{
private TextModel model;
//set a model
public void setModel(TextModel model){
this.model = model;
if (model != null)
//register the view as listener for the model
model.addActionListener(new ActionListener(){
@Override
public void actionPerformed(ActionEvent e){
repaint();
}
});
}
public TextModel getModel(){
return model;
}
@Override
public void paintComponent(Graphics g){
if (model != null){
super.paintComponent(g);
//g.setColor(model.getColor());
g.drawString(model.getText(), 190, 90);
}
}
}
public class TextController extends JPanel {
String[] colorStrings = { "Black", "Blue", "Red" };
private TextModel model;
private JTextField jtfText = new JTextField();
private JComboBox jcboColorList = new JComboBox(colorStrings);
//constructor
public TextController(){
//panel to group labels
JPanel panel1 = new JPanel();
panel1.setLayout(new GridLayout(3, 1));
panel1.add(new JLabel("Text"));
panel1.add(new JLabel("Color"));
panel1.add(new JLabel("Size"));
//panel to group text field, combo box and spinner
JPanel panel2 = new JPanel();
panel2.setLayout(new GridLayout(3, 1));
panel2.add(jtfText);
panel2.add(jcboColorList);
setLayout(new BorderLayout());
add(panel1, BorderLayout.WEST);
add(panel2, BorderLayout.CENTER);
//register listeners
jtfText.addActionListener(new ActionListener(){
@Override
public void actionPerformed(ActionEvent e){
if (model != null)
model.setText(jtfText.getText());
}
});
/*jcboColorList.addActionListener(new ActionListener(){
@Override
public void actionPerformed(ActionEvent e){
if (model != null)
model.set
}
});*/
}
public void setModel(TextModel model){
this.model = model;
}
public TextModel getModel(){
return model;
}
}
目前我只实施了JTextField
组件(尚未弄清楚如何正确执行JComboBox
和JSpinner
),即便这样也不完美。
当我第一次启动程序并打开视图和控制器面板时,默认字符串为&#34;您的学生ID#&#34;在视图中正确显示。但是当我在JTextField
中键入其他字符串并按Enter键时,TextView
中的输出字符串不会更新,除非我关闭视图面板并重新打开它。有人能指出造成这种行为的原因吗?
我怀疑它可能与我的程序中的事件处理部分有关。但我对GUI编程还很陌生,对触发和处理事件的方式有了非常基本的了解。如果有人能够以初学者友好的方式解释问题的根本原因,我将非常感激。
答案 0 :(得分:3)
您混合图层,模型和控制器都是非可视实体。我在手机中,所以我没有检查过任何深度的代码,但是,当值发生变化时,您的视图应该直接或直接通知控制器,控制器会相应更新模型通知控制器,进一步通知视图
在正式的MVC中,模型和视图不应该彼此了解,控制器用于将它们连接在一起。 Swing并没有遵循严格的MVC(它更像是一个MV-C),有时候试图将严格的MVC包裹起来可能会导致头痛无法结束。
相反,我所做的是将MVC包装在Swing周围,这意味着视图不需要公开其UI元素,而是依赖于控制器和视图之间的契约来确定每个派对可以做到
让我们从一个例子开始。
首先定义合同。这些应该是接口,因为它允许您以允许物理实现更改而不影响API的其他部分的方式解耦代码,可能类似于......
public interface TextModel {
public void setText(String text);
public String getText();
public void addChangeListener(ChangeListener listener);
public void removeChangeListener(ChangeListener listener);
}
public interface TextController {
public String getText();
public void setText(String text);
}
public interface TextView {
public TextController getController();
public void setController(TextController controller);
public void setText(String text);
}
现在,通常情况下,我考虑制作一些abstract
版本,以包含常用功能,但为了示例,我直接跳到了默认实现。
public class DefaultTextModel implements TextModel {
private String text;
private Set<ChangeListener> listeners;
public DefaultTextModel() {
listeners = new HashSet<>(25);
}
@Override
public String getText() {
return text;
}
@Override
public void setText(String value) {
if (text == null ? value != null : !text.equals(value)) {
this.text = value;
fireStateChanged();
}
}
@Override
public void addChangeListener(ChangeListener listener) {
listeners.add(listener);
}
@Override
public void removeChangeListener(ChangeListener listener) {
listeners.remove(listener);
}
protected void fireStateChanged() {
ChangeListener[] changeListeners = listeners.toArray(new ChangeListener[0]);
if (changeListeners != null && changeListeners.length > 0) {
ChangeEvent evt = new ChangeEvent(this);
for (ChangeListener listener : changeListeners) {
listener.stateChanged(evt);
}
}
}
}
public class DefaultTextController implements TextController {
private TextModel model;
private TextView view;
public DefaultTextController(TextModel model, TextView view) {
this.model = model;
this.view = view;
this.view.setController(this);
this.model.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
// You could simply make a "textWasChanged" method on the view
// and make the view ask the controller for the value, but where's
// the fun in that :P
getView().setText(getText());
}
});
}
public TextModel getModel() {
return model;
}
public TextView getView() {
return view;
}
@Override
public String getText() {
return getModel().getText();
}
@Override
public void setText(String text) {
getModel().setText(text);
}
}
现在,您应该问自己,这一切是如何运作的,您有输入和输出视图。现实情况是,它真的很好,但首先,我们需要两种不同的观点......
public class InputTextView extends JPanel implements TextView {
private TextController controller;
public InputTextView() {
setLayout(new GridBagLayout());
JTextField field = new JTextField(10);
add(field);
field.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
getController().setText(field.getText());
}
});
}
@Override
public TextController getController() {
return controller;
}
@Override
public void setController(TextController controller) {
this.controller = controller;
}
@Override
public void setText(String text) {
// We kind of don't care, because we're responsible for changing the
// text anyway :P
}
}
public class OutputTextView extends JPanel implements TextView {
private TextController controller;
public OutputTextView() {
}
@Override
public TextController getController() {
return controller;
}
@Override
public void setController(TextController controller) {
this.controller = controller;
}
@Override
public void setText(String text) {
revalidate();
repaint();
}
@Override
public Dimension getPreferredSize() {
Dimension size = new Dimension(200, 40);
TextController controller = getController();
if (controller != null) {
String text = controller.getText();
FontMetrics fm = getFontMetrics(getFont());
if (text == null || text.trim().isEmpty()) {
size.width = fm.stringWidth("M") * 10;
} else {
size.width = fm.stringWidth(text);
}
size.height = fm.getHeight();
}
return size;
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
TextController controller = getController();
String text = "";
if (controller != null) {
text = controller.getText();
}
if (text == null) {
text = "";
}
FontMetrics fm = g.getFontMetrics();
int x = (getWidth() - fm.stringWidth(text)) / 2;
int y = ((getHeight() - fm.getHeight()) / 2) + fm.getAscent();
g.drawString(text, x, y);
}
}
这些都是TextView
的实现,区别在于,一个视图(输入)仅设置文本并忽略对文本的更改,一个仅响应文本中的更改而从不设置它。 ..
大脑还没有应对?让我来证明......
InputTextView inputView = new InputTextView();
OutputTextView outputView = new OutputTextView();
TextModel model = new DefaultTextModel();
// Shared model!!
TextController inputController = new DefaultTextController(model, inputView);
TextController outputController = new DefaultTextController(model, outputView);
基本上,在这里,我们有两个视图,两个控制器和一个共享模型。当事物的输入端改变文本时,模型会通知输出端并更新它们
因为我知道复制单独的代码并将它们组合在一起是多么有趣......
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.HashSet;
import java.util.Set;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
InputTextView inputView = new InputTextView();
OutputTextView outputView = new OutputTextView();
TextModel model = new DefaultTextModel();
// Shared model!!
TextController inputController = new DefaultTextController(model, inputView);
TextController outputController = new DefaultTextController(model, outputView);
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new GridLayout(2, 0));
frame.add(inputView);
frame.add(outputView);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public interface TextModel {
public void setText(String text);
public String getText();
public void addChangeListener(ChangeListener listener);
public void removeChangeListener(ChangeListener listener);
}
public interface TextController {
public String getText();
public void setText(String text);
}
public interface TextView {
public TextController getController();
public void setController(TextController controller);
public void setText(String text);
}
public class DefaultTextModel implements TextModel {
private String text;
private Set<ChangeListener> listeners;
public DefaultTextModel() {
listeners = new HashSet<>(25);
}
@Override
public String getText() {
return text;
}
@Override
public void setText(String value) {
if (text == null ? value != null : !text.equals(value)) {
this.text = value;
fireStateChanged();
}
}
@Override
public void addChangeListener(ChangeListener listener) {
listeners.add(listener);
}
@Override
public void removeChangeListener(ChangeListener listener) {
listeners.remove(listener);
}
protected void fireStateChanged() {
ChangeListener[] changeListeners = listeners.toArray(new ChangeListener[0]);
if (changeListeners != null && changeListeners.length > 0) {
ChangeEvent evt = new ChangeEvent(this);
for (ChangeListener listener : changeListeners) {
listener.stateChanged(evt);
}
}
}
}
public class DefaultTextController implements TextController {
private TextModel model;
private TextView view;
public DefaultTextController(TextModel model, TextView view) {
this.model = model;
this.view = view;
this.view.setController(this);
this.model.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
// You could simply make a "textWasChanged" method on the view
// and make the view ask the controller for the value, but where's
// the fun in that :P
getView().setText(getText());
}
});
}
public TextModel getModel() {
return model;
}
public TextView getView() {
return view;
}
@Override
public String getText() {
return getModel().getText();
}
@Override
public void setText(String text) {
getModel().setText(text);
}
}
public class InputTextView extends JPanel implements TextView {
private TextController controller;
public InputTextView() {
setLayout(new GridBagLayout());
JTextField field = new JTextField(10);
add(field);
field.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
getController().setText(field.getText());
}
});
}
@Override
public TextController getController() {
return controller;
}
@Override
public void setController(TextController controller) {
this.controller = controller;
}
@Override
public void setText(String text) {
// We kind of don't care, because we're responsible for changing the
// text anyway :P
}
}
public class OutputTextView extends JPanel implements TextView {
private TextController controller;
public OutputTextView() {
}
@Override
public TextController getController() {
return controller;
}
@Override
public void setController(TextController controller) {
this.controller = controller;
}
@Override
public void setText(String text) {
revalidate();
repaint();
}
@Override
public Dimension getPreferredSize() {
Dimension size = new Dimension(200, 40);
TextController controller = getController();
if (controller != null) {
String text = controller.getText();
FontMetrics fm = getFontMetrics(getFont());
if (text == null || text.trim().isEmpty()) {
size.width = fm.stringWidth("M") * 10;
} else {
size.width = fm.stringWidth(text);
}
size.height = fm.getHeight();
}
return size;
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
TextController controller = getController();
String text = "";
if (controller != null) {
text = controller.getText();
}
if (text == null) {
text = "";
}
FontMetrics fm = g.getFontMetrics();
int x = (getWidth() - fm.stringWidth(text)) / 2;
int y = ((getHeight() - fm.getHeight()) / 2) + fm.getAscent();
g.drawString(text, x, y);
}
}
}