javaswingmouseovermousemotionlistener

Java Swing: Is there a way to check for MouseOver for a drawn shape without checking every time the mouse moves?


I have a JPanel that I draw upon. The shapes I draw are some objects stored in a list. I would like to register MouseOvers over these drawn objects. What I am currently doing is adding a MouseMotionListener that checks the list of objects for a hit every time the mouse moves. This is of course pretty inefficient once there are a lot of objects. Is there a better way to check for MouseOvers than just checking all the objects every time the mouse moves?

Here is a minimal example:

class Ui.java:

public class Ui {
    private static JFrame frame;
    private static DrawPanel drawPanel;

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> { createGui();
        });
    }
    private static void createGui() {
        frame = new JFrame("Test");
        drawPanel = new DrawPanel();
        frame.setContentPane(drawPanel);
        frame.pack();
        frame.setVisible(true);
        frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
    }
}

class SomeObject.java:

public class SomeObject {
    //class that represents some object that will be drawn. note that this is 
    //just a minmal example and that in my actual application, there are      
    //other aspects and functions to this class that have nothing to do with drawing. 
    //This is just kept small for the sake being a minimal example
    private String id;
    private Point2D origin;
    private int length;
    private int height;

    public SomeObject(String id, Point2D origin, int length, int height) {
        this.id = id;
        this.origin = origin;
        this.length = length;
        this.height = height;
    }

    public String getId() {
        return id;
    }

    public Point2D getOrigin() {
        return origin;
    }

    public int getLength() {
        return length;
    }

    public int getHeight() {
        return height;
    }
}

class CustomMouseMotionListener.java:

public class CustomMouseMotionListener implements java.awt.event.MouseMotionListener {
    public CustomMouseMotionListener() { }

    @Override
    public void mouseDragged(MouseEvent e) {

    }

    @Override
    public void mouseMoved(MouseEvent e) {
        for (SomeObject object: DrawPanel.objectsToDraw) {
            Shape s = new Rectangle((int)object.getOrigin().getX(), (int)object.getOrigin().getY(), object.getLength(), object.getHeight());
            if (s.contains(e.getPoint())) {
                System.out.println("hit: " + object.getId());
            }
        }
    }
}

class DrawPanel.java:

public class DrawPanel extends JPanel {
    public static List<SomeObject> objectsToDraw = new ArrayList<>();
    public DrawPanel() {
        objectsToDraw.add( new SomeObject("a", new Point2D.Float(20,1), 20,20));
        objectsToDraw.add( new SomeObject("b", new Point2D.Float(20,45), 20,20));

        addMouseMotionListener(new CustomMouseMotionListener());
        setFocusable(true);
        setVisible(true);
        grabFocus();
    }

    protected void paintComponent(Graphics g) {
        Graphics2D g2D = (Graphics2D) g;
        super.paintComponent(g2D);
        for (SomeObject object: objectsToDraw) {
            g.drawRect((int)object.getOrigin().getX(), (int)object.getOrigin().getY(), object.getLength(), object.getHeight());

        }
    }
}

Solution

  • There is more to SomeObject than just being drawn, so I think I can't just replace it with Shape,

    Then you keep a Rectangle instance as part of your SomeObject class, instead of your Point and length/height variables.

    Then you modify your methods to return the values from the Rectangle.

    This will prevent you from continually creating new Rectangle instances, make the process more memory efficient and reducing garbage collection.

    But, you should also be able to extend the Rectangle class and add your extra functionality in the same way that you extend JPanel to add custom painting logic.