javafx implement x-value labels for a line chart - javafx-2

I tried to implement the method of jewelsea according to this post label on barchart but labels are not visible and i don't understand why.
The only difference is I use an fxml file and my chart data are composed of several series.
To illustrate better the problem, I made a MVCE of the situation :
public class PaneController implements Initializable{
#FXML
private LineChart<Number, Number> lineChart;
#Override
public void initialize(URL location, ResourceBundle resources) {
// TODO Auto-generated method stub
lineChart.setLegendVisible(false);
lineChart.setCreateSymbols(false);
lineChart.setData(createData());
/** for each serie add label on the peak if intensity > 0 */
for(int i=0;i<lineChart.getData().size();i++){
for(XYChart.Data<Number, Number> value:lineChart.getData().get(i).getData()){
if(value.getYValue().intValue()>0){
value.nodeProperty().addListener(new ChangeListener<Node>() {
#Override public void changed(ObservableValue<? extends Node> ov, Node oldNode, final Node node) {
System.out.println("value:"+node);
if (node != null) {
displayLabelForData(value);
}
}
});
}
}
}
}
/** create data for the line chart */
public ObservableList<Series<Number,Number>> createData(){
ObservableList<Series<Number,Number>> data = FXCollections.observableArrayList();
for(int i=1;i<=10;i++){
XYChart.Series<Number, Number> peakSelect = new XYChart.Series<>();
peakSelect.getData().add(new XYChart.Data<Number, Number>(i,0));
peakSelect.getData().add(new XYChart.Data<Number, Number>(i,i*2));
data.add(peakSelect);
}
return data;
}
/** places a x value label on each peak */
private void displayLabelForData(XYChart.Data<Number, Number> data){
final Node node = data.getNode();
final Text dataText = new Text(data.getXValue() + "");
node.parentProperty().addListener(new ChangeListener<Parent>() {
#Override public void changed(ObservableValue<? extends Parent> ov, Parent oldParent, Parent parent) {
Group parentGroup = (Group) parent;
parentGroup.getChildren().add(dataText);
}
});
node.boundsInParentProperty().addListener(new ChangeListener<Bounds>() {
#Override public void changed(ObservableValue<? extends Bounds> ov, Bounds oldBounds, Bounds bounds) {
dataText.setLayoutX( Math.round( bounds.getMinX() + bounds.getWidth() / 2 - dataText.prefWidth(-1) / 2 ) );
dataText.setLayoutY( Math.round( bounds.getMinY() - dataText.prefHeight(-1) * 0.5 ) );
}
});
}
}
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.chart.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<AnchorPane xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.PaneController">
<children>
<LineChart fx:id="lineChart" prefHeight="400.0" prefWidth="500.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<xAxis>
<NumberAxis side="BOTTOM" />
</xAxis>
<yAxis>
<NumberAxis side="LEFT" />
</yAxis>
</LineChart>
</children>
</AnchorPane>
Main Class:
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(Main.class.getResource("Pane.fxml"));
AnchorPane rootPane = (AnchorPane) loader.load();
Scene scene = new Scene(rootPane);
primaryStage.setScene(scene);
primaryStage.setTitle("Test");
primaryStage.show();
PaneController controller = loader.getController();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
Thank you for any help !

I don't know how to fix your problem, however I can point you to another way to get a similar effect. If you need to use a line chart you could try this https://stackoverflow.com/a/14623439/4683264. However from your sample it looks like you could use a barchart, and use jewelsea's answer from the question you linked.

Related

What is blocking my UI thread in a JavaFX app?

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();
}
}

JavaFX ObservableList toTableview via Task Thread

I am building a multiscreen JavaFX app with data being pulled from a SQL database to ObservableLists and displayed in the interface via Tableview. Because of the multiscreen nature of the app, I am trying to initialize the data from the ObservableList to the Tableview via the controller. The SQL pull to ObservableList is done via a Task on a new thread. When I include the sqlCSEditTbl.itemsProperty().setValue((ObservableList) csSQLList) in the Task method nothing is displayed in the TableView when I run the program. If I place the code outside the Task method,the particular screen will not display. I don't know what I am missing here to be able to get the data to display on the particular screen. I've debugged the SQL to ObservableList and the data is being properly stored in the ObservableList. The problem is getting it from the ObservableList to the Tableview interface. Any help would be greatly appreciated. Thank you!
App Edit Screen Screenshot
SQLCalcScript Array Model
package model.calcs;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/**
* Created by jdsmith on 2/17/2016.
*/
public class SQLCalcScripts {
private final IntegerProperty calcScriptID;
private final IntegerProperty calcScriptIndex;
private final StringProperty calcScriptName;
private final StringProperty calcScriptServer;
private final StringProperty calcScriptApp;
private final StringProperty calcScriptGroup;
public SQLCalcScripts() {
this(null, null, null, null, null, null);
}
public SQLCalcScripts(Integer calcScriptID, Integer calcScriptIndex, String calcScriptName, String calcScriptServer, String calcScriptApp, String calcScriptGroup) {
this.calcScriptID = new SimpleIntegerProperty(calcScriptID);
this.calcScriptIndex = new SimpleIntegerProperty(calcScriptIndex);
this.calcScriptName = new SimpleStringProperty(calcScriptName);
this.calcScriptServer = new SimpleStringProperty(calcScriptServer);
this.calcScriptApp = new SimpleStringProperty(calcScriptApp);
this.calcScriptGroup = new SimpleStringProperty(calcScriptGroup);
this.calcScriptID.addListener((e) -> System.out.println("ID changed to " + this.calcScriptID.get()));
this.calcScriptIndex.addListener((e) -> System.out.println("ID changed to " + this.calcScriptIndex.get()));
this.calcScriptName.addListener((e) -> System.out.println("ID changed to " + this.calcScriptName.get()));
this.calcScriptServer.addListener((e) -> System.out.println("ID changed to " + this.calcScriptServer.get()));
this.calcScriptApp.addListener((e) -> System.out.println("ID changed to " + this.calcScriptApp.get()));
this.calcScriptGroup.addListener((e) -> System.out.println("ID changed to " + this.calcScriptGroup.get()));
}
// Getters and Setters for Calc Script
public Integer getCalcScriptID() {
return calcScriptID.get();
}
public void setCalcScriptID(Integer calcScriptID) {
this.calcScriptID.set(calcScriptID);
}
public IntegerProperty calcScriptIDProperty() {
return calcScriptID;
}
public Integer getCalcScriptIndex() {
return calcScriptIndex.get();
}
public void setCalcScriptIndex(Integer calcScriptIndex) {
this.calcScriptIndex.set(calcScriptIndex);
}
public IntegerProperty calcScriptIndexProperty() {
return calcScriptIndex;
}
public String getCalcScriptName() {
return calcScriptName.get();
}
public void setCalcScriptName(String calcScriptName) {
this.calcScriptName.set(calcScriptName);
}
public StringProperty calcScriptNameProperty() {
return calcScriptName;
}
public String getCalcScriptServer() {
return calcScriptServer.get();
}
public void setCalcScriptServer(String calcScriptServer) {
this.calcScriptServer.set(calcScriptServer);
}
public StringProperty calcScriptServerProperty() {
return calcScriptServer;
}
public String getCalcScriptApp() {
return calcScriptApp.get();
}
public void setCalcScriptApp(String calcScriptApp) {
this.calcScriptApp.set(calcScriptApp);
}
public StringProperty calcScriptAppProperty() {
return calcScriptApp;
}
public String getCalcScriptGroup() {
return calcScriptGroup.get();
}
public void setCalcScriptGroup(String calcScriptGroup) {
this.calcScriptGroup.set(calcScriptGroup);
}
public StringProperty calcScriptGroupProperty() {
return calcScriptGroup;
}
}
csEditInterface.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="1080.0" prefWidth="1920.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="essapp.csEditController">
<children>
<VBox alignment="TOP_CENTER" prefHeight="200.0" prefWidth="100.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<Label alignment="CENTER" contentDisplay="CENTER" text="Calculation Script Editor">
<font>
<Font name="System Bold" size="36.0" />
</font>
</Label>
<ScrollPane fitToHeight="true" fitToWidth="true" pannable="true" prefHeight="800.0" prefWidth="717.0">
<content>
<AnchorPane prefHeight="200.0" prefWidth="717.0">
<children>
<TableView editable="true" prefHeight="200.0" prefWidth="200.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<columns>
<TableColumn fx:id="csEditID" editable="false" prefWidth="100.0" sortable="false" text="ID" />
<TableColumn fx:id="csEditIndex" prefWidth="100.0" text="Index" />
<TableColumn fx:id="csEditName" prefWidth="250.0" sortable="false" text="Name" />
<TableColumn fx:id="csEditServer" editable="false" prefWidth="300.0" sortable="false" text="Server" />
<TableColumn fx:id="csEditApp" editable="false" prefWidth="250.0" sortable="false" text="Application" />
<TableColumn fx:id="csEditGroup" prefWidth="400.0" sortable="false" text="Calc Group" />
</columns>
</TableView>
</children>
</AnchorPane>
</content>
<VBox.margin>
<Insets left="50.0" right="50.0" top="50.0" />
</VBox.margin>
</ScrollPane>
<Button fx:id="csEditOkBtn" defaultButton="true" mnemonicParsing="false" onAction="#createTbl" prefHeight="25.0" prefWidth="151.0" text="Commit Changes">
<VBox.margin>
<Insets top="50.0" />
</VBox.margin>
</Button>
<Button fx:id="csEditExitBtn" cancelButton="true" mnemonicParsing="false" onAction="#goToCSInt" prefHeight="25.0" prefWidth="150.0" text="Cancel">
<VBox.margin>
<Insets top="25.0" />
</VBox.margin>
</Button>
</children>
</VBox>
</children>
</AnchorPane>
csEditController Code:
package essapp;
import com.sun.javafx.tk.Toolkit;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.*;
import javafx.scene.control.cell.ComboBoxTableCell;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.util.StringConverter;
import javafx.util.converter.DefaultStringConverter;
import model.calcs.*;
import java.net.URL;
import java.util.Map;
import java.util.ResourceBundle;
import static javafx.scene.input.KeyCode.T;
#SuppressWarnings("Duplicates")
public class csEditController implements Initializable, ControlledScreen {
ScreensController myController;
ObservableList<SQLCalcScripts> csEditCSList = FXCollections.observableArrayList();
ObservableList<CalcScripts> csEditEssSQL = FXCollections.observableArrayList();
/**
* Initializes the controller class.
*/
#Override
public void initialize(URL url, ResourceBundle rb) {
//Add SQL data to TableView
Task task = new Task<Void>() {
#Override
public Void call() throws Exception {
SQL2CalcScripts csSQLList = new SQL2CalcScripts();
csSQLList.sqlCalc("http://TEST-HYPRPT01:13080/aps/JAPI","GNLESB",csEditCSList);
sqlCSEditTbl.itemsProperty().setValue((ObservableList<SQLCalcScripts>) csSQLList);
return null;
}
};
// sqlCSEditTbl.itemsProperty().bind(task.valueProperty());
// sqlCSEditTbl.setItems(csEditCSList);
new Thread(task).start();
// Initialize table with columns
csEditID.setCellValueFactory(cellData -> cellData.getValue().calcScriptIDProperty().asObject());
csEditIndex.setCellValueFactory(cellData -> cellData.getValue().calcScriptIndexProperty().asObject());
csEditName.setCellValueFactory(cellData -> cellData.getValue().calcScriptNameProperty());
csEditServer.setCellValueFactory(cellData -> cellData.getValue().calcScriptServerProperty());
csEditApp.setCellValueFactory(cellData -> cellData.getValue().calcScriptAppProperty());
csEditGroup.setCellValueFactory(cellData -> cellData.getValue().calcScriptGroupProperty());
// TableView Calc Group ComboBox
ObservableList<String> csGroupList = FXCollections.observableArrayList("Supplement", "Wrapper", "Board Book");
csEditGroup.setCellFactory(ComboBoxTableCell.forTableColumn(new DefaultStringConverter(), csGroupList));
csEditGroup.setOnEditCommit(
(TableColumn.CellEditEvent<SQLCalcScripts,String> cb) -> {
((SQLCalcScripts) cb.getTableView().getItems().get(
cb.getTablePosition().getRow()
)).setCalcScriptGroup(cb.getNewValue());
}
);
}
public void setScreenParent(ScreensController screenParent){
myController = screenParent;
}
#FXML // ResourceBundle that was given to the FXMLLoader
private ResourceBundle resources;
#FXML // URL location of the FXML file that was given to the FXMLLoader
private URL location;
#FXML // fx:id="sqlCSEditTbl"
private TableView<SQLCalcScripts> sqlCSEditTbl; // Value injected by FXMLLoader
#FXML // fx:id="csEditGroup"
private TableColumn<SQLCalcScripts, String> csEditGroup; // Value injected by FXMLLoader
#FXML // fx:id="csEditExitBtn"
private Button csEditExitBtn; // Value injected by FXMLLoader
#FXML // fx:id="csEditServer"
private TableColumn<SQLCalcScripts, String> csEditServer; // Value injected by FXMLLoader
#FXML // fx:id="csEditID"
private TableColumn<SQLCalcScripts, Integer> csEditID; // Value injected by FXMLLoader
#FXML // fx:id="csEditIndex"
private TableColumn<SQLCalcScripts, Integer> csEditIndex; // Value injected by FXMLLoader
#FXML // fx:id="csEditOkBtn"
private Button csEditOkBtn; // Value injected by FXMLLoader
#FXML // fx:id="csEditApp"
private TableColumn<SQLCalcScripts, String> csEditApp; // Value injected by FXMLLoader
#FXML // fx:id="csEditName"
private TableColumn<SQLCalcScripts, String> csEditName; // Value injected by FXMLLoader
#FXML
private void goToCSInt(ActionEvent event){
myController.setScreen(ScreensFramework.calcScriptInterfaceID);
}
// #FXML
// private void createTbl(ActionEvent event) {
// sqlCSEditTbl.setItems(csEditCSList);
// }
}
ControlledScreen class Code:
package sample;
/**
* Created by jdsmith on 4/21/2016.
*/
public interface ControlledScreen {
//This method will allow the injection of the Parent ScreenPane
public void setScreenParent(ScreensController screenPage);
}
ScreensController class Code:
package essapp;
import java.util.HashMap;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.beans.property.DoubleProperty;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.layout.StackPane;
import javafx.util.Duration;
/**
* Created by jdsmith on 1/7/2016.
*/
public class ScreensController extends StackPane {
//Holds the screens to be displayed
private HashMap<String, Node> screens = new HashMap<>();
public ScreensController() {
super();
}
//Add the screen to the collection
public void addScreen(String name, Node screen) {
screens.put(name, screen);
}
//Returns the Node with the appropriate name
public Node getScreen(String name) {
return screens.get(name);
}
//Loads the fxml file, add the screen to the screens collection and
//finally injects the screenPane to the controller.
public boolean loadScreen(String name, String resource) {
try {
FXMLLoader myLoader = new FXMLLoader(getClass().getResource(resource));
Parent loadScreen = (Parent) myLoader.load();
ControlledScreen myScreenControler = ((ControlledScreen) myLoader.getController());
myScreenControler.setScreenParent(this);
addScreen(name, loadScreen);
return true;
} catch (Exception e) {
System.out.println(e.getMessage());
return false;
}
}
//This method tries to displayed the screen with a predefined name.
//First it makes sure the screen has been already loaded. Then if there is more than
//one screen the new screen is been added second, and then the current screen is removed.
// If there isn't any screen being displayed, the new screen is just added to the root.
public boolean setScreen(final String name) {
if (screens.get(name) != null) { //screen loaded
final DoubleProperty opacity = opacityProperty();
if (!getChildren().isEmpty()) { //if there is more than one screen
Timeline fade = new Timeline(
new KeyFrame(Duration.ZERO, new KeyValue(opacity, 1.0)),
new KeyFrame(new Duration(1000), new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent t) {
getChildren().remove(0); //remove the displayed screen
getChildren().add(0, screens.get(name)); //add the screen
Timeline fadeIn = new Timeline(
new KeyFrame(Duration.ZERO, new KeyValue(opacity, 0.0)),
new KeyFrame(new Duration(800), new KeyValue(opacity, 1.0)));
fadeIn.play();
}
}, new KeyValue(opacity, 0.0)));
fade.play();
} else {
setOpacity(0.0);
getChildren().add(screens.get(name)); //no one else been displayed, then just show
Timeline fadeIn = new Timeline(
new KeyFrame(Duration.ZERO, new KeyValue(opacity, 0.0)),
new KeyFrame(new Duration(2500), new KeyValue(opacity, 1.0)));
fadeIn.play();
}
return true;
} else {
System.out.println("screen hasn't been loaded!!! \n");
return false;
}
/*Node screenToRemove;
if(screens.get(name) != null){ //screen loaded
if(!getChildren().isEmpty()){ //if there is more than one screen
getChildren().add(0, screens.get(name)); //add the screen
screenToRemove = getChildren().get(1);
getChildren().remove(1); //remove the displayed screen
}else{
getChildren().add(screens.get(name)); //no one else been displayed, then just show
}
return true;
}else {
System.out.println("screen hasn't been loaded!!! \n");
return false;
}*/
}
//This method will remove the screen with the given name from the collection of screens
public boolean unloadScreen(String name) {
if (screens.remove(name) == null) {
System.out.println("Screen didn't exist");
return false;
} else {
return true;
}
}
}
ScreensFramework Main App Code:
package essapp;
import javafx.application.Application;
import javafx.geometry.Rectangle2D;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.stage.Screen;
import javafx.stage.Stage;
public class ScreensFramework extends Application {
public static String mainInterfaceID = "main";
public static String mainInterfaceFile = "mainInterface.fxml";
public static String msaInterfaceID = "msa";
public static String msaInterfaceFile = "msaInterface.fxml";
public static String creditRatingInterfaceID = "credit";
public static String creditRatingInterfaceFile = "creditRatingInterface.fxml";
public static String calcScriptEditID = "csEdit";
public static String calcScriptEditFile = "csEditInterface.fxml";
public static String calcScriptInterfaceID = "calc";
public static String calcScriptInterfaceFile = "calcScriptInterface.fxml";
public static String subVarInterfaceID = "subvar";
public static String subVarInterfaceFile = "subVarInterface.fxml";
#Override
public void start(Stage primaryStage) {
ScreensController mainContainer = new ScreensController();
mainContainer.loadScreen(ScreensFramework.mainInterfaceID, ScreensFramework.mainInterfaceFile);
mainContainer.loadScreen(ScreensFramework.calcScriptInterfaceID, ScreensFramework.calcScriptInterfaceFile);
mainContainer.loadScreen(ScreensFramework.calcScriptEditID, ScreensFramework.calcScriptEditFile);
mainContainer.loadScreen(ScreensFramework.subVarInterfaceID, ScreensFramework.subVarInterfaceFile);
mainContainer.loadScreen(ScreensFramework.msaInterfaceID, ScreensFramework.msaInterfaceFile);
mainContainer.loadScreen(ScreensFramework.creditRatingInterfaceID, ScreensFramework.creditRatingInterfaceFile);
mainContainer.setScreen(ScreensFramework.mainInterfaceID);
mainContainer.prefHeightProperty().bind(primaryStage.heightProperty());
mainContainer.prefWidthProperty().bind(primaryStage.widthProperty());
mainContainer.setCenterShape(true);
mainContainer.setScaleShape(true);
Group root = new Group();
root.getChildren().addAll(mainContainer);
Scene scene = new Scene(root);
Screen screen = Screen.getPrimary();
Rectangle2D bounds = screen.getVisualBounds();
primaryStage.setWidth(bounds.getWidth());
primaryStage.setHeight(bounds.getHeight());
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
When you call
sqlCSEditTbl.itemsProperty().setValue(...);
you are updating the UI (by updating the items displayed in the table). Like (almost?) all UI toolkits, JavaFX is single-threaded: updates to the UI can only happen on the FX Application Thread. Doing this in the Task, which is being executed on a background thread, will throw a IllegalStateException; so your call method never completes and you never see the update to the table.
The usual way to do this is to return the data from your call method:
Task<List<SQLCalcScripts>> task = new Task<List<SQLCalcScripts>>() {
#Override
public List<SQLCalcScripts> call() throws Exception {
List<SQLCalcScripts> data = /* get data.... */ ;
return data;
}
};
when the task completes, the value property is set to the value returned from the call method, so you can now do:
task.setOnSucceeded(e -> sqlCSEditTbl.getItems().setAll(task.getValue()));
It's probably a good idea to log any exceptions that occur with
task.setOnFailed(e -> task.getException().printStackTrace());
Then as before launch the task with
new Thread(task).start();

Vertical ProgressBar JavaFX

I have a StackPane with the size of (15px width, 400px height). I want to but a "Vertical ProgressBar" to that StackPane. What I was doing is to rotate the progressbar by 90 degree. However, the progressBar cannot fit in the stackpane with that rotation. It just shows as a small squared progressbar at the center of StackPane.
How can I fixed that?
Sample vertical progress bar.
class UpwardProgress {
private ProgressBar progressBar = new ProgressBar();
private Group progressHolder = new Group(progressBar);
public UpwardProgress(double width, double height) {
progressBar.setMinSize(StackPane.USE_PREF_SIZE, StackPane.USE_PREF_SIZE);
progressBar.setPrefSize(height, width);
progressBar.setMaxSize(StackPane.USE_PREF_SIZE, StackPane.USE_PREF_SIZE);
progressBar.getTransforms().setAll(
new Translate(0, height),
new Rotate(-90, 0, 0)
);
}
public ProgressBar getProgressBar() {
return progressBar;
}
public Group getProgressHolder() {
return progressHolder;
}
}
Used in a sample app.
import javafx.animation.*;
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.scene.*;
import javafx.scene.canvas.*;
import javafx.scene.control.*;
import javafx.scene.image.PixelWriter;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.transform.*;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.Random;
class UpwardProgress {
private ProgressBar progressBar = new ProgressBar();
private Group progressHolder = new Group(progressBar);
public UpwardProgress(double width, double height) {
progressBar.setMinSize(StackPane.USE_PREF_SIZE, StackPane.USE_PREF_SIZE);
progressBar.setPrefSize(height, width);
progressBar.setMaxSize(StackPane.USE_PREF_SIZE, StackPane.USE_PREF_SIZE);
progressBar.getTransforms().setAll(
new Translate(0, height),
new Rotate(-90, 0, 0)
);
}
public ProgressBar getProgressBar() {
return progressBar;
}
public Group getProgressHolder() {
return progressHolder;
}
}
public class StarCounter extends Application {
public static final Color INDIA_INK = Color.rgb(35, 39, 50);
private static final int CANVAS_SIZE = 400;
private static final int N_STARS = 1_000;
private final Canvas canvas = new Canvas(CANVAS_SIZE, CANVAS_SIZE);
private final Random random = new Random(42);
private final IntegerProperty visibleStars = new SimpleIntegerProperty(0);
private Timeline timeline;
#Override
public void start(final Stage stage) {
Group root = initProgress();
clearCanvas();
visibleStars.addListener((observable, oldValue, newValue) -> {
if (newValue.intValue() > oldValue.intValue()) {
addStars(newValue.intValue() - oldValue.intValue());
}
});
stage.setScene(
new Scene(
new HBox(canvas, root),
INDIA_INK
)
);
stage.show();
runSimulation();
stage.getScene().setOnMouseClicked(event -> {
resetSimulation();
runSimulation();
});
}
private Group initProgress() {
UpwardProgress upwardProgress = new UpwardProgress(15, 400);
ProgressIndicator bar = upwardProgress.getProgressBar();
bar.setStyle("-fx-base: skyblue; -fx-accent: gold;");
bar.progressProperty().bind(visibleStars.divide(N_STARS * 1.0));
return upwardProgress.getProgressHolder();
}
private void resetSimulation() {
clearCanvas();
if (timeline != null) {
timeline.stop();
timeline = null;
}
}
private void runSimulation() {
timeline = new Timeline(
new KeyFrame(
Duration.seconds(0),
new KeyValue(visibleStars, 0)
),
new KeyFrame(
Duration.seconds(10),
new KeyValue(visibleStars, N_STARS)
)
);
timeline.play();
}
private void clearCanvas() {
canvas.getGraphicsContext2D().setFill(INDIA_INK);
canvas.getGraphicsContext2D().fillRect(0, 0, CANVAS_SIZE, CANVAS_SIZE);
}
private void addStars(int nStarsToAdd) {
GraphicsContext context = canvas.getGraphicsContext2D();
PixelWriter writer = context.getPixelWriter();
for (int i = 0; i < nStarsToAdd; i++) {
writer.setColor(random.nextInt(CANVAS_SIZE), random.nextInt(CANVAS_SIZE), Color.GOLD);
}
}
public static void main(String[] args) {
launch(args);
}
}
There's another alternative to this, which is, to make your own custom vertical progress-bar. Sounds too much but isn't. The advantage of this approach is that this is more consistent in UI and more dynamically approachable. I used the above answer by #jewel but struggled with UI consistency and dynamic behavior of the progress-bar.
The approach being use a vbox for the progress-bar and inside it another vbox for bar.
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.VBox?>
<VBox xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.device.ui.VerticalProgressBarController">
<children>
<VBox fx:id="progress_bar" alignment="BOTTOM_CENTER" prefHeight="450.0" prefWidth="20.0" style="-fx-border-color: black; -fx-border-radius: 2 2 2 2;">
<children>
<VBox fx:id="bar" prefHeight="0.0" prefWidth="20.0" style="-fx-border-radius: 2 2 2 2;"/>
</children>
</VBox>
</children>
</VBox>
One can adjust the prefHeight of progress-bar and bar dynamically in controller or statically in .fxml file. Since here, only bar was the one I needed to adjust dynamically, so have set its prefHeight as 0 and adjust it appropriately in corresponding controller.
public class VerticalProgressBarController implements Initializable {
#FXML
VBox progress_bar;
#FXML
VBox bar;
private double progress_bar,fixed_capacity;
public void initialize(URL location, ResourceBundle resources) {
progressBarHeight = progress_bar.getPrefHeight();
bar.setMaxHeight(progressBarHeight);
// initial bar color
setGreenBar();
// set the max capacity of the progress bar
fixed_capacity = 100;
// pass in the proportion; here wanted to show 15 on a scale of 100
updateProgressBar(15 / fixed_capacity);
}
public void setGreenBar(){
bar.setStyle("-fx-background-color: green");
}
public void setYellowBar(){
bar.setStyle("-fx-background-color: yellow");
}
public void setRedBar(){
bar.setStyle("-fx-background-color: red");
}
public void updateProgressBar(double progress){
bar.setPrefHeight(progressBarHeight * progress);
if(progress <= .60){
setGreenBar();
} else if(progress > .60 &&
progress <= .75){
setYellowBar();
}else {
setRedBar();
}
}

Using FXML to Create ContextMenu within a Pane

I've got a working example for defining a ContextMenu on a Pane in JavaFX FXML, but am not sure it is optimal. Currently, only JavaFX standard controls (e.g. Button, TextField) define a property for specifying a popup ContextMenu. Yet I wanted to have a popup menu appear anywhere in a Pane, in my case a VBox.
I took the approach of extending VBox to support a context menu. It is a 'clunky' solution but works. Is there a better approach? Am I missing some fundamental concept?
Here is my solution...
FXML:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import custommenu.view.ContextMenuPane?>
<AnchorPane xmlns:fx="http://javafx.com/fxml" fx:controller="custommenu.controller.CustomMenuController">
<children>
<VBox fx:id="vbox" onContextMenuRequested="#showMenu"
onMousePressed="#hideMenu" prefHeight="200" prefWidth="200">
</VBox>
<ContextMenuPane>
<contextMenu>
<ContextMenu fx:id="menu">
<items>
<MenuItem text="add" onAction="#add" />
</items>
</ContextMenu>
</contextMenu>
</ContextMenuPane>
</children>
</AnchorPane>
CustomMenuPane...
package custommenu.view;
import javafx.scene.control.ContextMenu;
import javafx.scene.layout.Pane;
public class ContextMenuPane extends Pane {
private ContextMenu contextMenu;
public void setContextMenu(ContextMenu contextMenu) {
this.contextMenu = contextMenu;
}
public ContextMenu getContextMenu() {
return contextMenu;
}
}
Controller...
package custommenu.controller;
import javafx.fxml.FXML;
import javafx.scene.control.ContextMenu;
import javafx.scene.input.ContextMenuEvent;
import javafx.scene.layout.VBox;
public class CustomMenuController {
#FXML private VBox vbox;
#FXML private ContextMenu menu;
#FXML public void add() {
System.out.println("add");
}
#FXML
public void showMenu(ContextMenuEvent event) {
System.out.println("showMenu");
menu.show(vbox, event.getScreenX(), event.getScreenY());
event.consume();
}
#FXML public void hideMenu() {
menu.hide();
}
}
Main App...
package custommenu;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
public class CustomMenuApplication extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
Pane myPane = (Pane)FXMLLoader.load(getClass().getResource("/custommenu/custom_menu_main.fxml"));
Scene scene = new Scene(myPane);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Tried to do it within a Popup? Catch the MouseEvent check for MouseButton.SECONDARY and display your VBox in the Popup.
private void showContextMenu() // showContextMenu(MouseEvent event)
{
Popup popup = new Popup();
VBox content = new VBox();
Button b = new Button("Click Me!");
b.setOnAction(new EventHandler<ActionEvent>() { });
content.getChildren().addAll(b);
popup.getContent().add(content);
popup.setX(0); // or get mouse event x and y
popup.setY(0); // event.getY()
popup.setAutoHide(true);
popup.show(your popup owner);
}
In This example there is button which has context menu when click on left/right it will open the popup when you click on other area except button it will hide the popup context menu
public class Main extends Application{
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
Scene scene = new Scene(new Group(), 450, 250);
Button notification = new Button();
MenuItem item1 = new MenuItem("About");
item1.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent e) {
System.out.println("About");
}
});
MenuItem item2 = new MenuItem("Preferences");
item2.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent e) {
System.out.println("Preferences");
}
});
MenuItem item3 = new MenuItem("About");
item1.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent e) {
System.out.println("About");
}
});
MenuItem item4 = new MenuItem("Preferences");
item2.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent e) {
System.out.println("Preferences");
}
});
MenuItem item5 = new MenuItem("About");
item1.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent e) {
System.out.println("About");
}
});
MenuItem item6 = new MenuItem("Preferences");
item2.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent e) {
System.out.println("Preferences");
}
});
MenuItem item7 = new MenuItem("About");
item1.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent e) {
System.out.println("About");
}
});
MenuItem item8 = new MenuItem("Preferences");
item2.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent e) {
System.out.println("Preferences");
}
});
final ContextMenu contextMenu = new ContextMenu(item1, item2,item3, item4,item5, item6,item7, item8);
contextMenu.setMaxSize(50, 50);
contextMenu.setOnShowing(new EventHandler<WindowEvent>() {
public void handle(WindowEvent e) {
System.out.println("showing");
}
});
contextMenu.setOnShown(new EventHandler<WindowEvent>() {
public void handle(WindowEvent e) {
System.out.println("shown");
}
});
// contextMenu.hide();
notification.setContextMenu(contextMenu);
GridPane grid = new GridPane();
grid.setVgap(4);
grid.setHgap(10);
grid.setPadding(new Insets(5, 5, 5, 5));
grid.add(new Label("To: "), 0, 0);
grid.add(notification, 1, 0);
Group root = (Group) scene.getRoot();
root.getChildren().add(grid);
stage.setScene(scene);
stage.show();
notification.addEventHandler(MouseEvent.MOUSE_CLICKED, (MouseEvent me)->{
if(me.getButton()==MouseButton.PRIMARY ){
System.out.println("Mouse Left Pressed");
System.out.println(notification.getScaleX());
System.out.println(notification.getScaleY());
System.out.println(me.getScreenX());
System.out.println(me.getScreenY());
contextMenu.show(notification,me.getScreenX(),me.getScreenY());
}else{
contextMenu.hide();
}
});
}
}

