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

时间:2016-01-27 08:22:32

标签: java swing user-interface model-view-controller event-handling

我正在学习高级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<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组件(尚未弄清楚如何正确执行JComboBoxJSpinner),即便这样也不完美。

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

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

1 个答案:

答案 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);

基本上,在这里,我们有两个视图,两个控制器和一个共享模型。当事物的输入端改变文本时,模型会通知输出端并更新它们

因为我知道复制单独的代码并将它们组合在一起是多么有趣......

Text

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

    }
}