I've created a tree cell renderer/editor framework that is admittedly a little bit hacky, but it works perfectly on Windows and Linux. The image below illustrates a sample setup.
The goal is that if the user clicks on the image (numeral) 1 or 2, then the application responds to that click but does not select the tree row. If the user clicks on the text one or two, the application responds to that click and does select the tree row. The way I've implemented this, again, is a little hacky. Basically when the user clicks on the tree row, the editor component is displayed (which looks identical to the renderer component), and the editor component has a mouse listener which can determine where the user clicked in the row.
The fact that this works on Windows/Linux relies on something which I always thought seemed flimsy to rely on. Basically, if you click the row a single time, that single click both (a) brings up the editor and (b) activates the mouse listener on the editor component. This is the way I want it! However, when you try running the app on Mac OSX (10.6.2 if it matters), the aforementioned flimsy assumption no longer holds true. Any time you want to interact with the tree, you have to click twice (once to activate the editor, and again to activate the mouse listener).
The SSCCE below can reproduce the behavior. Of course, if you don't have OSX you can't reproduce the undesired behavior, but perhaps you can still recommend a smarter way to accomplish my goal. Watch the console for sysout
messages indicated what's happening as you click the various parts of the tree.
Oh, and the SSCCE references these two images:
package TreeTest;
import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.net.URL;
import java.util.EventObject;
import javax.imageio.ImageIO;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTree;
import javax.swing.UIManager;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
import javax.swing.event.CellEditorListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeCellEditor;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
@SuppressWarnings("serial")
public class TreeTest extends JComponent {
private JFrame frame;
private DefaultTreeModel treeModel;
private DefaultMutableTreeNode root;
private JTree tree;
public static void main(String[] args) {
try {
UIManager.setLookAndFeel(
UIManager.getSystemLookAndFeelClassName());
} catch (Throwable e) {
e.printStackTrace();
}
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
TreeTest window = new TreeTest();
window.frame.setVisible(true);
window.frame.requestFocusInWindow();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public TreeTest() {
initialize();
}
private void initialize() {
frame = new JFrame("Tree Test");
frame.setBounds(400, 400, 250, 150);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
root = new DefaultMutableTreeNode("root");
treeModel = new DefaultTreeModel(root);
tree = new JTree(treeModel);
tree.setEditable(true);
tree.getSelectionModel().setSelectionMode(
TreeSelectionModel.SINGLE_TREE_SELECTION);
tree.setRootVisible(false);
tree.setShowsRootHandles(false);
tree.setCellRenderer(new TreeRenderer());
tree.setCellEditor(new TreeEditor());
tree.putClientProperty("JTree.lineStyle", "None");
tree.setBackground(Color.white);
treeModel.insertNodeInto(new DefaultMutableTreeNode("two"), root, 0);
treeModel.insertNodeInto(new DefaultMutableTreeNode("one"), root, 0);
TreeNode[] nodes = treeModel.getPathToRoot(root);
tree.expandPath(new TreePath(nodes));
tree.addTreeSelectionListener(new TreeSelectionListener() {
@Override
public void valueChanged(TreeSelectionEvent e) {
System.out.println("SELECTION CHANGED!");
}
});
frame.getContentPane().add(tree);
}
public class TreeComponent extends JPanel {
public TreeComponent(JLabel numIcon, JLabel numText) {
this.setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
this.add(numIcon);
this.add(numText);
}
}
public class TreeRenderer implements TreeCellRenderer {
private ImageIcon oneIcon;
private ImageIcon twoIcon;
public TreeRenderer() {
try {
oneIcon = new ImageIcon(ImageIO.read(
new URL("https://i.stack.imgur.com/1dp6a.png")));
twoIcon = new ImageIcon(ImageIO.read(
new URL("https://i.stack.imgur.com/UteiP.png")));
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public Component getTreeCellRendererComponent(JTree tree, Object value,
boolean selected, boolean expanded, boolean leaf, int row,
boolean hasFocus) {
JLabel numIcon = new JLabel();
numIcon.setAlignmentX(JLabel.CENTER_ALIGNMENT);
numIcon.setAlignmentY(JLabel.CENTER_ALIGNMENT);
numIcon.setBorder(new EmptyBorder(0, 0, 0, 4));
JLabel numText = new JLabel();
TreeComponent comp = new TreeComponent(numIcon, numText);
String str = (String) ((DefaultMutableTreeNode) value).getUserObject();
if (str.equals("one")) {
numIcon.setIcon(oneIcon);
numText.setText("one");
} else if (str.equals("two")) {
numIcon.setIcon(twoIcon);
numText.setText("two");
}
numText.setOpaque(true);
if (selected) {
numText.setBackground(new Color(209, 230, 255));
numText.setBorder(new LineBorder(
new Color(132, 172, 221), 1, false));
} else {
numText.setBackground(Color.white);
numText.setBorder(new LineBorder(Color.white, 1, false));
}
comp.setFocusable(false);
comp.setBackground(Color.white);
return comp;
}
}
public class TreeEditor implements TreeCellEditor {
private TreeRenderer rend;
private TreeComponent editorComponent;
private JTree tree;
private DefaultTreeModel treeModel;
private DefaultMutableTreeNode node;
private String str;
public TreeEditor() {
rend = new TreeRenderer();
}
@Override
public Component getTreeCellEditorComponent(
final JTree tree, final Object value, boolean isSelected,
boolean expanded, boolean leaf, int row) {
this.tree = tree;
treeModel = (DefaultTreeModel) tree.getModel();
node = (DefaultMutableTreeNode) value;
Object userObject = node.getUserObject();
this.str = (String) userObject;
TreeNode[] nodes = treeModel.getPathToRoot(node);
final TreePath path = new TreePath(nodes);
editorComponent = (TreeComponent) rend.getTreeCellRendererComponent(
tree, value, isSelected, expanded, leaf, row, false);
editorComponent.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
TreeEditor.this.stopCellEditing();
int x = e.getX();
if (x >= 0 && x <= 16) {
System.out.println(
"you clicked the image for row " + str);
} else if (x > 16) {
System.out.println(
"you clicked the text for row " + str);
tree.setSelectionPath(path);
}
}
});
return editorComponent;
}
@Override
public boolean isCellEditable(EventObject anEvent) {
return true;
}
@Override
public boolean shouldSelectCell(EventObject anEvent) {
return false;
}
@Override
public boolean stopCellEditing() {
tree.cancelEditing();
return false;
}
@Override
public Object getCellEditorValue() {
return null;
}
@Override
public void cancelCellEditing() {
}
@Override
public void addCellEditorListener(CellEditorListener l) {
}
@Override
public void removeCellEditorListener(CellEditorListener l) {
}
}
}