Re-firing a consumed event in JavaFX - java

I am developing a system that allows the user to scan barcodes. The barcode scanner effectively behaves like a keyboard, "typing" each digit of the barcode at super-human speeds. For the sake of this example, let's say that most amount of time between successive "key strokes" is 10 milliseconds.
I began by implementing an EventHandler that listens for numeric KeyEvents on the application's Window. When a KeyEvent arrives, the handler does not yet know if it was entered by a human or by a barcode scanner (it will know 10 milliseconds from now). Unfortunately, I must make a decision now or risk locking up JavaFX's main thread, so I automatically call keyEvent.consume() to prevent it from being handled.
After 10 milliseconds have elapsed, a timer wakes up and decides whether or not the KeyEvent was part of a barcode. If it was, the KeyEvents are concatenated together and handled by the barcode processing logic. Otherwise, I want to let the application handle the KeyEvent normally.
How can I force the application to handle a KeyEvent after I have already called keyEvent.consume() on it?

Here is my take on how this might be done.
The solution works by filtering the key events for the app, cloning them and placing the cloned events in a queue, then consuming the original events in the filter. The cloned event queue is processed at a later time. Events from the barcode reader are not refired. Events that are not from the barcode reader are refired so that the system can process them. Data structures keep track of whether the events have been processed already or not, so that the system can know in the event filter whether it truly has to intercept and consume the events or let them pass through to the standard JavaFX event handlers.
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
// delays event key press handling so that some events can be intercepted
// and routed to a bar code a reader and others can be processed by the app.
public class EventRefire extends Application {
public static void main(String[] args) { launch(args); }
#Override public void start(final Stage stage) throws Exception {
// create the scene.
final VBox layout = new VBox();
final Scene scene = new Scene(layout);
// create a queue to hold delayed events which have not yet been processed.
final List<KeyEvent> unprocessedEventQueue = new ArrayList();
// create a queue to hold delayed events which have already been processed.
final List<KeyEvent> processedEventQueue = new ArrayList();
// create some controls for the app.
final TextField splitterField1 = new TextField(); splitterField1.setId("f1");
final TextField splitterField2 = new TextField(); splitterField2.setId("f2");
final Label forBarCode = new Label();
final Label forTextField = new Label();
// filter key events on the textfield and don't process them straight away.
stage.addEventFilter(KeyEvent.ANY, new EventHandler<KeyEvent>() {
#Override public void handle(KeyEvent event) {
if (event.getTarget() instanceof Node) {
if (!processedEventQueue.contains(event)) {
unprocessedEventQueue.add((KeyEvent) event.clone());
event.consume();
} else {
processedEventQueue.remove(event);
}
}
}
});
// set up a timeline to simulate handling delayed event processing from
// the barcode scanner.
Timeline timeline = new Timeline(
new KeyFrame(
Duration.seconds(1),
new EventHandler() {
#Override public void handle(Event timeEvent) {
// process the unprocessed events, routing them to the barcode reader
// or scheduling the for refiring as approriate.
final Iterator<KeyEvent> uei = unprocessedEventQueue.iterator();
final List<KeyEvent> refireEvents = new ArrayList();
while (uei.hasNext()) {
KeyEvent event = uei.next();
String keychar = event.getCharacter();
if ("barcode".contains(keychar)) {
forBarCode.setText(forBarCode.getText() + keychar);
} else {
forTextField.setText(forTextField.getText() + keychar);
refireEvents.add(event);
}
}
// all events have now been processed - clear the unprocessed event queue.
unprocessedEventQueue.clear();
// refire all of the events scheduled to refire.
final Iterator<KeyEvent> rei = refireEvents.iterator();
while (rei.hasNext()) {
KeyEvent event = rei.next();
processedEventQueue.add(event);
if (event.getTarget() instanceof Node) {
((Node) event.getTarget()).fireEvent(event);
}
}
}
}
)
);
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.play();
// layout the scene.
final GridPane grid = new GridPane();
grid.addRow(0, new Label("Input Field 1:"), splitterField1);
grid.addRow(1, new Label("Input Field 2:"), splitterField2);
grid.addRow(2, new Label("For App:"), forTextField);
grid.addRow(3, new Label("For BarCode:"), forBarCode);
grid.setStyle("-fx-padding: 10; -fx-vgap: 10; -fx-hgap: 10; -fx-background-color: cornsilk;");
Label instructions = new Label("Type letters - key events which generate the lowercase letters b, a, r, c, o, d, e will be routed to the barcode input processor, other key events will be routed back to the app and processed normally.");
instructions.setWrapText(true);
layout.getChildren().addAll(grid, instructions);
layout.setStyle("-fx-padding: 10; -fx-vgap: 10; -fx-background-color: cornsilk;");
layout.setPrefWidth(300);
stage.setScene(scene);
stage.show();
}
}
Sample program output:
Because I use a Timeline everything in my code runs on the FXApplicationThread, so I don't have to worry about concurrency in my implementation. In implementation with a real barcode reader and barcode event processor, you may need some added concurrency protection as possibly multiple threads will be involved. Also you might not need the Timeline used in my code to simulate the delayed processing of the barcode system.

