views:

538

answers:

2

I have an issue where Swing (in Java 1.6, Windows) doesn't seem to trigger mouseEntered and mouseExited events the way I want it to. I have an application where I wish to have a number of JPanels stacked vertically in a JScrollPane, and that they should be highlighted with a different colour when the mouse is over them. Simple enough problem, but whenever I scroll using the mouse wheel, it doesn't quite behave.

I have made a sample application to illustrate my problem (code found below). The images below are from that one, not the "real" application.

When I hold the mouse cursor over the edge of a panel, it's highlighted correctly. Now, when I use the mouse wheel to scroll down, I expect the cursor to be over box B, and the proper mouseEntered/mouseExited events to be triggered so that A becomes white and B becomes red.

alt text

alt text

However, that doesn't seem to happen.

Now, B becomes highlighted if I trigger another mouse event, be it "move 1 pixel", "click a button" or "scroll another step". Knowing this, I could perhaps solve it in a hackish way, but I'd rather not if there's a proper solution.

So basically what I'm wondering is if this is to be regarded as a bug in Swing, or am I just doing things wrong?

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;

public class ScrollTest extends JFrame {

    public static class LetterPanel extends JPanel {

        private static final Font BIG_FONT = new Font(Font.MONOSPACED, Font.BOLD, 24);

        public LetterPanel(String text) {
            setBackground(Color.WHITE);
            setBorder(BorderFactory.createLineBorder(Color.BLACK));

            addMouseListener(new MouseAdapter() {

                @Override
                public void mouseEntered(MouseEvent e) {
                    setBackground(Color.RED);
                }

                @Override
                public void mouseExited(MouseEvent e) {
                    setBackground(Color.WHITE);
                }
            });

            setLayout(new GridLayout(1, 1));
            setPreferredSize(new Dimension(-1, 50));

            JLabel label = new JLabel(text, SwingConstants.CENTER);
            label.setFont(BIG_FONT);
            add(label);
        }
    }

    public ScrollTest() {
        setLayout(new GridLayout(1, 1));
        setSize(400, 400);

        JPanel base = new JPanel();

        JScrollPane jsp = new JScrollPane(base);
        jsp.getVerticalScrollBar().setUnitIncrement(16);
        add(jsp);

        base.setLayout(new GridBagLayout());
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.gridx = 0;
        gbc.gridy = 0; 
        gbc.gridheight = 1;
        gbc.gridwidth = 1;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        gbc.insets = new Insets(0, 0, 10, 0);
        gbc.weightx = 1.0;

        for (char c = 'A'; c <= 'Z'; c++) {
            base.add(new LetterPanel(String.valueOf(c)), gbc);
            gbc.gridy++;
        }
    }

    public static void main(String[] args) {
        final JFrame f = new ScrollTest();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        SwingUtilities.invokeLater(new Runnable() {

            public void run() {
                f.setVisible(true);
            }
        });
    }
}
A: 

I can get your code to reproduce this reliably but only when I don't quite finish the scrolling. On my mouse at least there is sort of a "catch" when the mouse wheel finished scrolling. If I scroll very slowly I can have it move but it doesn't change the highlight until the mouse wheel has reached the "catch".

When I do that the mouse enter message is received on the previous panel (same behaviour you are seeing).

Looking at it I scroll the mouse and it does not actually receive the exited/entered events unless I scroll enough to have the mouse wheel "catch". It is possible that Windows does not send the message to Java until the "catch" happens... from my testing that is what it looks like.

You might want to look into the MouseWheelListener interface and the MouseInfo class. I guess you might be able to detect the wheel movement and then figure out where you are with MouseInfo.getPointerInfo().getLocation() and then figure out what component you are over and change the highlighting.

TofuBeer
+1  A: 

This seems like a similiar problem to the one described in Tooltips and Scrollpanes. That is, no mouse events are generated because the mouse itself doesn't move, the viewport moves. I'm not sure the exact solution other using the AdjustmentListener to track the component at the mouse location. Every time is changes you can fire a mouseExited event to the previous panel and a mouseEntered event to the new panel.

camickr