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.
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.
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.
There has already been more than one similar question(s), like these:
MVC pattern with many ActionListeners
Java swing - Where should the ActionListener go?
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)
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.
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);
}
}
A am currently learning Java in school. The teachers told us, that the listeners always have to be declared inside the Controller class. 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.
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.
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.
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 anotherActionListener
, 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...
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.
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.
CredentialsPane
The
CredentialsPane
is the physical implementation of aCredentialsView
, 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...LoginView
The
LoginView
is responsible for managing aCredentialsView
, but also for notifying theLoginViewController
when authentication should take place or if the process was cancelled by the user, via some means...Equally, the
LoginViewController
will tell the view when authentication is about to take place and if the authentication failed or was successful.LoginPane
The
LoginPane
is kind of special, it is acting as the view for theLoginViewController
, but it is also acting as the controller for theCredentialsView
. 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 theCredentialsView
, it makes sense to allow theLoginPane
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...Working example