Reputation: 90003
If I place a JCheckBox outside of a JTree it plays an animation when I hover over it. When I place the same JCheckbox inside a JTree node, it no longer receives any mouseMoved() events and no animation is played. I tried forwarding these events from the JTree to the JCheckBox but nothing shows up.
I'm guessing the problem is that the same JCheckBox instance is "stamped" by a JTree (once per node). When I forward mouseMoved() event to the shared instance, it doesn't know where to repaint itself.
Any ideas?
EDIT: Here is a self-contained testcase. Please note that making the JCheckBox clickable is outside the scope of this question (I've already done so in my application using a TreeCellEditor).
import java.awt.BorderLayout;
import java.awt.Component;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeCellRenderer;
public class HoverBug
{
public static class TreeRenderer implements TreeCellRenderer
{
private final JCheckBox checkbox = new JCheckBox();
@Override
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected,
boolean expanded, boolean leaf, int row, boolean hasFocus)
{
DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
checkbox.setSelected((Boolean) node.getUserObject());
return checkbox;
}
}
public static void main(String[] args)
{
JCheckBox checkbox = new JCheckBox("See... this works!");
DefaultMutableTreeNode root = new DefaultMutableTreeNode(Boolean.TRUE);
DefaultMutableTreeNode child1 = new DefaultMutableTreeNode(Boolean.FALSE);
DefaultMutableTreeNode child2 = new DefaultMutableTreeNode(Boolean.FALSE);
root.add(child1);
root.add(child2);
DefaultTreeModel model = new DefaultTreeModel(root);
JTree tree = new JTree(model);
tree.setCellRenderer(new TreeRenderer());
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().setLayout(new BorderLayout());
frame.getContentPane().add(checkbox, BorderLayout.NORTH);
frame.getContentPane().add(tree, BorderLayout.CENTER);
frame.setSize(800, 600);
frame.setVisible(true);
}
Upvotes: 2
Views: 1695
Reputation: 90003
I don't think there is a solution for two reasons:
In summary, JTree design makes it impractical to animate nodes.
For what it's worth, here is my attempt at forwarding events to the underlying node (it's buggy but you get the idea):
[...]
MouseAdapterImpl listener = new MouseAdapterImpl(tree);
tree.addMouseListener(listener);
tree.addMouseMotionListener(listener);
tree.addMouseWheelListener(listener);
[...]
private class MouseAdapterImpl implements MouseListener, MouseWheelListener, MouseMotionListener
{
private final JTree tree;
private int lastRow = -1;
public MouseAdapterImpl(JTree tree)
{
this.tree = tree;
}
/**
* Returns the mouse position relative to the JTree row.
* <p/>
* @param e the mouse event
*/
private void forwardEvent(MouseEvent e)
{
int row = tree.getRowForLocation(e.getX(), e.getY());
Rectangle bounds;
Point point;
if (row == -1)
{
bounds = null;
point = null;
}
else
{
bounds = tree.getRowBounds(row);
point = new Point(e.getX() - bounds.x, e.getY() - bounds.y);
}
if (lastRow != row)
{
if (lastRow != -1)
{
Rectangle lastBounds = tree.getRowBounds(lastRow);
if (lastBounds != null)
{
Point lastPoint = new Point(e.getX() - lastBounds.x, e.getY() - lastBounds.y);
dispatchEvent(new MouseEvent(checkbox, MouseEvent.MOUSE_EXITED,
System.currentTimeMillis(), 0, lastPoint.x, lastPoint.y, 0, false, 0), lastRow);
}
}
if (row != -1)
{
dispatchEvent(new MouseEvent(checkbox, MouseEvent.MOUSE_ENTERED,
System.currentTimeMillis(), 0, point.x, point.y, 0, false, 0), row);
}
}
lastRow = row;
if (row == -1)
return;
dispatchEvent(new MouseEvent(checkbox, e.getID(),
System.currentTimeMillis(), e.getModifiers(), point.x, point.y, e.getClickCount(),
e.isPopupTrigger(), e.getButton()), row);
}
private void dispatchEvent(MouseEvent e, int row)
{
checkbox.dispatchEvent(e);
TreePath pathForLocation = tree.getPathForRow(row);
if (pathForLocation == null)
return;
Object lastPathComponent = pathForLocation.getLastPathComponent();
if (lastPathComponent instanceof DefaultMutableTreeNode)
{
DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
model.nodeChanged((DefaultMutableTreeNode) lastPathComponent);
}
}
@Override
public void mouseEntered(MouseEvent e)
{
forwardEvent(e);
}
@Override
public void mouseMoved(MouseEvent e)
{
forwardEvent(e);
}
@Override
public void mouseExited(MouseEvent e)
{
forwardEvent(e);
}
@Override
public void mouseClicked(MouseEvent e)
{
forwardEvent(e);
}
@Override
public void mousePressed(MouseEvent e)
{
forwardEvent(e);
}
@Override
public void mouseReleased(MouseEvent e)
{
forwardEvent(e);
}
@Override
public void mouseWheelMoved(MouseWheelEvent e)
{
forwardEvent(e);
}
@Override
public void mouseDragged(MouseEvent e)
{
forwardEvent(e);
}
}
EDIT: Good news. They fixed this in JavaFX: http://javafx-jira.kenai.com/browse/RT-19027
Upvotes: 1
Reputation: 9334
Here's what I think you're looking for below, but if you're doing anything more complicated, you may want to look into creating a CellEditor instead.:
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreePath;
public class HoverBug {
public static class TreeRenderer implements TreeCellRenderer {
private final JCheckBox checkbox = new JCheckBox();
@Override
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected,
boolean expanded, boolean leaf, int row, boolean hasFocus) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
checkbox.setSelected((Boolean) node.getUserObject());
checkbox.setText("Row:" + row);
return checkbox;
}
}
public static void main(String[] args) {
JCheckBox checkbox = new JCheckBox("See... this works!");
DefaultMutableTreeNode root = new DefaultMutableTreeNode(Boolean.TRUE);
DefaultMutableTreeNode child1 = new DefaultMutableTreeNode(Boolean.FALSE);
DefaultMutableTreeNode child2 = new DefaultMutableTreeNode(Boolean.FALSE);
root.add(child1);
root.add(child2);
final DefaultTreeModel model = new DefaultTreeModel(root);
final JTree tree = new JTree(model);
tree.setCellRenderer(new TreeRenderer());
tree.addMouseListener(new MouseListener() {
@Override
public void mouseClicked(MouseEvent e) {
TreePath pathForLocation = tree.getPathForLocation(e.getX(), e.getY());
Object lastPathComponent = pathForLocation.getLastPathComponent();
if(lastPathComponent instanceof DefaultMutableTreeNode){
Boolean oldObject = (Boolean) ((DefaultMutableTreeNode)lastPathComponent).getUserObject();
((DefaultMutableTreeNode)lastPathComponent).setUserObject(!oldObject);
model.nodeChanged((DefaultMutableTreeNode)lastPathComponent);
}
}
@Override
public void mousePressed(MouseEvent e) {
}
@Override
public void mouseReleased(MouseEvent e) {
}
@Override
public void mouseEntered(MouseEvent e) {
}
@Override
public void mouseExited(MouseEvent e) {
}
});
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().setLayout(new BorderLayout());
frame.getContentPane().add(checkbox, BorderLayout.NORTH);
frame.getContentPane().add(tree, BorderLayout.CENTER);
frame.setSize(800, 600);
frame.setVisible(true);
}
}
Upvotes: 0