I'm currently trying to learn JavaFx, and I'm now stuck on a problem. By using a scanner I want to update my label on stage consecutively.
I have tried to use platform.runLater, but this only shows one update. It doesnt update the label every time I write something new in my console.
This is what I have been using:
Platform.runLater(new Runnable() {
#Override
public void run() {
label.setText(sc.nextLine());
}
});
The nextLine() method in Scanner is a blocking call: you should never block the FX Application Thread. You need to create a background thread to read from the scanner, and then update the label on the FX Application Thread:
import java.util.Scanner;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class UpdateLabelFromScanner extends Application {
#Override
public void start(Stage primaryStage) {
Label label = new Label();
Thread scannerReadThread = new Thread(() -> {
try (Scanner scanner = new Scanner(System.in)) {
while (scanner.hasNextLine()) {
String line = scanner.nextLine() ;
Platform.runLater(() -> label.setText(line));
}
} catch (Exception exc) {
exc.printStackTrace();
}
});
scannerReadThread.setDaemon(true);
scannerReadThread.start();
StackPane root = new StackPane(label);
primaryStage.setScene(new Scene(root, 180, 120));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
There are a few ways you can do this. One way to is to read the text into an observable property, and when that property changes, you update your label. One of the objects you can use to do this is called SimpleStringProperty.
Declare it like this:
private StringProperty someText = new SimpleStringProperty();
In a constructor or some initialization function, add a new ChangeListener to the property:
someText.addListener((observable, oldValue, newValue) -> {
Platform.runLater(() -> {
label.setText(newValue);
});
});
When you read input from your scanner, change the value of your observable and the listener you added will be invoked, thus changing the text of your label:
someText.set(sc.nextLine());
Related
I try to high frequently update the cells in a JFX TableView (proof of concept application). I load a TableView via FXML and start an ExecutorService to change the value of a cell.
When I start the application I notice, that the update works for the first 3-4 million elements and then it stucks. If I slow down the updates (see MAGIC#1) it works (10ms is still too fast, but 100ms delay works). So I thought it might be a threading issue.
But then I found out that if I add an empty ChangeListener (see MAGIC#2) to the property it works fine. Even without the need of MAGIC#1.
Am I doing something wrong? Do I have to update the cells in a different way?
Thanks in advance for your help!!
The elements in the TableView:
public class Element {
public static final AtomicInteger x = new AtomicInteger(0);
private final StringProperty nameProperty = new SimpleStringProperty("INIT");
public Element() {
// MAGIC#2
// this.nameProperty.addListener((observable, oldValue, newValue) -> {});
}
public void tick() {
this.setName(String.valueOf(x.incrementAndGet()));
}
public String getName() ...
public void setName(String name)...
public StringProperty nameProperty() ...
}
The controller for FXML:
public class TablePerformanceController implements Initializable {
private final ObservableList<Element> data = FXCollections.observableArrayList();
public Runnable changeValues = () -> {
while (true) {
if (Thread.currentThread().isInterrupted()) break;
data.get(0).tick();
// MAGIC#1
// try { Thread.sleep(100); } catch (Exception e) {}
}
};
private ExecutorService executor = null;
#FXML
public TableView<Element> table;
#Override
public void initialize(URL location, ResourceBundle resources) {
this.table.setEditable(true);
TableColumn<Element, String> nameCol = new TableColumn<>("Name");
nameCol.setCellValueFactory(cell -> cell.getValue().nameProperty());
this.table.getColumns().addAll(nameCol);
this.data.add(new Element());
this.table.setItems(this.data);
this.executor = Executors.newSingleThreadExecutor();
this.executor.submit(this.changeValues);
}
}
You are violating the single threaded rule for JavaFX: updates to the UI must only be made from the FX Application Thread. Your tick() method updates the nameProperty(), and since the table cell is observing the nameProperty(), tick() results in an update to the UI. Since you're calling tick() from a background thread, this update to the UI happens on the background thread. The resulting behavior is essentially undefined.
Additionally, your code ends up with too many requests to update the UI. So even if you fix the threading issues, you need to somehow throttle the requests so that you don't flood the FX Application Thread with too many requests to update, which will make it unresponsive.
The technique to do this is addressed in Throttling javafx gui updates. I'll repeat it here in the context of a table model class:
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Element {
// Note that in the example we only actually reference this from a single background thread,
// in which case we could just make this a regular int. However, for general use this might
// need to be threadsafe.
private final AtomicInteger x = new AtomicInteger(0);
private final StringProperty nameProperty = new SimpleStringProperty("INIT");
private final AtomicReference<String> name = new AtomicReference<>();
/** This method is safe to call from any thread. */
public void tick() {
if (name.getAndSet(Integer.toString(x.incrementAndGet())) == null) {
Platform.runLater(() -> nameProperty.set(name.getAndSet(null)));
}
}
public String getName() {
return nameProperty().get();
}
public void setName(String name) {
nameProperty().set(name);
}
public StringProperty nameProperty() {
return nameProperty;
}
}
The basic idea here is to use an AtomicReference<String to "shadow" the real property. Atomically update it and check if it's null, and if so schedule an update to the real property on the FX Application Thread. In the update, atomically retrieve the "shadow" value and reset it to null, and set the real property to the retrieved value. This ensures that new requests to update on the FX Application thread are only made as often as the FX Application Thread consumes them, ensuring that the FX Application Thread is not flooded. Of course, if there is a delay between scheduling the update on the FX Application Thread, and the update actually occurring, when the update does happen it will still retrieve the latest value to which the "shadow" value was set.
Here's a standalone test, which is basically equivalent to the controller code you showed:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.stage.Stage;
public class FastTableUpdate extends Application {
private final ObservableList<Element> data = FXCollections.observableArrayList();
public final Runnable changeValues = () -> {
while (true) {
if (Thread.currentThread().isInterrupted()) break;
data.get(0).tick();
}
};
private final ExecutorService executor = Executors.newSingleThreadExecutor(runnable -> {
Thread t = new Thread(runnable);
t.setDaemon(true);
return t ;
});
#Override
public void start(Stage primaryStage) {
TableView<Element> table = new TableView<>();
table.setEditable(true);
TableColumn<Element, String> nameCol = new TableColumn<>("Name");
nameCol.setPrefWidth(200);
nameCol.setCellValueFactory(cell -> cell.getValue().nameProperty());
table.getColumns().add(nameCol);
this.data.add(new Element());
table.setItems(this.data);
this.executor.submit(this.changeValues);
Scene scene = new Scene(table, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
What is the correct way to manipulate an Observable collection in a thread, where the collection is already bound to a JavaFX UI-node?
In my sample application, the connection between the collection and the nodes are broken before the thread can do any manipulation; and then they are re-connected after the thread is done. The methods are disconnectObservable() and connectObservable() respectively. Without these two methods, java.lang.IllegalStateException: Not on FX application thread is reported.
Ideally I would like ChangeObservableTask to make its changes to mWords, and then I would call some method to tell mObservable to refresh itself and notify its listeners. Is there such a thing?
Thanks.
package theapp;
import java.util.LinkedList;
import java.util.List;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
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.control.ListView;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class ThreadObList extends Application {
private final List<String> mWords;
private final ObservableList<String> mObservable;
private ListView mListView;
private Label mCount;
public ThreadObList() {
mWords = new LinkedList<>();
mObservable = FXCollections.observableList(mWords);
mWords.add("park");
}
#Override
public void start(Stage primaryStage) {
Button btn = new Button();
btn.setText("Start thread");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
ChangeObservableTask task = new ChangeObservableTask();
Thread thd = new Thread(task);
disconnectObservable();
thd.start();
try {
task.get();
System.out.println("ChangeObservableTask exited normally.");
}
catch(Exception ex) {
System.out.println(ex.getMessage());
}
connectObservable();
}
});
mCount = new Label();
mListView = new ListView();
VBox root = new VBox(5, btn, mCount, mListView);
VBox.setVgrow(mListView, Priority.ALWAYS);
connectObservable();
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
private void connectObservable() {
mListView.setItems(mObservable);
mCount.textProperty().bind(Bindings.size(mObservable).asString());
}
private void disconnectObservable() {
mListView.setItems(null);
mCount.textProperty().unbind();
}
private class ChangeObservableTask extends Task<Void> {
#Override
protected Void call() throws Exception {
mObservable.add("dart");
mObservable.add("truck");
mObservable.add("ocean");
return null;
}
}
}
Once the list is used as the contents of the ListView, you can only manipulate it from the FX Application Thread. See the Task javadocs for a bunch of usage examples.
You can create a copy of your ObservableList and pass it to your task, manipulate the copy and return the results. Then update the ObservableList with the results in the onSucceeded handler.
Also note that you shouldn't make any blocking calls, such as task.get() on the FX Application Thread, as you can make the UI unresponsive by doing so.
So you should do something along the lines of:
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
ChangeObservableTask task = new ChangeObservableTask(new ArrayList<>(mObservable));
Thread thd = new Thread(task);
task.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent event) {
mObservable.setAll(task.getValue());
}
});
thd.start();
}
});
and
private class ChangeObservableTask extends Task<List<String>> {
private final List<String> data ;
ChangeObservableTask(List<String> data) {
this.data = data ;
}
#Override
protected List<String> call() throws Exception {
data.add("dart");
data.add("truck");
data.add("ocean");
return data;
}
}
I need to be able to call a separate thread. The thread analysis a file and pull stats from that.
The analyzing of the file can take up to 2 minutes and during the analysis data is printed to the logs.
I would like to have a TextArea on the front end that needs to print out the analysis (as it analysis) and I would also like to have a progress bar to indicate the progress. All of this is determined inside the separate thread.
What I have done is creating a method in the UI class to add a string to the Text Area and pass in a reference of this class to the launched thread.
My Main Class
package trymutilthread;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class TryMutilThread extends Application {
TextArea ta;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
Button btn = new Button();
btn.setText("Start");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
startScheduledExecutorService();
}
});
ta = new TextArea();
VBox vBox = new VBox();
vBox.getChildren().addAll(btn, ta);
StackPane root = new StackPane();
root.getChildren().add(vBox);
Scene scene = new Scene(root, 300, 750);
primaryStage.setScene(scene);
primaryStage.show();
}
private void startScheduledExecutorService() {
final TryMutilThread classI = this;
Task<Void> task = new Task<Void>() {
#Override protected Void call() throws Exception {
try {
ta.appendText("Starting Thread\n");
new SomeProcess(classI).doTheLogic();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
};
Thread th = new Thread(task);
th.setDaemon(true);
th.start();
}
public void appendText(String string) {
ta.appendText(string);
}
}
The class that is executed in the thread
package trymutilthread;
public class SomeProcess {
TryMutilThread taClass = null;
public SomeProcess (TryMutilThread taClass) {
this.taClass = taClass;
}
public void doTheLogic() throws Exception{
taClass.appendText("Staring Thread");
for (int i = 0; i < 5000; i++) {
taClass.appendText(String.valueOf(i));
}
taClass.appendText("Ending Thread");
}
}
Now when I execute this it still only output the text to the TextArea once the thread has ended.
I did had a look at the following 2 posts:
JavaFX update textArea
Java client / server thread null pointer exception when quickly communicating messages
I am not able to get the data printed to logs until the process has ended.
I updated my code to create a Task.
But now I am getting the following error when it executes
Executing com.javafx.main.Main from F:\DEV\Projects\TryMutilThread\dist\run404234128\TryMutilThread.jar using platform C:\Program Files\Java\jdk1.7.0_10/bin/java
java.lang.NullPointerException
at com.sun.javafx.sg.prism.NGTextHelper$TextAttributes.computeLinePadding(NGTextHelper.java:405)
at com.sun.javafx.sg.prism.NGTextHelper$TextAttributes.access$200(NGTextHelper.java:292)
at com.sun.javafx.sg.prism.NGTextHelper.buildTextLines(NGTextHelper.java:2357)
at com.sun.javafx.sg.prism.NGTextHelper.validateText(NGTextHelper.java:1847)
at com.sun.javafx.sg.prism.NGTextHelper.getCaretShape(NGTextHelper.java:1435)
at javafx.scene.text.Text.getDecorationShapes(Text.java:1150)
at javafx.scene.text.Text.impl_geomChanged(Text.java:757)
at javafx.scene.text.Text$1.invalidated(Text.java:214)
at javafx.beans.property.StringPropertyBase.markInvalid(StringPropertyBase.java:127)
at javafx.beans.property.StringPropertyBase.set(StringPropertyBase.java:161)
at javafx.beans.property.StringPropertyBase.set(StringPropertyBase.java:67)
at javafx.scene.text.Text.setText(Text.java:188)
at com.sun.javafx.scene.control.skin.TextAreaSkin$17.invalidated(TextAreaSkin.java:610)
at com.sun.javafx.binding.ExpressionHelper$Generic.fireValueChangedEvent(ExpressionHelper.java:359)
at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:100)
at javafx.scene.control.TextInputControl$TextProperty.fireValueChangedEvent(TextInputControl.java:1034)
at javafx.scene.control.TextInputControl$TextProperty.markInvalid(TextInputControl.java:1038)
at javafx.scene.control.TextInputControl$TextProperty.invalidate(TextInputControl.java:978)
at javafx.scene.control.TextInputControl$TextProperty.access$200(TextInputControl.java:950)
at javafx.scene.control.TextInputControl$1.invalidated(TextInputControl.java:119)
at com.sun.javafx.binding.ExpressionHelper$SingleInvalidation.fireValueChangedEvent(ExpressionHelper.java:155)
at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:100)
at javafx.scene.control.TextArea$TextAreaContent.insert(TextArea.java:196)
at javafx.scene.control.TextInputControl.replaceText(TextInputControl.java:373)
at javafx.scene.control.TextInputControl.insertText(TextInputControl.java:308)
at javafx.scene.control.TextInputControl.appendText(TextInputControl.java:298)
at trymutilthread.TryMutilThread.appendText(TryMutilThread.java:80)
at trymutilthread.SomeProcess.doTheLogic(SomeProcess.java:26)
at trymutilthread.TryMutilThread$2.call(TryMutilThread.java:66)
at trymutilthread.TryMutilThread$2.call(TryMutilThread.java:62)
at javafx.concurrent.Task$TaskCallable.call(Task.java:1259)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)
at java.util.concurrent.FutureTask.run(FutureTask.java:166)
at java.lang.Thread.run(Thread.java:722)
I then google'd the error and it seems that I need to put the interaction code in a Platform.runlater().
Java client / server thread null pointer exception when quickly communicating messages
I changed the class to execute the thread to be
package trymutilthread;
import javafx.application.Platform;
public class SomeProcess {
TryMutilThread taClass = null;
public SomeProcess(TryMutilThread taClass) {
this.taClass = taClass;
}
public void doTheLogic() throws Exception {
taClass.appendText("Staring Thread");
for (int i = 0; i < 5000; i++) {
//remove this append line
//taClass.appendText(i + "\n");
//And replaced it with platform.runlater
Platform.runLater(new Runnable() {
#Override
public void run() {
taClass.appendText("AGREED" + "\n");
}
});
}
taClass.appendText("Ending Thread");
}
}
It executes without any errors but now it seems to be back to the start... The UI is frozen until all is added to the TextArea
The problem is just that you're flooding the FX Application Thread with too many requests; there's no actual work happening between your Platform.runLater(...) calls. This problem probably goes away with your real application instead of this test, but to mimic the actual long-running work, you can just put a Thread.sleep(...) in there:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.TextArea;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class TextAreaBackgroundUpdateExample extends Application {
#Override
public void start(Stage primaryStage) {
final BorderPane root = new BorderPane();
final TextArea textArea = new TextArea();
final ProgressBar progress = new ProgressBar();
final Button startButton = new Button("Start");
final int maxCount = 5000 ;
startButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
Task<Void> task = new Task<Void>() {
#Override
protected Void call() throws Exception {
for (int i = 1; i <= maxCount; i++) {
Thread.sleep(10);
final int count = i ;
Platform.runLater(new Runnable() {
#Override
public void run() {
textArea.appendText("Processed part " + count + " (of "+maxCount+")\n");
}
});
updateProgress(i, maxCount);
}
return null;
}
};
progress.progressProperty().bind(task.progressProperty());
Thread t = new Thread(task);
t.setDaemon(true);
t.start();
}
});
root.setCenter(textArea);
root.setTop(progress);
root.setBottom(startButton);
final Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I am working on JavaFX application, in my scenario is to show a password prompt created in JavaFX which takes password with two option OK and Cancel. I have returned the password entered by user.
My class of showing password dialog is -
public static String showPasswordDialog(String title, String message, Stage parentStage, double w, double h) {
try {
Stage stage = new Stage();
PasswordDialogController controller = (PasswordDialogController) Utility.replaceScene("Password.fxml", stage);
passwordDialogController.init(stage, message, "/images/password.png");
if (parentStage != null) {
stage.initOwner(parentStage);
}
stage.initModality(Modality.WINDOW_MODAL);
stage.initStyle(StageStyle.UTILITY);
stage.setResizable(false);
stage.setWidth(w);
stage.setHeight(h);
stage.showAndWait();
return controller.getPassword();
} catch (Exception ex) {
return null;
}
My code where to show password prompt is below, actually this prompt will be shown over other UI, so I need to inclose this inside Platform.runlater(), otherwise it throws Not on FX application thread. I need this password prompt to be shown until I get correct one. How can I get value of password if I inclosed showing password inside runlater.
Is there any other better way?
final String sPassword = null;
do {
Platform.runLater(new Runnable() {
#Override
public void run() {
sPassword = JavaFXDialog.showPasswordDialog(sTaskName + "Password", "Enter the password:", parentStage, 400.0, 160.0);
}
});
if (sPassword == null) {
System.out.println("Entering password cancelled.");
throw new Exception("Cancel");
}
} while (sPassword.equalsIgnoreCase(""));
I'd recommend wrapping the code within a FutureTask object. FutureTask is a construct useful (among other things) for executing a portion of code on one thread (usually a worker, in your case the event queue) and safely retrieving it on another. FutureTask#get will block until FutureTask#run has been invoked, therefore your password prompt could look like this:
final FutureTask query = new FutureTask(new Callable() {
#Override
public Object call() throws Exception {
return queryPassword();
}
});
Platform.runLater(query);
System.out.println(query.get());
As FutureTask implements Runnable, you can pass it directly to Platform#runLater(...). queryPassword() will be inokved on the event queue, and the subsequent call to get block until that method completes. Of course, you will want to invoke this code in a loop until the password actually matches.
Important
This code is for the specific case of when you have code which is not on the JavaFX application thread and you want to invoke code which is on the JavaFX application thread to display GUI to a user, then get a result from that GUI before continuing processing off the JavaFX application thread.
You must not be on the JavaFX application thread when you call CountdownLatch.await in the code snippet below. If you invoke CountDownLatch.await on the JavaFX Application thread, you will deadlock your application. Besides which, if you are already on the JavaFX application thread, you don't need to invoke Platform.runLater to execute something on the JavaFX application thread.
Most of the time you know if you are on the JavaFX application thread or not. If you are not sure, you can check your thread by calling Platform.isFxApplicationThread().
An alternate method using CountDownLatch. I like Sarcan's method better though ;-)
final CountDownLatch latch = new CountDownLatch(1);
final StringProperty passwordProperty = new SimpleStringProperty();
Platform.runLater(new Runnable() {
#Override public void run() {
passwordProperty.set(queryPassword());
latch.countDown();
}
});
latch.await();
System.out.println(passwordProperty.get());
Here is some executable sample code demonstrating use of a CountdownLatch to suspend execution of a non-JavaFX application thread until a JavaFX dialog has retrieved a result which can then be accessed by the non-JavaFX application thread.
The application prevents the JavaFX launcher thread for the application from continuing until the user has entered the correct password in a JavaFX dialog. The access granted stage is not shown until the correct password has been entered.
import javafx.application.*;
import javafx.beans.property.*;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.text.TextAlignment;
import javafx.stage.*;
import java.util.concurrent.CountDownLatch;
public class PasswordPrompter extends Application {
final StringProperty passwordProperty = new SimpleStringProperty();
#Override public void init() {
final CountDownLatch latch = new CountDownLatch(1);
Platform.runLater(new Runnable() {
#Override public void run() {
passwordProperty.set(new PasswordPrompt(null).getPassword());
latch.countDown();
}
});
try {
latch.await();
} catch (InterruptedException e) {
Platform.exit();
}
System.out.println(passwordProperty.get());
}
#Override public void start(final Stage stage) {
Label welcomeMessage = new Label("Access Granted\nwith password\n" + passwordProperty.get());
welcomeMessage.setTextAlignment(TextAlignment.CENTER);
StackPane layout = new StackPane();
layout.setStyle("-fx-background-color: cornsilk; -fx-padding: 20px;");
layout.getChildren().setAll(welcomeMessage);
stage.setScene(new Scene(layout));
stage.show();
}
public static void main(String[] args) { launch(args); }
}
class PasswordPrompt {
final Window owner;
PasswordPrompt(Window owner) {
this.owner = owner;
}
public String getPassword() {
final Stage dialog = new Stage();
dialog.setTitle("Pass is sesame");
dialog.initOwner(owner);
dialog.initStyle(StageStyle.UTILITY);
dialog.initModality(Modality.WINDOW_MODAL);
dialog.setOnCloseRequest(new EventHandler<WindowEvent>() {
#Override public void handle(WindowEvent windowEvent) {
Platform.exit();
}
});
final TextField textField = new TextField();
textField.setPromptText("Enter sesame");
final Button submitButton = new Button("Submit");
submitButton.setDefaultButton(true);
submitButton.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent t) {
if ("sesame".equals(textField.getText())) {
dialog.close();
}
}
});
final VBox layout = new VBox(10);
layout.setAlignment(Pos.CENTER_RIGHT);
layout.setStyle("-fx-background-color: azure; -fx-padding: 10;");
layout.getChildren().setAll(textField, submitButton);
dialog.setScene(new Scene(layout));
dialog.showAndWait();
return textField.getText();
}
}
The above program prints password to the screen and console purely for demonstration purposes, displaying or logging passwords is not something you would do in a real application.
I've spent like the last 24 hours trying to learn JavaFX. I'm trying to build a GUI that will display values from a data source (for example a database). My question is what the preferred way is to do this. So far I've come up with this code to build a simple GUI and get some data from a data source:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class AvcHmi extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
Text t = new Text(10, 50, "Replace/update this text periodically with data");
Group root = new Group();
root.getChildren().add(t);
primaryStage.setScene(new Scene(root, 400, 300));
primaryStage.show();
new Thread() {
private DataSource dataSource = new DataSource();
{ setDaemon(true); }
#Override
public void run() {
try {
for(;;) {
Thread.sleep(100);
Platform.runLater(new Runnable() {
#Override
public void run() {
System.out.println(dataSource.getDataMap().get("key1"));
}});
}
} catch(InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}
}
Datasource:
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
public class DataSource {
Map<String,String> dataMap = new HashMap<>();
public DataSource() {
dataMap.put("key1", "value1");
dataMap.put("key2", "value2");
dataMap.put("key3", "value3");
}
public Map<String, String> getDataMap() {
Random generator = new Random();
int randInt = generator.nextInt();
dataMap.put("key1", "value"+randInt);
return dataMap;
}
}
100 ms is OK interval to update this GUI as far as I'm concerned. But is this a viable solution?
The next step is to replace the text with a value from the data source. Been looking at Collections and ObservableMap and wondering if it's a preferred way of doing the actual GUI updates? I'm aving some problems with inner classes and final variables but might reason that out after some sleep.
Also, the target machine is not that powerful (somewhere between 350-512 mb RAM). Could this be an issue? My simple tests so far seems to run fine.
Thank you for any feedback on this.
This Oracle example shows how to achieve concurrency loading in data table, with source code; it might help you
You could also look at reading about javafx.concurrent.Task<V> API.
The code on the Oracle example is as follows:
public class UpdateCustomerTask extends Task<Customer> {
private final Customer customer;
public UpdateCustomerTask(Customer customer) {
this.customer = customer;
}
#Override protected Customer call() throws Exception {
// pseudo-code:
// query the database
// read the values
// Now update the customer
Platform.runLater(new Runnable() {
#Override public void run() {
customer.setF setFirstName(rs.getString("FirstName"));
// etc
}
});
return customer;
}
}