JavaScript script in a FXML - javafx

I'm trying to run the following example from FXML reference:
This example consists in declaring a String variable in a JavaScript script and using it later in the FXML with the $ operator for displaying the String in a Label.
The problem is that when I run this example with Java 8u40, the Label is empty instead of showing the declared String.
JavaFxComponent.java:
public class JavaFxComponent extends VBox {
public JavaFxComponent() throws Exception {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/JavaFxComponent.fxml"));
fxmlLoader.setRoot(this);
fxmlLoader.load();
}
}
JavaFxComponent.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?language javascript?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<fx:root type="javafx.scene.layout.VBox" xmlns="http://javafx.com/javafx/8"
xmlns:fx="http://javafx.com/fxml">
<fx:script>
var myText = "This is the text of my label.";
</fx:script>
<Label text="$myText" />
</fx:root>
JavaFxTest:
public class JavaFxTest extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setScene(new Scene(new JavaFxComponent()));
primaryStage.show();
}
public static final void main(String[] args) {
launch(args);
}
}

It seems that "var myText" doesn't create a reference in the scope where '$' performs lookup. This is probably not the answer you were looking for, however I believe it will be useful to mention alternatives for those who stumble upon the same issue, at least until this is resolved or someone sheds more light on the matter.
<fx:define>
<String fx:id="myText" fx:value="This is the text of my label." />
</fx:define>
<Label text="$myText" />
<Label fx:id="myLabel" />
<fx:script>
myLabel.text = "This is the text of my label.";
</fx:script>
Note: for 1st method to work <?import java.lang.String?> needs to be imported.

Related

JavaFx: Context menu dynamic style

I have a problem with context menus. The items in the context menu can be styled, but it doesn't work dynamic. I mean for example at the initialize I add a styleclass to the MenuItem and by an event I remove it, but the style still remains there. How can it be solved?
Here is a simple example to play with it:
Controller.java:
public class Controller implements Initializable {
private static final String STYLED = "styled";
#FXML
private ListView<String> listView;
#FXML
private Button change;
#Override
public void initialize(URL location, ResourceBundle resources) {
ContextMenu cm = new ContextMenu();
MenuItem miAdd = new MenuItem("Add");
miAdd.setOnAction(event -> listView.getItems().add("Apple"));
MenuItem miRemove = new MenuItem("Remove");
miRemove.disableProperty().bind(listView.getSelectionModel().selectedItemProperty().isNull());
miRemove.setOnAction(event -> listView.getItems().remove(listView.getSelectionModel().getSelectedItem()));
cm.getItems().addAll(miAdd, miRemove);
listView.setContextMenu(cm);
miRemove.getStyleClass().add(STYLED);
change.setOnAction(event -> {
if (!miRemove.getStyleClass().contains(STYLED)) {
miRemove.getStyleClass().add(STYLED);
} else {
miRemove.getStyleClass().remove(STYLED);
}
});
}
}
View.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.VBox?>
<AnchorPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="textField.Controller"
stylesheets="css/layout.css">
<VBox>
<Button fx:id="change" text="ChangeStyle"/>
<ListView fx:id="listView"/>
</VBox>
</AnchorPane>
layout.css:
.styled .text {
-fx-strikethrough: true;
}
.styled {
-fx-background-color: gray;
}
How can I manage to add/remove style to my MenuItem ?

Slider above the x axis in a chart?

