Chapter 32. The JTable Class

The JTable class is much like the JList class in that you can very easily program it to do simple things. Similarly, to do more sophisticated things, you can create a class derived from the AbstractTableModel class to hold your data.

A Simple JTable Program

In the simplest program, you can create a rectangular array of objects and use it in the constructor for the JTable. You can also include an array of strings to be used as column labels for the table.

public class SimpleTable extends JxFrame {
    public SimpleTable() {
        super("Simple table");
        JPanel jp = new JPanel();
        getContentPane().add(jp);
        Object[] [] musicData = {
            {"Tschaikovsky", "1812 Overture", new Boolean(true)},
            {"Stravinsky", "Le Sacre", new Boolean(true)}
            {"Lennon", "Eleanor Rigby", new Boolean(false)}
            {"Wagner", "Gotterdammerung", new Boolean (true)}
        };
        String[] columnNames = {"Composer", "Title",
                "Orchestral"};
        JTable table   = new JTable(musicData, columnNames);
        JScrollPane sp = new JScrollPane(table);
        table.setPreferredScrollableViewportSize(
                new Dimension(250,170));
        jp.add(sp);

        setSize(300,200);
        setVisible(true);
    }

This produces the table displayed in Figure 32.1.

A simple table display.

Figure 32.1. A simple table display.

This table has all cells editable and displays all of the cells using the toString method of each object. Like the JList interface, this interface to JTable creates a data model object under the covers. To produce a more flexible display, you need to create that data model yourself.

You can create a table model by extending the AbstractTableModel class. All of the methods have default values and operations, except the following three, which you must provide:

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

However, you can gain a good deal more control by overriding a couple of other methods. For example, you can use the method

public boolean isCellEditable(int row, int col)

to protect some cells from being edited. To allow editing of some cells, you must override the implementation for the method

public void setValueAt(Object obj, int row, int col)

Further, by adding a method that returns the data class of each object to be displayed, you can use some default cell-formatting behavior. The JTable's default cell renderer displays the following:

  • Numbers as right-aligned labels

  • ImageIcons as centered labels

  • Booleans as check boxes

  • Objects using their toString methods

To change this, you simply need to return the class of the objects in each column.

    publicClass getColumnClass( int col) {
        return getValueAt(0, col).getClass();
    }

Our complete table model class creates exactly the same array and table column captions as previously and implements the methods we just mentioned.

public class MusicModel extends AbstractTableModel {

    String[] columnNames = {"Composer", "Title", "Orchestral"};

