Skip to main content

TreeTable GUI update

15 replies [Last post]
Anonymous

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
kschaefe
Offline
Joined: 2006-06-08

Denis,

Here's the short of the long of it: you are viewing the TreeTableNodes as the data backing the model (as a list might back a ListModel). In some circumstances that may work, but it does not do so here. The reason for that is the DefaultTreeTableModel works with TreeTableNodes as though the node were part of the model. You should view DefaultTreeTableModel and TreeTableNodes as a way to place non-hierarchical data into a hierarchical view. In this case, the nodes are not the backing data of the model, but are, in fact, part of it, adapting the non-hierarchical data.

It sounds like you want to have TreeNodes and some type of RowAdapter that provides the mappings to adapter a "hierarchical" structure with a concept of disparate pieces into a tabular-tree view. If thats really the case, then build your "data" into a tree-structure using TreeNodes and the wrap the TreeNodes with TreeTableNodes and do the right thing with the DefaultTreeTableModel.

However, I have always maintained that any time a developer must adapt their underlying data structure into a hierarchical one via the use of TreeNodes that the developer should think twice about what they are doing. Core Swing does everyone a great disservice by only having a DefaultTreeModel that uses nodes. This convinces people incorrectly that they need to adapt their data. So, if your data is already hierarchical, why are you dealing with Tree(Table)Nodes in the first place? If it is not, why are you using a tree view for it?

Karl

kschaefe
Offline
Joined: 2006-06-08

Denis,

DefaultTreeTableModel should be considered final. Extending it is a bad idea. TreeTableNode are part of the model, not part of the data. If you return bogus values from the node, do not expect the model to work correctly. You are fighting the models and their design. If you use the API as intended, do you still have problems? If so, I will be happy to take a look at the problem.

Do note that [url=]https://swingx.dev.java.net/issues/show_bug.cgi?id=915Issue 915[/url] discusses the inability of DefaultTreeTableModel to easily configure values for getColumnClass. Otherwise, everything in that model should just work.

Karl

kschaefe
Offline
Joined: 2006-06-08

Furthermore, there should never be a reason to force the model to fire an update from outside the model. If it is necessary, then something is likely incorrect elsewhere in the code.

Karl

keksi
Offline
Joined: 2010-08-17

Hi Karl

The "Model" is - for me - the object structure in the background (in this case: a tree structure made up of subclasses of AbstractMutableTreeTableNode). This object structure can stand on its own, and implements all required data and operations, and is independent of any "View" onto it.

The "View" is a GUI showing some or all aspects of the "Model" mentioned above. In this case I use an instance of JXTreeTable to project the "Model" (a tree) onto the screen.

JXTreeTable needs an implementation of the interface TreeTableModel. However an implementation of TreeTableModel is not suitable as the "Model" because it deals a lot with "Columns" - and the Model has (per definition) no clue about columns: columns belong solely to the "View". The Model has only "Properties".

So I decided to use an implementation of TreeTableModel as a layer between the "View" and the "Model" in order to translate between "Columns" and "Properties":

View <-> Translation <-> Model
JXTreeTable <-> TreeTableModel <-> TreeTableNodes

So that's my architecture, and I find it's a quite reasonable one. If you think I'm fighting the models and their design, then I'd appreciate it very much if you would explained it shortly to me. I think that we have a different conception of the term "Model".

> Furthermore, there should never be a reason to force the model to fire
> an update from outside the model.

Agreed. I have to think it over.

kleopatra
Offline
Joined: 2003-06-11

>
> JXTreeTable needs an implementation of the interface
> TreeTableModel. However an implementation of
> TreeTableModel is not suitable as the "Model" because
> it deals a lot with "Columns" - and the Model has
> (per definition) no clue about columns: columns
> belong solely to the "View".

that's simply wrong (in the context of SwingX which you mention in your definition above) - both treeTableModel and TreeTableNode _do have_ the notion of columns. That's the distinguishing line from a TreeModel and TreeNode. So yes, you are fighting SwingX if you insist on your perspective.

CU
Jeanette

aephyr
Offline
Joined: 2009-11-20

> Hi Karl
>
> Because when I use DefaultTreeTableModel instead of
> AbstractTreeTableModel, I noticed that the tree nodes
> (which are subclasses of
> AbstractMutableTreeTableNode) are asked to supply the
> column count (the method int getColumnCount() is
> called). This violates the MVC-paradigm to separate
> the data model from the view(s) on this model.
>
> It's the same with the methods getValueAt(int column)
> and setValueAt(Object aValue, int column), which are
> methods of the interface TreeTableNode. These three
> methods
>
> getColumnCount()
> getValueAt(int column)
> setValueAt(Object aValue, int column)
>
> do not belong to the TreeTableNode, but to the
> TreeTableModel. Remember that somebody could want to
> have several different views (TreeTableModels) on the
> same tree structure (TreeTableNodes).

All three of those methods are in the TreeTableModel Interface as:

public int getColumnCount();
public Object getValueAt(Object node, int column);
public void setValueAt(Object value, Object node, int column);

