jay*_*Son 19 java model-view-controller user-interface swing awt
我正在编写一个模板Java应用程序,不知何故,如果我想干净地遵循MVC模式,我不确定ActionListeners所属的位置.
示例是基于Swing的,但它不是关于框架,而是关于Java中MVC的基本概念,使用任何框架来创建GUI.
我开始使用一个包含JFrame和JButton的绝对简单的应用程序(用于处理框架因此关闭应用程序).该帖子的代码落后.没有什么特别的,只是为了澄清我们在说什么.我没有从模型开始,因为这个问题太烦人了.
已经存在多个类似的问题,例如:
带有许多ActionListeners Java的MVC模式
- ActionListener应该去哪里?
但是,他们之间的确非常令人满意,因为我想知道两件事:
我希望这不是太笼统或模糊,我在这里问,但这让我想了一会儿.我总是使用自己的方式,让ActionHandler了解Controller,但这似乎不对,所以我最终想知道这是如何正确完成的.
亲切的问候,
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();
}
}
}
Run Code Online (Sandbox Code Playgroud)
视图:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
Mad*_*mer 18
That's a very difficult question to answer with Swing, as Swing is not a pure MVC implementation, the view and controller are mixed.
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...
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.
This would suggest that the ActionListener
s 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.
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);
}
Run Code Online (Sandbox Code Playgroud)
The controller would then subscribe to the view via this listener and the view would call didPerformClose
when (in this case) the close button is pressed.
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...
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...
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.
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);
}
Run Code Online (Sandbox Code Playgroud)
它CredentialsPane
是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;
}
}
Run Code Online (Sandbox Code Playgroud)
该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);
}
Run Code Online (Sandbox Code Playgroud)
The LoginPane
is 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.
Because the LoginPane
will need to change it's own state based on the changes in the CredentialsView
, it makes sense to allow the LoginPane
to 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...
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();
}
}
Run Code Online (Sandbox Code Playgroud)
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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
18434 次 |
最近记录: |