    Object[] [] musicData = {
        {"Tschaikovsky", "1812 Overture", new Boolean (true)},
        {"Stravinsky", "Le Sacre", new Boolean(true)},
        {"Lennon", "Eleanor Rigby", new Boolean (false)},
        {"Wagner", "Gotterdammerung", new Boolean(true)}
    };
    private int rowCount, columnCount;
    //--------------
    public MusicModel(int rowCnt, int colCnt) {
        rowCount = rowCnt;
        columnCount = colCnt;
    }
    //--------------
    public String getColumnName(int col) {
        return columnNames[col];
    }
    //--------------
    public int getRowCount() {
        return rowCount;
    }
    public int getColumnCount() {
        return columnCount;
    }
    //--------------
    public class getColumnClass(int col) {
        return getValueAt(0, col).getClass();
    }
    //--------------
    public boolean isCellEditable(int row, int col) {
        return(col > 1);
    }
    //--------------
    public void setValueAt(Object obj, int row, int col) {
        musicData[row][col] = obj;
        fireTableCellUpdated(row, col);
    }
    //--------------
    public Object getValueAt(int row, int col) {
        return musicData[row][col];
    }
}

The main program becomes the code called in the constructor when the class is instantiated from main.

public class ModelTable extends JxFrame {
    public ModelTable() {
        super("Simple table");
        JPanel jp = new JPanel();
        getContentPane().add(jp);
        JTable table = new JTable(new MusicModel(4, 3));
        JScrollPane sp = new JScrollPane(table);
        table.setPreferredScrollableViewportSize(new
                        Dimension(250,170));
        jp.add(sp);
        setSize(300,200);
        setVisible(true);
    }
//--------------
    static public void main(String argv[]) {
        new ModelTable();
    }
}

As Figure 32.2 shows, the boolean column is now automatically rendered as check boxes. You also allowed editing only of the right-most column, by overriding the isCellEditable method to disallow it for columns 0 and 1.

A JTable display using a data model that returns the data type of each column and controls the data display accordingly.

Figure 32.2. A JTable display using a data model that returns the data type of each column and controls the data display accordingly.

Just as the ListModel class does for JList objects, the TableModel class holds and manipulates the data and notifies the JTable whenever it changes. Thus the JTable is an Observer pattern, operating on the TableModel data.

Cell Renderers

Each cell in a table is rendered by a cell renderer. The default renderer is a JLabel, which may be used for all of the data in several columns. Thus these cell renderers can be thought of as Flyweight pattern implementations. The JTable class chooses the renderer according to the object's type, as discussed previously. However, you easily can change to a different renderer, such as one that uses another color, or to another visual interface.

Cell renderers are registered by type of data,

table.setDefaultRenderer(String.class, new ourRenderer());

and each renderer is passed the object, selected mode, row, and column using the only required public method.

public Component getTableCellRendererComponent(JTable jt,
    Object value, boolean isSelected,
    boolean hasFocus, int row, int column)

One common way to implement a cell renderer is to extend the JLabel type, catch each rendering request within the renderer, and return a properly configured JLabel object, usually the renderer itself. The following renderer displays cell (1, 1) in boldface and the remaining cells in plain black type:

public class ourRenderer extends JLabel
    implements TableCellRenderer {
        Font bold, plain;
        public ourRenderer() {
            super();
            setOpaque(true);
            setBackground(Color.white);
            bold  = new Font("SansSerif", Font.BOLD, 12);
            plain = new Font("SansSerif", Font.PLAIN, 12);
            setFont(plain);
        }
        public Component getTableCellRendererComponent(JTable jt,
                Object value, boolean isSelected,
                boolean hasFocus, int row, int column) {
        setText((String)value);
        if (row == 1 && column == 1) {
            setFont(bold);
            setForeground(Color.red);
        } else {
            setFont(plain);
            setForeground(Color.black);
        }
        return this;
    }
}

The results of this rendering are shown in Figure 32.3.

The music data using a CellRenderer that changes the font density and color in row 1, column 1 (Rows and columns are numbered starting at 0).

Figure 32.3. The music data using a CellRenderer that changes the font density and color in row 1, column 1 (Rows and columns are numbered starting at 0).

In this simple cell renderer, the renderer is itself a JLabel that returns a different font but the same object, depending on the row and column. More complex renderers are also possible, renderers in which one of several already instantiated objects is returned, thereby making the renderer a Component Factory pattern.

Rendering Other Kinds of Classes

Now let's suppose that we have more complicated classes that we want to render. Suppose that we have written a document mail system and want to display each document according to its type. However, the documents don't have easily located titles, and we can display them only by author and type. Since we intend that each document could be mailed, we start by creating an interface Mail to describe the properties that the various document types have in common.

public interface Mail {
    public ImageIcon getIcon();
    public String    getLabel();
    public String    getText();
}

Thus each type of document will have a simple label (the author) and a method for getting the full text (which we will not use here). Most important, each document type will have its own icon, which we can obtain with the getIcon method.

For example, the NewMail class would look as follows:

public class NewMail implements Mail {
    private String label;

    public NewMail (String mlabel) {
        label = mlabel;
    }
    public ImageIcon getIcon () {
        return new ImageIcon("images/mail.gif");
    }
    public String getText() {
        return "";
    }
    public String getLabel() {
        return label;
    }
}

The renderer for these types of mail documents gets the icon and label text and uses the DefaultCellRenderer(derived from JLabel) to render them.

public class myRenderer extends DefaultTableCellRenderer {
    private Mediator md;

