Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement MouseWheelListener for JPanel without breaking its default implementation?

Simply; I have a JPanel inside a JScrollPane;

As expected; JScrollPane by default listens to MouseWheelEvent so that scrolling is working well for when the wheel is rotating and while the cursor is hovering over the JPanel.

But After that; I just updated JPanel so that it implements MouseWheelListener, and I added this mouse wheel listener for the JPanel itself.

@Override
public void mouseWheelMoved(MouseWheelEvent e) {
    if (e.isControlDown()) {
        if (e.getWheelRotation() < 0) {
            System.out.println("mouse wheel Up");
        } else {
            System.out.println("mouse wheel Down");
        }
    }
}

The JPanel responds to this implementation for when both; the Ctrl is pressed down and the wheel is rotating and while the cursor is hovering over the JPanel. But the default behaviour of the JScrollPane is unexpectedly lost!!!

When I rotate the wheel while the cursor is hovering over the JPanel the scrolls of the JScrollPane is not responding!!!

It seems that; this implementation of MouseWheelListener breaks the default of a JPanel.

So; How to implement MouseWheelListener for JPanel without breaking its default implementation?

like image 684
Saleh Feek Avatar asked Feb 07 '16 21:02

Saleh Feek


3 Answers

MouseWheelEvents and the scrolling behavior have some subtle caveats.

For example, when you open this page (I mean THIS one, which you are currently reading), place the mouse in the middle, and start srolling down with the wheel, you will scroll over the code snippets. Note that although the code snippets are contained in code blocks that have a scrollbar, continuously rotating the mouse wheel will not trigger a scrolling in the code blocks, but only in the whole page. But when you once move the mouse while it is inside a code block, and afterwards roll the mouse wheel, then you will scroll in the code block only - and not the whole page.

Similarly, rotating the mouse wheel may not affect the hovered scrollable. I think it depends on the Window Manager and the Look & Feel, but in some cases, you will scroll the scroll pane that contains the focussed component - even if the mouse cursor is outside of this component, and even if it is over a scollable component (you can also observe this, for example, in the Windows Explorer)!


However, some of these mechanisms and subtleties can be found in the Swing components as well. For example, the redispatching mechanism that passes MouseWheelEvents to the ancestors if they are not handled by the component itself.

Following this pattern, a solution (that is conceptually similar to the one that LuxxMiner proposed, but may be a tad more generic) may be to simply re-dispatch the MouseWheelEvent to the parent component:

package stackoverflow;

import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;

public class MouseWheelListenerForPanelInScrollpane
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                createAndShowGUI();
            }
        });
    }

    private static void createAndShowGUI()
    {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        f.getContentPane().setLayout(new GridLayout(1,2));

        MouseWheelListenerPanel m = new MouseWheelListenerPanel();
        m.setPreferredSize(new Dimension(100,4000));
        JScrollPane scrollPane = new JScrollPane(m);
        f.getContentPane().add(scrollPane);

        f.setSize(500,500);
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }
}

class MouseWheelListenerPanel extends JPanel implements MouseWheelListener
{
    MouseWheelListenerPanel()
    {
        addMouseWheelListener(this);
    }

    @Override
    public void mouseWheelMoved(MouseWheelEvent e)
    {
        if (e.isControlDown())
        {
            if (e.getWheelRotation() < 0)
            {
                System.out.println("mouse wheel Up");
            }
            else
            {
                System.out.println("mouse wheel Down");
            }
        }
        else
        {
            getParent().dispatchEvent(e);
        }

    }
}
like image 151
Marco13 Avatar answered Nov 17 '22 07:11

Marco13


Add an else to re-dispatch the event directly to the scroll pane if ctrl is not down:

@Override
public void mouseWheelMoved(MouseWheelEvent e) {
    if (e.isControlDown()) {
        if (e.getWheelRotation() < 0) {
            System.out.println("mouse wheel Up");
        } else {
            System.out.println("mouse wheel Down");
        }
    } else {
        // pass the event on to the scroll pane
        getParent().dispatchEvent(e);
    }
}
like image 43
Ian Roberts Avatar answered Nov 17 '22 07:11

Ian Roberts


I don't know if this really qualifies as an proper answer, since it's kind of a workaround, but I came up with the following solution: Just invoke the mouseWheelMoved method of the scrollPane only when Ctrl isn't being pressed:

if (e.isControlDown()) {
    if (e.getWheelRotation() < 0) {
        infoLabel.setText("Mouse Wheel Up");
    } else {
        infoLabel.setText("Mouse Wheel Down");
    }
} else {
    scrollPane.getListeners(MouseWheelListener.class)[0].mouseWheelMoved(e);
}

Full Example:

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.border.TitledBorder;

public class Example {
    public Example() {
        JFrame frame = new JFrame();
        frame.setLayout(new BorderLayout());
        frame.add(new ScrollPanePanel());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(400, 400);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                new Example();
            }
        });
    }
}

class ScrollPanePanel extends JPanel implements MouseWheelListener {
    private JLabel infoLabel;
    private JScrollPane scrollPane;

    public ScrollPanePanel() {

        JPanel panel = new JPanel(new GridLayout(0, 1));
        for (int i = 1; i <= 100; i++) {
            panel.add(new JLabel("Label " + i));
        }
        panel.addMouseWheelListener(this);
        scrollPane = new JScrollPane(panel);

        infoLabel = new JLabel(" ");
        JPanel infoPanel = new JPanel();
        infoPanel.add(infoLabel);

        setLayout(new BorderLayout());
        add(scrollPane);
        add(infoPanel, BorderLayout.SOUTH);

    }

    @Override
    public void mouseWheelMoved(MouseWheelEvent e) {
        if (e.isControlDown()) {
            if (e.getWheelRotation() < 0) {
                infoLabel.setText("Mouse Wheel Up");
            } else {
                infoLabel.setText("Mouse Wheel Down");
            }
        } else {
            scrollPane.getListeners(MouseWheelListener.class)[0].mouseWheelMoved(e);
        }
    }
}
like image 4
Lukas Rotter Avatar answered Nov 17 '22 07:11

Lukas Rotter