Java和GUI - ActionListeners根据MVC模式属于哪里?

时间:2014-10-22 22:00:08

标签: java swing user-interface model-view-controller awt

我目前正在编写一个模板Java应用程序,不知何故,如果我想干净地遵循MVC模式,我不确定ActionListeners所属的位置。

示例是基于Swing的,但它不是关于框架,而是关于Java中MVC的基本概念,使用任何框架来创建GUI。

我开始使用一个包含JFrame和JButton的绝对简单的应用程序(用于处理框架因此关闭应用程序)。该帖子的代码落后。没有什么特别的,只是为了澄清我们在说什么。我没有从模型开始,因为这个问题太烦人了。

已经有不止一个类似的问题,如下:
MVC pattern with many ActionListeners
Java swing - Where should the ActionListener go?

但是他们之所以非常满意,因为我想知道两件事:

  • 将所有ActionListener放在一个单独的包中是否合理?
    • 为了View和Controller的可读性,我想这样做,尤其是如果有很多听众
  • 如果侦听器不是Controller内的子类,如何从ActionListener中执行Controller函数? (后续问题)

我希望这不是太笼统或模糊,我在这里问,但这让我想了一会儿。我总是使用我自己的方式,让ActionHandler了解Controller ,但这似乎不对,所以我最终想知道这是如何正确完成的。

亲切的问候,
杰森


控制器:

package controller;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import view.MainView;

public class MainController
{
    MainView mainView = new MainView();

    public MainController()
    {
        this.initViewActionListeners();
    }

    private void initViewActionListeners()
    {
        mainView.initButtons(new CloseListener());
    }

    public class CloseListener implements ActionListener
    {
        @Override
        public void actionPerformed(ActionEvent e)
        {
            mainView.dispose();
        }
    }
}


视图:

package view;

import java.awt.Dimension;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class MainView extends JFrame
{
    JButton button_close    = new JButton();
    JPanel  panel_mainPanel = new JPanel();

    private static final long   serialVersionUID    = 5791734712409634055L;

    public MainView()
    {
        setDefaultCloseOperation(DISPOSE_ON_CLOSE);
        this.setSize(500, 500);
        this.add(panel_mainPanel);
        setVisible(true);
    }

    public void initButtons(ActionListener actionListener)
    {
        this.button_close = new JButton("Close");
        this.button_close.setSize(new Dimension(100, 20));
        this.button_close.addActionListener(actionListener);
        this.panel_mainPanel.add(button_close);
    }
}

4 个答案:

答案 0 :(得分:16)

这对于Swing来说是一个非常难以回答的问题,因为Swing不是纯粹的MVC实现,视图和控制器是混合的。

从技术上讲,模型和控制器应该能够交互,控制器和视图应该能够交互,但视图和模型永远不应该交互,这显然不是Swing的工作方式,而是“#”。另一场辩论......

另一个问题是,你真的不想向任何人公开UI组件,控制器不应该关心某些动作是如何发生的,只是他们可以。

这表明视图应维护附加到UI控件的ActionListener。然后,视图应警告控制器已发生某种操作。为此,您可以使用由视图管理的另一个ActionListener,控制器订阅该文件。

更好的是,我会有一个专用的视图监听器,它描述了这个视图可能产生的动作,例如......

public interface MainViewListener {
    public void didPerformClose(MainView mainView);
}

然后控制器将通过此侦听器订阅视图,并且当(在这种情况下)按下关闭按钮时,视图将调用didPerformClose

即使在这个例子中,我也很想制作一个"主视图"接口,描述了任何实现保证提供的属性(setter和getters)和动作(监听器/回调),然后你不关心这些动作是如何发生的,只有当它们发生时,你才应该这样做东西...

在您想要问自己的每个级别,更改另一个实例的任何元素(更改模型或控制器或视图)有多容易?如果您发现自己必须解耦代码,那么您就遇到了问题。通过接口进行通信,并尝试减少各层之间的耦合量以及每个层对其他层知道的数量,直到他们只是维护合同

<强>更新...

让我们以此为例......

Login

