The MVC pattern and Swing
One of the design patterns which I find most difficult to get a real grasp of in "real Swing life" is the MVC pattern. I've been through quite a few of the posts at this site which discuss the pattern, but I still do not feel that I have a clear understanding of how to take advantage of the pattern in my Java Swing application.
Let's say that I have a JFrame which contains a table, a couple of text fields and a few buttons. I would probably use a TableModel to "bridge" the JTable with an underlying data model. However, all functions responsible for clearing fields, validating fields, locking fields along with button actions would usually go directly in the JFrame. However, doesn't that mix the Controller and View of the pattern?
As far as I can see, I manage to get the MVC pattern "correctly" implemented when looking at the JTable (and the model), but things get muddy when I look at the entire JFrame as a whole.
I'd really like to hear how others go about with regard to this. How do you go about when you need to display a table, a couple of fields and some buttons to a user using the MVC pattern?
Solution 1:
A book I'd highly recommend to you for MVC in swing would be "Head First Design Patterns" by Freeman and Freeman. They have a highly comprehensive explanation of MVC.
Brief Summary
You're the user--you interact with the view. The view is your window to the model. When you do something to the view (like click the Play button) then the view tells the controller what you did. It's the controller's job to handle that.
The controller asks the model to change its state. The controller takes your actions and interprets them. If you click on a button, it's the controller's job to figure out what that means and how the model should be manipulated based on that action.
The controller may also ask the view to change. When the controller receives an action from the view, it may need to tell the view to change as a result. For example, the controller could enable or disable certain buttons or menu items in the interface.
The model notifies the view when its state has changed. When something changes in the model, based either on some action you took (like clicking a button) or some other internal change (like the next song in the playlist has started), the model notifies the view that its state has changed.
The view asks the model for state. The view gets the state it displays directly from the model. For instance, when the model notifies the view that a new song has started playing, the view requests the song name from the model and displays it. The view might also ask the model for state as the result of the controller requesting some change in the view.
Source (In case you're wondering what a "creamy controller" is, think of an Oreo cookie, with the controller being the creamy center, the view being the top biscuit and the model being the bottom biscuit.)
Um, in case you're interested, you could download a fairly entertaining song about the MVC pattern from here!
One issue you may face with Swing programming involves amalgamating the SwingWorker and EventDispatch thread with the MVC pattern. Depending on your program, your view or controller might have to extend the SwingWorker and override the doInBackground()
method where resource intensive logic is placed. This can be easily fused with the typical MVC pattern, and is typical of Swing applications.
EDIT #1:
Additionally, it is important to consider MVC as a sort of composite of various patterns. For example, your model could be implemented using the Observer pattern (requiring the View to be registered as an observer to the model) while your controller might use the Strategy pattern.
EDIT #2:
I would additionally like to answer specifically your question. You should display your table buttons, etc in the View, which would obviously implement an ActionListener. In your actionPerformed()
method, you detect the event and send it to a related method in the controller (remember- the view holds a reference to the controller). So when a button is clicked, the event is detected by the view, sent to the controller's method, the controller might directly ask the view to disable the button or something. Next, the controller will interact with and modify the model (which will mostly have getter and setter methods, and some other ones to register and notify observers and so on). As soon as the model is modified, it will call an update on registered observers (this will be the view in your case). Hence, the view will now update itself.
Solution 2:
Not a fan of the idea that the view should be the one to be notified by the model when its data changes. I would delegate that functionality to the controller. In that case, if you change the application logic, you don't need to interfere to the view's code. The view's task is only for the applications components + layout nothing more nothing less. Layouting in swing is already a verbose task, why let it interfere with the applications logic?
My idea of MVC (which I'm currently working with, so far so good) is :
- The view is the dumbest of the three. It doesn't know anything about the controller and the model. Its concern is only the swing components' prostethics and layout.
- The model is also dumb, but not as dumb as the view. It performs the following functionalities.
- a. when one of its setter is called by the controller, it will fire notification to its listeners/observers (like I said, I would deligate this role to the controller). I prefer SwingPropertyChangeSupport for achieving this since its already optimized for this purpose.
- b. database interaction functionality.
- A very smart controller. Knows the view and the model very well. The controller has two functionalities:
- a. It defines the action that the view will execute when the user interacts to it.
- b. It listens to the model. Like what I've said, when the setter of the model is called, the model will fire notification to the controller. It's the controller's job to interpret this notification. It might need to reflect the change to the view.
Code Sample
The View :
Like I said creating the view is already verbose so just create your own implementation :)
interface View{
JTextField getTxtFirstName();
JTextField getTxtLastName();
JTextField getTxtAddress();
}
It's ideal to interface the three for testability purposes. I only provided my implementation of Model and Controller.
The Model :
public class MyImplementationOfModel implements Model{
...
private SwingPropertyChangeSupport propChangeFirer;
private String address;
private String firstName;
private String lastName;
public MyImplementationOfModel() {
propChangeFirer = new SwingPropertyChangeSupport(this);
}
public void addListener(PropertyChangeListener prop) {
propChangeFirer.addPropertyChangeListener(prop);
}
public void setAddress(String address){
String oldVal = this.address;
this.address = address;
//after executing this, the controller will be notified that the new address has been set. Its then the controller's
//task to decide what to do when the address in the model has changed. Ideally, the controller will update the view about this
propChangeFirer.firePropertyChange("address", oldVal, address);
}
...
//some other setters for other properties & code for database interaction
...
}
The Controller :
public class MyImplementationOfController implements PropertyChangeListener, Controller{
private View view;
private Model model;
public MyImplementationOfController(View view, Model model){
this.view = view;
this.model = model;
//register the controller as the listener of the model
this.model.addListener(this);
setUpViewEvents();
}
//code for setting the actions to be performed when the user interacts to the view.
private void setUpViewEvents(){
view.getBtnClear().setAction(new AbstractAction("Clear") {
@Override
public void actionPerformed(ActionEvent arg0) {
model.setFirstName("");
model.setLastName("");
model.setAddress("");
}
});
view.getBtnSave().setAction(new AbstractAction("Save") {
@Override
public void actionPerformed(ActionEvent arg0) {
...
//validate etc.
...
model.setFirstName(view.getTxtFName().getText());
model.setLastName(view.getTxtLName().getText());
model.setAddress(view.getTxtAddress().getText());
model.save();
}
});
}
public void propertyChange(PropertyChangeEvent evt){
String propName = evt.getPropertyName();
Object newVal = evt.getNewValue();
if("address".equalsIgnoreCase(propName)){
view.getTxtAddress().setText((String)newVal);
}
//else if property (name) that fired the change event is first name property
//else if property (name) that fired the change event is last name property
}
}
The Main, where the MVC is setup :
public class Main{
public static void main(String[] args){
View view = new YourImplementationOfView();
Model model = new MyImplementationOfModel();
...
//create jframe
//frame.add(view.getUI());
...
//make sure the view and model is fully initialized before letting the controller control them.
Controller controller = new MyImplementationOfController(view, model);
...
//frame.setVisible(true);
...
}
}