It just so happens that the default implementation forwards those method calls to the TreeTableNode implementation. You don't have to do that though. In fact, you don't have to use the TreeTableNode interface at all.

> Now here is another issue: the column widths. When I
> induce a tree view refresh by moving files or
> folders, SOMETIMES the column widths are reset so
> that all columns have the same width. Do you have a
> clue how I can avoid this?

If you fire a tree structure changed event for the root, then JXTreeTable sends a table structure changed event to the TableModel. I'm not sure that is what it should do. IMO, it should only send a table structure changed event to the TableModel if the path identifies a new root.

If you don't want your column structure to change, then call setAutoCreateColumnsFromModel(false) on the JXTreeTable.

keksi
Offline
Joined: 2010-08-17

Hi Karl

No, you're wrong. The method "refresh(TreePath path)" that is called twice in the code snippet above

(...)
model.refresh(treeTable.getPathForRow(targetIndex));
model.refresh(treeTable.getPathForRow(sourceIndex).getParentPath());
(...)

looks like this:

/**
* Refresh this (path) section of the view tree.
* @param path
*/
public void refresh(TreePath path)
{
modelSupport.fireTreeStructureChanged(path);
}

Best regards
:-Denis

kschaefe
Offline
Joined: 2006-06-08

So now that we know what refresh does....

Why are AbstractMutableTreeTableNodes being used with the AbstractTreeTableModel and not the DefaultTreeTableModel?

Karl

keksi
Offline
Joined: 2010-08-17

Hi Karl

Because when I use DefaultTreeTableModel instead of AbstractTreeTableModel, I noticed that the tree nodes (which are subclasses of AbstractMutableTreeTableNode) are asked to supply the column count (the method int getColumnCount() is called). This violates the MVC-paradigm to separate the data model from the view(s) on this model.

It's the same with the methods getValueAt(int column) and setValueAt(Object aValue, int column), which are methods of the interface TreeTableNode. These three methods

getColumnCount()
getValueAt(int column)
setValueAt(Object aValue, int column)

do not belong to the TreeTableNode, but to the TreeTableModel. Remember that somebody could want to have several different views (TreeTableModels) on the same tree structure (TreeTableNodes).

But however - now that I use DefaultTreeTableModel instead of AbstractTreeTableModel, no exceptions are thrown any more! That's great!

Many thanx!

Now here is another issue: the column widths. When I induce a tree view refresh by moving files or folders, SOMETIMES the column widths are reset so that all columns have the same width. Do you have a clue how I can avoid this?

Best regards
:-Denis

kschaefe
Offline
Joined: 2006-06-08

Denis,

I understand your concern about the possible MVC issues with TreeTableNode, but I disagree. I see TreeTableNode as an Adapter for converting non-tree-based data into a tree-based structure. As an adapter, it needs to support the methods necessary to properly adapt the underlying data structure to the model. Furthermore, I believe that tree structures are best modeled by a direct subclass of TreeTableModel and not by using TreeTableNodes. These were my beliefs behind designing the models as they currently exist.

As for the width problem, can you provide a small, runnable demo?

Karl

keksi
Offline
Joined: 2010-08-17

Hi Karl

I see. I built up the tree structure using subclasses of TreeTableNode and thought of subclasses of TreeTableModel to translate this tree structure into what I show in the GUI. So when the user wants to have another view onto the tree structure, I just create another TreeTableModel class for the other GUI view. So the column information for that specific view is stored in the specific TreeTableModel, without changing the tree structure classes themselves. Hence the TreeTableModel subclasses project the tree structure into the GUI, and that's where I want to put the "column" information in. I separate the tree structure from the views onto it.

Assume I have two different views for the same tree structure (one with 2 columns and one with 5), how would I implement the method getColumnCount() in the tree structure class?

OK then, I'm now creating a simple runnable demo about this column width problem, but I can't attach files to this reply. How then can I supply this demo to you?

Best regards
:-Denis

keksi
Offline
Joined: 2010-08-17

OK, here we go. There are two classes, the first one (TreeTableModel) contains a main() that opens a small window with a JXTreeTable and a button. When you klick the button, the columns widths get scrambled.

I hope this works with the code. Oh yes, I'm using swingx 1.6.1.

Best regards
:-Denis
---------------------------------------------------------------------------------------------------------
[code]
package treetableexample;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ListSelectionModel;
import javax.swing.table.TableColumnModel;
import javax.swing.tree.TreePath;

import org.jdesktop.swingx.JXTreeTable;
import org.jdesktop.swingx.treetable.DefaultTreeTableModel;

public class TreeTableModel extends DefaultTreeTableModel
{
static private JXTreeTable treeTable;

public static void main(String[] args)
{
JFrame mainWindow = new JFrame("Main Window");
mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

JPanel buttonsPanel = new JPanel(new FlowLayout());

JButton onButton = new JButton("Refresh Root Node");
onButton.addActionListener(
new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
TreeTableModel model = (TreeTableModel)treeTable.getTreeTableModel();
model.refresh(treeTable.getPathForRow(0)); // Refresh RootNode
}
});
onButton.setEnabled(true);
buttonsPanel.add(onButton);

