对于 Swing,这是一个很难回答的问题,因为 Swing 不是纯 MVC 实现,视图和控制器是混合的。
从技术上讲,模型和控制器应该能够交互,控制器和视图应该能够交互,但是视图和模型永远不应该交互,这显然不是 Swing 的工作原理,但这是另一个争论......
另一个问题是,您真的不想将 UI 组件暴露给任何人,控制器不应该关心某些操作是如何发生的,只要它们可以。
这表明附加到 UI 控件的 ActionListeners 应该由视图维护。然后视图应该提醒控制器发生了某种动作。为此,您可以使用另一个由控制器订阅的视图管理的ActionListener。
更好的是,我会有一个专门的视图监听器,它描述了这个视图可能产生的动作,例如......
public interface MainViewListener {
public void didPerformClose(MainView mainView);
}
然后,控制器将通过此侦听器订阅视图,并且当(在这种情况下)按下关闭按钮时,视图将调用 didPerformClose。
即使在这个例子中,我也很想创建一个“主视图”界面,它描述了任何实现都保证提供的属性(setter 和 getter)和操作(listeners/callbacks),然后你不关心这些动作是如何发生的,只是当它们发生时,你应该做某事......
在您想问自己的每个级别,为另一个实例更改任何元素(更改模型或控制器或视图)有多容易?如果你发现自己不得不解耦代码,那么你就有问题了。通过接口进行通信,并尝试减少层之间的耦合量以及每个层对其他层的了解程度,以达到他们只是维护合约的程度
更新...
我们以这个为例……
实际上有两个视图(不包括实际对话框),凭据视图和登录视图,是的,您将看到它们是不同的。
凭据视图
凭证视图负责收集要验证的详细信息、用户名和密码。它将向控制器提供信息,让其知道这些凭据何时已更改,因为控制器可能想要采取一些措施,例如启用“登录”按钮...
视图还想知道何时进行身份验证,因为它想禁用它的字段,因此用户无法在身份验证期间更新视图,同样,它需要知道当身份验证失败或成功时,它需要针对这些可能性采取措施。
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 是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 何时应该进行身份验证或用户是否通过某种方式取消进程...
同样,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 需要根据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;
}
}
}