Using Tables with the MVC

thingpastoralΛογισμικό & κατασκευή λογ/κού

14 Ιουλ 2012 (πριν από 5 χρόνια και 10 μέρες)

541 εμφανίσεις

1
Using Tables with the MVC

WRITER: Dr John Hunt
EMAIL: john.hunt@jaydeetechnology.co.uk
TELEPHONE: 01225 789255
FAX: 0870 0548872




Recently I have been writing about the use of patterns within Java client programs. In
particular I have talked about the use of the MVC and Hierarchical MVC (h-MVC)
patterns. This has prompted a number of questions regarding the use of the MVC with
tables. This is a classic question, that is “how does the MVC pattern (in either of its
forms) relate to the Swing JTable component and its table model?”. In this column we
will look at this issue and consider how a JTable can be supported by the MVC
architecture.

Overview of Basic MVC
As a bit of background we will first look at the concepts behind the MVC framework and
the extension known as the h-MVC framework. The key idea behind the MVC is the
separation of the user display, from the control of user input, from the underlying
information model as illustrated in Figure 1. This represents a division of roles within
elements of the GUI (a core idea in breaking an application into classes) as well as
encapsulation of data and behaviour into appropriate classes. The benefit of this
approach are that you gain:

reusability of application and / or user interface components,
the ability to develop the application and user interface separately,
the ability to inherit from different parts of the class hierarchy.
The ability to define control style classes which provide common features
separately from how these features may be displayed.


Figure 1: The Model-View-Controller architecture
Display/
View
Control of
User Input
Information
Model
2
This means that different interfaces can be used with the same application, without the
application knowing about it.

A significant development of the basic MVC framework is the Hierarchical MVC
framework (referred to in this column as h-MVC). This is a modification of the basic
MVC framework that encourages the decomposition of an application, or client, in an n-
tier architecture.
The hierarchical MVC (h-MVC) breaks the client part of an application down into
separate MVC triads. Each MVC is then responsible for one aspect of the client. This
thus becomes a layered architecture. Between the MVCs, well defined interfaces exist
to allow information and behaviour-oriented requests to flow. These interfaces are
supported by links between the controllers. To illustrate this idea see Figure 2. \this
figure illustrates a simple application constructed form three MVC triads. The links
between the MVC triads are maintained via the controllers.


Figure 2: The classes in the search application
An important distinction to note at this point is that we are talking about the MVC (and
later the hierarchical version of the MVC) in an architectural context and not at the low
level used by Java’s swing set of classes. Although the two approaches are related at
the abstract level, they differ greatly in their implementation.
3
JTable
The Swing set of graphical user interface components is, currently, the primary element
in the Java Foundation Classes (JFC). The Swing set represent a maturing of the
graphical components available in Java and as such are a much more powerful set of
components than were previously found in the AWT. For example, the swing
components include buttons with graphics and tool tips, dockable toolbars, tree and
table components as well as support for these components in terms of tree and table
sub packages.
Swing is now actually a standard Java extension (hence the swing set are all defined
within the javax.swing package or one of its sub packages).

A JTable is a swing component that provides for a tabular display within a graphical
client in an Java program (for example see Figure 3). It can allow editing of the cells in
the table as well as custom rendering of these cells.







Figure 3: A JTable
A JTable is not a trivial Swing component and is comprised of numerous elements (see
Figure 4). It is based on a TableModel that provides the data to be displayed in the table
itself. Associated with the table there can also be cell editors (for modifying the contents
of a cell) and cell renderers (for determining how to display the cell). In addition various
listeners can be added to a table to handle various events such as a cell or row
selection or a change to the table model etc.

JTable
TableModel
LisSelectionListener
TableModelListener
ListSelectionmodel
CellEditors
CellRenderers

Figure 4: The structure of a JTable component

4
Sophisticated tables can be generated by using combinations of renderers and editors.
For example, the table in
Figure 5
possesses columns that use the default cell renderer
(the column headword for example), columns that render HTML (such as Part 1) and
columns that possess renderers that display both images and text (such as Type). In
addition this table has two rows for column.