实际上有两个视图(折扣实际对话框),有凭据视图和登录视图,是的,它们是不同的,如您所见。

CredentialsView

凭据视图负责收集要进行身份验证的详细信息,用户名和密码。它将向控制器提供信息,让它知道何时更改了这些凭证,因为控制器可能想要采取某些行动,例如启用&#34;登录&#34;按钮...

该视图还希望知道何时将要进行身份验证,因为它将要禁用它的字段,因此用户无法在身份验证发生时更新视图,同样,它需要知道身份验证何时失败或成功,因为它需要对这些可能性采取行动。

public interface CredentialsView {

    public String getUserName();
    public char[] getPassword();

    public void willAuthenticate();
    public void authenticationFailed();
    public void authenticationSucceeded();

    public void setCredentialsViewController(CredentialsViewController listener);

}

public interface CredentialsViewController {

    public void credientialsDidChange(CredentialsView view);

}

CredentialsPane

CredentialsPaneCredentialsView的物理实现,它实现合同,但管理它自己的内部状态。如何管理合同对控制人来说是无关紧要的,它只关心合同的维护......

public class CredentialsPane extends JPanel implements CredentialsView {

    private CredentialsViewController controller;

    private JTextField userNameField;
    private JPasswordField passwordField;

    public CredentialsPane(CredentialsViewController controller) {
        setCredentialsViewController(controller);
        setLayout(new GridBagLayout());
        userNameField = new JTextField(20);
        passwordField = new JPasswordField(20);

        GridBagConstraints gbc = new GridBagConstraints();
        gbc.gridx = 0;
        gbc.gridy = 0;
        gbc.insets = new Insets(2, 2, 2, 2);
        gbc.anchor = GridBagConstraints.EAST;
        add(new JLabel("Username: "), gbc);

        gbc.gridy++;
        add(new JLabel("Password: "), gbc);

        gbc.gridx = 1;
        gbc.gridy = 0;
        gbc.anchor = GridBagConstraints.WEST;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        add(userNameField, gbc);
        gbc.gridy++;
        add(passwordField, gbc);

        DocumentListener listener = new DocumentListener() {
            @Override
            public void insertUpdate(DocumentEvent e) {
                getCredentialsViewController().credientialsDidChange(CredentialsPane.this);
            }

            @Override
            public void removeUpdate(DocumentEvent e) {
                getCredentialsViewController().credientialsDidChange(CredentialsPane.this);
            }

            @Override
            public void changedUpdate(DocumentEvent e) {
                getCredentialsViewController().credientialsDidChange(CredentialsPane.this);
            }
        };

        userNameField.getDocument().addDocumentListener(listener);
        passwordField.getDocument().addDocumentListener(listener);

    }

    @Override
    public CredentialsViewController getCredentialsViewController() {
        return controller;
    }

    @Override
    public String getUserName() {
        return userNameField.getText();
    }

    @Override
    public char[] getPassword() {
        return passwordField.getPassword();
    }

    @Override
    public void willAuthenticate() {
        userNameField.setEnabled(false);
        passwordField.setEnabled(false);
    }

    @Override
    public void authenticationFailed() {
        userNameField.setEnabled(true);
        passwordField.setEnabled(true);

        userNameField.requestFocusInWindow();
        userNameField.selectAll();

        JOptionPane.showMessageDialog(this, "Authentication has failed", "Error", JOptionPane.ERROR_MESSAGE);
    }

    @Override
    public void authenticationSucceeded() {
        // Really don't care, but you might want to stop animation, for example...
    }

    public void setCredentialsViewController(CredentialsViewController controller){
        this.controller = controller;
    }

}

LoginView

LoginView负责管理CredentialsView,但也用于通知LoginViewController何时进行身份验证,或者用户是否通过某种方式取消了流程。

同样,LoginViewController将告知视图何时进行身份验证以及身份验证失败或成功。

public interface LoginView {

    public CredentialsView getCredentialsView();

    public void willAuthenticate();
    public void authenticationFailed();
    public void authenticationSucceeded();

    public void dismissView();

    public LoginViewController getLoginViewController();

}

