0

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?

trashgod
  • 203,806
  • 29
  • 246
  • 1,045
3uPh0riC
  • 480
  • 1
  • 4
  • 18
  • Seems, when you scroll your `ViewPort` state changed several times, because of, you have multiple invocations of `stateChanged(ChangeEvent e)` method. – alex2410 Jan 23 '14 at 06:51
  • voting to close as off-topic (doesn't meet with standard on SO) – mKorbel Jan 23 '14 at 07:15

2 Answers2

2

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

    }

}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • Thanks guys ill try out the suggestions, much appreciated. What im doing is firing the event from a changelistener on my scrollpane in order to draw new components when the user scrolls past a certain point. I cant add all the components up front, its too big, causing memory issues. – 3uPh0riC Jan 23 '14 at 08:19
  • O by the way mKorbel, i appreciate the feedback really i do, but i dont understand saying things like 'voting to close question'. I just needed some help from some smart guys and asked a basic question, got some great answers and ill try them out, not sure whats wrong with that. For me to go and create a runnable short version of the monster program i have here will really be lots of work. If you find my question stupid or have some other problems with it just dont answer it, there are other guys more than willing to help out a knob like me. Thanks – 3uPh0riC Jan 23 '14 at 08:21
  • @3uPh0riC `For me to go and create a runnable short version of the monster program i have here will really be lots of work.` == (equals for me:-) very brazenly abusing that there are wise head, isn't it???, standard could be an [MCVE](http://stackoverflow.com/help/mcve) or link to API/tutorial (e.g. as you can to see in answer by MadProgrammer) – mKorbel Jan 23 '14 at 09:05
  • @3uPh0riC trust me, mKorbel was being helpful, had your question been closed without comment, you would have been wondering why. The fact there are people who are willing to let you know why they think the question should be closed or improved is a god send, to many people downvote and vote for closure without having the guts to state why. I took a massive stab in the dark and was lucky to provide at least one possible solution that might work for you, to often it takes hours of comments, re-work, bribery, cohersion and I get to the point where I just want to re-tract my answer – MadProgrammer Jan 23 '14 at 10:14
2

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.

alex2410
  • 10,904
  • 3
  • 25
  • 41
  • +1 now I don't remember the reason why I'm used [ChangeListener instead of AdjustmentListener](http://stackoverflow.com/a/8249353/714968), ??? there was/were an issue(s) ??? – mKorbel Jan 23 '14 at 07:13
  • @mKorbel Really, I don't remember when I used that listener in the past, but for that issue seems that will be work. ChangeListener for more broad changes, maybe. – alex2410 Jan 23 '14 at 07:39
  • +1 I like this idea - But in my testing, it did raise a lot of events, nice non-the-less – MadProgrammer Jan 23 '14 at 08:21