Why Document is null even after loadContent(...)? - (WebView JavaFx) - java

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>

Related

Issues with setting bounds of NumberAxis based on user input in FXML JavaFX application

SUMMARY: I am trying to design an application where the user inputs into TextFields, and upon clicking a button ONCE:
The values are retrieved
The scene switches to one containing a lineChart
The number axes' bounds are set corresponding to the values entered before.
However I'm finding it very hard to understand how to make the program flow correctly for this to work. My main issues are with switching the scene then accessing x_axis and y_axis, where for some reason "this.x_axis" is null.
Main Class:
public class Main extends Application {
#Override
public void start(Stage stage) throws IOException {
Parent root = FXMLLoader.load(getClass().getResource("menuScene.fxml"));
Scene scene = new Scene(root);
stage.setTitle("FT_Tool");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
Controller: (calculate is called after clicking button)
public class MenuController {
private Stage stage;
private Scene scene;
private Parent root;
#FXML
private TextField funcBox;
#FXML
private TextField sampBox;
#FXML
private TextField limitBox;
Expression func;
double sampFreq;
double limit;
#FXML
public void calculate(ActionEvent event) throws IOException{
func = new ExpressionBuilder(funcBox.getText()).variables("x").build();
sampFreq = Double.parseDouble(sampBox.getText());
limit = Double.parseDouble(limitBox.getText());
//=== SWITCHING SCENES ===//
root = FXMLLoader.load(getClass().getResource("graphScene.fxml"));
stage = (Stage) ((Node) event.getSource()).getScene().getWindow();
scene = new Scene(root);
stage.setScene(scene);
stage.show();
graphing();
}
#FXML
private LineChart sampleGraph;
#FXML
private NumberAxis x_axis;
#FXML
private NumberAxis y_axis;
public void graphing(){
x_axis.setUpperBound(limit);
}
}
2nd Scene:
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1">
<children>
<LineChart fx:id="sampleGraph" layoutX="24.0" layoutY="16.0" prefHeight="368.0" prefWidth="552.0" title="Samples taken">
<xAxis>
<NumberAxis fx:id="x_axis" autoRanging="false" label="Time (s)" tickUnit="1.0"/>
</xAxis>
<yAxis>
<NumberAxis fx:id="y_axis" autoRanging="false" label="Amplitude (m)"/>
</yAxis>
</LineChart>
</children>
</AnchorPane>
When I run it, application runs fine and after clicking button the scene switches and linechart is shown but without the right bounds.
This line of code: x_axis.setUpperBound(limit) causes the following error:
Caused by: java.lang.NullPointerException: Cannot invoke "javafx.scene.chart.NumberAxis.setUpperBound(double)" because "this.x_axis" is null
Despite not having the fx:controller attribute defined in your FXML file, it looks like—based on your FXML controller class—you're trying to use the same FXML controller class with multiple FXML files. This is strongly discouraged. By default, a new controller instance is created every time the FXML file is loaded. Consequently, it quickly becomes difficult to reason about which fields should have been injected (i.e., non-null) at any given time. An FXML file should have a one-to-one mapping with an FXML controller class (or possibly no controller).
You have two views, which suggests two FXML files and thus two FXML controller classes. When you load the second view, you should communicate to its controller the needed information. For trivial applications you can do this directly, but for more complex applications it would be better to communicate via a model shared between the two controllers.
Example
Here's an example (based on your code but simplified for demonstration). It only asks for the upper bounds of the X and Y axes and then graphs random data within those bounds. It also makes use of TextFormatter to make it easier to grab the values out of the TextFields, and to ensure the user only enters positive integers.
FXML files are assumed to be located in the com.example package.
Main.java:
package com.example;
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 {
var root = FXMLLoader.<Parent>load(Main.class.getResource("Form.fxml"));
primaryStage.setScene(new Scene(root, 1000, 650));
primaryStage.show();
}
}
Form.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.GridPane?>
<GridPane xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml"
fx:controller="com.example.FormController" alignment="CENTER" vgap="20" hgap="15">
<Label text="Upper X Bound:" GridPane.rowIndex="0" GridPane.columnIndex="0"/>
<TextField fx:id="xField" GridPane.rowIndex="0" GridPane.columnIndex="1" GridPane.columnSpan="2"/>
<Label text="Upper Y Bound:" GridPane.rowIndex="1" GridPane.columnIndex="0"/>
<TextField fx:id="yField" GridPane.rowIndex="1" GridPane.columnIndex="1" GridPane.columnSpan="2"/>
<Button text="Graph" onAction="#handleOnGraph" defaultButton="true" GridPane.rowIndex="2"
GridPane.columnIndex="2" GridPane.halignment="RIGHT"/>
</GridPane>
FormController.java:
package com.example;
import java.io.IOException;
import java.util.function.UnaryOperator;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.util.converter.IntegerStringConverter;
public class FormController {
#FXML private TextField xField;
#FXML private TextField yField;
private TextFormatter<Integer> xFieldFormatter;
private TextFormatter<Integer> yFieldFormatter;
#FXML
private void initialize() {
UnaryOperator<TextFormatter.Change> filter =
change -> {
if (change.getControlNewText().matches("\\d*")) {
return change;
}
return null;
};
xFieldFormatter = new TextFormatter<>(new IntegerStringConverter(), 50, filter);
xField.setTextFormatter(xFieldFormatter);
yFieldFormatter = new TextFormatter<>(new IntegerStringConverter(), 50, filter);
yField.setTextFormatter(yFieldFormatter);
}
#FXML
private void handleOnGraph(ActionEvent event) throws IOException {
event.consume();
int upperX = xFieldFormatter.getValue();
int upperY = yFieldFormatter.getValue();
var loader = new FXMLLoader(FormController.class.getResource("Chart.fxml"));
xField.getScene().setRoot(loader.load());
loader.<ChartController>getController().graph(upperX, upperY);
}
}
Chart.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.chart.LineChart?>
<?import javafx.scene.chart.NumberAxis?>
<LineChart fx:id="chart" xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml"
fx:controller="com.example.ChartController">
<xAxis>
<NumberAxis fx:id="xAxis" upperBound="50" autoRanging="false"/>
</xAxis>
<yAxis>
<NumberAxis fx:id="yAxis" upperBound="50" autoRanging="false"/>
</yAxis>
</LineChart>
ChartController.java:
package com.example;
import javafx.collections.FXCollections;
import javafx.fxml.FXML;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
public class ChartController {
#FXML private LineChart<Number, Number> chart;
#FXML private NumberAxis xAxis;
#FXML private NumberAxis yAxis;
void graph(int upperX, int upperY) {
xAxis.setUpperBound(upperX);
yAxis.setUpperBound(upperY);
generateAndGraphRandomData();
}
private void generateAndGraphRandomData() {
var data = FXCollections.<XYChart.Data<Number, Number>>observableArrayList();
for (int x = 0; x <= xAxis.getUpperBound(); x++) {
int y = (int) (Math.random() * yAxis.getUpperBound());
data.add(new XYChart.Data<>(x, y));
}
var series = new XYChart.Series<>("Random Demo Data", data);
chart.getData().add(series);
}
}

CallBack method implementation needed

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

JavaFX word-wrap on a Treeview, TreeItem/TreeCell

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.

How to I get the Scene of another UI interface - Javafx 8

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 */);
});
}
}

JavaFX Dialogbox communication

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

Categories

Resources