Java Forum / GUI / February 2008
Sortable Java Tree Table
yccheok - 26 Dec 2007 13:09 GMT Hi, I am using JXTreeTable from SwingLab, for my Java Tree Table component. However, there is a limitation from JXTreeTable - They do not support sorting :(
Any one know any free Java Tree Table component, which support sorting?
Thanks
Andrew Thompson - 26 Dec 2007 14:46 GMT > Hi, I am .. ..multi-posting. Please refrain from doing that in future.
(X-post to c.l.j.g./p., w/ f-u to c.l.j.g. only)
-- Andrew T.
Roedy Green - 27 Dec 2007 23:46 GMT On Wed, 26 Dec 2007 05:09:12 -0800 (PST), yccheok <yancheng.cheok@gmail.com> wrote, quoted or indirectly quoted someone who said :
>Hi, I am using JXTreeTable from SwingLab, for my Java Tree Table >component. However, there is a limitation from JXTreeTable - They do [quoted text clipped - 4 lines] > >Thanks see http://mindprod.com/jgloss/multiposting.html
 Signature Roedy Green Canadian Mind Products The Java Glossary http://mindprod.com
Rogan Dawes - 02 Jan 2008 12:57 GMT > Hi, I am using JXTreeTable from SwingLab, for my Java Tree Table > component. However, there is a limitation from JXTreeTable - They do [quoted text clipped - 4 lines] > > Thanks Apart from the other "don't multipost" comments, can you explain exactly how you would like this widget to behave?
Keep in mind that the data from your tree is explicitly ordered (by your tree structure), and the data in the columns is directly related to the tree node. How should the widget behave when you choose to sort on a column?
Regards,
Rogan
Roedy Green - 03 Jan 2008 04:40 GMT On Wed, 26 Dec 2007 05:09:12 -0800 (PST), yccheok <yancheng.cheok@gmail.com> wrote, quoted or indirectly quoted someone who said :
>Any one know any free Java Tree Table component, which support >sorting? The actually sorting is duck simple.
/** * sort in order by app name */ public void sort() { synchronized ( allRows ) { Collections.sort( allRows ); } tableModel.fireTableDataChanged(); }
The hard part is working out various widgets to trigger the various flavours of sort. Here is roughly what you need to do:
import java.awt.Component; import java.awt.Image; import java.awt.Toolkit; import java.net.URL;
import javax.swing.ImageIcon; import javax.swing.JLabel; import javax.swing.JTable; import javax.swing.SwingConstants; import javax.swing.border.Border; import javax.swing.border.EmptyBorder; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel;
/** * renders column headings with up or down * pointing arrow on sort column depending * if sort is ascending or descending. * * @author Roedy Green * @version 1.0 * @since 2002 Sep 5 */
public class SortHeadRenderer implements TableCellRenderer {
/** * Constructor * * @param sorter associated TableOrderer so we know which * column is being sorted on. */ public SortHeadRenderer ( TableOrderer sorter ) { this.sorter = sorter; }
private TableOrderer sorter;
/** * Returns the component used for drawing the cell. This method is * used to configure the renderer appropriately before drawing. * * @param table the <code>JTable</code> that is asking the * renderer to draw; can be <code>null</code> * @param value the value of the cell to be rendered. It is * up to the specific renderer to interpret * and draw the value. For example, if * <code>value</code> * is the string "true", it could be rendered as a * string or it could be rendered as a check * box that is checked. <code>null</code> is a * valid value * @param isSelected true if the cell is to be rendered with the * selection highlighted; otherwise false * @param hasFocus if true, render cell appropriately. For * example, put a special border on the cell, if * the cell can be edited, render in the color used * to indicate editing * @param row the row index of the cell being drawn. When * drawing the header, the value of * <code>row</code> is -1 * @param column the column index of the cell being drawn */ public Component getTableCellRendererComponent( JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { JLabel label;
// recycle three JLabels over and over. switch ( sorter.howColumnSorted( column ) ) { case TableOrderer.ASCENDING : label = ascendingLabel; break;
case TableOrderer.DESCENDING : label = descendingLabel; break;
default: case TableOrderer.UNSORTED: label = unsortedLabel; break; } label.setText( value.toString() ); return label;
}
/** * Up pointing arrow, indicates ascending sort. */ private static JLabel ascendingLabel = new JLabel("ascending");
/** * Down pointing arrow, indicates descending sort. */ private static JLabel descendingLabel = new JLabel("descending");
/** * No arrow, indicates unsorted. */ private static JLabel unsortedLabel = new JLabel("unsorted");
private static Border noFocusBorder = new EmptyBorder(1, 1, 1, 1); static { // get Images as a resources, from jar or from classpath JLabel[] trio = { ascendingLabel, descendingLabel, unsortedLabel}; String[] resourceIcon = { "ascending.gif", "descending.gif", null}; for ( int i=0; i<3; i++ ) { setIcon ( trio[i], resourceIcon[i] ); trio[i].setForeground( Config.HEADER_FOREGROUND ); trio[i].setBackground( Config.HEADER_BACKGROUND ); // needed to make pay attention to background colour setting. trio[i].setOpaque( true ); trio[i].setBorder( noFocusBorder ); // label is centred. trio[i].setHorizontalAlignment( SwingConstants.CENTER ); // text is to left of icon trio[i].setHorizontalTextPosition( SwingConstants.LEFT ); } }
/** * Attach an icon to a JLabel * * @param label JLabel to have an icon attached. * * @param gif name of resource e.g. "ascending.gif" * null means no gif */ public static void setIcon ( JLabel label, String gif ) { if ( gif == null ) { label.setIcon ( null ); } else { URL url = SortHeadRenderer.class.getResource( gif ); Image image = Toolkit.getDefaultToolkit().getImage( url ); ImageIcon icon = new ImageIcon( image ); label.setIcon( icon ); } }
/** * hook this renderer up on * every header column. * * @param table Jtable to to use this renderer on. * @param sorter Associated TableOrderer */ public static void install ( JTable jtable, TableOrderer sorter ) { jtable.setAutoCreateColumnsFromModel( false ); TableCellRenderer renderer = new SortHeadRenderer ( sorter ); TableColumnModel tcm = jtable.getColumnModel(); int cols = tcm.getColumnCount(); for ( int i=0; i<cols; i++ ) { TableColumn tc = tcm.getColumn( i ); tc.setHeaderRenderer( renderer ); } }
}
 Signature Roedy Green Canadian Mind Products The Java Glossary http://mindprod.com
Rogan Dawes - 07 Jan 2008 09:49 GMT > On Wed, 26 Dec 2007 05:09:12 -0800 (PST), yccheok > <yancheng.cheok@gmail.com> wrote, quoted or indirectly quoted someone [quoted text clipped - 16 lines] > tableModel.fireTableDataChanged(); > } What kind of TreeModel can you store in a simple Collection?
Note that the original question is regarding sorting a *Tree*Table, which depends on a TreeModel for its structure, and presents it as a TableModel. However, sorting the provided TableModel makes no sense by itself.
Rogan
arvim85@gmail.com - 11 Feb 2008 10:04 GMT anyOne give detailed idea of doing sorting for JTreeTable.....?? i cant understand the already posted things....need of some detailed .......
Roedy Green - 12 Feb 2008 09:33 GMT On Mon, 11 Feb 2008 02:04:05 -0800 (PST), "arvim85@gmail.com" <arvim85@gmail.com> wrote, quoted or indirectly quoted someone who said :
>anyOne give detailed idea of doing sorting for JTreeTable.....?? There is an example posted at http://mindprod.com/jgloss/jtable.html
Look at the VerCheck example. It sorts the model when you press a button, and fires the change event to repaint the screen.
What gets tricky is putting gismos on the header to get the user decide which columns to sort and whether ascending/descending.
I did it in one commercial app where there was a arrow the pointed in the direction of the current sort. If you clicked it sorted in the opposite direction. It also maintained the table is that order when the user entered data or the server sent new data. That was more complicated since I had to figure out the insertion point.
--
Roedy Green Canadian Mind Products The Java Glossary http://mindprod.com
Rogan Dawes - 12 Feb 2008 12:40 GMT > anyOne give detailed idea of doing sorting for JTreeTable.....?? > i cant understand the already posted things....need of some > detailed ....... What can't you understand?
The big thing that I was trying to point out in this thread, and which Roedy seems to be missing repeatedly, is that sorting a *TREE* is very different from sorting a *TABLE*.
Yes, a JTreeTable looks like a JTable (and in fact is implemented using an adapter from a JTree to a JTable), but the underlying order is determined by the *TREE*. Note that the TreeTableModel extends TreeModel, not TableModel!
Once you define how you want to sort your *TREE*, we can help you with an implementation. So, give us some information about what is being represented in your tree.
Is it a filesystem? If so, sorting the *TREE* could be done by sorting the individual files within their directories. e.g. to Sort by file size. Of course, it makes no sense to sort the *directories* by size, since the intrinsic size of a directory is pretty meaningless unless you define it to be the aggregate of the sizes of the files and directories, for example. In *that* case, you could sort the tree nodes hierarchically so that the directories with the biggest contents show up first in the tree.
My ultimate point being: Without defining how you want to sort the tree, we can't help you with an implementation.
Rogan (who is tired of this thread, and will give up on it unless someone actually posts useful information)
Roedy Green - 12 Feb 2008 18:02 GMT >The big thing that I was trying to point out in this thread, and which >Roedy seems to be missing repeatedly, is that sorting a *TREE* is very >different from sorting a *TABLE*. Sorry, I always muddle those two. Sorting a tree is not a common operation. I just read it as sort a table.
I presume you mean just sort the children of each node. It looks as though you must sort, and if the order has changed, remove all elts past the point of change, and re-add them in the new order.
--
Roedy Green Canadian Mind Products The Java Glossary http://mindprod.com
Roedy Green - 13 Feb 2008 16:31 GMT On Tue, 12 Feb 2008 18:02:43 GMT, Roedy Green <see_website@mindprod.com.invalid> wrote, quoted or indirectly quoted someone who said :
>I presume you mean just sort the children of each node. It looks as >though you must sort, and if the order has changed, remove all elts >past the point of change, and re-add them in the new order. I explain this in more detail at http://mindprod.com/jgloss/jtree.html#SORTING --
Roedy Green Canadian Mind Products The Java Glossary http://mindprod.com
Rogan Dawes - 14 Feb 2008 18:09 GMT > On Tue, 12 Feb 2008 18:02:43 GMT, Roedy Green > <see_website@mindprod.com.invalid> wrote, quoted or indirectly quoted [quoted text clipped - 6 lines] > I explain this in more detail at > http://mindprod.com/jgloss/jtree.html#SORTING I took a look at your approach, and have a couple of comments:
> Sorting > I have never actually done this, but if I wanted to sort a JTree, > here is how I would go about it: > > 1. Do a breadth first traversal of the the tree. Duplicated "the". :-)
> 2. At each node extract a list of the childen into an array. > 3. Sort the array. [quoted text clipped - 4 lines] > 5. When you have sorted the entire tree, fire a tree structure > change event on the root. Firing a TreeStructureChanged event at the root will tell JTree to collapse the tree, losing any expanded state. This is unlikely to be desirable. Unfortunately, the only good solution is store the selection and expansion state before the sort is executed, and restore it afterwards.
Here is a complete implementation of a Sortable TreeModel. It includes sample code to demonstrate its usage, including saving and restoring the selection and expansion states, as well as handling mutation events fired by the underlying TreeModel.
Enjoy.
Rogan Dawes
package sort;
/** * This code is released into the public domain */
import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.Map;
import javax.swing.event.TreeModelEvent; import javax.swing.event.TreeModelListener; import javax.swing.tree.TreeModel; import javax.swing.tree.TreePath;
/** * @author Rogan Dawes <SortedTreeModel @ dawes.za.net> * */ public class SortedTreeModel extends AbstractTreeModel {
private TreeModel delegate;
private Comparator<Object> comparator = null;
private Map<Object, int[]> viewToModel = new HashMap<Object, int[]>();
private Listener listener = new Listener();
/** * Constructs a sorted TreeModel, based on the data in the provided delegate, * sorted according to the initial Comparator provided * @param delegate the underlying TreeModel to wrap * @param comparator the initial Comparator to use when sorting the nodes */ public SortedTreeModel(TreeModel delegate, Comparator<Object> comparator) { this.delegate = delegate; setComparator(comparator); delegate.addTreeModelListener(listener); }
public Object getChild(Object parent, int index) { int[] viewToModel = getViewToModel(parent); if (viewToModel == null) return delegate.getChild(parent, index); return delegate.getChild(parent, viewToModel[index]); }
public int getChildCount(Object parent) { return delegate.getChildCount(parent); }
public int getIndexOfChild(Object parent, Object child) { int index = delegate.getIndexOfChild(parent, child); int[] viewToModel = getViewToModel(parent); if (viewToModel == null) return index; for (int i = 0; i < viewToModel.length; i++) if (viewToModel[i] == index) return i; throw new RuntimeException("This should never happen"); }
public Object getRoot() { return delegate.getRoot(); }
public boolean isLeaf(Object node) { return delegate.isLeaf(node); }
public void valueForPathChanged(TreePath path, Object newValue) { delegate.valueForPathChanged(path, newValue); }
/** * Set the new Comparator to use to sort the underlying tree nodes * @param comparator the comparator */ public void setComparator(Comparator<Object> comparator) { this.comparator = comparator; if (sort(new TreePath(getRoot()), true)) fireStructureChanged(); }
/** * Sorts the children of the node at the specified path, and optionally all of its * children * @param path the path to the node to sort * @param recursive whether to sort that node's children as well * @return true if any changes were made to the order of the nodes, false otherwise */ protected boolean sort(TreePath path, boolean recursive) { Object parent = path.getLastPathComponent(); int childCount = delegate.getChildCount(parent); if (childCount == 0) return false; Object[] children = new Object[childCount]; int[] viewToModel = new int[childCount]; for (int i = 0; i < childCount; i++) { children[i] = delegate.getChild(parent, i); } Arrays.sort(children, comparator); for (int i = 0; i < childCount; i++) viewToModel[i] = delegate.getIndexOfChild(parent, children[i]); boolean changed = setViewToModel(parent, viewToModel); if (recursive) for (int i=0; i<childCount; i++) changed |= sort(path.pathByAddingChild(children[i]), recursive); return changed; }
private int[] getViewToModel(Object parent) { return viewToModel.get(parent); }
/** * Store the array mapping entries from the underlying model to the sorted order * As an optimization, if the sort order is identical to the underlying data order, we * do not store a mapping * @param parent the node whose children are represented * @param mapping the mapping from view order to underlying model order * @return true if the mapping differs from the current mapping, false otherwise */ private boolean setViewToModel(Object parent, int[] mapping) { boolean identity = true; for (int i=0; i<mapping.length; i++) if (mapping[i] != i) { identity = false; break; } if (identity) mapping = null; int[] oldMapping = getViewToModel(parent); if (!equals(oldMapping, mapping)) { if (mapping == null) { viewToModel.remove(parent); } else { viewToModel.put(parent, mapping); } return true; } return false; }
/** * Compare two integer arrays for equality * @param a an array * @param b an array * @return true if both arrays are null, or contain the same values in order */ private boolean equals(int[] a, int[] b) { if (a == b) return true; if (a == null || b == null) return false; if (a.length != b.length) return false; for (int i=0; i<a.length; i++) if (a[i] != b[i]) return false; return true; }
private class Listener implements TreeModelListener {
/** * remaps the indices provided in the event to those in the sorted model * @param path the path of the parent node * @param indices the unsorted indices */ private int[] remapIndices(TreePath path, int[] indices) { int[] mapping = getViewToModel(path.getLastPathComponent()); if (mapping == null) return indices;
int[] newIndices = new int[indices.length]; for (int i=0; i<indices.length; i++) { for (int j=0; j<mapping.length; j++) { if (mapping[j] == indices[i]) { newIndices[i] = j; break; } } } return newIndices; }
/* (non-Javadoc) * @see javax.swing.event.TreeModelListener#treeNodesChanged(javax.swing.event.TreeModelEvent) */ public void treeNodesChanged(TreeModelEvent e) { int[] indices = e.getChildIndices(); TreePath path = e.getTreePath(); indices = remapIndices(path, indices); fireChildrenChanged(path, indices, e.getChildren()); }
/* (non-Javadoc) * @see javax.swing.event.TreeModelListener#treeNodesInserted(javax.swing.event.TreeModelEvent) */ public void treeNodesInserted(TreeModelEvent e) { int[] indices = e.getChildIndices(); TreePath path = e.getTreePath(); sort(e.getTreePath(), false); indices = remapIndices(path, indices); fireChildrenAdded(path, indices, e.getChildren()); }
/* (non-Javadoc) * @see javax.swing.event.TreeModelListener#treeNodesRemoved(javax.swing.event.TreeModelEvent) */ public void treeNodesRemoved(TreeModelEvent e) { int[] indices = e.getChildIndices(); TreePath path = e.getTreePath(); indices = remapIndices(path, indices); sort(e.getTreePath(), false); fireChildrenRemoved(path, indices, e.getChildren()); }
/* (non-Javadoc) * @see javax.swing.event.TreeModelListener#treeStructureChanged(javax.swing.event.TreeModelEvent) */ public void treeStructureChanged(TreeModelEvent e) { fireTreeStructureChanged(e.getTreePath()); }
}
public static void main(String[] args) { final javax.swing.tree.DefaultMutableTreeNode root = new javax.swing.tree.DefaultMutableTreeNode("node"); buildTree(root, 5, 3); final javax.swing.tree.DefaultTreeModel delegate = new javax.swing.tree.DefaultTreeModel(root); final SortedTreeModel sorted = new SortedTreeModel(delegate, new StringNodeComparator(1, true)); final javax.swing.JTree tree = new javax.swing.JTree(sorted); javax.swing.JFrame frame = new javax.swing.JFrame("SortableTree"); javax.swing.JScrollPane scrollPane = new javax.swing.JScrollPane(tree); javax.swing.JButton sort = new javax.swing.JButton("Sort"); final StringNodeComparator[] comparators = new StringNodeComparator[] { new StringNodeComparator(-1, false), new StringNodeComparator(1, false), new StringNodeComparator(-1, true), new StringNodeComparator(1, true), }; sort.addActionListener(new java.awt.event.ActionListener() { private int i = 0; private java.util.List<TreePath> expansion = new java.util.ArrayList<TreePath>(); private TreePath[] selection;
public void actionPerformed(java.awt.event.ActionEvent ae) { saveSelectionAndExpansion();
System.out.println(comparators[i]); sorted.setComparator(comparators[i]); i = (i + 1) % comparators.length;
restoreSelectionAndExpansion(); }
private void saveSelectionAndExpansion() { expansion.clear(); java.util.Enumeration<TreePath> e = tree.getExpandedDescendants(new TreePath(sorted.getRoot())); while (e.hasMoreElements()) expansion.add(e.nextElement()); selection = tree.getSelectionPaths(); }
private void restoreSelectionAndExpansion() { tree.setSelectionPaths(selection); java.util.Iterator<TreePath> it = expansion.iterator(); while (it.hasNext()) tree.expandPath(it.next()); } }); javax.swing.JButton mutate = new javax.swing.JButton("Mutate"); mutate.addActionListener(new java.awt.event.ActionListener() { private boolean running = false; private Thread mutator = new Thread(new Runnable() { public void run() { try { java.util.Random random = new java.util.Random(); while (running) { int i = Math.abs(random.nextInt()) % delegate.getChildCount(root); final javax.swing.tree.MutableTreeNode child = (javax.swing.tree.MutableTreeNode) delegate.getChild(root, i); java.awt.EventQueue.invokeLater(new Runnable() { public void run() { delegate.removeNodeFromParent(child);
} }); Thread.sleep(1000); java.awt.EventQueue.invokeLater(new Runnable() { public void run() { delegate.insertNodeInto(child, root, 0); } }); Thread.sleep(1000); } } catch (InterruptedException ie) { } } }); public void actionPerformed(java.awt.event.ActionEvent ae) { if (!running) { running = true; mutator.start(); } else { running = false; } } }); frame.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE); frame.getContentPane().setLayout(new java.awt.BorderLayout()); frame.getContentPane().add(scrollPane, java.awt.BorderLayout.CENTER); frame.getContentPane().add(sort, java.awt.BorderLayout.NORTH); frame.getContentPane().add(mutate, java.awt.BorderLayout.SOUTH); frame.setBounds(200, 200, 400, 400); frame.setVisible(true); }
private static void buildTree(javax.swing.tree.MutableTreeNode parent, int breadth, int depth) { if (depth == 0) return; for (int i=0; i<breadth; i++) { javax.swing.tree.MutableTreeNode node = new javax.swing.tree.DefaultMutableTreeNode(parent + "-" + i); parent.insert(node, i); buildTree(node, breadth, depth - 1); } }
private static class StringNodeComparator implements Comparator<Object> {
private int order; private boolean leavesOnly;
public StringNodeComparator(int order, boolean leavesOnly) { this.order = order; this.leavesOnly = leavesOnly; } /* (non-Javadoc) * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) */ public int compare(Object o1, Object o2) { javax.swing.tree.TreeNode t1 = (javax.swing.tree.TreeNode) o1; javax.swing.tree.TreeNode t2 = (javax.swing.tree.TreeNode) o2; int c = o1.toString().compareTo(o2.toString()); if (leavesOnly) if (t1.isLeaf() && t2.isLeaf()) { return order * c; } else return c; return order * c; }
public String toString() { return "Sorting " + (leavesOnly ? "leaves only " : "") + "in " + (order == -1 ? "reverse " : "") + "alphabetical order"; } } }
Rogan Dawes - 13 Feb 2008 16:57 GMT >> The big thing that I was trying to point out in this thread, and which >> Roedy seems to be missing repeatedly, is that sorting a *TREE* is very [quoted text clipped - 6 lines] > though you must sort, and if the order has changed, remove all elts > past the point of change, and re-add them in the new order. Well, I guess you could write a SortedTreeModel adapter class that contains a similar node pattern to the underlying TreeModel, along with int[] arrays mapping viewToModel and modelToView
e.g.
public class SortedTreeModel extends AbstractTreeModel {
private Map<Object, int[]> viewToModel; private Comparator comparator;
public SortedTreeModel(TreeModel delegate) { viewToModel = new HashMap<Object, int[]>(); this.delegate = delegate; }
public Object getChild(Object parent, int index) { int[] viewToModel = getViewToModel(parent); if (viewToModel == null) return delegate.getChild(parent, index); return delegate.getChild(parent, viewToModel[index]); }
public int getChildCount(Object parent) { return delegate.getChildCound(parent); }
public int getIndexOfChild(Object parent, Object child) { int index = delegate.getIndexOfChild(parent, child); int[] viewToModel = getViewToModel(parent); if (viewToModel == null) return index; for (int i=0; i<viewToModel.length; i++) if (viewToModel[i] == index) return i; throw new RuntimeException("This should never happen"); }
public Object getRoot() { return delegate.getRoot(); }
public boolean isLeaf(Object node) { return delegate.isLeaf(node); }
public void valueForPathChanged(TreePath path, Object newValue) { delegate.valueForPathChanged(path, newValue); }
protected int[] getViewToModel(Object parent) { return viewToModel.get(parent); }
public void setComparator(Comparator comparator) { this.comparator = comparator; sort(new TreePath(getRoot()), true); }
protected void sort(TreePath path, boolean recursive) { Object parent = path.lastPathComponent(); int childCount = delegate.getChildCount(parent); if (childCount == 0) return; Object[] children = new Object[childCount]; int[] viewToModel = new int[childCount]; for (int i=0; i<childCount; i++) { children[i] = delegate.getChild(parent, i); if (recursive) sort(path.pathByAddingChild(children[i]), recursive); } Arrays.sort(children, comparator); for (int i=0; i<childCount; i++) viewToModel[i] = delegate.getIndexOfChild(parent, children[i]); this.viewToModel.put(parent, viewToModel); fireChildrenChanged(path, childrenArray[children.length], children); }
private int[] childrenArray(int size) { int[] a = new int[size]; for (int i=0; i<size; i++) a[i] = i; return a; }
}
This *should* work, but since I wrote it in my news reader, I make no guarantees it will even compile.
Now, all you need to do is implement a Comparator for your nodes. And if it is a dynamic TreeModel, then you need to add the necessary listener to the delegate and call sort(node) (or sort(node, true) ) whenever the node changes.
Use it like so:
TreeModel delegate = . . . ; // your underlying TreeModel TreeModel sorted = new SortedTreeModel(delegate); JTree tree = new JTree(sorted); Comparator comp = new MyComparator(); sorted.setComparator(comp);
Obviously, you can extend the identical technique to TreeTableModel.
NOTE: You can try to be more fine grained in your event firing, of course.
Rogan
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|