I am trying to implement a Slider that has an XYChart below and is supposed to slider along the values of the x-axis. New values for the y-axis are to be dynamilcally added, so the chart may grow vertically. That's why I decided, it has to be inside a ScrollPane.
Now the question is:
How can I reliably position a slider above and with the same length of the x-axis?
I have tried to bind properties concerning the width and position of the Slider and the NumberAxis in fxml and java. See minimal (not) working (as desired) example below:
MainView.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import javafx.scene.chart.*?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.control.Slider?>
<VBox fx:id="timeBox" prefHeight="600" prefWidth="800" maxHeight="600" maxWidth="800"
xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Slider fx:id="timeSlider" blockIncrement="30.0" max="360.0" />
<HBox fx:id="scrollBox" fitwidth="${timeBox.width}" VBox.vgrow="ALWAYS">
<children>
<ScrollPane fx:id="timeScrollPane" fitToHeight="true"
fitToWidth="true" fitwidth="${scrollBox.width}" HBox.hgrow="SOMETIMES">
<content>
<LineChart fx:id="timeChart">
<xAxis>
<NumberAxis fx:id="timeAxis" minorTickCount="1"
minorTickLength="1.0" side="TOP" tickLabelGap="30.0"
tickLength="1.0" tickUnit="1.0" upperBound="360.0" />
</xAxis>
<yAxis>
<CategoryAxis fx:id="appearanceAxis" prefWidth="160.0"
side="LEFT" />
</yAxis>
</LineChart>
</content>
</ScrollPane>
</children>
</HBox>
</children>
</VBox>
MainViewController
public class MainViewController implements Initializable {
#FXML private VBox timeBox;
#FXML private HBox sliderBox;
#FXML private Slider timeSlider;
#FXML private HBox scrollBox;
#FXML private ScrollPane timeScrollPane;
#FXML private NumberAxis timeAxis;
#FXML private CategoryAxis appearanceAxis;
#FXML private LineChart<Number, String> timeChart;
#Override
public void initialize(URL location, ResourceBundle resources) {
// idea: set the same x value, but it does not change anything
timeSlider.setLayoutX(timeAxis.getLayoutX());
// idea: bind the width, but it stays longer
timeSlider.prefWidthProperty().bind(timeAxis.widthProperty());
}
}
Main.java
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import javafx.scene.Parent;
import javafx.scene.Scene;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("MainView.fxml"));
Parent root = loader.load();
Scene scene = new Scene(root,800,600);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
I know, what I have tried so far is not really much, but I just don't have an idea how to fix that.
The chart is meant to show the start-to-stop time of a video file on the x-axis and annotations with their appearance intervals on the y-axis.
Thanks for reading this…

JavaFX: Binding expression in FXML document

In JavaFX 8 is it still possible to bind a control property directly in FXML to a property of the controller?
Something like:
<?xml version="1.0" encoding="UTF-8"?>
<?import java.net.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>
<GridPane xmlns:fx="http://javafx.com/fxml"
fx:controller="application.PaneController" minWidth="200">
<Label id="counterLabel" text="${controller.counter}" />
<Button translateX="50" text="Subtract 1"
onAction="#handleStartButtonAction" />
</GridPane>
The above seems not to work.
Yes this is possible assuming you implement the correct methods in the controller:
public class PaneController {
private final IntegerProperty counter = new SimpleIntegerProperty(100);
public IntegerProperty counterProperty() {
return counter;
}
// this is also required
public int getCounter() {
return counter.get();
}
public void handleStartButtonAction() {
counter.set(counter.get() - 1);
}
}
Also I'm not sure if placing both Nodes in the same cell is the best decision...

JAVAFX - Change specific Pane only using 1 window

