JavaFX: how to make a netbeans like tool-window? - javafx

I am working on a new GUI client and we are considering using JavaFX. I was wondering if anyone has a suggestion regarding making a draggable ToolWindow:
(Customer window) in the example. This mini-window should be able to get minimized and dragged out of the parent window, and docked back to it. I really do not want to use netbeans platform (or eclipse or swing for that matter).

You just have to create a custom pane for that:
The complete example is available on gist.
CustomPane.java
import java.net.URL;
import java.util.ResourceBundle;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Rectangle;
public class CustomPane extends AnchorPane implements Initializable {
#FXML Button closePane;
#FXML Button minPane;
#FXML Label title;
#FXML Pane contentPane;
#FXML Rectangle separator;
private boolean restoreFlag = false;
private double prefX;
private double prefY;
public CustomPane(String title){
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(
"CustomPane.fxml"));
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
try {
fxmlLoader.load();
} catch (Exception exception) {
throw new RuntimeException(exception);
}
this.title.setText(title);
setButtonsLayout();
setStandardLayout();
}
public CustomPane(String title, double y, double x){
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(
"CustomPane.fxml"));
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
try {
fxmlLoader.load();
} catch (Exception exception) {
throw new RuntimeException(exception);
}
this.title.setText(title);
this.setPrefSize(x, y);
setButtonsLayout();
setStandardLayout();
restoreRoot();
}
#Override
public void initialize(URL location, ResourceBundle resources) {
closePane.setOnAction(new EventHandler<ActionEvent>(){
#Override
public void handle(ActionEvent event) {
setInvisible();
}
});
minPane.setOnAction(new EventHandler<ActionEvent>(){
#Override
public void handle(ActionEvent event) {
if(restoreFlag){
restoreFlag = false;
minPane.setText("_");
restoreRoot();
}
else{
restoreFlag = true;
minPane.setText("▭");
minimizeRoot();
}
}
});
}
protected void minimizeRoot() {
this.setPrefSize(prefX/2, title.getPrefHeight() + 5.0);
separator.setWidth(prefX/2 + 5);
this.setLayoutX(0);
this.setLayoutY(prefY-40);
setButtonsLayout();
contentPane.setVisible(false);
}
protected void restoreRoot() {
this.setPrefSize(prefX, prefY);
separator.setWidth(prefX);
contentPane.setVisible(true);
this.setLayoutX(0);
this.setLayoutY(0);
setButtonsLayout();
}
private void setStandardLayout() {
prefX = this.getPrefHeight();
prefY = this.getPrefWidth()-50;
}
public void setButtonsLayout(){
closePane.setLayoutX(this.getPrefWidth()-30);
minPane.setLayoutX(this.getPrefWidth()-55);
}
public void setInvisible(){
this.setVisible(false);
}
public ObservableList<Node> getContent(){
return contentPane.getChildren();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.shape.*?>
customPane.fxml
<fx:root prefHeight="400.0" prefWidth="400.0"
type="javafx.scene.layout.AnchorPane" xmlns="http://javafx.com/javafx/8"
xmlns:fx="http://javafx.com/fxml/1">
<children>
<Rectangle fx:id="separator" arcHeight="5.0" arcWidth="5.0" fill="WHITE"
height="33.0" layoutY="1.0" stroke="BLACK" strokeType="INSIDE" width="600.0" />
<Button fx:id="closePane" layoutX="573.0" layoutY="6.0"
mnemonicParsing="false" text="X" />
<Button fx:id="minPane" layoutX="545.0" layoutY="6.0"
mnemonicParsing="false" text="__" />
<Label fx:id="title" layoutX="14.0" layoutY="12.0" text="Title" />
<Pane fx:id="contentPane" layoutY="34.0" prefHeight="366.0"
prefWidth="600.0" />
</children>
</fx:root>

Related

Share the same reusable FXML component in multiple views

I have a FXML view that contains a TabView with multiple tabs.
Every Tab has it's own FXML component + controller.
I would like all tabs to report progress via a component defined defined in the main view.
I know that I can inject child controllers into the main controller via FXML.
But to my current understanding, I need to access the parent controller inside my child controllers since the component is located in the main view.
I can also access the child controllers inside the initialize method of my main view. If I follow this path, I would need to modify the child controllers to set the shared component.
This is suboptimal, since the child components would be dependent on a main view that is performing those changes.
I created a MWE to illustrate the dilemma.
The question in short:
How can I report progress of Service1 and Service2 to the progressAndStatusGrid in the main view?
Callenge in short:
Make this application not throw a NPE and report progress to the progress component ;)
MWE:
Launcher:
package org.example;
public class Launcher {
public static void main(final String[] args) {
SimpleApplication.main(args);
}
}
Application:
package org.example;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class SimpleApplication extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/fxml/MainView.fxml"));
#SuppressWarnings("unused")
MainViewController controller = fxmlLoader.getController();
final Parent root = fxmlLoader.load();
final Scene scene = new Scene(root);
primaryStage.setTitle("Hello World");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
MainController:
package org.example;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.layout.GridPane;
import java.net.URL;
import java.util.ResourceBundle;
public class MainViewController implements Initializable {
#FXML
GridPane progressAndStatusGrid;
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
}
}
MainView:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Tab?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.layout.BorderPane?>
<BorderPane prefHeight="150.0" prefWidth="350.0" xmlns="http://javafx.com/javafx/17.0.2-ea"
xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.example.MainViewController">
<padding>
<Insets top="4" right="4" bottom="4" left="4"/>
</padding>
<top>
</top>
<center>
<TabPane>
<Tab text="tab1" closable="false">
<fx:include fx:id="tab1" source="Tab1View.fxml"/>
</Tab>
<Tab text="tab2" closable="false">
<fx:include fx:id="tab2" source="Tab2View.fxml"/>
</Tab>
</TabPane>
</center>
<bottom>
<fx:include fx:id="progressAndStatusGrid"
source="ProgressAndStatusGridComponent.fxml"/>
</bottom>
</BorderPane>
"Shared" componentController:
package org.example;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.HBox;
import java.net.URL;
import java.util.ResourceBundle;
public class ProgressAndStatusGridComponentController implements Initializable {
#FXML
ProgressBar progressBar;
#FXML
HBox progressStatusBox;
#FXML
Label progressLabel;
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
}
}
"Shared" componentView:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ProgressBar?>
<?import javafx.scene.layout.*?>
<GridPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="org.example.ProgressAndStatusGridComponentController"
hgap="4" vgap="4">
<padding>
<Insets top="4" right="4" bottom="4" left="4"/>
</padding>
<fx:define>
<ColumnConstraints fx:id="colConstraints2" percentWidth="100"/>
</fx:define>
<columnConstraints>
<fx:reference source="colConstraints2"/>
<fx:reference source="colConstraints2"/>
</columnConstraints>
<ProgressBar fx:id="progressBar" GridPane.columnIndex="0" GridPane.rowIndex="0"/>
<HBox fx:id="progressStatusBox" alignment="CENTER" spacing="4" GridPane.columnIndex="1" GridPane.rowIndex="0">
<padding>
<Insets top="4" right="4" bottom="4" left="4"/>
</padding>
<Label fx:id="progressLabel"/>
</HBox>
</GridPane>
Tab1Controller:
package org.example;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import java.net.URL;
import java.util.ResourceBundle;
public class Tab1Controller implements Initializable {
#FXML
Button button1;
Service1 service1 = new Service1();
// How to get a reference, that is already initialized?
#FXML
ProgressAndStatusGridComponentController progressAndStatusGridComponentController;
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
button1.disableProperty().bind(service1.runningProperty());
}
public void handleButtonClick(ActionEvent actionEvent) {
service1.cancel();
service1.reset();
progressAndStatusGridComponentController.progressBar.progressProperty().bind(service1.progressProperty());
progressAndStatusGridComponentController.progressLabel.textProperty().bind(service1.messageProperty());
service1.start();
}
}
Tab1View:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.BorderPane?>
<BorderPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="org.example.Tab1Controller">
<center>
<Button fx:id="button1" text="Start Background Progress #1" onAction="#handleButtonClick"/>
</center>
</BorderPane>
Tab2Controller:
package org.example;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import java.net.URL;
import java.util.ResourceBundle;
public class Tab2Controller implements Initializable {
#FXML
Button button2;
Service2 service2 = new Service2();
// How to get a reference, that is already initialized?
#FXML
ProgressAndStatusGridComponentController progressAndStatusGridComponentController;
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
button2.disableProperty().bind(service2.runningProperty());
}
public void handleButtonClick(ActionEvent actionEvent) {
service2.cancel();
service2.reset();
progressAndStatusGridComponentController.progressBar.progressProperty().bind(service2.progressProperty());
progressAndStatusGridComponentController.progressLabel.textProperty().bind(service2.messageProperty());
service2.start();
}
}
Tab2View:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.BorderPane?>
<BorderPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="org.example.Tab2Controller">
<center>
<Button fx:id="button2" text="Start Background Progress #2" onAction="#handleButtonClick"/>
</center>
</BorderPane>
Service1:
package org.example;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
public class Service1 extends Service<Void> {
static final int workLoad = 100;
#Override
protected Task<Void> createTask() {
return new Task<>() {
#Override
protected Void call() throws Exception {
updateMessage("Starting Task #1..");
for (int i = 0; i < workLoad; i++) {
Thread.sleep(200);
updateProgress(i, workLoad);
updateMessage(i + " elements done");
}
updateMessage("Task #1 done!");
return null;
}
};
}
}
Service2:
package org.example;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
public class Service2 extends Service<Void> {
static final int workLoad = 100;
#Override
protected Task<Void> createTask() {
return new Task<>() {
#Override
protected Void call() throws Exception {
updateMessage("Starting Task #2..");
for (int i = 0; i < workLoad; i++) {
Thread.sleep(200);
updateProgress(i, workLoad);
updateMessage(i + " elements done");
}
updateMessage("Task #2 done!");
return null;
}
};
}
}