public interface LoginViewController {

    public void authenticationWasRequested(LoginView view);
    public void loginWasCancelled(LoginView view);

}

LoginPane

LoginPane有点特殊,它充当LoginViewController的视图,但它也充当CredentialsView的控制器。这很重要,因为没有什么可以说视图不能成为一个控制器,但我会小心你如何实现这些事情,因为这样做可能并不总是有意义,但因为这两个在这种情况下,视图正在共同收集信息和管理事件。

因为LoginPane需要根据CredentialsView中的更改来更改自己的状态,所以允许LoginPane充当控制器是有意义的在这种情况下,否则,您需要提供更多控制按钮状态的方法,但这会开始将UI逻辑渗透到控制器......

public static class LoginPane extends JPanel implements LoginView, CredentialsViewController {

    private LoginViewController controller;
    private CredentialsPane credientialsView;

    private JButton btnAuthenticate;
    private JButton btnCancel;

    private boolean wasAuthenticated;

    public LoginPane(LoginViewController controller) {
        setLoginViewController(controller);
        setLayout(new BorderLayout());
        setBorder(new EmptyBorder(8, 8, 8, 8));

        btnAuthenticate = new JButton("Login");
        btnCancel = new JButton("Cancel");

        JPanel buttons = new JPanel();
        buttons.add(btnAuthenticate);
        buttons.add(btnCancel);

        add(buttons, BorderLayout.SOUTH);

        credientialsView = new CredentialsPane(this);
        add(credientialsView);

        btnAuthenticate.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                getLoginViewController().authenticationWasRequested(LoginPane.this);
            }
        });
        btnCancel.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                getLoginViewController().loginWasCancelled(LoginPane.this);
                // I did think about calling dispose here,
                // but's not really the the job of the cancel button to decide what should happen here...
            }
        });

        validateCreientials();

    }

    public static boolean showLoginDialog(LoginViewController controller) {

        final LoginPane pane = new LoginPane(controller);

        JDialog dialog = new JDialog();
        dialog.setTitle("Login");
        dialog.setModal(true);
        dialog.add(pane);
        dialog.pack();
        dialog.setLocationRelativeTo(null);
        dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
        dialog.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                pane.getLoginViewController().loginWasCancelled(pane);
            }
        });
        dialog.setVisible(true);

        return pane.wasAuthenticated();

    }

    public boolean wasAuthenticated() {
        return wasAuthenticated;
    }

    public void validateCreientials() {

        CredentialsView view = getCredentialsView();
        String userName = view.getUserName();
        char[] password = view.getPassword();
        if ((userName != null && userName.trim().length() > 0) && (password != null && password.length > 0)) {

            btnAuthenticate.setEnabled(true);

        } else {

            btnAuthenticate.setEnabled(false);

        }

    }

    @Override
    public void dismissView() {
        SwingUtilities.windowForComponent(this).dispose();
    }

    @Override
    public CredentialsView getCredentialsView() {
        return credientialsView;
    }

    @Override
    public void willAuthenticate() {
        getCredentialsView().willAuthenticate();
        btnAuthenticate.setEnabled(false);
    }

    @Override
    public void authenticationFailed() {
        getCredentialsView().authenticationFailed();
        validateCreientials();
        wasAuthenticated = false;
    }

    @Override
    public void authenticationSucceeded() {
        getCredentialsView().authenticationSucceeded();
        validateCreientials();
        wasAuthenticated = true;
    }

    public LoginViewController getLoginViewController() {
        return controller;
    }

    public void setLoginViewController(LoginViewController controller) {
        this.controller = controller;
    }

    @Override
    public void credientialsDidChange(CredentialsView view) {
        validateCreientials();
    }

}

工作示例

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.EmptyBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import sun.net.www.protocol.http.HttpURLConnection;

public class Test {

    protected static final Random AUTHENTICATION_ORACLE = new Random();

    public static void main(String[] args) {
        new Test();
    }

    public interface CredentialsView {
        public String getUserName();
        public char[] getPassword();
        public void willAuthenticate();
        public void authenticationFailed();
        public void authenticationSucceeded();
        public CredentialsViewController getCredentialsViewController();
    }

