I'm unable to bind TableView items with ObservableList in FXML.
Everything works fine when I set materialTable.setItems(materialDataObservableList); in button click event.
But I don't want button to know about TableView so I wanted to bind materialTable.items to materialDataObservableList property.
What am I doing wrong?
Or maybe I don't understand how binding works...
Thanks for help!
<GridPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="464.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.xxx.sm.frontend.fx.MainSceneController">
/*
*/
<children>
<VBox prefHeight="200.0" prefWidth="100.0">
<children>
<Button fx:id="getMaterialsButton" mnemonicParsing="false" onAction="#getMaterialsButton" text="Get materials" />
<TableView fx:id="materialTable" editable="true" prefHeight="413.0" prefWidth="600.0" items="${materialDataObservableList}" >
//Columns here
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
</columnResizePolicy>
</TableView>
</children>
</VBox>
</children>
public class MainSceneController {
private ObservableList<MaterialData> materialDataObservableList;
public TableView materialTable;
public Button getMaterialsButton;
public void getMaterialsButton() {
getMaterialsFromRESTController();
}
private void getMaterialsFromRESTController() {
MaterialClient controller = new MaterialClient();
try {
materialDataObservableList = FXCollections.observableArrayList(controller.getMaterias());
} catch (IOException e) {
System.out.println("Failed to connect to RESTController");
}
//materialTable.setItems(materialDataObservableList);
}
public ObservableList<MaterialData> getMaterialDataObservableList() {
return materialDataObservableList;
}
}
If you are calling getMaterialsFromRESTController() from MainSceneController's constructor (so it is initialized as soon as the controller is available to the FXMLLoader), then
<TableView fx:id="materialTable" editable="true" prefHeight="413.0" prefWidth="600.0"
items="${controller.materialDataObservableList}" >
will work. (Note you access a property of the controller with ${controller.property}.)
If not, you can modify the controller as follows to make it work:
public class MainSceneController {
private final ObservableList<MaterialData> materialDataObservableList
= FXCollections.observableArrayList();
public TableView materialTable;
public Button getMaterialsButton;
public void getMaterialsButton() {
getMaterialsFromRESTController();
}
private void getMaterialsFromRESTController() {
MaterialClient controller = new MaterialClient();
try {
materialDataObservableList.setAll(controller.getMaterias());
} catch (IOException e) {
System.out.println("Failed to connect to RESTController");
}
}
public ObservableList<MaterialData> getMaterialDataObservableList() {
return materialDataObservableList;
}
}
and then the above FXML should work.
Here is a SSCCE:
BindTableItemsExample.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.cell.PropertyValueFactory?>
<?import javafx.scene.control.Button?>
<?import javafx.geometry.Insets?>
<BorderPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="TableController">
<center>
<TableView items="${controller.items}">
<columns>
<TableColumn text="Item">
<cellValueFactory><PropertyValueFactory property="name" /></cellValueFactory>
</TableColumn>
</columns>
</TableView>
</center>
<bottom>
<Button text="Load" onAction="#loadItems" BorderPane.alignment="center" >
<BorderPane.margin>
<Insets top="5" left="5" right="5" bottom="5"/>
</BorderPane.margin>
</Button>
</bottom>
</BorderPane>
TableController.java:
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
public class TableController {
private final ObservableList<Item> items = FXCollections.observableArrayList();
#FXML
private void loadItems() {
items.setAll(createItems());
}
private List<Item> createItems() {
return IntStream.rangeClosed(1, 100)
.mapToObj(i -> "Item "+i)
.map(Item::new)
.collect(Collectors.toList());
}
public ObservableList<Item> getItems() {
return items ;
}
public static class Item {
private final StringProperty name = new SimpleStringProperty();
public Item(String name) {
setName(name);
}
public final StringProperty nameProperty() {
return this.name;
}
public final java.lang.String getName() {
return this.nameProperty().get();
}
public final void setName(final java.lang.String name) {
this.nameProperty().set(name);
}
}
}
BindTableItemsTest.java (application class):
import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class BindTableItemsTest extends Application {
#Override
public void start(Stage primaryStage) throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource("BindTableItemsExample.fxml"));
primaryStage.setScene(new Scene(loader.load(), 600, 600));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Related
if i run the Main Class my JavaFX is not opening. A Java symbol appears in my menubar but no window appears. I don't get any error in the console. I am on a Mac machine and I am using eclipse, together with e(fx)clipes and Gluon Scene Builder. The code is actually 1 by 1 written down from a tutorial. In the tutorial the run configuration JRE is set to JavaSE-1.8. - however, I tested every available JRE, without any success. What do I need to adjust?
Main Class:
package application;
import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
public class Main extends Application {
private Stage primaryStage;
#Override
public void start(Stage primaryStage) {
this.primaryStage = primaryStage;
}
public void mainWindow() {
try {
FXMLLoader loader = new FXMLLoader(Main.class.getResource("MainWindow.fxml"));
AnchorPane pane = loader.load();
primaryStage.setMinHeight(400.00);
primaryStage.setMinWidth(500.00);
MainWindowController mainWindowController = loader.getController();
mainWindowController.setMain(this);
Scene scene = new Scene(pane);
primaryStage.setScene(scene);
primaryStage.show();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
Controller class:
package application;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
public class MainWindowController {
public Main main;
#FXML private Label label;
#FXML private TextField field;
#FXML private Button clear;
#FXML private Button changeText;
public void setMain(Main main) {
// TODO Auto-generated method stub
this.main = main;
}
#FXML
public void handleChangeText() {
String text = field.getText();
label.setText(text);
}
#FXML
public void handleClear() {
field.clear();
}
}
FXML file:
<?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.AnchorPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<AnchorPane prefHeight="400.0" prefWidth="500.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.MainWindowController">
<children>
<VBox alignment="CENTER" layoutY="83.0" spacing="30.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<Label fx:id="label" text="Label">
<font>
<Font size="24.0" />
</font></Label>
<HBox alignment="CENTER">
<children>
<TextField fx:id="field" prefWidth="220.0" />
</children>
</HBox>
<HBox alignment="CENTER" spacing="20.0">
<children>
<Button fx:id="changeText" mnemonicParsing="false" onAction="#handleChangeText" text="Change Text" />
<Button fx:id="clear" mnemonicParsing="false" onAction="#handleClear" text="Clear" />
</children>
</HBox>
</children>
</VBox>
</children>
</AnchorPane>
You didn't do anything in you start method. All you did was set your primaryStage variable, you should call your mainWindow method after that.
Overview
I am working on a simple JavaFX application with multiple scenes using the MVC Architectural pattern. I have the design in place but I am having trouble persisting the User model across the different scenes. At the moment I do not have any backend. I am looking to just instantiate the User model when the application starts and display that single instance across my application. Any changes to that user model will up updated across the scenes. I am not looking to persist after the application closes.
I believe the issue arises from a design choice I had early on in development. I use a Controller named SceneNavigatorControl that calls the class Navigator which handles the presentation of different scenes in the application. These scenes are .fxml files.
SceneNavigatorControl
public class SceneNavigatorControl implements Initializable {
#FXML
private BorderPane mainStage;
#FXML
private void displayHomeScene() {
Navigator object = new Navigator();
Pane view = object.getScene("Home");
handleButtonChange("Home");
mainStage.setCenter(view);
}
#FXML
private void displayProfileScene() {
Navigator object = new Navigator();
Pane view = object.getScene("Profile");
handleButtonChange("Profile");
mainStage.setCenter(view);
}
#Override
public void initialize(URL location, ResourceBundle resources) {
// Called when the application starts, should this be where I instantiate User?
//User user = new User();
//user.setName("Bob");
displayHomeScene();
}
}
The above code handles which scene to take the user to. Depending on which button is tapped. One of the above functions is called and the scene is displayed by using the Navigator Class. I would think this is where I instantiate User and pass it to all other controllers
Navigator Class
public class Navigator {
private Pane view;
public Pane getScene(String fileName) {
try {
URL fileUrl = ActivityTracker.class.getResource("/ActivityTracker/Views/" + fileName + ".fxml");
if (fileUrl == null) {
throw new java.io.FileNotFoundException("FXML File cannot be found");
}
view = new FXMLLoader().load(fileUrl);
} catch (Exception e) {
System.out.print("No page " + fileName + " please check FXMLLoader");
}
return view;
}
}
The navigator class simple checks if a .fxml file with a given name exists. If it does, it replaces the current scene on the stage with the one given.
HomeView
<AnchorPane fx:id="Home" prefHeight="500.0" prefWidth="500.0" xmlns="http://javafx.com/javafx/8.0.172-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ActivityTracker.Controllers.HomeController">
<children>
<Label fx:id="clockLabel" layoutX="90.0" layoutY="32.0" textFill="WHITE" AnchorPane.leftAnchor="90.0" AnchorPane.topAnchor="32.0">
<font>
<Font name="Lucida Grande" size="100.0" />
</font></Label>
<Pane layoutX="410.0" layoutY="-1.0" prefHeight="500.0" prefWidth="90.0" style="-fx-background-color: #1d1d1d;" />
</children>
This is our view which is created using an .fxml file. On the first line is where we declare what view is associated with what controller.
HomeController
public class HomeController implements Initializable {
#FXML
private Pane Home;
#Override
public void initialize(URL location, ResourceBundle resources) {
// Print users details
}
}
ActivityTracker (Main)
public class ActivityTracker extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
try {
Parent root = FXMLLoader.load(ActivityTracker.class.getResource("Views/Stage.fxml"));
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.setResizable(false);
primaryStage.show();
} catch (Exception ex) {
Logger.getLogger(ActivityTracker.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
User Model
public class User {
// Define all user stat variables for the model
private String userName;
/**
* Method that returns the users name
* #return users name of type String
*/
public String getUsersName() {
return userName;
}
/**
* Method that takes in a string value and sets it as the current users name
* #param name the current users weight
*/
public void setUsersName(String name) {
this.userName = name;
}
}
The following is an mre of setting one model instance to two controllers.
Define a simple model to be used by the two controllers:
class User {
private final String fName, lName;
public User(String fName, String lName) {
this.fName = fName;
this.lName = lName;
}
public String getFirstName() {
return fName;
}
public String getLastName() {
return lName;
}
}
Define two very basic fxml views, each with a controller:
FirstName.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.text.Font?>
<HBox alignment="CENTER_LEFT" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
prefHeight="30.0" prefWidth="300.0" xmlns="http://javafx.com/javafx/10.0.1" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="fx_tests.FNameController">
<children>
<Label text="First Name: " HBox.hgrow="NEVER">
<font>
<Font size="16.0" />
</font>
</Label>
<Label fx:id="fName" text=""-"">
<font>
<Font size="16.0" />
</font>
</Label>
</children>
</HBox>
LastName.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.text.Font?>
<HBox alignment="CENTER_LEFT" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
prefHeight="30.0" prefWidth="300.0" xmlns="http://javafx.com/javafx/10.0.1" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="fx_tests.LNameController">
<children>
<Label text="Last Name: " HBox.hgrow="NEVER">
<font>
<Font size="16.0" />
</font>
</Label>
<Label fx:id="lName" text=""-"">
<font>
<Font size="16.0" />
</font>
</Label>
</children>
</HBox>
Define a simple interface:
interface Controller {
void setModel(User model);
}
And have the two controllers implement it:
import javafx.fxml.FXML;
import javafx.scene.control.Label;
public class FNameController implements Controller{
#FXML
private Label fName;
#Override
public void setModel(User model) {
fName.setText(model.getFirstName());
}
}
import javafx.fxml.FXML;
import javafx.scene.control.Label;
public class LNameController implements Controller{
#FXML
private Label lName;
#Override
public void setModel(User model) {
lName.setText(model.getLastName());
}
}
Define the main view that is the parent of the FirstName.fxml and LastName.fxml:
Main.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<?import javafx.scene.text.Text?>
<BorderPane xmlns="http://javafx.com/javafx/10.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="fx_tests.MainController">
<top>
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="User Info" textAlignment="CENTER" wrappingWidth="250" BorderPane.alignment="CENTER">
<font>
<Font size="20.0" />
</font>
</Text>
</top>
<center>
<VBox fx:id="infoPane" alignment="CENTER" spacing="10.0" BorderPane.alignment="CENTER">
<BorderPane.margin>
<Insets left="20.0" />
</BorderPane.margin>
</VBox>
</center>
</BorderPane>
The controller of the main view sets the model to the two controllers:
import java.io.IOException;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
public class MainController {
#FXML
private VBox infoPane;
#FXML
void initialize() throws IOException{
try {
User model = new User("Ann","Davis"); //initialize model
FXMLLoader loader = new FXMLLoader(getClass().getResource("FirstName.fxml"));
Pane fName = loader.load();
Controller controller = loader.getController();
controller.setModel(model); //set model to first name controller
loader = new FXMLLoader(getClass().getResource("LastName.fxml"));
Pane lName = loader.load();
controller = loader.getController();
controller.setModel(model); //set model to last name controller
infoPane.getChildren().addAll(fName, lName);
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
Test it all using:
import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
public class FxmlTest extends Application {
#Override
public void start(Stage primaryStage) throws IOException {
Pane root = FXMLLoader.load(getClass().getResource("Main.fxml"));
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
public static void main(String[] args) {
launch(null);
}
}
In my application I have declared a custom component like this:
#DefaultProperty("todoItems")
public class TodoItemsVBox extends VBox {
private ObservableList<TodoItem> todoItems;
// Setter/Getter omitted
}
and now somewhere in the fxml I want to use the TodoItemsVBox component like this:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<BorderPane prefHeight="600" prefWidth="500.0" xmlns="http://javafx.com/javafx/11.0.1"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.todolist.controller.TodoListController"
stylesheets="#../css/app.css">
<top>
<HBox spacing="10.0">
<TextField fx:id="input" layoutX="35.0" layoutY="64.0" prefWidth="431.0" promptText="Enter todo task" HBox.hgrow="ALWAYS" onAction="#addTask"/>
<Button layoutX="216.0" layoutY="107.0" mnemonicParsing="false" onAction="#addTask" prefHeight="27.0" prefWidth="70.0" text="Add" HBox.hgrow="ALWAYS" />
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>
</HBox>
</top>
<center>
<ScrollPane fitToHeight="true" fitToWidth="true" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER">
<TodoItemsVBox fx:id="todoItemsVBox" todoItems="${todoTasks}"/>
</ScrollPane>
</center>
... so as we can see the fxml has it's controller TodoListController
public class TodoListController implements {
private final ObservableList<TodoItem> todoTasks = FXCollections.observableArrayList(/*Fill in the collection somehow - for now doesn't matter*/);
#FXML
private TodoItemsVBox todoItemsVBox;
// Setter/Getter omitted
}
So, here what I want to do: pass todoTasks into TodoItemsVBox defined in the FXML via such construction: todoItems="${todoTasks}" ---- unfortunately this doesn't work as I expected, because fxml files load before controllers initialised so todoTasks is always null. I also tried #NamedArg with one arg constructor in TodoItemsVBox - it even fails with exception: "Cannot bind to untyped object.”.
Could some one suggest a solution how to pass a collection of objects, defined in a controller, into a custom component via it's parameters?
There are two issues with the code as you have it:
For FXML expression binding, you need to expose properties from your class, not just the values themselves. This applies to ObservableLists as well as regular values. So your TodoItemsVBox class needs to expose a ListProperty todoItemsProperty()
FXML expression bindings (i.e. ${todoTasks}) reference the FXMLLoader's namespace, not the controller. The controller is automatically injected into the namespace (with key "controller"), so, given that the task list is stored in your controller (which is not necessarily a good idea) you can use ${controller.todoTasks} here.
Here's a minimal, complete version of your app which works.
A basic TodoItem.java:
public class TodoItem {
private final String name ;
public TodoItem(String name) {
this.name = name ;
}
public String getName() {
return name ;
}
}
A TodoItemsVBox that exposes the list as a property:
import javafx.beans.property.ListProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
public class TodoItemsVBox extends VBox {
private ListProperty<TodoItem> todoItems = new SimpleListProperty<>();
public TodoItemsVBox() {
// not efficient but works for demo:
todoItems.addListener((Change<? extends TodoItem> c) -> rebuildView());
}
private void rebuildView() {
getChildren().clear();
todoItems.stream()
.map(TodoItem::getName)
.map(Label::new)
.forEach(getChildren()::add);
}
public ListProperty<TodoItem> todoItemsProperty() {
return todoItems ;
}
public ObservableList<TodoItem> getTodoItems() {
return todoItemsProperty().get() ;
}
}
A simple controller:
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.TextField;
public class TodoListController {
private ObservableList<TodoItem> todoTasks = FXCollections.observableArrayList();
// not actually needed...
#FXML
private TodoItemsVBox todoItemsVBox;
#FXML
private TextField input ;
public ObservableList<TodoItem> getTodoTasks() {
return todoTasks;
}
#FXML
private void addTask() {
todoTasks.add(new TodoItem(input.getText()));
}
}
The FXML file (TodoList.fxml):
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<?import org.jamesd.examples.TodoItemsVBox ?>
<BorderPane prefHeight="600" prefWidth="500.0" xmlns="http://javafx.com/javafx/11.0.1"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.todolist.controller.TodoListController"
>
<top>
<HBox spacing="10.0">
<TextField fx:id="input" promptText="Enter todo task" HBox.hgrow="ALWAYS" onAction="#addTask"/>
<Button onAction="#addTask" text="Add" HBox.hgrow="ALWAYS" />
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>
</HBox>
</top>
<center>
<ScrollPane fitToWidth="true" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER">
<TodoItemsVBox fx:id="todoItemsVBox" todoItems="${controller.todoTasks}"/>
</ScrollPane>
</center>
</BorderPane>
And finally the application class:
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class TodoApp extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("TodoList.fxml"));
Scene scene = new Scene(loader.load());
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
Really, a controller is no place to be storing data; you should have a separate model class to do that, which is shared between the controller and view. This is reasonably straightforward to do here; you just need to do a little more work with the FXMLLoader (namely putting the model in the namespace, and manually creating and setting the controller).
For example:
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
public class TodoModel {
private ObservableList<TodoItem> todoTasks = FXCollections.observableArrayList();
public ObservableList<TodoItem> getTodoTasks() {
return todoTasks;
}
}
Then your controller becomes:
import javafx.fxml.FXML;
import javafx.scene.control.TextField;
public class TodoListController {
// not actually needed...
#FXML
private TodoItemsVBox todoItemsVBox;
#FXML
private TextField input ;
private TodoModel model ;
public TodoModel getModel() {
return model;
}
public void setModel(TodoModel model) {
this.model = model;
}
#FXML
private void addTask() {
model.getTodoTasks().add(new TodoItem(input.getText()));
}
}
Modify the FXML to use
<TodoItemsVBox fx:id="todoItemsVBox" todoItems="${model.todoTasks}"/>
And finally assemble the application with
public void start(Stage primaryStage) throws Exception {
TodoModel model = new TodoModel();
FXMLLoader loader = new FXMLLoader(getClass().getResource("TodoList.fxml"));
loader.getNamespace().put("model", model);
Scene scene = new Scene(loader.load());
TodoListController controller = loader.getController();
controller.setModel(model);
primaryStage.setScene(scene);
primaryStage.show();
}
The advantage to this approach is that your data are now separated from the UI (both view and controller), which becomes essential if you want to access the same data in another part of the UI (which would use another FXML and another controller).
This is what my view looks like:
Explanation
The entire window itself runs on one controller, called the CartWindowController, and the product list itself is a JavaFX Custom Control called CartItemComponent, which has it's own controller. Each item in the list, therefore has it's own instance of the controller as I programmatically make an instance and populate a VBox.
Problem
I cannot figure out how to notify the CartWindowController when the user clicks on the "X" button, which is handled by the "CartItemComponent" Controller. I would highly appreciate it if anyone could give me a heads up on how to tackle this problem.
Here's what my FXML looks like for the entire Window:
CartWindow.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.VBox?>
<AnchorPane id="AnchorPane" fx:id="parent" prefHeight="400.0" prefWidth="600.0" styleClass="pane" stylesheets="#../assets/userwindow.css" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ordermanagementsystem.controllers.CartWindowController">
<children>
<Button layoutX="25.0" layoutY="25.0" mnemonicParsing="false" onAction="#exitWindow" prefHeight="35.0" prefWidth="20.0" styleClass="back-button" />
<Label layoutX="262.0" layoutY="17.0" styleClass="heading" text="Cart" />
<ScrollPane hbarPolicy="NEVER" layoutY="97.0" prefHeight="315.0" prefWidth="600.0" styleClass="no-padding">
<content>
<AnchorPane prefWidth="600.0" styleClass="no-padding">
<children>
<VBox fx:id="productList" layoutX="25.0" prefWidth="350.0" />
<AnchorPane layoutX="425.0" layoutY="25.0" prefWidth="150.0" styleClass="no-padding">
<children>
<Label layoutX="18.0" styleClass="heading-sub" text="Summary" />
<Label layoutX="1.0" layoutY="55.0" text="Gross:" />
<Label fx:id="grossTotal" alignment="CENTER_RIGHT" layoutX="47.0" layoutY="55.0" prefHeight="17.0" prefWidth="103.0" text="RM0.00" />
<Label layoutX="1.0" layoutY="75.0" text="Packaging:" />
<Label fx:id="packagingTotal" alignment="CENTER_RIGHT" layoutX="73.0" layoutY="75.0" prefHeight="17.0" prefWidth="77.0" text="RM0.00" />
<Label layoutX="1.0" layoutY="95.0" text="Total:" />
<Label fx:id="total" alignment="CENTER_RIGHT" layoutX="40.0" layoutY="95.0" prefHeight="17.0" prefWidth="110.0" styleClass="green-text" text="RM0.00" />
<Button layoutY="125.0" mnemonicParsing="false" onAction="#checkout" prefHeight="39.0" prefWidth="150.0" styleClass="action-button" text="Check Out" />
</children>
</AnchorPane>
</children>
</AnchorPane>
</content>
</ScrollPane>
</children>
</AnchorPane>
CartWindowController.java
package ordermanagementsystem.controllers;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.Parent;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import ordermanagementsystem.cart.CartState;
import ordermanagementsystem.orders.models.OrderItem;
import ordermanagementsystem.viewcomponents.CartItemComponent;
public class CartWindowController extends ViewController implements Initializable {
#FXML
public Parent parent;
#FXML
private VBox productList;
#FXML
private Label grossTotal;
#FXML
private Label packagingTotal;
#FXML
private Label total;
private CartState cartState;
#FXML
private void exitWindow(ActionEvent event) {
try {
this.openPage(this.parent, "MainWindow.fxml");
} catch (IOException ex) {
ex.printStackTrace();
}
}
#FXML
private void checkout(ActionEvent event) {
}
#Override
public void initialize(URL url, ResourceBundle rb) {
this.cartState = CartState.getInstance();
for (OrderItem item : this.cartState.getItems()) {
this.productList.getChildren().add(new CartItemComponent(item));
}
}
}
CartItemComponent.java:
package ordermanagementsystem.viewcomponents;
import java.io.IOException;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;
import ordermanagementsystem.DialogBox;
import ordermanagementsystem.cart.CartState;
import ordermanagementsystem.orders.models.OrderItem;
public class CartItemComponent extends AnchorPane {
#FXML
private AnchorPane frame;
#FXML
private TextField quantity;
#FXML
private Label total;
#FXML
private Label productName;
private OrderItem item;
private CartState cartState;
public CartItemComponent(OrderItem item) {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/ordermanagementsystem/views/components/CartItemComponent.fxml"));
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
try {
fxmlLoader.load();
} catch (IOException exception) {
exception.getStackTrace();
}
this.cartState = CartState.getInstance();
this.item = item;
this.setQuantity(1);
this.quantity.setEditable(false);
this.productName.setText(this.item.getProduct().getName());
}
private void setQuantity(int quantity) {
this.quantity.setText(String.valueOf(quantity));
this.item.setQuantity(quantity);
this.cartState.updateItem(this.item);
this.total.setText("RM" + String.format("%.2f", item.getProduct().getPrice() * quantity));
}
private int getQuantity() {
return Integer.parseInt(this.quantity.getText());
}
private void updateSummary() {
}
#FXML
private void add(ActionEvent event) {
int quantity = this.getQuantity();
if (quantity == 99) {
DialogBox.showValidationDialog("The quantity cannot be over 99.");
} else {
this.setQuantity(quantity + 1);
}
}
#FXML
private void substract(ActionEvent event) {
int quantity = this.getQuantity();
if (quantity == 1) {
DialogBox.showValidationDialog("The quantity cannot be below 1.");
} else {
this.setQuantity(quantity - 1);
}
}
}
Something you could try is to pass the CartWindowController into the CartItemComponent constructor, then when you need to notify the CartWindowController, you call a method or set a flag or trigger an event, your choice! All you would need to do is add a parameter of type CartWindowController to you CartItemComponent and just save the reference.
I am unable to populate my tableview. I believe the problem is in the controller, in the way my data is being sent to the FXML file, because a system out print (see below) shows exactly what I have in my database.
Please let me know where I did a mistake. I went over all tutorials that exist on that, but nothing fits my problem.
Thanks
Main App:
package tableview;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class MainApp extends Application {
public static void main(String[] args) {
// TODO Auto-generated method stub
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("view/FXMLTable.fxml"));
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
}
Controller:
import tableview.model.Person;
public class FXMLTableController{
#FXML
public TableView<Person> tableview ;
#FXML
private TableColumn<Person, Number> clientIdColumn;
#FXML
private TableColumn<Person, String> firstNameColumn;
#FXML
private TableColumn<Person, String> lastNameColumn;
#FXML
private void initialize() {
assert tableview != null : "fx:id=\"tableview\" was not injected: check your FXML file 'UserMaster.fxml'.";
clientIdColumn.setCellValueFactory(cellData -> cellData.getValue().
clientIDProperty());
firstNameColumn.setCellValueFactory(cellData -> cellData.getValue()
.firstNameProperty());
lastNameColumn.setCellValueFactory(cellData -> cellData.getValue()
.lastNameProperty());
buildData();
}
private ObservableList<Person> data;
public void buildData(){
data = FXCollections.observableArrayList();
Connection con = null;
try {
Class.forName("org.sqlite.JDBC");
con = DriverManager.getConnection("jdbc:sqlite:tableviewdb.db");
String SQL = "Select * from INFO";
ResultSet rs = con.createStatement().executeQuery(SQL);
while(rs.next()){
Person per = new Person();
per.ClientID.set(rs.getInt("CLIENTID"));
per.FirstName.set(rs.getString("FIRSTNAME"));
per.LastName.set(rs.getString("LASTNAME"));
data.add(per);
}
tableview = new TableView<Person>();
tableview.setItems(data);
System.out.println(tableview.getItems().get(1).ClientID);
}
catch(Exception e){
e.printStackTrace();
System.out.println("Error on Building Data");
}
}
}
Model Class:
package tableview.model;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Person {
public SimpleIntegerProperty ClientID = new SimpleIntegerProperty();
public SimpleStringProperty FirstName = new SimpleStringProperty();
public SimpleStringProperty LastName = new SimpleStringProperty();
public SimpleIntegerProperty getClientID() {
return ClientID;
}
public SimpleStringProperty getFirstname() {
return FirstName;
}
public SimpleStringProperty getLastName() {
return LastName;
}
public IntegerProperty clientIDProperty(){
return ClientID;
}
public StringProperty firstNameProperty(){
return FirstName;
}
public StringProperty lastNameProperty(){
return LastName;
}
}
FXML file:
(disregard the save button for now...)
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8">
<children>
<SplitPane prefHeight="400.0" prefWidth="600.0">
<items>
<SplitPane dividerPositions="0.5" orientation="VERTICAL" prefHeight="200.0" prefWidth="160.0">
<items>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="100.0" prefWidth="160.0">
<children>
<TextField layoutX="93.0" layoutY="34.0" />
<TextField layoutX="93.0" layoutY="85.0" />
<Label layoutX="35.0" layoutY="39.0" text="name" />
<Label layoutX="35.0" layoutY="90.0" text="email" />
<Button layoutX="204.0" layoutY="140.0" mnemonicParsing="false" text="save" />
</children>
</AnchorPane>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="100.0" prefWidth="160.0">
<children>
<TableView layoutY="-2.0" prefHeight="200.0" prefWidth="598.0">
<columns>
<TableColumn prefWidth="302.0" text="name" />
<TableColumn prefWidth="295.0" text="email" />
</columns>
</TableView>
</children>
</AnchorPane>
</items>
</SplitPane>
</items>
</SplitPane>
</children>
</AnchorPane>
You're creating a new TableView and setting its items, instead of setting the items on the table that the FXML file defined. Remove the
tableView = new TableView<Person>();
from the controller.
To get the #FXML-annotated fields in the controller to be populated with the appropriate elements from the FXML file, you need to add fx:id attributes to those elements:
<TableView fx:id="tableview" ... >
<columns>
<TableColumn fx:id="firstNameColumn" ... />
...