Using JXTreeTable to display grouped data

Posted by {"name"=>"Palash Ray", "email"=>"paawak@gmail.com", "url"=>"https://www.linkedin.com/in/palash-ray/"} on December 25, 2014 · 4 mins read

Let me start by ranting about Swing. Its a lame duck. It could never really take off. But we can't live with it, can't live without it. Especially if the application is a legacy Swing application written 10 years ago.

So, when I had the prospect of displaying simple grouped data, the fear of writing my custom JTree absolutely seized me. That is when i thought about the SwingX project. In its present form, it buggy, unusable, messed up etc. It took me close to an hour to figure out a stable release from the quagmire of broken links that Google came up with. At last, the Maven Central came to my rescue in the form of the following 3 magical lines:

		
			org.swinglabs.swingx
			swingx-all
			1.6.4
		

The First 90%

After being associated with Swing for a good part of my life, I have come to believe that 90% of your job is done if you can create a good Model, especially for unwieldy components like JTable and JTree. So, I do the same for this as well. In this case, I represent grouped data with a Map>. All I want is to display the groups as nodes and the Lists under these nodes. This is how I model it:

public class BankDetailTreeTableModel extends AbstractTreeTableModel {
    private static final String[] COLUMN_NAMES = new String[] { "id", "age",
            "job", "marital", "education", "default", "balance" };
    private static final String ROOT = "_ROOT_";
    private final Map groupedBankDetails;
    private final List groups;
    public BankDetailTreeTableModel(
            Map groupedBankDetails) {
        super(ROOT);
        this.groupedBankDetails = groupedBankDetails;
        groups = getGroups(groupedBankDetails);
    }
    @Override
    public int getColumnCount() {
        return COLUMN_NAMES.length + 1;
    }
    @Override
    public String getColumnName(int column) {
        if (column == 0) {
            return "Group";
        }
        return COLUMN_NAMES[column - 1];
    }
    @Override
    public Object getValueAt(Object node, int column) {
        if (node instanceof String) {
            if (column == 0) {
                return node;
            }
            return COLUMN_NAMES[column - 1];
        } else if (node instanceof BankDetail) {
            if (column == 0) {
                return null;
            }
            return displayColumnValue((BankDetail) node, column - 1);
        }
        return null;
    }
    @Override
    public Object getChild(Object parent, int index) {
        if (ROOT.equals(parent)) {
            return groups.get(index);
        } else if (parent instanceof String) {
            return groupedBankDetails.get(parent).get(index);
        }
        return null;
    }
    @Override
    public int getChildCount(Object parent) {
        if (ROOT.equals(parent)) {
            return groups.size();
        } else if (parent instanceof String) {
            return groupedBankDetails.get(parent).size();
        }
        return 0;
    }
    @Override
    public int getIndexOfChild(Object parent, Object child) {
        return 0;
    }
    private List getGroups(
            Map groupedBankDetails) {
        List groups = new ArrayList<>(groupedBankDetails.keySet());
        Collections.sort(groups);
        return groups;
    }
    private String displayColumnValue(BankDetail bankDetail, int columnIndex) {
        switch (columnIndex) {
        case 0:
            return Integer.toString(bankDetail.getId());
        case 1:
            return Integer.toString(bankDetail.getAge());
        case 2:
            return bankDetail.getJob();
        case 3:
            return bankDetail.getMarital();
        case 4:
            return bankDetail.getEducation();
        case 5:
            return bankDetail.getDefaulted();
        case 6:
            return bankDetail.getBalance().toPlainString();
        default:
            throw new IllegalArgumentException("columnIndex " + columnIndex
                    + " is not handled");
        }
    }
}

The remaining 10%

The tying up the model to the JXTreeTable is the simplest thing on the earth:

            Map groupedBankDetails = bankDetailServiceToUse
                    .getBankDetails(selectedGroup);
            treeTblBankDetails.setTreeTableModel(new BankDetailTreeTableModel(
                    groupedBankDetails));

This is how it looks like:
UsingJXTreeTable_1UsingJXTreeTable_2
 

The source code can be found here:

https://github.com/paawak/blog/tree/master/code/lambda-demo

The main class to run is: GroupingDemoFrame.

I found the following post very useful in my efforts:

http://javanbswing.blogspot.com/2013/08/swing-treetable-example-using.html