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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/26517856/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 02:41:41  来源:igfitidea点击:

Java and GUI - Where do ActionListeners belong according to MVC pattern?

javaswinguser-interfacemodel-view-controllerawt

提问by jaySon

I'm currently writing a template Java application and somehow, I'm not sure about where the ActionListeners belong if I wanted to cleanly follow the MVC pattern.

我目前正在编写一个模板 Java 应用程序,不知何故,如果我想完全遵循 MVC 模式,我不确定 ActionListeners 属于哪里。

The example is Swing based, but it's not about the framework but rather the basic concept of MVC in Java, using any framework to create GUI.

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

I started with an absolutely simple application containing a JFrame and a JButton (to dispose the frame hence close the application). The code trailing this post. Nothing really special, just to clearify what we're talking about. I didn't start with the Model yet as this question was bugging me too much.

我从一个绝对简单的应用程序开始,其中包含一个 JFrame 和一个 JButton(用于处理框架,从而关闭应用程序)。这篇文章后面的代码。没什么特别的,只是为了弄清楚我们在说什么。我还没有从模型开始,因为这个问题困扰了我太多。

There has already been more than one similar question(s), like these:
MVC pattern with many ActionListeners
Java swing - Where should the ActionListener go?

已经有不止一个类似的问题,像这样:
MVC 模式与许多 ActionListeners
Java swing - ActionListener 应该去哪里?

But non of them was really satisfying as I'd like to know two things:

但他们都不是很满意,因为我想知道两件事:

  • Is it reasonable to have all ActionListeners in a separate package?
    • I'd like to do so for the sake of readability of View and Controller, esp. if there's a lot of listeners
  • How would I execute a Controller function from within an ActionListener, if the listener is not a sub class inside the Controller? (follow-up question)
  • 将所有 ActionListener 放在一个单独的包中是否合理?
    • 我想这样做是为了视图和控制器的可读性,尤其是。如果有很多听众
  • 如果侦听器不是 Controller 内的子类,我将如何从 ActionListener 内执行 Controller 函数?(后续问题)

I hope this is not too general or vague I'm asking here, but it makes me think for a while now. I always used sort of my own way, letting the ActionHandler know about the Controller, but this does not seem right, so I'd finally like to know how this is done properly.

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

Kind regards,
jaySon

亲切的问候,
杰森



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


View:


看法:

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

采纳答案by MadProgrammer

That's a very difficult question to answer with Swing, as Swing is not a pure MVC implementation, the view and controller are mixed.

这是一个很难用 Swing 回答的问题,因为 Swing 不是纯粹的 MVC 实现,视图和控制器是混合的。

Technically, a model and controller should be able to interact and the controller and view should be able to interact, but the view and model should never interact, which clearly isn't how Swing works, but that's another debate...

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

Another issue is, you really don't want to expose UI components to anybody, the controller shouldn't care how certain actions occur, only that they can.

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

This would suggest that the ActionListeners attached to your UI controls should be maintained by the view. The view should then alert the controller that some kind of action has occurred. For this, you could use another ActionListener, managed by the view, to which the controller subscribes to.

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

Better yet, I would have a dedicated view listener, which described the actions that this view might produce, for example...

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

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

The controller would then subscribe to the view via this listener and the view would call didPerformClosewhen (in this case) the close button is pressed.

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

Even in this example, I would be tempted to make a "main view" interface, which described the properties (setters and getters) and actions (listeners/callbacks) that any implementation is guaranteed to provide, then you don't care how these actions occur, only that when they do, you are expected to do something...

即使在这个例子中,我也很想制作一个“主视图”界面,它描述了任何实现都保证提供的属性(setter 和 getter)和动作(监听器/回调),那么你不关心这些行动发生,只有当他们这样做时,你应该做某事......

At each level you want to ask yourself, how easy would it be to change any element (change the model or the controller or the view) for another instance? If you find yourself having to decouple the code, then you have a problem. Communicate via interfaces and try and reduce the amount of coupling between the layers and the amount that each layer knows about the others to the point where they are simply maintaining contracts

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

Updated...

更新...

Let's take this for an example...

让我们以这个为例...

Login

登录

There are actually two views (discounting the actual dialog), there is the credentials view and the login view, yes they are different as you will see.

实际上有两个视图(不考虑实际对话框),即凭据视图和登录视图,是的,正如您将看到的那样,它们是不同的。

CredentialsView

凭证视图

The credentials view is responsible for collecting the details that are to be authenticated, the user name and password. It will provide information to the controller to let it know when those credentials have been changed, as the controller may want to take some action, like enabling the "login" button...

凭据视图负责收集要验证的详细信息、用户名和密码。它将向控制器提供信息,让其知道这些凭据何时发生更改,因为控制器可能想要采取一些措施,例如启用“登录”按钮...

The view will also want to know when authentication is about to take place, as it will want to disable it's fields, so the user can't update the view while the authentication is taking place, equally, it will need to know when the authentication fails or succeeds, as it will need to take actions for those eventualities.

视图还想知道何时进行身份验证,因为它想禁用它的字段,因此用户无法在身份验证进行时更新视图,同样,它需要知道何时进行身份验证失败或成功,因为它需要为这些不测事件采取行动。

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

凭据窗格

The CredentialsPaneis the physical implementation of a CredentialsView, it implements the contract, but manages it's own internal state. How the contract is managed is irrelevent to the controller, it only cares about the contract been upheld...

TheCredentialsPane是 a 的物理实现CredentialsView,它实现了合约,但管理着它自己的内部状态。合同如何管理与控制者无关,它只关心合同是否被维护......

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

登录视图

The LoginViewis responsible for managing a CredentialsView, but also for notifying the LoginViewControllerwhen authentication should take place or if the process was cancelled by the user, via some means...

LoginView负责管理CredentialsView,同时也为通知LoginViewController时应该存放着认证,或者整个过程由用户取消,通过一些手段...

Equally, the LoginViewControllerwill tell the view when authentication is about to take place and if the authentication failed or was successful.

同样,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

登录面板

The LoginPaneis kind of special, it is acting as the view for the LoginViewController, but it is also acting as the controller for the CredentialsView. This is important, as there is nothing saying that a view can't be a controller, but I would be careful about how you implement such things, as it might not always make sense to do it this way, but because the two views are working together to gather information and manage events, it made sense in this case.

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

Because the LoginPanewill need to change it's own state based on the changes in the CredentialsView, it makes sense to allow the LoginPaneto act as the controller in this case, otherwise, you'd need to supply more methods that controlled that state of the buttons, but this starts to bleed UI logic over to the controller...

因为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();
    }

}

Working example

工作示例

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

    }

}

回答by Hovercraft Full Of Eels

They are associated with the control, but they don't have to be a direct part of the control. For instance, please see the code posted below that I was preparing for another question, one on anonymous inner classes and coupling, here I give all my buttons anonymous inner Actions (which are ActionListeners, of course), and then use the Actions to change the GUI state. Any listeners to the GUI (the control) will be notified of this change, and can then act accordingly.

它们与控件相关联,但它们不必是控件的直接组成部分。例如,请参阅下面发布的代码,我正在准备另一个问题,一个关于匿名内部类和耦合的问题,这里我给我所有的按钮匿名内部操作(当然是 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);
   }
}

Note in warning though: I am not a professional coder or even a university trained coder, so please take this as just my opinion only.

但请注意:我不是专业的编码员,甚至不是受过大学培训的编码员,因此请仅将其视为我的意见。

回答by WayneEra

A am currently learning Java in school. The teachers told us, that the listeners always have to be declared inside the Controllerclass. The way I do it, is to implement a method e.g. listeners(). Inside are all listener-declarations using anonymous classes. That's the way my teachers want it to see, but frankly, i'm not really sure if they got it all correct.

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