Computer Science Canada

[Tutorial] SWING and the MVC

Author:  rizzix [ Thu Jan 26, 2006 6:23 pm ]
Post subject:  [Tutorial] SWING and the MVC

This tutorial is designed to help the SWING programmer better understand the SWING framework, by introducing the progammer to the foundation of SWING and many other user interaction framworks, i.e. the Model View Controller architecture -- MVC.

Before I continue, I recommend you to use the online javadocs, throughout this tutorial. I've quoted them wherever necessary, but I strongly suggest your use the online docs, as it will help you get a better understanding on how-things-work.

MVC is a philosophy about the separation of the three major concerns when dealing with user interaction: the model, the view and the controller. This separation, leads to better maintainablity, scalabitlity, reusability and ease-of-development.

The View
So what is the view? Well, this is easy to answer. Anything you see on your screen, is basically your View. So a JButton is a View, a JTextField is a View, a JTable is also View, etc. Basically anything the user can finally see or interact with, is your View.

The Model
This may not be so easy to answer, but a Model is basically the underlying data of your program. It is the collective state of your program that is essential to providing the view with useful information (the data relevent to the purpose of your program).
The model is usually (but not necessarily) serializable. This means that the Model can be saved as a file, into a database, or is transferable over the network.

The Controller
It is the means by which the View can communicate with the Model and visa-versa. Any change in the View that needs to update the model, will be carried out through the controller. Any changes in the model that need to notify the View of an update, is also carried out through the controller. It is within the controller that your decission logic is placed. Hence the controller, controls the flow of information within your program.



MVC by example...

It is said that the MVC in SWING is really MV/C. What this means it that most SWING components (Views) are pre-coupled with a default Model. The programmer only needs to add a Controller (EventListener) to it, to get it to a workable state.

While this is true for most cases, but usually only for simpler components, like JButton etc. The more complex components, like JTable, JTree or JList, require you to provide them with your own custom Model to get them to a fully functional state.

For the sake of keeping things simple, I'm going to look into the simplest of these components: JList. In our first example we are going to create a simple JList that is dynamically updatable. We will create our custom Model to do this, but we will not make is Serializable, since this isin't necessary.


The ListModel Interface
JList's Models have to implement the ListModel interface. First lets take a look at what this interface declares (right off the javadocs):

Java:
void addListDataListener(ListDataListener l);
Adds a listener to the list that's notified each time a change to the data model occurs.
Java:
Object getElementAt(int index)
Returns the value at the specified index.
Java:
int getSize();
Returns the length of the list.
Java:
void removeListDataListener(ListDataListener l);
Removes a listener from the list that's notified each time a change to the data model occurs.


Note that we need to implement the facility to add Controllers which need to be notified when the model changes... So let's go ahead and do it:

Java:
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;

class SimpleListModel<T> implements ListModel {
    private ArrayList<ListDataListener> listenerList = new ArrayList<ListDataListener>();
    private ArrayList<T> dataList = new ArrayList<T>();

    public void addListDataListener(ListDataListener l) {
        listenerList.add(l);
    }

    public void removeListDataListener(ListDataListener l) {
        listenerList.remove(l);
    }

    public int getSize() {
        return dataList.size();
    }

    public T getElementAt(int index) {
        int size = dataList.size();
        if (size > 0 && index > -1 && index < size)
            return dataList.get(index);
        else
            return null;
    }
}


Here we implemented the bare minimum. We implemented adding/removing of ListDataListeners, the ability to retrive data from the model, as well as the ability to inquire the size of the list model. This is all but the bare minimum to get the ListModel functional -- well sort of, but we'll come back to it in a minute.

Remember we are going to dynamically add data to the JList? Well the model needs to reflect that, so we need to move beyond the bare minimum. Let's implement an "add" method:

Java:
    public void add(T data) {
        dataList.add(data);
    }


Let's also implement "remove" and "update" methods as well.

Java:
    public boolean remove(T data) {
        return dataList.remove(data);
    }
    public void update(T data) {

    }


We'll implement the "update" method later. But the rest should work right?

Well, no. As mentioned before, we need to notify the View when the Model changes. Well, at the moment our ListModel does not do that. This is done through interacting with the Controllers. Never modify the View directy!

So how do we proceed? Well, it's rather simple really. First we look up the javadocs for ListDataListener class, since it is the controller that our Model needs to interact with. Here's what it has to say about it:

Java:
void contentsChanged(ListDataEvent e);
Sent when the contents of the list has changed in a way that's too complex to characterize with the previous methods.
Java:
void intervalAdded(ListDataEvent e);
Sent after the indices in the index0,index1 interval have been inserted in the data model.
Java:
void intervalRemoved(ListDataEvent e);
Sent after the indices in the index0,index1 interval have been removed from the data model.

Here we notice three methods, that should be called from our Model, accordingly. What's further interesting to know is that we need to pass these methods a ListDataEvent object. So let's look up the javadocs about this:

Java:
public ListDataEvent(Object source,
                     int type,
                     int index0,
                     int index1)
source - the source Object (typically this)
type - an int specifying CONTENTS_CHANGED, INTERVAL_ADDED, or INTERVAL_REMOVED
index0 - one end of the new interval
index1 - the other end of the new interval


It seems that, that one constructor, is all that we actually need. Note how useful the javadocs are. It tells us everything we could possibly need to know, to go about doing whatever are doing in Java. Smile

