Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaFX Spinner change is slow with click and hold of mouse button

The speed of Spinner update is slow when I click and hold the up/down arrow buttons. Is there a way to increase the change speed?

When I click, click, click with the mouse, the spinner values change as fast as I click. It also changes fast if I use the up/down arrows on the keyboard for each key press or if I hold down the up/down arrow keys. I want the values to change that fast when I click and hold on the arrow buttons.

Anyone know a way to do that?

like image 204
swilson Avatar asked Dec 08 '16 22:12

swilson


3 Answers

The SpinnerBehavior of the SpinnerSkin triggers updates every 750 ms. Unfortunately there is no way to simply set/modify this behavour without using reflection to access private members. Therefore the only way to do this without reflection is using event filters to trigger the updates at a faster rate:

private static final PseudoClass PRESSED = PseudoClass.getPseudoClass("pressed");

@Override
public void start(Stage primaryStage) {
    Spinner<Integer> spinner = new Spinner(Integer.MIN_VALUE, Integer.MAX_VALUE, 0);

    class IncrementHandler implements EventHandler<MouseEvent> {

        private Spinner spinner;
        private boolean increment;
        private long startTimestamp;

        private static final long DELAY = 1000l * 1000L * 750L; // 0.75 sec
        private Node button;

        private final AnimationTimer timer = new AnimationTimer() {

            @Override
            public void handle(long now) {
                if (now - startTimestamp >= DELAY) {
                    // trigger updates every frame once the initial delay is over
                    if (increment) {
                        spinner.increment();
                    } else {
                        spinner.decrement();
                    }
                }
            }
        };

        @Override
        public void handle(MouseEvent event) {
            if (event.getButton() == MouseButton.PRIMARY) {
                Spinner source = (Spinner) event.getSource();
                Node node = event.getPickResult().getIntersectedNode();

                Boolean increment = null;
                // find which kind of button was pressed and if one was pressed
                while (increment == null && node != source) {
                    if (node.getStyleClass().contains("increment-arrow-button")) {
                        increment = Boolean.TRUE;
                    } else if (node.getStyleClass().contains("decrement-arrow-button")) {
                        increment = Boolean.FALSE;
                    } else {
                        node = node.getParent();
                    }
                }
                if (increment != null) {
                    event.consume();
                    source.requestFocus();
                    spinner = source;
                    this.increment = increment;

                    // timestamp to calculate the delay
                    startTimestamp = System.nanoTime();

                    button = node;

                    // update for css styling
                    node.pseudoClassStateChanged(PRESSED, true);

                    // first value update
                    timer.handle(startTimestamp + DELAY);

                    // trigger timer for more updates later
                    timer.start();
                }
            }
        }

        public void stop() {
            timer.stop();
            button.pseudoClassStateChanged(PRESSED, false);
            button = null;
            spinner = null;
        }
    }

    IncrementHandler handler = new IncrementHandler();
    spinner.addEventFilter(MouseEvent.MOUSE_PRESSED, handler);
    spinner.addEventFilter(MouseEvent.MOUSE_RELEASED, evt -> {
        if (evt.getButton() == MouseButton.PRIMARY) {
            handler.stop();
        }
    });

    Scene scene = new Scene(spinner);

    primaryStage.setScene(scene);
    primaryStage.show();
}       
like image 134
fabian Avatar answered Nov 17 '22 04:11

fabian


I modified the answer of fabian a little bit to decrease the speed of the spinner while holding mouse down:

private int currentFrame = 0;
private int previousFrame = 0;        

@Override
public void handle(long now)
{
    if (now - startTimestamp >= initialDelay)
        {
        // Single or holded mouse click
        if (currentFrame == previousFrame || currentFrame % 10 == 0)
        {
            if (increment)
            {
                spinner.increment();
            }
            else
            {
                spinner.decrement();
            }
        }
    }

    ++currentFrame;
}

And after stopping the timer we adjust previousFrame again:

public void stop()
{
    previousFrame = currentFrame;

    [...]
}
like image 41
Nighty42 Avatar answered Nov 17 '22 04:11

Nighty42


A small improvement to Fabian's answer. Making the following mod to the MOUSE_RELEASED addEventerFilter will stop a NullPointerException caused when clicking the textfield associated with the spinner. Cheers Fabian!

    spinner.addEventFilter(MouseEvent.MOUSE_RELEASED, evt -> {
        Node node = evt.getPickResult().getIntersectedNode();
        if (node.getStyleClass().contains("increment-arrow-button") ||
            node.getStyleClass().contains("decrement-arrow-button")) {
                if (evt.getButton() == MouseButton.PRIMARY) {
                    handler.stop();
                }
        }
    });
like image 2
Art Avatar answered Nov 17 '22 04:11

Art