Home > Articles > Programming > Java

  • Print
  • + Share This
Like this article? We recommend

Beyond the Basics

The first problem with this little model is that it does not display informative column names. To correct this problem, you need to overload one of the methods that is already defined in the AbstractTableModel. Depending upon the situation, the names of the columns can be stored in one of many ways. In this simple example, they are held in a string array:

private String[] columns={"Name", "Value", 
  "Location", "Quantity"};

Now override the method to retrieve the names already defined in the AbstractTableModel. At the same time, you can update the getColumnCount method to be slightly more dynamic:

private int getColumnCount() {
  return columns.length;
}

private String getColumnName(int col) {
  return columns[col];
}

Now the column names are displayed based on the string array that was defined. There are a large number of ways to handle the column names, including using locale-specific resource bundles. However, this simple example demonstrates how they are controlled.

The next problem in this example is one of data. How do we get the data in? And (just as important) how do we get it back out? To get the data into the table model, I normally create two constructors and two methods. Let's start by detailing the constructors:

  public MyTableModel() {
   
  }

  public MyTableModel(List l) {
   datalist.addAll(l);
  }  

The first constructor is an override to the default. Quite simply, this creates the table model with an empty list of data. This constructor is helpful for when you will add data to the model incrementally (as you will see below).

The second constructor passes in a predefined java.util.List of the data. Normally, thanks to Java's pass by reference design, the data does not actually have to be in this array when it is passed in. However, instead of just referencing this list, I am actually taking the data from the passed-in list and adding it into the existing array list. The reason for this is simple. It prevents me from being lazy and holding on to a reference to the list outside of the table model. By handling the constructor in this manner I am forced to treat our table model as the "single source of truth".

The other way that I normally add data to a table model takes a bit more explaining. So first, let's see the code:

  public void addWidget(Widget w) {
   datalist.add(w);
   fireTableDataChanged();
  }
  
  public void addWidgetList(List l) {
   datalist.addAll(l);
   fireTableDataChanged();
  }

The first method accepts a single Widget instance as its parameter. You take this Widget and add it to your data list. After that is done, you need to notify the listeners (most importantly, the JTable, which is our view) that the data has changed and that they may need to react to it. Fortunately, the AbstractTableModel handles all the listener business for us. Therefore, we call the private method fireTableDataChanged(), and the parent will notify all the listeners.

The second method accepts a java.util.List reference. Similar to the constructor defined above, you take the data from this list and add it to the existing array list. After that is complete, the parent class once again notifies all the listeners that the data has changed.

That takes care of putting data into the table model. Now we have to define how to retrieve Widgets from the model. If you want to avoid writing any more code, you could just use the getValueAt method already defined. However, using this method would involve a lot of casting outside of the table model as well as remembering what data is in what column. We definitely want to avoid writing code that is clunky and fragile. So you can add a method that simply retrieves data from the table model:

  public Widget getWidgetAt(int row) {
   return (Widget)datalist.get(row);
  }  

Why should you not allow code outside of the model to iterate through the dataset? This would certainly be easy to do, but allowing the users of the table model to iterate through the data allows them to remove items from the data set without you knowing about it. If a user were to remove a Widget from the list, the table model and associated views would be out of sync and could potentially cause problems. Therefore, you force the users to go through you for all data manipulation.

Unfortunately, we cannot prevent users from modifying the Widget data without negatively impacting the simplicity of our model. If you run into a situation in which you must absolutely allow the data inside of a Widget to be changed outside of the table model, you could add a method to allow the outside caller to request that the listeners be updated. Although this can be done, it is not recommended.

The final method added to this table model allows the users to remove a row from the table model. This is a "MVC safe" way to remove a row and make sure that everyone stays in sync:

  public Widget removeWidgetAt(int row) {
   return (Widget)datalist.remove(row);
   fireTableDataChanged();
  }

This simple method serves two purposes: It removes the Widget from the datalist and it returns the removed Widget back to the caller. The method also informs the listeners about the change. By returning the Widget back to the caller, they can retain a reference to this removed Widget if they so choose. This is especially useful when the Widget is being moved from one table to another.

  • + Share This
  • 🔖 Save To Your Account