I would like to apply the MVC design to my Java application using Swing in a meaningful way. Therefore my question is, how the controllers would be structured in Java Swing?
I have two options in mind:
- Every Component Listener is an own class, as part of the controller package
- Every Component Listener is an anonymous class inside the view package which delegates its call to a class with controller methods.
Is both possible? Is it a question of preference, or is it clearly defined?
The Controller makes up the other half
of the component interface, mainly the
interaction half. The Controller takes
care of mouse and keyboard events.
in Swing components like JButton
etc are the controllers. and all listener classes redirect events to model that have your business logic
example :
main program
import javax.swing.*;
public class CalcMVC {
//... Create model, view, and controller. They are
// created once here and passed to the parts that
// need them so there is only one copy of each.
public static void main(String[] args) {
CalcModel model = new CalcModel();
CalcView view = new CalcView(model);
CalcController controller = new CalcController(model, view);
view.setVisible(true);
}
}
View
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
class CalcView extends JFrame {
//... Constants
private static final String INITIAL_VALUE = "1";
//... Components
private JTextField m_userInputTf = new JTextField(5);
private JTextField m_totalTf = new JTextField(20);
private JButton m_multiplyBtn = new JButton("Multiply");
private JButton m_clearBtn = new JButton("Clear");
private CalcModel m_model;
//======================================================= constructor
/** Constructor */
CalcView(CalcModel model) {
//... Set up the logic
m_model = model;
m_model.setValue(INITIAL_VALUE);
//... Initialize components
m_totalTf.setText(m_model.getValue());
m_totalTf.setEditable(false);
//... Layout the components.
JPanel content = new JPanel();
content.setLayout(new FlowLayout());
content.add(new JLabel("Input"));
content.add(m_userInputTf);
content.add(m_multiplyBtn);
content.add(new JLabel("Total"));
content.add(m_totalTf);
content.add(m_clearBtn);
//... finalize layout
this.setContentPane(content);
this.pack();
this.setTitle("Simple Calc - MVC");
// The window closing event should probably be passed to the
// Controller in a real program, but this is a short example.
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
void reset() {
m_totalTf.setText(INITIAL_VALUE);
}
String getUserInput() {
return m_userInputTf.getText();
}
void setTotal(String newTotal) {
m_totalTf.setText(newTotal);
}
void showError(String errMessage) {
JOptionPane.showMessageDialog(this, errMessage);
}
void addMultiplyListener(ActionListener mal) {
m_multiplyBtn.addActionListener(mal);
}
void addClearListener(ActionListener cal) {
m_clearBtn.addActionListener(cal);
}
}
controller
import java.awt.event.*;
public class CalcController {
//... The Controller needs to interact with both the Model and View.
private CalcModel m_model;
private CalcView m_view;
//========================================================== constructor
/** Constructor */
CalcController(CalcModel model, CalcView view) {
m_model = model;
m_view = view;
//... Add listeners to the view.
view.addMultiplyListener(new MultiplyListener());
view.addClearListener(new ClearListener());
}
////////////////////////////////////////// inner class MultiplyListener
/** When a mulitplication is requested.
* 1. Get the user input number from the View.
* 2. Call the model to mulitply by this number.
* 3. Get the result from the Model.
* 4. Tell the View to display the result.
* If there was an error, tell the View to display it.
*/
class MultiplyListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
String userInput = "";
try {
userInput = m_view.getUserInput();
m_model.multiplyBy(userInput);
m_view.setTotal(m_model.getValue());
} catch (NumberFormatException nfex) {
m_view.showError("Bad input: '" + userInput + "'");
}
}
}//end inner class MultiplyListener
//////////////////////////////////////////// inner class ClearListener
/** 1. Reset model.
* 2. Reset View.
*/
class ClearListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
m_model.reset();
m_view.reset();
}
}// end inner class ClearListener
}
Model
import java.math.BigInteger;
public class CalcModel {
//... Constants
private static final String INITIAL_VALUE = "0";
//... Member variable defining state of calculator.
private BigInteger m_total; // The total current value state.
//============================================================== constructor
/** Constructor */
CalcModel() {
reset();
}
//==================================================================== reset
/** Reset to initial value. */
public void reset() {
m_total = new BigInteger(INITIAL_VALUE);
}
//=============================================================== multiplyBy
/** Multiply current total by a number.
*@param operand Number (as string) to multiply total by.
*/
public void multiplyBy(String operand) {
m_total = m_total.multiply(new BigInteger(operand));
}
//================================================================= setValue
/** Set the total value.
*@param value New value that should be used for the calculator total.
*/
public void setValue(String value) {
m_total = new BigInteger(value);
}
//================================================================= getValue
/** Return current calculator total. */
public String getValue() {
return m_total.toString();
}
}
Platzhirsch,
Dude, this a SERIOUSLY heavy-weight question... one which nobody is going to answer adequately in a forum post. This artice Java SE Application Design With MVC by Robert Eckstein (one of the Java Gods) discusses the problem at length.
Personally, I ended-up using a "variant" of MVC which I called MBVC (Model Business View Controller), which is actually pretty close to MVVMC... MVVM is widely used in .NET cirles; adding a controller made sense to me, as I had some web MVC experience. I wish I'd read the above article BEFORE I set-out to MVC-ise my app. You can still read my rather perplexed posts on Sun's (now Oracle's) Java forums.
Cheers. Keith.
While the Swing framework already implements a form of MVC (explicit models; JXyz & UI classes = controller & view), this strict separation is rarely used on application level and looks rather weird.
To start I suggest to follow the following design:
- implement the client-side business logic with POJOs
- wrap the POJOs with custom Swing models where needed (ListModel, TableModel)
- Use a GUI builder to design the GUI
- Use the Mediator pattern to listen for events (the custom parent JPanel listens for events of its children and updates other children or fires own events if needed)
If you want to go a step further, use a RCP such as the NetBeans Platform (very recommended).
Look this MVC architecture
The view would encompass the GUI design. The actionPerformed method of the view should identify the event, and call respective methods on the ControllerInterface depending on the event. The controller implementation will implement this ControllerInterface, and the methods which are called by the view will be implemented here. These methods will presumably interact with the model in some way. The model itself will have a way to register observers (the view, in this case; incidentally, even controllers can be registered) and update observers each time the model changes. That, in essence is how you would structure your application. For more details on MVC, you can also refer to:
here!
For a clear separation of View and Controller, you could do it like this:
public class MyView extends SomeSwingComponent {
private Controller controller;
public View(Controller controller) {
this.controller = controller;
JButton saveButton = new JButton("Save");
button.addActionListner(controller.getSaveButtonListener());
}
}
public class MyController implements Controller {
private ActionListener saveButtonListener;
@Override
public ActionListener getSaveButtonListener() {
if (saveButtonListener == null) {
saveButtonListener = new ActionListener() {
// ...
}
}
}
Now we could implement different controllers which could be convenient, if we want to use the view component with different models. The controller is a strategy of the actual view instance.
In most cases it is just enough to define listeners as anonymous classes, because they usually just call a method on the controller instance. I use listeners usually as simple dispatchers - they receive a notification and send a message to another object. A listener class should implement too much business logic, that's beyond its responsibilties.
I have two options in mind:
- Every Component Listener is an own class, as part of the controller
package
- Every Component Listener is an anonymous class inside the view
package which delegates its call to a
class with controller methods. Is both
possible?
Yes both is possible but I will prefer to write Single class which implements all actionListioner and assign that same to all component in your app. This way you can track all actions in your app at single point.
Also this way you can make your view distinguish from controller.