Figure 5: A real JTable application

As has been stated, at the heart of a JTable is a TableModel. This is actually an
interface in swing. This interface specifies nine methods including:

isCellEditable(int rowIndex, int columnIndex) This method determines if the cell
specified is editable or not. To create a non editable table this method needs to
always return false.
getValueAt(int rowIndex, int columnIndex) / setValueAt(… ) These methods allow
the values at specific points in the tbale to be obtained and modified.
getRowCount() Tells the JTable how many rows the tbale has.
getColumnCount() Informs the JTable fo the number of columns in the
TableModel.
getColumnName(int columnIndex) Returns the name of a specific column.
5
getColumnClass(int columnIndex) Returns the class of a column. This can be
used to determine which cell editor and renderer to use if one has not been
explicitly set.

Two classes are provided to make the process of creating a TableModel class simpler.
These classes are the AbstractTableModel and the DefaultTableModel. The
AbstractTableModel class is the starting point for the creation of your own table models
if the DefaultTableModel (which is based on a list data structure) is not appropriate. The
AbstractTableModel provides for management of listeners and TableModelEvent
generation. However it leaves three methods abstract. The three abstract methods are:

int getRowCount();
int getColumnCount();
Object getValueAt(int row, int column);

The DefaultTableModel a list based subclass that provides a Vector like API.


A TableModel object Model generates events that are caught by the Jtable they work
with. The AbstractTableModel provides a variety of methods that "fire" events, these
include:

fireTableRowsInserted,
fireTableCellUpdated,
fireTableChanged,
fireTableDataChanged,
fireTableRowsDeleted,
fireTableRowsInserted,
fireTableRowsUpdated,
fireTableStructureChanged

These can be used to allow the table model to be changed and the Jtable to notice the
changes and modify the display appropriately.

A Cell Renderer is used to draw each of the cells in a column while a Cell Editor is then
used to allow a user to edit the cells contents. The default set of renderers and editors
support

Boolean - rendered with a check box.
Number - rendered by a right-aligned label.
ImageIcon - rendered by a centered label
Object - rendered by a label displaying the object's string value.

However, this set is quite limited and it is often necesaray to define your own cell
renderers (to render images, HTML, custom objects etc.) To do this you need to
implement the TableCellRenderer interface. A common way to do this is to extend an
existing Swing component to handle the actual renderering (such as a JLabel). It is
6
then possible to use the inherited behaviour fo the swing component to handle the
actual display. The one methods defined by the TableCellRenderer interface is
getTableCellRenderer. This method is presented below:

public Component getTableCellRendererComponent(JTable table, Object entity,
boolean isSelected, boolean hasFocus, int row, int column) {… }

Notice that the parameters to the method allow the renderer to determine the object to
be displayed, whether it is selected or not, whether the cel currently has focus and the
row and column of the cell within the table.

The roles of the View and the Model
It is useful to consider the roles or responsibilities of the view and the model within the
MVC. The model represents both application logic and the application state. Thus its
responsibility is to hold data and to process that data. In turn, it is the view’s role to
display information provided to it by the model in graphical components (e.g. swing
components). Thus a model might provide a string to a view, but it is the role of the view
to take the string and place it in a JTextField etc.

Notice that it is the responsibility of the model to provide application data to the view.
That is, the view provides raw data it does not provide swing data structures. This is
because it should be the view that deals with everything to do with the GUI and in
particular with Swing components. Thus, any swing specific wrapping that is required is
also the responsibility of the view. Thus if the string supplied by a model to its view
needs to be wrapped in a Document object for a textual graphical component, then this
is the job of the view (and not of the model). Remember in theory one model may
support multiple views, thus that string may be displayed in a JLabel in one place, used
as the basis of an image to display in another and placed within a custom formatted text
component in a third.

The general interaction between models and views it thus this:

1. Model notifies view of a change in its state.
2. View obtains current data (state) from model.
3. View updates graphical components with new data.

