3

Is there a way to keep listening to a property change, for a few seconds, then fire an event (call a method)?

For example, when the user enter data in a text field:

textField.textProperty().addListener(new ChangeListener<String>() {
    @Override
    public void changed(ObservableValue<? extends String> arg0, String arg1, String arg2) {
        //before calling a method to do something.. wait for a few seconds ...
        }
    }); 

A scenario would be firing an action based on the string value. For example, hitting "M" for move, or "MA" for mask. I would like to "keep listening" for 2 seconds before making an action.

melkhaldi
  • 899
  • 2
  • 19
  • 40

3 Answers3

5

As Jeffrey pointed out, you can use ReactFX:

EventStreams.valuesOf(textField.textProperty())
        .successionEnds(Duration.ofSeconds(2))
        .subscribe(s -> doSomething(s));
Tomas Mikula
  • 6,537
  • 25
  • 39
2

There are a few ways to solve this.

Usually I would recommend the Java Timer API, but if by "making an action" you imply updating stuff on the FX thread, you would have to synchronize the threads, which is bothersome.

Depending on your use case, you could instead use Transitions or the Timeline in FX.

Here is an example with a transition:

package application;

import javafx.animation.RotateTransition;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.image.ImageView;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.scene.text.FontPosture;
import javafx.scene.text.FontWeight;
import javafx.stage.Stage;
import javafx.util.Duration;

public class TransitionedInputExample extends Application {
  @Override
  public void start(final Stage stage) {
    final ImageView spinner =
        new ImageView("https://cdn1.iconfinder.com/data/icons/duesseldorf/16/process.png");
    spinner.setVisible(false);

    final Label title = new Label("Timed Action Commander Example", spinner);
    title.setContentDisplay(ContentDisplay.BOTTOM);
    title.setFont(Font.font("Helvetica", FontWeight.BOLD, FontPosture.REGULAR, 16));

    final TextField textInput = new TextField();
    textInput.setPromptText("Enter command");

    final TextArea textOutput = new TextArea();
    textOutput.setPromptText("Command results will show up here");

    final VBox layout = new VBox(title, textInput, textOutput);
    layout.setSpacing(24);

    // setup some transition that rotates an icon for 2 seconds
    final RotateTransition rotateTransition = new RotateTransition(Duration.seconds(1), spinner);
    rotateTransition.setByAngle(90);
    // delay rotation so that user can type without being distracted at once
    rotateTransition.setDelay(Duration.seconds(1));

    // restart transition on user input
    textInput.textProperty().addListener((observable, oldText, newText) -> {
      spinner.setVisible(true);
      rotateTransition.playFromStart();
    });

    rotateTransition.setOnFinished((finishHim) -> {
      // execute command
        textOutput.setText("Executing " + textInput.getText());
        spinner.setVisible(false);
      });


    final Scene scene = new Scene(layout);
    stage.setScene(scene);
    stage.show();
  }

  public static void main(final String[] args) {
    launch(args);
  }
}

For a solutition using a Timeline see this post .

Community
  • 1
  • 1
Oliver Jan Krylow
  • 1,758
  • 15
  • 22
1

You might consider using Inhibeans. It allows you to block the handling of an event until you want the change events to fire. The whole ReactFX project might also be useful because what essentially what you need to do is build a state machine of events. You are looking for patterns in the events like regex.

For example, let's say you use 'M' for move and 'MA' for mask. You'll have two paths through your state machine.

M

M -> A

Then you can use a timer to determine how long you'll wait for the events to pile up before you process it.

Checkout this sample copied from the ReactFX site:

reduceSuccessions

Accumulates events emitted in close temporal succession into one.

EventSource<Integer> source = new EventSource<>();
EventStream<Integer> accum = source.reduceSuccessions((a, b) -> a + b, Duration.ofMillis(200));

source.push(1);
source.push(2);
// wait 150ms
source.push(3);
// wait 150ms
source.push(4);
// wait 250ms
source.push(5);
// wait 250ms

In the above example, an event that is emitted no later than 200ms after the previous one is accumulated (added) to the previous one. accum emits these values: 10, 5.

Community
  • 1
  • 1
Jeffrey Guenther
  • 871
  • 10
  • 27