    public interface CredentialsViewController {
        public void credientialsDidChange(CredentialsView view);
    }

    public interface LoginView {
        public CredentialsView getCredentialsView();
        public void willAuthenticate();
        public void authenticationFailed();
        public void authenticationSucceeded();
        public void dismissView();
        public LoginViewController getLoginViewController();
    }

    public interface LoginViewController {
        public void authenticationWasRequested(LoginView view);
        public void loginWasCancelled(LoginView view);
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                LoginViewController controller = new LoginViewController() {

                    @Override
                    public void authenticationWasRequested(LoginView view) {
                        view.willAuthenticate();
                        LoginAuthenticator authenticator = new LoginAuthenticator(view);
                        authenticator.authenticate();
                    }

                    @Override
                    public void loginWasCancelled(LoginView view) {

                        view.dismissView();

                    }
                };

                if (LoginPane.showLoginDialog(controller)) {

                    System.out.println("You shell pass");

                } else {

                    System.out.println("You shell not pass");

                }

                System.exit(0);

            }
        });
    }

    public class LoginAuthenticator {

        private LoginView view;

        public LoginAuthenticator(LoginView view) {
            this.view = view;
        }

        public void authenticate() {

            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException ex) {
                        Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
                    }
                    SwingUtilities.invokeLater(new Runnable() {
                        @Override
                        public void run() {
                            if (AUTHENTICATION_ORACLE.nextBoolean()) {
                                view.authenticationSucceeded();
                                view.dismissView();
                            } else {
                                view.authenticationFailed();
                            }
                        }
                    });
                }
            });
            t.start();

        }

    }

    public static class LoginPane extends JPanel implements LoginView, CredentialsViewController {

        private LoginViewController controller;
        private CredentialsPane credientialsView;

        private JButton btnAuthenticate;
        private JButton btnCancel;

        private boolean wasAuthenticated;

        public LoginPane(LoginViewController controller) {
            setLoginViewController(controller);
            setLayout(new BorderLayout());
            setBorder(new EmptyBorder(8, 8, 8, 8));

            btnAuthenticate = new JButton("Login");
            btnCancel = new JButton("Cancel");

            JPanel buttons = new JPanel();
            buttons.add(btnAuthenticate);
            buttons.add(btnCancel);

            add(buttons, BorderLayout.SOUTH);

            credientialsView = new CredentialsPane(this);
            add(credientialsView);

            btnAuthenticate.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    getLoginViewController().authenticationWasRequested(LoginPane.this);
                }
            });
            btnCancel.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    getLoginViewController().loginWasCancelled(LoginPane.this);
                    // I did think about calling dispose here,
                    // but's not really the the job of the cancel button to decide what should happen here...
                }
            });

            validateCreientials();

        }

        public static boolean showLoginDialog(LoginViewController controller) {

            final LoginPane pane = new LoginPane(controller);

            JDialog dialog = new JDialog();
            dialog.setTitle("Login");
            dialog.setModal(true);
            dialog.add(pane);
            dialog.pack();
            dialog.setLocationRelativeTo(null);
            dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
            dialog.addWindowListener(new WindowAdapter() {
                @Override
                public void windowClosing(WindowEvent e) {
                    pane.getLoginViewController().loginWasCancelled(pane);
                }
            });
            dialog.setVisible(true);

            return pane.wasAuthenticated();

        }

        public boolean wasAuthenticated() {
            return wasAuthenticated;
        }

        public void validateCreientials() {

            CredentialsView view = getCredentialsView();
            String userName = view.getUserName();
            char[] password = view.getPassword();
            if ((userName != null && userName.trim().length() > 0) && (password != null && password.length > 0)) {

                btnAuthenticate.setEnabled(true);

            } else {

                btnAuthenticate.setEnabled(false);

            }

        }

        @Override
        public void dismissView() {
            SwingUtilities.windowForComponent(this).dispose();
        }

        @Override
        public CredentialsView getCredentialsView() {
            return credientialsView;
        }

        @Override
        public void willAuthenticate() {
            getCredentialsView().willAuthenticate();
            btnAuthenticate.setEnabled(false);
        }

        @Override
        public void authenticationFailed() {
            getCredentialsView().authenticationFailed();
            validateCreientials();
            wasAuthenticated = false;
        }

        @Override
        public void authenticationSucceeded() {
            getCredentialsView().authenticationSucceeded();
            validateCreientials();
            wasAuthenticated = true;
        }

        public LoginViewController getLoginViewController() {
            return controller;
        }

        public void setLoginViewController(LoginViewController controller) {
            this.controller = controller;
        }

        @Override
        public void credientialsDidChange(CredentialsView view) {
            validateCreientials();
        }

    }

    public static class CredentialsPane extends JPanel implements CredentialsView {

        private CredentialsViewController controller;

        private JTextField userNameField;
        private JPasswordField passwordField;

        public CredentialsPane(CredentialsViewController controller) {
            setCredentialsViewController(controller);
            setLayout(new GridBagLayout());
            userNameField = new JTextField(20);
            passwordField = new JPasswordField(20);

            GridBagConstraints gbc = new GridBagConstraints();
            gbc.gridx = 0;
            gbc.gridy = 0;
            gbc.insets = new Insets(2, 2, 2, 2);
            gbc.anchor = GridBagConstraints.EAST;
            add(new JLabel("Username: "), gbc);

            gbc.gridy++;
            add(new JLabel("Password: "), gbc);

            gbc.gridx = 1;
            gbc.gridy = 0;
            gbc.anchor = GridBagConstraints.WEST;
            gbc.fill = GridBagConstraints.HORIZONTAL;
            add(userNameField, gbc);
            gbc.gridy++;
            add(passwordField, gbc);

            DocumentListener listener = new DocumentListener() {
                @Override
                public void insertUpdate(DocumentEvent e) {
                    getCredentialsViewController().credientialsDidChange(CredentialsPane.this);
                }

                @Override
                public void removeUpdate(DocumentEvent e) {
                    getCredentialsViewController().credientialsDidChange(CredentialsPane.this);
                }

                @Override
                public void changedUpdate(DocumentEvent e) {
                    getCredentialsViewController().credientialsDidChange(CredentialsPane.this);
                }
            };

            userNameField.getDocument().addDocumentListener(listener);
            passwordField.getDocument().addDocumentListener(listener);

        }

        @Override
        public CredentialsViewController getCredentialsViewController() {
            return controller;
        }

        @Override
        public String getUserName() {
            return userNameField.getText();
        }

        @Override
        public char[] getPassword() {
            return passwordField.getPassword();
        }

        @Override
        public void willAuthenticate() {
            userNameField.setEnabled(false);
            passwordField.setEnabled(false);
        }

        @Override
        public void authenticationFailed() {
            userNameField.setEnabled(true);
            passwordField.setEnabled(true);

            userNameField.requestFocusInWindow();
            userNameField.selectAll();

            JOptionPane.showMessageDialog(this, "Authentication has failed", "Error", JOptionPane.ERROR_MESSAGE);
        }

        @Override
        public void authenticationSucceeded() {
            // Really don't care, but you might want to stop animation, for example...
        }

        public void setCredentialsViewController(CredentialsViewController controller) {
            this.controller = controller;
        }

    }

}