MVC Models and TableModels
The above discussion may be obvious (hide all swing details form the model – hey it
does not even know that a view is around!). However, this can become blurred when
people consider JTables.

The issue here is that the table model used by the JTable is actually the data structure
represented by the JTable. However possible because it is called a TableModel or
possibly because it appears to hold other data many people can be confused about the
role of the TableModel and the role of an MVC Model. The key to understanding where
each one fits is to replace the term “table model” with term “data structure”. What we are
talking about then is the data structure used by the JTable to determine what to display
7
(remember the cell renderers determine how to display the data). What we are talking
about then is an MVC model supplying a data structure to a view that should then use
that data to update, modified or create a table model (which is a swing specific data
structure). Thus this is exactly the same concept as supplying a string from a model to a
view – we supply data for the table that the view then wraps up as required by the swing
component.

The interaction between a view containing a JTable and the model that supplies its data
can therefore be:

1. Model notified view of a change in its state.
2. View obtains any basic data form the model.
3. View obtains the “raw” data for the table.
4. View wraps the raw data up in an appropriate table model.
5. View updates the graphical elements including the JTable.

Format of Model supplied data
An interesting aspect to consider is “what is the format of the data supplied by the
model to the view”? This data should of course be independent of any swing features.
However, at the end of the day either the view will need to extract information form the
“raw” data such that it can produce discreet data elements for presentation to cell
renderers or the “raw” data needs to take into account the appropriate structure of the
data that will eventually be required. That is, when the cell renderer receives the object
to be displayed it must be able to obtain the data to display, in an appropriate form, and
must be able to determine how to display that data when it is selected and when it is in
focus. This means that if the element is an object that provides an entry in a
bibliography then it must be possible to obtain the title, or the author or the actual
publication etc.

The MVC framework
The MVC framework has been talked about in previous columns, we shall therefore
merely present a class diagram at this stage (see Figure 6). The point to note is that the
View interface specifies a method (modelChanged(ModelEvent event)) that all views
must implement so that they can be notified of any changes in the models that they are
interested in. In turn a model must create a ModelEvent to notify a view that a change
has occurred. Both the linking behaviour and the notification behaviour are implemented
by the Abstract classes in the framework and thus can just be used by the concrete
classes (such as CitationModel and CitationView in the next section).
8

Figure 6: The MVC framework


A simple example
In this example we will create a table of bibliographic citations. Each citation contains
the author, the title, the publisher and the data of publication. However, citations are not
what the table displays. The table displays citation elements (in a cells) and each row in
the table is represented by an array of CitationElements. Thus the model only knows
about Citations and the JTable cell renderer only knows about CitationElements.
However the CitationElements are lightweight wrappers around the “raw” citation
objects. In addition as the cell renderer used to render the HTML is only used by the
CitationView with the JTable, the BibCellRenderer class is also an inner class of the
CitationView. This is illustrated in Figure 7.

For the purposes of this example the citation class is very simple, but the title and
publisher are presented in HTML to provide some visual interest.

9

Figure 7: The Citation MVC triad and Citation class

Notice that the NonEditableTableModel class is an inner class of the CitationView class,
thus it is only available to the View and is not used anywhere else. Also notice that the
model knows nothing about any swing component, instead it merely provides an array
of Citations to the view. Also notice that it is the view the wraps the appropriate objects
around the “raw” data to enable the swing components to be provided with the data they
require in the appropriate format they require. Again these wrappers (the
CitationElements) are inner classes of the CitationView class.

The following sequence illustrates the effect of a notification of change of state between
the model and the view.

1. User enters data into the text fields at the top of the screen
2. User clicks on submit
3. Event handler in BibFrameView receives notification of event, extracts data from
text fields and passes data to BibFrameController adCitation method.
4. BibFrameController.addCitation method creates a citation object and calls
CitationController.addCitation method
5. The CitationController passes the citation to the model via the
Citationmodel.addCitation method.
6. The Citationmodel adds the new citation to its array list of citations.
10
7. The CitationModel
notifies
any interested views that it has changed state (via
the ModelEvent object and the call to notifyChanged).
8. The View receivesnotification of the change in the models state (via the call to
modelChanged).
9. The CitationView obtains the current data form the CitationModel. Removes the
rows in the current table. Wraps CitationElements around each citation and
updates the JTable’s model. This notifies the JTable to refresh itself.
10. The JTable is refreshed and the new entry displayed.