How to connect multiple controllers to one model? Simple window application in JavaFX

I've seen bunch of posts connected with my problem but it seems like nothing works for me. I'm writing simple window app in Java FX and FXML. I have a Main class which has field with days names. I've got also two controller classes - Controller (which is like more important controller) and ShowDays. Each controller has their own FXML and they are responsible for switching scenes and working with user (and with Model = Main). In Controller class user can add a new day to our days names or remove last day. In ShowDays class user can print on window names of days. It's all connected with clicking buttons.
I know that I can organize my program easier and I can print names of days in Controller class and remove all ShowDays class but it's only a part of project I want to create and I just want to understand how real programmers would have done this kind of things.
This is my code:
public class Main extends Application {
private ArrayList<String> daysNames;
public Main() {
daysNames = new ArrayList<>();
}
public void addDay() {
String[] days = {"Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"};
System.out.println("I'm adding day!" + daysNames.size());
if (getDaysNames().size() < 7)
getDaysNames().add(days[getDaysNames().size()]);
else
System.out.println("Can't add more days!");
}
public void removeDay() {
if (!getDaysNames().isEmpty())
getDaysNames().remove(getDaysNames().get(getDaysNames().size()-1));
}
#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, 300, 275));
primaryStage.show();
}
//setters,getters...
}
Controller class:
public class Controller implements Initializable {
private Main myMain;
#FXML
private ShowDays showDays;
public Controller() {
myMain=new Main();
showDays = new ShowDays(myMain);
}
public Controller(Main main) {
this.myMain = main;
showDays = new ShowDays(myMain);
}
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
}
public void addDay() {
myMain.addDay();
}
public void removeDay() {
myMain.removeDay();
}
public void GOTOshowDays(ActionEvent event) throws IOException {
Parent showDaysParent = FXMLLoader.load(getClass().getResource("ShowDays.fxml"));
Scene showDaysScene = new Scene(showDaysParent);
Stage window = (Stage) ((Node)event.getSource()).getScene().getWindow();
window.setScene(showDaysScene);
window.show();
}
}
ShowDays class:
public class ShowDays implements Initializable {
#FXML
private Text text;
private Main myMain;
public ShowDays() {
myMain = new Main();
}
public ShowDays(Main main) {
myMain = main;
}
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
}
public void BackToController(ActionEvent event) throws IOException {
Parent controllerParent = FXMLLoader.load(getClass().getResource("sample.fxml"));
Scene controllerScene = new Scene(controllerParent);
Stage window = (Stage) ((Node)event.getSource()).getScene().getWindow();
window.setScene(controllerScene);
window.show();
}
public void showDaysInWindow(ActionEvent event) throws IOException {
String allText = "";
for (String day : myMain.getDaysNames())
allText += day + " ";
text.setText(allText);
}
}
and the FXML files:
sample.fxml
<HBox prefHeight="100.0" prefWidth="483.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
<children>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="100.0">
<children>
<Button layoutX="-8.0" layoutY="48.0" mnemonicParsing="false" onAction="#GOTOshowDays" text="GOTO show days" />
</children>
</AnchorPane>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="100.0" />
<Button mnemonicParsing="false" onAction="#addDay" text="add day" />
<Button mnemonicParsing="false" onAction="#removeDay" text="remove day" />
ShowDays.fxml
<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.ShowDays">
<center>
<Text fx:id="text" strokeType="OUTSIDE" strokeWidth="0.0" BorderPane.alignment="CENTER" />
</center>
<top>
<Button mnemonicParsing="false" onAction="#showDaysInWindow" text="Show days" BorderPane.alignment="CENTER" />
</top>
<bottom>
<Button mnemonicParsing="false" onAction="#BackToController" text="Go back" BorderPane.alignment="CENTER" />
</bottom>
</BorderPane>
I'm not sure if ShowDays user actions works as expected (it's all about that...) but Controller do. The problem is when I add a few days in Controller scene and then switch scene to ShowDays scene, it seems like I lose my Main instance... When I have one controller class, my code cope with working all time on the same instance of Main and adding/removing days works as expected but I can't cope with connecting multiple controllers to model and them each other... I've spend so much time trying to fix this and I don't really understand all of the tips which I found on the internet, so I ask you.
You should use Ideas from #James_D answer here. Your problem is how to pass a model from one Controller to the next. In this example, the model is a List<String>. The model is passed to the initial Controller named FXMLDocumentController. From that point, the model is passed between FXMLDocumentController and ShowDayscontroller. The showDaysInWindow method, shows the current model info in a Text node. addDay adds a day from the array days if the model is missing a day. removeDay removes a day from the model.
Main
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
/**
*
* #author blj0011
*/
public class JavaFXApplication320 extends Application
{
#Override
public void start(Stage stage)
{
try {
String[] days = {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"};
List<String> daysNames = new ArrayList();
for (int i = 0; i < days.length; i++) {
daysNames.add(days[i]);
}
//Load the FXML. Pass the model to initModel
FXMLLoader loader = new FXMLLoader(getClass().getResource("FXMLDocument.fxml"));
loader.load();
FXMLDocumentController fXMLDocumentController = loader.getController();
fXMLDocumentController.initModel(daysNames);
Scene scene = new Scene(loader.getRoot());
stage.setScene(scene);
stage.show();
}
catch (IOException ex) {
ex.printStackTrace();
}
}
/**
* #param args the command line arguments
*/
public static void main(String[] args)
{
launch(args);
}
}
FXMLDocumentController
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.stage.Stage;
/**
*
* #author blj0011
*/
public class FXMLDocumentController implements Initializable
{
List<String> model;
#FXML
private void goToShowDays(ActionEvent event)
{
try {
FXMLLoader showDaysLoader = new FXMLLoader(getClass().getResource("ShowDays.fxml"));
showDaysLoader.load();
ShowDaysController showDaysController = showDaysLoader.getController();
showDaysController.initModel(this.model);
Stage stage = (Stage) ((Button) event.getSource()).getScene().getWindow();
stage.setScene(new Scene(showDaysLoader.getRoot()));
}
catch (IOException ex) {
ex.printStackTrace();
}
}
#FXML
private void addDay(ActionEvent event)
{
String[] days = {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"};
List<String> tempDays = new ArrayList(Arrays.asList(days));
if (!this.model.containsAll(tempDays)) {
tempDays.removeAll(this.model);
if (tempDays.size() > 0) {
this.model.add(tempDays.get(0));
}
}
}
#FXML
private void removeDay(ActionEvent event)
{
if (this.model.size() > 0) {
Random random = new Random();
this.model.remove(random.nextInt(this.model.size()));
}
}
#Override
public void initialize(URL url, ResourceBundle rb)
{
// TODO
}
public void initModel(List<String> model)
{
// ensure model is only set once:
if (this.model != null) {
throw new IllegalStateException("Model can only be initialized once");
}
this.model = model;
this.model.forEach(System.out::println);
}
}
FXMLDocument
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<HBox prefHeight="100.0" prefWidth="483.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="javafxapplication320.FXMLDocumentController">
<children>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="100.0">
<children>
<Button layoutX="-8.0" layoutY="48.0" mnemonicParsing="false" onAction="#goToShowDays" text="GOTO show days" />
</children>
</AnchorPane>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="100.0" />
<Button mnemonicParsing="false" onAction="#addDay" text="add day" />
<Button mnemonicParsing="false" onAction="#removeDay" text="remove day" />
</children>
</HBox>
ShowDaysController
import java.io.IOException;
import java.net.URL;
import java.util.List;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.text.Text;
import javafx.stage.Stage;
/**
* FXML Controller class
*
* #author blj0011
*/
public class ShowDaysController implements Initializable
{
#FXML
Text text;
List<String> model;
#FXML
private void showDaysInWindow(ActionEvent event)
{
text.setText(String.join(" ", this.model));
}
#FXML
private void backToController(ActionEvent event)
{
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("FXMLDocument.fxml"));
loader.load();
FXMLDocumentController fXMLDocumentController = loader.getController();
fXMLDocumentController.initModel(this.model);
Stage stage = (Stage) ((Button) event.getSource()).getScene().getWindow();
stage.setScene(new Scene(loader.getRoot()));
}
catch (IOException ex) {
ex.printStackTrace();
}
}
/**
* Initializes the controller class.
*
* #param url
* #param rb
*/
#Override
public void initialize(URL url, ResourceBundle rb)
{
// TODO
}
public void initModel(List<String> model)
{
// ensure model is only set once:
if (this.model != null) {
throw new IllegalStateException("Model can only be initialized once");
}
this.model = model;
this.model.forEach(System.out::println);
}
}
ShowDays 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.*?>
<?import javafx.scene.text.*?>
<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="javafxapplication320.ShowDaysController">
<center>
<Text fx:id="text" strokeType="OUTSIDE" strokeWidth="0.0" BorderPane.alignment="CENTER" />
</center>
<top>
<Button mnemonicParsing="false" onAction="#showDaysInWindow" text="Show days" BorderPane.alignment="CENTER" />
</top>
<bottom>
<Button mnemonicParsing="false" onAction="#backToController" text="Go back" BorderPane.alignment="CENTER" />
</bottom>
</BorderPane>

How to update a scene without refreshing the scene after receiving data from another class/controller

I'm working on a personal weather project that contains Main.fxml, MainController.java, EnterCityDocument.fxml, and EnterCityDocumentController.java.
Main.fxml: contains a border-pane and at its center it has a ListView that displays names of cities. It also has a "Add" Button to open a new modal window(EnterCityDocument.fxml) to add a city to its ListView.
EnterCityDocument.fxml: has a listView that contains names of cities and a "Select" button to select a city the user wants to display in Main.fxml. When the user clicks the "Select" Button, the modal window (EnterCityDocument.fxml) closes and the Main.fxml continues to run.
MainController.java is the parent class of EnterCityDocumentController.java.
I've been looking for passing data from the child class(EnterCityDocumentController.java) to the parent class (MainController.java) and found a way to do it, but all of the methods i've found require to refresh the MainController.java class whenever the user selects a city in EnterCityDocumentController.java.
Is there a way to update the Main.fxml without refreshing the main scene when a new city is added into the listView of the Main.fxml?
Hope my question is clear enough. If you need a further explanation/code, please let me know!
All you need to do is arrange for the new city from the dialog to be added to the list view's backing list. Since you are using showAndWait() this is very easy: just define a method in the EnterCityDocumentController class that returns the new city, and call it after showAndWait():
public class EnterCityDocumentController {
#FXML
private TextField cityNameField ;
// other fields, etc...
#FXML
private void okButtonPressed() {
// just close the window:
cityNameField.getScene().getWindow().hide();
}
public City getUserCity() {
return new City(cityNameField.getText());
}
}
Then in the main controller:
public class MainController {
#FXML
private ListView<City> listView ;
// handler method:
#FXML
public void addNewCity() throws IOException {
FXMLLoader loader = new FXMLLoader(EnterCityDocumentController.class.getResource("EnterCityDocument.fxml"));
Scene scene = new Scene(loader.load());
Stage stage = new Stage();
stage.setScene(scene);
stage.showAndWait();
listView.getItems().add(controller.getUserCity());
}
}
Here is a SSCCE:
app/Main.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.layout.BorderPane?>
<BorderPane xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="app.MainController">
<center>
<ListView fx:id="listView" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER" />
</center>
<bottom>
<Button mnemonicParsing="false" onAction="#addNewElement" text="Add..." BorderPane.alignment="CENTER">
<BorderPane.margin>
<Insets bottom="2.0" left="2.0" right="2.0" top="2.0" />
</BorderPane.margin>
</Button>
</bottom>
</BorderPane>
app/AddNewElement.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<GridPane hgap="5.0" vgap="5.0" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="app.AddNewElementController">
<rowConstraints>
<RowConstraints />
<RowConstraints />
</rowConstraints>
<columnConstraints>
<ColumnConstraints halignment="CENTER" />
<ColumnConstraints halignment="CENTER" />
</columnConstraints>
<children>
<TextField fx:id="textField" GridPane.columnSpan="2" />
<Button defaultButton="true" mnemonicParsing="false" onAction="#ok" text="OK" GridPane.rowIndex="1" />
<Button cancelButton="true" mnemonicParsing="false" onAction="#cancel" text="Cancel" GridPane.columnIndex="1" GridPane.rowIndex="1" />
</children>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
</GridPane>
app/AddNewElementController.java:
package app;
import javafx.fxml.FXML;
import javafx.scene.control.TextField;
public class AddNewElementController {
#FXML
private TextField textField ;
private boolean approved ;
public boolean isApproved() {
return approved ;
}
public String getUserText() {
return isApproved() ? textField.getText() : null ;
}
#FXML
private void cancel() {
approved = false ;
hide();
}
#FXML
private void ok() {
approved = true ;
hide();
}
private void hide() {
textField.getScene().getWindow().hide();
}
}
app/MainController.java:
package app;
import java.io.IOException;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.stage.Modality;
import javafx.stage.Stage;
public class MainController {
#FXML
private ListView<String> listView ;
#FXML
private void addNewElement() throws IOException {
FXMLLoader loader = new FXMLLoader(AddNewElementController.class.getResource("AddNewElement.fxml"));
Parent root = loader.load();
AddNewElementController controller = loader.getController();
Scene scene = new Scene(root);
Stage stage = new Stage();
stage.initModality(Modality.APPLICATION_MODAL);
stage.initOwner(listView.getScene().getWindow());
stage.setScene(scene);
stage.showAndWait();
if (controller.isApproved()) {
listView.getItems().add(controller.getUserText());
}
}
}
app/Main.java:
package app;
import java.io.IOException;
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 IOException {
FXMLLoader loader = new FXMLLoader(MainController.class.getResource("Main.fxml"));
Parent root = loader.load();
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Another, very slightly different approach, is to pass the listview's backing list to the second controller. Then the second controller can add the item directly to the list (and the list view will automatically update, as before). This approach can be used even if you are not using showAndWait() (it doesn't rely on the code blocking until the user dismisses the window).
package app;
import java.util.List;
import javafx.fxml.FXML;
import javafx.scene.control.TextField;
public class AddNewElementController {
#FXML
private TextField textField ;
private List<String> itemList ;
public void setItemList(List<String> itemList) {
this.itemList = itemList ;
}
#FXML
private void cancel() {
hide();
}
#FXML
private void ok() {
itemList.add(textField.getText());
hide();
}
private void hide() {
textField.getScene().getWindow().hide();
}
}
and
package app;
import java.io.IOException;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.stage.Modality;
import javafx.stage.Stage;
public class MainController {
#FXML
private ListView<String> listView ;
#FXML
private void addNewElement() throws IOException {
FXMLLoader loader = new FXMLLoader(AddNewElementController.class.getResource("AddNewElement.fxml"));
Parent root = loader.load();
AddNewElementController controller = loader.getController();
controller.setItemList(listView.getItems());
Scene scene = new Scene(root);
Stage stage = new Stage();
stage.initModality(Modality.APPLICATION_MODAL);
stage.initOwner(listView.getScene().getWindow());
stage.setScene(scene);
stage.showAndWait();
}
}