答案 1 :(得分:4)

它们与控件相关联,但它们不必是控件的直接部分。例如,请看下面发布的代码,我正在准备另一个问题,一个关于匿名内部类和耦合,这里我给所有按钮匿名内部动作(当然是ActionListeners),然后使用Actions来改变GUI状态。将向GUI(控件)的任何监听器通知此更改,然后可以采取相应的行动。

import java.awt.*;
import java.awt.event.*; java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import javax.swing.*;
import javax.swing.event.SwingPropertyChangeSupport;

public class AnonymousInnerEg2 {
   private static void createAndShowUI() {
      GuiModel2 model = new GuiModel2();
      GuiPanel2 guiPanel = new GuiPanel2();
      GuiControl2 guiControl = new GuiControl2();
      guiControl.setGuiPanel(guiPanel);
      guiControl.setGuiModel(model);
      try {
         guiControl.init();
      } catch (GuiException2 e) {
         e.printStackTrace();
         System.exit(-1);
      }

      JFrame frame = new JFrame("AnonymousInnerEg");
      frame.getContentPane().add(guiPanel);
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.pack();
      frame.setLocationRelativeTo(null);
      frame.setVisible(true);
   }

   public static void main(String[] args) {
      java.awt.EventQueue.invokeLater(new Runnable() {
         public void run() {
            createAndShowUI();
         }
      });
   }
}

enum GuiState {
   BASE("Base"), START("Start"), END("End");
   private String name;

   private GuiState(String name) {
      this.name = name;
   }

   public String getName() {
      return name;
   }

}

class GuiModel2 {
   public static final String STATE = "state";
   private SwingPropertyChangeSupport support = new SwingPropertyChangeSupport(this);
   private GuiState state = GuiState.BASE;

   public GuiState getState() {
      return state;
   }

   public void setState(GuiState state) {
      GuiState oldValue = this.state;
      GuiState newValue = state;
      this.state = state;
      support.firePropertyChange(STATE, oldValue, newValue);
   }

   public void addPropertyChangeListener(PropertyChangeListener l) {
      support.addPropertyChangeListener(l);
   }

   public void removePropertyChangeListener(PropertyChangeListener l) {
      support.removePropertyChangeListener(l);
   }
}

@SuppressWarnings("serial")
class GuiPanel2 extends JPanel {
   public static final String STATE = "state";
   private String state = GuiState.BASE.getName();
   private JLabel stateField = new JLabel("", SwingConstants.CENTER);

   public GuiPanel2() {

      JPanel btnPanel = new JPanel(new GridLayout(1, 0, 5, 0));
      for (final GuiState guiState : GuiState.values()) {
         btnPanel.add(new JButton(new AbstractAction(guiState.getName()) {
            {
               int mnemonic = (int) getValue(NAME).toString().charAt(0);
               putValue(MNEMONIC_KEY, mnemonic);
            }

            @Override
            public void actionPerformed(ActionEvent e) {
               String name = getValue(NAME).toString();
               setState(name);
            }
         }));
      }

      setLayout(new BorderLayout());
      add(stateField, BorderLayout.PAGE_START);
      add(btnPanel, BorderLayout.CENTER);
   }

   public String getState() {
      return state;
   }

   public void setState(String state) {
      String oldValue = this.state;
      String newValue = state;
      this.state = state;
      firePropertyChange(STATE, oldValue, newValue);
   }

   public void setStateField(String name) {
      stateField.setText(name);
   }

}

class GuiControl2 {
   private GuiPanel2 guiPanel;
   private GuiModel2 model;
   private boolean allOK = false;

   public void setGuiPanel(GuiPanel2 guiPanel) {
      this.guiPanel = guiPanel;
      guiPanel.addPropertyChangeListener(GuiPanel2.STATE,
            new GuiPanelStateListener());
   }

   public void init() throws GuiException2 {
      if (model == null) {
         throw new GuiException2("Model is null");
      }
      if (guiPanel == null) {
         throw new GuiException2("GuiPanel is null");
      }
      allOK = true;
      guiPanel.setStateField(model.getState().getName());
   }

   public void setGuiModel(GuiModel2 model) {
      this.model = model;
      model.addPropertyChangeListener(new ModelListener());
   }

   private class GuiPanelStateListener implements PropertyChangeListener {
      @Override
      public void propertyChange(PropertyChangeEvent evt) {
         if (!allOK) {
            return;
         }
         if (GuiPanel2.STATE.equals(evt.getPropertyName())) {
            String text = guiPanel.getState();
            model.setState(GuiState.valueOf(text.toUpperCase()));
         }
      }
   }

   private class ModelListener implements PropertyChangeListener {
      @Override
      public void propertyChange(PropertyChangeEvent evt) {
         if (!allOK) {
            return;
         }
         if (GuiModel2.STATE.equals(evt.getPropertyName())) {
            GuiState state = (GuiState) evt.getNewValue();
            guiPanel.setStateField(state.getName());
         }
      }
   }
}

