JTextField输入无法更新MVC中TextView的输出

我正在研究高级Java并试图编写一个利用MVC设计模式的程序。 程序需要绘制一个字符串,该字符串可以由用户在JTextField的输入进行修改。 用户还可以分别通过JComboBoxJSpinner调整文本的颜色和字体大小。

这是我到目前为止:

 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 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(); } //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 list; synchronized (this){ if (actionListenerList == null) return; list = (ArrayList)(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组件(尚未弄清楚如何正确地执行JComboBoxJSpinner ),即便这样也不完美。

当我第一次启动程序并打开视图和控制器面板时,视图中会正确显示默认字符串“Your Student ID#”。 但是当我在JTextField键入其他字符串并按Enter键时, TextView中的输出字符串不会更新,除非我关闭视图面板并重新打开它。 有人能指出造成这种行为的原因吗?

我怀疑它可能与我的程序中的事件处理部分有关。 但我仍然是GUI编程的新手,对触发和处理事件的方式有了非常基本的了解。 如果有人能够以初学者友好的方式解释问题的根本原因,我将非常感激。

混合您的图层,模型和控制器都是非可视实体。 我在我的手机中,所以我没有在任何深度检查你的代码,但是,当值改变时,你的视图应该通知控制器(直接或直接),控制器将相应地更新模型,这将通知控制器哪个将进一步通知该观点

在正式的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版本,以结束常用function,但为了示例,我直接跳到了默认实现……

 public class DefaultTextModel implements TextModel { private String text; private Set 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实现,区别在于,一个视图(输入)仅设置文本并忽略对文本的更改,一个仅响应文本中的更改而从不设置它…

大脑还没有应对? 让我来certificate……

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