    public myRenderer(Mediator med) {
        setHorizontalAlignment(JLabel.LEADING);
        med = med;
    }
//--------------
    public Component getTableCellRendererComponent(
            JTable table, Object value, boolean isSelected,
            boolean hasFocus, int row, int col) {
        if (hasFocus) {
            setBackground(Color.lightGray );
            md.tableClicked ();
        } else
            setBackground(new Color(0xffffce));
        if (value != null) {
            Mail ml = (Mail) value;
            String title = ml.getLabel ();
            setText(title);            //set the text
            setIcon(ml.getIcon ());    //and the icon
        }
        return this;
    }
}

Since one of the arguments to the getTableCellRendererComponent method is whether the cell is selected, we have an easy way to return a somewhat different display when the cell is selected. In this case, we return a gray background instead of a white one. We also could set a different border if we wanted. Figure 32.4 shows a display of the program. Note that the selected cell is shown with a gray background.

Rendering objects that implement the Mail interface, using their icon and text properties.

Figure 32.4. Rendering objects that implement the Mail interface, using their icon and text properties.

Selecting Cells in a Table

We can select a row, separated rows, a contiguous set of rows, or single cells of a table, depending on the list selection mode.

In the following example, we want to be able to select single cells, so we choose the single selection mode:

setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

If we want to take a certain action when a table cell is selected, we get the ListSelectionModel from the table and add a list selection listener to it.

ListSelectModel lsm = getSelectionModel();
lsm.addListSelectionListener (new TableSelectionListener(med));

We also create a TableSelectionListener class that implements the valueChanged method and passes this information to a Mediator class.

    public class TableSelectionListener implements
    ListSelectionListener {

        Private Mediator md;
    public TableSelectionListener(Mediator med) {
        md = med;
    }
    public void valueChanged(ListSelectionEvent e) {
        ListSelectionModel lsm =
            (ListSelectionModel)e.getSource();
        if( ! lsm.isSelectionEmpty ())
            md.tableClicked();
    }
}

The Mediator class, as discussed previously, is the only class that deals with interactions between separate visual objects. In this case, it fetches the correct label for this class and displays it in the text field at the top of the window, as shown in Figure 32.4. Here is the entire Mediator class:

public class Mediator {
    Ftable         ftable;
    JTextField     txt;
    int            tableRow, tableColumn;
    FolderModel    fmodel;

    public Mediator ( JTextField tx) {
        txt = tx;
    }
    public void setTable(Ftable tbl) {
        ftable = tbl;
    }
    //--------------
    public void tableClicked() {
        int row = ftable.getSelectedRow ();
        int col = ftable.getSelectedColumn ();
//don't refresh if cell not changed
        if ((row != tableRow) || (col != tableColumn)) {
            tableRow = row;
            tableColumn = col;
            fmodel = ftable.getTableModel ();
            Mail ml = fmodel.getDoc(row, col);
            txt.setText (ml.getLabel ());
        }
    }
}

Patterns Used in This Image Table

The UML diagram in Figure 32.5 shows the use of several common design patterns in the program in Figure 32.4. As we noted earlier, the separation of the data from the viewer in the Swing JTable is an Observer pattern. By catching the table selection in a listener and then sending the result to a Mediator class, we are implementing the Mediator pattern. Finally, the three mail classes, each producing a different icon, comprise a Factory Method pattern. These patterns occur throughout Java programming, especially when you use Swing. You'll find it extremely helpful to recognize how often you can refer to patterns in your everyday programming to simplify communication between you and your colleagues.

A UML diagram of the classes in the Image table.

Figure 32.5. A UML diagram of the classes in the Image table.

Programs on the CD-ROM

Program Description

SwingTableSimpleTableSimpleTable.java

Creates a simple table from a two-dimensional String array.

SwingTableRenderTableModelTable.java

Creates a table using a TableModel, allowing the boolean column to be displayed as check boxes.

SwingTableRenderTableRenderTable.java

Creates a simple table using TableModel and a cell renderer that renders one cell in bold red.

SwingTableImageTableMailTable.java

Creates a set of mail images in a 3 × 2 table.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset