Append text to JavaFX TextArea during updateProgress - java

I have this simple code below which updates the TextArea during updateProgress:
textArea = new TextArea();
textArea.setEditable(false);
textArea.setFocusTraversable(false);
StackPane root = new StackPane();
root.setPadding(new Insets(10));
root.getChildren().add(textArea);
Scene scene = new Scene(root, 600, 350);
primaryStage.setTitle("JavaFX Concurrency");
primaryStage.setScene(scene);
primaryStage.show();
task = new Task<Integer>(){
#Override
protected Integer call() throws Exception {
int i;
for(i = 1; i <= 100; i++){
updateProgress(i, 100);
}
return i;
}
};
task.stateProperty().addListener(new ChangeListener<State>(){
#Override
public void changed(ObservableValue<? extends State> observable, State oldValue, State state) {
System.out.println(state);
}
});
task.progressProperty().addListener(new ChangeListener<Number>(){
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number val) {
Platform.runLater(new Runnable(){
#Override
public void run() {
textArea.appendText("Value : " + val.intValue() + "\n");
}
});
}
});
new Thread(task).start();
But unfortunately the result was wrong. Here's the output:
I was expecting that the output should be Value : 1 to Value : 100..
I was just trying to test the concurrency package in JavaFX. Can someone tell me what's going on?

If you insert a simple:
Thread.sleep(10);
after you call updateProgress(i, 100);, everything will be better.
This comes from the documentation of updateProgress:
Calls to updateProgress are coalesced and run later on the FX application thread, and calls to updateProgress, even from the FX Application thread, may not necessarily result in immediate updates to these properties, and intermediate workDone values may be coalesced to save on event notifications. max becomes the new value for totalWork.
After this you can experience that more text-lines are appended to your text box, but:
This does not ensure that all of your updates will be done on your textbox!
If you want to ensure this, call the GUI update directly in the call() of task wrapped in the Platform.runlater(...) block.

Related

Timer in JavaFX updating Label (Bindings)

I want to add a clock to my application that tells how long you have been doing the task. To simplify it, I have included a counter that increments every second in a new thread and update the label 'setTimer' with the counter number. For this I have a label fx:id="setTimer" in my .fxml file and imported it into my class.
#FXML
private Label setTimer;
And created another class in my class that extends the thread TimerTask and increments the counter by one on each call. Created a new Object 'text', which should always be updated with the current value of the counter.
SimpleStringProperty text = new SimpleStringProperty("undefined");
public class MyTask extends TimerTask {
#Override
public void run() {
counter++;
text.set(Integer.toString(counter));
}
}
To have this class called every second I created a timer in the initialize method and set it to one second.
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
MyTask myTask = new MyTask();
Timer timer = new Timer(true);
timer.scheduleAtFixedRate(myTask, 0 , 1000);
setTimer.textProperty().bind(text);
}
At the moment I get the exception 'Not on FX application thread; currentThread = Timer-0'.
I've tried many ways to solve my problem, but I haven't gotten to the right point.
My idea of ​​what I want to do should be clear, and I would be happy if someone could help me.
My Problem is to update the changes of the counter in the GUI.
It doesn't have to be solved the way I thought it would, just need a tip on how to best implement it.
Thank you
Ok, my comments are too long. This is how I would try to do it.
Start the stopwatch on the application being loaded
Create a new thread that launches itself every so often.
Inside there, get the time from the Stopwatch in seconds (sw.getTime(TimeUntis.seconds)). Convert that to hours and minutes if you want like shown in this SO post
Then, write the time to the UI using Platform.runLater(new Runnable(){ /* access ui element and write time here */ });
Using Platform.runLater() in a background thread is kind of a messy kludge that should probably be avoided. JavaFX has mechanisms to handle this kind of thing which you should use. Specifically, Task<> is designed to allow background threads to update data which is connected to JavaFX screen elements which need to be updated on the FXAT.
You CAN do what you're trying to do with a JavaFX Task, but using the Java Timer inside of it seems impossible, since there doesn't seem to be any way for a Java thread to wait on a Timer to complete. So, instead I've used a "for" loop with a sleep to do the same thing. It's clumsy, but it does demonstrate how to connect partial results from a Task to screen display:
public class Sample1 extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
Scene scene = new Scene(new Timer1(), 300, 200);
primaryStage.setScene(scene);
primaryStage.show();
}
}
public class Timer1 extends VBox {
public Timer1() {
Text time = new Text();
Button startButton = new Button("Start");
Button stopButton = new Button("Stop");
getChildren().addAll(time, startButton, stopButton);
startButton.setOnAction(startEvt -> {
Task<Integer> timerFxTask = new Task<>() {
{
updateValue(0);
}
#Override
protected Integer call() throws Exception {
for (int counter = 0; counter <= 1000; counter++) {
sleep(1000);
updateValue(counter);
}
return 1000;
}
};
stopButton.setOnAction(stopEvt -> timerFxTask.cancel());
time.textProperty().bind(Bindings.createStringBinding(() -> timerFxTask.getValue().toString(),
timerFxTask.valueProperty()));
Thread timerThread = new Thread(timerFxTask);
timerThread.start();
});
}
}
But there is a better way to do what you're trying to do, which is essentially an animation - and JavaFX has a facility to do exactly this. Usually, people use animations to morph the appearance of JavaFX screen elements, but you can also use it to animate the contents of a Text over time as well. What I've done here is create an IntegerProperty which can be transitioned from a start value to an end value interpolated linearly over time and then bound that value to the TextProperty of a Text on the screen. So you see it update once per second.
public class Sample1 extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
Scene scene = new Scene(new Timer2(), 300, 200);
primaryStage.setScene(scene);
primaryStage.show();
}
}
public class Timer2 extends VBox {
public Timer2() {
Text time = new Text();
Button startButton = new Button("Start");
Button stopButton = new Button("Stop");
getChildren().addAll(time, startButton, stopButton);
startButton.setOnAction(startEvt -> {
IntegerProperty counter = new SimpleIntegerProperty(0);
Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(1000), new KeyValue(counter, 1000)));
stopButton.setOnAction(stopEvt -> timeline.stop());
time.textProperty().bind(Bindings.createStringBinding(() -> Integer.toString(counter.get()), counter));
timeline.play();
});
}
}

How to add a Java ChangeListener to a method return

I've got a task-control GUI with an 'Abort' button which invokes some cancellation code (works fine). However, I'd like to sync the button's disabled state to whether a task is running or not so the option is disabled if the task isn't running. I can check the state of the task via its Boolean Future.isDone() method but I'd like to have JavaFX manage the state automatically via a bound property. I can't figure out how to establish the binding, though, or set up a ChangeListener on a method. Can anyone advise on how best to accomplish?
Edit: I think this question ultimately distills down to "How do I wrap Future.isDone() to make it an Observable?"
Guidance very much appreciated.
Use Task. You can observe the state property to modify the Button's disabled property:
private ExecutorService service;
#Override
public void stop() throws Exception {
service.shutdownNow();
}
#Override
public void init() throws Exception {
service = Executors.newSingleThreadScheduledExecutor();
// service = new ThreadPoolExecutor(1, 2, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10));
}
#Override
public void start(Stage primaryStage) {
Button startButton = new Button("Start");
Task<Void> task = new Task<Void>() {
#Override
protected Void call() throws Exception {
Thread.sleep(5000);
System.out.println("task finished");
return null;
}
};
Button cancelButton = new Button("cancel");
cancelButton.setOnAction(evt -> task.cancel());
cancelButton.setDisable(true); // button disabled before submitting task
startButton.setOnAction((ActionEvent event) -> {
startButton.setDisable(true);
// cancel Button enabled until task is succeeded/failed/was cancelled
cancelButton.disableProperty().bind(Bindings.createBooleanBinding(() -> {
switch (task.getState()) {
case CANCELLED:
case FAILED:
case SUCCEEDED:
return true;
default:
return false;
}
}, task.stateProperty()));
// simulate some work to be done by the service
service.submit(() -> {
try {
Thread.sleep(5000);
} catch (InterruptedException ex) {
}
System.out.println("runnable finished");
});
service.submit(task);
});
// show state as text
Text text = new Text();
text.textProperty().bind(task.stateProperty().asString());
VBox root = new VBox(10, startButton, cancelButton, text);
Scene scene = new Scene(root, 300, 300);
primaryStage.setScene(scene);
primaryStage.show();
}
Solved, or good enough. GUI controls bound to a SimpleBooleanProperty. Constructor kicks off a background thread Runnable that polls isDone() in an endless loop with half-second sleep and updates the bound SBP. Thank you again, fabian, for pointing me to a proper solution for future reference.

