Why does viewport changelistener get called multiple times

1.2k views Asked by At

I've got a viewport, and I've attached a change listener to it. Whenever I scroll through my viewport, my change listener gets called about four times. I'm not sure how to avoid this; I only want the call to happen once?

2

There are 2 answers

4
MadProgrammer On

There's no way around this, JViewport will fire several stateChanged events because it's providing notification about changes to a number of properties...

From the JavaDocs...

Adds a ChangeListener to the list that is notified each time the view's size, position, or the viewport's extent size has changed.

At this point, it's kind of hard to know what to suggest as we don't know what it is you are trying to achieve, however, if you have to use a ChangeListener, you could set up a coalescing mechanism. That is, rather then responding to each event, you basically wait until a long enough delay has occurred between events before responding to it...

For example...

public class DelayedChangeHandler implements ChangeListener {

    private Timer timer;
    private ChangeEvent last;

    public DelayedChangeHandler() {
        timer = new Timer(250, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                stableStateChanged();
            }
        });
        timer.setRepeats(false);
    }

    @Override
    public void stateChanged(ChangeEvent e) {
        last = e;
        timer.restart();
    }

    protected void stableStateChanged() {
        System.out.println("Finally...");
    }

}

Basically, this is a ChangeListener implementation that uses a non-repeating javax.swing.Timer with a short delay. Each time stateChanged is called, the timer is restart. Finally, when the timer is allowed to "tick", it calls stableStateChanged indicating that enough time has passed since the last event was raised.

This assumes that you don't so much care about what caused the event, only that the event occured...

A runnable example...

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class TestViewport {

    public static void main(String[] args) {
        new TestViewport();
    }

    public TestViewport() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JPanel pane = new JPanel() {
                    @Override
                    public Dimension getPreferredSize() {
                        return new Dimension(1000, 1000);
                    }
                };

                JScrollPane sp = new JScrollPane(pane);
                sp.getViewport().addChangeListener(new DelayedChangeHandler());
                sp.getViewport().addPropertyChangeListener(new PropertyChangeListener() {
                    @Override
                    public void propertyChange(PropertyChangeEvent evt) {
                        System.out.println(evt.getPropertyName());
                    }
                });

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(sp);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class DelayedChangeHandler implements ChangeListener {

        private Timer timer;
        private ChangeEvent last;

        public DelayedChangeHandler() {
            timer = new Timer(250, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    stableStateChanged();
                }
            });
            timer.setRepeats(false);
        }

        @Override
        public void stateChanged(ChangeEvent e) {
            last = e;
            timer.restart();
        }

        protected void stableStateChanged() {
            System.out.println("Finally...");
        }

    }

}
3
alex2410 On

You can try to use AdjustmentListener for gettign scroll event once, try next:

import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.io.UnsupportedEncodingException;

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;

public class Example {

    public static void main(String[] args) throws UnsupportedEncodingException {
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        final JScrollPane pane = new JScrollPane(new JTextArea());
        pane.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {

            @Override
            public void adjustmentValueChanged(AdjustmentEvent e) {
                if(e.getValueIsAdjusting()){
                    return;
                }
                System.out.println("vertical scrolled");
                System.out.println("bar value = " + e.getValue());
            }
        });
        frame.setContentPane(pane);
        frame.setSize(300, 200);
        frame.setVisible(true);
    }
}

Here is another example.