Java Tips Weblog

  • Blog Stats

    • 2,571,820 hits
  • Categories

  • Archives

Row Table Model

Posted by Rob Camick on November 21, 2008

The DefaultTableModel is a good general purpose model that allows great flexibility. It provides dynamic functionality when dealing with rows and columns. However, it has been my experience that most applications tend to deal with rows of data and dynamic rows more than column data and dynamic columns. So maybe if we remove the column functionality and improve the row functionality we can create a better general purpose table model.

A Common requirement is to display a business object in a JTable. This fits perfectly with a row based TableModel as the business object would be represented by a row in the model. To demonstrate the creation of a row based TableModel lets use a Person object. To keep it simple our Person object will only have 3 properties, each containing appropriate getter and setter methods:

public class Person
{
	String firstName;
	String lastName;
	Date birthDate;

	public String getFirstName()
	{
		return firstName;
	}

	public void setFirstName(String firstName)
	{
		this.firstName = firstName;
	}

	public String getLastName()
	{
		return lastName;
	}

	public void setLastName(String lastName)
	{
		this.lastName = lastName;
	}

	public Date getBirthDate()
	{
		return birthDate;
	}

	public void setBirthDate(Date birthDate)
	{
		this.birthDate = birthDate;
	}
}

When creating a custom TableModel you extend AbstractTableModel to

  • provide storage for data you add to the model
  • implement the TableModel interface to access this data

To start our implementation of a PersonTableModel we will implement the methods that are dependent on the data structures used to store the data. The implementation of these methods will not change from model to model.

public class PersonTableModel extends AbstractTableModel
{
	private String[] columnNames =
	{
		"First Name",
		"Last Name",
		"Birth Date"
	};

	private List<Person> persons;

	public PersonTableModel()
	{
		persons = new ArrayList<Person>();
	}

	public PersonTableModel(List<Person> persons)
	{
		this.persons = persons;
	}

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

	@Override
	public String getColumnName(int column)
	{
		return columnNames[column];
	}

	@Override
	public int getRowCount()
	{
		return persons.size();
	}

}


Next we will implement the methods that are dependent on the properties of the business object you wish to display.

@Override
public Class getColumnClass(int column)
{
	switch (column)
	{
		case 2: return Date.class;
		default: return String.class;
	}
}

@Override
public boolean isCellEditable(int row, int column)
{
	switch (column)
	{
		case 2: return true; // only the birth date is editable
		default: return false;
	}
}

@Override
public Object getValueAt(int row, int column)
{
	Person person = getPerson(row);

	switch (column)
	{
		case 0: return person.getFirstName();
		case 1: return person.getLastName();
		case 2: return person.getBirthDate();
		default: return null;
	}
}

@Override
public void setValueAt(Object value, int row, int column)
{
	Person person = getPerson(row);

	switch (column)
	{
		case 0: person.setFirstName((String)value); break;
		case 1: person.setLastName((String)value); break;
		case 2: person.setBirthDate((Date)value); break;
	}

	fireTableCellUpdated(row, column);
}

public Person getPerson(int row)
{
	return persons.get( row );
}


The TableModel interface has been implemented, so now we can add additional row based methods for increased functionality. Lets add the ability to dynamically add a Person to the model:

public void addPerson(Person person)
{
	insertPerson(getRowCount(), person);
}

public void insertPerson(int row, Person person)
{
	persons.add(row, person);
	fireTableRowsInserted(row, row);
}


Finally, we may want to be able to remove a Person from the model

public void removePerson(int row)
{
	persons.remove(row);
	fireTableRowsDeleted(row, row);
}


Put all these methods together and you have a simple row based TableModel. This model can be used as a template for creating a TableModel of any business object.

Or, instead of manually creating a TableModel from scratch every time, you could take advantage of Java generics to do most of the work for you. For this you can use the RowTableModel. The RowTableModel will implement all the functionality demonstrated above in addition to providing added functionality to make customization of the model easier.

The RowTableModel was designed to replace the DefaultTableModel as the base table model for tables that need to access data as rows of data. It will provide the storage for the data as well as the methods to access the data. It will also provide some convenience methods to make customizing the model easier.

The main functionality added is the ability to deal with rows of data easier. You don’t lose the cell related functionality, you just gain row level features. The row related methods are as follows:

  • addRow – convenience method that invokes insertRow to add a row Object to the end of the model
  • getRow – return the row Object at the specified row
  • getRowsAsArray – return the specified rows in an Array of the proper type
  • getRowsAsList – return the specified rows in a List of the proper type
  • insertRow – insert a row Object at the specified row in the model
  • insertRows – insert a List of row Objects at the specified row in the model
  • moveRow – move a range of row Objects to the specified location in the model
  • removeRowRange – remove a range of row Objects from the model
  • removeRows – remove 1 or more row Objects from the model
  • replaceRow – update the row with a new row Object

Now that we no longer need to worry about dynamic changes to the columns, we can add a few convenience methods to the model that will allow us to easily customize the behavour of the model.

  • setColumnClass – specify the class of individual columns
  • setModelEditable – specify editable property for the entire model.
  • setColumnEditable – specify editable property at a column level. This property will have preference over the model editable property.

The RowTableModel is an abstract class. It does not implement the getValueAt() or setValueAt() methods of the TableModel. This will allow you to extend the model to support row Objects of any type in the model. You will need to extend this class to implement these methods for your particular row Object type. For a simple example of how this might be done see the JButtonTableModel example code found below.

