JTextField 输入无法更新 MVC 中 TextView 中的输出
JTextField input fails to update output in TextView in MVC
我正在学习高级 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
),即使这样也不是完美的。
当我第一次启动程序并同时打开视图和控制器面板时,默认字符串 "Your Student ID #" 在视图中正确显示。但是,当我在 JTextField
中键入其他字符串并按下回车键时,TextView
中的输出字符串不会更新,除非我关闭视图面板并重新打开它。有人可以指出是什么导致了这种行为吗?
我怀疑这可能与我程序的事件处理部分有关。但我对 GUI 编程还是很陌生,对如何触发和处理事件有非常基本的了解。因此,如果有人能以对初学者友好的方式解释问题的根本原因,我将不胜感激。
你混合你的图层,模型和控制器都是非可视实体。我在我的 phone 中,所以我没有深入检查你的代码,但是,当值发生变化时,你的视图应该通知控制器(直接或直接),控制器将相应地更新模型,这将通知将进一步通知视图的控制器
在正式的 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);
}
}
}
我正在学习高级 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
),即使这样也不是完美的。
当我第一次启动程序并同时打开视图和控制器面板时,默认字符串 "Your Student ID #" 在视图中正确显示。但是,当我在 JTextField
中键入其他字符串并按下回车键时,TextView
中的输出字符串不会更新,除非我关闭视图面板并重新打开它。有人可以指出是什么导致了这种行为吗?
我怀疑这可能与我程序的事件处理部分有关。但我对 GUI 编程还是很陌生,对如何触发和处理事件有非常基本的了解。因此,如果有人能以对初学者友好的方式解释问题的根本原因,我将不胜感激。
你混合你的图层,模型和控制器都是非可视实体。我在我的 phone 中,所以我没有深入检查你的代码,但是,当值发生变化时,你的视图应该通知控制器(直接或直接),控制器将相应地更新模型,这将通知将进一步通知视图的控制器
在正式的 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);
}
}
}