I am new to JavafX. I wanted to change the CSS file of my first GUI through the second one.
I have the following code:
Main1.java
package javafxapplication3.a;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main1 extends Application{
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
Parent root = null;
try {
root = FXMLLoader.load(getClass().getResource("/main1.fxml"));
} catch (Exception e) {
}
String css = Main1.class.getResource("/main1.css").toExternalForm();
Scene scene = new Scene(root, 400, 400);
scene.getStylesheets().clear();
scene.getStylesheets().add(css);
primaryStage.setResizable(false);
primaryStage.setScene(scene);
primaryStage.setTitle("JCal");
primaryStage.show();
}
}
Main1Controller.java
package javafxapplication3.a;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.stage.Stage;
public class Main1Controller {
#FXML
private Button button1;
public void initialize() {
button1.setOnAction(value -> {
Stage primaryStage = new Stage();
Parent root = null;
try {
root = FXMLLoader.load(getClass().getResource("/main2.fxml"));
} catch (Exception e) {
}
Scene scene = new Scene(root, 400, 400);
primaryStage.setResizable(false);
primaryStage.setScene(scene);
primaryStage.setTitle("JCal");
primaryStage.show();
});
}
}
main1.fxml
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="javafxapplication3.a.Main1Controller">
<children>
<Button fx:id="button1" layoutX="271.0" layoutY="173.0" mnemonicParsing="false" text="Main-1" />
</children>
</AnchorPane>
main2.fxml
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="javafxapplication3.a.Main2Controller">
<children>
<Button fx:id="button" layoutX="271.0" layoutY="173.0" mnemonicParsing="false" text="Main-2" />
</children>
</AnchorPane>
In the FXML I have a Button called button1, when ever I click on it, it opens a new GUI which has another button in it called button. In the end what I wanted to do was that when ever I click on the second button i.e. button the colour of the button in the primary GUI should change should change.
I did try getting the controllers shown in this example, But this dint help me.
Do I need to create a second controller and create a new stage and scene all together? or is there any alternative way to it?
In the controller for your main2.fxml, provide a mechanism in it for setting an action to be executed when the button is pressed. For example:
public class Main2Controller {
#FXML
private Button button ;
private Runnable buttonAction = () -> {} ; // do nothing by default
public void setButtonAction(Runnable action) {
this.buttonAction = action ;
}
public void initialize() {
button.setOnAction(e -> buttonAction.run());
}
}
Now in your Main1Controller you can retrieve the controller when you load the FXML, and set the button action:
button1.setOnAction(value -> {
Stage primaryStage = new Stage();
Parent root = null;
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/main2.fxml"));
root = loader.load();
Main2Controller controller = loader.getController();
controller.setButtonAction(() -> {
// perform button action here...
});
} catch (Exception e) {
e.printStackTrace();
}
Scene scene = new Scene(root, 400, 400);
primaryStage.setResizable(false);
primaryStage.setScene(scene);
primaryStage.setTitle("JCal");
primaryStage.show();
});
An alternative approach is to let both controllers have access to the same observable state, e.g. an ObjectProperty<Color>. This approach might be better if you have a lot of actions in one controller that affect state elsewhere (you just bundle all the data into a single "model" class that you pass). This looks like:
public class Main1Controller {
private final ObjectProperty<Color> color = new SimpleObjectProperty<>();
#FXML
private Button button1;
public void initialize() {
color.addListener((obs, oldColor, newColor) -> {
String style = String.format("-fx-background-color: #%02x%02x%02x;",
(int) (newColor.getRed() * 255),
(int) (newColor.getGreen() * 255),
(int) (newColor.getBlue() * 255));
button1.setStyle(style);
});
button1.setOnAction(value -> {
Stage primaryStage = new Stage();
Parent root = null;
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/main2.fxml"));
root = loader.load();
Main2Controller controller = loader.getController();
controller.setColorModel(color);
} catch (Exception e) {
e.printStackTrace();
}
Scene scene = new Scene(root, 400, 400);
primaryStage.setResizable(false);
primaryStage.setScene(scene);
primaryStage.setTitle("JCal");
primaryStage.show();
});
}
}
and Main2Controller looks like
public class Main2Controller {
#FXML
private Button button ;
private ObjectProperty<Color> colorModel = new SimpleObjectProperty<>();
public void setColorModel(ObjectProperty<Color> color) {
this.colorModel = color ;
}
public void initialize() {
button.setOnAction(e -> {
colorModel.set(/* some color */);
});
}
}
Related
I've been building a cinema booking application and am trying to create a scene that displays movies and showing times. It works when I have used an anchor pane and vbox to display all the information but when I try to insert an additional scroll pane (in scenebuilder) the FXML loader returns a null pointer exception and I cant work out why...
Here is my FXML code
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<AnchorPane prefHeight="598.0" prefWidth="798.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="MovieShowingsController">
<children>
<MenuBar>
<menus>
<Menu mnemonicParsing="false" text="myBookings">
<items>
<MenuItem mnemonicParsing="false" text="Close" />
</items>
</Menu>
</menus>
</MenuBar>
<ScrollPane fx:id="scrollpane" hbarPolicy="NEVER" layoutY="22.0" prefHeight="576.0" prefWidth="798.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="22.0">
<content>
<VBox fx:id="vbox" prefHeight="555.0" prefWidth="740.0" />
</content>
</ScrollPane>
</children>
</AnchorPane>
Here is the controller class
public class MovieShowingsController {
#FXML
private VBox vbox;
private ArrayList<FilmInfo> filmList;
private ArrayList<Screening> screeningList;
private MovieShowings showings;
//FXML loader instance variable to be accessed by dynamic scene controller
private VBox holder;
#FXML
private void initialize() {
}
public MovieShowingsController() {
}
public MovieShowingsController(MovieShowings showings) {
String date = "2019-04-15";
Date sqlDate = Date.valueOf(date);
System.out.println("\n");
System.out.println("***Screenings for " + date + "***");
filmList = new ArrayList();
screeningList = DatabaseConnection.getInstance().retrieveScreeningsForDay(sqlDate);
for (Screening screeningInstance : screeningList) {
if (!filmList.contains(screeningInstance.getFilmInfo())) {
filmList.add(screeningInstance.getFilmInfo());
}
System.out.println(screeningInstance.toString());
}
Collections.sort(screeningList);
this.showings = showings;
//populating FXML instance variable from loader
this.holder = (VBox) showings.getRoot().lookup("#vbox");
buildMovieShowings(holder);
}
private void buildMovieShowings(VBox holder) {
holder.setSpacing(50);
for (int i = 0; i < filmList.size(); i++) {
VBox infoHolder = new VBox();
infoHolder.setSpacing(10);
Label title = new Label(String.format("%s%8s", filmList.get(i).getTitle(),
"(" + filmList.get(i).getRating() + ")"));
title.setStyle("-fx-font: 24 arial;");
Label duration = new Label(String.format("%s%s%s", "Film Length: ",
filmList.get(i).getDuration(), " mins"));
duration.setStyle("-fx-font: 24 arial;");
Label director = new Label(String.format("%s%s", "Directed By: ",
filmList.get(i).getDirector()));
director.setStyle("-fx-font: 24 arial;");
infoHolder.getChildren().addAll(title, duration, director);
HBox timesHolder = new HBox();
timesHolder.setSpacing(10);
for (int j = 0; j < screeningList.size(); j++) {
if (screeningList.get(j).getFilmInfo().equals(filmList.get(i))){
Label time = new Label();
Background black = new Background(new BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY));
Background red = new Background(new BackgroundFill(Color.RED, CornerRadii.EMPTY, Insets.EMPTY));
time.setBackground(black);
Screen screen = screeningList.get(j).getScreen();
Screening screening = screeningList.get(j);
time.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
try {
System.out.println(screening.getFilmInfo().getTitle() + screening.getShowTime());
time.setBackground(red);
SeatMap seatMap = new SeatMap();
SeatMapController seatMapController = new SeatMapController(seatMap,
screen);
Scene seatMapScene = seatMap.getScene();
Stage window = (Stage) ((Node) e.getSource()).getScene().getWindow();
window.setResizable(false);
window.setWidth(800);
window.setHeight(600);
window.setScene(seatMapScene);
window.show();
}
catch (IOException ex) {
ex.printStackTrace();
}
}
});
time.setPrefSize(100, 100);
time.setAlignment(Pos.CENTER);
time.setStyle("-fx-border-color: black");
time.setStyle("-fx-font: 22 arial;");
time.setStyle("-fx-text-fill: white");
time.setText(screeningList.get(j).getShowTime());
timesHolder.getChildren().add(time);
}
}
infoHolder.getChildren().add(timesHolder);
holder.getChildren().add(infoHolder);
}
}
}
The main class
public class MovieShowings{
private AnchorPane root;
public MovieShowings() {
try {
root = FXMLLoader.load(getClass().getResource("movieshowings.fxml"));
}
catch(IOException e){
e.printStackTrace();
}
}
public Scene getScene() {
Scene scene = new Scene(root,800,600);
return scene;
}
public AnchorPane getRoot() {
return root;
}
}
and the code that calls it after the user has logged in
if(DatabaseConnection.getInstance().login(Username.getText(), Password.getText())) {
MovieShowings films = new MovieShowings();
MovieShowingsController filmsController = new MovieShowingsController(films);
Scene movieShowings = films.getScene();
Stage window = (Stage) ((Node) e.getSource()).getScene().getWindow();
window.setScene(movieShowings);
window.show();
Any ideas as how to fix this?
Edit: The fx:id 'vbox' is not being accessed from the getRoot() method even though is has been injected into the FXML loader
The reason for this is that ScrollPane adds content, ScrollBars, ect. to the scene during the first layout pass when it's skin is created. This layout pass happens after the JavaFX application thread "regains control" (i.e. you're done with the event handler, Application.start method or similar way of having JavaFX execute your code).
Note that you're using your controller class in a pretty weird way. I recommend using one of the approaches described in the answers to this question to communicate with the controller: Passing Parameters JavaFX FXML
For example:
public class MovieShowings{
private AnchorPane root;
public MovieShowings() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("movieshowings.fxml"));
root = loader.load();
MovieShowingsController controller = loader.getController();
controller.initMovieShowings(this);
}
catch(IOException e){
e.printStackTrace();
}
}
...
}
public class MovieShowingsController {
...
public void initMovieShowings(MovieShowings showings) {
String date = "2019-04-15";
Date sqlDate = Date.valueOf(date);
System.out.println("\n");
System.out.println("***Screenings for " + date + "***");
filmList = new ArrayList();
screeningList = DatabaseConnection.getInstance().retrieveScreeningsForDay(sqlDate);
for (Screening screeningInstance : screeningList) {
if (!filmList.contains(screeningInstance.getFilmInfo())) {
filmList.add(screeningInstance.getFilmInfo());
}
System.out.println(screeningInstance.toString());
}
Collections.sort(screeningList);
this.showings = showings;
//populating FXML instance variable from loader
// use the injected field here
buildMovieShowings(vbox);
}
...
}
Since you don't actually use the MovieShowings object in your controller, the code could be simplified a bit by doing teh initialisation from a
#FXML
private void initialize()
method in the controller and remove every MovieShowings-related part from the controller code. This way you'd get rid of the necessity to pass it to the controller.
Using a ListView using custom cells could also be an option to display the movies...
I have a root screen which generates a popup and in popup I have a listview with button on it and I want to update the texfield in root screen and close the popupwindow when a button is clicked in popup. Code for popup and its controller.
POPUP
public void display() throws IOException {
Stage window =new Stage();
FXMLLoader loader=new FXMLLoader();
Parent root = loader.load(getClass().getResource("/ProfilePopup.fxml"));
window.setTitle("Your profile");
window.setScene(new Scene(root, 400, 500));
window.show();
}
PopUPController
public void initialize() {
listView.setEditable(true);
listView.setVisible(true);
listView.setItems(walletModel.myWallets);
listView.setCellFactory(param -> {
try {
return new EditableCell();
} catch (IOException e) {
e.printStackTrace();
}
return null;
});
listView.layout();
addWalletButton.setOnMouseClicked(event -> {
walletModel.createWallet();
listView.getFixedCellSize();
size.setText("Total Wallets: " + walletModel.walletSize());
});
if (walletModel.myWallets.size() == 0) {
walletModel.initializeWalletData();
walletModel.myWallets.add(walletModel.initializeWalletData());
}
size.setText("Wallet Size " + walletModel.walletSize());
}
static class EditableCell extends ListCell<WalletModel.WalletData> {
private final WalletCellController controller;
EditableCell() throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/selectButton.fxml"));
Node graphic = loader.load();
controller = loader.getController();
setGraphic(graphic);
}
#Override
protected void updateItem(WalletModel.WalletData item, boolean empty) {
if (empty) {
controller.rootView.setVisible(false);
} else {
controller.textField.setText(item.getName());
controller.rootView.setVisible(true);
}
}
}
}
I want the button on listview to update the root screen when its clicked and plus close the popup as well. Each listview is getting graphics from walletcellcontroller code below.
Here is how I am calling from the root screen.
Creating instance in root screen and then calling
(Popup popup=new Popup();)
public void popupOpen() throws IOException {
popup.display();
}
here is the code for listview item
public class WalletCellController implements OnClick {
public Button select;
public TextField textField;
public AnchorPane rootView;
public void initialize(){
onMouseClicked();
}
public void onMouseClicked() {
select.setOnAction(closeEvent -> {
Node source = (Node) closeEvent.getSource();
Stage stage = (Stage) source.getScene().getWindow();
stage.close();
});
}}
Can you tell me how to use the callbacks for actionevents here. I think I need call back from POPUP Controller to POPup and then from POPup to root screen.
I am new in java so I not sure about the implementation of it.
interface Callable {
public void callBackMethod();
}
class Worker {
// Worker gets a handle to the boss object via the Callable interface.
// There's no way this worker class can call any other method other than
// the one in Callable.
public void doSomeWork(Callable myBoss) {
myBoss.callBackMethod();
// ERROR!
//myBoss.directMethod();
}
}
class Boss implements Callable {
public Boss() {
// Boss creates a worker object, and tells it to do some work.
Worker w1 = new Worker();
// Notice, we're passing a reference of the boss to the worker.
w1.doSomeWork(this);
}
//developer that develop library just call controll the place of calling
public void callBackMethod() {
System.out.println("What do you want?");
}
public void directMethod() {
System.out.println("I'm out for coffee.");
}
}
public class Main {
public static void main(String[] args) {
Boss b = new Boss();
b.directMethod();
// write your code here
}
}
this is sample code of call back method
In cases like these I recommend using Dialog, since it allows you to query & wait for user input.
Example
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Dialog?>
<?import javafx.scene.control.DialogPane?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.VBox?>
<Dialog xmlns:fx="http://javafx.com/fxml/1" fx:id="dialog" fx:controller="fxml.DialogController">
<dialogPane>
<DialogPane headerText="Choose item!">
<content>
<VBox prefWidth="100" spacing="5">
<children>
<Button text="a" onAction="#choice" maxWidth="Infinity" />
<Button text="b" onAction="#choice" maxWidth="Infinity" />
<Button text="c" onAction="#choice" maxWidth="Infinity" />
<Button text="Cancel" onAction="#cancel" maxWidth="Infinity" />
</children>
</VBox>
</content>
</DialogPane>
</dialogPane>
</Dialog>
public class DialogController {
#FXML
private Dialog<String> dialog;
#FXML
private void choice(ActionEvent event) {
Button source = (Button) event.getSource();
dialog.setResult(source.getText());
dialog.close();
}
#FXML
private void cancel() {
dialog.setResult("");
dialog.close();
}
}
#Override
public void start(Stage primaryStage) {
TextField textField = new TextField();
Button btn = new Button("Choose");
btn.setOnAction((ActionEvent event) -> {
Dialog<String> dialog;
try {
dialog = FXMLLoader.load(getClass().getResource("/fxml/Dialog.fxml"));
} catch (IOException ex) {
throw new IllegalStateException(ex);
}
Optional<String> result = dialog.showAndWait();
if (!result.orElse("").isEmpty()) {
textField.setText(s);
}
});
VBox root = new VBox(textField, btn);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
I want to have a collapsible list so I'm using a TreeView, but longer strings are giving horizontal scrollbars instead of word wrapping. I've tried using the CSS property -fx-wrap-text on the .tree-cell class but unfortunately nothing seems to happen.
Are TreeCells not meant to span more than one line? What alternative is there if that's the case?
Here's an image of what it looks like now:
test.css
.tree-cell {
-fx-wrap-text: true;
-fx-text-fill: blue;
}
subjects.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import java.net.URL?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.TreeView?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="javafxapplication1.FXMLDocumentController">
<Button fx:id="button" layoutX="182.0" layoutY="14.0" onAction="#handleButtonAction" text="Click Me!" />
<TextField fx:id="filterSubject" layoutX="21.0" layoutY="14.0" />
<TreeView fx:id="subjectList" editable="true" layoutX="14.0" layoutY="61.0" prefHeight="200.0" prefWidth="365.0" />
<stylesheets>
<URL value="#test.css" />
</stylesheets>
</AnchorPane>
subjects.txt - just pairs of text where the first is subject, and second is description.
1
Somewhat long string that I want to wordwrap.
2
Somewhat long string that I want to wordwrap.
3
Somewhat long string that I want to wordwrap.
FXMLDocumentController.java
package javafxapplication1;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.control.TreeView;
import javafx.scene.control.TreeItem;
import java.io.*;
public class FXMLDocumentController implements Initializable {
#FXML
private TreeView<String> subjectList;
#FXML
private void handleButtonAction(ActionEvent event) {
System.out.println("You clicked me!");
}
private void getSubjects(){
TreeItem<String> subjectRoot = new TreeItem<String> ("Subject");
subjectRoot.setExpanded(true);
try {
BufferedReader fileReader = new BufferedReader(
new FileReader("subjects.txt")
);
String subject = null;
String description = null;
while((subject = fileReader.readLine()) != null) {
description = fileReader.readLine();
TreeItem<String> subjectTree = new TreeItem<String> (subject);
TreeItem<String> descriptionItem = new TreeItem<String> (description);
subjectTree.getChildren().add(descriptionItem);
subjectRoot.getChildren().add(subjectTree);
}
} catch(FileNotFoundException e) {
System.out.println("File not found");
} catch(IOException e) {
e.printStackTrace();
}
subjectList.setRoot(subjectRoot);
}
#Override
public void initialize(URL url, ResourceBundle rb) {
getSubjects();
}
}
JavaFXApplication1.Java
package javafxapplication1;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class JavaFXApplication1 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);
}
}
Actually the CSS class is working as you expect.
The problem is: The TreeView contains TreeCells. These TreeCells will wrap its text, in case of they will have not enough vertical space to grow vertically. As the TreeView has a built in "ScrollPane" which ones view-port will grow vertically, it will provide enouth space for the TreeCell instances in any case to grow, therefore the wrapping will never be enabled.
To avoid this you could set the cell factory to generate the TreeCell instances manually. And then you can bind the prefWidthProperty of these elements to the widthProperty of the TreeView.
Example
public class WrappedTreeView extends Application {
#Override
public void start(Stage stage) {
final Scene scene = new Scene(new Group(), 200, 400);
Group sceneRoot = (Group) scene.getRoot();
scene.getStylesheets().add(getClass().getResource("application.css").toString());
TreeItem<String> root = new TreeItem<>("Root");
root.setExpanded(true);
TreeItem<String> childNode1 = new TreeItem<>("I am a very long node - the first one -, my text must be wrapped! If it is not wrapped, it's a problem!");
TreeItem<String> childNode2 = new TreeItem<>("I am a very long node - the second one -, my text must be wrapped! If it is not wrapped, it's a problem!");
TreeItem<String> childNode3 = new TreeItem<>("I am a very long node - the third one -, my text must be wrapped! If it is not wrapped, it's a problem!");
root.getChildren().addAll(childNode1, childNode2, childNode3);
childNode2.getChildren().add(new TreeItem<>("And I am a very long embedded node, so my text must be wrapped!"));
TreeView<String> treeView = new TreeView<>(root);
treeView.setCellFactory(item -> {
TreeCell<String> treeCell = new TreeCell<String>() {
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty)
setText(item);
else
setText("");
}
};
treeCell.prefWidthProperty().bind(treeView.widthProperty().subtract(5.0));
return treeCell;
});
treeView.setMaxWidth(200);
sceneRoot.getChildren().add(treeView);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
The content of application.css
.tree-cell {
-fx-wrap-text: true;
-fx-text-fill: blue;
}
And the generated TreeView:
You can do this by setting the tree cell's prefWidth value in a treecell cellfactory (along with the treecell's wraptext value to true), which will then stop the cell from expanding horizontally with a scrollPane.
Here is the simple code of MainController class initialize(...) method:
WebEngine webEngine = webView.getEngine();
webEngine.loadContent("<h1>hello</h1>"); // Successfully loaded on form
Document doc = webEngine.getDocument(); // null
Why doc is null and how to fix it?
As I commented, you should add a listener, as loading takes time, to execute once the content is successfully loaded:
final WebView webView = new WebView();
final WebEngine webEngine = webView.getEngine();
webEngine.getLoadWorker().stateProperty().addListener((observable, oldState, newState) -> {
if (newState == State.SUCCEEDED) {
Document doc = webEngine.getDocument();
}
});
webEngine.loadContent("<h1>hello</h1>");
//webEngine.load("http://google.ch"); // This works too
Sometimes the engine sets the document to null even after successful loading.
This usually happens with more complicated web pages.
The more reliable way to make sure that the document is not null is to use the property listener.
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import org.w3c.dom.Document;
class MyClass {
private WebView view = new WebView();
private WebEngine engine = view.getEngine();
private Document document;
MyClass() {
engine.documentProperty().addListener((v, o, n) -> {
if (n != null) {
document = n;
}
});
}
}
I've lost a lot of time to solve this problem.
Here is my simple example which show text from textarea in the alert window by click on the button:
index.html
<!-- ... -->
<textarea id="inputText" rows="5" placeholder="Enter text"></textarea>
<!-- ... -->
<button id="goButton">Go!</button>
<!-- ... -->
MainController.java
public class MainController implements Initializable {
#FXML private WebView webDoc;
public void initialize(URL location, ResourceBundle resources) {
final WebEngine webEngine = webDoc.getEngine();
String url = this.getClass().getResource("/html/main.html").toExternalForm();
webEngine.load(url);
webEngine.getLoadWorker().stateProperty().addListener((observable, oldState, newState) -> {
if (newState == Worker.State.SUCCEEDED) {
final Document doc = webEngine.getDocument();
EventTarget button = (EventTarget) doc.getElementById("goButton");
button.addEventListener("click", evt -> {
HTMLTextAreaElement textField = (HTMLTextAreaElement) doc.getElementById("inputText");
alert(textField.getValue());
}, false);
}
});
}
private void alert(String text) {
Alert alert = new Alert(Alert.AlertType.CONFIRMATION, text);
alert.showAndWait();
}
}
Main.java
public class Main extends Application {
public static void main(String[] args) throws Exception {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
Scene scene = gecSceneFromXml("/fxml/main.fxml");
stage.setTitle("New window");
stage.setScene(scene);
stage.show();
}
private Scene gecSceneFromXml(String fileName) throws java.io.IOException {
FXMLLoader loader = new FXMLLoader();
Parent root = loader.load(getClass().getResourceAsStream(fileName));
return new Scene(root);
}
}
main.fxml
<AnchorPane id="AnchorPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400" prefWidth="500" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="controller.MainController">
<children>
<WebView fx:id="webDoc" maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="400.0" prefWidth="500.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
</children>
</AnchorPane>
I am new to JavaFX and trying to create an Confirmation Dialogbox.
I know already that there is no real build in dialogbox in JavaFX so I created one myself like this:
#FXML
public void delBox() {
try {
Stage dialogStage = new Stage();
AnchorPane root = FXMLLoader.load(getClass().getResource("Dialog.fxml"));
Scene scene = new Scene(root);
dialogStage.setScene(scene);
dialogStage.showAndWait();
} catch(Exception e) {
e.printStackTrace();
}
}
It looks pretty good already, but what I dont understand is, how those two Stages can communicate with each other? I want to pass a String to the dialog which is than shown in the message, also when one of the buttons in the dialog window is clicked I wanna react to this in the accordingly.
Can anyone explain me how communication between the stages works?
btw: I use .FXML files and controller classes.
You need a reference to the controller for the dialog. To do this, create an instance of FXMLLoader instead of using the static FXMLLoader.load(URL) method.
For example, suppose you have a class DialogController, so your Dialog.fxml looks like:
<AnchorPane xmlns:fx="..." fx:controller="DialogController.fxml">
...
</AnchorPane>
Then you can access the DialogController in the delBox() method above with
Stage dialogStage = new Stage();
FXMLLoader loader = new FXMLLoader(getClass().getResource("Dialog.fxml"));
AnchorPane root = (AnchorPane)loader.load();
DialogController controller = (DialogController) loader.getController();
Scene scene = new Scene(root);
dialogStage.setScene(scene);
dialogStage.showAndWait();
And now you can communicate between the two controllers. For example, in DialogController you could define a message property, and bind it to a Label :
public class DialogController {
private final StringProperty message = new SimpleStringProperty("");
public void setMessage(String message) {
this.message.set(message);
}
public String getMessage() {
return message.get();
}
public StringProperty messageProperty() {
return message ;
}
#FXML
private Label label ;
public void initialize() {
label.textProperty().bind(message);
// ...
}
}
And then back in your delBox() method:
//... as before:
AnchorPane root = (AnchorPane)loader.load();
DialogController controller = (DialogController) loader.getController();
controller.setMessage("Hello World");
// ...
Similarly, you can define properties which are set when controls are pressed in the dialog itself, and either observe them or query them after the showAndWait() call.
There are a bunch of other similar techniques. Some examples:
https://github.com/james-d/Shared-Data-Controller/tree/master/src
https://github.com/james-d/Dialog-FXML-Example/tree/master/src
https://github.com/james-d/Nested-Controller-Example/tree/master/src/nestedcontrollerexample
<AnchorPane xmlns:fx="..." fx:controller="DialogController.fxml">
...
</AnchorPane>
FX Controller is a java file, so it has to be DialogController and the Controller's path should be included i.e, fx:controller="applicationPackageName.DialogController"
The above mentioned fxml code does not work. It results in
javafx.fxml.LoadException
java.lang.InstantiationException
java.lang.NoSuchMethodException
Reason: The jvm looks for a class constructor with 0 parameters to build an instance. To overcome the error, the controller file needs to be loaded in the function coded in java:
loader.setController(new ControllerName(""));
To sum up (Working code):
FXML file:
<BorderPane id="background" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="240.0" prefWidth="320.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" >
<bottom>
<HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0">
<children>
<Button onAction="#close" text="OK" />
</children>
</HBox>
</bottom>
<center>
<Label fx:id="messageLabel" />
</center>
</BorderPane>
Controller file:
public class PiPreferenceController {
private final String message ;
#FXML
private Label messageLabel ;
#FXML
void initialize() {
messageLabel.setText(message);
}
public PiPreferenceController(String message) {
this.message = message ;
}
#FXML
public void close() {
messageLabel.getScene().getWindow().hide();
}
}
Function:
void dialogPreferences() throws IOException {
Stage dialogStage = new Stage();
FXMLLoader loader = new FXMLLoader(getClass().getResource(
"PiPreference.fxml"));
loader.setController(new PiPreferenceController(""));
BorderPane root = (BorderPane) loader.load();
Scene scene = new Scene(root);
dialogStage.setScene(scene);
dialogStage.showAndWait();
}