Related

JavaFX 2 User Idle Detection

I'm trying to make a simple Java transaction app with JavaFX as UI.
What I want to do now is to detect user idle state from my application which has 1 primary stage and many scenes.
Example : if user idle for 3 minutes then go back to main menu.
I already try some examples on the web about how to detect JavaFX idle state, but what I found is always -one function idle state detection which is occuring all scenes- method which is (I think) dangerous for transaction app (ex : apps detect idle state in the middle of transaction process).
It's possible to detect user idle state on every single scene? how?
Thanks.
EDIT :
Examples that I already try :
http://tomasmikula.github.io/blog/2014/06/04/timers-in-javafx-and-reactfx.html
and
http://ochafik.com/blog/?p=98
I don't really understand the point you are making about transactional behavior. Transactions concern guarantees about the data, and your transactional behavior should be defined at the data level and should not be impacted by what is happening in the UI. In other words, your atomic behavior should complete or rollback even if the UI resets due to the user being idle.
Maybe this will help, though. (Note I used Java 8 code in these examples, but you can fairly easily make it JavaF 2.2 compliant if you need.) This follows Tomas Mikula's general approach in that it uses a Timeline to implement the idle check. I didn't use Tomas' FX Timer wrapper but you could certainly do so if you like. This class encapsulates a monitor for whether the user is idle. You can register any node (or scene) and type of event: if an event of that type occurs on that node (or scene), the user is determined not to be idle. If the specified time elapses without any registered events occurring, the provided runnable is executed (on the FX Application Thread). This gives you the flexibility to create multiple monitors, if needed, and to register one or more nodes with each.
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.util.Duration;
public class IdleMonitor {
private final Timeline idleTimeline ;
private final EventHandler<Event> userEventHandler ;
public IdleMonitor(Duration idleTime, Runnable notifier, boolean startMonitoring) {
idleTimeline = new Timeline(new KeyFrame(idleTime, e -> notifier.run()));
idleTimeline.setCycleCount(Animation.INDEFINITE);
userEventHandler = e -> notIdle() ;
if (startMonitoring) {
startMonitoring();
}
}
public IdleMonitor(Duration idleTime, Runnable notifier) {
this(idleTime, notifier, false);
}
public void register(Scene scene, EventType<? extends Event> eventType) {
scene.addEventFilter(eventType, userEventHandler);
}
public void register(Node node, EventType<? extends Event> eventType) {
node.addEventFilter(eventType, userEventHandler);
}
public void unregister(Scene scene, EventType<? extends Event> eventType) {
scene.removeEventFilter(eventType, userEventHandler);
}
public void unregister(Node node, EventType<? extends Event> eventType) {
node.removeEventFilter(eventType, userEventHandler);
}
public void notIdle() {
if (idleTimeline.getStatus() == Animation.Status.RUNNING) {
idleTimeline.playFromStart();
}
}
public void startMonitoring() {
idleTimeline.playFromStart();
}
public void stopMonitoring() {
idleTimeline.stop();
}
}
Here's a test. The "Start" buttons are perhaps stand-ins for logging in. The main UI has a tab pane with two tabs: each individual tab starts with its own "Start" button and then the main content has a label, text field, and button.
The tab contents each have a (short, for testing) idle monitor associated with them. Any event on the content of the tab will reset the idle monitor, but events outside of the tab content will not reset it. There's also a "global" idle monitor for the entire window which resets the whole UI after 30 seconds.
Note that the data is preserved: i.e. if you timeout due to the idle, any text you type in the text field is preserved properly. This is why I think the issue with "transactions" should not matter at all.
import javafx.application.Application;
import javafx.event.Event;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TextField;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
public class IdleTest extends Application {
#Override
public void start(Stage primaryStage) {
StackPane root = new StackPane();
Parent mainUI = buildMainUI();
Scene scene = new Scene(root, 350, 150);
Parent startUI = buildStartUI(() -> root.getChildren().setAll(mainUI));
root.getChildren().add(startUI);
IdleMonitor idleMonitor = new IdleMonitor(Duration.seconds(30),
() -> root.getChildren().setAll(startUI), true);
idleMonitor.register(scene, Event.ANY);
primaryStage.setScene(scene);
primaryStage.show();
}
private Parent buildStartUI(Runnable start) {
Button button = new Button("Start");
button.setOnAction(e -> start.run());
StackPane root = new StackPane(button);
return root ;
}
private Parent buildMainUI() {
TabPane tabPane = new TabPane();
Tab tab1 = new Tab("One");
Parent tab1Content = buildTabUI("Tab 1");
Parent tab1StartContent = buildStartUI(() -> tab1.setContent(tab1Content));
tab1.setContent(tab1StartContent);
IdleMonitor tab1IdleMonitor = new IdleMonitor(Duration.seconds(5),
() -> tab1.setContent(tab1StartContent), true);
tab1IdleMonitor.register(tab1Content, Event.ANY);
Tab tab2 = new Tab("Two");
Parent tab2Content = buildTabUI("Tab 2") ;
Parent tab2StartContent = buildStartUI(() -> tab2.setContent(tab2Content));
tab2.setContent(tab2StartContent);
IdleMonitor tab2IdleMonitor = new IdleMonitor(Duration.seconds(10),
() -> tab2.setContent(tab2StartContent), true);
tab2IdleMonitor.register(tab2Content, Event.ANY);
tabPane.getTabs().addAll(tab1, tab2);
return tabPane ;
}
private Parent buildTabUI(String text) {
Button button = new Button("Click here");
button.setOnAction(e -> System.out.println("Click in "+text));
VBox content = new VBox(10, new Label(text), new TextField(), button);
content.setAlignment(Pos.CENTER);
return content ;
}
public static void main(String[] args) {
launch(args);
}
}