In my next couple of entries I will provide two concrete implementations. First, for rows of Lists, which can be used to replace the DefaultTableModel. The other will be a generic implementation for any bean Object.

So now you have options. You can:

  • provide a complete implementation of a TableModel for your business object
  • use the RowTableModel to provide a generic implementation, while only implementing a couple of methods.

The choice is yours.

Get The Code

RowTableModel.java
JButtonTableModel.java

See Also

List Table Model
Bean Table Model

Related Reading

Creating a Table Model

14 Responses to “Row Table Model”

  1. rhuanca said

    Very good, this is very good.

  2. henrik said

    fantastic, thank you!!

  3. Jörg said

    Hello Rob,

    just a note of two typos:
    1. The last sentence of the description here: “A custom implementation will always be more efficient THAN …”

    2. The last sentence of the initial text in the source code: “methods either indirectly, … , or DIRECTLY.”

    Greetings

    Jörg

  4. dm1try said

    Thanks)
    you halp me…

  5. Thank you so much :).

  6. Sultan said

    Hello Rob, i have an ArrayList cointaining Orders, an order contains a String and a double value, how can i use your Row table model to make a table and display the objects in my ArrayList?

    • Rob Camick said

      You need to extend the RowTableModel and implement a couple of methods. Look at the JButtonTableModel.java example code that I provided for you to give you an idea how to do this.

  7. Sherebyah Tisbi said

    This is excellent piece of code Rob. I have class which doesn’t have get/set of properties but have a custom annotation Visible true/false and out of 7 properties 4 are visible true. I have my own TableModel extended from AbstractTableModel. I am preparing ArrayList of column names thru reflection and only creating columns which are visible true. But when I see the getColumnCount it returns 7 means all properties are taken up as columns. Dont understand how to fix this. Below is my SermonTableModel

    package com.tishbi.ui;

    import java.lang.reflect.Field;
    import java.util.ArrayList;

    import javax.swing.JCheckBox;
    import javax.swing.table.AbstractTableModel;
    import javax.swing.table.TableColumnModel;

    import com.tishbi.sitecontent.Sermon;
    import com.tishbi.sitecontent.TableMappings;
    import com.tishbi.sitecontent.AllEnums.DownloadStatus;
    import com.tishbi.util.Utilities;

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

    private ArrayList sermons;
    private ArrayList columns;
    Field[] properties = Sermon.class.getFields();

    public SermonTableModel(ArrayList _sermons, ArrayList _columns)
    {
    CollectColumns();
    this.sermons = _sermons;
    this.columns = _columns;
    }

    @Override
    public int getRowCount()
    {
    return sermons.size();
    }

    @Override
    public String getColumnName(int index)
    {
    String colname = null;
    if (index<columns.size())
    colname = columns.get(index);
    if (colname==null)
    colname = super.getColumnName(index);
    return colname;
    }

    @Override
    public Object getValueAt(int row, int col)
    {
    Sermon _sermon = sermons.get(row);
    switch(col)
    {
    case 0: return _sermon.Title;
    case 1: return _sermon.Length;
    case 2:
    {
    if (_sermon.Status==DownloadStatus.NOT_STARTED)
    return "0%";
    }
    case 3: return _sermon.isSermonSelected;
    default: return null;
    }
    }

    public Sermon getValueAt(int row)
    {
    return sermons.get(row);
    }

    public void setValueAt(Object obj, int row, int col)
    {
    if (col != 3) return;
    Sermon _sermon = sermons.get(row);
    _sermon.isSermonSelected = (boolean) obj;
    fireTableDataChanged();
    }

    public void setValueAt(Sermon sermon, int row)
    {
    sermons.remove(row);
    sermons.add(row, sermon);
    fireTableDataChanged();
    }

    @Override
    public Class getColumnClass(int index)
    {
    Class columnclass = null;
    if (index<columns.size())
    columnclass= getValueAt(0,index).getClass();

    if (columnclass == null)
    columnclass= super.getColumnClass(index);
    return columnclass;
    }

    @Override
    public boolean isCellEditable(int row, int col)
    {
    if (col ==3)
    return true;
    else
    return false;
    }

    public ArrayList getSermons()
    {
    return sermons;
    }

    @Override
    public int getColumnCount()
    {
    Field[] cols = Sermon.class.getFields();
    return cols.length;
    }

    public void addRow(Sermon _sermon)
    {
    sermons.add(_sermon);
    }

    private void CollectColumns()
    {
    columns = new ArrayList();
    for (Field f : properties)
    {
    TableMappings fieldAttributes = Utilities.MetaData(f);
    if (fieldAttributes.Visible())
    {
    columns.add(fieldAttributes.Header());
    }
    }
    fireTableStructureChanged();
    }
    }

    Will truly appreciate your help on this.

    • Rob Camick said

      This is a blog, not a forum. This question has nothing to do with the RowTableModel. I suggest you post your question in a proper forum for help. Also, I doubt people will be able to help you since you are using many custom classes. Question on a forum should include a SSCCE with the question so people can execute your code.

  8. Anonymous said

    Does the constructor update the table?

        public PersonTableModel(List persons)
        {
            this.persons = persons;
        }
    

    Shouldn’t there any “fire-methods” be? Like fireTableDataChanged() ???

    • Rob Camick said

      When you create a TableModel it hasn’t been added to a JTable yet so there is no need to invoke that method since there is no table to notify of changes to the data.

      When you create the table using the model or you use the setModel(…) method of the table, the appropriate event will be generated.

Leave a comment