【问题标题】:Forwarding mouseMoved() events to JTree nodes?将 mouseMoved() 事件转发到 JTree 节点?
【发布时间】:2012-01-17 17:10:40
【问题描述】:

如果我将 JCheckBox 放在 JTree 之外,它会在我将鼠标悬停在它上面时播放动画。当我在 JTree 节点中放置相同的 JCheckbox 时,它不再接收任何 mouseMoved() 事件并且不播放动画。我尝试将这些事件从 JTree 转发到 JCheckBox,但没有任何显示。

我猜问题是同一个 JCheckBox 实例被 JTree“标记”(每个节点一次)。当我将 mouseMoved() 事件转发给共享实例时,它不知道在哪里重新绘制自己。

有什么想法吗?

编辑:这是一个独立的测试用例。请注意,使 JCheckBox 可点击超出了这个问题的范围(我已经在我的应用程序中使用 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);
    }

【问题讨论】:

  • 你是对的。 JCheckBox 用作渲染器。 JTree 是接收鼠标事件的组件。如果你有一个独立的代码示例,我可以提供进一步的帮助。
  • 您可能必须实现自己的渲染器。
  • @Kylar:我添加了一个测试用例。
  • 我用 mouseClicked() 实现了一个简单的,但逻辑是相同的,以检测你想要在 mouseMoved、mouseEntered 等中的哪个节点。

标签: java swing mouseover jtree


【解决方案1】:

我认为没有解决方案有两个原因:

  1. 无论何时修改外观,JCheckBox UI 类(例如 BasicCheckBoxUI)都会调用 repaint(),但您不会看到这些更改,除非您使用 DefaultTreeModel.nodeChanged(TreeNode) 请求 JTree 重绘节点。
  2. 即使您侦听与 UI 类相同的模型事件并在 repaint() 之后触发 DefaultTreeModel.nodeChanged(TreeNode),一些 L&F(尤其是 Insubstantial)也会检查 JCheckBox 是否在 CellRendererPane 内并在这种情况下禁用动画(可能是出于性能原因)。

总之,JTree 设计使得动画节点变得不切实际。

对于它的价值,这是我将事件转发到底层节点的尝试(它有问题,但你明白了):

[...]
    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);
    }
}

编辑:好消息。他们在 JavaFX 中修复了这个问题:http://javafx-jira.kenai.com/browse/RT-19027

【讨论】:

  • +1 用于事件转发代码。如果您能够弄清楚摇摆/awt 事件的所有细节,这可以工作。整个单元格渲染器模式对我来说似乎有限制。
【解决方案2】:

以下是我认为您正在寻找的内容,但如果您正在做更复杂的事情,您可能需要考虑创建一个 CellEditor。:

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);
}
}

【讨论】:

  • 这是另一个实现了实际 MouseMoved 的:pastebin.com/1LLccNyi
  • 请在 Stackoverflow 中编辑您的答案,而不是在 pastebin 上发布。您的 mouseMoved() 解决方案不起作用。如果您将鼠标悬停在真正的 JCheckBox 上而不是 JTree 中的那些上,您会注意到真正的复选框在您将鼠标悬停在其上时会绘制内边框,而 JTree 则不会。
猜你喜欢
  • 1970-01-01
  • 2015-10-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多