I'm trying to use JTree. Here is my example test class:
package inspector;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
/**
*
* @author Alex
*/
public class TreeTest {
private static ValueMap prepareTree() {
ValueMap obj = new ValueMap();
obj.set("x", new Value(1));
obj.set("y", new Value(1));
ValueArray users = new ValueArray();
obj.set("users", users);
ValueMap user = new ValueMap();
user.set("login", new Value("Alex654"));
user.set("password", new Value("123456"));
ValueMap info = new ValueMap();
info.set("city", new Value("Moscow"));
info.set("job", new Value("Developer"));
info.set("firstName", new Value("Alex"));
info.set("lastName", new Value("Popov"));
user.set("bio", info);
users.push(user);
ValueMap user2 = new ValueMap();
user2.set("login", new Value("admin"));
user2.set("password", new Value("test"));
user2.set("bio", new Value(null));
users.push(user2);
return obj;
}
static class Value {
Value() {
value = null;
}
Value(Object val) {
this.value = val;
}
@Override
public String toString() {
if (value instanceof Integer || value instanceof Double) {
return value + "";
} else {
return "\"" + value.toString() + "\"";
}
}
Object value;
}
static class ValueArray extends Value {
ValueArray() {
items = new ArrayList<Value>();
}
ValueArray(Value val) {
items = new ArrayList<Value>();
items.add(val);
}
void push(Value val) {
items.add(val);
}
ArrayList<Value> items;
}
static class ValueMap extends Value {
ValueMap() {
items = new HashMap<String, Value>();
}
Value get(String key) {
return items.get(key);
}
void put(String key, Value val) {
items.put(key, val);
}
void set(String key, Value val) {
items.put(key, val);
}
HashMap<String, Value> items;
}
public static class ValueWrapper {
ValueWrapper(String label, Value val) {
this.label = label;
this.val = val;
}
String getLabel() {
return label;
}
Value getValue() {
return val;
}
@Override
public String toString() {
String result = label;
if (val instanceof ValueArray) {
result += ": Array";
}
else if (val instanceof ValueMap) {
result += ": { Object }";
}
else result += ": " + val.toString();
return result;
}
private String label;
private Value val;
}
public static void processChildren(DefaultMutableTreeNode node) {
Value val = ((ValueWrapper)node.getUserObject()).getValue();
if (val instanceof ValueArray) {
ArrayList<Value> items = ((ValueArray)val).items;
for (int i = 0; i < items.size(); i++) {
DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(new ValueWrapper(i + "", items.get(i)));
node.add(newNode);
processChildren(newNode);
}
} else if (val instanceof ValueMap) {
HashMap<String, Value> props = ((ValueMap)val).items;
Set<String> keys = props.keySet();
for (String key: keys) {
DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(new ValueWrapper(key, props.get(key)));
node.add(newNode);
if (!key.equals("__proto__")) {
processChildren(newNode);
}
}
}
}
public static void main(String[] args) {
try {
UIManager.setLookAndFeel(
UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {}
final ValueMap root = prepareTree();
if (root == null) return;
final JFrame frame = new JFrame("Object Inspector");
JPanel cp = new JPanel();
cp.setBorder(BorderFactory.createEmptyBorder(9, 10, 9, 10));
frame.setContentPane(cp);
cp.setLayout(new BoxLayout(cp, BoxLayout.PAGE_AXIS));
DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode(new ValueWrapper("obj", root));
processChildren(rootNode);
DefaultTreeModel treeModel = new DefaultTreeModel(rootNode, true);
final JTree tree = new JTree(treeModel);
final JPanel contentpane = new JPanel();
contentpane.setBackground(Color.WHITE);
contentpane.setOpaque(true);
contentpane.setLayout(new BorderLayout());
contentpane.add(tree);
//contentpane.setBounds(0, 0, 490, 380);
final int width = 490, height = 418;
cp.setPreferredSize(new Dimension(width, height));
final JScrollPane scrollpane = new JScrollPane(contentpane);
scrollpane.setOpaque(false);
scrollpane.getInsets();
cp.add(scrollpane);
scrollpane.setBackground(Color.WHITE);
scrollpane.setOpaque(true);
//setSize(518, 420);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
I expect to get a nice looking tree, like in NetBeans IDE that I use everyday.
But however I get this:
What the heck is wrong with it? Also should note that when my labels were longer (without the "wrapper" inner class) the output labels in leaves was 2-3 times wider, but the labels were also extremely cut down in width.
UPDATE:
Unfortunalely, the result after suggested solution is not ideal. See the screenshot below:
- The symbols are cut by 1-2 pixels from the bottom (look at the square brackets). Maybe it is a windows bug (I use Windows 7 with 150% zoom), but is there a way to fix that? To increase the font size of the labels or the vertical gap between them (so called "line height")?
- I don't like that leaves have "+" on them until I click them. I solved it adding
else { node.setAllowsChildren(false); }to the end of myprocessChildren()method. But now I would like to have a single custom icon for all of my nodes, whether they have children or not.
Seems that all the methods I needed were available either in
JTreeitself or inDefaultTreeCellRendererclass. Here is the final working code:Note that if the tree contains "loops", e.g. in HTML document each child has a link to its parent, and every two adjacent siblings link to each other, then using simple approach of initializing all the node's children will lead to stackoverflow error and the program will crash.
The solution is to use lazy loading of children, and load only one level at a time, until the user clicks one of the existing "non-leaf" nodes.