JavaFX - Sequential Transition - playing back and forth (step by step)

I'm struggling with making an animation showing the searchin in Binary Search Tree in JavaFX.
The goal is to make a visualization of comparing the value of tree node with possibility to:
pause and play it any time
being able to play the animation backwards (to go at least one step back),
give a user an ability to play the animation step-by-step or whole at once.
The preview of visualization
My vision was to make a series of TranslateTransitions(TT) added in one SequentialTransition(ST). If the animation is marked as "step-by-step" the each TT pauses the whole ST in their OnFinished handler. However this kinda works only for going one-way.
My question is. What is the best approach to maintain going fluent and step-by-step animation in reverse direction ?
I was thinking about:
maybe making another sequence of inverse transitions (but how to tell
it from which step to continue ?)
somehow work with rate property ? is it possible to change it while the ST is running ?
Thank you very much for your answers.
In general, you can change the rate property of an Animation while it is in progress. The idea of using a SequentialTransition is appealing, but it doesn't work as easily as you might think. The problem arises when the sequential transition is paused at the boundary between two individual transitions: you don't have any way to tell which of the individual transitions is considered the current one (i.e. the next one or the previous one). So when you try to reverse the rate and play, the sequential transition can get confused and immediately think it's at the end of the one it's trying to play.
You might be able to hack this a little by using Animation.getCurrentTime() and Animation.jumpTo(...) to "nudge" the sequential transition a tiny amount in the correct direction before starting to play any step, but I think it's probably easier just to manage the individual transitions on their own instead of using a SequentialTransition.
Here's a simple example of using this technique to move a rectangle around:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import javafx.animation.Animation;
import javafx.animation.Animation.Status;
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class ReverseSequentialTransitionTest extends Application {
#Override
public void start(Stage primaryStage) {
Pane pane = new Pane();
Rectangle rect = new Rectangle(50, 50, 250, 150);
rect.setFill(Color.color(.5, .5, .1));
pane.getChildren().add(rect);
TranslateTransition ttForward = new TranslateTransition(Duration.seconds(1), rect);
ttForward.setFromX(0);
ttForward.setToX(400);
TranslateTransition ttDown = new TranslateTransition(Duration.seconds(1), rect);
ttDown.setFromY(0);
ttDown.setToY(100);
TranslateTransition ttBackward = new TranslateTransition(Duration.seconds(1), rect);
ttBackward.setFromX(400);
ttBackward.setToX(0);
TranslateTransition ttUp = new TranslateTransition(Duration.seconds(1), rect);
ttUp.setFromY(100);
ttUp.setToY(0);
List<Animation> transitions = Arrays.asList(ttForward, ttDown, ttBackward, ttUp);
IntegerProperty nextTransitionIndex = new SimpleIntegerProperty();
Button playButton = new Button("Play Forward");
playButton.setOnAction(event -> {
int index = nextTransitionIndex.get();
Animation anim = transitions.get(index);
anim.setOnFinished(evt -> nextTransitionIndex.set(index+1));
anim.setRate(1);
anim.play();
});
Button reverseButton = new Button("Play backward");
reverseButton.setOnAction(event -> {
int index = nextTransitionIndex.get()-1;
Animation anim = transitions.get(index);
anim.setOnFinished(evt -> nextTransitionIndex.set(index));
anim.setRate(-1);
anim.play();
});
// This is not really part of the answer to the current question, but the
// next three statements just disable the buttons when appropriate.
// This is a binding which is true if and only if any of the transitions are
// currently running:
BooleanBinding anyPlaying = createAnyPlayingBinding(transitions);
// Disable playButton if we are at the end of the last transition, or if
// any transitions are playing:
playButton.disableProperty().bind(
nextTransitionIndex.greaterThanOrEqualTo(transitions.size())
.or(anyPlaying)
);
// Disable reverseButton if we are at the beginning of the first transition,
// or if any transitions are currently playing:
reverseButton.disableProperty().bind(
nextTransitionIndex.lessThanOrEqualTo(0)
.or(anyPlaying));
HBox controls = new HBox(5);
controls.setAlignment(Pos.CENTER);
controls.getChildren().addAll(playButton, reverseButton);
BorderPane root = new BorderPane();
root.setCenter(pane);
root.setBottom(controls);
primaryStage.setScene(new Scene(root, 800, 400));
primaryStage.show();
}
private BooleanBinding createAnyPlayingBinding(List<Animation> transitions) {
return new BooleanBinding() {
{ // Anonymous constructor
// bind to the status properties of all the transitions
// (i.e. mark this binding as invalid if any of the status properties change)
transitions.stream()
.map(Animation::statusProperty)
.forEach(this::bind);
}
#Override
protected boolean computeValue() {
// return true if any of the transitions statuses are equal to RUNNING:
return transitions.stream()
.anyMatch(anim -> anim.getStatus()==Status.RUNNING);
}
};
}
public static void main(String[] args) {
launch(args);
}
}
In JDK 7, the event handler for the playButton looks like this:
playButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
final int index = nextTransitionIndex.get();
Animation anim = transitions.get(index);
anim.setOnFinished(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent evt) {
nextTransitionIndex.set(index + 1) ;
}
});
anim.setRate(1);
anim.play();
}
});
and similarly for reverseButton. You will need to declare a couple of things as final as well. The createAnyPlayingBinding method is something like
private BooleanBinding createAnyPlayingBinding(final List<Animation> transitions) {
return new BooleanBinding() {
{
for (Animation transition : transitions) {
this.bind(transition.statusProperty();
}
}
#Override
protected boolean computeValue() {
// return true if any of the transitions statuses are equal to RUNNING:
for (Animation anim : transitions) {
if (anim.getStatus() == Status.RUNNING) {
return true ;
}
}
return false ;
}
};
}

Updating UI from different threads in JavaFX

I am developing an application with several TextField objects that need to be updated to reflect changes in associated back-end properties. The TextFields are not editable, only the back-end may change their content.
As I understand, the correct way about this is to run the heavy computation on a separate thread so as not to block the UI. I did this using javafx.concurrent.Task and communicated a single value back to the JavaFX thread using updateMessage(), which worked well. However, I need more than one value to be updated as the back-end does its crunching.
Since the back-end values are stored as JavaFX properties, I tried simply binding them to the textProperty of each GUI element and let the bindings do the work. This doesn't work, however; after running for a few moments, the TextFields stop updating even though the back-end task is still running. No exceptions are raised.
I also tried using Platform.runLater() to actively update the TextFields rather than binding. The issue here is that the runLater() tasks are scheduled faster than the platform can run them, and so the GUI becomes sluggish and needs to time to "catch up" even after the back-end task is finished.
I found a few questions on here:
Logger entries translated to the UI stops being updated with time
Multithreading in JavaFX hangs the UI
but my issue persists.
In summary: I have a back-end making changes to properties, and I want those changes to appear on the GUI. The back-end is a genetic algorithm, so its operation is broken down into discrete generations. What I would like is for the TextFields to refresh at least once in between generations, even if this delays the next generation. It is more important that the GUI responds well than that the GA runs fast.
I can post a few code examples if I haven't made the issue clear.
UPDATE
I managed to do it following James_D's suggestion. To solve the issue of the back-end having to wait for the console to print, I implemented a buffered console of sorts. It stores the strings to print in a StringBuffer and actually appends them to the TextArea when a flush() method is called. I used an AtomicBoolean to prevent the next generation from happening until the flush is complete, as it is done by a Platform.runLater() runnable. Also note that this solution is incredibly slow.
Not sure if I completely understand, but I think this may help.
Using Platform.runLater(...) is an appropriate approach for this.
The trick to avoiding flooding the FX Application Thread is to use an Atomic variable to store the value you're interested in. In the Platform.runLater method, retrieve it and set it to a sentinel value. From your background thread, update the Atomic variable, but only issue a new Platform.runLater if it's been set back to its sentinel value.
I figured this out by looking at the source code for Task. Have a look at how the updateMessage method (line 1131 at the time of writing) is implemented.
Here's an example which uses the same technique. This just has a (busy) background thread which counts as fast as it can, updating an IntegerProperty. An observer watches that property and updates an AtomicInteger with the new value. If the current value of the AtomicInteger is -1, it schedules a Platform.runLater.
In the Platform.runLater, I retrieve the value of the AtomicInteger and use it to update a Label, setting the value back to -1 in the process. This signals that I am ready for another UI update.
import java.text.NumberFormat;
import java.util.concurrent.atomic.AtomicInteger;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
public class ConcurrentModel extends Application {
#Override
public void start(Stage primaryStage) {
final AtomicInteger count = new AtomicInteger(-1);
final AnchorPane root = new AnchorPane();
final Label label = new Label();
final Model model = new Model();
final NumberFormat formatter = NumberFormat.getIntegerInstance();
formatter.setGroupingUsed(true);
model.intProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(final ObservableValue<? extends Number> observable,
final Number oldValue, final Number newValue) {
if (count.getAndSet(newValue.intValue()) == -1) {
Platform.runLater(new Runnable() {
#Override
public void run() {
long value = count.getAndSet(-1);
label.setText(formatter.format(value));
}
});
}
}
});
final Button startButton = new Button("Start");
startButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
model.start();
}
});
AnchorPane.setTopAnchor(label, 10.0);
AnchorPane.setLeftAnchor(label, 10.0);
AnchorPane.setBottomAnchor(startButton, 10.0);
AnchorPane.setLeftAnchor(startButton, 10.0);
root.getChildren().addAll(label, startButton);
Scene scene = new Scene(root, 100, 100);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
public class Model extends Thread {
private IntegerProperty intProperty;
public Model() {
intProperty = new SimpleIntegerProperty(this, "int", 0);
setDaemon(true);
}
public int getInt() {
return intProperty.get();
}
public IntegerProperty intProperty() {
return intProperty;
}
#Override
public void run() {
while (true) {
intProperty.set(intProperty.get() + 1);
}
}
}
}
If you really want to "drive" the back end from the UI: that is throttle the speed of the backend implementation so you see all updates, consider using an AnimationTimer. An AnimationTimer has a handle(...) which is called once per frame render. So you could block the back-end implementation (for example by using a blocking queue) and release it once per invocation of the handle method. The handle(...) method is invoked on the FX Application Thread.
The handle(...) method takes a parameter which is a timestamp (in nanoseconds), so you can use that to slow the updates further, if once per frame is too fast.
For example:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.beans.property.LongProperty;
import javafx.beans.property.SimpleLongProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
final BlockingQueue<String> messageQueue = new ArrayBlockingQueue<>(1);
TextArea console = new TextArea();
Button startButton = new Button("Start");
startButton.setOnAction(event -> {
MessageProducer producer = new MessageProducer(messageQueue);
Thread t = new Thread(producer);
t.setDaemon(true);
t.start();
});
final LongProperty lastUpdate = new SimpleLongProperty();
final long minUpdateInterval = 0 ; // nanoseconds. Set to higher number to slow output.
AnimationTimer timer = new AnimationTimer() {
#Override
public void handle(long now) {
if (now - lastUpdate.get() > minUpdateInterval) {
final String message = messageQueue.poll();
if (message != null) {
console.appendText("\n" + message);
}
lastUpdate.set(now);
}
}
};
timer.start();
HBox controls = new HBox(5, startButton);
controls.setPadding(new Insets(10));
controls.setAlignment(Pos.CENTER);
BorderPane root = new BorderPane(console, null, null, controls, null);
Scene scene = new Scene(root,600,400);
primaryStage.setScene(scene);
primaryStage.show();
}
private static class MessageProducer implements Runnable {
private final BlockingQueue<String> messageQueue ;
public MessageProducer(BlockingQueue<String> messageQueue) {
this.messageQueue = messageQueue ;
}
#Override
public void run() {
long messageCount = 0 ;
try {
while (true) {
final String message = "Message " + (++messageCount);
messageQueue.put(message);
}
} catch (InterruptedException exc) {
System.out.println("Message producer interrupted: exiting.");
}
}
}
public static void main(String[] args) {
launch(args);
}
}
The best way to performing this is by usage of Task in JavaFx. This is be by far the best technique I've come across to update UI Controls in JavaFx.
Task task = new Task<Void>() {
#Override public Void call() {
static final int max = 1000000;
for (int i=1; i<=max; i++) {
updateProgress(i, max);
}
return null;
}
};
ProgressBar bar = new ProgressBar();
bar.progressProperty().bind(task.progressProperty());
new Thread(task).start();