Can you Help me how to change the Spesific pane in 1 scene.
So when i want to click the Menu A. The Content will change to content A.
And when i click the menu B. The Content will be change to Content B
i try with the 2 FXML and using normal method like load screen A an screen B
But the result only change the window. i want to change the content with 1 window only.
Is there any suggestion how to change the specific pane in 1 window?
As an option.
Make FXML and controller for any "Content" and when the some button is clicked to delete the old "Content" and upload new.
Working example below (edited according to James_D comment):
Main.java
public class Main extends Application {
Parent root;
Stage stage;
#Override
public void start(Stage primaryStage) {
try {
root = FXMLLoader.load(getClass().getResource("Main.fxml"));
stage = primaryStage;
stage.setTitle("Stage");
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
Main.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>
<BorderPane fx:id="mainPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.Controller">
<left>
<ToolBar orientation="VERTICAL" BorderPane.alignment="CENTER">
<items>
<Button mnemonicParsing="false" onAction="#onBtnAClick" text="A" />
<Button mnemonicParsing="false" onAction="#onBtnBClick" text="B" />
<Button mnemonicParsing="false" onAction="#onBtnCClick" text="C" />
</items>
</ToolBar>
</left>
</BorderPane>
Controller.java
import javafx.fxml.FXML;
import javafx.scene.layout.BorderPane;
public class Controller {
#FXML
BorderPane mainPane;
#FXML
public void onBtnAClick(){
ContentA contentA = new ContentA();
mainPane.setCenter(contentA);
}
#FXML
public void onBtnBClick(){
ContentB contentB = new ContentB();
mainPane.setCenter(contentB);
}
#FXML
public void onBtnCClick(){
ContentC contentC = new ContentC();
mainPane.setCenter(contentC);
}
}
And some sample of Content:
ContentA.java
public class ContentA extends AnchorPane{
public ContentA(){
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("ContentA.fxml"));
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
try {
fxmlLoader.load();
} catch (IOException e) {
e.printStackTrace();
}
}
}
ContentA.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>
<fx:root prefHeight="200.0" prefWidth="300.0" type="AnchorPane" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<children>
<ListView layoutX="50.0" layoutY="-26.0" prefHeight="200.0" prefWidth="200.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
</children>
</fx:root>
try to use this, its easy
Parent root = null;
try {
root=FXMLLoader.load(getClass().getResource("your_FXML_File.fxml"));
} catch (Exception e) {
}
borderpane.setCenter(root);

Why isnt my JavaFX Progress Indicator not updating?

I have the following layout in my JavaFX application,
StackPane
- BorderPane
- Text (on Top)
- Vbox (on Center)
- ProgressIndicator
- Label
- BorderPane
- Scroll Pane (with Tile Pane inside) (Center of Border Pane)
- Button (Bottom of Border)
- BorderPane
- Vbox (on Center)
- Progress Indicator
- Label
- Button ( Bottom)
When showing the first Border Pane, the Progress Indicator works perfectly. I set it up using the following code,
progress.progressProperty().bind(iconLoadTask.progressProperty());
progresspane.visibleProperty().bind(iconLoadTask.runningProperty());
When the iconLoadTask completes, the progress pane becomes invisible and the next pane is shown correctly.
From the next BorderPane (which has the Scroll pane), I show the last border pane (with a progress indicator - indeterminate). This progress indicator doesnt animate at all.
Edit:
As suggested, I have tried reproducing this issue,
I have the following controller file. Here you can see two progress indicators (progress and stopProfInd). progress is updated from the task appLoadTask. This is a finite task and I can update progress from the task. This works fine.
I have another indicator called stopProfInd. This is set indeterminate from Scene Builder. Here you can see that I make that pane visible. I expect the indicator to animate, but it doesn't.
public class AppController
implements Initializable {
#FXML
private StackPane stackpaneapps;
#FXML
private BorderPane progresspane;
#FXML
private ProgressIndicator progress;
#FXML
private ProgressIndicator stopProfInd;
#FXML
private BorderPane profilingpane;
#FXML
private Label devicename;
private ObservableValue<Number> profProperty;
// Reference to the main application.
private Main mainApp;
private AppIconsTask appLoadTask;
private ProfilingTask prTask;
private Queue<MyVbox> clickedAppQueue;
#Override // This method is called by the FXMLLoader when initialization is complete
public void initialize(URL fxmlFileLocation, ResourceBundle resources) {
clickedAppQueue = new LinkedList<MyVbox>();
}
/**
* Is called by the main application to give a reference back to itself.
*
* #param mainApp
*/
public void setMainApp(Main mainApp) {
this.mainApp = mainApp;
}
/**
* Is called by the main application to give a reference back to itself.
*
* #param task
*
*/
public void setAppLoadTask(AppIconsTask task) {
this.appLoadTask = task;
progress.progressProperty().bind(appLoadTask.progressProperty());
progresspane.visibleProperty().bind(appLoadTask.runningProperty());
appLoadTask.setOnSucceeded(t -> drawAppIcons(appLoadTask.getApps()));
profilingpane.setVisible(false);
}
void drawAppIcons(ObservableList<App> apps){
progress.setVisible(false);
profilingpane.setVisible(true);
prTask = new ProfilingTask();
stopProfInd.progressProperty().bind(prTask.progressProperty());
new Thread(prTask).start();
}
}
Any clues?
FXML:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.text.*?>
<?import javafx.scene.paint.*?>
<?import javafx.scene.effect.*?>
<?import javafx.scene.*?>
<?import javafx.scene.image.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<AnchorPane prefHeight="263.0" prefWidth="304.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.gamebench.ios.controller.AppController">
<children>
<StackPane fx:id="stackpaneapps" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="10.0">
<children>
<BorderPane fx:id="profilingpane" prefHeight="200.0" prefWidth="200.0">
<center>
<VBox alignment="CENTER" prefHeight="200.0" prefWidth="100.0" BorderPane.alignment="CENTER">
<children>
<ProgressIndicator fx:id="stopProfInd" />
</children>
</VBox>
</center>
</BorderPane>
<BorderPane fx:id="progresspane" prefHeight="200.0" prefWidth="200.0">
<center>
<VBox alignment="CENTER" prefHeight="200.0" prefWidth="100.0" BorderPane.alignment="CENTER">
<children>
<ProgressIndicator fx:id="progress" maxHeight="100.0" maxWidth="100.0" prefHeight="68.0" prefWidth="77.0" />
</children>
</VBox>
</center>
</BorderPane>
</children>
</StackPane>
</children>
</AnchorPane>
Adding Task implementation as requested. The following is for appIconsTask which works well.
protected ObservableList<App> call() throws Exception {
String deviceId = "blah";
/* We will start before we get the list of installed apps */
updateProgress(0.0f , 100);
// Get list of installed apps
App[] installedApps = getAppsList(deviceId , false);
appList = FXCollections.observableList(new ArrayList<App>());
int numApps = installedApps.length;
double progress = 1.0f;
for(App app: installedApps){
byte[] pngData;
pngData = comm.getAppIcon(deviceId, app.getBundleId());
app.setAppIcon(pngData);
appList.add(app);
updateProgress((progress/numApps)*100, 100);
progress += 1.0f;
}
return appList;
}
Task implementation for the next progress indicator,
protected Void call() throws Exception {
double f = 1.0;
while(!getStopProf()){
Thread.sleep(30);
}
return null;
}
Reproduction of the issue here,
https://github.com/h-karthik/BugReproStackOverflow
I think you are running into this bug which, at the time of writing, is fixed in the latest GA release (JDK 8u20). The easiest fix, if it's possible for you to do, is to upgrade to and require that version.
If you can't do that, the next most intuitive option is to avoid use of setVisible(...) and just add the progress indicator to the scene graph when you need it. You can define elements in FXML which are not contained in the scene graph by using <fx:define>. Inject this as usual, and then just add the element to the scene graph as you need it. (You can remove it in similar fashion, if you need.)
Your FXML file becomes:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.text.*?>
<?import javafx.scene.paint.*?>
<?import javafx.scene.effect.*?>
<?import javafx.scene.*?>
<?import javafx.scene.image.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<AnchorPane prefHeight="263.0" prefWidth="304.0"
xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.stackoverflow.repro.controller.AppController">
<children>
<StackPane fx:id="stackpaneapps" AnchorPane.bottomAnchor="0.0"
AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="5.0"
AnchorPane.topAnchor="10.0">
<children>
<BorderPane fx:id="progresspane" prefHeight="200.0"
prefWidth="200.0">
<center>
<VBox alignment="CENTER" prefHeight="200.0" prefWidth="100.0"
BorderPane.alignment="CENTER">
<children>
<ProgressIndicator fx:id="progress" maxHeight="100.0"
maxWidth="100.0" prefHeight="68.0" prefWidth="77.0" />
</children>
</VBox>
</center>
</BorderPane>
</children>
</StackPane>
</children>
<fx:define>
<BorderPane prefHeight="200.0" prefWidth="200.0" fx:id="profilingpane">
<center>
<VBox alignment="CENTER" prefHeight="200.0" prefWidth="100.0"
BorderPane.alignment="CENTER">
<children>
<ProgressIndicator fx:id="stopProfInd" />
</children>
</VBox>
</center>
</BorderPane>
</fx:define>
</AnchorPane>
and the controller becomes
package com.stackoverflow.repro.controller;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import com.stackoverflow.repro.Main;
import com.stackoverflow.repro.tasks.AppIconsTask;
import com.stackoverflow.repro.tasks.ProfilingTask;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.*;
public class AppController
implements Initializable {
#FXML
private StackPane stackpaneapps;
#FXML
private BorderPane progresspane;
#FXML
private ProgressIndicator progress;
#FXML
private ProgressIndicator stopProfInd;
#FXML
private BorderPane profilingpane;
// Reference to the main application.
private Main mainApp;
private AppIconsTask appLoadTask;
private ProfilingTask prTask;
#Override // This method is called by the FXMLLoader when initialization is complete
public void initialize(URL fxmlFileLocation, ResourceBundle resources) {
}
/**
* Is called by the main application to give a reference back to itself.
*
* #param mainApp
*/
public void setMainApp(Main mainApp) {
this.mainApp = mainApp;
}
/**
* Is called by the main application to give a reference back to itself.
*
* #param task
*
*/
public void setAppLoadTask(AppIconsTask task) {
this.appLoadTask = task;
progress.progressProperty().bind(appLoadTask.progressProperty());
progresspane.visibleProperty().bind(appLoadTask.runningProperty());
// profilingpane.setVisible(false);
appLoadTask.setOnSucceeded(t -> drawAppIcons());
}
void drawAppIcons(){
// profilingpane.setVisible(true);
stackpaneapps.getChildren().add(profilingpane);
prTask = new ProfilingTask();
new Thread(prTask).start();
}
}
Your workaround with the opacity works as well, but this just feels like a more natural approach. There is a slight difference in functionality, which is not evident from everything being in a StackPane. With both setVisible (when it works correctly) and the setOpacity(...) workaround, the progress indicator will take up space in the layout. With adding and removing it when it is required, it will not take up space in the layout when it is not present. So the choice of approach probably depends on how you want it to behave in terms of layout.
Just a couple of other comments about your code:
If you make the thread running the prTask a daemon thread, it will not prevent the JVM from exiting when you close the last window. Obviously you may have other mechanisms for shutting this down in your real app, but it can be a useful trick (and is less annoying if you're testing this from Eclipse...):
Thread prThread = new Thread(prTask);
prThread.setDaemon(true);
prThread.start();
Also, your threading code in the profiling task looks like it is not quite correct. The stopProf field is almost certainly changed on a different thread to the one on which it is read. This means there is no guarantee of liveness of the field (there can be an arbitrary delay, possible indefinite, between it being changed in one thread and that change being visible in another). You should do one of the following:
Mark the field as volatile, or
Mark the getStopProf() and setStopProf(...) methods as synchronized, or
Replace the boolean field with an AtomicBoolean.
This third option is the one I prefer (favor high-level APIs over low-level primitives):
package com.stackoverflow.repro.tasks;
import java.util.concurrent.atomic.AtomicBoolean;
import javafx.concurrent.Task;
/**
* Created by karthik on 17/02/15.
*/
public class ProfilingTask extends Task<Void> {
AtomicBoolean stopProf;
#Override
protected Void call() throws Exception {
double f = 1.0;
while(!getStopProf()){
Thread.sleep(30);
}
return null;
}
public boolean getStopProf(){
return stopProf.get();
}
public void setStopProf(boolean stop){
stopProf.set(stop);
}
}
So, it turns out that it might be my ignorance or possibly a bug.
In my code instead of the following line
profilingpane.setVisible(true);
I use the following and the problem goes away. This is more of a hack to solve the problem.
profilingpane.setOpacity(0.0);
And then when I want the pane to show up,
profilingpane.setOpacity(1.0);

Resources