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

3

8

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?

Aylsworth answered 7/2, 2016 at 21:8 Comment(0)
S
12

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

    }
}
Spermato answered 7/2, 2016 at 22:44 Comment(0)
S
7

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);
    }
}
Spoiler answered 7/2, 2016 at 22:39 Comment(0)
F
4

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);
        }
    }
}
Fording answered 7/2, 2016 at 21:25 Comment(4)
I am sorry; both solutions work like this: When I press down [Ctrl] while rotating the wheel of the mouse - it unnecessarily invoke both; My implemented MouseWheelListener and the default scrolling of the page - How to stop the default scrolling while pressing [Ctrl]Aylsworth
@SalehFeek Sorry, I misunderstood the question. See my updated code, I hope it works now. It should only update the text when pressing CTRL and should only invoke the default scrolling of the JScrollPane when not pressing CTRL.Fording
Your solution now is working OK - Thanks - But I have a small question: this line is invoked repeatedly as the wheel moves scrollPane.getListeners(MouseWheelListener.class)[0].mouseWheelMoved(e);- so my question is; is it a wise practice to invoke repeatedly the same line of code like this; whereas it achieves the same thing every time?!Aylsworth
@SalehFeek The getListeners part of it probably isn't wise, I think the solution with dispatchEvent() that Ian Roberts and Marco13 mentioned is better. I don't see a problem with invoking the same line of code every time though.Fording

© 2022 - 2024 — McMap. All rights reserved.