mainWindow.add(buttonsPanel, BorderLayout.NORTH);

treeTable = new JXTreeTable(new TreeTableModel());

treeTable.setRootVisible(true);
treeTable.setShowsRootHandles(true);
treeTable.expandAll();
treeTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

mainWindow.add(new JScrollPane(treeTable), BorderLayout.CENTER);

TableColumnModel columnModel = treeTable.getColumnModel();
columnModel.getColumn(0).setPreferredWidth(200);
columnModel.getColumn(1).setPreferredWidth(20);
columnModel.getColumn(2).setPreferredWidth(20);
columnModel.getColumn(3).setPreferredWidth(20);
columnModel.getColumn(4).setPreferredWidth(20);

mainWindow.setSize(new Dimension(400, 400));
mainWindow.setLocation(100, 100);
mainWindow.setVisible(true);
}

protected TreeTableModel()
{
this.root = TreeNode.generateExample();
}

@Override
public boolean isCellEditable(Object node, int column)
{
return false;
}

@Override
public int getColumnCount()
{
return 5;
}

@Override
public int getHierarchicalColumn()
{
return 0;
}

@Override
public Class getColumnClass(int column)
{
switch (column)
{
case 0: return String.class;
case 1: return Boolean.class;
case 2: return Integer.class;
case 3: return Integer.class;
case 4: return Integer.class;
}

return null;
}

@Override
public String getColumnName(int column)
{
switch (column)
{
case 0: return "Name";
case 1: return "is Folder";
case 2: return "Value";
case 3: return "Depth";
case 4: return "Kids";
}

return null;
}

@Override
public Object getValueAt(Object node, int column)
{
TreeNode treeNode = (TreeNode)node;

switch (column)
{
case 0: return treeNode.getLabel();
case 1: return !treeNode.isLeaf();
case 2: return treeNode.getValue();
case 3: return treeNode.getDepth();
case 4: return treeNode.getChildCount();
}

return null;
}

@Override
public Object getChild(Object parent, int index)
{
return ((TreeNode)parent).getChildAt(index);
}

@Override
public int getChildCount(Object parent)
{
return ((TreeNode)parent).getChildCount();
}

@Override
public int getIndexOfChild(Object parent, Object child)
{
return ((TreeNode)parent).getIndex((TreeNode)child);
}

@Override
public boolean isLeaf(Object node)
{
return ((TreeNode)node).getChildCount() == 0;
}

public void refresh(TreePath path)
{
this.modelSupport.fireTreeStructureChanged(path);
}

}

[/code]
---------------------------------------------------------------------------------------------------------
[code]
package treetableexample;

import org.jdesktop.swingx.treetable.AbstractMutableTreeTableNode;
import org.jdesktop.swingx.treetable.MutableTreeTableNode;

public class TreeNode extends AbstractMutableTreeTableNode
{
private String label;
private Integer value;

TreeNode(TreeNode parent, String label, Integer value)
{
this.label = label;
this.value = value;
this.setParent(parent);
}

static public TreeNode generateExample()
{
TreeNode root = new TreeNode(null, "Root", 0);

TreeNode one = new TreeNode(root, "One", 1);
new TreeNode(one, "One.One", 3);
new TreeNode(one, "One.Two", 4);
new TreeNode(one, "One.three", 5);
TreeNode two = new TreeNode(root, "Two", 2);
new TreeNode(two, "Two.One", 6);
new TreeNode(two, "Two.Two", 7);

return root;
}

public String getLabel()
{
return label;
}

public Integer getValue()
{
return value;
}

public Integer getDepth()
{
TreeNode parent = (TreeNode)this.parent;
return (parent == null)? 0: 1 + parent.getDepth();
}

@Override
public int getColumnCount()
{
return 2;
}

@Override
public Object getValueAt(int arg0)
{
return null;
}

public String toString()
{
return new StringBuilder("[TreeNode:(")
.append(this.getDepth())
.append(")")
.append(this.label)
.append(":")
.append(this.value)
.append("]")
.toString();
}

public String treeString(Integer indent)
{
StringBuilder buf = new StringBuilder(100);
buf.append("\n");
for (int i = 0; i < indent; i++) buf.append("\t");
buf.append(this.toString());
for (MutableTreeTableNode child: children) buf.append(((TreeNode)child).treeString(indent + 1));

return buf.toString();
}

}
[/code]
---------------------------------------------------------------------------------------------------------

Message was edited by: keksi

keksi
Offline
Joined: 2010-08-17

Oh boy... somebody removed all those tab characters. Sorry for that.

:-Denis

kleopatra
Offline
Joined: 2003-06-11

you can format code by taggin it with [ code ] .... [ /code ] - without the spaces. Please complain to the site admin that this isn't documented anywhere ;-)

CU
Jeanette

Please edit your post and add the tags

Message was edited by: kleopatra

kschaefe
Offline
Joined: 2006-06-08

I bet you are not firing model changes in your mutable methods, cf. TreeModelSupport.

Karl