@SuppressWarnings("serial")
class GuiException2 extends Exception {

   public GuiException2() {
      super();
   }

   public GuiException2(String message) {
      super(message);
   }
}

请注意警告:我不是专业的程序员,甚至不是受过大学训练的程序员,所以请仅以我的观点为准。

答案 2 :(得分:0)

我正在学校学习Java。老师告诉我们,听众总是必须在 Controller 类中声明。我这样做的方法是实现一种方法,例如听众()的。 Inside是使用匿名类的所有侦听器声明。这就是我的老师希望它看到的方式,但坦率地说,我不确定他们是否完全正确。

答案 3 :(得分:0)

简介

模型/视图/控制器 (MVC) 模式可以通过以下方式应用于 Swing。

  1. 视图从模型中读取信息。
  2. 视图可能不会更新模型。
  3. 控制器将更新模型并重新绘制/重新验证视图。

现在,在 Swing 中,通常没有主控制器来“统治所有人”。每个 ActionListenerAbstractAction 都是自己的控制器,负责特定 JButton 或键盘键绑定的操作。

这是一个使用 MVC 编码的 Swing GUI 示例。

Ripples

此 GUI 会在您左键单击图形 JPanel 时绘制不断扩大的圆圈。就像池塘里的涟漪。 GUI 足够简单,可以在 Stack Overflow 答案中进行解释,但也足够复杂,可以作为一个很好的 MVC 说明。

说明

MVC 模式被称为 MVC 模式,因为您首先创建模型,然后是视图,最后是控制器。现在,这可以是一个迭代过程。通常,我发现在构建视图时需要在模型中添加一些内容。

从视图开始,或者更糟的是,从控制器开始,通常会导致无法测试或调试的混乱。当然,有时您需要视图来验证模型。但我不是一个优秀的开发人员,我可以编写几十行工作代码。

我写了一点,测试了很多,通常发现我给自己带来了问题。由于我写的很少,所以我一次只有一点点代码要调试。

型号

该模型由两个类组成,RipplesModel 类和 Circle 类。 Circle 类是一个普通的 Java getter / setter 类,它包含中心 java.awt.Pointint 半径和用于轮廓颜色的 java.awt.Color。是的,我在模型中使用了 java.awt 类。这些 awt 类用于保存绘图信息。

RipplesModel 类是一个普通的 Java getter / setter 类,它包含 java.util.List 实例的 Circle。绘图 List 将使用此 JPanel 来绘制 Circle 实例。

通常,您的 Swing 模型将由一个或多个普通的 Java getter/setter 类组成。

查看

视图由 JFrame 和绘图 JPanel 组成。 JFrame 代码包含一个 WindowListener,以便我可以停止动画 ThreadWindowListener 是控制器类之一。很简单,所以我把它做成了一个匿名类。

控制器代码可以驻留在其中一个视图类中。 MVC 模型没有规定代码所在的位置。 MVC 模型规定了代码的执行位置。

绘图 JPanel 在绘图 Circle 上绘制 JPanel 实例。时期。控制器负责更改 Circle 实例的半径并运行动画。

我使用 JFrame。我扩展 JPanel 是因为我覆盖了 paintComponent 方法。只有当您想要覆盖一个或多个类方法时,才应该扩展 Swing 组件。

控制器

此 GUI 中有三个控制器类。我已经提到了停止动画 WindowListener 的匿名 Thread 类。

RipplesListener 类是一个 MouseListener 类,用于监听鼠标按下。侦听器创建一个 Circle 实例,您可以在其中单击鼠标左键。

Animation Runnable 会增加每个 Circle 实例的半径,并要求重新绘制绘图 JPanelJFrame 类包含一个 repaint 方法,该方法依次调用绘图 JPanel repaint 方法。

控制器类不必知道视图是如何工作的。他们只需要知道 JFrame(主视图)类有一个 repaint 方法。这有助于强制关注点分离,这是您使用 MVC 模式的主要原因之一。