JavaFX KeyEvent propagation order

I want to listen to some KeyEvent in my scene, say KeyCode.ESCAPE(close the scene when pressed).
scene.addEventHandler(KeyEvent.ANY, event -> {
if (event.isConsumed())
return;
switch (event.getCode()) {
case ESCAPE:
stage.hide();
event.consume();
break;
default:
break;
}
});
Now, the nodes inside the scene could have listened to ESCAPE too.
// ....
someOtherNode.addEventHandler(KeyEvent.ANY, e -> {
if (e.getCode() == KeyCode.ESCAPE) {
// do stuff
e.consume();
}
});
// ....
How do I make sure that the KeyEvent will be consumed from the node and not the scene?
Based on the diagram from Oracle, A workaround would be adding a dummy Node at the end of the Node hierarchy that listens to KeyCodes
But is there a better solution, like inverting the propagation route?
EDIT:
The use case:
A popup-like node that blocks other nodes would need to listens to the ESC key or focusProperty() so that it can close itself.
There's two ways you can affect events:
Use the Node.addEventFilter(...) method to register a filter. A filter will execute on the capturing phase of the event (as the window is getting more specific, determining which Nodes should get the event).
Use the Node.addEventHandler(...) method to register a handler. The handler will execute starting at the most specific node found in the capturing phase, heading down until it is consumed.
So in the capturing phase, a stack is created. Starting with the window (topmost parent), each node that this event could potentially execute on is added to the stack (ending with the bottom most child). A filter can interrupt this process, or just execute an event during this process.
In the bubbling phase, the event handlers will start firing from the top of the stack (created in the capturing phase) until the stack is empty or the event is consumed.
In your case, you really shouldn't have anything to worry about. If any node cares about processing the "ESC" event, they will do so in the bubbling phase (and they should consume the event to prevent further processing). You can see this behavior in the ComboBox. If they don't care, it will bubble up to your Scene and that handler will execute. Just make sure any custom code you create that processes an "ESC" press also consumes that event.
For more information, there is a explanation and tutorial here: http://docs.oracle.com/javafx/2/events/jfxpub-events.htm
And here is some sample code demonstrating the Escape functionality. Pressing ESC while focused on the ComboBox will not cause the application to close, while it will close with the other controls.
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.TableColumn.CellDataFeatures;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Callback;
import javafx.util.converter.DefaultStringConverter;
public class FXEventFiltering extends Application {
public static void main(String[] args) { launch(args); }
#Override
public void start(final Stage stage) throws Exception {
//All the controls are added here
VBox box = new VBox();
ComboBox<String> dropdown = new ComboBox<>();
TextField field = new TextField();
CheckBox check = new CheckBox("Check");
RadioButton radio = new RadioButton("Radio!");
TextArea area = new TextArea();
TableView<String> table = new TableView<String>(FXCollections.observableArrayList(new String[]{"one","two"}));
TableColumn<String, String> tc = new TableColumn<String, String>("Column1");
tc.setEditable(true);
tc.setCellFactory(TextFieldTableCell.<String,String>forTableColumn(new DefaultStringConverter()));
tc.setCellValueFactory(new Callback<CellDataFeatures<String,String>, ObservableValue<String>>(){
#Override
public ObservableValue<String> call(CellDataFeatures<String, String> arg0) {
return new SimpleStringProperty(arg0.getValue());
}});
table.getColumns().add(tc);
box.getChildren().addAll(dropdown, field, check, radio, area, table);
//Setting up your scene
Scene scene = new Scene(box);
stage.setScene(scene);
scene.addEventHandler(KeyEvent.ANY, new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
System.out.println("KEYS!" + event.getEventType().getName());
switch (event.getCode()) {
case ESCAPE:
System.out.println("Escape!");
stage.hide();
event.consume();
break;
default:
break;
}
}
});
box.requestFocus(); // Removing default focus
stage.show();
}
}
Maybe you could loop over all nodes after catching the event in the scene to find out which node has actual focus? Then you could call node method to close?

