4

I'm working on an JavaFX 8 app right now, where i have a tableView and some textFields above which make it possible to search/filter for certain columns in the tableView. I have added a listener to the textFields, to trigger the filtering automatically when a change is detected. I used the code below to do this.

textField_filterAddress.textProperty().addListener((observable, oldValue, newValue) -> {
            doSomething(); // in this case, filter table data and refresh tableView afterwards
        });

My question now is: what's the easiest way to integrate some kind of time delay, before the filtering gets triggered? I'd like to wait a few milliseconds, because everytime the user is filtering it's executing a new database query and i don't think this is necessary for every single char that the user puts in. I'd rather wait until he/she finished his input. Is there some kind of feature like this already built into the whole listener thing? Or do i have to implement my own solution? If so, how? I thought about some kind of concurrency solution, so the rest of the software won't freeze during the waiting period. But i thought i'd ask here if there is an easier solution before thinking too much about my own way...

Big thanks in advance!

benkenobi26
  • 39
  • 1
  • 4
  • 1
    How much data in total are you expecting? If it's not too much it may be reasonable to load everything initially and use a `FilteredList`. – Itai Jan 14 '16 at 07:58
  • sillyfly, unfortunatelly i wasn't allowed to load everything initially, because it was a university project with certain restrictions – benkenobi26 Feb 03 '16 at 09:32

2 Answers2

19

The code below will schedule to do something after a 1 second delay from the last time a text field changes. If the text field changes within that 1 second window, the previous change is ignored and the something is scheduled to be done with the new value 1 second from the most recent change.

PauseTransition pause = new PauseTransition(Duration.seconds(1));
textField.textProperty().addListener(
    (observable, oldValue, newValue) -> {
        pause.setOnFinished(event -> doSomething(newValue));
        pause.playFromStart();
    }
);

I didn't test this, but it should work :-)

A more sophisticated solution might be to make use of a "forgetful" ReactFX suspendable event stream. ReactFX based solutions are discussed in the related question:

Community
  • 1
  • 1
jewelsea
  • 150,031
  • 14
  • 366
  • 406
  • 2
    hi jewelsea, my reply is also time delayed :D i ended up using your solution, if worked perfectly, thanks for that! – benkenobi26 Feb 03 '16 at 09:30
0

The PauseTransition solution can be made more generic.

public abstract class DelayedListener<T> implements ChangeListener<T> {
    private final PauseTransition pause = new PauseTransition(javafx.util.Duration.seconds(1));
    private T ov, nv;

    public DelayedListener() {
        pause.setOnFinished(event -> {
            if (!Objects.equals(nv, ov)) {
                onChanged(nv);
                ov = nv;
            }
        });
    }

    @Override
    public void changed(@NotNull ObservableValue<? extends T> observable, T oldValue, T newValue) {
        nv = newValue;
        pause.playFromStart();
    }

    public abstract void onChanged(T value);
}

Then use it with any property like this (I usually need only newValue):

textField.textProperty().addListener(new DelayedListener<>() {
        @Override
        public void onChanged(String value) {
            System.out.println("Changed to " + value);
        }
    });
RoK
  • 960
  • 1
  • 7
  • 6