JavaFX deployment - images get lost - java

I finished a small private project in JavaFX with e(fx)clipse. Now I would like to export it as a runnable jar file. Everything works fine, except the fact that the pane and button background images get lost. The paths to these images were defined in a seperate CSS file. Other definitions from this file are implemented well, only the images are missing.
Any idea what could be the reason for this? Or is there even a better way to publish a finished java project?

This seems to be a common problem. I struggled with it myself. Take a look at how I reference the css and the image in the css.
Here's a solution that works in the development environment, in Scene Builder and in a packaged JAR.
The folder structure:
Main.java:
package application;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
FXMLLoader loader = new FXMLLoader(Main.class.getResource("view/RootLayout.fxml"));
AnchorPane rootLayout = (AnchorPane) loader.load();
Scene scene = new Scene(rootLayout, 400, 400);
scene.getStylesheets().add(getClass().getResource("css/application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
RootLayout.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.view.RootLayoutController">
<children>
<Pane layoutX="0.0" layoutY="0.0" prefHeight="200.0" prefWidth="200.0">
<children>
<Button fx:id="sunButton" layoutX="74.0" layoutY="88.0" mnemonicParsing="false" onAction="#handleSunButtonClick" styleClass="sun-button" stylesheets="#../css/toolbar.css" text="Button" />
</children>
</Pane>
</children>
</AnchorPane>
RootLayoutController.java:
package application.view;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
public class RootLayoutController {
#FXML
Button sunButton;
#FXML
public void handleSunButtonClick() {
System.out.println( "Button clicked");
}
}
toolbar.css:
.sun-button {
-fx-graphic: url('./icons/sun.png');
}
application.css:
.root {
-fx-background-color:lightgray;
}
sun.png:
This works in both the development environment and when you package the JAR (choose "Extract required libraries into generated JAR" in Eclipse).
Screenshot (just a button with an icon loaded via css)

Related

How do I change the properties of an object in a fxml file

I was trying to load a fxml file and then change the properties of its objects (like the text of a button). I know how to load the fxml but I can't seem to figure out how to change the properties (like the text of a button) of objects
I know that you can somehow interact with objects by calling their Id but I dont know how to do that.
So how do you interact with objects in a fxml file.
Code:
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.net.URL;
public class App extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(new URL("file:///PathToFXML"));
VBox vbox = loader.<VBox>load();
Scene scene = new Scene(vbox);
primaryStage.setScene(scene);
primaryStage.show();
}
}
Fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Button?>
<VBox xmlns:fx="http://javafx.com/fxml">
<Button fx:id="button" text="Button"/>
</VBox>

Trouble applying CSS to programatically created Pane on MouseEvent