Get visible state of Node in JavaFX 8

I need to detect if a node is currently displaying.
I.e. if my Node is in a TabPane, I need to know if it is in a selected tab or not.
In the example, I want to know when the HBox is displaying.The visibleProperty and managedProperty of Node, does not seem to help me:
public class VisibleTest extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
TabPane tabpane = new TabPane();
tabpane.getTabs().add(new Tab("Tab1", new Label("Label1")));
HBox hbox = new HBox(new Label("Label2"));
hbox.setStyle("-fx-background-color: aquamarine;");
hbox.visibleProperty().addListener((observable, oldValue, newValue) -> {
System.out.println("Hbox visible changed. newValue: " + newValue);
});
hbox.managedProperty().addListener((observable, oldValue, newValue) -> {
System.out.println("Hbox managed changed. newValue: " + newValue);
});
Tab tab2 = new Tab("tab2", hbox);
tabpane.getTabs().add(tab2);
primaryStage.setScene(new Scene(tabpane));
primaryStage.setWidth(600);
primaryStage.setHeight(500);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I know, it is possible to listen on the selectedProperty state of the tab, but this does not solve my real problem.
Node.impl_isTreeVisible() does what I want, but this is depricated API.
Any ideas?
------------------------------------ update--------------------
I realize the code example above does not explain well what I'm trying to accomplish. Below is some Swing code that kind of demonstrates what I am trying to accomplish in JavaFX. Detect if the JComponent/Node is visible/shown, and based on that state, start or stop background processes. How would the constructor look like if it was a javaFX class.
public class SwingVisible extends JComponent {
String instanceNR;
Thread instanceThread;
boolean doExpensiveStuff = false;
public SwingVisible(String instanceNR) {
this.instanceNR = instanceNR;
this.setLayout(new FlowLayout());
this.add(new JLabel(instanceNR));
instanceThread = new Thread(new Runnable() {
#Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (doExpensiveStuff) {
/*
* do expensive stuff.
*/
System.out.println(instanceNR + " is visible " + isVisible());
}
}
}
});
/*
* How to do this in FX?
*/
addComponentListener(new ComponentAdapter() {
#Override
public void componentShown(ComponentEvent e) {
if (!instanceThread.isAlive()) {
instanceThread.start();
}
doExpensiveStuff = true;
}
#Override
public void componentHidden(ComponentEvent e) {
doExpensiveStuff = false;
}
});
}
public static void main(String[] args) {
/*
* This block represents code that is external to my library. End user
* can put instances of SwingVisible in JTabbedPanes, JFrames, JWindows,
* or other JComponents. How many instances there will bee is not in my
* control.
*/
JTabbedPane jtp = new JTabbedPane();
jtp.add("tab1", new SwingVisible("1"));
jtp.add("tab2", new SwingVisible("2"));
jtp.add("tab3", new SwingVisible("3"));
JFrame f = new JFrame("test");
f.setContentPane(jtp);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(300, 300);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
Output when tab1 is selected:
1 is visible true
1 is visible true
1 is visible true
...
Output when tab2 is selected:
2 is visible true
2 is visible true
2 is visible true
...
You can use Tab's selectedProperty to know if it is selected or not, and by extension if its content is visible or not. It is a boolean property.
I've converted your Swing code to JavaFX based on your initial JavaFX example:
public class VisibleTest extends Application {
public class FXVisible extends Tab {
FXVisible(String id) {
super(id, new Label(id));
Timeline thread = new Timeline(
new KeyFrame(Duration.ZERO, e -> {
if (isSelected()) {
// do expensive stuff
System.out.println(id + " is visible");
}
}),
new KeyFrame(Duration.seconds(1))
);
thread.setCycleCount(Timeline.INDEFINITE);
selectedProperty().addListener((selectedProperty, wasSelected, isSelected) -> {
if (isSelected) {
if (thread.getStatus() != Status.RUNNING) {
System.out.println(id + " starting thread");
thread.play();
}
}
// else, it is not selected -> content not shown
});
}
}
#Override
public void start(Stage primaryStage) throws Exception {
TabPane tabpane = new TabPane();
tabpane.getTabs().add(new FXVisible("1"));
tabpane.getTabs().add(new FXVisible("2"));
tabpane.getTabs().add(new FXVisible("3"));
// add as many as you want
primaryStage.setScene(new Scene(tabpane));
primaryStage.setWidth(600);
primaryStage.setHeight(500);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I replaced your thread with a JavaFX Timeline. Your question is not about this topic so I won't go into details here, though it's self explanatory.
I don't understand why in the Swing example you have a listener changing a boolean that indicates if the component is visible or not when you can just call isVisible() directly in the thread (see comments below for a note about threading). This is why in my code above I took the approach of checking isSelected() directly with no self-declared boolean. If you need to revert to your design it's rather straightforward. Just noting this for clarity.
The ComponentListener can be replaced with a change listener on selectedProperty() and querying the new value. Just be sure that your example does what it's supposed to do: the first time the tab is selected the thread/timer starts. After that the thread/timer does nothing. You might have wanted to pause the computation for non-displaying content. Again, just noting it because it seemed like a potential mistake to me, otherwise you're fine.
Updated answer.
tab2.getContent().isVisible();
It seems to me that my original answer is correct. If not, you need to ask your question in a better way. You want to know when the hbox is visible(meaning you can see the hbox on the screen).
tabpane.getSelectionModel().selectedItemProperty().addListener((obsVal, oldTab, newTab)->{
System.out.println(newTab.getText());
if(newTab.getText().equals("tab2"))
{
//You can use this code to set the hbox visibility, that way you can force the behavior you are looking for.
hbox.setVisible(true);
System.out.println("hbox is visible!");
}
else
{
//You can use this code to set the hbox visibility, that way you can force the behavior you are looking for.
hbox.setVisible(false);
System.out.println("hbox is not visible!");
}
});
From quick checking this seemed to work for both checking the window is showing and that the tab it is in is displaying. I have also checked and it seems to work as expected for titled panes too that are collapsible.
public static boolean detectVisible( Node node )
{
Node current = node;
while( current != null ) {
if( !current.isVisible() ) {
return false;
}
current = current.getParent();
}
Window window = Optional.of( node ).map( Node::getScene ).map( Scene::getWindow ).orElse( null );
if( window == null ) {
return false;
}
if( window instanceof Stage && ( (Stage) window ).isIconified() ) {
return false;
}
return window.isShowing();
}

How to run some code repeatedly after an interval that can changes?

I'm using a thread in JavaFX to repeat my code after an interval (initially 1s), but I want to be able change the interval that the thread is using to 500ms or 333ms based on user choice (I have a button in a menu bar to change for each choice). I did tried things like shutDown() if the user clicks on one of the buttons and initiate it again with the new value, but didn't work. Any ideas?
Here's the relevant part of my code:
ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor();
exec.scheduleAtFixedRate(() -> {
//refresh users, line and "guiche"
updateFila(usuarios, guiches, fila);
updateGuiche(guiches, fila, graphicsContext);
turno++;
//ends the code after the end of the line
if (done) {
exec.shutdown();
}
}, 0, 1000, TimeUnit.MILLISECONDS); //This is interval that I need to change after user choice
I know that I'm executing scheduleAtFixedRate() right now, but it was just to see if the logic was fine.
Additionally, I need to pause, resume and reset the thread, all based on user click.
You could use a Timeline to execute a event handler every second and set the rate at which the animation runs to the number of times the update should happen per second, i.e. 2 or 3...
In the following example I use 5 instead of 3 for a more recognizable effect:
#Override
public void start(Stage primaryStage) {
Line line = new Line(25, 125, 125, 125);
Rotate rotate = new Rotate(0, 125, 125);
line.getTransforms().add(rotate);
ToggleButton btn = new ToggleButton();
btn.textProperty().bind(Bindings.when(btn.selectedProperty()).then("5 Hz").otherwise("2 Hz"));
StackPane.setAlignment(btn, Pos.BOTTOM_LEFT);
// rotate by one 60th of a full rotation each time
Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(1), evt -> rotate.setAngle((rotate.getAngle() + (360d / 60d)) % 360)));
timeline.setCycleCount(Animation.INDEFINITE);
// rate depends on button state
timeline.rateProperty().bind(Bindings.when(btn.selectedProperty()).then(5d).otherwise(2d));
Pane linePane = new Pane(line);
linePane.setMinSize(250, 250);
linePane.setMaxSize(250, 250);
StackPane root = new StackPane();
root.getChildren().addAll(linePane, btn);
Scene scene = new Scene(root, 300, 300);
primaryStage.setScene(scene);
timeline.play();
primaryStage.show();
}
The binding is simply an example of setting the update frequency. You could of course use different means to assign this value, e.g.
ComboBox<Duration> combo = new ComboBox<>();
Duration initial = Duration.seconds(1);
combo.getItems().addAll(initial, Duration.seconds(1/3d), Duration.seconds(1/2d));
combo.setValue(initial);
combo.valueProperty().addListener((observable, oldValue, newValue) -> timeline.setRate(1/newValue.toSeconds()));
If you use only single thread you can create your own implementation based on classic thread.
public class Worker extends Thread {
private static final Logger logger = Logger.getLogger(Worker.class);
private volatile int delayInSec = 1;
private CountDownLatch latch;
private final int STARTED = 0;
private final int STOPPED = 1;
private volatile int state = STOPPED;
public Worker(){}
#Override
public void run() {
logger.debug("enter to execution method");
while (!isInterrupted()) {
// stop if needed (it's additional feature)
if (state == STOPPED) {
logger.debug("stopped and locked");
try {
latch = new CountDownLatch(1);
latch.await();
} catch (InterruptedException e) {
logger.warning("got interruption while waiting for action ", e);
break;
}
logger.debug("awake");
}
// do your stuff here
try {
// then delay
logger.debug("go to sleep for %s sec.",delayInSec);
latch = new CountDownLatch(1);
latch.await(delayInSec, TimeUnit.SECONDS);
} catch (InterruptedException e) {
logger.warning("got interruption while waiting for action ", e);
break;
}
}
logger.debug("exit from execution method");
}
public void startJob(){
state = STARTED;
logger.debug("started");
if (latch!=null)
latch.countDown();
}
public void stopJob(){
state = STOPPED;
logger.debug("stopped");
}
public void shutdown(){
logger.debug("shutdown");
interrupt();
}
public void changeDelay(int delayInSec) {
logger.debug("set new delay %s", delayInSec);
this.delayInSec = delayInSec;
}
}

Show always the last line in a TextArea

I have a JavaFx TextArea where i update the text from another thread.
/**
* Start a new thread to update the text in the textarea. The progressmanager is called in a loop to deleiver the updated text
*/
private void startTextAreaUpdate() {
final Task<Void> updateTextAreaTask = new Task<Void>() {
#Override
protected Void call() throws Exception {
// Start updating the progresstext
while (!prog.getIsDone()) {
updateMessage(prog.getProgressText());
Thread.sleep(100);
}
return null;
}
};
//Here the TextArea text is bound to the task
taProgressText.textProperty()
.bind(updateTextAreaTask.messageProperty());
// Start the action in a new thread
Thread th = new Thread(updateTextAreaTask);
th.setDaemon(true);
th.start();
}
Because the text doesn't fit in the textarea i always want to show the last line of the text.
In the main thread i add a ChangeListener.
taProgressText.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable,
String oldValue, String newValue) {
int lastPos = taProgressText.getText().length();
taProgressText.positionCaret(lastPos);
}
});
However the position doesn't change. What is going wrong here?
I think the position of the carat changes, but it doesn't actually scroll.
Try this (ugly hack...) instead of the binding and the current listener you have:
updateTextAreaTask.messageProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> obs, String oldMessage, String newMessage) {
taProgressText.setText(newMessage);
ScrollBar scrollBar = (ScrollBar) taProgressText.lookup(".scroll-bar:vertical");
if (scrollBar != null) {
scrollBar.setValue(scrollBar.getMax());
}
}
});

Categories