The end application is illustrated in Figure 8. To mimic changes occur to the MVC
model, the user can input a new citation that is added to the MVC Model. The MVC
Model then notifies the view of a change in its data and the update of the display
follows.


Figure 8: The working application
The complete class diagram for this application is presented in *. This illustrates the
Main class (that initiates the application) as well as the BibFrame MVC that holds the
11
top level controller and frame view and creates the new citation detaisl form above the
citation details table.


Figure 9: The application architecture

Summary

It is easy when using the MVC framework (or indeed the h-MVC) framework to be
unclear about the role of the TableModel object when using a JTable component.
However, in reality there is a clear division of labour between the MVC model and the
View that dictate when the TabelModel should reside and what the MVC model should
provide to the view (containing the JTable). This can greatly simpligy the construction of
the MVC Model and of the views’ components.

Listings

Listing 1: Main class
package citation;

public class Main {
/** @link dependency */
/*#BibFrameController lnkBibFrameController;*/
public static void main(String [] args) {
new BibFrameController();
}
}
12
Listing 2: The BibFrameController class
package citation;

import mvc.*;

public class BibFrameController extends AbstractController {
/** @link dependency */
/*#BibFrameView lnkBibFrameView;*/
private CitationController citationController;
public BibFrameController() {
setView(new BibFrameView(this));
citationController = new CitationController();

((BibFrameView)getView()).setCitationView((CitationView)citationController.getView());
((JFrameView)getView()).pack();
((JFrameView)getView()).setVisible(true);
}

public void submit(String author, String title, String publisher, String date) {
Citation c = new Citation(author, title, publisher, date);
citationController.addCitation(c);
}
}

Listing 3: The BibFrameView class
package citation;

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

import mvc.*;

public class BibFrameView extends JFrameView {
private Container contentPane;
private JTextField author = new JTextField(4);
private JTextField title= new JTextField(6);
private JTextField publisher = new JTextField(5);
private JTextField date = new JTextField(3);
private JButton submit = new JButton("Submit");
public BibFrameView(BibFrameController cont) {
super(cont);
// Set the close operation to do nothing, we handle it our selves
setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
setTitle("Bibliography Viewer");
contentPane = getContentPane();
13
initDisplay();
}
public void setCitationView(JPanelView view) {
contentPane.add(view);
}
public void modelChanged(ModelEvent event) {}
private void initDisplay() {
JPanel p = new JPanel();
p.add(new JLabel("Author: "));
p.add(author);
p.add(new JLabel("Title: "));
p.add(title);
p.add(new JLabel("Publisher: "));
p.add(publisher);
p.add(new JLabel("Date: "));
p.add(date);
p.add(submit);
submit.addActionListener(new SubmitHandler());
contentPane.add(p, "North");
}
// Inner class for event handling
class SubmitHandler implements ActionListener {
public void actionPerformed(ActionEvent e) {
((BibFrameController)getController()).submit(
author.getText(),
title.getText(),
publisher.getText(),
date.getText());
}
}
}

Listing 4: The Citation class
package citation;

public class Citation {
private String author;
private String title;
private String publisher;
private String date;

public Citation(String author, String title, String pub, String date) {
this.author = author;
this.title = title;
this.publisher = pub;
this.date = date;
14
}

public String getAuthor() {
return author;
}

public String getPublisher() {
return "<html><body><i>" + publisher + "</i></body></html>";
}

public String getTitle() {
return "<html><body><b>" + title + "</b></body></html>";
}

public String getDate() {
return date;
}
}

Listing 5: The CitationController class
package citation;

import mvc.AbstractController;
public class CitationController extends AbstractController {

public CitationController() {
setModel(new CitationModel());
setView(new CitationView((CitationModel)getModel(),
this));
}
public void addCitation(Citation citation) {
((CitationModel)getModel()).addCitation(citation);
}
}

