2005-08-03
| Table of Contents: |
| Rate This Article: | Add This Article To: |
( Page 3 of 3 )
The Model
Because the models for JTree, JList, and JTable are interfaces, I can construct a single model design.
As can be seen from the code, I extended the AbstractTableModel and implemented both the TreeModel interface and the ListModel interface.
The reason that I extended the AbstractTableModel was for the sake of brevity. There are quite a few methods in the TableModel interface; by using the abstract, I could avoid implementing some of them.
Since Java only allows single inheritance, I implemented the interfaces for the other two models.
Below is the code for the model itself:
package com.zarrastudios.swingobjects;
import java.util.ArrayList;
import javax.swing.ListModel;
import javax.swing.event.ListDataListener;
import javax.swing.event.TreeModelListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
public class ExampleModel extends AbstractTableModel
implements ListModel, TreeModel, DataChangeListener {
private ArrayList data;
private ArrayList listListeners;
private ArrayList treeListeners;
private String columnNames[] = {"Name", "Address", "City", "State", "Postal"};
private DefaultMutableTreeNode rootNode;
public ExampleModel() {
listListeners = new ArrayList();
treeListeners = new ArrayList();
rootNode = new DefaultMutableTreeNode();
//build some test data
data = new ArrayList();
for (int i = 0; i < 10; i++) {
ExampleDataObject edo = new ExampleDataObject();
edo.setName("Test " + i);
edo.setAddress1("Address " + i);
edo.setCity("City " + i);
edo.setState("State " + i);
edo.setPostal("Postal " + i);
data.add(edo);
edo.addDataListener(this);
}
}
public int getColumnCount() {
return columnNames.length;
}
public int getRowCount() {
return data.size();
}
public Object getValueAt(int row, int col) {
ExampleDataObject edo = (ExampleDataObject)data.get(row);
switch (col) {
case 0:
return edo.getName();
case 1:
return edo.getAddress1();
case 2:
return edo.getCity();
case 3:
return edo.getState();
case 4:
return edo.getPostal();
default:
return null;
}
}
public int getSize() {
return data.size();
}
public Object getElementAt(int row) {
return data.get(row).toString();
}
public void addListDataListener(ListDataListener ldl) {
listListeners.add(ldl);
}
public void removeListDataListener(ListDataListener ldl) {
listListeners.remove(ldl);
}
public Object getRoot() {
return rootNode;
}
public int getChildCount(Object parent) {
if (parent == rootNode) return data.size();
if (parent instanceof ExampleDataObject) {
return 5;
}
return 0;
}
public boolean isLeaf(Object parent) {
if (parent == rootNode) return false;
if (parent instanceof ExampleDataObject) return false;
return true;
}
public void addTreeModelListener(TreeModelListener tml) {
treeListeners.add(tml);
}
public void removeTreeModelListener(TreeModelListener tml) {
treeListeners.remove(tml);
}
public Object getChild(Object parent, int childIndex) {
if (parent == rootNode) return data.get(childIndex);
if (parent instanceof ExampleDataObject) {
ExampleDataObject edo = (ExampleDataObject)parent;
switch (childIndex) {
case 0:
return edo.getName();
case 1:
return edo.getAddress1();
case 2:
return edo.getCity();
case 3:
return edo.getState();
case 4:
return edo.getPostal();
default:
return null;
}
}
return null;
}
public int getIndexOfChild(Object parent, Object child) {
if (parent == rootNode) return data.indexOf(child);
if (parent instanceof ExampleDataObject) {
ExampleDataObject edo = (ExampleDataObject)child;
if (child.equals(edo.getName())) return 0;
if (child.equals(edo.getAddress1())) return 1;
if (child.equals(edo.getCity())) return 2;
if (child.equals(edo.getState())) return 3;
if (child.equals(edo.getPostal())) return 4;
}
return 0;
}
public void valueForPathChanged(TreePath path, Object node) {
}
public void dataChanged(ExampleDataObject edo) {
//propogate the change to all of the listeners of this model
}
}
This model is incomplete, in that it does not send out events for changes in the data model, although it does hold onto references to its listeners. For this example, it is unnecessary to detail the firing of events; if this were to be a fully functional application, that code would need to be added.
Reviewing the code, you will note that even the model uses reusable objects instead of temporary objects for storing the data. The model holds a direct reference to the data objects used in the application instead of storing copies. This creates a single source of truth for the data and any changes in that data is propagated across the entire application. Changes made to the data outside of the model is handled via the DataChangeListener interface which the model implements. The DataChangeListener interface contains one method:
package com.zarrastudios.swingobjects;
public interface DataChangeListener {
public void dataChanged(ExampleDataObject edo);
}
The Data Object
When data is changed inside one of the data objects, it fires off an event that the model receives and can then rebroadcast to the listeners as appropriate. The code for the data objects in this example is very straight-forward, utilizing the standard Java bean design:
package com.zarrastudios.swingobjects;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
public class ExampleDataObject implements Serializable {
private transient ArrayList dataListeners;
private String name;
private String address1;
private String city;
private String state;
private String postal;
public void addDataListener(DataChangeListener dcl) {
if (dataListeners == null) dataListeners = new ArrayList();
dataListeners.add(dcl);
}
public void removeDataListener(DataChangeListener dcl) {
if (dataListeners == null) return;
dataListeners.remove(dcl);
}
private void fireChange() {
for (Iterator i = dataListeners.iterator(); i.hasNext();) {
DataChangeListener dcl = (DataChangeListener)i.next();
dcl.dataChanged(this);
}
}
public String getAddress1() {
return address1;
}
public void setAddress1(String s) {
address1 = s;
fireChange();
}
public String getCity() {
return city;
}
public void setCity(String s) {
city = s;
fireChange();
}
public String getName() {
return name;
}
public void setName(String s) {
name = s;
fireChange();
}
public String getPostal() {
return postal;
}
public void setPostal(String s) {
postal = s;
fireChange();
}
public String getState() {
return state;
}
public void setState(String s) {
state = s;
fireChange();
}
public String toString() {
return name;
}
}
Each of the setter methods calls fireChange(), which broadcasts the change in the data. When the data is first initialized, there are no listeners, so the change does not get broadcast. During the life of the application, the model listens for these changes so it can update the views as needed.
While this example seems rather simple, it illustrates a way to eliminate a tremendous number of temporary objects from a JFC/Swing Application. The end result is an application that runs smoother, avoids unnecessary garbage collection, and has smoother memory management.
![]() |
|


