I have a simple JavaFX app that has two little circles that are supposed to change their location every 0.5 s. Later on this is supposed to become a planets simulation. At the moment the location of my space objects changes in a separate thread which is launched when the button "Start simulation" is pressed. Simultaneously I want my circles (representing the planets) to be drawn again and again always using the current location stored in the spaceObject objects. When I limit the re-drawing to three times (instead of an unlimited amount via a while ( true ) { which is what I actually want) I see that the GUI is not updating while the loop is running. But after the loop is finished the circles move to the new location while the calculations thread in the background is still running. Why is my GUI thread blocked for the time of the while ( i < 3 ) { and how can I simultaneously update my GUI with the current location of the circles? Here is my code:
Main.java
package plantenbahnen;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Controller.java
package plantenbahnen;
import java.net.URL;
import java.util.ArrayList;
import java.util.ResourceBundle;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.layout.Pane;
public class Controller implements Initializable {
#FXML private Pane paneDraw;
#FXML private Pane paneControls;
private ArrayList<SpaceObject> universe = new ArrayList<>();
private Thread calcThread;
#Override
public void initialize(URL url, ResourceBundle rb) {
SpaceObject sun = new SpaceObject("sun", 600, 600);
universe.add(sun);
SpaceObject earth = new SpaceObject("earth", 450, 450);
universe.add(earth);
MyCalculations myCalc = new MyCalculations(universe);
calcThread = new Thread(myCalc);
Draw.drawPlanets(universe, paneDraw);
}
#FXML private void buttonStartSimulation(ActionEvent event) throws InterruptedException {
calcThread.start();
Platform.runLater(new Runnable() {
#Override public void run() {
int i = 0;
//while ( true ) { // this line is what I want
while ( i < 3 ) {
Draw.drawPlanets(universe, paneDraw);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println(e);
}
i++;
}
}
});
}
}
MyCalculations.java
package plantenbahnen;
import java.util.ArrayList;
public class MyCalculations implements Runnable {
ArrayList<SpaceObject> universe;
public MyCalculations (ArrayList<SpaceObject> universe) {
this.universe = universe;
}
#Override
public void run(){
double toAdd = 100.0;
while ( true ) {
for (SpaceObject so: universe) {
so.setx(so.getx() + toAdd);
so.sety(so.gety() + toAdd);
}
if ( toAdd > 0.0 ) {
toAdd = -300.0;
} else {
toAdd = 300.0;
}
}
}
}
Draw.java
package plantenbahnen;
import java.util.ArrayList;
import javafx.scene.Node;
import javafx.scene.layout.Pane;
public class Draw {
public static void drawPlanets(ArrayList<SpaceObject> universe, Pane pane) {
for (Node child: pane.getChildren()) {
System.out.println(child);
}
// Clear objects first
for (SpaceObject so: universe) {
if ( pane.getChildren().contains(so) ) {
pane.getChildren().remove(so);
System.out.println("Removing ... " + so.getName());
}
}
double paneHalfWidth = pane.getPrefWidth() / 2.0;
double paneHalfHeight = pane.getPrefHeight() / 2.0;
double scaleFactor = 0.1;
for (SpaceObject so: universe) {
so.setCenterX(so.getx() * scaleFactor + paneHalfWidth);
so.setCenterY(so.gety() * scaleFactor + paneHalfHeight);
System.out.println("x=" + so.getCenterX() + " y=" + so.getCenterY());
so.setRadius(2);
//so.setColour(Color.BLACK);
pane.getChildren().add(so);
}
}
}
SpaceObject.java
package plantenbahnen;
import javafx.scene.shape.Circle;
public class SpaceObject extends Circle {
private double x,y;
private String name;
SpaceObject(String name, double x, double y) {
this.x = x;
this.y = y;
this.name = name;
}
public double getx(){
return this.x;
}
public void setx(double value){
this.x=value;
}
public double gety(){
return this.y;
}
public void sety(double value){
this.y=value;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
FXMLDocument.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane fx:id="AnchorPane" prefHeight="700.0" prefWidth="800.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="plantenbahnen.Controller">
<children>
<Pane fx:id="paneDraw" prefHeight="700.0" prefWidth="800.0">
<children>
<Pane fx:id="paneControls" prefHeight="66.0" prefWidth="174.0">
<children>
<Button fx:id="buttonStartSimulation" layoutX="26.0" layoutY="21.0" mnemonicParsing="false" onAction="#buttonStartSimulation" text="Start simulation" />
</children>
</Pane>
</children></Pane>
</children>
</AnchorPane>
Thanks a lot in advance for your help.
Try something like this:
#FXML private void buttonStartSimulation(ActionEvent event) throws InterruptedException {
calcThread.start();
Thread updaterThread = new Thread( () -> {
#Override public void run () {
int i = 0;
while ( true ) { // this line is what I want
Platform.runLater( () -> Draw.drawPlanets(universe, paneDraw) );
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println(e);
}
i++;
}
}
}
updaterThread.setDaemon ( true );
updaterThread.start();
}
You want to make sure all of your calls to Platform.runLater() are short, have no sleeps involved, return quickly, and do minimal calculations -- all of these calls have to be done "in-between" other updates to the UI, like resizing windows, managing button presses, etc.
By the way -- I'm not sure if you need a "calcThread" and an "updaterThread". I suspect they should be one thread. But this is a good proof of concept.
Thanks everybody for your help. I ended up using Timeline. All I had to change is the Controller and it looks like this (only the relevant code is shown):
public class Controller implements Initializable {
// ...
private Thread calcThread;
Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(0.5), new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
Draw.drawPlanets(universe, paneDraw);
}
}));
#Override
public void initialize(URL url, ResourceBundle rb) {
// ...
MyCalculations myCalc = new MyCalculations(universe);
calcThread = new Thread(myCalc);
// ...
}
#FXML private void buttonStartSimulation(ActionEvent event) throws InterruptedException {
calcThread.start();
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.play();
}
}
Related
I'm building a UI for a Simulator running in background. Since this Simulator may not hold for a long time, it of course has to be in a separate thread from the JavaFx Thread. I want to start, pause, resume and stop/terminate the Simulator when the corresponding button is clicked.
The service class that advances the simulator looks like this:
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
public class SimulatorService extends Service<Void> {
private Simulator simulator;
private long cycleLengthMS = 1000;
private final AtomicBoolean simulatorStopped = new AtomicBoolean(false);
public SimulatorService(Simulator simulator){
this.simulator = simulator;
}
#Override
protected Task<Void> createTask() {
return new Task<>() {
#Override
protected Void call() {
System.out.println("Requested start of Simulator");
int state;
do {
// advance
state = simulator.nextStep();
try {
TimeUnit.MILLISECONDS.sleep(cycleLengthMS);
if(simulatorStopped.get()){
super.cancel();
}
} catch (InterruptedException e) {
return null;
}
}
while (state == 0 && !simulatorStopped.get());
return null;
}
};
}
#Override
public void start(){
if(getState().equals(State.READY)){
simulatorStopped.set(false);
super.start();
}
else if(getState().equals(State.CANCELLED)){
simulatorStopped.set(false);
super.restart();
}
}
#Override
public boolean cancel(){
if(simulatorStopped.get()){
simulatorStopped.set(true);
return false;
} else{
simulatorStopped.set(true);
return true; //if value changed
}
}
}
The Simulator starts the Service if a button on the GUI is pressed:
import javafx.application.Platform;
import java.util.concurrent.atomic.AtomicInteger;
public class Simulator {
Model model;
private final SimulatorService simulatorService;
public Simulator(Model model){
this.model = model;
simulatorService = new SimulatorService(this);
}
public int nextStep(){
final AtomicInteger res = new AtomicInteger(0);
Platform.runLater(new Thread(()-> {
res.set(model.nextStep());
}));
return res.get();
}
public boolean stopSimulationService() throws IllegalStateException{
return simulatorService.cancel();
}
public void startSimulationService() throws IllegalStateException{
simulatorService.start();
}
}
Parts of the window are redrawn if a observed value in the model changes:
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
public class Model {
private final IntegerProperty observedValue = new SimpleIntegerProperty(0);
public int nextStep() {
observedValue.set(observedValue.get() + 1);
return observedValue.get() > 500000 ? 1 : 0;
}
public int getObservedValue() {
return observedValue.get();
}
public IntegerProperty observedValueProperty() {
return observedValue;
}
public void setObservedValue(int observedValue) {
this.observedValue.set(observedValue);
}
}
The redraw happens in another class:
import javafx.beans.value.ChangeListener;
public class ViewController {
private View view;
private Simulator simulator;
private Model model;
public ViewController(Simulator simulator) {
this.simulator = simulator;
this.view = new View();
setModel(simulator.model);
view.nextStep.setOnMouseClicked(click -> {
simulator.nextStep();
});
view.startSim.setOnMouseClicked(click -> {
simulator.startSimulationService();
});
view.stopSim.setOnMouseClicked(click ->{
simulator.stopSimulationService();
});
}
public View getView() {
return view;
}
private final ChangeListener<Number> observedValueListener = (observableValue, oldInt, newInt) -> {
view.updateView(newInt.intValue());
};
public void setModel(Model m) {
this.model = m;
m.observedValueProperty().addListener(observedValueListener);
}
}
The corresponding view:
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.text.Text;
public class View extends BorderPane {
Button nextStep = new Button("next step");
Button startSim = new Button("start");
Button stopSim = new Button("stop");
GridPane buttons = new GridPane();
Text num = new Text();
public View(){
buttons.add(nextStep,0,0);
buttons.add(startSim,0,1);
buttons.add(stopSim,0,2);
buttons.setAlignment(Pos.BOTTOM_LEFT);
setCenter(buttons);
setTop(num);
}
public void updateView(int num){
this.num.setText("" + num);
}
}
Main:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
ViewController c = new ViewController(new Simulator(new Model()));
Scene scene = new Scene(c.getView(),200,200);
stage.setScene(scene);
stage.show();
}
}
I want study how to work with Threads in JavaFX. For example, 2 processes, which should change text on the lables every 100 ms, and updating information on the screen also every 100 ms.
But in this case it doesnt works. IDEA writes:
Exception in thread "Thread-4" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-4
I have read many examples with the same problem, but any of their solutions doesnt worked.
What I should to do?
Thanks.
sample.fxml
...
<Button fx:id="startBut" layoutX="100.0" layoutY="50.0" mnemonicParsing="false" onAction="#testingOfThread" prefHeight="25.0" prefWidth="65.0" text="Export" />
<Label fx:id="firstStatus" layoutX="100.0" layoutY="100" text="Status" />
<Label fx:id="secondStatus" layoutX="100.0" layoutY="150" text="Status" />
...
Main.java
package sample;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
primaryStage.setTitle("Sample");
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
//Take control to Controller
public void initializeController(){
FXMLLoader loader = new FXMLLoader();
Controller controller = loader.getController();
controller.setMain(this);
}
public static void main(String[] args) {
launch(args);
}
}
Controller.java
package sample;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.scene.control.*;
public class Controller {
#FXML
private Label firstStatus;
#FXML
private Label secondStatus;
#FXML
public Button startBut;
//Link to MainApp
private Main Main;
//Constructor
public Controller(){
}
//Link for himself
public void setMain(Main main){
this.Main = main;
}
#FXML
private void testingOfThread(){
Task<Void> task = new Task<Void>() {
#Override public Void call() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.interrupted();
break;
}
System.out.println(i + 1);
firstStatus.setText(i+"");
}
return null;
}
};
Thread th = new Thread(task);
th.setDaemon(true);
th.start();
Task<Void> task2 = new Task<Void>() {
#Override public Void call() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.interrupted();
break;
}
System.out.println(i + 1);
secondStatus.setText(i+"");
}
return null;
}
};
Thread th2 = new Thread(task2);
th2.start();
}
}
Find the code which update the GUI from a thread other than the application thread,then put them in runLater().
Platform.runLater(new Runnable() {
#Override
public void run() {
//update application thread
}
});
I have a background thread which updates a list with incoming buy sell orders, prices etc. I get the data from my broker asynchronously. It’s important that the updates in the list happens in the background thread sequentially.
I want to display the mainPortfolioList in my javafx table without the risk of a “Not on FX application thread IllegalStateException”. The nearest solution I found was JavaFX refresh TableView thread. But this will not work if the list is in another thread, as I understand it.
I’m quite new in java and tried to solve my problem with an addlistener. I made a simplified example to show what I want and what I’ve done so far.
How can I display updates from my mainPortfolioList in the JavaFX table?
PortfolioController
import application.Test;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.util.converter.NumberStringConverter;
public class PortfolioController {
#FXML private Button btnTest;
#FXML private TableView<Portfolio> tblPortfolio;
#FXML private TableColumn<Portfolio, String> colSymbol;
#FXML private TableColumn<Portfolio, Number> colQuantity;
#FXML private TableColumn<Portfolio, Number> colPrice;
private ObservableList<Portfolio> fxPortfolioList;
#FXML
private void initialize() {
tblPortfolio.setEditable(true);
colSymbol.setCellValueFactory(data -> data.getValue().colSymbolProperty());
colQuantity.setCellValueFactory(data -> data.getValue().colQuantityProperty());
colPrice.setCellValueFactory(data -> data.getValue().colPriceProperty());
colSymbol.setCellFactory(TextFieldTableCell.forTableColumn());
colQuantity.setCellFactory(TextFieldTableCell.<Portfolio, Number>forTableColumn(
new NumberStringConverter("#,##0.00")));
colQuantity.setOnEditCommit(event -> {
int newValue = event.getNewValue().intValue();
event.getRowValue().setColQuantity(newValue);});
colPrice.setCellFactory(TextFieldTableCell.<Portfolio, Number>forTableColumn(
new NumberStringConverter("#,##0.00")));
fxPortfolioList = FXCollections.observableArrayList();
tblPortfolio.setItems(fxPortfolioList);
Test.mainPortfolioList.addListener((ListChangeListener.Change<? extends Portfolio> c) -> {
while (c.next()) {
if (c.wasAdded()) {
Platform.runLater(() -> {
for (Portfolio asset : c.getAddedSubList()) {
fxPortfolioList.add(asset);
}
});
} else if (c.wasRemoved()) {
Platform.runLater(() -> {
for (Portfolio asset : c.getRemoved()) {
fxPortfolioList.remove(asset);
}
});
} else if (c.wasUpdated()) {
Platform.runLater(() -> {
for (int i = c.getFrom(); i < c.getTo(); ++i) {
fxPortfolioList.set(i, c.getList().get(i));
}
});
}
}
});
}
#FXML
void btnTestClicked(ActionEvent event) {
Test test = new Test();
test.dataStream(this);
}
}
Portfolio
import javafx.beans.Observable;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.util.Callback;
public class Portfolio {
private final StringProperty colSymbol;
private final IntegerProperty colQuantity;
private final DoubleProperty colPrice;
public Portfolio(String symbol, int quantity, double price) {
this.colSymbol = new SimpleStringProperty(symbol);
this.colQuantity = new SimpleIntegerProperty(quantity);
this.colPrice = new SimpleDoubleProperty(price);
}
// extractor
public static Callback<Portfolio, Observable[]> extractor() {
return (Portfolio p) -> new Observable[] {
p.colSymbolProperty(),
p.colQuantityProperty(),
p.colPriceProperty(),
};
}
// property
public StringProperty colSymbolProperty() {
return colSymbol;
}
public IntegerProperty colQuantityProperty() {
return colQuantity;
}
public DoubleProperty colPriceProperty() {
return colPrice;
}
// getter
public String getColSymbol() {
return colSymbol.get();
}
public int getColQuantity() {
return colQuantity.get();
}
public double getColPrice() {
return colPrice.get();
}
// setter
public void setColSymbol(String newValue) {
colSymbol.set(newValue);
}
public void setColQuantity(int newValue) {
colQuantity.set(newValue);
}
public void setColPrice(double newValue) {
colPrice.set(newValue);
}
}
Test simulation
import controller.portfolio.Portfolio;
import controller.portfolio.PortfolioController;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
public class Test {
public static ObservableList<Portfolio> mainPortfolioList =
FXCollections.observableArrayList(Portfolio.extractor());
public void dataStream(PortfolioController portfolioController) {
// Need to be sequentially
// The task only simulates simplified operations
Runnable task = () -> {
// add stock
mainPortfolioList.add(new Portfolio("AAPL", 13, 153.03));
mainPortfolioList.add(new Portfolio("MSFT", 31, 67.51));
// Change the quantity
for (Portfolio asset : mainPortfolioList) {
if (asset.getColSymbol().equals("AAPL")) {
asset.setColQuantity(55);
}
}
// run price updates
for (int k = 0; k < 15; k++) {
for (int m = 0; m < mainPortfolioList.size(); m++) {
double random = Math.random() * 50 + 1;
String symbol = mainPortfolioList.get(m).getColSymbol();
setTickPrice(symbol, 4, random);
randomSleep();
}
}
// remove stock
for (Portfolio asset : mainPortfolioList) {
if (asset.getColSymbol().equals("AAPL")) {
mainPortfolioList.remove(asset);
}
}
};
Thread t = new Thread(task, "Simulation");
t.setDaemon(true);
t.start();
}
public void setTickPrice(String symbol, int tickType, double price) {
for (Portfolio asset : mainPortfolioList) {
if (asset.getColSymbol().equals(symbol)) {
switch(tickType){
case 4: // Last Price
asset.setColPrice(price);
break;
}
}
}
}
private void randomSleep() {
try {
Thread.sleep((int)(Math.random() * 300));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Main
import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws IOException {
Parent root = FXMLLoader.load(getClass().getResource("/view/Portfolio.fxml"));
Scene scene = new Scene(root, 500, 300);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
view
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<BorderPane prefHeight="600.0" prefWidth="800.0" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="controller.portfolio.PortfolioController">
<top>
</top>
<center>
<TableView fx:id="tblPortfolio" prefHeight="200.0" prefWidth="200.0" tableMenuButtonVisible="true" BorderPane.alignment="CENTER">
<columns>
<TableColumn fx:id="colSymbol" maxWidth="80.0" minWidth="60.0" prefWidth="-1.0" text="Symbol" />
<TableColumn fx:id="colQuantity" maxWidth="60.0" minWidth="40.0" prefWidth="-1.0" text="Quantity" />
<TableColumn fx:id="colPrice" maxWidth="69.0" minWidth="49.0" prefWidth="-1.0" text="Price" />
</columns>
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
</columnResizePolicy>
</TableView>
</center>
<bottom>
</bottom>
<top>
<HBox BorderPane.alignment="CENTER">
<children>
<Button fx:id="btnTest" mnemonicParsing="false" onAction="#btnTestClicked" text="Test">
<HBox.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</HBox.margin>
</Button>
</children>
</HBox>
</top>
</BorderPane>
Everything coming from the reader thread(assuming IB) that will change something on the screen has to be wrapped in Platform.runLater.
eg.
Platform.runLater(() -> asset.setColPrice(price));
The same for all other calls, like add remove etc.
What would be easier is if in the wrapper for updatePortfolio you just wrap a new call to your data model updater with Platform.runLater.
//in wrapper implementation, this call happens on EReader thread.
void updatePortfolio(Contract contract, int position, double marketPrice, double marketValue,
double averageCost, double unrealizedPNL, double realizedPNL, String accountName){
//this moves it to FXApplication thread.
Platform.runLater(() ->
updateMyPortfolio(contract, position, marketPrice));//etc.. more flds
}
That way you can use all the new data in the scene with no worries.
I want to update the code of this example:
http://www.java2s.com/Code/Java/JavaFX/StackPanebasedwizard.htm
package javafxwizard;
import javafx.application.Application;
import javafx.stage.Stage;
import java.util.Stack;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
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.LabelBuilder;
import javafx.scene.control.RadioButton;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextAreaBuilder;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.StackPaneBuilder;
import javafx.scene.layout.VBox;
import javafx.scene.layout.VBoxBuilder;
public class JavaFXWizard extends Application {
public static void main(String[] args) throws Exception {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(new SurveyWizard(stage), 400, 250));
stage.show();
}
}
class Wizard extends StackPane {
private static final int UNDEFINED = -1;
private ObservableList<WizardPage> pages = FXCollections
.observableArrayList();
private Stack<Integer> history = new Stack<>();
private int curPageIdx = UNDEFINED;
Wizard(WizardPage... nodes) {
pages.addAll(nodes);
navTo(0);
setStyle("-fx-padding: 10; -fx-background-color: cornsilk;");
}
void nextPage() {
if (hasNextPage()) {
navTo(curPageIdx + 1);
}
}
void priorPage() {
if (hasPriorPage()) {
navTo(history.pop(), false);
}
}
boolean hasNextPage() {
return (curPageIdx < pages.size() - 1);
}
boolean hasPriorPage() {
return !history.isEmpty();
}
void navTo(int nextPageIdx, boolean pushHistory) {
if (nextPageIdx < 0 || nextPageIdx >= pages.size()) {
return;
}
if (curPageIdx != UNDEFINED) {
if (pushHistory) {
history.push(curPageIdx);
}
}
WizardPage nextPage = pages.get(nextPageIdx);
curPageIdx = nextPageIdx;
getChildren().clear();
getChildren().add(nextPage);
nextPage.manageButtons();
}
void navTo(int nextPageIdx) {
navTo(nextPageIdx, true);
}
void navTo(String id) {
Node page = lookup("#" + id);
if (page != null) {
int nextPageIdx = pages.indexOf(page);
if (nextPageIdx != UNDEFINED) {
navTo(nextPageIdx);
}
}
}
public void finish() {
}
public void cancel() {
}
}
/**
* basic wizard page class
*/
abstract class WizardPage extends VBox {
Button priorButton = new Button("_Previous");
Button nextButton = new Button("N_ext");
Button cancelButton = new Button("Cancel");
Button finishButton = new Button("_Finish");
WizardPage(String title) {
getChildren().add(
LabelBuilder.create().text(title)
.style("-fx-font-weight: bold; -fx-padding: 0 0 5 0;").build());
setId(title);
setSpacing(5);
setStyle("-fx-padding:10; -fx-background-color: honeydew; -fx-border-color: derive(honeydew, -30%); -fx-border-width: 3;");
Region spring = new Region();
VBox.setVgrow(spring, Priority.ALWAYS);
getChildren().addAll(getContent(), spring, getButtons());
priorButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
priorPage();
}
});
nextButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
nextPage();
}
});
cancelButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
getWizard().cancel();
}
});
finishButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
getWizard().finish();
}
});
}
HBox getButtons() {
Region spring = new Region();
HBox.setHgrow(spring, Priority.ALWAYS);
HBox buttonBar = new HBox(5);
cancelButton.setCancelButton(true);
finishButton.setDefaultButton(true);
buttonBar.getChildren().addAll(spring, priorButton, nextButton,
cancelButton, finishButton);
return buttonBar;
}
abstract Parent getContent();
boolean hasNextPage() {
return getWizard().hasNextPage();
}
boolean hasPriorPage() {
return getWizard().hasPriorPage();
}
void nextPage() {
getWizard().nextPage();
}
void priorPage() {
getWizard().priorPage();
}
void navTo(String id) {
getWizard().navTo(id);
}
Wizard getWizard() {
return (Wizard) getParent();
}
public void manageButtons() {
if (!hasPriorPage()) {
priorButton.setDisable(true);
}
if (!hasNextPage()) {
nextButton.setDisable(true);
}
}
}
/**
* This class shows a satisfaction survey
*/
class SurveyWizard extends Wizard {
Stage owner;
public SurveyWizard(Stage owner) {
super(new ComplaintsPage(), new MoreInformationPage(), new ThanksPage());
this.owner = owner;
}
#Override
public void finish() {
System.out.println("Had complaint? "
+ SurveyData.instance.hasComplaints.get());
if (SurveyData.instance.hasComplaints.get()) {
System.out.println("Complaints: "
+ (SurveyData.instance.complaints.get().isEmpty() ? "No Details"
: "\n" + SurveyData.instance.complaints.get()));
}
owner.close();
}
#Override
public void cancel() {
System.out.println("Cancelled");
owner.close();
}
}
/**
* Simple placeholder class for the customer entered survey response.
*/
class SurveyData {
BooleanProperty hasComplaints = new SimpleBooleanProperty();
StringProperty complaints = new SimpleStringProperty();
static SurveyData instance = new SurveyData();
}
/**
* This class determines if the user has complaints. If not, it jumps to the
* last page of the wizard.
*/
class ComplaintsPage extends WizardPage {
private RadioButton yes;
private RadioButton no;
private ToggleGroup options = new ToggleGroup();
public ComplaintsPage() {
super("Complaints");
nextButton.setDisable(true);
finishButton.setDisable(true);
yes.setToggleGroup(options);
no.setToggleGroup(options);
options.selectedToggleProperty().addListener(new ChangeListener<Toggle>() {
#Override
public void changed(ObservableValue<? extends Toggle> observableValue,
Toggle oldToggle, Toggle newToggle) {
nextButton.setDisable(false);
finishButton.setDisable(false);
}
});
}
#Override
Parent getContent() {
yes = new RadioButton("Yes");
no = new RadioButton("No");
SurveyData.instance.hasComplaints.bind(yes.selectedProperty());
return VBoxBuilder.create().spacing(5)
.children(new Label("Do you have complaints?"), yes, no).build();
}
#Override
void nextPage() {
// If they have complaints, go to the normal next page
if (options.getSelectedToggle().equals(yes)) {
super.nextPage();
} else {
// No complaints? Short-circuit the rest of the pages
navTo("Thanks");
}
}
}
/**
* This page gathers more information about the complaint
*/
class MoreInformationPage extends WizardPage {
public MoreInformationPage() {
super("More Info");
}
#Override
Parent getContent() {
TextArea textArea = TextAreaBuilder.create().wrapText(true).build();
nextButton.setDisable(true);
textArea.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observableValue,
String oldValue, String newValue) {
nextButton.setDisable(newValue.isEmpty());
}
});
SurveyData.instance.complaints.bind(textArea.textProperty());
return VBoxBuilder.create().spacing(5)
.children(new Label("Please enter your complaints."), textArea).build();
}
}
/**
* This page thanks the user for taking the survey
*/
class ThanksPage extends WizardPage {
public ThanksPage() {
super("Thanks");
}
#Override
Parent getContent() {
StackPane stack = StackPaneBuilder.create().children(new Label("Thanks!"))
.build();
VBox.setVgrow(stack, Priority.ALWAYS);
return stack;
}
}
Can you help me to update the code? In Java 8 LabelBuilder TextAreaBuilder StackPaneBuilder VBoxBuilder are obsolete code?
Can you help me to simplify the code and update it for Java 8?
Here is the same code, but without the builders... Buts its the same Logic:
import javafx.application.Application;
import javafx.stage.Stage;
import java.util.Stack;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
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.RadioButton;
import javafx.scene.control.TextArea;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
public class JavaFXWizard extends Application {
public static void main(String[] args) throws Exception {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(new SurveyWizard(stage), 400, 250));
stage.show();
}
}
class Wizard extends StackPane {
private static final int UNDEFINED = -1;
private ObservableList<WizardPage> pages = FXCollections
.observableArrayList();
private Stack<Integer> history = new Stack<>();
private int curPageIdx = UNDEFINED;
Wizard(WizardPage... nodes) {
pages.addAll(nodes);
navTo(0);
setStyle("-fx-padding: 10; -fx-background-color: cornsilk;");
}
void nextPage() {
if (hasNextPage()) {
navTo(curPageIdx + 1);
}
}
void priorPage() {
if (hasPriorPage()) {
navTo(history.pop(), false);
}
}
boolean hasNextPage() {
return (curPageIdx < pages.size() - 1);
}
boolean hasPriorPage() {
return !history.isEmpty();
}
void navTo(int nextPageIdx, boolean pushHistory) {
if (nextPageIdx < 0 || nextPageIdx >= pages.size()) {
return;
}
if (curPageIdx != UNDEFINED) {
if (pushHistory) {
history.push(curPageIdx);
}
}
WizardPage nextPage = pages.get(nextPageIdx);
curPageIdx = nextPageIdx;
getChildren().clear();
getChildren().add(nextPage);
nextPage.manageButtons();
}
void navTo(int nextPageIdx) {
navTo(nextPageIdx, true);
}
void navTo(String id) {
Node page = lookup("#" + id);
if (page != null) {
int nextPageIdx = pages.indexOf(page);
if (nextPageIdx != UNDEFINED) {
navTo(nextPageIdx);
}
}
}
public void finish() {
}
public void cancel() {
}
}
/**
* basic wizard page class
*/
abstract class WizardPage extends VBox {
Button priorButton = new Button("_Previous");
Button nextButton = new Button("N_ext");
Button cancelButton = new Button("Cancel");
Button finishButton = new Button("_Finish");
WizardPage(String title) {
Label label = new Label(title);
label.setStyle("-fx-font-weight: bold; -fx-padding: 0 0 5 0;");
getChildren().add(label);
setId(title);
setSpacing(5);
setStyle("-fx-padding:10; -fx-background-color: honeydew; -fx-border-color: derive(honeydew, -30%); -fx-border-width: 3;");
Region spring = new Region();
VBox.setVgrow(spring, Priority.ALWAYS);
getChildren().addAll(getContent(), spring, getButtons());
priorButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
priorPage();
}
});
nextButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
nextPage();
}
});
cancelButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
getWizard().cancel();
}
});
finishButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
getWizard().finish();
}
});
}
HBox getButtons() {
Region spring = new Region();
HBox.setHgrow(spring, Priority.ALWAYS);
HBox buttonBar = new HBox(5);
cancelButton.setCancelButton(true);
finishButton.setDefaultButton(true);
buttonBar.getChildren().addAll(spring, priorButton, nextButton,
cancelButton, finishButton);
return buttonBar;
}
abstract Parent getContent();
boolean hasNextPage() {
return getWizard().hasNextPage();
}
boolean hasPriorPage() {
return getWizard().hasPriorPage();
}
void nextPage() {
getWizard().nextPage();
}
void priorPage() {
getWizard().priorPage();
}
void navTo(String id) {
getWizard().navTo(id);
}
Wizard getWizard() {
return (Wizard) getParent();
}
public void manageButtons() {
if (!hasPriorPage()) {
priorButton.setDisable(true);
}
if (!hasNextPage()) {
nextButton.setDisable(true);
}
}
}
/**
* This class shows a satisfaction survey
*/
class SurveyWizard extends Wizard {
Stage owner;
public SurveyWizard(Stage owner) {
super(new ComplaintsPage(), new MoreInformationPage(), new ThanksPage());
this.owner = owner;
}
#Override
public void finish() {
System.out.println("Had complaint? "
+ SurveyData.instance.hasComplaints.get());
if (SurveyData.instance.hasComplaints.get()) {
System.out
.println("Complaints: "
+ (SurveyData.instance.complaints.get().isEmpty() ? "No Details"
: "\n"
+ SurveyData.instance.complaints
.get()));
}
owner.close();
}
#Override
public void cancel() {
System.out.println("Cancelled");
owner.close();
}
}
/**
* Simple placeholder class for the customer entered survey response.
*/
class SurveyData {
BooleanProperty hasComplaints = new SimpleBooleanProperty();
StringProperty complaints = new SimpleStringProperty();
static SurveyData instance = new SurveyData();
}
/**
* This class determines if the user has complaints. If not, it jumps to the
* last page of the wizard.
*/
class ComplaintsPage extends WizardPage {
private RadioButton yes;
private RadioButton no;
private ToggleGroup options = new ToggleGroup();
public ComplaintsPage() {
super("Complaints");
nextButton.setDisable(true);
finishButton.setDisable(true);
yes.setToggleGroup(options);
no.setToggleGroup(options);
options.selectedToggleProperty().addListener(
new ChangeListener<Toggle>() {
#Override
public void changed(
ObservableValue<? extends Toggle> observableValue,
Toggle oldToggle, Toggle newToggle) {
nextButton.setDisable(false);
finishButton.setDisable(false);
}
});
}
#Override
Parent getContent() {
yes = new RadioButton("Yes");
no = new RadioButton("No");
SurveyData.instance.hasComplaints.bind(yes.selectedProperty());
VBox vBox = new VBox();
vBox.setSpacing(5);
vBox.getChildren()
.addAll(new Label("Do you have complaints?"), yes, no);
return vBox;
}
#Override
void nextPage() {
// If they have complaints, go to the normal next page
if (options.getSelectedToggle().equals(yes)) {
super.nextPage();
} else {
// No complaints? Short-circuit the rest of the pages
navTo("Thanks");
}
}
}
/**
* This page gathers more information about the complaint
*/
class MoreInformationPage extends WizardPage {
public MoreInformationPage() {
super("More Info");
}
#Override
Parent getContent() {
TextArea textArea = new TextArea();
textArea.setWrapText(true);
nextButton.setDisable(true);
textArea.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(
ObservableValue<? extends String> observableValue,
String oldValue, String newValue) {
nextButton.setDisable(newValue.isEmpty());
}
});
SurveyData.instance.complaints.bind(textArea.textProperty());
VBox vBox = new VBox();
vBox.setSpacing(5);
vBox.getChildren().addAll(new Label("Please enter your complaints."),
textArea);
return vBox;
}
}
/**
* This page thanks the user for taking the survey
*/
class ThanksPage extends WizardPage {
public ThanksPage() {
super("Thanks");
}
#Override
Parent getContent() {
StackPane stack = new StackPane();
stack.getChildren().add(new Label("Thanks!"));
VBox.setVgrow(stack, Priority.ALWAYS);
return stack;
}
}
I want to develop a client app for website .
I want the app to reside in system tray when minimised.
I dont know how to accomplish this task .
Is their any example for this type of operation.
The key here is to set the implicit exit to false Platform.setImplicitExit(false);
Also is important to show and hide the stage in a new thread.
Platform.runLater(new Runnable() {
#Override
public void run() {
stage.show();
}
});
Platform.runLater(new Runnable() {
#Override
public void run() {
stage.hide();
}
});
Next, the whole code:
import java.awt.AWTException;
import java.awt.MenuItem;
import java.awt.PopupMenu;
import java.awt.SystemTray;
import java.awt.TrayIcon;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.net.URL;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import javax.imageio.ImageIO;
/**
*
* #author alvaro
*/
public class TrayTest extends Application {
private boolean firstTime;
private TrayIcon trayIcon;
public static void main(String[] args)
{
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
createTrayIcon(stage);
firstTime = true;
Platform.setImplicitExit(false);
Scene scene = new Scene(new Group(), 800, 600);
stage.setScene(scene);
stage.show();
}
public void createTrayIcon(final Stage stage) {
if (SystemTray.isSupported()) {
// get the SystemTray instance
SystemTray tray = SystemTray.getSystemTray();
// load an image
java.awt.Image image = null;
try {
URL url = new URL("http://www.digitalphotoartistry.com/rose1.jpg");
image = ImageIO.read(url);
} catch (IOException ex) {
System.out.println(ex);
}
stage.setOnCloseRequest(new EventHandler<WindowEvent>() {
#Override
public void handle(WindowEvent t) {
hide(stage);
}
});
// create a action listener to listen for default action executed on the tray icon
final ActionListener closeListener = new ActionListener() {
#Override
public void actionPerformed(java.awt.event.ActionEvent e) {
System.exit(0);
}
};
ActionListener showListener = new ActionListener() {
#Override
public void actionPerformed(java.awt.event.ActionEvent e) {
Platform.runLater(new Runnable() {
#Override
public void run() {
stage.show();
}
});
}
};
// create a popup menu
PopupMenu popup = new PopupMenu();
MenuItem showItem = new MenuItem("Show");
showItem.addActionListener(showListener);
popup.add(showItem);
MenuItem closeItem = new MenuItem("Close");
closeItem.addActionListener(closeListener);
popup.add(closeItem);
/// ... add other items
// construct a TrayIcon
trayIcon = new TrayIcon(image, "Title", popup);
// set the TrayIcon properties
trayIcon.addActionListener(showListener);
// ...
// add the tray image
try {
tray.add(trayIcon);
} catch (AWTException e) {
System.err.println(e);
}
// ...
}
}
public void showProgramIsMinimizedMsg() {
if (firstTime) {
trayIcon.displayMessage("Some message.",
"Some other message.",
TrayIcon.MessageType.INFO);
firstTime = false;
}
}
private void hide(final Stage stage) {
Platform.runLater(new Runnable() {
#Override
public void run() {
if (SystemTray.isSupported()) {
stage.hide();
showProgramIsMinimizedMsg();
} else {
System.exit(0);
}
}
});
}
}
As far as I know it will be possible in JFX 8. Right now the best solution is to embed your application into AWT and hide the AWT window itself.