Listing 6: The CitationModel class
package citation;

import mvc.*;
import java.util.*;

public class CitationModel extends AbstractModel {
private ArrayList citations = new ArrayList();

public Citation [] getTableData() {
Citation [] cits = new Citation[citations.size()];
15
return (Citation [])citations.toArray(cits);
}

public void addCitation(Citation cit) {
citations.add(cit);
ModelEvent me = new ModelEvent(this, 1, "citation update");
notifyChanged(me);
}
}

Listing 7: The CitationView class
package citation;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.table.*;
import mvc.*;

public class CitationView extends JPanelView {
private JTable table;

public CitationView(CitationModel model, CitationController controller) {
super(model, controller);
//create the table
Citation[][] rowData = new Citation[1][4];
String[] titles = {"Author", "Title", "Publisher", "Date"};
table = new JTable( new NonEditableTableModel(rowData,titles) ) ;
table.setAutoResizeMode( JTable.AUTO_RESIZE_NEXT_COLUMN );
table.sizeColumnsToFit( -1 );
// Set up the cell renderer
BibCellRenderer bibCellRenderer = new BibCellRenderer();
TableColumnModel tcModel = table.getColumnModel() ;
TableColumn column = null ;
for( int i = 0 ; i < 4 ; i++ ){
column = tcModel.getColumn( i ) ;
column.setCellRenderer( bibCellRenderer ) ;
}
//create the scroll pane
JScrollPane scrollPane = new JScrollPane(table ) ;
this.add( scrollPane) ;
}

// Now implement the necessary event handling code
public void modelChanged(ModelEvent event) {
CitationModel m = (CitationModel)getModel();
16
Citation [] rows = m.getTableData();

((NonEditableTableModel)table.getModel()).removeAllRows() ;

CitationElement [] elements;
for( int i = 0 ; i < rows.length ; i++ ){
elements = getCitationElements(rows[i]);
((NonEditableTableModel)table.getModel()).addRow( elements ) ;
}
}

private CitationElement [] getCitationElements(Citation citation) {
CitationElement [] elements = new CitationElement[4];
elements[0] = new CitationElement(citation.getAuthor());
elements[1] = new CitationElement(citation.getTitle());
elements[2] = new CitationElement(citation.getPublisher());
elements[3] = new CitationElement(citation.getDate());
return elements;
}

//==================== Inner classes ===============================

class CitationElement {
private String value;
public CitationElement(String value) {
this.value = value;
}
public String toString() {
return getValue();
}
public String getValue() {
return value;
}
}

/**
* Extends DefaultTableModel, overrides isCellEditable() to always
* return false, to provide a table model which the user cannot
* edit interactively.
*/
class NonEditableTableModel extends DefaultTableModel{

NonEditableTableModel( Object[][] data, Object[] titles ){
super( data, titles ) ;
}

17
/**
* Override the isCellEditable() method so that it always returns false,
* ensuring that the user cannot interactively edit the contents of
* the specified cell.
*/
public boolean isCellEditable( int row, int col){
return false ;
}

/**
* Remove the existing rows from the table - called when a new page of
* data is obtained, so that the rows of the new page can be added. Note
* that the rows are removed, rather than calling setValue() on each
* cell, because there may not be the full number of rows in the new
* page.
*/
protected void removeAllRows(){
int rowCount = getRowCount() ;
if( rowCount > 0 ){
for( int i = rowCount ; i > 0 ; i-- ){
super.removeRow( i -1) ;
}
}
}
}

class BibCellRenderer extends JLabel implements TableCellRenderer {
public Component getTableCellRendererComponent(JTable table,
Object value,
boolean isSelected,
boolean hasFocus,
int row,
int column) {
CitationElement element = (CitationElement)value;

JLabel label = new JLabel();
if (isSelected) {
label.setBackground(Color.blue);
}
if (element != null) {
label.setText(element.getValue());
}
return label;
}
}
}