Table Controls

Sun wanted to decouple the look and feel of a control from its function (to fire such and such an event). They employed the Model-View-Controller pattern to solve this problem. Every GUI control has three parts: model, view, and control. The view is the look and feel of the GUI control, the control is the event firing mechanism, and the model is the

Normally we can ignore these details, but in Swing's more advanced controls the details are important.

The best example is Swing's table control.

In this example a primitive spread sheet allows users to enter two numbers in the first two columns of a table row. The last four columns display the sum., product, difference, and quotient of these two numbers:

 

The Design

Main

The imports

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

The Model (TableCalculator.java)

We begin be defining the table control's model. This component contains the table's data (a 2D array) and the table's logic: getValueAt, setValueAt, etc:

class TableCalculator extends AbstractTableModel {
   private static final long serialVersionUID = 1L;

    private String[] columnNames = {
         "Input 1",
           "Input 2",
           "Sum",
           "Product",
           "Difference",
           "Quotient"};

    private Double[][] data = {
         {10.0, 2.0, 12.0, 20.0, 8.0, 5.0},
         {100.0, 20.0, 120.0, 2000.0, 80.0, 5.0},
         {1.0, 1.0, 2.0, 1.0, 0.0, 1.0},
         {25.0, 5.0, 30.0, 5.0, 20.0, 5.0}
   };

   public String[] getColumnNames() { return columnNames; }

    public int getColumnCount() {
        return columnNames.length;
    }

    public int getRowCount() {
        return data.length;
    }

    public Class getColumnClass(int c) {
        return getValueAt(0, c).getClass();
    }

    public String getColumnName(int col) {
        return columnNames[col];
    }

    public Object getValueAt(int row, int col) {
        return data[row][col];
    }

    public boolean isCellEditable(int row, int col) {
        return (col < 2);
    }

    public void setValueAt(Object value, int row, int col) {
        data[row][col] = (Double)value;
        fireTableCellUpdated(row, col);
    }
}

Note that changing a cell fires a "cell updated" event.

The Table Component (TablePanel.java)

JTable is the view of the table model. The JTable is contained in a JPanel. The panel implements the TableModelListener. In effect, it is the control. It is notified when a cell has been edited. It responds by updating the arithmetic cells.

public class TablePanel extends JPanel implements TableModelListener {

   private static final long serialVersionUID = 1L;

   private JTable table;

   public TablePanel(TableCalculator model) {
      model.addTableModelListener(this);
      table = new JTable(model);
      table.setPreferredScrollableViewportSize(new Dimension(500, 70));
      setLayout(new BorderLayout());
      add(table.getTableHeader(), BorderLayout.PAGE_START);
      add(table, BorderLayout.CENTER);
   }

   public void tableChanged(TableModelEvent e) {
        int row = e.getFirstRow();
        int column = e.getColumn();
        if (column < 2) {
        TableCalculator model = (TableCalculator)e.getSource();
        double arg1 = (Double)model.getValueAt(row, column);
        double arg2 = (Double)model.getValueAt(row, (column + 1) % 2);

        model.setValueAt(new Double(arg1 + arg2), row, 2);
        model.setValueAt(new Double(arg1 * arg2), row, 3);
        model.setValueAt(new Double(arg1 - arg2), row, 4);
        model.setValueAt(new Double(arg1 / arg2), row, 5);
   }
}

The Viewer (TableGUI.java)

public class TableGUI {
   public static void main(String[] args) {
     JFrame frame = new JFrame();
     frame.setTitle("Table Calculator");
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
     TableCalculator model = new TableCalculator();
     TablePanel panel = new TablePanel(model);

     frame.add(panel);
     frame.pack(); // shrink wrap the frame to the table
     frame.setVisible(true);
   }
}