MouseMotionListener in Java Swing, using it with components inside components etc
Asked Answered
C

3

2

I am working on a Touch User interface in Swing. While I know this isn't optimal, I am on a short deadline and don't have time to Touch-screen specific GUI packages (if there are any).

I want my users to be able to 'swipe' their finger across the screen, and the view of a special JScrollPane I made moves with it. The code is very simple -

    public class PanScrollPane extends JScrollPane implements MouseMotionListener{  
public PanScrollPane() {
    super();        
    this.addMouseMotionListener(this);      
}
@Override
public void mouseDragged(MouseEvent arg0) {
    System.out.println("Mouse Dragged!");       
}
@Override
public void mouseMoved(MouseEvent arg0) {
    System.out.println("Mouse Moved!");     
}

The problem I'm having is that the JScrollPane is a container for all sorts of JComponents. When I first started working on this, I figured the MouseMovedEvent and MouseDraggedEvent would propagate up the 'GUI tree', untill they encountered a Component with a listener specifically for that event. Now it seems that any component I add to the panScrollPane blocks any of these MouseMotion events, leaving me unable to pan.

    panScrollPane.add(new JButton("This thing blocks any mouse motion events"));

I figured propagating the MouseEvent by hand (adding listeners to every single component and then having them send the event to their parent) would work. This, however, is a very time-intensive undertaking and as I would rather spend my time working on other things, I was wondering if any of you know any work-around for this problem.

Thanks for reading, and hopefully thanks for answering! :)

edit: To make my intentions clearer. I only want the mousemotion events to be caught by the panPanel, any other event (like MouseClick, MouseRelease) should be processed normally

Cubitiere answered 26/8, 2011 at 7:44 Comment(0)
Q
2

How about using a GlassPane? I think its meant to address exactly these types of situations.

Quince answered 26/8, 2011 at 8:30 Comment(3)
Looks promising, but will the glasspane 'catch' only the events I'm interested in having it 'catch' (MouseMotion ones) or every single possible event?Cubitiere
Right, the tutorial code shows how to make it propagate any remaining events, I'll test around with this glass pane :) Thanks alot!Cubitiere
@Erwin You could add a MouseInputAdapter as a mouseListner to the parent to listen to the drag events. Other events, I presume will only be dispatched to the child component through it.Quince
G
7

This ad hoc approach leverages the existing JScrollPane actions that are usually used in key bindings. You'll have to tune N to your implementation of Scrollable.

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.Action;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JViewport;
import javax.swing.Timer;

/** @see http://stackoverflow.com/questions/7201509 */
public class ScrollAction extends JPanel {

    private static final int TILE = 64;
    private static final int DELTA = 16;

    public ScrollAction() {
        this.setOpaque(false);
        this.setFocusable(true);
        this.setPreferredSize(new Dimension(50 * TILE, 50 * TILE));
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.setColor(Color.lightGray);
        int w = this.getWidth() / TILE + 1;
        int h = this.getHeight() / TILE + 1;
        for (int row = 0; row < h; row++) {
            for (int col = 0; col < w; col++) {
                if ((row + col) % 2 == 0) {
                    g.fillRect(col * TILE, row * TILE, TILE, TILE);
                }
            }
        }
    }

    private void display() {
        JFrame f = new JFrame("ScrollAction");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        final JScrollPane scrollPane = new JScrollPane(this);
        final ScrollTimer left = new ScrollTimer(scrollPane, "scrollLeft");
        final ScrollTimer right = new ScrollTimer(scrollPane, "scrollRight");
        final ScrollTimer up = new ScrollTimer(scrollPane, "scrollUp");
        final ScrollTimer down = new ScrollTimer(scrollPane, "scrollDown");
        final JViewport viewPort = scrollPane.getViewport();
        viewPort.setPreferredSize(new Dimension(5 * TILE, 5 * TILE));
        viewPort.addMouseMotionListener(new MouseAdapter() {

            @Override
            public void mouseMoved(MouseEvent e) {
                left.stop();
                if (e.getX() < DELTA) {
                    left.start();
                }
                right.stop();
                if (e.getX() > viewPort.getWidth() - DELTA) {
                    right.start();
                }
                up.stop();
                if (e.getY() < DELTA) {
                    up.start();
                }
                down.stop();
                if (e.getY() > viewPort.getHeight() - DELTA) {
                    down.start();
                }
            }
        });
        f.add(scrollPane);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    private static final class ScrollTimer implements ActionListener {

        private static int N = 10;
        private static int DELAY = 100;
        private String cmd;
        private Timer timer;
        private Action action;
        private JScrollPane scrollPane;
        private int count;

        public ScrollTimer(JScrollPane scrollPane, String action) {
            this.cmd = action;
            this.timer = new Timer(DELAY, this);
            this.action = scrollPane.getActionMap().get(action);
            this.scrollPane = scrollPane;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            if (count++ < N) {
                action.actionPerformed(new ActionEvent(scrollPane, 0, cmd));
            } else {
                timer.stop();
            }
        }

        public void start() {
            count = 0;
            timer.start();
        }

        public void stop() {
            timer.stop();
            count = 0;
        }
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                new ScrollAction().display();
            }
        });
    }
}
Gush answered 26/8, 2011 at 10:34 Comment(2)
then just add mouseDragged() for firing Vertical Action +1Gynecocracy
Example added to include vertical actions.Gush
Q
2

How about using a GlassPane? I think its meant to address exactly these types of situations.

Quince answered 26/8, 2011 at 8:30 Comment(3)
Looks promising, but will the glasspane 'catch' only the events I'm interested in having it 'catch' (MouseMotion ones) or every single possible event?Cubitiere
Right, the tutorial code shows how to make it propagate any remaining events, I'll test around with this glass pane :) Thanks alot!Cubitiere
@Erwin You could add a MouseInputAdapter as a mouseListner to the parent to listen to the drag events. Other events, I presume will only be dispatched to the child component through it.Quince
K
2

Getting mouseEvents for a component and all its children is ... tricky to get right. You might consider to rely on stable (and extensively tested :-) code around. The jdk7 way of doing it is to use a JLayer (which internally registers an AWTEventListener as it has all priviledges). For earlier versions, you can use its predecessor JXLayer

Kurus answered 26/8, 2011 at 8:51 Comment(1)
or just to determine if MouseButton is still pressed down +1Gynecocracy

© 2022 - 2024 — McMap. All rights reserved.