我目前正在编写模板Java应用程序,并且如果我想完全遵循MVC模式,就不确定ActionListener的位置。
该示例基于Swing,但与框架无关,而是Java中使用任何框架创建GUI的MVC的基本概念。
我从一个绝对简单的应用程序开始,该应用程序包含一个JFrame和一个JButton(以放置框架,从而关闭该应用程序)。此帖子后面的代码。没什么特别的,只是为了澄清我们在说什么。我还没有开始使用Model,因为这个问题困扰了我太多。
已经有一个以上类似的问题,例如:
但是我想知道两件事,但没有一个让我真正满意:
此致, jaySon
控制器:
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); } }
对于Swing来说,这是一个很难回答的问题,因为Swing不是纯MVC实现,因此视图和控制器是混合的。
从技术上讲,模型和控制器应该可以交互,而控制器和视图应该可以交互,但是视图和模型永远不能交互,这显然不是Swing的工作原理,但这是另一个争论……
另一个问题是,你真的不想将UI组件公开给任何人,控制器不应该关心某些操作是如何发生的,只有它们可以发生。
这建议ActionListener附加到UI控件的s应该由视图维护。然后,视图应警告控制器已发生某种动作。为此,你可以使用ActionListener控制器订阅的视图(由视图管理)。
ActionListener
更好的是,我将拥有一个专用的视图侦听器,该监听器描述了该视图可能产生的动作,例如…
public interface MainViewListener { public void didPerformClose(MainView mainView); }
然后,控制器将通过此侦听器订阅该视图,并且该视图将didPerformClose在(在这种情况下)按下关闭按钮时调用。
即使在此示例中,我也很想创建一个“主视图”界面,该界面描述了任何实现都可以保证提供的属性(设置程序和获取器)和操作(侦听器/回调),那么你无需关心这些会发生一些动作,只有当他们这样做时,你才有望做某事…
在每个级别,你都想问自己,为另一个实例更改任何元素(更改模型,控制器或视图)有多容易?如果发现自己必须解耦代码,那么你就遇到了问题。通过接口进行通信,并尝试减少各层之间的耦合量以及每一层对其他层的了解,以至于它们仅维护合同即可
更新…
让我们以这个为例…
实际上有两个视图(为实际对话框打折),有凭据视图和登录视图,是的,你将看到它们是不同的。
CredentialsView
凭证视图负责收集要验证的详细信息,用户名和密码。它将向控制器提供信息,以便在更改那些凭据时通知它,因为控制器可能要采取一些措施,例如启用“登录”按钮…
该视图还希望知道何时进行认证,因为它将禁用其字段,因此用户在进行认证时无法更新视图,同样,它需要知道何时进行认证失败或成功,因为它将需要针对这些突发事件采取行动。
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
该CredentialsPane是一个物理实现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负责管理CredentialsView,同时也为通知LoginViewController时应该存放着认证,或者整个过程由用户取消,通过一些手段…
LoginView
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特殊之处在于,它充当的视图LoginViewController,但也充当的控制器CredentialsView。这很重要,因为没有任何说法说视图不能成为控制器,但是我会谨慎考虑如何实现这样的事情,因为这样做并非总是有意义,但是因为这两个视图是一起收集信息和管理事件,在这种情况下很有意义。
LoginPane
由于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(); }