javaswingjslider

constantly changing JSlider, event on user interaction?


I am currently working on a little MP3-Player with Java Swing. I want to implement a progress bar that allows the user to skip to a random position. I thought using a JSlider might be the best option.

The problem now is, that I use a Swing Timer to update the progress bar every 200m. That way, the slider's change event fires every time. Is there any way that I can filter out event calls that were not caused by the user?

I am currently trying with setValueIsAdjusting(true) in the timer, but that seems to be very inconsistent...

Timer progressTimer = new Timer(200, e -> {
    updateProgress(player.getPosition());
});
progressTimer.start();

progressSlider.addChangeListener(new ChangeListener() {
    @Override
    public void stateChanged(ChangeEvent e) {
        if (progressSlider.getValueIsAdjusting()) {
            return;
        }
        System.out.println("Skipping to "+progressSlider.getValue());
        player.skipTo(progressSlider.getValue());
    }
});

the System.out sometimes prints the position the user clicks on, sometimes the position the slider got updated with the timer. Any help?


Solution

  • The way I have always solved this for JSliders is via a private boolean flag. Directly before I set the JSlider value programmatically, I set the flag to true. At the beginning of the JSlider event code, I immediately return in case the flag is true.

    Setting the flag to false is the tricky part, because if you do that immediately after setting the JSlider value programmatically, the change event won't even have executed yet, so when it actually executes, the flag will be false and nothing will have been gained.

    Excerpt from the JavaDocs of SwingUtilities.invokeLater():

    "If invokeLater is called from the event dispatching thread [...] will still be deferred until all pending events have been processed."

    Since you're already in the AWT EDT, and the goal is to let the JSlider react to the change (and immediately return) and then to set the flag to false, the solution is brilliantly simple and has never failed me:

    ignoreEvents = true;
    jSlider.setValue(newValue);
    SwingUtilities.invokeLater(() -> ignoreEvents = false);
    


    EDIT:

    If the situation is really simple (You want to ignore just one event.), you could do this:

    Like above, set the boolean flag before calling setValue. In the JSlider event code, if the flag is set, clear the flag and return. No SwingUtilities.invokeLater() needed here.