How to perform functions on the stage in JavaFX's controller file

this question contains the code for my earlier question
Main class
#Override
public void start(Stage mainStage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("FXMLfile.fxml"));
Scene scene = new Scene(root);
scene.setFill(Color.TRANSPARENT);
stage.initStyle(StageStyle.TRANSPARENT);
stage.setScene(scene);
stage.show();
}
FXMLController class
#FXML
private void getAxisLoc(ActionEvent axis) {
Stage stage;
stage = (Stage) root.getScene().getWindow();
int locX;
locX = (int) stage.getX();
int locY;
locY = (int) stage.getY();
}
The exception is fired here:
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at sun.reflect.misc.MethodUtil.invoke(MethodUtil.java:279)
at javafx.fxml.FXMLLoader$ControllerMethodEventHandler.handle(FXMLLoader.java:1435)
... 48 more
Caused by: java.lang.NullPointerException
at myJavaFile.FXMLfileController.getAxisLoc(FXMLfileController.java:112)
... 58 more`
Blindly, I guess that the NullPointerExeption is fired here:
stage = (Stage) root.getScene().getWindow();
If so, make sure that you added fx:id="root" in the root pane tag .
example (FXML):
<BorderPane fx:id="root" xmlns:fx="http://javafx.com/fxml" fx:controller="YourController">
and reference it in your controller class:
#FXML
Parent root;
SSCCE
Sample.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 id="AnchorPane" fx:id="root" prefHeight="200" prefWidth="320" xmlns:fx="http://javafx.com/fxml" fx:controller="SampleController">
<children>
<Button layoutX="126" layoutY="90" text="Click Me!" onAction="#handleButtonAction" fx:id="button" />
<Label layoutX="126" layoutY="120" minHeight="16" minWidth="69" fx:id="label" />
</children>
</AnchorPane>
SampleController.java
public class SampleController implements Initializable {
#FXML
private Label label;
#FXML
private Pane root;
#FXML
private void handleButtonAction(ActionEvent event) {
Stage stage = (Stage) root.getScene().getWindow();
//you can use label instead of root.
//Stage stage= (Stage) label.getScence().getWindow();
stage.close();
}
#Override
public void initialize(URL url, ResourceBundle rb) { //TODO }
}
App.java
public class App extends Application {
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("Sample.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) { launch(args); }
}

Resources