Animation Runnable 在单独的 Thread 中运行,因此 GUI 线程 Event Dispatch Thread 不会被阻塞。今天,我可能会使用 Swing Timer,但是当我写这个的时候,我已经习惯了写我自己的动画 Runnable

代码

这是完整的可运行代码。我把所有的类都放在了内部类中,这样我就可以将这段代码作为一个块发布。

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class Ripples implements Runnable {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Ripples());
    }

    private Animation animation;

    private DrawingPanel drawingPanel;

    private RipplesModel model;

    public Ripples() {
        model = new RipplesModel();
    }

    @Override
    public void run() {
        JFrame frame = new JFrame("Ripples");
        frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
        frame.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent event) {
                stopAnimation();
                frame.dispose();
                System.exit(0);
            }
        });

        drawingPanel = new DrawingPanel(model);
        frame.add(drawingPanel, BorderLayout.CENTER);

        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);

        animation = new Animation(this, model);
        new Thread(animation).start();
    }

    public void repaint() {
        drawingPanel.repaint();
    }

    private void stopAnimation() {
        if (animation != null) {
            animation.setRunning(false);
        }
    }

    public class DrawingPanel extends JPanel {

        private static final long serialVersionUID = 1L;

        private RipplesModel model;

        public DrawingPanel(RipplesModel model) {
            this.model = model;
            setBackground(Color.BLACK);
            setPreferredSize(new Dimension(500, 500));
            addMouseListener(new RipplesListener(model));
        }

        @Override
        public void paintComponent(Graphics g) {
            super.paintComponent(g);

            Graphics2D g2 = (Graphics2D) g;
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON);
            g2.setStroke(new BasicStroke(5f));

            List<Circle> circles = model.getCircles();
            for (Circle circle : circles) {
                Point p = circle.getCenter();
                int radius = circle.getRadius();
                g2.setColor(circle.getColor());
                g2.drawOval(p.x - radius, p.y - radius,
                        2 * radius, 2 * radius);
            }
        }

    }

    public class RipplesListener extends MouseAdapter {

        private Random random;

        private RipplesModel model;

        public RipplesListener(RipplesModel model) {
            this.model = model;
            this.random = new Random();
        }

        @Override
        public void mousePressed(MouseEvent event) {
            model.addCircle(new Circle(event.getPoint(),
                    createColor()));
        }

        private Color createColor() {
            int r = random.nextInt(128) + 128;
            int g = random.nextInt(128) + 128;
            int b = random.nextInt(128) + 128;
            return new Color(r, g, b);
        }
    }

    public class Animation implements Runnable {

        private volatile boolean running;

        private Ripples frame;

        private RipplesModel model;

        public Animation(Ripples frame, RipplesModel model) {
            this.frame = frame;
            this.model = model;
            this.running = true;
        }

        @Override
        public void run() {
            while (running) {
                sleep(20L);
                incrementRadius();
                repaint();
            }
        }

        private void incrementRadius() {
            List<Circle> circles = model.getCircles();
            for (Circle circle : circles) {
                circle.incrementRadius();
            }
        }

        private void sleep(long delay) {
            try {
                Thread.sleep(delay);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        private void repaint() {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    frame.repaint();
                }
            });
        }

        public synchronized void setRunning(boolean running) {
            this.running = running;
        }

    }

    public class RipplesModel {

        private List<Circle> circles;

        public RipplesModel() {
            this.circles = new ArrayList<>();
        }

        public void addCircle(Circle circle) {
            this.circles.add(circle);
        }

        public List<Circle> getCircles() {
            return circles;
        }

    }

    public class Circle {

        private int radius;

        private final Color color;

        private final Point center;

        public Circle(Point center, Color color) {
            this.center = center;
            this.color = color;
            this.radius = 10;
        }

        public void incrementRadius() {
            radius = (++radius > 200) ? 10 : radius;
        }

        public Color getColor() {
            return color;
        }

        public int getRadius() {
            return radius;
        }

        public Point getCenter() {
            return center;
        }

    }

}