简体   繁体   中英

Getting correct mouse position inside a JScrollPane

Here's my issue:

在此处输入图片说明

I'm basically creating a drop-down menu when an element in a JTree is right clicked, problem is that JTree is inside a JScrollPane and the more scrolled the pane becomes, the further off my popup menu goes.

The right click fires a MouseEvent which gets intercepted in the following code, the results of which is to create a new popup menu.

@Override
public void mouseClicked(MouseEvent e) {
    if (SwingUtilities.isRightMouseButton(e)) {

        int row = tree.getClosestRowForLocation(e.getX(), e.getY());
        tree.setSelectionRow(row);

        TreePath path = tree.getPathForRow(row);  

        DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();  

        // If this object is a Search...
        if(node.getUserObject().getClass() == Search.class) {
            jp = new ItemEditPopUpMenu(tree, row, true);
        } else {
            jp = new ItemEditPopUpMenu(tree, row, false);
        }

        jp.show(this, e.getX(), e.getY());
    }
}

As you can see, all I'm doing is grabbing the position from the MouseEvent and using it as the location in which the popup menu is to be created in the following line:

jp.show(this, e.getX(), e.getY());

Now it's been brought to my attention that this is a relative position to the element that sent the event, and that explains the issue, the JScrollPanel gets offset from the JFrame when it is scrolled, the question is how can I tell how far?

I just don't know and that's what I need help with. Thanks so much in advance!

I don't seem to have an issue.

If I use the view ports "view" as the reference to the popup, the popup shows exactly where the mouse clicked.

I can only imagine that you are not passing the reference to the tree to the popup...

在此处输入图片说明

I thought it was going to be a mess of point conversions, but as it turns out, it was simple as can be...

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class TestScrollPane {

    public static void main(String[] args) {
        new TestScrollPane();
    }

    public TestScrollPane() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new JScrollPane(new TestPane()));
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private List<Point> points;
        private JPopupMenu pm;

        public TestPane() {
            pm = new JPopupMenu();
            pm.add(new JLabel("Suprise"));
            points = new ArrayList<>(3);
            addMouseListener(new MouseAdapter() {
                @Override
                public void mouseClicked(MouseEvent e) {
                    Point p = e.getPoint();
                    points.clear();
                    points.add(p);
                    // View port
                    Container parent = getParent();
                    Point pp = SwingUtilities.convertPoint(TestPane.this, p, parent);
                    points.add(pp);
                    // ScrollPane...
                    parent = parent.getParent();
                    Point ppp = SwingUtilities.convertPoint(TestPane.this, p, parent);
                    points.add(ppp);

                    pm.show(TestPane.this, p.x, p.y);

                    repaint();
                }
            });
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(1000, 1000);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            if (points.size() == 3) {
                g2d.setColor(Color.RED);
                Point p = points.get(0);
                g2d.fillOval(p.x - 2, p.y - 2, 4, 4);

                g2d.setColor(Color.GREEN);
                p = points.get(1);
                g2d.fillOval(p.x - 2, p.y - 2, 4, 4);

                g2d.setColor(Color.BLUE);
                p = points.get(2);
                g2d.fillOval(p.x - 2, p.y - 2, 4, 4);
            }
            g2d.dispose();
        }
    }
}

Just to be sure I wasn't missing something, here's a version using a JTree

在此处输入图片说明

(You can ditch the custom cell rendering for testing, this was some scrap code I had laying around)

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.UIManager;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;

public class TestCustomTreeNode {

    public static void main(String args[]) {
        new TestCustomTreeNode();
    }
    private JPopupMenu pm;

    public TestCustomTreeNode() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (Exception ex) {
                }

                pm = new JPopupMenu();
                pm.add(new JLabel("Suprise"));

                JFrame f = new JFrame("JTree Sample");
                f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

                JPanel pnlMain = new JPanel(new BorderLayout());
                pnlMain.setBackground(Color.white);

                createTree(pnlMain);

                f.setContentPane(new JScrollPane(pnlMain));

                f.setSize(300, 200);
                f.setVisible(true);
            }
        });
    }

    private void createTree(JPanel pnlMain) {
        Employee bigBoss = new Employee(Employee.randomName(), true);
        Employee[] level1 = new Employee[5];
        bigBoss.employees = level1;

        for (int i = 0; i < level1.length; i++) {
            level1[i] = new Employee(Employee.randomName(), true);
        }


        for (int i = 0; i < level1.length; i++) {
            Employee employee = level1[i];
            if (employee.isBoss) {
                int count = 5;
                employee.employees = new Employee[count];

                for (int j = 0; j < employee.employees.length; j++) {
                    employee.employees[j] = new Employee(Employee.randomName(), false);
                }
            }
        }

        CustomTreeNode root = new CustomTreeNode(loadResource("/pirate.png"), bigBoss);
        root.setUserObject("Root");
        DefaultTreeModel model = new DefaultTreeModel(root);

        for (Employee employee : bigBoss.employees) {
            CustomTreeNode boss = new CustomTreeNode(loadResource("/angel.png"), employee);
            root.add(boss);
            if (employee.isBoss) {
                for (Employee employee1 : employee.employees) {
                    CustomTreeNode emp = new CustomTreeNode(loadResource("/devil.png"), employee1);
                    boss.add(emp);
                }
            }
        }

        JTree tree = new JTree(model);
        tree.setCellRenderer(new CustomeTreeCellRenderer());
        pnlMain.add(tree, BorderLayout.CENTER);

        tree.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseReleased(MouseEvent e) {
                if (e.isPopupTrigger()) {
                    pm.show(e.getComponent(), e.getX(), e.getY());
                }
            }
        });

    }

    protected ImageIcon loadResource(String name) {

        ImageIcon image = null;
        try {
            image = new ImageIcon(ImageIO.read(getClass().getResource(name)));
            System.out.println(name + " - " + image);
        } catch (IOException ex) {
            ex.printStackTrace();
        }

        return image;

    }

    public static class Employee {

        public String name;
        public int id;
        public boolean isBoss;
        public Employee[] employees;

        public Employee(String name, boolean isBoss) {
            this.name = name;
            this.isBoss = isBoss;
            this.id = new Random(System.currentTimeMillis()).nextInt(Integer.MAX_VALUE);
        }

        @Override
        public String toString() {
            return this.name;
        }

        static String randomName() {
            String chars = "abcdefghijklmnopqrstuvwxyz";
            StringBuilder builder = new StringBuilder();
            Random r = new Random(System.currentTimeMillis());
            int length = r.nextInt(10) + 1;
            for (int i = 0; i < length; i++) {
                builder.append(chars.charAt(r.nextInt(chars.length())));
            }

            return builder.toString();
        }
    }

    public class CustomTreeNode extends DefaultMutableTreeNode {

        /**
         * The icon which is displayed on the JTree object. open, close, leaf icon.
         */
        private ImageIcon icon;

        public CustomTreeNode(ImageIcon icon) {
            this.icon = icon;
        }

        public CustomTreeNode(ImageIcon icon, Object userObject) {
            super(userObject);
            this.icon = icon;
        }

        public CustomTreeNode(ImageIcon icon, Object userObject, boolean allowsChildren) {
            super(userObject, allowsChildren);
            this.icon = icon;
        }

        public ImageIcon getIcon() {
            return icon;
        }

        public void setIcon(ImageIcon icon) {
            this.icon = icon;
        }
    }

    class CustomeTreeCellRenderer extends DefaultTreeCellRenderer {

        public CustomeTreeCellRenderer() {
        }

        @Override
        public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {

//            if (!leaf) {
            CustomTreeNode node = (CustomTreeNode) value;

            if (node.getIcon() != null) {
                System.out.println(node + " - " + node.getIcon());
                setClosedIcon(node.getIcon());
                setOpenIcon(node.getIcon());
                setLeafIcon(node.getIcon());
            } else {
                System.out.println(node + " - default");
                setClosedIcon(getDefaultClosedIcon());
                setLeafIcon(getDefaultLeafIcon());
                setOpenIcon(getDefaultOpenIcon());
            }
//            }

            super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);

            return this;
        }
    }
}

nb: You might need to change the mouse event as the popup trigger can be different for different platforms, this works just fine on Windows

From this, I can only conclude, that the component reference you are passing to JPopupMenu#show is not the one that generated the mouse events.

I used:

PointerInfo a = MouseInfo.getPointerInfo();
Point b = a.getLocation();
SwingUtilities.convertPointFromScreen(b, component);

Where component was my JTree. This appears to grab the mouses screen position and uses a conversion utility function buried in Swing to make it work out. Hope this helps someone out!

This might not be good solution but it will solve your problem.

The problem here is when you say e.getX() or e.getY() , the co-ordinates returned are relative to JTree. As I can see from given screenshots, you have scrolled down the JTree, you need to substract the difference of vertical scroll bar value from "Y" co-ordinate.

int verticalScrollValue = scrollPane.getVerticalScrollBar().getValue();
jp.show(this, e.getX(), e.getY() - verticalScrollValue);

I had this issue verbatim, JTree and all. While I found the accepted answer useful, it still took some digging to figure out what's wrong and what a fix looks like.

Ergo, here's what problem code looks like:

final Component parent = ...
final JTree tree = ...
final JPopupMenu menu = ...

parent.add(tree);

tree.addMouseListener(new MouseAdapter() {
    @Override
    public void mousePressed(MouseEvent e) {
        if (e.getButton() == MouseEvent.BUTTON3) // Right click
            menu.show(parent, e.getX(), e.getY());
    }
});

The error should be obvious now!

The MouseEvent was generated by tree from the added MouseListener , but we are passing parent as the invoker to menu.show(...) . This produces the undesirable behavior in your screenshots.

The fix is to pass tree as the invoker to menu.show(...) as below:

final Component parent = ...
final JTree tree = ...
final JPopupMenu menu = ...

parent.add(tree);

tree.addMouseListener(new MouseAdapter() {
    @Override
    public void mousePressed(MouseEvent e) {
        if (e.getButton() == MouseEvent.BUTTON3) // Right click
            menu.show(tree, e.getX(), e.getY());
    }
});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM