The lack of documentation for this really simple feature is disturbing. I have a TreeViewer and want to select a node. And the only way this makes sense is if the tree expands all elements up to the selection, else the user can't see it.
public class TreeWindow extends ApplicationWindow {
public static void main(String[] args) {
new TreeWindow().open();
}
public TreeWindow() {
super(null);
setBlockOnOpen(true);
}
#Override
protected Control createContents(Composite parent) {
final TreeViewer treeViewer = new TreeViewer(parent);
treeViewer.setContentProvider(new FileTreeContentProvider());
treeViewer.setLabelProvider(new LabelProvider() {
#Override
public String getText(Object element) {
String result = ((File) element).getName();
if (result.isEmpty()) {
result = ((File) element).getPath(); // root like C:\
}
return result;
}
});
treeViewer.setInput(File.listRoots());
// expand
final File fileToExpand = new File("src");
System.out.println("Expand to file: " + fileToExpand.getAbsolutePath());
return treeViewer.getControl();
}
static class FileTreeContentProvider extends ArrayContentProvider implements ITreeContentProvider {
#Override
public Object[] getChildren(Object parentElement) {
return ((File) parentElement).listFiles();
}
#Override
public Object getParent(Object element) {
return ((File) element).getParentFile();
}
#Override
public boolean hasChildren(Object element) {
return ((File) element).isDirectory();
}
}
}
What I tried:
treeViewer.setSelection(new StructuredSelection(fileToExpand));
System.out.println("Selection: " + treeViewer.getSelection());
The selection doesn't get set. (I saw multiple times that TreeViewer#setSelection(ISelection, boolean) was used, but the JavaDoc states "Currently the reveal parameter is not honored because Tree does not provide an API to only select an item without scrolling it into view").
treeViewer.expandToLevel(fileToExpand, AbstractTreeViewer.ALL_LEVELS);
This method... does nothing?
final Tree tree = treeViewer.getTree();
final TreeItem[] items = tree.getSelection();
for (int i = 0; i < items.length; ++i) {
final TreeItem item = items[i];
TreeItem treeParent = item.getParentItem();
while (treeParent != null) {
treeParent.setExpanded(true);
treeParent = treeParent.getParentItem();
}
}
Might work maybe? But the selection does not get set, so...
treeViewer.expandAll();
This method normally works, but I don't think it's a good idea to try it in the above example. It does not do what I want, so it's a moot point anyways.
The problem seems to be that the TreeItem is created lazily. To check that you can try this:
for (final TreeItem item : this.treeViewer.getTree().getItems()) {
System.out.println(item.getData() + " " + item.getItemCount());
}
This method outputs either 1 if the item has children or 0 if not, but not the actual item count. Also, if you try to get the children's data, it's null.
How do I select a node that is not expanded? How do I expand the tree to the selection / a specified node?
Possible duplicate:
How to expand a specific node in TreeViewer(org.eclipse.jface) (I'm not sure if this is the same problem, but there is no solution either way)
I was able to get this to work on JFace 3.13.2:
// element is any object of your tree content provider data model,
// in your case a File.
treeViewer.expandToLevel(element, 0);
treeViewer.setSelection(new StructuredSelection(element));
The above code will make all the nodes down to the level of the selected element expanded, scroll the relevant part of the tree into view and mark the element as selected.
In my case the tree was four levels deep and the selected element was a leaf node. If you wanted to expand levels below the selected node, you can provide a level higher than 0 as second parameter to expandToLevel, or TreeViewer.ALL_LEVELS to expand all levels in the subtree.
Related
I am building am application where I need to have a tree view alongside a pane where data is presented. When someone selects an item in the tree view, the application must identify what they have selected and seek the correct data from a database, then present it in the format I've chosen. It must not matter how the user selects the item in the tree view (mouse click, tabbing, arrow keys, etc), just that when an item gets focus, a method is triggered to present the data to the user.
I got this working perfectly for mouse clicks only in the following way:
// Application thread method to build the tree map, used in the generateTree
// method.
public void treeBuilder(TreeMap<ModelSites, ArrayList<ModelPlants>> map) {
TreeMap<ModelSites, ArrayList<ModelPlants>> treeMap = map;
final TreeItemProperties<String, String> rootTreeItem = new TreeItemProperties<String, String>("EMT", null);
TreeItemProperties<String, Integer> site = null;
TreeItemProperties<String, Integer> plant = null;
for (Map.Entry<ModelSites, ArrayList<ModelPlants>> entry : treeMap.entrySet()) {
site = new TreeItemProperties<String, Integer>(entry.getKey().getLongName(), entry.getKey().getPrimaryKey());
rootTreeItem.getChildren().add(site);
if (site.getValue().equalsIgnoreCase("test item")) {
site.setExpanded(true);
}
for (int i = 0; i < entry.getValue().size(); i++) {
plant = new TreeItemProperties<String, Integer>(entry.getValue().get(i).getSitePlantId() + " " + entry.getValue().get(i).getShortName(), entry.getValue().get(i).getPrimaryKey());
site.getChildren().add(plant);
}
}
//Cell Factory is used to effectively turn the tree items into nodes, which they are not natively.
//This is necessary to have actions linked to the tree items (eg. double click an item to open an edit window).
emtTree.setCellFactory(new Callback<TreeView<String>, TreeCell<String>>() {
#Override
public TreeCell<String> call(TreeView<String> param) {
FactoryTreeCell<String> cell = new FactoryTreeCell<String>();
cell.setOnMouseClicked(event -> {
if (!cell.isEmpty()) {
#SuppressWarnings("unchecked")
TreeItemProperties<String, Integer> treeItem = (TreeItemProperties<String, Integer>) cell.getTreeItem();
generateEquipmentPanes(treeItem.getPropertyValue());
}
});
return cell;
}
});
rootTreeItem.setExpanded(true);
emtTree.setRoot(rootTreeItem);
}
// Populate the main screen with all equipment items in the selected plant.
public void generateEquipmentPanes(int plantId) {
int plant = plantId;
Task<LinkedList<ModelEquipment>> task = new Task<LinkedList<ModelEquipment>>() {
#Override
public LinkedList<ModelEquipment> call() {
LinkedList<ModelEquipment> equipmentList = DAOEquipment.listEquipmentByPlant(plant);
return equipmentList;
}
};
// When list is built successfully, send the results back to the application
// thread to load the equipment panes in the GUI.
task.setOnSucceeded(e -> equipmentPaneBuilder(task.getValue()));
task.setOnFailed(e -> task.getException().printStackTrace());
task.setOnCancelled(null);
String methodName = new Object() {}.getClass().getEnclosingMethod().getName();
Thread thread = new Thread(task);
thread.setName(methodName);
//System.out.println("Thread ID: " + thread.getId() + ", Thread Name: " + thread.getName());
thread.setDaemon(true);
thread.start();
}
// Application thread method to build the equipment panes, used in the
// generateEquipmentPanes method.
public void equipmentPaneBuilder(LinkedList<ModelEquipment> list) {
LinkedList<ModelEquipment> equipmentList = list;
EquipmentPanels.getChildren().clear();
for (int i = 0; i < equipmentList.size(); i++) {
ModelEquipment item = equipmentList.get(i);
try {
PaneEquipment equipmentPane = new PaneEquipment();
equipmentPane.updateFields(item.getTechId(), item.getShortName(), item.getLongDesc()); equipmentPane.setId("equipPane" + i);
EquipmentPanels.getChildren().add(equipmentPane);
} catch (Exception e) {
e.printStackTrace();
}
}
}
I've done a tonne of searching and I sort of figured out how to implement listeners instead of handlers as this seemed to be the way to do what I want - put a listener on a property of the cell. But when replace the event handler with the listener, like the below two examples, I get many issues.
emtTree.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
if (newValue != null) {
#SuppressWarnings("unchecked")
TreeItemProperties<String, Integer> treeItem = (TreeItemProperties<String, Integer>) cell.getTreeItem();
generateEquipmentPanes(treeItem.getPropertyValue());
}
});
cell.focusedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if (!cell.isEmpty()) {
#SuppressWarnings("unchecked")
TreeItemProperties<String, Integer> treeItem = (TreeItemProperties<String, Integer>) cell.getTreeItem();
generateEquipmentPanes(treeItem.getPropertyValue());
}
}
});
For a start I get nullpointerexceptions every time I click on a tree item, which comes from the line generateEquipmentPanes(treeItem.getPropertyValue());. Secondly, it tends to pick data from the wrong item, not the one I selected. Then after a few clicks it seems to break down altogether and do nothing except provide more nullpointerexceptions.
From what I can understand, I think that the issue is the location of the listener relative to the variables that need to be passed to the method generateEquipmentPanes. And something about removing listeners at a certain point and re-adding them later.
Should I somehow be putting the listener into the cell factory? At the moment it just looks like this:
import javafx.scene.control.TreeCell;
public class FactoryTreeCell<T> extends TreeCell<T> {
public FactoryTreeCell() {
}
/*
* The update item method simply displays the cells in place of the tree items (which disappear when setCellFactory is set.
* This can be used for many more things (such as customising appearance) not implemented here.
*/
#Override
protected void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
setGraphic(null); //Note that a graphic can be many things, not just an image. Refer openJFX website for more details.
} else {
setText(item.toString());
}
}
}
Another thing I've noticed is that there are other ways to implement the Callback, that might work better with listeners, but I don't know how to do that.
I've been stuck on this for ages so a breakthrough would be great.
You should not be using a tree cell to examine the selected value. Your ChangeListener already receives the new value directly:
emtTree.getSelectionModel().selectedItemProperty().addListener(
(observable, oldSelection, newSelection) -> {
if (newSelection != null) {
TreeItemProperties<String, Integer> treeItem = newSelection;
generateEquipmentPanes(treeItem.getPropertyValue());
}
});
I'm working in Swing and I would like to disable the expand (plus [+]) sign on a certain type of nodes.
Not sure how to do it because my nodes aren't leaves and I also cannot use setShowsRootHandles (which is only for the root).
I'm referring to to JTree: suppose i got this structure:
Root
--[+] node1
--[+] node2
when I load this structure i would like not to see the [+] sign on node2 (because it a special type node). But I also would like to expand it by using a special command.
I've overridden isLeaf() (method from DefaultMutableTreeNode) so it would set to to TRUE when i'm in the special type node, but then when I'm trying to expand it, it wouldn't expand because isLeaf() == TRUE...
Hope this will make things more clear.
While it is not possible to remove the handles, it is possible to restrict the expansion of nodes. The way to go is a TreeWillExpandListener combined with a custom treeNode that has state to restrict expansion:
the custom node below has an expandable property that's false by default
when detecting custom nodes, the listener allows/vetoes expansion based on that expandable property
for programmatic expansion, the expandable property is set to true temporarily to pass the listener
Example code:
// mixed tree of normal/restricted noded
DefaultMutableTreeNode root = new DefaultMutableTreeNode("root");
DefaultMutableTreeNode normalSubTree = new DefaultMutableTreeNode("normal");
normalSubTree.add(new DefaultMutableTreeNode("normalChild"));
MyNode restrictedSubTree = new MyNode("restrictedSubtree");
restrictedSubTree.add(new DefaultMutableTreeNode("restrictedChild"));
root.add(normalSubTree);
root.add(restrictedSubTree);
final JTree tree = new JTree(root);
// the listener which vetos expansion of MyNodes that are not expandable
TreeWillExpandListener l = new TreeWillExpandListener() {
#Override
public void treeWillExpand(TreeExpansionEvent event)
throws ExpandVetoException {
TreePath path = event.getPath();
if (path.getLastPathComponent() instanceof MyNode) {
if (!((MyNode) path.getLastPathComponent()).isExpandable()) {
throw new ExpandVetoException(event, "node not expandable");
}
}
}
#Override
public void treeWillCollapse(TreeExpansionEvent event)
throws ExpandVetoException {
}
};
tree.addTreeWillExpandListener(l);
Action expand = new AbstractAction("Expand") {
#Override
public void actionPerformed(ActionEvent e) {
TreePath selected = tree.getSelectionPath();
if (selected == null) return;
if (selected.getLastPathComponent() instanceof MyNode) {
MyNode last = (MyNode) selected.getLastPathComponent();
boolean old = last.isExpandable();
last.setExpandable(true);
tree.expandPath(selected);
last.setExpandable(old);
}
}
};
JXFrame frame = wrapWithScrollingInFrame(tree, "veto expand");
addAction(frame, expand);
show(frame);
}
// custom node which has an expandable property
public static class MyNode extends DefaultMutableTreeNode {
private boolean expandable;
public MyNode() {
this(null);
}
public MyNode(Object userObject) {
super(userObject);
}
public void setExpandable(boolean expandable) {
this.expandable = expandable;
}
public boolean isExpandable() {
return expandable;
}
}
It's possible to remove the handles - despite what others have mentioned.
I've attached a snippet on how to do this below. The key thing is to override shouldPaintExpandControl in BasicTreeUI.
jtree.setUI(new BasicTreeUI() {
#Override
protected boolean shouldPaintExpandControl(final TreePath path, final int row
, final boolean isExpanded, final boolean hasBeenExpanded, final boolean isLeaf)
{
boolean shouldDisplayExpandControl = false;
return shouldDisplayExpandControl;
}
This should really be documented in the JTree API but that's another issue.
Another approach to consider:
If you call DefaultTreeModel(TreeNode root, boolean asksAllowsChildren) the model will "ask" the nodes you insert if they are allowed to have children. If they cannot, it should not display the expand icon.
Be sure to override javax.swing.tree.TreeNode.getAllowsChildren() in your class.
I want to know how to implement this feature:
I have editable JTree where I can edit name of the nodes. If I have some node which is branch node (it has some leaf nodes in it) and this branch node is expanded while editing, after editing, this node will be collapsed.
I want to leave that branch node open if it is open and collapsed if it is collapsed, after editing is done.
I tried to look at TreeWillExpandListener but it seems it does not solve my problem because I need to recognize if actual node is in edit mode BEFORE I call these methods ...
How to do this trick? It is so obvious it is necessary thing but I can't find the answer at all :/
Ok so this is the code, I'll try to explain it. First of all, I have the class ContactTreeModel which implements TreeModel. The constructor just loads addressbook and group manager from the main app frame, I create new root and I load data from the database in the second method.
public ContactTreeModel() {
addressBookManager = ContactManagerFrame.getAddressBookManager();
groupManager = ContactManagerFrame.getGroupManager();
root = new DefaultMutableTreeNode();
processTreeHierarchy();
}
private void processTreeHierarchy() {
DefaultMutableTreeNode group, contact;
for (Group g : addressBookManager.getGroups()) {
group = new DefaultMutableTreeNode(g);
root.add(group);
for (Contact c : addressBookManager.getContactsFromGroup(g)) {
contact = new DefaultMutableTreeNode(c);
group.add(contact);
}
}
}
I read that the method valueForPathChanged in the TreeModel is fired up if
Messaged when the user has altered the value for the item identified by path to newValue. If newValue signifies a truly new value the model should post a treeNodesChanged event.
So I wrote that method like this:
#Override
public void valueForPathChanged(TreePath path, Object newValue) {
// backup of the original group
Group oldGroup = (Group) path.getLastPathComponent();
try {
Group testGroup = (Group) path.getLastPathComponent();
testGroup.setName((String) newValue);
// validation of the group to be updated
groupManager.validateGroup(testGroup, true);
oldGroup.setName((String) newValue);
// updating of the group in db
groupManager.updateGroup(oldGroup);
} catch (ServiceFailureException | ValidationException ex) {
// if database error occured or validation exception is raised,
// update label in gui
ContactManagerFrame.getStatusPanelLabel().setText(ex.getMessage());
} finally {
fireTreeStructureChanged();
}
}
Notice the method in the finally block, that method is always fired up. It looks like
protected void fireTreeStructureChanged() {
Object[] o = {root};
TreeModelEvent e = new TreeModelEvent(this, o);
for (TreeModelListener l : treeModelListeners) {
l.treeNodesChanged(e);
}
}
This is little bit tricky and the reason why it does not work (I guess). I just iterate through all treeModelListeners and fire up the treeNodesChanged method.
I have the array specified for tree model listeners as a private attribute in the ContactTreeModel and related methods too:
private Vector<TreeModelListener> treeModelListeners = new Vector<>();
#Override
public void addTreeModelListener(TreeModelListener l) {
treeModelListeners.addElement(l);
}
#Override
public void removeTreeModelListener(TreeModelListener l) {
treeModelListeners.removeElement(l);
}
The last thing, how does the model listener I added to the model look like? Here it comes:
public class ContactTreeModelListener implements TreeModelListener {
#Override
public void treeNodesChanged(TreeModelEvent e) {
System.out.println("nodes changed");
}
#Override
public void treeNodesInserted(TreeModelEvent e) {
System.out.println("nodes inserted");
}
#Override
public void treeNodesRemoved(TreeModelEvent e) {
System.out.println("nodes removed");
}
#Override
public void treeStructureChanged(TreeModelEvent e) {
System.out.println("structure changed");
}
}
So it does basically nothing. I registered listener in the other place, it does not matter right now.
So, when I execute it, in this state, the tree does NOT collapse and the behavior is as wanted. But even it really rewrite the name of that node (label), if the original string was about 5 characters and I changed it e.g to 4 characters, there will be only 4 characters in the label but also the empty space for the fifth. So the size of the original label has not changed after I finished editing that label. Similarly, when I expand the name of the group,
e.g I rename it from 4 to 5 characters, the label of that node will contain dots indicating
that the whole text is too large to display. That's weird ... How do I make the udpdate
of that label?
The very last thing ... since I have custom icons in the JTree + I do the recognition between empty and non-empty group, I need to check the actual icon in db every time I do some action in the tree (I checked it is executed even I just open and close the nodes). This is very inefficient so I extended DefaultTreeCellRenderer where I do the actual caching:
#Override
public Component getTreeCellRendererComponent(
JTree tree,
Object value,
boolean sel,
boolean expanded,
boolean leaf,
int row,
boolean hasFocus) {
if (iconCache == null) {
throw new NullPointerException("iconCache in "
+ this.getClass().getName() + " is null");
}
super.getTreeCellRendererComponent(
tree, value, sel,
expanded, leaf, row,
hasFocus);
if (value instanceof Group) {
Group g = (Group) value;
Icon groupIcon = iconCache.get(g);
if (groupIcon == null) {
if (groupHasContacts(g)) {
groupIcon = groupNonEmpty;
} else {
groupIcon = groupEmptyIcon;
}
iconCache.put(g, groupIcon);
}
JLabel result = (JLabel) super.getTreeCellRendererComponent(tree,
g.getName(), sel, expanded, leaf, row, hasFocus);
result.setIcon(groupIcon);
return result;
}
else if (value instanceof Contact) {
Contact c = (Contact) value;
Icon icon = iconCache.get(c);
if (icon == null) {
icon = this.contactIcon;
iconCache.put(c, icon);
}
JLabel result = (JLabel) super.getTreeCellRendererComponent(
tree, c.getName() + c.getSurname(),
sel, expanded, leaf, row, hasFocus);
result.setIcon(icon);
return result;
}
JLabel defaultNode = (JLabel) super.getTreeCellRendererComponent(
tree, "?", sel, expanded, leaf, row, hasFocus);
defaultNode.setIcon(unknownNode);
return defaultNode;
}
The problem with update of the label that is not repainted properly is probably related to the fact that you fire treeNodesChanged event with only root in the array that identifies the path.
Try to call the following from valueForPathChanged:
public void fireTreeNodesChanged(TreePath path) {
TreeModelEvent e = new TreeModelEvent(this, path.getPath());
for (TreeModelListener l : treeModelListeners) {
l.treeNodesChanged(e);
}
}
By the way, your name for fireTreeStructureChanged that actually fires treeNodesChanged is very misleading.
I'm working with a TreeTable (from wicket-extensions) and I'd like to be able to select a row by clicking anywhere within it instead of the usual behavior of clicking the link in one cell to select the row. I understand this should be possible by adding an AjaxEventBehavior("onclick") to the component representing the row, but I can't seem to find any methods where the row component is exposed.
I figured out a solution after. The row element is available in the populateTreeItem method from TreeTable. When you're creating your treetable, override this method like so:
#Override
protected void populateTreeItem(final WebMarkupContainer item, final int level) {
super.populateTreeItem(item, level);
item.add(new AjaxEventBehavior("onclick") {
#Override
protected void onEvent(final AjaxRequestTarget target) {
final TreeNode node = ((TreeNode) item.getDefaultModelObject());
rowClickSelect(node);
});
}
};
Generally useful in adding behaviors to rows. In my case, I'll have to do some more overriding to reconcile this toggle-on-click behavior with the clicks that are supposed to expand/contract nodes as well as link clicks.
Just toggling selection again in these cases has the unfortunate effect of briefly toggling the node in and out of the unwanted state, which is not ideal. Instead, override the onJunctionLinkClicked and onNodeLinkClicked methods, which will be touched by a click event before it gets to the onClick behavior we just set-up in populateTreeItem:
#Override
protected void onJunctionLinkClicked(final AjaxRequestTarget target, final TreeNode node) {
super.onJunctionLinkClicked(target, node);
skipNextRowClick();
}
#Override
protected void onNodeLinkClicked(final AjaxRequestTarget target, final TreeNode node) {
super.onNodeLinkClicked(target, node);
skipNextRowClick();
}
Finally, add the methods skipNextRowClick and rowClickSelect:
/**
* Ensure the next call to rowClickSelect() will have no effect.
*/
private void skipNextRowClick() {
this.skipNextClickSelect = true;
}
private void rowClickSelect(final TreeNode node) {
if (this.skipNextClickSelect) {
this.skipNextClickSelect = false;
return;
}
// select on click row
final boolean isSelected = Log4jPanel.this.treeTable.getTreeState().isNodeSelected(node);
treeTable.getTreeState().selectNode(node, !isSelected);
}
I have an unusual situation where I need to have a JTree with each node containing 2 checkboxes and a label (with the ability to add a listener to tell when any of the potential checkboxes are checked). I also need the root node to have the same layout (which I'm assuming means creating a JPanel with 2 JCheckBoxes and a JLabel), with the ability to select all the checkboxes down the tree if one in the root is checked.
Any guidance or examples? I've checked out previous questions on here and associated examples...some of which allowed me to get to the point of having the tree "look" but without giving me a direction for implementing the action behind it.
Thanks!
This might be a good time to look at the old JTreeTable code, which will give you a tree rendered in the first column, and the freedom to render the cells for each column to the right of the tree node as you wish, in your case putting in checkboxes and a label, and allowing you to have TableCellEditors working with your JTable as you are used to. A warning is that, while the code in that link works, it is a little convoluted.
There is an alternative. I have demoed below a Tree Table implementation that is supposed to be better, called Outline, provided by NetBeans (though you don't need to develop with the NetBeans IDE, you just need the jar). This article indicates how easy it is to be to get started.
I was able to mock up a quick example of the Outline tree table in Eclipse (with the org-netbeans-swing-outline.jar imported to my project) in about 30 minutes (I am slow at typing):
private void buildFrame() {
frame = new JFrame("Demo");
frame.setSize(300, 300);
addStuffToFrame();
frame.setVisible(true);
}
private void addStuffToFrame() {
MyTreeNode top = new MyTreeNode("top");
createNodes(top);
DefaultTreeModel model = new DefaultTreeModel(top);
//here are the netBeans tree table classes
OutlineModel outlineModel =
DefaultOutlineModel.createOutlineModel(model, new MyRowModel());
Outline outline = new Outline();
outline.setRootVisible(true);
outline.setModel(outlineModel);
frame.getContentPane().add(new JScrollPane(outline));
}
private void createNodes(MyTreeNode top) {
MyTreeNode child = new MyTreeNode("child 2");
top.add(new MyTreeNode("child 1"));
child.add(new MyTreeNode("g-child1"));
child.add(new MyTreeNode("g-child2"));
child.add(new MyTreeNode("g-child3"));
top.add(child);
top.add(new MyTreeNode("child3"));
top.add(new MyTreeNode("child4"));
}
I create a TreeNode to hold the Booleans that will interoperate well with the JTable's built-in checkbox rendering mechnanism.
public class MyTreeNode extends DefaultMutableTreeNode {
Boolean data1 = null;
Boolean data2 = null;
String name = null;
MyTreeNode (String name) {
this.name=name;
}
void setData1(Boolean val) {data1=val;}
void setData2(Boolean val) {data2=val;}
Boolean getData1() {return data1;}
Boolean getData2() {return data2;}
String getName() {return name;}
}
The netBeans RowModel is the key to making this a table instead of a simple JTree:
public class MyRowModel implements RowModel {
public Class getColumnClass(int col) {
switch (col) {
case 0: return String.class;
case 1: return Boolean.class; //these return class definitions will
case 2: return Boolean.class; //trigger the checkbox rendering
default:return null;
}
}
public int getColumnCount() {
return 3;
}
public String getColumnName(int col) {
return "";
}
public Object getValueFor(Object node, int col) {
MyTreeNode n = (MyTreeNode)node;
switch (col) {
case 0: return n.getName();
case 1: return n.getData1();
case 2: return n.getData2();
default:return null;
}
}
public boolean isCellEditable(Object node, int col) {
return col > 0;
}
public void setValueFor(Object node, int col, Object val) {
MyTreeNode n = (MyTreeNode)node;
if (col == 1) {n.setData1((Boolean)val);}
else if (col == 2) {n.setData2((Boolean)val);}
//EDIT: here is a recursive method to set all children
// selected for one of the two checkboxes as it is
// checked by the parent
for (Enumeration children = n.children();
children.hasMoreElements(); ) {
MyTreeNode child = (MyTreeNode) children.nextElement();
setValueFor(child, col, val);
}
}
}
here is the finished, albeit simplistic, product:
alt text http://img17.imageshack.us/img17/6643/picture1hz.png
I have updated the setValueFor method to iterate over a node's children and set the checkboxes as selected or deselected when a parent has been modified.
Take a look at http://www.java2s.com/Code/Java/Swing-JFC/CheckBoxNodeTreeSample.htm
It wasn't clear where the buildFrame(), addStuffToFrame() and createNodes() methods go. I put them all into an OutlineJFrame class I created that extends JFrame, and deleted the 'frame.' preface where-ever it appeared. Then in my project's main() method, it just created one of those OutlineJFrame objects and set its visible to true. When it ran, I got a resizable but empty window. Where were the rows? Where were the nodes?
Then I asked Geertjan, the NetBeans guru, what I was doing wrong, and he sent me a re-write. But it had the same behaviour.
But I know that my java is fine, because another demo project I did (FileTreeJFrame) displays outline.java objects just fine.