Friday, February 24, 2012

A Netbeans Tree Table

An Editable Netbeans Tree Table: The OutlineView

This post is going to cover the creation of a Tree Table in Netbeans using an OutlineView. The OutlineView is Netbean's latest and greatest version of a Tree Table, replacing their previous effort, the TreeTableView. Below is my own list of the advantages and disadvantages of using an OutlineView versus the most common Tree Table class in Java, the SwingX JXTreeTable.

Advantages
  1. Much cleaner and more professional UI than the SwingX JXTreeTable
  2. Automatically inherited behavior for sorting columns and filtering rows
  3. Complete abstraction (and removal) of dealing with Table, Tree, and TreeTable models (along with editors and renderers for textual input)
Disadvantages
  1. Throw away everything you know about JTables, JTrees, and JXTreeTable
  2. Can only be implemented within the Netbeans IDE

This blog post is going to cover the implementation of an OutlineView and even includes some small UI improvements. For the sake of brevity, this post is just going to have direct links to the java classes I created. I believe I've added enough javadoc to the classes to answer any questions you may have. You're absolutely welcome to put these java files directly into your code and edit them as you see fit. I just ask that you keep my name and blog address somewhere in the javadoc :)  By the time we're done, you'll have a fully editable Tree Table that looks like this:


Now let's get started....

Step 1: Setting Up Your Module's Dependencies

The first thing you'll need to do is ensure your Netbeans module has the proper dependencies. Add the following APIs to your module:
  1. Nodes API
  2. Explorer and Property Sheet API
  3. ETable and Outline API
To add a dependency to your module, right-click on the module and select Properties. From the Categories pane on the left side of the Project Properties window, select Libraries. In the Module Dependencies pane in the middle of the window, you'll see your current list of dependencies. Click the Add Dependency... button to launch another the Add Module Dependency window. In the Filter text field, simply type the first word of the dependency you want to add, select it in the Module pane, and click OK. Once you've added all the dependencies, click OK in the Project Properties window.

Step 2: Extend and Improve OutlineView

We're going to extend OutlineView and fix what I consider to be some minor UI bugs:
  1. Replacing the default empty border with the default JScrollPane border
  2. Enabling the alternative show/hide columns popup (this is really a personal preference, not a bug)
  3. Providing a visual distinction for a column that has its values filtered
Below are 3 separate snapshots of the code for TreeTable. These snapshots are highlighting the key logic used to build our TreeTable. If you scroll down a little further, you can download the full TreeTable class. Our first snapshot is for the constructor code. If the text is too small for you to read, you can always download the full file further below.


You'll notice we install our own TableColumnRenderer for each column that gets added to the table. In this example, I defined it as a private class within TreeTable, but you may want to make it a public class in case you want to extend it or use it somewhere outside of your TreeTable. Here's a snapshot of our TableColumnRenderer:


Last, but not least, I've included the implementation of method initialize(Node). Adding columns to an OutlineView can be unintuitive, to say the least, so I just created this public method as a convenient way to automatically create columns by simply passing in the root node.



Below are 2 different versions of my TreeTable class you can download: A .java version you can directly paste in your project or a Google docs version if you just want to look at the code. Keep in mind it is a valid java bean so you can add it to your Netbeans Palette to be dragged-and-dropped onto your GUI Form.

TreeTable.java
TreeTable

Step 3: Let's Make Some Nodes

This really isn't a posting about Netbeans Nodes, but you can't make an OutlineView without them. If you're trying to learn the code, you'll want to look at the PersonNode java class and note that the "columns" are added via the createSheet method and that the PropertySupport class is used to display the values for each row. Because these classes aren't production level and quality code, I've just got links to the text for the code below.

Person
PersonNode

Step 4: Putting It All Together

If you've added TreeTable.java to your Project, you can add it to the palette and drag-and-drop it onto a design Form. Here's how to add it to your palette:
  1. Clean and Build your Project
  2. Right-click on TreeTable, in the pop-up menu select Tools->Add To Palette
  3. In your Design view of your Form, it will appear in the Palette section under Beans
In your Module, your view will have to implement the ExplorerManager.Provider interface. Implementing this interface is a breeze. All you have to do is instantiate a new ExplorerMananger (literally, private ExplorerManager manager = new ExplorerManager()) and have the getExplorerManager() method return that instance. When you create your Nodes, the root Node will need to be set in your ExplorerManager instance via the setRootContext(Node) method. I'll delve much deeper into how to use ExplorerManager and what it does in a later post.

If you looked at TreeTable.java, you'll notice you have to call the initialize(Node) method and pass in a root Node to actually populate the TreeTable. You'll want to pass in the same Node you passed into your ExplorerManager's setRootContext(Node) method (or you get it by calling 'manager.getRootContext()'). To save you some time, here's some code that creates a root Node for you and passes it to both of those methods:

this.manager = new ExplorerManager();

Person grandPa = new Person("Grandpa", "Arthur", 88, Person.HAIR_COLOR.WHITE);
Person child1 = new Person("Jane", "Miller", 42, Person.HAIR_COLOR.BLONDE);
Person child2 = new Person("John", "Johnson", 44, Person.HAIR_COLOR.BLACK);
grandPa.setChildren(child1, child2);
Person grandchild1 = new Person("Janey", "Smith", 28, Person.HAIR_COLOR.BROWN);
Person grandchild2 = new Person("Johnny", "Doe", 21, Person.HAIR_COLOR.BROWN);
child1.setChildren(grandchild1, grandchild2);
Person grandchild3 = new Person("Baby", "Anderson", 2, Person.HAIR_COLOR.BLONDE);
child2.setChildren(grandchild3);
PersonNode grandpaNode = new PersonNode(grandPa);
       
this.manager.setRootContext(grandpaNode);
this.treeTable1.initialize(grandpaNode);


Step 5: Watch It Work

Run your Module and bring up your GUI. Try out the following features of the TreeTable:
  1. Clicking on a column will first sort by ascending order, then descending order, and then revert back to the original order.
  2. Right-click a cell and select the Select only rows where option. You'll be given options to only show rows that are equal to, less than (or equal to), and greater than (or equal) the value you clicked on. You are also given options to show only rows that have null or non-null values.
  3. You can click on column headers and reorder them across your table (a standard JTable feature).
  4. If you right-click on a column header or if you have a vertical scroll bar present and click the button in the top-right corner of your TreeTable, you'll be presented with a pop-up that allows you to show and/or hide columns.
  5. Enter input for the Name and Age cells. You'll notice your TreeTable does automatic type checking for you in the Age cells. That is, it will disallow any input in an Age cell that is not a number.
  6. You'll notice there are 2 ways to enter input into a Name cell:  By clicking directly into the cell and typing or by clicking the button to the right of the cell (the button may only appear in Windows environments).
If you've looked through the Person.java class, you'll notice the HAIR_COLOR enum attribute. So why didn't I display it in the TreeTable? I haven't gotten around to it yet, but I promise I will soon. It's not as straight-forward as a String or Number property and requires the creation of one more class. Stay Tuned!