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.
Related
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.
I have a problem with my app which uses JavaFX... In one view I have a tableview which contains list of people and I want change row style one person. Here is my code:
personTable.setRowFactory(new Callback<TableView<Person>, TableRow<Person>>() {
#Override
public TableRow<Person> call(TableView<Person> personTableView) {
return new TableRowRightFormat();
}
});
...
private class TableRowRightFormat extends TableRow {
#Override
protected void updateItem(Object o, boolean b) {
super.updateItem(o, b);
if(o == null) {
return;
}
getStyleClass().remove("headPerson");
if(((Person)o).getId()==2) {
getStyleClass().add("headPerson");
}
}
}
And it is working for one person(id=2) but when I scroll up my table and when person with id=2 disappears another person with id!=2 gets style called 'headPerson' (always one Person on visible elements in tableview has style 'headPerson', but above code is executing once time). What is the problem?
Update: I tested your code on both JavaFX 2.2 and JDK 8 and it seemed to work fine for my simple test case. The logic seems right; the one thing you have to be really careful of in these types of style-class based cell and row implementations is making sure you don't add multiple copies of a given string to the list of style classes - in your implementation this looks right. Double check and make sure you have the strings exactly the same in the add(...) and remove(...) methods.
I like to completely bullet-proof these at a slight cost to performance:
private final String headPersonStyleClass = "headPerson" ;
private class TableRowRightFormat extends TableRow<Person> {
#Override
protected void updateItem(Person p, boolean b) {
super.updateItem(p, b);
ObservableList<String> styleClass = getStyleClass();
if (p != null && p.getId()==2 && (! styleClass.contains(headPersonStyleClass))) {
styleClass.add(headPersonStyleClass);
} else {
// remove all occurrences:
styleClass.removeAll(Collections.singleton(headPersonStyleClass));
}
}
}
If you are using JavaFX 8, a better approach is to use a PseudoClass for this.
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 trying to use the example taken from the http://java.sun.com/products/jfc/tsc/articles/treetable2/index.html, in which I've substituted the filesystem model with my model.
I initially create a model, I display it in the JTreeTable, but now I'd like to update my model and then the JTreeTable (for example I want to add a node at the tree, modify a node, remove a node, etc.).
I don't know how I can do it. I can't see method that allow me to do what I want, I only see some method like treeNodesChanged, treeNodesInserted, etc., but probably I miss something in the global logic of this JTreeTable component.
Besides I'm not sure that I correctly create the model, because in various example I've seen people call various method over a "model" object (model.insertNodeInto, model.reload), inspite of I haven't a model object..In the example above, is simply called the abstract class AbstractTreeTableModel which implements TreeTableModel..
Update
public class TableModel extends AbstractTreeTableModel
implements TreeTableModel {
static protected String[] cNames = {"TrackNumber", "MWRTN", "LSRTN", "RFTN","TrackStatus","Prova","Prova2"};
// Types of the columns.
static protected Class[] cTypes = {TreeTableModel.class,Integer.class, Integer.class, Integer.class, Integer.class,String.class,String.class};
private ArrayList<Object> data=new ArrayList<Object>();
public void insertNode(Object node)
{ this.data.add(node); super.setRoot(data.get(0));}
In my main class I add objects to my model in this way:
...
model =new TableModel();
model.insertNode(threatList.get(i)); //inserting the root node
model.addChild(threatList.get(i),threatList.get(j)); // inserting the child
...
Then I pass the model to my JTreeTable and add it to my frame:
treeTable = new JTreeTable(model);
JScrollPane scroll=new JScrollPane(treeTable);
scroll.setAutoscrolls(false);
scroll.setPreferredSize(new Dimension(1000,80));
frame.add(scroll);
And this is the JTreeTable class:
public class JTreeTable extends JTable {
protected TreeTableCellRenderer tree;
public JTreeTable(TreeTableModel treeTableModel) {
super();
// Create the tree. It will be used as a renderer and editor.
tree = new TreeTableCellRenderer(treeTableModel);
// Install a tableModel representing the visible rows in the tree.
super.setModel(new TreeTableModelAdapter(treeTableModel, tree));
// Force the JTable and JTree to share their row selection models.
tree.setSelectionModel(new DefaultTreeSelectionModel() {
// Extend the implementation of the constructor, as if:
/* public this() */ {
setSelectionModel(listSelectionModel);
}
});
// Make the tree and table row heights the same.
tree.setRowHeight(getRowHeight());
// Install the tree editor renderer and editor.
setDefaultRenderer(TreeTableModel.class, tree);
setDefaultEditor(TreeTableModel.class, new TreeTableCellEditor());
setShowGrid(false);
setIntercellSpacing(new Dimension(0, 0));
setPreferredSize(new Dimension(60,60));
}
/* Workaround for BasicTableUI anomaly. Make sure the UI never tries to
* paint the editor. The UI currently uses different techniques to
* paint the renderers and editors and overriding setBounds() below
* is not the right thing to do for an editor. Returning -1 for the
* editing row in this case, ensures the editor is never painted.
*/
public int getEditingRow() {
return (getColumnClass(editingColumn) == TreeTableModel.class) ? -1 : editingRow;
}
//
// The renderer used to display the tree nodes, a JTree.
//
public class TreeTableCellRenderer extends JTree implements TableCellRenderer {
protected int visibleRow;
public TreeTableCellRenderer(TreeModel model) {
super(model);
}
public void setBounds(int x, int y, int w, int h) {
super.setBounds(x, 0, w, JTreeTable.this.getHeight());
}
public void paint(Graphics g) {
g.translate(0, -visibleRow * getRowHeight());
super.paint(g);
}
public Component getTableCellRendererComponent(JTable table,
Object value,
boolean isSelected,
boolean hasFocus,
int row, int column) {
if(isSelected)
setBackground(table.getSelectionBackground());
else
setBackground(table.getBackground());
visibleRow = row;
return this;
}
}
//
// The editor used to interact with tree nodes, a JTree.
//
public class TreeTableCellEditor extends AbstractCellEditor implements TableCellEditor {
public Component getTableCellEditorComponent(JTable table, Object value,
boolean isSelected, int r, int c) {
return tree;
}
#Override
public Object getCellEditorValue() {
// TODO Auto-generated method stub
return null;
}
}
What I would is to fire an event after adding(or modifying, or removing) a child.
The model is the class holding your data. It must tell its view each time the data changes, so that the view refreshes itself and shows the new data of the model. This is the goal of the methods fireXxx().
Like with the other Swing components, when you change the data displayed by the component, you should thus do it by changing the data in the model, and call the appropriate fireXxx methods. The best thing to do is to encapsulate this in the model class, by adding specific methods in your subclass of AbstractTreeTableModel which perform the data modification and fire the appropriate event using one or several calls to fireXxx.
I suggest you read the Swing tutorial about tables and or trees and then apply what you learn here to your tree table. The idea is the same.
I have this in several areas of an app I'm working on and I can see no way to replicate it outside of this app. I can't create a sscce since I can't manage to replicate this at all - This leads me to believe that it must be something caused by the parent frame / app, but I have no idea where to look.
What I see is that part of the left hand side of popup menus are not painted. I see this behaviour with JCombobox popups as well as JPopupMenu's. I've attached a couple of images to show what I mean. most of these did work properly previously and without any changes to the code where the popupmenu's are created or displayed, this problem has spread to a lot of other places now.
I'm not mixing heavyweight and lightweight components, as we only use Swing components and the two examples I show below are in completely different parts of the app. The first one is in a fairly simple panel with very little functionality, but the second example (JPoopupMenu) is in a very complex legacy panel.
On both of these and other place where I see it, I'm not altering the parent's clipping region at all and in all case, these popups are constructed and displayed on the EDT.
I know this question is rather vague, but that is because of the nature of the problem. I'll provide any requested info.
This specific case happens to be a custom combobox model, but we've seen it when using the DefaultComboBoxModel as well:
public class GroupListModel extends AbstractListModel
implements ComboBoxModel{
private List<groupObject> groups;
private groupObject selectedItem = null;
public GroupListModel() {
this(new ArrayList<groupObject>());
}
public GroupListModel(List<groupObject> groups) {
this.groups = groups;
}
#Override
public int getSize() {
return groups.size();
}
#Override
public Object getElementAt(int index) {
if(index>=groups.size()){
throw new IndexOutOfBoundsException();
}
return groups.get(index);
}
public void setGroups(List<groupObject> groups){
this.groups = groups;
fireContentsChanged(this, 0, groups.size());
}
public void addElement(groupObject group){
groups.add(group);
fireIntervalAdded(this, groups.size()-1, groups.size()-1);
}
public void addElement(groupObject group, int index){
groups.add(index, group);
fireIntervalAdded(this, index, index+1);
}
#Override
public void setSelectedItem(Object anItem) {
if(anItem instanceof groupObject){
selectedItem = (groupObject) anItem;
}else{
throw new IllegalArgumentException();
}
fireContentsChanged(this, 0, groups.size());
}
#Override
public Object getSelectedItem() {
return selectedItem;
}
This is a JPopupMenu that gets displayed when you right click using the following code:
public void mouseClicked(MouseEvent e) {
if( e.getButton()==e.BUTTON3 ){
lastClickedID = tmp.getUniqueID();
lastClickedGui = (bigEventGui) gui;
itmComplete.setText(
completed ?
ctOne.getLang("uncomplete") :
ctOne.getLang("complete") );
itmComplete.setIcon( (completed ?
iconFramework.getIcon(
iconFramework.UNCOMPLETE_ITEM,
24, false) :
iconFramework.getIcon(
iconFramework.COMPLETE_ITEM,
24, false) ));
popRCEvent.show(gui, e.getX(), e.getY() );
}
Taking out JPopupMenu.setDefaultLightWeightPopupEnabled(false); fixed it... Can somebody please try and explain why?