JavaFX: Strange case with DataView fill operation

I have a very strange problem with my test application. I need to fill the JavaFX TableView element with some data. Here is the code:
fxmldocumentController.java
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.TableView; //A
import javafx.scene.control.TableColumn; //B
import javafx.scene.control.cell.PropertyValueFactory; //C
public class fxmldocumentController implements Initializable
{
#FXML
private TableView<employees> mainTableView;
#FXML
private TableColumn<employees, Integer> age;
#FXML
private TableColumn<employees, String> userName, companyName;
#Override
public void initialize(URL url, ResourceBundle rb)
{
// TODO:
mainTableView.getItems().
add(new employees("Yuri P. Bodrov", "VMware", 35));
mainTableView.getItems().
add(new employees("Ivan Y. Bodrov", "VMware", 5));
mainTableView.getItems().
add(new employees("Peter Y. Bodrov", "VMware", 2));
// A problem starts here:
age.setCellValueFactory(new PropertyValueFactory<>("age"));
userName.setCellValueFactory(new PropertyValueFactory<>("userName"));
companyName.
setCellValueFactory(new PropertyValueFactory<>("companyName"));
}
}
fxmldocument.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.text.*?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane id="AnchorPane" prefHeight="300.0" prefWidth="400.0" style="-fx-
background-color: white;"
xmlns="http://javafx.com/javafx/8"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="sampletableviewapp00.fxmldocumentController">
<children>
<Label fx:id="testLabel" layoutX="14.0" layoutY="14.0" style="-fx-
background-color: white;" text="Employees. TableView." textFill="#505050">
<font>
<Font size="14.0" />
</font>
</Label>
<TableView fx:id="mainTableView" layoutX="12.0" layoutY="50.0"
prefHeight="200.0" prefWidth="377.0">
<columns>
<TableColumn prefWidth="90.0" text="UserName" />
<TableColumn prefWidth="119.0" text="CompanyName" />
<TableColumn prefWidth="84.0" text="Age" />
</columns>
</TableView>
</children>
</AnchorPane>
Sampletableviewapp00.java
package sampletableviewapp00;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Sampletableviewapp00 extends Application
{
#Override
public void start(Stage stage) throws Exception
{
Parent root = FXMLLoader.load(getClass().
getClassLoader().getResource("fxmldocument.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args)
{
launch(args);
}
}
employees.java
package sampletableviewapp00;
public class employees
{
String userName, companyName;
int age;
// Generate Properties. Getters:
public int GetAge()
{
return age;
}
public String GetUserName()
{
return userName;
}
public String GetCompanyName()
{
return companyName;
}
// Generate Properties. Setters:
public void SetAge(int age)
{
this.age = age;
}
public void SetUserName(String userName)
{
this.userName = userName;
}
public void SetCompanyName(String companyName)
{
this.companyName = companyName;
}
// Generate Constructor of Employees class:
public employees(String userName, String companyName, int age)
{
this.userName = userName;
this.companyName = companyName;
this.age = age;
}
}
When I run this application the NetBeans IDE 8.2 returns this stack of exceptions/errors: see outputError.png as attachment
outputError.png
outputError02.PNG
Dear colleagues! Do you have any ideas to resolve this problem? Could you try to write this code by yourself and run? Thanks in advance! :-)
You have fxmldocument.xml but tried to load "fxmldocument.fxml".
Rename the file to have fxml extension.
Also make sure you put the fxml file under /resources/yourpackagepath/ folder and load as:
Sampletableviewapp00.class.getResource("fxmldocument.fxml")
This line is triggering the exception:
Parent root = FXMLLoader.load(getClass().
getClassLoader().getResource("fxmldocument.fxml"));
Your fxml document's extension is xml in your project and you are trying to load it as .fxml in the above line.
Rename fxmldocument.xml to fxmldocument.fxml.

Using ImageView in JavaFX controller class

I created a javaFX fxml application in NetBeans and want to display an image using a filechooser opened in a handleButtonAction event.
If I drag an ImageView into the panel using the gui builder I see no way to get it's object generated in code.
If I add an Imageview manually to the main class, I do not have the button handler method available, and if I add it to the controller class, I do not have the main panel available to attach the Imageview to. I think I am not understanding how to elements from the ui builder generated into code.
The generated NetBeans projects starts with a hello button that also has no visible object in the source files, so it seems clear that I am missing something about how the ui builder sets the proper xml data to have those elements available to the controller, but there doesn't appear to me to be any way add these to the controller from the ui builder. Is it just that the UI builder is unreliable?
Any help would be appreciated.
The main application class looks like:
package javafxapplication2;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class JavaFXApplication2 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();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
And the Controller:
package javafxapplication2;
import java.net.URL;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.embed.swing.SwingFXUtils;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
//import javafx.scene.image.Image;
//import javafx.scene.image.ImageView;
//import javafx.stage.FileChooser;
//import javax.imageio.ImageIO;
//import java.io.File;
//import java.io.IOException;
public class FXMLDocumentController implements Initializable {
// private ImageView imgview = new ImageView();
// private FileChooser filechooser = new FileChooser();
#FXML
private void handleButtonAction(ActionEvent event) {
//commented out because it displays nothing
// try {
// File infile = filechooser.showOpenDialog(null);
// Image img = SwingFXUtils.toFXImage(ImageIO.read(infile), null);
// imgview.setImage(img);
// } catch (IOException ex) {
// Logger.getLogger(FXMLDocumentController.class.getName()).log(Level.SEVERE, null, ex);
// }
System.out.println("button clicked");
}
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
}
And the XML:
<?import javafx.scene.image.*?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane id="AnchorPane" prefHeight="355.0" prefWidth="411.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="javafxapplication2.FXMLDocumentController">
<children>
<Button fx:id="button" layoutX="14.0" layoutY="312.0" onAction="#handleButtonAction" text="open file" />
<ImageView fitHeight="150.0" fitWidth="200.0" layoutX="84.0" layoutY="73.0" pickOnBounds="true" preserveRatio="true" />
<Button layoutX="212.0" layoutY="310.0" mnemonicParsing="false" text="Button" />
</children>
</AnchorPane>
You aren't using the ImageView that you have created in the FXML, inside your controller.
You should assign the ImageView a fx:id and then inject it in the controller. This way the same ImageView will be used while setting the Image in the ImageView.
In FXML:
<ImageView fx:id="imgview" fitHeight="150.0" fitWidth="200.0"
layoutX="84.0" layoutY="73.0" pickOnBounds="true" preserveRatio="true" />
In Controller:
public class FXMLDocumentController implements Initializable {
#FXML
private ImageView imgview;
...
}
MCVE
FXML
<?import javafx.scene.control.Button?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane id="AnchorPane" prefHeight="355.0" prefWidth="411.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="FXMLDocumentController">
<children>
<Button fx:id="button" layoutX="14.0" layoutY="312.0" onAction="#handleButtonAction" text="open file" />
<ImageView fx:id="imageView" fitHeight="150.0" fitWidth="200.0" layoutX="84.0" layoutY="73.0" pickOnBounds="true" preserveRatio="true" />
<Button layoutX="212.0" layoutY="310.0" mnemonicParsing="false" text="Button" />
</children>
</AnchorPane>
Controller
import javafx.embed.swing.SwingFXUtils;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.stage.FileChooser;
import javax.imageio.ImageIO;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
public class FXMLDocumentController implements Initializable {
#FXML
private ImageView imageView;
private FileChooser filechooser = new FileChooser();
#FXML
private void handleButtonAction(ActionEvent event) {
try {
File infile = filechooser.showOpenDialog(null);
Image img = SwingFXUtils.toFXImage(ImageIO.read(infile), null);
imageView.setImage(img);
} catch (IOException ex) {
Logger.getLogger(TestController.class.getName()).log(Level.SEVERE, null, ex);
}
}
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
}
Main
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Resources