How to set a Var from inside a Task?

First of all I'm new at JavaFX, so sorry if this question is stupid. How can I get an return Object from a Task?
Heres my Problem:
I want to get a List of Objects from a Mock. The Mock has a delay from 1 up to 5 seconds. But I dont want, that my GUI freeze in this time.
In Java.Swing it was easy with an Thread, but JavaFX has, as far as I know, Tasks.
I've read much tutorials, but everywhere they return a text property. So here is my question: how can I set the value of an Object with the result of the calculation from a Task/Thread (in my case a List)
Thank you.
Ray,
You are right in that the examples seem to gloss over getting back results from tasks. There are two ways that you can get back results that I know of:
Through the getValue() method of the Task class (this is the way I have done it)
Through the get() method of the parent FutureTask class (I haven't used this, but in principle it should work).
With the first getValue() aproach you need to make sure the task sets the value through the updateValue(...) method in the call method of the task. Then put a listener on the WorkerStateEvent
myTask.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#SuppressWarnings("unchecked")
#Override
public void handle(WorkerStateEvent event) {
ReturnType rt = (ReturnType) event.getSource().getValue()
// ... other stuff to do here ...
}
});
The first approach is a little verbose but it works and allows for some more complicated operations after the task has finished.
The second approach is a little simpler and straightforward but doesn't give you as much control over what to do when the task finishes. With the get() method of FutureTask, the code should block until the Task returns with the value. So using it should be as simple as:
//
// Start the task in a thread (using whatever approach you like)
//before calling the get() method.
//
ReturnType rt = myTask.get();
I have used Future objects with other code, but I have not used FutureTask with the FX api, so I can not tell you if there are hidden gotchas in it.
Good luck,
chooks
The Task is a generic type. That means that if you apply a type to a Task like Task<Integer> the Task class will have functions that returns you an Integer. One of this functions is the valueProperty(), that can be bind to other scene elements. Being shown in a Label, or whatever. I recommend you to read the javafx binding tutorial, to get a better comprehension.
Here is a sample of Task using or their properties.
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class TestingTasks extends Application{
public static void main(String[] args) {launch(args);}
#Override
public void start(Stage stage) throws Exception {
VBox vbox = new VBox(10);
vbox.setAlignment(Pos.TOP_CENTER);
ListView<String> list = new ListView<>();
HBox hbox = new HBox(10);
hbox.setAlignment(Pos.CENTER_LEFT);
Label labelMessage = new Label();
hbox.getChildren().addAll(new Label("Message: "), labelMessage);
ProgressBar progress = new ProgressBar(-1);
progress.setVisible(false);
Button button = new Button("Executing Task");
button.setOnAction(event(button, list, progress, labelMessage));
vbox.getChildren().addAll(list, hbox, button, progress);
Scene scene = new Scene(vbox, 400, 300);
stage.setScene(scene);
stage.show();
}
private EventHandler<ActionEvent> event(final Button button, final ListView<String> list, final ProgressBar progress, final Label labelMessage) {
return new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
Task<ObservableList<String>> task = generateTask();
list.itemsProperty().bind(task.valueProperty());
progress.visibleProperty().bind(task.runningProperty());
labelMessage.textProperty().bind(task.messageProperty());
button.disableProperty().bind(task.runningProperty());
task.runningProperty().addListener(listenerRunningTask());
Thread t = new Thread(task);
t.setDaemon(true);
t.start();
}
};
}
private Task<ObservableList<String>> generateTask() {
return new Task<ObservableList<String>>() {
#Override
protected ObservableList<String> call() throws Exception {
updateMessage("Waiting...");
Thread.sleep(5000);
updateMessage("Waking up");
return FXCollections.observableArrayList("One", "Two", "Three");
}
};
}
private ChangeListener<? super Boolean> listenerRunningTask() {
return new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if(oldValue && !newValue){
//TODO when finish
}
}
};
}
}
So basically, you can return a variable in the Task, or wait the Task ends and execute something, create your own bindings...
If you want to modify something of the screen from the thread, you need to do it from the FX Thread, the Task function call is outside the FX Thread, for that reason the screen it isn't freeze. But all the bind elements will occur in the FX Thread, so are safe to modify the GUI.
If you want to modify safely the GUI from a not FX Thread, just do:
Platform.runLater(new Runnable() {
#Override
public void run() {
//Safe modification in the FX Thread
}
});
Also take a look on concurrency in JavaFX2. This explain more deeply the concurrency, Service, Task...
Hope it helps!

Categories