I have a stack pane that when the mouse enters, I create a pane, stick some text in it, and display it near the mouse.
StatisticsController.java
stackPane.setOnMouseEntered(event -> {
Pane pane = new Pane();
Text text = new Text("Example Text");
//Add the text to the pane and set it near the mouse
pane.getChildren().add(text);
pane.setLayoutY(event.getSceneY() - 50);
pane.setLayoutX(event.getSceneX() - 100);
//add the style class to give it a blueBG background
pane.getStyleClass().add("blueBG");
// add it too our root
getRoot().getChildren().add(pane);
});
As you can see below when the Pane and text appear when I roll over the stackPane (the black circle and question mark in the image).
However it doesn't have the blue background I'm looking for.
What I find strange is if instead of adding it to the root and I add it to an existing Vbox, it styles correctly (Here I roll over the same stackPane):
So:
//Styles Correctly
getInfoBox().getChildren().add(pane);
// Adds but does not style
getRoot().getChildren().add(pane);
Some things that may be worth noting:
1)getInfoBox() is a static getter. I have multiple controllers which extend a MasterController which has static instance variables to things all controllers would want to be able to access - like the infoBox, to display information.
2)The root is a BorderPane made in the Main class:
Main.java
//Create the root
BorderPane root = new BorderPane();
// I've cut it out to save space but this repeats 4 times to set
//an .fxml file for the top, left, right and bottom of the borderPane ///
FXMLLoader headerLoader = new FXMLLoader(getClass().getResource("/view/header.fxml"));
Parent headerRoot = headerLoader.load();
root.setTop(headerRoot);
//------------------------------------------//
//Set the static reference in the MasterController
MasterController.setRoot(root);
Scene scene = new Scene(root,1366,768);
primaryStage.setScene(scene);
primaryStage.show();
3) The css files are all declared in the .fxml files using Scene Builder. The top, bottom, left, right .fxml files all have the same css (main.css). So does the statistic.fxml which is loaded on button click into the center (you can see in the image)
Suggestion: Could it be because the CSS is not defined for the Scene or BorderPane itself, it only applies to nodes added into the borderPane sections? If so how would I go about letting the enter Scene/Stage use the same css and would that negate adding in the css to each .fxml?
Edit:
When adding this code to the main to apply the CSS to the scene:
Scene scene = new Scene(root,1366,768);
scene.getStylesheets().add(getClass().getResource("/css/main.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
I get this:
So the CSS for the text is now working, but not for the Pane? I have no idea why this might happen.
// ---- REPRODUCTION -- //
Main.java
package sample;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
BorderPane bp = new BorderPane();
MasterController.setRoot(bp);
Parent left = FXMLLoader.load(getClass().getResource("left.fxml"));
Parent right = FXMLLoader.load(getClass().getResource("right.fxml"));
bp.setLeft(left);
bp.setRight(right);
Scene scene = new Scene(bp, 600, 400);
scene.getStylesheets().add(getClass().getResource("main.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
MasterController.java
package sample;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
public class MasterController {
private static VBox rightBox;
private static BorderPane root;
public static VBox getRightBox() {
return rightBox;
}
public static void setRightBox(VBox rightBox) {
MasterController.rightBox = rightBox;
}
public static BorderPane getRoot() {
return root;
}
public static void setRoot(BorderPane root) {
MasterController.root = root;
}
}
LeftController.java
package sample;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import java.io.IOException;
public class LeftController extends MasterController {
#FXML
public void loadCenter(ActionEvent event) {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("center.fxml"));
Parent center = loader.load();
getRoot().setCenter(center);
} catch (IOException e){
e.printStackTrace();
}
}
}
CenterController.java
package sample;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
public class CenterController extends MasterController {
#FXML
private VBox center;
public void initialize() {
Platform.runLater(this::build);
}
public void build() {
center.getChildren().add(new Text("loaded"));
StackPane stackPane = new StackPane();
Text text = new Text("ROLL OVER");
stackPane.getChildren().add(text);
center.getChildren().add(stackPane);
stackPane.setOnMouseEntered(event -> {
getRightBox().getChildren().clear();
Pane pane1 = new Pane();
Text exampleText1 = new Text("Example Text");
pane1.getStyleClass().add("blueBG");
Pane pane2 = new Pane();
Text exampleText2 = new Text("Example Text");
pane2.getStyleClass().add("blueBG");
pane1.getChildren().add(exampleText1);
pane2.getChildren().add(exampleText2);
pane1.setLayoutY(event.getSceneY() + 40);
pane1.setLayoutX(event.getSceneX() - 40);
getRoot().getChildren().add(pane1);
getRightBox().getChildren().add(pane2);
});
}
}
RightController.java
package sample;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.layout.VBox;
public class RightController extends MasterController {
#FXML
private VBox rightBox;
public void initialize() {
setRightBox(rightBox);
}
}
left.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.*?>
<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="200.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.LeftController">
<children>
<Button mnemonicParsing="false" onAction="#loadCenter" prefHeight="38.0" prefWidth="200.0" text="Click me to load center" />
</children>
</VBox>
center.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.text.*?>
<?import javafx.scene.layout.*?>
<VBox fx:id="center" alignment="TOP_CENTER" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="200.0" stylesheets="#main.css" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.CenterController">
<children>
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="Center" />
</children>
</VBox>
right.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.text.*?>
<?import javafx.scene.layout.*?>
<VBox fx:id="rightBox" alignment="TOP_CENTER" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="200.0" stylesheets="#main.css" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.RightController">
<children>
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="Right Box" />
</children>
</VBox>
main.css
.blueBG {
-fx-background-color: aqua;
}
You should get the following result. You can see the pane is being styled correctly when added to the right, but not when added too the root.
BorderPane only applies layout to the center, left, right, top and bottom children. It's however the parent of a layout (or the scene in case of a root) that sets the size of a Region during a layout pass (or not, like in this case).
The result is that the style is applied to the Pane, but the size of the Pane remains 0. You can verify this by adding
pane1.resize(100, 100);
to the event handler to set the size of the pane.
You need to use a different kind of layout as parent for pane1's size to become non-empty or resize it yourself.

JavaFX Stage shows always empty scene

Using:
IntelliJ IDEA community ide.
Java JDK 9.0.1
Scene builder 9.0.1
I always get empty scene.
Here is the fxml file code:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.GridPane?>
<GridPane alignment="center" hgap="10" vgap="10" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/9.0.1" fx:controller="view.Controller">
<children>
<Button mnemonicParsing="false" text="Button" />
</children>
</GridPane>
Here is the main class code:
package view;
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("Hello World");
primaryStage.setScene(new Scene(root, 600, 400));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Changing sceenes with IntelliJ 2018.1 and Scene Builder 9.0.1

I have created a new JavaFx project.
In the project I have a Main class, a Controller class and a design file.
When I click on "New products" I want to load a new fxml file named new_product.fxml.
However I get a error when I click the "New product" button:
Caused by: java.lang.NullPointerException
at sample.Main.changeScene(Main.java:33)
at sample.Controller.buttonNewProductOnMouseClicked(Controller.java:21)
In Main.java line 33 is the primaryStage. Looks like my controller can't access it?
package sample;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.io.IOException;
public class Main extends Application {
Stage primaryStage;
#Override
public void start(Stage primaryStage) throws Exception{
this.primaryStage = primaryStage;
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
Scene primaryScene = new Scene(root, 300, 275);
primaryStage.setScene(primaryScene);
primaryStage.show();
}
public void changeScene(String fxml){
Parent pane = null;
try {
pane = FXMLLoader.load(getClass().getResource(fxml));
} catch (IOException e) {
e.printStackTrace();
}
Scene scene = new Scene( pane );
primaryStage.setScene(scene);
}
public static void main(String[] args) {
launch(args);
}
}
My design file
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<BorderPane xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
<top>
<HBox prefHeight="100.0" prefWidth="200.0" BorderPane.alignment="CENTER">
<children>
<Button fx:id="buttonProducts" mnemonicParsing="false" onMouseClicked="#buttonProductsOnMouseClicked" text="Products" />
<Button fx:id="buttonNewProduct" mnemonicParsing="false" onMouseClicked="#buttonNewProductOnMouseClicked" text="New products">
<HBox.margin>
<Insets left="5.0" />
</HBox.margin></Button>
</children>
</HBox>
</top>
<center>
<Label text="Label" BorderPane.alignment="CENTER" />
</center>
</BorderPane>
The Controller class for the design file
package sample;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
public class Controller {
#FXML
private Button buttonProducts;
#FXML
private Button buttonNewProduct;
public void buttonProductsOnMouseClicked(javafx.scene.input.MouseEvent mouseEvent) {
buttonProducts.setText("You have clicked on me!");
}
public void buttonNewProductOnMouseClicked(javafx.scene.input.MouseEvent mouseEvent) {
buttonNewProduct.setText("You have clicked on me!");
Main mainclass = new Main();
mainclass.changeScene("new_product/new_product.fxml");
}
}

How do you add nodes to an object that is not the main parent (javafx)?

I could not find anything on the topic anywhere i looked. I would like to add a rectangle to my AnchorPane (anchorPaneOne) that is inside my ScrollPane (scrollPane) but whatever i see to do i keep getting errors.
Here is my code:
Main Class:
package application;
import javafx.application.Application;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.AnchorPane;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class Main extends Application {
#FXML
ScrollPane scrollPane;
#FXML
AnchorPane main;
#FXML
AnchorPane anchorPaneOne;
public void start(Stage primaryStage) {
try {
Parent root = FXMLLoader.load(getClass().getResource("/fxml/Main.fxml"));
Scene scene = new Scene(root,600,400);
primaryStage.setScene(scene);
primaryStage.setTitle("Stack Overflow Example");
primaryStage.show();
Rectangle r = new Rectangle();
//It will not let me do anchorPaneOne.getChildren().add(r);
} 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.*?>
<AnchorPane fx:id="main" 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">
<children>
<ScrollPane fx:id="scrollPane" prefHeight="400.0" prefWidth="600.0">
<content>
<AnchorPane fx:id="anchorPaneOne" minHeight="0.0" minWidth="0.0" prefHeight="800.0" prefWidth="585.0" />
</content>
</ScrollPane>
</children>
</AnchorPane>
Any assistance would be appreciated.
Thanks
The loader needs to know where to inject the instances to - without the field anchorPaneOne can't be instantiated and remains null. That's done by the controller property which must be set before actually loading the ui:
// create a loader
FXMLLoader loader = new FXMLLoader(getClass().getResource(resource));
// set this instance as its controller
loader.setController(this);
// load the ui
Parent root = loader.load();
Scene scene = new Scene(root, 600, 400);
primaryStage.setScene(scene);
primaryStage.setTitle("Stack Overflow Example");
primaryStage.show();
Rectangle r = new Rectangle(100, 100);
// now the field is instantiated and can be accessed without NPE
anchorPaneOne.getChildren().add(r);

Categories