On a side note, since it's not quite apparent here, but it is if you are reading the docs directly: CONTENTS_CHANGED, INTERVAL_ADDED, and INTERVAL_REMOVED are all defined within the ListDataEvent class. So, keep that in mind when you are using them in code.

Now that we know what we need to do, here's how we'll implement it: we will write three new private methods for the three respective events:

Java:
    private void fireContentsChangedEvent(int index0, int index1) {
        ListDataEvent lde = new ListDataEvent(
            this,
            ListDataEvent.CONTENTS_CHANGED,
            index0,
            index1
        );
        for (ListDataListener listener : listenerList)
            listener.contentsChanged(lde);
    }

    private void fireIntervalAddedEvent(int index0, int index1) {
        ListDataEvent lde = new ListDataEvent(
            this,
            ListDataEvent.INTERVAL_ADDED,
            index0,
            index1
        );
        for (ListDataListener listener : listenerList)
            listener.intervalAdded(lde);
    }

    private void fireIntervalRemovedEvent(int index0, int index1) {
        ListDataEvent lde = new ListDataEvent(
            this,
            ListDataEvent.INTERVAL_REMOVED,
            index0,
            index1
        );
        for (ListDataListener listener : listenerList)
            listener.intervalRemoved(lde);
    }


Well, I hope what I've written there is pretty self explainatory, but basically what we do is, send each ListDataListener the appropriate ListDataEvent object at the respective event. Simple as that.

Now we need to fire these events at the appropriate time. To do that we need to modify our mutative methods to something like this:

Java:
    public void add(T data) {
        int oldLastIndex = dataList.size() - 1;

        dataList.add(data);
        fireIntervalAddedEvent(oldLastIndex + 1, oldLastIndex + 1);
    }

    public boolean remove(T data) {
        int oldItemIndex = dataList.indexOf(data);

        if (dataList.remove(data)) {
            fireIntervalRemovedEvent(oldItemIndex, oldItemIndex);
            return true;
        }
        return false;
    }
   
    public void update(T data) {
        int itemIndex = dataList.indexOf(data);

        if (itemIndex != -1) //if object exists
            fireContentsChangedEvent(itemIndex, itemIndex);
    }


So.. now it should work right?
Yep. It's functional. Let's go ahead an try it out. To do this, we need to create a JList, a JButton and a JTextField:

Java:
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;

public class TestFrame extends JFrame {

    private JTextField  textField;
    private JButton     button;
    private JList       list;
    private SimpleListModel<String> listModel;

    public TestFrame() {
        super("Test Application");
        Dimension screenSize = getToolkit().getScreenSize();

        setBounds(screenSize.width/4, screenSize.height/4,
                  screenSize.width/2, screenSize.height/2);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        listModel = new SimpleListModel<String>();
        list = new JList(listModel);

        this.add(list, BorderLayout.CENTER);

        ActionListener actionListener = new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                String text = textField.getText();
                if (text != "") {
                    listModel.add(text);
                    textField.setText("");
                }
            }
        };

        textField = new JTextField();
        textField.addActionListener(actionListener);

        button = new JButton("Add");
        button.addActionListener(actionListener);

        JPanel panel = new JPanel(new GridLayout(1,2));
        panel.add(textField);
        panel.add(button);

        this.add(panel, BorderLayout.SOUTH);
    }

    public static void main(String[] args) {
        TestFrame frame = new TestFrame();
        frame.setVisible(true);
    }
}


Once again notice how the actual interaction is done only through Controllers. SWING is designed from the ground up using the MVC architecture. In fact, JButton and JTextField both have their own Models, but for the most part the default one is usually the best choice. This is obviously not always the case, since JList required a more flexible Model (SimpleListModel<?>) to make it do something useful.

Although we did not actually make any use of the "remove" and "update" methods, they too are functional. You can make use of them in the similar way we did with the "add" method. Just make sure you always call update after you updated a data element within the ListModel.

For example:
Java:
SomeObject s = listModel.getElementAt(2);
s.setName("bla");
listModel.update(s);

It may seem odd to call update on "s" even though we just modified it, but that's the way we designed our SimpleListModel. There are ways to avoid this, in the MVC architecture, but for now let's keep it simple. Simple is always good. =)

Attached are the Files written throughout this tutorial.

More to come...

Author:  wtd [ Thu Jan 26, 2006 6:44 pm ]
Post subject: 

I can't help but beleft with the impression that the tutorial may benefit from the addition of simpler introductory examples of MVC. Like having a very simple app that lets the user input a name, allows for saving that name, then has a button which prints the name to the console or such. It'd require a textfield, a couple of buttons, and a variable as the model.

Author:  rizzix [ Thu Jan 26, 2006 7:04 pm ]
Post subject: 

The truth is, I was planing on hitting two birds with one stone: JList and an introduction to MVC.

But, I'm also planing on doing it like this:

Part 1) A specific example of MVC -- JList
Part 2) Generalizing MVC. -- A simple example (haven't thought about it yet, but it's due to come) Smile
Part 3) ...

Author:  wtd [ Thu Jan 26, 2006 7:22 pm ]
Post subject: 

Good. You have to give people examples they can sink their teeth into. JList is eminently practical, but involves lots of big names and a fair deal of "magic".

Author:  Martin [ Thu Jan 26, 2006 7:54 pm ]
Post subject: 

Nice job Rizzix.

Wikipedia on the MVC: http://en.wikipedia.org/wiki/Model_view_controller

Author:  shadowman544 [ Tue May 27, 2008 8:40 pm ]
Post subject:  Re: [Tutorial] SWING and the MVC

thanks


: